blob: a43bf6c2ae83bcd8a11731947e240b036bb8d70f [file] [log] [blame]
/*
* Copyright (c) 2023 Antmicro
* Copyright (c) 2024-2025 Silicon Laboratories Inc.
* SPDX-License-Identifier: Apache-2.0
*/
#include <nwp.h>
#include "siwx91x_wifi.h"
#include "sl_rsi_utility.h"
#include "sl_net.h"
LOG_MODULE_DECLARE(siwx91x_wifi);
static int siwx91x_nwp_reboot_if_required(const struct device *dev, uint8_t oper_mode)
{
struct siwx91x_dev *sidev = dev->data;
int ret;
if (sidev->reboot_needed) {
ret = siwx91x_nwp_mode_switch(oper_mode, sidev->hidden_ssid, sidev->max_num_sta);
if (ret < 0) {
LOG_ERR("Failed to reboot the device: %d", ret);
return ret;
}
sidev->reboot_needed = false;
}
return 0;
}
static int siwx91x_set_hidden_ssid(const struct device *dev, bool enable)
{
struct siwx91x_dev *sidev = dev->data;
if (sidev->hidden_ssid != enable) {
sidev->hidden_ssid = enable;
sidev->reboot_needed = true;
}
return 0;
}
static int siwx91x_set_max_sta(const struct device *dev, uint8_t max_sta)
{
struct siwx91x_dev *sidev = dev->data;
if (sidev->max_num_sta != max_sta) {
sidev->max_num_sta = max_sta;
sidev->reboot_needed = true;
}
return 0;
}
static int siwx91x_map_ap_security(enum wifi_security_type security)
{
switch (security) {
case WIFI_SECURITY_TYPE_NONE:
return SL_WIFI_OPEN;
case WIFI_SECURITY_TYPE_WPA_PSK:
return SL_WIFI_WPA;
case WIFI_SECURITY_TYPE_PSK:
return SL_WIFI_WPA2;
case WIFI_SECURITY_TYPE_WPA_AUTO_PERSONAL:
return SL_WIFI_WPA_WPA2_MIXED;
default:
return -EINVAL;
}
}
int siwx91x_ap_disable(const struct device *dev)
{
sl_wifi_interface_t interface = sl_wifi_get_default_interface();
struct siwx91x_dev *sidev = dev->data;
int ret;
ret = sl_wifi_stop_ap(interface);
if (ret) {
LOG_ERR("Failed to disable Wi-Fi AP mode: 0x%x", ret);
wifi_mgmt_raise_ap_disable_result_event(sidev->iface, WIFI_STATUS_AP_FAIL);
return -EIO;
}
if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE)) {
net_if_dormant_on(sidev->iface);
}
wifi_mgmt_raise_ap_disable_result_event(sidev->iface, WIFI_STATUS_AP_SUCCESS);
sidev->state = WIFI_STATE_INTERFACE_DISABLED;
return 0;
}
static int siwx91x_ap_disable_if_required(const struct device *dev,
struct wifi_connect_req_params *new_params)
{
struct wifi_iface_status prev_params = { };
uint32_t prev_psk_length = WIFI_PSK_MAX_LEN;
struct siwx91x_dev *sidev = dev->data;
uint8_t prev_psk[WIFI_PSK_MAX_LEN];
sl_net_credential_type_t psk_type;
int ret;
ret = siwx91x_status(dev, &prev_params);
if (ret < 0) {
return ret;
}
if (sidev->reboot_needed || siwx91x_param_changed(&prev_params, new_params)) {
return siwx91x_ap_disable(dev);
}
if (new_params->security != WIFI_SECURITY_TYPE_NONE) {
ret = sl_net_get_credential(SL_NET_DEFAULT_WIFI_AP_CREDENTIAL_ID, &psk_type,
prev_psk, &prev_psk_length);
if (ret < 0) {
LOG_ERR("Failed to get credentials: 0x%x", ret);
return -EIO;
}
if (new_params->psk_length != prev_psk_length ||
memcmp(new_params->psk, prev_psk, prev_psk_length) != 0) {
return siwx91x_ap_disable(dev);
}
}
LOG_ERR("Device already in active state");
return -EALREADY;
}
int siwx91x_ap_enable(const struct device *dev, struct wifi_connect_req_params *params)
{
sl_wifi_interface_t interface = sl_wifi_get_default_interface();
struct siwx91x_dev *sidev = dev->data;
sl_wifi_ap_configuration_t siwx91x_ap_cfg = {
.credential_id = SL_NET_DEFAULT_WIFI_AP_CREDENTIAL_ID,
.keepalive_type = SL_SI91X_AP_NULL_BASED_KEEP_ALIVE,
.rate_protocol = SL_WIFI_RATE_PROTOCOL_AUTO,
.encryption = SL_WIFI_DEFAULT_ENCRYPTION,
.channel.bandwidth = SL_WIFI_BANDWIDTH_20MHz,
.maximum_clients = sidev->max_num_sta,
.tdi_flags = SL_WIFI_TDI_NONE,
.client_idle_timeout = 0xFF,
.beacon_interval = 100,
.dtim_beacon_count = 3,
.beacon_stop = 0,
.options = 0,
.is_11n_enabled = 1,
};
sl_wifi_ap_configuration_t saved_ap_cfg;
int ret;
if (FIELD_GET(SIWX91X_INTERFACE_MASK, interface) != SL_WIFI_AP_INTERFACE) {
LOG_ERR("Interface not in AP mode");
wifi_mgmt_raise_ap_enable_result_event(sidev->iface,
WIFI_STATUS_AP_OP_NOT_PERMITTED);
return -EINVAL;
}
/* Device hiddes both length and ssid at same time */
siwx91x_set_hidden_ssid(dev, params->ignore_broadcast_ssid);
if (sidev->state == WIFI_STATE_COMPLETED) {
ret = siwx91x_ap_disable_if_required(dev, params);
if (ret < 0) {
wifi_mgmt_raise_ap_enable_result_event(sidev->iface, WIFI_STATUS_AP_FAIL);
return ret;
}
}
if (params->band != WIFI_FREQ_BAND_UNKNOWN && params->band != WIFI_FREQ_BAND_2_4_GHZ) {
LOG_ERR("Unsupported band");
wifi_mgmt_raise_ap_enable_result_event(sidev->iface,
WIFI_STATUS_AP_OP_NOT_SUPPORTED);
return -ENOTSUP;
}
if (params->bandwidth != WIFI_FREQ_BANDWIDTH_20MHZ) {
LOG_ERR("Unsupported bandwidth");
wifi_mgmt_raise_ap_enable_result_event(sidev->iface,
WIFI_STATUS_AP_OP_NOT_SUPPORTED);
return -ENOTSUP;
}
if (params->ssid_length == 0 || params->ssid_length > WIFI_SSID_MAX_LEN) {
LOG_ERR("Invalid ssid length");
wifi_mgmt_raise_ap_enable_result_event(sidev->iface,
WIFI_STATUS_AP_SSID_NOT_ALLOWED);
return -EINVAL;
}
ret = siwx91x_map_ap_security(params->security);
if (ret < 0) {
LOG_ERR("Invalid security type");
wifi_mgmt_raise_ap_enable_result_event(sidev->iface,
WIFI_STATUS_AP_AUTH_TYPE_NOT_SUPPORTED);
return -EINVAL;
}
siwx91x_ap_cfg.security = ret;
if (params->security != WIFI_SECURITY_TYPE_NONE) {
ret = sl_net_set_credential(siwx91x_ap_cfg.credential_id, SL_NET_WIFI_PSK,
params->psk, params->psk_length);
if (ret) {
LOG_ERR("Failed to set credentials: 0x%x", ret);
wifi_mgmt_raise_ap_enable_result_event(sidev->iface, WIFI_STATUS_AP_FAIL);
return -EINVAL;
}
}
ret = siwx91x_nwp_reboot_if_required(dev, WIFI_SOFTAP_MODE);
if (ret < 0) {
wifi_mgmt_raise_ap_enable_result_event(sidev->iface, WIFI_STATUS_AP_FAIL);
return ret;
}
sli_get_saved_ap_configuration(&saved_ap_cfg);
if (saved_ap_cfg.client_idle_timeout != 0) {
siwx91x_ap_cfg.client_idle_timeout = saved_ap_cfg.client_idle_timeout;
}
siwx91x_ap_cfg.ssid.length = params->ssid_length;
strncpy(siwx91x_ap_cfg.ssid.value, params->ssid, params->ssid_length);
if (params->mfp != WIFI_MFP_DISABLE) {
LOG_WRN("Needed MFP disable but got MFP %s, hence setting to MFP disable",
wifi_mfp_txt(params->mfp));
}
if (params->channel == WIFI_CHANNEL_ANY) {
siwx91x_ap_cfg.channel.channel = SL_WIFI_AUTO_CHANNEL;
} else {
siwx91x_ap_cfg.channel.channel = params->channel;
}
if (params->channel == 14) {
/* Disable HT on channel 14 */
siwx91x_ap_cfg.is_11n_enabled = 0;
}
ret = sl_wifi_start_ap(SL_WIFI_AP_INTERFACE | SL_WIFI_2_4GHZ_INTERFACE, &siwx91x_ap_cfg);
if (ret) {
LOG_ERR("Failed to enable AP mode: 0x%x", ret);
wifi_mgmt_raise_ap_enable_result_event(sidev->iface, WIFI_STATUS_AP_FAIL);
return -EIO;
}
if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE)) {
net_if_dormant_off(sidev->iface);
}
wifi_mgmt_raise_ap_enable_result_event(sidev->iface, WIFI_STATUS_AP_SUCCESS);
sidev->state = WIFI_STATE_COMPLETED;
return 0;
}
int siwx91x_ap_sta_disconnect(const struct device *dev, const uint8_t *mac_addr)
{
sl_wifi_interface_t interface = sl_wifi_get_default_interface();
sl_mac_address_t mac = { };
int ret;
ARG_UNUSED(dev);
__ASSERT(mac_addr, "mac_addr cannot be NULL");
if (FIELD_GET(SIWX91X_INTERFACE_MASK, interface) != SL_WIFI_AP_INTERFACE) {
LOG_ERR("Interface not in AP mode");
return -EINVAL;
}
memcpy(mac.octet, mac_addr, WIFI_MAC_ADDR_LEN);
ret = sl_wifi_disconnect_ap_client(SL_WIFI_AP_INTERFACE | SL_WIFI_2_4GHZ_INTERFACE,
&mac, SL_WIFI_DEAUTH);
if (ret) {
LOG_ERR("Failed to disconnect: 0x%x", ret);
return -EIO;
}
return ret;
}
sl_status_t siwx91x_on_ap_sta_connect(sl_wifi_event_t event, void *data,
uint32_t data_length, void *arg)
{
struct siwx91x_dev *sidev = arg;
struct wifi_ap_sta_info sta_info = { };
ARG_UNUSED(data_length);
ARG_UNUSED(event);
__ASSERT(data, "data cannot be NULL");
__ASSERT(arg, "arg cannot be NULL");
memcpy(sta_info.mac, data, WIFI_MAC_ADDR_LEN);
sta_info.mac_length = WIFI_MAC_ADDR_LEN;
sta_info.link_mode = WIFI_LINK_MODE_UNKNOWN;
wifi_mgmt_raise_ap_sta_connected_event(sidev->iface, &sta_info);
return SL_STATUS_OK;
}
sl_status_t siwx91x_on_ap_sta_disconnect(sl_wifi_event_t event, void *data,
uint32_t data_length, void *arg)
{
struct siwx91x_dev *sidev = arg;
struct wifi_ap_sta_info sta_info = { };
ARG_UNUSED(data_length);
ARG_UNUSED(event);
__ASSERT(data, "data cannot be NULL");
__ASSERT(arg, "arg cannot be NULL");
memcpy(sta_info.mac, data, WIFI_MAC_ADDR_LEN);
sta_info.mac_length = WIFI_MAC_ADDR_LEN;
wifi_mgmt_raise_ap_sta_disconnected_event(sidev->iface, &sta_info);
return SL_STATUS_OK;
}
int siwx91x_ap_config_params(const struct device *dev, struct wifi_ap_config_params *params)
{
sl_wifi_interface_t interface = sl_wifi_get_default_interface();
sl_wifi_ap_configuration_t siwx91x_ap_cfg;
__ASSERT(params, "params cannot be NULL");
if (FIELD_GET(SIWX91X_INTERFACE_MASK, interface) != SL_WIFI_AP_INTERFACE) {
LOG_ERR("Wi-Fi not in AP mode");
return -EINVAL;
}
if ((params->type & WIFI_AP_CONFIG_PARAM_BANDWIDTH) &&
params->bandwidth != WIFI_FREQ_BANDWIDTH_20MHZ) {
return -ENOTSUP;
}
sli_get_saved_ap_configuration(&siwx91x_ap_cfg);
siwx91x_ap_cfg.channel.bandwidth = SL_WIFI_BANDWIDTH_20MHz;
if (params->type & WIFI_AP_CONFIG_PARAM_MAX_INACTIVITY) {
siwx91x_ap_cfg.client_idle_timeout = params->max_inactivity * 1000;
}
if (params->type & WIFI_AP_CONFIG_PARAM_MAX_NUM_STA) {
siwx91x_set_max_sta(dev, params->max_num_sta);
}
sli_save_ap_configuration(&siwx91x_ap_cfg);
return 0;
}