Chinaunix首页 | 论坛 | 博客
  • 博客访问: 42103
  • 博文数量: 8
  • 博客积分: 20
  • 博客等级: 民兵
  • 技术积分: 20
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-07 15:33
文章分类
文章存档

2017年(1)

2016年(6)

2014年(1)

我的朋友

分类:

2016-08-03 10:28:33

rtl8139_open(打开启动设备函数)

􀁺 8139 有一个接收缓冲寄存器,用于存放接收缓存的首地址,网卡一边把网线上的发出
的数据放到内部FIFO,一边从FIFO 中把数据通过DMA 传送到由接收寄存器指定的内存
地址中,接收到的数据依次排放,当长度超过默认的缓冲区长度时,会回过头来放到开
始的地方,所以接收缓冲区被称为环形缓冲区。
􀁺 8139 有四个发送地址寄存器,CPU 将要发送的数据在内存中的地址写入这四个寄存器
中的任何一个,网卡就会通过DMA 操作把数据发送出去。
􀁺 当发送或者接送完成后,网卡会发出中断,中断处理程序通过读取网卡的中断状态寄存
器来识别出是发送完成发出的中断,接收到数据包的中断,还是错误中断。
当运行ifconfig ethx up 的时候,rtl8139_open 得到调用。该函数的任务就是分配和初始
化接收及发送缓冲区,分配中断号,挂接中断处理程序等。
当网卡发生中断(如数据到达)时,中断控制器8259A 把中断号发给CPU, CPU 根据这个
中断号找到处理程序,这里就是rtl8139_interrupt,然后执行。rtl8139_interrupt 也是在我们
的程序中定义 好了的,这是驱动程序的一个重要的义务,也是一个基本的功能。request_irq
的代码在arch/i386/kernel/irq.c 中。
① 注册这个设备的中断处理函数。当网卡发送数据完成或者接收到数据时,是用中断
的形式来告知的,比如有数据从网线传来,中断也通知了我们,那么必须要有一个
处理这个中断的函数来完成数据的接收。就是中断号的分配,和内存地址映射一样,
中断号也是BIOS 在初始化阶段分配并写入设备的配置空间的,然后Linux 在建立
pci_dev 时从配置空间读 出这个中断号然后写入pci_dev 的irq 成员中,所以我们注
册中断程序需要中断号就是直接从pci_dev 里取就可以了。
request_irq 的代码在arch/i386/kernel/irq.c 中
② 分配发送和接收的缓存空间
根据官方文档,发送一个数据包的过程是这样的:先从应用程序中把数据包拷贝到
一段连续的内存中(这段内存就是我们这里要分配的缓存),然后把这段内存的地
址写进网卡的数据发送地址寄存器(TSAD)中,这个寄存器的偏移量是TxAddr0 =
0x20。在把这个数据包的长度写进另一个寄存器(TSD)中,它的偏移量是TxStatus0
= 0x10。然后就把这段内存的数据发送到网卡内部的发送缓冲中(FIFO),最后由这个
发送缓冲区把数据发送到网线上。
tx_bufs 是发送缓冲内存的首地址,rx_ring 是接收缓存内存的首地址,他们都是虚
拟地址,而最后 一个参数tx_bufs_dma 和rx_ring_dma 均是这一段内存的物理地址。
为什么同一个事物,既用虚拟地址来表示它还要用物理地址呢,是这样 的,CPU
执行程序用到这个地址时,用虚拟地址,而网卡设备向这些内存中存取数据时用的
是物理地址( 因为网卡相对CPU 属于头脑比较简单型的)。
pci_alloc_consistent 的代码在Linux/arch/i386/kernel/pci‐dma.c
中。
③发送和接收缓冲区初始化和网卡开始工作的操作
􀁺 RTL8139 有4 个发送描述符(包括4 个发送缓冲区的基地址寄存器(TSAD0‐TSAD3)
和4 个发送状态寄存器(TSD0‐TSD3)。也就是说我们分配的缓冲区要分成四个等分
并把这四个空间的地址都写到相关寄存器里去,下面这段代码完成了这个操作。
for (i = 0; i < NUM_TX_DESC; i++)
((struct rtl8139_private*)dev‐>priv)‐>tx_buf[i] =
&((struct rtl8139_private*)dev‐>priv)‐>tx_bufs[i * TX_BUF_SIZE];
上面这段代码负责把发送缓冲区虚拟空间进行了分割。
for (i = 0; i < NUM_TX_DESC; i++)
{
writel(tp‐>tx_bufs_dma+(tp‐>tx_buf[i]tp‐>tx_bufs),ioaddr+TxAddr0+(i*4));
readl(ioaddr+TxAddr0+(i * 4));
}
上面这段代码负责把发送缓冲区物理空间进行了分割,并把它写到了相关寄存器
中,这样在网卡开始工作后就能够迅速定位和找到这些内存并存取他们的数据。
writel(tp‐>rx_ring_dma,ioaddr+RxBuf);
上面这行代码是把接收缓冲区的物理地址写到了相关寄存器中,这样网卡接收到
数据后就能准确的把数据从网卡中搬运到这些内存空间中,等待CPU 来领走他们。

static int rtl8139_open (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
int retval;
void *ioaddr = tp->mmio_addr;
//注册中断处理函数rtl8139_interrupt

//就是中断号的分配,和内存地址映射一样,中断号也是BIOS 在初始化阶

段分配
//并写入设备的配置空间的,然后Linux 在建立pci_dev 时从配置空间读 出

这个中断
//号然后写入pci_dev 的irq 成员中,所以我们注册中断程序需要中断号就

是直接从pci_dev 里取就可以了。
retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ,
dev->name, dev);
if (retval)
return retval;
//分配并初始化 8139 所需的接收与传送缓冲区

//分配接收,发送缓冲区, DMA 没有CPU 的MMU 单元,因此只

//能使用物理地址上连续的内存空间。

// tx_bufs 是发送缓冲内存的首地址,是虚拟地址,参数tx_bufs_dma 是

物理地址
tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
&tp->tx_bufs_dma);//发送缓冲区

