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

2017年(1)

2016年(6)

2014年(1)

我的朋友

分类: LINUX

2016-08-03 10:27:31

设备侦听/设备初始化(rtl8139_init_one)

rtl8139_init_one 用来初始化一个 8139 芯片。PCI 驱动程序最大的好处是 PCI BUS
提供了组态空间 (configuration space) 来存放驱动程序所需的 IO 位址及中断号码等资料,
我们不必再像 ISA 驱动程序一样需要指定这些资源。
rtl8139_init_one,它主要做了七件事:
① 建立 net_device 结构,让它在内核中代表这个网络设备。但是读者可能会问,pci_dev
也是代表着这个设备,那么两者有什么区别呢,正如我们上 面讨论的,网卡设备
既要遵循PCI 规范,也要担负起其作为网卡设备的职责,于是就分了两块,pci_dev
用来负责网卡的PCI 规范,而这里要说的 net_device 则是负责网卡的网络设备这个
职责。
② 开启这个设备(其实是开启了设备的寄存器映射到内存的功能), \
pci_enable_device 也是一个内核开发出来的接口,代码在drivers/pci/pci.c 中,笔者
跟踪发现这个函数主要就是把PCI 配 置空间的Command 域的0 位和1 位置成了1,
从而达到了开启设备的目的,因为rtl8139 的官方datasheet 中,说明了这两位的作
用就是开启 内存映射和I/O 映射,如果不开的话,那我们以上讨论的把控制寄存器
空间映射到内存空间的这一功能就被屏蔽了,这对我们是非常不利的,除此之外,
pci_enable_device 还做了些中断开启工作。
③ 获得各项资源
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
也许疑问我们的寄存器被映射到内存中的什么地方是什么时候有谁决定的呢。是这
样的,在硬件加电初始化时,BIOS 固件同一检查了所有的PCI 设备,并 统一为他
们分配了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们
的寄存器,这些地址被BIOS 写进了各个设备的配置空间,因为这个活 动是一个
PCI 的标准的活动,所以自然写到各个设备的配置空间里而不是他们风格各异的控
制寄存器空间里。当然只有BIOS 可以访问配置空间。当操作系统初 始化时,他为
每个PCI 设备分配了pci_dev 结构,并且把BIOS 获得的并写到了配置空间中的地址
读出来写到了pci_dev 中的resource 字 段中。这样以后我们在读这些地址就不需
要在访问配置空间了,直接跟pci_dev 要就可以了,我们这里的四个函数就是直接
从pci_dev 读出了相关数 据,代码在include/linux/pci.h 中。定义如下:

