欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux内核网络栈1.2.13-网卡设备的初始化流程

程序员文章站 2022-03-07 09:33:25
...

参考资料

<<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, &eth3_dev, ethif_probe };
static struct device eth1_dev = {
    "eth1", 0,0,0,0,0xffe0 /* I/O base*/, 0,0,0,0, &eth2_dev, ethif_probe };

static struct device eth0_dev = {
    "eth0", 0, 0, 0, 0, ETH0_ADDR, ETH0_IRQ, 0, 0, 0, &eth1_dev, ethif_probe };

#   undef NEXT_DEV
#   define NEXT_DEV	(&eth0_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,该函数就是具体的讲数据发送出去。至此基本的初始化工作就完成了。

总结

本文仅仅是概述了驱动的初始化过程,该过程初始化的时候,主要就是设置相关的中断处理函数,并根据操作系统的网络设备,初始化不同的驱动程序,该驱动程序就是直接从网络设备上获取数据与发送数据,该层并没有涉及到具体使用的协议层的数据解析,基本上都是协议处理的数据在驱动层发送数据。由于本人才疏学浅,如有错误请批评指正。

相关标签: Linux网络协议栈