From e29bf6f93d0bded2d3bc3ba88092cdecc89383d9 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sun, 8 Oct 2023 10:25:34 +0200 Subject: [PATCH] Add DHCP server to Pico-W in AP mode Use a DHCP server that is bundled with Pico SDK (btstack) The DHCP server unfortunatley has no callback so we cannot implement the network driver callback yet. There still is no DNS server and no router. Also fix a bug where the default SSID name was atomvm-000000000000 because the MAC address wasn't set. Workaround consists in always enabling STA mode. Signed-off-by: Paul Guyot --- CHANGELOG.md | 3 +- src/platforms/rp2040/src/lib/CMakeLists.txt | 12 ++- src/platforms/rp2040/src/lib/networkdriver.c | 82 +++++++++++++++++++- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9771651..97fae1f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for creations of binaries with unaligned strings - Added `-h` and `-v` flags to generic_unix AtomVM command - Removed support to ESP32 NVS from network module in order to make it generic. See also [UPDATING.md]. -- Added initial support for Pico-W: on-board LED, connection to wifi network. +- Added initial support for Pico-W: on-board LED, Wifi (STA and AP modes). ### Changed + - Changed offset of atomvmlib and of program on Pico. See also [UPDATING.md]. ### Fixed diff --git a/src/platforms/rp2040/src/lib/CMakeLists.txt b/src/platforms/rp2040/src/lib/CMakeLists.txt index 9928f4e5f..6a7187d25 100644 --- a/src/platforms/rp2040/src/lib/CMakeLists.txt +++ b/src/platforms/rp2040/src/lib/CMakeLists.txt @@ -66,7 +66,17 @@ if (NOT AVM_USE_32BIT_FLOAT) endif() if (PICO_CYW43_SUPPORTED) - target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC pico_cyw43_arch_lwip_threadsafe_background) + set(BTSTACK_ROOT ${PICO_SDK_PATH}/lib/btstack) + set(BTSTACK_3RD_PARTY_PATH ${BTSTACK_ROOT}/3rd-party) + + add_library(pan_lwip_dhserver INTERFACE) + target_sources(pan_lwip_dhserver INTERFACE + ${BTSTACK_3RD_PARTY_PATH}/lwip/dhcp-server/dhserver.c + ) + target_include_directories(pan_lwip_dhserver INTERFACE + ${BTSTACK_3RD_PARTY_PATH}/lwip/dhcp-server + ) + target_link_libraries(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC pico_cyw43_arch_lwip_threadsafe_background INTERFACE pan_lwip_dhserver) target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,networkregister_port_driver") endif() diff --git a/src/platforms/rp2040/src/lib/networkdriver.c b/src/platforms/rp2040/src/lib/networkdriver.c index 0d05bf0ca..421a1c755 100644 --- a/src/platforms/rp2040/src/lib/networkdriver.c +++ b/src/platforms/rp2040/src/lib/networkdriver.c @@ -31,6 +31,7 @@ #pragma GCC diagnostic ignored "-Wpedantic" #include +#include #include #pragma GCC diagnostic pop @@ -68,6 +69,7 @@ struct NetworkDriverData int link_status; int stas_count; uint8_t *stas_mac; + struct dhcp_config *dhcp_config; }; // Callbacks do not allow for user data @@ -212,7 +214,9 @@ static term start_sta(term sta_config, GlobalContext *global) static char *get_default_device_name() { uint8_t mac[6]; - int err = cyw43_wifi_get_mac(&cyw43_state, CYW43_ITF_STA, mac); + // Device name is used for AP mode. It seems the interface parameter is + // ignored and both interfaces have the same MAC address. + int err = cyw43_wifi_get_mac(&cyw43_state, CYW43_ITF_AP, mac); if (err) { return NULL; } @@ -268,6 +272,64 @@ static void network_driver_cyw43_assoc_cb(bool assoc) driver_data->stas_count = nb_stas; } +static term setup_dhcp_server() +{ + int max_stas; + // Supposedly, max_stas doesn't change. + cyw43_wifi_ap_get_max_stas(&cyw43_state, &max_stas); + // max_stas is 10, but let's work for up to 253. + // we do networking on a /24 and we reserve 0, 255 (broadcast) and our own address. + if (max_stas > 253) { + max_stas = 253; + } + + size_t dhcp_config_size = sizeof(struct dhcp_config) + max_stas * sizeof(struct dhcp_entry); + driver_data->dhcp_config = malloc(dhcp_config_size); + bzero(driver_data->dhcp_config, dhcp_config_size); + driver_data->dhcp_config->num_entry = max_stas; + driver_data->dhcp_config->entries = (dhcp_entry_t *) ((uint8_t *) driver_data->dhcp_config + sizeof(struct dhcp_config)); + uint32_t ip_addr4 = ntohl(ip4_addr_get_u32(netif_ip4_addr(&cyw43_state.netif[CYW43_ITF_AP]))); + driver_data->dhcp_config->addr[0] = ip_addr4 >> 24; + driver_data->dhcp_config->addr[1] = (ip_addr4 >> 16) & 0xFF; + driver_data->dhcp_config->addr[2] = (ip_addr4 >> 8) & 0xFF; + driver_data->dhcp_config->addr[3] = ip_addr4 & 0xFF; + + int self_last_ip_byte = ip_addr4 & 0xFF; + int dhcp_client_addr = 0; + + for (int i = 0; i < max_stas; i++) { + driver_data->dhcp_config->entries[i].addr[0] = ip_addr4 >> 24; + driver_data->dhcp_config->entries[i].addr[1] = (ip_addr4 >> 16) & 0xFF; + driver_data->dhcp_config->entries[i].addr[2] = (ip_addr4 >> 8) & 0xFF; + dhcp_client_addr++; + if (dhcp_client_addr == self_last_ip_byte) { + dhcp_client_addr++; + } + driver_data->dhcp_config->entries[i].addr[3] = dhcp_client_addr; + driver_data->dhcp_config->entries[i].subnet[0] = 255; + driver_data->dhcp_config->entries[i].subnet[1] = 255; + driver_data->dhcp_config->entries[i].subnet[2] = 255; + driver_data->dhcp_config->entries[i].subnet[3] = 0; + driver_data->dhcp_config->entries[i].lease = 86400; + } + + // We don't have a DNS server yet but we can't route anything either. + driver_data->dhcp_config->dns[0] = ip_addr4 >> 24; + driver_data->dhcp_config->dns[1] = (ip_addr4 >> 16) & 0xFF; + driver_data->dhcp_config->dns[2] = (ip_addr4 >> 8) & 0xFF; + driver_data->dhcp_config->dns[3] = ip_addr4 & 0xFF; + driver_data->dhcp_config->port = 67; + + err_t err = dhserv_init(driver_data->dhcp_config); + if (err) { + free(driver_data->dhcp_config); + driver_data->dhcp_config = NULL; + return BADARG_ATOM; + } + + return OK_ATOM; +} + static term start_ap(term ap_config, GlobalContext *global) { term ssid_term = interop_kv_get_value(ap_config, ssid_atom, global); @@ -307,7 +369,9 @@ static term start_ap(term ap_config, GlobalContext *global) free(ssid); free(psk); - return OK_ATOM; + // We need to start dhcp server after tcp/ip is setup on AP. + // There can be a race condition here, but clients will retry resending DHCP Requests + return setup_dhcp_server(); } static void network_driver_netif_status_cb(struct netif *netif) @@ -341,12 +405,14 @@ static void start_network(Context *ctx, term pid, term ref, term config) if (driver_data == NULL) { driver_data = malloc(sizeof(struct NetworkDriverData)); driver_data->stas_mac = NULL; + driver_data->dhcp_config = NULL; } driver_data->global = ctx->global; driver_data->owner_process_id = term_to_local_process_id(pid); driver_data->ref_ticks = term_to_ref_ticks(ref); driver_data->link_status = CYW43_LINK_DOWN; free(driver_data->stas_mac); + free(driver_data->dhcp_config); driver_data->stas_count = 0; driver_data->stas_mac = NULL; @@ -369,7 +435,9 @@ static void start_network(Context *ctx, term pid, term ref, term config) return; } } else { - cyw43_arch_disable_sta_mode(); + // Always enable sta mode so the bus is initialized and we get a MAC + // address. + cyw43_arch_enable_sta_mode(); } if (ap_config) { @@ -379,6 +447,10 @@ static void start_network(Context *ctx, term pid, term ref, term config) port_send_reply(ctx, pid, ref, error); return; } + if (!sta_config) { + // We can disable sta mode now. + cyw43_arch_disable_sta_mode(); + } } else { cyw43_arch_disable_ap_mode(); } @@ -453,6 +525,10 @@ void network_driver_destroy(GlobalContext *global) if (driver_data) { free(driver_data->stas_mac); + if (driver_data->dhcp_config) { + dhserv_free(); + } + free(driver_data->dhcp_config); } free(driver_data); driver_data = NULL;