// rx_ring 是接收缓存内存的首地址,是虚拟地址,参数rx_ring_dma 是

物理地址
tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
&tp->rx_ring_dma);//接受缓冲区

if (tp->tx_bufs == NULL || tp->rx_ring == NULL) {
free_irq(dev->irq, dev);
if (tp->tx_bufs)
pci_free_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
tp->tx_bufs, tp->tx_bufs_dma);
if (tp->rx_ring)
pci_free_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
tp->rx_ring, tp->rx_ring_dma);
return -ENOMEM;
}
tp->mii.full_duplex = tp->mii.force_media;
tp->tx_flag = (TX_FIFO_THRESH << 11) & 0x003f0000;
//初始化发送和接收缓冲区

//由于有四个发送地址寄存器,因此把发送缓冲区分成4 组,以后发送请

求到来的时候,
//将待发送内容拷贝到第□组,再将第□组的地址写□寄存器,之后依次轮

流使用第二,三,四,一组。. . .
rtl8139_init_ring (dev);
//对网卡硬件进行相关的初始化

rtl8139_hw_start (dev);
//设置网络设备的状态,启用接收队列

netif_start_queue (dev);
//如果网络设备的状态启动,输出调试信息。

if (netif_msg_ifup(tp))
printk(KERN_DEBUG "%s: rtl8139_open() ioaddr %#lx IRQ %d"
" GP Pins %2.2x %s-duplex.\n",
dev->name, pci_resource_start (tp->pci_dev, 1),
dev->irq, RTL_R8 (MediaStatus),
tp->mii.full_duplex ? "full" : "half");
//产生一个 kernel thread 负责查看网络连线的状态

rtl8139_start_thread(dev);
return 0;
}

rtl8139_init_ring (dev);

/* Initialize the Rx and Tx rings, along with various 'dev' bits. */
static void rtl8139_init_ring (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
int i;
tp->cur_rx = 0;//当前接受缓冲区指针位置

tp->cur_tx = 0;//当前发送缓冲区指针位置

tp->dirty_tx = 0;//

for (i = 0; i < NUM_TX_DESC; i++)
tp->tx_buf[i] = &tp->tx_bufs[i * TX_BUF_SIZE];
}

rtl8139_hw_start (dev);

rtl8139_hw_start 主要是对网卡芯片进行初始化,主要就是根据网卡的硬件手册,
Programming guide 向一些寄存器写入某些值。接下来我们将看到大量类似的操作。

