| /* |
| * Copyright (c) 2023 Antmicro |
| * Copyright (c) 2024-2025 Silicon Laboratories Inc. |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <zephyr/logging/log.h> |
| |
| #include <nwp.h> |
| #include "siwx91x_wifi.h" |
| #include "siwx91x_wifi_socket.h" |
| #include "siwx91x_wifi_ps.h" |
| |
| #include "sl_rsi_utility.h" |
| #include "sl_net_default_values.h" |
| #include "sl_net.h" |
| |
| LOG_MODULE_DECLARE(siwx91x_wifi); |
| |
| enum { |
| STATE_IDLE = 0x00, |
| /* Failover Roam */ |
| STATE_BEACON_LOSS = 0x10, |
| /* AP induced Roam/Deauth from supplicant */ |
| STATE_DEAUTHENTICATION = 0x20, |
| STATE_CURRENT_AP_BEST = 0x50, |
| /* While roaming */ |
| STATE_BETTER_AP_FOUND = 0x60, |
| STATE_NO_AP_FOUND = 0x70, |
| STATE_ASSOCIATED = 0x80, |
| STATE_UNASSOCIATED = 0x90 |
| }; |
| |
| enum { |
| WLAN_REASON_NO_REASON = 0x00, |
| WLAN_REASON_AUTH_DENIAL, |
| WLAN_REASON_ASSOC_DENIAL, |
| WLAN_REASON_AP_NOT_PRESENT, |
| WLAN_REASON_UNKNOWN, |
| WLAN_REASON_HANDSHAKE_FAILURE, |
| WLAN_REASON_USER_DEAUTH, |
| WLAN_REASON_PSK_NOT_CONFIGURED, |
| WLAN_REASON_AP_INITIATED_DEAUTH, |
| WLAN_REASON_ROAMING_DISABLED, |
| WLAN_REASON_MAX |
| }; |
| |
| static const char *siwx91x_get_reason_string(uint8_t reason_code) |
| { |
| static const struct { |
| uint8_t code; |
| const char *str; |
| } reason_strings[] = { |
| { WLAN_REASON_NO_REASON, "No reason specified" }, |
| { WLAN_REASON_AUTH_DENIAL, "Authentication denial" }, |
| { WLAN_REASON_ASSOC_DENIAL, "Association denial" }, |
| { WLAN_REASON_AP_NOT_PRESENT, "AP not present" }, |
| { WLAN_REASON_UNKNOWN, "Unknown" }, |
| { WLAN_REASON_HANDSHAKE_FAILURE, "Four way handshake failure" }, |
| { WLAN_REASON_USER_DEAUTH, "Deauthentication from User" }, |
| { WLAN_REASON_PSK_NOT_CONFIGURED, "PSK not configured" }, |
| { WLAN_REASON_AP_INITIATED_DEAUTH, |
| "De-authentication (AP induced Roam/Deauth from supplicant)" }, |
| { WLAN_REASON_ROAMING_DISABLED, "Roaming not enabled" }, |
| }; |
| |
| ARRAY_FOR_EACH(reason_strings, i) { |
| if (reason_strings[i].code == reason_code) { |
| return reason_strings[i].str; |
| } |
| } |
| |
| return "Unknown"; |
| } |
| |
| sl_status_t siwx91x_wifi_module_stats_event_handler(sl_wifi_event_t event, void *response, |
| uint32_t result_length, void *arg) |
| { |
| sl_si91x_module_state_stats_response_t *notif = response; |
| const char *reason_str = siwx91x_get_reason_string(notif->reason_code); |
| uint8_t module_state = notif->state_code & 0xF0; |
| struct siwx91x_dev *sidev = arg; |
| |
| ARG_UNUSED(event); |
| ARG_UNUSED(result_length); |
| |
| switch (module_state) { |
| case STATE_BEACON_LOSS: |
| LOG_WRN("Beacon Loss"); |
| break; |
| case STATE_BETTER_AP_FOUND: |
| LOG_DBG("Better AP found while roaming"); |
| break; |
| case STATE_ASSOCIATED: |
| sidev->state = WIFI_STATE_COMPLETED; |
| break; |
| case STATE_UNASSOCIATED: |
| wifi_mgmt_raise_disconnect_result_event(sidev->iface, |
| WIFI_REASON_DISCONN_SUCCESS); |
| |
| sidev->state = WIFI_STATE_DISCONNECTED; |
| break; |
| default: |
| return 0; |
| } |
| |
| LOG_DBG("Reason: %s", reason_str); |
| return 0; |
| } |
| |
| static enum wifi_mfp_options siwx91x_set_sta_mfp_option(sl_wifi_security_t security, |
| enum wifi_mfp_options mfp_conf) |
| { |
| uint8_t join_config; |
| |
| switch (security) { |
| case SL_WIFI_OPEN: |
| case SL_WIFI_WPA: |
| return WIFI_MFP_DISABLE; |
| case SL_WIFI_WPA2: |
| case SL_WIFI_WPA_WPA2_MIXED: |
| if (mfp_conf == WIFI_MFP_REQUIRED) { |
| /* Handling the case for WPA2_SHA256 security type */ |
| /* Directly enabling the MFP Required bit in the Join Feature |
| * bitmap. This ensures that MFP is enforced for connections using |
| * WPA2_SHA256. |
| * |
| * Note: This is a workaround to configure MFP as the current SDK |
| * does not provide a dedicated API to configure MFP settings. |
| * By manipulating the join feature bitmap directly, we achieve |
| * the desired MFP configuration for enhanced security. |
| * |
| * This case will be updated in the future when the SDK adds |
| * dedicated support for configuring MFP. |
| */ |
| sl_si91x_get_join_configuration(SL_WIFI_CLIENT_INTERFACE, &join_config); |
| join_config |= SL_SI91X_JOIN_FEAT_MFP_CAPABLE_REQUIRED; |
| sl_si91x_set_join_configuration(SL_WIFI_CLIENT_INTERFACE, join_config); |
| return WIFI_MFP_REQUIRED; |
| } |
| /* Handling the case for WPA2 security type */ |
| /* Ensuring the connection happened in WPA2-PSK |
| * by clearing the MFP Required bit in the Join Feature bitmap. |
| */ |
| sl_si91x_get_join_configuration(SL_WIFI_CLIENT_INTERFACE, &join_config); |
| join_config &= ~(SL_SI91X_JOIN_FEAT_MFP_CAPABLE_REQUIRED); |
| sl_si91x_set_join_configuration(SL_WIFI_CLIENT_INTERFACE, join_config); |
| return WIFI_MFP_OPTIONAL; |
| case SL_WIFI_WPA3: |
| return WIFI_MFP_REQUIRED; |
| case SL_WIFI_WPA3_TRANSITION: |
| return WIFI_MFP_OPTIONAL; |
| default: |
| return WIFI_MFP_DISABLE; |
| } |
| |
| return WIFI_MFP_UNKNOWN; |
| } |
| |
| 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); |
| |
| siwx91x_apply_power_save(sidev); |
| |
| return 0; |
| } |
| |
| int siwx91x_disconnect(const struct device *dev) |
| { |
| sl_wifi_interface_t interface = sl_wifi_get_default_interface(); |
| struct siwx91x_dev *sidev = dev->data; |
| int ret; |
| |
| if (sidev->state != WIFI_STATE_COMPLETED) { |
| LOG_ERR("Command given in invalid state"); |
| return -EINVAL; |
| } |
| |
| ret = sl_wifi_disconnect(interface); |
| if (ret) { |
| wifi_mgmt_raise_disconnect_result_event(sidev->iface, ret); |
| return -EIO; |
| } |
| |
| if (IS_ENABLED(CONFIG_WIFI_SILABS_SIWX91X_NET_STACK_NATIVE)) { |
| net_if_dormant_on(sidev->iface); |
| } |
| |
| return 0; |
| } |
| |
| static int siwx91x_disconnect_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; |
| 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 (siwx91x_param_changed(&prev_params, new_params)) { |
| return siwx91x_disconnect(dev); |
| } |
| |
| if (new_params->security != WIFI_SECURITY_TYPE_NONE) { |
| ret = sl_net_get_credential(SL_NET_DEFAULT_WIFI_CLIENT_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_disconnect(dev); |
| } |
| } |
| |
| LOG_ERR("Device already in active state"); |
| return -EALREADY; |
| } |
| |
| int siwx91x_connect(const struct device *dev, struct wifi_connect_req_params *params) |
| { |
| sl_wifi_interface_t interface = sl_wifi_get_default_interface(); |
| sl_wifi_client_configuration_t wifi_config = { |
| .bss_type = SL_WIFI_BSS_TYPE_INFRASTRUCTURE, |
| .encryption = SL_WIFI_DEFAULT_ENCRYPTION, |
| .credential_id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID, |
| }; |
| struct siwx91x_dev *sidev = dev->data; |
| enum wifi_mfp_options mfp_conf; |
| int ret; |
| |
| if (sidev->state == WIFI_STATE_COMPLETED) { |
| ret = siwx91x_disconnect_if_required(dev, params); |
| if (ret < 0) { |
| wifi_mgmt_raise_connect_result_event(sidev->iface, WIFI_STATUS_CONN_FAIL); |
| return ret; |
| } |
| } |
| |
| switch (params->security) { |
| case WIFI_SECURITY_TYPE_NONE: |
| wifi_config.security = SL_WIFI_OPEN; |
| break; |
| case WIFI_SECURITY_TYPE_WPA_PSK: |
| wifi_config.security = SL_WIFI_WPA; |
| break; |
| case WIFI_SECURITY_TYPE_PSK: |
| /* This case is meant to fall through to the next */ |
| case WIFI_SECURITY_TYPE_PSK_SHA256: |
| /* Use WPA2 security as the device supports only SHA256 |
| * key derivation for WPA2-PSK |
| */ |
| wifi_config.security = SL_WIFI_WPA2; |
| break; |
| case WIFI_SECURITY_TYPE_SAE_AUTO: |
| /* Use WPA3 security as the device supports only HNP and H2E |
| * methods for SAE |
| */ |
| wifi_config.security = SL_WIFI_WPA3; |
| break; |
| case WIFI_SECURITY_TYPE_WPA_AUTO_PERSONAL: |
| /* Use WPA2/WPA3 security as the device supports both */ |
| wifi_config.security = SL_WIFI_WPA3_TRANSITION; |
| 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) { |
| wifi_mgmt_raise_connect_result_event(sidev->iface, WIFI_STATUS_CONN_FAIL); |
| return -ENOTSUP; |
| } |
| |
| if (params->psk_length) { |
| ret = sl_net_set_credential(SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID, |
| SL_NET_WIFI_PSK, params->psk, params->psk_length); |
| } else if (params->sae_password_length) { |
| ret = sl_net_set_credential(SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID, |
| SL_NET_WIFI_PSK, params->sae_password, |
| params->sae_password_length); |
| } else { |
| ret = 0; |
| } |
| |
| if (ret) { |
| LOG_ERR("Failed to set credentials: 0x%x", ret); |
| wifi_mgmt_raise_connect_result_event(sidev->iface, WIFI_STATUS_CONN_FAIL); |
| return -EINVAL; |
| } |
| |
| if (params->security == WIFI_SECURITY_TYPE_PSK_SHA256) { |
| mfp_conf = siwx91x_set_sta_mfp_option(wifi_config.security, WIFI_MFP_REQUIRED); |
| } else { |
| mfp_conf = siwx91x_set_sta_mfp_option(wifi_config.security, params->mfp); |
| } |
| |
| if (params->mfp != mfp_conf) { |
| LOG_WRN("Needed MFP %s but got MFP %s, hence setting to MFP %s", |
| wifi_mfp_txt(mfp_conf), wifi_mfp_txt(params->mfp), wifi_mfp_txt(mfp_conf)); |
| } |
| |
| if (params->channel != WIFI_CHANNEL_ANY) { |
| wifi_config.channel.channel = params->channel; |
| } else { |
| wifi_config.channel.channel = 0; |
| } |
| |
| wifi_config.ssid.length = params->ssid_length, |
| memcpy(wifi_config.ssid.value, params->ssid, params->ssid_length); |
| |
| ret = sl_wifi_connect(interface, &wifi_config, 0); |
| if (ret != SL_STATUS_IN_PROGRESS) { |
| wifi_mgmt_raise_connect_result_event(sidev->iface, WIFI_STATUS_CONN_FAIL); |
| return -EIO; |
| } |
| |
| return 0; |
| } |