3c501.c驱动的理解流程

来源:互联网 发布:majority voting 算法 编辑:程序博客网 时间:2024/04/29 18:32

计划,把这个3c501自己看完后,再看一下潘刚的文章, 应该可以对整个网络的流程有个更清晰的认识了. 周五前把这个代码看完,周五开始看潘刚

 

621

这个驱动是3c501芯片的驱动。 是作为模块插入的。

模块的开始在

init_module()开始其中:

首先,net_device的结构 的中断请求编号irq赋值,这个值默认为5,可以在插入模块的时候指定;设定 i/o地址,默认为0x280,也可以在加入模块的时候指定。

其次,register_netdev&net_device)注册这个设备。这个函数做的事情就是调用net_device init定义的函数入口:el1_probe()

 

el1_probestruct net_device *dev)函数

       首先 设置为内核模块的设备。

              SET_MODULE_OWER(dev) ;//equal todev->owner=THIS_MODULE;

       而后 根据设置的i/o地址范围  调用el1_probe1(dev,base_addr)

 

el1_probe1

      首先request_regionioaddr,16 ,dev->name为设备请求一个从ioaddr开始,16个端口数目的I/O端口。

       而后 el1_dataptr写入0――5 端口地址el2_saprom 读取字节。保存在sataion_addr//这个是做什么呢?给不同的地址写入,然后看saprom的值?

       再根据station_addr的前3个参数来确定vender的值 。如果没有信息则释放所占用的i/o端口 release_region(ioaddr,El1_IO_EXTENT);

      

       接下来 dev->irq进行判断,主要是考虑用户指定irq小于2的情况。

              Autoirq_setup(2)  自动设定中断探测

              InbRX_STATUS //程序中注释为清楚未决的中断inb除了读出还有清空?

             InbTX_STATUS 读出i/o地址的信息?可是为什么没有保存给一个变量呢?

              然后给ax寄存器分别写入一些值,具体值的含义也许和芯片有关系吧,

              再调用autoirq_report(1) 表示1 jiffies之后 调用autoirq_setup返回irq数量。如              为零则表示无法分配中断号了,则释放先前申请的I/O地址

 

void autoirq_setup(int waittime); 该函数设置中断探测,在这里忽略了waittime参数。
int autoirq_report(int waittime);
该函数延迟给定的时间(以jiffies计算),然后返回自调用autoirq_setup以后产生的IRQ数量。 这些函数最初是在网络驱动程序的代码中使用,由于历史原因,它们现在用probe_irq_onprobe_irq_off实现。通常没有必要使用 autoirq_ 函数,而应该使用 probe_irq_

 

 如果dev->irq>2,则直接 设定寄存器值, 设定为loopback模式

 设定dev->base_addrio地址

设定dev->dev_addr ,即硬件地址,//大概是网卡mac地址?

 

if (dev->mem_start & 0xf) 这个是什么意思呢? 设备在存储器中占用的起始位置不能是从0000开始嘛?0000开始又意味着什么呢? 

如果不是从零地址开始的,就要和0x7,获得一个状态信息?为什么是和0x7比较呢? 0111

 

明显结果是获得低三位的值, 如果不为零就答应一个版本信息

BIOS会在内存的开始地址(地址0000:0000)建立一个表格,表格内记载了每一个中断服务程序的起始地址。只要有中断调用产生,就可先到此表格中根据中断编号找出该中断服务程序的起始地址,进而执行中断服务程

 

到这里完成了对设备 i/o地址的分配和中断的分配 ,接下来要做的就是对dev结构中的一些入口函数进行设定了:

首先 分配一个net_local 自定义的空间

而后 生成一个自选锁, ,设定每个入口函数地址:

dev->open= &el_open;    //打开设备的函数

    dev->hard_start_xmit =&el_start_xmit;   //开始发送数据的函数 这个是由网络协议栈调用的

    dev->tx_timeout = &el_timeout;     //超时函数

    dev->watchdog_timeo = HZ;

    dev->stop = &el1_close;             //关闭设备调用

    dev->get_stats = &el1_get_stats;

    dev->set_multicast_list =&set_multicast_list;  //多波的处理函数

    dev->do_ioctl = netdev_ioctl;  //ioctl的功能,对网卡的状态进行设定.

 

 

//今天晚上根据tcp/ip那个文章,自己把流程画出来把.

