1 中断函数
static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)
所有有网卡产生的中断都会引起该中断函数的调用,这是在
static int rtl8139_open (struct net_device *dev)
{
struct rtl8139_private *tp = netdev_priv(dev);
int retval;
void __iomem *ioaddr = tp->mmio_addr;
/* 注册中断处理函数 ,中断号为共享 */
retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);
完成的。
中断函数处理的事件可以大致分为几类:
A 数据包到达产生的中断(RxAckBits = RxFIFOOver | RxOverflow | RxOK);
B 异常事件,通常都是出错的情况(RxAckBits = RxFIFOOver | RxOverflow | RxOK)
C发送完成事件(TxOK | TxErr)
下面我们看看具体的代码
static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)
{
/* 参数dev_instance是在上面注册中断处理函数的时候传入的 */
struct net_device *dev = (struct net_device *) dev_instance;
/* tp 为网卡驱动自定义的驱动特有的数据,和dev一起分配的 */
struct rtl8139_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->mmio_addr;
u16 status, ackstat;
int link_changed = 0; /* avoid bogus "uninit" warning */
int handled = 0;
/* 对驱动数据加锁*/
spin_lock (&tp->lock);
/*读中断状态寄存器,获取中断状态*/
status = RTL_R16 (IntrStatus);
/* 这时由共享此中断号的其它设备产生的中断 */
if (unlikely((status & rtl8139_intr_mask) == 0))
goto out;
handled = 1;
/* 硬件错误 */
if (unlikely(status == 0xFFFF))
goto out;
/* 设备已关闭*/
if (unlikely(!netif_running(dev))) {
/* 屏蔽所有中断*/
RTL_W16 (IntrMask, 0);
goto out;
}
/* Acknowledge all of the current interrupt sources ASAP, but
an first get an additional status bit from CSCR. */
if (unlikely(status & RxUnderrun))
link_changed = RTL_R16 (CSCR) & CSCR_LinkChangeBit;
ackstat = status & ~(RxAckBits | TxErr);
if (ackstat)
RTL_W16 (IntrStatus, ackstat);
下一步处理数据包到达事件
/* Receive packets are processed by poll routine.
If not running start it now. */
if (status & RxAckBits){
if (netif_rx_schedule_prep(dev, &tp->napi)) {
RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
__netif_rx_schedule(dev, &tp->napi);
}
}
先强调下8139网卡驱动的数据接收方式采用的是Linux内核的NAPI新机制。
NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据。随着网络的接收速度的增加,NIC 触发的中断能做到不断减少。
使用 NAPI 先决条件:
驱动可以继续使用老的 2.4 内核的网络驱动程序接口,NAPI 的加入并不会导致向前兼容性的丧失,但是 NAPI 的使用至少要得到下面的保证:
1. 要使用 DMA 的环形输入队列(也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。
2. 在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称 rx-ring)处理队列中。 (这就是锁NAPI机制并不是所有的网卡都能支持的,只有在关闭中断的情况下,任然能通过DMA接收数据包的网卡才可以)
NAPI 对数据包到达的事件的处理采用轮询方法,在数据包达到的时候,NAPI 就会强制执行dev->poll 方法。而和不像以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。
--------------------------------------------------------------------------------
如果定义了采用NAPI模式接收数据包,则进入这个调用点。首先调用netif_rx_schedule_prep(dev),确定设备处于运行,而且设备还没有被添加到网络层的 POLL 处理队列中,在调用 netif_rx_schedule之前会调用这个函数。接下来调用__netif_rx_schedule(dev),将设备的 POLL 方法添加到网络层次的 POLL 处理队列中去,排队并且准备接收数据包,在使用之前需要调用 netif_rx_reschedule_prep,并且返回的数为 1,并且触发一个 NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。处理完成。
下面介绍一下__netif_rx_schedule(netdev)函数的作用:
static inline void __netif_rx_schedule(struct net_device *dev)
{
unsigned long flags;
/* 获取当前CPU。 */
int cpu = smp_processor_id();
local_irq_save(flags);
dev_hold(dev);
/*将当前设备加入CPU相关全局队列softnet_data的轮询设备列表中,不过值得注意的是,这个列表中的设备不一定都执行轮询接收数据包,这里的poll_list只是表示当前设备需要接收数据,具体采用中断还是轮询的方式,取决于设备提供的poll方法。*/
list_add_tail(&dev->poll_list, &softnet_data[cpu].poll_list);
/*
调用函数产生网络接收软中断。也就是系统将运行net_rx_action()处理网络数据。
*/
__cpu_raise_softirq(cpu, NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
接下来是多出错事件以及数据包发送完成事件的处理
/* Check uncommon events with one test. */
if (unlikely(status & (PCIErr | PCSTimeout | RxUnderrun | RxErr)))
rtl8139_weird_interrupt (dev, tp, ioaddr,
status, link_changed);
if (status & (TxOK | TxErr)) {
rtl8139_tx_interrupt (dev, tp, ioaddr);
if (status & TxErr)
RTL_W16 (IntrStatus, TxErr);
}
出错事件的处理较为简单,只是对一些出错计数的处理。下面我们看看发送完成事件的处理
2 发送完成事件处理
static void rtl8139_tx_interrupt (struct net_device *dev,
struct rtl8139_private *tp,
void __iomem *ioaddr)
{
unsigned long dirty_tx, tx_left;
assert (dev != NULL);
assert (ioaddr != NULL);
/*dirty_tx是最近发送数据包时,没有经中断处理的最早数据包所对应的