Skip to content

Commit

Permalink
Merge pull request #864 from pguyot/w40/picow-ap-dhcpserver
Browse files Browse the repository at this point in the history
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.

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Oct 8, 2023
2 parents 37d3ea9 + e29bf6f commit 5c66e69
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 5 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion src/platforms/rp2040/src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
82 changes: 79 additions & 3 deletions src/platforms/rp2040/src/lib/networkdriver.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#pragma GCC diagnostic ignored "-Wpedantic"

#include <cyw43.h>
#include <dhserver.h>
#include <pico/cyw43_arch.h>

#pragma GCC diagnostic pop
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;

Expand All @@ -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) {
Expand All @@ -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();
}
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 5c66e69

Please sign in to comment.