1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-26 18:02:35 +00:00

feat(core/prodtest): add prodtest emulator

[no changelog]
This commit is contained in:
tychovrahe 2025-05-16 21:04:30 +02:00 committed by TychoVrahe
parent 9e8f989c80
commit 9683efb5b9
10 changed files with 478 additions and 12 deletions

View File

@ -16,6 +16,7 @@ BOOTLOADER_BUILD_DIR = $(BUILD_DIR)/bootloader
BOOTLOADER_CI_BUILD_DIR = $(BUILD_DIR)/bootloader_ci BOOTLOADER_CI_BUILD_DIR = $(BUILD_DIR)/bootloader_ci
BOOTLOADER_EMU_BUILD_DIR = $(BUILD_DIR)/bootloader_emu BOOTLOADER_EMU_BUILD_DIR = $(BUILD_DIR)/bootloader_emu
PRODTEST_BUILD_DIR = $(BUILD_DIR)/prodtest PRODTEST_BUILD_DIR = $(BUILD_DIR)/prodtest
PRODTEST_EMU_BUILD_DIR = $(BUILD_DIR)/prodtest_emu
REFLASH_BUILD_DIR = $(BUILD_DIR)/reflash REFLASH_BUILD_DIR = $(BUILD_DIR)/reflash
KERNEL_BUILD_DIR = $(BUILD_DIR)/kernel KERNEL_BUILD_DIR = $(BUILD_DIR)/kernel
FIRMWARE_BUILD_DIR = $(BUILD_DIR)/firmware FIRMWARE_BUILD_DIR = $(BUILD_DIR)/firmware
@ -295,6 +296,9 @@ build_bootloader_emu_debug: ## build the unix bootloader emulator
build_prodtest: ## build production test firmware build_prodtest: ## build production test firmware
$(SCONS) $(PRODTEST_BUILD_DIR)/prodtest.bin $(SCONS) $(PRODTEST_BUILD_DIR)/prodtest.bin
build_prodtest_emu: ## build the unix prodtest emulator
$(SCONS) $(PRODTEST_EMU_BUILD_DIR)/prodtest.elf
build_reflash: ## build reflash firmware + reflash image build_reflash: ## build reflash firmware + reflash image
$(SCONS) $(REFLASH_BUILD_DIR)/reflash.bin $(SCONS) $(REFLASH_BUILD_DIR)/reflash.bin
dd if=build/boardloader/boardloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=0 dd if=build/boardloader/boardloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=0

View File