画着玩.vsd

 

相关函数设定结束后调用  ether_setup(dev);  针对以太网填充dev相应的一些字段包括

……

dev->hard_header        = eth_header;
dev->rebuild_header     = eth_rebuild_header;

……

http://lxr.linux.no/source/drivers/net/net_init.c?v=2.4.18#L405

 

 

这样就完成了对这个设备的i/o分配,地址分配和相关函数的设定了。下面看每个函数的调用说明和实现

 

el_open() 当通过ifconfig改变一些设定时 ,才会调用次函数。

 首先调用 request_irq (dev->irq,&el_interrupt, 0, dev->name, dev) 来为el_interrupt函数申请一个值为dev->irq的中断号.Intel平台,硬件中断号范围0--15,如果成功则返回 ,当由中断发生是就调用el_interrupt函数 ,第三个参数代表irq的类型

SA_SHIRQ                Interrupt is shared

 SA_INTERRUPT            Disablelocal interrupts while processin

 

. http://lxr.linux.no/source/arch/i386/kernel/irq.c?v=2.4.18#L677 

 

如果申请中断号失败, 开启自选锁,

调用el_reset()

关闭自选锁.

 

设置RX/TX模式 接受/发送

outb(); 给状态寄存器设定值,设定为接受模式

netif_start_queue(dev)    //允许网卡开始传输包

 

现在看看el_interrrupt函数

这部分代码好像寄存器德读写比较多,明天再看把. 明天就得看完啊~

 

6.23

 加锁

 首先读取 状态寄存器的值.

 然后判断是否处于发送状态,

是则读取寄存器中发送状态的值,如果装载冲突,则重新装载 lp->loading=2 下一步 调用netif_wake_queue() 会使得上层协定开始传送新的资料下来 然后又判断是否超时,是的话,改变寄存器的一些值,保存一些信息,然后再调用netif_wake_queue然后是重新发送时的处理.其他情况则进入接受状态 ,进行接受

如果处于发送状态 首先看是否处于rx-runt rx_missed状态 如果正常则进入el_receve函数进行处理,   其他情况则调用el_reset

 

最后重新设置为接受状态

outb(AX_RX,AX_CMD);

       outw(0x00, RX_BUF_CLR);

       inb(RX_STATUS);           /* Be certain that interrupts are cleared. */

       inb(TX_STATUS);

 

el_interrupt就结束了,看看el_reset函数的过程 

 

el_reset()完成了对各个寄存器的复位工作和读取hw地址信息

 

再看el_receive的过程 ,在这里终于看到了sk_buffer这个结构体了 .

 pkt_lenrx_low中读取的是包的长度 ,应该是包括eth头在内的包长

以太网最小包长为60字节 最大传输数据帧的长度为1500字节,为什么这里比较上限是1536

 

:

以太网数据帧最小为60字节,最大为1500字节 也就是MTU ,这个MTU是包含了上层协议头和payload 再加上以太网14字节的头部 4个字节的crc 1518,

可是代码了怎么判断的是1536呢?

 

自己找到的一些答案 ,给大家分享一下了:

MTUIEEE 802.3 定义为1500 那么加上以太网的头 crc  就是1518.

ETHER II 头有了一些变化,多了一些字段

1514             Basic maximum Ethernet packet sizew/headers

    +4            Packet with 4 byte CRC

    +2          Align the IP header           

   +16          Prepend a descriptor

 

进入命令模式

dev_alloc_skb()分配一个sbk的空间,不过这里为什么又多分2个字节长度 ,看后面的skb_reserver应该是预留出2个字节空间 给后移用的。

dev_alloc_skb()保留16字节的帧头空间。主要用在Ethernet驱动程序

如果申请到了空间,则调用skb_reserve() ,在一个申请好的sk_buff的缓冲区里保留一块空间。这个空间一般是用做下一层协议的头空间的 .数据区和尾部指针都后移一个长度。

Insb() ;从8位的dataport的中读取一个pkt_len的字节,保存到skb中,因为刚分配的skb->taildata执行的地址一样,所以就是把内容从data起始的地址保存

Skb->protocol=eth_type_trans;获得数据帧的协议类型。

netif_rx(dev);//终于看到这个函数了,这个是连接底层驱动和网络核心的关键函数。看看这个函数的流程:

  最主要的就是将skb放入队列,然后调用cpu_raise_softirq(this_cpu,NET_RX_SOFTIRQ);

 

