############################################################################ # tools/espressif/chip_info.py # # SPDX-License-Identifier: Apache-2.0 # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. The # ASF licenses this file to you 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. # ############################################################################ import os import platform import re import subprocess TOPDIR = "." ARCH_ESP_HALDIR = "{TOPDIR}/arch/{ARCH_DIR}/src/chip/esp-hal-3rdparty" def get_python_modules(): """ Gets the list of python modules installed on the host system. Args: None. Returns: list: Python modules (with version) installed on the host system. """ modules = [] output = subprocess.check_output( ["pip", "list", "--format=freeze"], universal_newlines=True ) for line in output.splitlines(): if line.startswith("#"): continue package_info = line.split("==") if len(package_info) > 1: modules.append("{}-{}".format(package_info[0], package_info[1])) else: modules.append(package_info[0]) return modules def run_espressif_tool(cmd): tool = "esptool.py" strings_out = "" command = "{} {}".format(tool, cmd) strings_out = subprocess.run( command, universal_newlines=True, shell=True, capture_output=True ) return strings_out.stdout def get_espressif_chip_id(): output = run_espressif_tool("chip_id") string_out = next((s for s in output.split("\n") if "Chip ID" in s), "Not found") if string_out != "Not found": string_out = string_out.split("Warning: ")[-1] return string_out def get_espressif_flash_id(): strings_out = [] output = run_espressif_tool("flash_id") strings_out.append( next( (s for s in output.split("\n") if "Manufacturer" in s), "Manufacturer: Not found", ) ) strings_out.append( next((s for s in output.split("\n") if "Device" in s), "Device: Not found") ) return strings_out def get_espressif_security_info(): output = run_espressif_tool("get_security_info") start_string = "=====================" stop_string = "Hard resetting via RTS pin..." output = output.split("\n") strings_out = [] str_out = next((s for s in output if start_string in s), "Not found") if str_out != "Not found": start_index = output.index(start_string) + 1 stop_index = output.index(stop_string) strings_out = output[start_index:stop_index] else: strings_out.append(str_out) return strings_out def get_espressif_flash_status(): output = run_espressif_tool("read_flash_status") string_out = next( (s for s in output.split("\n") if "Status value" in s), "Not found" ) if string_out != "Not found": string_out = string_out.split("Status value: ")[-1] return string_out def get_espressif_mac_address(): output = run_espressif_tool("read_mac") string_out = next((s for s in output.split("\n") if "MAC" in s), "Not found") if string_out != "Not found": string_out = string_out.split("MAC: ")[-1] return string_out def get_espressif_bootloader_version(bindir): """ Get the bootloader version for Espressif chips from the bootloader binary. This function works on Linux, Windows, and macOS. Args: bindir (str): The path to the bootloader binary directory. Returns: dict: A dictionary containing the bootloader version for each chip. """ regex = r"^(?=.*\bv\d+(\.\d+){1,2}\b).+$" bootloader_chips = [ "esp32", "esp32s2", "esp32s3", "esp32c2", "esp32c3", "esp32c6", "esp32h2", ] bootloader_version = {} for chip in bootloader_chips: file = "bootloader-{}.bin".format(chip) path = os.path.join(bindir, file) if os.path.isfile(path): if platform.system() == "Linux": process = subprocess.Popen(["strings", path], stdout=subprocess.PIPE) elif platform.system() == "Windows": process = subprocess.Popen( [ "powershell", "Get-Content -Raw -Encoding Byte {} |".format(path) + " % { [char[]]$_ -join \"\" } | Select-String -Pattern '[\\x20-\\x7E]+'" + " -AllMatches | % { $_.Matches } | % { $_.Value }", ], stdout=subprocess.PIPE, ) elif platform.system() == "Darwin": process = subprocess.Popen( ["strings", "-", path], stdout=subprocess.PIPE ) else: bootloader_version[chip] = "Not supported on host OS" break output, error = process.communicate() strings_out = output.decode("utf-8", errors="ignore") matches = re.finditer(regex, strings_out, re.MULTILINE) try: bootloader_version[chip] = next(matches).group(0) except StopIteration: bootloader_version[chip] = "Unknown" else: bootloader_version[chip] = "Bootloader image not found" return bootloader_version def get_espressif_toolchain_version(): """ Get the version of different toolchains used by Espressif chips. Args: None. Returns: dict: A dictionary containing the toolchain version for each toolchain. """ toolchain_version = {} toolchain_bins = [ "clang", "gcc", "xtensa-esp32-elf-gcc", "xtensa-esp32s2-elf-gcc", "xtensa-esp32s3-elf-gcc", "riscv32-esp-elf-gcc", "riscv64-unknown-elf-gcc", ] for binary in toolchain_bins: try: version_output = subprocess.check_output( [binary, "--version"], stderr=subprocess.STDOUT, universal_newlines=True ) version_lines = version_output.split("\n") version = version_lines[0].strip() toolchain_version[binary] = version except (subprocess.CalledProcessError, FileNotFoundError): toolchain_version[binary] = "Not found" return toolchain_version def get_espressif_hal_version(hal_dir): """ Get the version of the ESP HAL used by Espressif chips. Args: None. Returns: str: The ESP HAL version. """ hal_version = "Not found" try: if os.path.isdir(os.path.join(hal_dir, ".git")): hal_version_output = subprocess.check_output( ["git", "describe", "--tags", "--always"], cwd=hal_dir, stderr=subprocess.STDOUT, universal_newlines=True, ) hal_version = hal_version_output.strip() except (subprocess.CalledProcessError, FileNotFoundError): pass return hal_version def get_arch(config): """ Get the architecture type. Args: config (list): Listed version of .config file. Returns: str: Architecture name (xtensa or risc-v). """ arch_key = "CONFIG_ARCH=" arch = [s for s in config if arch_key in s][0].replace(arch_key, "") if "risc" in arch: return "risc-v" return "xtensa" def check_chip_info(config): """ Get chip info flag to fetch data. Args: config (list): Listed version of .config file. Returns: bool: Indication of getting chip information bool: Indication of getting chip information on runtime """ chip_info_key = "CONFIG_SYSTEM_NXDIAG_ESPRESSIF_CHIP=" chip_info_runtime_key = "CONFIG_SYSTEM_NXDIAG_ESPRESSIF_CHIP_WO_TOOL=" nxdiag_key = "CONFIG_SYSTEM_NXDIAG=" chip_info = [s for s in config if chip_info_key in s] chip_info_runtime = [s for s in config if chip_info_runtime_key in s] nxdiag = [s for s in config if nxdiag_key in s] if nxdiag == []: return True, False return chip_info != [], chip_info_runtime != [] def get_vendor_info(config): """ Get vendor specific information. Args: config (list): Listed version of .config file. Returns: str: Parsed vendor specific information for sysinfo.h, str: Parsed vendor specific information for print during build """ info = {} output = "" build_output = "" # Espressif bootloader version info["ESPRESSIF_BOOTLOADER"] = get_espressif_bootloader_version(TOPDIR) output += "#define ESPRESSIF_BOOTLOADER_ARRAY_SIZE {}\n".format( len(info["ESPRESSIF_BOOTLOADER"]) ) output += "static const char *ESPRESSIF_BOOTLOADER[ESPRESSIF_BOOTLOADER_ARRAY_SIZE] =\n{\n" build_output = "Bootloader version:\n" for key, value in info["ESPRESSIF_BOOTLOADER"].items(): output += ' "{}: {}",\n'.format(key, value) build_output += " {}: {},\n".format(key, value) output += "};\n\n" build_output += "\n\n" # Espressif toolchain version info["ESPRESSIF_TOOLCHAIN"] = get_espressif_toolchain_version() output += "#define ESPRESSIF_TOOLCHAIN_ARRAY_SIZE {}\n".format( len(info["ESPRESSIF_TOOLCHAIN"]) ) output += ( "static const char *ESPRESSIF_TOOLCHAIN[ESPRESSIF_TOOLCHAIN_ARRAY_SIZE] =\n{\n" ) build_output += "Toolchain version:\n" for key, value in info["ESPRESSIF_TOOLCHAIN"].items(): output += ' "{}: {}",\n'.format(key, value) build_output += " {}: {},\n".format(key, value) output += "};\n\n" build_output += "\n\n" # Espressif esptool version info["ESPRESSIF_ESPTOOL"] = next( (s for s in get_python_modules() if "esptool" in s), "Not found" ) output += 'static const char ESPRESSIF_ESPTOOL[] = "{}";\n\n'.format( info["ESPRESSIF_ESPTOOL"].split("-")[1] ) build_output += "Esptool version: {}\n\n".format( info["ESPRESSIF_ESPTOOL"].split("-")[1] ) # Espressif HAL version arch = get_arch(config) hal_path = ARCH_ESP_HALDIR.format(TOPDIR=TOPDIR, ARCH_DIR=arch) info["ESPRESSIF_HAL"] = get_espressif_hal_version(hal_path) output += 'static const char ESPRESSIF_HAL[] = "{}";\n\n'.format( info["ESPRESSIF_HAL"] ) build_output += "HAL version: {}\n\n".format(info["ESPRESSIF_HAL"]) chip_info, chip_runtime = check_chip_info(config) if (chip_info and not chip_runtime) and info[ "ESPRESSIF_ESPTOOL" ] not in "Not found": info["ESPRESSIF_CHIP_ID"] = get_espressif_chip_id() output += 'static const char ESPRESSIF_CHIP_ID[] = "{}";\n\n'.format( info["ESPRESSIF_CHIP_ID"] ) build_output += "CHIP ID: = {}\n\n".format(info["ESPRESSIF_CHIP_ID"]) info["ESPRESSIF_FLASH_ID"] = get_espressif_flash_id() output += "#define ESPRESSIF_FLASH_ID_ARRAY_SIZE {}\n".format( len(info["ESPRESSIF_FLASH_ID"]) ) output += "static const char *ESPRESSIF_FLASH_ID[ESPRESSIF_FLASH_ID_ARRAY_SIZE] =\n{\n" build_output += "Flash ID:\n" for each_item in info["ESPRESSIF_FLASH_ID"]: output += ' "{}",\n'.format(each_item) build_output += " {}\n".format(each_item) output += "};\n\n" build_output += "\n\n" info["ESPRESSIF_SECURITY_INFO"] = get_espressif_security_info() output += "#define ESPRESSIF_SECURITY_INFO_ARRAY_SIZE {}\n".format( len(info["ESPRESSIF_SECURITY_INFO"]) ) output += "static const char *ESPRESSIF_SECURITY_INFO[ESPRESSIF_SECURITY_INFO_ARRAY_SIZE] =\n{\n" build_output += "Security information: \n" for each_item in info["ESPRESSIF_SECURITY_INFO"]: output += ' "{}",\n'.format(each_item) build_output += " {}\n".format(each_item) output += "};\n\n" build_output += "\n\n" info["ESPRESSIF_FLASH_STAT"] = get_espressif_flash_status() output += 'static const char ESPRESSIF_FLASH_STAT[] = "{}";\n\n'.format( info["ESPRESSIF_FLASH_STAT"] ) build_output += "Flash status: {}\n\n".format(info["ESPRESSIF_FLASH_STAT"]) info["ESPRESSIF_MAC_ADDR"] = get_espressif_mac_address() output += 'static const char ESPRESSIF_MAC_ADDR[] = "{}";\n\n'.format( info["ESPRESSIF_MAC_ADDR"] ) build_output += "MAC address: {}\n\n".format(info["ESPRESSIF_MAC_ADDR"]) elif chip_runtime: output += "#define ESPRESSIF_INFO_SIZE 384\n" output += "static char ESPRESSIF_INFO[ESPRESSIF_TOOLCHAIN_ARRAY_SIZE] =\n{\n 0\n};\n\n" return output, build_output