􀁺 pci_resource_start
􀁺 pci_resource_end
􀁺 pci_resource_flags
􀁺 pci_resource_len
每个PCI 设备有0‐5 一共6 个地址空间,我们通常只使用前两个,这里我们把参数
1 传给了bar 就是使用内存映射的地址空间。
④ 把得到的地址进行映射
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
printk ("cannot remap MMIO, aborting\n");
rc = ‐EIO;
goto err_out_free_res;
} ioremap 是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经
从pci_dev 中读了出来(上一步),这样就水到渠成的成功映射了而 不会和其他地
址有冲突。映射完了有什么效果呢,我举个例子,比如某个网卡有100 个寄存器,
他们都是连在一块的,位置是固定的,加入每个寄存器占4 个字 节,那么一共400
个字节的空间被映射到内存成功后,ioaddr 就是这段地址的开头(注意ioaddr 是虚
拟地址,而mmio_start 是物理地址, 它是BIOS 得到的,肯定是物理地址,而保护
模式下CPU 不认物理地址,只认虚拟地址),ioaddr+0 就是第一个寄存器的地址,
ioaddr+4 就是 第二个寄存器地址(每个寄存器占4 个字节),以此类推,我们就能
够在内存中访问到所有的寄存器进而操控他们了。
⑤ 重启网卡设备
重启网卡设备是初始化网卡设备的一个重要部分,它的原理就是向寄存器中写入命
令就可以了(注意这里写寄存器,而不是配置空间,因为跟PCI 没有什么关系),代
码如下:
writeb ((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdReset,ioaddr+ChipCmd);
是 我们看到第二参数ioaddr+ChipCmd,ChipCmd 是一个位移,使地址刚好对应的
就是ChipCmd 哪个寄存器,读者可以查阅官方 datasheet 得到这个位移量,我们在
程序中定义的这个值为:ChipCmd = 0x37;与datasheet 是吻合的。我们把这个命令
寄存器中相应位(RESET)置1 就可以完成操作。
⑥ 获得 MAC 地址,并把它存储到net_device 中。
for(i = 0; i < 6; i++) { /* Hardware Address */
dev‐>dev_addr[i] = readb(ioaddr+i);
dev‐>broadcast[i] = 0xff;
}
我们可以看到读的地址是ioaddr+0 到ioaddr+5,读者查看官方datasheet 会发现
寄存器地址空间的开头6 个字节正好存的是这个网卡设备的MAC 地址,MAC 地址
是网络中标识网卡的物理地址,这个地址在今后的收发数据包时会用的上。
⑦ 向 net_device 中登记一些主要的函数
dev‐>open = rtl8139_open;
dev‐>hard_start_xmit = rtl8139_start_xmit;
dev‐>stop = rtl8139_close;
由于dev(net_device)代表着设备,把这些函数注册完后,rtl8139_open 就是用于
打开这个设备, rtl8139_start_xmit 就是当应用程序要通过这个设备往外面发数据
时被调用,具体的其实这个函数是在网络协议层中调用的,这就涉及到 Linux 网络
协议栈的内容,不再我们讨论之列,我们只是负责实现它。rtl8139_close 用来关掉
这个设备。

static int __devinit rtl8139_init_one (struct pci_dev *pdev,
const struct pci_device_id *ent)
// rtl8139_init_one 的参数就是PCI 总线驱动程序在设备枚举过程中创建的。

{
struct net_device *dev = NULL;
struct rtl8139_private *tp;
int i, addr_len, option;
void *ioaddr;
static int board_idx = -1;
u8 pci_rev;
assert (pdev != NULL);
assert (ent != NULL);
board_idx++;
/* when we're built into the kernel, the driver version message
* is only printed if at least one 8139 board has been found
*/

#ifndef MODULE
{
static int printed_version;
if (!printed_version++)
printk (KERN_INFO RTL8139_DRIVER_NAME "\n");
}
#endif
pci_read_config_byte(pdev, PCI_REVISION_ID, &pci_rev);//读取该pci

设备的基本信息
if (pdev->vendor == PCI_VENDOR_ID_REALTEK &&
pdev->device == PCI_DEVICE_ID_REALTEK_8139 && pci_rev >= 0x20)
{
printk(KERN_INFO PFX "pci dev %s (id %04x:%04x rev %02x) is an
enhanced 8139C+ chip\n"
,
pci_name(pdev), pdev->vendor, pdev->device, pci_rev);
printk(KERN_INFO PFX "Use the \"8139cp\" driver for improved
performance and stability.\n"
);
}
//rtl8139_init_one 会呼叫 rtl8139_init_board 来初始化芯片,基本上 8139

这个芯片
//算是一个很容易使用的芯片,基本的 PCI 初始化后就可以直接使用了。

//所以 rtl8139_init_one 和 rtl8139_init_board

//其实多半是在做一些错误检查的动作,并由 PCI 表格中所得稍后会用的到的资源

i = rtl8139_init_board (pdev, &dev);//初始化该pci 设备的板级信息(1)

if (i < 0)
return i;
assert (dev != NULL);
tp = dev->priv;
assert (tp != NULL);
ioaddr = tp->mmio_addr;
assert (ioaddr != NULL);
addr_len = read_eeprom (ioaddr, 0, 8) == 0x8129 ? 8 : 6;
for (i = 0; i < 3; i++)
((u16 *) (dev->dev_addr))[i] =
le16_to_cpu (read_eeprom (ioaddr, i + 7, addr_len));
/* The Rtl8139-specific entries in the device structure. */
//配置RTL8139 的net_device 的其他配置信息(4)

// /􀗛 设置功能驱动程序对象的函数指针集􀗛/

dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->poll = rtl8139_poll;
dev->weight = 64;
dev->stop = rtl8139_close;
dev->get_stats = rtl8139_get_stats;
dev->set_multicast_list = rtl8139_set_rx_mode;
dev->do_ioctl = netdev_ioctl;
dev->ethtool_ops = &rtl8139_ethtool_ops;
dev->tx_timeout = rtl8139_tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
#ifdef CONFIG_NET_POLL_CONTROLLER
dev->poll_controller = rtl8139_poll_controller;
#endif
/* note: the hardware is not capable of sg/csum/highdma, however
* through the use of skb_copy_and_csum_dev we enable these
* features
*/

dev->features |= NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_HIGHDMA;
dev->irq = pdev->irq;
/* dev->priv/tp zeroed and aligned in alloc_etherdev */
tp = dev->priv;
/* note: tp->chipset set in rtl8139_init_board */
tp->drv_flags = board_info[ent->driver_data].hw_flags;
tp->mmio_addr = ioaddr;
tp->msg_enable =
(debug < 0 ? RTL8139_DEF_MSG_ENABLE : ((1 << debug) - 1));
spin_lock_init (&tp->lock);//(5)

spin_lock_init (&tp->rx_lock);
init_waitqueue_head (&tp->thr_wait);//(6)

init_completion (&tp->thr_exited);//(7)

tp->mii.dev = dev;
tp->mii.mdio_read = mdio_read;
tp->mii.mdio_write = mdio_write;
tp->mii.phy_id_mask = 0x3f;
tp->mii.reg_num_mask = 0x1f;
/* dev is fully set up and ready to use now */
//设备已经正常启动,显示提示信息,并注册网络设备(8)

DPRINTK("about to register device named %s (%p)...\n", dev->name,
dev);
i = register_netdev (dev);
if (i) goto err_out;//如果注册设备失败,就进行进一步的清理。

pci_set_drvdata (pdev, dev);//(9)

printk (KERN_INFO "%s: %s at 0x%lx, "
"%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
"IRQ %d\n",
dev->name,
board_info[ent->driver_data].name,
dev->base_addr,
dev->dev_addr[0], dev->dev_addr[1],
dev->dev_addr[2], dev->dev_addr[3],
dev->dev_addr[4], dev->dev_addr[5],
dev->irq);
printk (KERN_DEBUG "%s: Identified 8139 chip type '%s'\n",
dev->name, rtl_chip_info[tp->chipset].name);
/* Find the connected MII xcvrs.
Doing this in open() would allow detecting external xcvrs later,
but
takes too much time. */
//10

#ifdef CONFIG_8139TOO_8129
if (tp->drv_flags & HAS_MII_XCVR) {
int phy, phy_idx = 0;
for (phy = 0; phy < 32 && phy_idx < sizeof(tp->phys); phy++) {
int mii_status = mdio_read(dev, phy, 1);
if (mii_status != 0xffff && mii_status != 0x0000) {
u16 advertising = mdio_read(dev, phy, 4);
tp->phys[phy_idx++] = phy;
printk(KERN_INFO "%s: MII transceiver %d status 0x%4.4x "
"advertising %4.4x.\n",
dev->name, phy, mii_status, advertising);
}
}
if (phy_idx == 0) {
printk(KERN_INFO "%s: No MII transceivers found! Assuming
SYM "

"transceiver.\n",
dev->name);
tp->phys[0] = 32;
}
} else
#endif
tp->phys[0] = 32;
tp->mii.phy_id = tp->phys[0];
/* The lower four bits are the media type. */
option = (board_idx >= MAX_UNITS) ? 0 : media[board_idx];
if (option > 0) {
tp->mii.full_duplex = (option & 0x210) ? 1 : 0;
tp->default_port = option & 0xFF;
if (tp->default_port)
tp->mii.force_media = 1;
}
if (board_idx < MAX_UNITS && full_duplex[board_idx] > 0)
tp->mii.full_duplex = full_duplex[board_idx];
if (tp->mii.full_duplex) {
printk(KERN_INFO "%s: Media type forced to Full Duplex.\n",
dev->name);
/* Changing the MII-advertised media because might prevent
re-connection. */

tp->mii.force_media = 1;
}
if (tp->default_port) {
printk(KERN_INFO " Forcing %dMbps %s-duplex operation.\n",
(option & 0x20 ? 100 : 10),
(option & 0x10 ? "full" : "half"));//11

mdio_write(dev, tp->phys[0], 0,
((option & 0x20) ? 0x2000 : 0) | /* 100Mbps? */
((option & 0x10) ? 0x0100 : 0)); /* Full duplex? */
}
/* Put the chip into low-power mode. */
if (rtl_chip_info[tp->chipset].flags & HasHltClk)
RTL_W8 (HltClk, 'H'); /* 'R' would leave the clock running.
*/
//12

return 0;
err_out:
__rtl8139_cleanup_dev (dev);//13

return i;
}

rtl8139_init_board (pdev, &dev);//初始化该pci 设备的板级信息(1)

static int __devinit rtl8139_init_board (struct pci_dev *pdev,
struct net_device **dev_out)
{
void *ioaddr;
struct net_device *dev;
struct rtl8139_private *tp;
u8 tmp8;
int rc;
unsigned int i;
unsigned long pio_start, pio_end, pio_flags, pio_len;
unsigned long mmio_start, mmio_end, mmio_flags, mmio_len;
u32 version;
assert (pdev != NULL);
*dev_out = NULL;
/* dev and dev->priv zeroed in alloc_etherdev */
dev = alloc_etherdev (sizeof (*tp));
if (dev == NULL) {
printk (KERN_ERR PFX "%s: Unable to alloc new net device\n",
pci_name(pdev));
return -ENOMEM;
}
SET_MODULE_OWNER(dev);//设置模块的名字,用于计数。

SET_NETDEV_DEV(dev, &pdev->dev);
tp = dev->priv;
tp->pci_dev = pdev;
/* enable device (incl. PCI PM wakeup and hotplug setup) */
//启用设备的memory/ I o 译码,如果设备处于休眠状态,则唤醒设备。

在启用设备之前,
//虽然设备的基址寄存器中设置了值,但是当总线有主设备对该地址进行寻

址的时候,
//设备是不会响应的。具体启用操作由总线驱动程序封装,这里通过调用

pci 总线
//驱动程序提供的函数来完成该任务

// pci_enable_device 也是一个内核开发出来的接口,代码在

drivers/pci/pci.c 中,
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
// 由 PCI 子系统中读出所需的资源

// 大多数设备上有自己的板载存储空间,其中包括memory 空间和IO 空间,

//在X86 这样的IO 与Memory 分立编址的系统上,他们的区别就在于独立的IO 访

问指令,
//独立的地址译码,在多数统一编址的系统上memeor 空间和IO 空间没有本质区别。

//CPU 给出的指令中的地址落在哪里,设备在电路级别会访问到对应地址的板载存储空

间。
//并从基址寄存器中读出各个设备的基地址,也可以推倒从新统□分配。

//这里功能驱动程序需要知道自己如何访问到目标设备,所以从总线物理设备对象中读取

基址信息。../
pio_start = pci_resource_start (pdev, 0);
pio_end = pci_resource_end (pdev, 0);
pio_flags = pci_resource_flags (pdev, 0);
pio_len = pci_resource_len (pdev, 0);
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
/* set this immediately, we need to know before
* we talk to the chip directly */

DPRINTK("PIO region size == 0x%02X\n", pio_len);
DPRINTK("MMIO region size == 0x%02lX\n", mmio_len);
// PCI 设备板载存储空间可以通过IO 或者Memory 方式来访问, PCI 设备上

分别有IO
//和Memory 基地址寄存器,在X86 上的有独立的IO 指令,对于某些统□编址

的体系结构则
//只有通过Memory 方式来访问。

#ifdef USE_IO_OPS
/* make sure PCI base addr 0 is PIO */
if (!(pio_flags & IORESOURCE_IO)) {
printk (KERN_ERR PFX "%s: region #0 not a PIO resource,
aborting\n"
, pci_name(pdev));
rc = -ENODEV;
goto err_out;
}
/* check for weird/broken PCI region reporting */
if (pio_len < RTL_MIN_IO_SIZE) {
printk (KERN_ERR PFX "%s: Invalid PCI I/O region size(s),
aborting\n"
, pci_name(pdev));
rc = -ENODEV;
goto err_out;
}
#else
/* make sure PCI base addr 1 is MMIO */
if (!(mmio_flags & IORESOURCE_MEM)) {
printk (KERN_ERR PFX "%s: region #1 not an MMIO resource,
aborting\n"
, pci_name(pdev));
rc = -ENODEV;
goto err_out;
}
if (mmio_len < RTL_MIN_IO_SIZE) {
printk (KERN_ERR PFX "%s: Invalid PCI mem region size(s),
aborting\n"
, pci_name(pdev));
rc = -ENODEV;
goto err_out;
}
#endif
//// 将这些资源保留下来

// PCI 设备的地址是由软件配置的,那么必然需要□种机制来防止地址冲突,系统维护

了io r e s o u r c e
//和memory r e s o u r c e ,分别登记了已经分配出去的地址空间,当为新设备分

配地址的时候,
//必须根据登记的信息找到空闲的地址空间,这里就是在系统中登记,表示该设备要使用

这个地址空间了。
rc = pci_request_regions (pdev, "8139too");
if (rc)
goto err_out;
/* enable PCI bus-mastering */
pci_set_master (pdev);
#ifdef USE_IO_OPS
ioaddr = (void *) pio_start;
dev->base_addr = pio_start;
tp->mmio_addr = ioaddr;
tp->regs_len = pio_len;
#else
/* ioremap MMIO region */
//映射设备地址,这里需要搞清楚3 种地址:

// 1 . 虚拟地址:经过页表页目录映射,访问的时候通过CPU 的MMU 转换

得到物理地址。
// 2 . 物理地址:在实模式下使用的或保护模式下经过CPU 的MMU 转换后

的地址。
// 3 . 总线地址:经过各种总线桥接器转换后在出现在总线的地址线上的

地址
//调用的方式取用其上的暂存器,在有些不支援 IO 调用的架构中,这些唯一取得装置暂

存器的方法。
//举个例说,如果你要调用第 100 号暂存器,你可以使用

//unsigned int *ap = (unsigned int *) mmio_start + 0x100;

//printf("register 0x100 = %x\n", *ap);

//接下来我们一一解释这些函数。

ioaddr = ioremap (mmio_start, mmio_len);//转化得到的地址是虚拟地址

// ioaddr 就是这段地址的开头(注意ioaddr 是虚拟地址,而mmio_start

是物理地址,
//它是BIOS 得到的,肯定是物理地址,而保护模式下CPU 不认物理地址,

只认虚拟地址),
if (ioaddr == NULL) {
printk (KERN_ERR PFX "%s: cannot remap MMIO, aborting\n",
pci_name(pdev));
rc = -EIO;
goto err_out;
}
dev->base_addr = (long) ioaddr;
tp->mmio_addr = ioaddr;
tp->regs_len = mmio_len;
#endif /* USE_IO_OPS */
/* Bring old chips out of low-power mode. */
RTL_W8 (HltClk, 'R');
/* check for missing/broken hardware */
if (RTL_R32 (TxConfig) == 0xFFFFFFFF) {
printk (KERN_ERR PFX "%s: Chip not responding, ignoring board\n",
pci_name(pdev));
rc = -EIO;
goto err_out;
}
/* identify chip attached to board */
version = RTL_R32 (TxConfig) & HW_REVID_MASK;
for (i = 0; i < ARRAY_SIZE (rtl_chip_info); i++)
if (version == rtl_chip_info[i].version) {
tp->chipset = i;
goto match;
}
/* if unknown chip, assume array element #0, original RTL-8139 in this
case */

printk (KERN_DEBUG PFX "%s: unknown chip version, assuming
RTL-8139\n"
,
pci_name(pdev));
printk (KERN_DEBUG PFX "%s: TxConfig = 0x%lx\n", pci_name(pdev),
RTL_R32 (TxConfig));
tp->chipset = 0;
match:
DPRINTK ("chipset id (%d) == index %d, '%s'\n",
version, i, rtl_chip_info[i].name);
if (tp->chipset >= CH_8139B) {
u8 new_tmp8 = tmp8 = RTL_R8 (Config1);
DPRINTK("PCI PM wakeup\n");
if ((rtl_chip_info[tp->chipset].flags & HasLWake) &&
(tmp8 & LWAKE))
new_tmp8 &= ~LWAKE;
new_tmp8 |= Cfg1_PM_Enable;
if (new_tmp8 != tmp8) {
RTL_W8 (Cfg9346, Cfg9346_Unlock);
RTL_W8 (Config1, tmp8);
RTL_W8 (Cfg9346, Cfg9346_Lock);
}
if (rtl_chip_info[tp->chipset].flags & HasLWake) {
tmp8 = RTL_R8 (Config4);
if (tmp8 & LWPTN) {
RTL_W8 (Cfg9346, Cfg9346_Unlock);
RTL_W8 (Config4, tmp8 & ~LWPTN);
RTL_W8 (Cfg9346, Cfg9346_Lock);
}
}
} else {
DPRINTK("Old chip wakeup\n");
tmp8 = RTL_R8 (Config1);
tmp8 &= ~(SLEEP | PWRDN);
RTL_W8 (Config1, tmp8);
}
// // reset 就是向命令寄存器写个reset 命令

rtl8139_chip_reset (ioaddr);
*dev_out = dev;
return 0;
err_out:
__rtl8139_cleanup_dev (dev);
return rc;
}

