From 3fb70c677aa92fa600aa05e2c21cdf981c1ffd41 Mon Sep 17 00:00:00 2001 From: Joseph Liu Date: Tue, 13 Aug 2024 17:28:33 +0800 Subject: [PATCH] soc: arm: add nuvoton npcm400 support Add initial support for nuvoton npcm400, which is a chip family of Satellite Management Controller(SMC). Add ecst python scripts to append the header used by ROM Code Signed-off-by: Tyrone Ting Signed-off-by: James Chiang Signed-off-by: Joseph Liu --- dts/arm/nuvoton/npcm/npcm.dtsi | 24 + dts/arm/nuvoton/npcm/npcm4.dtsi | 28 + dts/arm/nuvoton/npcm400.dtsi | 23 + soc/nuvoton/npcm/CMakeLists.txt | 4 + soc/nuvoton/npcm/Kconfig | 9 + soc/nuvoton/npcm/Kconfig.defconfig | 9 + soc/nuvoton/npcm/Kconfig.soc | 11 + soc/nuvoton/npcm/common/CMakeLists.txt | 16 + soc/nuvoton/npcm/common/esiost/esiost.py | 596 ++++++++++++++++++ soc/nuvoton/npcm/common/esiost/esiost_args.py | 130 ++++ soc/nuvoton/npcm/npcm4/CMakeLists.txt | 15 + soc/nuvoton/npcm/npcm4/Kconfig | 10 + soc/nuvoton/npcm/npcm4/Kconfig.defconfig | 14 + soc/nuvoton/npcm/npcm4/Kconfig.soc | 19 + soc/nuvoton/npcm/npcm4/soc.c | 20 + soc/nuvoton/npcm/npcm4/soc.h | 18 + soc/nuvoton/npcm/soc.yml | 6 + 17 files changed, 952 insertions(+) create mode 100644 dts/arm/nuvoton/npcm/npcm.dtsi create mode 100644 dts/arm/nuvoton/npcm/npcm4.dtsi create mode 100644 dts/arm/nuvoton/npcm400.dtsi create mode 100644 soc/nuvoton/npcm/CMakeLists.txt create mode 100644 soc/nuvoton/npcm/Kconfig create mode 100644 soc/nuvoton/npcm/Kconfig.defconfig create mode 100644 soc/nuvoton/npcm/Kconfig.soc create mode 100644 soc/nuvoton/npcm/common/CMakeLists.txt create mode 100755 soc/nuvoton/npcm/common/esiost/esiost.py create mode 100755 soc/nuvoton/npcm/common/esiost/esiost_args.py create mode 100644 soc/nuvoton/npcm/npcm4/CMakeLists.txt create mode 100644 soc/nuvoton/npcm/npcm4/Kconfig create mode 100644 soc/nuvoton/npcm/npcm4/Kconfig.defconfig create mode 100644 soc/nuvoton/npcm/npcm4/Kconfig.soc create mode 100644 soc/nuvoton/npcm/npcm4/soc.c create mode 100644 soc/nuvoton/npcm/npcm4/soc.h create mode 100644 soc/nuvoton/npcm/soc.yml diff --git a/dts/arm/nuvoton/npcm/npcm.dtsi b/dts/arm/nuvoton/npcm/npcm.dtsi new file mode 100644 index 00000000000..c16607be9a7 --- /dev/null +++ b/dts/arm/nuvoton/npcm/npcm.dtsi @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu0: cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-m4"; + reg = <0>; + }; + }; +}; + +&nvic { + arm,num-irq-priority-bits = <3>; +}; diff --git a/dts/arm/nuvoton/npcm/npcm4.dtsi b/dts/arm/nuvoton/npcm/npcm4.dtsi new file mode 100644 index 00000000000..b23a80a966b --- /dev/null +++ b/dts/arm/nuvoton/npcm/npcm4.dtsi @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Device tree declarations of npcm soc family */ +#include "npcm.dtsi" + +/ { + soc { + + mdc: mdc@4000c000 { + compatible = "syscon"; + reg = <0x4000c000 0xa>; + reg-io-width = <1>; + }; + + mdc_header: mdc@4000c00a { + compatible = "syscon"; + reg = <0x4000c00a 0x4>; + reg-io-width = <2>; + }; + }; + + soc-if { + }; +}; diff --git a/dts/arm/nuvoton/npcm400.dtsi b/dts/arm/nuvoton/npcm400.dtsi new file mode 100644 index 00000000000..580d7813b2e --- /dev/null +++ b/dts/arm/nuvoton/npcm400.dtsi @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Device tree declarations of npcm soc family */ +#include +#include "npcm/npcm4.dtsi" + +/ { + flash0: flash@80000 { + reg = <0x00080000 DT_SIZE_M(1)>; + }; + + sram0: memory@10008000 { + compatible = "mmio-sram"; + reg = <0x10008000 DT_SIZE_K(764)>; + }; + + soc { + }; +}; diff --git a/soc/nuvoton/npcm/CMakeLists.txt b/soc/nuvoton/npcm/CMakeLists.txt new file mode 100644 index 00000000000..c5f97039eb7 --- /dev/null +++ b/soc/nuvoton/npcm/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 + +add_subdirectory(common) +add_subdirectory(${SOC_SERIES}) diff --git a/soc/nuvoton/npcm/Kconfig b/soc/nuvoton/npcm/Kconfig new file mode 100644 index 00000000000..4debfc5ceef --- /dev/null +++ b/soc/nuvoton/npcm/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +if SOC_FAMILY_NPCM + +rsource "*/Kconfig" + +endif # SOC_FAMILY_NPCM diff --git a/soc/nuvoton/npcm/Kconfig.defconfig b/soc/nuvoton/npcm/Kconfig.defconfig new file mode 100644 index 00000000000..aa7662f0cf2 --- /dev/null +++ b/soc/nuvoton/npcm/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +if SOC_FAMILY_NPCM + +rsource "*/Kconfig.defconfig" + +endif # SOC_FAMILY_NPCM diff --git a/soc/nuvoton/npcm/Kconfig.soc b/soc/nuvoton/npcm/Kconfig.soc new file mode 100644 index 00000000000..4e8d7805dc9 --- /dev/null +++ b/soc/nuvoton/npcm/Kconfig.soc @@ -0,0 +1,11 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +config SOC_FAMILY_NPCM + bool + +config SOC_FAMILY + default "npcm" if SOC_FAMILY_NPCM + +rsource "*/Kconfig.soc" diff --git a/soc/nuvoton/npcm/common/CMakeLists.txt b/soc/nuvoton/npcm/common/CMakeLists.txt new file mode 100644 index 00000000000..a2194845e5b --- /dev/null +++ b/soc/nuvoton/npcm/common/CMakeLists.txt @@ -0,0 +1,16 @@ +# +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories(.) + +set(NPCM_BIN_NAME ${CONFIG_KERNEL_BIN_NAME}.npcm.bin) +string(TOUPPER "${SOC_NAME}" soc_name_upper) + +set_property(GLOBAL APPEND PROPERTY extra_post_build_commands + COMMAND ${PYTHON_EXECUTABLE} ${SOC_${soc_name_upper}_DIR}/common/esiost/esiost.py + -i ${KERNEL_BIN_NAME} + -o ${NPCM_BIN_NAME} + -v + ) diff --git a/soc/nuvoton/npcm/common/esiost/esiost.py b/soc/nuvoton/npcm/common/esiost/esiost.py new file mode 100755 index 00000000000..cb346fff5dd --- /dev/null +++ b/soc/nuvoton/npcm/common/esiost/esiost.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 Nuvoton Technology Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +# This script will append/paste specific header to tell ROM code (Booter) of +# NPCM eSIO series how to load the firmware from flash to code ram +# Usage python3 ${ZEPHYR_BASE}/scripts/esiost.py +# -i in_file.bin -o out_file.bin +# [-chip ] [-v] + +import sys +import hashlib +from colorama import init, Fore +from esiost_args import EsiostArgs, exit_with_failure +from pathlib import Path + +# ESIOST +ESIOST_VER = "1.0.0" + +# Offsets inside the header +HDR_ANCHOR_OFFSET = 0x0 +HDR_FW_ENTRY_POINT_OFFSET = 0x21C +HDR_FW_FLASH_ADDR_START_LOAD_OFFSET = 0x220 +HDR_FW_FLASH_ADDR_END_LOAD_OFFSET = 0x224 +HDR_FW_LOAD_START_ADDR_OFFSET = 0x228 +HDR_FW_LENGTH_OFFSET = 0x22C +HDR_FW_LOAD_HASH_OFFSET = 0x480 +HDR_FW_SEG1_START_OFFSET = 0x4C0 +HDR_FW_SEG1_SIZE_OFFSET = 0x4C4 +HDR_FW_SEG2_START_OFFSET = 0x4C8 +HDR_FW_SEG2_SIZE_OFFSET = 0x4CC +HDR_FW_SEG3_START_OFFSET = 0x4D0 +HDR_FW_SEG3_SIZE_OFFSET = 0x4D4 +HDR_FW_SEG4_START_OFFSET = 0x4D8 +HDR_FW_SEG4_SIZE_OFFSET = 0x4DC +HDR_FW_SEG1_HASH_OFFSET = 0x500 +HDR_FW_SEG2_HASH_OFFSET = 0x540 +HDR_FW_SEG3_HASH_OFFSET = 0x580 +HDR_FW_SEG4_HASH_OFFSET = 0x5C0 +FW_IMAGE_OFFSET = 0x600 + +ARM_FW_ENTRY_POINT_OFFSET = 0x004 + +# Header field known values +FW_HDR_ANCHOR = '%FiMg94@' +FW_HDR_SEG1_START = 0x210 +FW_HDR_SEG1_SIZE = 0x2F0 +FW_HDR_SEG2_START = 0x0 +FW_HDR_SEG2_SIZE = 0x0 +FW_HDR_SEG3_START = 0x600 +FW_HDR_SEG4_START = 0x0 +FW_HDR_SEG4_SIZE = 0x0 + +# Header fields default values. +ADDR_16_BYTES_ALIGNED_MASK = 0x0000000f +ADDR_4_BYTES_ALIGNED_MASK = 0x00000003 +ADDR_4K_BYTES_ALIGNED_MASK = 0x00000fff + +INVALID_INPUT = -1 +HEADER_SIZE = FW_IMAGE_OFFSET + +# Verbose related values +NO_VERBOSE = 0 +REG_VERBOSE = 0 + +# Success/failure codes +EXIT_SUCCESS_STATUS = 0 +EXIT_FAILURE_STATUS = 1 + +def _bt_mode_handler(esiost_args): + """creates the bootloader table using the provided arguments. + + :param esiost_args: the object representing the command line arguments. + """ + + output_file = _set_input_and_output(esiost_args) + _check_chip(output_file, esiost_args) + + _copy_image(output_file, esiost_args) + _set_anchor(output_file, esiost_args) + _set_firmware_load_start_address(output_file, esiost_args) + _set_firmware_entry_point(output_file, esiost_args) + _set_firmware_length(output_file, esiost_args) + _set_firmware_load_hash(output_file, esiost_args) + _set_firmware_segment(output_file, esiost_args) + _set_firmware_segment_hash(output_file, esiost_args) + + _exit_with_success() + +def _set_input_and_output(esiost_args): + """checks the input file and output and sets the output file. + + checks input file existence, creates an output file according + to the 'output' argument. + + Note: input file size has to be greater than 0, and named differently + from output file + + :param esiost_args: the object representing the command line arguments. + + :returns: output file path object, or -1 if fails + """ + input_file = esiost_args.input + output = esiost_args.output + input_file_size = 0 + + if not input_file: + exit_with_failure("Define input file, using -i flag") + + input_file_path = Path(input_file) + + if not input_file_path.exists(): + exit_with_failure(f'Cannot open {input_file}') + elif input_file_path.stat().st_size == 0: + exit_with_failure(f'BIN Input file ({input_file}) is empty') + else: + input_file_size = input_file_path.stat().st_size + + if not output: + output_file = Path("out_" + input_file_path.name) + else: + output_file = Path(output) + + if output_file.exists(): + if output_file.samefile(input_file_path): + exit_with_failure(f'Input file name {input_file} ' + f'should be differed from' + f' Output file name {output}') + output_file.unlink() + + output_file.touch() + + if esiost_args.verbose == REG_VERBOSE: + print(Fore.LIGHTCYAN_EX + f'\nBIN file: {input_file}, size:' + f' {input_file_size} bytes') + print(f'Output file name: {output_file.name} \n') + + return output_file + +def _check_chip(output, esiost_args): + """checks if the chip entered is a legal chip, generates an error + and closes the application, deletes the output file if the chip name + is illegal. + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + + if esiost_args.chip_name == INVALID_INPUT: + message = f'Invalid chip name, ' + message += "should be npcm400." + _exit_with_failure_delete_file(output, message) + +def _set_anchor(output, esiost_args): + """writes the anchor value to the output file + + :param output: the output file object. + :param esiost_args: the object representing the command line arguments. + """ + + if len(FW_HDR_ANCHOR) > 8: + message = f'ANCHOR max support 8 bytes' + _exit_with_failure_delete_file(output, message) + + with output.open("r+b") as output_file: + output_file.seek(HDR_ANCHOR_OFFSET) + anchor_hex = FW_HDR_ANCHOR.encode('ascii') + output_file.write(anchor_hex) + if esiost_args.verbose == REG_VERBOSE: + print(f'- HDR - FW Header ANCHOR - Offset ' + f'{HDR_ANCHOR_OFFSET} - %s' % FW_HDR_ANCHOR) + + output_file.close() + +def _set_firmware_load_start_address(output, esiost_args): + """writes the fw load address to the output file + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + input_file_path = Path(esiost_args.input) + + start_ram = esiost_args.chip_ram_address + end_ram = start_ram + esiost_args.chip_ram_size + fw_load_addr = esiost_args.firmware_load_address + fw_length = esiost_args.firmware_length + fw_end_addr = fw_load_addr + fw_length + start_flash_addr = esiost_args.chip_flash_address + HEADER_SIZE + end_flash_addr = start_flash_addr + fw_length + + start_ram_to_print = _hex_print_format(start_ram) + end_ram_to_print = _hex_print_format(end_ram) + fw_load_addr_to_print = _hex_print_format(fw_load_addr) + fw_end_addr_to_print = _hex_print_format(fw_end_addr) + + if fw_length == INVALID_INPUT: + message = f'Cannot read firmware length' + _exit_with_failure_delete_file(output, message) + + if fw_load_addr is INVALID_INPUT: + message = f'Cannot read FW Load start address' + _exit_with_failure_delete_file(output, message) + + if fw_load_addr & ADDR_16_BYTES_ALIGNED_MASK != 0: + message = f'Firmware load address ({fw_load_addr_to_print}) ' \ + f'is not 16 bytes aligned' + _exit_with_failure_delete_file(output, message) + + if (fw_load_addr > end_ram) or (fw_load_addr < start_ram): + message = f'Firmware load address ({fw_load_addr_to_print}) ' \ + f'should be between start ({start_ram_to_print}) '\ + f'and end ({end_ram_to_print}) of RAM' + _exit_with_failure_delete_file(output, message) + + with output.open("r+b") as output_file: + # check fw_entry pt location in flash or not + with input_file_path.open("r+b") as input_file: + input_file.seek(ARM_FW_ENTRY_POINT_OFFSET) + fw_arm_entry_byte = input_file.read(4) + fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") + + if fw_arm_entry_pt == 0: + input_file.seek(ARM_FW_ENTRY_POINT_OFFSET + HEADER_SIZE) + fw_arm_entry_byte = input_file.read(4) + fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") + else: + if fw_end_addr > end_ram: + message = f'Firmware end address ({fw_end_addr_to_print}) should be ' + message += f'less than end of RAM address ({end_ram_to_print})' + _exit_with_failure_delete_file(output, message) + + if start_flash_addr < fw_arm_entry_pt < end_flash_addr: + fw_load_addr = 0x0 + start_flash_addr = 0x0 + end_flash_addr = 0x0 + + input_file.close() + + # set start load flash address + output_file.seek(HDR_FW_FLASH_ADDR_START_LOAD_OFFSET) + output_file.write(start_flash_addr.to_bytes(4, "little")) + + # set end load flash address + output_file.seek(HDR_FW_FLASH_ADDR_END_LOAD_OFFSET) + output_file.write(end_flash_addr.to_bytes(4, "little")) + + # set load start address (RAM) + output_file.seek(HDR_FW_LOAD_START_ADDR_OFFSET) + output_file.write(fw_load_addr.to_bytes(4, "little")) + + if esiost_args.verbose == REG_VERBOSE: + print(f'- HDR - FW load start address - Offset ' + f'{HDR_FW_LOAD_START_ADDR_OFFSET} - ' + f'{_hex_print_format(fw_load_addr)}') + print(f'- HDR - flash load start address - Offset ' + f'{HDR_FW_FLASH_ADDR_START_LOAD_OFFSET} - ' + f'{_hex_print_format(start_flash_addr)}') + print(f'- HDR - flash load end address - Offset ' + f'{HDR_FW_FLASH_ADDR_END_LOAD_OFFSET} - ' + f'{_hex_print_format(end_flash_addr)}') + + output_file.close() + +def _set_firmware_entry_point(output, esiost_args): + """writes the fw entry point to the output file. + proportions: + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + input_file_path = Path(esiost_args.input) + fw_entry_pt = esiost_args.firmware_entry_point + start_flash_addr = esiost_args.chip_flash_address + HEADER_SIZE + end_flash_addr = start_flash_addr + esiost_args.chip_flash_size + + # check if fwep flag wasn't set and set it to fw load address if needed + if fw_entry_pt is None: + fw_entry_pt = esiost_args.firmware_load_address + + # check fw_entry pt location in flash or not + with input_file_path.open("r+b") as input_file: + input_file.seek(ARM_FW_ENTRY_POINT_OFFSET) + fw_arm_entry_byte = input_file.read(4) + fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") + + if fw_arm_entry_pt == 0: + input_file.seek(ARM_FW_ENTRY_POINT_OFFSET + HEADER_SIZE) + fw_arm_entry_byte = input_file.read(4) + fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") + + if start_flash_addr < fw_arm_entry_pt < end_flash_addr: + fw_entry_pt = start_flash_addr + + input_file.close() + + with output.open("r+b") as output_file: + output_file.seek(HDR_FW_ENTRY_POINT_OFFSET) + output_file.write(fw_entry_pt.to_bytes(4, "little")) + output_file.close() + + if esiost_args.verbose == REG_VERBOSE: + print(f'- HDR - FW Entry point - Offset ' + f'{HDR_FW_ENTRY_POINT_OFFSET} - ' + f'{_hex_print_format(fw_entry_pt)}') + +def _openssl_digest(filepath): + """Computes the SHA-256 digest of a file using hashlib. + + :param filepath: Path to the file to digest. + :return: The SHA-256 digest of the file as a bytearray. + """ + sha256_hash = hashlib.sha256() + with open(filepath, "rb") as f: + # Read and update hash string value in blocks of 4K + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + return bytearray(sha256_hash.digest()) + +def _set_firmware_length(output, esiost_args): + """writes the flash size value to the output file + Note: the firmware length value has already been checked before + this method + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + + fw_length = esiost_args.firmware_length + fw_length_to_print = _hex_print_format(fw_length) + + with output.open("r+b") as output_file: + output_file.seek(HDR_FW_LENGTH_OFFSET) + output_file.write(fw_length.to_bytes(4, "big")) + if esiost_args.verbose == REG_VERBOSE: + print(f'- HDR - FW Length - Offset ' + f'{HDR_FW_LENGTH_OFFSET} - ' + f'{fw_length_to_print}') + output_file.close() + +def _set_firmware_load_hash(output, esiost_args): + """writes the load hash value to the output file + Note: the firmware length value has already been checked before + this method + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + sha256_hash = hashlib.sha256() + with output.open("r+b") as f: + f.seek(HEADER_SIZE) + # Read and update hash string value in blocks of 4K + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + + hash_data = bytearray(sha256_hash.digest()) + + with output.open("r+b") as output_file: + output_file.seek(HDR_FW_LOAD_HASH_OFFSET) + output_file.write(hash_data) + output_file.close() + +def _set_firmware_segment(output, esiost_args): + """writes the segment start and size value to the output file + Note: the firmware length value has already been checked before + this method + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + + fw_length = esiost_args.firmware_length + + with output.open("r+b") as output_file: + # set segment_1 start and size + output_file.seek(HDR_FW_SEG1_START_OFFSET) + output_file.write(FW_HDR_SEG1_START.to_bytes(4, "little")) + output_file.seek(HDR_FW_SEG1_SIZE_OFFSET) + output_file.write(FW_HDR_SEG1_SIZE.to_bytes(4, "little")) + + # set segment_2 start and size + output_file.seek(HDR_FW_SEG2_START_OFFSET) + output_file.write(FW_HDR_SEG2_START.to_bytes(4, "little")) + output_file.seek(HDR_FW_SEG2_SIZE_OFFSET) + output_file.write(FW_HDR_SEG2_SIZE.to_bytes(4, "little")) + + # set segment_3 start and size + output_file.seek(HDR_FW_SEG3_START_OFFSET) + output_file.write(FW_HDR_SEG3_START.to_bytes(4, "little")) + output_file.seek(HDR_FW_SEG3_SIZE_OFFSET) + output_file.write(fw_length.to_bytes(4, "little")) + + # set segment_4 start and size + output_file.seek(HDR_FW_SEG4_START_OFFSET) + output_file.write(FW_HDR_SEG4_START.to_bytes(4, "little")) + output_file.seek(HDR_FW_SEG4_SIZE_OFFSET) + output_file.write(FW_HDR_SEG4_SIZE.to_bytes(4, "little")) + + segment1_start_to_print = _hex_print_format(FW_HDR_SEG1_START) + segment1_size_to_print = _hex_print_format(FW_HDR_SEG1_SIZE) + segment2_start_to_print = _hex_print_format(FW_HDR_SEG2_START) + segment2_size_to_print = _hex_print_format(FW_HDR_SEG2_SIZE) + segment3_start_to_print = _hex_print_format(FW_HDR_SEG3_START) + segment3_size_to_print = _hex_print_format(fw_length) + segment4_start_to_print = _hex_print_format(FW_HDR_SEG4_START) + segment4_size_to_print = _hex_print_format(FW_HDR_SEG4_SIZE) + + if esiost_args.verbose == REG_VERBOSE: + print(f'- HDR - Segment1 start address - Offset ' + f'{HDR_FW_SEG1_START_OFFSET} - ' + f'{segment1_start_to_print}') + print(f'- HDR - Segment1 size - Offset ' + f'{HDR_FW_SEG1_SIZE_OFFSET} - ' + f'{segment1_size_to_print}') + print(f'- HDR - Segment2 start address - Offset ' + f'{HDR_FW_SEG2_START_OFFSET} - ' + f'{segment2_start_to_print}') + print(f'- HDR - Segment2 size - Offset ' + f'{HDR_FW_SEG2_SIZE_OFFSET} - ' + f'{segment2_size_to_print}') + print(f'- HDR - Segment3 start address - Offset ' + f'{HDR_FW_SEG3_START_OFFSET} - ' + f'{segment3_start_to_print}') + print(f'- HDR - Segment3 size - Offset ' + f'{HDR_FW_SEG3_SIZE_OFFSET} - ' + f'{segment3_size_to_print}') + print(f'- HDR - Segment4 start address - Offset ' + f'{HDR_FW_SEG4_START_OFFSET} - ' + f'{segment4_start_to_print}') + print(f'- HDR - Segment4 size - Offset ' + f'{HDR_FW_SEG4_SIZE_OFFSET} - ' + f'{segment4_size_to_print}') + + output_file.close() + +def _clearup_tempfiles(output, esiost_args): + """clearup the tempfiles + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + + output_file = Path(output) + + seg1_file = Path("seg1_" + output_file.name) + if seg1_file.exists(): + seg1_file.unlink() + +def _set_firmware_segment_hash(output, esiost_args): + """Writes the segment hash value to the output file. + Note: the firmware length value has already been checked before this method. + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + + # Generate segment files + with output.open("r+b") as output_file: + # seg1 + output_file.seek(HDR_FW_SEG1_START_OFFSET) + seg1_start = int.from_bytes(output_file.read(4), "little") + output_file.seek(HDR_FW_SEG1_SIZE_OFFSET) + seg1_size = int.from_bytes(output_file.read(4), "little") + output_file.seek(seg1_start) + + seg1_data = output_file.read(seg1_size) + + seg1_file_path = Path("seg1_" + output_file.name) + with seg1_file_path.open("wb") as seg1_file: + seg1_file.write(seg1_data) + + # set hash + + # seg1 hash + hash_data = _openssl_digest(seg1_file_path) + output_file.seek(HDR_FW_SEG1_HASH_OFFSET) + output_file.write(hash_data) + + # seg3 hash + sha256_hash = hashlib.sha256() + output_file.seek(HEADER_SIZE) + # Read and update hash string value in blocks of 4K + for byte_block in iter(lambda: output_file.read(4096), b""): + sha256_hash.update(byte_block) + + hash_data = bytearray(sha256_hash.digest()) + + output_file.seek(HDR_FW_SEG3_HASH_OFFSET) + output_file.write(hash_data) + + _clearup_tempfiles(output, esiost_args) + +def _copy_image(output, esiost_args): + """copies the fw image from the input file to the output file + if firmware header offset is defined, just copies the input file to the + output file + + :param output: the output file object, + :param esiost_args: the object representing the command line arguments. + """ + + # check input file offset + with open(esiost_args.input, "rb") as firmware_image: + firmware_image.seek(ARM_FW_ENTRY_POINT_OFFSET) + fw_arm_entry_byte = firmware_image.read(4) + fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") + if fw_arm_entry_pt != 0: + image_offset = 0 + input_file_size = Path(esiost_args.input).stat().st_size + else: + firmware_image.seek(ARM_FW_ENTRY_POINT_OFFSET + HEADER_SIZE) + fw_arm_entry_byte = firmware_image.read(4) + fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") + if fw_arm_entry_pt == 0: + sys.exit(EXIT_FAILURE_STATUS) + else: + image_offset = 1 + input_file_size = Path(esiost_args.input).stat().st_size - HEADER_SIZE + + firmware_image.close() + + with open(esiost_args.input, "rb") as firmware_image: + with open(output, "r+b") as output_file: + if image_offset == 0: + output_file.seek(HEADER_SIZE) + for line in firmware_image: + output_file.write(line) + output_file.close() + firmware_image.close() + + # update firmware length if needed + fw_length = esiost_args.firmware_length + if fw_length is None: + esiost_args.firmware_length = input_file_size + +def _hex_print_format(value): + """hex representation of an integer + + :param value: an integer to be represented in hex + """ + return "0x{:08x}".format(value) + +def _exit_with_failure_delete_file(output, message): + """formatted failure message printer, prints the + relevant error message, deletes the output file, + and exits the application. + + :param message: the error message to be printed + """ + output_file = Path(output) + if output_file.exists(): + output_file.unlink() + + message = '\n' + message + message += '\n' + message += '******************************\n' + message += '*** FAILED ***\n' + message += '******************************\n' + print(Fore.RED + message) + + sys.exit(EXIT_FAILURE_STATUS) + +def _exit_with_success(): + """formatted success message printer, prints the + success message and exits the application. + """ + message = '\n' + message += '******************************\n' + message += '*** SUCCESS ***\n' + message += '******************************\n' + print(Fore.GREEN + message) + + sys.exit(EXIT_SUCCESS_STATUS) + +def main(): + """main of the application + """ + init() # colored print initialization for windows + + if len(sys.argv) < 2: + sys.exit(EXIT_FAILURE_STATUS) + + esiost_obj = EsiostArgs() + + if esiost_obj.error_args: + for err_arg in esiost_obj.error_args: + message = f'unKnown flag: {err_arg}' + exit_with_failure(message) + sys.exit(EXIT_SUCCESS_STATUS) + + # Start to handle booter header table + _bt_mode_handler(esiost_obj) + +if __name__ == '__main__': + main() diff --git a/soc/nuvoton/npcm/common/esiost/esiost_args.py b/soc/nuvoton/npcm/common/esiost/esiost_args.py new file mode 100755 index 00000000000..16e468e8d0e --- /dev/null +++ b/soc/nuvoton/npcm/common/esiost/esiost_args.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 Nuvoton Technology Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +# This file contains general functions for ESIOST application + +import sys +import argparse +import colorama +from colorama import Fore + +INVALID_INPUT = -1 +EXIT_FAILURE_STATUS = 1 + +# Verbose related values +NO_VERBOSE = 0 +REG_VERBOSE = 1 + +# argument default values. +DEFAULT_VERBOSE = NO_VERBOSE + +# Chips: convert from name to index. +CHIPS_INFO = { + 'npcm400': {'flash_address': 0x80000, 'flash_size': 0x20000, 'ram_address': 0x10008000, 'ram_size': 0xC0000}, +} + +DEFAULT_CHIP = 'npcm400' + +class EsiostArgs: + """creates an arguments object for the ESIOST, + the arguments are taken from the command line and/or + argument file + """ + error_args = None + + help = False + verbose = DEFAULT_VERBOSE + input = None + output = None + args_file = None + chip_name = DEFAULT_CHIP + chip_ram_address = CHIPS_INFO[DEFAULT_CHIP]['ram_address'] + chip_ram_size = CHIPS_INFO[DEFAULT_CHIP]['ram_address'] + chip_flash_address = CHIPS_INFO[DEFAULT_CHIP]['flash_address'] + chip_flash_size = CHIPS_INFO[DEFAULT_CHIP]['flash_size'] + firmware_load_address = None + firmware_entry_point = None + firmware_length = None + + def __init__(self): + + arguments = _create_parser("") + valid_arguments = arguments[0] + invalid_arguments = arguments[1] + self.error_args = invalid_arguments + + _populate_args(self, valid_arguments) + _populate_chip_fields(self) + +def _populate_chip_fields(self): + """populate the chip related fields for the esiost""" + self.chip_name = self.chip_name + chip = str(self.chip_name).lower() + + if chip not in CHIPS_INFO: + self.chip_name = INVALID_INPUT + return + + self.chip_ram_address = CHIPS_INFO[chip]['ram_address'] + self.chip_ram_size = CHIPS_INFO[chip]['ram_size'] + self.chip_flash_address = CHIPS_INFO[DEFAULT_CHIP]['flash_address'] + self.chip_flash_size = CHIPS_INFO[DEFAULT_CHIP]['flash_size'] + if self.firmware_load_address is None: + self.firmware_load_address = self.chip_ram_address + +def _populate_args(self, argument_list): + """populate the esiost arguments according to the command line/ args file""" + for arg in vars(argument_list): + if (arg == "input") & (argument_list.input is not None): + self.input = argument_list.input + + elif (arg == "output") & (argument_list.output is not None): + self.output = argument_list.output + + elif (arg == "chip") & (argument_list.chip is not None): + self.chip_name = argument_list.chip + _populate_chip_fields(self) + + elif (arg == "verbose") & argument_list.verbose: + self.verbose = REG_VERBOSE + +def _create_parser(arg_list): + """create argument parser according to pre-defined arguments + + :param arg_list: when empty, parses command line arguments, + else parses the given string + """ + + parser = argparse.ArgumentParser(conflict_handler='resolve', allow_abbrev=False) + parser.add_argument("-i", nargs='?', dest="input") + parser.add_argument("-o", nargs='?', dest="output") + parser.add_argument("-chip", dest="chip") + parser.add_argument("-v", action="store_true", dest="verbose") + + args = parser.parse_known_args(arg_list.split()) + + if arg_list == "": + args = parser.parse_known_args() + + return args + +def exit_with_failure(message): + """formatted failure message printer, prints the + relevant error message and exits the application. + + :param message: the error message to be printed + """ + + message = '\n' + message + message += '\n' + message += '******************************\n' + message += '*** FAILED ***\n' + message += '******************************\n' + print(Fore.RED + message) + + sys.exit(EXIT_FAILURE_STATUS) + +colorama.init() diff --git a/soc/nuvoton/npcm/npcm4/CMakeLists.txt b/soc/nuvoton/npcm/npcm4/CMakeLists.txt new file mode 100644 index 00000000000..9a246a41b34 --- /dev/null +++ b/soc/nuvoton/npcm/npcm4/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +zephyr_include_directories( + . + ${ZEPHYR_BASE}/drivers +) + +zephyr_sources( + soc.c +) + +set(SOC_LINKER_SCRIPT ${ZEPHYR_BASE}/include/zephyr/arch/arm/cortex_m/scripts/linker.ld CACHE INTERNAL "") diff --git a/soc/nuvoton/npcm/npcm4/Kconfig b/soc/nuvoton/npcm/npcm4/Kconfig new file mode 100644 index 00000000000..a393fad27f9 --- /dev/null +++ b/soc/nuvoton/npcm/npcm4/Kconfig @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +config SOC_SERIES_NPCM4 + select ARM + select CPU_CORTEX_M4 + select CPU_CORTEX_M_HAS_DWT + select CORTEX_M_SYSTICK + select CPU_HAS_ARM_MPU diff --git a/soc/nuvoton/npcm/npcm4/Kconfig.defconfig b/soc/nuvoton/npcm/npcm4/Kconfig.defconfig new file mode 100644 index 00000000000..a3988963b2d --- /dev/null +++ b/soc/nuvoton/npcm/npcm4/Kconfig.defconfig @@ -0,0 +1,14 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +if SOC_SERIES_NPCM4 + +config NUM_IRQS + default 82 + +config ROM_START_OFFSET + default 0x600 if XIP + default 0x0 if !XIP + +endif # SOC_SERIES_NPCM4 diff --git a/soc/nuvoton/npcm/npcm4/Kconfig.soc b/soc/nuvoton/npcm/npcm4/Kconfig.soc new file mode 100644 index 00000000000..3d4cb6b471d --- /dev/null +++ b/soc/nuvoton/npcm/npcm4/Kconfig.soc @@ -0,0 +1,19 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 + +config SOC_SERIES_NPCM4 + bool + select SOC_FAMILY_NPCM + help + Enable support for Nuvoton NPCM4 series + +config SOC_NPCM400 + bool + select SOC_SERIES_NPCM4 + +config SOC_SERIES + default "npcm4" if SOC_SERIES_NPCM4 + +config SOC + default "npcm400" if SOC_NPCM400 diff --git a/soc/nuvoton/npcm/npcm4/soc.c b/soc/nuvoton/npcm/npcm4/soc.c new file mode 100644 index 00000000000..42536543233 --- /dev/null +++ b/soc/nuvoton/npcm/npcm4/soc.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(soc, CONFIG_SOC_LOG_LEVEL); + +static int soc_npcm4_init(void) +{ + return 0; +} + +SYS_INIT(soc_npcm4_init, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/soc/nuvoton/npcm/npcm4/soc.h b/soc/nuvoton/npcm/npcm4/soc.h new file mode 100644 index 00000000000..6449769087e --- /dev/null +++ b/soc/nuvoton/npcm/npcm4/soc.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NUVOTON_NPCM_SOC_H_ +#define _NUVOTON_NPCM_SOC_H_ + +/* CMSIS required definitions */ +#define __FPU_PRESENT CONFIG_CPU_HAS_FPU +#define __MPU_PRESENT CONFIG_CPU_HAS_ARM_MPU + +/* Add include for DTS generated information */ +#include +#include + +#endif /* _NUVOTON_NPCM_SOC_H_ */ diff --git a/soc/nuvoton/npcm/soc.yml b/soc/nuvoton/npcm/soc.yml new file mode 100644 index 00000000000..df9c8dadfa5 --- /dev/null +++ b/soc/nuvoton/npcm/soc.yml @@ -0,0 +1,6 @@ +family: +- name: npcm + series: + - name: npcm4 + socs: + - name: npcm400