| /**************************************************************************** |
| * arch/xtensa/src/esp32s3/esp32s3_wlan.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #ifdef CONFIG_ESP32S3_WIFI |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <arpa/inet.h> |
| |
| #include <nuttx/crc64.h> |
| #include <nuttx/nuttx.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/queue.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/wdog.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/net/ip.h> |
| #include <nuttx/net/netdev.h> |
| |
| #if defined(CONFIG_NET_PKT) |
| # include <nuttx/net/pkt.h> |
| #endif |
| |
| #include "esp32s3_wlan.h" |
| #include "esp32s3_wifi_utils.h" |
| #include "esp32s3_wifi_adapter.h" |
| #include "esp32s3_wireless.h" |
| #include "esp32s3_systemreset.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* TX timeout = 1 minute */ |
| |
| #define WLAN_TXTOUT (60 * CLK_TCK) |
| |
| /* Low-priority work queue processes RX/TX */ |
| |
| #define WLAN_WORK LPWORK |
| |
| /* Ethernet frame: |
| * Resource address : 6 bytes |
| * Destination address: 6 bytes |
| * Type : 2 bytes |
| * Payload : MAX 1500 |
| * Checksum : Ignore |
| * |
| * Total size : 1514 |
| */ |
| |
| #define WLAN_BUF_SIZE (CONFIG_NET_ETH_PKTSIZE + \ |
| CONFIG_NET_LL_GUARDSIZE + \ |
| CONFIG_NET_GUARDSIZE) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* WLAN operations */ |
| |
| struct wlan_ops |
| { |
| int (*start)(void); |
| int (*send)(void *pdata, size_t n); |
| int (*essid)(struct iwreq *iwr, bool set); |
| int (*bssid)(struct iwreq *iwr, bool set); |
| int (*passwd)(struct iwreq *iwr, bool set); |
| int (*mode)(struct iwreq *iwr, bool set); |
| int (*auth)(struct iwreq *iwr, bool set); |
| int (*freq)(struct iwreq *iwr, bool set); |
| int (*bitrate)(struct iwreq *iwr, bool set); |
| int (*txpower)(struct iwreq *iwr, bool set); |
| int (*channel)(struct iwreq *iwr, bool set); |
| int (*country)(struct iwreq *iwr, bool set); |
| int (*rssi)(struct iwreq *iwr, bool set); |
| int (*connect)(void); |
| int (*disconnect)(void); |
| int (*event)(pid_t pid, struct sigevent *event); |
| int (*stop)(void); |
| }; |
| |
| /* The wlan_priv_s encapsulates all state information for a single |
| * hardware interface |
| */ |
| |
| struct wlan_priv_s |
| { |
| int ref; /* Reference count */ |
| |
| bool ifup; /* true:ifup false:ifdown */ |
| |
| struct wdog_s txtimeout; /* TX timeout timer */ |
| |
| struct work_s rxwork; /* Send packet work */ |
| struct work_s txwork; /* Receive packet work */ |
| struct work_s pollwork; /* Poll work */ |
| struct work_s toutwork; /* Send packet timeout work */ |
| |
| const struct wlan_ops *ops; /* WLAN operations */ |
| |
| /* This holds the information visible to the NuttX network */ |
| |
| struct net_driver_s dev; |
| |
| /* RX packet queue */ |
| |
| struct iob_queue_s rxb; |
| |
| /* TX ready packet queue */ |
| |
| struct iob_queue_s txb; |
| |
| /* Flat buffer swap */ |
| |
| uint8_t flatbuf[WLAN_BUF_SIZE]; |
| |
| spinlock_t lock; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Reference count of register Wi-Fi handler */ |
| |
| static uint8_t g_callback_register_ref = 0; |
| |
| static struct wlan_priv_s g_wlan_priv[ESP32S3_WLAN_DEVS]; |
| |
| #ifdef ESP32S3_WLAN_HAS_STA |
| static const struct wlan_ops g_sta_ops = |
| { |
| .start = esp_wifi_sta_start, |
| .send = esp_wifi_sta_send_data, |
| .essid = esp_wifi_sta_essid, |
| .bssid = esp_wifi_sta_bssid, |
| .passwd = esp_wifi_sta_password, |
| .mode = esp_wifi_sta_mode, |
| .auth = esp_wifi_sta_auth, |
| .freq = esp_wifi_sta_freq, |
| .bitrate = esp_wifi_sta_bitrate, |
| .txpower = esp_wifi_sta_txpower, |
| .channel = esp_wifi_sta_channel, |
| .country = esp_wifi_sta_country, |
| .rssi = esp_wifi_sta_rssi, |
| .connect = esp_wifi_sta_connect, |
| .disconnect = esp_wifi_sta_disconnect, |
| .event = esp_wifi_notify_subscribe, |
| .stop = esp_wifi_sta_stop |
| }; |
| #endif /* ESP32S3_WLAN_HAS_STA */ |
| |
| #ifdef ESP32S3_WLAN_HAS_SOFTAP |
| static const struct wlan_ops g_softap_ops = |
| { |
| .start = esp_wifi_softap_start, |
| .send = esp_wifi_softap_send_data, |
| .essid = esp_wifi_softap_essid, |
| .bssid = esp_wifi_softap_bssid, |
| .passwd = esp_wifi_softap_password, |
| .mode = esp_wifi_softap_mode, |
| .auth = esp_wifi_softap_auth, |
| .freq = esp_wifi_softap_freq, |
| .bitrate = esp_wifi_softap_bitrate, |
| .txpower = esp_wifi_softap_txpower, |
| .channel = esp_wifi_softap_channel, |
| .country = esp_wifi_softap_country, |
| .rssi = esp_wifi_softap_rssi, |
| .connect = esp_wifi_softap_connect, |
| .disconnect = esp_wifi_softap_disconnect, |
| .event = esp_wifi_notify_subscribe, |
| .stop = esp_wifi_softap_stop |
| }; |
| #endif /* ESP32S3_WLAN_HAS_SOFTAP */ |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Common TX logic */ |
| |
| static void wlan_transmit(struct wlan_priv_s *priv); |
| static void wlan_rxpoll(void *arg); |
| static int wlan_txpoll(struct net_driver_s *dev); |
| static void wlan_dopoll(struct wlan_priv_s *priv); |
| |
| /* Watchdog timer expirations */ |
| |
| static void wlan_txtimeout_work(void *arg); |
| static void wlan_txtimeout_expiry(wdparm_t arg); |
| |
| /* NuttX callback functions */ |
| |
| static int wlan_ifup(struct net_driver_s *dev); |
| static int wlan_ifdown(struct net_driver_s *dev); |
| |
| static void wlan_txavail_work(void *arg); |
| static int wlan_txavail(struct net_driver_s *dev); |
| |
| #if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) |
| static int wlan_addmac(struct net_driver_s *dev, const uint8_t *mac); |
| #endif |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int wlan_rmmac(struct net_driver_s *dev, const uint8_t *mac); |
| #endif |
| |
| #ifdef CONFIG_NETDEV_IOCTL |
| static int wlan_ioctl(struct net_driver_s *dev, int cmd, |
| unsigned long arg); |
| #endif |
| |
| #ifdef CONFIG_NET_ICMPv6 |
| static void wlan_ipv6multicast(struct wlan_priv_s *priv); |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /* Note: |
| * All TX done/RX done/Error trigger functions are not called from |
| * interrupts, this is much different from ethernet driver, including: |
| * * wlan_rx_done |
| * * wlan_tx_done |
| * |
| * These functions are called in a Wi-Fi private thread. So we just use |
| * mutex/semaphore instead of disable interrupt, if necessary. |
| */ |
| |
| /**************************************************************************** |
| * Function: wlan_cache_txpkt_tail |
| * |
| * Description: |
| * Cache packet from dev->d_buf into tail of TX ready queue. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void wlan_cache_txpkt_tail(struct wlan_priv_s *priv) |
| { |
| if (priv->dev.d_iob) |
| { |
| iob_tryadd_queue(priv->dev.d_iob, &priv->txb); |
| } |
| |
| netdev_iob_clear(&priv->dev); |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_recvframe |
| * |
| * Description: |
| * Try to receive RX packet from RX done packet queue. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * RX packet if success or NULl if no packet in queue. |
| * |
| ****************************************************************************/ |
| |
| static struct iob_s *wlan_recvframe(struct wlan_priv_s *priv) |
| { |
| struct iob_s *iob; |
| |
| iob = iob_remove_queue(&priv->rxb); |
| |
| return iob; |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_transmit |
| * |
| * Description: |
| * Try to send all TX packets in TX ready queue to Wi-Fi driver. If this |
| * sending fails, then breaks loop and returns. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_transmit(struct wlan_priv_s *priv) |
| { |
| uint16_t llhdrlen = NET_LL_HDRLEN(&priv->dev); |
| unsigned int offset = CONFIG_NET_LL_GUARDSIZE - llhdrlen; |
| struct iob_s *iob; |
| int ret; |
| |
| while ((iob = iob_peek_queue(&priv->txb)) != NULL) |
| { |
| iob_copyout(priv->flatbuf + llhdrlen, iob, iob->io_pktlen, 0); |
| memcpy(priv->flatbuf, iob->io_data + offset, llhdrlen); |
| |
| ret = priv->ops->send(priv->flatbuf, iob->io_pktlen + llhdrlen); |
| if (ret == -ENOMEM) |
| { |
| wd_start(&priv->txtimeout, WLAN_TXTOUT, |
| wlan_txtimeout_expiry, (uint32_t)priv); |
| break; |
| } |
| else |
| { |
| if (ret < 0) |
| { |
| nwarn("WARN: Failed to send pkt, ret: %d\n", ret); |
| } |
| |
| iob_remove_queue(&priv->txb); |
| |
| /* And free the I/O buffer chain */ |
| |
| iob_free_chain(iob); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_tx_done |
| * |
| * Description: |
| * Wi-Fi TX done callback function. If this is called, it means sending |
| * next packet. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_tx_done(struct wlan_priv_s *priv) |
| { |
| wd_cancel(&priv->txtimeout); |
| |
| wlan_txavail(&priv->dev); |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_rx_done |
| * |
| * Description: |
| * Wi-Fi RX done callback function. If this is called, it means receiving |
| * packet. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * buffer - Wi-Fi received packet buffer |
| * len - Length of received packet |
| * eb - Wi-Fi receive callback input eb pointer |
| * |
| * Returned Value: |
| * 0 on success or a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int wlan_rx_done(struct wlan_priv_s *priv, void *buffer, |
| uint16_t len, void *eb) |
| { |
| struct net_driver_s *dev = &priv->dev; |
| struct iob_s *iob = NULL; |
| irqstate_t flags; |
| int ret = 0; |
| |
| if (!priv->ifup) |
| { |
| goto out; |
| } |
| |
| if (len > WLAN_BUF_SIZE) |
| { |
| nwarn("ERROR: Wlan receive %d larger than %d\n", |
| len, WLAN_BUF_SIZE); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (len > iob_navail(false) * CONFIG_IOB_BUFSIZE) |
| { |
| ret = -ENOBUFS; |
| goto out; |
| } |
| |
| iob = iob_tryalloc(false); |
| if (iob == NULL) |
| { |
| ret = -ENOBUFS; |
| goto out; |
| } |
| |
| iob_reserve(iob, CONFIG_NET_LL_GUARDSIZE - NET_LL_HDRLEN(dev)); |
| |
| ret = iob_trycopyin(iob, buffer, len, 0, false); |
| if (ret != len) |
| { |
| ret = -ENOBUFS; |
| goto out; |
| } |
| |
| flags = spin_lock_irqsave(&priv->lock); |
| ret = iob_tryadd_queue(iob, &priv->rxb); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| if (ret < 0) |
| { |
| ret = -ENOBUFS; |
| goto out; |
| } |
| |
| out: |
| |
| if (eb != NULL) |
| { |
| esp_wifi_free_eb(eb); |
| } |
| |
| if (ret != OK && iob != NULL) |
| { |
| iob_free_chain(iob); |
| } |
| |
| if (work_available(&priv->rxwork)) |
| { |
| work_queue(WLAN_WORK, &priv->rxwork, wlan_rxpoll, priv, 0); |
| } |
| |
| wlan_txavail(&priv->dev); |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_rxpoll |
| * |
| * Description: |
| * Try to receive packets from RX done queue and pass packets into IP |
| * stack and send packets which is from IP stack if necessary. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_rxpoll(void *arg) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)arg; |
| struct net_driver_s *dev = &priv->dev; |
| struct eth_hdr_s *eth_hdr; |
| struct iob_s *iob; |
| |
| /* Try to send all cached TX packets for TX ack and so on */ |
| |
| wlan_transmit(priv); |
| |
| /* Loop while while iob_remove_queue() successfully retrieves valid |
| * Ethernet frames. |
| */ |
| |
| net_lock(); |
| |
| while ((iob = wlan_recvframe(priv)) != NULL) |
| { |
| dev->d_iob = iob; |
| dev->d_len = iob->io_pktlen; |
| |
| iob_reserve(iob, CONFIG_NET_LL_GUARDSIZE); |
| |
| #ifdef CONFIG_NET_PKT |
| |
| /* When packet sockets are enabled, |
| * feed the frame into the packet tap. |
| */ |
| |
| pkt_input(&priv->dev); |
| #endif |
| |
| eth_hdr = (struct eth_hdr_s *) |
| &dev->d_iob->io_data[CONFIG_NET_LL_GUARDSIZE - |
| NET_LL_HDRLEN(dev)]; |
| |
| /* We only accept IP packets of the configured type and ARP packets */ |
| |
| #ifdef CONFIG_NET_IPv4 |
| if (eth_hdr->type == HTONS(ETHTYPE_IP)) |
| { |
| ninfo("IPv4 frame\n"); |
| |
| /* Receive an IPv4 packet from the network device */ |
| |
| ipv4_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data |
| * that should be sent out on the network, |
| * the field d_len will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* And send the packet */ |
| |
| wlan_cache_txpkt_tail(priv); |
| } |
| } |
| else |
| #endif |
| #ifdef CONFIG_NET_IPv6 |
| if (eth_hdr->type == HTONS(ETHTYPE_IP6)) |
| { |
| ninfo("IPv6 frame\n"); |
| |
| /* Give the IPv6 packet to the network layer */ |
| |
| ipv6_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data |
| * that should be sent out on the network, the field |
| * d_len will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* And send the packet */ |
| |
| wlan_cache_txpkt_tail(priv); |
| } |
| } |
| else |
| #endif |
| #ifdef CONFIG_NET_ARP |
| if (eth_hdr->type == HTONS(ETHTYPE_ARP)) |
| { |
| ninfo("ARP frame\n"); |
| |
| /* Handle ARP packet */ |
| |
| arp_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data |
| * that should be sent out on the network, the field |
| * d_len will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| wlan_cache_txpkt_tail(priv); |
| } |
| } |
| else |
| #endif |
| { |
| ninfo("INFO: Dropped, Unknown type: %04x\n", eth_hdr->type); |
| } |
| |
| netdev_iob_release(&priv->dev); |
| } |
| |
| /* Try to send all cached TX packets */ |
| |
| wlan_transmit(priv); |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_txpoll |
| * |
| * Description: |
| * The transmitter is available, check if the network has any outgoing |
| * packets ready to send. This is a callback from devif_poll(). |
| * devif_poll() may be called: |
| * |
| * 1. When the preceding TX packets send times out and the interface is |
| * reset |
| * 2. During normal TX polling |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int wlan_txpoll(struct net_driver_s *dev) |
| { |
| struct wlan_priv_s *priv = dev->d_private; |
| |
| wlan_cache_txpkt_tail(priv); |
| wlan_transmit(priv); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_dopoll |
| * |
| * Description: |
| * The function is called in order to perform an out-of-sequence TX poll. |
| * This is done: |
| * |
| * 1. When new TX data is available (wlan_txavail) |
| * 2. After a TX timeout to restart the sending process |
| * (wlan_txtimeout_expiry). |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_dopoll(struct wlan_priv_s *priv) |
| { |
| struct net_driver_s *dev = &priv->dev; |
| |
| /* Try to let TCP/IP to send all packets to netcard driver */ |
| |
| while (devif_poll(dev, wlan_txpoll)); |
| |
| /* Try to send all cached TX packets */ |
| |
| wlan_transmit(priv); |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_txtimeout_work |
| * |
| * Description: |
| * Perform TX timeout related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() as called. |
| * |
| * Returned Value: |
| * OK on success |
| * |
| ****************************************************************************/ |
| |
| static void wlan_txtimeout_work(void *arg) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)arg; |
| |
| /* Try to send all cached TX packets */ |
| |
| wlan_transmit(priv); |
| |
| net_lock(); |
| |
| wlan_ifdown(&priv->dev); |
| wlan_ifup(&priv->dev); |
| |
| /* Then poll for new XMIT data */ |
| |
| wlan_dopoll(priv); |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_txtimeout_expiry |
| * |
| * Description: |
| * Our TX watchdog timed out. Called from the timer callback handler. |
| * The last TX never completed. Reset the hardware and start again. |
| * |
| * Input Parameters: |
| * argc - The number of available arguments |
| * arg - The first argument |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_txtimeout_expiry(wdparm_t arg) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)arg; |
| |
| /* Schedule to perform the TX timeout processing on the worker thread. */ |
| |
| if (work_available(&priv->toutwork)) |
| { |
| work_queue(WLAN_WORK, &priv->toutwork, wlan_txtimeout_work, priv, 0); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_txavail_work |
| * |
| * Description: |
| * Perform an out-of-cycle poll on the worker thread. |
| * |
| * Input Parameters: |
| * arg - Reference to the NuttX driver state structure (cast to void*) |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called on the higher priority worker thread. |
| * |
| ****************************************************************************/ |
| |
| static void wlan_txavail_work(void *arg) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)arg; |
| |
| /* Try to send all cached TX packets even if net is down */ |
| |
| wlan_transmit(priv); |
| |
| /* Lock the network and serialize driver operations if necessary. |
| * NOTE: Serialization is only required in the case where the driver work |
| * is performed on an LP worker thread and where more than one LP worker |
| * thread has been configured. |
| */ |
| |
| net_lock(); |
| |
| /* Ignore the notification if the interface is not yet up */ |
| |
| if (priv->ifup) |
| { |
| /* Poll the network for new XMIT data */ |
| |
| wlan_dopoll(priv); |
| } |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_ifup |
| * |
| * Description: |
| * NuttX Callback: Bring up the Ethernet interface when an IP address is |
| * provided |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int wlan_ifup(struct net_driver_s *dev) |
| { |
| int ret; |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)dev->d_private; |
| |
| #ifdef CONFIG_NET_IPv4 |
| ninfo("Bringing up: %u.%u.%u.%u\n", |
| ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr), |
| ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr)); |
| #endif |
| #ifdef CONFIG_NET_IPv6 |
| ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", |
| dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2], |
| dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5], |
| dev->d_ipv6addr[6], dev->d_ipv6addr[7]); |
| #endif |
| |
| net_lock(); |
| |
| if (priv->ifup) |
| { |
| net_unlock(); |
| return OK; |
| } |
| |
| ret = priv->ops->start(); |
| if (ret < 0) |
| { |
| net_unlock(); |
| nerr("ERROR: Failed to start Wi-Fi ret=%d\n", ret); |
| return ret; |
| } |
| |
| #ifdef CONFIG_NET_ICMPv6 |
| |
| /* Set up IPv6 multicast address filtering */ |
| |
| wlan_ipv6multicast(priv); |
| #endif |
| |
| IOB_QINIT(&priv->rxb); |
| IOB_QINIT(&priv->txb); |
| |
| priv->dev.d_buf = NULL; |
| priv->dev.d_len = 0; |
| |
| priv->ifup = true; |
| if (g_callback_register_ref == 0) |
| { |
| ret = esp32s3_register_shutdown_handler(esp_wifi_stop_callback); |
| if (ret < 0) |
| { |
| nwarn("WARN: Failed to register handler ret=%d\n", ret); |
| } |
| } |
| |
| ++g_callback_register_ref; |
| net_unlock(); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_ifdown |
| * |
| * Description: |
| * NuttX Callback: Stop the interface. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int wlan_ifdown(struct net_driver_s *dev) |
| { |
| int ret; |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)dev->d_private; |
| |
| net_lock(); |
| |
| if (!priv->ifup) |
| { |
| net_unlock(); |
| return OK; |
| } |
| |
| /* Cancel the TX poll timer and TX timeout timers */ |
| |
| wd_cancel(&priv->txtimeout); |
| |
| /* Mark the device "down" */ |
| |
| priv->ifup = false; |
| |
| iob_free_queue(&priv->rxb); |
| iob_free_queue(&priv->txb); |
| |
| ret = priv->ops->stop(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to stop Wi-Fi ret=%d\n", ret); |
| } |
| |
| --g_callback_register_ref; |
| if (g_callback_register_ref == 0) |
| { |
| ret = esp32s3_unregister_shutdown_handler(esp_wifi_stop_callback); |
| if (ret < 0) |
| { |
| nwarn("WARN: Failed to unregister handler ret=%d\n", ret); |
| } |
| } |
| |
| net_unlock(); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_txavail |
| * |
| * Description: |
| * Driver callback invoked when new TX data is available. This is a |
| * stimulus perform an out-of-cycle poll and, thereby, reduce the TX |
| * latency. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called in normal user mode |
| * |
| ****************************************************************************/ |
| |
| static int wlan_txavail(struct net_driver_s *dev) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)dev->d_private; |
| |
| if (work_available(&priv->txwork)) |
| { |
| /* Schedule to serialize the poll on the worker thread. */ |
| |
| work_queue(WLAN_WORK, &priv->txwork, wlan_txavail_work, priv, 0); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_addmac |
| * |
| * Description: |
| * NuttX Callback: Add the specified MAC address to the hardware multicast |
| * address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be added |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) |
| static int wlan_addmac(struct net_driver_s *dev, const uint8_t *mac) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)dev->d_private; |
| |
| /* Add the MAC address to the hardware multicast routing table */ |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: wlan_rmmac |
| * |
| * Description: |
| * NuttX Callback: Remove the specified MAC address from the |
| * hardware multicast address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be removed |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int wlan_rmmac(struct net_driver_s *dev, const uint8_t *mac) |
| { |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)dev->d_private; |
| |
| /* Add the MAC address to the hardware multicast routing table */ |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: wlan_ipv6multicast |
| * |
| * Description: |
| * Configure the IPv6 multicast MAC address. |
| * |
| * Input Parameters: |
| * priv - A reference to the private driver state structure |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_ICMPv6 |
| static void wlan_ipv6multicast(struct wlan_priv_s *priv) |
| { |
| struct net_driver_s *dev; |
| uint16_t tmp16; |
| uint8_t mac[6]; |
| |
| /* For ICMPv6, we need to add the IPv6 multicast address |
| * |
| * For IPv6 multicast addresses, the Ethernet MAC is derived by |
| * the four low-order octets OR'ed with the MAC 33:33:00:00:00:00, |
| * so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map |
| * to the Ethernet MAC address 33:33:00:01:00:03. |
| * |
| * NOTES: This appears correct for the ICMPv6 Router Solicitation |
| * Message, but the ICMPv6 Neighbor Solicitation message seems to |
| * use 33:33:ff:01:00:03. |
| */ |
| |
| mac[0] = 0x33; |
| mac[1] = 0x33; |
| |
| dev = &priv->dev; |
| tmp16 = dev->d_ipv6addr[6]; |
| mac[2] = 0xff; |
| mac[3] = tmp16 >> 8; |
| |
| tmp16 = dev->d_ipv6addr[7]; |
| mac[4] = tmp16 & 0xff; |
| mac[5] = tmp16 >> 8; |
| |
| ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |
| |
| wlan_addmac(dev, mac); |
| |
| #ifdef CONFIG_NET_ICMPv6_AUTOCONF |
| /* Add the IPv6 all link-local nodes Ethernet address. This is the |
| * address that we expect to receive ICMPv6 Router Advertisement |
| * packets. |
| */ |
| |
| wlan_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet); |
| #endif /* CONFIG_NET_ICMPv6_AUTOCONF */ |
| |
| #ifdef CONFIG_NET_ICMPv6_ROUTER |
| /* Add the IPv6 all link-local routers Ethernet address. This is the |
| * address that we expect to receive ICMPv6 Router Solicitation |
| * packets. |
| */ |
| |
| wlan_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet); |
| #endif /* CONFIG_NET_ICMPv6_ROUTER */ |
| } |
| #endif /* CONFIG_NET_ICMPv6 */ |
| |
| /**************************************************************************** |
| * Name: wlan_ioctl |
| * |
| * Description: |
| * Handle network IOCTL commands directed to this device. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * cmd - The IOCTL command |
| * arg - The argument for the IOCTL command |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NETDEV_IOCTL |
| static int wlan_ioctl(struct net_driver_s *dev, |
| int cmd, |
| unsigned long arg) |
| { |
| int ret; |
| struct iwreq *iwr = (struct iwreq *)arg; |
| struct wlan_priv_s *priv = (struct wlan_priv_s *)dev->d_private; |
| const struct wlan_ops *ops = priv->ops; |
| |
| /* Decode and dispatch the driver-specific IOCTL command */ |
| |
| switch (cmd) |
| { |
| #ifdef CONFIG_NETDEV_PHY_IOCTL |
| #ifdef CONFIG_ARCH_PHY_INTERRUPT |
| case SIOCMIINOTIFY: /* Set up for PHY event notifications */ |
| { |
| struct mii_ioctl_notify_s *req = (struct mii_ioctl_notify_s *)arg; |
| ret = ops->event(req->pid, &req->event); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to subscribe event\n"); |
| } |
| } |
| break; |
| #endif |
| #endif |
| |
| case SIOCSIWENCODEEXT: |
| ret = ops->passwd(iwr, true); |
| |
| break; |
| |
| case SIOCGIWENCODEEXT: |
| ret = ops->passwd(iwr, false); |
| break; |
| |
| case SIOCSIWESSID: |
| if ((iwr->u.essid.flags == IW_ESSID_ON) || |
| (iwr->u.essid.flags == IW_ESSID_DELAY_ON)) |
| { |
| ret = ops->essid(iwr, true); |
| if (ret < 0) |
| { |
| break; |
| } |
| |
| if (iwr->u.essid.flags == IW_ESSID_ON) |
| { |
| ret = ops->connect(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to connect\n"); |
| break; |
| } |
| } |
| } |
| else |
| { |
| ret = ops->disconnect(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to disconnect\n"); |
| break; |
| } |
| } |
| |
| break; |
| |
| case SIOCGIWESSID: /* Get ESSID */ |
| ret = ops->essid(iwr, false); |
| break; |
| |
| case SIOCSIWAP: /* Set access point MAC addresses */ |
| if (iwr->u.ap_addr.sa_data[0] != 0 && |
| iwr->u.ap_addr.sa_data[1] != 0 && |
| iwr->u.ap_addr.sa_data[2] != 0) |
| { |
| ret = ops->bssid(iwr, true); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to set BSSID\n"); |
| break; |
| } |
| |
| ret = ops->connect(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to connect\n"); |
| break; |
| } |
| } |
| else |
| { |
| ret = ops->disconnect(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to disconnect\n"); |
| break; |
| } |
| } |
| |
| break; |
| |
| case SIOCGIWAP: /* Get access point MAC addresses */ |
| ret = ops->bssid(iwr, false); |
| break; |
| |
| case SIOCSIWSCAN: |
| ret = esp_wifi_start_scan(iwr); |
| break; |
| |
| case SIOCGIWSCAN: |
| ret = esp_wifi_get_scan_results(iwr); |
| break; |
| |
| case SIOCSIWCOUNTRY: /* Set country code */ |
| ret = ops->country(iwr, true); |
| break; |
| |
| case SIOCGIWSENS: /* Get sensitivity (dBm) */ |
| ret = ops->rssi(iwr, false); |
| break; |
| |
| case SIOCSIWMODE: /* Set operation mode */ |
| ret = ops->mode(iwr, true); |
| break; |
| |
| case SIOCGIWMODE: /* Get operation mode */ |
| ret = ops->mode(iwr, false); |
| break; |
| |
| case SIOCSIWAUTH: /* Set authentication mode params */ |
| ret = ops->auth(iwr, true); |
| break; |
| |
| case SIOCGIWAUTH: /* Get authentication mode params */ |
| ret = ops->auth(iwr, false); |
| break; |
| |
| case SIOCSIWFREQ: /* Set channel/frequency (MHz) */ |
| ret = ops->freq(iwr, true); |
| break; |
| |
| case SIOCGIWFREQ: /* Get channel/frequency (MHz) */ |
| ret = ops->freq(iwr, false); |
| break; |
| |
| case SIOCSIWRATE: /* Set default bit rate (Mbps) */ |
| wlwarn("WARNING: SIOCSIWRATE not implemented\n"); |
| ret = -ENOSYS; |
| break; |
| |
| case SIOCGIWRATE: /* Get default bit rate (Mbps) */ |
| ret = ops->bitrate(iwr, false); |
| break; |
| |
| case SIOCSIWTXPOW: /* Set transmit power (dBm) */ |
| ret = ops->txpower(iwr, true); |
| break; |
| |
| case SIOCGIWTXPOW: /* Get transmit power (dBm) */ |
| ret = ops->txpower(iwr, false); |
| break; |
| |
| case SIOCGIWRANGE: /* Get range of parameters */ |
| ret = ops->channel(iwr, false); |
| break; |
| |
| default: |
| nerr("ERROR: Unrecognized IOCTL command: %d\n", cmd); |
| ret = -ENOTTY; /* Special return value for this case */ |
| break; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_NETDEV_IOCTL */ |
| |
| /**************************************************************************** |
| * Name: esp32s3_net_initialize |
| * |
| * Description: |
| * Initialize the ESP32-S3 driver |
| * |
| * Input Parameters: |
| * devno - The device number |
| * mac_addr - MAC address |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| static int esp32s3_net_initialize(int devno, uint8_t *mac_addr, |
| const struct wlan_ops *ops) |
| { |
| int ret; |
| struct wlan_priv_s *priv; |
| struct net_driver_s *netdev; |
| |
| priv = &g_wlan_priv[devno]; |
| if (priv->ref) |
| { |
| priv->ref++; |
| return OK; |
| } |
| |
| netdev = &priv->dev; |
| |
| /* Initialize the driver structure */ |
| |
| memset(priv, 0, sizeof(struct wlan_priv_s)); |
| |
| netdev->d_ifup = wlan_ifup; /* I/F down callback */ |
| netdev->d_ifdown = wlan_ifdown; /* I/F up (new IP address) callback */ |
| netdev->d_txavail = wlan_txavail; /* New TX data callback */ |
| #ifdef CONFIG_NET_MCASTGROUP |
| netdev->d_addmac = wlan_addmac; /* Add multicast MAC address */ |
| netdev->d_rmmac = wlan_rmmac; /* Remove multicast MAC address */ |
| #endif |
| #ifdef CONFIG_NETDEV_IOCTL |
| netdev->d_ioctl = wlan_ioctl; /* Handle network IOCTL commands */ |
| #endif |
| |
| /* Used to recover private state from dev */ |
| |
| netdev->d_private = (void *)priv; |
| |
| memcpy(netdev->d_mac.ether.ether_addr_octet, mac_addr, MAC_LEN); |
| |
| ret = netdev_register(netdev, NET_LL_IEEE80211); |
| if (ret < 0) |
| { |
| nerr("ERROR: Initialization of IEEE 802.11 block failed: %d\n", ret); |
| return ret; |
| } |
| |
| priv->ops = ops; |
| |
| priv->ref++; |
| |
| ninfo("INFO: Initialize Wi-Fi adapter No.%d success\n", devno); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: wlan_sta_rx_done |
| * |
| * Description: |
| * Wi-Fi station RX done callback function. If this is called, it means |
| * station receiveing packet. |
| * |
| * Input Parameters: |
| * buffer - Wi-Fi received packet buffer |
| * len - Length of received packet |
| * eb - Wi-Fi receive callback input eb pointer |
| * |
| * Returned Value: |
| * 0 on success or a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef ESP32S3_WLAN_HAS_STA |
| static int wlan_sta_rx_done(void *buffer, uint16_t len, void *eb) |
| { |
| struct wlan_priv_s *priv = &g_wlan_priv[ESP32S3_WLAN_STA_DEVNO]; |
| |
| return wlan_rx_done(priv, buffer, len, eb); |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_sta_tx_done |
| * |
| * Description: |
| * Wi-Fi station TX done callback function. If this is called, it means |
| * station sending next packet. |
| * |
| * Input Parameters: |
| * data - Pointer to the data transmitted. |
| * len - Length of the data transmitted. |
| * status - True if data was transmitted successfully or false if failed. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_sta_tx_done(uint8_t *data, uint16_t *len, bool status) |
| { |
| struct wlan_priv_s *priv = &g_wlan_priv[ESP32S3_WLAN_STA_DEVNO]; |
| |
| wlan_tx_done(priv); |
| } |
| #endif /* ESP32S3_WLAN_HAS_STA */ |
| |
| /**************************************************************************** |
| * Function: wlan_softap_rx_done |
| * |
| * Description: |
| * Wi-Fi softAP RX done callback function. If this is called, it means |
| * softAP receiveing packet. |
| * |
| * Input Parameters: |
| * buffer - Wi-Fi received packet buffer |
| * len - Length of received packet |
| * eb - Wi-Fi receive callback input eb pointer |
| * |
| * Returned Value: |
| * 0 on success or a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef ESP32S3_WLAN_HAS_SOFTAP |
| static int wlan_softap_rx_done(void *buffer, uint16_t len, void *eb) |
| { |
| struct wlan_priv_s *priv = &g_wlan_priv[ESP32S3_WLAN_SOFTAP_DEVNO]; |
| |
| return wlan_rx_done(priv, buffer, len, eb); |
| } |
| |
| /**************************************************************************** |
| * Name: wlan_softap_tx_done |
| * |
| * Description: |
| * Wi-Fi softAP TX done callback function. If this is called, it means |
| * softAP sending next packet. |
| * |
| * Input Parameters: |
| * ifidx - The interface ID that the TX callback has been triggered from. |
| * data - Pointer to the data transmitted. |
| * len - Length of the data transmitted. |
| * status - True if data was transmitted successfully or false if failed. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void wlan_softap_tx_done(uint8_t *data, uint16_t *len, bool status) |
| { |
| struct wlan_priv_s *priv = &g_wlan_priv[ESP32S3_WLAN_SOFTAP_DEVNO]; |
| |
| wlan_tx_done(priv); |
| } |
| #endif /* ESP32S3_WLAN_HAS_SOFTAP */ |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: esp32s3_wlan_sta_set_linkstatus |
| * |
| * Description: |
| * Set Wi-Fi station link status |
| * |
| * Parameters: |
| * linkstatus - true Notifies the networking layer about an available |
| * carrier, false Notifies the networking layer about an |
| * disappeared carrier. |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef ESP32S3_WLAN_HAS_STA |
| int esp32s3_wlan_sta_set_linkstatus(bool linkstatus) |
| { |
| struct wlan_priv_s *priv = &g_wlan_priv[ESP32S3_WLAN_STA_DEVNO]; |
| |
| if (linkstatus == true) |
| { |
| netdev_carrier_on(&priv->dev); |
| } |
| else |
| { |
| netdev_carrier_off(&priv->dev); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: esp32s3_wlan_sta_initialize |
| * |
| * Description: |
| * Initialize the ESP32-S3 WLAN station netcard driver |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| int esp32s3_wlan_sta_initialize(void) |
| { |
| int ret; |
| uint8_t eth_mac[6]; |
| |
| ret = esp_wifi_adapter_init(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Initialize Wi-Fi adapter error: %d\n", ret); |
| return ret; |
| } |
| |
| ret = esp_wifi_sta_read_mac(eth_mac); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read MAC address\n"); |
| return ret; |
| } |
| |
| ninfo("Wi-Fi station MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", |
| eth_mac[0], eth_mac[1], eth_mac[2], |
| eth_mac[3], eth_mac[4], eth_mac[5]); |
| |
| ret = esp32s3_net_initialize(ESP32S3_WLAN_STA_DEVNO, eth_mac, &g_sta_ops); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to initialize net\n"); |
| return ret; |
| } |
| |
| ret = esp_wifi_sta_register_recv_cb(wlan_sta_rx_done); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to register RX callback\n"); |
| return ret; |
| } |
| |
| esp_wifi_sta_register_txdone_cb(wlan_sta_tx_done); |
| |
| ninfo("INFO: Initialize Wi-Fi station success net\n"); |
| |
| return OK; |
| } |
| #endif /* ESP32S3_WLAN_HAS_STA */ |
| |
| /**************************************************************************** |
| * Name: esp32s3_wlan_softap_initialize |
| * |
| * Description: |
| * Initialize the ESP32-S3 WLAN softAP netcard driver |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef ESP32S3_WLAN_HAS_SOFTAP |
| int esp32s3_wlan_softap_initialize(void) |
| { |
| int ret; |
| uint8_t eth_mac[6]; |
| |
| ret = esp_wifi_adapter_init(); |
| if (ret < 0) |
| { |
| nerr("ERROR: Initialize Wi-Fi adapter error: %d\n", ret); |
| return ret; |
| } |
| |
| ret = esp_wifi_softap_read_mac(eth_mac); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read MAC address\n"); |
| return ret; |
| } |
| |
| ninfo("Wi-Fi softAP MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", |
| eth_mac[0], eth_mac[1], eth_mac[2], |
| eth_mac[3], eth_mac[4], eth_mac[5]); |
| |
| ret = esp32s3_net_initialize(ESP32S3_WLAN_SOFTAP_DEVNO, eth_mac, |
| &g_softap_ops); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to initialize net\n"); |
| return ret; |
| } |
| |
| ret = esp_wifi_softap_register_recv_cb(wlan_softap_rx_done); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to register RX callback\n"); |
| return ret; |
| } |
| |
| esp_wifi_softap_register_txdone_cb(wlan_softap_tx_done); |
| |
| ninfo("INFO: Initialize Wi-Fi softAP net success\n"); |
| |
| return OK; |
| } |
| #endif /* ESP32S3_WLAN_HAS_SOFTAP */ |
| |
| #endif /* CONFIG_ESP32S3_WIFI */ |