Add plugin manager system.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2026-06-30 11:53:18 +02:00
parent 76601bced4
commit d811b2b233
7 changed files with 382 additions and 3 deletions
+79
View File
@@ -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="$<TARGET_FILE:${TARGET}>"
)
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_FILE_DIR:${TARGET}>/${TARGET}.uf2
$<TARGET_FILE_DIR:${TARGET}>/${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()
+33 -3
View File
@@ -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
}
]
}
+44
View File
@@ -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})
+8
View File
@@ -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);
+2
View File
@@ -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")
+64
View File
@@ -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 <stdint.h>
#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
+152
View File
@@ -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 <stddef.h>
#include <stdio.h>
#ifdef ENABLE_EMULATION
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <stdlib.h>
#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");
}