Skip to content

Latest commit

 

History

History
956 lines (749 loc) · 37.9 KB

File metadata and controls

956 lines (749 loc) · 37.9 KB

二十二、网络接口卡驱动

我们都知道网络是 Linux 内核固有的。几年前,Linux 只是用来做网络性能的,现在情况变了;Linux 不仅仅是一个服务器,它运行在数十亿个嵌入式设备上。多年来,Linux 获得了最佳网络操作系统的美誉。尽管如此,Linux 不能做所有的事情。鉴于以太网控制器的种类繁多,Linux 除了向需要为其网络设备编写驱动或需要以一般方式执行内核网络开发的开发人员公开应用编程接口之外,没有找到其他方法。这个应用编程接口提供了足够的抽象层,允许评估开发代码的丰富性,以及移植到其他架构上。本章将简单介绍一下这个 API 中涉及网络接口卡 ( 网卡)驱动开发的部分,并讨论其数据结构和方法。

在本章中,我们将涵盖以下主题:

  • 网卡驱动数据结构及其主套接字缓冲区结构
  • 网卡驱动架构和方法描述,以及数据包传输和接收
  • 为测试目的开发虚拟网卡驱动

驱动数据结构

在处理网卡设备时,您需要处理两种数据结构:

  • include/linux/skbuff.h中定义的struct sk_buff结构,它是 Linux 网络代码中的基本数据结构,应该包含在您的代码中:
#include <linux/skbuff.h>  
  • 使用这种数据结构处理发送或接收的每个数据包。
  • struct net_device结构;这是内核中表示任何网卡设备的结构。它是进行数据传输的接口。它在include/linux/netdevice.h中定义,也应该包含在您的代码中:
#include <linux/netdevice.h> 

代码中应该包含的其他文件是用于 MAC 和以太网相关功能的include/linux/etherdevice.h(如alloc_etherdev())和用于以太网工具支持的include/linux/ethtool.h:

#include <linux/ethtool.h>  
#include <linux/etherdevice.h> 

套接字缓冲结构

这种结构包装了通过网卡传输的任何数据包:

struct sk_buff { 
  struct sk_buff * next; 
  struct sk_buff * prev; 
  ktime_t tstamp; 
  struct rb_node     rbnode; /* used in netem & tcp stack */ 
  struct sock * sk; 
  struct net_device * dev; 
  unsigned int       len; 
  unsigned int       data_len; 
  __u16              mac_len; 
  __u16              hdr_len; 
  unsigned int len; 
  unsigned int data_len; 
  __u16 mac_len; 
  __u16 hdr_len; 
  __u32 priority; 
  dma_cookie_t dma_cookie; 
  sk_buff_data_t tail; 
  sk_buff_data_t end; 
  unsigned char * head; 
  unsigned char * data; 
  unsigned int truesize; 
  atomic_t users; 
}; 

以下是结构中元素的含义:

  • nextprev:表示列表中下一个和上一个缓冲区。
  • sk:这是与此数据包关联的套接字。
  • tstamp:这是包到达/离开的时间。
  • rbnode:这是红黑树代表的next / prev的替代品。
  • dev:表示该数据包到达/离开的设备。此字段与此处未列出的另外两个字段相关联。这是input_devreal_dev。它们跟踪与数据包相关的设备。因此,input_dev总是指接收数据包的设备。
  • len:这是数据包中的字节总数。套接字缓冲区(SKB)由一个线性数据缓冲区和一组称为房间的一个或多个区域组成。如果有这样的房间,data_len将保存数据区的总字节数。
  • mac_len:保存 MAC 报头的长度。
  • csum:包含数据包的校验和。
  • Priority:表示 QoS 中的数据包优先级。
  • truesize:这记录了一个包消耗了多少字节的系统内存,包括struct sk_buff结构本身占用的内存。
  • users:用于 SKB 物体的参考计数。
  • Head:头、数据、尾是套接字缓冲区中不同区域(房间)的指针。
  • end:这指向套接字缓冲区的末端。

这里只讨论了这种结构的几个领域。在include/linux/skbuff.h中有完整的描述。,这是您应该包含的头文件,用于处理套接字缓冲区。

套接字缓冲区分配

套接字缓冲区的分配有点棘手,因为它至少需要三个不同的功能:

  • 首先,整个内存分配应该使用netdev_alloc_skb()函数来完成
  • skb_reserve()功能增加并对齐头部空间
  • 使用skb_put()功能扩展缓冲区(将包含数据包)的已用数据区。

让我们看看下图:

Socket buffers allocation process

  1. 我们通过netdev_alloc_skb()函数分配一个足够大的缓冲区来包含一个数据包和以太网报头:
struct sk_buff *netdev_alloc_skb(struct net_device *dev, 
                                   unsigned int length) 

该功能失败时返回NULL。因此,即使它分配内存,也可以从原子上下文中调用netdev_alloc_skb()