/* Start the hardware at open or resume. */
static void rtl8139_hw_start (struct net_device *dev)
{
struct rtl8139_private *tp = dev->priv;
void *ioaddr = tp->mmio_addr;
u32 i;
u8 tmp;
/* Bring old chips out of low-power mode. */
if (rtl_chip_info[tp->chipset].flags & HasHltClk)
RTL_W8 (HltClk, 'R');
// /􀗛 向网卡命令寄存器写入一个RESET 命令,这样网卡的各个寄存器恢

复到默认状态。&#1050075;/
rtl8139_chip_reset (ioaddr);
/* unlock Config[01234] and BMCR register writes */
RTL_W8_F (Cfg9346, Cfg9346_Unlock);
/* Restore our idea of the MAC address. */
// MAC0 = 0, /* Ethernet hardware address. */

// /􀗛 MAC0 被定义成0 ,在8 1 3 9 网卡PCI 空间基址偏移为0 的6 个

字节是用于
//存放网卡 MAC 地址的,现在把之前从EEPROM 中读出来的MAC 地址写入

这个地址,
//将来网卡在收包的时候,就会根据这个寄存器中的值来确定自己的MAC 地

址。
//现在大家该明白为什么我们平时能改MAC 地址了吧。􀗛/

RTL_W32_F (MAC0 + 0, cpu_to_le32 (*(u32 *) (dev->dev_addr + 0)));
RTL_W32_F (MAC0 + 4, cpu_to_le32 (*(u32 *) (dev->dev_addr + 4)));
/* Must enable Tx/Rx before setting transfer thresholds! */
// /􀗛 向命令写□命令,允许发送和接送。􀗛/

RTL_W8 (ChipCmd, CmdRxEnb | CmdTxEnb);
//向接收配置寄存器写□配置,以后该网卡只接收广播帧和目的MAC 地址

是自□的帧。
//设为混杂模式的时候,则是向这个寄存器写□。AcceptAllPhys

tp->rx_config = rtl8139_rx_config | AcceptBroadcast | AcceptMyPhys;
RTL_W32 (RxConfig, tp->rx_config);
/* Check this value: the documentation for IFG contradicts ifself.
*/

RTL_W32 (TxConfig, rtl8139_tx_config);
tp->cur_rx = 0;
rtl_check_media (dev, 1);
if (tp->chipset >= CH_8139B) {
/* Disable magic packet scanning, which is enabled
* when PM is enabled in Config1. It can be reenabled
* via ETHTOOL_SWOL if desired. */

RTL_W8 (Config3, RTL_R8 (Config3) & ~Cfg3_Magic);
}
DPRINTK("init buffer addresses\n");
/* Lock Config[01234] and BMCR register writes */
RTL_W8 (Cfg9346, Cfg9346_Lock);
/* init Rx ring buffer DMA address */
RTL_W32_F (RxBuf, tp->rx_ring_dma);
/* init Tx buffer DMA addresses */
for (i = 0; i < NUM_TX_DESC; i++)
RTL_W32_F (TxAddr0 + (i * 4), tp->tx_bufs_dma + (tp->tx_buf[i] -
tp->tx_bufs));
RTL_W32 (RxMissed, 0);
rtl8139_set_rx_mode (dev);
/* no early-rx interrupts */
RTL_W16 (MultiIntr, RTL_R16 (MultiIntr) & MultiIntrClear);
/* make sure RxTx has started */
tmp = RTL_R8 (ChipCmd);
if ((!(tmp & CmdRxEnb)) || (!(tmp & CmdTxEnb)))
RTL_W8 (ChipCmd, CmdRxEnb | CmdTxEnb);
/* Enable all known interrupts by setting the interrupt mask. */
//向中断屏蔽寄存器写□中断允许位。默认允许接送,发送,错误等等中断。

//如果屏蔽了接送中断,那么当网卡接收到帧的时候就不会发出中断了

// NAPI 就是通过屏蔽这里的接收中断,而通过轮询接收状态寄存器来查看

是不是有帧收到了
//来减少中断次数,提高效率的。由于网卡接收小包的速度快,如果按常规

处理流程,
//中断−> CPU 保存一堆寄存器然后中断处理−> CPU 恢复一堆寄存器−> 调

度决策&#8722;> . . . . . .
//在小包高速发送的环境下,尤其是在测试的时候,很可能在刚一恢复线程

上下文,中断又来了。
// 又得重新保存上下文进入中断处理,关闭中断进行轮询就是要节省这几

个NAPI CPU 时钟周期。
//。对大包来说,一个包收的要慢一点,很可能在执行poll 轮询的时候,

//第一次检测网卡状态寄存器发现有包了, CPU Copy 出来处理,之后再检

测那个寄存器的时候,
//下一个大包的接收还没完成,于是开了中断,结束一次,然后恢复线程上

下文,
//然后过了很小的一段时间,中断又来了,所以大包省不了几个中断切换的

时钟周期,
//效果不明显。poll ∗/

RTL_W16 (IntrMask, rtl8139_intr_mask);
}


