700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 嵌入式Linux开发-网络设备驱动

嵌入式Linux开发-网络设备驱动

时间:2021-04-20 01:47:41

相关推荐

嵌入式Linux开发-网络设备驱动

0.前言

离职已经有一段时间了,现在疫情还没过去,能不出门还是别出门,天天感觉度日如年,现在也没有去找工作,最近正好在翻LDD3,之前工作移植过AR8031的驱动程序,正好趁这段时间记录总结一下,毕竟好记性不如烂笔头。

1.Linux网络设备驱动结构

Linux网络设备驱动程序的体系结构,从上到下分为4层:

网络协议接口层

向网络层协议提供统一的数据包收发接口,通过dev_queue_xmit()函数发送数据,通过netif_rx()函数接收数据。使上层协议独立于具体的设备

网络设备接口层

向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。

提供实际功能的设备驱动功能层

该层的各函数是网络设备接口层net_device数据结构体的具体成员,是网络设备硬件完成相应动作的程序,通过ndo_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。

网络设备与媒介层(对于Linux系统而言,网络设备和媒介都可以是虚拟的)

完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。

网络协议接口层

网络协议接口层最主要的功能是给上层协议提供透明的数据包发送和接收接口。当上层ARP或IP需要发送数据包时,将调用网络协议接口层的dev_queue_xmit()函数发送数据包,同时需要传递给该函数一个指向struct sk_buff数据接口的指针。上层对数据包的接收也是通过向nentif_rx()函数传递一个strcut sk_buff数据结构的指针完成。

当发送数据包时,Linux内核的网络处理模块必须建立一个包含有要传输的数据包的sk_buff,然后将sk_buff传递给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样,当网络设备从网络媒介上接收到数据包后,必须将接收到的数据转换给sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。

网络设备接口层

为千变万化的网络设备定义统一、抽象的数据结构net_device结构体。

net_device结构体在内核中指代一个网络设备,定义于include/linux/netdevice.h文件中,网络设备驱动程序只需通过填充net_device的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。

2.网络设备驱动程序

设备注册

网络设备驱动程序在其模块初始化函数中的注册方法,和字符驱动程序不同。因为对网络接口来讲,没有和主设备号及次设备号等价的东西,所以网络驱动程序不必请求这种设备号。驱动程序对每个新检测到的接口,向全局的网络设备链表中插入一个数据接构。

每个接口由一个net_device结构描述。由于和其他结构相关,因此必须被动态分配。用来执行分配的内核函数是alloc_netdev_mqs。

struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,unsigned char name_assign_type,void (*setup)(struct net_device *),unsigned int txqs, unsigned int rxqs)

sizeof_priv是驱动程序的“私有数据”区的大小,这个区成员和net_device结构一同分配给网络设备。name是接口的名字,其在用户空间可见。steup是一个初始化函数,用来设置net_device结构剩余的部分。txqs是分配的TX子队列的数目。rxqs是分配的RX子队列的数目。

必须检查函数的返回值,以确定分配工作成功完成。

一旦net_device结构被初始化后,剩余的工作就是将该结构体传递给register_netdev函数。

ret = register_netdev(ndev);if (ret)goto failed_register;

当调用register_netdev函数后,就可以调用驱动程序操作设备了。因此必须在初始化一切事情后再注册。