由于以太网报头是 14 字节长,它需要有一些对齐,以便中央处理器在访问缓冲区的这一部分时不会遇到任何性能问题。header_len参数的适当名称应为header_alignment,因为该参数用于对齐。通常的值是 2,这就是内核在include/linux/skbuff.h中为此定义了一个专用宏NET_IP_ALIGN的原因:

#define NET_IP_ALIGN 2 
  1. 第二步通过减少尾部空间为头部保留对齐的内存。起作用的是skb_reserve():
void skb_reserve(struct sk_buff *skb, int len) 
  1. 最后一步是通过skb_put()功能将缓冲区的已用数据区扩展到与数据包大小一样大。该函数返回指向数据区第一个字节的指针:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len) 

分配的套接字缓冲区应该被转发到内核网络层。这是套接字缓冲区生命周期的最后一步。应该使用netif_rx_ni()功能:

int netif_rx_ni(struct sk_buff *skb) 

我们将在本章的数据包接收部分讨论如何使用前面的步骤。

网络接口结构

网络接口在内核中表示为struct net_device结构的一个实例,在include/linux/netdevice.h中定义:

struct net_device { 
   char name[IFNAMSIZ]; 
   char *ifalias; 
   unsigned long mem_end; 
   unsigned long mem_start; 
   unsigned long base_addr; 
   int irq; 
   netdev_features_t features; 
   netdev_features_t hw_features; 
   netdev_features_t  wanted_features; 
   int ifindex; 
   struct net_device_stats stats; 
   atomic_long_t rx_dropped; 
   atomic_long_t  tx_dropped; 
   const struct net_device_ops *netdev_ops; 
   const struct ethtool_ops *ethtool_ops; 
   unsigned int flags; 
   unsigned int priv_flags; 
   unsigned char link_mode; 
      unsigned char if_port; 
   unsigned char dma; 
   unsigned int mtu; 
   unsigned short type; 
   /* Interface address info. */ 
   unsigned char perm_addr[MAX_ADDR_LEN]; 
   unsigned char addr_assign_type; 
   unsigned char addr_len; 
   unsigned short neigh_priv_len; 
   unsigned short dev_id; 
   unsigned short dev_port; 
   unsigned long last_rx; 
   /* Interface address info used in eth_type_trans() */ 
   unsigned char *dev_addr; 

   struct device dev; 
   struct phy_device *phydev; 
}; 

struct net_device结构属于需要动态分配的内核数据结构,有自己的分配功能。通过alloc_etherdev()功能在内核中分配网卡。

struct net_device *alloc_etherdev(int sizeof_priv); 

失败时,该功能返回NULLsizeof_priv参数表示分配给专用数据结构的内存大小,该数据结构附加在网卡上,可以通过netdev_priv()功能提取:

void *netdev_priv(const struct net_device *dev) 

鉴于struct priv_struct,是我们的私有结构,以下是如何分配网络设备和私有数据结构的实现:

struct net_device *net_dev; 
struct priv_struct *priv_net_struct; 
net_dev = alloc_etherdev(sizeof(struct priv_struct)); 
my_priv_struct = netdev_priv(dev); 

应使用free_netdev()功能释放未使用的网络设备,该功能还会释放分配给私有数据的内存。只有在设备从内核中注销后,才应该调用此方法:

void free_netdev(struct net_device *dev) 

当你的net_device结构完成并填充后,你应该在上面调用register_netdev()。本章后面的驱动方法一节将解释该功能。只要记住这个函数向内核注册我们的网络设备,这样它就可以使用了。也就是说,在调用这个函数之前,您应该确保设备确实能够处理网络操作。

int register_netdev(struct net_device *dev) 

设备方法

网络设备属于不出现在/dev目录中的设备类别(不像块设备、输入设备或充电设备)。因此,像所有这些类型的设备一样,网卡驱动公开了一组工具来执行。内核通过struct net_device_ops结构展示可以在网络接口上执行的操作,该结构是struct net_device结构的一个字段,代表网络设备(dev->netdev_ops)。struct net_device_ops字段描述如下:

struct net_device_ops { 
   int (*ndo_init)(struct net_device *dev); 
   void (*ndo_uninit)(struct net_device *dev); 
   int (*ndo_open)(struct net_device *dev); 
   int (*ndo_stop)(struct net_device *dev); 
   netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, 
                              struct net_device *dev); 
   void (*ndo_change_rx_flags)(struct net_device *dev, int flags); 
   void (*ndo_set_rx_mode)(struct net_device *dev); 
   int (*ndo_set_mac_address)(struct net_device *dev, void *addr); 
   int (*ndo_validate_addr)(struct net_device *dev); 
   int (*ndo_do_ioctl)(struct net_device *dev, 
                             struct ifreq *ifr, int cmd); 
   int (*ndo_set_config)(struct net_device *dev, struct ifmap *map); 
   int (*ndo_change_mtu)(struct net_device *dev, int new_mtu); 
   void (*ndo_tx_timeout) (struct net_device *dev); 

   struct net_device_stats* (*ndo_get_stats)( 
   struct net_device *dev); 
}; 

