mirror of
https://github.com/polhenarejos/pico-keys-sdk.git
synced 2026-07-20 12:33:03 +01:00
Add plugin manager system.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user