static const struct net_device_ops fec_netdev_ops = {.ndo_open= fec_enet_open,.ndo_stop= fec_enet_close,.ndo_start_xmit= fec_enet_start_xmit,.ndo_set_rx_mode= set_multicast_list,.ndo_validate_addr= eth_validate_addr,.ndo_tx_timeout= fec_timeout,.ndo_set_mac_address= fec_set_mac_address,.ndo_do_ioctl= fec_enet_ioctl,#ifdef CONFIG_NET_POLL_CONTROLLER.ndo_poll_controller= fec_poll_controller,#endif.ndo_set_features= fec_set_features,};static int fec_enet_init(struct net_device *ndev){struct fec_enet_private *fep = netdev_priv(ndev);struct bufdesc *cbd_base;dma_addr_t bd_dma;int bd_size;unsigned int i;unsigned dsize = fep->bufdesc_ex ? sizeof(struct bufdesc_ex) :sizeof(struct bufdesc);unsigned dsize_log2 = __fls(dsize);int ret;WARN_ON(dsize != (1 << dsize_log2));#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)fep->rx_align = 0xf;fep->tx_align = 0xf;#elsefep->rx_align = 0x3;fep->tx_align = 0x3;#endif/* Check mask of the streaming and coherent API */ret = dma_set_mask_and_coherent(&fep->pdev->dev, DMA_BIT_MASK(32));if (ret < 0) {dev_warn(&fep->pdev->dev, "No suitable DMA available\n");return ret;}fec_enet_alloc_queue(ndev);bd_size = (fep->total_tx_ring_size + fep->total_rx_ring_size) * dsize;/* Allocate memory for buffer descriptors. */cbd_base = dmam_alloc_coherent(&fep->pdev->dev, bd_size, &bd_dma,GFP_KERNEL);if (!cbd_base) {return -ENOMEM;}/* Get the Ethernet address */fec_get_mac(ndev);/* make sure MAC we just acquired is programmed into the hw */fec_set_mac_address(ndev, NULL);/* Set receive and transmit descriptor base. */for (i = 0; i < fep->num_rx_queues; i++) {struct fec_enet_priv_rx_q *rxq = fep->rx_queue[i];unsigned size = dsize * rxq->bd.ring_size;rxq->bd.qid = i;rxq->bd.base = cbd_base;rxq->bd.cur = cbd_base;rxq->bd.dma = bd_dma;rxq->bd.dsize = dsize;rxq->bd.dsize_log2 = dsize_log2;rxq->bd.reg_desc_active = fep->hwp + offset_des_active_rxq[i];bd_dma += size;cbd_base = (struct bufdesc *)(((void *)cbd_base) + size);rxq->bd.last = (struct bufdesc *)(((void *)cbd_base) - dsize);}for (i = 0; i < fep->num_tx_queues; i++) {struct fec_enet_priv_tx_q *txq = fep->tx_queue[i];unsigned size = dsize * txq->bd.ring_size;txq->bd.qid = i;txq->bd.base = cbd_base;txq->bd.cur = cbd_base;txq->bd.dma = bd_dma;txq->bd.dsize = dsize;txq->bd.dsize_log2 = dsize_log2;txq->bd.reg_desc_active = fep->hwp + offset_des_active_txq[i];bd_dma += size;cbd_base = (struct bufdesc *)(((void *)cbd_base) + size);txq->bd.last = (struct bufdesc *)(((void *)cbd_base) - dsize);}/* The FEC Ethernet specific entries in the device structure */ndev->watchdog_timeo = TX_TIMEOUT;ndev->netdev_ops = &fec_netdev_ops;ndev->ethtool_ops = &fec_enet_ethtool_ops;writel(FEC_RX_DISABLED_IMASK, fep->hwp + FEC_IMASK);netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi, NAPI_POLL_WEIGHT);if (fep->quirks & FEC_QUIRK_HAS_VLAN)/* enable hw VLAN support */ndev->features |= NETIF_F_HW_VLAN_CTAG_RX;if (fep->quirks & FEC_QUIRK_HAS_CSUM) {ndev->gso_max_segs = FEC_MAX_TSO_SEGS;/* enable hw accelerator */ndev->features |= (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM| NETIF_F_RXCSUM | NETIF_F_SG | NETIF_F_TSO);fep->csum_flags |= FLAG_RX_CSUM_ENABLED;}if (fep->quirks & FEC_QUIRK_HAS_AVB) {fep->tx_align = 0;fep->rx_align = 0x3f;}ndev->hw_features = ndev->features;fec_restart(ndev);if (fep->quirks & FEC_QUIRK_MIB_CLEAR)fec_enet_clear_ethtool_stats(ndev);elsefec_enet_update_ethtool_stats(ndev);return 0;}

static intfec_probe(struct platform_device *pdev){.../*分配网络设备*/ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private) +FEC_STATS_SIZE, num_tx_qs, num_rx_qs);if (!ndev)return -ENOMEM;SET_NETDEV_DEV(ndev, &pdev->dev);...ret = fec_enet_init(ndev);if (ret)goto failed_init;.../*网络设备注册*/ret = register_netdev(ndev);if (ret)goto failed_register;device_init_wakeup(&ndev->dev, fep->wol_flag &FEC_WOL_HAS_MAGIC_PACKET);...return ret;}

模块的卸载