让我们看看结构中每个柠檬的含义是什么:

  • int (*ndo_init)(struct net_device *dev)void(*ndo_uninit)(struct net_device *dev);它们是额外的初始化/单元化功能,分别在驱动调用register_netdev() / unregister_netdev()时执行,以便向内核注册/注销网络设备。大多数司机不提供这些功能,因为真正的工作是由ndo_open()ndo_stop()功能完成的。
  • int (*ndo_open)(struct net_device *dev);准备并打开界面。每当ipifconfig实用程序激活该界面时,该界面就会打开。在这种方法中,驱动应该请求/映射/注册它需要的任何系统资源(输入/输出端口、IRQ、DMA 等),打开硬件,并执行设备所需的任何其他设置。
  • int (*ndo_stop)(struct net_device *dev):接口关闭时内核执行该功能(例如ifconfig <name> down等)。该功能应执行与在ndo_open()中所做操作相反的操作。
  • int (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev):每当内核想要通过这个接口发送一个包的时候都会调用这个方法。
  • void (*ndo_set_rx_mode)(struct net_device *dev):调用此方法改变接口地址列表过滤模式,组播或混杂。建议提供此功能。
  • void (*ndo_tx_timeout)(struct net_device *dev):当数据包传输未能在合理时间内完成时,内核调用此方法,通常为dev->watchdog滴答。驱动应该检查发生了什么,处理问题,并恢复数据包传输。
  • struct net_device_stats *(*get_stats)(struct net_device *dev):此方法返回设备统计。这是当netstat -iifconfig运行时可以看到的。

前面的描述遗漏了很多字段。完整的结构描述可在include/linux/netdevice.h文件中找到。实际上,只有ndo_start_xmit是强制的,但是提供尽可能多的助手钩子是一个很好的实践,因为你的设备有很多特性。

打开和关闭

每当授权用户(例如管理员)使用任何用户空间实用程序(如ifconfigip)配置该网络接口时,内核都会调用ndo_open()函数。

与其他网络设备操作一样,ndo_open()功能接收一个struct net_device对象作为其参数,在分配net_device对象时,驱动应该从该对象获取存储在priv字段中的设备特定对象。

每当网络控制器接收或完成数据包传输时,它通常会发出中断。驱动需要注册一个中断处理程序,每当控制器发出中断时就会调用该程序。驱动可以在init() / probe()程序或open功能中注册中断处理程序。一些器件需要通过在硬件的特殊寄存器中设置来使能中断。在这种情况下,可以在probe功能中请求中断,并在打开/关闭方法中设置/清除使能位。

让我们总结一下open函数应该做什么:

  1. 更新接口媒体访问控制地址(如果用户更改了它,并且您的设备允许)。
  2. 如有必要,重置硬件,并使其退出低功耗模式。
  3. 请求任何资源(输入/输出内存、直接内存分配通道、内部资源请求)。
  4. 映射 IRQ 并注册中断处理程序。
  5. 检查接口链接状态。
  6. 在设备上调用net_if_start_queue()以便让内核知道你的设备已经准备好传输数据包。

open功能示例如下:

/* 
 * This routine should set everything up new at each open, even 
 * registers that should only need to be set once at boot, so that 
 * there is non-reboot way to recover if something goes wrong. 
 */ 
static int enc28j60_net_open(struct net_device *dev) 
{ 
   struct priv_net_struct *priv = netdev_priv(dev); 

   if (!is_valid_ether_addr(dev->dev_addr)) { 
         [...] /* Maybe print a debug message ? */ 
         return -EADDRNOTAVAIL; 
   } 
   /* 
 * Reset the hardware here and take it out of low 
 * power mode 
 */ 
   my_netdev_lowpower(priv, false); 

   if (!my_netdev_hw_init(priv)) { 
         [...] /* handle hardware reset failure */ 
         return -EINVAL; 
   } 

   /* Update the MAC address (in case user has changed it) 
    * The new address is stored in netdev->dev_addr field 
 */ 
set_hw_macaddr_registers(netdev, MAC_REGADDR_START, 
netdev->addr_len, netdev->dev_addr); 

   /* Enable interrupts */ 
   my_netdev_hw_enable(priv); 

   /* We are now ready to accept transmit requests from 
    * the queueing layer of the networking. 
    */ 
   netif_start_queue(dev); 

   return 0; 
} 

netif_start_queue()只是允许上层调用设备ndo_start_xmit例程。换句话说,它通知内核设备准备好处理传输请求。

另一侧的关闭方法只需执行与设备打开时相反的操作:

/* The inverse routine to net_open(). */ 
static int enc28j60_net_close(struct net_device *dev) 
{ 
   struct priv_net_struct *priv = netdev_priv(dev); 

   my_netdev_hw_disable(priv); 
   my_netdev_lowpower(priv, true); 

    /** 
     *   netif_stop_queue - stop transmitted packets 
     * 
     *   Stop upper layers calling the device ndo_start_xmit routine. 
     *   Used for flow control when transmit resources are unavailable. 
     */ 
   netif_stop_queue(dev); 

   return 0; 
} 

netif_stop_queue()简单地做netif_start_queue()的反向,告诉内核停止调用设备ndo_start_xmit例程。我们不能再处理传输请求了。

数据包处理

数据包处理包括数据包的发送和接收。这是任何网络接口驱动的主要任务。传输仅指发送输出帧,而接收指接收帧。

有两种方法可以驱动网络数据交换:轮询或中断。轮询是一种定时器驱动的中断,由内核以给定的时间间隔持续检查设备的任何变化组成。另一方面,中断模式包括内核什么也不做,监听一条 IRQ 线路,并等待设备通过 IRQ 通知变化。中断驱动的数据交换会在高流量期间增加系统开销。这就是为什么有些司机把这两种方法混在一起。内核中允许混合使用这两种方法的部分称为新 API ( NAPI ),它包括在流量高的时候使用轮询,在流量正常时使用中断 IRQ 驱动的管理。如果硬件支持,新的驱动应该使用 NAPI。但是,本章不讨论 NAPI,它将集中讨论中断驱动方法。

数据包接收

当数据包到达网络接口卡时,驱动必须在其周围建立一个新的套接字缓冲区,并将数据包复制到sk_ff->data字段。拷贝的种类其实并不重要,DMA 也可以使用。驱动通常通过中断知道新的数据到达。当网卡收到数据包时,它会产生一个中断,该中断将由驱动处理,驱动必须检查设备的中断状态寄存器,并检查产生该中断的真正原因(可能是接收正常、接收错误等)。对应于引发中断的事件的位将在状态寄存器中设置。

棘手的部分是分配和构建套接字缓冲区。但幸运的是,我们已经在本章的第一部分讨论过了。因此,让我们不要浪费时间,让我们跳到一个示例 RX 处理程序。驱动必须执行与其收到的数据包数量一样多的sk_buff分配:

/* 
 * RX handler 
 * This function is called in the work responsible of packet 
 * reception (bottom half) handler. We use work because access to  
 * our device (which sit on a SPI bus) may sleep 
 */ 
static int my_rx_interrupt(struct net_device *ndev) 
{ 
   struct priv_net_struct *priv = netdev_priv(ndev); 
   int pk_counter, ret; 

   /* Let's get the number of packet our device received */ 
   pk_counter = my_device_reg_read(priv, REG_PKT_CNT); 

   if (pk_counter > priv->max_pk_counter) { 
         /* update statistics */ 
         priv->max_pk_counter = pk_counter; 
   } 
   ret = pk_counter; 

   /* set receive buffer start */ 
   priv->next_pk_ptr = KNOWN_START_REGISTER; 
   while (pk_counter-- > 0) 
         /*  
* By calling this internal helper function in a "while" 
* loop, packets get extracted one by one from the device 
* and forwarder to the network layer.  
*/ 
         my_hw_rx(ndev); 

   return ret; 
} 

以下助手负责从设备获取一个数据包,将其转发到内核网络,并递减数据包计数器:

/* 
 * Hardware receive function. 
 * Read the buffer memory, update the FIFO pointer to 
 * free the buffer. 
 * This function decrements the packet counter. 
 */ 
static void my_hw_rx(struct net_device *ndev) 
{ 
   struct priv_net_struct *priv = netdev_priv(ndev); 
   struct sk_buff *skb = NULL; 
   u16 erxrdpt, next_packet, rxstat; 
   u8 rsv[RSV_SIZE]; 
   int packet_len; 

   packet_len = my_device_read_current_packet_size(); 
   /* Can't cross boundaries */ 
   if ((priv->next_pk_ptr > RXEND_INIT)) { 
         /* packet address corrupted: reset RX logic */ 
         [...] 
         /* Update RX errors stats */ 
         ndev->stats.rx_errors++; 
         return; 
   } 
   /* Read next packet pointer and rx status vector 
    * This is device-specific 
    */ 
   my_device_reg_read(priv, priv->next_pk_ptr, sizeof(rsv), rsv); 

   /* Check for errors in the device RX status reg, 
    * and update error stats accordingly 
    */ 
   if(an_error_is_detected_in_device_status_registers()) 
         /* Depending on the error, 
          * stats.rx_errors++; 
          * ndev->stats.rx_crc_errors++; 
          * ndev->stats.rx_frame_errors++; 
          * ndev->stats.rx_over_errors++; 
          */ 
   } else { 
         skb = netdev_alloc_skb(ndev, len + NET_IP_ALIGN); 
         if (!skb) { 
               ndev->stats.rx_dropped++; 
         } else { 
               skb_reserve(skb, NET_IP_ALIGN); 
               /* 
                 * copy the packet from the device' receive buffer 
                 * to the socket buffer data memory. 
                 * Remember skb_put() return a pointer to the 
                 * beginning of data region. 
                 */ 
               my_netdev_mem_read(priv, 
                     rx_packet_start(priv->next_pk_ptr), 
                     len, skb_put(skb, len)); 

               /* Set the packet's protocol ID */ 
               skb->protocol = eth_type_trans(skb, ndev); 
               /* update RX statistics */ 
               ndev->stats.rx_packets++; 
               ndev->stats.rx_bytes += len; 

               /* Submit socket buffer to the network layer */ 
               netif_rx_ni(skb); 
         } 
   } 
   /* Move the RX read pointer to the start of the next 
    * received packet. 
    */ 
   priv->next_pk_ptr = my_netdev_update_reg_next_pkt(); 
} 

