参考资料

<<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_DUMMYextern 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)
#endifextern 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};
#elseint 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 seemto 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;}
#elseprintk(" 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 cannotshare 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;
#endifei_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_PINGPONGei_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_MULTICASTdev->set_multicast_list = &set_multicast_list;
#endifether_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内核网络栈1.2.13-网卡设备的初始化流程相关推荐

  1. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的&qu ...

  2. Linux内核--网络栈实现分析(三)--驱动程序层+链路层(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7497260 更多请看专栏,地址 ...

  3. Linux内核--网络栈实现分析(一)--网络栈初始化--转

    转载地址 http://blog.csdn.net/yming0221/article/details/7488828 作者:闫明 本文分析基于内核Linux Kernel 1.2.13 以后的系列博 ...

  4. linux内核网络初始化,Linux内核--网络栈实现分析

    本文分析基于内核Linux Kernel 1.2.13 以后的系列博文将深入分析Linux内核的网络栈实现原理,这里看到曹桂平博士的分析后,也决定选择Linux内核1.2.13版本进行分析. 原因如下 ...

  5. 显示驱动包含在Linux内核层,驱动程序层(上) - Linux内核--网络栈实现分析_Linux编程_Linux公社-Linux系统门户网站...

    经过前面两篇博文的分析,已经对Linux的内核网络栈的结构有了一个模糊的认识,这里我们开始从底层开始详细分析Linux内核网络栈的实现.由于这是早期版本,代码的层次隔离做的还不是很好,这里说是从底层分 ...

  6. Linux内核网络栈1.2.13-route.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> route路由表概述 在IP协议的实现中,只要发送数据包都要查询路由表,选择合适的路由选项,确定下一站的地址,并构造MAC ...

  7. Linux内核网络栈1.2.13-icmp.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> icmp协议 在实现的过程中, ICMP协议工作再IP协议之上,但又不与TCP协议工作再一级,而是在下一级,在一般ICMP ...

  8. Linux内核网络栈1.2.13-tcp.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> af_inet.c文件中调用函数在协议层的实现 本文主要根据在af_inet.c文件中根据初始化不同的协议,来调用不同的协 ...

  9. Linux内核网络栈1.2.13-af_inet.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> socket常用函数继续调用分析 根据socket提供的常用库函数,socket.read和write等函数,继续往下一层 ...

最新文章

  1. 2021牛客暑期多校训练营3 I-Kuriyama Mirai and Exclusive Or (差分+位运算)
  2. Centos7 系统下搭建.NET Core2.0+Nginx+Supervisor+Mysql环境
  3. 云为 | 提供海外 IT 人才派遣、猎头、人力资源外包服务
  4. rds基于什么开发_为什么不学基于TypeScript的Node.js服务端开发?
  5. 读书 | 数字化转型的道与术(上)
  6. C语言“悬空指针”和“野指针”究竟是什么意思?
  7. Mac查看Python安装路径和版本
  8. 多分类的梯度以及logsumexp
  9. CSS 12个趣味小技巧大公开 | 原力计划
  10. mysql int 转 varchar_Java后端程序员必备:MySQL索引失效的十大杂症
  11. x264编码指南——码率控制
  12. thrift编写服务端 客户端
  13. 1130 无法登录 mysql_无法登录phpmyadmin,报1130错误
  14. 三维人体姿态估计年度进展综述(周晓巍教授)
  15. 苹果手机在微信里自带计算机功能,苹果版微信又更新,这些功能全是我想要的!...
  16. uni-app 退出app操作
  17. 小鹏用计算机计算38X596时,物理化学-表面化学部分选择题
  18. Bootstrap插件(一)——模态框(modal.js)
  19. 2022年金砖国家职业技能大赛(决赛)网络空间安全赛项 | 浙江赛区选拔赛 任务书
  20. Arch Linux安装Firefox 火狐中文版

热门文章

  1. 真没想到,Python还能实现5毛特效
  2. 华为最强自研NPU问世,麒麟810“抛弃”寒武纪
  3. Python如何爬取实时变化的WebSocket数据
  4. 马云:很多P2P公司披着互联网金融的外衣做非法金融服务
  5. 8月精选Python开源项目Top10
  6. 22首很棒的诗词欣赏,你相信这是AI的杰作吗?
  7. 百度AI开放平台3.0:平等赋能成为百度AI关键词
  8. 如何选择合适的损失函数,请看......
  9. 巴菲特评科技股:投资 IBM 是个错误,还会增持苹果,亚马逊简直是奇迹
  10. 刚刚,DeepMind被IJCAI授予杰出成就奖,因为他家把AlphaGo Zero做成了暖心的新垣结衣?