From 0c1c41be525838a654a0be8d508c330cbba8a548 Mon Sep 17 00:00:00 2001 From: Natchanon Nuntanirund Date: Mon, 2 Sep 2024 11:56:39 +0700 Subject: [PATCH] Port to RP2350 --- .gitignore | 4 +- .gitmodules | 2 +- .vscode/c_cpp_properties.json | 38 ++- .vscode/launch.json | 52 +++- 3rdparty/FreeRTOS | 2 +- 3rdparty/pico-sdk | 2 +- BENCHMARK.md | 32 +-- CMakeLists.txt | 30 ++- README.md | 98 +++++--- include/httpd.h | 14 +- include/isere.h | 7 +- include/js.h | 10 +- include/polyfills.h | 12 +- include/tcp.h | 2 +- {examples => js}/handler.js | 0 portable/include/platform.h | 6 +- portable/linux/include/isere_config.h | 12 + portable/linux/platform_features.cmake | 2 +- portable/linux/src/tcp.c | 64 +++-- portable/{rp2040 => pico}/README.md | 4 +- .../{rp2040 => pico}/include/FreeRTOSConfig.h | 0 portable/{rp2040 => pico}/include/arch/cc.h | 0 portable/{rp2040 => pico}/include/lwipopts.h | 0 .../{rp2040 => pico}/include/tusb_config.h | 0 .../{rp2040 => pico}/include/tusb_lwip_glue.h | 0 .../{rp2040 => pico}/platform_extras.cmake | 2 + .../{rp2040 => pico}/platform_features.cmake | 2 +- .../{rp2040 => pico}/platform_sources.cmake | 0 portable/{rp2040 => pico}/src/fs.c | 0 portable/{rp2040 => pico}/src/loader.c | 0 portable/{rp2040 => pico}/src/logger.c | 0 portable/{rp2040 => pico}/src/platform.c | 3 + portable/{rp2040 => pico}/src/rtc.c | 0 portable/{rp2040 => pico}/src/tcp.c | 2 +- .../{rp2040 => pico}/src/tusb_lwip_glue.c | 2 +- .../{rp2040 => pico}/src/usb_descriptor.c | 6 +- portable/pico2/README.md | 39 +++ portable/pico2/include/FreeRTOSConfig.h | 141 +++++++++++ portable/pico2/include/arch/cc.h | 80 ++++++ portable/pico2/include/isere_config.h | 10 + portable/pico2/include/lwipopts.h | 128 ++++++++++ portable/pico2/include/tusb_config.h | 124 ++++++++++ portable/pico2/include/tusb_lwip_glue.h | 25 ++ portable/pico2/platform_extras.cmake | 27 ++ portable/pico2/platform_features.cmake | 6 + portable/pico2/platform_sources.cmake | 16 ++ portable/pico2/src/fs.c | 128 ++++++++++ portable/pico2/src/loader.c | 39 +++ portable/pico2/src/logger.c | 78 ++++++ portable/pico2/src/platform.c | 29 +++ portable/pico2/src/rtc.c | 34 +++ portable/pico2/src/tcp.c | 233 +++++++++++++++++ portable/pico2/src/tusb_lwip_glue.c | 195 +++++++++++++++ portable/pico2/src/usb_descriptor.c | 234 ++++++++++++++++++ src/http_handler.c | 52 ++-- src/httpd.c | 77 +++--- src/js.c | 133 +++++----- src/main.c | 20 +- src/polyfills/fetch.c | 20 +- src/polyfills/timer.c | 40 +-- unittests.cmake => tests/CMakeLists.txt | 4 +- tests/{handler.js => handler_for_test.js} | 0 tests/js_test.cpp | 54 +++- 63 files changed, 2072 insertions(+), 304 deletions(-) rename {examples => js}/handler.js (100%) create mode 100644 portable/linux/include/isere_config.h rename portable/{rp2040 => pico}/README.md (90%) rename portable/{rp2040 => pico}/include/FreeRTOSConfig.h (100%) rename portable/{rp2040 => pico}/include/arch/cc.h (100%) rename portable/{rp2040 => pico}/include/lwipopts.h (100%) rename portable/{rp2040 => pico}/include/tusb_config.h (100%) rename portable/{rp2040 => pico}/include/tusb_lwip_glue.h (100%) rename portable/{rp2040 => pico}/platform_extras.cmake (90%) rename portable/{rp2040 => pico}/platform_features.cmake (58%) rename portable/{rp2040 => pico}/platform_sources.cmake (100%) rename portable/{rp2040 => pico}/src/fs.c (100%) rename portable/{rp2040 => pico}/src/loader.c (100%) rename portable/{rp2040 => pico}/src/logger.c (100%) rename portable/{rp2040 => pico}/src/platform.c (91%) rename portable/{rp2040 => pico}/src/rtc.c (100%) rename portable/{rp2040 => pico}/src/tcp.c (98%) rename portable/{rp2040 => pico}/src/tusb_lwip_glue.c (98%) rename portable/{rp2040 => pico}/src/usb_descriptor.c (99%) create mode 100644 portable/pico2/README.md create mode 100644 portable/pico2/include/FreeRTOSConfig.h create mode 100644 portable/pico2/include/arch/cc.h create mode 100644 portable/pico2/include/isere_config.h create mode 100644 portable/pico2/include/lwipopts.h create mode 100644 portable/pico2/include/tusb_config.h create mode 100644 portable/pico2/include/tusb_lwip_glue.h create mode 100644 portable/pico2/platform_extras.cmake create mode 100644 portable/pico2/platform_features.cmake create mode 100644 portable/pico2/platform_sources.cmake create mode 100644 portable/pico2/src/fs.c create mode 100644 portable/pico2/src/loader.c create mode 100644 portable/pico2/src/logger.c create mode 100644 portable/pico2/src/platform.c create mode 100644 portable/pico2/src/rtc.c create mode 100644 portable/pico2/src/tcp.c create mode 100644 portable/pico2/src/tusb_lwip_glue.c create mode 100644 portable/pico2/src/usb_descriptor.c rename unittests.cmake => tests/CMakeLists.txt (93%) rename tests/{handler.js => handler_for_test.js} (100%) diff --git a/.gitignore b/.gitignore index c73c0ad..7ccb1b5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ .DS_Store *.so build/ -examples/*.c -examples/*.c.bak +js/*.c +js/*.c.bak tests/js/*.c tests/js/*.c.bak isere diff --git a/.gitmodules b/.gitmodules index acca977..bd03bd6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "3rdparty/FreeRTOS"] path = 3rdparty/FreeRTOS - url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git + url = https://github.com/raspberrypi/FreeRTOS-Kernel.git [submodule "3rdparty/quickjs"] path = 3rdparty/quickjs url = https://github.com/jeeyo/quickjs.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 3f99c48..49861b6 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "POSIX", - "intelliSenseMode": "gcc-arm64", + "intelliSenseMode": "gcc-x64", "includePath": [ "${workspaceFolder}/include", "${workspaceFolder}/portable/include", @@ -30,23 +30,45 @@ }, { "name": "RP2040", - "intelliSenseMode": "gcc-arm64", + "intelliSenseMode": "gcc-x64", "includePath": [ - "/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/include", "${workspaceFolder}/include", "${workspaceFolder}/portable/include", - "${workspaceFolder}/portable/rp2040/include", + "${workspaceFolder}/portable/pico/include", "${workspaceFolder}/schemas", "${workspaceFolder}/3rdparty/FreeRTOS/include", - "${workspaceFolder}/3rdparty/FreeRTOS/portable/ThirdParty/GCC/Posix", - "${workspaceFolder}/3rdparty/FreeRTOS/portable/ThirdParty/GCC/Posix/utils", + "${workspaceFolder}/3rdparty/FreeRTOS/portable/ThirdParty/GCC/RP2040/include", + "${workspaceFolder}/3rdparty/quickjs", + "${workspaceFolder}/3rdparty/llhttp/include", + "${workspaceFolder}/3rdparty/libyuarel", + "${workspaceFolder}/3rdparty/c-capnproto/lib" + ], + "cStandard": "c99", + "cppStandard": "c++17", + "browse": { + "path": [ + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + }, + "configurationProvider": "ms-vscode.cpptools" + }, + { + "name": "RP2350", + "intelliSenseMode": "gcc-x64", + "includePath": [ + "${workspaceFolder}/include", + "${workspaceFolder}/portable/include", + "${workspaceFolder}/portable/pico2/include", + "${workspaceFolder}/schemas", + "${workspaceFolder}/3rdparty/FreeRTOS/include", + "${workspaceFolder}/3rdparty/FreeRTOS/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure", "${workspaceFolder}/3rdparty/quickjs", "${workspaceFolder}/3rdparty/llhttp/include", "${workspaceFolder}/3rdparty/libyuarel", - "${workspaceFolder}/3rdparty/cpputest/include", "${workspaceFolder}/3rdparty/c-capnproto/lib" ], - "compilerPath": "/usr/bin/gcc", "cStandard": "c99", "cppStandard": "c++17", "browse": { diff --git a/.vscode/launch.json b/.vscode/launch.json index 25b16e4..f5874db 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,9 +12,15 @@ "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}/build", - "environment": [], + "environment": [ + { + "name": "LD_LIBRARY_PATH", + "value": "${workspaceFolder}/build" + } + ], "externalConsole": false, - "MIMode": "lldb" + "miDebuggerPath": "/usr/bin/gdb", + "MIMode": "gdb", }, { "name": "Pico Debug", @@ -23,12 +29,9 @@ "request": "launch", "type": "cortex-debug", "servertype": "openocd", - // This may need to be "arm-none-eabi-gdb" for some previous builds - // "gdbPath" : "gdb-multiarch", "gdbPath": "arm-none-eabi-gdb", "device": "RP2040", "configFiles": [ - // This may need to be "interface/picoprobe.cfg" for some previous builds "interface/cmsis-dap.cfg", "target/rp2040.cfg" ], @@ -39,7 +42,42 @@ "break main", "continue" ], - "openOCDLaunchCommands": ["adapter speed 5000"] + "openOCDLaunchCommands": [ + "adapter speed 5000" + ], + "postLaunchCommands": [ + "monitor arm semihosting enable", + "monitor arm semihosting ioclient 2", // 1: telnet (port 2333); 2: gdb; 3: both telnet and gdbclient output + ] + }, + { + "name": "Pico 2 Debug", + "cwd": "${workspaceRoot}", + "executable": "${workspaceFolder}/build/isere.elf", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "gdbPath": "arm-none-eabi-gdb", + "device": "RP2350", + "configFiles": [ + "interface/cmsis-dap.cfg", + "target/rp2350.cfg" + ], + "svdFile": "${workspaceFolder}/3rdparty/pico-sdk/src/rp2350/hardware_regs/RP2350.svd", + "runToEntryPoint": "main", + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ], + "openOCDLaunchCommands": [ + "bindto 0.0.0.0", + "adapter speed 5000" + ], + "postLaunchCommands": [ + "monitor arm semihosting enable", + "monitor arm semihosting ioclient 2", // 1: telnet (port 2333); 2: gdb; 3: both telnet and gdbclient output + ] } ] -} \ No newline at end of file +} diff --git a/3rdparty/FreeRTOS b/3rdparty/FreeRTOS index fffed5e..4f7299d 160000 --- a/3rdparty/FreeRTOS +++ b/3rdparty/FreeRTOS @@ -1 +1 @@ -Subproject commit fffed5e8096b1f56e97ac7ab27392a7920e6d431 +Subproject commit 4f7299d6ea746b27a9dd19e87af568e34bd65b15 diff --git a/3rdparty/pico-sdk b/3rdparty/pico-sdk index 6a7db34..efe2103 160000 --- a/3rdparty/pico-sdk +++ b/3rdparty/pico-sdk @@ -1 +1 @@ -Subproject commit 6a7db34ff63345a7badec79ebea3aaef1712f374 +Subproject commit efe2103f9b28458a1615ff096054479743ade236 diff --git a/BENCHMARK.md b/BENCHMARK.md index 585343e..5dd3e3f 100644 --- a/BENCHMARK.md +++ b/BENCHMARK.md @@ -15,22 +15,7 @@ Requests/sec: 85861.04 Transfer/sec: 14.08MB ``` -### isère - -##### without QuickJS - -``` -$ wrk -t12 -c400 -d10s http://127.0.0.1:8080 -Running 10s test @ http://127.0.0.1:8080 - 12 threads and 400 connections - Thread Stats Avg Stdev Max +/- Stdev - Latency 2.73ms 20.49ms 439.61ms 99.78% - Req/Sec 428.20 429.25 1.60k 80.27% - 6367 requests in 10.09s, 118.14KB read - Socket errors: connect 155, read 12230, write 21, timeout 0 -Requests/sec: 631.03 -Transfer/sec: 11.71KB -``` +### isère on Apple M1 ##### with QuickJS @@ -46,3 +31,18 @@ Running 10s test @ http://127.0.0.1:8080 Requests/sec: 77.85 Transfer/sec: 5.32KB ``` + +### isère on Raspberry Pi Pico 2 + +``` +$ wrk -t12 -c400 -d10s http://192.168.7.1:8080 +Running 10s test @ http://192.168.7.1:8080 + 12 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.19s 239.32ms 1.88s 72.73% + Req/Sec 0.73 0.83 3.00 86.36% + 22 requests in 10.08s, 770.00B read + Socket errors: connect 0, read 1321, write 0, timeout 0 +Requests/sec: 2.18 +Transfer/sec: 76.40B +``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 8eb606f..8ccaeb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,12 @@ cmake_minimum_required(VERSION 3.5) set(TARGET_PLATFORM linux CACHE STRING "Platform to compile isere for") +# build configuration option(DEBUG "Debug mode" OFF) -option(NEED_LWIP "Include LwIP" OFF) +option(WITH_QUICKJS "Include QuickJS" ON) + +# platform-specific configuration +option(WITH_LWIP "Include LwIP" OFF) option(SUPPORT_DYNLINK "Platform supports Dynamic Linking" OFF) include(portable/${TARGET_PLATFORM}/platform_features.cmake) @@ -17,7 +21,7 @@ set(LWIP_DIR ./3rdparty/lwip) set(LLHTTP_DIR ./3rdparty/llhttp) set(LIBYUAREL_DIR ./3rdparty/libyuarel) set(CAPNPROTO_DIR ./3rdparty/c-capnproto) -set(EXAMPLES_DIR ./examples) +set(JS_DIR ./js) set(SOURCES ) @@ -28,7 +32,6 @@ list(APPEND SOURCES ./src/ini.c ./src/js.c ./src/quickjs-libc.c - # ./src/rb.c ./src/polyfills/fetch.c ./src/polyfills/timer.c ./src/polyfills/pvPortRealloc.c @@ -63,7 +66,7 @@ list(APPEND SOURCES include(portable/${TARGET_PLATFORM}/platform_sources.cmake OPTIONAL) -if(NEED_LWIP) +if(WITH_LWIP) set(LWIP_INCLUDE_DIRS ${LWIP_DIR}/src/include ./portable/${TARGET_PLATFORM}/include @@ -84,11 +87,11 @@ endif() add_custom_command( OUTPUT handler.c - COMMAND xxd -i ../${EXAMPLES_DIR}/handler.js handler.c + COMMAND xxd -i ../${JS_DIR}/handler.js handler.c COMMAND sed -i.bak -E "s/[a-z_]+\\[\\]/handler\\[\\]/" handler.c # change code array variable name to "handler" COMMAND sed -i.bak -E "s/[a-z_]+_len/handler_len/" handler.c # change code length variable name to "handler_len" COMMENT "Compiling JavaScript files" - MAIN_DEPENDENCY ../${EXAMPLES_DIR}/handler.js + MAIN_DEPENDENCY ../${JS_DIR}/handler.js VERBATIM ) @@ -103,9 +106,14 @@ execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD OUTPUT_VARIABLE target_compile_definitions(isere PRIVATE _GNU_SOURCE CONFIG_BIGNUM EMSCRIPTEN CONFIG_VERSION="${COMMIT_ID}") if(DEBUG) - target_compile_options(isere PRIVATE -O0 -g -pg) - target_compile_definitions(isere PRIVATE DUMP_LEAKS) - # target_compile_definitions(isere PRIVATE DUMP_LEAKS DUMP_MEM) + target_compile_options(isere PRIVATE -O0 -g) + + # enable profiling on Linux + if(TARGET_PLATFORM STREQUAL "linux") + # target_compile_definitions(isere PRIVATE DUMP_LEAKS DUMP_MEM) + target_compile_options(isere PRIVATE -pg) + target_link_options(isere PRIVATE -pg) + endif() endif() include(portable/${TARGET_PLATFORM}/platform_extras.cmake OPTIONAL) @@ -122,7 +130,7 @@ target_include_directories(isere PRIVATE ${CAPNPROTO_DIR}/lib ) -if(NEED_LWIP) +if(WITH_LWIP) target_include_directories(isere PRIVATE ${LWIP_DIR}/src/include ) @@ -140,5 +148,5 @@ add_custom_command( ) if(TARGET_PLATFORM STREQUAL "linux") - include(unittests.cmake) + include(./tests/CMakeLists.txt) endif() diff --git a/README.md b/README.md index 8ad30c3..431905f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # isère -isère [(/iːˈzɛər/)](https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=fr&q=Isère) +![workflow](https://github.com/jeeyo/isere/actions/workflows/ci.yml/badge.svg) -A serverless platform aimed to be running on Microcontrollers - -The goal is to create a low-power serverless platform that can handle simple stateless request like logging in which will require accessing to database and creating JWT token +A serverless platform aimed to be running on Microcontrollers, powered by FreeRTOS, LwIP, and QuickJS ### Current progress - [x] FreeRTOS as Kernel -- [x] QuickJS as JavaScript runtime +- [ ] JavaScript runtime + - [x] QuickJS + - [ ] JerryScript (for memory-constrained port) +- [ ] Python runtime (?) + - [ ] MicroPython - [x] HTTP server - [x] Event Loop (no Keep-Alive support) - [x] Socket @@ -22,39 +24,40 @@ The goal is to create a low-power serverless platform that can handle simple sta - [ ] http handler - [ ] logger - [x] Unit tests on CI -- [x] File System Abstraction -- [x] Configuration File -- [ ] Watchdog -- [ ] Integration tests (Native / QEMU) +- [ ] File System +- [ ] Configuration File +- [ ] Watchdog timer +- [ ] Integration tests - [ ] Integration tests on CI - [ ] APIs (ref. [Minimum Common Web Platform API](https://common-min-api.proposal.wintercg.org/)) - [ ] buffer - [ ] crypto - [ ] events - [ ] path (?) + - [ ] fs - [ ] fetch - [ ] base64 - - [x] setTimeout (FreeRTOS Timer) + - [x] setTimeout / clearTimeout (FreeRTOS Timer) - [ ] Memory Leak Check - [ ] Valgrind -- [ ] gprof profiling +- [x] gprof profiling - [ ] Optimize libc usage - [ ] Use less printf() -- [ ] QuickJS Project Template +- [ ] Project Template - [ ] Low-power mode - [x] Benchmark -- [x] Port to Raspberry Pi Pico (RP2040) - - [x] Multi-core support +- [ ] Port + - [x] Raspberry Pi Pico (RP2040) + - [ ] Raspberry Pi Pico 2 (RP2350) + - [ ] ESP32? - [ ] Monitoring - [ ] CPU Usage ([vTaskGetRunTimeStats](https://www.freertos.org/rtos-run-time-stats.html)) - [ ] Memory Usage ([vPortGetHeapStats](https://www.freertos.org/a00111.html)) -- [ ] Port to ESP32? -- [x] MicroPython (will not do ❌) ### Limitations - No Keep-Alive support -- JavaScript handler function needs to be stored sequentially and addressible in a Flash memory +- JavaScript handler function needs to be stored sequentially and addressible in a memory - File system is for storing static files and configuration files ### Building and Running @@ -64,34 +67,71 @@ Prerequisites: - make - gcc - cpputest -- xxd (libtool) +- xxd - ninja (optional for building c-capnproto) -```sh -# install dependencies -brew install gcc cmake make libtool cpputest ninja -# or -sudo apt install -y build-essential make cmake xxd cpputest ninja-build +### Install dependencies + +#### macOS + +```zsh +brew install gcc cmake make libtool ninja +``` + +If you want to build unit tests, you also need to install CppUTest + +```zsh +brew install cpputest +export CPPUTEST_HOME=/opt/homebrew/Cellar/cpputest/4.0/ +``` + +#### Debian-based Linux + +```bash +sudo apt-get install -y build-essential make cmake xxd ninja-build +``` + +For installing CppUTest, please follow [Using CppUTest with MakefileWorker.mk and gcc](https://cpputest.github.io/) section on CppUTest website. +### Building + +```sh git clone https://github.com/jeeyo/isere.git git submodule update --init -# build mkdir build cd build -cmake .. +cmake -DTARGET_PLATFORM=linux -DDEBUG=on .. # see Build configurations for more options make -j +``` + +#### Build configurations + +|Name|Description|Supported values| +|-|-|-| +|TARGET_PLATFORM|Target platform to build isère executable for|linux (default), ~~pico~~, pico2| +|DEBUG|Whether to build isère executable with debug symbol|off (default), on| + +### Running -# run isere +```sh ./isere ``` -try to access `http://localhost:8080/` and see the process logs - -feel free to try modify `examples/handler.js` +A web server will start on port 8080 with the function defined in [js/handler.js](js/handler.js) ### Running Tests ```sh ./unittests ``` + +### Benchmark + +See [BENCHMARK.md](BENCHMARK.md) + +### Acknowledgment + +Special thanks to + +[maxnet](https://github.com/maxnet/pico-webserver/) for tinyusb RNDIS to LwIP glue for Raspberry Pi Pico diff --git a/include/httpd.h b/include/httpd.h index e377ba4..11c0ae6 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -47,6 +47,18 @@ extern "C" { #define PARSED (METHODED | PATHED | HEADERED | BODYED) #define DONE (PARSED | PROCESSED | WROTE) +#ifndef ISERE_HTTPD_SERVER_TASK_STACK_SIZE +#define ISERE_HTTPD_SERVER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE +#endif /* ISERE_HTTPD_SERVER_TASK_STACK_SIZE */ + +#ifndef ISERE_HTTPD_SERVER_PARSER_TASK_STACK_SIZE +#define ISERE_HTTPD_SERVER_PARSER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE +#endif /* ISERE_HTTPD_SERVER_PARSER_TASK_STACK_SIZE */ + +#ifndef ISERE_HTTPD_SERVER_POLLER_TASK_STACK_SIZE +#define ISERE_HTTPD_SERVER_POLLER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE +#endif /* ISERE_HTTPD_SERVER_POLLER_TASK_STACK_SIZE */ + // #define HTTP_STATUS_BAD_REQUEST -400 // #define HTTP_STATUS_NOT_FOUND -404 // #define HTTP_STATUS_PAYLOAD_TOO_LARGE -413 @@ -62,7 +74,7 @@ typedef struct { tcp_socket_t *socket; int32_t recvd; // number of bytes received - isere_js_t js; + isere_js_context_t js; llhttp_t llhttp; llhttp_settings_t llhttp_settings; diff --git a/include/isere.h b/include/isere.h index 85529b4..3d8db80 100644 --- a/include/isere.h +++ b/include/isere.h @@ -5,6 +5,8 @@ extern "C" { #endif +#include "isere_config.h" + #include #include "platform.h" @@ -65,7 +67,9 @@ typedef struct { JSContext *context; JSValue future; polyfill_timer_t timers[ISERE_JS_POLYFILLS_MAX_TIMERS]; -} isere_js_t; +} isere_js_context_t; + +typedef void * isere_js_t; typedef void * isere_httpd_t; @@ -80,6 +84,7 @@ typedef void * isere_rtc_t; typedef struct { isere_logger_t *logger; isere_loader_t *loader; + isere_js_t *js; isere_tcp_t *tcp; isere_httpd_t *httpd; isere_fs_t *fs; diff --git a/include/js.h b/include/js.h index aec7c15..aed2558 100644 --- a/include/js.h +++ b/include/js.h @@ -7,8 +7,6 @@ extern "C" { #include "isere.h" -#include "quickjs.h" - #include "FreeRTOS.h" #include "croutine.h" @@ -24,10 +22,12 @@ extern "C" { // TODO: make this configurable #define ISERE_JS_STACK_SIZE 4096 -int isere_js_init(isere_js_t *js); +int isere_js_init(isere_t *isere, isere_js_t *js); int isere_js_deinit(isere_js_t *js); -int isere_js_eval(isere_js_t *js, unsigned char *handler, unsigned int handler_len); -int isere_js_poll(isere_js_t *js); +int isere_js_new_context(isere_js_t *js, isere_js_context_t *ctx); +int isere_js_free_context(isere_js_context_t *ctx); +int isere_js_eval(isere_js_context_t *ctx, unsigned char *handler, unsigned int handler_len); +int isere_js_poll(isere_js_context_t *ctx); #ifdef __cplusplus } diff --git a/include/polyfills.h b/include/polyfills.h index ee948b9..235197d 100644 --- a/include/polyfills.h +++ b/include/polyfills.h @@ -7,13 +7,13 @@ extern "C" { #include "isere.h" -void isere_js_polyfill_timer_init(isere_js_t *js); -void isere_js_polyfill_timer_deinit(isere_js_t *js); -int isere_js_polyfill_timer_poll(isere_js_t *js); +void isere_js_polyfill_timer_init(isere_js_context_t *ctx); +void isere_js_polyfill_timer_deinit(isere_js_context_t *ctx); +int isere_js_polyfill_timer_poll(isere_js_context_t *ctx); -void isere_js_polyfill_fetch_init(isere_js_t *js); -void isere_js_polyfill_fetch_deinit(isere_js_t *js); -int isere_js_polyfill_fetch_poll(isere_js_t *js); +void isere_js_polyfill_fetch_init(isere_js_context_t *ctx); +void isere_js_polyfill_fetch_deinit(isere_js_context_t *ctx); +int isere_js_polyfill_fetch_poll(isere_js_context_t *ctx); #ifdef __cplusplus } diff --git a/include/tcp.h b/include/tcp.h index 756d150..30b04cf 100644 --- a/include/tcp.h +++ b/include/tcp.h @@ -28,7 +28,7 @@ typedef struct { tcp_socket_t *isere_tcp_socket_new(); int isere_tcp_socket_init(tcp_socket_t *sock); -void isere_tcp_socket_close(tcp_socket_t *sock); +void isere_tcp_close(tcp_socket_t *sock); int isere_tcp_listen(tcp_socket_t *sock, uint16_t port); tcp_socket_t *isere_tcp_accept(tcp_socket_t *sock, char *ip_addr); ssize_t isere_tcp_recv(tcp_socket_t *sock, char *buf, size_t len); diff --git a/examples/handler.js b/js/handler.js similarity index 100% rename from examples/handler.js rename to js/handler.js diff --git a/portable/include/platform.h b/portable/include/platform.h index e07a219..4693803 100644 --- a/portable/include/platform.h +++ b/portable/include/platform.h @@ -1,6 +1,6 @@ -#ifndef ISERE_PLATFORM_H -#define ISERE_PLATFORM_H +#ifndef ISERE_PLATFORM_H_ +#define ISERE_PLATFORM_H_ void platform_init(); -#endif /* ISERE_PLATFORM_H */ +#endif /* ISERE_PLATFORM_H_ */ diff --git a/portable/linux/include/isere_config.h b/portable/linux/include/isere_config.h new file mode 100644 index 0000000..db281a0 --- /dev/null +++ b/portable/linux/include/isere_config.h @@ -0,0 +1,12 @@ +#ifndef ISERE_CONFIG_H_ +#define ISERE_CONFIG_H_ + +#include "FreeRTOSConfig.h" + +#define ISERE_TCP_MAX_CONNECTIONS 4 + +#define ISERE_HTTPD_SERVER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE +#define ISERE_HTTPD_SERVER_PARSER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE +#define ISERE_HTTPD_SERVER_POLLER_TASK_STACK_SIZE configMINIMAL_STACK_SIZE + +#endif /* ISERE_CONFIG_H_ */ diff --git a/portable/linux/platform_features.cmake b/portable/linux/platform_features.cmake index 8a5e9a3..f6a47d6 100644 --- a/portable/linux/platform_features.cmake +++ b/portable/linux/platform_features.cmake @@ -1,2 +1,2 @@ -set(NEED_LWIP OFF) +set(WITH_LWIP OFF) set(SUPPORT_DYNLINK ON) diff --git a/portable/linux/src/tcp.c b/portable/linux/src/tcp.c index 43faf12..e116d12 100644 --- a/portable/linux/src/tcp.c +++ b/portable/linux/src/tcp.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "FreeRTOS.h" #include "task.h" @@ -16,7 +17,7 @@ static isere_t *__isere = NULL; -static tcp_socket_t __sockets[ISERE_TCP_MAX_CONNECTIONS]; +static uint32_t __num_of_tcp_conns = 0; int isere_tcp_init(isere_t *isere, isere_tcp_t *tcp) { @@ -26,15 +27,6 @@ int isere_tcp_init(isere_t *isere, isere_tcp_t *tcp) return -1; } - for (int i = 0; i < ISERE_TCP_MAX_CONNECTIONS; i++) { - tcp_socket_t *socket = &__sockets[i]; - memset(socket, 0, sizeof(tcp_socket_t)); - - socket->fd = -1; - socket->events = 0; - socket->revents = 0; - } - return 0; } @@ -49,13 +41,19 @@ int isere_tcp_deinit(isere_tcp_t *tcp) static tcp_socket_t *__tcp_get_free_slot() { - for (int i = 0; i < ISERE_TCP_MAX_CONNECTIONS; i++) { - if (__sockets[i].fd == -1) { - return &__sockets[i]; - } + if (__num_of_tcp_conns >= ISERE_TCP_MAX_CONNECTIONS) { + return NULL; } - return NULL; + tcp_socket_t *socket = (tcp_socket_t *)pvPortMalloc(sizeof(tcp_socket_t)); + memset(socket, 0, sizeof(tcp_socket_t)); + + socket->fd = -1; + socket->events = 0; + socket->revents = 0; + + __num_of_tcp_conns++; + return socket; } tcp_socket_t *isere_tcp_socket_new() @@ -87,13 +85,11 @@ int isere_tcp_socket_init(tcp_socket_t *sock) return 0; } -void isere_tcp_socket_close(tcp_socket_t *sock) +void isere_tcp_close(tcp_socket_t *sock) { close(sock->fd); - - sock->fd = -1; - sock->events = 0; - sock->revents = 0; + vPortFree(sock); + __num_of_tcp_conns--; } int isere_tcp_listen(tcp_socket_t *sock, uint16_t port) @@ -119,32 +115,34 @@ int isere_tcp_listen(tcp_socket_t *sock, uint16_t port) tcp_socket_t *isere_tcp_accept(tcp_socket_t *sock, char *ip_addr) { - tcp_socket_t *newsock = __tcp_get_free_slot(); - if (newsock == NULL) { - return NULL; - } - - // reserve this new socket - newsock->fd = -2; - struct sockaddr_in source_addr; socklen_t addr_len = sizeof(source_addr); - newsock->fd = accept(sock->fd, (struct sockaddr *)&source_addr, &addr_len); - if (newsock->fd < 0 && errno == EINTR) { - newsock->fd = -1; + int newfd = accept(sock->fd, (struct sockaddr *)&source_addr, &addr_len); + if (newfd < 0 && errno == EINTR) { + newfd = -1; return NULL; } - if (newsock->fd < 0) { - newsock->fd = -1; + if (newfd < 0) { + newfd = -1; return NULL; } + tcp_socket_t *newsock = __tcp_get_free_slot(); + if (newsock == NULL) { + close(newfd); + return NULL; + } + newsock->fd = newfd; + // disable tcp keepalive int keep_alive = 0; setsockopt(newsock->fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(int)); + // catch SIGPIPE on EPIPE + signal(SIGPIPE, SIG_IGN); + // copy ip address string strncpy(ip_addr, inet_ntoa(((struct sockaddr_in *)&source_addr)->sin_addr), INET_ADDRSTRLEN); diff --git a/portable/rp2040/README.md b/portable/pico/README.md similarity index 90% rename from portable/rp2040/README.md rename to portable/pico/README.md index 404365e..9d3010e 100644 --- a/portable/rp2040/README.md +++ b/portable/pico/README.md @@ -7,7 +7,7 @@ flowchart LR httpd --> http_handler(HTTP Request Handler) http_handler -->|execute JavaScript code| js(QuickJS wrapper) js --> timer_polyfill(Timer polyfill) - js --> fetch_polyfill(Fetch polyfill) + js --> fetch_polyfill(Fetch polyfill*) isere --> ini(Configuration*) end @@ -32,7 +32,7 @@ flowchart LR ota --> rom ``` -Note that the component with asterisk (*) is not implemented yet. +Note that the components with asterisk (*) are not implemented yet. For RP2040 port, isere doesn't support over-the-air JavaScript code or configuration update. In this alpha version, we statically link JavaScript code with the main binary. diff --git a/portable/rp2040/include/FreeRTOSConfig.h b/portable/pico/include/FreeRTOSConfig.h similarity index 100% rename from portable/rp2040/include/FreeRTOSConfig.h rename to portable/pico/include/FreeRTOSConfig.h diff --git a/portable/rp2040/include/arch/cc.h b/portable/pico/include/arch/cc.h similarity index 100% rename from portable/rp2040/include/arch/cc.h rename to portable/pico/include/arch/cc.h diff --git a/portable/rp2040/include/lwipopts.h b/portable/pico/include/lwipopts.h similarity index 100% rename from portable/rp2040/include/lwipopts.h rename to portable/pico/include/lwipopts.h diff --git a/portable/rp2040/include/tusb_config.h b/portable/pico/include/tusb_config.h similarity index 100% rename from portable/rp2040/include/tusb_config.h rename to portable/pico/include/tusb_config.h diff --git a/portable/rp2040/include/tusb_lwip_glue.h b/portable/pico/include/tusb_lwip_glue.h similarity index 100% rename from portable/rp2040/include/tusb_lwip_glue.h rename to portable/pico/include/tusb_lwip_glue.h diff --git a/portable/rp2040/platform_extras.cmake b/portable/pico/platform_extras.cmake similarity index 90% rename from portable/rp2040/platform_extras.cmake rename to portable/pico/platform_extras.cmake index 13095c4..0b5d8c4 100644 --- a/portable/rp2040/platform_extras.cmake +++ b/portable/pico/platform_extras.cmake @@ -6,6 +6,7 @@ target_include_directories(isere PRIVATE target_link_libraries(isere pico_stdlib + pico_stdio_semihosting pico_multicore pico_lwip pico_lwip_contrib_freertos @@ -21,6 +22,7 @@ target_compile_definitions(isere PRIVATE PICO_STDOUT_MUTEX=0) pico_enable_stdio_usb(isere 0) pico_enable_stdio_uart(isere 0) +pico_enable_stdio_semihosting(isere 1) # create map/bin/hex file etc. pico_add_extra_outputs(isere) diff --git a/portable/rp2040/platform_features.cmake b/portable/pico/platform_features.cmake similarity index 58% rename from portable/rp2040/platform_features.cmake rename to portable/pico/platform_features.cmake index e6c01a9..de9eed2 100644 --- a/portable/rp2040/platform_features.cmake +++ b/portable/pico/platform_features.cmake @@ -1,4 +1,4 @@ include(3rdparty/pico-sdk/pico_sdk_init.cmake) -set(NEED_LWIP OFF) # use included LwIP in pico-sdk +set(WITH_LWIP OFF) # use included LwIP in pico-sdk set(SUPPORT_DYNLINK OFF) diff --git a/portable/rp2040/platform_sources.cmake b/portable/pico/platform_sources.cmake similarity index 100% rename from portable/rp2040/platform_sources.cmake rename to portable/pico/platform_sources.cmake diff --git a/portable/rp2040/src/fs.c b/portable/pico/src/fs.c similarity index 100% rename from portable/rp2040/src/fs.c rename to portable/pico/src/fs.c diff --git a/portable/rp2040/src/loader.c b/portable/pico/src/loader.c similarity index 100% rename from portable/rp2040/src/loader.c rename to portable/pico/src/loader.c diff --git a/portable/rp2040/src/logger.c b/portable/pico/src/logger.c similarity index 100% rename from portable/rp2040/src/logger.c rename to portable/pico/src/logger.c diff --git a/portable/rp2040/src/platform.c b/portable/pico/src/platform.c similarity index 91% rename from portable/rp2040/src/platform.c rename to portable/pico/src/platform.c index 625ac64..3ac1af8 100644 --- a/portable/rp2040/src/platform.c +++ b/portable/pico/src/platform.c @@ -2,8 +2,11 @@ #include "hardware/gpio.h" #include "hardware/watchdog.h" +#include "pico/stdio_semihosting.h" void platform_init() { + stdio_semihosting_init(); + // if (watchdog_caused_reboot()) { // // TODO: Log the watchdog reboot // } diff --git a/portable/rp2040/src/rtc.c b/portable/pico/src/rtc.c similarity index 100% rename from portable/rp2040/src/rtc.c rename to portable/pico/src/rtc.c diff --git a/portable/rp2040/src/tcp.c b/portable/pico/src/tcp.c similarity index 98% rename from portable/rp2040/src/tcp.c rename to portable/pico/src/tcp.c index 02376b4..d8761a7 100644 --- a/portable/rp2040/src/tcp.c +++ b/portable/pico/src/tcp.c @@ -227,7 +227,7 @@ static void __isere_tusb_task(void *param) tud_task(); } - __isere->logger->error(ISERE_HTTPD_LOG_TAG, "tusb task was unexpectedly closed"); + __isere->logger->error(ISERE_TCP_LOG_TAG, "tusb task was unexpectedly closed"); should_exit = 1; vTaskDelete(NULL); } diff --git a/portable/rp2040/src/tusb_lwip_glue.c b/portable/pico/src/tusb_lwip_glue.c similarity index 98% rename from portable/rp2040/src/tusb_lwip_glue.c rename to portable/pico/src/tusb_lwip_glue.c index 7a10947..ebebd0e 100644 --- a/portable/rp2040/src/tusb_lwip_glue.c +++ b/portable/pico/src/tusb_lwip_glue.c @@ -44,7 +44,7 @@ static QueueHandle_t rxed_queue; /* this is used by this code, ./class/net/net_driver.c, and usb_descriptors.c */ /* ideally speaking, this should be generated from the hardware's unique ID (if available) */ /* it is suggested that the first byte is 0x02 to indicate a link-local address */ -const uint8_t tud_network_mac_address[6] = { 0x02, 0x02, 0x84, 0x6A, 0x96, 0x00 }; +uint8_t tud_network_mac_address[6] = { 0x02, 0x02, 0x84, 0x6A, 0x96, 0x00 }; /* network parameters of this MCU */ static const ip_addr_t ipaddr = IPADDR4_INIT_BYTES(192, 168, 7, 1); diff --git a/portable/rp2040/src/usb_descriptor.c b/portable/pico/src/usb_descriptor.c similarity index 99% rename from portable/rp2040/src/usb_descriptor.c rename to portable/pico/src/usb_descriptor.c index 8c5f3ca..14f13ed 100644 --- a/portable/rp2040/src/usb_descriptor.c +++ b/portable/pico/src/usb_descriptor.c @@ -175,6 +175,7 @@ static char const* string_desc_arr [] = }; static uint16_t _desc_str[32 + 1]; +static pico_unique_board_id_t id; // Invoked when received GET STRING DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete @@ -188,8 +189,7 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { chr_count = 1; break; - case STRID_SERIAL: - pico_unique_board_id_t id; + case STRID_SERIAL: { pico_get_unique_board_id(&id); for (unsigned i=0; i> 0) & 0xf]; } break; - + } case STRID_MAC: // Convert MAC address into UTF-16 for (unsigned i=0; i httpd(HTTP Server) + httpd --> http_handler(HTTP Request Handler) + http_handler -->|execute JavaScript code| js(QuickJS wrapper) + js --> timer_polyfill(Timer polyfill) + js --> fetch_polyfill(Fetch polyfill*) + isere --> ini(Configuration*) + end + + subgraph portable + isere -->|load JavaScript code| loader(loader.c) + httpd -->|TCP server| tcp(tcp.c) + + isere -->|get/set DateTime| rtc(rtc.c) + + isere -->|send logs| logger(logger.c) + ini -->|read/write files| fs(fs.c) + isere -->|over-the-air update JavaScript code| ota(ota.c*) + end + + fetch_polyfill -->|TCP Client| tcp + + tcp --> lwip[LwIP] + tcp --> tusb[TinyUSB RNDIS] + tusb <--> lwip + + loader --> rom[ROM] + ota --> rom +``` + +Note that the components with asterisk (*) are not implemented yet. +For RP2350 port, isere doesn't support over-the-air JavaScript code or configuration update. + +In this alpha version, we statically link JavaScript code with the main binary. +Since the main goal is to make it able to run the server for the benchmark. diff --git a/portable/pico2/include/FreeRTOSConfig.h b/portable/pico2/include/FreeRTOSConfig.h new file mode 100644 index 0000000..d87098a --- /dev/null +++ b/portable/pico2/include/FreeRTOSConfig.h @@ -0,0 +1,141 @@ +/* + * FreeRTOS V202112.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#include "rp2040_config.h" + +/* RP2350 grows some features */ +#define configENABLE_FPU 1 +#define configENABLE_MPU 0 +#define configENABLE_TRUSTZONE 0 +#define configRUN_FREERTOS_SECURE_ONLY 1 +#define configMAX_SYSCALL_INTERRUPT_PRIORITY 16 + +/*----------------------------------------------------------- +* Application specific definitions. +* +* These definitions should be adjusted for your particular hardware and +* application requirements. +* +* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE +* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. See +* https://www.FreeRTOS.org/a00110.html +*----------------------------------------------------------*/ + +#define configUSE_PREEMPTION 1 +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configUSE_IDLE_HOOK 1 +#define configUSE_TICK_HOOK 1 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 1 +#define configTICK_RATE_HZ ( 1000 ) /* In this non-real time simulated environment the tick frequency has to be at least a multiple of the Win32 tick frequency, and therefore very slow. */ +#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 70 ) /* In this simulated case, the stack only has to hold one small structure as the real stack is part of the win32 thread. */ +// #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 65 * 1024 ) ) /* This has no effect when using heap_3 */ +#define configMAX_TASK_NAME_LEN ( 12 ) +#define configUSE_TRACE_FACILITY 1 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_MUTEXES 1 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configQUEUE_REGISTRY_SIZE 15 +#define configUSE_APPLICATION_TASK_TAG 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_ALTERNATIVE_API 0 +#define configUSE_QUEUE_SETS 1 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 + +/* Software timer related configuration options. The maximum possible task + * priority is configMAX_PRIORITIES - 1. The priority of the timer task is + * deliberately set higher to ensure it is correctly capped back to + * configMAX_PRIORITIES - 1. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) + +#define configMAX_PRIORITIES ( 7 ) + +/* Co-routine related configuration options. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) + +/* This demo can use of one or more example stats formatting functions. These + * format the raw data provided by the uxTaskGetSystemState() function in to human + * readable ASCII form. See the notes in the implementation of vTaskList() within + * FreeRTOS/Source/tasks.c for limitations. */ +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +/* Enables the test whereby a stack larger than the total heap size is + * requested. */ +#define configSTACK_DEPTH_TYPE uint32_t + +/* SMP port only */ +#define configNUMBER_OF_CORES 2 +#define configTICK_CORE 0 +#define configRUN_MULTIPLE_PRIORITIES 1 + +/* RP2040 specific */ +#define configSUPPORT_PICO_SYNC_INTEROP 1 +#define configSUPPORT_PICO_TIME_INTEROP 1 + +/* SMP Related config. */ +#define configUSE_PASSIVE_IDLE_HOOK 0 +#define portSUPPORT_SMP 1 +#define configUSE_CORE_AFFINITY 1 + +/* Set the following definitions to 1 to include the API function, or zero + * to exclude the API function. In most cases the linker will remove unused + * functions anyway. */ +#define INCLUDE_vTaskPrioritySet 0 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskCleanUpResources 0 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_uxTaskGetStackHighWaterMark2 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTimerGetTimerDaemonTaskHandle 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 +#define INCLUDE_xTaskGetHandle 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_xSemaphoreGetMutexHolder 0 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 + +extern void vAssertCalled( const char * const pcFileName, + unsigned long ulLine ); + +#include +/* Define to trap errors during development. */ +#define configASSERT(x) assert(x) + +#endif /* FREERTOS_CONFIG_H */ diff --git a/portable/pico2/include/arch/cc.h b/portable/pico2/include/arch/cc.h new file mode 100644 index 0000000..6f43127 --- /dev/null +++ b/portable/pico2/include/arch/cc.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ +#ifndef __CC_H__ +#define __CC_H__ + +#include + +/* define compiler specific symbols */ +#if defined (__ICCARM__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x +#define PACK_STRUCT_USE_INCLUDES + +#elif defined (__CC_ARM) + +#define PACK_STRUCT_BEGIN __packed +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#elif defined (__GNUC__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#elif defined (__TASKING__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#endif + +#ifndef LWIP_PLATFORM_ASSERT +#include "pico.h" +#define LWIP_PLATFORM_ASSERT(x) panic(x) +#endif + +#ifndef LWIP_RAND +#include "pico/rand.h" +// Use the pico_rand library which goes to reasonable lengths to try to provide good entropy +#define LWIP_RAND() get_rand_32() +#endif + +#endif /* __CC_H__ */ diff --git a/portable/pico2/include/isere_config.h b/portable/pico2/include/isere_config.h new file mode 100644 index 0000000..eaf4e41 --- /dev/null +++ b/portable/pico2/include/isere_config.h @@ -0,0 +1,10 @@ +#ifndef ISERE_CONFIG_H_ +#define ISERE_CONFIG_H_ + +#define ISERE_TCP_MAX_CONNECTIONS 4 + +#define ISERE_HTTPD_SERVER_TASK_STACK_SIZE (1024) +#define ISERE_HTTPD_SERVER_PARSER_TASK_STACK_SIZE (1024 * 4) +#define ISERE_HTTPD_SERVER_POLLER_TASK_STACK_SIZE (1024) + +#endif /* ISERE_CONFIG_H_ */ diff --git a/portable/pico2/include/lwipopts.h b/portable/pico2/include/lwipopts.h new file mode 100644 index 0000000..21f81dc --- /dev/null +++ b/portable/pico2/include/lwipopts.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Simon Goldschmidt + * + */ +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +// Common settings used in most of the pico_w examples +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) + +#define NO_SYS 0 +#define LWIP_SOCKET 1 +#define MEM_LIBC_MALLOC 0 +#define MEM_ALIGNMENT 4 +#define MEM_SIZE 4000 +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif /* NDEBUG */ + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +#define LWIP_TIMEVAL_PRIVATE 0 +#define LWIP_ERRNO_STDINCLUDE + +#define TCPIP_THREAD_STACKSIZE 1024 +#define DEFAULT_THREAD_STACKSIZE 1024 +#define TCPIP_MBOX_SIZE 12 +#define DEFAULT_RAW_RECVMBOX_SIZE 12 +#define DEFAULT_TCP_RECVMBOX_SIZE 12 +#define DEFAULT_ACCEPTMBOX_SIZE 12 + +#include "FreeRTOS.h" +#include "task.h" + +#define TCPIP_THREAD_PRIO (tskIDLE_PRIORITY + 4) +#define DEFAULT_THREAD_PRIO (tskIDLE_PRIORITY + 4) + +#define LWIP_TCPIP_CORE_LOCKING 1 +#define LWIP_TCPIP_CORE_LOCKING_INPUT 1 + +#endif /* __LWIPOPTS_H__ */ diff --git a/portable/pico2/include/tusb_config.h b/portable/pico2/include/tusb_config.h new file mode 100644 index 0000000..9b3ed57 --- /dev/null +++ b/portable/pico2/include/tusb_config.h @@ -0,0 +1,124 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif /* BOARD_TUD_RHPORT */ + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif /* BOARD_TUD_MAX_SPEED */ + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif /* CFG_TUSB_MCU */ + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_FREERTOS +#endif /* CFG_TUSB_OS */ + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif /* CFG_TUSB_MEM_SECTION */ + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif /* CFG_TUSB_MEM_ALIGN */ + +// can be defined by compiler in DEBUG build +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif /* CFG_TUSB_DEBUG */ + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif /* CFG_TUSB_MEM_SECTION */ + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif /* CFG_TUSB_MEM_ALIGN */ + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif /* CFG_TUD_ENDPOINT0_SIZE */ + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_ECM_RNDIS 1 + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/portable/pico2/include/tusb_lwip_glue.h b/portable/pico2/include/tusb_lwip_glue.h new file mode 100644 index 0000000..9487f6e --- /dev/null +++ b/portable/pico2/include/tusb_lwip_glue.h @@ -0,0 +1,25 @@ +#ifndef _TUSB_LWIP_GLUE_H_ +#define _TUSB_LWIP_GLUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tusb.h" +#include "dhserver.h" +#include "dnserver.h" + +#include "lwip/init.h" +#include "lwip/timeouts.h" + +void rndis_tusb_init(); +void lwip_freertos_init(void); +void lwip_add_netif(); +void wait_for_netif_is_up(); +void dhcpd_init(); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_LWIP_GLUE_H_ */ diff --git a/portable/pico2/platform_extras.cmake b/portable/pico2/platform_extras.cmake new file mode 100644 index 0000000..16742e1 --- /dev/null +++ b/portable/pico2/platform_extras.cmake @@ -0,0 +1,27 @@ +target_include_directories(isere PRIVATE + ${FREERTOS_DIR}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure + ${PICO_TINYUSB_PATH}/src + ${PICO_TINYUSB_PATH}/lib/networking +) + +target_link_libraries(isere + pico_stdlib + pico_stdio_semihosting + pico_multicore + pico_lwip + pico_lwip_contrib_freertos + pico_rand + hardware_exception + pico_unique_id + tinyusb_device +) + +target_compile_definitions(isere PRIVATE PICO_STDOUT_MUTEX=0) +# target_compile_options(isere PRIVATE -fno-math-errno) + +pico_enable_stdio_usb(isere 0) +pico_enable_stdio_uart(isere 0) +pico_enable_stdio_semihosting(isere 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(isere) diff --git a/portable/pico2/platform_features.cmake b/portable/pico2/platform_features.cmake new file mode 100644 index 0000000..985c8ec --- /dev/null +++ b/portable/pico2/platform_features.cmake @@ -0,0 +1,6 @@ +set(PICO_BOARD pico2) + +include(3rdparty/pico-sdk/pico_sdk_init.cmake) + +set(WITH_LWIP OFF) # use included LwIP in pico-sdk +set(SUPPORT_DYNLINK OFF) diff --git a/portable/pico2/platform_sources.cmake b/portable/pico2/platform_sources.cmake new file mode 100644 index 0000000..f2d2804 --- /dev/null +++ b/portable/pico2/platform_sources.cmake @@ -0,0 +1,16 @@ +# initialize the Raspberry Pi Pico SDK +pico_sdk_init() + +set(PICO_TINYUSB_PATH 3rdparty/pico-sdk/lib/tinyusb) +set(TINYUSB_LIBNETWORKING_SOURCES + ${PICO_TINYUSB_PATH}/lib/networking/dhserver.c + ${PICO_TINYUSB_PATH}/lib/networking/rndis_reports.c +) + +list(APPEND SOURCES + portable/${TARGET_PLATFORM}/src/tusb_lwip_glue.c + portable/${TARGET_PLATFORM}/src/usb_descriptor.c + ${FREERTOS_DIR}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure/port.c + ${FREERTOS_DIR}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/non_secure/portasm.c + ${TINYUSB_LIBNETWORKING_SOURCES} +) diff --git a/portable/pico2/src/fs.c b/portable/pico2/src/fs.c new file mode 100644 index 0000000..427e5df --- /dev/null +++ b/portable/pico2/src/fs.c @@ -0,0 +1,128 @@ +#include "fs.h" + +// #include +// #include +#include +#include + +#define FS_ROOT_DIR "romfs/" + +static isere_t *__isere = NULL; + +int fs_init(isere_t *isere, isere_fs_t *fs) +{ + __isere = isere; + + if (isere->logger == NULL) { + return -1; + } + + return 0; +} + +void fs_deinit(isere_fs_t *fs) +{ + if (__isere) { + __isere = NULL; + } + + return; +} + +int fs_open(isere_fs_t *fs, fs_file_t *file, const char *path, int flags) +{ + if (file == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "file is NULL"); + return -1; + } + + char filepath[256]; + memset(filepath, 0, sizeof(filepath)); + strncpy(filepath, FS_ROOT_DIR, sizeof(filepath) - 1); + strncat(filepath, path, sizeof(filepath) - 1); + + // *file = open(filepath, flags, 0666); + // if (*file < 0) { + // __isere->logger->error(ISERE_FS_LOG_TAG, "open() error: %s", strerror(errno)); + // return -1; + // } + + return 0; +} + +int fs_close(isere_fs_t *fs, fs_file_t *file) +{ + if (file == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "file is NULL"); + return -1; + } + + // int ret = close(*file); + // if (ret != 0) { + // // __isere->logger->error(ISERE_FS_LOG_TAG, "close() error: %s", strerror(errno)); + // return -1; + // } + + *file = -1; + return 0; +} + +int fs_read(isere_fs_t *fs, fs_file_t *file, uint8_t *buf, size_t size) +{ + if (file == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "file is NULL"); + return -1; + } + + if (buf == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "buf is NULL"); + return -1; + } + + // ssize_t ret = read(*file, buf, size); + // if (ret <= 0) { + // __isere->logger->error(ISERE_FS_LOG_TAG, "read() error: %s", strerror(errno)); + // return -1; + // } + + // return ret; + return 0; +} + +int fs_write(isere_fs_t *fs, fs_file_t *file, uint8_t *buf, size_t size) +{ + if (file == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "file is NULL"); + return -1; + } + + if (buf == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "buf is NULL"); + return -1; + } + + // ssize_t ret = write(*file, buf, size); + // if (ret < 0) { + // __isere->logger->error(ISERE_FS_LOG_TAG, "write() error: %s", strerror(errno)); + // return -1; + // } + + // return ret; + return 0; +} + +int fs_rewind(isere_fs_t *fs, fs_file_t *file) +{ + if (file == NULL) { + __isere->logger->error(ISERE_FS_LOG_TAG, "file is NULL"); + return -1; + } + + // off_t ret = lseek(*file, 0, SEEK_SET); + // if (ret < 0) { + // __isere->logger->error(ISERE_FS_LOG_TAG, "lseek() error: %s", strerror(errno)); + // return -1; + // } + + return 0; +} diff --git a/portable/pico2/src/loader.c b/portable/pico2/src/loader.c new file mode 100644 index 0000000..bcf79d7 --- /dev/null +++ b/portable/pico2/src/loader.c @@ -0,0 +1,39 @@ +#include "loader.h" + +#include + +static isere_t *__isere = NULL; + +static uint8_t *loader_get_fn(isere_loader_t *loader, uint32_t *size) +{ + *size = handler_len; + return handler; +} + +int isere_loader_init(isere_t *isere, isere_loader_t *loader) +{ + __isere = isere; + + if (isere->logger == NULL) { + return -1; + } + + if (loader->fn != NULL) { + __isere->logger->error(ISERE_LOADER_LOG_TAG, "loader already initialized"); + return -1; + } + + // load javascript source code from the shared library + loader->fn = loader_get_fn(loader, &loader->fn_size); + + return 0; +} + +int isere_loader_deinit(isere_loader_t *loader) +{ + if (__isere) { + __isere = NULL; + } + + return 0; +} diff --git a/portable/pico2/src/logger.c b/portable/pico2/src/logger.c new file mode 100644 index 0000000..56403b7 --- /dev/null +++ b/portable/pico2/src/logger.c @@ -0,0 +1,78 @@ +#include "logger.h" + +#include + +// static SemaphoreHandle_t _stdio_mut = NULL; +// static StaticSemaphore_t _stdio_mutbuf; + +static void __logger_print(const char *tag, isere_log_level_t level, const char *fmt, va_list vargs) +{ + // // xSemaphoreTake(_stdio_mut, portMAX_DELAY); + + // // TO-DO: log level + // printf("[%s] ", tag); + // vprintf(fmt, vargs); + // printf("\n"); + + // // xSemaphoreGive(_stdio_mut); +} + +static void logger_error(const char *tag, const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + __logger_print(tag, LOG_LEVEL_ERROR, fmt, vargs); + va_end(vargs); +} + +static void logger_warning(const char *tag, const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + __logger_print(tag, LOG_LEVEL_WARNING, fmt, vargs); + va_end(vargs); +} + +static void logger_info(const char *tag, const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + __logger_print(tag, LOG_LEVEL_INFO, fmt, vargs); + va_end(vargs); +} + +static void logger_debug(const char *tag, const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + __logger_print(tag, LOG_LEVEL_DEBUG, fmt, vargs); + va_end(vargs); +} + +int isere_logger_init(isere_t *isere, isere_logger_t *logger) +{ + (void)isere; + + // if (_stdio_mut == NULL) { + // _stdio_mut = xSemaphoreCreateMutexStatic(&_stdio_mutbuf); + // } + + *logger = (isere_logger_t){ + .error = &logger_error, + .warning = &logger_warning, + .info = &logger_info, + .debug = &logger_debug, + }; + + return 0; +} + +void isere_logger_deinit(isere_logger_t *logger) +{ + (void)logger; + + // if (_stdio_mut) { + // vSemaphoreDelete(_stdio_mut); + // _stdio_mut = NULL; + // } +} \ No newline at end of file diff --git a/portable/pico2/src/platform.c b/portable/pico2/src/platform.c new file mode 100644 index 0000000..3ac1af8 --- /dev/null +++ b/portable/pico2/src/platform.c @@ -0,0 +1,29 @@ +#include "platform.h" + +#include "hardware/gpio.h" +#include "hardware/watchdog.h" +#include "pico/stdio_semihosting.h" + +void platform_init() { + stdio_semihosting_init(); + + // if (watchdog_caused_reboot()) { + // // TODO: Log the watchdog reboot + // } + + // // Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot + // // second arg is pause on debug which means the watchdog will pause when stepping through code + // watchdog_enable(100, 1); + + // // Keep updating the watchdog every 100ms + // for (int i = 0; i < 10; i++) { + // sleep_ms(10); + // watchdog_update(); + // } + +// #ifdef PICO_DEFAULT_LED_PIN + gpio_init(25); + gpio_set_dir(25, GPIO_OUT); + gpio_put(25, 1); +// #endif +} diff --git a/portable/pico2/src/rtc.c b/portable/pico2/src/rtc.c new file mode 100644 index 0000000..689e551 --- /dev/null +++ b/portable/pico2/src/rtc.c @@ -0,0 +1,34 @@ +#include "rtc.h" + +static isere_t *__isere = NULL; + +int isere_rtc_init(isere_t *isere, isere_rtc_t *rtc) +{ + __isere = isere; + + if (isere->logger == NULL) { + return -1; + } + + return 0; +} + +void isere_rtc_deinit(isere_rtc_t *rtc) +{ + if (__isere) { + __isere = NULL; + } + + return; +} + + +int isere_rtc_get_datetime(isere_rtc_t *rtc, rtc_datetime_t *datetime) +{ + return 0; +} + +int isere_rtc_set_datetime(isere_rtc_t *rtc, rtc_datetime_t *datetime) +{ + return 0; +} diff --git a/portable/pico2/src/tcp.c b/portable/pico2/src/tcp.c new file mode 100644 index 0000000..1266f78 --- /dev/null +++ b/portable/pico2/src/tcp.c @@ -0,0 +1,233 @@ +#include "tcp.h" + +#include "pico/multicore.h" +#include "hardware/watchdog.h" +#include "hardware/structs/watchdog.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" + +#include "lwip/sockets.h" +#include "lwip/inet.h" + +#include "tusb_lwip_glue.h" + +static uint8_t should_exit = 0; +static uint8_t initialized = 0; + +static isere_t *__isere = NULL; + +static tcp_socket_t __sockets[ISERE_TCP_MAX_CONNECTIONS]; + +static TaskHandle_t __tusb_task_handle; + +static void __isere_tusb_task(void *param); + +int isere_tcp_init(isere_t *isere, isere_tcp_t *tcp) +{ + __isere = isere; + + if (isere->logger == NULL) { + return -1; + } + + for (int i = 0; i < ISERE_TCP_MAX_CONNECTIONS; i++) { + tcp_socket_t *socket = &__sockets[i]; + memset(socket, 0, sizeof(tcp_socket_t)); + + socket->fd = -1; + socket->events = 0; + socket->revents = 0; + } + + if (xTaskCreate(__isere_tusb_task, "usb", 512, NULL, tskIDLE_PRIORITY + 3, &__tusb_task_handle)) { + __isere->logger->error(ISERE_TCP_LOG_TAG, "Unable to create tusb task"); + } + + return 0; +} + +int isere_tcp_deinit(isere_tcp_t *tcp) +{ + if (__isere) { + __isere = NULL; + } + + should_exit = 1; + + return 0; +} + +static tcp_socket_t *__tcp_get_free_slot() +{ + for (int i = 0; i < ISERE_TCP_MAX_CONNECTIONS; i++) { + if (__sockets[i].fd == -1) { + return &__sockets[i]; + } + } + + return NULL; +} + +tcp_socket_t *isere_tcp_socket_new() +{ + tcp_socket_t *sock = __tcp_get_free_slot(); + if (sock == NULL) { + return NULL; + } + + int ret = isere_tcp_socket_init(sock); + if (ret < 0) { + return NULL; + } + + return sock; +} + +int isere_tcp_socket_init(tcp_socket_t *sock) +{ + sock->fd = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + sock->events = 0; + sock->revents = 0; + + if (sock->fd < 0) { + sock->fd = -1; + return -1; + } + + return 0; +} + +void isere_tcp_close(tcp_socket_t *sock) +{ + lwip_close(sock->fd); + + sock->fd = -1; + sock->events = 0; + sock->revents = 0; +} + +int isere_tcp_listen(tcp_socket_t *sock, uint16_t port) +{ + struct sockaddr_in dest_addr; + bzero(&dest_addr, sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr.sin_port = htons(port); + + int err = lwip_bind(sock->fd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err != 0) { + return -1; + } + + err = lwip_listen(sock->fd, ISERE_TCP_MAX_CONNECTIONS); + if (err != 0) { + return -1; + } + + return 0; +} + + +tcp_socket_t *isere_tcp_accept(tcp_socket_t *sock, char *ip_addr) +{ + tcp_socket_t *newsock = __tcp_get_free_slot(); + if (newsock == NULL) { + return NULL; + } + + // reserve this new socket + newsock->fd = -2; + + struct sockaddr_in source_addr; + socklen_t addr_len = sizeof(source_addr); + + newsock->fd = lwip_accept(sock->fd, (struct sockaddr *)&source_addr, &addr_len); + if (newsock->fd < 0 && errno == EINTR) { + newsock->fd = -1; + return NULL; + } + + if (newsock->fd < 0) { + newsock->fd = -1; + return NULL; + } + + // disable tcp keepalive + int keep_alive = 0; + setsockopt(newsock->fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(int)); + + // copy ip address string + strncpy(ip_addr, inet_ntoa(((struct sockaddr_in *)&source_addr)->sin_addr), INET_ADDRSTRLEN); + + return newsock; +} + +ssize_t isere_tcp_recv(tcp_socket_t *sock, char *buf, size_t len) +{ + int recvd = lwip_recv(sock->fd, buf, len, 0); + if (recvd < 0) { + if (errno == EINTR) { + return -2; + } + + return -1; + } + + return recvd; +} + +ssize_t isere_tcp_write(tcp_socket_t *sock, const char *buf, size_t len) +{ + return lwip_write(sock->fd, buf, len); +} + +int isere_tcp_poll(tcp_socket_t *sock, int timeout_ms) +{ + if (sock->fd < 0) { + return -1; + } + + sock->revents = 0; + + struct pollfd pfd; + pfd.fd = sock->fd; + pfd.events = POLLIN; + + int ready = lwip_poll(&pfd, 1, timeout_ms); + if (ready < 0 && errno == EINTR) { + return -1; + } + + // TODO: POLLOUT & POLLERR + if (pfd.revents & POLLIN) { + sock->revents |= TCP_POLL_READ_READY; + } + + return sock->revents > 0 ? 1 : 0; +} + +int isere_tcp_is_initialized() +{ + return initialized; +} + +static void __isere_tusb_task(void *param) +{ + rndis_tusb_init(); + + lwip_freertos_init(); + wait_for_netif_is_up(); + dhcpd_init(); + initialized = 1; + + while (!should_exit) + { + tud_task(); + } + + __isere->logger->error(ISERE_TCP_LOG_TAG, "tusb task was unexpectedly closed"); + should_exit = 1; + vTaskDelete(NULL); +} diff --git a/portable/pico2/src/tusb_lwip_glue.c b/portable/pico2/src/tusb_lwip_glue.c new file mode 100644 index 0000000..ebebd0e --- /dev/null +++ b/portable/pico2/src/tusb_lwip_glue.c @@ -0,0 +1,195 @@ +/* + * The MIT License (MIT) + * + * Based on tinyUSB example that is: Copyright (c) 2020 Peter Lawrence + * Modified for Pico by Floris Bos + * + * influenced by lrndis https://github.com/fetisov/lrndis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb_lwip_glue.h" +#include "pico/unique_id.h" + +#include "lwip/tcpip.h" +#include "tusb.h" + +#include "FreeRTOS.h" +#include "semphr.h" + +/* lwip context */ +static struct netif netif_data; + +/* shared between tud_network_recv_cb() and service_traffic() */ +static QueueHandle_t rxed_queue; + +/* this is used by this code, ./class/net/net_driver.c, and usb_descriptors.c */ +/* ideally speaking, this should be generated from the hardware's unique ID (if available) */ +/* it is suggested that the first byte is 0x02 to indicate a link-local address */ +uint8_t tud_network_mac_address[6] = { 0x02, 0x02, 0x84, 0x6A, 0x96, 0x00 }; + +/* network parameters of this MCU */ +static const ip_addr_t ipaddr = IPADDR4_INIT_BYTES(192, 168, 7, 1); +static const ip_addr_t netmask = IPADDR4_INIT_BYTES(255, 255, 255, 0); +static const ip_addr_t gateway = IPADDR4_INIT_BYTES(0, 0, 0, 0); + +/* database IP addresses that can be offered to the host; this must be in RAM to store assigned MAC addresses */ +static dhcp_entry_t entries[] = +{ + /* mac ip address lease time */ + { {0}, IPADDR4_INIT_BYTES(192, 168, 7, 2), 24 * 60 * 60 }, + { {0}, IPADDR4_INIT_BYTES(192, 168, 7, 3), 24 * 60 * 60 }, + { {0}, IPADDR4_INIT_BYTES(192, 168, 7, 4), 24 * 60 * 60 }, +}; + +static const dhcp_config_t dhcp_config = +{ + .router = IPADDR4_INIT_BYTES(0, 0, 0, 0), /* router address (if any) */ + .port = 67, /* listen port */ + .dns = IPADDR4_INIT_BYTES(0, 0, 0, 0), /* dns server (if any) */ + "usb", /* dns suffix */ + TU_ARRAY_SIZE(entries), /* num entry */ + entries /* entries */ +}; + +static err_t linkoutput_fn(struct netif *netif, struct pbuf *p) +{ + (void)netif; + + for (;;) + { + /* if TinyUSB isn't ready, we must signal back to lwip that there is nothing we can do */ + if (!tud_ready()) + return ERR_USE; + + /* if the network driver can accept another packet, we make it happen */ + if (tud_network_can_xmit(p->tot_len)) + { + tud_network_xmit(p, 0 /* unused for this example */); + return ERR_OK; + } + + /* transfer execution to TinyUSB in the hopes that it will finish transmitting the prior packet */ + tud_task(); + } +} + +static err_t output_fn(struct netif *netif, struct pbuf *p, const ip_addr_t *addr) +{ + return etharp_output(netif, p, addr); +} + +static err_t netif_init_cb(struct netif *netif) +{ + LWIP_ASSERT("netif != NULL", (netif != NULL)); + netif->mtu = CFG_TUD_NET_MTU; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; + netif->state = NULL; + netif->name[0] = 'E'; + netif->name[1] = 'X'; + netif->linkoutput = linkoutput_fn; + netif->output = output_fn; + return ERR_OK; +} + +static void tcpip_init_done(void *param) { + lwip_add_netif(); + xSemaphoreGive((SemaphoreHandle_t)param); +} + +void lwip_freertos_init(void) +{ + SemaphoreHandle_t init_sem = xSemaphoreCreateBinary(); + tcpip_init(tcpip_init_done, init_sem); + xSemaphoreTake(init_sem, portMAX_DELAY); + vSemaphoreDelete(init_sem); +} + +void rndis_tusb_init(void) +{ + /* TODO: Fixup MAC address based on flash serial */ + // pico_unique_board_id_t id; + // pico_get_unique_board_id(&id); + // memcpy( (tud_network_mac_address)+1, id.id, 5); + // Fixing up does not work because tud_network_mac_address is const + + /* Initialize TinyUSB */ + tud_init(BOARD_TUD_RHPORT); + + rxed_queue = xQueueCreate(10, sizeof(struct pbuf *)); +} + +void lwip_add_netif() +{ + struct netif *netif = &netif_data; + + /* the lwip virtual MAC address must be different from the host's; to ensure this, we toggle the LSbit */ + netif->hwaddr_len = sizeof(tud_network_mac_address); + memcpy(netif->hwaddr, tud_network_mac_address, sizeof(tud_network_mac_address)); + netif->hwaddr[5] ^= 0x01; + + netif = netif_add(netif, &ipaddr, &netmask, &gateway, NULL, netif_init_cb, tcpip_input); + netif_set_default(netif); +} + +void tud_network_init_cb(void) +{ +} + +bool tud_network_recv_cb(const uint8_t *src, uint16_t size) +{ + if (!size) return true; + + struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL); + if (p == NULL) return false; + + /* pbuf_alloc() has already initialized struct; all we need to do is copy the data */ + pbuf_take(p, src, size); + + /* store away the pointer for service_traffic() to later handle */ + struct netif *netif = &netif_data; + if (netif->input(p, netif) != ERR_OK) { + pbuf_free(p); + } + + tud_network_recv_renew(); + + return true; +} + +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) +{ + struct pbuf *p = (struct pbuf *)ref; + + (void)arg; /* unused for this example */ + + return pbuf_copy_partial(p, dst, p->tot_len, 0); +} + +void dhcpd_init() +{ + while (dhserv_init(&dhcp_config) != ERR_OK); +} + +void wait_for_netif_is_up() +{ + while (!netif_is_up(&netif_data)); +} diff --git a/portable/pico2/src/usb_descriptor.c b/portable/pico2/src/usb_descriptor.c new file mode 100644 index 0000000..14f13ed --- /dev/null +++ b/portable/pico2/src/usb_descriptor.c @@ -0,0 +1,234 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "tusb.h" +#include "pico/unique_id.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] NET | VENDOR | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) | _PID_MAP(ECM_RNDIS, 5) | _PID_MAP(NCM, 5) ) + +// String Descriptor Index +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_INTERFACE, + STRID_MAC +}; + +enum +{ + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, + ITF_NUM_TOTAL +}; + +enum +{ + CONFIG_ID_RNDIS = 0, + CONFIG_ID_ECM = 1, + CONFIG_ID_COUNT +}; + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + // Use Interface Association Descriptor (IAD) device class + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0101, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = CONFIG_ID_COUNT // multiple configurations +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +#define MAIN_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_RNDIS_DESC_LEN) +#define ALT_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_ECM_DESC_LEN) +#define NCM_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_NCM_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... + #define EPNUM_NET_NOTIF 0x81 + #define EPNUM_NET_OUT 0x02 + #define EPNUM_NET_IN 0x82 + +#elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X + // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_NET_NOTIF 0x81 + #define EPNUM_NET_OUT 0x02 + #define EPNUM_NET_IN 0x83 + +#else + #define EPNUM_NET_NOTIF 0x81 + #define EPNUM_NET_OUT 0x02 + #define EPNUM_NET_IN 0x82 +#endif + +static uint8_t const rndis_configuration[] = +{ + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_RNDIS+1, ITF_NUM_TOTAL, 0, MAIN_CONFIG_TOTAL_LEN, 0, 100), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_RNDIS_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, EPNUM_NET_NOTIF, 8, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE), +}; + +static uint8_t const ecm_configuration[] = +{ + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_ECM+1, ITF_NUM_TOTAL, 0, ALT_CONFIG_TOTAL_LEN, 0, 100), + + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_ECM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU), +}; + +// Configuration array: RNDIS and CDC-ECM +// - Windows only works with RNDIS +// - MacOS only works with CDC-ECM +// - Linux will work on both +static uint8_t const * const configuration_arr[2] = +{ + [CONFIG_ID_RNDIS] = rndis_configuration, + [CONFIG_ID_ECM ] = ecm_configuration +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + return (index < CONFIG_ID_COUNT) ? configuration_arr[index] : NULL; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +static char const* string_desc_arr [] = +{ + [STRID_LANGID] = (const char[]) { 0x09, 0x04 }, // supported language is English (0x0409) + [STRID_MANUFACTURER] = "TinyUSB", // Manufacturer + [STRID_PRODUCT] = "isere", // Product + [STRID_SERIAL] = NULL, // Serials will use unique ID if possible + [STRID_INTERFACE] = "TinyUSB Network Interface" // Interface Description + + // STRID_MAC index is handled separately +}; + +static uint16_t _desc_str[32 + 1]; +static pico_unique_board_id_t id; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + (void) langid; + unsigned int chr_count = 0; + + switch ( index ) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: { + pico_get_unique_board_id(&id); + + for (unsigned i=0; i> 4) & 0xf]; + _desc_str[1+chr_count++] = "0123456789ABCDEF"[(id.id[i] >> 0) & 0xf]; + } + break; + } + case STRID_MAC: + // Convert MAC address into UTF-16 + for (unsigned i=0; i> 4) & 0xf]; + _desc_str[1+chr_count++] = "0123456789ABCDEF"[(tud_network_mac_address[i] >> 0) & 0xf]; + } + break; + + default: + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL; + + const char *str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + if ( chr_count > max_count ) chr_count = max_count; + + // Convert ASCII string into UTF-16 + for ( size_t i = 0; i < chr_count; i++ ) { + _desc_str[1 + i] = str[i]; + } + break; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8 ) | (2*chr_count + 2)); + + return _desc_str; +} diff --git a/src/http_handler.c b/src/http_handler.c index df1e561..d9f2c62 100644 --- a/src/http_handler.c +++ b/src/http_handler.c @@ -16,60 +16,60 @@ int __http_handler( uint32_t request_headers_len, const char *body) { - isere_js_t *js = &conn->js; + isere_js_context_t *ctx = &conn->js; // populate params { - JSValue global_obj = JS_GetGlobalObject(js->context); + JSValue global_obj = JS_GetGlobalObject(ctx->context); // TODO: add `event` object: https://aws-lambda-for-python-developers.readthedocs.io/en/latest/02_event_and_context/ - JSValue event = JS_NewObject(js->context); - JS_SetPropertyStr(js->context, event, "httpMethod", JS_NewString(js->context, method)); - JS_SetPropertyStr(js->context, event, "path", JS_NewString(js->context, path)); + JSValue event = JS_NewObject(ctx->context); + JS_SetPropertyStr(ctx->context, event, "httpMethod", JS_NewString(ctx->context, method)); + JS_SetPropertyStr(ctx->context, event, "path", JS_NewString(ctx->context, path)); // TODO: multi-value headers - JSValue headers = JS_NewObject(js->context); + JSValue headers = JS_NewObject(ctx->context); for (int i = 0; i < request_headers_len; i++) { - JS_SetPropertyStr(js->context, headers, request_headers[i].name, JS_NewString(js->context, request_headers[i].value)); + JS_SetPropertyStr(ctx->context, headers, request_headers[i].name, JS_NewString(ctx->context, request_headers[i].value)); } - JS_SetPropertyStr(js->context, event, "headers", headers); + JS_SetPropertyStr(ctx->context, event, "headers", headers); // TODO: query string params // TODO: multi-value query string params - JS_SetPropertyStr(js->context, event, "query", JS_NewString(js->context, query != NULL ? query : "")); + JS_SetPropertyStr(ctx->context, event, "query", JS_NewString(ctx->context, query != NULL ? query : "")); // TODO: check `Content-Type: application/json` - JSValue parsedBody = JS_ParseJSON(js->context, body, strlen(body), ""); + JSValue parsedBody = JS_ParseJSON(ctx->context, body, strlen(body), ""); if (!JS_IsObject(parsedBody)) { - JS_FreeValue(js->context, parsedBody); - parsedBody = JS_NewString(js->context, body); + JS_FreeValue(ctx->context, parsedBody); + parsedBody = JS_NewString(ctx->context, body); } - JS_SetPropertyStr(js->context, event, "body", parsedBody); + JS_SetPropertyStr(ctx->context, event, "body", parsedBody); // TODO: binary body - JS_SetPropertyStr(js->context, event, "isBase64Encoded", JS_FALSE); - JS_SetPropertyStr(js->context, global_obj, "__event", event); + JS_SetPropertyStr(ctx->context, event, "isBase64Encoded", JS_FALSE); + JS_SetPropertyStr(ctx->context, global_obj, "__event", event); // add `context` object - JSValue context = JS_NewObject(js->context); + JSValue context = JS_NewObject(ctx->context); // // TODO: a C function? - // JS_SetPropertyStr(js->context, context, "getRemainingTimeInMillis", NULL); + // JS_SetPropertyStr(ctx->context, context, "getRemainingTimeInMillis", NULL); // TODO: make some of these dynamic and some correct - JS_SetPropertyStr(js->context, context, "functionName", JS_NewString(js->context, "handler")); - JS_SetPropertyStr(js->context, context, "functionVersion", JS_NewString(js->context, ISERE_APP_VERSION)); - JS_SetPropertyStr(js->context, context, "memoryLimitInMB", JS_NewInt32(js->context, 128)); - JS_SetPropertyStr(js->context, context, "logGroupName", JS_NewString(js->context, ISERE_APP_NAME)); - JS_SetPropertyStr(js->context, context, "logStreamName", JS_NewString(js->context, ISERE_APP_NAME)); + JS_SetPropertyStr(ctx->context, context, "functionName", JS_NewString(ctx->context, "handler")); + JS_SetPropertyStr(ctx->context, context, "functionVersion", JS_NewString(ctx->context, ISERE_APP_VERSION)); + JS_SetPropertyStr(ctx->context, context, "memoryLimitInMB", JS_NewInt32(ctx->context, 128)); + JS_SetPropertyStr(ctx->context, context, "logGroupName", JS_NewString(ctx->context, ISERE_APP_NAME)); + JS_SetPropertyStr(ctx->context, context, "logStreamName", JS_NewString(ctx->context, ISERE_APP_NAME)); // TODO: callbackWaitsForEmptyEventLoop - JS_SetPropertyStr(js->context, context, "callbackWaitsForEmptyEventLoop", JS_NewBool(js->context, 1)); - JS_SetPropertyStr(js->context, global_obj, "__context", context); + JS_SetPropertyStr(ctx->context, context, "callbackWaitsForEmptyEventLoop", JS_NewBool(ctx->context, 1)); + JS_SetPropertyStr(ctx->context, global_obj, "__context", context); - JS_FreeValue(js->context, global_obj); + JS_FreeValue(ctx->context, global_obj); } // evaluate handler function // TODO: make this async - if (isere_js_eval(js, isere->loader->fn, isere->loader->fn_size) < 0) { + if (isere_js_eval(ctx, isere->loader->fn, isere->loader->fn_size) < 0) { // TODO: this should be logged } diff --git a/src/httpd.c b/src/httpd.c index 0a88472..64e4fb8 100644 --- a/src/httpd.c +++ b/src/httpd.c @@ -207,19 +207,19 @@ int isere_httpd_init(isere_t *isere, isere_httpd_t *httpd, httpd_handler_t *hand } // start web server task - if (xTaskCreate(__httpd_server_task, "httpd_server", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &__httpd_server_task_handle) != pdPASS) { + if (xTaskCreate(__httpd_server_task, "httpd_server", ISERE_HTTPD_SERVER_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &__httpd_server_task_handle) != pdPASS) { isere->logger->error(ISERE_HTTPD_LOG_TAG, "Unable to create httpd server task"); return -1; } // start processor task - if (xTaskCreate(__httpd_parser_task, "httpd_parser", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &__httpd_process_task_handle) != pdPASS) { + if (xTaskCreate(__httpd_parser_task, "httpd_parser", ISERE_HTTPD_SERVER_PARSER_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &__httpd_process_task_handle) != pdPASS) { isere->logger->error(ISERE_HTTPD_LOG_TAG, "Unable to create httpd parser task"); return -1; } // start poller task - if (xTaskCreate(__httpd_poller_task, "httpd_poller", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &__httpd_poll_task_handle) != pdPASS) { + if (xTaskCreate(__httpd_poller_task, "httpd_poller", ISERE_HTTPD_SERVER_POLLER_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &__httpd_poll_task_handle) != pdPASS) { isere->logger->error(ISERE_HTTPD_LOG_TAG, "Unable to create httpd poller task"); return -1; } @@ -239,7 +239,7 @@ int isere_httpd_deinit(isere_httpd_t *httpd) __httpd_cleanup_conn(conn); } - isere_tcp_socket_close(&__server_socket); + isere_tcp_close(&__server_socket); // stop httpd tasks should_exit = 1; @@ -261,10 +261,10 @@ static httpd_conn_t *__httpd_get_free_slot() static void __httpd_cleanup_conn(httpd_conn_t *conn) { llhttp_reset(&conn->llhttp); - isere_js_deinit(&conn->js); + isere_js_free_context(&conn->js); // cleanup client socket - isere_tcp_socket_close(conn->socket); + isere_tcp_close(conn->socket); memset(conn, 0, sizeof(httpd_conn_t)); conn->socket = NULL; conn->recvd = 0; @@ -356,6 +356,11 @@ static void __httpd_parser_task(void *param) continue; } + // close error connections + if (conn->socket->revents & TCP_POLL_ERROR_READY) { + goto finally; + } + // ignore connections with no incoming data if (!(conn->socket->revents & TCP_POLL_READ_READY)) { continue; @@ -447,7 +452,7 @@ static void __httpd_server_task(void *params) httpd_conn_t *conn = __httpd_get_free_slot(); if (conn == NULL) { - isere_tcp_socket_close(newsock); + isere_tcp_close(newsock); continue; } @@ -468,7 +473,7 @@ static void __httpd_server_task(void *params) conn->llhttp.data = (void *)conn; if (__httpd_handler != NULL) { - isere_js_init(&conn->js); + isere_js_new_context(__isere->js, &conn->js); } conn->socket = newsock; @@ -482,63 +487,63 @@ static void __httpd_server_task(void *params) static void __httpd_writeback(httpd_conn_t *conn) { - isere_js_t *js = &conn->js; + isere_js_context_t *ctx = &conn->js; - JS_FreeValue(js->context, js->future); + JS_FreeValue(ctx->context, ctx->future); - JSValue global_obj = JS_GetGlobalObject(js->context); - JSValue response_obj = JS_GetPropertyStr(js->context, global_obj, ISERE_JS_HANDLER_FUNCTION_RESPONSE_OBJ_NAME); + JSValue global_obj = JS_GetGlobalObject(ctx->context); + JSValue response_obj = JS_GetPropertyStr(ctx->context, global_obj, ISERE_JS_HANDLER_FUNCTION_RESPONSE_OBJ_NAME); // send HTTP response code isere_tcp_write(conn->socket, "HTTP/1.1 ", 9); - JSValue statusCode = JS_GetPropertyStr(js->context, response_obj, ISERE_JS_RESPONSE_STATUS_CODE_PROP_NAME); + JSValue statusCode = JS_GetPropertyStr(ctx->context, response_obj, ISERE_JS_RESPONSE_STATUS_CODE_PROP_NAME); if (!JS_IsNumber(statusCode)) { isere_tcp_write(conn->socket, "200 OK", 3); } else { size_t len = 0; - const char *statusCode1 = JS_ToCStringLen(js->context, &len, statusCode); + const char *statusCode1 = JS_ToCStringLen(ctx->context, &len, statusCode); isere_tcp_write(conn->socket, statusCode1, len); // TODO: status code to status text - JS_FreeCString(js->context, statusCode1); + JS_FreeCString(ctx->context, statusCode1); } isere_tcp_write(conn->socket, "\r\n", 2); - JS_FreeValue(js->context, statusCode); + JS_FreeValue(ctx->context, statusCode); // send HTTP response headers - JSValue headers = JS_GetPropertyStr(js->context, response_obj, ISERE_JS_RESPONSE_HEADERS_PROP_NAME); + JSValue headers = JS_GetPropertyStr(ctx->context, response_obj, ISERE_JS_RESPONSE_HEADERS_PROP_NAME); if (JS_IsObject(headers)) { JSPropertyEnum *props = NULL; uint32_t props_len = 0; - if (JS_GetOwnPropertyNames(js->context, &props, &props_len, headers, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) == 0) { + if (JS_GetOwnPropertyNames(ctx->context, &props, &props_len, headers, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) == 0) { for (int i = 0; i < props_len; i++) { - const char *header_field_str = JS_AtomToCString(js->context, props[i].atom); + const char *header_field_str = JS_AtomToCString(ctx->context, props[i].atom); - JSValue header_value = JS_GetProperty(js->context, headers, props[i].atom); + JSValue header_value = JS_GetProperty(ctx->context, headers, props[i].atom); size_t header_value_len = 0; - const char *header_value_str = JS_ToCStringLen(js->context, &header_value_len, header_value); + const char *header_value_str = JS_ToCStringLen(ctx->context, &header_value_len, header_value); isere_tcp_write(conn->socket, header_field_str, strlen(header_field_str)); isere_tcp_write(conn->socket, ": ", 2); isere_tcp_write(conn->socket, header_value_str, header_value_len); isere_tcp_write(conn->socket, "\r\n", 2); - JS_FreeCString(js->context, header_field_str); - JS_FreeCString(js->context, header_value_str); - JS_FreeValue(js->context, header_value); + JS_FreeCString(ctx->context, header_field_str); + JS_FreeCString(ctx->context, header_value_str); + JS_FreeValue(ctx->context, header_value); - JS_FreeAtom(js->context, props[i].atom); + JS_FreeAtom(ctx->context, props[i].atom); } - js_free(js->context, props); + js_free(ctx->context, props); } } - JS_FreeValue(js->context, headers); + JS_FreeValue(ctx->context, headers); // TODO: `Date` const char *server_header = "Server: isere\r\n"; @@ -546,26 +551,26 @@ static void __httpd_writeback(httpd_conn_t *conn) isere_tcp_write(conn->socket, "\r\n", 2); // send HTTP response body - JSValue body = JS_GetPropertyStr(js->context, response_obj, ISERE_JS_RESPONSE_BODY_PROP_NAME); + JSValue body = JS_GetPropertyStr(ctx->context, response_obj, ISERE_JS_RESPONSE_BODY_PROP_NAME); size_t body_len = 0; const char *body_str = NULL; if (JS_IsObject(body)) { - JSValue stringifiedBody = JS_JSONStringify(js->context, body, JS_UNDEFINED, JS_UNDEFINED); - body_str = JS_ToCStringLen(js->context, &body_len, stringifiedBody); - JS_FreeValue(js->context, stringifiedBody); + JSValue stringifiedBody = JS_JSONStringify(ctx->context, body, JS_UNDEFINED, JS_UNDEFINED); + body_str = JS_ToCStringLen(ctx->context, &body_len, stringifiedBody); + JS_FreeValue(ctx->context, stringifiedBody); } else if (JS_IsString(body)) { - body_str = JS_ToCStringLen(js->context, &body_len, body); + body_str = JS_ToCStringLen(ctx->context, &body_len, body); } if (body_str != NULL) { isere_tcp_write(conn->socket, body_str, body_len); - JS_FreeCString(js->context, body_str); + JS_FreeCString(ctx->context, body_str); } - JS_FreeValue(js->context, body); + JS_FreeValue(ctx->context, body); - JS_FreeValue(js->context, response_obj); - JS_FreeValue(js->context, global_obj); + JS_FreeValue(ctx->context, response_obj); + JS_FreeValue(ctx->context, global_obj); isere_tcp_write(conn->socket, "\r\n\r\n", 4); diff --git a/src/js.c b/src/js.c index a643769..668828a 100644 --- a/src/js.c +++ b/src/js.c @@ -7,6 +7,8 @@ #include "quickjs.h" #include "quickjs-libc.h" +static isere_t *__isere = NULL; + static JSValue __logger_internal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, const char *color) { puts(color); @@ -134,84 +136,91 @@ static const JSMallocFunctions __mf = { NULL, }; -int isere_js_init(isere_js_t *js) +int isere_js_init(isere_t *isere, isere_js_t *js) +{ + __isere = isere; + + return 0; +} + +int isere_js_deinit(isere_js_t *js) { - if (js->runtime != NULL || js->context != NULL) { - // __isere->logger->error(ISERE_JS_LOG_TAG, "QuickJS runtime and context already initialized"); + return 0; +} + +int isere_js_new_context(isere_js_t *js, isere_js_context_t *ctx) +{ + if (ctx->runtime != NULL || ctx->context != NULL) { + __isere->logger->error(ISERE_JS_LOG_TAG, "QuickJS runtime or context already initialized"); return -1; } // initialize quickjs runtime - // js->runtime = JS_NewRuntime(); - js->runtime = JS_NewRuntime2(&__mf, NULL); - if (js->runtime == NULL) + // ctx->runtime = JS_NewRuntime(); + ctx->runtime = JS_NewRuntime2(&__mf, NULL); + if (ctx->runtime == NULL) { - // __isere->logger->error(ISERE_JS_LOG_TAG, "failed to create QuickJS runtime"); + __isere->logger->error(ISERE_JS_LOG_TAG, "failed to create QuickJS runtime"); return -1; } + // TODO: custom memory allocation with JS_NewRuntime2() // TODO: set global memory limit with JS_SetMemoryLimit() - JS_SetMaxStackSize(js->runtime, ISERE_JS_STACK_SIZE); + // JS_SetMaxStackSize(js->runtime, ISERE_JS_STACK_SIZE); // initialize quickjs context - js->context = JS_NewContextRaw(js->runtime); - if (js->context == NULL) + ctx->context = JS_NewContextRaw(ctx->runtime); + if (ctx->context == NULL) { - // __isere->logger->error(ISERE_JS_LOG_TAG, "failed to create QuickJS context"); + __isere->logger->error(ISERE_JS_LOG_TAG, "failed to create QuickJS context"); return -1; } - // attach isere_js_t object to QuickJS context - JS_SetContextOpaque(js->context, js); - - JS_AddIntrinsicBaseObjects(js->context); - JS_AddIntrinsicDate(js->context); - JS_AddIntrinsicEval(js->context); - JS_AddIntrinsicStringNormalize(js->context); - JS_AddIntrinsicRegExp(js->context); - JS_AddIntrinsicJSON(js->context); - JS_AddIntrinsicProxy(js->context); - JS_AddIntrinsicMapSet(js->context); - JS_AddIntrinsicTypedArrays(js->context); - JS_AddIntrinsicPromise(js->context); - JS_AddIntrinsicBigInt(js->context); - - JSValue global_obj = JS_GetGlobalObject(js->context); + // attach isere_js_context_t object to QuickJS context + JS_SetContextOpaque(ctx->context, ctx); + + JS_AddIntrinsicBaseObjects(ctx->context); + JS_AddIntrinsicDate(ctx->context); + JS_AddIntrinsicEval(ctx->context); + JS_AddIntrinsicJSON(ctx->context); + JS_AddIntrinsicPromise(ctx->context); + + JSValue global_obj = JS_GetGlobalObject(ctx->context); // add console.log(), console.warn(), and console.error() function - JSValue console = JS_NewObject(js->context); - JS_SetPropertyStr(js->context, console, "log", JS_NewCFunction(js->context, __console_log, "log", 1)); - JS_SetPropertyStr(js->context, console, "warn", JS_NewCFunction(js->context, __console_warn, "warn", 1)); - JS_SetPropertyStr(js->context, console, "error", JS_NewCFunction(js->context, __console_error, "error", 1)); - JS_SetPropertyStr(js->context, global_obj, "console", console); + JSValue console = JS_NewObject(ctx->context); + JS_SetPropertyStr(ctx->context, console, "log", JS_NewCFunction(ctx->context, __console_log, "log", 1)); + JS_SetPropertyStr(ctx->context, console, "warn", JS_NewCFunction(ctx->context, __console_warn, "warn", 1)); + JS_SetPropertyStr(ctx->context, console, "error", JS_NewCFunction(ctx->context, __console_error, "error", 1)); + JS_SetPropertyStr(ctx->context, global_obj, "console", console); // TODO: environment variables // add process.env - JSValue process = JS_NewObject(js->context); - JSValue env = JS_NewObject(js->context); - JS_SetPropertyStr(js->context, env, "NODE_ENV", JS_NewString(js->context, "production")); - JS_SetPropertyStr(js->context, process, "env", env); - JS_SetPropertyStr(js->context, global_obj, "process", process); + JSValue process = JS_NewObject(ctx->context); + JSValue env = JS_NewObject(ctx->context); + JS_SetPropertyStr(ctx->context, env, "NODE_ENV", JS_NewString(ctx->context, "production")); + JS_SetPropertyStr(ctx->context, process, "env", env); + JS_SetPropertyStr(ctx->context, global_obj, "process", process); // add setTimeout / clearTimeout - isere_js_polyfill_timer_init(js); - // isere_js_polyfill_fetch_init(js->context); + isere_js_polyfill_timer_init(ctx); + // isere_js_polyfill_fetch_init(ctx->context); - JS_FreeValue(js->context, global_obj); + JS_FreeValue(ctx->context, global_obj); return 0; } -int isere_js_deinit(isere_js_t *js) +int isere_js_free_context(isere_js_context_t *ctx) { - if (js->context) { - isere_js_polyfill_timer_deinit(js); - // isere_js_polyfill_fetch_deinit(js->context); + if (ctx->context) { + isere_js_polyfill_timer_deinit(ctx); + // isere_js_polyfill_fetch_deinit(ctx->context); - JS_FreeContext(js->context); + JS_FreeContext(ctx->context); } - if (js->runtime) { - JS_FreeRuntime(js->runtime); + if (ctx->runtime) { + JS_FreeRuntime(ctx->runtime); } return 0; @@ -247,51 +256,51 @@ static JSValue __handler_cb(JSContext *ctx, JSValueConst this_val, int argc, JSV return JS_UNDEFINED; } -int isere_js_eval(isere_js_t *js, unsigned char *handler, unsigned int handler_len) +int isere_js_eval(isere_js_context_t *ctx, unsigned char *handler, unsigned int handler_len) { - JSValue global_obj = JS_GetGlobalObject(js->context); + JSValue global_obj = JS_GetGlobalObject(ctx->context); // add callback for getting the result - JS_SetPropertyStr(js->context, global_obj, "cb", JS_NewCFunction(js->context, __handler_cb, "cb", 1)); - JS_FreeValue(js->context, global_obj); + JS_SetPropertyStr(ctx->context, global_obj, "cb", JS_NewCFunction(ctx->context, __handler_cb, "cb", 1)); + JS_FreeValue(ctx->context, global_obj); - JSValue h = JS_Eval(js->context, (const char *)handler, handler_len, "handler", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + JSValue h = JS_Eval(ctx->context, (const char *)handler, handler_len, "handler", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); if (JS_IsException(h)) { // TODO: error goes to logger - js_std_dump_error(js->context); - JS_FreeValue(js->context, h); + js_std_dump_error(ctx->context); + JS_FreeValue(ctx->context, h); return -1; } - js_module_set_import_meta(js->context, h, 0, 0); - JS_FreeValue(js->context, h); + js_module_set_import_meta(ctx->context, h, 0, 0); + JS_FreeValue(ctx->context, h); const char *eval = "import { handler } from 'handler';" "const handler1 = new Promise(resolve => handler(__event, __context, resolve).then(resolve));" "Promise.resolve(handler1).then(cb)"; - js->future = JS_Eval(js->context, eval, strlen(eval), "", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_BACKTRACE_BARRIER); - if (JS_IsException(js->future)) { + ctx->future = JS_Eval(ctx->context, eval, strlen(eval), "", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_BACKTRACE_BARRIER); + if (JS_IsException(ctx->future)) { // TODO: error goes to logger - js_std_dump_error(js->context); + js_std_dump_error(ctx->context); return -1; } return 0; } -int isere_js_poll(isere_js_t *js) +int isere_js_poll(isere_js_context_t *ctx) { JSContext *ctx1; int err; int tmrerr = 0; // execute the pending timers - tmrerr = isere_js_polyfill_timer_poll(js); + tmrerr = isere_js_polyfill_timer_poll(ctx); // execute the pending jobs - err = JS_ExecutePendingJob(JS_GetRuntime(js->context), &ctx1); + err = JS_ExecutePendingJob(JS_GetRuntime(ctx->context), &ctx1); if (err < 0) { js_std_dump_error(ctx1); } diff --git a/src/main.c b/src/main.c index ca3eb33..9dbc309 100644 --- a/src/main.c +++ b/src/main.c @@ -25,12 +25,14 @@ void sigint(int dummy) { isere_httpd_deinit(isere.httpd); isere_tcp_deinit(isere.tcp); + isere_js_deinit(isere.js); isere_loader_deinit(isere.loader); // fs_deinit(isere.fs); // isere_ini_deinit(isere.ini); isere_logger_deinit(isere.logger); vTaskEndScheduler(); + exit(EXIT_SUCCESS); } #endif @@ -49,7 +51,7 @@ int main(void) return EXIT_FAILURE; } isere.logger = &logger; - + // // initialize file system module // isere_fs_t fs; // memset(&fs, 0, sizeof(isere_fs_t)); @@ -72,7 +74,7 @@ int main(void) // logger.info(ISERE_LOG_TAG, " timeout: %d", isere_ini_get_timeout(&ini)); // logger.info(ISERE_LOG_TAG, "===================================="); - // dynamically loading javascript serverless handler + // load handler.js isere_loader_t loader; memset(&loader, 0, sizeof(isere_loader_t)); if (isere_loader_init(&isere, &loader) < 0) { @@ -81,6 +83,15 @@ int main(void) } isere.loader = &loader; + // initialize js module + isere_js_t js; + memset(&js, 0, sizeof(isere_js_t)); + if (isere_js_init(&isere, &js) < 0) { + logger.error(ISERE_LOG_TAG, "Unable to initialize js module"); + return EXIT_FAILURE; + } + isere.js = &js; + // initialize tcp module isere_tcp_t tcp; memset(&tcp, 0, sizeof(isere_tcp_t)); @@ -93,8 +104,11 @@ int main(void) // initialize web server module isere_httpd_t httpd; memset(&httpd, 0, sizeof(isere_httpd_t)); +#ifdef ISERE_WITH_QUICKJS if (isere_httpd_init(&isere, &httpd, &__http_handler) < 0) { - // if (isere_httpd_init(&isere, &httpd, NULL) < 0) { +#else + if (isere_httpd_init(&isere, &httpd, NULL) < 0) { +#endif /* ISERE_WITH_QUICKJS */ logger.error(ISERE_LOG_TAG, "Unable to initialize httpd module"); return EXIT_FAILURE; } diff --git a/src/polyfills/fetch.c b/src/polyfills/fetch.c index 5b6fb12..929d57e 100644 --- a/src/polyfills/fetch.c +++ b/src/polyfills/fetch.c @@ -16,7 +16,7 @@ static JSValue __polyfill_fetch_internal(JSContext *ctx, JSValueConst this_val, return JS_TRUE; } -void isere_js_polyfill_fetch_init(isere_js_t *js) +void isere_js_polyfill_fetch_init(isere_js_context_t *ctx) { /* Resource object @@ -34,9 +34,9 @@ void isere_js_polyfill_fetch_init(isere_js_t *js) ``` */ - JSValue global_obj = JS_GetGlobalObject(js->context); - JS_SetPropertyStr(js->context, global_obj, "__fetch", JS_NewCFunction(js->context, __polyfill_fetch_internal, "__fetch", 1)); - JS_FreeValue(js->context, global_obj); + JSValue global_obj = JS_GetGlobalObject(ctx->context); + JS_SetPropertyStr(ctx->context, global_obj, "__fetch", JS_NewCFunction(ctx->context, __polyfill_fetch_internal, "__fetch", 1)); + JS_FreeValue(ctx->context, global_obj); // TODO: support standard Fetch Request object (https://developer.mozilla.org/en-US/docs/Web/API/Request) const char *str = "globalThis.fetch = async function(resource) {\n" @@ -50,17 +50,17 @@ void isere_js_polyfill_fetch_init(isere_js_t *js) " return globalThis.__fetch(url, method);\n" "};"; - JS_Eval(js->context, str, strlen(str), "", JS_EVAL_TYPE_MODULE); + JS_Eval(ctx->context, str, strlen(str), "", JS_EVAL_TYPE_MODULE); } -void isere_js_polyfill_fetch_deinit(isere_js_t *js) +void isere_js_polyfill_fetch_deinit(isere_js_context_t *ctx) { - JSValue global_obj = JS_GetGlobalObject(js->context); - JS_DeleteProperty(js->context, global_obj, JS_NewAtom(js->context, "__fetch"), 0); - JS_FreeValue(js->context, global_obj); + JSValue global_obj = JS_GetGlobalObject(ctx->context); + JS_DeleteProperty(ctx->context, global_obj, JS_NewAtom(ctx->context, "__fetch"), 0); + JS_FreeValue(ctx->context, global_obj); } -int isere_js_polyfill_fetch_poll(isere_js_t *js) +int isere_js_polyfill_fetch_poll(isere_js_context_t *ctx) { return 1; } diff --git a/src/polyfills/timer.c b/src/polyfills/timer.c index a0b86cd..eb99547 100644 --- a/src/polyfills/timer.c +++ b/src/polyfills/timer.c @@ -61,7 +61,7 @@ JSValue polyfill_timer_setTimeout(JSContext *ctx, JSValueConst this_val, int arg return obj; } - isere_js_t *js = (isere_js_t *)JS_GetContextOpaque(ctx); + isere_js_context_t *js = (isere_js_context_t *)JS_GetContextOpaque(ctx); polyfill_timer_t *tmr = __polyfill_timer_get_free_slot(js->timers); if (!tmr) { JS_FreeValue(ctx, obj); @@ -99,26 +99,26 @@ JSValue polyfill_timer_clearTimeout(JSContext *ctx, JSValueConst this_val, int a return JS_UNDEFINED; } -void isere_js_polyfill_timer_init(isere_js_t *js) +void isere_js_polyfill_timer_init(isere_js_context_t *ctx) { - JSValue global_obj = JS_GetGlobalObject(js->context); - JS_SetPropertyStr(js->context, global_obj, "setTimeout", JS_NewCFunction(js->context, polyfill_timer_setTimeout, "setTimeout", 2)); - JS_SetPropertyStr(js->context, global_obj, "clearTimeout", JS_NewCFunction(js->context, polyfill_timer_clearTimeout, "clearTimeout", 1)); - JS_FreeValue(js->context, global_obj); + JSValue global_obj = JS_GetGlobalObject(ctx->context); + JS_SetPropertyStr(ctx->context, global_obj, "setTimeout", JS_NewCFunction(ctx->context, polyfill_timer_setTimeout, "setTimeout", 2)); + JS_SetPropertyStr(ctx->context, global_obj, "clearTimeout", JS_NewCFunction(ctx->context, polyfill_timer_clearTimeout, "clearTimeout", 1)); + JS_FreeValue(ctx->context, global_obj); for (int i = 0; i < ISERE_JS_POLYFILLS_MAX_TIMERS; i++) { - polyfill_timer_t *tmr = &js->timers[i]; + polyfill_timer_t *tmr = &ctx->timers[i]; memset(tmr, 0, sizeof(polyfill_timer_t)); tmr->timer = NULL; - tmr->ctx = js->context; + tmr->ctx = ctx->context; tmr->func = JS_UNDEFINED; } } -void isere_js_polyfill_timer_deinit(isere_js_t *js) +void isere_js_polyfill_timer_deinit(isere_js_context_t *ctx) { for (int i = 0; i < ISERE_JS_POLYFILLS_MAX_TIMERS; i++) { - polyfill_timer_t *tmr = &js->timers[i]; + polyfill_timer_t *tmr = &ctx->timers[i]; if (tmr->timer != NULL) { TimerHandle_t timer = tmr->timer; @@ -132,23 +132,23 @@ void isere_js_polyfill_timer_deinit(isere_js_t *js) JS_FreeValue(ctx, func); } - JSValue global_obj = JS_GetGlobalObject(js->context); + JSValue global_obj = JS_GetGlobalObject(ctx->context); - JSAtom setTimeout = JS_NewAtom(js->context, "setTimeout"); - JS_DeleteProperty(js->context, global_obj, setTimeout, 0); - JS_FreeAtom(js->context, setTimeout); + JSAtom setTimeout = JS_NewAtom(ctx->context, "setTimeout"); + JS_DeleteProperty(ctx->context, global_obj, setTimeout, 0); + JS_FreeAtom(ctx->context, setTimeout); - JSAtom clearTimeout = JS_NewAtom(js->context, "clearTimeout"); - JS_DeleteProperty(js->context, global_obj, clearTimeout, 0); - JS_FreeAtom(js->context, clearTimeout); + JSAtom clearTimeout = JS_NewAtom(ctx->context, "clearTimeout"); + JS_DeleteProperty(ctx->context, global_obj, clearTimeout, 0); + JS_FreeAtom(ctx->context, clearTimeout); - JS_FreeValue(js->context, global_obj); + JS_FreeValue(ctx->context, global_obj); } -int isere_js_polyfill_timer_poll(isere_js_t *js) +int isere_js_polyfill_timer_poll(isere_js_context_t *ctx) { for (int i = 0; i < ISERE_JS_POLYFILLS_MAX_TIMERS; i++) { - polyfill_timer_t *tmr = &js->timers[i]; + polyfill_timer_t *tmr = &ctx->timers[i]; TimerHandle_t timer = tmr->timer; if (timer != NULL && xTimerIsTimerActive(timer)) { return 1; diff --git a/unittests.cmake b/tests/CMakeLists.txt similarity index 93% rename from unittests.cmake rename to tests/CMakeLists.txt index 4d38487..fe08a94 100644 --- a/unittests.cmake +++ b/tests/CMakeLists.txt @@ -1,11 +1,11 @@ if (DEFINED ENV{CPPUTEST_HOME} AND NOT "$ENV{CPPUTEST_HOME}" STREQUAL "") add_custom_command( OUTPUT handler_for_test.c - COMMAND xxd -i ../tests/handler.js handler_for_test.c + COMMAND xxd -i ../tests/handler_for_test.js handler_for_test.c COMMAND sed -i.bak -E "s/[a-z_]+\\[\\]/handler\\[\\]/" handler_for_test.c # change code array variable name to "handler" COMMAND sed -i.bak -E "s/[a-z_]+_len/handler_len/" handler_for_test.c # change code length variable name to "handler_len" COMMENT "Compiling JavaScript files for unit tests" - MAIN_DEPENDENCY ./tests/handler.js + MAIN_DEPENDENCY ./tests/handler_for_test.js VERBATIM ) diff --git a/tests/handler.js b/tests/handler_for_test.js similarity index 100% rename from tests/handler.js rename to tests/handler_for_test.js diff --git a/tests/js_test.cpp b/tests/js_test.cpp index d88af4a..aaca112 100644 --- a/tests/js_test.cpp +++ b/tests/js_test.cpp @@ -7,39 +7,73 @@ TEST_GROUP(JSTest) {}; +static void fake_logger_fn(const char *tag, const char *fmt, ...) {} + +static isere_logger_t fake_logger = { + .error = fake_logger_fn, + .warning = fake_logger_fn, + .info = fake_logger_fn, + .debug = fake_logger_fn, +}; + TEST(JSTest, ShouldReturnErrorWhenQuickJSRuntimeIsAlreadyInitialized) { + isere_t isere; + memset(&isere, 0, sizeof(isere_t)); + isere.logger = &fake_logger; + isere_js_t js; memset(&js, 0, sizeof(isere_js_t)); - js.runtime = (JSRuntime *)malloc(1); + isere_js_init(&isere, &js); - int ret = isere_js_init(&js); + isere_js_context_t ctx; + memset(&ctx, 0, sizeof(isere_js_context_t)); + ctx.runtime = (JSRuntime *)malloc(1); - free(js.runtime); + int ret = isere_js_new_context(&js, &ctx); - LONGS_EQUAL_TEXT(ret, -1, "isere_js_init() did not return -1 when runtime is already initialized"); + free(ctx.runtime); + + LONGS_EQUAL_TEXT(ret, -1, "isere_js_new_context() did not return -1 when runtime is already initialized"); } TEST(JSTest, ShouldReturnErrorWhenQuickJSContextIsAlreadyInitialized) { + isere_t isere; + memset(&isere, 0, sizeof(isere_t)); + isere.logger = &fake_logger; + isere_js_t js; memset(&js, 0, sizeof(isere_js_t)); - js.context = (JSContext *)malloc(1); + isere_js_init(&isere, &js); - int ret = isere_js_init(&js); + isere_js_context_t ctx; + memset(&ctx, 0, sizeof(isere_js_context_t)); + ctx.context = (JSContext *)malloc(1); - free(js.context); + int ret = isere_js_new_context(&js, &ctx); - LONGS_EQUAL_TEXT(ret, -1, "isere_js_init() did not return -1 when context is already initialized"); + free(ctx.context); + + LONGS_EQUAL_TEXT(ret, -1, "isere_js_new_context() did not return -1 when context is already initialized"); } TEST(JSTest, ShouldInitializeSuccessfully) { + isere_t isere; + memset(&isere, 0, sizeof(isere_t)); + isere_js_t js; memset(&js, 0, sizeof(isere_js_t)); + isere_js_init(&isere, &js); + + isere_js_context_t ctx; + memset(&ctx, 0, sizeof(isere_js_context_t)); + + int ret = isere_js_new_context(&js, &ctx); + isere_js_free_context(&ctx); - int ret = isere_js_init(&js); isere_js_deinit(&js); - LONGS_EQUAL_TEXT(ret, 0, "isere_js_init() did not return 0"); + LONGS_EQUAL_TEXT(ret, 0, "isere_js_new_context() did not return 0"); }