diff --git a/drivers/wifi/CMakeLists.txt b/drivers/wifi/CMakeLists.txt index 8192278f6dd..96c24d245b6 100644 --- a/drivers/wifi/CMakeLists.txt +++ b/drivers/wifi/CMakeLists.txt @@ -26,3 +26,4 @@ add_subdirectory_ifdef(CONFIG_WIFI_WINC1500 winc1500) add_subdirectory_ifdef(CONFIG_WIFI_NXP nxp) add_subdirectory_ifdef(CONFIG_WIFI_AIROC infineon) add_subdirectory_ifdef(CONFIG_WIFI_NRF70 nrf_wifi) +add_subdirectory_ifdef(CONFIG_WIFI_SILABS_SIWX91X siwx91x) diff --git a/drivers/wifi/Kconfig b/drivers/wifi/Kconfig index 55b2a943ff2..b6f7cfcb84c 100644 --- a/drivers/wifi/Kconfig +++ b/drivers/wifi/Kconfig @@ -43,5 +43,6 @@ source "drivers/wifi/esp32/Kconfig.esp32" source "drivers/wifi/nxp/Kconfig.nxp" source "drivers/wifi/infineon/Kconfig.airoc" source "drivers/wifi/nrf_wifi/Kconfig.nrfwifi" +source "drivers/wifi/siwx91x/Kconfig.siwx91x" endif # WIFI diff --git a/drivers/wifi/siwx91x/CMakeLists.txt b/drivers/wifi/siwx91x/CMakeLists.txt new file mode 100644 index 00000000000..4085341bca0 --- /dev/null +++ b/drivers/wifi/siwx91x/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 Silicon Laboratories Inc. + +if(CONFIG_WIFI_SILABS_SIWX91X) + zephyr_library_sources(siwx91x_wifi.c) + + if(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD) + zephyr_library_sources(siwx91x_wifi_socket.c) + endif() +endif() diff --git a/drivers/wifi/siwx91x/Kconfig.siwx91x b/drivers/wifi/siwx91x/Kconfig.siwx91x new file mode 100644 index 00000000000..ad059a072a7 --- /dev/null +++ b/drivers/wifi/siwx91x/Kconfig.siwx91x @@ -0,0 +1,48 @@ +# Copyright (c) 2024 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +config WIFI_SILABS_SIWX91X + bool "Silabs SiWx91x SoC series WiFi driver" + default y + depends on DT_HAS_SILABS_SIWX91X_WIFI_ENABLED + depends on NETWORKING + select WISECONNECT_NETWORK_STACK + select EVENTS + select NET_L2_WIFI_MGMT + help + Enable WiFi driver for the Silabs SiWx91x SoC series. + +if WIFI_SILABS_SIWX91X + +choice + prompt "Network stack" + default WIFI_SILABS_SIWX91X_NET_STACK_NATIVE + help + Choose "Native" stack if you want a better compatibility with Zephyr + features. "Offloaded" stack may provide better resource (power and + memory) consumption. + +config WIFI_SILABS_SIWX91X_NET_STACK_NATIVE + bool "Native" + select WIFI_USE_NATIVE_NETWORKING + select NET_L2_ETHERNET + +config WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD + bool "Offloaded" + select WIFI_OFFLOAD + +endchoice + +config NET_TCP_WORKQ_STACK_SIZE + default 2048 + +config NET_RX_STACK_SIZE + default 2048 + +config NET_MGMT_EVENT_STACK_SIZE + default 2048 + +config NET_MGMT_EVENT_QUEUE_SIZE + default 10 + +endif diff --git a/drivers/wifi/siwx91x/siwx91x_wifi.c b/drivers/wifi/siwx91x/siwx91x_wifi.c new file mode 100644 index 00000000000..6a446e39c5c --- /dev/null +++ b/drivers/wifi/siwx91x/siwx91x_wifi.c @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2023 Antmicro + * Copyright (c) 2024 Silicon Laboratories Inc. + * SPDX-License-Identifier: Apache-2.0 + */ +#define DT_DRV_COMPAT silabs_siwx91x_wifi + +#include +#include + +#include "siwx91x_wifi.h" +#include "siwx91x_wifi_socket.h" + +#include "sl_rsi_utility.h" +#include "sl_net_constants.h" +#include "sl_wifi_types.h" +#include "sl_wifi_callback_framework.h" +#include "sl_wifi.h" +#include "sl_net.h" + +LOG_MODULE_REGISTER(siwx91x_wifi); + +NET_BUF_POOL_FIXED_DEFINE(siwx91x_tx_pool, 1, _NET_ETH_MAX_FRAME_SIZE, 0, NULL); + +static unsigned int siwx91x_on_join(sl_wifi_event_t event, + char *result, uint32_t result_size, void *arg) +{ + struct siwx91x_dev *sidev = arg; + + if (*result != 'C') { + /* TODO: report the real reason of failure */ + wifi_mgmt_raise_connect_result_event(sidev->iface, WIFI_STATUS_CONN_FAIL); + sidev->state = WIFI_STATE_INACTIVE; + return 0; + } + + wifi_mgmt_raise_connect_result_event(sidev->iface, WIFI_STATUS_CONN_SUCCESS); + sidev->state = WIFI_STATE_COMPLETED; + + if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE)) { + net_if_dormant_off(sidev->iface); + } + + siwx91x_on_join_ipv4(sidev); + siwx91x_on_join_ipv6(sidev); + + return 0; +} + +static int siwx91x_connect(const struct device *dev, struct wifi_connect_req_params *params) +{ + sl_wifi_client_configuration_t wifi_config = { + .bss_type = SL_WIFI_BSS_TYPE_INFRASTRUCTURE, + }; + int ret; + + switch (params->security) { + case WIFI_SECURITY_TYPE_NONE: + wifi_config.security = SL_WIFI_OPEN; + wifi_config.encryption = SL_WIFI_NO_ENCRYPTION; + break; + case WIFI_SECURITY_TYPE_WPA_PSK: + wifi_config.security = SL_WIFI_WPA; + wifi_config.encryption = SL_WIFI_DEFAULT_ENCRYPTION; + wifi_config.credential_id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; + break; + case WIFI_SECURITY_TYPE_PSK: + wifi_config.security = SL_WIFI_WPA2; + wifi_config.encryption = SL_WIFI_TKIP_ENCRYPTION; + wifi_config.credential_id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; + break; + case WIFI_SECURITY_TYPE_PSK_SHA256: + wifi_config.security = SL_WIFI_WPA2; + wifi_config.encryption = SL_WIFI_CCMP_ENCRYPTION; + wifi_config.credential_id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; + break; + case WIFI_SECURITY_TYPE_SAE: + /* TODO: Support the case where MFP is not required */ + wifi_config.security = SL_WIFI_WPA3; + wifi_config.credential_id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; + break; + case WIFI_SECURITY_TYPE_WPA_AUTO_PERSONAL: + wifi_config.security = SL_WIFI_WPA2; + wifi_config.encryption = SL_WIFI_DEFAULT_ENCRYPTION; + wifi_config.credential_id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; + break; + /* Zephyr WiFi shell doesn't specify how to pass credential for these + * key managements. + */ + case WIFI_SECURITY_TYPE_WEP: /* SL_WIFI_WEP/SL_WIFI_WEP_ENCRYPTION */ + case WIFI_SECURITY_TYPE_EAP: /* SL_WIFI_WPA2_ENTERPRISE/ */ + case WIFI_SECURITY_TYPE_WAPI: + default: + return -ENOTSUP; + } + + if (params->band != WIFI_FREQ_BAND_UNKNOWN && params->band != WIFI_FREQ_BAND_2_4_GHZ) { + return -ENOTSUP; + } + + if (params->psk_length) { + sl_net_set_credential(SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID, SL_NET_WIFI_PSK, + params->psk, params->psk_length); + } + + if (params->sae_password_length) { + sl_net_set_credential(SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID, SL_NET_WIFI_PSK, + params->sae_password, params->sae_password_length); + } + + if (params->channel != WIFI_CHANNEL_ANY) { + wifi_config.channel.channel = params->channel; + } + + wifi_config.ssid.length = params->ssid_length, + memcpy(wifi_config.ssid.value, params->ssid, params->ssid_length); + + ret = sl_wifi_connect(SL_WIFI_CLIENT_INTERFACE, &wifi_config, 0); + if (ret != SL_STATUS_IN_PROGRESS) { + return -EIO; + } + + return 0; +} + +static int siwx91x_disconnect(const struct device *dev) +{ + struct siwx91x_dev *sidev = dev->data; + int ret; + + ret = sl_wifi_disconnect(SL_WIFI_CLIENT_INTERFACE); + if (ret) { + return -EIO; + } + if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE)) { + net_if_dormant_on(sidev->iface); + } + sidev->state = WIFI_STATE_INACTIVE; + return 0; +} + +static void siwx91x_report_scan_res(struct siwx91x_dev *sidev, sl_wifi_scan_result_t *result, + int item) +{ + static const struct { + int sl_val; + int z_val; + } security_convert[] = { + { SL_WIFI_OPEN, WIFI_SECURITY_TYPE_NONE }, + { SL_WIFI_WEP, WIFI_SECURITY_TYPE_WEP }, + { SL_WIFI_WPA, WIFI_SECURITY_TYPE_WPA_PSK }, + { SL_WIFI_WPA2, WIFI_SECURITY_TYPE_PSK }, + { SL_WIFI_WPA3, WIFI_SECURITY_TYPE_SAE }, + { SL_WIFI_WPA3_TRANSITION, WIFI_SECURITY_TYPE_SAE }, + { SL_WIFI_WPA_ENTERPRISE, WIFI_SECURITY_TYPE_EAP }, + { SL_WIFI_WPA2_ENTERPRISE, WIFI_SECURITY_TYPE_EAP }, + }; + struct wifi_scan_result tmp = { + .channel = result->scan_info[item].rf_channel, + .rssi = result->scan_info[item].rssi_val, + .ssid_length = strlen(result->scan_info[item].ssid), + .mac_length = sizeof(result->scan_info[item].bssid), + .security = WIFI_SECURITY_TYPE_UNKNOWN, + .mfp = WIFI_MFP_UNKNOWN, + /* FIXME: fill .mfp, .band and .channel */ + }; + + memcpy(tmp.ssid, result->scan_info[item].ssid, tmp.ssid_length); + memcpy(tmp.mac, result->scan_info[item].bssid, tmp.mac_length); + ARRAY_FOR_EACH(security_convert, i) { + if (security_convert[i].sl_val == result->scan_info[item].security_mode) { + tmp.security = security_convert[i].z_val; + } + } + sidev->scan_res_cb(sidev->iface, 0, &tmp); +} + +static unsigned int siwx91x_on_scan(sl_wifi_event_t event, sl_wifi_scan_result_t *result, + uint32_t result_size, void *arg) +{ + struct siwx91x_dev *sidev = arg; + int i; + + if (!sidev->scan_res_cb) { + return -EFAULT; + } + for (i = 0; i < result->scan_count; i++) { + siwx91x_report_scan_res(sidev, result, i); + } + sidev->scan_res_cb(sidev->iface, 0, NULL); + sidev->state = WIFI_STATE_INACTIVE; + return 0; +} + +static int siwx91x_scan(const struct device *dev, struct wifi_scan_params *z_scan_config, + scan_result_cb_t cb) +{ + sl_wifi_scan_configuration_t sl_scan_config = { }; + struct siwx91x_dev *sidev = dev->data; + int ret; + + __ASSERT(z_scan_config, "z_scan_config cannot be NULL"); + + if (sidev->state != WIFI_STATE_INACTIVE) { + return -EBUSY; + } + + /* The enum values are same, no conversion needed */ + sl_scan_config.type = z_scan_config->scan_type; + + sl_scan_config.channel_bitmap_2g4 = 0xFFFF; + memset(sl_scan_config.channel_bitmap_5g, 0xFF, sizeof(sl_scan_config.channel_bitmap_5g)); + + sidev->scan_res_cb = cb; + ret = sl_wifi_start_scan(SL_WIFI_CLIENT_INTERFACE, NULL, &sl_scan_config); + if (ret != SL_STATUS_IN_PROGRESS) { + return -EIO; + } + sidev->state = WIFI_STATE_SCANNING; + + return 0; +} + +static int siwx91x_status(const struct device *dev, struct wifi_iface_status *status) +{ + struct siwx91x_dev *sidev = dev->data; + int32_t rssi = -1; + + memset(status, 0, sizeof(*status)); + status->state = sidev->state; + sl_wifi_get_signal_strength(SL_WIFI_CLIENT_INTERFACE, &rssi); + status->rssi = rssi; + return 0; +} + +#ifdef CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE + +static int siwx91x_send(const struct device *dev, struct net_pkt *pkt) +{ + size_t pkt_len = net_pkt_get_len(pkt); + struct net_buf *buf = NULL; + int ret; + + if (net_pkt_get_len(pkt) > _NET_ETH_MAX_FRAME_SIZE) { + LOG_ERR("unexpected buffer size"); + return -ENOBUFS; + } + buf = net_buf_alloc(&siwx91x_tx_pool, K_FOREVER); + if (!buf) { + return -ENOBUFS; + } + if (net_pkt_read(pkt, buf->data, pkt_len)) { + net_buf_unref(buf); + return -ENOBUFS; + } + net_buf_add(buf, pkt_len); + + ret = sl_wifi_send_raw_data_frame(SL_WIFI_CLIENT_INTERFACE, buf->data, pkt_len); + if (ret) { + return -EIO; + } + + net_pkt_unref(pkt); + net_buf_unref(buf); + + return 0; +} + +/* Receive callback. Keep the name as it is declared weak in WiseConnect */ +sl_status_t sl_si91x_host_process_data_frame(sl_wifi_interface_t interface, + sl_wifi_buffer_t *buffer) +{ + sl_si91x_packet_t *si_pkt = sl_si91x_host_get_buffer_data(buffer, 0, NULL); + struct net_if *iface = net_if_get_first_wifi(); + struct net_pkt *pkt; + int ret; + + pkt = net_pkt_rx_alloc_with_buffer(iface, buffer->length, AF_UNSPEC, 0, K_NO_WAIT); + if (!pkt) { + LOG_ERR("net_pkt_rx_alloc_with_buffer() failed"); + return SL_STATUS_FAIL; + } + ret = net_pkt_write(pkt, si_pkt->data, si_pkt->length); + if (ret < 0) { + LOG_ERR("net_pkt_write(): %d", ret); + goto unref; + } + ret = net_recv_data(iface, pkt); + if (ret < 0) { + LOG_ERR("net_recv_data((): %d", ret); + goto unref; + } + return 0; + +unref: + net_pkt_unref(pkt); + return SL_STATUS_FAIL; +} + +#endif + +static void siwx91x_ethernet_init(struct net_if *iface) +{ + struct ethernet_context *eth_ctx; + + if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE)) { + eth_ctx = net_if_l2_data(iface); + eth_ctx->eth_if_type = L2_ETH_IF_TYPE_WIFI; + ethernet_init(iface); + } +} + +static void siwx91x_iface_init(struct net_if *iface) +{ + struct siwx91x_dev *sidev = iface->if_dev->dev->data; + sl_status_t status; + + sidev->state = WIFI_STATE_INTERFACE_DISABLED; + sidev->iface = iface; + + sl_wifi_set_scan_callback(siwx91x_on_scan, sidev); + sl_wifi_set_join_callback(siwx91x_on_join, sidev); + + status = sl_wifi_get_mac_address(SL_WIFI_CLIENT_INTERFACE, &sidev->macaddr); + if (status) { + LOG_ERR("sl_wifi_get_mac_address(): %#04x", status); + return; + } + net_if_set_link_addr(iface, sidev->macaddr.octet, sizeof(sidev->macaddr.octet), + NET_LINK_ETHERNET); + siwx91x_sock_init(iface); + siwx91x_ethernet_init(iface); + + sidev->state = WIFI_STATE_INACTIVE; +} + +static int siwx91x_dev_init(const struct device *dev) +{ + return 0; +} + +static const struct wifi_mgmt_ops siwx91x_mgmt = { + .scan = siwx91x_scan, + .connect = siwx91x_connect, + .disconnect = siwx91x_disconnect, + .iface_status = siwx91x_status, +}; + +static const struct net_wifi_mgmt_offload siwx91x_api = { + .wifi_iface.iface_api.init = siwx91x_iface_init, +#ifdef CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE + .wifi_iface.send = siwx91x_send, +#else + .wifi_iface.get_type = siwx91x_get_type, +#endif + .wifi_mgmt_api = &siwx91x_mgmt, +}; + +static struct siwx91x_dev sidev; + +#ifdef CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE +ETH_NET_DEVICE_DT_INST_DEFINE(0, siwx91x_dev_init, NULL, &sidev, NULL, + CONFIG_WIFI_INIT_PRIORITY, &siwx91x_api, NET_ETH_MTU); +#else +NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, siwx91x_dev_init, NULL, &sidev, NULL, + CONFIG_WIFI_INIT_PRIORITY, &siwx91x_api, NET_ETH_MTU); +#endif diff --git a/drivers/wifi/siwx91x/siwx91x_wifi.h b/drivers/wifi/siwx91x/siwx91x_wifi.h new file mode 100644 index 00000000000..e08f4e7f7eb --- /dev/null +++ b/drivers/wifi/siwx91x/siwx91x_wifi.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Silicon Laboratories Inc. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SIWX91X_WIFI_H +#define SIWX91X_WIFI_H + +#include +#include +#include +#include + +#include "sl_ieee802_types.h" +#include "sl_si91x_types.h" +#include "sl_si91x_protocol_types.h" + +struct siwx91x_dev { + struct net_if *iface; + sl_mac_address_t macaddr; + enum wifi_iface_state state; + scan_result_cb_t scan_res_cb; + +#ifdef CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD + struct k_event fds_recv_event; + sl_si91x_fd_set fds_watch; + struct { + net_context_recv_cb_t cb; + void *user_data; + struct net_context *context; + } fds_cb[NUMBER_OF_SOCKETS]; +#endif +}; + +#endif diff --git a/drivers/wifi/siwx91x/siwx91x_wifi_socket.c b/drivers/wifi/siwx91x/siwx91x_wifi_socket.c new file mode 100644 index 00000000000..47440176bec --- /dev/null +++ b/drivers/wifi/siwx91x/siwx91x_wifi_socket.c @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2023 Antmicro + * Copyright (c) 2024 Silicon Laboratories Inc. + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include "siwx91x_wifi.h" +#include "siwx91x_wifi_socket.h" + +#include "sl_status.h" +#include "sl_net_ip_types.h" +#include "sl_net_si91x.h" +#include "sl_si91x_types.h" +#include "sl_si91x_socket.h" +#include "sl_si91x_socket_utility.h" + +LOG_MODULE_DECLARE(siwx91x_wifi); + +BUILD_ASSERT(NUMBER_OF_SOCKETS < sizeof(uint32_t) * 8); +BUILD_ASSERT(NUMBER_OF_SOCKETS < SIZEOF_FIELD(sl_si91x_fd_set, __fds_bits) * 8); + +NET_BUF_POOL_FIXED_DEFINE(siwx91x_tx_pool, 1, NET_ETH_MTU, 0, NULL); +NET_BUF_POOL_FIXED_DEFINE(siwx91x_rx_pool, 10, NET_ETH_MTU, 0, NULL); + +enum offloaded_net_if_types siwx91x_get_type(void) +{ + return L2_OFFLOADED_NET_IF_TYPE_WIFI; +} + +/* SiWx91x does not use the standard struct sockaddr (despite it uses the same + * name): + * - uses Little Endian for port number while Posix uses big endian + * - IPv6 addresses are bytes swapped + * Note: this function allows to have in == out. + */ +static void siwx91x_sockaddr_swap_bytes(struct sockaddr *out, + const struct sockaddr *in, socklen_t in_len) +{ + const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)in; + struct sockaddr_in6 *out6 = (struct sockaddr_in6 *)out; + + /* In Zephyr, size of sockaddr == size of sockaddr_storage + * (while in Posix sockaddr is smaller than sockaddr_storage). + */ + memcpy(out, in, in_len); + if (in->sa_family == AF_INET6) { + ARRAY_FOR_EACH(in6->sin6_addr.s6_addr32, i) { + out6->sin6_addr.s6_addr32[i] = ntohl(in6->sin6_addr.s6_addr32[i]); + } + out6->sin6_port = ntohs(in6->sin6_port); + } else if (in->sa_family == AF_INET) { + out6->sin6_port = ntohs(in6->sin6_port); + } +} + +void siwx91x_on_join_ipv4(struct siwx91x_dev *sidev) +{ + sl_net_ip_configuration_t ip_config4 = { + .mode = SL_IP_MANAGEMENT_DHCP, + .type = SL_IPV4, + }; + struct in_addr addr4 = { }; + int ret; + + if (!IS_ENABLED(CONFIG_NET_IPV4)) { + return; + } + /* FIXME: support for static IP configuration */ + ret = sl_si91x_configure_ip_address(&ip_config4, SL_SI91X_WIFI_CLIENT_VAP_ID); + if (!ret) { + memcpy(addr4.s4_addr, ip_config4.ip.v4.ip_address.bytes, sizeof(addr4.s4_addr)); + /* FIXME: also report gateway (net_if_ipv4_router_add()) */ + net_if_ipv4_addr_add(sidev->iface, &addr4, NET_ADDR_DHCP, 0); + } else { + LOG_ERR("sl_si91x_configure_ip_address(): %#04x", ret); + } +} + +void siwx91x_on_join_ipv6(struct siwx91x_dev *sidev) +{ + sl_net_ip_configuration_t ip_config6 = { + .mode = SL_IP_MANAGEMENT_DHCP, + .type = SL_IPV6, + }; + struct in6_addr addr6 = { }; + int ret; + + if (!IS_ENABLED(CONFIG_NET_IPV6)) { + return; + } + /* FIXME: support for static IP configuration */ + ret = sl_si91x_configure_ip_address(&ip_config6, SL_SI91X_WIFI_CLIENT_VAP_ID); + if (!ret) { + ARRAY_FOR_EACH(addr6.s6_addr32, i) { + addr6.s6_addr32[i] = ntohl(ip_config6.ip.v6.global_address.value[i]); + } + /* SiWx91x already take care of DAD and sending ND is not + * supported anyway. + */ + net_if_flag_set(sidev->iface, NET_IF_IPV6_NO_ND); + /* FIXME: also report gateway and link local address */ + net_if_ipv6_addr_add(sidev->iface, &addr6, NET_ADDR_AUTOCONF, 0); + } else { + LOG_ERR("sl_si91x_configure_ip_address(): %#04x", ret); + } +} + +static int siwx91x_sock_recv_sync(struct net_context *context, + net_context_recv_cb_t cb, void *user_data) +{ + struct net_if *iface = net_context_get_iface(context); + int sockfd = (int)context->offload_context; + struct net_pkt *pkt; + struct net_buf *buf; + int ret; + + pkt = net_pkt_rx_alloc_on_iface(iface, K_MSEC(100)); + if (!pkt) { + return -ENOBUFS; + } + buf = net_buf_alloc(&siwx91x_rx_pool, K_MSEC(100)); + if (!buf) { + net_pkt_unref(pkt); + return -ENOBUFS; + } + net_pkt_append_buffer(pkt, buf); + + ret = sl_si91x_recvfrom(sockfd, buf->data, NET_ETH_MTU, 0, NULL, NULL); + if (ret < 0) { + net_pkt_unref(pkt); + ret = -errno; + } else { + net_buf_add(buf, ret); + net_pkt_cursor_init(pkt); + ret = 0; + } + if (cb) { + cb(context, pkt, NULL, NULL, ret, user_data); + } + return ret; +} + +static void siwx91x_sock_on_recv(sl_si91x_fd_set *read_fd, sl_si91x_fd_set *write_fd, + sl_si91x_fd_set *except_fd, int status) +{ + /* When CONFIG_NET_SOCKETS_OFFLOAD is set, only one interface exist */ + struct siwx91x_dev *sidev = net_if_get_first_wifi()->if_dev->dev->data; + + ARRAY_FOR_EACH(sidev->fds_cb, i) { + if (SL_SI91X_FD_ISSET(i, read_fd)) { + if (sidev->fds_cb[i].cb) { + siwx91x_sock_recv_sync(sidev->fds_cb[i].context, + sidev->fds_cb[i].cb, + sidev->fds_cb[i].user_data); + } else { + SL_SI91X_FD_CLR(i, &sidev->fds_watch); + k_event_post(&sidev->fds_recv_event, 1U << i); + } + } + } + + sl_si91x_select(NUMBER_OF_SOCKETS, &sidev->fds_watch, NULL, NULL, NULL, + siwx91x_sock_on_recv); +} + +static int siwx91x_sock_get(sa_family_t family, enum net_sock_type type, + enum net_ip_protocol ip_proto, struct net_context **context) +{ + struct siwx91x_dev *sidev = net_if_get_first_wifi()->if_dev->dev->data; + int sockfd; + + sockfd = sl_si91x_socket(family, type, ip_proto); + if (sockfd < 0) { + return -errno; + } + assert(!sidev->fds_cb[sockfd].cb); + (*context)->offload_context = (void *)sockfd; + return sockfd; +} + +static int siwx91x_sock_put(struct net_context *context) +{ + struct siwx91x_dev *sidev = net_context_get_iface(context)->if_dev->dev->data; + int sockfd = (int)context->offload_context; + int ret; + + SL_SI91X_FD_CLR(sockfd, &sidev->fds_watch); + memset(&sidev->fds_cb[sockfd], 0, sizeof(sidev->fds_cb[sockfd])); + sl_si91x_select(NUMBER_OF_SOCKETS, &sidev->fds_watch, NULL, NULL, NULL, + siwx91x_sock_on_recv); + ret = sl_si91x_shutdown(sockfd, 0); + if (ret < 0) { + ret = -errno; + } + return ret; +} + +static int siwx91x_sock_bind(struct net_context *context, + const struct sockaddr *addr, socklen_t addrlen) +{ + struct siwx91x_dev *sidev = net_context_get_iface(context)->if_dev->dev->data; + int sockfd = (int)context->offload_context; + struct sockaddr addr_le; + int ret; + + /* Zephyr tends to call bind() even if the TCP socket is a client. 917 + * return an error in this case. + */ + if (net_context_get_proto(context) == IPPROTO_TCP && + !((struct sockaddr_in *)addr)->sin_port) { + return 0; + } + siwx91x_sockaddr_swap_bytes(&addr_le, addr, addrlen); + ret = sl_si91x_bind(sockfd, &addr_le, addrlen); + if (ret) { + return -errno; + } + /* WiseConnect refuses to run select on TCP listening sockets */ + if (net_context_get_proto(context) == IPPROTO_UDP) { + SL_SI91X_FD_SET(sockfd, &sidev->fds_watch); + sl_si91x_select(NUMBER_OF_SOCKETS, &sidev->fds_watch, NULL, NULL, NULL, + siwx91x_sock_on_recv); + } + return 0; +} + +static int siwx91x_sock_connect(struct net_context *context, + const struct sockaddr *addr, socklen_t addrlen, + net_context_connect_cb_t cb, int32_t timeout, void *user_data) +{ + struct siwx91x_dev *sidev = net_context_get_iface(context)->if_dev->dev->data; + int sockfd = (int)context->offload_context; + struct sockaddr addr_le; + int ret; + + /* sl_si91x_connect() always return immediately, so we ignore timeout */ + siwx91x_sockaddr_swap_bytes(&addr_le, addr, addrlen); + ret = sl_si91x_connect(sockfd, &addr_le, addrlen); + if (ret) { + ret = -errno; + } + SL_SI91X_FD_SET(sockfd, &sidev->fds_watch); + sl_si91x_select(NUMBER_OF_SOCKETS, &sidev->fds_watch, NULL, NULL, NULL, + siwx91x_sock_on_recv); + net_context_set_state(context, NET_CONTEXT_CONNECTED); + if (cb) { + cb(context, ret, user_data); + } + return ret; +} + +static int siwx91x_sock_listen(struct net_context *context, int backlog) +{ + int sockfd = (int)context->offload_context; + int ret; + + ret = sl_si91x_listen(sockfd, backlog); + if (ret) { + return -errno; + } + net_context_set_state(context, NET_CONTEXT_LISTENING); + return 0; +} + +static int siwx91x_sock_accept(struct net_context *context, + net_tcp_accept_cb_t cb, int32_t timeout, void *user_data) +{ + struct siwx91x_dev *sidev = net_context_get_iface(context)->if_dev->dev->data; + int sockfd = (int)context->offload_context; + struct net_context *newcontext; + struct sockaddr addr_le; + int ret; + + /* TODO: support timeout != K_FOREVER */ + assert(timeout < 0); + + ret = net_context_get(net_context_get_family(context), + net_context_get_type(context), + net_context_get_proto(context), &newcontext); + if (ret < 0) { + return ret; + } + /* net_context_get() calls siwx91x_sock_get() but sl_si91x_accept() also + * allocates a socket. + */ + ret = siwx91x_sock_put(newcontext); + if (ret < 0) { + return ret; + } + /* The iface is reset when getting a new context. */ + newcontext->iface = context->iface; + ret = sl_si91x_accept(sockfd, &addr_le, sizeof(addr_le)); + if (ret < 0) { + return -errno; + } + newcontext->flags |= NET_CONTEXT_REMOTE_ADDR_SET; + newcontext->offload_context = (void *)ret; + siwx91x_sockaddr_swap_bytes(&newcontext->remote, &addr_le, sizeof(addr_le)); + + SL_SI91X_FD_SET(ret, &sidev->fds_watch); + sl_si91x_select(NUMBER_OF_SOCKETS, &sidev->fds_watch, NULL, NULL, NULL, + siwx91x_sock_on_recv); + if (cb) { + cb(newcontext, &addr_le, sizeof(addr_le), 0, user_data); + } + + return 0; +} + +static int siwx91x_sock_sendto(struct net_pkt *pkt, + const struct sockaddr *addr, socklen_t addrlen, + net_context_send_cb_t cb, int32_t timeout, void *user_data) +{ + struct net_context *context = pkt->context; + int sockfd = (int)context->offload_context; + struct sockaddr addr_le; + struct net_buf *buf; + int ret; + + /* struct net_pkt use fragmented buffers while SiWx91x API need a + * continuous buffer. + */ + if (net_pkt_get_len(pkt) > NET_ETH_MTU) { + LOG_ERR("unexpected buffer size"); + ret = -ENOBUFS; + goto out_cb; + } + buf = net_buf_alloc(&siwx91x_tx_pool, K_FOREVER); + if (!buf) { + ret = -ENOBUFS; + goto out_cb; + } + if (net_pkt_read(pkt, buf->data, net_pkt_get_len(pkt))) { + ret = -ENOBUFS; + goto out_release_buf; + } + net_buf_add(buf, net_pkt_get_len(pkt)); + + /* sl_si91x_sendto() always return immediately, so we ignore timeout */ + siwx91x_sockaddr_swap_bytes(&addr_le, addr, addrlen); + ret = sl_si91x_sendto(sockfd, buf->data, net_pkt_get_len(pkt), 0, &addr_le, addrlen); + if (ret < 0) { + ret = -errno; + goto out_release_buf; + } + net_pkt_unref(pkt); + +out_release_buf: + net_buf_unref(buf); + +out_cb: + if (cb) { + cb(pkt->context, ret, user_data); + } + return ret; +} + +static int siwx91x_sock_send(struct net_pkt *pkt, + net_context_send_cb_t cb, int32_t timeout, void *user_data) +{ + return siwx91x_sock_sendto(pkt, NULL, 0, cb, timeout, user_data); +} + +static int siwx91x_sock_recv(struct net_context *context, + net_context_recv_cb_t cb, int32_t timeout, void *user_data) +{ + struct net_if *iface = net_context_get_iface(context); + struct siwx91x_dev *sidev = iface->if_dev->dev->data; + int sockfd = (int)context->offload_context; + int ret; + + ret = k_event_wait(&sidev->fds_recv_event, 1U << sockfd, false, + timeout < 0 ? K_FOREVER : K_MSEC(timeout)); + if (timeout == 0) { + sidev->fds_cb[sockfd].context = context; + sidev->fds_cb[sockfd].cb = cb; + sidev->fds_cb[sockfd].user_data = user_data; + } else { + memset(&sidev->fds_cb[sockfd], 0, sizeof(sidev->fds_cb[sockfd])); + } + + if (ret) { + k_event_clear(&sidev->fds_recv_event, 1U << sockfd); + ret = siwx91x_sock_recv_sync(context, cb, user_data); + SL_SI91X_FD_SET(sockfd, &sidev->fds_watch); + } + + sl_si91x_select(NUMBER_OF_SOCKETS, &sidev->fds_watch, NULL, NULL, NULL, + siwx91x_sock_on_recv); + return ret; +} + +static struct net_offload siwx91x_offload = { + .get = siwx91x_sock_get, + .put = siwx91x_sock_put, + .bind = siwx91x_sock_bind, + .listen = siwx91x_sock_listen, + .connect = siwx91x_sock_connect, + .accept = siwx91x_sock_accept, + .sendto = siwx91x_sock_sendto, + .send = siwx91x_sock_send, + .recv = siwx91x_sock_recv, +}; + +void siwx91x_sock_init(struct net_if *iface) +{ + struct siwx91x_dev *sidev = iface->if_dev->dev->data; + + iface->if_dev->offload = &siwx91x_offload; + k_event_init(&sidev->fds_recv_event); +} diff --git a/drivers/wifi/siwx91x/siwx91x_wifi_socket.h b/drivers/wifi/siwx91x/siwx91x_wifi_socket.h new file mode 100644 index 00000000000..6ae06aaee4e --- /dev/null +++ b/drivers/wifi/siwx91x/siwx91x_wifi_socket.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Antmicro + * Copyright (c) 2024 Silicon Laboratories Inc. + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SIWX91X_WIFI_SOCKET_H +#define SIWX91X_WIFI_SOCKET_H + +#include +#include +#include + +struct siwx91x_dev; + +#ifdef CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD + +enum offloaded_net_if_types siwx91x_get_type(void); +void siwx91x_on_join_ipv4(struct siwx91x_dev *sidev); +void siwx91x_on_join_ipv6(struct siwx91x_dev *sidev); +void siwx91x_sock_init(struct net_if *iface); + +#else /* CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD */ + +enum offloaded_net_if_types siwx91x_get_type(void) +{ + assert(0); +} + +void siwx91x_on_join_ipv4(struct siwx91x_dev *sidev) +{ +} + +void siwx91x_on_join_ipv6(struct siwx91x_dev *sidev) +{ +} + +void siwx91x_sock_init(struct net_if *iface) +{ +} + +#endif /* CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD */ + +#endif diff --git a/dts/bindings/wifi/silabs,siwx91x-wifi.yaml b/dts/bindings/wifi/silabs,siwx91x-wifi.yaml new file mode 100644 index 00000000000..fe93e4910d0 --- /dev/null +++ b/dts/bindings/wifi/silabs,siwx91x-wifi.yaml @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Antmicro +# Copyright (c) 2024 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: Silabs SiWx91x SoC WiFi + +compatible: "silabs,siwx91x-wifi" diff --git a/modules/hal_silabs/wiseconnect/CMakeLists.txt b/modules/hal_silabs/wiseconnect/CMakeLists.txt index 05a86f60826..a7f83f6d7c7 100644 --- a/modules/hal_silabs/wiseconnect/CMakeLists.txt +++ b/modules/hal_silabs/wiseconnect/CMakeLists.txt @@ -62,6 +62,36 @@ zephyr_library_sources_ifdef(CONFIG_DMA_SILABS_SIWX91X ${WISECONNECT_DIR}/components/device/silabs/si91x/mcu/drivers/rom_driver/src/rsi_rom_table_si91x.c ) +if(CONFIG_WIFI_SILABS_SIWX91X) + zephyr_library_sources( + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/sl_net/src/sl_si91x_net_credentials.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/sl_net/src/sl_si91x_net_internal_stack.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/sl_net/src/sl_net_si91x_integration_handler.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/sl_net/src/sl_net_rsi_utility.c + ${WISECONNECT_DIR}/components/protocol/wifi/src/sl_wifi_basic_credentials.c + ${WISECONNECT_DIR}/components/service/network_manager/si91x/sl_net_si91x.c + ${WISECONNECT_DIR}/components/service/network_manager/src/sl_net_basic_profiles.c + ${WISECONNECT_DIR}/components/service/network_manager/src/sl_net_credentials.c + ) + zephyr_compile_definitions_ifdef(CONFIG_NET_IPV6 + SLI_SI91X_ENABLE_IPV6 + ) + zephyr_compile_definitions_ifdef(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD + SLI_SI91X_OFFLOAD_NETWORK_STACK + SLI_SI91X_SOCKETS + ) + zephyr_include_directories_ifdef(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD + # Needed for + ${ZEPHYR_BASE}/include/zephyr/posix + ) + zephyr_library_sources_ifdef(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/socket/src/sl_si91x_socket_utility.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/asynchronous_socket/src/sl_si91x_socket.c + ${WISECONNECT_DIR}/components/device/silabs/si91x/wireless/sl_net/src/sl_net_si91x_callback_framework.c + ${WISECONNECT_DIR}/components/service/network_manager/src/sl_net.c + ) +endif() # CONFIG_WIFI_SILABS_SIWX91X + if(CONFIG_BT_SILABS_SIWX91X) zephyr_compile_definitions( SLI_SI91X_ENABLE_BLE diff --git a/soc/silabs/silabs_siwx91x/siwg917/nwp_init.c b/soc/silabs/silabs_siwx91x/siwg917/nwp_init.c index 998cfaf77b2..fb508dae710 100644 --- a/soc/silabs/silabs_siwx91x/siwg917/nwp_init.c +++ b/soc/silabs/silabs_siwx91x/siwg917/nwp_init.c @@ -30,14 +30,45 @@ static int siwg917_nwp_init(void) .ext_tcp_ip_feature_bit_map = SL_SI91X_CONFIG_FEAT_EXTENSION_VALID, .config_feature_bit_map = SL_SI91X_ENABLE_ENHANCED_MAX_PSP, .custom_feature_bit_map = SL_SI91X_CUSTOM_FEAT_EXTENSION_VALID, - /* Even if neither WiFi or BLE is used we have to specify a Coex mode */ - .coex_mode = SL_SI91X_BLE_MODE, .ext_custom_feature_bit_map = MEMORY_CONFIG | SL_SI91X_EXT_FEAT_XTAL_CLK | SL_SI91X_EXT_FEAT_FRONT_END_SWITCH_PINS_ULP_GPIO_4_5_0, } }; + sl_si91x_boot_configuration_t *cfg = &network_config.boot_config; + + if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X) && IS_ENABLED(CONFIG_BT_SILABS_SIWX91X)) { + cfg->coex_mode = SL_SI91X_WLAN_BLE_MODE; + } else if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X)) { + cfg->coex_mode = SL_SI91X_WLAN_ONLY_MODE; + } else if (IS_ENABLED(CONFIG_BT_SILABS_SIWX91X)) { + cfg->coex_mode = SL_SI91X_BLE_MODE; + } else { + /* + * Even if neither WiFi or BLE is used we have to specify a Coex mode + */ + cfg->coex_mode = SL_SI91X_BLE_MODE; + } + +#ifdef CONFIG_WIFI_SILABS_SIWX91X + cfg->feature_bit_map |= SL_SI91X_FEAT_SECURITY_OPEN | SL_SI91X_FEAT_WPS_DISABLE, + cfg->ext_custom_feature_bit_map |= SL_SI91X_EXT_FEAT_IEEE_80211W; + if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_OFFLOAD)) { + cfg->ext_tcp_ip_feature_bit_map |= SL_SI91X_EXT_TCP_IP_WINDOW_SCALING; + cfg->ext_tcp_ip_feature_bit_map |= SL_SI91X_EXT_TCP_IP_TOTAL_SELECTS(10); + cfg->tcp_ip_feature_bit_map |= SL_SI91X_TCP_IP_FEAT_ICMP; + if (IS_ENABLED(CONFIG_NET_IPV6)) { + cfg->tcp_ip_feature_bit_map |= SL_SI91X_TCP_IP_FEAT_DHCPV6_CLIENT; + cfg->tcp_ip_feature_bit_map |= SL_SI91X_TCP_IP_FEAT_IPV6; + } + if (IS_ENABLED(CONFIG_NET_IPV4)) { + cfg->tcp_ip_feature_bit_map |= SL_SI91X_TCP_IP_FEAT_DHCPV4_CLIENT; + } + } else { + cfg->tcp_ip_feature_bit_map |= SL_SI91X_TCP_IP_FEAT_BYPASS; + } +#endif #ifdef CONFIG_BT_SILABS_SIWX91X cfg->ext_custom_feature_bit_map |= SL_SI91X_EXT_FEAT_BT_CUSTOM_FEAT_ENABLE;