read_eeprom (ioaddr, 0, 8) == 0x8129 ? 8 : 6;//读取I/O地址信息(2)

/* Delay between EEPROM clock transitions.
No extra delay is needed with 33Mhz PCI, but 66Mhz may change this.
*/

#define eeprom_delay() readl(ee_addr)
/* The EEPROM commands include the alway-set leading bit. */
#define EE_WRITE_CMD (5)
#define EE_READ_CMD (6)
#define EE_ERASE_CMD (7)
static int __devinit read_eeprom (void *ioaddr, int location, int
addr_len)
{
int i;
unsigned retval = 0;
void *ee_addr = ioaddr + Cfg9346;
int read_cmd = location | (EE_READ_CMD << addr_len);
writeb (EE_ENB & ~EE_CS, ee_addr);
writeb (EE_ENB, ee_addr);
eeprom_delay ();
/* Shift the read command bits out. */
for (i = 4 + addr_len; i >= 0; i--) {
int dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
writeb (EE_ENB | dataval, ee_addr);
eeprom_delay ();
writeb (EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
eeprom_delay ();
}
writeb (EE_ENB, ee_addr);
eeprom_delay ();
for (i = 16; i > 0; i--) {
writeb (EE_ENB | EE_SHIFT_CLK, ee_addr);
eeprom_delay ();
retval =
(retval << 1) | ((readb (ee_addr) & EE_DATA_READ) ? 1 :
0);
writeb (EE_ENB, ee_addr);
eeprom_delay ();
}
/* Terminate the EEPROM access. */
writeb (~EE_CS, ee_addr);
eeprom_delay ();
return retval;
}


配置RTL8139 的net_device 的其他配置信息(4)

dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->poll = rtl8139_poll;
dev->weight = 64;
dev->stop = rtl8139_close;
dev->get_stats = rtl8139_get_stats;
dev->set_multicast_list = rtl8139_set_rx_mode;
dev->do_ioctl = netdev_ioctl;
dev->ethtool_ops = &rtl8139_ethtool_ops;
dev->tx_timeout = rtl8139_tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
#ifdef CONFIG_NET_POLL_CONTROLLER
dev->poll_controller = rtl8139_poll_controller;
#endif


spin_lock_init (&tp‐>lock);
init_waitqueue_head (&tp‐>thr_wait);
init_completion (&tp‐>thr_exited);
register_netdev (dev);
pci_set_drvdata (pdev, dev);
阅读(2022) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~