unregister_netdev函数从系统中删除接口,free_netdev函数将net_device结构返回给系统。

只有当设备被注销后,内部的清除函数才能被执行。必须在将net_device结构返回给系统之前执行,一旦调用了free_netdevice,则不能再对设备或者私有数据区进行引用。

static intfec_drv_remove(struct platform_device *pdev){struct net_device *ndev = platform_get_drvdata(pdev);struct fec_enet_private *fep = netdev_priv(ndev);struct device_node *np = pdev->dev.of_node;int ret;ret = pm_runtime_get_sync(&pdev->dev);if (ret < 0)return ret;cancel_work_sync(&fep->tx_timeout_work);fec_ptp_stop(pdev);unregister_netdev(ndev);fec_enet_mii_remove(fep);if (fep->reg_phy)regulator_disable(fep->reg_phy);if (of_phy_is_fixed_link(np))of_phy_deregister_fixed_link(np);of_node_put(fep->phy_node);free_netdev(ndev);clk_disable_unprepare(fep->clk_ahb);clk_disable_unprepare(fep->clk_ipg);pm_runtime_put_noidle(&pdev->dev);pm_runtime_disable(&pdev->dev);return 0;}

打开和关闭 open函数

在接口能够传输数据包之前,内核必须打开接口并赋予其地址。

在接口能够和外界通讯之前,要将硬件地址(MAC)从硬件设备复制到dev->dev_addr。硬件地址可在打开期间拷贝到设备中。

一旦准备好开始发送数据后,open方法还应该启动接口的传输队列(允许接口接受传输数据包)。内核提供如下函数可启动该队列。

static inline void netif_tx_start_all_queues(struct net_device *dev){unsigned int i;for (i = 0; i < dev->num_tx_queues; i++) {struct netdev_queue *txq = netdev_get_tx_queue(dev, i);netif_tx_start_queue(txq);}}

static __always_inline void netif_tx_start_queue(struct netdev_queue *dev_queue){clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);}

static intfec_enet_open(struct net_device *ndev){struct fec_enet_private *fep = netdev_priv(ndev);int ret;bool reset_again;...ret = fec_enet_alloc_buffers(ndev);if (ret)goto err_enet_alloc;/* 初始化MAC */fec_restart(ndev);/* Probe and connect to PHY when open the interface */ret = fec_enet_mii_probe(ndev);if (ret)goto err_enet_mii_probe;/*启动传输队列*/netif_tx_start_all_queues(ndev);device_set_wakeup_enable(&ndev->dev, fep->wol_flag &FEC_WOL_FLAG_ENABLE);return 0;err_enet_mii_probe:fec_enet_free_buffers(ndev);err_enet_alloc:fec_enet_clk_enable(ndev, false);clk_enable:pm_runtime_mark_last_busy(&fep->pdev->dev);pm_runtime_put_autosuspend(&fep->pdev->dev);pinctrl_pm_select_sleep_state(&fep->pdev->dev);return ret;}

close函数

close函数是open函数的反操作。

static __always_inline void netif_tx_stop_queue(struct netdev_queue *dev_queue);

是netif_tx_start_queue的逆操作,它标记设备不能传输其他数据包。在接口被关闭时,必须调用该函数,但该函数也可以用来临时停止传输。

static inline void netif_tx_disable(struct net_device *dev){unsigned int i;int cpu;local_bh_disable();cpu = smp_processor_id();for (i = 0; i < dev->num_tx_queues; i++) {struct netdev_queue *txq = netdev_get_tx_queue(dev, i);__netif_tx_lock(txq, cpu);netif_tx_stop_queue(txq);__netif_tx_unlock(txq);}local_bh_enable();}

static __always_inline void netif_tx_stop_queue(struct netdev_queue *dev_queue){set_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);}

static intfec_enet_close(struct net_device *ndev){struct fec_enet_private *fep = netdev_priv(ndev);phy_stop(ndev->phydev);if (netif_device_present(ndev)) {napi_disable(&fep->napi);netif_tx_disable(ndev);fec_stop(ndev);}phy_disconnect(ndev->phydev);if (fep->quirks & FEC_QUIRK_ERR006687)imx6q_cpuidle_fec_irqs_unused();fec_enet_update_ethtool_stats(ndev);fec_enet_clk_enable(ndev, false);pinctrl_pm_select_sleep_state(&fep->pdev->dev);pm_runtime_mark_last_busy(&fep->pdev->dev);pm_runtime_put_autosuspend(&fep->pdev->dev);fec_enet_free_buffers(ndev);return 0;}