然后再设置一些统计信息,el_receive函数就结束了.那么这个skb就送交网络核心层进行处理了.和底层驱动就没有关系了.

 

 

El_open 和相关调用看完了,下面看看el_start_xmit;就是发送的函数

 

网络接口核心层通过dev_queue_xmit()(net/core/dev.c,line975)这个函数向上层提供统一的发送接口,也就是说无论是IP,还是ARP 协议,通过这个函数把要发送的数据传递给这一层,想发送数据的时候就调用这个函数就可以了。dev_queue_xmit()做的工作最后会落实到dev ->hard_start_xmit(),而dev->hard_start_xmit()会调用实际的驱动程序来完成发送的任务在这也就是el_start_xmit函数了.

 首先 spin_lock_irqsave 就是保存flag,然后将设备锁住.

Netif_stop_queue 使系统暂停向驱动程序请求发送包.

然后进入无限循环

{

获得skb的长度,

pad这个参数,大概是为了填充慢以太网的字段而设定的吧??

Gp_start=0x800-(len+pad)   //是什么意思嗯???

Lp->tx_pkt_start=gp_start,// 表示从gp_start的地址处作为发送的数据的开始地址?

outb_p(AX_SYS, AX_CMD); 看起来像是给ax状态寄存器写入AX_SYS ,ax_sys预定义为0x40注释为loadthe buffer. 莫非只此方法就可以是网卡装载一个上层来得skb?

 

Inb_p(RX_STATUS); //从一个端口读取一个字节,其中inb_p() 会一直阻塞直到从端口得到字节为止 ,看这个意思只是读取,不会改变端口的值,可是程序中的注释维和说clear the status???

而后关闭自旋锁.

随后的三个语句程序中有注释, 其中outsb(DATAPORT,buf,len); 就是向ioaddr+oxof发送以buff开始的一个长度为len的字节,

随后的pad填充数据段的尾部,看来前面对pad用途的判断还算是正确的:)

是不是这样一个数据报就算完全填充完毕,应该是一个封装了以太网头的数据帧了.

接着又调用outw(gp_start,GP_LOW);重新把gp_low赋值为gp_start ,  这个复位会直接影响发送时读取数据读取地址吧,各个寄存器至今也是互相协调工作的应该。不过这个和硬件有关应该。

如果ip->loading!=2,那么就调用了outb(AXMIT,AX_CMD)  //真的就把数据帧发送出去了.

结束循环

完成收尾工作.

 

}无限循环结束

 

 

下面看超时的处理函数el_timeout , 在那里引起这个函数调用呢?为什么会超时呢? 会不会是一直处于接受状,,接受的数据量较大,虽然网卡处于双工状态,仍旧会发生无法分得处理机会呢?

 仍旧是对网卡本身寄存器的写入操作,

首先中断正在进行的事情,而后单独占用oxA8地址, 再关闭中断请求,放开对缓冲区的访问(应该是网卡缓冲区,不是skb???) 而后进入接收状态

netif_wake_queue();会使得上层协定开始传送新的资料下来

 

 

现在el1_close()函数

netif_stop_queue使系统暂时停止发送数据包.

释放中断号.

置状态字寄存器为reset

 

设定多波和读取状态信息就不说了,完全使寄存器的读写.应该适合硬件设计有关系,人家就定义写一个0x07给某个寄存器就是干什么事情.

 

Dev->do_ioctl=netdev_ioctl  理解为内核态的ioctl应该没有错误吧J

Get_user从用户空间获得命令 根据不同的命令执行不同的函数

copy_from_usercopy_to_user 的意思就是将得到的结果返回给用户空间或者从用户空间读取值,这个用户空间就是由useraddr来衔接用户命令和内核的调用 。偶是这么以为的

 

至此所有函数就分析完了

clean_module就不用说了,不外乎释放一些变量和i/o资源

 


 

对简单的网卡驱动的过程有了一个大概地认识,不过对那些寄存器的读写还是糊里糊涂的,是不是真的和网卡硬件的设置有关系呢?

我看的主要就是图中红色的部分了,再往上还没有开始,打算再看看潘刚的那篇文章,应该这里的不明白得问题 就会清楚许多把。

 

 

 

参考了交叉索引和李元佳的tcp/ip协议占阅读笔记