Linux内核网络栈1.2.13-网卡设备的初始化流程
参考资料
<<linux内核网络栈源代码情景分析>>
网卡设备的初始化
本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相关的网卡中断处理函数,协议的初始化等内容。
初始化过程
首先在操作系统初始化的过程中,在main函数中的sock_init函数;
void proto_init(void)
{
extern struct net_proto protocols[]; /* Network protocols Protocals.cÖж¨ÒåµÄ¾²Ì¬Êý×é*/
struct net_proto *pro; // 获取注册完成的协议列表
/* Kick all configured protocols. */
pro = protocols;
/* {NULL, NULL} ÊǸö½áÊø±êÖ¾ */
while (pro->name != NULL)
{
(*pro->init_func)(pro); // 调用初始化函数 初始化协议
pro++;
}
/* We're all done... */
}
void sock_init(void)
{
int i;
printk("Swansea University Computer Society NET3.019\n");
/*
* Initialize all address (protocol) families.
*/
for (i = 0; i < NPROTO; ++i) pops[i] = NULL;
/*
* Initialize the protocols module.
*/
/* ³õʼ»¯²»Í¬ÀàÐ͵ÄÐÒéÕ»(Óò) */
proto_init(); // 协议初始化函数
#ifdef CONFIG_NET
/*
* Initialize the DEV module.
*/
dev_init(); // 设备初始化函数
/*
* And the bottom half handler
*/
bh_base[NET_BH].routine= net_bh;
enable_bh(NET_BH); // 使能中断处理函数
#endif
}
此时在net/protocols.c文件中就初始化了相关的协议的数组;
struct net_proto protocols[] = {
#ifdef CONFIG_UNIX
{ "UNIX", unix_proto_init }, // 套接字通信
#endif
#if defined(CONFIG_IPX)||defined(CONFIG_ATALK)
{ "802.2", p8022_proto_init }, // 802.2 协议
{ "SNAP", snap_proto_init },
#endif
#ifdef CONFIG_AX25
{ "AX.25", ax25_proto_init },
#endif
#ifdef CONFIG_INET
{ "INET", inet_proto_init }, // inet协议,网络栈协议
#endif
#ifdef CONFIG_IPX
{ "IPX", ipx_proto_init },
#endif
#ifdef CONFIG_ATALK
{ "DDP", atalk_proto_init },
#endif
{ NULL, NULL }
};
我们目前主要关注的INET协议的初始化流程,待后文再分析。现在我们主要分析dev_init的初始化过程;
void dev_init(void)
{
struct device *dev, *dev2;
/*
* Add the devices.
* If the call to dev->init fails, the dev is removed
* from the chain disconnecting the device until the
* next reboot.
*/
dev2 = NULL;
for (dev = dev_base; dev != NULL; dev=dev->next) // 遍历设备列表
{
if (dev->init && dev->init(dev)) // 调用设备的初始化函数
{
/*
* It failed to come up. Unhook it.
*/
if (dev2 == NULL) // 加入到dev的链表中
dev_base = dev->next;
else
dev2->next = dev->next;
}
else
{
dev2 = dev;
}
}
}
对应的dev_base对应的数据定义在了drivers/net/Space.c文件中;
static struct device eth3_dev = {
"eth3", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, NEXT_DEV, ethif_probe };
static struct device eth2_dev = {
"eth2", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð3_dev, ethif_probe };
static struct device eth1_dev = {
"eth1", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, ð2_dev, ethif_probe };
static struct device eth0_dev = {
"eth0", 0, 0, 0, 0, ETH0_ADDR, ETH0_IRQ, 0, 0, 0, ð1_dev, ethif_probe };
# undef NEXT_DEV
# define NEXT_DEV (ð0_dev)
...
#ifdef CONFIG_DUMMY
extern int dummy_init(struct device *dev);
static struct device dummy_dev = {
"dummy", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NEXT_DEV, dummy_init, };
# undef NEXT_DEV
# define NEXT_DEV (&dummy_dev)
#endif
extern int loopback_init(struct device *dev);
struct device loopback_dev = {
"lo", /* Software Loopback interface */
0x0, /* recv memory end */
0x0, /* recv memory start */
0x0, /* memory end */
0x0, /* memory start */
0, /* base I/O address */
0, /* IRQ */
0, 0, 0, /* flags */
NEXT_DEV, /* next device */ // 下一个设备在头部上面文件中就初始化完成 要么是eth0_dev 要么是ppp0_dev等
loopback_init /* loopback_init should set up the rest */
};
struct device *dev_base = &loopback_dev; // 设置头部设备为loopback_dev
默认注册的就是eth0_dev设备,此时这些设备使用了同一个初始化函数就是ethif_probe函数;
static int
ethif_probe(struct device *dev)
{
short base_addr = dev->base_addr;
if (base_addr < 0 || base_addr == 1)
return 1; /* ENXIO */
if (1
#if defined(CONFIG_ULTRA)
&& ultra_probe(dev)
#endif
#if defined(CONFIG_WD80x3) || defined(WD80x3)
&& wd_probe(dev)
#endif
#if defined(CONFIG_EL2) || defined(EL2) /* 3c503 */
&& el2_probe(dev)
#endif
#if defined(CONFIG_NE2000) || defined(NE2000) // 如果是NE网卡类型就调用该初始化函数
&& ne_probe(dev)
#endif
) {
return 1; /* -ENODEV or -EAGAIN would be more accurate. */
}
return 0;
}
此时继续查看ne_probe函数,此时就会调用ne_probe1函数来进行具体的业务处理;
#ifdef HAVE_DEVLIST
struct netdev_entry netcard_drv =
{"ne", ne_probe1, NE_IO_EXTENT, netcard_portlist};
#else
int ne_probe(struct device *dev)
{
int i;
int base_addr = dev ? dev->base_addr : 0;
if (base_addr > 0x1ff) /* Check a single specified location. */
return ne_probe1(dev, base_addr);
else if (base_addr != 0) /* Don't probe at all. */
return ENXIO;
for (i = 0; netcard_portlist[i]; i++) {
int ioaddr = netcard_portlist[i];
if (check_region(ioaddr, NE_IO_EXTENT))
continue;
if (ne_probe1(dev, ioaddr) == 0)
return 0;
}
return ENODEV;
}
#endif
此时ne_probe1函数的执行过程;
static int ne_probe1(struct device *dev, int ioaddr)
{
int i;
unsigned char SA_prom[32];
int wordlength = 2;
char *name = NULL;
int start_page, stop_page;
int neX000, ctron;
int reg0 = inb_p(ioaddr); // 检查是否地址可读
if (reg0 == 0xFF)
return ENODEV;
/* Do a preliminary verification that we have a 8390. */
{ int regd;
outb_p(E8390_NODMA+E8390_PAGE1+E8390_STOP, ioaddr + E8390_CMD);
regd = inb_p(ioaddr + 0x0d);
outb_p(0xff, ioaddr + 0x0d);
outb_p(E8390_NODMA+E8390_PAGE0, ioaddr + E8390_CMD);
inb_p(ioaddr + EN0_COUNTER0); /* Clear the counter by reading. */
if (inb_p(ioaddr + EN0_COUNTER0) != 0) {
outb_p(reg0, ioaddr);
outb_p(regd, ioaddr + 0x0d); /* Restore the old values. */
return ENODEV;
}
}
printk("NE*000 ethercard probe at %#3x:", ioaddr);
/* Read the 16 bytes of station address PROM.
We must first initialize registers, similar to NS8390_init(eifdev, 0).
We can't reliably read the SAPROM address without this.
(I learned the hard way!). */
{
struct {unsigned char value, offset; } program_seq[] = {
{E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/
{0x48, EN0_DCFG}, /* Set byte-wide (0x48) access. */
{0x00, EN0_RCNTLO}, /* Clear the count regs. */
{0x00, EN0_RCNTHI},
{0x00, EN0_IMR}, /* Mask completion irq. */
{0xFF, EN0_ISR},
{E8390_RXOFF, EN0_RXCR}, /* 0x20 Set to monitor */
{E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */
{32, EN0_RCNTLO},
{0x00, EN0_RCNTHI},
{0x00, EN0_RSARLO}, /* DMA starting at 0x0000. */
{0x00, EN0_RSARHI},
{E8390_RREAD+E8390_START, E8390_CMD},
};
for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++)
outb_p(program_seq[i].value, ioaddr + program_seq[i].offset);
}
for(i = 0; i < 32 /*sizeof(SA_prom)*/; i+=2) {
SA_prom[i] = inb(ioaddr + NE_DATAPORT);
SA_prom[i+1] = inb(ioaddr + NE_DATAPORT);
if (SA_prom[i] != SA_prom[i+1])
wordlength = 1;
}
if (wordlength == 2) {
/* We must set the 8390 for word mode. */
outb_p(0x49, ioaddr + EN0_DCFG);
/* We used to reset the ethercard here, but it doesn't seem
to be necessary. */
/* Un-double the SA_prom values. */
for (i = 0; i < 16; i++)
SA_prom[i] = SA_prom[i+i];
start_page = NESM_START_PG;
stop_page = NESM_STOP_PG;
} else {
start_page = NE1SM_START_PG;
stop_page = NE1SM_STOP_PG;
}
for(i = 0; i < ETHER_ADDR_LEN; i++) {
dev->dev_addr[i] = SA_prom[i];
printk(" %2.2x", SA_prom[i]);
}
neX000 = (SA_prom[14] == 0x57 && SA_prom[15] == 0x57);
ctron = (SA_prom[0] == 0x00 && SA_prom[1] == 0x00 && SA_prom[2] == 0x1d);
/* Set up the rest of the parameters. */
if (neX000) {
name = (wordlength == 2) ? "NE2000" : "NE1000";
} else if (ctron) {
name = "Cabletron";
start_page = 0x01;
stop_page = (wordlength == 2) ? 0x40 : 0x20;
} else {
#ifdef CONFIG_NE_BAD_CLONES
/* Ack! Well, there might be a *bad* NE*000 clone there.
Check for total bogus addresses. */
for (i = 0; bad_clone_list[i].name8; i++) {
if (SA_prom[0] == bad_clone_list[i].SAprefix[0] &&
SA_prom[1] == bad_clone_list[i].SAprefix[1] &&
SA_prom[2] == bad_clone_list[i].SAprefix[2]) {
if (wordlength == 2) {
name = bad_clone_list[i].name16;
} else {
name = bad_clone_list[i].name8;
}
break;
}
}
if (bad_clone_list[i].name8 == NULL) {
printk(" not found (invalid signature %2.2x %2.2x).\n",
SA_prom[14], SA_prom[15]);
return ENXIO;
}
#else
printk(" not found.\n");
return ENXIO;
#endif
}
if (dev == NULL)
dev = init_etherdev(0, sizeof(struct ei_device), 0);
if (dev->irq < 2) {
autoirq_setup(0);
outb_p(0x50, ioaddr + EN0_IMR); /* Enable one interrupt. */
outb_p(0x00, ioaddr + EN0_RCNTLO);
outb_p(0x00, ioaddr + EN0_RCNTHI);
outb_p(E8390_RREAD+E8390_START, ioaddr); /* Trigger it... */
outb_p(0x00, ioaddr + EN0_IMR); /* Mask it again. */
dev->irq = autoirq_report(0);
if (ei_debug > 2)
printk(" autoirq is %d\n", dev->irq);
} else if (dev->irq == 2)
/* Fixup for users that don't know that IRQ 2 is really IRQ 9,
or don't know which one to set. */
dev->irq = 9;
/* Snarf the interrupt now. There's no point in waiting since we cannot
share and the board will usually be enabled. */
{
int irqval = request_irq (dev->irq, ei_interrupt, 0, wordlength==2 ? "ne2000":"ne1000"); // 设置中断函数
if (irqval) {
printk (" unable to get IRQ %d (irqval=%d).\n", dev->irq, irqval);
return EAGAIN;
}
}
dev->base_addr = ioaddr; // 设置驱动的地址
request_region(ioaddr, NE_IO_EXTENT, wordlength==2 ? "ne2000":"ne1000"); // 申请空间
for(i = 0; i < ETHER_ADDR_LEN; i++)
dev->dev_addr[i] = SA_prom[i];
ethdev_init(dev); // dev字段的初始化
printk("\n%s: %s found at %#x, using IRQ %d.\n",
dev->name, name, ioaddr, dev->irq);
if (ei_debug > 0)
printk(version);
ei_status.name = name;
ei_status.tx_start_page = start_page;
ei_status.stop_page = stop_page;
ei_status.word16 = (wordlength == 2);
ei_status.rx_start_page = start_page + TX_PAGES;
#ifdef PACKETBUF_MEMSIZE
/* Allow the packet buffer size to be overridden by know-it-alls. */
ei_status.stop_page = ei_status.tx_start_page + PACKETBUF_MEMSIZE;
#endif
ei_status.reset_8390 = &ne_reset_8390;
ei_status.block_input = &ne_block_input;
ei_status.block_output = &ne_block_output;
NS8390_init(dev, 0);
return 0;
}
该函数初始化内容较多,主要做了一些硬件设备的初始化,并检查了设备的模块,重置了参数,设置中断,并申请了内存给该网络设备使用;其中较为重要的函数就是ethdev_init函数,该函数的初始化过程中配置相关的回调执行函数;
/* Initialize the rest of the 8390 device structure. */
int ethdev_init(struct device *dev)
{
if (ei_debug > 1)
printk(version);
if (dev->priv == NULL) {
struct ei_device *ei_local;
dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL); // 申请内存
memset(dev->priv, 0, sizeof(struct ei_device)); // 申请的内存置空
ei_local = (struct ei_device *)dev->priv;
#ifndef NO_PINGPONG
ei_local->pingpong = 1;
#endif
}
/* The open call may be overridden by the card-specific code. */
if (dev->open == NULL)
dev->open = &ei_open;
/* We should have a dev->stop entry also. */
dev->hard_start_xmit = &ei_start_xmit; // 设置处理回调函数
dev->get_stats = get_stats;
#ifdef HAVE_MULTICAST
dev->set_multicast_list = &set_multicast_list;
#endif
ether_setup(dev); // 初始化剩余的字段
return 0;
}
...
void ether_setup(struct device *dev)
{
int i;
/* Fill in the fields of the device structure with ethernet-generic values.
This should be in a common file instead of per-driver. */
for (i = 0; i < DEV_NUMBUFFS; i++)
skb_queue_head_init(&dev->buffs[i]);
/* register boot-defined "eth" devices */
if (dev->name && (strncmp(dev->name, "eth", 3) == 0)) {
i = simple_strtoul(dev->name + 3, NULL, 0);
if (ethdev_index[i] == NULL) {
ethdev_index[i] = dev;
}
else if (dev != ethdev_index[i]) {
/* Really shouldn't happen! */
printk("ether_setup: Ouch! Someone else took %s\n",
dev->name);
}
}
dev->hard_header = eth_header; // 初始化头部信息
dev->rebuild_header = eth_rebuild_header;
dev->type_trans = eth_type_trans; // 协议处理
dev->type = ARPHRD_ETHER;
dev->hard_header_len = ETH_HLEN;
dev->mtu = 1500; /* eth_mtu */ // 设置mtu时间
dev->addr_len = ETH_ALEN;
for (i = 0; i < ETH_ALEN; i++) {
dev->broadcast[i]=0xff;
}
/* New-style flags. */ // 设置相关标识位
dev->flags = IFF_BROADCAST|IFF_MULTICAST;
dev->family = AF_INET;
dev->pa_addr = 0;
dev->pa_brdaddr = 0;
dev->pa_mask = 0;
dev->pa_alen = sizeof(unsigned long);
}
其中hard_start_xmit函数会在dev_queue_xmit函数调用的时候被执行,该函数调用操作具体的硬件将数据发送到物理设备上去,此时定义的函数位ei_start_xmit,该函数就是具体的讲数据发送出去。至此基本的初始化工作就完成了。
总结
本文仅仅是概述了驱动的初始化过程,该过程初始化的时候,主要就是设置相关的中断处理函数,并根据操作系统的网络设备,初始化不同的驱动程序,该驱动程序就是直接从网络设备上获取数据与发送数据,该层并没有涉及到具体使用的协议层的数据解析,基本上都是协议处理的数据在驱动层发送数据。由于本人才疏学浅,如有错误请批评指正。
上一篇: 强制推送本地代码到远程仓库