数据包传输

内核要传输一个数据包,会调用驱动程序的ndo_start_xmit将数据放入外发队列。内核处理的每个数据包位于一个套接字缓冲区结构(sk_buff)中。所有套接字的输入/输出缓冲区都是sk_buff结构形成的链表。

传递给ndo_start_xmit的套接字缓冲区包含了物理数据包(以它在介质上的格式),并拥有完整的传输层数据包头。skb->data指向要传输的数据包,skb->len是以octet为单位的长度。

static intfec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev){struct fec_enet_private *fep = netdev_priv(dev);struct bufdesc *bdp;void *bufaddr;unsigned shortstatus;unsigned longestatus;unsigned long flags;.../*禁用中断*/spin_lock_irqsave(&fep->hw_lock, flags);/*TX ring的入口*/bdp = fep->cur_tx;status = bdp->cbd_sc;.../*设置buffer的长度和buffer的指针*/bufaddr = skb->data;bdp->cbd_datlen = skb->len;/*4字节对齐*/if (((unsigned long) bufaddr) & FEC_ALIGNMENT) {unsigned int index;index = bdp - fep->tx_bd_base;memcpy(fep->tx_bounce[index], (void *)skb->data, skb->len);bufaddr = fep->tx_bounce[index];}.../* Save skb pointer */fep->tx_skbuff[fep->skb_cur] = skb;dev->stats.tx_bytes += skb->len;fep->skb_cur = (fep->skb_cur+1) & TX_RING_MOD_MASK;/* 建立流式DMA映射*/bdp->cbd_bufaddr = dma_map_single(&dev->dev, bufaddr,FEC_ENET_TX_FRSIZE, DMA_TO_DEVICE);/* 告诉FEC已经准备好,发送完成后中断*/status |= (BD_ENET_TX_READY | BD_ENET_TX_INTR| BD_ENET_TX_LAST | BD_ENET_TX_TC);bdp->cbd_sc = status;/*触发传输*/writel(0, fep->hwp + FEC_X_DES_ACTIVE);/* If this was the last BD in the ring, start at the beginning again. */if (status & BD_ENET_TX_WRAP)bdp = fep->tx_bd_base;elsebdp++;if (bdp == fep->dirty_tx) {fep->tx_full = 1;/*通知网络系统在硬件能够接受新数据之前,不能启动其他的数据包传输*/netif_stop_queue(dev);}fep->cur_tx = bdp;spin_unlock_irqrestore(&fep->hw_lock, flags);return NETDEV_TX_OK;}