当然,我们从延迟工作中调用 RX 处理程序的唯一原因是因为我们坐在 SPI 总线上。如果是 MMIO 设备,上述所有操作都可以在 hwriq 中执行。请看drivers/net/ethernet/freescale/fec.c中的恩智浦 FEC 驱动,了解这是如何实现的。

包传输

当内核需要将数据包送出接口时,它会调用驱动的ndo_start_xmit方法,该方法应该在成功时返回NETDEV_TX_OK,或者在失败时返回NETDEV_TX_BUSY,在这种情况下,您不能对套接字缓冲区做任何事情,因为当错误返回时,它仍然属于网络排队层。这意味着您不能修改任何 SKB 字段,或释放 SKB,等等。自旋锁保护这个函数不被并发调用。

在大多数情况下,包传输是异步进行的。传输包的sk_buff由上层填充。其data字段包含要发送的数据包。驱动应该从sk_buff->data提取数据包,并将其写入设备硬件先进先出,或者在将其写入设备硬件先进先出之前,将其放入临时发送缓冲区(如果设备在发送之前需要一定大小的数据)。只有当先进先出达到阈值(通常由驱动定义,或在器件数据手册中提供)或驱动通过在器件的特殊寄存器中设置一个位(一种触发器)有意开始传输时,数据才会真正发送。也就是说,驱动需要通知内核在硬件准备好接受新数据之前不要开始任何传输。此通知通过netif_st op_queue()功能完成。

void netif_stop_queue(struct net_device *dev) 

发送数据包后,网络接口卡将发出中断。中断处理程序应该检查中断发生的原因。传输中断时,应更新其统计数据(net_device->stats.tx_errorsnet_device->stats.tx_packets),并通知内核设备可以自由发送新数据包。本通知通过netif_wake_queue()方式完成:

void netif_wake_queue(struct net_device *dev) 

总而言之,数据包传输分为两部分:

  • ndo_start_xmit操作,通知内核设备忙,设置好一切,开始传输。
  • 发送中断处理程序,它更新发送统计数据,并通知内核设备再次可用。

ndo_start_xmit功能必须大致包含以下步骤:

  1. 在网络设备上调用netif_stop_queue(),以通知内核设备将忙于数据传输。
  2. sk_buff->data内容写入设备 FIFO。
  3. 触发传输(指示设备开始传输)。

Operations (2) and (3) may lead to sleep for devices sitting on slow buses (SPI for example) and may need to be deferred to the work structure. This is the case for our sample.

一旦数据包被传输,发送中断处理程序应执行以下步骤:

  1. 根据正在进行内存映射的设备或位于访问功能可能休眠的总线上的设备,以下操作应直接在 hwirq 处理程序中执行或在工作(或线程化 irq)中调度:

1.检查中断是否为传输中断。

2.读取传输描述符状态寄存器,查看数据包的状态。

3.如果传输中有任何问题,增加错误统计。

4.成功传输的数据包的增量统计。

  1. 启动传输队列,允许内核通过netif_wake_queue()函数再次调用驱动的ndo_start_xmit方法。

让我们用一个简短的示例代码来总结一下:

/* Somewhere in the code */ 
INIT_WORK(&priv->tx_work, my_netdev_hw_tx); 

static netdev_tx_t my_netdev_start_xmit(struct sk_buff *skb, 
                           struct net_device *dev) 
{ 
   struct priv_net_struct *priv = netdev_priv(dev); 

   /* Notify the kernel our device will be busy */ 
   netif_stop_queue(dev); 

   /* Remember the skb for deferred processing */ 
   priv->tx_skb = skb; 

   /* This work will copy data from sk_buffer->data to 
    * the hardware's FIFO and start transmission 
    */ 
   schedule_work(&priv->tx_work); 

   /* Everything is OK */ 
   return NETDEV_TX_OK; 
} 
The work is described below: 
/* 
 * Hardware transmit function. 
 * Fill the buffer memory and send the contents of the 
 * transmit buffer onto the network 
 */ 
