walnux/drivers/net/netdev_upperhalf.c
Zhe Weng 4b5585a316 netdev/upper: Delay replied packets to prevent TX quota become negated
When our netstack is replying packets when processing RX, the replied
packets will be sent without considering of TX quota because we don't
want to drop them. Now we may delay them by pushing them to a queue and
send them later, which can always keep quota working.

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
2024-08-20 06:37:46 +08:00

1609 lines
42 KiB
C

/****************************************************************************
* drivers/net/netdev_upperhalf.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>
#include <debug.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/mm/iob.h>
#include <nuttx/net/can.h>
#include <nuttx/net/net.h>
#include <nuttx/net/netdev_lowerhalf.h>
#include <nuttx/net/pkt.h>
#include <nuttx/semaphore.h>
#include <nuttx/spinlock.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define NETDEV_TX_CONTINUE 1 /* Return value for devif_poll */
#define NETDEV_THREAD_NAME_FMT "netdev-%s"
#ifdef CONFIG_NETDEV_HPWORK_THREAD
# define NETDEV_WORK HPWORK
#else
# define NETDEV_WORK LPWORK
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure describes the state of the upper half driver */
struct netdev_upperhalf_s
{
FAR struct netdev_lowerhalf_s *lower;
/* Deferring poll work to work queue or thread */
#ifdef CONFIG_NETDEV_WORK_THREAD
pid_t tid;
sem_t sem;
sem_t sem_exit;
#else
struct work_s work;
#endif
/* TX queue for re-queueing replies */
#if CONFIG_IOB_NCHAINS > 0
struct iob_queue_s txq;
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: quota_fetch_inc/dec
*
* Description:
* Fetch the quota and add/sub one to it. It works like atomic_fetch_xxx,
* just because currently we don't have atomic on some platform. We may
* switch to atomic later.
*
****************************************************************************/
static int quota_fetch_inc(FAR struct netdev_lowerhalf_s *lower,
enum netpkt_type_e type)
{
#ifndef CONFIG_HAVE_ATOMICS
irqstate_t flags = spin_lock_irqsave(&lower->lock);
int ret = lower->quota[type]++;
spin_unlock_irqrestore(&lower->lock, flags);
return ret;
#else
return atomic_fetch_add(&lower->quota[type], 1);
#endif
}
static int quota_fetch_dec(FAR struct netdev_lowerhalf_s *lower,
enum netpkt_type_e type)
{
#ifndef CONFIG_HAVE_ATOMICS
irqstate_t flags = spin_lock_irqsave(&lower->lock);
int ret = lower->quota[type]--;
spin_unlock_irqrestore(&lower->lock, flags);
return ret;
#else
return atomic_fetch_sub(&lower->quota[type], 1);
#endif
}
/****************************************************************************
* Name: quota_is_valid
*
* Description:
* Check if the quota of the lower half is not too big.
*
****************************************************************************/
static bool quota_is_valid(FAR struct netdev_lowerhalf_s *lower)
{
int total = 0;
int type;
for (type = 0; type < NETPKT_TYPENUM; type++)
{
total += netdev_lower_quota_load(lower, type);
}
if (total > NETPKT_BUFNUM)
{
nerr("ERROR: Too big quota when registering device: %d\n", total);
return false;
}
if (total > NETPKT_BUFNUM / 2)
{
nwarn("WARNING: The quota of the registering device may consume more "
"than half of the network buffers, which may hurt performance. "
"Please consider decreasing driver quota or increasing nIOB.\n");
}
return true;
}
/****************************************************************************
* Name: netpkt_get
*
* Description:
* Wraps the d_iob of dev to a netpkt.
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static FAR netpkt_t *netpkt_get(FAR struct net_driver_s *dev,
enum netpkt_type_e type)
{
FAR struct netdev_upperhalf_s *upper;
FAR netpkt_t *pkt;
DEBUGASSERT(dev && dev->d_iob);
upper = (FAR struct netdev_upperhalf_s *)dev->d_private;
pkt = dev->d_iob;
netdev_iob_clear(dev);
/* Do not limit quota here (simply relay iob instead of dropping), most
* cases will be limited by netdev_upper_can_tx and seldom reaches here.
*/
if (quota_fetch_dec(upper->lower, type) <= 0)
{
nwarn("WARNING: Allowing temperarily exceeding quota of %s.\n",
dev->d_ifname);
}
return pkt;
}
/****************************************************************************
* Name: netpkt_put
*
* Description:
* Relay IOB to dev.
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static void netpkt_put(FAR struct net_driver_s *dev, FAR netpkt_t *pkt,
enum netpkt_type_e type)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
DEBUGASSERT(dev && pkt);
/* TODO: Using netdev_iob_release instead of netdev_iob_replace now,
* because netdev_iob_replace sets d_len = L3_LEN and d_buf,
* but we don't want these changes.
*/
quota_fetch_inc(upper->lower, type);
netdev_iob_release(dev);
dev->d_iob = pkt;
dev->d_len = netpkt_getdatalen(upper->lower, pkt);
}
/****************************************************************************
* Name: netdev_upper_alloc
*
* Description:
* Get the upper half of lower half structure, create one if not present.
*
****************************************************************************/
static FAR struct netdev_upperhalf_s *
netdev_upper_alloc(FAR struct netdev_lowerhalf_s *dev)
{
/* Allocate the upper-half data structure */
FAR struct netdev_upperhalf_s *upper;
DEBUGASSERT(dev != NULL && dev->netdev.d_private == NULL);
upper = kmm_zalloc(sizeof(struct netdev_upperhalf_s));
if (upper == NULL)
{
nerr("ERROR: Allocation failed\n");
return NULL;
}
upper->lower = dev;
dev->netdev.d_private = upper;
return upper;
}
/****************************************************************************
* Name: netdev_upper_can_tx
*
* Description:
* Check if we allow tx on this device.
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static inline bool netdev_upper_can_tx(FAR struct netdev_upperhalf_s *upper)
{
return netdev_lower_quota_load(upper->lower, NETPKT_TX) > 0;
}
/****************************************************************************
* Name: netdev_upper_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 packet send is complete
* 2. When the preceding TX packet send times out and the interface is
* reset
* 3. During normal TX polling
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* Negated errno value - Error number that occurs.
* NETDEV_TX_CONTINUE - Driver can send more, continue the poll.
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static int netdev_upper_txpoll(FAR struct net_driver_s *dev)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
FAR struct netdev_lowerhalf_s *lower = upper->lower;
FAR netpkt_t *pkt;
int ret;
DEBUGASSERT(dev->d_len > 0);
NETDEV_TXPACKETS(dev);
#ifdef CONFIG_NET_PKT
/* When packet sockets are enabled, feed the tx frame into it */
pkt_input(dev);
#endif
pkt = netpkt_get(dev, NETPKT_TX);
if (netpkt_getdatalen(lower, pkt) > NETDEV_PKTSIZE(dev))
{
nerr("ERROR: Packet too long to send!\n");
ret = -EMSGSIZE;
}
else
{
ret = lower->ops->transmit(lower, pkt);
}
if (ret != OK)
{
/* Stop polling on any error
* REVISIT: maybe store the pkt in upper half and retry later?
*/
NETDEV_TXERRORS(dev);
netpkt_put(dev, pkt, NETPKT_TX);
return ret;
}
return NETDEV_TX_CONTINUE;
}
/****************************************************************************
* Name: netdev_upper_tx
*
* Description:
* Do the actual transmission of packets, including pre-queued packets and
* packets from the network stack.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* Negated errno value - Error number that occurs.
* NETDEV_TX_CONTINUE - Driver can send more, continue the poll.
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static int netdev_upper_tx(FAR struct net_driver_s *dev)
{
#if CONFIG_IOB_NCHAINS > 0
FAR struct netdev_upperhalf_s *upper = dev->d_private;
if (!IOB_QEMPTY(&upper->txq))
{
/* Put the packet back to the device */
netdev_iob_replace(dev, iob_remove_queue(&upper->txq));
return netdev_upper_txpoll(dev);
}
#endif
/* No more TX packets in queue, poll the net stack to get more packets */
return devif_poll(dev, netdev_upper_txpoll);
}
/****************************************************************************
* Name: netdev_upper_txavail_work
*
* Description:
* Perform an out-of-cycle tx poll on the worker thread.
*
* Input Parameters:
* upper - Reference to the upper half driver structure
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static void netdev_upper_txavail_work(FAR struct netdev_upperhalf_s *upper)
{
FAR struct net_driver_s *dev = &upper->lower->netdev;
/* Ignore the notification if the interface is not yet up */
if (IFF_IS_UP(dev->d_flags))
{
DEBUGASSERT(dev->d_buf == NULL); /* Make sure: IOB only. */
while (netdev_upper_can_tx(upper) &&
netdev_upper_tx(dev) == NETDEV_TX_CONTINUE);
}
}
/****************************************************************************
* Name: netdev_upper_queue_tx
*
* Description:
* Queue a TX packet to the upper half for sending later.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
#if defined(CONFIG_NET_LOOPBACK) || defined(CONFIG_NET_ETHERNET) || \
defined(CONFIG_DRIVERS_IEEE80211) || defined(CONFIG_NET_MBIM)
static void netdev_upper_queue_tx(FAR struct net_driver_s *dev)
{
#if CONFIG_IOB_NCHAINS > 0
FAR struct netdev_upperhalf_s *upper = dev->d_private;
int ret;
if ((ret = iob_tryadd_queue(dev->d_iob, &upper->txq)) >= 0)
{
netdev_iob_clear(dev);
}
else
{
nwarn("WARNING: Failed to queue TX packet, dropping: %d\n", ret);
}
#else
/* Fall back to send the packet directly if we don't have IOB queue. */
netdev_upper_txpoll(dev);
#endif
}
#endif
/****************************************************************************
* Name: eth_input
*
* Description:
* Handle L2 packet input.
*
* Input Parameters:
* dev - Reference to the NuttX network driver state structure
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
#if defined(CONFIG_NET_LOOPBACK) || defined(CONFIG_NET_ETHERNET) || \
defined(CONFIG_DRIVERS_IEEE80211)
static void eth_input(FAR struct net_driver_s *dev)
{
FAR struct eth_hdr_s *eth_hdr = (FAR struct eth_hdr_s *)NETLLBUF;
/* Check if this is an 802.1Q VLAN tagged packet */
if (eth_hdr->type == HTONS(TPID_8021QVLAN))
{
/* Need to remove the 4 octet VLAN Tag, by moving src and dest
* addresses 4 octets to the right, and then read the actual
* ethertype. The VLAN ID and priority fields are currently
* ignored.
*/
memmove((FAR uint8_t *)eth_hdr + 4, eth_hdr,
offsetof(struct eth_hdr_s, type));
dev->d_iob = iob_trimhead(dev->d_iob, 4);
dev->d_len -= 4;
eth_hdr = (FAR struct eth_hdr_s *)NETLLBUF;
}
/* 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");
NETDEV_RXIPV4(dev);
/* Receive an IPv4 packet from the network device */
ipv4_input(dev);
}
else
#endif
#ifdef CONFIG_NET_IPv6
if (eth_hdr->type == HTONS(ETHTYPE_IP6))
{
ninfo("IPv6 frame\n");
NETDEV_RXIPV6(dev);
/* Give the IPv6 packet to the network layer */
ipv6_input(dev);
}
else
#endif
#ifdef CONFIG_NET_ARP
if (eth_hdr->type == HTONS(ETHTYPE_ARP))
{
ninfo("ARP frame\n");
NETDEV_RXARP(dev);
/* Handle ARP packet */
arp_input(dev);
}
else
#endif
{
ninfo("INFO: Dropped, Unknown type: %04x\n", eth_hdr->type);
NETDEV_RXDROPPED(dev);
dev->d_len = 0;
}
/* 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 (dev->d_len > 0)
{
/* And queue the packet for sending later.
* Note: RX is tried before TX, so we don't need to call txavail here.
*/
netdev_upper_queue_tx(dev);
}
}
#endif
/****************************************************************************
* Name: ip_input
*
* Description:
* Handle L3 packet input.
*
* Input Parameters:
* dev - Reference to the NuttX network driver state structure
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
#ifdef CONFIG_NET_MBIM
static void ip_input(FAR struct net_driver_s *dev)
{
/* We only accept IP packets of the configured type */
#ifdef CONFIG_NET_IPv4
if ((IPv4BUF->vhl & IP_VERSION_MASK) == IPv4_VERSION)
{
ninfo("IPv4 frame\n");
NETDEV_RXIPV4(dev);
/* Receive an IPv4 packet from the network device */
ipv4_input(dev);
}
else
#endif
#ifdef CONFIG_NET_IPv6
if ((IPv6BUF->vtc & IP_VERSION_MASK) == IPv6_VERSION)
{
ninfo("IPv6 frame\n");
NETDEV_RXIPV6(dev);
/* Give the IPv6 packet to the network layer */
ipv6_input(dev);
}
else
#endif
{
ninfo("INFO: Dropped, Unknown type\n");
NETDEV_RXDROPPED(dev);
dev->d_len = 0;
}
/* 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 (dev->d_len > 0)
{
/* And queue the packet for sending later.
* Note: RX is tried before TX, so we don't need to call txavail here.
*/
netdev_upper_queue_tx(dev);
}
}
#endif
/****************************************************************************
* Function: netdev_upper_rxpoll_work
*
* Description:
* Try to receive packets from device and pass packets into IP
* stack and send packets which is from IP stack if necessary.
*
* Input Parameters:
* upper - Reference to the upper half driver structure
*
* Assumptions:
* Called with the network locked.
*
****************************************************************************/
static void netdev_upper_rxpoll_work(FAR struct netdev_upperhalf_s *upper)
{
FAR struct netdev_lowerhalf_s *lower = upper->lower;
FAR struct net_driver_s *dev = &lower->netdev;
FAR netpkt_t *pkt;
/* Loop while receive() successfully retrieves valid Ethernet frames. */
while ((pkt = lower->ops->receive(lower)) != NULL)
{
NETDEV_RXPACKETS(dev);
if (!IFF_IS_UP(dev->d_flags))
{
/* Interface down, drop frame */
NETDEV_RXDROPPED(dev);
netpkt_free(lower, pkt, NETPKT_RX);
continue;
}
netpkt_put(dev, pkt, NETPKT_RX);
#ifdef CONFIG_NET_PKT
/* When packet sockets are enabled, feed the frame into the tap */
pkt_input(dev);
#endif
switch (dev->d_lltype)
{
#ifdef CONFIG_NET_LOOPBACK
case NET_LL_LOOPBACK:
#endif
#ifdef CONFIG_NET_ETHERNET
case NET_LL_ETHERNET:
#endif
#ifdef CONFIG_DRIVERS_IEEE80211
case NET_LL_IEEE80211:
#endif
#if defined(CONFIG_NET_LOOPBACK) || defined(CONFIG_NET_ETHERNET) || \
defined(CONFIG_DRIVERS_IEEE80211)
eth_input(dev);
break;
#endif
#ifdef CONFIG_NET_MBIM
case NET_LL_MBIM:
ip_input(dev);
break;
#endif
#ifdef CONFIG_NET_CAN
case NET_LL_CAN:
ninfo("CAN frame");
can_input(dev);
break;
#endif
default:
nerr("Unknown link type %d\n", dev->d_lltype);
break;
}
}
}
/****************************************************************************
* Name: netdev_upper_txavail_work
*
* Description:
* Perform an out-of-cycle poll on a dedicated thread or the worker thread.
*
* Input Parameters:
* arg - Reference to the upper half driver structure (cast to void *)
*
****************************************************************************/
static void netdev_upper_work(FAR void *arg)
{
FAR struct netdev_upperhalf_s *upper = arg;
/* RX may release quota and driver buffer, so do RX first. */
net_lock();
netdev_upper_rxpoll_work(upper);
netdev_upper_txavail_work(upper);
net_unlock();
}
/****************************************************************************
* Name: netdev_upper_loop
*
* Description:
* The loop for dedicated thread.
*
****************************************************************************/
#ifdef CONFIG_NETDEV_WORK_THREAD
static int netdev_upper_loop(int argc, FAR char *argv[])
{
FAR struct netdev_upperhalf_s *upper =
(FAR struct netdev_upperhalf_s *)((uintptr_t)strtoul(argv[1], NULL, 16));
while (nxsem_wait(&upper->sem) == OK && upper->tid != INVALID_PROCESS_ID)
{
netdev_upper_work(upper);
}
nwarn("WARNING: Netdev work thread quitting.");
nxsem_post(&upper->sem_exit);
return 0;
}
#endif
/****************************************************************************
* Name: netdev_upper_queue_work
*
* Description:
* Called when there is any work to do.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
****************************************************************************/
static inline void netdev_upper_queue_work(FAR struct net_driver_s *dev)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
#ifdef CONFIG_NETDEV_WORK_THREAD
int semcount;
if (nxsem_get_value(&upper->sem, &semcount) == OK && semcount <= 0)
{
nxsem_post(&upper->sem);
}
#else
if (work_available(&upper->work))
{
/* Schedule to serialize the poll on the worker thread. */
work_queue(NETDEV_WORK, &upper->work, netdev_upper_work, upper, 0);
}
#endif
}
/****************************************************************************
* Name: netdev_upper_txavail
*
* Description:
* Called when new TX data is available.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
****************************************************************************/
static int netdev_upper_txavail(FAR struct net_driver_s *dev)
{
netdev_upper_queue_work(dev);
return OK;
}
/****************************************************************************
* Name: netdev_upper_wireless_ioctl
*
* Description:
* Support for wireless handlers in ioctl.
*
****************************************************************************/
#ifdef CONFIG_NETDEV_WIRELESS_HANDLER
int netdev_upper_wireless_ioctl(FAR struct netdev_lowerhalf_s *lower,
int cmd, unsigned long arg)
{
int ret = -ENOTTY; /* Default to ENOTTY to indicate not serving. */
FAR struct iwreq *iwr = (FAR struct iwreq *)arg;
FAR const struct wireless_ops_s *ops = lower->iw_ops;
struct ether_addr zero;
/* Decode and dispatch the driver-specific IOCTL command */
switch (cmd)
{
case SIOCSIWENCODEEXT: /* Set encoding token & mode */
if (ops->passwd)
{
ret = ops->passwd(lower, iwr, true);
}
break;
case SIOCGIWENCODEEXT: /* Get encoding token & mode */
if (ops->passwd)
{
ret = ops->passwd(lower, iwr, false);
}
break;
case SIOCSIWESSID: /* Set ESSID */
if (ops->essid)
{
if ((iwr->u.essid.flags == IW_ESSID_ON) ||
(iwr->u.essid.flags == IW_ESSID_DELAY_ON))
{
ret = ops->essid(lower, iwr, true);
if (ret < 0)
{
break;
}
if (iwr->u.essid.flags == IW_ESSID_ON)
{
ret = ops->connect(lower);
if (ret < 0)
{
nerr("ERROR: Failed to connect\n");
break;
}
}
}
else
{
ret = ops->disconnect(lower);
if (ret < 0)
{
nerr("ERROR: Failed to disconnect\n");
break;
}
}
}
break;
case SIOCGIWESSID: /* Get ESSID */
if (ops->essid)
{
ret = ops->essid(lower, iwr, false);
}
break;
case SIOCSIWAP: /* Set access point MAC addresses */
if (ops->bssid)
{
memset(&zero, 0, sizeof(zero));
if (memcmp(iwr->u.ap_addr.sa_data, &zero, sizeof(zero)) != 0)
{
ret = ops->bssid(lower, iwr, true);
if (ret < 0)
{
nerr("ERROR: Failed to set BSSID\n");
break;
}
ret = ops->connect(lower);
if (ret < 0)
{
nerr("ERROR: Failed to connect\n");
break;
}
}
else
{
ret = ops->disconnect(lower);
if (ret < 0)
{
nerr("ERROR: Failed to disconnect\n");
break;
}
}
}
break;
case SIOCGIWAP: /* Get access point MAC addresses */
if (ops->bssid)
{
ret = ops->bssid(lower, iwr, false);
}
break;
case SIOCSIWSCAN: /* Trigger scanning */
if (ops->scan)
{
ret = ops->scan(lower, iwr, true);
}
break;
case SIOCGIWSCAN: /* Get scanning results */
if (ops->scan)
{
ret = ops->scan(lower, iwr, false);
}
break;
case SIOCSIWCOUNTRY: /* Set country code */
if (ops->country)
{
ret = ops->country(lower, iwr, true);
}
break;
case SIOCGIWCOUNTRY: /* Get country code */
if (ops->country)
{
ret = ops->country(lower, iwr, false);
}
break;
case SIOCSIWSENS: /* Set sensitivity (dBm) */
if (ops->sensitivity)
{
ret = ops->sensitivity(lower, iwr, true);
}
break;
case SIOCGIWSENS: /* Get sensitivity (dBm) */
if (ops->sensitivity)
{
ret = ops->sensitivity(lower, iwr, false);
}
break;
case SIOCSIWMODE: /* Set operation mode */
if (ops->mode)
{
ret = ops->mode(lower, iwr, true);
}
break;
case SIOCGIWMODE: /* Get operation mode */
if (ops->mode)
{
ret = ops->mode(lower, iwr, false);
}
break;
case SIOCSIWAUTH: /* Set authentication mode params */
if (ops->auth)
{
ret = ops->auth(lower, iwr, true);
}
break;
case SIOCGIWAUTH: /* Get authentication mode params */
if (ops->auth)
{
ret = ops->auth(lower, iwr, false);
}
break;
case SIOCSIWFREQ: /* Set channel/frequency (MHz) */
if (ops->freq)
{
ret = ops->freq(lower, iwr, true);
}
break;
case SIOCGIWFREQ: /* Get channel/frequency (MHz) */
if (ops->freq)
{
ret = ops->freq(lower, iwr, false);
}
break;
case SIOCSIWRATE: /* Set default bit rate (Mbps) */
if (ops->bitrate)
{
ret = ops->bitrate(lower, iwr, true);
}
break;
case SIOCGIWRATE: /* Get default bit rate (Mbps) */
if (ops->bitrate)
{
ret = ops->bitrate(lower, iwr, false);
}
break;
case SIOCSIWTXPOW: /* Set transmit power (dBm) */
if (ops->txpower)
{
ret = ops->txpower(lower, iwr, true);
}
break;
case SIOCGIWTXPOW: /* Get transmit power (dBm) */
if (ops->txpower)
{
ret = ops->txpower(lower, iwr, false);
}
break;
case SIOCGIWRANGE: /* Get range of parameters */
if (ops->range)
{
ret = ops->range(lower, iwr);
}
break;
default:
nerr("ERROR: Unrecognized IOCTL command: %d\n", cmd);
break;
}
return ret;
}
#endif /* CONFIG_NETDEV_WIRELESS_HANDLER */
/****************************************************************************
* Name: netdev_upper_ifup/ifdown/addmac/rmmac/ioctl
*
* Description:
* Called by net stack and relayed to lower half driver.
*
****************************************************************************/
static int netdev_upper_ifup(FAR struct net_driver_s *dev)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
#ifdef CONFIG_NETDEV_WORK_THREAD
/* Try to bring up a dedicated thread for work. */
if (upper->tid <= 0)
{
FAR char *argv[2];
char arg1[32];
char name[32];
snprintf(arg1, sizeof(arg1), "%p", upper);
snprintf(name, sizeof(name), NETDEV_THREAD_NAME_FMT, dev->d_ifname);
argv[0] = arg1;
argv[1] = NULL;
upper->tid = kthread_create(name, CONFIG_NETDEV_WORK_THREAD_PRIORITY,
CONFIG_DEFAULT_TASK_STACKSIZE,
netdev_upper_loop, argv);
if (upper->tid < 0)
{
return upper->tid;
}
}
#endif
if (upper->lower->ops->ifup)
{
return upper->lower->ops->ifup(upper->lower);
}
return -ENOSYS;
}
static int netdev_upper_ifdown(FAR struct net_driver_s *dev)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
#ifndef CONFIG_NETDEV_WORK_THREAD
work_cancel(NETDEV_WORK, &upper->work);
#endif
if (upper->lower->ops->ifdown)
{
return upper->lower->ops->ifdown(upper->lower);
}
return -ENOSYS;
}
#ifdef CONFIG_NET_MCASTGROUP
static int netdev_upper_addmac(FAR struct net_driver_s *dev,
FAR const uint8_t *mac)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
if (upper->lower->ops->addmac)
{
return upper->lower->ops->addmac(upper->lower, mac);
}
return -ENOSYS;
}
#endif
#ifdef CONFIG_NET_MCASTGROUP
static int netdev_upper_rmmac(FAR struct net_driver_s *dev,
FAR const uint8_t *mac)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
if (upper->lower->ops->rmmac)
{
return upper->lower->ops->rmmac(upper->lower, mac);
}
return -ENOSYS;
}
#endif
#ifdef CONFIG_NETDEV_IOCTL
static int netdev_upper_ioctl(FAR struct net_driver_s *dev, int cmd,
unsigned long arg)
{
FAR struct netdev_upperhalf_s *upper = dev->d_private;
FAR struct netdev_lowerhalf_s *lower = upper->lower;
#ifdef CONFIG_NETDEV_WIRELESS_HANDLER
if (lower->iw_ops)
{
int ret = netdev_upper_wireless_ioctl(lower, cmd, arg);
if (ret != -ENOTTY)
{
return ret;
}
}
#endif
if (lower->ops->ioctl)
{
return lower->ops->ioctl(lower, cmd, arg);
}
return -ENOTTY;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: netdev_lower_register
*
* Description:
* Register a network device driver and assign a name to it so that it can
* be found in subsequent network ioctl operations on the device.
*
* Input Parameters:
* dev - The lower half device driver structure to be registered.
* lltype - Link level protocol used by the driver (Ethernet, SLIP, TUN,
* ...
*
* Returned Value:
* 0:Success; negated errno on failure.
*
****************************************************************************/
int netdev_lower_register(FAR struct netdev_lowerhalf_s *dev,
enum net_lltype_e lltype)
{
FAR struct netdev_upperhalf_s *upper;
int ret;
if (dev == NULL || quota_is_valid(dev) == false || dev->ops == NULL ||
dev->ops->transmit == NULL || dev->ops->receive == NULL)
{
return -EINVAL;
}
if ((upper = netdev_upper_alloc(dev)) == NULL)
{
return -ENOMEM;
}
#ifndef CONFIG_HAVE_ATOMICS
spin_initialize(&dev->lock, SP_UNLOCKED);
#endif
dev->netdev.d_ifup = netdev_upper_ifup;
dev->netdev.d_ifdown = netdev_upper_ifdown;
dev->netdev.d_txavail = netdev_upper_txavail;
#ifdef CONFIG_NET_MCASTGROUP
dev->netdev.d_addmac = netdev_upper_addmac;
dev->netdev.d_rmmac = netdev_upper_rmmac;
#endif
#ifdef CONFIG_NETDEV_IOCTL
dev->netdev.d_ioctl = netdev_upper_ioctl;
#endif
dev->netdev.d_private = upper;
ret = netdev_register(&dev->netdev, lltype);
if (ret < 0)
{
kmm_free(upper);
dev->netdev.d_private = NULL;
}
#ifdef CONFIG_NETDEV_WORK_THREAD
nxsem_init(&upper->sem, 0, 0);
nxsem_init(&upper->sem_exit, 0, 0);
#endif
return ret;
}
/****************************************************************************
* Name: netdev_lower_unregister
*
* Description:
* Unregister a network device driver.
*
* Input Parameters:
* dev - The lower half device driver structure to un-register
*
* Returned Value:
* 0:Success; negated errno on failure
*
****************************************************************************/
int netdev_lower_unregister(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct netdev_upperhalf_s *upper;
int ret;
if (dev == NULL || dev->netdev.d_private == NULL)
{
return -EINVAL;
}
upper = (FAR struct netdev_upperhalf_s *)dev->netdev.d_private;
ret = netdev_unregister(&dev->netdev);
if (ret < 0)
{
return ret;
}
#ifdef CONFIG_NETDEV_WORK_THREAD
if (upper->tid > 0)
{
/* Try to tear down the dedicated thread for work. */
upper->tid = INVALID_PROCESS_ID;
nxsem_post(&upper->sem);
nxsem_wait(&upper->sem_exit);
}
nxsem_destroy(&upper->sem);
nxsem_destroy(&upper->sem_exit);
#endif
#if CONFIG_IOB_NCHAINS > 0
iob_free_queue(&upper->txq);
#endif
kmm_free(upper);
dev->netdev.d_private = NULL;
return OK;
}
/****************************************************************************
* Name: netdev_lower_carrier_on
*
* Description:
* Notifies the networking layer about an available carrier.
* (e.g. a cable was plugged in)
*
* Input Parameters:
* dev - The lower half device driver structure
*
****************************************************************************/
void netdev_lower_carrier_on(FAR struct netdev_lowerhalf_s *dev)
{
netdev_carrier_on(&dev->netdev);
}
/****************************************************************************
* Name: netdev_lower_carrier_off
*
* Description:
* Notifies the networking layer about an disappeared carrier.
* (e.g. a cable was unplugged)
*
* Input Parameters:
* dev - The lower half device driver structure
*
****************************************************************************/
void netdev_lower_carrier_off(FAR struct netdev_lowerhalf_s *dev)
{
netdev_carrier_off(&dev->netdev);
}
/****************************************************************************
* Name: netdev_lower_rxready
*
* Description:
* Notifies the networking layer about an RX packet is ready to read.
*
* Input Parameters:
* dev - The lower half device driver structure
*
****************************************************************************/
void netdev_lower_rxready(FAR struct netdev_lowerhalf_s *dev)
{
netdev_upper_queue_work(&dev->netdev);
}
/****************************************************************************
* Name: netdev_lower_txdone
*
* Description:
* Notifies the networking layer about a TX packet is sent.
*
* Input Parameters:
* dev - The lower half device driver structure
*
****************************************************************************/
void netdev_lower_txdone(FAR struct netdev_lowerhalf_s *dev)
{
NETDEV_TXDONE(&dev->netdev);
netdev_upper_queue_work(&dev->netdev);
}
/****************************************************************************
* Name: netdev_lower_quota_load
*
* Description:
* Fetch the quota, works like atomic_load.
*
* Input Parameters:
* dev - The lower half device driver structure
* type - Whether get quota for TX or RX
*
****************************************************************************/
int netdev_lower_quota_load(FAR struct netdev_lowerhalf_s *dev,
enum netpkt_type_e type)
{
#ifndef CONFIG_HAVE_ATOMICS
irqstate_t flags = spin_lock_irqsave(&dev->lock);
int ret = dev->quota[type];
spin_unlock_irqrestore(&dev->lock, flags);
return ret;
#else
return atomic_load(&dev->quota[type]);
#endif
}
/****************************************************************************
* Name: netpkt_alloc
*
* Description:
* Allocate a netpkt structure.
*
* Input Parameters:
* dev - The lower half device driver structure
* type - Whether used for TX or RX
*
* Returned Value:
* Pointer to the packet, NULL on failure
*
****************************************************************************/
FAR netpkt_t *netpkt_alloc(FAR struct netdev_lowerhalf_s *dev,
enum netpkt_type_e type)
{
FAR netpkt_t *pkt;
if (quota_fetch_dec(dev, type) <= 0)
{
quota_fetch_inc(dev, type);
return NULL;
}
pkt = iob_tryalloc(false);
if (pkt == NULL)
{
quota_fetch_inc(dev, type);
return NULL;
}
iob_reserve(pkt, CONFIG_NET_LL_GUARDSIZE);
return pkt;
}
/****************************************************************************
* Name: netpkt_free
*
* Description:
* Release a netpkt structure.
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The packet to release
* type - Whether used for TX or RX
*
****************************************************************************/
void netpkt_free(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt,
enum netpkt_type_e type)
{
quota_fetch_inc(dev, type);
iob_free_chain(pkt);
}
/****************************************************************************
* Name: netpkt_copyin
*
* Description:
* Copy 'len' bytes of data from a buffer into the netpkt, starting at
* 'offset' of netpkt.
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The net packet
* src - The source buffer
* len - How many bytes to copy
* offset - The offset of netpkt to put the data
*
* Returned Value:
* 0:Success; negated errno on failure
*
****************************************************************************/
int netpkt_copyin(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt,
FAR const uint8_t *src, unsigned int len, int offset)
{
return iob_trycopyin(pkt, src, len,
offset - NET_LL_HDRLEN(&dev->netdev), false);
}
/****************************************************************************
* Name: netpkt_copyout
*
* Description:
* Copy 'len' bytes of data from netpkt into a buffer, starting at
* 'offset' of netpkt.
*
* Input Parameters:
* dev - The lower half device driver structure
* dest - The destination buffer
* pkt - The net packet
* len - How many bytes to copy
* offset - The offset of netpkt to get the data
*
* Returned Value:
* 0:Success; negated errno on failure
*
****************************************************************************/
int netpkt_copyout(FAR struct netdev_lowerhalf_s *dev, FAR uint8_t *dest,
FAR const netpkt_t *pkt, unsigned int len, int offset)
{
return iob_copyout(dest, pkt, len, offset - NET_LL_HDRLEN(&dev->netdev));
}
/****************************************************************************
* Name: netpkt_getdata/getbase
*
* Description:
* Get the pointer of data/base in a netpkt, used when NETPKT_BUFLEN is
* big enough to fit a full packet in.
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The net packet
*
* Returned Value:
* Pointer data/base, NULL on failure.
*
****************************************************************************/
FAR uint8_t *netpkt_getdata(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt)
{
return IOB_DATA(pkt) - NET_LL_HDRLEN(&dev->netdev);
}
FAR uint8_t *netpkt_getbase(FAR netpkt_t *pkt)
{
return pkt->io_data;
}
/****************************************************************************
* Name: netpkt_setdatalen
*
* Description:
* Set the length of data in netpkt, used when data is written into
* netpkt by data/base pointer, no need to set this length after
* copyin.
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The net packet
* len - The length of data in netpkt
*
* Returned Value:
* The new effective data length, or a negated errno value on error.
*
****************************************************************************/
int netpkt_setdatalen(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt, unsigned int len)
{
uint8_t llhdrlen = NET_LL_HDRLEN(&dev->netdev);
int ret = iob_update_pktlen(pkt, len - llhdrlen, false);
return ret >= 0 ? ret + llhdrlen : ret;
}
/****************************************************************************
* Name: netpkt_getdatalen
*
* Description:
* Get the length of data in netpkt.
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The net packet
*
* Returned Value:
* The length of data in netpkt.
*
****************************************************************************/
unsigned int netpkt_getdatalen(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt)
{
return pkt->io_pktlen + NET_LL_HDRLEN(&dev->netdev);
}
/****************************************************************************
* Name: netpkt_reset_reserved
*
* Description:
* Reset the reserved length (the starting point of data) of netpkt
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The net packet
* len - The reserved length
*
****************************************************************************/
void netpkt_reset_reserved(FAR struct netdev_lowerhalf_s *dev,
FAR netpkt_t *pkt, unsigned int len)
{
iob_reserve(pkt, len + NET_LL_HDRLEN(&dev->netdev));
}
/****************************************************************************
* Name: netpkt_is_fragmented
*
* Description:
* Returns whether the netpkt is fragmented into different blocks.
* In other words, NETPKT_BUFLEN < reserved + total data
*
* Input Parameters:
* pkt - The net packet
*
****************************************************************************/
bool netpkt_is_fragmented(FAR netpkt_t *pkt)
{
return pkt->io_flink != NULL;
}
/****************************************************************************
* Name: netpkt_to_iov
*
* Description:
* Write each piece of data/len into iov array.
*
* Input Parameters:
* dev - The lower half device driver structure
* pkt - The net packet
* iov - The iov array to write
* iovcnt - The number of elements in the iov array
*
* Returned Value:
* The actual written count of iov entries.
*
****************************************************************************/
int netpkt_to_iov(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt,
FAR struct iovec *iov, int iovcnt)
{
int i;
for (i = 0; pkt != NULL && i < iovcnt; pkt = pkt->io_flink, i++)
{
if (i == 0)
{
iov[i].iov_base = IOB_DATA(pkt) - NET_LL_HDRLEN(&dev->netdev);
iov[i].iov_len = pkt->io_len + NET_LL_HDRLEN(&dev->netdev);
}
else
{
iov[i].iov_base = IOB_DATA(pkt);
iov[i].iov_len = pkt->io_len;
}
}
return i;
}