static voidfec_enet_tx(struct net_device *dev){structfec_enet_private *fep;struct fec_ptp_private *fpp;struct bufdesc *bdp;unsigned short status;unsigned long estatus;structsk_buff*skb;fep = netdev_priv(dev);fpp = fep->ptp_priv;spin_lock(&fep->hw_lock);bdp = fep->dirty_tx;while (((status = bdp->cbd_sc) & BD_ENET_TX_READY) == 0) {.../*删除DMA映射*/dma_unmap_single(&dev->dev, bdp->cbd_bufaddr, FEC_ENET_TX_FRSIZE, DMA_TO_DEVICE);bdp->cbd_bufaddr = 0;skb = fep->tx_skbuff[fep->skb_dirty];/*检查错误*/if (status & (BD_ENET_TX_HB | BD_ENET_TX_LC |BD_ENET_TX_RL | BD_ENET_TX_UN |BD_ENET_TX_CSL)) {dev->stats.tx_errors++;if (status & BD_ENET_TX_HB) /* No heartbeat */dev->stats.tx_heartbeat_errors++;if (status & BD_ENET_TX_LC) /* Late collision */dev->stats.tx_window_errors++;if (status & BD_ENET_TX_RL) /* Retrans limit */dev->stats.tx_aborted_errors++;if (status & BD_ENET_TX_UN) /* Underrun */dev->stats.tx_fifo_errors++;if (status & BD_ENET_TX_CSL) /* Carrier lost */dev->stats.tx_carrier_errors++;} else {dev->stats.tx_packets++;}if (status & BD_ENET_TX_READY)printk("HEY! Enet xmit interrupt and TX_READY.\n");/* Deferred means some collisions occurred during transmit,* but we eventually sent the packet OK.*/if (status & BD_ENET_TX_DEF)dev->stats.collisions++;#if defined(CONFIG_ENHANCED_BD)if (fep->ptimer_present) {estatus = bdp->cbd_esc;if (estatus & BD_ENET_TX_TS)fec_ptp_store_txstamp(fpp, skb, bdp);}#elif defined(CONFIG_IN_BAND)if (fep->ptimer_present) {if (status & BD_ENET_TX_PTP)fec_ptp_store_txstamp(fpp, skb, bdp);}#endif/*释放skb缓冲区*/dev_kfree_skb_any(skb);fep->tx_skbuff[fep->skb_dirty] = NULL;fep->skb_dirty = (fep->skb_dirty + 1) & TX_RING_MOD_MASK;/* 更新指针指向下一个要传输的缓冲区描述符*/if (status & BD_ENET_TX_WRAP)bdp = fep->tx_bd_base;elsebdp++;/* Since we have freed up a buffer, the ring is no longer full*/if (fep->tx_full) {fep->tx_full = 0;if (netif_queue_stopped(dev))/*并发传输,通知网络系统再次开始传输数据包*/netif_wake_queue(dev);}}fep->dirty_tx = bdp;spin_unlock(&fep->hw_lock);}

数据包接收

从网络上接收数据要比传输数据复杂一些,因为必须要在原子上下文中分配一个sk_buff并传递给上层处理。网络驱动程序实现了两种模式接收数据包:中断驱动方式和轮询方式。

(1).更新其统计计数器,以记录已接收到的每个数据包,主要成员rx_packets、rx_bytes等。

(2).配一个保存数据包的缓冲区,缓冲区的分配函数需要知道数据长度。

(3).一旦拥有一个合法的sk_buff指针,则调用memcpy将数据包数据拷贝到缓冲区内。

(4).在处理数据包之前,网络层必须知道数据包的一些信息,为此必须将缓冲区传递到上层之前,对dev和protocol成员正确赋值。

(5).最后,由netif_rx执行,将套接字缓冲区传递给上层软件处理。

static voidfec_enet_rx(struct net_device *dev){structfec_enet_private *fep = netdev_priv(dev);struct fec_ptp_private *fpp = fep->ptp_priv;struct bufdesc *bdp;unsigned short status;structsk_buff*skb;ushortpkt_len;__u8 *data;#ifdef CONFIG_M532xflush_cache_all();#endifspin_lock(&fep->hw_lock);/*获取传入数据包的信息*/bdp = fep->cur_rx;while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) {.../*检查错误*/#if 0if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO |BD_ENET_RX_CR | BD_ENET_RX_OV)) {#elseif (status & (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO | BD_ENET_RX_OV)) {#endifdev->stats.rx_errors++;if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH)) {/* Frame too long or too short. */dev->stats.rx_length_errors++;}if (status & BD_ENET_RX_NO)/* Frame alignment */dev->stats.rx_frame_errors++;if (status & BD_ENET_RX_CR)/* CRC Error */dev->stats.rx_crc_errors++;if (status & BD_ENET_RX_OV)/* FIFO overrun */dev->stats.rx_fifo_errors++;}/* Report late collisions as a frame error.* On this error, the BD is closed, but we don't know what we* have in the buffer. So, just drop this frame on the floor.*/if (status & BD_ENET_RX_CL) {dev->stats.rx_errors++;dev->stats.rx_frame_errors++;goto rx_processing_done;}/*更新统计计数器,传输的octet总量,数据的地址*/dev->stats.rx_packets++;pkt_len = bdp->cbd_datlen;dev->stats.rx_bytes += pkt_len;data = (__u8*)__va(bdp->cbd_bufaddr);dma_unmap_single(NULL, bdp->cbd_bufaddr, bdp->cbd_datlen,DMA_FROM_DEVICE);#ifdef CONFIG_ARCH_MXSswap_buffer(data, pkt_len);#endif/* 分配一个保存数据包的缓冲区,16字节对齐,包长度包括FCS,但我们不希望在传递上层时包括FCS*/skb = dev_alloc_skb(pkt_len - 4 + NET_IP_ALIGN);if (unlikely(!skb)) {printk("%s: Memory squeeze, dropping packet.\n",dev->name);dev->stats.rx_dropped++;} else {/*增加data和tail。可在填充缓冲区之前保留报文头空间。大多数以太网接口在数据包之前保留2个字节,这样IP头可在14字节的以太网头之后,在16字节边界上对齐*/skb_reserve(skb, NET_IP_ALIGN);/*更新sk_buff结构中的tail和len成员,在缓冲区尾部添加数据。*/skb_put(skb, pkt_len - 4);/* Make room *//*将数据包数据拷贝到缓冲区内*/skb_copy_to_linear_data(skb, data, pkt_len - 4);/* 1588 messeage TS handle */if (fep->ptimer_present)fec_ptp_store_rxstamp(fpp, skb, bdp);/*确定数据包的协议ID*/skb->protocol = eth_type_trans(skb, dev);/*将套接字缓冲区传递给上层软件处理*/netif_rx(skb);}bdp->cbd_bufaddr = dma_map_single(NULL, data, bdp->cbd_datlen,DMA_FROM_DEVICE);rx_processing_done:/* Clear the status flags for this buffer */status &= ~BD_ENET_RX_STATS;/* Mark the buffer empty */status |= BD_ENET_RX_EMPTY;bdp->cbd_sc = status;#ifdef CONFIG_ENHANCED_BDbdp->cbd_esc = BD_ENET_RX_INT;bdp->cbd_prot = 0;bdp->cbd_bdu = 0;#endif/* Update BD pointer to next entry */if (status & BD_ENET_RX_WRAP)bdp = fep->rx_bd_base;elsebdp++;/* Doing this here will keep the FEC running while we process* incoming frames. On a heavily loaded network, we should be* able to keep up at the expense of system resources.*/writel(0, fep->hwp + FEC_R_DES_ACTIVE);}fep->cur_rx = bdp;spin_unlock(&fep->hw_lock);}

中断处理

大多数硬件接口通过中断处理例程来控制。接口在两种可能的事件下触发中断:新数据包到达、或者外发数据包的传输已完成。网络接口还能产生中断通知错误的产生、连接状态等。

中断例程通过检查物理设备中的状态寄存器,以区分新数据包到达中断和数据传输完成中断。

对于“传输完毕”的处理,统计信息要被更新,而且要调用dev_kfree_skb_any将不再使用的套接字缓冲区返回给系统。最后,如果驱动程序暂时终止了传输队列,同时使用netif_wake_queue重新启动传输队列(见数据包传输)。

与传输相反,数据包的接受不需要其他任何特殊的终端处理。

static irqreturn_tfec_enet_interrupt(int irq, void * dev_id){structnet_device *dev = dev_id;struct fec_enet_private *fep = netdev_priv(dev);struct fec_ptp_private *fpp = fep->ptp_priv;uintint_events;irqreturn_t ret = IRQ_NONE;do {int_events = readl(fep->hwp + FEC_IEVENT);writel(int_events, fep->hwp + FEC_IEVENT);if (int_events & FEC_ENET_RXF) {ret = IRQ_HANDLED;fec_enet_rx(dev);}/*传输完成,或非致命错误。更新缓冲区描述符*/if (int_events & FEC_ENET_TXF) {ret = IRQ_HANDLED;fec_enet_tx(dev);}if (int_events & FEC_ENET_TS_TIMER) {ret = IRQ_HANDLED;if (fep->ptimer_present && fpp)fpp->prtc++;}if (int_events & FEC_ENET_MII) {ret = IRQ_HANDLED;complete(&fep->mdio_done);}} while (int_events);return ret;}

不使用接收中断

当接口接收到每一个数据包时,处理器都会被中断,这样会使系统性能全面下降。为了能提高Linux在宽带系统上的性能,提供了另外一种基于轮询方式的接口,称之为NAPI。

一个NAPI接口必须能够保存多个数据包。接口对接收数据包能够禁止中断,同时又能对传输和其他时间打开中断。

在初始化时,必须对napi_struct的weight和poll成员进行设置,poll成员必须设置为驱动程序的轮询函数。

struct napi_struct {...intweight;...int(*poll)(struct napi_struct *, int);...};

static int fec_enet_init(struct net_device *ndev){struct fec_enet_private *fep = netdev_priv(ndev);struct bufdesc *cbd_base;dma_addr_t bd_dma;int bd_size;unsigned int i;unsigned dsize = fep->bufdesc_ex ? sizeof(struct bufdesc_ex) :sizeof(struct bufdesc);unsigned dsize_log2 = __fls(dsize);int ret;...writel(FEC_RX_DISABLED_IMASK, fep->hwp + FEC_IMASK);/*设置napi_struct的weight和poll成员*/netif_napi_add(ndev, &fep->napi, fec_enet_rx_napi, NAPI_POLL_WEIGHT);...return 0;}

创建NAPI驱动程序的下一步是修改中断处理例程。当接口通知数据包到达的时候,中断程序不能处理该数据包。它要禁止接收中断,并告诉内核,从现在开始启动轮询接口。

static irqreturn_tfec_enet_interrupt(int irq, void *dev_id){struct net_device *ndev = dev_id;struct fec_enet_private *fep = netdev_priv(ndev);uint int_events;irqreturn_t ret = IRQ_NONE;int_events = readl(fep->hwp + FEC_IEVENT);writel(int_events, fep->hwp + FEC_IEVENT);fec_enet_collect_events(fep, int_events);if ((fep->work_tx || fep->work_rx) && fep->link) {ret = IRQ_HANDLED;/*检查napi是否可以调度*/if (napi_schedule_prep(&fep->napi)) {/*禁用中断*/writel(FEC_NAPI_IMASK, fep->hwp + FEC_IMASK);/*调度接收函数,调用poll函数*/__napi_schedule(&fep->napi);}}if (int_events & FEC_ENET_MII) {ret = IRQ_HANDLED;complete(&fep->mdio_done);}return ret;}

当接口通知数据包到来的时候,中断处理例程与接口相分离,调用__napi_schedule函数,在此后调用poll函数。

static int fec_enet_rx_napi(struct napi_struct *napi, int budget){struct net_device *ndev = napi->dev;struct fec_enet_private *fep = netdev_priv(ndev);int pkts;/*接收数据包*/pkts = fec_enet_rx(ndev, budget);fec_enet_tx(ndev);if (pkts < budget) {/*如果处理完所有的数据包,调用napi_complete_done关闭轮询函数,并且重新打开中断*/napi_complete_done(napi, pkts);writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);}return pkts;}

套接字缓冲区

sk_buff结构体非常重要,定义在include/linux/skbuff.h文件中,含义为“套接字缓冲区”,用于在Linux网络子系统中的各层之间传递数据。

当发送数据包时,Linux内核的网络处理模块必须分配一个包含有要传输数据包的sk_buff,然后将sk_buff传递给下层,各层在sk_buff中添加不同的协议头,直至交给网络设备发送。同样,当网络设备从网络媒介上接收到数据包时,必须将接收到的数据拷贝到sk_buff数据结构中并传递给上层,各层剥去相应的协议头直至交给用户。

struct sk_buff {union {struct {/* These two members must be first. */struct sk_buff*next;struct sk_buff*prev;union {struct net_device*dev; /*接收和发送该缓冲区的设备*//* Some protocols might use this space to store information,* while device pointer would be NULL.* UDP receive path is one user.*/unsigned longdev_scratch;};};struct rb_noderbnode; /* used in netem, ip4 defrag, and tcp stack */struct list_headlist;};union {struct sock*sk;intip_defrag_offset;};union {ktime_ttstamp;u64skb_mstamp_ns; /* earliest departure time */};/** This is the control buffer. It is free to use for every* layer. Please put your private variables there. If you* want to keep them across layers you have to do a skb_clone()* first. This is owned by whoever has the skb queued ATM.*/charcb[48] __aligned(8);union {struct {unsigned long_skb_refdst;void(*destructor)(struct sk_buff *skb);};struct list_headtcp_tsorted_anchor;};#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)unsigned long _nfct;#endif/*len是在数据包中全部数据的长度,data_len是分割存储的数据片段的长度。*/unsigned intlen,data_len;__u16mac_len,hdr_len;/* Following fields are _not_ copied in __copy_skb_header()* Note that queue_mapping is here mostly to fill a hole.*/__u16queue_mapping;/* if you move cloned around you also must adapt those constants */#ifdef __BIG_ENDIAN_BITFIELD#define CLONED_MASK(1 << 7)#else#define CLONED_MASK1#endif#define CLONED_OFFSET()offsetof(struct sk_buff, __cloned_offset)__u8__cloned_offset[0];__u8cloned:1,nohdr:1,fclone:2,peeked:1,head_frag:1,pfmemalloc:1;#ifdef CONFIG_SKB_EXTENSIONS__u8active_extensions;#endif/* fields enclosed in headers_start/headers_end are copied* using a single memcpy() in __copy_skb_header()*//* private: */__u32headers_start[0];/* public: *//* if you move pkt_type around you also must adapt those constants */#ifdef __BIG_ENDIAN_BITFIELD#define PKT_TYPE_MAX(7 << 5)#else#define PKT_TYPE_MAX7#endif#define PKT_TYPE_OFFSET()offsetof(struct sk_buff, __pkt_type_offset)__u8__pkt_type_offset[0];__u8pkt_type:3; /*在发送过程中使用的数据包类型。驱动程序负责将其设置为PACKET_HOST、PACKET_OTHERHOST、PACKET_BROADCAST或者是PACKET_MULTICAST。*/__u8ignore_df:1;__u8nf_trace:1;__u8ip_summed:2; /*对数据包的校验策略,由驱动程序对传入数据包进行设置。*/__u8ooo_okay:1;__u8l4_hash:1;__u8sw_hash:1;__u8wifi_acked_valid:1;__u8wifi_acked:1;__u8no_fcs:1;/* Indicates the inner headers are valid in the skbuff. */__u8encapsulation:1;__u8encap_hdr_csum:1;__u8csum_valid:1;#ifdef __BIG_ENDIAN_BITFIELD#define PKT_VLAN_PRESENT_BIT7#else#define PKT_VLAN_PRESENT_BIT0#endif#define PKT_VLAN_PRESENT_OFFSET()offsetof(struct sk_buff, __pkt_vlan_present_offset)__u8__pkt_vlan_present_offset[0];__u8vlan_present:1;__u8csum_complete_sw:1;__u8csum_level:2;__u8csum_not_inet:1;__u8dst_pending_confirm:1;#ifdef CONFIG_IPV6_NDISC_NODETYPE__u8ndisc_nodetype:2;#endif__u8ipvs_property:1;__u8inner_protocol_type:1;__u8remcsum_offload:1;#ifdef CONFIG_NET_SWITCHDEV__u8offload_fwd_mark:1;__u8offload_l3_fwd_mark:1;#endif#ifdef CONFIG_NET_CLS_ACT__u8tc_skip_classify:1;__u8tc_at_ingress:1;__u8tc_redirected:1;__u8tc_from_ingress:1;#endif#ifdef CONFIG_TLS_DEVICE__u8decrypted:1;#endif#ifdef CONFIG_NET_SCHED__u16tc_index;/* traffic control index */#endifunion {__wsumcsum;struct {__u16csum_start;__u16csum_offset;};};__u32priority;intskb_iif;__u32hash;__be16vlan_proto;__u16vlan_tci;#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)union {unsigned intnapi_id;unsigned intsender_cpu;};#endif#ifdef CONFIG_NETWORK_SECMARK__u32secmark;#endifunion {__u32mark;__u32reserved_tailroom;};union {__be16inner_protocol;__u8inner_ipproto;};__u16inner_transport_header;__u16inner_network_header;__u16inner_mac_header;/*数据包中各个层的报文头,transport_header是传输层报文头,network_header是网络层报文头,mac_header是链路层报文头*/__be16protocol;__u16transport_header;__u16network_header;__u16mac_header;/* private: */__u32headers_end[0];/* public: *//* These elements must be at the end, see alloc_skb() for details. *//*指向数据包中数据的指针,head指向已分配空间的开头,data是有效octet(通常比head大一些)的开头,*tail是有效octet的结尾,而end指向tail可达到的最大地址。*还可以从这些指针得出可用缓冲区空间为skb->end - skb->head,当前已使用的数据空间为skb->tail - skb->data。*/sk_buff_data_ttail;sk_buff_data_tend;unsigned char*head,*data;unsigned inttruesize;refcount_tusers;#ifdef CONFIG_SKB_EXTENSIONS/* only useable after checking ->active_extensions != 0 */struct skb_ext*extensions;#endif};

参考资料:

《LINUX设备驱动程序第三版》

linux-5.4.9

linux-2.6.35.3

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。