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

参考资料

<>

网卡设备的初始化

本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相关的网卡中断处理函数,协议的初始化等内容。

初始化过程

首先在操作系统初始化的过程中,在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,该函数就是具体的讲数据发送出去。至此基本的初始化工作就完成了。

总结

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

你可能感兴趣的:(操作系统)