├── picowota_reboot ├── CMakeLists.txt ├── include │ └── picowota │ │ └── reboot.h └── reboot.c ├── dhcpserver ├── LICENSE ├── dhcpserver.h └── dhcpserver.c ├── tcp_comm.h ├── LICENSE ├── mkasm.py ├── pico_sdk_import.cmake ├── gen_imghdr.py ├── lwipopts.h ├── README.md ├── CMakeLists.txt ├── standalone.ld ├── bootloader_shell.ld ├── tcp_comm.c └── main.c /picowota_reboot/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(picowota_reboot INTERFACE) 2 | 3 | target_include_directories(picowota_reboot INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/include 5 | ) 6 | 7 | target_sources(picowota_reboot INTERFACE 8 | ${CMAKE_CURRENT_LIST_DIR}/reboot.c 9 | ) 10 | 11 | target_link_libraries(picowota_reboot INTERFACE 12 | cmsis_core 13 | hardware_structs 14 | ) 15 | -------------------------------------------------------------------------------- /picowota_reboot/include/picowota/reboot.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 Brian Starkey 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #ifndef __PICOWOTA_REBOOT_H__ 8 | #define __PICOWOTA_REBOOT_H__ 9 | 10 | #include 11 | 12 | #define PICOWOTA_BOOTLOADER_ENTRY_MAGIC 0xb105f00d 13 | 14 | void picowota_reboot(bool to_bootloader); 15 | 16 | #endif /* __PICOWOTA_REBOOT_H__ */ 17 | -------------------------------------------------------------------------------- /picowota_reboot/reboot.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 Brian Starkey 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "RP2040.h" 8 | #include "hardware/structs/watchdog.h" 9 | #include "hardware/watchdog.h" 10 | 11 | #include "picowota/reboot.h" 12 | 13 | void picowota_reboot(bool to_bootloader) 14 | { 15 | hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS); 16 | if (to_bootloader) { 17 | watchdog_hw->scratch[5] = PICOWOTA_BOOTLOADER_ENTRY_MAGIC; 18 | watchdog_hw->scratch[6] = ~PICOWOTA_BOOTLOADER_ENTRY_MAGIC; 19 | } else { 20 | watchdog_hw->scratch[5] = 0; 21 | watchdog_hw->scratch[6] = 0; 22 | } 23 | watchdog_reboot(0, 0, 0); 24 | while (1) { 25 | tight_loop_contents(); 26 | asm(""); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /dhcpserver/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2022 Damien P. George 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /tcp_comm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 Brian Starkey 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | #ifndef __TCP_COMM_H__ 7 | #define __TCP_COMM_H__ 8 | 9 | #include 10 | #include 11 | 12 | #define TCP_COMM_MAX_DATA_LEN 1024 13 | #define TCP_COMM_RSP_OK (('O' << 0) | ('K' << 8) | ('O' << 16) | ('K' << 24)) 14 | #define TCP_COMM_RSP_ERR (('E' << 0) | ('R' << 8) | ('R' << 16) | ('!' << 24)) 15 | 16 | 17 | struct comm_command { 18 | uint32_t opcode; 19 | uint32_t nargs; 20 | uint32_t resp_nargs; 21 | uint32_t (*size)(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out); 22 | uint32_t (*handle)(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out); 23 | }; 24 | 25 | struct tcp_comm_ctx; 26 | 27 | err_t tcp_comm_listen(struct tcp_comm_ctx *ctx, uint16_t port); 28 | err_t tcp_comm_server_close(struct tcp_comm_ctx *ctx); 29 | bool tcp_comm_server_done(struct tcp_comm_ctx *ctx); 30 | 31 | struct tcp_comm_ctx *tcp_comm_new(const struct comm_command *const *cmds, 32 | unsigned int n_cmds, uint32_t sync_opcode); 33 | void tcp_comm_delete(struct tcp_comm_ctx *ctx); 34 | 35 | #endif /* __TCP_COMM_H__ */ 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Brian Starkey 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /dhcpserver/dhcpserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the MicroPython project, http://micropython.org/ 3 | * 4 | * The MIT License (MIT) 5 | * 6 | * Copyright (c) 2018-2019 Damien P. George 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | #ifndef MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H 27 | #define MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H 28 | 29 | #include "lwip/ip_addr.h" 30 | 31 | #define DHCPS_BASE_IP (16) 32 | #define DHCPS_MAX_IP (8) 33 | 34 | typedef struct _dhcp_server_lease_t { 35 | uint8_t mac[6]; 36 | uint16_t expiry; 37 | } dhcp_server_lease_t; 38 | 39 | typedef struct _dhcp_server_t { 40 | ip_addr_t ip; 41 | ip_addr_t nm; 42 | dhcp_server_lease_t lease[DHCPS_MAX_IP]; 43 | struct udp_pcb *udp; 44 | } dhcp_server_t; 45 | 46 | void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm); 47 | void dhcp_server_deinit(dhcp_server_t *d); 48 | 49 | #endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H 50 | -------------------------------------------------------------------------------- /mkasm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Derived from pad_checksum in the Pico SDK, which carries the following 3 | # LICENSE.txt: 4 | # Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. 5 | # 6 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 7 | # following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 10 | # disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 13 | # disclaimer in the documentation and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 16 | # derived from this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | import argparse 27 | import binascii 28 | import struct 29 | import sys 30 | 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument("ifile", help="Input file (binary)") 33 | parser.add_argument("ofile", help="Output file (assembly)") 34 | args = parser.parse_args() 35 | 36 | try: 37 | idata = open(args.ifile, "rb").read() 38 | except: 39 | sys.exit("Could not open input file '{}'".format(args.ifile)) 40 | 41 | odata = idata 42 | 43 | try: 44 | with open(args.ofile, "w") as ofile: 45 | ofile.write("// ASM-ified version of: {}\n\n".format(args.ifile)) 46 | ofile.write(".cpu cortex-m0plus\n") 47 | ofile.write(".thumb\n\n") 48 | ofile.write(".section .boot3, \"ax\"\n\n") 49 | for offs in range(0, len(odata), 16): 50 | chunk = odata[offs:min(offs + 16, len(odata))] 51 | ofile.write(".byte {}\n".format(", ".join("0x{:02x}".format(b) for b in chunk))) 52 | except: 53 | sys.exit("Could not open output file '{}'".format(args.ofile)) 54 | -------------------------------------------------------------------------------- /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 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /gen_imghdr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Derived from pad_checksum in the Pico SDK, which carries the following 3 | # LICENSE.txt: 4 | # Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. 5 | # 6 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 7 | # following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 10 | # disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 13 | # disclaimer in the documentation and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 16 | # derived from this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | import argparse 27 | import binascii 28 | import struct 29 | import sys 30 | 31 | def any_int(x): 32 | try: 33 | return int(x, 0) 34 | except: 35 | raise argparse.ArgumentTypeError("expected an integer, not '{!r}'".format(x)) 36 | 37 | parser = argparse.ArgumentParser() 38 | parser.add_argument("ifile", help="Input application binary (binary)") 39 | parser.add_argument("ofile", help="Output header file (binary)") 40 | parser.add_argument("-a", "--addr", help="Load address of the application image", 41 | type=any_int, default=0x10004000) 42 | parser.add_argument("-m", "--map", help="Map file to scan for application image section") 43 | parser.add_argument("-s", "--section", help="Section name to look for in map file", 44 | default=".app_bin") 45 | args = parser.parse_args() 46 | 47 | try: 48 | idata = open(args.ifile, "rb").read() 49 | except: 50 | sys.exit("Could not open input file '{}'".format(args.ifile)) 51 | 52 | if args.map: 53 | vtor = None 54 | with open(args.map) as mapfile: 55 | for line in mapfile: 56 | if line.startswith(args.section): 57 | parts = line.split() 58 | if len(parts)>1: 59 | vtor = int(parts[1],0) 60 | print('found address 0x%x for %s in %s'%(vtor,args.section,args.map)) 61 | break 62 | 63 | if vtor is None: 64 | sys.exit("Could not find section {} in {}".format(args.section,args.map)) 65 | else: 66 | vtor = args.addr 67 | 68 | size = len(idata) 69 | crc = binascii.crc32(idata) 70 | 71 | odata = vtor.to_bytes(4, byteorder='little') + size.to_bytes(4, byteorder='little') + crc.to_bytes(4, byteorder='little') 72 | 73 | try: 74 | with open(args.ofile, "wb") as ofile: 75 | ofile.write(odata) 76 | except: 77 | sys.exit("Could not open output file '{}'".format(args.ofile)) 78 | -------------------------------------------------------------------------------- /lwipopts.h: -------------------------------------------------------------------------------- 1 | #ifndef _LWIPOPTS_EXAMPLE_COMMONH_H 2 | #define _LWIPOPTS_EXAMPLE_COMMONH_H 3 | 4 | 5 | // Common settings used in most of the pico_w examples 6 | // (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) 7 | 8 | // allow override in some examples 9 | #ifndef NO_SYS 10 | #define NO_SYS 1 11 | #endif 12 | // allow override in some examples 13 | #ifndef LWIP_SOCKET 14 | #define LWIP_SOCKET 0 15 | #endif 16 | #if PICO_CYW43_ARCH_POLL 17 | #define MEM_LIBC_MALLOC 1 18 | #else 19 | // MEM_LIBC_MALLOC is incompatible with non polling versions 20 | #define MEM_LIBC_MALLOC 0 21 | #endif 22 | #define MEM_ALIGNMENT 4 23 | #define MEM_SIZE 4000 24 | #define MEMP_NUM_TCP_SEG 32 25 | #define MEMP_NUM_ARP_QUEUE 10 26 | #define PBUF_POOL_SIZE 24 27 | #define LWIP_ARP 1 28 | #define LWIP_ETHERNET 1 29 | #define LWIP_ICMP 1 30 | #define LWIP_RAW 1 31 | #define TCP_WND (8 * TCP_MSS) 32 | #define TCP_MSS 1460 33 | #define TCP_SND_BUF (8 * TCP_MSS) 34 | #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) 35 | #define LWIP_NETIF_STATUS_CALLBACK 1 36 | #define LWIP_NETIF_LINK_CALLBACK 1 37 | #define LWIP_NETIF_HOSTNAME 1 38 | #define LWIP_NETCONN 0 39 | #define MEM_STATS 0 40 | #define SYS_STATS 0 41 | #define MEMP_STATS 0 42 | #define LINK_STATS 0 43 | // #define ETH_PAD_SIZE 2 44 | #define LWIP_CHKSUM_ALGORITHM 3 45 | #define LWIP_DHCP 1 46 | #define LWIP_IPV4 1 47 | #define LWIP_TCP 1 48 | #define LWIP_UDP 1 49 | #define LWIP_DNS 1 50 | #define LWIP_TCP_KEEPALIVE 1 51 | #define LWIP_NETIF_TX_SINGLE_PBUF 1 52 | #define DHCP_DOES_ARP_CHECK 0 53 | #define LWIP_DHCP_DOES_ACD_CHECK 0 54 | 55 | #ifndef NDEBUG 56 | #define LWIP_DEBUG 1 57 | #define LWIP_STATS 1 58 | #define LWIP_STATS_DISPLAY 1 59 | #endif 60 | 61 | #define ETHARP_DEBUG LWIP_DBG_OFF 62 | #define NETIF_DEBUG LWIP_DBG_OFF 63 | #define PBUF_DEBUG LWIP_DBG_OFF 64 | #define API_LIB_DEBUG LWIP_DBG_OFF 65 | #define API_MSG_DEBUG LWIP_DBG_OFF 66 | #define SOCKETS_DEBUG LWIP_DBG_OFF 67 | #define ICMP_DEBUG LWIP_DBG_OFF 68 | #define INET_DEBUG LWIP_DBG_OFF 69 | #define IP_DEBUG LWIP_DBG_OFF 70 | #define IP_REASS_DEBUG LWIP_DBG_OFF 71 | #define RAW_DEBUG LWIP_DBG_OFF 72 | #define MEM_DEBUG LWIP_DBG_OFF 73 | #define MEMP_DEBUG LWIP_DBG_OFF 74 | #define SYS_DEBUG LWIP_DBG_OFF 75 | #define TCP_DEBUG LWIP_DBG_OFF 76 | #define TCP_INPUT_DEBUG LWIP_DBG_OFF 77 | #define TCP_OUTPUT_DEBUG LWIP_DBG_OFF 78 | #define TCP_RTO_DEBUG LWIP_DBG_OFF 79 | #define TCP_CWND_DEBUG LWIP_DBG_OFF 80 | #define TCP_WND_DEBUG LWIP_DBG_OFF 81 | #define TCP_FR_DEBUG LWIP_DBG_OFF 82 | #define TCP_QLEN_DEBUG LWIP_DBG_OFF 83 | #define TCP_RST_DEBUG LWIP_DBG_OFF 84 | #define UDP_DEBUG LWIP_DBG_OFF 85 | #define TCPIP_DEBUG LWIP_DBG_OFF 86 | #define PPP_DEBUG LWIP_DBG_OFF 87 | #define SLIP_DEBUG LWIP_DBG_OFF 88 | #define DHCP_DEBUG LWIP_DBG_OFF 89 | 90 | #endif /* __LWIPOPTS_H__ */ 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `picowota` - Raspberry Pi Pico W OTA bootloader 2 | 3 | > `picowota`, kinda sounds like you're speaking [Belter](https://expanse.fandom.com/wiki/Belter) 4 | 5 | This project implements a bootloader for the Raspberry Pi Pico W which allows 6 | upload of program code over WiFi ("Over The Air"). 7 | 8 | The easiest way to use it is to include this repository as a submodule in the 9 | application which you want to be able to update over WiFi. 10 | 11 | There's an example project using picowota at https://github.com/usedbytes/picowota_blink 12 | 13 | The simplest way to build `picowota` by itself is: 14 | 15 | ``` 16 | mkdir build 17 | cd build 18 | export PICOWOTA_WIFI_SSID=picowota 19 | export PICOWOTA_WIFI_PASS=password 20 | export PICOWOTA_WIFI_AP=1 21 | cmake -DPICO_BOARD=pico_w -DPICO_SDK_PATH=/your/path/to/pico-sdk ../ 22 | make 23 | ``` 24 | 25 | ## Using in your project 26 | 27 | First add `picowota` as a submodule to your project: 28 | ``` 29 | git submodule add https://github.com/usedbytes/picowota 30 | git submodule update --init picowota 31 | git commit -m "Add picowota submodule" 32 | ``` 33 | 34 | Then modifiy your project's CMakeLists.txt to include the `picowota` directory: 35 | 36 | ``` 37 | add_subdirectory(picowota) 38 | ``` 39 | 40 | `picowota` either connects to an existing WiFi network (by default) or 41 | creates one, in both cases with the given SSID and password. 42 | 43 | You can either provide the following as environment variables, or set them 44 | as CMake variables: 45 | 46 | ``` 47 | PICOWOTA_WIFI_SSID # The WiFi network SSID 48 | PICOWOTA_WIFI_PASS # The WiFi network password 49 | PICOWOTA_WIFI_AP # Optional; 0 = connect to the network, 1 = create it 50 | ``` 51 | 52 | Then, you can either build just your standalone app binary (suitable for 53 | updating via `picowota` when it's already on the Pico), or a combined binary 54 | which contains the bootloader and the app (suitable for flashing the first 55 | time): 56 | 57 | ``` 58 | picowota_build_standalone(my_executable_name) 59 | picowota_build_combined(my_executable_name) 60 | ``` 61 | 62 | Note: The combined target will also build the standalone binary. 63 | 64 | To be able to update your app, you must provide a way to return to the 65 | bootloader. By default, if GPIO15 is pulled low at boot time, then `picowota` 66 | will stay in bootloader mode, ready to receive new app code. 67 | 68 | You can also return to the bootloader from your app code - for example when a 69 | button is pressed, or in response to some network request. The 70 | `picowota_reboot` library provides a `picowota_reboot(bool to_bootloader)` 71 | function, which your app can call to get back in to the bootloader. 72 | 73 | ``` 74 | CMakeLists.txt: 75 | 76 | target_link_libraries(my_executable_name picowota_reboot) 77 | 78 | your_c_code.c: 79 | 80 | #include "picowota/reboot.h" 81 | 82 | ... 83 | 84 | { 85 | 86 | ... 87 | 88 | if (should_reboot_to_bootloader) { 89 | picowota_reboot(true); 90 | } 91 | 92 | ... 93 | 94 | } 95 | ``` 96 | 97 | ## Uploading code via `picowota` 98 | 99 | Once you've got the `picowota` bootloader installed on your Pico, you can use 100 | the https://github.com/usedbytes/serial-flash tool to upload code to it. 101 | 102 | As long as the Pico is "in" the `picowota` bootloader (i.e. because there's no 103 | valid app code uploaded yet, or your app called `picowota_reboot(true);`), you 104 | can upload an app `.elf` file which was built by `picowota_build_standalone()`: 105 | 106 | If using the AP mode, the Pico's IP address will be (at the time of writing) 107 | 192.168.4.1/24, and the connected device's something in the same subnet. 108 | Otherwise it depends on your network settings. 109 | 110 | (Assuming your Pico's IP address is 192.168.1.123): 111 | ``` 112 | serial-flash tcp:192.168.1.123:4242 my_executable_name.elf 113 | ``` 114 | 115 | After uploading the code, if successful, the Pico will jump to the newly 116 | uploaded app. 117 | 118 | ## How it works 119 | 120 | This is derived from my Pico non-W bootloader, https://github.com/usedbytes/rp2040-serial-bootloader, which I wrote about in a blog post: https://blog.usedbytes.com/2021/12/pico-serial-bootloader/ 121 | 122 | The bootloader code attempts to avoid "bricking" by storing a CRC of the app 123 | code which gets uploaded. If the CRC doesn't match, then the app won't get run 124 | and the Pico will stay in `picowota` bootloader mode. This should make it fairly 125 | robust against errors in transfers etc. 126 | 127 | ## Known issues 128 | 129 | ### Bootloader/app size and `cyw43` firmware 130 | 131 | The WiFi chip on a Pico W needs firmware, which gets built in to any program 132 | you build with the Pico SDK. This is relatively large - 300-400 kB, which is why 133 | this bootloader is so large. 134 | 135 | This gets duplicated in the `picowota` bootloader binary and _also_ the app 136 | binary, which obviously uses up a significant chunk of the Pico's 2 MB flash. 137 | 138 | It would be nice to be able to avoid this duplication, but the Pico SDK 139 | libraries don't give a mechanism to do so. 140 | 141 | I've raised https://github.com/raspberrypi/pico-sdk/issues/928 for consideration. 142 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Derived from the Pico SDK, which carries the following 2 | # LICENSE.txt: 3 | # Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. 4 | # 5 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 6 | # following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 9 | # disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 12 | # disclaimer in the documentation and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 15 | # derived from this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 18 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | cmake_minimum_required(VERSION 3.13) 26 | 27 | include(pico_sdk_import.cmake) 28 | 29 | project(picowota C CXX ASM) 30 | 31 | set(CMAKE_C_STANDARD 11) 32 | set(CMAKE_CXX_STANDARD 17) 33 | 34 | pico_sdk_init() 35 | 36 | add_executable(picowota 37 | main.c 38 | tcp_comm.c 39 | dhcpserver/dhcpserver.c 40 | ) 41 | 42 | function(target_cl_options option) 43 | target_compile_options(picowota PRIVATE ${option}) 44 | target_link_options(picowota PRIVATE ${option}) 45 | endfunction() 46 | 47 | target_cl_options("-Wall") 48 | target_cl_options("-Os") 49 | target_cl_options("-ffunction-sections") 50 | target_cl_options("-fdata-sections") 51 | target_link_options(picowota PRIVATE "LINKER:--gc-sections") 52 | 53 | pico_add_extra_outputs(picowota) 54 | 55 | target_include_directories(picowota PRIVATE 56 | ${CMAKE_CURRENT_LIST_DIR} # Needed so that lwip can find lwipopts.h 57 | ${CMAKE_CURRENT_LIST_DIR}/dhcpserver) 58 | 59 | pico_enable_stdio_usb(picowota 1) 60 | 61 | add_subdirectory(picowota_reboot) 62 | 63 | target_link_libraries(picowota 64 | cmsis_core 65 | hardware_dma 66 | hardware_flash 67 | hardware_resets 68 | hardware_structs 69 | pico_cyw43_arch_lwip_poll 70 | pico_stdlib 71 | pico_sync 72 | pico_util 73 | picowota_reboot 74 | ) 75 | 76 | # Retrieves build variables from the environment if present 77 | function(picowota_retrieve_variable name hidden) 78 | if (DEFINED ENV{${name}} AND (NOT ${name})) 79 | set(${name} $ENV{${name}} PARENT_SCOPE) 80 | if (hidden) 81 | set(log_value "hidden") 82 | else() 83 | set(log_value "'$ENV{${name}}'") 84 | endif() 85 | message("Using ${name} from environment (${log_value})") 86 | endif() 87 | endfunction() 88 | 89 | picowota_retrieve_variable(PICOWOTA_WIFI_SSID false) 90 | picowota_retrieve_variable(PICOWOTA_WIFI_PASS true) 91 | picowota_retrieve_variable(PICOWOTA_WIFI_AP false) 92 | 93 | if ((NOT PICOWOTA_WIFI_SSID) OR (NOT PICOWOTA_WIFI_PASS)) 94 | message(FATAL_ERROR 95 | "WiFi SSID/Pass not set, please set PICOWOTA_WIFI_SSID/PICOWOTA_WIFI_PASS." 96 | ) 97 | endif () 98 | 99 | # TODO: This causes a full rebuild if they change, configure_file might 100 | # be better. 101 | target_compile_definitions(picowota PUBLIC PICOWOTA_WIFI_SSID=${PICOWOTA_WIFI_SSID}) 102 | target_compile_definitions(picowota PUBLIC PICOWOTA_WIFI_PASS=${PICOWOTA_WIFI_PASS}) 103 | 104 | # Use the WiFi AP mode upon request 105 | if (PICOWOTA_WIFI_AP) 106 | target_compile_definitions(picowota PUBLIC PICOWOTA_WIFI_AP=1) 107 | message("Building in WiFi AP mode.") 108 | endif() 109 | 110 | # Provide a helper to build a standalone target 111 | function(picowota_build_standalone NAME) 112 | get_target_property(PICOWOTA_SRC_DIR picowota SOURCE_DIR) 113 | pico_set_linker_script(${NAME} ${PICOWOTA_SRC_DIR}/standalone.ld) 114 | pico_add_bin_output(${NAME}) 115 | endfunction() 116 | 117 | # Provide a helper to build a combined target 118 | # The build process is roughly: 119 | # 1. Build the bootloader, using a special linker script which leaves 120 | # two sections to be filled in with the header (.app_hdr) and 121 | # app binary (.app_bin) 122 | # 2. Build the app binary, using a special linker script to set the load 123 | # address properly and skip boot2. 124 | # 3. Calculate the checksum of the app binary 125 | # 4. Update the header and binary sections in the ELF from 1. 126 | function(picowota_build_combined NAME) 127 | set(APP_BIN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.bin) 128 | set(APP_HDR_BIN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_hdr.bin) 129 | set(COMBINED picowota_${NAME}) 130 | get_target_property(PICOWOTA_SRC_DIR picowota SOURCE_DIR) 131 | get_target_property(PICOWOTA_BIN_DIR picowota BINARY_DIR) 132 | 133 | get_directory_property(initial_clean ADDITIONAL_MAKE_CLEAN_FILES) 134 | list(APPEND clean_files ${initial_clean}) 135 | list(APPEND clean_files ${APP_BIN}) 136 | list(APPEND clean_files ${APP_HDR_BIN}) 137 | 138 | # The app must be built with the correct linker script (and a .bin) 139 | picowota_build_standalone(${NAME}) 140 | 141 | # Build the bootloader with the sections to fill in 142 | pico_set_linker_script(picowota ${PICOWOTA_SRC_DIR}/bootloader_shell.ld) 143 | 144 | add_custom_target(${NAME}_hdr DEPENDS ${NAME} picowota) 145 | add_custom_command(TARGET ${NAME}_hdr 146 | COMMAND ${PICOWOTA_SRC_DIR}/gen_imghdr.py --map ${PICOWOTA_BIN_DIR}/picowota.elf.map --section .app_bin ${APP_BIN} ${APP_HDR_BIN} 147 | ) 148 | 149 | add_custom_target(${COMBINED} ALL) 150 | add_dependencies(${COMBINED} picowota ${NAME}_hdr) 151 | add_custom_command(TARGET ${COMBINED} 152 | COMMAND ${CMAKE_OBJCOPY} 153 | --update-section .app_hdr=${APP_HDR_BIN} 154 | --update-section .app_bin=${APP_BIN} ${PICOWOTA_BIN_DIR}/picowota.elf ${COMBINED}.elf 155 | ) 156 | list(APPEND clean_files ${COMBINED}.bin) 157 | list(APPEND clean_files ${COMBINED}.elf) 158 | 159 | add_custom_command(TARGET ${COMBINED} POST_BUILD 160 | COMMAND ${CMAKE_OBJCOPY} -Obinary ${COMBINED}.elf ${COMBINED}.bin 161 | ) 162 | 163 | if (NOT ELF2UF2_FOUND) 164 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PICO_SDK_PATH}/tools) 165 | find_package(ELF2UF2) 166 | endif() 167 | if (ELF2UF2_FOUND) 168 | add_custom_command(TARGET ${COMBINED} POST_BUILD 169 | COMMAND ELF2UF2 ${COMBINED}.elf ${COMBINED}.uf2 170 | ) 171 | list(APPEND clean_files ${COMBINED}.uf2) 172 | endif() 173 | 174 | set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${clean_files}") 175 | endfunction() 176 | -------------------------------------------------------------------------------- /standalone.ld: -------------------------------------------------------------------------------- 1 | /* Based on GCC ARM embedded samples. 2 | Defines the following symbols for use by code: 3 | __exidx_start 4 | __exidx_end 5 | __etext 6 | __data_start__ 7 | __preinit_array_start 8 | __preinit_array_end 9 | __init_array_start 10 | __init_array_end 11 | __fini_array_start 12 | __fini_array_end 13 | __data_end__ 14 | __bss_start__ 15 | __bss_end__ 16 | __end__ 17 | end 18 | __HeapLimit 19 | __StackLimit 20 | __StackTop 21 | __stack (== StackTop) 22 | */ 23 | 24 | /* Skip 364kB at the start of flash, that's where the bootloader is */ 25 | MEMORY 26 | { 27 | FLASH(rx) : ORIGIN = 0x10000000 + 364k, LENGTH = 2048k - 364k 28 | RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k 29 | SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k 30 | SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k 31 | } 32 | 33 | ENTRY(_entry_point) 34 | 35 | SECTIONS 36 | { 37 | .flash_begin : { 38 | __flash_binary_start = .; 39 | } > FLASH 40 | 41 | /* boot2 would normally go here, but we don't want it */ 42 | 43 | .text : { 44 | __logical_binary_start = .; 45 | KEEP (*(.vectors)) 46 | KEEP (*(.binary_info_header)) 47 | __binary_info_header_end = .; 48 | KEEP (*(.reset)) 49 | /* TODO revisit this now memset/memcpy/float in ROM */ 50 | /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from 51 | * FLASH ... we will include any thing excluded here in .data below by default */ 52 | *(.init) 53 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) 54 | *(.fini) 55 | /* Pull all c'tors into .text */ 56 | *crtbegin.o(.ctors) 57 | *crtbegin?.o(.ctors) 58 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) 59 | *(SORT(.ctors.*)) 60 | *(.ctors) 61 | /* Followed by destructors */ 62 | *crtbegin.o(.dtors) 63 | *crtbegin?.o(.dtors) 64 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) 65 | *(SORT(.dtors.*)) 66 | *(.dtors) 67 | 68 | *(.eh_frame*) 69 | . = ALIGN(4); 70 | } > FLASH 71 | 72 | .rodata : { 73 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) 74 | . = ALIGN(4); 75 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) 76 | . = ALIGN(4); 77 | } > FLASH 78 | 79 | .ARM.extab : 80 | { 81 | *(.ARM.extab* .gnu.linkonce.armextab.*) 82 | } > FLASH 83 | 84 | __exidx_start = .; 85 | .ARM.exidx : 86 | { 87 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 88 | } > FLASH 89 | __exidx_end = .; 90 | 91 | /* Machine inspectable binary information */ 92 | . = ALIGN(4); 93 | __binary_info_start = .; 94 | .binary_info : 95 | { 96 | KEEP(*(.binary_info.keep.*)) 97 | *(.binary_info.*) 98 | } > FLASH 99 | __binary_info_end = .; 100 | . = ALIGN(4); 101 | 102 | /* End of .text-like segments */ 103 | __etext = .; 104 | 105 | .ram_vector_table (COPY): { 106 | *(.ram_vector_table) 107 | } > RAM 108 | 109 | .data : { 110 | __data_start__ = .; 111 | *(vtable) 112 | 113 | *(.time_critical*) 114 | 115 | /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ 116 | *(.text*) 117 | . = ALIGN(4); 118 | *(.rodata*) 119 | . = ALIGN(4); 120 | 121 | *(.data*) 122 | 123 | . = ALIGN(4); 124 | *(.after_data.*) 125 | . = ALIGN(4); 126 | /* preinit data */ 127 | PROVIDE_HIDDEN (__mutex_array_start = .); 128 | KEEP(*(SORT(.mutex_array.*))) 129 | KEEP(*(.mutex_array)) 130 | PROVIDE_HIDDEN (__mutex_array_end = .); 131 | 132 | . = ALIGN(4); 133 | /* preinit data */ 134 | PROVIDE_HIDDEN (__preinit_array_start = .); 135 | KEEP(*(SORT(.preinit_array.*))) 136 | KEEP(*(.preinit_array)) 137 | PROVIDE_HIDDEN (__preinit_array_end = .); 138 | 139 | . = ALIGN(4); 140 | /* init data */ 141 | PROVIDE_HIDDEN (__init_array_start = .); 142 | KEEP(*(SORT(.init_array.*))) 143 | KEEP(*(.init_array)) 144 | PROVIDE_HIDDEN (__init_array_end = .); 145 | 146 | . = ALIGN(4); 147 | /* finit data */ 148 | PROVIDE_HIDDEN (__fini_array_start = .); 149 | *(SORT(.fini_array.*)) 150 | *(.fini_array) 151 | PROVIDE_HIDDEN (__fini_array_end = .); 152 | 153 | *(.jcr) 154 | . = ALIGN(4); 155 | /* All data end */ 156 | __data_end__ = .; 157 | } > RAM AT> FLASH 158 | 159 | .uninitialized_data (COPY): { 160 | . = ALIGN(4); 161 | *(.uninitialized_data*) 162 | } > RAM 163 | 164 | /* Start and end symbols must be word-aligned */ 165 | .scratch_x : { 166 | __scratch_x_start__ = .; 167 | *(.scratch_x.*) 168 | . = ALIGN(4); 169 | __scratch_x_end__ = .; 170 | } > SCRATCH_X AT > FLASH 171 | __scratch_x_source__ = LOADADDR(.scratch_x); 172 | 173 | .scratch_y : { 174 | __scratch_y_start__ = .; 175 | *(.scratch_y.*) 176 | . = ALIGN(4); 177 | __scratch_y_end__ = .; 178 | } > SCRATCH_Y AT > FLASH 179 | __scratch_y_source__ = LOADADDR(.scratch_y); 180 | 181 | .bss : { 182 | . = ALIGN(4); 183 | __bss_start__ = .; 184 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) 185 | *(COMMON) 186 | . = ALIGN(4); 187 | __bss_end__ = .; 188 | } > RAM 189 | 190 | .heap (COPY): 191 | { 192 | __end__ = .; 193 | end = __end__; 194 | *(.heap*) 195 | __HeapLimit = .; 196 | } > RAM 197 | 198 | /* .stack*_dummy section doesn't contains any symbols. It is only 199 | * used for linker to calculate size of stack sections, and assign 200 | * values to stack symbols later 201 | * 202 | * stack1 section may be empty/missing if platform_launch_core1 is not used */ 203 | 204 | /* by default we put core 0 stack at the end of scratch Y, so that if core 1 205 | * stack is not used then all of SCRATCH_X is free. 206 | */ 207 | .stack1_dummy (COPY): 208 | { 209 | *(.stack1*) 210 | } > SCRATCH_X 211 | .stack_dummy (COPY): 212 | { 213 | *(.stack*) 214 | } > SCRATCH_Y 215 | 216 | .flash_end : { 217 | __flash_binary_end = .; 218 | } > FLASH 219 | 220 | /* stack limit is poorly named, but historically is maximum heap ptr */ 221 | __StackLimit = ORIGIN(RAM) + LENGTH(RAM); 222 | __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); 223 | __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); 224 | __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); 225 | __StackBottom = __StackTop - SIZEOF(.stack_dummy); 226 | PROVIDE(__stack = __StackTop); 227 | 228 | /* Check if data + heap + stack exceeds RAM limit */ 229 | ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") 230 | 231 | ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") 232 | /* todo assert on extra code */ 233 | } 234 | 235 | -------------------------------------------------------------------------------- /bootloader_shell.ld: -------------------------------------------------------------------------------- 1 | /* Based on GCC ARM embedded samples. 2 | Defines the following symbols for use by code: 3 | __exidx_start 4 | __exidx_end 5 | __etext 6 | __data_start__ 7 | __preinit_array_start 8 | __preinit_array_end 9 | __init_array_start 10 | __init_array_end 11 | __fini_array_start 12 | __fini_array_end 13 | __data_end__ 14 | __bss_start__ 15 | __bss_end__ 16 | __end__ 17 | end 18 | __HeapLimit 19 | __StackLimit 20 | __StackTop 21 | __stack (== StackTop) 22 | */ 23 | 24 | /* 25 | * TODO: Hard-code 360k, which is the max size I see on my machine with 26 | * 'sta' mode and debug printf. 27 | * There must be a better way to determine the size dynamically somehow. 28 | */ 29 | MEMORY 30 | { 31 | FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 360k 32 | FLASH_IMGHDR(rx) : ORIGIN = 0x10000000 + 360k, LENGTH = 4k 33 | FLASH_APP(rx) : ORIGIN = 0x10000000 + 364k, LENGTH = 2048k - 364k 34 | RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k 35 | SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k 36 | SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k 37 | } 38 | 39 | ENTRY(_entry_point) 40 | 41 | SECTIONS 42 | { 43 | /* Second stage bootloader is prepended to the image. It must be 256 bytes big 44 | and checksummed. It is usually built by the boot_stage2 target 45 | in the Raspberry Pi Pico SDK 46 | */ 47 | 48 | .flash_begin : { 49 | __flash_binary_start = .; 50 | } > FLASH 51 | 52 | .boot2 : { 53 | __boot2_start__ = .; 54 | KEEP (*(.boot2)) 55 | __boot2_end__ = .; 56 | } > FLASH 57 | 58 | ASSERT(__boot2_end__ - __boot2_start__ == 256, 59 | "ERROR: Pico second stage bootloader must be 256 bytes in size") 60 | 61 | /* 62 | * Name a section for the image header and app binary 63 | * In combined builds, the contents will get replaced post-build 64 | */ 65 | .app_hdr : { 66 | . = ALIGN(4k); 67 | app_image_header = .; 68 | LONG(0xdeaddead) 69 | LONG(0) 70 | LONG(0xdeaddead) 71 | } > FLASH_IMGHDR 72 | 73 | .app_bin : { 74 | . = ALIGN(4k); 75 | __app_bin = .; 76 | LONG(0) 77 | } > FLASH_APP 78 | 79 | /* The second stage will always enter the image at the start of .text. 80 | The debugger will use the ELF entry point, which is the _entry_point 81 | symbol if present, otherwise defaults to start of .text. 82 | This can be used to transfer control back to the bootrom on debugger 83 | launches only, to perform proper flash setup. 84 | */ 85 | 86 | .text : { 87 | __logical_binary_start = .; 88 | KEEP (*(.vectors)) 89 | KEEP (*(.binary_info_header)) 90 | __binary_info_header_end = .; 91 | KEEP (*(.reset)) 92 | /* TODO revisit this now memset/memcpy/float in ROM */ 93 | /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from 94 | * FLASH ... we will include any thing excluded here in .data below by default */ 95 | *(.init) 96 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) 97 | *(.fini) 98 | /* Pull all c'tors into .text */ 99 | *crtbegin.o(.ctors) 100 | *crtbegin?.o(.ctors) 101 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) 102 | *(SORT(.ctors.*)) 103 | *(.ctors) 104 | /* Followed by destructors */ 105 | *crtbegin.o(.dtors) 106 | *crtbegin?.o(.dtors) 107 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) 108 | *(SORT(.dtors.*)) 109 | *(.dtors) 110 | 111 | *(.eh_frame*) 112 | . = ALIGN(4); 113 | } > FLASH 114 | 115 | .rodata : { 116 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) 117 | . = ALIGN(4); 118 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) 119 | . = ALIGN(4); 120 | } > FLASH 121 | 122 | .ARM.extab : 123 | { 124 | *(.ARM.extab* .gnu.linkonce.armextab.*) 125 | } > FLASH 126 | 127 | __exidx_start = .; 128 | .ARM.exidx : 129 | { 130 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 131 | } > FLASH 132 | __exidx_end = .; 133 | 134 | /* Machine inspectable binary information */ 135 | . = ALIGN(4); 136 | __binary_info_start = .; 137 | .binary_info : 138 | { 139 | KEEP(*(.binary_info.keep.*)) 140 | *(.binary_info.*) 141 | } > FLASH 142 | __binary_info_end = .; 143 | . = ALIGN(4); 144 | 145 | /* End of .text-like segments */ 146 | __etext = .; 147 | 148 | .ram_vector_table (COPY): { 149 | *(.ram_vector_table) 150 | } > RAM 151 | 152 | .data : { 153 | __data_start__ = .; 154 | *(vtable) 155 | 156 | *(.time_critical*) 157 | 158 | /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ 159 | *(.text*) 160 | . = ALIGN(4); 161 | *(.rodata*) 162 | . = ALIGN(4); 163 | 164 | *(.data*) 165 | 166 | . = ALIGN(4); 167 | *(.after_data.*) 168 | . = ALIGN(4); 169 | /* preinit data */ 170 | PROVIDE_HIDDEN (__mutex_array_start = .); 171 | KEEP(*(SORT(.mutex_array.*))) 172 | KEEP(*(.mutex_array)) 173 | PROVIDE_HIDDEN (__mutex_array_end = .); 174 | 175 | . = ALIGN(4); 176 | /* preinit data */ 177 | PROVIDE_HIDDEN (__preinit_array_start = .); 178 | KEEP(*(SORT(.preinit_array.*))) 179 | KEEP(*(.preinit_array)) 180 | PROVIDE_HIDDEN (__preinit_array_end = .); 181 | 182 | . = ALIGN(4); 183 | /* init data */ 184 | PROVIDE_HIDDEN (__init_array_start = .); 185 | KEEP(*(SORT(.init_array.*))) 186 | KEEP(*(.init_array)) 187 | PROVIDE_HIDDEN (__init_array_end = .); 188 | 189 | . = ALIGN(4); 190 | /* finit data */ 191 | PROVIDE_HIDDEN (__fini_array_start = .); 192 | *(SORT(.fini_array.*)) 193 | *(.fini_array) 194 | PROVIDE_HIDDEN (__fini_array_end = .); 195 | 196 | *(.jcr) 197 | . = ALIGN(4); 198 | /* All data end */ 199 | __data_end__ = .; 200 | } > RAM AT> FLASH 201 | 202 | .uninitialized_data (COPY): { 203 | . = ALIGN(4); 204 | *(.uninitialized_data*) 205 | } > RAM 206 | 207 | /* Start and end symbols must be word-aligned */ 208 | .scratch_x : { 209 | __scratch_x_start__ = .; 210 | *(.scratch_x.*) 211 | . = ALIGN(4); 212 | __scratch_x_end__ = .; 213 | } > SCRATCH_X AT > FLASH 214 | __scratch_x_source__ = LOADADDR(.scratch_x); 215 | 216 | .scratch_y : { 217 | __scratch_y_start__ = .; 218 | *(.scratch_y.*) 219 | . = ALIGN(4); 220 | __scratch_y_end__ = .; 221 | } > SCRATCH_Y AT > FLASH 222 | __scratch_y_source__ = LOADADDR(.scratch_y); 223 | 224 | .bss : { 225 | . = ALIGN(4); 226 | __bss_start__ = .; 227 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) 228 | *(COMMON) 229 | . = ALIGN(4); 230 | __bss_end__ = .; 231 | } > RAM 232 | 233 | .heap (COPY): 234 | { 235 | __end__ = .; 236 | end = __end__; 237 | *(.heap*) 238 | __HeapLimit = .; 239 | } > RAM 240 | 241 | /* .stack*_dummy section doesn't contains any symbols. It is only 242 | * used for linker to calculate size of stack sections, and assign 243 | * values to stack symbols later 244 | * 245 | * stack1 section may be empty/missing if platform_launch_core1 is not used */ 246 | 247 | /* by default we put core 0 stack at the end of scratch Y, so that if core 1 248 | * stack is not used then all of SCRATCH_X is free. 249 | */ 250 | .stack1_dummy (COPY): 251 | { 252 | *(.stack1*) 253 | } > SCRATCH_X 254 | .stack_dummy (COPY): 255 | { 256 | *(.stack*) 257 | } > SCRATCH_Y 258 | 259 | .flash_end : { 260 | __flash_binary_end = .; 261 | } > FLASH 262 | 263 | /* stack limit is poorly named, but historically is maximum heap ptr */ 264 | __StackLimit = ORIGIN(RAM) + LENGTH(RAM); 265 | __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); 266 | __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); 267 | __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); 268 | __StackBottom = __StackTop - SIZEOF(.stack_dummy); 269 | PROVIDE(__stack = __StackTop); 270 | 271 | /* Check if data + heap + stack exceeds RAM limit */ 272 | ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") 273 | 274 | ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") 275 | /* todo assert on extra code */ 276 | } 277 | -------------------------------------------------------------------------------- /dhcpserver/dhcpserver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the MicroPython project, http://micropython.org/ 3 | * 4 | * The MIT License (MIT) 5 | * 6 | * Copyright (c) 2018-2019 Damien P. George 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | // For DHCP specs see: 28 | // https://www.ietf.org/rfc/rfc2131.txt 29 | // https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "cyw43_config.h" 36 | #include "dhcpserver.h" 37 | #include "lwip/udp.h" 38 | 39 | #define DHCPDISCOVER (1) 40 | #define DHCPOFFER (2) 41 | #define DHCPREQUEST (3) 42 | #define DHCPDECLINE (4) 43 | #define DHCPACK (5) 44 | #define DHCPNACK (6) 45 | #define DHCPRELEASE (7) 46 | #define DHCPINFORM (8) 47 | 48 | #define DHCP_OPT_PAD (0) 49 | #define DHCP_OPT_SUBNET_MASK (1) 50 | #define DHCP_OPT_ROUTER (3) 51 | #define DHCP_OPT_DNS (6) 52 | #define DHCP_OPT_HOST_NAME (12) 53 | #define DHCP_OPT_REQUESTED_IP (50) 54 | #define DHCP_OPT_IP_LEASE_TIME (51) 55 | #define DHCP_OPT_MSG_TYPE (53) 56 | #define DHCP_OPT_SERVER_ID (54) 57 | #define DHCP_OPT_PARAM_REQUEST_LIST (55) 58 | #define DHCP_OPT_MAX_MSG_SIZE (57) 59 | #define DHCP_OPT_VENDOR_CLASS_ID (60) 60 | #define DHCP_OPT_CLIENT_ID (61) 61 | #define DHCP_OPT_END (255) 62 | 63 | #define PORT_DHCP_SERVER (67) 64 | #define PORT_DHCP_CLIENT (68) 65 | 66 | #define DEFAULT_DNS MAKE_IP4(8, 8, 8, 8) 67 | #define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds 68 | 69 | #define MAC_LEN (6) 70 | #define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) 71 | 72 | typedef struct { 73 | uint8_t op; // message opcode 74 | uint8_t htype; // hardware address type 75 | uint8_t hlen; // hardware address length 76 | uint8_t hops; 77 | uint32_t xid; // transaction id, chosen by client 78 | uint16_t secs; // client seconds elapsed 79 | uint16_t flags; 80 | uint8_t ciaddr[4]; // client IP address 81 | uint8_t yiaddr[4]; // your IP address 82 | uint8_t siaddr[4]; // next server IP address 83 | uint8_t giaddr[4]; // relay agent IP address 84 | uint8_t chaddr[16]; // client hardware address 85 | uint8_t sname[64]; // server host name 86 | uint8_t file[128]; // boot file name 87 | uint8_t options[312]; // optional parameters, variable, starts with magic 88 | } dhcp_msg_t; 89 | 90 | static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { 91 | // family is AF_INET 92 | // type is SOCK_DGRAM 93 | 94 | *udp = udp_new(); 95 | if (*udp == NULL) { 96 | return -ENOMEM; 97 | } 98 | 99 | // Register callback 100 | udp_recv(*udp, cb_udp_recv, (void *)cb_data); 101 | 102 | return 0; // success 103 | } 104 | 105 | static void dhcp_socket_free(struct udp_pcb **udp) { 106 | if (*udp != NULL) { 107 | udp_remove(*udp); 108 | *udp = NULL; 109 | } 110 | } 111 | 112 | static int dhcp_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) { 113 | ip_addr_t addr; 114 | IP4_ADDR(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); 115 | // TODO convert lwIP errors to errno 116 | return udp_bind(*udp, &addr, port); 117 | } 118 | 119 | static int dhcp_socket_sendto(struct udp_pcb **udp, const void *buf, size_t len, uint32_t ip, uint16_t port) { 120 | if (len > 0xffff) { 121 | len = 0xffff; 122 | } 123 | 124 | struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); 125 | if (p == NULL) { 126 | return -ENOMEM; 127 | } 128 | 129 | memcpy(p->payload, buf, len); 130 | 131 | ip_addr_t dest; 132 | IP4_ADDR(&dest, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff); 133 | err_t err = udp_sendto(*udp, p, &dest, port); 134 | 135 | pbuf_free(p); 136 | 137 | if (err != ERR_OK) { 138 | return err; 139 | } 140 | 141 | return len; 142 | } 143 | 144 | static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) { 145 | for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) { 146 | if (opt[i] == cmd) { 147 | return &opt[i]; 148 | } 149 | i += 2 + opt[i + 1]; 150 | } 151 | return NULL; 152 | } 153 | 154 | static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, void *data) { 155 | uint8_t *o = *opt; 156 | *o++ = cmd; 157 | *o++ = n; 158 | memcpy(o, data, n); 159 | *opt = o + n; 160 | } 161 | 162 | static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) { 163 | uint8_t *o = *opt; 164 | *o++ = cmd; 165 | *o++ = 1; 166 | *o++ = val; 167 | *opt = o; 168 | } 169 | 170 | static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) { 171 | uint8_t *o = *opt; 172 | *o++ = cmd; 173 | *o++ = 4; 174 | *o++ = val >> 24; 175 | *o++ = val >> 16; 176 | *o++ = val >> 8; 177 | *o++ = val; 178 | *opt = o; 179 | } 180 | 181 | static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { 182 | dhcp_server_t *d = arg; 183 | (void)upcb; 184 | (void)src_addr; 185 | (void)src_port; 186 | 187 | // This is around 548 bytes 188 | dhcp_msg_t dhcp_msg; 189 | 190 | #define DHCP_MIN_SIZE (240 + 3) 191 | if (p->tot_len < DHCP_MIN_SIZE) { 192 | goto ignore_request; 193 | } 194 | 195 | size_t len = pbuf_copy_partial(p, &dhcp_msg, sizeof(dhcp_msg), 0); 196 | if (len < DHCP_MIN_SIZE) { 197 | goto ignore_request; 198 | } 199 | 200 | dhcp_msg.op = DHCPOFFER; 201 | memcpy(&dhcp_msg.yiaddr, &d->ip.addr, 4); 202 | 203 | uint8_t *opt = (uint8_t *)&dhcp_msg.options; 204 | opt += 4; // assume magic cookie: 99, 130, 83, 99 205 | 206 | switch (opt[2]) { 207 | case DHCPDISCOVER: { 208 | int yi = DHCPS_MAX_IP; 209 | for (int i = 0; i < DHCPS_MAX_IP; ++i) { 210 | if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { 211 | // MAC match, use this IP address 212 | yi = i; 213 | break; 214 | } 215 | if (yi == DHCPS_MAX_IP) { 216 | // Look for a free IP address 217 | if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { 218 | // IP available 219 | yi = i; 220 | } 221 | uint32_t expiry = d->lease[i].expiry << 16 | 0xffff; 222 | if ((int32_t)(expiry - cyw43_hal_ticks_ms()) < 0) { 223 | // IP expired, reuse it 224 | memset(d->lease[i].mac, 0, MAC_LEN); 225 | yi = i; 226 | } 227 | } 228 | } 229 | if (yi == DHCPS_MAX_IP) { 230 | // No more IP addresses left 231 | goto ignore_request; 232 | } 233 | dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; 234 | opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPOFFER); 235 | break; 236 | } 237 | 238 | case DHCPREQUEST: { 239 | uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP); 240 | if (o == NULL) { 241 | // Should be NACK 242 | goto ignore_request; 243 | } 244 | if (memcmp(o + 2, &d->ip.addr, 3) != 0) { 245 | // Should be NACK 246 | goto ignore_request; 247 | } 248 | uint8_t yi = o[5] - DHCPS_BASE_IP; 249 | if (yi >= DHCPS_MAX_IP) { 250 | // Should be NACK 251 | goto ignore_request; 252 | } 253 | if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { 254 | // MAC match, ok to use this IP address 255 | } else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { 256 | // IP unused, ok to use this IP address 257 | memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN); 258 | } else { 259 | // IP already in use 260 | // Should be NACK 261 | goto ignore_request; 262 | } 263 | d->lease[yi].expiry = (cyw43_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16; 264 | dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; 265 | opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPACK); 266 | printf("DHCPS: client connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x IP=%u.%u.%u.%u\n", 267 | dhcp_msg.chaddr[0], dhcp_msg.chaddr[1], dhcp_msg.chaddr[2], dhcp_msg.chaddr[3], dhcp_msg.chaddr[4], dhcp_msg.chaddr[5], 268 | dhcp_msg.yiaddr[0], dhcp_msg.yiaddr[1], dhcp_msg.yiaddr[2], dhcp_msg.yiaddr[3]); 269 | break; 270 | } 271 | 272 | default: 273 | goto ignore_request; 274 | } 275 | 276 | opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &d->ip.addr); 277 | opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &d->nm.addr); 278 | opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &d->ip.addr); // aka gateway; can have mulitple addresses 279 | opt_write_u32(&opt, DHCP_OPT_DNS, DEFAULT_DNS); // can have mulitple addresses 280 | opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); 281 | *opt++ = DHCP_OPT_END; 282 | dhcp_socket_sendto(&d->udp, &dhcp_msg, opt - (uint8_t *)&dhcp_msg, 0xffffffff, PORT_DHCP_CLIENT); 283 | 284 | ignore_request: 285 | pbuf_free(p); 286 | } 287 | 288 | void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { 289 | ip_addr_copy(d->ip, *ip); 290 | ip_addr_copy(d->nm, *nm); 291 | memset(d->lease, 0, sizeof(d->lease)); 292 | if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) { 293 | return; 294 | } 295 | dhcp_socket_bind(&d->udp, 0, PORT_DHCP_SERVER); 296 | } 297 | 298 | void dhcp_server_deinit(dhcp_server_t *d) { 299 | dhcp_socket_free(&d->udp); 300 | } 301 | -------------------------------------------------------------------------------- /tcp_comm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 Brian Starkey 3 | * 4 | * Parts based on the Pico W tcp_server example: 5 | * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. 6 | * 7 | * SPDX-License-Identifier: BSD-3-Clause 8 | */ 9 | #include 10 | 11 | #include "pico/cyw43_arch.h" 12 | 13 | #include "lwip/pbuf.h" 14 | #include "lwip/tcp.h" 15 | 16 | #include "tcp_comm.h" 17 | 18 | #ifdef DEBUG 19 | #include 20 | #define DEBUG_printf(...) printf(__VA_ARGS__) 21 | #else 22 | #define DEBUG_printf(...) { } 23 | #endif 24 | 25 | #define POLL_TIME_S 5 26 | 27 | #define COMM_MAX_NARG 5 28 | 29 | enum conn_state { 30 | CONN_STATE_WAIT_FOR_SYNC, 31 | CONN_STATE_READ_OPCODE, 32 | CONN_STATE_READ_ARGS, 33 | CONN_STATE_READ_DATA, 34 | CONN_STATE_HANDLE, 35 | CONN_STATE_WRITE_RESP, 36 | CONN_STATE_WRITE_ERROR, 37 | CONN_STATE_CLOSED, 38 | }; 39 | 40 | struct tcp_comm_ctx { 41 | struct tcp_pcb *serv_pcb; 42 | volatile bool serv_done; 43 | enum conn_state conn_state; 44 | 45 | struct tcp_pcb *client_pcb; 46 | // Note: sizeof(buf) is used elsewhere, so if this is changed to not 47 | // be an array, those will need updating 48 | uint8_t buf[(sizeof(uint32_t) * (1 + COMM_MAX_NARG)) + TCP_COMM_MAX_DATA_LEN]; 49 | 50 | uint16_t rx_start_offs; 51 | uint16_t rx_bytes_received; 52 | uint16_t rx_bytes_needed; 53 | 54 | uint16_t tx_bytes_sent; 55 | uint16_t tx_bytes_remaining; 56 | 57 | uint32_t resp_data_len; 58 | 59 | const struct comm_command *cmd; 60 | const struct comm_command *const *cmds; 61 | unsigned int n_cmds; 62 | uint32_t sync_opcode; 63 | }; 64 | 65 | #define COMM_BUF_OPCODE(_buf) ((uint32_t *)((uint8_t *)(_buf))) 66 | #define COMM_BUF_ARGS(_buf) ((uint32_t *)((uint8_t *)(_buf) + sizeof(uint32_t))) 67 | #define COMM_BUF_BODY(_buf, _nargs) ((uint8_t *)(_buf) + (sizeof(uint32_t) * ((_nargs) + 1))) 68 | 69 | static const struct comm_command *find_command_desc(struct tcp_comm_ctx *ctx, uint32_t opcode) 70 | { 71 | unsigned int i; 72 | 73 | for (i = 0; i < ctx->n_cmds; i++) { 74 | if (ctx->cmds[i]->opcode == opcode) { 75 | return ctx->cmds[i]; 76 | } 77 | } 78 | 79 | return NULL; 80 | } 81 | 82 | static bool is_error(uint32_t status) 83 | { 84 | return status == TCP_COMM_RSP_ERR; 85 | } 86 | 87 | static int tcp_comm_sync_begin(struct tcp_comm_ctx *ctx); 88 | static int tcp_comm_sync_complete(struct tcp_comm_ctx *ctx); 89 | static int tcp_comm_opcode_begin(struct tcp_comm_ctx *ctx); 90 | static int tcp_comm_opcode_complete(struct tcp_comm_ctx *ctx); 91 | static int tcp_comm_args_begin(struct tcp_comm_ctx *ctx); 92 | static int tcp_comm_args_complete(struct tcp_comm_ctx *ctx); 93 | static int tcp_comm_data_begin(struct tcp_comm_ctx *ctx, uint32_t data_len); 94 | static int tcp_comm_data_complete(struct tcp_comm_ctx *ctx); 95 | static int tcp_comm_response_begin(struct tcp_comm_ctx *ctx); 96 | static int tcp_comm_response_complete(struct tcp_comm_ctx *ctx); 97 | static int tcp_comm_error_begin(struct tcp_comm_ctx *ctx); 98 | 99 | static int tcp_comm_sync_begin(struct tcp_comm_ctx *ctx) 100 | { 101 | ctx->conn_state = CONN_STATE_WAIT_FOR_SYNC; 102 | ctx->rx_bytes_needed = sizeof(uint32_t); 103 | 104 | return 0; 105 | } 106 | 107 | static int tcp_comm_sync_complete(struct tcp_comm_ctx *ctx) 108 | { 109 | if (ctx->sync_opcode != *COMM_BUF_OPCODE(ctx->buf)) { 110 | DEBUG_printf("sync not correct: %c%c%c%c\n", ctx->buf[0], ctx->buf[1], ctx->buf[2], ctx->buf[3]); 111 | return tcp_comm_error_begin(ctx); 112 | } 113 | 114 | return tcp_comm_opcode_complete(ctx); 115 | } 116 | 117 | static int tcp_comm_opcode_begin(struct tcp_comm_ctx *ctx) 118 | { 119 | ctx->conn_state = CONN_STATE_READ_OPCODE; 120 | ctx->rx_bytes_needed = sizeof(uint32_t); 121 | 122 | return 0; 123 | } 124 | 125 | static int tcp_comm_opcode_complete(struct tcp_comm_ctx *ctx) 126 | { 127 | ctx->cmd = find_command_desc(ctx, *COMM_BUF_OPCODE(ctx->buf)); 128 | if (!ctx->cmd) { 129 | DEBUG_printf("no command for '%c%c%c%c'\n", ctx->buf[0], ctx->buf[1], ctx->buf[2], ctx->buf[3]); 130 | return tcp_comm_error_begin(ctx); 131 | } else { 132 | DEBUG_printf("got command '%c%c%c%c'\n", ctx->buf[0], ctx->buf[1], ctx->buf[2], ctx->buf[3]); 133 | } 134 | 135 | return tcp_comm_args_begin(ctx); 136 | } 137 | 138 | static int tcp_comm_args_begin(struct tcp_comm_ctx *ctx) 139 | { 140 | ctx->conn_state = CONN_STATE_READ_ARGS; 141 | ctx->rx_bytes_needed = ctx->cmd->nargs * sizeof(uint32_t); 142 | 143 | if (ctx->cmd->nargs == 0) { 144 | return tcp_comm_args_complete(ctx); 145 | } 146 | 147 | return 0; 148 | } 149 | 150 | static int tcp_comm_args_complete(struct tcp_comm_ctx *ctx) 151 | { 152 | const struct comm_command *cmd = ctx->cmd; 153 | 154 | uint32_t data_len = 0; 155 | 156 | if (cmd->size) { 157 | uint32_t status = cmd->size(COMM_BUF_ARGS(ctx->buf), 158 | &data_len, 159 | &ctx->resp_data_len); 160 | if (is_error(status)) { 161 | return tcp_comm_error_begin(ctx); 162 | } 163 | } 164 | 165 | return tcp_comm_data_begin(ctx, data_len); 166 | } 167 | 168 | static int tcp_comm_data_begin(struct tcp_comm_ctx *ctx, uint32_t data_len) 169 | { 170 | ctx->conn_state = CONN_STATE_READ_DATA; 171 | ctx->rx_bytes_needed = data_len; 172 | 173 | if (data_len == 0) { 174 | return tcp_comm_data_complete(ctx); 175 | } 176 | 177 | return 0; 178 | } 179 | 180 | static int tcp_comm_data_complete(struct tcp_comm_ctx *ctx) 181 | { 182 | const struct comm_command *cmd = ctx->cmd; 183 | 184 | if (cmd->handle) { 185 | uint32_t status = cmd->handle(COMM_BUF_ARGS(ctx->buf), 186 | COMM_BUF_BODY(ctx->buf, cmd->nargs), 187 | COMM_BUF_ARGS(ctx->buf), 188 | COMM_BUF_BODY(ctx->buf, cmd->resp_nargs)); 189 | if (is_error(status)) { 190 | return tcp_comm_error_begin(ctx); 191 | } 192 | 193 | *COMM_BUF_OPCODE(ctx->buf) = status; 194 | } else { 195 | // TODO: Should we just assert(desc->handle)? 196 | *COMM_BUF_OPCODE(ctx->buf) = TCP_COMM_RSP_OK; 197 | } 198 | 199 | return tcp_comm_response_begin(ctx); 200 | } 201 | 202 | static int tcp_comm_response_begin(struct tcp_comm_ctx *ctx) 203 | { 204 | ctx->conn_state = CONN_STATE_WRITE_RESP; 205 | ctx->tx_bytes_sent = 0; 206 | ctx->tx_bytes_remaining = ctx->resp_data_len + ((ctx->cmd->resp_nargs + 1) * sizeof(uint32_t)); 207 | 208 | err_t err = tcp_write(ctx->client_pcb, ctx->buf, ctx->tx_bytes_remaining, 0); 209 | if (err != ERR_OK) { 210 | return -1; 211 | } 212 | 213 | return 0; 214 | } 215 | 216 | static int tcp_comm_error_begin(struct tcp_comm_ctx *ctx) 217 | { 218 | ctx->conn_state = CONN_STATE_WRITE_ERROR; 219 | ctx->tx_bytes_sent = 0; 220 | ctx->tx_bytes_remaining = sizeof(uint32_t); 221 | 222 | *COMM_BUF_OPCODE(ctx->buf) = TCP_COMM_RSP_ERR; 223 | 224 | err_t err = tcp_write(ctx->client_pcb, ctx->buf, ctx->tx_bytes_remaining, 0); 225 | if (err != ERR_OK) { 226 | return -1; 227 | } 228 | 229 | return 0; 230 | } 231 | 232 | 233 | static int tcp_comm_response_complete(struct tcp_comm_ctx *ctx) 234 | { 235 | return tcp_comm_opcode_begin(ctx); 236 | } 237 | 238 | static int tcp_comm_rx_complete(struct tcp_comm_ctx *ctx) 239 | { 240 | switch (ctx->conn_state) { 241 | case CONN_STATE_WAIT_FOR_SYNC: 242 | return tcp_comm_sync_complete(ctx); 243 | case CONN_STATE_READ_OPCODE: 244 | return tcp_comm_opcode_complete(ctx); 245 | case CONN_STATE_READ_ARGS: 246 | return tcp_comm_args_complete(ctx); 247 | case CONN_STATE_READ_DATA: 248 | return tcp_comm_data_complete(ctx); 249 | default: 250 | return -1; 251 | } 252 | } 253 | 254 | static int tcp_comm_tx_complete(struct tcp_comm_ctx *ctx) 255 | { 256 | switch (ctx->conn_state) { 257 | case CONN_STATE_WRITE_RESP: 258 | return tcp_comm_response_complete(ctx); 259 | case CONN_STATE_WRITE_ERROR: 260 | return -1; 261 | default: 262 | return -1; 263 | } 264 | } 265 | 266 | static err_t tcp_comm_client_close(struct tcp_comm_ctx *ctx) 267 | { 268 | err_t err = ERR_OK; 269 | 270 | cyw43_arch_gpio_put (0, false); 271 | ctx->conn_state = CONN_STATE_CLOSED; 272 | 273 | if (!ctx->client_pcb) { 274 | return err; 275 | } 276 | 277 | tcp_arg(ctx->client_pcb, NULL); 278 | tcp_poll(ctx->client_pcb, NULL, 0); 279 | tcp_sent(ctx->client_pcb, NULL); 280 | tcp_recv(ctx->client_pcb, NULL); 281 | tcp_err(ctx->client_pcb, NULL); 282 | err = tcp_close(ctx->client_pcb); 283 | if (err != ERR_OK) { 284 | DEBUG_printf("close failed %d, calling abort\n", err); 285 | tcp_abort(ctx->client_pcb); 286 | err = ERR_ABRT; 287 | } 288 | 289 | ctx->client_pcb = NULL; 290 | 291 | return err; 292 | } 293 | 294 | err_t tcp_comm_server_close(struct tcp_comm_ctx *ctx) 295 | { 296 | err_t err = ERR_OK; 297 | 298 | err = tcp_comm_client_close(ctx); 299 | if ((err != ERR_OK) && ctx->serv_pcb) { 300 | tcp_arg(ctx->serv_pcb, NULL); 301 | tcp_abort(ctx->serv_pcb); 302 | ctx->serv_pcb = NULL; 303 | return ERR_ABRT; 304 | } 305 | 306 | if (!ctx->serv_pcb) { 307 | return err; 308 | } 309 | 310 | tcp_arg(ctx->serv_pcb, NULL); 311 | err = tcp_close(ctx->serv_pcb); 312 | if (err != ERR_OK) { 313 | tcp_abort(ctx->serv_pcb); 314 | err = ERR_ABRT; 315 | } 316 | ctx->serv_pcb = NULL; 317 | 318 | return err; 319 | } 320 | 321 | static void tcp_comm_server_complete(void *arg, int status) 322 | { 323 | struct tcp_comm_ctx *ctx = (struct tcp_comm_ctx *)arg; 324 | if (status == 0) { 325 | DEBUG_printf("server completed normally\n"); 326 | } else { 327 | DEBUG_printf("server error %d\n", status); 328 | } 329 | 330 | tcp_comm_server_close(ctx); 331 | ctx->serv_done = true; 332 | } 333 | 334 | static err_t tcp_comm_client_complete(void *arg, int status) 335 | { 336 | struct tcp_comm_ctx *ctx = (struct tcp_comm_ctx *)arg; 337 | if (status == 0) { 338 | DEBUG_printf("conn completed normally\n"); 339 | } else { 340 | DEBUG_printf("conn error %d\n", status); 341 | } 342 | return tcp_comm_client_close(ctx); 343 | } 344 | 345 | static err_t tcp_comm_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) 346 | { 347 | struct tcp_comm_ctx *ctx = (struct tcp_comm_ctx *)arg; 348 | DEBUG_printf("tcp_comm_server_sent %u\n", len); 349 | 350 | cyw43_arch_lwip_check(); 351 | if (len > ctx->tx_bytes_remaining) { 352 | DEBUG_printf("tx len %d > remaining %d\n", len, ctx->tx_bytes_remaining); 353 | return tcp_comm_client_complete(ctx, ERR_ARG); 354 | } 355 | 356 | ctx->tx_bytes_remaining -= len; 357 | ctx->tx_bytes_sent += len; 358 | 359 | if (ctx->tx_bytes_remaining == 0) { 360 | int res = tcp_comm_tx_complete(ctx); 361 | if (res) { 362 | return tcp_comm_client_complete(ctx, ERR_ARG); 363 | } 364 | } 365 | 366 | return ERR_OK; 367 | } 368 | 369 | static err_t tcp_comm_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) 370 | { 371 | struct tcp_comm_ctx *ctx = (struct tcp_comm_ctx *)arg; 372 | if (!p) { 373 | DEBUG_printf("no pbuf\n"); 374 | return tcp_comm_client_complete(ctx, 0); 375 | } 376 | 377 | // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you 378 | // can use this method to cause an assertion in debug mode, if this method is called when 379 | // cyw43_arch_lwip_begin IS needed 380 | cyw43_arch_lwip_check(); 381 | if (p->tot_len > 0) { 382 | DEBUG_printf("tcp_comm_server_recv %d err %d\n", p->tot_len, err); 383 | 384 | if (p->tot_len > (sizeof(ctx->buf) - ctx->rx_bytes_received)) { 385 | // Doesn't fit in buffer at all. Protocol error. 386 | DEBUG_printf("not enough space in buffer: %d vs %d\n", p->tot_len, sizeof(ctx->buf) - ctx->rx_bytes_received); 387 | 388 | // TODO: Invoking the error response state here feels 389 | // like a bit of a layering violation, but this is a 390 | // protocol error, rather than a failure in the stack 391 | // somewhere, so it's nice to try and report it rather 392 | // than just dropping the connection. 393 | if (tcp_comm_error_begin(ctx)) { 394 | return tcp_comm_client_complete(ctx, ERR_ARG); 395 | } 396 | return ERR_OK; 397 | } else if (p->tot_len > (sizeof(ctx->buf) - (ctx->rx_start_offs + ctx->rx_bytes_received))) { 398 | // There will be space, but we need to shift the data back 399 | // to the start of the buffer 400 | DEBUG_printf("memmove %d bytes to make space for %d bytes\n", ctx->rx_bytes_received, p->tot_len); 401 | memmove(ctx->buf, ctx->buf + ctx->rx_start_offs, ctx->rx_bytes_received); 402 | ctx->rx_start_offs = 0; 403 | } 404 | 405 | uint8_t *dst = ctx->buf + ctx->rx_start_offs + ctx->rx_bytes_received; 406 | 407 | // We can always handle the full packet 408 | if (pbuf_copy_partial(p, dst, p->tot_len, 0) != p->tot_len) { 409 | DEBUG_printf("wrong copy len\n"); 410 | return tcp_comm_client_complete(ctx, ERR_ARG); 411 | } 412 | 413 | ctx->rx_bytes_received += p->tot_len; 414 | tcp_recved(tpcb, p->tot_len); 415 | 416 | while (ctx->rx_bytes_received >= ctx->rx_bytes_needed) { 417 | uint16_t consumed = ctx->rx_bytes_needed; 418 | 419 | int res = tcp_comm_rx_complete(ctx); 420 | if (res) { 421 | return tcp_comm_client_complete(ctx, ERR_ARG); 422 | } 423 | 424 | ctx->rx_start_offs += consumed; 425 | ctx->rx_bytes_received -= consumed; 426 | 427 | if (ctx->rx_bytes_received == 0) { 428 | ctx->rx_start_offs = 0; 429 | break; 430 | } 431 | } 432 | } 433 | pbuf_free(p); 434 | 435 | return ERR_OK; 436 | } 437 | 438 | static err_t tcp_comm_client_poll(void *arg, struct tcp_pcb *tpcb) 439 | { 440 | DEBUG_printf("tcp_comm_server_poll_fn\n"); 441 | return ERR_OK; 442 | } 443 | 444 | static void tcp_comm_client_err(void *arg, err_t err) 445 | { 446 | struct tcp_comm_ctx *ctx = (struct tcp_comm_ctx *)arg; 447 | 448 | DEBUG_printf("tcp_comm_err %d\n", err); 449 | 450 | ctx->client_pcb = NULL; 451 | ctx->conn_state = CONN_STATE_CLOSED; 452 | ctx->rx_bytes_needed = 0; 453 | cyw43_arch_gpio_put (0, false); 454 | } 455 | 456 | static void tcp_comm_client_init(struct tcp_comm_ctx *ctx, struct tcp_pcb *pcb) 457 | { 458 | ctx->client_pcb = pcb; 459 | tcp_arg(pcb, ctx); 460 | 461 | cyw43_arch_gpio_put (0, true); 462 | 463 | tcp_comm_sync_begin(ctx); 464 | 465 | tcp_sent(pcb, tcp_comm_client_sent); 466 | tcp_recv(pcb, tcp_comm_client_recv); 467 | tcp_poll(pcb, tcp_comm_client_poll, POLL_TIME_S * 2); 468 | tcp_err(pcb, tcp_comm_client_err); 469 | } 470 | 471 | static err_t tcp_comm_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) 472 | { 473 | struct tcp_comm_ctx *ctx = (struct tcp_comm_ctx *)arg; 474 | 475 | if (err != ERR_OK || client_pcb == NULL) { 476 | DEBUG_printf("Failure in accept\n"); 477 | tcp_comm_server_complete(ctx, err); 478 | return ERR_VAL; 479 | } 480 | DEBUG_printf("Connection opened\n"); 481 | 482 | if (ctx->client_pcb) { 483 | DEBUG_printf("Already have a connection\n"); 484 | tcp_abort(client_pcb); 485 | return ERR_ABRT; 486 | } 487 | 488 | tcp_comm_client_init(ctx, client_pcb); 489 | 490 | return ERR_OK; 491 | } 492 | 493 | err_t tcp_comm_listen(struct tcp_comm_ctx *ctx, uint16_t port) 494 | { 495 | DEBUG_printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), port); 496 | 497 | ctx->serv_done = false; 498 | 499 | struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); 500 | if (!pcb) { 501 | DEBUG_printf("failed to create pcb\n"); 502 | return ERR_MEM; 503 | } 504 | 505 | err_t err = tcp_bind(pcb, NULL, port); 506 | if (err) { 507 | DEBUG_printf("failed to bind to port %d\n", port); 508 | tcp_abort(pcb); 509 | return err; 510 | } 511 | 512 | ctx->serv_pcb = tcp_listen_with_backlog_and_err(pcb, 1, &err); 513 | if (!ctx->serv_pcb) { 514 | DEBUG_printf("failed to listen: %d\n", err); 515 | return err; 516 | } 517 | 518 | tcp_arg(ctx->serv_pcb, ctx); 519 | tcp_accept(ctx->serv_pcb, tcp_comm_server_accept); 520 | 521 | return ERR_OK; 522 | } 523 | 524 | struct tcp_comm_ctx *tcp_comm_new(const struct comm_command *const *cmds, 525 | unsigned int n_cmds, uint32_t sync_opcode) 526 | { 527 | struct tcp_comm_ctx *ctx = calloc(1, sizeof(struct tcp_comm_ctx)); 528 | if (!ctx) { 529 | return NULL; 530 | } 531 | 532 | unsigned int i; 533 | for (i = 0; i < n_cmds; i++) { 534 | assert(cmds[i]->nargs <= COMM_MAX_NARG); 535 | assert(cmds[i]->resp_nargs <= COMM_MAX_NARG); 536 | } 537 | 538 | ctx->cmds = cmds; 539 | ctx->n_cmds = n_cmds; 540 | ctx->sync_opcode = sync_opcode; 541 | 542 | return ctx; 543 | } 544 | 545 | void tcp_comm_delete(struct tcp_comm_ctx *ctx) 546 | { 547 | tcp_comm_server_close(ctx); 548 | free(ctx); 549 | } 550 | 551 | bool tcp_comm_server_done(struct tcp_comm_ctx *ctx) 552 | { 553 | return ctx->serv_done; 554 | } 555 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 Brian Starkey 3 | * 4 | * Based on the Pico W tcp_server example: 5 | * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. 6 | * 7 | * SPDX-License-Identifier: BSD-3-Clause 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include "RP2040.h" 14 | #include "pico/critical_section.h" 15 | #include "pico/time.h" 16 | #include "pico/util/queue.h" 17 | #include "hardware/dma.h" 18 | #include "hardware/flash.h" 19 | #include "hardware/structs/dma.h" 20 | #include "hardware/structs/watchdog.h" 21 | #include "hardware/gpio.h" 22 | #include "hardware/resets.h" 23 | #include "hardware/uart.h" 24 | #include "hardware/watchdog.h" 25 | 26 | #include "pico/stdlib.h" 27 | #include "pico/cyw43_arch.h" 28 | 29 | #include "tcp_comm.h" 30 | 31 | #include "picowota/reboot.h" 32 | 33 | #ifdef DEBUG 34 | #include 35 | #include "pico/stdio_usb.h" 36 | #define DBG_PRINTF_INIT() stdio_usb_init() 37 | #define DBG_PRINTF(...) printf(__VA_ARGS__) 38 | #else 39 | #define DBG_PRINTF_INIT() { } 40 | #define DBG_PRINTF(...) { } 41 | #endif 42 | 43 | #if PICOWOTA_WIFI_AP == 1 44 | #include "dhcpserver.h" 45 | static dhcp_server_t dhcp_server; 46 | #endif 47 | 48 | #define QUOTE(name) #name 49 | #define STR(macro) QUOTE(macro) 50 | 51 | #ifndef PICOWOTA_WIFI_SSID 52 | #warning "PICOWOTA_WIFI_SSID not defined" 53 | #else 54 | const char *wifi_ssid = STR(PICOWOTA_WIFI_SSID); 55 | #endif 56 | 57 | #ifndef PICOWOTA_WIFI_PASS 58 | #warning "PICOWOTA_WIFI_PASS not defined" 59 | #else 60 | const char *wifi_pass = STR(PICOWOTA_WIFI_PASS); 61 | #endif 62 | 63 | critical_section_t critical_section; 64 | 65 | #define EVENT_QUEUE_LENGTH 8 66 | queue_t event_queue; 67 | 68 | enum event_type { 69 | EVENT_TYPE_REBOOT = 1, 70 | EVENT_TYPE_GO, 71 | EVENT_TYPE_SERVER_DONE, 72 | }; 73 | 74 | struct event { 75 | enum event_type type; 76 | union { 77 | struct { 78 | bool to_bootloader; 79 | } reboot; 80 | struct { 81 | uint32_t vtor; 82 | } go; 83 | }; 84 | }; 85 | 86 | #define BOOTLOADER_ENTRY_PIN 15 87 | 88 | #define TCP_PORT 4242 89 | 90 | struct image_header app_image_header; 91 | #define IMAGE_HEADER_ADDR ((uint32_t)&app_image_header) 92 | #define IMAGE_HEADER_OFFSET (IMAGE_HEADER_ADDR - XIP_BASE) 93 | 94 | #define WRITE_ADDR_MIN (IMAGE_HEADER_ADDR + FLASH_SECTOR_SIZE) 95 | #define ERASE_ADDR_MIN (IMAGE_HEADER_ADDR) 96 | #define FLASH_ADDR_MAX (XIP_BASE + PICO_FLASH_SIZE_BYTES) 97 | 98 | #define CMD_SYNC (('S' << 0) | ('Y' << 8) | ('N' << 16) | ('C' << 24)) 99 | #define RSP_SYNC (('W' << 0) | ('O' << 8) | ('T' << 16) | ('A' << 24)) 100 | #define CMD_INFO (('I' << 0) | ('N' << 8) | ('F' << 16) | ('O' << 24)) 101 | 102 | #define CMD_READ (('R' << 0) | ('E' << 8) | ('A' << 16) | ('D' << 24)) 103 | #define CMD_CSUM (('C' << 0) | ('S' << 8) | ('U' << 16) | ('M' << 24)) 104 | #define CMD_CRC (('C' << 0) | ('R' << 8) | ('C' << 16) | ('C' << 24)) 105 | #define CMD_ERASE (('E' << 0) | ('R' << 8) | ('A' << 16) | ('S' << 24)) 106 | #define CMD_WRITE (('W' << 0) | ('R' << 8) | ('I' << 16) | ('T' << 24)) 107 | #define CMD_SEAL (('S' << 0) | ('E' << 8) | ('A' << 16) | ('L' << 24)) 108 | #define CMD_GO (('G' << 0) | ('O' << 8) | ('G' << 16) | ('O' << 24)) 109 | #define CMD_REBOOT (('B' << 0) | ('O' << 8) | ('O' << 16) | ('T' << 24)) 110 | 111 | static uint32_t handle_sync(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 112 | { 113 | return RSP_SYNC; 114 | } 115 | 116 | const struct comm_command sync_cmd = { 117 | .opcode = CMD_SYNC, 118 | .nargs = 0, 119 | .resp_nargs = 0, 120 | .size = NULL, 121 | .handle = &handle_sync, 122 | }; 123 | 124 | static uint32_t size_read(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out) 125 | { 126 | uint32_t size = args_in[1]; 127 | if (size > TCP_COMM_MAX_DATA_LEN) { 128 | return TCP_COMM_RSP_ERR; 129 | } 130 | 131 | // TODO: Validate address 132 | 133 | *data_len_out = 0; 134 | *resp_data_len_out = size; 135 | 136 | return TCP_COMM_RSP_OK; 137 | } 138 | 139 | static uint32_t handle_read(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 140 | { 141 | uint32_t addr = args_in[0]; 142 | uint32_t size = args_in[1]; 143 | 144 | memcpy(resp_data_out, (void *)addr, size); 145 | 146 | return TCP_COMM_RSP_OK; 147 | } 148 | 149 | const struct comm_command read_cmd = { 150 | // READ addr len 151 | // OKOK [data] 152 | .opcode = CMD_READ, 153 | .nargs = 2, 154 | .resp_nargs = 0, 155 | .size = &size_read, 156 | .handle = &handle_read, 157 | }; 158 | 159 | static uint32_t size_csum(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out) 160 | { 161 | uint32_t addr = args_in[0]; 162 | uint32_t size = args_in[1]; 163 | 164 | if ((addr & 0x3) || (size & 0x3)) { 165 | // Must be aligned 166 | return TCP_COMM_RSP_ERR; 167 | } 168 | 169 | // TODO: Validate address 170 | 171 | *data_len_out = 0; 172 | *resp_data_len_out = 0; 173 | 174 | return TCP_COMM_RSP_OK; 175 | } 176 | 177 | static uint32_t handle_csum(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 178 | { 179 | uint32_t dummy_dest; 180 | uint32_t addr = args_in[0]; 181 | uint32_t size = args_in[1]; 182 | 183 | int channel = dma_claim_unused_channel(true); 184 | 185 | dma_channel_config c = dma_channel_get_default_config(channel); 186 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 187 | channel_config_set_read_increment(&c, true); 188 | channel_config_set_write_increment(&c, false); 189 | channel_config_set_sniff_enable(&c, true); 190 | 191 | dma_hw->sniff_data = 0; 192 | dma_sniffer_enable(channel, 0xf, true); 193 | 194 | dma_channel_configure(channel, &c, &dummy_dest, (void *)addr, size / 4, true); 195 | 196 | dma_channel_wait_for_finish_blocking(channel); 197 | 198 | dma_sniffer_disable(); 199 | dma_channel_unclaim(channel); 200 | 201 | *resp_args_out = dma_hw->sniff_data; 202 | 203 | return TCP_COMM_RSP_OK; 204 | } 205 | 206 | struct comm_command csum_cmd = { 207 | // CSUM addr len 208 | // OKOK csum 209 | .opcode = CMD_CSUM, 210 | .nargs = 2, 211 | .resp_nargs = 1, 212 | .size = &size_csum, 213 | .handle = &handle_csum, 214 | }; 215 | 216 | static uint32_t size_crc(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out) 217 | { 218 | uint32_t addr = args_in[0]; 219 | uint32_t size = args_in[1]; 220 | 221 | if ((addr & 0x3) || (size & 0x3)) { 222 | // Must be aligned 223 | return TCP_COMM_RSP_ERR; 224 | } 225 | 226 | // TODO: Validate address 227 | 228 | *data_len_out = 0; 229 | *resp_data_len_out = 0; 230 | 231 | return TCP_COMM_RSP_OK; 232 | } 233 | 234 | // ptr must be 4-byte aligned and len must be a multiple of 4 235 | static uint32_t calc_crc32(void *ptr, uint32_t len) 236 | { 237 | uint32_t dummy_dest, crc; 238 | 239 | int channel = dma_claim_unused_channel(true); 240 | dma_channel_config c = dma_channel_get_default_config(channel); 241 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 242 | channel_config_set_read_increment(&c, true); 243 | channel_config_set_write_increment(&c, false); 244 | channel_config_set_sniff_enable(&c, true); 245 | 246 | // Seed the CRC calculation 247 | dma_hw->sniff_data = 0xffffffff; 248 | 249 | // Mode 1, then bit-reverse the result gives the same result as 250 | // golang's IEEE802.3 implementation 251 | dma_sniffer_enable(channel, 0x1, true); 252 | dma_hw->sniff_ctrl |= DMA_SNIFF_CTRL_OUT_REV_BITS; 253 | 254 | dma_channel_configure(channel, &c, &dummy_dest, ptr, len / 4, true); 255 | 256 | dma_channel_wait_for_finish_blocking(channel); 257 | 258 | // Read the result before resetting 259 | crc = dma_hw->sniff_data ^ 0xffffffff; 260 | 261 | dma_sniffer_disable(); 262 | dma_channel_unclaim(channel); 263 | 264 | return crc; 265 | } 266 | 267 | static uint32_t handle_crc(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 268 | { 269 | uint32_t addr = args_in[0]; 270 | uint32_t size = args_in[1]; 271 | 272 | resp_args_out[0] = calc_crc32((void *)addr, size); 273 | 274 | return TCP_COMM_RSP_OK; 275 | } 276 | 277 | struct comm_command crc_cmd = { 278 | // CRCC addr len 279 | // OKOK crc 280 | .opcode = CMD_CRC, 281 | .nargs = 2, 282 | .resp_nargs = 1, 283 | .size = &size_crc, 284 | .handle = &handle_crc, 285 | }; 286 | 287 | static uint32_t handle_erase(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 288 | { 289 | uint32_t addr = args_in[0]; 290 | uint32_t size = args_in[1]; 291 | 292 | if ((addr < ERASE_ADDR_MIN) || (addr + size >= FLASH_ADDR_MAX)) { 293 | // Outside flash 294 | return TCP_COMM_RSP_ERR; 295 | } 296 | 297 | if ((addr & (FLASH_SECTOR_SIZE - 1)) || (size & (FLASH_SECTOR_SIZE - 1))) { 298 | // Must be aligned 299 | return TCP_COMM_RSP_ERR; 300 | } 301 | 302 | critical_section_enter_blocking(&critical_section); 303 | flash_range_erase(addr - XIP_BASE, size); 304 | critical_section_exit(&critical_section); 305 | 306 | return TCP_COMM_RSP_OK; 307 | } 308 | 309 | struct comm_command erase_cmd = { 310 | // ERAS addr len 311 | // OKOK 312 | .opcode = CMD_ERASE, 313 | .nargs = 2, 314 | .resp_nargs = 0, 315 | .size = NULL, 316 | .handle = &handle_erase, 317 | }; 318 | 319 | static uint32_t size_write(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out) 320 | { 321 | uint32_t addr = args_in[0]; 322 | uint32_t size = args_in[1]; 323 | 324 | if ((addr < WRITE_ADDR_MIN) || (addr + size >= FLASH_ADDR_MAX)) { 325 | // Outside flash 326 | return TCP_COMM_RSP_ERR; 327 | } 328 | 329 | if ((addr & (FLASH_PAGE_SIZE - 1)) || (size & (FLASH_PAGE_SIZE -1))) { 330 | // Must be aligned 331 | return TCP_COMM_RSP_ERR; 332 | } 333 | 334 | if (size > TCP_COMM_MAX_DATA_LEN) { 335 | return TCP_COMM_RSP_ERR; 336 | } 337 | 338 | // TODO: Validate address 339 | 340 | *data_len_out = size; 341 | *resp_data_len_out = 0; 342 | 343 | return TCP_COMM_RSP_OK; 344 | } 345 | 346 | static uint32_t handle_write(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 347 | { 348 | uint32_t addr = args_in[0]; 349 | uint32_t size = args_in[1]; 350 | 351 | critical_section_enter_blocking(&critical_section); 352 | flash_range_program(addr - XIP_BASE, data_in, size); 353 | critical_section_exit(&critical_section); 354 | 355 | resp_args_out[0] = calc_crc32((void *)addr, size); 356 | 357 | return TCP_COMM_RSP_OK; 358 | } 359 | 360 | struct comm_command write_cmd = { 361 | // WRIT addr len [data] 362 | // OKOK crc 363 | .opcode = CMD_WRITE, 364 | .nargs = 2, 365 | .resp_nargs = 1, 366 | .size = &size_write, 367 | .handle = &handle_write, 368 | }; 369 | 370 | struct image_header { 371 | uint32_t vtor; 372 | uint32_t size; 373 | uint32_t crc; 374 | uint8_t pad[FLASH_PAGE_SIZE - (3 * 4)]; 375 | }; 376 | static_assert(sizeof(struct image_header) == FLASH_PAGE_SIZE, "image_header must be FLASH_PAGE_SIZE bytes"); 377 | 378 | static bool image_header_ok(struct image_header *hdr) 379 | { 380 | uint32_t *vtor = (uint32_t *)hdr->vtor; 381 | 382 | uint32_t calc = calc_crc32((void *)hdr->vtor, hdr->size); 383 | 384 | // CRC has to match 385 | if (calc != hdr->crc) { 386 | return false; 387 | } 388 | 389 | // Stack pointer needs to be in RAM 390 | if (vtor[0] < SRAM_BASE) { 391 | return false; 392 | } 393 | 394 | // Reset vector should be in the image, and thumb (bit 0 set) 395 | if ((vtor[1] < hdr->vtor) || (vtor[1] > hdr->vtor + hdr->size) || !(vtor[1] & 1)) { 396 | return false; 397 | } 398 | 399 | // Looks OK. 400 | return true; 401 | } 402 | 403 | 404 | static uint32_t handle_seal(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 405 | { 406 | struct image_header hdr = { 407 | .vtor = args_in[0], 408 | .size = args_in[1], 409 | .crc = args_in[2], 410 | }; 411 | 412 | if ((hdr.vtor & 0xff) || (hdr.size & 0x3)) { 413 | // Must be aligned 414 | return TCP_COMM_RSP_ERR; 415 | } 416 | 417 | if (!image_header_ok(&hdr)) { 418 | return TCP_COMM_RSP_ERR; 419 | } 420 | 421 | critical_section_enter_blocking(&critical_section); 422 | flash_range_erase(IMAGE_HEADER_OFFSET, FLASH_SECTOR_SIZE); 423 | flash_range_program(IMAGE_HEADER_OFFSET, (const uint8_t *)&hdr, sizeof(hdr)); 424 | critical_section_exit(&critical_section); 425 | 426 | struct image_header *check = &app_image_header; 427 | if (memcmp(&hdr, check, sizeof(hdr))) { 428 | return TCP_COMM_RSP_ERR; 429 | } 430 | 431 | return TCP_COMM_RSP_OK; 432 | } 433 | 434 | struct comm_command seal_cmd = { 435 | // SEAL vtor len crc 436 | // OKOK 437 | .opcode = CMD_SEAL, 438 | .nargs = 3, 439 | .resp_nargs = 0, 440 | .size = NULL, 441 | .handle = &handle_seal, 442 | }; 443 | 444 | static void disable_interrupts(void) 445 | { 446 | SysTick->CTRL &= ~1; 447 | 448 | NVIC->ICER[0] = 0xFFFFFFFF; 449 | NVIC->ICPR[0] = 0xFFFFFFFF; 450 | } 451 | 452 | static void reset_peripherals(void) 453 | { 454 | reset_block(~( 455 | RESETS_RESET_IO_QSPI_BITS | 456 | RESETS_RESET_PADS_QSPI_BITS | 457 | RESETS_RESET_SYSCFG_BITS | 458 | RESETS_RESET_PLL_SYS_BITS 459 | )); 460 | } 461 | 462 | static void jump_to_vtor(uint32_t vtor) 463 | { 464 | // Derived from the Leaf Labs Cortex-M3 bootloader. 465 | // Copyright (c) 2010 LeafLabs LLC. 466 | // Modified 2021 Brian Starkey 467 | // Originally under The MIT License 468 | uint32_t reset_vector = *(volatile uint32_t *)(vtor + 0x04); 469 | 470 | SCB->VTOR = (volatile uint32_t)(vtor); 471 | 472 | asm volatile("msr msp, %0"::"g" 473 | (*(volatile uint32_t *)vtor)); 474 | asm volatile("bx %0"::"r" (reset_vector)); 475 | } 476 | 477 | 478 | static uint32_t handle_go(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 479 | { 480 | struct event ev = { 481 | .type = EVENT_TYPE_GO, 482 | .go = { 483 | .vtor = args_in[0], 484 | }, 485 | }; 486 | 487 | if (!queue_try_add(&event_queue, &ev)) { 488 | return TCP_COMM_RSP_ERR; 489 | } 490 | 491 | return TCP_COMM_RSP_OK; 492 | } 493 | 494 | struct comm_command go_cmd = { 495 | // GOGO vtor 496 | // NO RESPONSE 497 | .opcode = CMD_GO, 498 | .nargs = 1, 499 | .resp_nargs = 0, 500 | .size = NULL, 501 | .handle = &handle_go, 502 | }; 503 | 504 | static uint32_t handle_info(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 505 | { 506 | resp_args_out[0] = WRITE_ADDR_MIN; 507 | resp_args_out[1] = (XIP_BASE + PICO_FLASH_SIZE_BYTES) - WRITE_ADDR_MIN; 508 | resp_args_out[2] = FLASH_SECTOR_SIZE; 509 | resp_args_out[3] = FLASH_PAGE_SIZE; 510 | resp_args_out[4] = TCP_COMM_MAX_DATA_LEN; 511 | 512 | return TCP_COMM_RSP_OK; 513 | } 514 | 515 | const struct comm_command info_cmd = { 516 | // INFO 517 | // OKOK flash_start flash_size erase_size write_size max_data_len 518 | .opcode = CMD_INFO, 519 | .nargs = 0, 520 | .resp_nargs = 5, 521 | .size = NULL, 522 | .handle = &handle_info, 523 | }; 524 | 525 | static uint32_t size_reboot(uint32_t *args_in, uint32_t *data_len_out, uint32_t *resp_data_len_out) 526 | { 527 | *data_len_out = 0; 528 | *resp_data_len_out = 0; 529 | 530 | return TCP_COMM_RSP_OK; 531 | } 532 | 533 | static uint32_t handle_reboot(uint32_t *args_in, uint8_t *data_in, uint32_t *resp_args_out, uint8_t *resp_data_out) 534 | { 535 | struct event ev = { 536 | .type = EVENT_TYPE_REBOOT, 537 | .reboot = { 538 | .to_bootloader = !!args_in[0], 539 | }, 540 | }; 541 | 542 | if (!queue_try_add(&event_queue, &ev)) { 543 | return TCP_COMM_RSP_ERR; 544 | } 545 | 546 | return TCP_COMM_RSP_OK; 547 | } 548 | 549 | struct comm_command reboot_cmd = { 550 | // BOOT to_bootloader 551 | // NO RESPONSE 552 | .opcode = CMD_REBOOT, 553 | .nargs = 1, 554 | .resp_nargs = 0, 555 | .size = &size_reboot, 556 | .handle = &handle_reboot, 557 | }; 558 | 559 | static bool should_stay_in_bootloader() 560 | { 561 | bool wd_says_so = (watchdog_hw->scratch[5] == PICOWOTA_BOOTLOADER_ENTRY_MAGIC) && 562 | (watchdog_hw->scratch[6] == ~PICOWOTA_BOOTLOADER_ENTRY_MAGIC); 563 | 564 | return !gpio_get(BOOTLOADER_ENTRY_PIN) || wd_says_so; 565 | } 566 | 567 | static void network_deinit() 568 | { 569 | #if PICOWOTA_WIFI_AP == 1 570 | dhcp_server_deinit(&dhcp_server); 571 | #endif 572 | cyw43_arch_deinit(); 573 | } 574 | 575 | int main() 576 | { 577 | err_t err; 578 | 579 | gpio_init(BOOTLOADER_ENTRY_PIN); 580 | gpio_pull_up(BOOTLOADER_ENTRY_PIN); 581 | gpio_set_dir(BOOTLOADER_ENTRY_PIN, 0); 582 | 583 | sleep_ms(10); 584 | 585 | if (!should_stay_in_bootloader() && image_header_ok(&app_image_header)) { 586 | uint32_t vtor = *(uint32_t *)IMAGE_HEADER_ADDR; 587 | disable_interrupts(); 588 | reset_peripherals(); 589 | jump_to_vtor(vtor); 590 | } 591 | 592 | DBG_PRINTF_INIT(); 593 | 594 | queue_init(&event_queue, sizeof(struct event), EVENT_QUEUE_LENGTH); 595 | 596 | if (cyw43_arch_init()) { 597 | DBG_PRINTF("failed to initialise\n"); 598 | return 1; 599 | } 600 | 601 | #if PICOWOTA_WIFI_AP == 1 602 | cyw43_arch_enable_ap_mode(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK); 603 | DBG_PRINTF("Enabled the WiFi AP.\n"); 604 | 605 | ip4_addr_t gw, mask; 606 | IP4_ADDR(&gw, 192, 168, 4, 1); 607 | IP4_ADDR(&mask, 255, 255, 255, 0); 608 | 609 | dhcp_server_t dhcp_server; 610 | dhcp_server_init(&dhcp_server, &gw, &mask); 611 | DBG_PRINTF("Started the DHCP server.\n"); 612 | #else 613 | cyw43_arch_enable_sta_mode(); 614 | 615 | DBG_PRINTF("Connecting to WiFi...\n"); 616 | if (cyw43_arch_wifi_connect_timeout_ms(wifi_ssid, wifi_pass, CYW43_AUTH_WPA2_AES_PSK, 30000)) { 617 | DBG_PRINTF("failed to connect.\n"); 618 | return 1; 619 | } else { 620 | DBG_PRINTF("Connected.\n"); 621 | } 622 | #endif 623 | 624 | critical_section_init(&critical_section); 625 | 626 | const struct comm_command *cmds[] = { 627 | &sync_cmd, 628 | &read_cmd, 629 | &csum_cmd, 630 | &crc_cmd, 631 | &erase_cmd, 632 | &write_cmd, 633 | &seal_cmd, 634 | &go_cmd, 635 | &info_cmd, 636 | &reboot_cmd, 637 | }; 638 | 639 | struct tcp_comm_ctx *tcp = tcp_comm_new(cmds, sizeof(cmds) / sizeof(cmds[0]), CMD_SYNC); 640 | 641 | struct event ev = { 642 | .type = EVENT_TYPE_SERVER_DONE, 643 | }; 644 | 645 | queue_add_blocking(&event_queue, &ev); 646 | 647 | for ( ; ; ) { 648 | while (queue_try_remove(&event_queue, &ev)) { 649 | switch (ev.type) { 650 | case EVENT_TYPE_SERVER_DONE: 651 | err = tcp_comm_listen(tcp, TCP_PORT); 652 | if (err != ERR_OK) { 653 | DBG_PRINTF("Failed to start server: %d\n", err); 654 | } 655 | break; 656 | case EVENT_TYPE_REBOOT: 657 | tcp_comm_server_close(tcp); 658 | network_deinit(); 659 | picowota_reboot(ev.reboot.to_bootloader); 660 | /* Should never get here */ 661 | break; 662 | case EVENT_TYPE_GO: 663 | tcp_comm_server_close(tcp); 664 | network_deinit(); 665 | disable_interrupts(); 666 | reset_peripherals(); 667 | jump_to_vtor(ev.go.vtor); 668 | /* Should never get here */ 669 | break; 670 | }; 671 | } 672 | 673 | cyw43_arch_poll(); 674 | sleep_ms(5); 675 | } 676 | 677 | network_deinit(); 678 | return 0; 679 | } 680 | --------------------------------------------------------------------------------