├── .gitignore ├── .vscode ├── launch.json ├── launch.json-fast ├── launch.json-picoprobe └── settings.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── adi.c ├── adi.h ├── breakpoint.c ├── breakpoint.h ├── c2.c ├── cmdline.c ├── cmdline.h ├── config ├── config.c ├── config.cf ├── config.h └── gen.pl ├── delme └── circ.h ├── filegen ├── files ├── rp2040_features.xml ├── rp2040_memory_map.xml └── rp2040_threads.xml ├── flash.c ├── flash.h ├── gdb.c ├── gdb.h ├── id_usb.c ├── lwipopts.h ├── main.c ├── modules ├── lerp_circ.orig │ ├── CMakeLists.txt │ ├── circ.c │ └── include │ │ └── lerp │ │ └── circ.h ├── lerp_circ │ ├── CMakeLists.txt │ ├── circ.c │ └── include │ │ └── lerp │ │ └── circ.h ├── lerp_debug │ ├── CMakeLists.txt │ ├── debug.c │ └── include │ │ └── lerp │ │ └── debug.h ├── lerp_flash │ ├── CMakeLists.txt │ ├── flash.c │ └── include │ │ └── lerp │ │ └── flash.h ├── lerp_interact │ ├── CMakeLists.txt │ ├── include │ │ └── lerp │ │ │ └── interact.h │ └── interact.c ├── lerp_io │ ├── CMakeLists.txt │ ├── include │ │ └── lerp │ │ │ └── io.h │ └── io.c ├── lerp_task │ ├── CMakeLists.txt │ ├── include │ │ └── lerp │ │ │ └── task.h │ ├── list.h │ ├── plat_rp2040.c │ └── task.c └── lerp_tokeniser │ ├── CMakeLists.txt │ ├── include │ └── lerp │ │ └── tokeniser.h │ └── tokeniser.c ├── pico_extras_import.cmake ├── pico_sdk_import.cmake ├── swd.c ├── swd.h ├── swd.pio ├── tusb_config.h ├── uart.c ├── uart.h ├── utils.c ├── utils.h ├── wifi.c └── wifi.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # MAIN STUFF FOR PROJECT 55 | /build/ 56 | /.vscode/*.state.json 57 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Pico Debug", 6 | "cwd": "${workspaceRoot}", 7 | "executable": "${command:cmake.launchTargetPath}", 8 | "request": "launch", 9 | "type": "cortex-debug", 10 | "servertype": "openocd", 11 | // This may need to be arm-none-eabi-gdb depending on your system 12 | "gdbPath" : "gdb-multiarch", 13 | "device": "RP2040", 14 | "configFiles": [ 15 | "interface/picoprobe.cfg", 16 | "target/rp2040.cfg" 17 | ], 18 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 19 | "runToEntryPoint": "main", 20 | // Work around for stopping at main on restart 21 | "postRestartCommands": [ 22 | "break main", 23 | "continue" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/launch.json-fast: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Pico Debug", 6 | "cwd": "${workspaceRoot}", 7 | "executable": "${command:cmake.launchTargetPath}", 8 | "request": "launch", 9 | "type": "cortex-debug", 10 | "servertype": "external", 11 | // This may need to be arm-none-eabi-gdb depending on your system 12 | "gdbPath" : "gdb-multiarch", 13 | "gdbTarget" : "/dev/ttyACM2", 14 | "device": "RP2040", 15 | "configFiles": [ 16 | "interface/picoprobe.cfg", 17 | "target/rp2040.cfg" 18 | ], 19 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 20 | // runToEntryPoint causes differences in behavour between launch and reset 21 | // so best avoided for this use case. 22 | //"runToEntryPoint": "main", 23 | 24 | // breakAfterReset means it consistantly stops in the reset handler code 25 | // so we can follow that with some commands to get us to main 26 | "breakAfterReset": true, 27 | 28 | // get_to_main puts a breakpoint at main, gets there, and then remove is 29 | // immediately after flashing. This means that by the time any ram based 30 | // breakpoints are applied the relevant stuff is in RAM. 31 | "postLaunchCommands": [ 32 | "break main", "continue", "clear main", 33 | // "monitor get_to_main" 34 | ], 35 | // With breakAfterReset we have a consistent approach so can use the same 36 | // commands to get us to main after a restart... 37 | "postRestartCommands": [ 38 | // "monitor get_to_main" 39 | "break main", 40 | "continue", 41 | "clear main" 42 | ] 43 | 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /.vscode/launch.json-picoprobe: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Pico Debug", 6 | "cwd": "${workspaceRoot}", 7 | "executable": "${command:cmake.launchTargetPath}", 8 | "request": "launch", 9 | "type": "cortex-debug", 10 | "servertype": "openocd", 11 | // This may need to be arm-none-eabi-gdb depending on your system 12 | "gdbPath" : "gdb-multiarch", 13 | "device": "RP2040", 14 | "configFiles": [ 15 | "interface/picoprobe.cfg", 16 | "target/rp2040.cfg" 17 | ], 18 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 19 | "runToEntryPoint": "main", 20 | // Work around for stopping at main on restart 21 | "postRestartCommands": [ 22 | "break main", 23 | "continue" 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // These settings tweaks to the cmake plugin will ensure 3 | // that you debug using cortex-debug instead of trying to launch 4 | // a Pico binary on the host 5 | "cmake.statusbar.advanced": { 6 | "debug": { 7 | "visibility": "hidden" 8 | }, 9 | "launch": { 10 | "visibility": "hidden" 11 | }, 12 | "build": { 13 | "visibility": "hidden" 14 | }, 15 | "buildTarget": { 16 | "visibility": "hidden" 17 | } 18 | }, 19 | "cmake.buildBeforeRun": true, 20 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 21 | "files.associations": { 22 | "cctype": "c", 23 | "config.h": "c", 24 | "cmdline.h": "c", 25 | "tokeniser.h": "c", 26 | "*.tcc": "c", 27 | "cstdio": "c", 28 | "ctype.h": "c", 29 | "i2c.h": "c", 30 | "string.h": "c", 31 | "rmii_config.h": "c", 32 | "conn.h": "c", 33 | "ssi.h": "c", 34 | "flash.h": "c" 35 | }, 36 | "cortex-debug.variableUseNaturalFormat": false, 37 | "cortex-debug.registerUseNaturalFormat": false 38 | } 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | include(pico_sdk_import.cmake) 4 | ##include(pico_extras_import.cmake) 5 | 6 | project(pico-debug_project C CXX ASM) 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(FAMILY rp2040) 10 | #set(BOARD pico_sdk) 11 | set(PICO_BOARD pico_w) 12 | set(TINYUSB_FAMILY_PROJECT_NAME_PREFIX "tinyusb_dev_") 13 | 14 | pico_sdk_init() 15 | 16 | add_executable(pico-debug 17 | main.c 18 | 19 | swd.c swd.h swd.pio 20 | adi.c adi.h 21 | 22 | id_usb.c 23 | tusb_config.h 24 | lwipopts.h 25 | 26 | flash.c flash.h 27 | uart.c uart.h 28 | wifi.c wifi.h 29 | 30 | gdb.c gdb.h 31 | breakpoint.c breakpoint.h 32 | 33 | cmdline.c cmdline.h 34 | utils.c utils.h 35 | 36 | config/config.h 37 | config/config.c 38 | 39 | filedata.c filedata.h 40 | 41 | files/rp2040_features.xml 42 | files/rp2040_memory_map.xml 43 | files/rp2040_threads.xml 44 | ) 45 | 46 | # 47 | # Now make sure we can generate the file data 48 | # 49 | file (GLOB files ${CMAKE_CURRENT_LIST_DIR}/files/* ) 50 | 51 | add_custom_target(fgen DEPENDS filedata.c filedata.h) 52 | add_custom_command(OUTPUT filedata.c filedata.h 53 | DEPENDS ${files} 54 | COMMAND ${CMAKE_CURRENT_LIST_DIR}/filegen ${CMAKE_CURRENT_LIST_DIR}/files filedata.c filedata.h 55 | ) 56 | set_source_files_properties(filedata.c filedata.h PROPERTIES 57 | GENERATED TRUE) 58 | add_dependencies(pico-debug fgen) 59 | 60 | 61 | pico_generate_pio_header(pico-debug ${CMAKE_CURRENT_LIST_DIR}/swd.pio) 62 | 63 | # 64 | # Make sure TinyUSB can find tusb_config.h 65 | # 66 | target_include_directories(pico-debug PUBLIC 67 | ${CMAKE_CURRENT_LIST_DIR}) 68 | 69 | 70 | # Add all of our modules 71 | add_subdirectory(modules/lerp_task) 72 | add_subdirectory(modules/lerp_circ) 73 | add_subdirectory(modules/lerp_debug) 74 | add_subdirectory(modules/lerp_io) 75 | add_subdirectory(modules/lerp_interact) 76 | add_subdirectory(modules/lerp_flash) 77 | add_subdirectory(modules/lerp_tokeniser) 78 | 79 | target_compile_definitions(pico-debug PRIVATE 80 | # We don't want the default alarm pool (and the IRQ's associated!) 81 | PICO_TIME_DEFAULT_ALARM_POOL_DISABLED=1 82 | 83 | # Main pins for the SWD connections 84 | PIN_SWDCLK=2 85 | PIN_SWDIO=3 86 | 87 | # Pins for the UART connection 88 | PIN_UART_TX=4 89 | PIN_UART_RX=5 90 | UART_INTERFACE=uart1 91 | UART_BAUD=115200 92 | 93 | # Which CDC port to use for GDB 94 | GDB_CDC=0 95 | GDB_TCP=3333 96 | 97 | # Which CDC port to use for UART 98 | UART_CDC=1 99 | UART_TCP=3334 100 | 101 | # Which CDC port to use for CMD interface 102 | CMD_CDC=2 103 | CMD_TCP=3335 104 | 105 | # Debug on CDC2 106 | # DEBUG_CDC=2 107 | DEBUG_BUF=1 108 | ) 109 | 110 | target_compile_options(pico-debug PUBLIC 111 | -Wall 112 | ) 113 | 114 | 115 | #pico_enable_stdio_usb(pico-debug 0) 116 | #pico_enable_stdio_uart(pico-debug 0) 117 | pico_add_extra_outputs(pico-debug) 118 | 119 | #pico_generate_pio_header(pico-debug ${CMAKE_CURRENT_LIST_DIR}/gpib/gpib.pio) 120 | #pico_generate_pio_header(pico-debug ${CMAKE_CURRENT_LIST_DIR}/monitor.pio) 121 | 122 | 123 | target_link_libraries(pico-debug 124 | pico_stdlib 125 | tinyusb_device 126 | tinyusb_board 127 | pico_unique_id 128 | hardware_pio 129 | 130 | # Support LWIP in polling mode... 131 | pico_cyw43_arch_lwip_poll 132 | 133 | lerp_task 134 | lerp_circ 135 | lerp_io 136 | lerp_debug 137 | lerp_interact 138 | lerp_flash 139 | lerp_tokeniser 140 | ) 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Lee Essen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico_debug 2 | 3 | A reimagined picoprobe -- much faster debugging/flashing for RP2040 based devices 4 | 5 | **NOTE:** this has been built specifically to program rp2040 based boards, it's NOT a generic programmer. 6 | 7 | This is very much work-in-progress and will develop over time, currently working is: 8 | 9 | - Works over USB or Wifi (on the Pico W) 10 | - PIO based SWD mechanism 11 | - SWD non-success checking in PIO stopping unneccessary delay 12 | - Clockable up to 25Mhz (over 6" jumper leads!) 13 | - Very very basic GDB server (over USB CDC) to eliminate need for OpenOCD 14 | - Supports UART forwarding as per the picoprobe 15 | - Delta based flashing (only program blocks if needed) 16 | - Calls the boot_stage2 to improve flash performance for delta compare 17 | - Comparing full flashing (not delta) for a 276K image: pico-probe is 17kb/s or 16s. This is 94kb/s or 3s! 18 | - Small memory cache for optimising GDB reads, significantly improves stepping performance. 19 | - Efficient co-operative multitasking speeds up transfers and general interaction. 20 | - Orders of magnitude better performance 21 | - Simple command line interface for setting parameters (eg. wifi ssid) 22 | - NOWHERE NEAR COMPLETE OR PROPERLY TESTED - USE AT YOUR OWN RISK 23 | 24 | Still to do: 25 | 26 | - Proper handling of maskints for halt/step/continue 27 | - Parallel flashing test - i.e. transfer & flash at the same time 28 | - Mechanism to change the speed (currently fixed at 25MHz) 29 | - Lots of code tidy up ... genericising where possible. 30 | - LOTS 31 | 32 | **Note:** I haven't done any testing of ISR's yet, it's next on my list. So it's highly likely that these will not work yet. 33 | 34 | ## Background 35 | 36 | I've been working on a fairly long term RP2040 project (on a custom board) and it's been progressing well, however as I've incorportated more into the build, including a growing number of static files, the debug time has increased signficantly ... mostly because it takes well over 10 seconds to flash the >200kb image that I currently have. 37 | 38 | I envisage that the image could well grow to 3 times that, and having to wait 30+ seconds or more for each debug attempt was going to become untennable. 39 | 40 | I did have an experiement with the J-Link EDU Mini which was much better, but didn't work with my custom board ... I think it uses a specific flash helper rather than the standard ROM routines, and this didn't work with the slightly different flash on my board (and there wasn't a huge amount of interest in resolving this!) 41 | 42 | So ... if you can't find a solution to your problem, build one! 43 | 44 | In my opinion there are a number of performance issues with the Picoprobe approach ... don't get me wrong, I think it's a brilliant solution, and is very clearly documented as something that was very quickly thrown together, so we shouldn't expect it to be high performance .. and the issues aren't just with the Picoprobe .. I think OpenOCD and even GDB contribute to the problem. 45 | 46 | **Issue 1:** Picoprobe uses as very simplisitic approach to SWD. It basically takes requests over USB for individual sets of "read bits" and "write bits" which means a typical transaction to write to memory or update a register has a quite alarming number of backwards and forwards to get it done. It does group things together into USB transactions, but then if you get a WAIT it all has to be re-done (I think!) 47 | 48 | **Issue 2:** For some reason that I haven't fully dug into, the SWD speed seems to be limited to 5Mhz in the OpenOCD scripts, not sure why -- but when compounded with the naive SWD approach this slows it down from it's potential quite a bit. I did have some fun with the "trn" bit when I was working on this, so it may be issues relating to that. 49 | 50 | **Issue 3:** OpenOCD ... is a stunning piece of work, but it's built to be able to support hundreds of targets, loads of different protocols, and lots of debuggers. It does a lot of auto-probing, and it has a lot of work arounds for known errata's etc. So it seems like it actually often does more interactions with the target than it needs to in the simplistic case (i.e. when you know you have an rp2040.) This is really not a criticism of OpenOCD .. I think it's brilliant, and I do think I'll build a version of this that plugs into it (so you keep the broad support and flexibility!) 51 | 52 | **Issue 4:** Flash Programming ... the current OpenOCD/picoprobe implementation of the flash programming phase is suboptimal in that it also causes a lot of SWD transactions. For example every single time it calls a RP2040 rom function it iterates through the target rom to find the trampoline function, then the function you want to call, this will result in many tens of SWD transactions that are completely unneccessary. 53 | 54 | **Issue 5:** GDB ... is fairly inefficient in terms of how it interacts with the target. After a breakpoint it naturally gathers a lot of information about the code region, but it seems to do this in lots of duplicate 2 and 4 byte reads rather than reading a chunk of memory. Not a criticism of GDB, but when remote debugging on a target with an inefficient mechanism, this all adds to poor performance. 55 | 56 | All of these issues compound and you get overall poor debug performance and slow flashing times. 57 | 58 | ## Solution -- pico_debug (yes, I'll think of a proper name!) 59 | 60 | **1. Efficient PIO based SWD** ... the original picoprobe uses PIO to run the SWD interaction with the target device, however it's very simple "read x bits", or "write x bits". The PIO devices are very powerful and it's possible to build more logic into the state machine. So my solution has both of the above, but it also has "read 3 bits, check the status, and then continue or not depending on it" ... which means that CPU interaction in the process is reduced, and the number of cycles wasted in an SWD exchange are reduced. 61 | 62 | **2. Supporting high speeds** ... I'm running my "probe" Pico at 150Mhz (because of future ethernet integration plans) and with a /3 PIO, you get a SWD bandwidth of 25Mhz (which is above the 24MHz recommendation in the datasheet, but seems to work nicely.) At the moment the PIO code has no delay cycles in it, which means the whole thing runs at 25MHz. My plan is to put delay cycles in the clock phases so I can run it at 150MHz and reduce wasted time in non-clock phases. 63 | 64 | **3. Removing the need for OpenOCD** ... this solution embeds a small GDB server and delivers access over a USB CDC (serial) interface. This way the master GDB instance is communicating directly with the probe and not going through an intermediary. This also signficiantly reduces the amount of data transferred over USB further improving speed. 65 | 66 | **4. Delta based flashing** ... when flashing a device I copy some custom code to the target, then copy the firmware over as the requests come in, once it gets to 64K then the custom code runs a comparison and if there are only minor differences (up to two 4k pages) then only they are flashed, anything more and the whole 64K chunk is done. This results is signficantly improved flashing times, and no flashing if the code is the same! 67 | 68 | **5. Memory Cache** ... I've added a small (4 words) memory cache which is cleared whenever a core halt or a memory write is done, this means that many of the inefficiencies about how GDB reads memory on a halt are covered and it doesn't require an SWD read. 69 | 70 | With all of the above you get much quicker flashing times and a much more (almost instant) debug experience when stepping through code. I haven't done normal debug cycle comparisons yet, but forced full flash timings are significantly better (3s vs 16s) and I have some further thoughts about double buffering do I can continue to transfer data while I'm waiting for the erase/programming to happen. 71 | 72 | IMPORTANT NOTE: Prior to starting work on this I had zero knowledge of SWD, zero knowledge of the internals of ARM debugging, and very little understanding of GDB, so this has been a huge learning curve and there will be a myriad of mistakes that need fixing! 73 | 74 | ## Configuring 75 | 76 | This is primarily aimed at VSCode users (at least that's where I've done the most testing) and makes use of the same cortex-debug extension that's used with the normal approach with OpenOCD. The difference is the launch.json file, specifically it uses the "external" servertype. 77 | 78 | So follow the normal instructions to get your VSCode environment up and running, but you don't need OpenOCD, you then use something like the below as a launch.json file: 79 | 80 | ``` 81 | { 82 | "version": "0.2.0", 83 | "configurations": [ 84 | { 85 | "name": "Pico Debug", 86 | "cwd": "${workspaceRoot}", 87 | "executable": "${command:cmake.launchTargetPath}", 88 | "request": "launch", 89 | "type": "cortex-debug", 90 | "servertype": "external", 91 | "gdbPath" : "gdb-multiarch", 92 | "gdbTarget" : "/dev/ttyACM2", 93 | "device": "RP2040", 94 | "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", 95 | 96 | // runToEntryPoint causes differences in behavour between launch and reset 97 | // so best avoided for this use case. 98 | //"runToEntryPoint": "main", 99 | 100 | // breakAfterReset means it consistantly stops in the reset handler code 101 | // so we can follow that with some commands to get us to main 102 | "breakAfterReset": true, 103 | 104 | // get_to_main puts a breakpoint at main, gets there, and then remove is 105 | // immediately after flashing. This means that by the time any ram based 106 | // breakpoints are applied the relevant stuff is in RAM. 107 | "postLaunchCommands": [ 108 | "break main", "continue", "clear main", 109 | ], 110 | // With breakAfterReset we have a consistent approach so can use the same 111 | // commands to get us to main after a restart... 112 | "postRestartCommands": [ 113 | "break main", 114 | "continue", 115 | "clear main" 116 | ] 117 | } 118 | ] 119 | } 120 | ``` 121 | 122 | This is working well for me and gives good consistent experience for both launching and resetting, and in each case stops at main ready for your debugging session (I had lots of issues with OpenOCD previously on reset where it would run for a bit and then eventually break at some random point.) 123 | 124 | NOTE: the "ttyACM2" reference is the serial port which is the "GDB" port presented by the this debugger. You need to be careful here at the device will currently present three serial interfaces (see below.) 125 | 126 | ## Debugger Ports 127 | 128 | This debugger actually presents three serial interfaces (USB CDC) to the host, on linux they always seem to be nicely ordered, whereas on Windows it seems to apply some randmisation just to make your life difficult. 129 | 130 | 1. The remote "gdb" port -- this is where you need to point GDB, in our case in the launch.json file. 131 | 2. The "uart" port -- this will be a mirror of the picoprobe UART capability, but I haven't done it yet. 132 | 3. The "debug" port -- I'm using this for debugging, it will output most of the GDB packets and some other stuff. 133 | 134 | The debug port has a 4K circular buffer that it uses to keep any debug output, if you connect to that port with a serial/terminal program it will output anything it has bufferred before becoming more real time, so if something stops working you can connect to it and see what has happened, you don't need to be connected all the time. (Note: debugging messages are not of a high quality! Currently.) 135 | 136 | On Windows I did note that you can look at the "bus reported device descriptor" in the serial port properties and you'll see the text I've defined for each port (debug-gdb, debug-uart, and debug-debug) so that may help making sure you connect to the right one. 137 | 138 | NOTE: the newest versions now include wifi support which has slighty changed the above. There are still three ports, but the third one is now a combined command-line interface and debug output. You can also connect to each via either USB or WIFI (USB will prevail) and you can mix and match between ports, so you could have GDB on USB and the debug output on Wifi for example. I will add more details shortly, but for now use port 3333, 3334, and 3335 to connect via wifi once you have configured the wifi (the commands are: set wifi.ssid and set wifi.creds, join, status, and save.) 139 | 140 | ## Code and Releases 141 | 142 | I'll try to keep a reasonably up-to-date pico-debug.uf2 file around, but I'll only create these at reasonable points as I continue to develop this. 143 | 144 | ## Futures 145 | 146 | The other project I'm working on already has nice and stable ethernet (rmii) and PoE, and I think a debugger with ethernet and poe would be really nice ... so I may look at that, although I do need to get back to that first project ... that's why I created this! 147 | 148 | Any comments welcome ... but please bear in mind I do this for fun, I'm not an expert, lots of things will be wrong. 149 | -------------------------------------------------------------------------------- /adi.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file swd.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-06-27 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef __ADI_H 13 | #define __ADI_H 14 | 15 | #define SWD_OK 0 16 | #define SWD_WAIT 1 17 | #define SWD_FAULT 2 18 | #define SWD_ERROR 3 19 | #define SWD_PARITY 4 20 | 21 | enum { 22 | REASON_DBGRQ = 0, 23 | REASON_BREAKPOINT = 1, 24 | REASON_WATCHPOINT = 2, 25 | REASON_WPTANDBKPT = 3, 26 | REASON_SINGLESTEP = 4, 27 | REASON_NOTHALTED = 5, 28 | REASON_EXIT = 6, 29 | REASON_EXC_CATCH = 7, 30 | REASON_UNDEFINED = 8, 31 | }; 32 | 33 | int swd_init(); 34 | int dp_init(); 35 | int swd_test(); 36 | 37 | int mem_read8(uint32_t addr, uint8_t *res); 38 | int mem_read16(uint32_t addr, uint16_t *res); 39 | int mem_read32(uint32_t addr, uint32_t *res); 40 | int mem_read_block(uint32_t addr, uint32_t count, uint8_t *dest); 41 | 42 | int mem_write8(uint32_t addr, uint8_t value); 43 | int mem_write16(uint32_t addr, uint16_t value); 44 | int mem_write32(uint32_t addr, uint32_t value); 45 | int mem_write_block(uint32_t addr, uint32_t count, uint8_t *src); 46 | 47 | int core_select(int num); 48 | int core_get(); 49 | int core_enable_debug(); 50 | int core_halt(); 51 | int core_unhalt(); 52 | int core_step(); 53 | int core_step_avoiding_breakpoint(); 54 | int core_is_halted(); 55 | int core_reset_halt(); 56 | int check_cores(); 57 | int core_get_reason(int num); 58 | 59 | uint32_t rp2040_find_rom_func(char ch1, char ch2); 60 | int rp2040_call_function(uint32_t addr, uint32_t args[], int argc); 61 | 62 | int reg_read(int reg, uint32_t *res); 63 | int reg_write(int reg, uint32_t value); 64 | 65 | int bp_set(uint32_t addr); 66 | int bp_clr(uint32_t addr); 67 | int bp_is_set(uint32_t addr); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /breakpoint.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | #include "breakpoint.h" 4 | #include "swd.h" 5 | #include "adi.h" 6 | 7 | // 8 | // Rushed implementation of software breakpoints ... need to redo 9 | // 10 | 11 | struct swbp { 12 | struct swbp *next; 13 | uint32_t addr; 14 | int size; 15 | uint32_t orig; 16 | }; 17 | struct swbp *sw_breakpoints = NULL; 18 | 19 | static struct swbp *find_swbp(uint32_t addr) { 20 | struct swbp *p = sw_breakpoints; 21 | while (p) { 22 | if (p->addr == addr) return p; 23 | p = p->next; 24 | } 25 | return NULL; 26 | } 27 | 28 | int sw_bp_set(uint32_t addr, int size) { 29 | if (!find_swbp(addr)) { 30 | struct swbp *bp = malloc(sizeof(struct swbp)); 31 | bp->addr = addr; 32 | bp->size = size; 33 | CHECK_OK(mem_read_block(addr, size, (uint8_t *)&(bp->orig))); 34 | uint32_t code = 0xbe11be11; 35 | mem_write_block(addr, size, (uint8_t *)&code); 36 | bp->next = sw_breakpoints; 37 | sw_breakpoints = bp; 38 | } 39 | return SWD_OK; 40 | } 41 | int sw_bp_clr(uint32_t addr, int size) { 42 | struct swbp *bp = find_swbp(addr); 43 | if (bp) { 44 | CHECK_OK(mem_write_block(bp->addr, bp->size, (uint8_t *)&bp->orig)); 45 | if (sw_breakpoints == bp) { 46 | sw_breakpoints = bp->next; 47 | } else { 48 | struct swbp *p = sw_breakpoints; 49 | while (p) { 50 | if (p->next == bp) { 51 | p->next = bp->next; 52 | break; 53 | } 54 | p = p->next; 55 | } 56 | } 57 | free(bp); 58 | } 59 | return SWD_OK; 60 | } 61 | -------------------------------------------------------------------------------- /breakpoint.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef __BREAKPOINT_H 4 | #define __BREAKPOINT_H 5 | 6 | int sw_bp_set(uint32_t addr, int size); 7 | int sw_bp_clr(uint32_t addr, int size); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /c2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "pico/stdlib.h" 4 | #include "hardware/gpio.h" 5 | 6 | 7 | const uint LED_PIN = 18; 8 | 9 | #define C2CLK 26 10 | #define C2D 27 11 | 12 | /* C2 registers */ 13 | #define C2PORT_DEVICEID 0x00 14 | #define C2PORT_REVID 0x01 15 | #define C2PORT_FPCTL 0x02 16 | #define C2PORT_FPDAT 0xB4 17 | 18 | /* C2 interface commands */ 19 | #define C2PORT_GET_VERSION 0x01 20 | #define C2PORT_DEVICE_ERASE 0x03 21 | #define C2PORT_BLOCK_READ 0x06 22 | #define C2PORT_BLOCK_WRITE 0x07 23 | #define C2PORT_PAGE_ERASE 0x08 24 | 25 | /* C2 status return codes */ 26 | #define C2PORT_INVALID_COMMAND 0x00 27 | #define C2PORT_COMMAND_FAILED 0x02 28 | #define C2PORT_COMMAND_OK 0x0d 29 | 30 | 31 | 32 | static void c2_reset() { 33 | gpio_put(C2CLK, false); 34 | busy_wait_us(25); 35 | gpio_put(C2CLK, true); 36 | } 37 | 38 | static void c2_strobe_clk() { 39 | gpio_put(C2CLK, false); 40 | busy_wait_us(1); 41 | gpio_put(C2CLK, true); 42 | } 43 | 44 | static void c2_write_ar(uint8_t addr) { 45 | int i; 46 | 47 | /* START field */ 48 | c2_strobe_clk(); 49 | 50 | /* INS field (11b, LSB first) */ 51 | gpio_set_dir(C2D, true); 52 | gpio_put(C2D, 1); 53 | c2_strobe_clk(); 54 | gpio_put(C2D, 1); 55 | c2_strobe_clk(); 56 | 57 | /* ADDRESS field */ 58 | for (i = 0; i < 8; i++) { 59 | gpio_put(C2D, (addr & 0x01)); 60 | c2_strobe_clk(); 61 | 62 | addr >>= 1; 63 | } 64 | 65 | /* STOP field */ 66 | gpio_set_dir(C2D, false); 67 | c2_strobe_clk(); 68 | } 69 | 70 | static int c2_read_ar(uint8_t *addr) { 71 | int i; 72 | 73 | /* START field */ 74 | c2_strobe_clk(); 75 | 76 | /* INS field (10b, LSB first) */ 77 | gpio_set_dir(C2D, true); 78 | gpio_put(C2D, 0); 79 | c2_strobe_clk(); 80 | gpio_put(C2D, 1); 81 | c2_strobe_clk(); 82 | 83 | /* ADDRESS field */ 84 | gpio_set_dir(C2D, false); 85 | *addr = 0; 86 | for (i = 0; i < 8; i++) { 87 | *addr >>= 1; /* shift in 8-bit ADDRESS field LSB first */ 88 | 89 | c2_strobe_clk(); 90 | if (gpio_get(C2D)) 91 | *addr |= 0x80; 92 | } 93 | 94 | /* STOP field */ 95 | c2_strobe_clk(); 96 | return 0; 97 | } 98 | 99 | static int c2_write_dr(uint8_t data) 100 | { 101 | int timeout, i; 102 | 103 | /* START field */ 104 | c2_strobe_clk(); 105 | 106 | /* INS field (01b, LSB first) */ 107 | gpio_set_dir(C2D, true); 108 | gpio_put(C2D, 1); 109 | c2_strobe_clk(); 110 | gpio_put(C2D, 0); 111 | c2_strobe_clk(); 112 | 113 | /* LENGTH field (00b, LSB first -> 1 byte) */ 114 | gpio_put(C2D, 0); 115 | c2_strobe_clk(); 116 | gpio_put(C2D, 0); 117 | c2_strobe_clk(); 118 | 119 | /* DATA field */ 120 | for (i = 0; i < 8; i++) { 121 | gpio_put(C2D, data & 0x01); 122 | c2_strobe_clk(); 123 | data >>= 1; 124 | } 125 | 126 | /* WAIT field */ 127 | gpio_set_dir(C2D, false); 128 | timeout = 20; 129 | do { 130 | c2_strobe_clk(); 131 | if (gpio_get(C2D)) 132 | break; 133 | 134 | busy_wait_us(1); 135 | } while (--timeout > 0); 136 | if (timeout == 0) 137 | return -1; 138 | 139 | /* STOP field */ 140 | c2_strobe_clk(); 141 | 142 | return 0; 143 | } 144 | 145 | static int c2_read_dr(uint8_t *data) 146 | { 147 | int timeout, i; 148 | 149 | /* START field */ 150 | c2_strobe_clk(); 151 | 152 | /* INS field (00b, LSB first) */ 153 | gpio_set_dir(C2D, true); 154 | gpio_put(C2D, 0); 155 | c2_strobe_clk(); 156 | gpio_put(C2D, 0); 157 | c2_strobe_clk(); 158 | 159 | /* LENGTH field (00b, LSB first -> 1 byte) */ 160 | gpio_put(C2D, 0); 161 | c2_strobe_clk(); 162 | gpio_put(C2D, 0); 163 | c2_strobe_clk(); 164 | 165 | /* WAIT field */ 166 | gpio_set_dir(C2D, false); 167 | timeout = 20; 168 | do { 169 | c2_strobe_clk(); 170 | if (gpio_get(C2D)) 171 | break; 172 | 173 | busy_wait_us(1); 174 | } while (--timeout > 0); 175 | if (timeout == 0) 176 | return -1; 177 | 178 | /* DATA field */ 179 | *data = 0; 180 | for (i = 0; i < 8; i++) { 181 | *data >>= 1; /* shift in 8-bit DATA field LSB first */ 182 | 183 | c2_strobe_clk(); 184 | if (gpio_get(C2D)) 185 | *data |= 0x80; 186 | } 187 | 188 | /* STOP field */ 189 | c2_strobe_clk(); 190 | 191 | return 0; 192 | } 193 | 194 | static int c2_poll_in_busy() 195 | { 196 | uint8_t addr; 197 | int ret, timeout = 20; 198 | 199 | do { 200 | ret = (c2_read_ar(&addr)); 201 | if (ret < 0) 202 | return -1; 203 | 204 | if (!(addr & 0x02)) 205 | break; 206 | 207 | busy_wait_us(1); 208 | } while (--timeout > 0); 209 | if (timeout == 0) 210 | return -1; 211 | 212 | return 0; 213 | } 214 | static int c2_poll_out_ready() 215 | { 216 | uint8_t addr; 217 | int ret, timeout = 10000; /* erase flash needs long time... */ 218 | 219 | do { 220 | ret = (c2_read_ar(&addr)); 221 | if (ret < 0) 222 | return -1; 223 | 224 | if (addr & 0x01) 225 | break; 226 | 227 | busy_wait_us(1); 228 | } while (--timeout > 0); 229 | if (timeout == 0) 230 | return -1; 231 | 232 | return 0; 233 | } 234 | 235 | volatile int ret; 236 | 237 | int main() 238 | { 239 | gpio_init(C2CLK); 240 | gpio_init(C2D); 241 | 242 | gpio_set_dir(C2CLK, true); // clk is output 243 | gpio_set_dir(C2D, false); // d in input (for now) 244 | 245 | // Set CLK high as a starting point 246 | gpio_put(C2CLK, true); 247 | 248 | sleep_ms(200); 249 | 250 | c2_reset(); 251 | 252 | uint8_t data; 253 | // int ret; 254 | 255 | /* Select DEVICEID register for C2 data register accesses */ 256 | c2_write_ar(C2PORT_DEVICEID); 257 | 258 | /* Read and return the device ID register */ 259 | ret = c2_read_dr(&data); 260 | 261 | sleep_ms(200); 262 | 263 | // Initialise the PI... 264 | c2_write_ar(C2PORT_FPCTL); 265 | c2_write_dr(0x02); 266 | c2_write_dr(0x04); 267 | c2_write_dr(0x01); 268 | sleep_ms(20); 269 | 270 | 271 | uint8_t d1, d2; 272 | 273 | c2_write_ar(C2PORT_FPDAT); 274 | c2_write_dr(0x09); // DATA_WRITE 275 | if (c2_poll_in_busy() < 0) { 276 | while (1); 277 | } 278 | 279 | if (c2_poll_out_ready() < 0) { 280 | while(1); 281 | } 282 | ret = c2_read_dr(&d1); 283 | 284 | c2_write_dr(0xAD); // DERIVID SFR 285 | 286 | if (c2_poll_in_busy() < 0) { 287 | while(1); 288 | } 289 | 290 | c2_write_dr(0x01); // ? 291 | if (c2_poll_in_busy() < 0) { 292 | while(1); 293 | } 294 | 295 | if (c2_poll_out_ready() < 0) { 296 | while(1); 297 | } 298 | ret = c2_read_dr(&d2); 299 | 300 | printf("ret is %d\r\n"); 301 | while(1); 302 | 303 | while(1); 304 | 305 | } 306 | -------------------------------------------------------------------------------- /cmdline.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "cmdline.h" 4 | #include "lerp/io.h" 5 | #include "lerp/debug.h" 6 | 7 | #include "lerp/task.h" 8 | #include "lerp/interact.h" 9 | #include "lerp/io.h" 10 | #include "lerp/tokeniser.h" 11 | #include "config/config.h" 12 | 13 | #include "pico/cyw43_arch.h" 14 | 15 | 16 | // Circular buffer for the command line ... 17 | CIRC_DEFINE(buffer, 2048); 18 | 19 | // 20 | // Output some status information 21 | // 22 | void cmd_status(struct io *io, __unused struct circ *circ) { 23 | uint8_t mac[6]; 24 | int wifi_state; 25 | char *state; 26 | uint32_t ip; 27 | 28 | cyw43_wifi_get_mac(&cyw43_state, CYW43_ITF_STA, mac); 29 | io_printf(io, "MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 30 | 31 | wifi_state = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA); 32 | 33 | switch(wifi_state) { 34 | case CYW43_LINK_JOIN: state = "joined"; break; 35 | case CYW43_LINK_FAIL: state = "failed"; break; 36 | case CYW43_LINK_NONET: state = "no-net"; break; 37 | case CYW43_LINK_BADAUTH: state = "bad-auth"; break; 38 | case CYW43_LINK_DOWN: state = "down"; break; 39 | default: state = "unknown"; break; 40 | } 41 | io_printf(io, "WIFI State: %s (%d)\r\n", state, wifi_state); 42 | 43 | ip = cyw43_state.netif->ip_addr.addr; 44 | io_printf(io, "IP Address: %d.%d.%d.%d\r\n", ip&0xff, (ip>>8)&0xff, (ip>>16)&0xff, (ip>>24)); 45 | } 46 | 47 | // 48 | // Dirty mechanism for seting an SSID and credentials and causing a rejoin 49 | // 50 | // TODO: this will need to be saved into flash 51 | // 52 | // set ssid=rest of the line 53 | // set creds=rest of the line 54 | // wifi-join 55 | // 56 | void cmd_set(struct io *io, struct circ *circ) { 57 | int tok; 58 | char item[32]; 59 | 60 | tok = token_get(circ); 61 | if (tok == TOK_END) { 62 | // should output a list 63 | int maxlen = cf_max_name_len(); 64 | char *item = cf_next_item(NULL); 65 | while (item) { 66 | int padding = maxlen - strlen(item); 67 | io_printf(io, "%s:%*s %s\r\n", item, padding, "", cf_get_strval(item)); 68 | item = cf_next_item(item); 69 | } 70 | return; 71 | } 72 | 73 | if (tok != TOK_WORD) { 74 | io_printf(io, "expect set =\r\n"); 75 | return; 76 | } 77 | strcpy(item, token_string()); 78 | 79 | tok = token_get(circ); 80 | if (tok == TOK_END) { 81 | // this is a request of the value 82 | io_printf(io, "have requested value of %s\r\n", item); 83 | return; 84 | } 85 | if (tok != TOK_EQUALS) { 86 | io_printf(io, "expect set =\r\n"); 87 | return; 88 | } 89 | 90 | char *err = cf_set_with_tokens(item, circ); 91 | if (err) { 92 | io_printf(io, "Blah error: %s\r\n", err); 93 | } 94 | 95 | // Now do a set with tokens... 96 | /* 97 | if (strncmp(word, "set ssid=", 9) == 0) { 98 | strcpy(wifi_ssid, buffer+9); 99 | } else if (strncmp(buffer, "set creds=", 10) == 0) { 100 | strcpy(wifi_creds, buffer+10); 101 | } else { 102 | io_printf(io, "Error: can only use 'set ssid=' or 'set creds='\r\n"); 103 | } 104 | */ 105 | } 106 | 107 | void cmd_join(struct io *io, __unused struct circ *circ) { 108 | if (*cf->main->wifi.ssid && *cf->main->wifi.creds) { 109 | cyw43_arch_wifi_connect_async(cf->main->wifi.ssid, cf->main->wifi.creds, CYW43_AUTH_WPA2_AES_PSK); 110 | io_printf(io, "join: started.\r\n"); 111 | } else { 112 | io_printf(io, "join: wifi ssid or credentials missing.\r\n"); 113 | } 114 | } 115 | 116 | void cmd_save(struct io *io, __unused struct circ *circ) { 117 | config_save(); 118 | io_printf(io, "config saved to flash.\r\n"); 119 | } 120 | 121 | 122 | struct cmd_item { 123 | char *cmd; 124 | void (*func)(struct io *io, struct circ *circ); 125 | }; 126 | 127 | struct cmd_item commands[] = { 128 | { "status", cmd_status }, 129 | { "set", cmd_set }, 130 | { "join", cmd_join }, 131 | { "save", cmd_save }, 132 | { NULL, NULL }, 133 | }; 134 | 135 | 136 | // 137 | // Quick command line interpretation.... will need to be redone, perhaps rethinking the 138 | // tokeniser approach. 139 | // 140 | int process_cmdline(struct io *io, struct circ *buffer) { 141 | // let's make sure we are zero terminated... 142 | *buffer->head = 0; 143 | 144 | int tok = token_get(buffer); 145 | if (tok == TOK_END) return 0; 146 | if (tok != TOK_WORD) { 147 | io_printf(io, "expected a command word.\r\n"); 148 | return -1; 149 | } 150 | 151 | char *word = token_string(); 152 | 153 | struct cmd_item *c = commands; 154 | while (c->cmd) { 155 | if (strcmp(word, c->cmd) == 0) { 156 | c->func(io, buffer); 157 | return 0; 158 | } 159 | c++; 160 | } 161 | 162 | io_printf(io, "uunrecognised command.\r\n"); 163 | return 1; 164 | } 165 | 166 | 167 | 168 | // Get at the debug output... 169 | extern struct circ *circ_debug; 170 | 171 | // Async func for doing something... 172 | // 173 | // Let's have a play with debug output ... if there is a line (\r\n) then output it 174 | // otherwise if we get over 100 chars, output that. 175 | // 176 | // TODO: this is an experiment and needs to be done properly. 177 | // 178 | static char db_line[120]; 179 | static int db_lpos = 0; 180 | 181 | 182 | char *asf() { 183 | if (db_lpos == 0) { 184 | db_line[0] = 'D'; 185 | db_line[1] = '/'; 186 | db_line[2] = ' '; 187 | db_lpos = 3; 188 | } 189 | 190 | while(!circ_is_empty(circ_debug)) { 191 | int ch = circ_get_byte(circ_debug); 192 | db_line[db_lpos++] = ch; 193 | if ((db_lpos == 100) || (ch == '\n')) { 194 | db_line[db_lpos] = 0; 195 | db_lpos = 0; 196 | return db_line; 197 | } 198 | } 199 | return NULL; 200 | } 201 | 202 | struct task *cmdline_task = NULL; 203 | struct io *cmdline_io = NULL; 204 | 205 | 206 | 207 | DEFINE_TASK(cmdline, 1024); 208 | 209 | DEFINE_TASK(dummy, 1024); 210 | 211 | 212 | void func_cmdline(void *arg) { 213 | 214 | // Initialise the IO mechanism for the commandline/debug output... 215 | cmdline_io = io_init(CMD_CDC, CMD_TCP, 4096); 216 | cmdline_io->support_telnet = 1; 217 | 218 | // struct interact *i = interact_with_buf(cmdline_io, buffer, sizeof(buffer), "pico-debug> "); 219 | struct interact *i = interact_with_circ(cmdline_io, buffer, "pico-debug> "); 220 | 221 | cmdline_task = current_task(); 222 | 223 | while (1) { 224 | while (!io_is_connected(cmdline_io)) { 225 | task_sleep_ms(100); 226 | } 227 | int err = interact(i, asf); 228 | debug_printf("Have i=%d\r\n", err); 229 | int len = circ_used(i->cmd); 230 | debug_printf("CMD is [%.*s] (len=%d)\r\n", len, buffer, len); 231 | process_cmdline(cmdline_io, buffer); 232 | } 233 | } 234 | 235 | // Nudge the interactive process every 200ms... 236 | // We need to switch this to a trigger from debug_printf ideally so 237 | // we won't need this at all 238 | // TODO: 239 | void func_dummy(void *arg) { 240 | while (1) { 241 | if (cmdline_task) { 242 | if (cmdline_task->state == BLOCKED) { 243 | task_wake(cmdline_task, -10); 244 | } 245 | } 246 | task_sleep_ms(200); 247 | } 248 | 249 | } 250 | 251 | void cmdline_init() { 252 | CREATE_TASK(cmdline, func_cmdline, NULL); 253 | CREATE_TASK(dummy, func_dummy, NULL); 254 | } 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /cmdline.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CMDLINE_H 3 | #define __CMDLINE_H 4 | 5 | void cmdline_init(); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /config/config.c: -------------------------------------------------------------------------------- 1 | 2 | #include "pico/stdlib.h" 3 | #include 4 | #include 5 | #include "config.h" 6 | 7 | #include "lerp/flash.h" 8 | #include "lerp/tokeniser.h" 9 | 10 | 11 | struct cf _cf; 12 | struct cf *cf = &_cf; 13 | 14 | struct cf_main _cf_main; 15 | struct cf_live _cf_live; 16 | 17 | // Used for error messages and return values 18 | static char cf_err[80]; 19 | 20 | struct cf_info { 21 | char *name; // name of the item 22 | char *desc; // description of the item 23 | int offset; // offset into main cf array 24 | int iv1, iv2; // integer values for general use 25 | void *vp1, *vp2; // void pointers for general use 26 | char *(*get_str_func)(struct cf_info *); 27 | char *(*set_int_func)(struct cf_info *, int); 28 | char *(*set_str_func)(struct cf_info *, char *); 29 | char *(*set_tok_func)(struct cf_info *, struct circ *); 30 | }; 31 | 32 | #define CF_OFFSET(item) (offsetof(struct cf_main, item)) 33 | #define CF_INTP(offset) ((int *)((void *)(cf->main) + offset)) 34 | #define CF_STRP(offset) ((char *)((void *)(cf->main) + offset)) 35 | 36 | // --------------------------------------------------------------------------------- 37 | // Generic get and set functions 38 | // --------------------------------------------------------------------------------- 39 | static char *set_integer_int(struct cf_info *c, int val) { 40 | if ((val < c->iv1) || (val > c->iv2)) { 41 | sprintf(cf_err, "value must be between %d and %d", c->iv1, c->iv2); 42 | return cf_err; 43 | } 44 | *CF_INTP(c->offset) = val; 45 | return NULL; 46 | } 47 | static char *get_integer_str(struct cf_info *c) { 48 | sprintf(cf_err, "%d", *CF_INTP(c->offset)); 49 | return cf_err; 50 | } 51 | static char *set_boolean_int(struct cf_info *c, int val) { 52 | if (val < 0 || val > 1) return "value must be 0 (false) or 1 (true)"; 53 | *CF_INTP(c->offset) = val; 54 | return NULL; 55 | } 56 | static char *set_boolean_str(struct cf_info *c, char *val) { 57 | int *p = CF_INTP(c->offset); 58 | if (strcasecmp(val, "true") == 0) *p = 1; 59 | else if (strcasecmp(val, "yes") == 0) *p = 1; 60 | else if (strcasecmp(val, "enable") == 0) *p = 1; 61 | else if (strcasecmp(val, "false") == 0) *p = 0; 62 | else if (strcasecmp(val, "no") == 0) *p = 0; 63 | else if (strcasecmp(val, "disable") == 0) *p = 0; 64 | else return "value must be true, yes, enable, false, no, disable."; 65 | return NULL; 66 | } 67 | static char *get_boolean_str(struct cf_info *c) { 68 | int v = *CF_INTP(c->offset); 69 | if (v) return "true"; 70 | return "false"; 71 | } 72 | static char *set_string_str(struct cf_info *c, char *val) { 73 | int l = strlen(val); 74 | if ((l < c->iv1) || (l > c->iv2)) { 75 | sprintf(cf_err, "value must be between %d and %d characters", c->iv1, c->iv2); 76 | return cf_err; 77 | } 78 | strcpy(CF_STRP(c->offset), val); 79 | return NULL; 80 | } 81 | static char *get_string_str(struct cf_info *c) { 82 | return CF_STRP(c->offset); 83 | } 84 | 85 | 86 | // --------------------------------------------------------------------------------- 87 | // Default values for our config... 88 | // --------------------------------------------------------------------------------- 89 | 90 | static const struct cf_main cf_main_defaults = { 91 | .version = CF_VERSION, 92 | .swd = { .speed = 25000, .pin_clk = 2, .pin_io = 3 }, 93 | .dhcp = { .enable = 1 }, 94 | .wifi = { .ssid = "", .creds = "" }, 95 | }; 96 | 97 | // --------------------------------------------------------------------------------- 98 | // The main configuration table... 99 | // --------------------------------------------------------------------------------- 100 | 101 | static const struct cf_info cf_list[] = { 102 | { "swd.speed", "sets the speed of the SWD interface in kHz (1-25000)", 103 | CF_OFFSET(swd.speed), 1, 25000, NULL, NULL, 104 | get_integer_str, set_integer_int, NULL, NULL }, 105 | { "swd.pin_clk", "the CLK pin for the SWD interface", 106 | CF_OFFSET(swd.pin_clk), 1, 31, NULL, NULL, 107 | get_integer_str, set_integer_int, NULL, NULL }, 108 | { "swd.pin_io", "the DATA pin for the SWD interface", 109 | CF_OFFSET(swd.pin_io), 1, 31, NULL, NULL, 110 | get_integer_str, set_integer_int, NULL, NULL }, 111 | 112 | { "wifi.ssid", "the SSID for the WIFI interface", 113 | CF_OFFSET(wifi.ssid), 0, 32, NULL, NULL, 114 | get_string_str, NULL, set_string_str, NULL }, 115 | { "wifi.creds", "the password for the WIFI interface", 116 | CF_OFFSET(wifi.creds), 0, 32, NULL, NULL, 117 | get_string_str, NULL, set_string_str, NULL }, 118 | 119 | { NULL, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL } 120 | }; 121 | 122 | struct cf_info *cf_get_byname(char *name) { 123 | struct cf_info *c = (struct cf_info *)cf_list; 124 | while (c->name) { 125 | if (strcmp(c->name, name) == 0) return c; 126 | c++; 127 | } 128 | return NULL; 129 | } 130 | 131 | char *cf_next_item(char *prev) { 132 | if (!prev) return cf_list[0].name; 133 | 134 | struct cf_info *c = cf_get_byname(prev); 135 | if (!c) return NULL; 136 | c++; 137 | return c->name; 138 | } 139 | 140 | char *cf_get_desc(char *item) { 141 | struct cf_info *c = cf_get_byname(item); 142 | if (!c) return ""; 143 | return c->desc; 144 | } 145 | char *cf_get_strval(char *item) { 146 | struct cf_info *c = cf_get_byname(item); 147 | if (!c) return ""; 148 | return (c->get_str_func(c)); 149 | } 150 | 151 | int cf_max_name_len() { 152 | static int max_l = 0; 153 | 154 | if (max_l) return max_l; 155 | 156 | struct cf_info *c = (struct cf_info *)cf_list; 157 | while (c->name) { 158 | int l = strlen(c->name); 159 | if (l > max_l) max_l = l; 160 | c++; 161 | } 162 | return max_l; 163 | } 164 | 165 | char *cf_set_with_tokens(char *name, struct circ *circ) { 166 | const struct cf_info *c = cf_get_byname(name); 167 | 168 | if (!c) return "unknown configuration item"; 169 | 170 | // If we have a token processing function, then use that... 171 | if (c->set_tok_func) { 172 | return c->set_tok_func((struct cf_info *)c, circ); 173 | } 174 | // Otherwise figure out what kind of token it is... 175 | int token = token_get(circ); 176 | 177 | // We need at least one... 178 | 179 | switch(token) { 180 | case TOK_END: 181 | case TOK_ERROR: 182 | return "expected a value"; 183 | 184 | case TOK_INTEGER: 185 | if (!c->set_int_func) return "unable to set item with integer"; 186 | return c->set_int_func((struct cf_info *)c, token_int()); 187 | 188 | case TOK_WORD: 189 | case TOK_STRING: 190 | if (!c->set_str_func) return "unable to set item with word/string"; 191 | return c->set_str_func((struct cf_info *)c, token_string()); 192 | 193 | default: 194 | return "unable to set item with that value"; 195 | } 196 | } 197 | 198 | /** 199 | * @brief Handle config changes between versions 200 | * 201 | * @param oldversion 202 | */ 203 | void cf_version_upgrade(int oldversion) { 204 | // Start with the defaults... 205 | memcpy(cf->main, &cf_main_defaults, sizeof(struct cf_main)); 206 | 207 | 208 | // Then copy over (or adjust) anything we want to keep 209 | switch(oldversion) { 210 | 211 | case 1: 212 | break; 213 | 214 | 215 | case 0: // we didn't have anything, or... 216 | default: // anythng else we just live with the defaults 217 | break; 218 | } 219 | return; 220 | } 221 | 222 | 223 | void config_save() { 224 | write_file("config.cf", (uint8_t *)cf->main, sizeof(struct cf_main)); 225 | // And update cf->flash to point to the new file... 226 | cf->flash = file_addr("config.cf", NULL); 227 | } 228 | 229 | 230 | void config_init() { 231 | 232 | flash_init(); 233 | 234 | // Now see if we have a flash config file... 235 | cf->flash = file_addr("config.cf", NULL); 236 | 237 | // Setup the main config structures... 238 | cf->main = &_cf_main; 239 | cf->live = &_cf_live; 240 | 241 | if (!cf->flash || (cf->flash->version != CF_VERSION)) { 242 | // Missing config or old version... 243 | cf_version_upgrade(cf->flash ? cf->flash->version : 0); 244 | config_save(); 245 | } else { 246 | // Copy flash to the main cf... 247 | memcpy(cf->main, cf->flash, sizeof(struct cf_main)); 248 | } 249 | } -------------------------------------------------------------------------------- /config/config.cf: -------------------------------------------------------------------------------- 1 | 2 | 3 | swd.speed: 4 | type=uint32_t 5 | default=25000 6 | min=100 7 | max=25000 8 | help="sets the speed of the SWD interface" 9 | 10 | swd.pinclk: 11 | type=uint32_t 12 | default=21 13 | min=1 14 | max=32 15 | help="sets the CLK pin of the SWD interface" 16 | 17 | swd.pindata: 18 | type=uint32_t 19 | default=22 20 | min=1 21 | max=32 22 | help="sets the DATA pin of the SWD interface" 23 | 24 | wifi.ssid: 25 | type=string 26 | default="" 27 | min=0 28 | max=32 29 | help="the SSID to use for the wifi connection" 30 | 31 | wifi.pass: 32 | type=string 33 | default="" 34 | min=0 35 | max=32 36 | help="the password to use for the wifi connection" 37 | 38 | ip.dhcp.enable: 39 | type=boolean 40 | default=true 41 | 42 | ip.one.two.three.four.fred: 43 | bla=1 44 | 45 | -------------------------------------------------------------------------------- /config/config.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __CONFIG_H 3 | #define __CONFIG_H 4 | 5 | #include 6 | #include "lerp/circ.h" 7 | 8 | #define CF_VERSION 1 9 | 10 | struct cf_main { 11 | int version; 12 | 13 | struct { 14 | uint32_t speed; 15 | uint32_t pin_clk; 16 | uint32_t pin_io; 17 | } swd; 18 | 19 | struct { 20 | bool enable; 21 | } dhcp; 22 | 23 | struct { 24 | char ssid[32]; 25 | char creds[32]; 26 | } wifi; 27 | 28 | struct { 29 | uint32_t pin_rx; 30 | uint32_t pin_tx; 31 | } uart; 32 | }; 33 | 34 | 35 | struct cf_live { 36 | struct { 37 | uint32_t addr; 38 | uint32_t gateway; 39 | uint32_t netmask; 40 | } ip; 41 | }; 42 | 43 | struct cf { 44 | struct cf_main *flash; 45 | struct cf_main *main; 46 | struct cf_live *live; 47 | }; 48 | 49 | extern struct cf *cf; 50 | 51 | void config_init(); 52 | void config_save(); 53 | char *cf_set_with_tokens(char *name, struct circ *circ); 54 | int cf_max_name_len(); 55 | char *cf_next_item(char *prev); 56 | char *cf_get_desc(char *item); 57 | char *cf_get_strval(char *item); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /config/gen.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | 3 | use strict; 4 | use Data::Dumper; 5 | 6 | my %structs = (); 7 | 8 | 9 | sub handle_new_item { 10 | my $itemname = shift @_; 11 | my @elems = split(/\./, $itemname); 12 | my $item = pop @elems; 13 | 14 | # Now make sure we have a structure setup for each item in the list 15 | # and keep a reference to the final one so we can add the item 16 | my $ref = \%structs; 17 | foreach my $x (@elems) { 18 | if (not exists ${$ref}{$x}) { 19 | ${$ref}{$x} = { "x"=>1, "y"=> 2 }; 20 | print("Created $x\n"); 21 | $ref = %{$ref}{$x}; 22 | } 23 | } 24 | # 25 | # Now put the actual item in the list... 26 | # 27 | ${$ref}{$item} = { "item" => 1 }; 28 | $ref = %{$ref}{$item}; 29 | return $ref; 30 | } 31 | 32 | 33 | 34 | while (<>) { 35 | print($_); 36 | if (/^([\w\.]+):/) { 37 | print("GOT word: $1\n"); 38 | my $ref = handle_new_item($1); 39 | ${$ref}{"lee"} = 45; 40 | next; 41 | } 42 | } 43 | 44 | print Dumper(%structs); 45 | -------------------------------------------------------------------------------- /delme/circ.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file circ.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-31 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | * Circular Buffer implementation 11 | * 12 | */ 13 | 14 | #ifndef ___CIRC_H 15 | #define ___CIRC_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "pico/stdlib.h" 23 | 24 | struct circ { 25 | // "constants" 26 | uint8_t *data; // where is the data 27 | uint8_t *end; // position of the end of the buffer (one past) 28 | uint32_t size; // keep the size here for calcs 29 | 30 | // variables 31 | uint8_t *head; // where do we write to 32 | uint8_t *tail; // where to we read from 33 | 34 | // Flags 35 | int flush; // flag to signal interactive flush needed 36 | int full; // are we full? 37 | int last; // flag to signal the last byte in the stream is "last" 38 | }; 39 | 40 | /** 41 | * @brief Define and initialise a fixed size circular buffer of a given name 42 | * 43 | */ 44 | #define CIRC_DEFINE(name, sz) static uint8_t _c_data_##name[sz]; \ 45 | static struct circ _c_circ_##name = { \ 46 | .data = _c_data_##name, \ 47 | .end = _c_data_##name + sz, \ 48 | .size = sz, \ 49 | .head = _c_data_##name, \ 50 | .tail = _c_data_##name, \ 51 | .full = 0, .flush = 0, .last = 0 }; \ 52 | struct circ *name = &_c_circ_##name; 53 | 54 | 55 | /** 56 | * @brief Is the circular buffer empty? 57 | * 58 | * @param c 59 | * @return int 60 | */ 61 | static inline int circ_is_empty(struct circ *c) { 62 | return (!c->full && (c->head == c->tail)); 63 | } 64 | 65 | static inline int circ_is_full(struct circ *c) { 66 | return c->full; 67 | } 68 | 69 | static inline int circ_is_empty_notfull(struct circ *c) { 70 | return c->head == c->tail; 71 | } 72 | 73 | static inline int circ_has_data(struct circ *c) { 74 | return (c->full || (c->head != c->tail)); 75 | } 76 | 77 | static inline int circ_space(struct circ *c) { 78 | if (c->full) return 0; 79 | return ((c->tail > c->head) ? (c->tail - c->head) : (c->size - (c->head - c->tail))); 80 | } 81 | 82 | static inline int circ_space_before_wrap(struct circ *c) { 83 | return MIN(circ_space(c), c->end - c->head); 84 | } 85 | 86 | static inline void circ_advance_head(struct circ *c, int count) { 87 | c->head += count; 88 | if (c->head >= c->end) c->head -= c->size; 89 | c->full = (c->head == c->tail); 90 | } 91 | 92 | static inline int circ_used(struct circ *c) { 93 | if (c->full) return c->size; 94 | return ((c->tail > c->head) ? (c->size - (c->tail - c->head)) : (c->head - c->tail)); 95 | } 96 | 97 | static inline int circ_bytes_before_wrap(struct circ *c) { 98 | return MIN(circ_used(c), c->end - c->tail); 99 | } 100 | static inline int circ_bytes_after_wrap(struct circ *c) { 101 | if (c->head < c->tail) { 102 | return c->head - c->data; 103 | } else return 0; 104 | } 105 | 106 | static inline void circ_advance_tail(struct circ *c, int count) { 107 | c->tail += count; 108 | if (c->tail >= c->end) c->tail -= c->size; 109 | c->full = 0; 110 | } 111 | 112 | /** 113 | * @brief Add a byte to a circ, move tail on if already full 114 | * 115 | * This means we will lose the oldest data if we overflow the 116 | * buffer, which is probably the right approach. 117 | * 118 | * @param c 119 | * @param b 120 | */ 121 | static inline void circ_add_byte(struct circ *c, uint8_t byte) { 122 | if (c->full) { 123 | if (++(c->tail) == c->end) c->tail = c->data; 124 | } 125 | *(c->head++) = byte; 126 | if (c->head == c->end) c->head = c->data; 127 | c->full = (c->head == c->tail); 128 | } 129 | 130 | /** 131 | * @brief Add a set of bytes to the circular buffer 132 | * 133 | * If there's not enough space then we simulate the effect of 134 | * overriting as per add_byte. 135 | * 136 | * @param c 137 | * @param data 138 | * @param len 139 | */ 140 | static inline void circ_add_bytes(struct circ *c, uint8_t *data, int len) { 141 | int remaining, xfer, left; 142 | 143 | while (len) { 144 | remaining = c->end - c->head; // space from head until the end of the buffer 145 | xfer = MIN(len, remaining); // how much can we copy this time around? 146 | left = circ_space(c) - xfer; // how much space will be left after this? 147 | 148 | memcpy(c->head, data, xfer); 149 | c->head += xfer; if (c->head >= c->end) c->head -= c->size; 150 | 151 | // Did we overflow the size? (double negative) 152 | if (left <= 0) { 153 | c->tail -= left; if (c->tail >= c->end) c->tail -= c->size; 154 | c->full = 1; 155 | } 156 | len -= xfer; 157 | data += xfer; 158 | } 159 | } 160 | 161 | /** 162 | * @brief Move the used data from one circ and append to the other 163 | * 164 | * Note: no capacity checking is done, it needs to fit! 165 | * 166 | * @param dst 167 | * @param src 168 | */ 169 | static inline int circ_move(struct circ *dst, struct circ *src) { 170 | int size; 171 | int rc = 0; 172 | 173 | size = circ_bytes_before_wrap(src); 174 | if (size) { 175 | circ_add_bytes(dst, src->tail, size); 176 | circ_advance_tail(src, size); 177 | rc += size; 178 | } 179 | size = circ_bytes_before_wrap(src); 180 | if (size) { 181 | circ_add_bytes(dst, src->tail, size); 182 | circ_advance_tail(src, size); 183 | rc += size; 184 | } 185 | return rc; 186 | } 187 | 188 | 189 | /** 190 | * @brief Get a byte from the circular buffer, return -1 if none available 191 | * 192 | * @param c 193 | * @return int 194 | */ 195 | static inline int circ_get_byte(struct circ *c) { 196 | int rc = -1; 197 | 198 | if (circ_has_data(c)) { 199 | rc = *c->tail++; 200 | if (c->tail == c->end) c->tail = c->data; 201 | c->full = 0; 202 | } 203 | return rc; 204 | } 205 | 206 | /** 207 | * @brief Copy data out from a circular buffer to a max size 208 | * 209 | * @param c 210 | * @param buffer 211 | * @param max 212 | * @return int 213 | */ 214 | static inline int circ_get_bytes(struct circ *c, uint8_t *buffer, int max) { 215 | int remaining = c->end - c->tail; // bytes before the end of the buffer 216 | int count = MIN(circ_used(c), max); 217 | int rc = count; 218 | 219 | // Do we overflow what's remaining... 220 | if (count > remaining) { 221 | memcpy(buffer, c->tail, remaining); 222 | c->tail = c->data; 223 | count -= remaining; 224 | } 225 | 226 | // We fit in what's left 227 | memcpy(buffer, c->tail, count); 228 | c->tail += count; if (c->tail >= c->end) c->tail -= c->size; 229 | c->full = 0; 230 | return rc; 231 | } 232 | 233 | /** 234 | * @brief See if the first count bytes match 235 | * 236 | * Returns 1 on match, 0 on non-match (i.e. not like strcmp) 237 | * 238 | * @param c 239 | * @param bytes 240 | * @param count 241 | */ 242 | static inline int circ_compare(struct circ *c, uint8_t *bytes, int count) { 243 | int n = circ_bytes_before_wrap(c); 244 | 245 | if (n >= count) { 246 | // Simple one-off comparison... 247 | return (memcmp(c->tail, bytes, count) == 0); 248 | } 249 | // Otherwise we need to do this in two chunks... 250 | if (memcmp(c->tail, bytes, n) != 0) return 0; 251 | 252 | count -= n; 253 | bytes += n; 254 | return (memcmp(c->data, bytes, count) == 0); 255 | } 256 | static inline int circ_casecompare(struct circ *c, uint8_t *bytes, int count) { 257 | int n = circ_bytes_before_wrap(c); 258 | 259 | if (n >= count) { 260 | // Simple one-off comparison... 261 | return (strncasecmp((const char *)c->tail, (const char *)bytes, count) == 0); 262 | } 263 | // Otherwise we need to do this in two chunks... 264 | if (memcmp(c->tail, bytes, n) != 0) return 0; 265 | 266 | count -= n; 267 | bytes += n; 268 | return (strncasecmp((const char *)c->data, (const char *)bytes, count) == 0); 269 | } 270 | 271 | 272 | /** 273 | * @brief Clean out a circular buffer, i.e. initialise variable bits 274 | * 275 | * @param c 276 | */ 277 | static inline void circ_clean(struct circ *c) { 278 | c->head = c->tail = c->data; 279 | c->full = 0; 280 | c->last = 0; 281 | c->flush = 0; 282 | } 283 | 284 | 285 | static inline void circ_set_last(struct circ *c, int v) { 286 | c->last = v; 287 | } 288 | 289 | /** 290 | * @brief Set the flush flag in the given circular buffer 291 | * 292 | * @param c 293 | */ 294 | static inline void circ_set_flush(struct circ *c, int v) { 295 | c->flush = v; 296 | } 297 | 298 | 299 | #endif -------------------------------------------------------------------------------- /filegen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | 5 | 6 | sub decode { 7 | my $data = shift @_; 8 | my $len = length($data); 9 | my $rc = ""; 10 | 11 | my $i = 0; 12 | while ($i < $len) { 13 | $rc .= sprintf("0x%02x,", unpack("C", substr($data, $i, 1))); 14 | $i = $i + 1; 15 | if ($i % 16 == 0) { $rc .= "\n" }; 16 | } 17 | if ($i % 16 != 0) { $rc .= "\n" }; 18 | return $rc; 19 | } 20 | 21 | sub slurp { 22 | my $fname = shift @_; 23 | 24 | open my $fh, '<', ${fname} or die "Can't open file ($fname) $!"; 25 | my $rc = do { local $/; <$fh> }; 26 | return $rc; 27 | } 28 | 29 | 30 | ($#ARGV == 2) or die("Usage: fsgen ${dest_c}") or die("Error: unable to open c file for writing\n"); 42 | open(FH, ">${dest_h}") or die("Error: unable to open h file for writing\n"); 43 | 44 | my $header = <<'EOF'; 45 | // 46 | // Autogenerated file - do not edit 47 | // 48 | 49 | EOF 50 | 51 | print FC $header; 52 | print FH $header; 53 | 54 | my $path = "/"; 55 | 56 | sub do_dir { 57 | my $dir = shift @_; 58 | my $rep = shift @_; 59 | 60 | opendir(DH, $dir) or die("Error: unable to open directory ($dir)\n"); 61 | my @files = readdir(DH); 62 | closedir(DH); 63 | 64 | foreach my $file (@files) { 65 | next if ($file eq "."); 66 | next if ($file eq ".."); 67 | 68 | if (-d "${dir}/${file}") { 69 | do_dir("${dir}/${file}", "${rep}/${file}"); 70 | next; 71 | } 72 | 73 | my $sname = "${file}"; 74 | $sname =~ s/\./_/g; 75 | $sname =~ s/\//_/g; 76 | 77 | print("Got file: [$dir/$file] [$sname]\n"); 78 | 79 | print FC "const unsigned char ${sname}[] = {\n"; 80 | 81 | my $flen = -s "${dir}/${file}"; 82 | print FC "/* raw file data ($flen bytes) */\n"; 83 | my $data = slurp("${dir}/${file}"); 84 | print FC decode($data); 85 | print FC "};\n\n"; 86 | 87 | print FH "extern const unsigned char ${sname}[${flen}];\n"; 88 | } 89 | } 90 | 91 | do_dir(${source}, ""); 92 | 93 | close(FC); 94 | close(FH); 95 | exit(0); 96 | -------------------------------------------------------------------------------- /files/rp2040_features.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | arm 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /files/rp2040_memory_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0x1000 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /files/rp2040_threads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Name: rp2040.core0, state: undefined 4 | Name: rp2040.core1, state: undefined 5 | 6 | -------------------------------------------------------------------------------- /flash.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file flash.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-07-01 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | * Routines that support flash programming on RP2040 based devices. 11 | * 12 | * This includes an algorithm that should optmise the flash programming times 13 | * by balancing erase and programming block sizes with actual deltas in the 14 | * code. 15 | * 16 | * Basic approach: 17 | * 18 | * 1. Copy over stuff in up to 64k chunks 19 | * 2. Go throug the 64k chunk and compare it in 4k blocks with existing image 20 | * 3. If more than 2 x 4k blocks need programming then do the whole thing 21 | * 4. Otherwise do the 4k block individually 22 | * 23 | * But this algorithm really needs to run on the remote device so we need 24 | * to produce some relocatable code to do it. 25 | * 26 | */ 27 | 28 | #include "flash.h" 29 | #include "swd.h" 30 | #include 31 | #include 32 | #include "pico/stdlib.h" 33 | #include "lerp/debug.h" 34 | 35 | #include "adi.h" 36 | 37 | #define FOR_TARGET __attribute__((noinline, section("for_target"))) 38 | #define DATA_BUFFER 0x20000000 39 | #define CODE_START 0x20010000 40 | #define BOOT2_START 0x20020000 41 | #define STACK_ADDDR 0x20040800 42 | #define FLASH_BASE 0x10000000 43 | 44 | static int flash_code_copied = 0; 45 | 46 | /** 47 | * @brief Called once the block is already on the target 48 | * 49 | * This will copy over the code (if needed) and execute the remote function 50 | * 51 | * @param offset 52 | * @param length 53 | * @return int 54 | */ 55 | static int rp2040_program_flash_chunk(int offset, int length) { 56 | extern char __start_for_target[]; 57 | extern char __stop_for_target[]; 58 | int rc; 59 | 60 | debug_printf("FLASH: Request to flash 0x%08x (len=%d)\r\n", offset, length); 61 | 62 | // Copy the code over ... only needed once per flashing cycle... 63 | 64 | if (!flash_code_copied) { 65 | int code_len = (__stop_for_target - __start_for_target); 66 | debug_printf("FLASH: Copying custom flash code to 0x%08x (%d bytes)\r\n", CODE_START, code_len); 67 | rc = mem_write_block(CODE_START, code_len, (uint8_t *)__start_for_target); 68 | if (rc != SWD_OK) return rc; 69 | flash_code_copied = 1; 70 | } 71 | 72 | uint32_t t = time_us_32(); 73 | 74 | uint32_t args[] = { offset, DATA_BUFFER, length }; 75 | rc = rp2040_call_function(CODE_START, args, sizeof(args)/sizeof(uint32_t)); 76 | if (rc != SWD_OK) return rc; 77 | 78 | uint32_t r0, erased, programmed; 79 | rc = reg_read(0, &r0); 80 | 81 | erased = (r0 >> 24) * 4; 82 | programmed = r0 & 0x00ffffff; 83 | 84 | debug_printf("FLASH: Erased %dk and programmed %d bytes in %dms\r\n", erased, programmed, (time_us_32() - t)/1000); 85 | 86 | return 0; 87 | } 88 | 89 | /** 90 | * @brief Add some data to the existing flash chunk 91 | * 92 | * This will add data (and copy it to the target) up to 64k, then it will 93 | * call rp2040_program_flash_chunk() to actually do the programming. 94 | * 95 | * If we call this with a high offset and zero size then it will cause 96 | * any residual chunk to be written out. 97 | * 98 | * @param offset 99 | * @param src 100 | * @param size 101 | * @return int 102 | */ 103 | static uint32_t chunk_start = 0; // flash address of the start 104 | static uint32_t chunk_size = 0; // how much is already done 105 | 106 | int rp2040_add_flash_bit(uint32_t offset, uint8_t *src, int size) { 107 | int rc; 108 | 109 | debug_printf("FLASH: writing %d bytes to flash at 0x%08x\r\n", size, offset); 110 | 111 | 112 | // If we are starting outside the range of an existing block... 113 | if (chunk_size && (offset >= (chunk_start + 65536))) { 114 | rp2040_program_flash_chunk(chunk_start, chunk_size); 115 | chunk_size = 0; 116 | } 117 | 118 | // If size is zero here then we are the last bit... 119 | if (size == 0) { 120 | flash_code_copied = 0; 121 | return 0; 122 | } 123 | 124 | while (size) { 125 | // If we are a new chunk... 126 | if (chunk_size == 0) { 127 | chunk_start = offset; 128 | } 129 | // Now how much can we fit in... 130 | int space = 65536 - chunk_size; 131 | int count = MIN(space, size); 132 | 133 | // Let's copy it over... 134 | uint32_t t = time_us_32(); 135 | 136 | rc = mem_write_block(DATA_BUFFER + (offset - chunk_start), count, src); 137 | if (rc != SWD_OK) { 138 | debug_printf("COPY FAILED: %d\r\n", rc); 139 | return 1;; 140 | } 141 | debug_printf("FLASH: Copied %d bytes to location 0x%08x (%d ms)\r\n", count, DATA_BUFFER+(offset-chunk_start), 142 | (time_us_32() - t)/1000); 143 | chunk_size += count; 144 | 145 | // If we have a full one... 146 | if (chunk_size == 65536) { 147 | rp2040_program_flash_chunk(chunk_start, chunk_size); 148 | chunk_size = 0; 149 | } 150 | 151 | // Now process the remainder... 152 | offset += count; 153 | size -= count; 154 | src += count; 155 | } 156 | return 0; 157 | } 158 | 159 | // ----------------------------------------------------------------------------------- 160 | // THIS CODE IS DESIGNED TO RUN ON THE TARGET AND WILL BE COPIED OVER 161 | // (hence it has it's own section) 162 | // ----------------------------------------------------------------------------------- 163 | // 164 | // Memory Map on target for programming: 165 | // 166 | // 0x2000 0000 64K incoming data buffer 167 | // 0x2001 0000 start of code 168 | // 0x2002 0000 stage2 bootloader copy 169 | // 0x2004 0800 top of stack 170 | // 171 | 172 | 173 | #define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)rom_address) 174 | #define fn(a, b) (uint32_t)((b << 8) | a) 175 | typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code); 176 | 177 | typedef void *(*rom_void_fn)(void); 178 | typedef void *(*rom_flash_erase_fn)(uint32_t addr, size_t count, uint32_t block_size, uint8_t block_cmd); 179 | typedef void *(*rom_flash_prog_fn)(uint32_t addr, const uint8_t *data, size_t count); 180 | 181 | FOR_TARGET int flash_block(uint32_t offset, uint8_t *src, int length) { 182 | // Fill in the rom functions... 183 | rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn)rom_hword_as_ptr(0x18); 184 | uint16_t *function_table = (uint16_t *)rom_hword_as_ptr(0x14); 185 | 186 | rom_void_fn _connect_internal_flash = rom_table_lookup(function_table, fn('I', 'F')); 187 | rom_void_fn _flash_exit_xip = rom_table_lookup(function_table, fn('E', 'X')); 188 | rom_flash_erase_fn _flash_range_erase = rom_table_lookup(function_table, fn('R', 'E')); 189 | rom_flash_prog_fn flash_range_program = rom_table_lookup(function_table, fn('R', 'P')); 190 | rom_void_fn _flash_flush_cache = rom_table_lookup(function_table, fn('F', 'C')); 191 | rom_void_fn _flash_enter_cmd_xip = rom_table_lookup(function_table, fn('C', 'X')); 192 | 193 | // We want to make sure the flash is connected so that we can compare 194 | // with it's current contents... 195 | 196 | _connect_internal_flash(); 197 | // _flash_flush_cache(); 198 | // _flash_enter_cmd_xip(); // would be better to call the boot stage2 to speed things up 199 | _flash_exit_xip(); 200 | 201 | // If we are being called with a zero offset, then the block will include the bootloader 202 | // so we can just copy it to use later... 203 | if (offset == 0) { 204 | uint32_t *s = (uint32_t *)src; 205 | uint32_t *d = (uint32_t *)BOOT2_START; 206 | for (int i=0; i < 64; i++) { 207 | *d++ = *s++; 208 | } 209 | } 210 | // Call the second stage bootloader... reconnect XIP 211 | ((void (*)(void))BOOT2_START+1)(); 212 | 213 | // Now lets get started... 214 | uint8_t *changed[3]; 215 | int change_count = 0; 216 | 217 | // Go through in 4k chunks seeing if we have deltas... 218 | uint8_t *src_block = src; 219 | uint8_t *dst_block = (uint8_t *)(FLASH_BASE + offset); 220 | int left = length; 221 | 222 | 223 | while(left > 0) { 224 | // Look at this block... 225 | uint8_t *s = src_block; 226 | uint8_t *d = dst_block; 227 | int size = MIN(4096, left); 228 | for (int i=0; i < size; i++) { 229 | if (*s++ != *d++) { 230 | changed[change_count++] = src_block; 231 | break; 232 | } 233 | } 234 | if (change_count > 2) break; 235 | src_block += 4096; 236 | dst_block += 4096; 237 | left -= 4096; 238 | } 239 | if (!change_count) return 0; // nothing to do 240 | 241 | // turn off xip so we can do stuff... 242 | //_connect_internal_flash(); // do we need this? 243 | _flash_exit_xip(); 244 | 245 | // We will return the number of 4k blocks erased, and the size flashed in this... 246 | uint32_t rc = 0; 247 | 248 | if (change_count <= 2) { 249 | 250 | // Program the individual blocks... 251 | for (int i = 0; i < change_count; i++) { 252 | uint8_t *block = changed[i]; 253 | uint32_t faddr = offset + (block - src); 254 | uint32_t size = MIN(4096, length-offset); 255 | 256 | // erase 4k 257 | // program 4k 258 | _flash_range_erase(faddr, 4096, 4096, 0x20); // sector erase (4K) 259 | flash_range_program(faddr, block, size); 260 | rc += size; 261 | } 262 | // reconnect xip 263 | _flash_flush_cache(); 264 | // _flash_enter_cmd_xip(); 265 | // Call the second stage bootloader... reconnect XIP 266 | ((void (*)(void))BOOT2_START+1)(); 267 | return (change_count << 24) | rc; 268 | } 269 | 270 | // Otherwise we program the whole thing... either 32K (if less than that) or all 64k 271 | if (length <= 32768) { 272 | _flash_range_erase(offset, 32768, 32768, 0x52); // 32K erase 273 | rc = 0x08000000 | length; 274 | } else { 275 | _flash_range_erase(offset, 65536, 65536, 0xD8); // 64K erase 276 | rc = 0x10000000 | length; 277 | } 278 | // program everything... 279 | flash_range_program(offset, src, length); 280 | // reconnect xip 281 | _flash_flush_cache(); 282 | // _flash_enter_cmd_xip(); 283 | // Call the second stage bootloader... reconnect XIP 284 | ((void (*)(void))BOOT2_START+1)(); 285 | 286 | return rc; 287 | } 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /flash.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __FLASH_H 3 | #define __FLASH_H 4 | 5 | #include 6 | 7 | int rp2040_add_flash_bit(uint32_t offset, uint8_t *src, int size); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /gdb.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef __GDB_H 4 | #define __GDB_H 5 | 6 | void gdb_init(); 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /id_usb.c: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @file usb.c 4 | * @author Lee Essen (lee.essen@nowonline.co.uk) 5 | * @brief 6 | * @version 0.1 7 | * @date 2022-02-25 8 | * 9 | * @copyright Copyright (c) 2022 10 | * 11 | */ 12 | 13 | #include 14 | #include "tusb.h" 15 | //#include "hardware/irq.h" 16 | #include "pico/unique_id.h" 17 | 18 | 19 | //#define USBD_VID (0x2E8A) // Raspberry Pi 20 | //#define USBD_PID (0x000a) // Raspberry Pi Pico SDK CDC 21 | 22 | #define USBD_VID (0x3333) // Dummy Testing Values 23 | #define USBD_PID (0x1111) // Dummy Testing Values 24 | 25 | #define USB_IRQ 31 26 | #define USB_WORKER_INTERVAL_US 1000 27 | #define USB_WRITE_TIMEOUT_US 500000 28 | 29 | #define USBD_CDC_0_EP_CMD (0x81) 30 | #define USBD_CDC_0_EP_OUT (0x02) 31 | #define USBD_CDC_0_EP_IN (0x82) 32 | 33 | #define USBD_CDC_1_EP_CMD (0x83) 34 | #define USBD_CDC_1_EP_OUT (0x03) 35 | #define USBD_CDC_1_EP_IN (0x84) 36 | 37 | #define USBD_CDC_2_EP_CMD (0x85) 38 | #define USBD_CDC_2_EP_OUT (0x04) 39 | #define USBD_CDC_2_EP_IN (0x86) 40 | 41 | #define USBD_CDC_CMD_MAX_SIZE (8) 42 | #define USBD_CDC_IN_OUT_MAX_SIZE (64) 43 | 44 | #define USBD_STR_0 (0x00) 45 | #define USBD_STR_MANUF (0x01) 46 | #define USBD_STR_PRODUCT (0x02) 47 | #define USBD_STR_SERIAL (0x03) 48 | #define USBD_STR_CDC0 (0x04) 49 | #define USBD_STR_CDC1 (0x05) 50 | #define USBD_STR_CDC2 (0x06) 51 | 52 | // 53 | // Officially, according to the USB specs, we shoul duse TUSB_CLASS_MISC, SUBCLASS_COMMON, and PROTOCOL_IAD 54 | // so that we can "group" the two CDC interfaces together, however if you do that then the NI tools don't 55 | // seem to work properly! 56 | // 57 | // Breaking the standard, and sticking with 0, 0, 0 seems to work for both Linux and Windows, ultimately if 58 | // this becomes a problem then it could be configurable since the CDC interface works fine in both cases. 59 | // 60 | 61 | 62 | static const tusb_desc_device_t usbd_desc_device = { 63 | .bLength = sizeof(tusb_desc_device_t), 64 | .bDescriptorType = TUSB_DESC_DEVICE, 65 | .bcdUSB = 0x0200, 66 | .bDeviceClass = TUSB_CLASS_MISC, 67 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 68 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 69 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 70 | .idVendor = USBD_VID, 71 | .idProduct = USBD_PID, 72 | .bcdDevice = 0x0100, 73 | .iManufacturer = USBD_STR_MANUF, 74 | .iProduct = USBD_STR_PRODUCT, 75 | .iSerialNumber = USBD_STR_SERIAL, 76 | .bNumConfigurations = 1, 77 | }; 78 | 79 | 80 | enum 81 | { 82 | ITF_NUM_CDC_0 = 0, 83 | ITF_NUM_CDC_0_DATA, 84 | ITF_NUM_CDC_1, 85 | ITF_NUM_CDC_1_DATA, 86 | ITF_NUM_CDC_2, 87 | ITF_NUM_CDC_2_DATA, 88 | ITF_NUM_TOTAL 89 | }; 90 | 91 | 92 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + (3 * TUD_CDC_DESC_LEN)) 93 | 94 | uint8_t const usbd_desc_cfg[] = 95 | { 96 | // Config number, interface count, string index, total length, attribute, power in mA 97 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), 98 | 99 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, USBD_STR_CDC0, USBD_CDC_0_EP_CMD, 100 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_0_EP_OUT, USBD_CDC_0_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), 101 | 102 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1, USBD_STR_CDC1, USBD_CDC_1_EP_CMD, 103 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_1_EP_OUT, USBD_CDC_1_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), 104 | 105 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_2, USBD_STR_CDC2, USBD_CDC_2_EP_CMD, 106 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_2_EP_OUT, USBD_CDC_2_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), 107 | }; 108 | 109 | 110 | const uint8_t *tud_descriptor_device_cb(void) { 111 | return (const uint8_t *)&usbd_desc_device; 112 | } 113 | 114 | const uint8_t *tud_descriptor_configuration_cb(__unused uint8_t index) { 115 | return usbd_desc_cfg; 116 | } 117 | 118 | /** 119 | * @brief Convert a normal c string into a USB string descriptor 120 | * 121 | * @param str 122 | * @param desc_str 123 | * @return uint8_t 124 | */ 125 | static uint8_t string_to_descriptor(char *str, uint16_t *desc_str) { 126 | #define DESC_STR_MAX (32) 127 | 128 | int len; 129 | 130 | for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) { 131 | desc_str[1 + len] = str[len]; 132 | } 133 | return len; 134 | } 135 | 136 | /** 137 | * @brief USB Callback to provide descriptor strings (from our config) 138 | * 139 | * @param index 140 | * @param langid 141 | * @return const uint16_t* 142 | */ 143 | const uint16_t *tud_descriptor_string_cb(uint8_t index, __unused uint16_t langid) { 144 | static uint16_t desc_str[DESC_STR_MAX]; 145 | 146 | uint8_t len; 147 | switch (index) { 148 | case 0: 149 | desc_str[1] = 0x0409; // supported language is English 150 | len = 1; 151 | break; 152 | 153 | case USBD_STR_MANUF: 154 | len = string_to_descriptor("manuf", desc_str); 155 | break; 156 | 157 | case USBD_STR_PRODUCT: 158 | len = string_to_descriptor("product", desc_str); 159 | break; 160 | 161 | case USBD_STR_SERIAL: 162 | len = string_to_descriptor("11223344", desc_str); 163 | break; 164 | 165 | case USBD_STR_CDC0: 166 | len = string_to_descriptor("debug-gdb", desc_str); 167 | break; 168 | 169 | case USBD_STR_CDC1: 170 | len = string_to_descriptor("debug-uart", desc_str); 171 | break; 172 | 173 | case USBD_STR_CDC2: 174 | len = string_to_descriptor("debug-debug", desc_str); 175 | break; 176 | 177 | default: 178 | return NULL; 179 | } 180 | // first byte is length (including header), second byte is string type 181 | desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * len + 2)); 182 | 183 | return desc_str; 184 | } 185 | -------------------------------------------------------------------------------- /lwipopts.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _LWIPOPTS_H 3 | #define _LWIPOPTS_H 4 | 5 | 6 | // Common settings used in most of the pico_w examples 7 | // (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) 8 | 9 | #define NO_SYS 1 10 | #define LWIP_SOCKET 0 11 | #define MEM_LIBC_MALLOC 1 12 | #define MEM_ALIGNMENT 4 13 | #define MEM_SIZE 4000 14 | #define MEMP_NUM_TCP_SEG 32 15 | #define MEMP_NUM_ARP_QUEUE 10 16 | #define PBUF_POOL_SIZE 24 17 | #define LWIP_ARP 1 18 | #define LWIP_ETHERNET 1 19 | #define LWIP_ICMP 1 20 | #define LWIP_RAW 1 21 | #define TCP_WND (8 * TCP_MSS) 22 | #define TCP_MSS 1460 23 | #define TCP_SND_BUF (8 * TCP_MSS) 24 | #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) 25 | #define LWIP_NETIF_STATUS_CALLBACK 1 26 | #define LWIP_NETIF_LINK_CALLBACK 1 27 | #define LWIP_NETIF_HOSTNAME 1 28 | #define LWIP_NETCONN 0 29 | #define MEM_STATS 0 30 | #define SYS_STATS 0 31 | #define MEMP_STATS 0 32 | #define LINK_STATS 0 33 | // #define ETH_PAD_SIZE 2 34 | #define LWIP_CHKSUM_ALGORITHM 3 35 | #define LWIP_DHCP 1 36 | #define LWIP_IPV4 1 37 | #define LWIP_TCP 1 38 | #define LWIP_UDP 1 39 | #define LWIP_DNS 1 40 | #define LWIP_TCP_KEEPALIVE 1 41 | #define LWIP_NETIF_TX_SINGLE_PBUF 1 42 | #define DHCP_DOES_ARP_CHECK 0 43 | #define LWIP_DHCP_DOES_ACD_CHECK 0 44 | 45 | /* 46 | #ifndef NDEBUG 47 | #define LWIP_DEBUG 1 48 | #define LWIP_STATS 1 49 | #define LWIP_STATS_DISPLAY 1 50 | #endif 51 | */ 52 | 53 | #define ETHARP_DEBUG LWIP_DBG_OFF 54 | #define NETIF_DEBUG LWIP_DBG_OFF 55 | #define PBUF_DEBUG LWIP_DBG_OFF 56 | #define API_LIB_DEBUG LWIP_DBG_OFF 57 | #define API_MSG_DEBUG LWIP_DBG_OFF 58 | #define SOCKETS_DEBUG LWIP_DBG_OFF 59 | #define ICMP_DEBUG LWIP_DBG_OFF 60 | #define INET_DEBUG LWIP_DBG_OFF 61 | #define IP_DEBUG LWIP_DBG_OFF 62 | #define IP_REASS_DEBUG LWIP_DBG_OFF 63 | #define RAW_DEBUG LWIP_DBG_OFF 64 | #define MEM_DEBUG LWIP_DBG_OFF 65 | #define MEMP_DEBUG LWIP_DBG_OFF 66 | #define SYS_DEBUG LWIP_DBG_OFF 67 | #define TCP_DEBUG LWIP_DBG_OFF 68 | #define TCP_INPUT_DEBUG LWIP_DBG_OFF 69 | #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF 70 | #define TCP_RTO_DEBUG LWIP_DBG_OFF 71 | #define TCP_CWND_DEBUG LWIP_DBG_OFF 72 | #define TCP_WND_DEBUG LWIP_DBG_OFF 73 | #define TCP_FR_DEBUG LWIP_DBG_OFF 74 | #define TCP_QLEN_DEBUG LWIP_DBG_OFF 75 | #define TCP_RST_DEBUG LWIP_DBG_OFF 76 | #define UDP_DEBUG LWIP_DBG_OFF 77 | #define TCPIP_DEBUG LWIP_DBG_OFF 78 | #define PPP_DEBUG LWIP_DBG_OFF 79 | #define SLIP_DEBUG LWIP_DBG_OFF 80 | #define DHCP_DEBUG LWIP_DBG_OFF 81 | 82 | #endif /* __LWIPOPTS_H__ */ 83 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "pico/stdlib.h" 6 | #include "hardware/gpio.h" 7 | 8 | #include "pico/printf.h" 9 | 10 | #include "lerp/task.h" 11 | #include "lerp/circ.h" 12 | #include "lerp/io.h" 13 | #include "lerp/debug.h" 14 | 15 | #include "config/config.h" 16 | 17 | #include "swd.h" 18 | #include "adi.h" 19 | #include "flash.h" 20 | #include "uart.h" 21 | #include "tusb.h" 22 | #include "filedata.h" 23 | #include "gdb.h" 24 | #include "cmdline.h" 25 | 26 | #include "pico/cyw43_arch.h" 27 | 28 | 29 | /** 30 | * @brief Main polling function called regularly by lerp_task 31 | * 32 | * We need to do time sensitive things here and we mustn't block. 33 | * 34 | */ 35 | void main_poll() { 36 | // make sure usb/wifi is running and we're processing data... 37 | io_poll(); 38 | 39 | // make sure the PIO blocks are managed... 40 | swd_pio_poll(); 41 | 42 | // see if we need to transfer any uart data 43 | dbg_uart_poll(); 44 | 45 | // handle any debug output... 46 | debug_poll(); 47 | } 48 | 49 | 50 | int main() { 51 | // Take us to 150Mhz (for future rmii support) 52 | set_sys_clock_khz(150 * 1000, true); 53 | 54 | // Initialise the configuration system 55 | config_init(); 56 | 57 | // Initialise the USB stack... 58 | tusb_init(); 59 | 60 | // Initialise the PIO SWD system... 61 | if (swd_init() != SWD_OK) 62 | lerp_panic("unable to init SWD"); 63 | 64 | // Experiment with wireless on the pico-w 65 | //io_init(); 66 | 67 | cmdline_init(); 68 | 69 | // Initialise the UART 70 | dbg_uart_init(); 71 | 72 | // Create the GDB server task.. 73 | gdb_init(); 74 | 75 | // See if we have enough information to start the wifi... 76 | // This is horrible here, needs to be a separate func/file with wifi/net related stuff. 77 | if (*cf->main->wifi.ssid && *cf->main->wifi.creds) { 78 | cyw43_arch_wifi_connect_async(cf->main->wifi.ssid, cf->main->wifi.creds, CYW43_AUTH_WPA2_AES_PSK); 79 | } 80 | 81 | 82 | 83 | // And start the scheduler... 84 | leos_init(main_poll); 85 | leos_start(); 86 | } 87 | -------------------------------------------------------------------------------- /modules/lerp_circ.orig/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_circ) 4 | 5 | add_library(lerp_circ INTERFACE) 6 | 7 | target_include_directories(lerp_circ INTERFACE include) 8 | 9 | target_sources(lerp_circ INTERFACE 10 | CMakeLists.txt 11 | 12 | circ.c 13 | include/lerp/circ.h 14 | ) 15 | 16 | target_compile_options(lerp_circ INTERFACE 17 | -Wall 18 | ) 19 | 20 | endif() 21 | -------------------------------------------------------------------------------- /modules/lerp_circ.orig/circ.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file circ.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-31 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #include "lerp/circ.h" 13 | #include "pico/printf.h" 14 | 15 | void circ_init(struct circ *c, uint8_t *data, int len) { 16 | c->data = data; 17 | c->end = data + len; 18 | c->head = c->tail = c->data; 19 | c->last = 0; 20 | c->size = len; 21 | c->flush = 0; 22 | } 23 | 24 | 25 | 26 | static void _circ_out(char ch, void *arg) { 27 | struct circ *c = (struct circ *)arg; 28 | 29 | circ_add_byte(c, ch); 30 | } 31 | 32 | /** 33 | * @brief printf to a circ buffer, will discard the oldest data if full 34 | * 35 | * @param c 36 | * @param format 37 | * @param ... 38 | * @return int 39 | */ 40 | int circ_printf(struct circ *c, char *format, ...) { 41 | int len; 42 | va_list args; 43 | 44 | va_start(args, format); 45 | len = vfctprintf(_circ_out, (void *)c, format, args); 46 | va_end(args); 47 | return len; 48 | } 49 | 50 | /** 51 | * @brief printf to a circ buffer, using a va_list 52 | * 53 | * @param c 54 | * @param format 55 | * @param va 56 | * @return int 57 | */ 58 | int circ_aprintf(struct circ *c, char *format, va_list va) { 59 | return vfctprintf(_circ_out, (void *)c, format, va); 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /modules/lerp_circ.orig/include/lerp/circ.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file circ.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-31 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | * Circular Buffer implementation 11 | * 12 | */ 13 | 14 | #ifndef __LEOS_CIRC_H 15 | #define __LEOS_CIRC_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "pico/stdlib.h" 23 | 24 | struct circ { 25 | // "constants" 26 | uint8_t *data; // where is the data 27 | uint8_t *end; // position of the end of the buffer (one past) 28 | uint32_t size; // keep the size here for calcs 29 | 30 | // variables 31 | uint8_t *head; // where do we write to 32 | uint8_t *tail; // where to we read from 33 | 34 | // Flags 35 | int flush; // flag to signal interactive flush needed 36 | int full; // are we full? 37 | int last; // flag to signal the last byte in the stream is "last" 38 | }; 39 | 40 | /** 41 | * @brief Define and initialise a fixed size circular buffer of a given name 42 | * 43 | */ 44 | #define CIRC_DEFINE(name, sz) static uint8_t _c_data_##name[sz]; \ 45 | static struct circ _c_circ_##name = { \ 46 | .data = _c_data_##name, \ 47 | .end = _c_data_##name + sz, \ 48 | .size = sz, \ 49 | .head = _c_data_##name, \ 50 | .tail = _c_data_##name, \ 51 | .full = 0, .flush = 0, .last = 0 }; \ 52 | struct circ *name = &_c_circ_##name; 53 | 54 | void circ_init(struct circ *c, uint8_t *data, int len); 55 | int circ_aprintf(struct circ *c, char *format, va_list va); 56 | int circ_printf(struct circ *c, char *format, ...); 57 | 58 | 59 | /** 60 | * @brief Is the circular buffer empty? 61 | * 62 | * @param c 63 | * @return int 64 | */ 65 | static inline int circ_is_empty(struct circ *c) { 66 | return (!c->full && (c->head == c->tail)); 67 | } 68 | 69 | static inline int circ_is_full(struct circ *c) { 70 | return c->full; 71 | } 72 | 73 | static inline int circ_is_empty_notfull(struct circ *c) { 74 | return c->head == c->tail; 75 | } 76 | 77 | static inline int circ_has_data(struct circ *c) { 78 | return (c->full || (c->head != c->tail)); 79 | } 80 | 81 | static inline int circ_space(struct circ *c) { 82 | if (c->full) return 0; 83 | return ((c->tail > c->head) ? (c->tail - c->head) : (c->size - (c->head - c->tail))); 84 | } 85 | 86 | static inline int circ_space_before_wrap(struct circ *c) { 87 | return MIN(circ_space(c), c->end - c->head); 88 | } 89 | 90 | static inline void circ_advance_head(struct circ *c, int count) { 91 | c->head += count; 92 | if (c->head >= c->end) c->head -= c->size; 93 | c->full = (c->head == c->tail); 94 | } 95 | 96 | static inline int circ_used(struct circ *c) { 97 | if (c->full) return c->size; 98 | return ((c->tail > c->head) ? (c->size - (c->tail - c->head)) : (c->head - c->tail)); 99 | } 100 | 101 | static inline int circ_bytes_before_wrap(struct circ *c) { 102 | return MIN(circ_used(c), c->end - c->tail); 103 | } 104 | static inline int circ_bytes_after_wrap(struct circ *c) { 105 | if (c->head < c->tail) { 106 | return c->head - c->data; 107 | } else return 0; 108 | } 109 | 110 | static inline void circ_advance_tail(struct circ *c, int count) { 111 | c->tail += count; 112 | if (c->tail >= c->end) c->tail -= c->size; 113 | c->full = 0; 114 | } 115 | 116 | /** 117 | * @brief Add a byte to a circ, move tail on if already full 118 | * 119 | * This means we will lose the oldest data if we overflow the 120 | * buffer, which is probably the right approach. 121 | * 122 | * @param c 123 | * @param b 124 | */ 125 | static inline void circ_add_byte(struct circ *c, uint8_t byte) { 126 | if (c->full) { 127 | if (++(c->tail) == c->end) c->tail = c->data; 128 | } 129 | *(c->head++) = byte; 130 | if (c->head == c->end) c->head = c->data; 131 | c->full = (c->head == c->tail); 132 | } 133 | 134 | /** 135 | * @brief Add a set of bytes to the circular buffer 136 | * 137 | * If there's not enough space then we simulate the effect of 138 | * overriting as per add_byte. 139 | * 140 | * @param c 141 | * @param data 142 | * @param len 143 | */ 144 | static inline void circ_add_bytes(struct circ *c, uint8_t *data, int len) { 145 | int remaining, xfer, left; 146 | 147 | while (len) { 148 | remaining = c->end - c->head; // space from head until the end of the buffer 149 | xfer = MIN(len, remaining); // how much can we copy this time around? 150 | left = circ_space(c) - xfer; // how much space will be left after this? 151 | 152 | memcpy(c->head, data, xfer); 153 | c->head += xfer; if (c->head >= c->end) c->head -= c->size; 154 | 155 | // Did we overflow the size? (double negative) 156 | if (left <= 0) { 157 | c->tail -= left; if (c->tail >= c->end) c->tail -= c->size; 158 | c->full = 1; 159 | } 160 | len -= xfer; 161 | data += xfer; 162 | } 163 | } 164 | 165 | /** 166 | * @brief Move the used data from one circ and append to the other 167 | * 168 | * Note: no capacity checking is done, it needs to fit! 169 | * 170 | * @param dst 171 | * @param src 172 | */ 173 | static inline int circ_move(struct circ *dst, struct circ *src) { 174 | int size; 175 | int rc = 0; 176 | 177 | size = circ_bytes_before_wrap(src); 178 | if (size) { 179 | circ_add_bytes(dst, src->tail, size); 180 | circ_advance_tail(src, size); 181 | rc += size; 182 | } 183 | size = circ_bytes_before_wrap(src); 184 | if (size) { 185 | circ_add_bytes(dst, src->tail, size); 186 | circ_advance_tail(src, size); 187 | rc += size; 188 | } 189 | return rc; 190 | } 191 | 192 | 193 | /** 194 | * @brief Get a byte from the circular buffer, return -1 if none available 195 | * 196 | * @param c 197 | * @return int 198 | */ 199 | static inline int circ_get_byte(struct circ *c) { 200 | int rc = -1; 201 | 202 | if (circ_has_data(c)) { 203 | rc = *c->tail++; 204 | if (c->tail == c->end) c->tail = c->data; 205 | c->full = 0; 206 | } 207 | return rc; 208 | } 209 | 210 | /** 211 | * @brief Copy data out from a circular buffer to a max size 212 | * 213 | * @param c 214 | * @param buffer 215 | * @param max 216 | * @return int 217 | */ 218 | static inline int circ_get_bytes(struct circ *c, uint8_t *buffer, int max) { 219 | int remaining = c->end - c->tail; // bytes before the end of the buffer 220 | int count = MIN(circ_used(c), max); 221 | int rc = count; 222 | 223 | // Do we overflow what's remaining... 224 | if (count > remaining) { 225 | memcpy(buffer, c->tail, remaining); 226 | c->tail = c->data; 227 | count -= remaining; 228 | } 229 | 230 | // We fit in what's left 231 | memcpy(buffer, c->tail, count); 232 | c->tail += count; if (c->tail >= c->end) c->tail -= c->size; 233 | c->full = 0; 234 | return rc; 235 | } 236 | 237 | /** 238 | * @brief See if the first count bytes match 239 | * 240 | * Returns 1 on match, 0 on non-match (i.e. not like strcmp) 241 | * 242 | * @param c 243 | * @param bytes 244 | * @param count 245 | */ 246 | static inline int circ_compare(struct circ *c, uint8_t *bytes, int count) { 247 | int n = circ_bytes_before_wrap(c); 248 | 249 | if (n >= count) { 250 | // Simple one-off comparison... 251 | return (memcmp(c->tail, bytes, count) == 0); 252 | } 253 | // Otherwise we need to do this in two chunks... 254 | if (memcmp(c->tail, bytes, n) != 0) return 0; 255 | 256 | count -= n; 257 | bytes += n; 258 | return (memcmp(c->data, bytes, count) == 0); 259 | } 260 | static inline int circ_casecompare(struct circ *c, uint8_t *bytes, int count) { 261 | int n = circ_bytes_before_wrap(c); 262 | 263 | if (n >= count) { 264 | // Simple one-off comparison... 265 | return (strncasecmp((const char *)c->tail, (const char *)bytes, count) == 0); 266 | } 267 | // Otherwise we need to do this in two chunks... 268 | if (memcmp(c->tail, bytes, n) != 0) return 0; 269 | 270 | count -= n; 271 | bytes += n; 272 | return (strncasecmp((const char *)c->data, (const char *)bytes, count) == 0); 273 | } 274 | 275 | 276 | /** 277 | * @brief Clean out a circular buffer, i.e. initialise variable bits 278 | * 279 | * @param c 280 | */ 281 | static inline void circ_clean(struct circ *c) { 282 | c->head = c->tail = c->data; 283 | c->full = 0; 284 | c->last = 0; 285 | c->flush = 0; 286 | } 287 | 288 | 289 | static inline void circ_set_last(struct circ *c, int v) { 290 | c->last = v; 291 | } 292 | 293 | /** 294 | * @brief Set the flush flag in the given circular buffer 295 | * 296 | * @param c 297 | */ 298 | static inline void circ_set_flush(struct circ *c, int v) { 299 | c->flush = v; 300 | } 301 | 302 | 303 | #endif -------------------------------------------------------------------------------- /modules/lerp_circ/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_circ) 4 | 5 | add_library(lerp_circ INTERFACE) 6 | 7 | target_include_directories(lerp_circ INTERFACE include) 8 | 9 | target_sources(lerp_circ INTERFACE 10 | CMakeLists.txt 11 | 12 | circ.c 13 | include/lerp/circ.h 14 | ) 15 | 16 | target_compile_options(lerp_circ INTERFACE 17 | -Wall 18 | ) 19 | 20 | endif() 21 | -------------------------------------------------------------------------------- /modules/lerp_circ/circ.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file circ.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-31 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #include "lerp/circ.h" 13 | #include "pico/printf.h" 14 | 15 | void circ_init(struct circ *c, uint8_t *data, int len) { 16 | c->data = data; 17 | c->end = data + len; 18 | c->head = c->tail = c->data; 19 | c->size = len; 20 | c->full = 0; 21 | } 22 | 23 | 24 | 25 | static void _circ_out(char ch, void *arg) { 26 | struct circ *c = (struct circ *)arg; 27 | 28 | circ_add_byte(c, ch); 29 | } 30 | 31 | /** 32 | * @brief printf to a circ buffer, will discard the oldest data if full 33 | * 34 | * @param c 35 | * @param format 36 | * @param ... 37 | * @return int 38 | */ 39 | int circ_printf(struct circ *c, char *format, ...) { 40 | int len; 41 | va_list args; 42 | 43 | va_start(args, format); 44 | len = vfctprintf(_circ_out, (void *)c, format, args); 45 | va_end(args); 46 | return len; 47 | } 48 | 49 | /** 50 | * @brief printf to a circ buffer, using a va_list 51 | * 52 | * @param c 53 | * @param format 54 | * @param va 55 | * @return int 56 | */ 57 | int circ_aprintf(struct circ *c, char *format, va_list va) { 58 | return vfctprintf(_circ_out, (void *)c, format, va); 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /modules/lerp_circ/include/lerp/circ.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file circ.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-31 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | * Circular Buffer implementation with blocking... 11 | * 12 | */ 13 | 14 | #ifndef __LEOS_CIRC_H 15 | #define __LEOS_CIRC_H 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "pico/stdlib.h" 23 | 24 | struct circ { 25 | // "constants" 26 | uint8_t *data; // where is the data 27 | uint8_t *end; // position of the end of the buffer (one past) 28 | uint32_t size; // keep the size here for calcs 29 | 30 | // variables 31 | uint8_t *head; // where do we write to 32 | uint8_t *tail; // where to we read from 33 | 34 | // Flags 35 | int full; // keep track if we are full or empty 36 | }; 37 | 38 | /** 39 | * @brief Define and initialise a fixed size circular buffer of a given name 40 | * 41 | */ 42 | #define CIRC_DEFINE(name, sz) static uint8_t _c_data_##name[sz]; \ 43 | static struct circ _c_circ_##name = { \ 44 | .data = _c_data_##name, \ 45 | .end = _c_data_##name + sz, \ 46 | .size = sz, \ 47 | .head = _c_data_##name, \ 48 | .tail = _c_data_##name, \ 49 | .full = 0 }; \ 50 | struct circ *name = &_c_circ_##name; 51 | 52 | void circ_init(struct circ *c, uint8_t *data, int len); 53 | int circ_aprintf(struct circ *c, char *format, va_list va); 54 | int circ_printf(struct circ *c, char *format, ...); 55 | 56 | 57 | /** 58 | * @brief Is the circular buffer empty? 59 | * 60 | * @param c 61 | * @return int 62 | */ 63 | static inline int circ_is_empty(struct circ *c) { 64 | return (!c->full && (c->head == c->tail)); 65 | } 66 | 67 | static inline int circ_is_full(struct circ *c) { 68 | return c->full; 69 | } 70 | 71 | static inline int circ_is_empty_notfull(struct circ *c) { 72 | return c->head == c->tail; 73 | } 74 | 75 | static inline int circ_has_data(struct circ *c) { 76 | return (c->full || (c->head != c->tail)); 77 | } 78 | 79 | static inline int circ_space(struct circ *c) { 80 | if (c->full) return 0; 81 | return ((c->tail > c->head) ? (c->tail - c->head) : (c->size - (c->head - c->tail))); 82 | } 83 | 84 | static inline int circ_space_before_wrap(struct circ *c) { 85 | if (c->tail < c->head) return 0; 86 | return MIN(circ_space(c), c->end - c->head); 87 | } 88 | 89 | static inline void circ_advance_head(struct circ *c, int count) { 90 | c->head += count; 91 | if (c->head >= c->end) c->head -= c->size; 92 | c->full = (c->head == c->tail); 93 | } 94 | 95 | static inline int circ_used(struct circ *c) { 96 | if (c->full) return c->size; 97 | return ((c->tail > c->head) ? (c->size - (c->tail - c->head)) : (c->head - c->tail)); 98 | } 99 | 100 | static inline int circ_bytes_before_wrap(struct circ *c) { 101 | return MIN(circ_used(c), c->end - c->tail); 102 | } 103 | static inline int circ_bytes_after_wrap(struct circ *c) { 104 | if (c->head < c->tail) { 105 | return c->head - c->data; 106 | } else return 0; 107 | } 108 | 109 | static inline void circ_advance_tail(struct circ *c, int count) { 110 | c->tail += count; 111 | if (c->tail >= c->end) c->tail -= c->size; 112 | c->full = 0; 113 | } 114 | 115 | /** 116 | * @brief Add a byte to a circ, move tail on if already full 117 | * 118 | * This means we will lose the oldest data if we overflow the 119 | * buffer, which is probably the right approach. 120 | * 121 | * @param c 122 | * @param b 123 | */ 124 | static inline void circ_add_byte(struct circ *c, uint8_t byte) { 125 | if (c->full) { 126 | if (++(c->tail) == c->end) c->tail = c->data; 127 | } 128 | *(c->head++) = byte; 129 | if (c->head == c->end) c->head = c->data; 130 | c->full = (c->head == c->tail); 131 | } 132 | 133 | /** 134 | * @brief Add a set of bytes to the circular buffer 135 | * 136 | * If there's not enough space then we simulate the effect of 137 | * overriting as per add_byte. 138 | * 139 | * @param c 140 | * @param data 141 | * @param len 142 | */ 143 | static inline void circ_add_bytes(struct circ *c, uint8_t *data, int len) { 144 | int remaining, xfer, left; 145 | 146 | while (len) { 147 | remaining = c->end - c->head; // space from head until the end of the buffer 148 | xfer = MIN(len, remaining); // how much can we copy this time around? 149 | left = circ_space(c) - xfer; // how much space will be left after this? 150 | 151 | memcpy(c->head, data, xfer); 152 | c->head += xfer; if (c->head >= c->end) c->head -= c->size; 153 | 154 | // Did we overflow the size? (double negative) 155 | if (left <= 0) { 156 | c->tail -= left; if (c->tail >= c->end) c->tail -= c->size; 157 | c->full = 1; 158 | } 159 | len -= xfer; 160 | data += xfer; 161 | } 162 | } 163 | 164 | /** 165 | * @brief Move the used data from one circ and append to the other 166 | * 167 | * Note: no capacity checking is done, it needs to fit! 168 | * 169 | * @param dst 170 | * @param src 171 | */ 172 | static inline int circ_move(struct circ *dst, struct circ *src) { 173 | int size; 174 | int rc = 0; 175 | 176 | size = circ_bytes_before_wrap(src); 177 | if (size) { 178 | circ_add_bytes(dst, src->tail, size); 179 | circ_advance_tail(src, size); 180 | rc += size; 181 | } 182 | size = circ_bytes_before_wrap(src); 183 | if (size) { 184 | circ_add_bytes(dst, src->tail, size); 185 | circ_advance_tail(src, size); 186 | rc += size; 187 | } 188 | return rc; 189 | } 190 | 191 | 192 | /** 193 | * @brief Get a byte from the circular buffer, return -1 if none available 194 | * 195 | * @param c 196 | * @return int 197 | */ 198 | static inline int circ_get_byte(struct circ *c) { 199 | int rc = -1; 200 | 201 | if (circ_has_data(c)) { 202 | rc = *c->tail++; 203 | if (c->tail == c->end) c->tail = c->data; 204 | c->full = 0; 205 | } 206 | return rc; 207 | } 208 | 209 | /** 210 | * @brief Copy data out from a circular buffer to a max size 211 | * 212 | * @param c 213 | * @param buffer 214 | * @param max 215 | * @return int 216 | */ 217 | static inline int circ_get_bytes(struct circ *c, uint8_t *buffer, int max) { 218 | int remaining = c->end - c->tail; // bytes before the end of the buffer 219 | int count = MIN(circ_used(c), max); 220 | int rc = count; 221 | 222 | // Do we overflow what's remaining... 223 | if (count > remaining) { 224 | memcpy(buffer, c->tail, remaining); 225 | c->tail = c->data; 226 | count -= remaining; 227 | } 228 | 229 | // We fit in what's left 230 | memcpy(buffer, c->tail, count); 231 | c->tail += count; if (c->tail >= c->end) c->tail -= c->size; 232 | c->full = 0; 233 | return rc; 234 | } 235 | 236 | /** 237 | * @brief See if the first count bytes match 238 | * 239 | * Returns 1 on match, 0 on non-match (i.e. not like strcmp) 240 | * 241 | * @param c 242 | * @param bytes 243 | * @param count 244 | */ 245 | static inline int circ_compare(struct circ *c, uint8_t *bytes, int count) { 246 | int n = circ_bytes_before_wrap(c); 247 | 248 | if (n >= count) { 249 | // Simple one-off comparison... 250 | return (memcmp(c->tail, bytes, count) == 0); 251 | } 252 | // Otherwise we need to do this in two chunks... 253 | if (memcmp(c->tail, bytes, n) != 0) return 0; 254 | 255 | count -= n; 256 | bytes += n; 257 | return (memcmp(c->data, bytes, count) == 0); 258 | } 259 | static inline int circ_casecompare(struct circ *c, uint8_t *bytes, int count) { 260 | int n = circ_bytes_before_wrap(c); 261 | 262 | if (n >= count) { 263 | // Simple one-off comparison... 264 | return (strncasecmp((const char *)c->tail, (const char *)bytes, count) == 0); 265 | } 266 | // Otherwise we need to do this in two chunks... 267 | if (memcmp(c->tail, bytes, n) != 0) return 0; 268 | 269 | count -= n; 270 | bytes += n; 271 | return (strncasecmp((const char *)c->data, (const char *)bytes, count) == 0); 272 | } 273 | 274 | 275 | /** 276 | * @brief Clean out a circular buffer, i.e. initialise variable bits 277 | * 278 | * @param c 279 | */ 280 | static inline void circ_clean(struct circ *c) { 281 | c->head = c->tail = c->data; 282 | c->full = 0; 283 | } 284 | 285 | #endif -------------------------------------------------------------------------------- /modules/lerp_debug/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_debug) 4 | 5 | add_library(lerp_debug INTERFACE) 6 | 7 | target_include_directories(lerp_debug INTERFACE include) 8 | 9 | target_sources(lerp_debug INTERFACE 10 | CMakeLists.txt 11 | 12 | debug.c 13 | include/lerp/debug.h 14 | ) 15 | 16 | target_compile_options(lerp_debug INTERFACE 17 | -Wall 18 | ) 19 | 20 | endif() 21 | -------------------------------------------------------------------------------- /modules/lerp_debug/debug.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file leos_debug.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-04-15 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | #include "lerp/circ.h" 15 | #include "lerp/debug.h" 16 | 17 | #if defined DEBUG_UART || defined DEBUG_CDC || defined DEBUG_BUF 18 | 19 | #ifdef DEBUG_CDC 20 | #include "tusb.h" 21 | #endif 22 | 23 | CIRC_DEFINE(circ_debug, 4096); 24 | 25 | /** 26 | * @brief Send output to the debug UART (if debugging is enabled) 27 | * 28 | * This sends to the circular buffer and requires debug_poll() to be 29 | * regularly called to ensure it's transmitted. 30 | * 31 | * @param format 32 | * @param ... 33 | */ 34 | int debug_printf(char *format, ...) { 35 | int len = 0; 36 | va_list args; 37 | 38 | va_start(args, format); 39 | len = circ_aprintf(circ_debug, format, args); 40 | va_end(args); 41 | return len; 42 | } 43 | 44 | /** 45 | * @brief We may want to mirror something on a char by char basis 46 | * 47 | * @param ch 48 | * @return int 49 | */ 50 | void debug_putch(char ch) { 51 | circ_add_byte(circ_debug, ch); 52 | } 53 | 54 | /** 55 | * @brief Send any contents of the debug circular buffer to the UART 56 | * 57 | * This is non blocking so will only send if there is space to do so. 58 | * 59 | * @return int zero if no more chars are waiting to be sent 60 | */ 61 | int debug_poll() { 62 | #ifdef DEBUG_UART 63 | while (circ_has_data(circ_debug)) { 64 | if (!uart_is_writable(DEBUG_PORT)) return 1; 65 | uart_get_hw(DEBUG_PORT)->dr = circ_get_byte(circ_debug); 66 | } 67 | #endif 68 | #ifdef DEBUG_CDC 69 | int need_flush = 0; 70 | if (!tud_cdc_n_connected(DEBUG_CDC)) return 0; 71 | 72 | while (circ_has_data(circ_debug)) { 73 | if (!tud_cdc_n_write_available(DEBUG_CDC)) return 1; 74 | tud_cdc_n_write_char(DEBUG_CDC, circ_get_byte(circ_debug)); 75 | need_flush = 1; 76 | } 77 | if (need_flush) tud_cdc_n_write_flush(DEBUG_CDC); 78 | #endif 79 | return 0; 80 | } 81 | 82 | /** 83 | * @brief Initialise the debug UART and setup the circular buffer 84 | * 85 | */ 86 | void debug_init() { 87 | #ifdef DEBUG_UART 88 | uart_init(DEBUG_UART, DEBUG_UART_BAUD); 89 | 90 | // Set the TX and RX pins by using the function select on the GPIO 91 | // Set datasheet for more information on function select 92 | gpio_set_function(DEBUG_UART_TX_PIN, GPIO_FUNC_UART); 93 | gpio_set_function(DEBUG_UART_RX_PIN, GPIO_FUNC_UART); 94 | #endif 95 | } 96 | 97 | /** 98 | * @brief Imediately flush any debug output, waiting until we have sent it 99 | * 100 | */ 101 | void debug_flush() { 102 | while (debug_poll()); 103 | } 104 | 105 | /** 106 | * @brief Called in a fatal situation with useful debug information 107 | * 108 | * Will push the info into the circ buffer and then loop running flush 109 | * so that we output the data if a connection is made. Used in conjunction 110 | * with the lerp_panic() macro to get file and line information 111 | * 112 | * @param file 113 | * @param line 114 | * @param format 115 | * @param ... 116 | */ 117 | void _lerp_panic(char *file, int line, char *format, ...) { 118 | va_list args; 119 | 120 | circ_printf(circ_debug, "PANIC: %s:%d: ", file, line); 121 | va_start(args, format); 122 | circ_aprintf(circ_debug, format, args); 123 | va_end(args); 124 | 125 | while(1) { 126 | debug_poll(); 127 | } 128 | } 129 | 130 | #endif // DEBUG_PORT 131 | -------------------------------------------------------------------------------- /modules/lerp_debug/include/lerp/debug.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file leos_debug.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-04-15 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef __LERP_DEBUG_H 13 | #define __LERP_DEBUG_H 14 | 15 | // Simple debug output system, uses a circular buffer to store the output and 16 | // then a polling mechanism that will send the output to the serial port when 17 | // space is avaailable. 18 | // 19 | // We control the output mechanism depending on what define is defined... 20 | // 21 | // DEBUG_UART (=uart0 or =uart1) 22 | // DEBUG_CDC (=0, 1, 2, etc) 23 | // DEBUG_BUF (just buffer it, something else will collect it) 24 | 25 | #if defined DEBUG_UART || defined DEBUG_CDC || defined DEBUG_BUF 26 | 27 | typedef unsigned long uint32_t; 28 | 29 | // Init and poll are used by leos... 30 | void debug_init(); 31 | int debug_poll(); 32 | 33 | // Printf and flush are usable for the app... 34 | int debug_printf(char *format, ...); 35 | void debug_putch(char ch); 36 | void debug_flush(); 37 | 38 | // Supporting the panic situation... 39 | void _lerp_panic(char *file, int line, char *format, ...); 40 | #define lerp_panic(...) _lerp_panic(__FILE__, __LINE__, __VA_ARGS__) 41 | 42 | #else // DEBUG_UART or DEBUG_CDC 43 | 44 | #define debug_printf(...) 45 | #define debug_putch(...) 46 | #define debug_flush() 47 | #define debug_init() 48 | #define debug_poll() 49 | #define lerp_panic(...) panic("") 50 | 51 | #endif // DEBUG_UART or DEBUG_CDC 52 | #endif // __LERP_DEBUG_H 53 | 54 | -------------------------------------------------------------------------------- /modules/lerp_flash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_flash) 4 | 5 | add_library(lerp_flash INTERFACE) 6 | 7 | target_include_directories(lerp_flash INTERFACE include) 8 | 9 | target_sources(lerp_flash INTERFACE 10 | CMakeLists.txt 11 | 12 | flash.c 13 | include/lerp/flash.h 14 | ) 15 | 16 | target_compile_options(lerp_flash INTERFACE 17 | -Wall 18 | ) 19 | 20 | target_link_libraries(lerp_flash INTERFACE 21 | ) 22 | 23 | endif() 24 | -------------------------------------------------------------------------------- /modules/lerp_flash/include/lerp/flash.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file flash.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-05-04 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef __FLASH_H 13 | #define __FLASH_H 14 | 15 | #include 16 | 17 | /** 18 | * @brief A superblock record, needs to be 64 bytes in length 19 | * 20 | */ 21 | struct superblock { 22 | union { 23 | char name[64-12]; // filename 24 | struct { 25 | uint32_t m1; 26 | uint32_t m2; 27 | } magic; 28 | } u; 29 | uint32_t flags; // flags (overloaded as version for first item) 30 | void *ptr; // where is it 31 | uint32_t len; 32 | }; 33 | #define SB_NAME_LEN (sizeof((struct superblock *){0}->u.name)) 34 | 35 | 36 | void flash_init(); 37 | void debug_file_list(); 38 | 39 | struct superblock *find_file(const char *name); 40 | 41 | void *file_addr(const char *name, int *len); 42 | 43 | void write_file(char *name, uint8_t *data, int len); 44 | int file_start_write(char *name, int max_size); 45 | int file_write_block(uint8_t *data, int len, int last); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /modules/lerp_interact/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_interact) 4 | 5 | add_library(lerp_interact INTERFACE) 6 | 7 | target_include_directories(lerp_interact INTERFACE include) 8 | 9 | target_sources(lerp_interact INTERFACE 10 | CMakeLists.txt 11 | 12 | interact.c 13 | include/lerp/interact.h 14 | ) 15 | 16 | target_compile_options(lerp_interact INTERFACE 17 | -Wall 18 | ) 19 | target_link_libraries(lerp_interact INTERFACE 20 | lerp_circ 21 | lerp_io 22 | lerp_task 23 | ) 24 | 25 | 26 | endif() 27 | -------------------------------------------------------------------------------- /modules/lerp_interact/include/lerp/interact.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file interactive.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-02-25 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef __INTERACT_H 13 | #define __INTERACT_H 14 | 15 | #include "lerp/circ.h" 16 | 17 | #define INTA_OK 0 18 | #define INTA_ERR -1 19 | #define INTA_ABORT -2 20 | 21 | #define CMDLINE_RUNNING 0 // Nothing more to do 22 | #define CMDLINE_DONE 1 // We have pressed return 23 | #define CMDLINE_ADV 2 // Switch to advanced mode 24 | #define CMDLINE_COMP 3 // Completion called 25 | 26 | #define PROMPT_MAX 32 27 | #define HISTORY_MAX 1024 28 | #define CONN_TIME_WAIT_US 200000 29 | 30 | struct interact { 31 | struct io *io; 32 | 33 | // In order to use the interactive system we need to be able to call 34 | // a number of io funcions. 35 | // int (*func_getch)(void); // a getch function 36 | // int (*func_putch)(int ch); // a putch function 37 | // void (*func_rdflush)(void); // a read flush function 38 | // void (*func_wrflush)(void); // a write flush function 39 | // int (*func_printf)(char *fmt, ...); // a printf function 40 | // void (*func_close)(void); // a close function 41 | 42 | // struct connection *c; // our connection for io 43 | struct circ *cmd; // our command line 44 | 45 | // History bits... 46 | char history[HISTORY_MAX]; 47 | char *hptr; 48 | 49 | char prompt[PROMPT_MAX]; 50 | int plen; 51 | int cols; 52 | 53 | // A struct circ for use when we have a buffer in real life 54 | struct circ _circ; 55 | 56 | // A task pointer so we can potentially nudge the task to 57 | // re-look at the async function, but we should only do that 58 | // when we are waiting for input characters. 59 | struct task *nudgeable; 60 | }; 61 | 62 | int interact(struct interact *i, char *(*async_func)(void)); 63 | int interact_nudge(struct interact *i); 64 | struct interact *interact_with_circ(struct io *io, struct circ *cmd, char *prompt); 65 | struct interact *interact_with_buf(struct io *io, char *buf, int maxlen, char *prompt); 66 | 67 | #endif -------------------------------------------------------------------------------- /modules/lerp_interact/interact.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file interact.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-02-25 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | * Module to do command line interaction (command line editing, history etc) 11 | * ideally supporting both buffer/length and circ options (compile time) 12 | */ 13 | 14 | #include "lerp/interact.h" 15 | #include 16 | #include 17 | #include 18 | #include // isprint() 19 | #include "lerp/task.h" 20 | #include "lerp/io.h" 21 | #include "lerp/debug.h" 22 | 23 | 24 | /* 25 | A stack of strings ... we push new strings on the top, and the old 26 | ones fall off the back if they won't fit 27 | */ 28 | static void push_history(struct interact *i) { 29 | // Just move the data along enough to fit our string in with 30 | // the zero terminator 31 | int len = i->cmd->head - i->cmd->tail; 32 | char *p = i->history; 33 | int hsize = HISTORY_MAX; 34 | 35 | // If it's empty we don't add it.. 36 | // If it's the same as our previous one, we also don't add it... 37 | // But we do need to reset hptr 38 | if (len == 0 || strcmp((char *)(i->cmd->tail), p) == 0) { 39 | i->hptr = NULL; 40 | return; 41 | } 42 | 43 | int needed = len + 1; // need zero term 44 | 45 | memmove(p + needed, p, hsize-needed); 46 | memcpy(p, i->cmd->tail, len); 47 | *(p+len) = 0; 48 | i->hptr = NULL; // not pointing at anyting 49 | } 50 | 51 | // Move the history pointer on, make sure we have a whole string in the 52 | // buffer, and then update the cmd structure... 53 | static void set_prev_history(struct interact *i) { 54 | struct circ *cmd = i->cmd; 55 | char *p = i->hptr; 56 | char *end = i->history + HISTORY_MAX; 57 | 58 | if (p != NULL) { 59 | // We know the current string fits, so move on and see if the 60 | // next one does... 61 | while (*p++); 62 | // p now points at the previous string (next in memory) 63 | } else { 64 | p = i->history; 65 | } 66 | 67 | char *new = p; 68 | // Now see if it fits... 69 | while (*p && p < end) p++; 70 | if (p >= end || !*new) { 71 | // The next string doesn't fit or is empty, we stay where we are... 72 | new = i->hptr; 73 | } 74 | strcpy((char *)cmd->data, new); 75 | cmd->head = cmd->data + strlen(new); 76 | cmd->tail = cmd->head; 77 | i->hptr = new; 78 | return; 79 | } 80 | // Move the history point back ... if we are already at the start or NULL 81 | // then we just clear it. 82 | static void set_next_history(struct interact *i) { 83 | struct circ *cmd = i->cmd; 84 | char *p = i->hptr; 85 | 86 | if (p == i->history || p == NULL) { 87 | i->hptr = NULL; 88 | cmd->head = cmd->tail = cmd->data; 89 | return; 90 | } 91 | // Go back past the terminating zero from the prior string 92 | p--; p--; 93 | // Now go backwards until we get a zero, or go one beyond the start 94 | while (*p && p >= i->history) p--; 95 | p++; 96 | 97 | strcpy((char *)cmd->data, p); 98 | cmd->head = cmd->data + strlen(p); 99 | cmd->tail = cmd->head; 100 | i->hptr = p; 101 | return; 102 | } 103 | 104 | /** 105 | * @brief Get the number of columns in the window 106 | * 107 | * @param i 108 | * @return int 109 | */ 110 | static int get_columns(struct interact *i) { 111 | struct io *io = i->io; 112 | int row = 0; 113 | int col = 0; 114 | int ch; 115 | 116 | // Make sure we don't have any input waiting... 117 | io_read_flush(io); 118 | 119 | // save pos, go right, report pos, restore pos... 120 | io_printf(io, "\0337\033[999C\033[6n\0338"); 121 | // Now write flush concept anymore (handled by io) 122 | 123 | // Expect ... ESC [ 1 8 ; 8 R (where 18=row, 8=col) 124 | ch = io_get_byte(io); 125 | if (ch != '\x1b') return -1; 126 | ch = io_get_byte(io); 127 | if (ch != '[') return -1; 128 | while (1) { 129 | ch = io_get_byte(io); 130 | if (isdigit(ch)) { 131 | row *= 10; 132 | row += ch - '0'; 133 | continue; 134 | } else if (ch == ';') { 135 | break; 136 | } else { 137 | return -1; 138 | } 139 | } 140 | while (1) { 141 | ch = io_get_byte(io); 142 | if (isdigit(ch)) { 143 | col *= 10; 144 | col += ch - '0'; 145 | continue; 146 | } else if (ch == 'R') { 147 | break; 148 | } else { 149 | return -1; 150 | } 151 | } 152 | return col; 153 | } 154 | 155 | 156 | static void refresh_line(struct interact *i) { 157 | struct io *io = i->io; 158 | struct circ *cmd = i->cmd; 159 | int plen = i->plen; 160 | 161 | uint8_t *buf = cmd->data; 162 | int l = cmd->head - cmd->data; // length 163 | int p = cmd->tail - cmd->data; // position 164 | 165 | while (i->plen + p >= i->cols) { 166 | buf++; l--; p--; 167 | } 168 | while (i->plen + l >= i->cols) { 169 | l--; 170 | } 171 | io_printf(io, "\r%s", i->prompt); 172 | if (l) io_printf(io, "%.*s", l, buf); 173 | io_printf(io, "\033[0K\r\033[%dC", p + plen); 174 | } 175 | 176 | 177 | struct interact *interact_with_circ(struct io *io, struct circ *cmd, char *prompt) { 178 | assert(strlen(prompt) < PROMPT_MAX); 179 | struct interact *i = malloc(sizeof(struct interact)); 180 | if (!i) return NULL; 181 | 182 | i->io = io; 183 | i->nudgeable = NULL; 184 | 185 | strcpy(i->prompt, prompt); 186 | i->plen = strlen(prompt); 187 | memset(i->history, 0, HISTORY_MAX); 188 | 189 | i->cmd = cmd; 190 | return i; 191 | } 192 | 193 | struct interact *interact_with_buf(struct io *io, char *buf, int maxlen, char *prompt) { 194 | assert(strlen(prompt) < PROMPT_MAX); 195 | struct interact *i = malloc(sizeof(struct interact)); 196 | if (!i) return NULL; 197 | 198 | i->io = io; 199 | i->nudgeable = NULL; 200 | 201 | strcpy(i->prompt, prompt); 202 | i->plen = strlen(prompt); 203 | memset(i->history, 0, HISTORY_MAX); 204 | 205 | // Setup the internal circ to use the buffer... 206 | i->_circ.data = (uint8_t *)buf; 207 | i->_circ.end = (uint8_t *)buf + maxlen; 208 | i->_circ.size = maxlen; 209 | i->_circ.head = i->_circ.tail = (uint8_t *)buf; 210 | i->_circ.full = 0; 211 | 212 | i->cmd = &(i->_circ); 213 | return i; 214 | } 215 | 216 | 217 | int interact_nudge(struct interact *i) { 218 | if (i->nudgeable) { 219 | task_wake(i->nudgeable, -10); 220 | } 221 | } 222 | 223 | 224 | int interact(struct interact *i, char *(*async_func)(void)) { 225 | struct io *io = i->io; 226 | struct circ *cmd = i->cmd; 227 | int ch; 228 | 229 | // Make sure our cmdline circ is properly reset 230 | cmd->head = cmd->tail = cmd->data; 231 | *cmd->data = 0; 232 | 233 | // Try to figure out the columns 234 | i->cols = get_columns(i); 235 | if (i->cols < 0) i->cols = 80; 236 | 237 | // Output the prompt... 238 | refresh_line(i); 239 | 240 | while(1) { 241 | // See if we have some async stuff to display... 242 | if (async_func) { 243 | char *async = async_func(); 244 | if (async) { 245 | io_printf(io, "\r\033[0K"); 246 | io_printf(io, "%s", async); 247 | refresh_line(i); 248 | continue; 249 | } 250 | } 251 | // Limit nudging for this getch() only... 252 | i->nudgeable = current_task(); 253 | ch = io_get_byte(io); 254 | i->nudgeable = NULL; 255 | if (ch == -10) continue; // interactive nudge 256 | if (ch < 0) return INTA_ERR; 257 | 258 | switch (ch) { 259 | case '\x1b': // Escape sequence... 260 | ch = io_get_byte(io); 261 | if (ch == -1) return INTA_ERR; 262 | if (ch != '[') break; // we only want ESC [ xxx 263 | ch = io_get_byte(io); 264 | if (ch == -1) return INTA_ERR; 265 | switch (ch) { 266 | case 'A': // UP 267 | set_prev_history(i); 268 | refresh_line(i); 269 | break; 270 | case 'B': // DOWN 271 | set_next_history(i); 272 | refresh_line(i); 273 | break; 274 | case 'D': // LEFT 275 | if (cmd->tail > cmd->data) { 276 | cmd->tail--; 277 | refresh_line(i); 278 | } 279 | break; 280 | case 'C': // RIGHT 281 | if (cmd->tail < cmd->head) { 282 | cmd->tail++; 283 | refresh_line(i); 284 | } 285 | break; 286 | } 287 | break; 288 | 289 | case '\x03': // CTRL-C 290 | cmd->head = cmd->tail; // put end where cursor is 291 | refresh_line(i); 292 | io_printf(io, "^C\r\n"); 293 | return INTA_ABORT; 294 | 295 | case '\x04': // CTRL-D 296 | // should close the connection (if possible) if we have nothing typed 297 | if (cmd->head == cmd->data) { 298 | io_close(io); 299 | return INTA_ERR; 300 | } 301 | break; 302 | 303 | case '\x08': // BACKSPACE 304 | case 0x7f: // BACKSPACE in telnet (otherwise isn't it delete?) 305 | if (cmd->tail > cmd->data && cmd->head > cmd->data) { 306 | memmove(cmd->tail-1, cmd->tail, cmd->head - cmd->tail); 307 | cmd->head--; 308 | cmd->tail--; 309 | refresh_line(i); 310 | } 311 | break; 312 | 313 | case '\r': // ENTER 314 | cmd->tail = cmd->head; 315 | refresh_line(i); 316 | io_printf(io, "\r\n"); 317 | cmd->tail = cmd->data; // ready to process 318 | push_history(i); 319 | return INTA_OK; 320 | 321 | default: 322 | if (isprint(ch) && cmd->head < cmd->end) { 323 | if (cmd->head == cmd->tail) { 324 | *cmd->tail++ = ch; 325 | cmd->head++; 326 | if (i->plen + (cmd->head - cmd->data) < i->cols) { 327 | io_printf(io, "%c", ch); 328 | } else { 329 | refresh_line(i); 330 | } 331 | } else { 332 | memmove(cmd->tail+1, cmd->tail, cmd->head - cmd->tail); 333 | *cmd->tail++ = ch; 334 | cmd->head++; 335 | refresh_line(i); 336 | } 337 | } 338 | break; 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /modules/lerp_io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_io) 4 | 5 | add_library(lerp_io INTERFACE) 6 | 7 | target_include_directories(lerp_io INTERFACE include) 8 | 9 | target_sources(lerp_io INTERFACE 10 | CMakeLists.txt 11 | 12 | io.c 13 | include/lerp/io.h 14 | ) 15 | 16 | target_compile_options(lerp_io INTERFACE 17 | -Wall 18 | ) 19 | 20 | target_link_libraries(lerp_io INTERFACE 21 | lerp_circ 22 | lerp_task 23 | ) 24 | 25 | endif() 26 | -------------------------------------------------------------------------------- /modules/lerp_io/include/lerp/io.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef __IOIO_H 4 | #define __IOIO_H 5 | 6 | #include 7 | #include "lwip/tcp.h" 8 | #include "lwip/pbuf.h" 9 | #include "lerp/circ.h" 10 | 11 | // Possible special return codes from io_get_byte 12 | #define IO_DATA 0 // internal 13 | #define IO_ERROR -1 14 | #define IO_CONNECT -2 15 | #define IO_DISCONNECT -3 16 | 17 | 18 | struct io { 19 | struct io *next; // linked list of io ports 20 | 21 | struct circ *input; 22 | struct circ *output; 23 | 24 | // We currently support either cdc ports or TCP connections... 25 | int cdc_port; 26 | int tcp_port; 27 | int support_telnet; 28 | 29 | // Will be populated when we are connected... 30 | int usb_is_connected; 31 | struct tcp_pcb *pcb; 32 | struct pbuf *tcpdata; // tcp data waiting 33 | 34 | // Task pointers for blocking... 35 | struct task *waiting_on_input; 36 | struct task *waiting_on_output; 37 | 38 | // Internal circ structures for input and output... 39 | struct circ _input; 40 | struct circ _output; 41 | }; 42 | 43 | struct io *io_init(int cdc_port, int tcp_port, int buf_size); 44 | 45 | void io_poll(); 46 | int io_get_byte(struct io *io); 47 | int io_put_byte(struct io *io, uint8_t ch); 48 | int io_put_hexbyte(struct io *io, uint8_t b); 49 | int io_printf(struct io *io, char *format, ...); 50 | int io_aprintf(struct io *io, char *format, va_list args); 51 | int io_peek_byte(struct io *io); 52 | int io_read_flush(struct io *io); 53 | int io_is_connected(struct io *io); 54 | void io_close(struct io *io); 55 | #endif 56 | -------------------------------------------------------------------------------- /modules/lerp_io/io.c: -------------------------------------------------------------------------------- 1 | 2 | #include "pico/printf.h" 3 | #include "lerp/task.h" 4 | #include "lerp/circ.h" 5 | #include "lerp/debug.h" 6 | #include "lerp/io.h" 7 | #include "tusb.h" 8 | #include "pico/cyw43_arch.h" 9 | 10 | #include "lwip/pbuf.h" 11 | #include "lwip/tcp.h" 12 | 13 | 14 | // 15 | // Which IO ports do we have active... 16 | // 17 | struct io *ios = NULL; 18 | 19 | 20 | /** 21 | * Input mechanism -- needs to support both USB and Ethernet, and unfortunately 22 | * one of those works as a pull and one as a push. 23 | * 24 | * So... use a circular buffer for input which we refill from usb and the ethernet 25 | * stack fills as packets come in. 26 | * 27 | */ 28 | 29 | // ----------------------------------------------------------------------------------- 30 | // USB FUNCTIONS 31 | // ----------------------------------------------------------------------------------- 32 | 33 | static int refill_from_usb(struct io *io) 34 | { 35 | int count = 0; 36 | 37 | int space = circ_space_before_wrap(io->input); 38 | if (space) 39 | { 40 | int avail = tud_cdc_n_available(io->cdc_port); 41 | if (avail) 42 | { 43 | int size = MIN(space, avail); 44 | count = tud_cdc_n_read(io->cdc_port, io->input->head, size); 45 | circ_advance_head(io->input, count); 46 | } 47 | } 48 | if (count && io->waiting_on_input) { 49 | task_wake(io->waiting_on_input, IO_DATA); 50 | io->waiting_on_input = NULL; 51 | } 52 | return count; 53 | } 54 | 55 | static void send_to_usb(struct io *io) { 56 | int have = circ_bytes_before_wrap(io->output); 57 | if (!have) return; 58 | int space = tud_cdc_n_write_available(io->cdc_port); 59 | if (!space) return; 60 | 61 | while (space && have) { 62 | int size = MIN(have, space); 63 | int count = tud_cdc_n_write(io->cdc_port, io->output->tail, size); 64 | circ_advance_tail(io->output, count); 65 | space -= count; 66 | have = circ_bytes_before_wrap(io->output); 67 | } 68 | tud_cdc_n_write_flush(io->cdc_port); 69 | // If we get here then we must have sent something so we 70 | // will have some space... 71 | if (io->waiting_on_output) { 72 | task_wake(io->waiting_on_output, IO_DATA); 73 | io->waiting_on_output = NULL; 74 | } 75 | } 76 | 77 | // ----------------------------------------------------------------------------------- 78 | // Support for TELNET processing when needed... 79 | // ----------------------------------------------------------------------------------- 80 | 81 | #define TELNET_IAC ((uint8_t) 255) 82 | #define TELNET_WILL ((uint8_t) 251) 83 | #define TELNET_WONT ((uint8_t) 252) 84 | #define TELNET_DO ((uint8_t) 253) 85 | #define TELNET_DONT ((uint8_t) 254) 86 | #define TELNET_SE ((uint8_t) 240) 87 | #define TELNET_NOP ((uint8_t) 241) 88 | #define TELNET_DATA_MARK ((uint8_t) 242) 89 | #define TELNET_BREAK ((uint8_t) 243) 90 | #define TELNET_IP ((uint8_t) 244) 91 | #define TELNET_AO ((uint8_t) 245) 92 | #define TELNET_AYT ((uint8_t) 246) 93 | #define TELNET_EC ((uint8_t) 247) 94 | #define TELNET_EL ((uint8_t) 248) 95 | #define TELNET_GA ((uint8_t) 249) 96 | #define TELNET_SB ((uint8_t) 250) 97 | 98 | #define TELNET_OPT_BINARY ((uint8_t) 0) 99 | #define TELNET_OPT_ECHO ((uint8_t) 1) 100 | #define TELNET_OPT_SUPPRESS_GA ((uint8_t) 3) 101 | #define TELNET_OPT_STATUS ((uint8_t) 5) 102 | #define TELNET_OPT_TIMING_MARK ((uint8_t) 6) 103 | #define TELNET_OPT_EXOPL ((uint8_t) 255) 104 | 105 | const static char connect_sequence[] = { TELNET_IAC, TELNET_WILL, TELNET_OPT_SUPPRESS_GA, 106 | TELNET_IAC, TELNET_WILL, TELNET_OPT_BINARY, 107 | TELNET_IAC, TELNET_WILL, TELNET_OPT_ECHO }; 108 | 109 | static void inline tsend(struct io *io, uint8_t cmd, uint8_t ch) { 110 | circ_add_byte(io->output, TELNET_IAC); 111 | circ_add_byte(io->output, cmd); 112 | circ_add_byte(io->output, ch); 113 | } 114 | 115 | /** 116 | * @brief State machine for handling telnet escape sequences 117 | * 118 | * @param io 119 | * @return int 120 | */ 121 | static int telnet_process_char(struct io *io, int ch) { 122 | enum { STATE_NORMAL = 0, STATE_IAC, STATE_WILL, STATE_WONT, STATE_DO, STATE_DONT }; 123 | static int state = STATE_NORMAL; 124 | 125 | switch(state) { 126 | case STATE_NORMAL: 127 | if (ch == TELNET_IAC) { 128 | state = STATE_IAC; break; 129 | } else { 130 | circ_add_byte(io->input, ch); return 1; 131 | } 132 | case STATE_IAC: 133 | switch(ch) { 134 | case TELNET_IAC: state = STATE_NORMAL; circ_add_byte(io->input, 255); return 1; 135 | case TELNET_WILL: state = STATE_WILL; break; 136 | case TELNET_WONT: state = STATE_WONT; break; 137 | case TELNET_DO: state = STATE_DO; break; 138 | case TELNET_DONT: state = STATE_DONT; break; 139 | case TELNET_AYT: state = STATE_NORMAL; break; // error really // 140 | case TELNET_GA: 141 | case TELNET_NOP: 142 | default: state = STATE_NORMAL; break; 143 | } 144 | break; 145 | case STATE_WILL: 146 | if ((ch != TELNET_OPT_BINARY) && (ch != TELNET_OPT_ECHO) && (ch != TELNET_OPT_SUPPRESS_GA)) { 147 | tsend(io, TELNET_DONT, ch); 148 | } 149 | state = STATE_NORMAL; 150 | break; 151 | case STATE_DO: 152 | if ((ch != TELNET_OPT_BINARY) && (ch != TELNET_OPT_ECHO) && (ch != TELNET_OPT_SUPPRESS_GA)) { 153 | tsend(io, TELNET_WONT, ch); 154 | } 155 | state = STATE_NORMAL; 156 | break; 157 | case STATE_WONT: // no response 158 | case STATE_DONT: // no response 159 | default: // error, just go back to normal 160 | state = STATE_NORMAL; 161 | break; 162 | } 163 | return 0; 164 | } 165 | 166 | // ----------------------------------------------------------------------------------- 167 | // TCP FUNCTIONS 168 | // ----------------------------------------------------------------------------------- 169 | 170 | static int refill_from_tcp(struct io *io) 171 | { 172 | int count = 0; 173 | int size; 174 | 175 | // Don't do anything if we don't have any data... 176 | if (!io->tcpdata) return 0; 177 | if (circ_is_full(io->input)) return 0; 178 | 179 | if (io->support_telnet) { 180 | // Telnet version... let's work through as much of the first pbuf as we can 181 | // we'll come back again to do the next one etc. 182 | while (!circ_is_full(io->input)) { 183 | telnet_process_char(io, ((uint8_t *)(io->tcpdata->payload))[count++]); 184 | if (count == io->tcpdata->len) break; 185 | } 186 | io->tcpdata = pbuf_free_header(io->tcpdata, count); 187 | } else { 188 | // Otherwise try and copy as much as we can fit into the space before 189 | // the circ wrap (we'll be back for more) 190 | size = MIN(circ_space_before_wrap(io->input), io->tcpdata->tot_len); 191 | count = pbuf_copy_partial(io->tcpdata, io->input->head, size, 0); 192 | io->tcpdata = pbuf_free_header(io->tcpdata, count); 193 | circ_advance_head(io->input, count); 194 | } 195 | if (count && io->waiting_on_input) { 196 | task_wake(io->waiting_on_input, IO_DATA); 197 | io->waiting_on_input = NULL; 198 | } 199 | return count; 200 | } 201 | 202 | 203 | void netio_close(struct io *io, char *message) { 204 | err_t err; 205 | 206 | if (message) { 207 | err = tcp_write(io->pcb, message, strlen(message), 0); 208 | tcp_output(io->pcb); 209 | } 210 | tcp_close(io->pcb); 211 | 212 | io->pcb = NULL; 213 | } 214 | 215 | 216 | /** 217 | * @brief Try to send any pending outgoing data (if there is any) 218 | * 219 | * @param pcb 220 | * @return err_t 221 | */ 222 | static err_t netio_send(struct io *io) { 223 | struct tcp_pcb *pcb = io->pcb; 224 | err_t err; 225 | int sent = 0; 226 | 227 | // while (!circ_is_empty(outgoing)) { 228 | if (!circ_is_empty(io->output)) { 229 | int size = MIN(tcp_sndbuf(pcb), circ_bytes_before_wrap(io->output)); 230 | // debug_printf("TCPWRITE [%.*s]\r\n", size, outgoing->tail); 231 | err = tcp_write(pcb, io->output->tail, size, TCP_WRITE_FLAG_COPY); 232 | // if (err != ERR_OK) break; 233 | if (err != ERR_OK) return ERR_OK; 234 | circ_advance_tail(io->output, size); 235 | sent += size; 236 | } 237 | if (sent) { 238 | tcp_output(pcb); 239 | if (io->waiting_on_output) { 240 | task_wake(io->waiting_on_output, IO_DATA); 241 | io->waiting_on_output = NULL; 242 | } 243 | } 244 | return ERR_OK; 245 | } 246 | 247 | /* 248 | static err_t netio_sent(void *arg, struct tcp_pcb *pcb, uint16_t len) { 249 | debug_printf("sent %d bytes\r\n", len); 250 | // Try to send some more... 251 | //netio_send(pcb); 252 | return ERR_OK; 253 | } 254 | */ 255 | 256 | static err_t netio_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { 257 | struct io *io = (struct io *)arg; 258 | 259 | if (!p) { 260 | // This is a disconnect... 261 | tcp_close(pcb); 262 | io->pcb = NULL; 263 | if (io->tcpdata) { 264 | pbuf_free(io->tcpdata); 265 | io->tcpdata = NULL; 266 | } 267 | debug_printf("TCP Connection dropped\r\n"); 268 | if (io->waiting_on_input) { 269 | task_wake(io->waiting_on_input, IO_DISCONNECT); 270 | io->waiting_on_input = NULL; 271 | } 272 | return ERR_OK; 273 | } 274 | 275 | // We need to make sure "tcpdata" has some data in it, but we don't 276 | // want it to grow too big, so don't add to it if it's already over 277 | // 4k? 278 | if (io->tcpdata && (io->tcpdata->tot_len > 4096)) { 279 | // We can't accept this yet... 280 | return ERR_MEM; 281 | } 282 | tcp_recved(pcb, p->tot_len); 283 | if (!io->tcpdata) { 284 | io->tcpdata = p; 285 | } else { 286 | pbuf_cat(io->tcpdata, p); 287 | } 288 | return ERR_OK; 289 | } 290 | 291 | 292 | static err_t netio_accept(void *arg, struct tcp_pcb *pcb, err_t err) { 293 | static const char already_usb[] = "Session refused, USB already connected.\r\n"; 294 | static const char already_net[] = "Session refused, net already connected.\r\n"; 295 | struct io *io = (struct io *)arg; 296 | 297 | if (err != ERR_OK || pcb == NULL) { 298 | debug_printf("accept failed\r\n"); 299 | return ERR_VAL; 300 | } 301 | if (io->usb_is_connected) { 302 | err = tcp_write(pcb, already_usb, sizeof(already_usb)-1, 0); 303 | tcp_output(pcb); 304 | tcp_close(pcb); 305 | return ERR_OK; 306 | } 307 | if (io->pcb) { 308 | err = tcp_write(pcb, already_net, sizeof(already_net)-1, 0); 309 | tcp_output(pcb); 310 | tcp_close(pcb); 311 | return ERR_OK; 312 | } 313 | 314 | tcp_arg(pcb, (void *)io); 315 | //tcp_sent(pcb, netio_sent); 316 | tcp_recv(pcb, netio_recv); 317 | io->pcb = pcb; 318 | 319 | debug_printf("Have new TCP connection pcb=%08x\r\n", io->pcb); 320 | 321 | // Send the TELNET connection sequence if needed... 322 | if (io->support_telnet) { 323 | tcp_write(pcb, connect_sequence, sizeof(connect_sequence), 0); 324 | tcp_output(pcb); 325 | } 326 | return ERR_OK; 327 | } 328 | 329 | 330 | 331 | 332 | // ----------------------------------------------------------------------------------- 333 | // GENERIC IO FUNCTIONS 334 | // ----------------------------------------------------------------------------------- 335 | 336 | int io_is_connected(struct io *io) { 337 | return io->usb_is_connected || (io->pcb != NULL); 338 | } 339 | 340 | int io_get_byte(struct io *io) { 341 | int reason; 342 | 343 | // TODO: if not connected return -3 otherwise we could just hang if we drop a 344 | // connection when we aren't waiting (maybe only do this in the is_empty bit?) 345 | 346 | if (circ_is_empty(io->input)) { 347 | io->waiting_on_input = current_task(); 348 | reason = task_block(); 349 | if (reason < 0) return reason; 350 | } 351 | return circ_get_byte(io->input); 352 | } 353 | 354 | int io_peek_byte(struct io *io) { 355 | if (circ_is_empty(io->input)) return -1; 356 | return *(io->input->tail); 357 | } 358 | 359 | int io_put_byte(struct io *io, uint8_t ch) { 360 | int reason; 361 | 362 | if (circ_is_full(io->output)) { 363 | io->waiting_on_output = current_task(); 364 | reason = task_block(); 365 | if (reason < 0) return reason; 366 | } 367 | //debug_putch(ch); 368 | circ_add_byte(io->output, ch); 369 | return 0; 370 | } 371 | 372 | int io_read_flush(struct io *io) { 373 | if (io->usb_is_connected) tud_cdc_n_read_flush(io->cdc_port); 374 | circ_clean(io->input); 375 | } 376 | 377 | int io_put_hexbyte(struct io *io, uint8_t b) { 378 | static const char hexdigits[] = "0123456789abcdef"; 379 | uint8_t sum = 0; 380 | uint8_t ch; 381 | 382 | ch = hexdigits[b >> 4]; 383 | sum += ch; 384 | io_put_byte(io, ch); 385 | ch = hexdigits[b & 0xf]; 386 | sum += ch; 387 | io_put_byte(io, ch); 388 | return sum; 389 | } 390 | 391 | 392 | 393 | static void _io_out(char ch, void *arg) { 394 | struct io *io = (struct io *)arg; 395 | io_put_byte(io, ch); 396 | } 397 | int io_printf(struct io *io, char *format, ...) { 398 | int len; 399 | va_list args; 400 | va_start(args, format); 401 | len = vfctprintf(_io_out, (void *)io, format, args); 402 | va_end(args); 403 | return len; 404 | } 405 | int io_aprintf(struct io *io, char *format, va_list args) { 406 | return vfctprintf(_io_out, (void *)io, format, args); 407 | } 408 | 409 | void io_close(struct io *io) { 410 | // TODO: close the network becasue we can 411 | } 412 | 413 | 414 | // ----------------------------------------------------------------------------------- 415 | // CONNECTION HANDLING 416 | // ----------------------------------------------------------------------------------- 417 | // 418 | // We need to keep track of whether we have a conection or not and wake up anything 419 | // waiting if something disconnects. 420 | // 421 | // We also need to disconnect TCP if we get an incoming USB connection as USB will 422 | // take priority (mainly because we can't easily bump it!) 423 | // 424 | // TODO: puts will block if something disconects and the circ is full, do we just 425 | // want to consume everything if we are not connected? 426 | // 427 | 428 | static void check_connections(struct io*io) { 429 | if (io->cdc_port >= 0) { 430 | // New connection... 431 | if (!io->usb_is_connected && tud_cdc_n_connected(io->cdc_port)) { 432 | io->usb_is_connected = 1; 433 | debug_printf("HAVE USB CONNECT\r\n"); 434 | if (io->pcb) { 435 | netio_close(io, "Session closed due to USB connection.\r\n"); 436 | } 437 | } 438 | if (io->usb_is_connected && !tud_cdc_n_connected(io->cdc_port)) { 439 | io->usb_is_connected = 0; 440 | debug_printf("HAVE USB DISCONNECT\r\n"); 441 | if (io->waiting_on_input) { 442 | task_wake(io->waiting_on_input, IO_DISCONNECT); 443 | io->waiting_on_input = NULL; 444 | } 445 | } 446 | } 447 | } 448 | 449 | 450 | 451 | static int cyw43_is_up = 0; 452 | 453 | void io_poll() { 454 | // Generic polling first... 455 | tud_task(); 456 | 457 | if (cyw43_is_up) { 458 | cyw43_arch_poll(); 459 | } 460 | 461 | struct io *io = ios; 462 | while (io) { 463 | check_connections(io); 464 | 465 | if (io->usb_is_connected) { 466 | refill_from_usb(io); 467 | send_to_usb(io); 468 | } 469 | if (io->pcb) { 470 | refill_from_tcp(io); 471 | //send_to_tcp(io); 472 | netio_send(io); 473 | } 474 | io = io->next; 475 | } 476 | } 477 | 478 | /** 479 | * @brief Intialise an IO interface, can we a CDC port or TCP port 480 | * 481 | * If both are specified then it becomes auto-switching with USB 482 | * taking precedent and dropping the other one. 483 | * 484 | * @param cdc_port 485 | * @param tcp_port 486 | * @return struct io* 487 | */ 488 | struct io *io_init(int cdc_port, int tcp_port, int buf_size) { 489 | struct io *io = malloc(sizeof(struct io)); 490 | if (!io) return NULL; 491 | 492 | io->cdc_port = cdc_port; 493 | io->tcp_port = tcp_port; 494 | io->pcb = NULL; 495 | io->tcpdata = NULL; 496 | io->waiting_on_input = NULL; 497 | io->waiting_on_output = NULL; 498 | io->usb_is_connected = 0; 499 | io->support_telnet = 0; 500 | 501 | circ_init(&io->_input, malloc(buf_size), buf_size); 502 | circ_init(&io->_output, malloc(buf_size), buf_size); 503 | io->input = &io->_input; 504 | io->output = &io->_output; 505 | 506 | if (tcp_port) { 507 | if (!cyw43_is_up) { 508 | if (cyw43_arch_init()) { 509 | debug_printf("Failed to initialise wifi\r\n"); 510 | return NULL; 511 | } 512 | cyw43_arch_enable_sta_mode(); 513 | cyw43_is_up = 1; 514 | } 515 | 516 | struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); 517 | err_t err = tcp_bind(pcb, NULL, tcp_port); 518 | if (err) { 519 | debug_printf("failed to bind to port %d, err=%d\r\n", tcp_port, err); 520 | return NULL; 521 | } 522 | struct tcp_pcb *svr_pcb = tcp_listen(pcb); 523 | if (!svr_pcb) { 524 | debug_printf("failed to listen on port %d, err=%d\r\n", tcp_port, err); 525 | return NULL; 526 | } 527 | tcp_arg(svr_pcb, (void *)io); 528 | tcp_accept(svr_pcb, netio_accept); 529 | } 530 | // Add to our linked list... 531 | io->next = ios; 532 | ios = io; 533 | // And return... 534 | return io; 535 | } 536 | 537 | -------------------------------------------------------------------------------- /modules/lerp_task/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_task) 4 | 5 | add_library(lerp_task INTERFACE) 6 | 7 | target_include_directories(lerp_task INTERFACE include) 8 | 9 | target_sources(lerp_task INTERFACE 10 | CMakeLists.txt 11 | 12 | task.c 13 | include/lerp/task.h 14 | list.h 15 | plat_rp2040.c 16 | ) 17 | 18 | target_compile_options(lerp_task INTERFACE 19 | -Wall 20 | ) 21 | target_link_libraries(lerp_task INTERFACE 22 | pico_stdlib 23 | hardware_pio 24 | hardware_i2c 25 | hardware_exception 26 | ) 27 | 28 | 29 | endif() 30 | -------------------------------------------------------------------------------- /modules/lerp_task/include/lerp/task.h: -------------------------------------------------------------------------------- 1 | /* ======================================== 2 | * 3 | * Copyright YOUR COMPANY, THE YEAR 4 | * All Rights Reserved 5 | * UNPUBLISHED, LICENSED SOFTWARE. 6 | * 7 | * CONFIDENTIAL AND PROPRIETARY INFORMATION 8 | * WHICH IS THE PROPERTY OF your company. 9 | * 10 | * ======================================== 11 | */ 12 | 13 | #ifndef _LERP_TASK_H 14 | #define _LERP_TASK_H 15 | 16 | #include "pico/stdlib.h" 17 | 18 | // --------------------------------------------------------------------- 19 | // Main struct and values for the task 20 | // --------------------------------------------------------------------- 21 | 22 | enum state { 23 | READY = 0, 24 | BLOCKED = 1, 25 | IRQWAIT = 2, // special case, can't be woken by anything else 26 | RUNNING = 3, 27 | }; 28 | 29 | // TODO ... remove this, we don't want to expose this 30 | enum wake_reason { 31 | WAKE_UNKNOWN = 0, 32 | WAKE_IRQ, 33 | WAKE_READY, // we yieled as READY, so just rescheduled. 34 | WAKE_GENERIC, 35 | WAKE_TIMEOUT, 36 | WAKE_FLAG, 37 | WAKE_MUTEX, 38 | }; 39 | 40 | struct task { 41 | uint32_t *sp; 42 | uint32_t *stack_end; 43 | enum state state; 44 | int wake_reason; 45 | uint32_t wait_time; // used when blocked for time 46 | 47 | struct task *task_next; 48 | struct task *task_prev; 49 | struct task *time_next; 50 | struct task *time_prev; 51 | 52 | // Defined above so we don't have to include list.h 53 | // LIST_REF(task, struct task *); 54 | // LIST_REF(time, struct task *); 55 | }; 56 | 57 | #define DEFINE_TASK(name, stacksize) struct task name; uint8_t _stack_##name[stacksize] 58 | #define CREATE_TASK(name, f, arg) task_create(&name, f, arg, _stack_##name, sizeof(_stack_##name)) 59 | 60 | // 61 | // We expose a couple of otherwise internal variables so that we have efficient 62 | // access to them rather than needing a function call 63 | // 64 | extern volatile struct task *currentTCB; 65 | //extern volatile uint32_t current_tick; 66 | 67 | #define current_task() ((struct task *)currentTCB) 68 | //#define task_current_tick() (current_tick) 69 | 70 | // 71 | // Work out how many ticks have elasped, but taking into account overflow 72 | // This assumes a uint32_t to ensure correct overflow 73 | // 74 | #define ELAPSED(then,now) (uint32_t)((then) < (now) ? (now) - (then) : ~(then)+1+(now)) 75 | #define TIME_EXPIRED 0xffffffff 76 | #define TIME_FOREVER 0xffffffff 77 | 78 | // 79 | // Main return values for success and failure... 80 | // 81 | #define LE_OK 0 82 | #define LE_ERR 1 83 | #define LE_ABORT 2 84 | 85 | 86 | // 87 | // Fundamental functions.... 88 | // 89 | //struct task *current_task(); 90 | int task_block(); 91 | void task_halt(); 92 | int task_block_with_time(uint32_t time); 93 | void task_wake(struct task *task, int reason); 94 | int task_wake_reason(); 95 | 96 | static inline int task_sleep_us(uint32_t us) { return task_block_with_time(us); } 97 | static inline int task_sleep_ms(uint32_t ms) { return task_block_with_time(ms * 1000); } 98 | static inline int task_sleep(uint32_t s) { return task_block_with_time(s * 1000 * 1000); } 99 | 100 | void leos_init(void (*poll_func)(void)); 101 | 102 | void leos_start(); 103 | void task_create(struct task *task, void (*function)(void *), void *arg, uint8_t *stack, int stack_size); 104 | void task_yield( void ); 105 | void task_yield_if_required(); 106 | 107 | #endif // _LERP_TASK_H 108 | 109 | -------------------------------------------------------------------------------- /modules/lerp_task/list.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file leos_list.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-04-15 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef _LEOS_LIST_H 13 | #define _LEOS_LIST_H 14 | 15 | #define DEFINE_LIST(name, type) struct { type *head; type *tail; } name = { .head=NULL, .tail=NULL }; 16 | #define LIST_REF(name, type) type name##_next; type name##_prev 17 | 18 | // 19 | // Add to the end of a given list (using 'ref' next/prev items) 20 | // 21 | #define LIST_ADD(list,item,ref) \ 22 | (item)->ref##_prev = (list)->tail; \ 23 | if ((list)->tail) { \ 24 | (list)->tail->ref##_next = (item); \ 25 | } \ 26 | (list)->tail = (item); \ 27 | if (!(list)->head) (list)->head = (item); 28 | 29 | // 30 | // Add to a list, before the given item (if item is NULL then at tail) 31 | // 32 | #define LIST_ADD_BEFORE(list,item,ref,other) \ 33 | (item)->ref##_next = (other); \ 34 | if (other) { \ 35 | (item)->ref##_prev = (other)->ref##_prev; \ 36 | } else { \ 37 | (item)->ref##_prev = (list)->tail; \ 38 | } \ 39 | if ((item)->ref##_prev) { \ 40 | (item)->ref##_prev->ref##_next = (item); \ 41 | } else { \ 42 | (list)->head = (item); \ 43 | } \ 44 | if ((item)->ref##_next) { \ 45 | (item)->ref##_next->ref##_prev = (item); \ 46 | } else { \ 47 | (list)->tail = (item); \ 48 | } 49 | 50 | // 51 | // Add to the front of a given list (using 'ref' next/prev items) 52 | // 53 | #define LIST_PUSH(list,item,ref) \ 54 | (item)->ref##_next = (list)->head; \ 55 | if ((list)->head) { \ 56 | (list)->head->ref##_prev = (item); \ 57 | } \ 58 | (list)->head = (item); \ 59 | if (!(list)->tail) (list)->tail = (item); 60 | 61 | 62 | // 63 | // Pop the first item from the list (need to provide type for this) 64 | // 65 | #define LIST_POP(list,ref,type) ({ \ 66 | type v = (list)->head; \ 67 | if (v) { \ 68 | (list)->head = v->ref##_next; \ 69 | if ((list)->head) { \ 70 | (list)->head->ref##_prev = NULL; \ 71 | } else { \ 72 | (list)->tail = NULL; \ 73 | } \ 74 | v->ref##_next = NULL; \ 75 | } v; }) 76 | 77 | // 78 | // Remove an item from the list 79 | // 80 | #define LIST_REMOVE(list,item,ref) \ 81 | if ((item) == (list)->head) { \ 82 | (list)->head = (item)->ref##_next; \ 83 | } \ 84 | if ((item) == (list)->tail) { \ 85 | (list)->tail = (item)->ref##_prev; \ 86 | } \ 87 | if ((item)->ref##_next) { \ 88 | (item)->ref##_next->ref##_prev = (item)->ref##_prev; \ 89 | } \ 90 | if ((item)->ref##_prev) { \ 91 | (item)->ref##_prev->ref##_next = (item)->ref##_next; \ 92 | } \ 93 | (item)->ref##_next = NULL; \ 94 | (item)->ref##_prev = NULL; 95 | 96 | 97 | // 98 | // Add an item to a list taking into account a field (time) that represents a time 99 | // delta. So the item goes in the correct place and the timings are updated accordingly. 100 | // 101 | #define LIST_ADD_TO_TIMED(list, item, ref, time) \ 102 | { \ 103 | typeof(item) e = (list)->head; \ 104 | while (e) { \ 105 | if (e->time > (item)->time) break; \ 106 | (item)->time -= e->time; \ 107 | e = e->ref##_next; \ 108 | } \ 109 | LIST_ADD_BEFORE(list, item, ref, e); \ 110 | if (e) e->time -= (item)->time; \ 111 | } 112 | 113 | // 114 | // Remove an item from the timed list, ensuring any remaining time is added back 115 | // to the next item. 116 | // 117 | #define LIST_REMOVE_FROM_TIMED(list, item, ref, time) \ 118 | if ((item)->time && (item)->ref##_next) { \ 119 | (item)->ref##_next->time += (item)->time; \ 120 | } \ 121 | LIST_REMOVE(list, item, ref); 122 | 123 | // 124 | // Reduce the time on the list by a given amount, may involve changing multiple 125 | // records but won't take time below zero 126 | // 127 | #define LIST_UPDATE_TIMED(list, ref, time, delta) \ 128 | { \ 129 | uint32_t d = (delta); \ 130 | typeof((list)->head) e = (list)->head; \ 131 | while (e) { \ 132 | if (e->time < d) { \ 133 | d -= e->time; \ 134 | e->time = 0; \ 135 | e = e->ref##_next; \ 136 | continue; \ 137 | } \ 138 | e->time -= (delta); \ 139 | break; \ 140 | } \ 141 | } 142 | 143 | 144 | #endif // _LIST_H -------------------------------------------------------------------------------- /modules/lerp_task/plat_rp2040.c: -------------------------------------------------------------------------------- 1 | /* ======================================== 2 | * 3 | * Copyright YOUR COMPANY, THE YEAR 4 | * All Rights Reserved 5 | * UNPUBLISHED, LICENSED SOFTWARE. 6 | * 7 | * CONFIDENTIAL AND PROPRIETARY INFORMATION 8 | * WHICH IS THE PROPERTY OF your company. 9 | * 10 | * ======================================== 11 | */ 12 | 13 | #include "pico/stdlib.h" 14 | 15 | #include "hardware/regs/m0plus.h" 16 | #include "hardware/structs/scb.h" 17 | #include "hardware/irq.h" 18 | #include "hardware/exception.h" 19 | #include "hardware/clocks.h" 20 | 21 | #include 22 | 23 | #define NVIC_INT_CTRL_REG (*(volatile uint32_t *)(PPB_BASE + M0PLUS_ICSR_OFFSET)) 24 | #define NVIC_PENDSVSET_BIT M0PLUS_ICSR_PENDSVSET_BITS 25 | 26 | #define NVIC_SHPR3_REG (*(volatile uint32_t *)(PPB_BASE + M0PLUS_SHPR3_OFFSET)) 27 | #define MIN_INTERRUPT_PRIORITY (255UL) 28 | #define NVIC_PENDSV_PRI (MIN_INTERRUPT_PRIORITY << 16UL) 29 | 30 | #define INITIAL_XPSR (0x01000000) 31 | 32 | #define NAKED __attribute__( ( naked ) ) 33 | 34 | /** 35 | * @brief Yield the current task by raising a PendSV exception... 36 | * 37 | */ 38 | void task_yield( void ) 39 | { 40 | 41 | /* Set a PendSV to request a context switch. */ 42 | NVIC_INT_CTRL_REG = NVIC_PENDSVSET_BIT; 43 | //*(volatile uint32_t *)0xe000ed04 = NVIC_PENDSVSET_BIT; 44 | 45 | /* Barriers are normally not required but do ensure the code is completely 46 | * within the specified behaviour for the architecture. */ 47 | __asm volatile ( "dsb" ::: "memory" ); 48 | __asm volatile ( "isb" ); 49 | } 50 | 51 | // NOTE: I've removed the disabling of IRQs aroudn the switchContext function as 52 | // we are now fully co-operative, and no scheduling happens from an IRQ so theres 53 | // no reason to disable anything. 54 | NAKED void __time_critical_func(PendSV_Handler)(void) { 55 | __asm volatile 56 | ( 57 | " .syntax unified \n" 58 | " mrs r0, psp \n" 59 | " \n" 60 | " ldr r3, =currentTCB \n"/* Get the location of the current TCB. */ 61 | " ldr r2, [r3] \n" 62 | " \n" 63 | " subs r0, r0, #32 \n"/* Make space for the remaining low registers. */ 64 | " str r0, [r2] \n"/* Save the new top of stack. */ 65 | " stmia r0!, {r4-r7} \n"/* Store the low registers that are not saved automatically. */ 66 | " mov r4, r8 \n"/* Store the high registers. */ 67 | " mov r5, r9 \n" 68 | " mov r6, r10 \n" 69 | " mov r7, r11 \n" 70 | " stmia r0!, {r4-r7} \n" 71 | " \n" 72 | " push {r3, r14} \n" 73 | // " cpsid i \n" 74 | " bl switchContext \n" 75 | // " cpsie i \n" 76 | " pop {r2, r3} \n"/* lr goes in r3. r2 now holds tcb pointer. */ 77 | " \n" 78 | " ldr r1, [r2] \n" 79 | " ldr r0, [r1] \n"/* The first item in pxCurrentTCB is the task top of stack. */ 80 | " adds r0, r0, #16 \n"/* Move to the high registers. */ 81 | " ldmia r0!, {r4-r7} \n"/* Pop the high registers. */ 82 | " mov r8, r4 \n" 83 | " mov r9, r5 \n" 84 | " mov r10, r6 \n" 85 | " mov r11, r7 \n" 86 | " \n" 87 | " msr psp, r0 \n"/* Remember the new top of stack for the task. */ 88 | " \n" 89 | " subs r0, r0, #32 \n"/* Go back for the low registers that are not automatically restored. */ 90 | " ldmia r0!, {r4-r7} \n"/* Pop low registers. */ 91 | " \n" 92 | " bx r3 \n" 93 | " \n" 94 | " .align 4 \n" 95 | ); 96 | } 97 | 98 | NAKED void leos_platform_start_first(void) 99 | { 100 | /* The MSP stack is not reset as, unlike on M3/4 parts, there is no vector 101 | * table offset register that can be used to locate the initial stack value. 102 | * Not all M0 parts have the application vector table at address 0. */ 103 | __asm volatile ( 104 | " .syntax unified \n" 105 | " ldr r2, =currentTCB \n"/* Obtain location of pxCurrentTCB. */ 106 | " ldr r3, [r2] \n" 107 | " ldr r0, [r3] \n"/* The first item in pxCurrentTCB is the task top of stack. */ 108 | " adds r0, #32 \n"/* Discard everything up to r0. */ 109 | " msr psp, r0 \n"/* This is now the new top of stack to use in the task. */ 110 | " movs r0, #2 \n"/* Switch to the psp stack. */ 111 | " msr CONTROL, r0 \n" 112 | " isb \n" 113 | " pop {r0-r5} \n"/* Pop the registers that are saved automatically. */ 114 | " mov lr, r5 \n"/* lr is now in r5. */ 115 | " pop {r3} \n"/* Return address is now in r3. */ 116 | " pop {r2} \n"/* Pop and discard XPSR. */ 117 | " cpsie i \n"/* The first task has its context and interrupts can be enabled. */ 118 | " bx r3 \n"/* Finally, jump to the user defined task code. */ 119 | " \n" 120 | " .align 4 \n" 121 | ); 122 | } 123 | 124 | /* 125 | * This has got to be platform specific... 126 | */ 127 | uint32_t * leos_platform_init_stack(uint32_t *sp, void (*main)(void *), void *param, void (*taskexit)(void)) { 128 | /* Simulate the stack frame as it would be created by a context switch 129 | * interrupt. */ 130 | 131 | // Shouldn't this actually be ... 132 | // *--sp = ... if we do that we don't need the "offset" 133 | // 134 | 135 | sp--; // Offset because of the way the MCU uses the stack on entry/exit of IRQ's 136 | *sp-- = INITIAL_XPSR; // xPSR 137 | *sp-- = (uint32_t)main; // PC 138 | *sp-- = (uint32_t)taskexit; // If we exit 139 | sp -= 4; // R12, R3, R2, and R1. 140 | *sp-- = (uint32_t)param; // R0 141 | sp -= 7; // R11, R10, R9, R8, R7, R6, R5, E4 142 | return sp; 143 | } 144 | 145 | void leos_platform_init() { 146 | NVIC_SHPR3_REG |= NVIC_PENDSV_PRI; 147 | 148 | // Setup the handler for the PendSV call... 149 | exception_set_exclusive_handler(PENDSV_EXCEPTION, PendSV_Handler); 150 | } 151 | -------------------------------------------------------------------------------- /modules/lerp_task/task.c: -------------------------------------------------------------------------------- 1 | /* ======================================== 2 | * 3 | * Copyright YOUR COMPANY, THE YEAR 4 | * All Rights Reserved 5 | * UNPUBLISHED, LICENSED SOFTWARE. 6 | * 7 | * CONFIDENTIAL AND PROPRIETARY INFORMATION 8 | * WHICH IS THE PROPERTY OF your company. 9 | * 10 | * ======================================== 11 | */ 12 | 13 | #include "lerp/task.h" 14 | #include "list.h" 15 | 16 | //#include "lerp/debug.h" 17 | 18 | #include "pico/stdlib.h" 19 | #include 20 | #include 21 | #include 22 | 23 | #ifndef IDLE_STACK_SIZE 24 | #define IDLE_STACK_SIZE 4096 25 | #endif 26 | 27 | #define NAKED __attribute__( ( naked ) ) 28 | 29 | // 30 | // We expect these items to be in the platform specific file... 31 | // 32 | extern void leos_platform_init(void); 33 | uint32_t *leos_platform_init_stack( uint32_t *sp, void (*main)(void *), void *param, void (*taskexit)(void)); 34 | extern NAKED void leos_platform_start_first(void); 35 | 36 | 37 | // 38 | // A few queues to handle different use cases... 39 | // 40 | DEFINE_LIST(ready_list, struct task); // tasks ready to run 41 | DEFINE_LIST(timeout_list, struct task); // tasks waiting for a specified time 42 | 43 | // 44 | // Time as updated by the idle thread, so we don't call time_us_64() multiple times 45 | // 46 | volatile uint64_t _idle_now = 0; 47 | 48 | // To optimise the scheduler we keep track of when we last did a timewait 49 | // update, and also the smallest timewait in the queue, that way we only 50 | // need to run the update after that amount of time, and only if there is 51 | // something in the queue. 52 | // 53 | static uint32_t last_timewait_update = 0; 54 | static uint32_t min_timewait_time = TIME_FOREVER; 55 | 56 | // 57 | // Structure and stack for the IDLE process 58 | // 59 | static uint8_t idle_stack[IDLE_STACK_SIZE]; 60 | struct task idle_TCB; 61 | 62 | // 63 | // Our current task 64 | // 65 | volatile struct task *currentTCB = NULL; 66 | 67 | // 68 | // Add a task onto the timeout list and update the min_timewait 69 | // accordingly. 70 | // 71 | // ISSUE: if we add something then the ELAPSED() time is going to not be from 72 | // the point we added it so we effectively need to re-zero the timebase 73 | // by updating the timeouts first... 74 | 75 | static void update_timewait_tasks(uint32_t delta); 76 | 77 | static inline void add_timewait_task(struct task *task) { 78 | uint32_t time = task->wait_time; 79 | uint32_t delta = ELAPSED(last_timewait_update, time_us_32()); 80 | 81 | if (timeout_list.head) { 82 | if (delta) update_timewait_tasks(delta); 83 | } else { 84 | last_timewait_update = time_us_32(); 85 | } 86 | LIST_ADD(&timeout_list, (struct task *)currentTCB, time); 87 | if (time < min_timewait_time) min_timewait_time = time; 88 | } 89 | 90 | // 91 | // This removed an unexpired task from the timeout list and updates the time 92 | // so that it reflects reality... 93 | // 94 | static inline void remove_timewait_task(struct task *task) { 95 | uint32_t delta = ELAPSED(last_timewait_update, time_us_32()); 96 | 97 | LIST_REMOVE(&timeout_list, task, time); 98 | if (task->wait_time > delta) { 99 | task->wait_time -= delta; 100 | } else { 101 | task->wait_time = 0; 102 | } 103 | } 104 | 105 | 106 | // 107 | // Block a task with no timeout, just a straight block... return value 108 | // is the wake reason 109 | // 110 | inline int task_block() { 111 | currentTCB->state = BLOCKED; 112 | task_yield(); 113 | return currentTCB->wake_reason; 114 | } 115 | void task_halt() __attribute__((alias("task_block"))); 116 | 117 | // 118 | // Main method of blocking a process, takes time as an argument. If time is zero 119 | // then this is the same as yeild() staying in the READY state. If time is 120 | // TIME_FOREVER then we block without a timeout, otherwise we setup a timeout 121 | // so we are woken by that if nothing else. 122 | // 123 | // The return value is the wake reason. 124 | // 125 | inline int task_block_with_time(uint32_t time) { 126 | if (time == 0) { 127 | task_yield(); 128 | return WAKE_READY; 129 | } 130 | currentTCB->state = BLOCKED; 131 | currentTCB->wait_time = time; 132 | if (time == TIME_FOREVER) { 133 | task_yield(); 134 | } else { 135 | add_timewait_task((struct task *)currentTCB); 136 | task_yield(); 137 | if (currentTCB->wake_reason != WAKE_TIMEOUT) { 138 | remove_timewait_task((struct task *)currentTCB); 139 | } 140 | } 141 | return currentTCB->wake_reason; 142 | } 143 | //int task_sleep(uint32_t time) __attribute__((alias("task_block_with_time"))); 144 | 145 | // 146 | // Waking up a task needs a reason, and the task must be BLOCKED 147 | // (you can't wake an IRQWAIT task, that's ignored.) 148 | // 149 | inline void task_wake(struct task *task, int reason) { 150 | if (task->state == IRQWAIT) return; 151 | 152 | assert(task->state == BLOCKED); 153 | task->wake_reason = reason; 154 | task->state = READY; 155 | LIST_ADD(&ready_list, task, task); 156 | } 157 | 158 | // 159 | // Before a task is awoken the reason is stored, this returns it. 160 | // 161 | int task_wake_reason() { 162 | return currentTCB->wake_reason; 163 | } 164 | 165 | // --------------------------------------------------------------------- 166 | // TIME WAIT related 167 | // --------------------------------------------------------------------- 168 | 169 | // 170 | // Remove delta from each waiting timer and see if any need to be unblocked 171 | // as a result. 172 | // 173 | static void update_timewait_tasks(uint32_t delta) { 174 | uint32_t min_time = UINT_MAX; 175 | struct task *t = timeout_list.head; 176 | while(t) { 177 | struct task *next = t->time_next; // so we can remove while traversing 178 | 179 | if (t->wait_time > delta) { 180 | t->wait_time -= delta; 181 | if (t->wait_time < min_time) { 182 | min_time = t->wait_time; 183 | } 184 | } else { 185 | // If we are BLOCKED here then our timer has expired ahead of anything else 186 | // so we can mark WAKE_TIMEOUT and unblock as normal. 187 | // If we are not blocked, then we need to rely on the blocking process to 188 | // remove us from the list as something else unblocked us. 189 | t->wait_time = 0; 190 | if (t->state == BLOCKED) { 191 | LIST_REMOVE(&timeout_list, t, time); 192 | task_wake(t, WAKE_TIMEOUT); 193 | } 194 | } 195 | t = next; 196 | } 197 | min_timewait_time = min_time; 198 | last_timewait_update = time_us_32(); 199 | } 200 | 201 | 202 | // 203 | // This is called by the SVC processor and needs to end with a new task set 204 | // in currentTCB so that it can execute. 205 | // 206 | // We currently round-robin the task list, but we probablt want to also look 207 | // at the previous one and see if it needs to be removed from the active list 208 | // if it's blocked on something. 209 | // 210 | void __time_critical_func(switchContext)() { 211 | struct task *prior = (struct task *)currentTCB; 212 | 213 | #if LEOS_CHECK_STACK > 0 214 | // See if we have a sensible amount of stack free... 215 | if (calc_stack_free(prior) < LEOS_CHECK_STACK) { 216 | debug_printf(D_GEN|D_INF, "WARNING: unused stack in %p is %d\r\n", prior, calc_stack_free(prior)); 217 | } 218 | #endif 219 | 220 | // If the prior task isn't blocked then we can add it to the end of 221 | // the ready list (unless it was the IDLE task)... 222 | // 223 | // Theoretically we could also get here as "READY" in the case of a bit 224 | // of a race condition in the IRQ system, but that's not an issue since 225 | // we will be picked up by the irq_triggered section, we just need to 226 | // ensure we don't add it to the ready list. 227 | if (prior->state == RUNNING) { 228 | if (prior != &idle_TCB) { 229 | LIST_ADD(&ready_list, prior, task); 230 | } 231 | prior->state = READY; 232 | } 233 | 234 | // See if we need to update our timewait clocks... 235 | // TODO: don't like this as we are interrupts disabled while we 236 | // are in this function... is there a better way? (Do it in idle?) 237 | // if (timeout_list.head) { 238 | // uint32_t delta = ELAPSED(last_timewait_update, time_us_32()); 239 | // if (delta >= min_timewait_time) { 240 | // update_timewait_tasks(delta); 241 | // } 242 | // } 243 | 244 | // Pop the next item from the ready list, we run IDLE if there aren't 245 | // any, or if it's the task we just yielded from... 246 | struct task *task = ready_list.head; 247 | if ((task == NULL) || (task == prior)) { 248 | task = &idle_TCB; 249 | } else { 250 | task = LIST_POP(&ready_list, task, struct task *); 251 | } 252 | task->state = RUNNING; 253 | currentTCB = task; 254 | } 255 | 256 | /** 257 | * @brief Yield the current process if there's something else ready to run 258 | * 259 | * This does similar checks to the scheduler to see if something needs to be 260 | * run. Namely is there something on the time-wait list, or is something 261 | * with an IRQ or otherwise ready. 262 | * 263 | * It should be fairly quick. 264 | * 265 | */ 266 | void task_yield_if_required() { 267 | if (timeout_list.head) { 268 | uint32_t delta = ELAPSED(last_timewait_update, time_us_32()); 269 | if (delta >= min_timewait_time) { 270 | // We need to update times and yield... 271 | update_timewait_tasks(delta); 272 | } 273 | } 274 | if (ready_list.head) { 275 | // We need to yield... 276 | task_yield(); 277 | } 278 | 279 | // If we get here then we don't need to yield... 280 | return; 281 | } 282 | 283 | 284 | 285 | static void task_exit_error( void ) { 286 | // debug_printf(D_GEN|D_INF, "TASK EXIT ERROR\r\n"); 287 | // debug_flush(); 288 | while(1); 289 | } 290 | 291 | 292 | // 293 | // Setup stack for trying to detect overflows... 294 | // 295 | static void setup_stack(struct task *task, uint8_t *stack, int size) { 296 | uint32_t *top = (uint32_t *)(stack + size); 297 | uint32_t *bot = (uint32_t *)stack; 298 | 299 | task->sp = top; 300 | task->stack_end = bot; 301 | } 302 | 303 | /** 304 | * @brief Create a task 305 | * 306 | * @param task 307 | * @param function 308 | * @param arg 309 | * @param stack 310 | * @param stack_size 311 | */ 312 | void task_create(struct task *task, void (*function)(void *), void *arg, uint8_t *stack, int stack_size) { 313 | memset(stack, 0x55, stack_size); // for stack usage detection 314 | setup_stack(task, stack, stack_size); 315 | task->sp = leos_platform_init_stack(task->sp, function, arg, task_exit_error); 316 | task->state = READY; 317 | 318 | if (task != &idle_TCB) { 319 | LIST_ADD(&ready_list, task, task); 320 | } 321 | } 322 | 323 | /** 324 | * @brief Idle Process -- run when nothing else needs to, so it can poll! 325 | * 326 | * NOTE: This must not block! 327 | * 328 | * @param param 329 | */ 330 | void idle_task(void *param) { 331 | void (*func)(void) = param; // ToDO: need to cast this properly? 332 | 333 | while(1) { 334 | task_yield_if_required(); 335 | 336 | // Call this one for each loop, so that each polling function doesn't 337 | // need to do it again... 338 | _idle_now = time_us_64(); 339 | 340 | // Call the app dependent polling function 341 | if (func) func(); 342 | 343 | // debug_poll(); 344 | } 345 | } 346 | 347 | void leos_start() { 348 | currentTCB = &idle_TCB; 349 | last_timewait_update = time_us_32(); 350 | leos_platform_start_first(); 351 | while(1); // shouldn't get here at all 352 | } 353 | 354 | void leos_init(void (*poll_func)(void)) { 355 | // debug_init(); 356 | leos_platform_init(); 357 | task_create(&idle_TCB, idle_task, (void *)poll_func, idle_stack, IDLE_STACK_SIZE); 358 | } 359 | 360 | -------------------------------------------------------------------------------- /modules/lerp_tokeniser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT TARGET lerp_tokeniser) 4 | 5 | add_library(lerp_tokeniser INTERFACE) 6 | 7 | target_include_directories(lerp_tokeniser INTERFACE include) 8 | 9 | target_sources(lerp_tokeniser INTERFACE 10 | CMakeLists.txt 11 | 12 | tokeniser.c 13 | include/lerp/tokeniser.h 14 | ) 15 | 16 | target_compile_options(lerp_tokeniser INTERFACE 17 | -Wall 18 | ) 19 | 20 | target_link_libraries(lerp_tokeniser INTERFACE 21 | lerp_circ 22 | ) 23 | 24 | endif() 25 | -------------------------------------------------------------------------------- /modules/lerp_tokeniser/include/lerp/tokeniser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tokeniser.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-21 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef __TOKENISER_H 13 | #define __TOKENISER_H 14 | 15 | #include "lerp/circ.h" 16 | #include 17 | 18 | 19 | #define MAX_TOK_SIZE 128 20 | 21 | enum tokens { 22 | TOK_ERROR = 0, 23 | TOK_END, 24 | TOK_EQUALS, 25 | TOK_COLON, 26 | TOK_COMMA, 27 | TOK_PLUS, 28 | TOK_MINUS, 29 | TOK_INTEGER, 30 | TOK_STRING, 31 | TOK_MACADDR, 32 | TOK_IPADDR, 33 | TOK_WORD, 34 | }; 35 | 36 | int token_get(struct circ *); 37 | int token_is_last(struct circ *); 38 | uint32_t token_ip_address(); 39 | char *token_mac_address(); 40 | char *token_string(); 41 | int token_int(); 42 | 43 | #endif -------------------------------------------------------------------------------- /modules/lerp_tokeniser/tokeniser.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tokeniser.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-03-21 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lerp/tokeniser.h" 17 | 18 | 19 | static char token_str[MAX_TOK_SIZE]; 20 | static int token_ints[6]; 21 | 22 | 23 | // Some helper routines to extract mac and ipaddresses in more useful forms 24 | uint32_t token_ip_address() { 25 | return (token_ints[0] << 24) | (token_ints[1] << 16) | (token_ints[2] << 8) | (token_ints[3]); 26 | } 27 | 28 | char *token_mac_address() { 29 | for (int i=0; i < 6; i++) { 30 | token_str[i] = (char)token_ints[i]; 31 | } 32 | return token_str; 33 | } 34 | 35 | char *token_string() { 36 | return token_str; 37 | } 38 | 39 | int token_int() { 40 | return token_ints[0]; 41 | } 42 | 43 | 44 | static const char hex_digits[] = "0123456789abcdef"; 45 | static const char digits[] = "0123456789"; 46 | static const char valid_chars[] = "abcdefghijklmnopqrstuvwxyz0123456789_."; 47 | 48 | static int get_int(uint8_t **ptr, int base) { 49 | uint8_t *p = *ptr; 50 | int rc = 0; 51 | 52 | if (!*p) return -1; // we need at least one char 53 | while (*p) { 54 | char *x = index(hex_digits, tolower(*p)); 55 | if (!x) break; 56 | int v = (int)(x - hex_digits); 57 | rc *= base; 58 | rc += v; 59 | p++; 60 | } 61 | *ptr = p; 62 | return rc; 63 | } 64 | 65 | /** 66 | * @brief Make sure we are at the end of the input without overwriting the last token 67 | * 68 | * Returns 1 if there are no more tokens, or 0 if we're not at the end 69 | * 70 | * @param ptr 71 | * @return int 72 | */ 73 | int token_is_last(struct circ *circ) { 74 | uint8_t *p = circ->tail; 75 | 76 | while(*p == ' ' || *p == '\t') p++; 77 | 78 | if (!*p) return 1; 79 | return 0; 80 | } 81 | 82 | 83 | int token_get(struct circ *circ) { 84 | uint8_t *p = circ->tail; 85 | 86 | // First we get past any whitespace 87 | while(*p == ' ' || *p == '\t') p++; 88 | 89 | // Are we at the end? 90 | if (!*p) return TOK_END; 91 | 92 | // Simple case of "=" 93 | if (*p == '=') { 94 | circ->tail = p + 1; 95 | return TOK_EQUALS; 96 | } 97 | if (*p == ',') { 98 | circ->tail = p + 1; 99 | return TOK_COMMA; 100 | } 101 | 102 | 103 | // ------------------------------------------------------------------------------ 104 | // If we have a quote then it should be a quoted string... 105 | // ------------------------------------------------------------------------------ 106 | if (*p == '\"') { 107 | int esc = 0; 108 | int i = 0; 109 | 110 | p++; 111 | while(*p) { 112 | if (*p == '\\') { 113 | p++; 114 | esc = 1; 115 | continue; 116 | } 117 | if (*p == '\"' && !esc) { 118 | circ->tail = p + 1; 119 | token_str[i] = '\0'; 120 | return TOK_STRING; 121 | } 122 | token_str[i++] = *p; 123 | esc = 0; 124 | p++; 125 | } 126 | return TOK_ERROR; // no terminating quote 127 | } 128 | 129 | // ------------------------------------------------------------------------------ 130 | // Simple Hex number... i.e. 0x12345 131 | // ------------------------------------------------------------------------------ 132 | if (*p == '0' && *(p+1) == 'x') { 133 | // *ptr = p + 2; 134 | p += 2; 135 | token_ints[0] = get_int(&p, 16); 136 | if (token_ints[0] == -1) return TOK_ERROR; 137 | circ->tail = p; 138 | return TOK_INTEGER; 139 | } 140 | 141 | // ------------------------------------------------------------------------------ 142 | // See if we are a mac address ... if we start with a hex digit, then look for 143 | // six hex numbers (0-255) separated by colons. 144 | // ------------------------------------------------------------------------------ 145 | if (index(hex_digits, tolower(*p))) { 146 | uint8_t *new_ptr = p; 147 | int i = 0; 148 | while(1) { 149 | int v = get_int(&new_ptr, 16); 150 | if (v >= 0 && v < 256) { 151 | token_ints[i] = v; 152 | i++; 153 | } else { 154 | // Not a mac address... 155 | break; 156 | } 157 | // Do we have a colon... 158 | if (i >= 6 || *new_ptr != ':') break; 159 | new_ptr++; 160 | }; 161 | if (i == 6) { 162 | // *ptr = new_ptr; 163 | circ->tail = new_ptr; 164 | return TOK_MACADDR; 165 | } 166 | } 167 | 168 | // ------------------------------------------------------------------------------ 169 | // See if we are an IP address ... if we start with a digit, then look for 170 | // four numbers (0-255) separated by dots. 171 | // ------------------------------------------------------------------------------ 172 | if (index(digits, *p)) { 173 | uint8_t *new_ptr = p; 174 | int i = 0; 175 | while(1) { 176 | int v = get_int(&new_ptr, 10); 177 | if (v >= 0 && v < 256) { 178 | token_ints[i] = v; 179 | i++; 180 | } else { 181 | // Not an IP address... 182 | break; 183 | } 184 | // Do we have a dot... 185 | if (i >= 4 || *new_ptr != '.') break; 186 | new_ptr++; 187 | }; 188 | if (i == 4) { 189 | // *ptr = new_ptr; 190 | circ->tail = new_ptr; 191 | return TOK_IPADDR; 192 | } 193 | } 194 | 195 | // ------------------------------------------------------------------------------ 196 | // If we match a number at this point, then we're probably an ordinary integer 197 | // otherwise we must be an error... but there are three variants ... 198 | // base number, +number, and -number 199 | // ------------------------------------------------------------------------------ 200 | int is_digit = 0; 201 | 202 | if (*p == '+' && index(digits, *(p+1))) { is_digit = 1; p++; } 203 | else if (*p == '-' && index(digits, *(p+1))) { is_digit = -1; p++; } 204 | else if (index(digits, *p)) { is_digit = 1; } 205 | 206 | if (is_digit) { 207 | token_ints[0] = get_int(&p, 10); 208 | if (token_ints[0] == -1) return TOK_ERROR; 209 | token_ints[0] *= is_digit; // handle negatives 210 | circ->tail = p; 211 | return TOK_INTEGER; 212 | } 213 | 214 | // ------------------------------------------------------------------------------ 215 | // If we get here, then we are most likely some kind of string, either a command 216 | // or an identified (config item) so we just eat anything that isn't a separator 217 | // ------------------------------------------------------------------------------ 218 | if (index(valid_chars, tolower(*p))) { 219 | int i=0; 220 | while(*p && index(valid_chars, tolower(*p))) { 221 | token_str[i++] = *p++; 222 | if (i == MAX_TOK_SIZE) { 223 | return TOK_ERROR; 224 | } 225 | } 226 | token_str[i] = '\0'; 227 | circ->tail = p; 228 | return TOK_WORD; 229 | } 230 | 231 | // ------------------------------------------------------------------------------ 232 | // Some specfic tokens we care about... 233 | // ------------------------------------------------------------------------------ 234 | if (*p == ':') { 235 | circ->tail = p + 1; 236 | return TOK_COLON; 237 | } 238 | if (*p == '+') { 239 | circ->tail = p + 1; 240 | return TOK_PLUS; 241 | } 242 | if (*p == '-') { 243 | circ->tail = p + 1; 244 | return TOK_MINUS; 245 | } 246 | 247 | // ------------------------------------------------------------------------------ 248 | // Theoretically anything here is a separator of some kind... 249 | // ------------------------------------------------------------------------------ 250 | circ->tail = p; 251 | return TOK_ERROR; 252 | } 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /pico_extras_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_extras_import.cmake 2 | 3 | # This can be dropped into an external project to help locate pico-extras 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) 7 | set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) 8 | message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) 12 | set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) 13 | message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) 17 | set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | if (NOT PICO_EXTRAS_PATH) 22 | if (PICO_EXTRAS_FETCH_FROM_GIT) 23 | include(FetchContent) 24 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 25 | if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) 26 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 27 | endif () 28 | FetchContent_Declare( 29 | pico_extras 30 | GIT_REPOSITORY https://github.com/raspberrypi/pico-extras 31 | GIT_TAG master 32 | ) 33 | if (NOT pico_extras) 34 | message("Downloading Raspberry Pi Pico Extras") 35 | FetchContent_Populate(pico_extras) 36 | set(PICO_EXTRAS_PATH ${pico_extras_SOURCE_DIR}) 37 | endif () 38 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 39 | else () 40 | if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") 41 | set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) 42 | message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") 43 | else() 44 | message(FATAL_ERROR 45 | "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif() 48 | endif () 49 | endif () 50 | 51 | set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") 52 | set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") 53 | set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") 54 | 55 | get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 56 | if (NOT EXISTS ${PICO_EXTRAS_PATH}) 57 | message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") 58 | endif () 59 | 60 | set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) 61 | 62 | add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) 63 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading Raspberry Pi Pico SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /swd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "pico/stdlib.h" 4 | #include "hardware/gpio.h" 5 | 6 | #pragma GCC optimize ("O0") 7 | 8 | 9 | #include "hardware/pio.h" 10 | #include "swd.pio.h" 11 | 12 | #include "lerp/task.h" 13 | #include "lerp/debug.h" 14 | #include "swd.h" 15 | 16 | 17 | //#define PIN_SWDCLK 26 18 | //#define PIN_SWDIO 22 19 | 20 | #define OUT 1 21 | #define IN 0 22 | 23 | static PIO swd_pio = pio0; 24 | static uint swd_sm; 25 | 26 | #define CHECK_OK(func) { int rc = func; if (rc != SWD_OK) return rc; } 27 | 28 | 29 | 30 | static struct task *waiting_on_put = NULL; 31 | static struct task *waiting_on_get = NULL; 32 | 33 | /** 34 | * @brief Blocking (lerp_task) version of pio_sm_put 35 | * 36 | * @param pio 37 | * @param sm 38 | * @param data 39 | */ 40 | static inline void lerp_sm_put_blocking(PIO pio, uint sm, uint32_t data) { 41 | if (pio_sm_is_tx_fifo_full(pio, sm)) { 42 | waiting_on_put = current_task(); 43 | task_block(); 44 | } 45 | return pio_sm_put(pio, sm, data); 46 | } 47 | 48 | /** 49 | * @brief Blocking (lerp_task) version of pio_sm_get 50 | * 51 | * @param pio 52 | * @param sm 53 | * @return uint32_t 54 | */ 55 | static inline uint32_t lerp_sm_get_blocking(PIO pio, uint sm) { 56 | if (pio_sm_is_rx_fifo_empty(pio, sm)) { 57 | waiting_on_get = current_task(); 58 | task_block(); 59 | } 60 | return pio_sm_get(pio, sm); 61 | } 62 | 63 | /** 64 | * @brief The poll function that tracks pio/sm block and releases them 65 | * 66 | */ 67 | void swd_pio_poll() { 68 | if (waiting_on_put && !pio_sm_is_tx_fifo_full(swd_pio, swd_sm)) { 69 | task_wake(waiting_on_put, 0); 70 | waiting_on_put = NULL; 71 | } 72 | if (waiting_on_get && !pio_sm_is_rx_fifo_empty(swd_pio, swd_sm)) { 73 | task_wake(waiting_on_get, 0); 74 | waiting_on_get = NULL; 75 | } 76 | } 77 | 78 | 79 | /** 80 | * @brief Return parity for a given 4 bit number 81 | * 82 | * @param value 83 | * @return int 84 | */ 85 | static int parity4(uint32_t value) { 86 | static const uint32_t plook[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; 87 | return plook[value]; 88 | } 89 | 90 | /** 91 | * @brief Return parity for a given 32 bit number 92 | * 93 | * @param value 94 | * @return int 95 | */ 96 | static inline int parity32(uint32_t value) { 97 | uint32_t p16 = ((value & 0xffff0000) >> 16) ^ (value & 0xffff); 98 | uint32_t p8 = ((p16 & 0xff00) >> 8) ^ (p16 & 0xff); 99 | uint32_t p4 = ((p8 & 0xf0) >> 4) ^ (p8 & 0x0f); 100 | return parity4(p4); 101 | } 102 | 103 | 104 | /** 105 | * @brief Load the PIO with the SWD code and start it 106 | * 107 | * @param pio 108 | * @return int 109 | */ 110 | int swd_init() { 111 | pio_sm_config c; 112 | PIO pio = swd_pio; 113 | 114 | // Load the program at offset zero so the jumps are constant... 115 | pio_add_program_at_offset(pio, &swd_program, 0); 116 | c = swd_program_get_default_config(0); // 0=offset 117 | 118 | // Map the appropriate pins... 119 | sm_config_set_out_pins(&c, PIN_SWDIO, 1); 120 | sm_config_set_set_pins(&c, PIN_SWDIO, 1); 121 | sm_config_set_in_pins(&c, PIN_SWDIO); 122 | sm_config_set_sideset_pins(&c, PIN_SWDCLK); 123 | 124 | // Setup PIO to GPIO 125 | pio_gpio_init(pio, PIN_SWDCLK); 126 | pio_gpio_init(pio, PIN_SWDIO); 127 | gpio_pull_up(PIN_SWDIO); 128 | 129 | // Get a state machine... 130 | swd_sm = pio_claim_unused_sm(pio, true); 131 | 132 | // Setup direction... we want lsb first (so shift right) 133 | sm_config_set_out_shift(&c, true, true, 32); 134 | sm_config_set_in_shift(&c, true, true, 32); 135 | 136 | // Set directions... 137 | pio_sm_set_consecutive_pindirs(pio, swd_sm, PIN_SWDCLK, 1, true); 138 | pio_sm_set_consecutive_pindirs(pio, swd_sm, PIN_SWDIO, 1, false); 139 | 140 | // And initialise 141 | pio_sm_init(pio, swd_sm, 0, &c); // 0=offset 142 | 143 | // Go slow for the minute... 144 | //pio_sm_set_clkdiv_int_frac(pio, swd_sm, 128, 0); 145 | pio_sm_set_clkdiv_int_frac(pio, swd_sm, 3, 0); 146 | 147 | // 125MHz / 2 / 3 = ~ 20Mhz --> divider of 3 148 | // 150Mhz / 2 / 3 = 25Mhz --> divider of 3 (and it works!) 149 | 150 | pio_sm_set_enabled(swd_pio, swd_sm, true); 151 | return SWD_OK; 152 | } 153 | 154 | /** 155 | * @brief Sends up to 21 bits of data using a single control word 156 | * 157 | * @param count 158 | * @param data 159 | */ 160 | static inline void swd_short_output(int count, uint32_t data) { 161 | assert(count > 0); 162 | assert(count <= 21); 163 | 164 | lerp_sm_put_blocking(swd_pio, swd_sm, (data << 10) | ((count-1) << 5) | swd_offset_short_output); 165 | } 166 | 167 | /** 168 | * @brief Receive up to 32 bits (a single return value) 169 | * 170 | * Note we can ask for more but will need to read the rest outselves 171 | * 172 | * @param count 173 | * @return uint32_t 174 | */ 175 | static inline uint32_t swd_short_input(int count) { 176 | assert(count > 0); 177 | 178 | lerp_sm_put_blocking(swd_pio, swd_sm, ((count-1) << 5) | swd_offset_input); 179 | return lerp_sm_get_blocking(swd_pio, swd_sm); 180 | } 181 | 182 | /** 183 | * @brief Select a specific target 184 | * 185 | * This is a custom routine because we can't check the ACK bits so we can't use 186 | * the main conditional function. 187 | * 188 | * @param target 189 | * @return int 190 | */ 191 | void swd_targetsel(uint32_t target) { 192 | uint32_t parity = parity32(target); 193 | 194 | // First we send the 8 bits out real output (inc park)... 195 | swd_short_output(8, 0b10011001); 196 | 197 | // Now we read 5 bits (trn, ack0, ack1, ack2, trn) ... (will send it back) 198 | lerp_sm_put_blocking(swd_pio, swd_sm, ((5-1) << 5) | swd_offset_input); 199 | 200 | // Now we can write the target id (lsb) and a parity bit 201 | lerp_sm_put_blocking(swd_pio, swd_sm, ((33-1) << 5) | swd_offset_output); 202 | lerp_sm_put_blocking(swd_pio, swd_sm, target); // lsb first 203 | lerp_sm_put_blocking(swd_pio, swd_sm, parity); 204 | 205 | // Now we can read back the three bits (well 6) that should be waiting for us 206 | // and discard them 207 | lerp_sm_get_blocking(swd_pio, swd_sm); 208 | } 209 | 210 | /** 211 | * @brief Perform an SWD read operation 212 | * 213 | * We pull out bits A[2:3] from the address field 214 | * 215 | * @param APnDP 216 | * @param addr 217 | * @param result 218 | * @return int 219 | */ 220 | static int _swd_read(int APnDP, int addr, uint32_t *result) { 221 | uint32_t ack; 222 | uint32_t res; 223 | uint32_t parity; 224 | 225 | // We care about 4 bits for parity, 1=RD, APnDP, A3/2 226 | uint32_t packpar = parity4((addr & 0xc) | 2 | APnDP); 227 | 228 | // First we send the 7 bits out real output... (will send one value back) 229 | // LSB -> start, (APnDP), 1=RD, A2, A3, parity, stop, 1=park) 230 | uint32_t data = (1 << 7) // park bit 231 | | (0 << 6) // stop bit 232 | | (packpar << 5) // parity 233 | | (addr & 0xc) << 1 // A3/A2 234 | | (1 << 2) // Read 235 | | (APnDP << 1) 236 | | (1); // start bit 237 | 238 | swd_short_output(8, data); 239 | 240 | // Now we do a conditional read... 241 | // 242 | // 5 bits -- location for conditional 243 | // 8 bits -- how many bits (value of x) for read 244 | // 5 bits -- location to jump to if good 245 | // 14 bits -- locatiom to jump to if we fail 246 | // 247 | lerp_sm_put_blocking(swd_pio, swd_sm, swd_offset_start << (5+8+5) // go to start on failure 248 | | swd_offset_in_jmp << (5+8) // to in_jump on success 249 | | (33-1) << 5 // 32 + 1 parity 250 | | swd_offset_conditional); // conditional 251 | ack = lerp_sm_get_blocking(swd_pio, swd_sm) >> 1; 252 | 253 | // 254 | // We expect an OK response, otherwise decode the error... there will be no 255 | // further output if we have an error. 256 | // 257 | if (ack != 1) { 258 | // We need a trn if we fail... 259 | swd_short_output(1, 0); 260 | if (ack == 2) return SWD_WAIT; 261 | if (ack == 4) return SWD_FAULT; 262 | return SWD_ERROR; 263 | } 264 | 265 | res = lerp_sm_get_blocking(swd_pio, swd_sm); 266 | parity = (lerp_sm_get_blocking(swd_pio, swd_sm) & 0x80000000) == 0x80000000; 267 | if (parity != parity32(res)) return SWD_PARITY; 268 | *result = res; 269 | 270 | // We always need a trn after a read... 271 | swd_short_output(1, 0); 272 | return SWD_OK; 273 | } 274 | int swd_read(int APnDP, int addr, uint32_t *result) { 275 | int rc; 276 | 277 | do { 278 | rc = _swd_read(APnDP, addr, result); 279 | } while (rc == SWD_WAIT); 280 | return rc; 281 | } 282 | 283 | /** 284 | * @brief Perform an SWD write operation 285 | * 286 | * We pull out bits A[2:3] from the address field 287 | * 288 | * @param APnDP 289 | * @param addr 290 | * @param result 291 | * @return int 292 | */ 293 | static int _swd_write(int APnDP, int addr, uint32_t value) { 294 | uint32_t ack; 295 | 296 | // We care about 4 bits for parity, 0=WR, APnDP, A3/2 297 | uint32_t packpar = parity4((addr & 0xc) | 0 | APnDP); 298 | uint32_t parity = parity32(value); 299 | 300 | // First we send the 7 bits out real output... (will send one value back) 301 | // LSB -> start, (APnDP), 1=RD, A2, A3, parity, stop, 1=park) 302 | uint32_t data = (1 << 7) // park 303 | | (0 << 6) // stop bit 304 | | (packpar << 5) // parity 305 | | ((addr & 0xc) << 1) // A3/A2 306 | | (0 << 2) // Write 307 | | (APnDP << 1) 308 | | (1); // start bit 309 | 310 | swd_short_output(8, data); 311 | 312 | // Now we do a conditional rite... 313 | // 314 | // 5 bits -- location for conditional 315 | // 8 bits -- how many bits (value of x) for write 316 | // 5 bits -- location to jump to if good 317 | // 14 bits -- locatiom to jump to if we fail 318 | // 319 | lerp_sm_put_blocking(swd_pio, swd_sm, swd_offset_cond_write_fail << (5+8+5) // for failure 320 | | swd_offset_cond_write_ok << (5+8) // to in_jump on success 321 | | (33-1) << 5 // 32 + 1 parity 322 | | swd_offset_conditional); // conditional 323 | lerp_sm_put_blocking(swd_pio, swd_sm, value); 324 | lerp_sm_put_blocking(swd_pio, swd_sm, parity); 325 | 326 | ack = lerp_sm_get_blocking(swd_pio, swd_sm) >> 1; 327 | 328 | // 329 | // We expect an OK response, otherwise decode the error... the system will throw 330 | // away our other output if there's an error 331 | // 332 | if (ack != 1) { 333 | // We need a trn if we fail... 334 | swd_short_output(1, 0); 335 | if (ack == 2) return SWD_WAIT; 336 | if (ack == 4) return SWD_FAULT; 337 | return SWD_ERROR; 338 | } 339 | return SWD_OK; 340 | } 341 | int swd_write(int APnDP, int addr, uint32_t value) { 342 | int rc; 343 | 344 | do { 345 | rc = _swd_write(APnDP, addr, value); 346 | } while (rc == SWD_WAIT); 347 | return rc; 348 | } 349 | 350 | /** 351 | * @brief This sends an arbitary number of bits to the target 352 | * 353 | * It sends each 32bit word in turn LSB first, so it's a little 354 | * counter intuitive, but keeps the code simple for other parts. 355 | * 356 | * @param data 357 | * @param bitcount 358 | */ 359 | void swd_send_bits(uint32_t *data, int bitcount) { 360 | // Jump to the output function... 361 | lerp_sm_put_blocking(swd_pio, swd_sm, ((bitcount-1) << 5) | swd_offset_output); 362 | 363 | // And write them... 364 | while (bitcount > 0) { 365 | lerp_sm_put_blocking(swd_pio, swd_sm, *data++); 366 | bitcount -= 32; 367 | } 368 | // Do we need a sacrificial word? 369 | if (bitcount == 0) lerp_sm_put_blocking(swd_pio, swd_sm, 0); 370 | } 371 | 372 | void swd_line_reset() { 373 | // Reset is at least 50 bits of 1 followed by at least 2 zero's 374 | static const uint32_t reset_seq[] = { 0xffffffff, 0x0003ffff }; 375 | 376 | swd_send_bits((uint32_t *)reset_seq, 52); 377 | } 378 | 379 | void swd_from_dormant() { 380 | // Selection alert sequence... 128bits 381 | static const uint32_t selection_alert_seq[] = { 0x6209F392, 0x86852D95, 0xE3DDAFE9, 0x19BC0EA2 }; 382 | 383 | // Zeros and ones sequences... 384 | static const uint32_t zero_seq[] = { 0x00000000 }; 385 | static const uint32_t ones_seq[] = { 0xffffffff }; 386 | 387 | // Activation sequence (8 bits) 388 | static const uint32_t act_seq[] = { 0x1a }; 389 | 390 | swd_send_bits((uint32_t *)ones_seq, 8); 391 | swd_send_bits((uint32_t *)selection_alert_seq, 128); 392 | swd_send_bits((uint32_t *)zero_seq, 4); 393 | swd_send_bits((uint32_t *)act_seq, 8); 394 | 395 | // At this point SWD will be in protocol error state, so needs a reset 396 | } 397 | 398 | void swd_to_dormant() { 399 | static const uint32_t to_dorm_seq[] = { 0xe3bc }; 400 | 401 | swd_line_reset(); 402 | swd_send_bits((uint32_t *)to_dorm_seq, 16); 403 | } 404 | -------------------------------------------------------------------------------- /swd.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file swd.h 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-06-27 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #ifndef __SWD_H 13 | #define __SWD_H 14 | 15 | #define SWD_OK 0 16 | #define SWD_WAIT 1 17 | #define SWD_FAULT 2 18 | #define SWD_ERROR 3 19 | #define SWD_PARITY 4 20 | 21 | #define CHECK_OK(func) { int rc = func; if (rc != SWD_OK) return rc; } 22 | 23 | int swd_init(); 24 | void swd_pio_poll(); 25 | void swd_targetsel(uint32_t target); 26 | int swd_read(int APnDP, int addr, uint32_t *result); 27 | int swd_write(int APnDP, int addr, uint32_t value); 28 | void swd_send_bits(uint32_t *data, int bitcount); 29 | 30 | void swd_line_reset(); 31 | void swd_from_dormant(); 32 | void swd_to_dormant(); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /swd.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; 3 | 4 | .program swd 5 | 6 | ; We side set the clock signal... 7 | .side_set 1 opt 8 | 9 | .wrap_target 10 | 11 | ; The default approach is to just accept jump targets in the fifo so they effectively 12 | ; become a series of function calls, each function can then process arguments as needed 13 | ; The jump target is 5 bits (the least significant 5) 14 | public start: 15 | out pc, 5 16 | 17 | ; // BULK OUTPUT // 18 | ; 19 | ; Simple sends up to 2^27 bits worth of data over the link. The remaining 27 bits of the 20 | ; first control word dictate how many bits will be sent, then the bits follow in 32bit words 21 | ; until no more are needed. If we end of a exact 32 bit boundary then we must send another 22 | ; word (this saves on an instruction in here.) 23 | public output: 24 | out x, 27 25 | public out_jmp: 26 | set pindirs, 1 side 0 27 | bulk_out_loop: 28 | out pins, 1 side 0 29 | jmp x--, bulk_out_loop side 1 30 | set pins, 0 side 0 31 | out NULL, 32 ; clear anything not sent 32 | jmp start 33 | 34 | ; // INPUT // -- reads in a certain number of bits and pushes them into the fifo, this 35 | ; will push at the end 36 | public input: 37 | out x, 27 38 | public in_jmp: 39 | set pindirs, 0 side 0 40 | bulk_in_loop: 41 | in pins, 1 side 1 42 | jmp x--, bulk_in_loop side 0 43 | push 44 | jmp start 45 | 46 | ; // SHORT OUTPUT // 47 | ; 48 | ; Using a single control word sends up to 21 bits of data. The first 5 will be the jump 49 | ; target (i.e. to here), then 5 to say how many bits, then up to 21 bits of data to send 50 | ; (cant be 22 otherwise we'd end on a 32bit boundary and need two words) 51 | public short_output: 52 | out x, 5 53 | jmp out_jmp 54 | 55 | 56 | ; // NEW CONDITIONAL // 57 | ; 58 | ; First 5 bits of the jump target to here, then we have 8 bits telling us how many 59 | ; bits we will be reading or writing. 60 | ; Then 5 bits of the location to go to if we are successful (cond_write_ok, or in_jmp) 61 | ; Then 14 bits of the location to go if we have failed (cond_write_fail, or start) 62 | ; 63 | public conditional: 64 | ; We always have a trn, then we need to read 3 bits 65 | set pindirs, 0 side 1 66 | set x, 2 side 0 67 | cond_in_loop: 68 | in pins, 1 side 1 69 | jmp x--, cond_in_loop side 0 70 | 71 | ; Now we have three bits... 72 | in NULL, 28 ; move them to the lsb's (bar 1 to stop autopush) 73 | mov y, ISR ; keep a copy in y 74 | push ; and return the value 75 | set x, 0b0010 ; load the good value in x 76 | jmp x!=y, cond_fail ; failure 77 | 78 | ; If we get here then we are good... 79 | ; For a read we just keep reading (we'll add the trn later) 80 | ; For a write we need a trn 81 | out x, 8 ; load the value of x 82 | out pc, (14 + 5) ; clear out and jump to the good value 83 | 84 | public cond_write_ok: 85 | ; If we are ok on a write, then we need a trn before we output the data 86 | jmp out_jmp side 1 87 | 88 | cond_fail: 89 | ; we always have a trn to do, but we don't have space to set pindirs and output 90 | ; so we'll do that in the code. 91 | out NULL, (8 + 5) ; move past the bit count and good jmp 92 | out pc, 14 ; clear out and jump to the error value 93 | 94 | public cond_write_fail: 95 | ; if we failed on a write then we need to throw away the following two words 96 | out NULL, 32 97 | out NULL, 32 98 | .wrap 99 | 100 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | #ifndef __TUSB_CONFIG_H 2 | #define __TUSB_CONFIG_H 3 | 4 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) 5 | 6 | #define CFG_TUD_CDC (3) 7 | #define CFG_TUD_CDC_RX_BUFSIZE (256) 8 | #define CFG_TUD_CDC_TX_BUFSIZE (256) 9 | 10 | 11 | // We use a vendor specific interface but with our own driver 12 | #define CFG_TUD_VENDOR (0) 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /uart.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file uart.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-07-12 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #include "pico/stdlib.h" 13 | #include "hardware/gpio.h" 14 | #include "lerp/debug.h" 15 | #include "lerp/io.h" 16 | #include "uart.h" 17 | #include "tusb.h" 18 | 19 | static struct io *uart_io = NULL; 20 | 21 | /** 22 | * @brief Handle the UART data sending across to USB or Network 23 | * 24 | * The network side doesn't give us the abilty to dynamically change the 25 | * baud rate, and for some reason it seems to keep switching to 9600, not 26 | * 100% sure why. So let's reset everytime we get a net connection. 27 | * 28 | */ 29 | void dbg_uart_poll() { 30 | static int uart_is_conected = 0; 31 | 32 | // If we don't have a connection we simply consume... 33 | if (!io_is_connected(uart_io)) { 34 | uart_is_conected = 0; 35 | while (uart_is_readable(UART_INTERFACE)) { 36 | uart_get_hw(UART_INTERFACE)->dr; 37 | } 38 | } else { 39 | // If we are a new network connection then reset the baud rate... 40 | if (!uart_is_conected && (uart_io->pcb)) { 41 | uart_is_conected = 1; 42 | uart_init(UART_INTERFACE, UART_BAUD); 43 | } 44 | while (uart_is_readable(UART_INTERFACE) && circ_space(uart_io->output)) { 45 | circ_add_byte(uart_io->output, uart_get_hw(UART_INTERFACE)->dr); 46 | } 47 | while (uart_is_writable(UART_INTERFACE) && circ_has_data(uart_io->input)) { 48 | uart_get_hw(UART_INTERFACE)->dr = circ_get_byte(uart_io->input); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * @brief Handle baud rate changes... 55 | * 56 | * @param itf 57 | * @param line_coding 58 | */ 59 | void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding) { 60 | if (uart_io->usb_is_connected && (itf == UART_CDC)) { 61 | debug_printf("UART baud rate change: %d\r\n", line_coding->bit_rate); 62 | uart_init(UART_INTERFACE, line_coding->bit_rate); 63 | } 64 | } 65 | 66 | /** 67 | * @brief Initialise the UART interface to the default settings 68 | * 69 | */ 70 | void dbg_uart_init() { 71 | gpio_set_function(PIN_UART_TX, GPIO_FUNC_UART); 72 | gpio_set_function(PIN_UART_RX, GPIO_FUNC_UART); 73 | uart_init(UART_INTERFACE, UART_BAUD); 74 | 75 | uart_io = io_init(UART_CDC, UART_TCP, 256); 76 | uart_io->support_telnet = 1; 77 | } -------------------------------------------------------------------------------- /uart.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __UART_H 3 | #define __UART_H 4 | 5 | void dbg_uart_init(); 6 | void dbg_uart_poll(); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file utils.c 3 | * @author Lee Essen (lee.essen@nowonline.co.uk) 4 | * @brief 5 | * @version 0.1 6 | * @date 2022-07-12 7 | * 8 | * @copyright Copyright (c) 2022 9 | * 10 | */ 11 | 12 | #include "utils.h" 13 | #include "stdlib.h" 14 | 15 | int hex_digit(char ch) { 16 | if (ch >= '0' && ch <= '9') return (ch - '0'); 17 | if (ch >= 'a' && ch <= 'f') return (ch - ('a'-10)); 18 | if (ch >= 'A' && ch <= 'F') return (ch - ('A'-10)); 19 | return -1; 20 | } 21 | 22 | int hex_byte(char *packet) { 23 | int rc; 24 | int v; 25 | 26 | v = hex_digit(*packet++); 27 | if (v == -1) 28 | return -1; 29 | rc = v << 4; 30 | v = hex_digit(*packet); 31 | if (v == -1) 32 | return -1; 33 | rc |= v; 34 | return rc; 35 | } 36 | 37 | /** 38 | * @brief Read in 8 chars and convert into a litte endian word 39 | * 40 | */ 41 | uint32_t hex_word_le32(char *packet) { 42 | uint32_t rc = 0; 43 | 44 | for (int i = 0; i < 4; i++) { 45 | rc >>= 8; 46 | int v = hex_byte(packet); 47 | if (v == -1) 48 | return 0xffffffff; 49 | packet += 2; 50 | rc |= (v << 24); 51 | } 52 | return rc; 53 | } 54 | 55 | char *get_two_hex_numbers(char *packet, char sepch, uint32_t *one, uint32_t *two) { 56 | char *sep; 57 | char *end; 58 | 59 | *one = strtoul(packet, &sep, 16); 60 | if (*sep != sepch) 61 | return NULL; 62 | *two = strtoul(sep + 1, &end, 16); 63 | return end; 64 | } 65 | 66 | /** 67 | * @brief Convert a sequence of hex bytes into binary form in the same place, returning length 68 | * 69 | * @param packet 70 | * @param sep 71 | * @return int 72 | */ 73 | int hex_to_bin(char *packet) { 74 | int v; 75 | uint8_t *dst = (uint8_t *)packet; 76 | int len = 0; 77 | 78 | while((v = hex_byte(packet)) >= 0) { 79 | *dst++ = v; 80 | packet += 2; 81 | len++; 82 | } 83 | return len; 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __UTILS_H 3 | #define __UTILS_H 4 | 5 | #include 6 | 7 | int hex_digit(char ch); 8 | int hex_byte(char *packet); 9 | uint32_t hex_word_le32(char *packet); 10 | 11 | char *get_two_hex_numbers(char *packet, char sepch, uint32_t *one, uint32_t *two); 12 | 13 | int hex_to_bin(char *packet); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /wifi.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include "wifi.h" 5 | #include "pico/stdlib.h" 6 | #include "pico/cyw43_arch.h" 7 | 8 | #include "lwip/pbuf.h" 9 | #include "lwip/tcp.h" 10 | 11 | #include "lerp/debug.h" 12 | 13 | #include "tusb.h" 14 | 15 | // Keep track of whether we have a live TCP connection 16 | int _tcp_connected = 0; 17 | 18 | 19 | static int scan_result(void *env, const cyw43_ev_scan_result_t *result) { 20 | if (result) { 21 | debug_printf("ssid: %-32s rssi: %4d chan: %3d mac: %02x:%02x:%02x:%02x:%02x:%02x sec: %u\n", 22 | result->ssid, result->rssi, result->channel, 23 | result->bssid[0], result->bssid[1], result->bssid[2], result->bssid[3], result->bssid[4], result->bssid[5], 24 | result->auth_mode); 25 | } 26 | return 0; 27 | } 28 | 29 | enum { 30 | MODE_DISCONNECTED = 0, 31 | MODE_CONNECTED 32 | }; 33 | 34 | int mode = MODE_DISCONNECTED; 35 | 36 | void wifi_poll() { 37 | cyw43_arch_poll(); 38 | 39 | 40 | if (mode == MODE_DISCONNECTED) { 41 | int ls; 42 | ls = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA); 43 | if (ls == CYW43_LINK_JOIN) { 44 | mode = MODE_CONNECTED; 45 | debug_printf("CONNECTED TO NETOWKR\r\n"); 46 | return; 47 | } 48 | } 49 | if (mode == MODE_CONNECTED) { 50 | int ls; 51 | ls = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA); 52 | if (ls != CYW43_LINK_JOIN) { 53 | mode = MODE_DISCONNECTED; 54 | debug_printf("NOT CONNECTED\r\n"); 55 | } 56 | } 57 | 58 | /* 59 | if (mode == MODE_SCANNING) { 60 | if (!cyw43_wifi_scan_active(&cyw43_state)) { 61 | timeout = time_us_64() + (10 * 1000 * 1000); 62 | mode = MODE_SLEEP; 63 | return; 64 | } 65 | } 66 | 67 | if (mode == MODE_SLEEP) { 68 | if (time_us_64() > timeout) { 69 | debug_printf("STARTING NEW SCAN\r\n"); 70 | cyw43_wifi_scan_options_t scan_options = {0}; 71 | int err = cyw43_wifi_scan(&cyw43_state, &scan_options, NULL, scan_result); 72 | if (err != 0) { 73 | debug_printf("scan srart failed %d\r\n", err); 74 | } 75 | mode = MODE_SCANNING; 76 | return; 77 | } 78 | } 79 | */ 80 | } 81 | 82 | static err_t my_sent(void *arg, struct tcp_pcb *pcb, uint16_t len) { 83 | debug_printf("sent %d bytes\r\n", len); 84 | return ERR_OK; 85 | } 86 | 87 | static err_t my_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { 88 | debug_printf("Have data\r\n"); 89 | if (!p) { 90 | // This is a disconnect... 91 | tcp_close(pcb); 92 | _tcp_connected = 0; 93 | debug_printf("TCP Connection dropped\r\n"); 94 | } 95 | tcp_recved(pcb, p->tot_len); 96 | pbuf_free(p); 97 | return ERR_OK; 98 | } 99 | 100 | 101 | static err_t my_accept(void *arg, struct tcp_pcb *pcb, err_t err) { 102 | if (err != ERR_OK || pcb == NULL) { 103 | debug_printf("accept failed\r\n"); 104 | return ERR_VAL; 105 | } 106 | tcp_sent(pcb, my_sent); 107 | tcp_recv(pcb, my_recv); 108 | _tcp_connected = 1; 109 | return ERR_OK; 110 | } 111 | 112 | 113 | 114 | void wifi_init() { 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /wifi.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __WIFI_H 3 | #define __WIFI_H 4 | 5 | void wifi_init(); 6 | void wifi_poll(); 7 | 8 | extern int _tcp_connected; 9 | 10 | static int inline is_tcp_connected() { return _tcp_connected; } 11 | 12 | #endif 13 | --------------------------------------------------------------------------------