static void my_netdev_hw_tx(struct priv_net_struct *priv) 
{ 
   /* Write packet to hardware device TX buffer memory */ 
   my_netdev_packet_write(priv, priv->tx_skb->len, 
priv->tx_skb->data); 

/*  
 * does this network device support write-verify? 
 * Perform it  
 */ 
[...]; 

   /* set TX request flag, 
 * so that the hardware can perform transmission. 
 * This is device-specific 
 */ 
   my_netdev_reg_bitset(priv, ECON1, ECON1_TXRTS); 
} 

发送中断管理将在下一节讨论。

驱动示例

我们可以在下面的假以太网驱动中总结上面讨论的概念:

#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/errno.h> 
#include <linux/init.h> 
#include <linux/netdevice.h> 
#include <linux/etherdevice.h> 
#include <linux/ethtool.h> 
#include <linux/skbuff.h> 
#include <linux/slab.h> 
#include <linux/of.h>                   /* For DT*/ 
#include <linux/platform_device.h>      /* For platform devices */ 

struct eth_struct { 
    int bar; 
    int foo; 
    struct net_device *dummy_ndev; 
}; 

static int fake_eth_open(struct net_device *dev) { 
    printk("fake_eth_open called\n"); 
    /* We are now ready to accept transmit requests from 
    * the queueing layer of the networking. 
    */ 
   netif_start_queue(dev); 
    return 0; 
} 

static int fake_eth_release(struct net_device *dev) { 
    pr_info("fake_eth_release called\n"); 
    netif_stop_queue(dev); 
    return 0; 
} 

static int fake_eth_xmit(struct sk_buff *skb, struct net_device *ndev) {     
    pr_info("dummy xmit called...\n"); 
    ndev->stats.tx_bytes += skb->len; 
    ndev->stats.tx_packets++; 

    skb_tx_timestamp(skb); 
   dev_kfree_skb(skb); 
   return NETDEV_TX_OK; 
} 

static int fake_eth_init(struct net_device *dev) 
{ 
    pr_info("fake eth device initialized\n"); 
    return 0; 
}; 

static const struct net_device_ops my_netdev_ops = { 
     .ndo_init = fake_eth_init, 
     .ndo_open = fake_eth_open, 
     .ndo_stop = fake_eth_release, 
     .ndo_start_xmit = fake_eth_xmit, 
     .ndo_validate_addr    = eth_validate_addr, 
     .ndo_validate_addr    = eth_validate_addr, 
}; 

static const struct of_device_id fake_eth_dt_ids[] = { 
    { .compatible = "packt,fake-eth", }, 
    { /* sentinel */ } 
}; 

static int fake_eth_probe(struct platform_device *pdev) 
{ 
    int ret; 
    struct eth_struct *priv; 
    struct net_device *dummy_ndev; 

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 
    if (!priv) 
        return -ENOMEM; 

    dummy_ndev = alloc_etherdev(sizeof(struct eth_struct)); 
    dummy_ndev->if_port = IF_PORT_10BASET; 
    dummy_ndev->netdev_ops = &my_netdev_ops; 

    /* If needed, dev->ethtool_ops = &fake_ethtool_ops; */ 

    ret = register_netdev(dummy_ndev); 
    if(ret) { 
        pr_info("dummy net dev: Error %d initalizing card ...", ret); 
        return ret; 
    } 

    priv->dummy_ndev = dummy_ndev; 
    platform_set_drvdata(pdev, priv); 
    return 0; 
} 

static int fake_eth_remove(struct platform_device *pdev) 
{ 
    struct eth_struct *priv; 
   priv = platform_get_drvdata(pdev); 
   pr_info("Cleaning Up the Module\n"); 
    unregister_netdev(priv->dummy_ndev); 
    free_netdev(priv->dummy_ndev); 

   return 0; 
} 

static struct platform_driver mypdrv = { 
    .probe      = fake_eth_probe, 
    .remove     = fake_eth_remove, 
    .driver     = { 
        .name     = "fake-eth", 
        .of_match_table = of_match_ptr(fake_eth_dt_ids),   
        .owner    = THIS_MODULE, 
    }, 
}; 
module_platform_driver(mypdrv); 

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("John Madieu <[email protected]>"); 
MODULE_DESCRIPTION("Fake Ethernet driver"); 

一旦模块被加载并且设备被匹配,以太网接口将在系统上被创建。首先,让我们看看dmesg命令向我们展示了什么:

# dmesg
[...]
[146698.060074] fake eth device initialized
[146698.087297] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready

如果运行ifconfig -a命令,界面将打印在屏幕上:

# ifconfig -a
[...]
eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

最后可以配置接口,分配一个 IP 地址,使用ifconfig显示:

# ifconfig eth0 192.168.1.45
# ifconfig
[...]
eth0 Link encap:Ethernet HWaddr 00:00:00:00:00:00
inet addr:192.168.1.45 Bcast:192.168.1.255 Mask:255.255.255.0
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

状态和控制