@ -132,6 +132,7 @@ FEATURES_AVAILABLE = models.configure_board(TREZOR_MODEL, HW_REVISION, FEATURES_
SOURCE_PRODTEST = [ SOURCE_PRODTEST = [
'embed/projects/prodtest/header.S', 'embed/projects/prodtest/header.S',
'embed/projects/prodtest/main.c', 'embed/projects/prodtest/main.c',
'embed/projects/prodtest/commands.c',
'embed/projects/prodtest/cmd/prodtest_boardloader.c', 'embed/projects/prodtest/cmd/prodtest_boardloader.c',
'embed/projects/prodtest/cmd/prodtest_ble.c', 'embed/projects/prodtest/cmd/prodtest_ble.c',
'embed/projects/prodtest/cmd/prodtest_bootloader.c', 'embed/projects/prodtest/cmd/prodtest_bootloader.c',

View File

@ -0,0 +1,289 @@
# pylint: disable=E0602
import os
import shlex
import tools, models, ui
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T2T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
HW_REVISION = 'emulator'
FEATURE_FLAGS = {
"AES_GCM": True,
}
if not models.has_emulator(TREZOR_MODEL):
# skip prodtest build
env = Environment()
def build_prodtest(target,source,env):
print(f'prodtest: nothing to build for Model {TREZOR_MODEL}')
program_bin = env.Command(
target='prodtest.elf',
source=None,
action=build_prodtest
)
Return()
FEATURES_WANTED = ["input", "rgb_led", "display", "tropic", "sd_card", "usb"]
CCFLAGS_MOD = ''
CPPPATH_MOD = []
CPPDEFINES_MOD = [
'KERNEL_MODE',
'AES_128',
'USE_INSECURE_PRNG',
]
SOURCE_MOD = []
SOURCE_MOD_CRYPTO = []
CPPDEFINES_HAL = []
SOURCE_HAL = []
PATH_HAL = []
RUST_UI_FEATURES = []
# modtrezorcrypto
CPPPATH_MOD += [
'vendor/trezor-crypto',
'vendor/trezor-storage',
]
SOURCE_MOD += [
'vendor/trezor-storage/flash_area.c',
]
SOURCE_MOD_CRYPTO += [
'vendor/trezor-crypto/aes/aes_modes.c',
'vendor/trezor-crypto/aes/aesccm.c',
'vendor/trezor-crypto/aes/aescrypt.c',
'vendor/trezor-crypto/aes/aeskey.c',
'vendor/trezor-crypto/aes/aestab.c',
'vendor/trezor-crypto/bignum.c',
'vendor/trezor-crypto/blake256.c',
'vendor/trezor-crypto/blake2b.c',
'vendor/trezor-crypto/buffer.c',
'vendor/trezor-crypto/chacha_drbg.c',
'vendor/trezor-crypto/chacha20poly1305/chacha_merged.c',
'vendor/trezor-crypto/der.c',
'vendor/trezor-crypto/ecdsa.c',
'vendor/trezor-crypto/ed25519-donna/curve25519-donna-32bit.c',
'vendor/trezor-crypto/ed25519-donna/curve25519-donna-helpers.c',
'vendor/trezor-crypto/ed25519-donna/curve25519-donna-scalarmult-base.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-donna-32bit-tables.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-donna-basepoint-table.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-donna-impl-base.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-keccak.c',
'vendor/trezor-crypto/ed25519-donna/ed25519-sha3.c',
'vendor/trezor-crypto/ed25519-donna/ed25519.c',
'vendor/trezor-crypto/ed25519-donna/modm-donna-32bit.c',
'vendor/trezor-crypto/groestl.c',
'vendor/trezor-crypto/hasher.c',
'vendor/trezor-crypto/hmac.c',
'vendor/trezor-crypto/hmac_drbg.c',
'vendor/trezor-crypto/memzero.c',
'vendor/trezor-crypto/nist256p1.c',
'vendor/trezor-crypto/rand.c',
'vendor/trezor-crypto/ripemd160.c',
'vendor/trezor-crypto/rfc6979.c',
'vendor/trezor-crypto/secp256k1.c',
'vendor/trezor-crypto/sha2.c',
'vendor/trezor-crypto/sha3.c',
'vendor/trezor-crypto/tls_prf.c',
]
# AES-GCM
if FEATURE_FLAGS["AES_GCM"]:
CPPDEFINES_MOD += [
'USE_AES_GCM',
'AES_VAR',
]
SOURCE_MOD_CRYPTO += [
'vendor/trezor-crypto/aes/gf128mul.c',
'vendor/trezor-crypto/aes/aesgcm.c',
]
# modtrezorui
CPPPATH_MOD += [
'vendor/micropython/lib/uzlib'
]
SOURCE_MOD += [
'embed/gfx/bitblt/gfx_bitblt.c',
'embed/gfx/bitblt/gfx_bitblt_rgb565.c',
'embed/gfx/bitblt/gfx_bitblt_rgba8888.c',
'embed/gfx/bitblt/gfx_bitblt_mono8.c',
'embed/gfx/fonts/font_bitmap.c',
'embed/gfx/gfx_color.c',
'embed/gfx/gfx_draw.c',
'embed/gfx/terminal.c',
'embed/io/display/display_utils.c',
'embed/util/image/image.c',
'embed/util/rsod/rsod.c',
'embed/util/scm_revision/scm_revision.c',
'embed/rtl/cli.c',
'embed/rtl/error_handling.c',
'embed/rtl/mini_printf.c',
'embed/rtl/strutils.c',
'embed/rtl/unit_test.c',
'vendor/micropython/lib/uzlib/adler32.c',
'vendor/micropython/lib/uzlib/crc32.c',
'vendor/micropython/lib/uzlib/tinflate.c',
]
ui.init_ui(TREZOR_MODEL, "prodtest", RUST_UI_FEATURES)
env = Environment(
ENV=os.environ,
CFLAGS=ARGUMENTS.get('CFLAGS', '') + f" -DCONFIDENTIAL= -DPRODUCTION={ARGUMENTS.get('PRODUCTION', '0')}",
CPPDEFPREFIX="-D'",
CPPDEFSUFFIX="'",
)
FEATURES_AVAILABLE = models.configure_board(TREZOR_MODEL, HW_REVISION, FEATURES_WANTED, env, CPPDEFINES_HAL, SOURCE_HAL, PATH_HAL)
SOURCE_PRODTEST = [
'embed/projects/prodtest/main.c',
'embed/projects/prodtest/commands.c',
'embed/projects/prodtest/cmd/prodtest_boardloader.c',
'embed/projects/prodtest/cmd/prodtest_ble.c',
'embed/projects/prodtest/cmd/prodtest_bootloader.c',
'embed/projects/prodtest/cmd/prodtest_button.c',
'embed/projects/prodtest/cmd/prodtest_display.c',
'embed/projects/prodtest/cmd/prodtest_prodtest.c',
'embed/projects/prodtest/cmd/prodtest_backup_ram.c',
'embed/projects/prodtest/cmd/prodtest_get_cpuid.c',
'embed/projects/prodtest/cmd/prodtest_haptic.c',
'embed/projects/prodtest/cmd/prodtest_help.c',
'embed/projects/prodtest/cmd/prodtest_hw_revision.c',
'embed/projects/prodtest/cmd/prodtest_nfc.c',
'embed/projects/prodtest/cmd/prodtest_nrf.c',
'embed/projects/prodtest/cmd/prodtest_optiga.c',
'embed/projects/prodtest/cmd/prodtest_otp_batch.c',
'embed/projects/prodtest/cmd/prodtest_otp_variant.c',
'embed/projects/prodtest/cmd/prodtest_ping.c',
'embed/projects/prodtest/cmd/prodtest_power_manager.c',
'embed/projects/prodtest/cmd/prodtest_reboot.c',
'embed/projects/prodtest/cmd/prodtest_rgbled.c',
'embed/projects/prodtest/cmd/prodtest_sdcard.c',
'embed/projects/prodtest/cmd/prodtest_tamper.c',
'embed/projects/prodtest/cmd/prodtest_sbu.c',
'embed/projects/prodtest/cmd/prodtest_touch.c',
'embed/projects/prodtest/cmd/prodtest_tropic.c',
'embed/projects/prodtest/cmd/prodtest_wpc',
'embed/projects/prodtest/emulator.c',
]
SOURCE_HAL += [
'embed/projects/unix/profile.c',
]
env.Replace(
CAT='cat',
CP='cp',
AS='as',
AR='ar',
CC='gcc',
LINK='ld',
SIZE='size',
STRIP='strip',
OBJCOPY='objcopy',
PYTHON='python',
MAKECMAKELISTS='$PYTHON tools/make_cmakelists.py', )
MODEL_AS_NUMBER = str(models.get_hw_model_as_number(TREZOR_MODEL))
ALLPATHS = ['embed/rust',
'embed/projects/prodtest',
'embed/rtl/inc',
'embed/models',
'embed/projects/unix',
'embed/gfx/inc',
'embed/util/image/inc',
'embed/util/rsod/inc',
'embed/util/scm_revision/inc',
'embed/util/translations/inc',
] + CPPPATH_MOD + PATH_HAL
env.Replace(
COPT=env.get('ENV').get('OPTIMIZE', '-Os'),
CCFLAGS='$COPT '
'-g '
'-nostdlib '
'-std=gnu11 -Wall -Werror -Wpointer-arith -Wno-missing-braces -fno-common '
'-fdata-sections -ffunction-sections '
'-ffreestanding '
'-fstack-protector-all '
+ CCFLAGS_MOD,
LINKFLAGS='',
CPPPATH=ALLPATHS,
CPPDEFINES=[
'TREZOR_PRODTEST',
'TREZOR_EMULATOR',
'TREZOR_MODEL_'+TREZOR_MODEL,
] + CPPDEFINES_MOD + CPPDEFINES_HAL,
ASPPFLAGS='$CFLAGS $CCFLAGS', )
try:
env.ParseConfig('pkg-config --cflags --libs libjpeg')
except OSError:
print("libjpeg not installed, Emulator build is not possible")
try:
env.ParseConfig('pkg-config --cflags --libs sdl2 SDL2_image')
except OSError:
print("SDL2 not installed, Emulator build is not possible")
env.Replace(
ALLSOURCES=SOURCE_MOD + SOURCE_MOD_CRYPTO + SOURCE_PRODTEST + SOURCE_HAL,
ALLDEFS=tools.get_defs_for_cmake(env['CPPDEFINES']))
cmake_gen = env.Command(
target='CMakeLists.txt',
source='',
action='$MAKECMAKELISTS --sources $ALLSOURCES --dirs $CPPPATH --defs $ALLDEFS',
)
#
# Rust library
#
env.get("ENV")["RUST_TARGET"] = os.popen("rustc -vV | sed -n 's/host: //p'").read().strip()
features = ['prodtest',] + FEATURES_AVAILABLE + RUST_UI_FEATURES
if ARGUMENTS.get('TREZOR_EMULATOR_DEBUGGABLE', '0') == '1':
profile = 'dev'
else:
profile = 'release'
rust = tools.add_rust_lib(
env=env,
build='prodtest_emu',
profile=profile,
features=features,
all_paths=ALLPATHS,
build_dir=str(Dir('.').abspath),
)
env.Append(LINKFLAGS=['-lm'])
if env['PLATFORM'] == 'darwin':
env.Append(LINKFLAGS=['-Wl,-dead_strip'])
else:
env.Append(LINKFLAGS=['-Wl,--gc-sections'])
#
# Program objects
#
obj_program = []
obj_program += env.Object(source=SOURCE_MOD)
obj_program += env.Object(source=SOURCE_MOD_CRYPTO, CCFLAGS='$CCFLAGS -ftrivial-auto-var-init=zero')
obj_program += env.Object(source=SOURCE_PRODTEST)
obj_program += env.Object(source=SOURCE_HAL)
program_elf = env.Command(
target='prodtest.elf',
source=obj_program,
action=
'$CC -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $LINKFLAGS')
env.Depends(program_elf, rust)
if CMAKELISTS != 0:
env.Depends(program_elf, cmake_gen)

View File

@ -10,6 +10,7 @@ SConscript('SConscript.bootloader_emu', variant_dir='build/bootloader_emu', dupl
SConscript('SConscript.kernel', variant_dir='build/kernel', duplicate=False) SConscript('SConscript.kernel', variant_dir='build/kernel', duplicate=False)
SConscript('SConscript.firmware', variant_dir='build/firmware', duplicate=False) SConscript('SConscript.firmware', variant_dir='build/firmware', duplicate=False)
SConscript('SConscript.prodtest', variant_dir='build/prodtest', duplicate=False) SConscript('SConscript.prodtest', variant_dir='build/prodtest', duplicate=False)
SConscript('SConscript.prodtest_emu', variant_dir='build/prodtest_emu', duplicate=False)
SConscript('SConscript.reflash', variant_dir='build/reflash', duplicate=False) SConscript('SConscript.reflash', variant_dir='build/reflash', duplicate=False)
SConscript('SConscript.unix', variant_dir='build/unix', duplicate=False) SConscript('SConscript.unix', variant_dir='build/unix', duplicate=False)

View File

@ -21,6 +21,8 @@
#include <rtl/cli.h> #include <rtl/cli.h>
#include "commands.h"
static void prodtest_help(cli_t* cli) { static void prodtest_help(cli_t* cli) {
const char* prefix = cli_arg(cli, "prefix"); const char* prefix = cli_arg(cli, "prefix");
size_t prefix_len = strlen(prefix); size_t prefix_len = strlen(prefix);
@ -36,12 +38,8 @@ static void prodtest_help(cli_t* cli) {
cli_trace(cli, "Available commands (filtered):"); cli_trace(cli, "Available commands (filtered):");
} }
extern cli_command_t _prodtest_cli_cmd_section_start; const cli_command_t* cmd = commands_get_ptr();
extern cli_command_t _prodtest_cli_cmd_section_end; for (int i = 0; i < commands_count(); i++) {
cli_command_t* cmd = &_prodtest_cli_cmd_section_start;
while (cmd < &_prodtest_cli_cmd_section_end) {
if (cmd->name[0] != '$' && strncmp(cmd->name, prefix, prefix_len) == 0) { if (cmd->name[0] != '$' && strncmp(cmd->name, prefix, prefix_len) == 0) {
cli_trace(cli, " %s - %s", cmd->name, cmd->info); cli_trace(cli, " %s - %s", cmd->name, cmd->info);
} }

View File

@ -0,0 +1,55 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "commands.h"
#ifdef TREZOR_EMULATOR
#include <stdlib.h>
const cli_command_t **_cmd_list;
size_t _cmd_count;
void register_cli_command(const cli_command_t *cmd) {
_cmd_list = realloc(_cmd_list, sizeof(*_cmd_list) * (_cmd_count + 1));
_cmd_list[_cmd_count++] = cmd;
}
#endif
const cli_command_t *commands_get_ptr(void) {
#ifdef TREZOR_EMULATOR
return *_cmd_list;
#else
extern cli_command_t _prodtest_cli_cmd_section_start;
return &_prodtest_cli_cmd_section_start;
#endif
}
size_t commands_count(void) {
#ifdef TREZOR_EMULATOR
return _cmd_count;
#else
extern cli_command_t _prodtest_cli_cmd_section_start;
extern cli_command_t _prodtest_cli_cmd_section_end;
return &_prodtest_cli_cmd_section_end - &_prodtest_cli_cmd_section_start;
#endif
}

View File

@ -0,0 +1,28 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <trezor_types.h>
#include <rtl/cli.h>
const cli_command_t* commands_get_ptr(void);
size_t commands_count(void);

View File

@ -0,0 +1,73 @@
#include <trezor_model.h>
#include <trezor_rtl.h>
#include <unistd.h>
#include <SDL.h>
#include <io/display.h>
#include <util/flash.h>
#include <util/flash_otp.h>
#include <stdlib.h>
int prodtest_main(void);
void usage(void) {
printf("Usage: ./build/prodtest/prodtest_emu [options]\n");
printf(
"To connect via terminal, install socat (i.e. 'sudo apt-get install "
"socat' in Ubuntu)\n");
printf(
"Bind the UDP with 'socat -d -d "
"pty,link=/dev/ttyVCP0,mode=666,raw,echo=0 UDP:127.0.0.1:21424'\n");
printf("Then you can connect with you terminal to /dev/ttyVCP0\n");
printf(" -h show this help\n");
}
static int sdl_event_filter(void *userdata, SDL_Event *event) {
switch (event->type) {
case SDL_QUIT:
exit(3);
return 0;
case SDL_KEYUP:
if (event->key.repeat) {
return 0;
}
switch (event->key.keysym.sym) {
case SDLK_ESCAPE:
exit(3);
return 0;
case SDLK_p:
display_save("emu");
return 0;
}
break;
}
return 1;
}
int main(int argc, char **argv) {
SDL_SetEventFilter(sdl_event_filter, NULL);
display_init(DISPLAY_RESET_CONTENT);
flash_init();
flash_otp_init();
int opt;
while ((opt = getopt(argc, argv, "h")) != -1) {
switch (opt) {
default:
usage();
exit(1);
}
}
int exit_code = prodtest_main();
char msg[64];
snprintf(msg, sizeof(msg), "Exit code: %d", exit_code);
error_shutdown_ex("PRODTEST ERROR", msg, "UNEXPECTED EXIT");
return 0;
}

View File

@ -31,6 +31,7 @@
#include <util/rsod.h> #include <util/rsod.h>
#include <util/unit_properties.h> #include <util/unit_properties.h>
#include "commands.h"
#include "rust_types.h" #include "rust_types.h"
#include "rust_ui_prodtest.h" #include "rust_ui_prodtest.h"
#include "sys/sysevent.h" #include "sys/sysevent.h"
@ -133,6 +134,8 @@ static void vcp_intr(void) { cli_abort(&g_cli); }
#define VCP_PACKET_LEN 512 #define VCP_PACKET_LEN 512
#elif defined(USE_USB_FS) #elif defined(USE_USB_FS)
#define VCP_PACKET_LEN 64 #define VCP_PACKET_LEN 64
#elif defined(TREZOR_EMULATOR)
#define VCP_PACKET_LEN 64
#else #else
#error "USB type not defined" #error "USB type not defined"
#endif #endif
@ -171,9 +174,13 @@ static void usb_init_all(void) {
.rx_intr_byte = 3, // Ctrl-C .rx_intr_byte = 3, // Ctrl-C
.iface_num = VCP_IFACE, .iface_num = VCP_IFACE,
.data_iface_num = 0x01, .data_iface_num = 0x01,
#ifdef TREZOR_EMULATOR
.emu_port = 21424,
#else
.ep_cmd = 0x02, .ep_cmd = 0x02,
.ep_in = 0x01, .ep_in = 0x01,
.ep_out = 0x01, .ep_out = 0x01,
#endif
.polling_interval = 10, .polling_interval = 10,
.max_packet_len = VCP_PACKET_LEN, .max_packet_len = VCP_PACKET_LEN,
}; };
@ -254,7 +261,11 @@ void prodtest_show_homescreen(void) {
} }
} }
#ifndef TREZOR_EMULATOR
int main(void) { int main(void) {
#else
int prodtest_main(void) {
#endif
system_init(&rsod_panic_handler); system_init(&rsod_panic_handler);
drivers_init(); drivers_init();
@ -263,12 +274,7 @@ int main(void) {
// Initialize command line interface // Initialize command line interface
cli_init(&g_cli, console_read, console_write, NULL); cli_init(&g_cli, console_read, console_write, NULL);
extern cli_command_t _prodtest_cli_cmd_section_start; cli_set_commands(&g_cli, commands_get_ptr(), commands_count());
extern cli_command_t _prodtest_cli_cmd_section_end;
cli_set_commands(
&g_cli, &_prodtest_cli_cmd_section_start,
&_prodtest_cli_cmd_section_end - &_prodtest_cli_cmd_section_start);
#ifdef USE_OPTIGA #ifdef USE_OPTIGA
optiga_init(); optiga_init();

View File

@ -66,12 +66,23 @@ typedef struct {
#define CONCAT_INDIRECT(x, y) x##y #define CONCAT_INDIRECT(x, y) x##y
#define CONCAT(x, y) CONCAT_INDIRECT(x, y) #define CONCAT(x, y) CONCAT_INDIRECT(x, y)
#ifdef TREZOR_EMULATOR
#define PRODTEST_CLI_CMD(...) PRODTEST_CLI_CMD_IMPL(__COUNTER__, __VA_ARGS__)
#define PRODTEST_CLI_CMD_IMPL(cnt, ...) \
extern void register_cli_command(const cli_command_t* cmd); \
static const cli_command_t CONCAT(_cli_cmd_handler, cnt) = {__VA_ARGS__}; \
__attribute__((constructor)) static void CONCAT( \
__register, CONCAT(_cli_cmd_handler, cnt))(void) { \
register_cli_command(&CONCAT(_cli_cmd_handler, cnt)); \
}
#else
// Registers a command handler by placing its registration structure // Registers a command handler by placing its registration structure
// into a specially designated linker script section // into a specially designated linker script section
#define PRODTEST_CLI_CMD(...) \ #define PRODTEST_CLI_CMD(...) \
__attribute__((used, \ __attribute__((used, \
section(".prodtest_cli_cmd"))) static const cli_command_t \ section(".prodtest_cli_cmd"))) static const cli_command_t \
CONCAT(_cli_cmd_handler, __COUNTER__) = {__VA_ARGS__}; CONCAT(_cli_cmd_handler, __COUNTER__) = {__VA_ARGS__};
#endif
// Callback for writing characters to console output // Callback for writing characters to console output
typedef size_t (*cli_write_cb_t)(void* ctx, const char* buf, size_t len); typedef size_t (*cli_write_cb_t)(void* ctx, const char* buf, size_t len);