以上都是向某个地址写入一些值,基地址是PCI 总线驱动程序配置的,某个偏移位置的地址
代表什么意思,该写入什么值是功能设备的芯片逻辑规定的。以接收配置寄存器为例:
接收模式参数寄存器编号为RxConfig = 0x44, 即该寄存器地址偏移是0x44
AcceptAllPhys 被定义为0x01, AcceptMyPhys 被定义为0x02,
就是说该寄存器的最低位为1 时,网卡会进入混杂模式接收所有的帧,
第1 位为0,第2 位为1 时,只接收目的MAC 地址为自己的帧。
彻底的搞清楚每一个寄存器的每个BIT 代表什么实在是没有必要,不同的网卡芯片都是不一
样的。

netif_start_queue(netdevice.h)

src/include/linux/netdevice.h
static inline void netif_start_queue(struct net_device *dev)
{
clear_bit(__LINK_STATE_XOFF, &dev‐>state);
}

netif_msg_ifup(netdevice.h)

src/include/linux/netdevice.h
#define netif_msg_ifup(p) ((p)>msg_enable & NETIF_MSG_IFUP)

rtl8139_start_thread

static void rtl8139_start_thread(struct rtl8139_private *tp)
{
tp->twistie = 0;
if (tp->chipset == CH_8139_K)
tp->twistie = 1;
else if (tp->drv_flags & HAS_LNK_CHNG)
return;
tp->have_thread = 1;
tp->watchdog_fired = 0;
schedule_delayed_work(&tp->thread, next_tick);
}

8139 的物理寄存器

/* Symbolic offsets to registers. */
enum RTL8139_registers {
MAC0 = 0, /* Ethernet hardware address. */
MAR0 = 8, /* Multicast filter. */
TxStatus0 = 0x10, /* Transmit status (Four 32bit registers). */
TxAddr0 = 0x20, /* Tx descriptors (also four 32bit). */
RxBuf = 0x30,
ChipCmd = 0x37,
RxBufPtr = 0x38,
RxBufAddr = 0x3A,
IntrMask = 0x3C,
IntrStatus = 0x3E,
TxConfig = 0x40,
RxConfig = 0x44,
Timer = 0x48, /* A general-purpose counter. */
RxMissed = 0x4C, /* 24 bits valid, write clears. */
Cfg9346 = 0x50,
Config0 = 0x51,
Config1 = 0x52,
FlashReg = 0x54,
MediaStatus = 0x58,
Config3 = 0x59,
Config4 = 0x5A, /* absent on RTL-8139A */
HltClk = 0x5B,
MultiIntr = 0x5C,
TxSummary = 0x60,
BasicModeCtrl = 0x62,
BasicModeStatus = 0x64,
NWayAdvert = 0x66,
NWayLPAR = 0x68,
NWayExpansion = 0x6A,
/* Undocumented registers, but required for proper operation. */
FIFOTMS = 0x70, /* FIFO Control and test. */
CSCR = 0x74, /* Chip Status and Configuration Register. */
PARA78 = 0x78,
PARA7c = 0x7c, /* Magic transceiver parameter register. */
Config5 = 0xD8, /* absent on RTL-8139A */
};

8139 接收寄存器模式参数

/* Bits in RxConfig. */
enum rx_mode_bits {
AcceptErr = 0x20,
AcceptRunt = 0x10,
AcceptBroadcast = 0x08,
AcceptMulticast = 0x04,
AcceptMyPhys = 0x02,
AcceptAllPhys = 0x01,
};


阅读(1625) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~