tools: Add dfu_flash script

Added the `pw flash` command to flash gonk with dfu-util.

Change-Id: Ibe255bc3953f5b865b2d40b36cb6541023969ca0
Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/185551
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/README.md b/README.md
index b3d5d7c..3e166f1 100644
--- a/README.md
+++ b/README.md
@@ -78,12 +78,10 @@
 
 1. Unplug gonk from USB and replug with MODE button held down.
 
-1. Run dfu-util to flash.
+1. Run `pw flash` on the MCU binary.
 
    ```sh
-   dfu-util -d 0483:df11 -s 0x08000000:leave \
-     --serial STM32FxSTM32 -a 0 \
-     -D ./out/gn/arduino_size_optimized/obj/applications/fpga_config/fpga_config.bin
+   pw flash ./out/gn/arduino_size_optimized/obj/applications/fpga_config/fpga_config.bin
    ```
 
 ### Write the FPGA bitstream
@@ -101,12 +99,10 @@
 
 1. Unplug gonk from USB and replug with MODE button held down.
 
-1. Run dfu-util to flash.
+1. Run `pw flash` on the MCU binary.
 
    ```sh
-   dfu-util -d 0483:df11 -s 0x08000000:leave \
-     --serial STM32FxSTM32 -a 0 \
-     -D ./out/gn/arduino_size_optimized/obj/applications/spi_flash_test/spi_flash_test.bin
+   pw flash ./out/gn/arduino_size_optimized/obj/applications/spi_flash_test/spi_flash_test.bin
    ```
 
 1. Unplug Gonk from USB and replug to reset the hardware. SPI bus issues have
@@ -137,6 +133,7 @@
 ```
 
 ## pw_system Example
+
 ### Run on Host
 
 Run the host app and connect to it via `pw-system-console`:
diff --git a/pigweed.json b/pigweed.json
index e3f36ed..8775657 100644
--- a/pigweed.json
+++ b/pigweed.json
@@ -10,6 +10,10 @@
           "module": "gonk_tools.build_project",
           "function": "watch_project"
         },
+        "flash": {
+          "module": "gonk_tools.flash",
+          "function": "dfu_flash"
+        },
         "presubmit": {
           "module": "gonk_tools.presubmit_checks",
           "function": "main"
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index 41bb95a..0b04cfe 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -25,6 +25,7 @@
     "gonk_tools/__init__.py",
     "gonk_tools/build_project.py",
     "gonk_tools/find_serial_port.py",
+    "gonk_tools/flash.py",
     "gonk_tools/presubmit_checks.py",
     "gonk_tools/write_fpga.py",
   ]
diff --git a/tools/gonk_tools/flash.py b/tools/gonk_tools/flash.py
new file mode 100644
index 0000000..7b7c2c1
--- /dev/null
+++ b/tools/gonk_tools/flash.py
@@ -0,0 +1,88 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Flash Gonk via dfu-util."""
+
+import argparse
+from pathlib import Path
+import shutil
+import signal
+import subprocess
+import sys
+
+import pw_cli.color
+
+_COLOR = pw_cli.color.colors()
+
+DFU_SERIAL_STRING = 'STM32FxSTM32'
+
+
+def _parse_args():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument(
+        '--dfu-serial-string',
+        default=DFU_SERIAL_STRING,
+        help=f'Serial string to pass to dfu-util. Default: {DFU_SERIAL_STRING}',
+    )
+
+    parser.add_argument(
+        'bin_file',
+        help='Binary to flash with dfu-util',
+        type=Path,
+    )
+    return parser.parse_args()
+
+
+def main(
+    bin_file: Path,
+    dfu_serial_string: str,
+) -> int:
+    """Flash Gonk via dfu-util."""
+
+    dfu_util_binary = shutil.which('dfu-util')
+    if not dfu_util_binary or not Path(dfu_util_binary).is_file():
+        raise FileNotFoundError('Unable to find "dfu-util"')
+
+    if not bin_file.is_file():
+        raise FileNotFoundError(f'Unable to find the file "{bin_file}"')
+
+    dfu_flash_args = [
+        dfu_util_binary,
+        '--device',
+        '0483:df11',
+        '--dfuse-address',
+        '0x08000000:leave',
+        '--serial',
+        dfu_serial_string,
+        '--alt',
+        '0',
+        '--download',
+        str(bin_file),
+    ]
+
+    print(
+        _COLOR.cyan('Running ==>'),
+        ' '.join(dfu_flash_args),
+    )
+
+    # Ignore Ctrl-C to allow dfu-util to handle normally.
+    signal.signal(signal.SIGINT, signal.SIG_IGN)
+    return subprocess.run(dfu_flash_args, check=False).returncode
+
+
+def dfu_flash() -> None:
+    sys.exit(main(**vars(_parse_args())))
+
+
+if __name__ == '__main__':
+    dfu_flash()