blob: 3e3639b49d3f47920bea2f458651ff7c84946ee1 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2024 Project CHIP 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
#
# http://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 an Infineon PSoC6 device.
This is layered so that a caller can perform individual operations
through an `Flasher` instance, or operations according to a command line.
For `Flasher`, see the class documentation. For the parse_command()
interface or standalone execution:
usage: psoc6_firmware_utils.py [-h] [--verbose] [--erase] [--application FILE]
[--verify_application] [--reset] [--skip_reset]
[--device DEVICE]
Flash PSoC6 device
optional arguments:
-h, --help show this help message and exit
configuration:
--verbose, -v Report more verbosely
--device DEVICE, -d DEVICE
Device family or platform to target
operations:
--erase Erase device
--application FILE Flash an image
--verify_application, --verify-application
Verify the image after flashing
--reset Reset device after flashing
--skip_reset, --skip-reset
Do not reset device after flashing
"""
import os
import platform as plt
import subprocess
import sys
import firmware_utils
# Additional options that can be use to configure an `Flasher`
# object (as dictionary keys) and/or passed as command line options.
PSOC6_OPTIONS = {
# Configuration options define properties used in flashing operations.
'configuration': {
'openocd': {
'help': 'File name of the hex image',
'default': None,
'argparse': {
'metavar': 'FILE',
},
},
'device': {
'help': 'Device family or platform to target',
'default': 'PSoC6',
'alias': ['-d'],
'argparse': {
'metavar': 'DEVICE'
},
},
'sdk_path': {
'help': 'Path to psoc6 sdk',
'default': 'third_party/infineon/psoc6/psoc6_sdk',
'alias': ['-p'],
'argparse': {
'metavar': 'SDK_PATH'
},
},
},
}
# Global variables
FW_LOADER = "fw-loader"
KIT_PROG_STR = "KitProg3 CMSIS-DAP"
# General utility functions
def _get_board_serial_number(tools_path):
"""Obtains the MCU serial number"""
board_ser_num = None
# if system is windows, add the .exe extension
exe = FW_LOADER + (".exe" if plt.system() == "Windows" else "")
fw_loader_exe = os.path.join(tools_path, FW_LOADER, "bin", exe)
try:
# runs fw-loader --device-list to get the connected device info
output = subprocess.run([fw_loader_exe, "--device-list"], check=True, capture_output=True).stdout.decode().split("\n")
except subprocess.SubprocessError:
# Do not error here, during matter build this code is interpreted by the python wrapper
# If a device is not connected in the build stage it will complain and generate an error
return None
for line in output:
# look for a line like: Info: Connected - KitProg3 CMSIS-DAP BULK-0F03131601051400
if KIT_PROG_STR in line:
board_ser_num = line.split("-")[-1].strip()
break
if board_ser_num is None:
raise Exception("Could not detect CMSIS DAP device.")
return board_ser_num
def _find_tools_path():
"""Obtains the path to the latest ModusToolbox tools package"""
tools_version = ""
tools_path = os.environ.get("CY_TOOLS_PATHS", None)
# If `CY_TOOLS_PATHS` env variable is set, return that value
if tools_path is not None:
return tools_path
path_to_search = "/Applications/ModusToolbox/" if plt.system() == "Darwin" else os.path.join(os.path.expanduser("~"), "ModusToolbox")
if not os.path.exists(path_to_search):
raise Exception(
"Could not find ModusToolbox installation. Please install ModusToolbox and export CY_TOOLS_PATHS=<path to MTB tools_x.y>")
dirs = os.listdir(path_to_search)
for directory in dirs:
# find the latest version of ModusToolbox that is installed
if directory.startswith("tools_"):
if directory > tools_version:
tools_version = directory
return os.path.join(path_to_search, tools_version)
class Flasher(firmware_utils.Flasher):
"""Manage PSoC6 flashing."""
def __init__(self, **options):
super().__init__(platform='PSOC6', module=__name__, **options)
self.define_options(PSOC6_OPTIONS)
def verify(self, image):
raise NotImplementedError()
def reset(self):
raise NotImplementedError()
def erase(self):
tools_path = _find_tools_path()
open_ocd_dir = os.path.join(tools_path, "openocd", "bin", "openocd")
return self.run_tool(
'openocd',
[
"-s", f"{open_ocd_dir}/scripts",
"-c", "source [find interface/kitprog3.cfg]",
"-c", "source [find target/psoc6_2m.cfg]",
"-c", "init; reset init",
"-c", "flash erase_sector 0 0 last; shutdown",
],
name='Erase device')
def flash(self, image):
"""Flash image."""
tools_path = _find_tools_path()
ser_num = _get_board_serial_number(tools_path)
open_ocd_dir = os.path.join(tools_path, "openocd", "bin", "openocd")
return self.run_tool(
'openocd',
[
"-s", f"{open_ocd_dir}/scripts",
"-c", "source [find interface/kitprog3.cfg]",
"-c", "source [find target/psoc6_2m.cfg]",
"-c", f"adapter serial {ser_num}",
"-c", "init; reset init",
"-c", f"program {image} verify reset exit",
],
name='Flash')
def actions(self):
"""Perform actions on the device according to self.option."""
self.log(3, 'Options:', self.option)
if self.option.erase:
if self.erase().err:
return self
elif self.option.reset:
if self.reset().err:
return self
elif self.option.application:
application = self.option.application
if self.flash(application).err:
return self
if self.option.verify_application:
if self.verify(application).err:
return self
return self
def locate_tool(self, tool):
"""Gets the path to infineon shipped openocd asset"""
tools_path = _find_tools_path()
if tools_path is None:
raise Exception(
"CY_TOOLS_PATHS environment variable is not set. Please set it to the location of your ModusToolbox tools directory.")
if tool != "openocd":
raise Exception(f"Tool '{tool}' is not supported for flashing PSoC6. Please use openocd.")
return os.path.join(tools_path, "openocd", "bin", "openocd")
if __name__ == '__main__':
sys.exit(Flasher().flash_command(sys.argv))