drivers: wifi: Introduce SiWx91x WiFi driver

This driver allow to use Zephyr native IP stack or the IP stack provided
by HAL / WiseConnect.

The WiseConnect implementation may take advantage of the specific
features provided by the 917 (power consumption, speed,
validation...).

Some notable features are not available with this interface:
  - It seems Zephyr does not provide API to offload multicast membership
    management. User should be to directly call WiseConnect APIs
  - Support for ICMP frames is difficult. Note that WiseConnect
    automatically answer to ping request. It is just not possible to
    send ping requests and receive ping responses.
  - Zephyr and WiseConnect both support TLS offloading. However this
    patch does not implement it.
  - Reentrancy in the WiseConnect side is uncertain.

This implementation has been tested with samples/net/wifi/ (which relies
on subsys/net/lib/shell).

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
This commit is contained in:
Jérôme Pouiller 2025-02-03 14:48:09 +01:00 committed by Benjamin Cabé
commit 1544354862
11 changed files with 988 additions and 2 deletions

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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 <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#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/<various> */
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

View file

@ -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 <zephyr/net/net_context.h>
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/net/wifi.h>
#include <zephyr/kernel.h>
#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

View file

@ -0,0 +1,414 @@
/*
* Copyright (c) 2023 Antmicro
* Copyright (c) 2024 Silicon Laboratories Inc.
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net/net_offload.h>
#include <zephyr/logging/log.h>
#include <assert.h>
#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);
}

View file

@ -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 <zephyr/net/net_if.h>
#include <zephyr/net/offloaded_netdev.h>
#include <assert.h>
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

View file

@ -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"

View file

@ -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 <sys/socket.h>
${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

View file

@ -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;