设备控制是指内核需要主动或响应用户操作来改变接口属性的情况。然后,它可以使用通过struct net_device_ops结构暴露的操作,如上所述,或者使用另一个控制工具 ethtool ,这需要驱动引入一组新的钩子,我们将在下一节中讨论。相反,状态包括报告接口的状态。

中断处理程序

到目前为止,我们只处理了两种不同的中断:当一个新的数据包到达时,或者当一个输出数据包的传输完成时;但是现在,硬件接口变得越来越智能,它们能够报告它们的状态,或者是为了健全,或者是为了数据传输。通过这种方式,网络接口还可以生成中断来通知错误、链路状态变化等。它们都应该在中断处理程序中处理。

这就是我们的 hwrirq 处理程序的样子:

static irqreturn_t my_netdev_irq(int irq, void *dev_id) 
{ 
   struct priv_net_struct *priv = dev_id; 

   /* 
    * Can't do anything in interrupt context because we need to 
    * block (spi_sync() is blocking) so fire of the interrupt 
    * handling workqueue. 
    * Remember, we access our netdev registers through SPI bus 
    * via spi_sync() call. 
    */ 
   schedule_work(&priv->irq_work); 

   return IRQ_HANDLED; 
} 

因为我们的设备位于一条 SPI 总线上,所以一切都被延迟到一个work_struct,定义如下:

