diff --git a/cmake/plugin.cmake b/cmake/plugin.cmake new file mode 100644 index 0000000..204ec16 --- /dev/null +++ b/cmake/plugin.cmake @@ -0,0 +1,79 @@ +set(PICOKEYS_PLUGIN_SDK_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") + +function(picokeys_add_plugin TARGET) + set(oneValueArgs SOURCE LINKER OUTPUT_UF2) + cmake_parse_arguments(PK_PLUGIN "" "${oneValueArgs}" "" ${ARGN}) + + if(NOT PK_PLUGIN_SOURCE) + message(FATAL_ERROR "picokeys_add_plugin(${TARGET}) requires SOURCE") + endif() + if(NOT PK_PLUGIN_OUTPUT_UF2) + set(PK_PLUGIN_OUTPUT_UF2 "${TARGET}.uf2") + endif() + + if(ENABLE_EMULATION) + add_library(${TARGET} MODULE ${PK_PLUGIN_SOURCE}) + set_target_properties(${TARGET} PROPERTIES + PREFIX "" + WINDOWS_EXPORT_ALL_SYMBOLS ON + ) + if(NOT MSVC) + target_compile_options(${TARGET} PRIVATE + -fvisibility=default + ) + endif() + target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + PICOKEYS_EMULATION_PLUGIN_PATH="$" + ) + else() + if(NOT PK_PLUGIN_LINKER) + message(FATAL_ERROR "picokeys_add_plugin(${TARGET}) requires LINKER for non-emulation builds") + endif() + + add_executable(${TARGET} + ${PK_PLUGIN_SOURCE} + ) + + target_link_options(${TARGET} PRIVATE + "LINKER:--defsym=__picokeys_plugin_flash_base=${PICOKEYS_PLUGIN_FLASH_BASE}" + "LINKER:--defsym=__picokeys_plugin_flash_size=${PICOKEYS_PLUGIN_FLASH_SIZE}" + "LINKER:-T,${PK_PLUGIN_LINKER}" + "LINKER:-n" + "LINKER:--gc-sections" + "LINKER:--build-id=none" + -nostartfiles + -nostdlib + -nodefaultlibs + ) + + pico_set_uf2_family(${TARGET} 0xe48bff57) + pico_add_extra_outputs(${TARGET}) + + if(NOT PK_PLUGIN_OUTPUT_UF2 STREQUAL "${TARGET}.uf2") + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $/${TARGET}.uf2 + $/${PK_PLUGIN_OUTPUT_UF2} + ) + endif() + endif() + + target_include_directories(${TARGET} PRIVATE + ${PICOKEYS_PLUGIN_SDK_ROOT}/src/plugin + ) + + target_compile_options(${TARGET} PRIVATE + -Os + -ffunction-sections + -fdata-sections + ) + + target_compile_definitions(${TARGET} PRIVATE + PICOKEYS_PLUGIN_FLASH_BASE=${PICOKEYS_PLUGIN_FLASH_BASE} + PICOKEYS_PLUGIN_FLASH_SIZE=${PICOKEYS_PLUGIN_FLASH_SIZE} + ) +endfunction() + +function(picokeys_add_flash_plugin TARGET) + picokeys_add_plugin(${TARGET} ${ARGN}) +endfunction() diff --git a/config/rp2350/pt.json b/config/rp2350/pt.json index 48acfc5..c1b149b 100644 --- a/config/rp2350/pt.json +++ b/config/rp2350/pt.json @@ -24,13 +24,13 @@ { "name": "PicoKeys Data", "id": 1, - "start": "1032K", - "size": "3064K", + "start": "2096K", + "size": "2000K", "families": ["data"], "permissions": { "secure": "rw", "nonsecure": "", - "bootloader": "r" + "bootloader": "" }, "link": ["owner", 0], "ignored_during_arm_boot": true, @@ -50,6 +50,36 @@ "link": ["owner", 0], "ignored_during_arm_boot": true, "ignored_during_riscv_boot": true + }, + { + "name": "PicoKeys Static Region", + "id": 3, + "start": "1032K", + "size": "64K", + "families": ["data"], + "permissions": { + "secure": "rw", + "nonsecure": "", + "bootloader": "" + }, + "link": ["owner", 0], + "ignored_during_arm_boot": true, + "ignored_during_riscv_boot": true + }, + { + "name": "PicoKeys Plugin", + "id": 4, + "start": "1096K", + "size": "1000K", + "families": ["rp2350-arm-s"], + "permissions": { + "secure": "rw", + "nonsecure": "", + "bootloader": "w" + }, + "link": ["owner", 0], + "ignored_during_arm_boot": true, + "ignored_during_riscv_boot": true } ] } diff --git a/picokeys_sdk_import.cmake b/picokeys_sdk_import.cmake index 3c87e21..1c33f43 100644 --- a/picokeys_sdk_import.cmake +++ b/picokeys_sdk_import.cmake @@ -115,6 +115,13 @@ else() add_compile_definitions(CFG_TUSB_CONFIG_FILE="${CMAKE_CURRENT_LIST_DIR}/src/usb/tusb_config.h") endif() +if(PICO_RP2350 OR ENABLE_EMULATION) + set(PICOKEYS_PLUGIN_FLASH_BASE "0x10112000" CACHE STRING "XIP address where the premium plugin UF2 starts" FORCE) + set(PICOKEYS_PLUGIN_FLASH_SIZE "0x000fa000" CACHE STRING "Maximum premium plugin size in bytes" FORCE) + set(PICOKEYS_PLUGIN_DIR "" CACHE PATH "Optional external PicoKeys plugin source directory") + include(${CMAKE_CURRENT_LIST_DIR}/cmake/plugin.cmake) +endif() + message(STATUS "USB VID/PID:\t\t\t ${USB_VID}:${USB_PID}") include(${CMAKE_CURRENT_LIST_DIR}/cmake/deps.cmake) @@ -283,6 +290,9 @@ if(ESP_PLATFORM) ${CMAKE_CURRENT_LIST_DIR}/src/otp/otp_esp32.c ) elseif(ENABLE_EMULATION) + list(APPEND PICOKEYS_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/plugin/plugin_loader.c + ) if(MSVC) list(APPEND PICOKEYS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/otp/otp_windows.c @@ -304,6 +314,7 @@ elseif(ENABLE_EMULATION) elseif(PICO_RP2350) list(APPEND PICOKEYS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/otp/otp_rp2350.c + ${CMAKE_CURRENT_LIST_DIR}/src/plugin/plugin_loader.c ) elseif(PICO_RP2040) list(APPEND PICOKEYS_SOURCES @@ -364,6 +375,7 @@ list(APPEND INCLUDES ${CMAKE_CURRENT_LIST_DIR}/src/rng ${CMAKE_CURRENT_LIST_DIR}/src/led ${CMAKE_CURRENT_LIST_DIR}/src/otp + ${CMAKE_CURRENT_LIST_DIR}/src/plugin ${CMAKE_CURRENT_LIST_DIR}/third-party/mbedtls/library ) set(SYSTEM_INCLUDES @@ -660,9 +672,31 @@ if(USB_ITF_LWIP) endif() endif() +if(ENABLE_EMULATION) + list(APPEND LIBRARIES ${CMAKE_DL_LIBS}) + + if(PICOKEYS_PLUGIN_DIR) + get_filename_component(PICOKEYS_PLUGIN_DIR_ABS "${PICOKEYS_PLUGIN_DIR}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + if(NOT EXISTS "${PICOKEYS_PLUGIN_DIR_ABS}/CMakeLists.txt") + message(FATAL_ERROR "PICOKEYS_PLUGIN_DIR does not contain CMakeLists.txt: ${PICOKEYS_PLUGIN_DIR_ABS}") + endif() + add_subdirectory("${PICOKEYS_PLUGIN_DIR_ABS}" "${CMAKE_BINARY_DIR}/picokeys_plugin_manual") + elseif(EXISTS "${CMAKE_SOURCE_DIR}/plugin/CMakeLists.txt") + add_subdirectory("${CMAKE_SOURCE_DIR}/plugin" "${CMAKE_BINARY_DIR}/picokeys_plugin") + endif() +endif() + if(PICO_RP2350) pico_set_uf2_family(${CMAKE_PROJECT_NAME} "rp2350-arm-s") pico_embed_pt_in_binary(${CMAKE_PROJECT_NAME} "${CMAKE_CURRENT_LIST_DIR}/config/rp2350/pt.json") + target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + PICOKEYS_PLUGIN_FLASH_BASE=${PICOKEYS_PLUGIN_FLASH_BASE} + PICOKEYS_PLUGIN_FLASH_SIZE=${PICOKEYS_PLUGIN_FLASH_SIZE} + ) + target_link_options(${CMAKE_PROJECT_NAME} PRIVATE + "LINKER:--defsym=__picokeys_plugin_flash_base=${PICOKEYS_PLUGIN_FLASH_BASE}" + "LINKER:-T,${CMAKE_CURRENT_LIST_DIR}/src/plugin/core_plugin_region_assert.ld" + ) if(NOT IS_CYW43) pico_set_binary_type(${CMAKE_PROJECT_NAME} copy_to_ram) endif() @@ -687,6 +721,16 @@ if(PICO_RP2350) ) add_compile_definitions(MBEDTLS_SHA256_ALT=1) list(APPEND LIBRARIES pico_sha256) + + if(NOT ENABLE_EMULATION AND PICOKEYS_PLUGIN_DIR) + get_filename_component(PICOKEYS_PLUGIN_DIR_ABS "${PICOKEYS_PLUGIN_DIR}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + if(NOT EXISTS "${PICOKEYS_PLUGIN_DIR_ABS}/CMakeLists.txt") + message(FATAL_ERROR "PICOKEYS_PLUGIN_DIR does not contain CMakeLists.txt: ${PICOKEYS_PLUGIN_DIR_ABS}") + endif() + add_subdirectory("${PICOKEYS_PLUGIN_DIR_ABS}" "${CMAKE_BINARY_DIR}/picokeys_plugin_manual") + elseif(NOT ENABLE_EMULATION AND EXISTS "${CMAKE_SOURCE_DIR}/plugin/CMakeLists.txt") + add_subdirectory("${CMAKE_SOURCE_DIR}/plugin" "${CMAKE_BINARY_DIR}/picokeys_plugin") + endif() endif() set(INTERNAL_SOURCES ${PICOKEYS_SOURCES}) diff --git a/src/main.c b/src/main.c index 1064171..bd9daf2 100644 --- a/src/main.c +++ b/src/main.c @@ -102,6 +102,11 @@ WEAK int picokey_init(void) { return 0; } + +#if defined(PICO_RP2350) || defined(ENABLE_EMULATION) +void plugin_init(void); +#endif + void execute_tasks(void); void execute_tasks(void) { #if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) @@ -214,6 +219,9 @@ int main(void) { #ifndef ENABLE_EMULATION picokey_init(); #endif +#if defined(PICO_RP2350) || defined(ENABLE_EMULATION) + plugin_init(); +#endif #ifdef ESP_PLATFORM xTaskCreatePinnedToCore(core0_loop, "core0", 4096*ITF_TOTAL*2, NULL, CONFIG_TINYUSB_TASK_PRIORITY - 1, &hcore0, ESP32_CORE0); diff --git a/src/plugin/core_plugin_region_assert.ld b/src/plugin/core_plugin_region_assert.ld new file mode 100644 index 0000000..a7a6548 --- /dev/null +++ b/src/plugin/core_plugin_region_assert.ld @@ -0,0 +1,2 @@ +ASSERT(!DEFINED(__picokeys_plugin_flash_base) || __flash_binary_end <= __picokeys_plugin_flash_base, + "pico_fido flash image overlaps the premium plugin region") diff --git a/src/plugin/picokeys_plugin_api.h b/src/plugin/picokeys_plugin_api.h new file mode 100644 index 0000000..6e41ff5 --- /dev/null +++ b/src/plugin/picokeys_plugin_api.h @@ -0,0 +1,64 @@ +/* + * This file is part of the Pico Keys SDK distribution (https://github.com/polhenarejos/pico-keys-sdk). + * Copyright (c) 2026 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, version 3. + */ + +#ifndef PICOKEYS_PLUGIN_API_H +#define PICOKEYS_PLUGIN_API_H + +#include + +#if defined(_WIN32) +#define PICOKEYS_PLUGIN_EXPORT __declspec(dllexport) +#elif defined(__GNUC__) || defined(__clang__) +#define PICOKEYS_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define PICOKEYS_PLUGIN_EXPORT +#endif + +#if defined(ENABLE_EMULATION) +#if defined(__GNUC__) || defined(__clang__) +#define PICOKEYS_PLUGIN_HEADER_ATTR __attribute__((used)) +#else +#define PICOKEYS_PLUGIN_HEADER_ATTR +#endif +#elif defined(__GNUC__) || defined(__clang__) +#define PICOKEYS_PLUGIN_HEADER_ATTR __attribute__((used, section(".plugin_header"))) +#else +#define PICOKEYS_PLUGIN_HEADER_ATTR +#endif + +#define PICOKEYS_PLUGIN_MAGIC 0x504b504cUL /* PKPL */ +#define PICOKEYS_PLUGIN_ABI_VERSION 1u + +#ifndef PICOKEYS_PLUGIN_FLASH_BASE +#define PICOKEYS_PLUGIN_FLASH_BASE 0x10112000UL +#endif + +#ifndef PICOKEYS_PLUGIN_FLASH_SIZE +#define PICOKEYS_PLUGIN_FLASH_SIZE 0x000fa000UL +#endif + +typedef struct pk_plugin_imports { + uint32_t abi_version; + uint32_t struct_size; + int (*printf)(const char *fmt, ...); +} pk_plugin_imports_t; + +typedef void (*pk_plugin_hello_fn)(const pk_plugin_imports_t *imports); + +typedef struct pk_plugin_header { + uint32_t magic; + uint32_t abi_version; + uint32_t header_size; + uint32_t image_size; + pk_plugin_hello_fn hello; +} pk_plugin_header_t; + +void plugin_init(void); + +#endif diff --git a/src/plugin/plugin_loader.c b/src/plugin/plugin_loader.c new file mode 100644 index 0000000..c655478 --- /dev/null +++ b/src/plugin/plugin_loader.c @@ -0,0 +1,152 @@ +/* + * This file is part of the Pico Keys SDK distribution (https://github.com/polhenarejos/pico-keys-sdk). + * Copyright (c) 2026 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, version 3. + */ + +#include "picokeys_plugin_api.h" + +#include +#include + +#ifdef ENABLE_EMULATION +#ifdef _WIN32 +#include +#else +#include +#endif +#include + +#ifndef PICOKEYS_EMULATION_PLUGIN_PATH +#define PICOKEYS_EMULATION_PLUGIN_PATH "" +#endif + +static const pk_plugin_header_t *pk_plugin_get_valid(void) { + const char *path = getenv("PICOKEYS_PLUGIN_PATH"); + if (!path || !path[0]) { + path = PICOKEYS_EMULATION_PLUGIN_PATH; + } + if (!path || !path[0]) { + printf("No plugin path configured\n"); + return NULL; + } + +#ifdef _WIN32 + HMODULE handle = LoadLibraryA(path); + if (!handle) { + printf("Plugin LoadLibrary failed: %lu\n", (unsigned long)GetLastError()); + return NULL; + } + + const pk_plugin_header_t *plugin = (const pk_plugin_header_t *)GetProcAddress(handle, "pk_plugin"); + if (!plugin) { + printf("Plugin GetProcAddress failed: %lu\n", (unsigned long)GetLastError()); + return NULL; + } +#else + void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (!handle) { + printf("Plugin dlopen failed: %s\n", dlerror()); + return NULL; + } + + dlerror(); + const pk_plugin_header_t *plugin = (const pk_plugin_header_t *)dlsym(handle, "pk_plugin"); + const char *err = dlerror(); + if (err) { + printf("Plugin dlsym failed: %s\n", err); + return NULL; + } +#endif + + printf("Plugin loaded from %s: magic=0x%08lx abi=%lu header=%lu image=%lu hello=%p\n", + path, + (unsigned long)plugin->magic, + (unsigned long)plugin->abi_version, + (unsigned long)plugin->header_size, + (unsigned long)plugin->image_size, + (void *)plugin->hello); + if (plugin->magic != PICOKEYS_PLUGIN_MAGIC) { + printf("Plugin rejected: bad magic\n"); + return NULL; + } + if (plugin->abi_version != PICOKEYS_PLUGIN_ABI_VERSION) { + printf("Plugin rejected: bad ABI\n"); + return NULL; + } + if (plugin->header_size < sizeof(*plugin)) { + printf("Plugin rejected: short header\n"); + return NULL; + } + if (!plugin->hello) { + printf("Plugin rejected: missing entry point\n"); + return NULL; + } + return plugin; +} +#else +static const pk_plugin_header_t *pk_plugin_header(void) { + return (const pk_plugin_header_t *)(uintptr_t)PICOKEYS_PLUGIN_FLASH_BASE; +} + +static int pk_plugin_ptr_in_range(const void *ptr) { + uintptr_t addr = (uintptr_t)ptr; + addr &= ~(uintptr_t)1u; + return addr >= (uintptr_t)PICOKEYS_PLUGIN_FLASH_BASE && + addr < ((uintptr_t)PICOKEYS_PLUGIN_FLASH_BASE + (uintptr_t)PICOKEYS_PLUGIN_FLASH_SIZE); +} + +static const pk_plugin_header_t *pk_plugin_get_valid(void) { + const pk_plugin_header_t *plugin = pk_plugin_header(); + + printf("Plugin header @ 0x%08lx: magic=0x%08lx abi=%lu header=%lu image=%lu hello=0x%08lx\n", + (unsigned long)PICOKEYS_PLUGIN_FLASH_BASE, + (unsigned long)plugin->magic, + (unsigned long)plugin->abi_version, + (unsigned long)plugin->header_size, + (unsigned long)plugin->image_size, + (unsigned long)(uintptr_t)plugin->hello); + if (plugin->magic != PICOKEYS_PLUGIN_MAGIC) { + printf("Plugin rejected: bad magic\n"); + return NULL; + } + if (plugin->abi_version != PICOKEYS_PLUGIN_ABI_VERSION) { + printf("Plugin rejected: bad ABI\n"); + return NULL; + } + if (plugin->header_size < sizeof(*plugin)) { + printf("Plugin rejected: short header\n"); + return NULL; + } + if (plugin->image_size != 0u && + (plugin->image_size < plugin->header_size || plugin->image_size > PICOKEYS_PLUGIN_FLASH_SIZE)) { + printf("Plugin rejected: bad image size\n"); + return NULL; + } + if (!plugin->hello || !pk_plugin_ptr_in_range((const void *)plugin->hello)) { + printf("Plugin rejected: hello pointer out of range\n"); + return NULL; + } + return plugin; +} +#endif + +void plugin_init(void) { + const pk_plugin_header_t *plugin = pk_plugin_get_valid(); + if (!plugin) { + printf("No valid plugin found\n"); + return; + } + + const pk_plugin_imports_t imports = { + .abi_version = PICOKEYS_PLUGIN_ABI_VERSION, + .struct_size = sizeof(imports), + .printf = printf, + }; + printf("Calling plugin hello function\n"); + plugin->hello(&imports); + printf("Plugin hello function returned\n"); +}