| /* |
| * Copyright (c) 2020 Espressif Systems (Shanghai) Co., Ltd. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT espressif_esp32_wifi |
| |
| #define _POSIX_C_SOURCE 200809 |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(esp32_wifi, CONFIG_WIFI_LOG_LEVEL); |
| |
| #include <zephyr/net/ethernet.h> |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/net/net_if.h> |
| #include <zephyr/net/wifi_mgmt.h> |
| #include <zephyr/device.h> |
| #include <soc.h> |
| #include <ethernet/eth_stats.h> |
| #include "esp_networking_priv.h" |
| #include "esp_private/wifi.h" |
| #include "esp_event.h" |
| #include "esp_timer.h" |
| #include "esp_system.h" |
| #include "esp_wpa.h" |
| |
| #define DHCPV4_MASK (NET_EVENT_IPV4_DHCP_BOUND | NET_EVENT_IPV4_DHCP_STOP) |
| |
| /* use global iface pointer to support any ethernet driver */ |
| /* necessary for wifi callback functions */ |
| static struct net_if *esp32_wifi_iface; |
| static struct esp32_wifi_runtime esp32_data; |
| |
| enum esp32_state_flag { |
| ESP32_STA_STOPPED, |
| ESP32_STA_STARTED, |
| ESP32_STA_CONNECTING, |
| ESP32_STA_CONNECTED, |
| ESP32_AP_CONNECTED, |
| ESP32_AP_DISCONNECTED, |
| ESP32_AP_STOPPED, |
| }; |
| |
| struct esp32_wifi_runtime { |
| uint8_t mac_addr[6]; |
| uint8_t frame_buf[NET_ETH_MAX_FRAME_SIZE]; |
| #if defined(CONFIG_NET_STATISTICS_ETHERNET) |
| struct net_stats_eth stats; |
| #endif |
| scan_result_cb_t scan_cb; |
| uint8_t state; |
| }; |
| |
| static void esp_wifi_event_task(void); |
| |
| K_MSGQ_DEFINE(esp_wifi_msgq, sizeof(system_event_t), 10, 4); |
| K_THREAD_STACK_DEFINE(esp_wifi_event_stack, CONFIG_ESP32_WIFI_EVENT_TASK_STACK_SIZE); |
| static struct k_thread esp_wifi_event_thread; |
| |
| static struct net_mgmt_event_callback esp32_dhcp_cb; |
| |
| static void wifi_event_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event, |
| struct net_if *iface) |
| { |
| const struct wifi_status *status = (const struct wifi_status *)cb->info; |
| |
| switch (mgmt_event) { |
| case NET_EVENT_IPV4_DHCP_BOUND: |
| wifi_mgmt_raise_connect_result_event(esp32_wifi_iface, 0); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* internal wifi library callback function */ |
| esp_err_t esp_event_send_internal(esp_event_base_t event_base, |
| int32_t event_id, |
| void *event_data, |
| size_t event_data_size, |
| uint32_t ticks_to_wait) |
| { |
| system_event_t evt = { |
| .event_id = event_id, |
| }; |
| |
| if (event_data_size > sizeof(evt.event_info)) { |
| LOG_ERR("MSG %d wont find %d > %d", |
| event_id, event_data_size, sizeof(evt.event_info)); |
| return -EIO; |
| } |
| |
| memcpy(&evt.event_info, event_data, event_data_size); |
| k_msgq_put(&esp_wifi_msgq, &evt, K_FOREVER); |
| return 0; |
| } |
| |
| static int esp32_wifi_send(const struct device *dev, struct net_pkt *pkt) |
| { |
| struct esp32_wifi_runtime *data = dev->data; |
| const int pkt_len = net_pkt_get_len(pkt); |
| |
| /* Read the packet payload */ |
| if (net_pkt_read(pkt, data->frame_buf, pkt_len) < 0) { |
| return -EIO; |
| } |
| |
| /* Enqueue packet for transmission */ |
| esp_wifi_internal_tx(ESP_IF_WIFI_STA, (void *)data->frame_buf, pkt_len); |
| |
| LOG_DBG("pkt sent %p len %d", pkt, pkt_len); |
| |
| return 0; |
| } |
| |
| static esp_err_t eth_esp32_rx(void *buffer, uint16_t len, void *eb) |
| { |
| struct net_pkt *pkt; |
| |
| if (esp32_wifi_iface == NULL) { |
| LOG_ERR("network interface unavailable"); |
| return -EIO; |
| } |
| |
| pkt = net_pkt_rx_alloc_with_buffer(esp32_wifi_iface, len, AF_UNSPEC, 0, K_MSEC(100)); |
| if (!pkt) { |
| LOG_ERR("Failed to get net buffer"); |
| return -EIO; |
| } |
| |
| if (net_pkt_write(pkt, buffer, len) < 0) { |
| LOG_ERR("Failed to write pkt"); |
| goto pkt_unref; |
| } |
| |
| if (net_recv_data(esp32_wifi_iface, pkt) < 0) { |
| LOG_ERR("Failed to push received data"); |
| goto pkt_unref; |
| } |
| |
| esp_wifi_internal_free_rx_buffer(eb); |
| return 0; |
| |
| pkt_unref: |
| net_pkt_unref(pkt); |
| return -EIO; |
| } |
| |
| static void scan_done_handler(void) |
| { |
| uint16_t aps = 0; |
| wifi_ap_record_t *ap_list_buffer; |
| struct wifi_scan_result res = { 0 }; |
| |
| esp_wifi_scan_get_ap_num(&aps); |
| if (!aps) { |
| LOG_INF("No Wi-Fi AP found"); |
| goto out; |
| } |
| |
| ap_list_buffer = k_malloc(aps * sizeof(wifi_ap_record_t)); |
| if (ap_list_buffer == NULL) { |
| LOG_INF("Failed to malloc buffer to print scan results"); |
| goto out; |
| } |
| |
| if (esp_wifi_scan_get_ap_records(&aps, (wifi_ap_record_t *)ap_list_buffer) == ESP_OK) { |
| for (int k = 0; k < aps; k++) { |
| memset(&res, 0, sizeof(struct wifi_scan_result)); |
| int ssid_len = strnlen(ap_list_buffer[k].ssid, WIFI_SSID_MAX_LEN); |
| |
| res.ssid_length = ssid_len; |
| strncpy(res.ssid, ap_list_buffer[k].ssid, ssid_len); |
| res.rssi = ap_list_buffer[k].rssi; |
| res.channel = ap_list_buffer[k].primary; |
| res.security = WIFI_SECURITY_TYPE_NONE; |
| if (ap_list_buffer[k].authmode > WIFI_AUTH_OPEN) { |
| res.security = WIFI_SECURITY_TYPE_PSK; |
| } |
| |
| if (esp32_data.scan_cb) { |
| esp32_data.scan_cb(esp32_wifi_iface, 0, &res); |
| |
| /* ensure notifications get delivered */ |
| k_yield(); |
| } |
| } |
| } else { |
| LOG_INF("Unable to retrieve AP records"); |
| } |
| |
| k_free(ap_list_buffer); |
| |
| out: |
| /* report end of scan event */ |
| esp32_data.scan_cb(esp32_wifi_iface, 0, NULL); |
| esp32_data.scan_cb = NULL; |
| } |
| |
| static void esp_wifi_handle_connect_event(void) |
| { |
| esp32_data.state = ESP32_STA_CONNECTED; |
| if (IS_ENABLED(CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4)) { |
| net_dhcpv4_start(esp32_wifi_iface); |
| } else { |
| wifi_mgmt_raise_connect_result_event(esp32_wifi_iface, 0); |
| } |
| } |
| |
| static void esp_wifi_handle_disconnect_event(void) |
| { |
| if (esp32_data.state == ESP32_STA_CONNECTED) { |
| if (IS_ENABLED(CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4)) { |
| net_dhcpv4_stop(esp32_wifi_iface); |
| } |
| wifi_mgmt_raise_disconnect_result_event(esp32_wifi_iface, 0); |
| } else { |
| wifi_mgmt_raise_disconnect_result_event(esp32_wifi_iface, -1); |
| } |
| |
| if (IS_ENABLED(CONFIG_ESP32_WIFI_STA_RECONNECT)) { |
| esp32_data.state = ESP32_STA_CONNECTING; |
| esp_wifi_connect(); |
| } else { |
| esp32_data.state = ESP32_STA_STARTED; |
| } |
| } |
| |
| static void esp_wifi_event_task(void) |
| { |
| system_event_t evt; |
| uint8_t s_con_cnt = 0; |
| |
| while (1) { |
| k_msgq_get(&esp_wifi_msgq, &evt, K_FOREVER); |
| |
| switch (evt.event_id) { |
| case ESP32_WIFI_EVENT_STA_START: |
| esp32_data.state = ESP32_STA_STARTED; |
| net_eth_carrier_on(esp32_wifi_iface); |
| break; |
| case ESP32_WIFI_EVENT_STA_STOP: |
| esp32_data.state = ESP32_STA_STOPPED; |
| net_eth_carrier_off(esp32_wifi_iface); |
| break; |
| case ESP32_WIFI_EVENT_STA_CONNECTED: |
| esp_wifi_handle_connect_event(); |
| break; |
| case ESP32_WIFI_EVENT_STA_DISCONNECTED: |
| esp_wifi_handle_disconnect_event(); |
| break; |
| case ESP32_WIFI_EVENT_SCAN_DONE: |
| scan_done_handler(); |
| break; |
| case ESP32_WIFI_EVENT_AP_STOP: |
| esp32_data.state = ESP32_AP_STOPPED; |
| break; |
| case ESP32_WIFI_EVENT_AP_STACONNECTED: |
| esp32_data.state = ESP32_AP_CONNECTED; |
| if (!s_con_cnt) { |
| esp_wifi_internal_reg_rxcb(WIFI_IF_AP, eth_esp32_rx); |
| } |
| s_con_cnt++; |
| break; |
| case ESP32_WIFI_EVENT_AP_STADISCONNECTED: |
| esp32_data.state = ESP32_AP_DISCONNECTED; |
| s_con_cnt--; |
| if (!s_con_cnt) { |
| esp_wifi_internal_reg_rxcb(WIFI_IF_AP, NULL); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static int esp32_wifi_disconnect(const struct device *dev) |
| { |
| struct esp32_wifi_runtime *data = dev->data; |
| int ret = esp_wifi_disconnect(); |
| |
| if (ret != ESP_OK) { |
| LOG_INF("Failed to disconnect from hotspot"); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| } |
| |
| static int esp32_wifi_connect(const struct device *dev, |
| struct wifi_connect_req_params *params) |
| { |
| struct esp32_wifi_runtime *data = dev->data; |
| int ret; |
| |
| if (data->state == ESP32_STA_CONNECTING || data->state == ESP32_STA_CONNECTED) { |
| wifi_mgmt_raise_connect_result_event(esp32_wifi_iface, -1); |
| return -EALREADY; |
| } |
| |
| if (data->state != ESP32_STA_STARTED) { |
| LOG_ERR("Wi-Fi not in station mode"); |
| wifi_mgmt_raise_connect_result_event(esp32_wifi_iface, -1); |
| return -EIO; |
| } |
| |
| data->state = ESP32_STA_CONNECTING; |
| |
| wifi_config_t wifi_config; |
| |
| memset(&wifi_config, 0, sizeof(wifi_config_t)); |
| |
| memcpy(wifi_config.sta.ssid, params->ssid, params->ssid_length); |
| wifi_config.sta.ssid[params->ssid_length] = '\0'; |
| |
| if (params->security == WIFI_SECURITY_TYPE_PSK) { |
| memcpy(wifi_config.sta.password, params->psk, params->psk_length); |
| wifi_config.sta.password[params->psk_length] = '\0'; |
| wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; |
| } else if (params->security == WIFI_SECURITY_TYPE_NONE) { |
| wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN; |
| } else { |
| LOG_ERR("Authentication method not supported"); |
| return -EIO; |
| } |
| |
| wifi_config.sta.pmf_cfg.capable = true; |
| wifi_config.sta.pmf_cfg.required = false; |
| |
| ret = esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); |
| ret |= esp_wifi_set_mode(ESP32_WIFI_MODE_STA); |
| ret |= esp_wifi_connect(); |
| |
| if (ret != ESP_OK) { |
| LOG_ERR("Failed to connect to Wi-Fi access point"); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| } |
| |
| static int esp32_wifi_scan(const struct device *dev, scan_result_cb_t cb) |
| { |
| struct esp32_wifi_runtime *data = dev->data; |
| int ret = 0; |
| |
| if (data->scan_cb != NULL) { |
| LOG_INF("Scan callback in progress"); |
| return -EINPROGRESS; |
| } |
| |
| data->scan_cb = cb; |
| |
| wifi_scan_config_t scan_config = { 0 }; |
| |
| ret = esp_wifi_set_mode(ESP32_WIFI_MODE_STA); |
| ret |= esp_wifi_scan_start(&scan_config, false); |
| |
| if (ret != ESP_OK) { |
| LOG_ERR("Failed to start Wi-Fi scanning"); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| }; |
| |
| static int esp32_wifi_ap_enable(const struct device *dev, |
| struct wifi_connect_req_params *params) |
| { |
| struct esp32_wifi_data *data = dev->data; |
| esp_err_t ret = 0; |
| |
| /* Build Wi-Fi configuration for AP mode */ |
| wifi_config_t wifi_config = { |
| .ap = { |
| .max_connection = 5, |
| }, |
| }; |
| |
| strncpy((char *) wifi_config.ap.ssid, params->ssid, params->ssid_length); |
| |
| if (params->psk_length == 0) { |
| memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password)); |
| wifi_config.ap.authmode = WIFI_AUTH_OPEN; |
| } else { |
| strncpy((char *) wifi_config.ap.password, params->psk, params->psk_length); |
| wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK; |
| } |
| |
| /* Start Wi-Fi in AP mode with configuration built above */ |
| ret = esp_wifi_set_mode(ESP32_WIFI_MODE_AP); |
| ret |= esp_wifi_set_config(WIFI_IF_AP, &wifi_config); |
| ret |= esp_wifi_start(); |
| if (ret != ESP_OK) { |
| LOG_ERR("Failed to enable Wi-Fi AP mode"); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| }; |
| |
| static int esp32_wifi_ap_disable(const struct device *dev) |
| { |
| struct esp32_wifi_data *data = dev->data; |
| |
| esp_err_t ret = esp_wifi_set_mode(ESP32_WIFI_MODE_NULL); |
| |
| ret |= esp_wifi_start(); |
| if (ret != ESP_OK) { |
| LOG_ERR("Failed to disable Wi-Fi AP mode"); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| }; |
| |
| static void esp32_wifi_init(struct net_if *iface) |
| { |
| const struct device *dev = net_if_get_device(iface); |
| struct esp32_wifi_runtime *dev_data = dev->data; |
| |
| esp32_wifi_iface = iface; |
| dev_data->state = ESP32_STA_STOPPED; |
| |
| /* Start interface when we are actually connected with Wi-Fi network */ |
| esp_read_mac(dev_data->mac_addr, ESP_MAC_WIFI_STA); |
| |
| /* Assign link local address. */ |
| net_if_set_link_addr(iface, dev_data->mac_addr, 6, NET_LINK_ETHERNET); |
| |
| ethernet_init(iface); |
| net_if_carrier_off(iface); |
| |
| esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, eth_esp32_rx); |
| } |
| |
| #if defined(CONFIG_NET_STATISTICS_ETHERNET) |
| static struct net_stats_eth *esp32_wifi_stats(const struct device *dev) |
| { |
| struct esp32_wifi_runtime *data = dev->data; |
| |
| return &(data->stats); |
| } |
| #endif |
| |
| static int esp32_wifi_dev_init(const struct device *dev) |
| { |
| esp_timer_init(); |
| |
| k_tid_t tid = k_thread_create(&esp_wifi_event_thread, esp_wifi_event_stack, |
| CONFIG_ESP32_WIFI_EVENT_TASK_STACK_SIZE, |
| (k_thread_entry_t)esp_wifi_event_task, NULL, NULL, NULL, |
| CONFIG_ESP32_WIFI_EVENT_TASK_PRIO, K_INHERIT_PERMS, |
| K_NO_WAIT); |
| |
| k_thread_name_set(tid, "esp_event"); |
| |
| if (IS_ENABLED(CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4)) { |
| net_mgmt_init_event_callback(&esp32_dhcp_cb, wifi_event_handler, DHCPV4_MASK); |
| net_mgmt_add_event_callback(&esp32_dhcp_cb); |
| } |
| |
| wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); |
| esp_err_t ret = esp_wifi_init(&config); |
| |
| ret |= esp_wifi_set_mode(ESP32_WIFI_MODE_STA); |
| ret |= esp_wifi_start(); |
| |
| if (ret != ESP_OK) { |
| LOG_ERR("Failed to start Wi-Fi driver"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static const struct net_wifi_mgmt_offload esp32_api = { |
| .wifi_iface.iface_api.init = esp32_wifi_init, |
| .wifi_iface.send = esp32_wifi_send, |
| #if defined(CONFIG_NET_STATISTICS_ETHERNET) |
| .wifi_iface.get_stats = esp32_wifi_stats, |
| #endif |
| .scan = esp32_wifi_scan, |
| .connect = esp32_wifi_connect, |
| .disconnect = esp32_wifi_disconnect, |
| .ap_enable = esp32_wifi_ap_enable, |
| .ap_disable = esp32_wifi_ap_disable, |
| }; |
| |
| NET_DEVICE_DT_INST_DEFINE(0, |
| esp32_wifi_dev_init, NULL, |
| &esp32_data, NULL, CONFIG_ETH_INIT_PRIORITY, |
| &esp32_api, ETHERNET_L2, |
| NET_L2_GET_CTX_TYPE(ETHERNET_L2), NET_ETH_MTU); |