static void my_netdev_irq_work_handler(struct work_struct *work) 
{ 
   struct priv_net_struct *priv = 
         container_of(work, struct priv_net_struct, irq_work); 
   struct net_device *ndev = priv->netdev; 
   int intflags, loop; 

   /* disable further interrupts */ 
   my_netdev_reg_bitclear(priv, EIE, EIE_INTIE); 

   do { 
         loop = 0; 
         intflags = my_netdev_regb_read(priv, EIR); 
         /* DMA interrupt handler (not currently used) */ 
         if ((intflags & EIR_DMAIF) != 0) { 
               loop++; 
               handle_dma_complete(); 
               clear_dma_interrupt_flag(); 
         } 
         /* LINK changed handler */ 
         if ((intflags & EIR_LINKIF) != 0) { 
               loop++; 
               my_netdev_check_link_status(ndev); 
               clear_link_interrupt_flag(); 
         } 
         /* TX complete handler */ 
         if ((intflags & EIR_TXIF) != 0) { 
               bool err = false; 
               loop++; 
               priv->tx_retry_count = 0; 
               if (locked_regb_read(priv, ESTAT) & ESTAT_TXABRT) 
                     clear_tx_interrupt_flag(); 

         /* TX Error handler */ 
         if ((intflags & EIR_TXERIF) != 0) { 
               loop++; 
               /* 
                * Reset TX logic by setting/clearing appropriate 
                 * bit in the right register 
                 */ 
               [...] 

               /* Transmit Late collision check for retransmit */ 
               if (my_netdev_cpllision_bit_set())  
                     /* Handlecollision */ 
                     [...] 
         } 
         /* RX Error handler */ 
         if ((intflags & EIR_RXERIF) != 0) { 
               loop++; 
               /* Check free FIFO space to flag RX overrun */ 
               [...] 
         } 
         /* RX handler */ 
         if (my_rx_interrupt(ndev)) 
               loop++; 
   } while (loop); 

   /* re-enable interrupts */ 
   my_netdev_reg_bitset(priv, EIE, EIE_INTIE); 
} 

Ethtool 支持

Ethtool 是一个小工具,用于检查和调整基于以太网的网络接口的设置。使用 ethtool,可以控制各种参数,例如:

  • 速度
  • 媒体类型
  • 双工操作
  • 获取/设置 eeprom 寄存器内容
  • 硬件校验和
  • 局域网唤醒等等。

需要 ethtool 支持的驱动应该包括<linux/ethtool.h>。它依赖于struct ethtool_ops结构,这是这个特性的核心,并且包含了一套 ethtool 操作支持的方法。这些方法大多相对简单;详见include/linux/ethtool.h

为了使 ethtool 支持完全成为驱动的一部分,驱动应该填充一个ethtool_ops结构,并将其分配给struct net_device结构的.ethtool_ops字段。

my_netdev->ethtool_ops = &my_ethtool_ops; 

SET_ETHTOOL_OPS也可以用于此目的。请注意,即使当接口关闭时,也可以调用您的 ethtool 方法。

例如,以下驱动实现了 ethtool 支持:

  • drivers/net/ethernet/microchip/enc28j60.c
  • drivers/net/ethernet/freescale/fec.c
  • drivers/net/usb/rtl8150.c

驱动方法

驱动方法是probe()remove()功能。他们负责向内核注册(取消)网络设备。驱动必须通过struct net_device结构通过设备方法向内核提供其功能。这些是可以在网络接口上执行的操作:

static const struct net_device_ops my_netdev_ops = { 
   .ndo_open         = my_netdev_open, 
   .ndo_stop         = my_netdev_close, 
   .ndo_start_xmit   = my_netdev_start_xmit, 
   .ndo_set_rx_mode  = my_netdev_set_multicast_list, 
   .ndo_set_mac_address    = my_netdev_set_mac_address, 
   .ndo_tx_timeout   = my_netdev_tx_timeout, 
   .ndo_change_mtu   = eth_change_mtu, 
   .ndo_validate_addr      = eth_validate_addr, 
}; 

以上是大多数驱动实现的操作。

探测功能

probe功能相当基础,只需要执行一个设备的早期init,然后向内核注册我们的网络设备。

换句话说,probe功能必须:

  1. 使用alloc_etherdev()功能分配网络设备及其私有数据(在netdev_priv()的帮助下)。
  2. 初始化私有数据字段(互斥体、自旋锁、工作队列等)。如果设备位于访问功能可能休眠的总线上(例如 SPI),应该使用工作队列(和互斥)。在这种情况下,hwirq 只需确认内核代码,并安排将在设备上执行操作的工作。另一种解决方案是使用线程化 IRQ。如果设备是 MMIO,可以使用自旋锁来保护关键部分,摆脱工作队列。
  3. 初始化总线特定的参数和功能(SPI、USB、PCI 等)。
  4. 请求和映射资源(输入/输出内存、直接内存分配通道、内部资源分配)。
  5. 如有必要,生成一个随机的媒体访问控制地址并将其分配给设备。
  6. 填充任务(或有用的)网络开发属性:if_portirqnetdev_opsethtool_ops等等。
  7. 将设备置于低功耗状态(open()功能会将其从该模式中移除)。
  8. 最后,在设备上调用register_netdev()

有了 SPI 网络设备,probe功能可以如下所示:

static int my_netdev_probe(struct spi_device *spi) 
{ 
   struct net_device *dev; 
   struct priv_net_struct *priv; 
   int ret = 0; 

   /* Allocate network interface */ 
   dev = alloc_etherdev(sizeof(struct priv_net_struct)); 
   if (!dev) 
         [...] /* handle -ENOMEM error */ 

   /* Private data */ 
   priv = netdev_priv(dev); 

   /* set private data and bus-specific parameter */ 
   [...] 

   /* Initialize some works */ 
   INIT_WORK(&priv->tx_work, data_tx_work_handler); 
   [...] 

   /* Devicerealy init, only few things */ 
   if (!my_netdev_chipset_init(dev)) 
         [...] /* handle -EIO error */  

   /* Generate and assign random MAC address to the device */ 
   eth_hw_addr_random(dev); 
   my_netdev_set_hw_macaddr(dev); 

   /* Board setup must set the relevant edge trigger type; 
    * level triggers won't currently work. 
    */ 
   ret = request_irq(spi->irq, my_netdev_irq, 0, DRV_NAME, priv); 
   if (ret < 0) 
         [...]; /* Handle irq request failure */ 

   /* Fill some netdev mandatory or useful properties */ 
   dev->if_port = IF_PORT_10BASET; 
   dev->irq = spi->irq; 
   dev->netdev_ops = &my_netdev_ops; 
   dev->ethtool_ops = &my_ethtool_ops; 

   /* Put device into sleep mode */ 
   My_netdev_lowpower(priv, true); 

   /* Register our device with the kernel */ 
   if (register_netdev(dev)) 
         [...]; /* Handle registration failure error */ 

   dev_info(&dev->dev, DRV_NAME " driver registered\n"); 

   return 0; 
} 

This whole chapter is heavily inspired by the enc28j60 from Microchip. You may have a look into its code in drivers/net/ethernet/microchip/enc28j60.c.

register_netdev()函数获取一个完成的struct net_device对象,并将其添加到内核接口中;成功时返回 0,失败时返回负错误代码。struct net_device对象应该存储在总线设备结构中,以便以后访问。也就是说,如果你的网络设备是全球私有结构的一部分,你应该注册的就是这个结构。

Do note that the duplicate device name may lead to registration failure.

模块卸载

这就是清洁功能,它依赖于两个功能。我们的驱动发布功能可能如下所示:

static int my_netdev_remove(struct spi_device *spi) 
{ 
   struct priv_net_struct *priv = spi_get_drvdata(spi); 

   unregister_netdev(priv->netdev); 
   free_irq(spi->irq, priv); 
   free_netdev(priv->netdev); 

   return 0; 
} 

unregister_netdev()函数将接口从系统中移除,内核无法再调用其方法;free_netdev()释放struct net_device结构本身使用的内存以及为私有数据分配的内存,以及与网络设备相关的任何内部分配的内存。一定要注意千万不要自己解放netdev->priv

摘要

本章解释了编写网卡设备驱动所需的一切。即使本章依赖于位于 SPI 总线上的网络接口,但对于 USB 或 PCI 网络接口,原理是相同的。也可以使用为测试目的而提供的虚拟驱动。在这一章之后,很明显网卡驱动对你来说不再是谜。