freebsd网卡驱动程序详解

当网络上一台计算机准备发送数据时,他的网卡开始工作了,首先网卡的芯片侦听在网络上是否有数据在
流动,如果没有,他就把数据发送到网络上,在侦听和发送之间有一段极小的时间延迟,在这段时间内,也有
可能在网络上有其他的计算机也准备发送数据,也侦听到网络上没有数据在流动,这就可能两台甚至多台
的数据一起发送到网络上,产生数据的碰撞,发送数据的计算机的网卡芯片当然要在发送完成后再校验返回
的数据,如果发现和发送的数据不一致,那就是说产生了碰撞,所以在一个以太网络中的计算机数量不宜过多,
他不但会增加广播包在网络中的数量,也请也会增加数据包的碰撞次数.
我们的计算机的网卡芯片在接收到一完整的数据包后,芯片的一引脚通知8259中断控制器,中断控制器再
发出中断给CPU,由此,CPU随即调用该网卡的中断例程,如:

DOS是这样的 
屏蔽所有中断(cli) 
push any register 
因为中断向量在段0 
所以xor ax,ax 
mov ds,ax 
mul ax,中断号 
那么在数据段的[ax]偏移处是该中断例程的指针了 
call [ax]就到该中断例程了 
...(DOS是比较遥远的事情了,我所描述的是他的原理,当然不会这么简单,如果那位网友有兴趣详细描述一下 
上面的原理,纠正或替换掉我所写的就感激不尽了) 

总之,在本例程中,CPU将调用elintr中断例程,并带有参数unit即该种网卡的第几块(因为在计算机中,你有可能
装了相同的网卡有几块),elintr的作用是把数据从网卡的数据存储器中读到我们在该网卡初始化时预先分配好
的数据缓冲区中,他调用的函数就只有elread,同样elread也只调用了elget一个函数.elread函数比较简单,就是
调用elget,elget则相对比较复杂一点,涉及到核心内存分配mbuf,mbuf是比较恐怖的东西,正如STEVEN所写的,为
了节约当时”巨大”的4M内存,牺牲了性能搞出了这个mbuf东东,mbuf是必须要弄懂的,虽然在设备驱动程序中调用
他的宏和函数不多,但在后面的IP协议,TCP协议中有不少涉及的地方.

关于数据发送方面和接收差不多,在上层协议放置好数据到mbuf链后,调用el_start函数,该函数把mbuf链中
的数据放置到本块网卡的发送队列缓冲el_pktbuf中,然后再调用el_xmit函数,此函数把发送队列缓冲el_pktbuf
中的数据有传递到网卡的数据存储器中.我认为,这中间的内存拷贝是多于的,应该在el_start函数中直接把mbuf
中的数据传递到网卡的数据存储器中,这样会使性能有较大幅度的提升,因为在驱动程序设计时,最好减少大量的
内存拷贝,他占用的时间太多了.

FreeBSD的3COM以太网设备驱动程序

本段头文件是在编译核心时产生的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
#include "el.h" /*此三文件为编译时产生的头文件,内容是定制核心的一些常量*/ 
#include "opt_inet.h"
#include "opt_ipx.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/syslog.h>

#include <net/ethernet.h>
#include <net/if.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net/bpf.h>

#include <machine/clock.h>

#include <i386/isa/isa_device.h>
#include <i386/isa/if_elreg.h>/*此头文件是3COM卡的寄存器常量*/

/* 为了调试方便 */
#ifdef EL_DEBUG
#define dprintf(x) printf x /*如果定义了DEBUG调试,则打印到屏幕*/
#else
#define dprintf(x)
#endif

/* softc结构,每种网卡的该结构是不同的,主要是该第一个成员必须是一以太网的共用结构arpcom*/
static struct el_softc {
struct arpcom arpcom; /* 以太网公共部分 */
u_short el_base; /* 基本输入输出地址 */
char el_pktbuf[EL_BUFSIZ]; /* 帧缓冲大小2048 */
} el_softc[NEL]; /*NEL在el.h中定义,即编译时产生的头文件,意思为支持的网卡数*/
/*
看看arpcom结构吧

* 该结构是以太网设备驱动程序和ARP程序所共享.

struct arpcom {
/*
* ifnet 结构必须在此结构的第一个位置.
/
struct ifnet ac_if;
u_char ac_enaddr[6]; /* 以太网硬件地址/
int ac_multicnt; /* 多播地址列表数 /
void *ac_netgraph; /* netgraph 节点信息,即我们所说的PPPoE,也就是ADSL宽带所用到的 /
};


*/

/* 一些函数申明 */
static int el_attach(struct isa_device *);/*第二步:填充相关的数据结构*/
static void el_init(void *); /*不用说,是初始化,在probe,attach之后被调用*/
static int el_ioctl(struct ifnet *,u_long,caddr_t);/*控制网卡的函树指针*/
static int el_probe(struct isa_device *);/*第一步:探测程序.查看是否卡存在和是否在正确的位置.*/
static void el_start(struct ifnet *);/*把数据包从硬件接口输出去*/
static void el_reset(void *);/* 该例程重设接口. 在el_watchdog()中调用*/
static void el_watchdog(struct ifnet *);/*一般该函数用于包在一定时间内没发送出去,就调用他,在
本驱动程序中并不支持该函数,在我的rtl8139说明中有*/
static void el_stop(void *);/*停止接口,在el_ioctl()和el_reset()中调用*/
static int el_xmit(struct el_softc *,int);/*把数据包放到芯片内,发送到以太网上*/
static ointhand2_t elintr;/*中断例程*/
static __inline void elread(struct el_softc *,caddr_t,int);/* 传递包到更高一级协议处理,即ether_input()例程.由elintr()调用 */
static struct mbuf *elget(caddr_t,int,struct ifnet *); /* 从网卡上下载数据包,len是数据的长度,本地以太网头部被分开*/
static __inline void el_hardreset(void *);/* 这是一个子程序,目的是重设硬件.*/

/* isa_driver结构 为 autoconf准备 */
/* isa_driver结构说明:
该结构来之于文件isa_device.h头文件
结构成员:
/*
* 通用的设备驱动程序结构.
*
* 没一设备驱动程序定义一组例程入口,由设置程序在启动时使用.

struct isa_driver {
int (*probe) __P((struct isa_device *idp));
/* 测试设备是否存在
int (*attach) __P((struct isa_device *idp));
/* 为设备设置驱动程序
char *name; /* 设备名称
int sensitive_hw; /* 探测发生有冲突时为真,ISA设备的老毛病
};
*/
struct isa_driver eldriver = {
el_probe, el_attach, "el"
};


/* 探测程序.查看是否卡存在和是否在正确的位置. */
static int
el_probe(struct isa_device *idev)
{
/*
isa_device 是设备的通用结构,该结构说明在isa_device.h头文件中,说明如下:
struct isa_device {
int id_id; /* 设备的 id
struct isa_driver *id_driver; 指向设备的驱动程序结构
int id_iobase; /* 基本IO地址
int id_iosize; /* 基本IO地址的长度
u_int id_irq; /* 中断
int id_drq; /* DMA
caddr_t id_maddr; /* 在总线中的物理IO内存地址(即便要)
int id_msize; /* IO内存的大小
union {
inthand2_t *id_i;
ointhand2_t *id_oi;中断例程指针
} id_iu; /* 中断接口例程
#define id_intr id_iu.id_i
#define id_ointr id_iu.id_oi
int id_unit; /* 在该类设备中是第几个
int id_flags; /* flags
int id_enabled; /* 设备激活了吗
struct isa_device *id_next; /* 在 userconfig()中用于isa_devlist
struct device *id_device;
};

*/
struct el_softc *sc;
u_short base; /* 仅仅为了方便 */
u_char station_addr[ETHER_ADDR_LEN];/*以太网的硬件地址*/
int i;

/* 搜集一些信息 */
sc = &el_softc[idev->id_unit];/*sc是softc结构,如果你有NEL块el卡的话就有NEL个softc
结构,当然也有可能同时还有其他的xx_softc结构*/
sc->el_base = idev->id_iobase;/*该块卡的基本I/O地址*/
base = sc->el_base;/*有一点多余,只是为了方便下面的引用*/

/* 第一次检查地址,看看基本地址是否在0X280到0X3F0之内 */
if((base < 0x280) || (base > 0x3f0)) {
printf("el%d: ioaddr must be between 0x280 and 0x3f0\n",
idev->id_unit);
return(0);
}

/* 现在尝试从PROM中获取地址,看看是否包含了3COM供应商的标识代码.
*/
dprintf(("Probing 3c501 at 0x%x...\n",base));/*在调试时会打印出*/

/* 重置板卡 */
dprintf(("Resetting board...\n"));
outb(base+EL_AC,EL_AC_RESET);/*我们一般定义基地址为0X300,EL_AC=0E,是辅助命令寄存器*/
DELAY(5);/*延迟5毫秒*/
outb(base+EL_AC,0);
dprintf(("Reading station address...\n"));
/* 读硬件地址,共六次 */
for(i=0;i<ETHER_ADDR_LEN;i++) {
outb(base+EL_GPBL,i);
station_addr[i] = inb(base+EL_EAW);/*EL_EAW是该卡的地址口,station_addr是函数内部变量,
下面判断了生产厂家后就没用的*/
}
dprintf(("Address is %6D\n",station_addr, ":"));

/* 如果厂商标识代码正确,那么返回1.
*/
if((station_addr[0] != 0x02) || (station_addr[1] != 0x60)
|| (station_addr[2] != 0x8c)) {
dprintf(("Bad vendor code.\n"));/*3COM厂商此种卡的代码为02608C*/
return(0);
} else {
dprintf(("Vendor code ok.\n"));
/* 把地址拷贝到arpcom结构中 */
bcopy(station_addr,sc->arpcom.ac_enaddr,ETHER_ADDR_LEN);
return(1);
}
}

/* 这是一个子程序,目的是重设硬件. 在el_init()中调用,在elintr()中调用,产生中断,有溢出发生时调用*/
static __inline void
el_hardreset(xsc)
void *xsc;
{
register struct el_softc *sc = xsc;/*记住在C中,寄存器变量只能有三个,可加快速度*/
register int base;
register int j;

base = sc->el_base;

/* 第一步,重设板卡,和el_probe中的一样(前面) */
outb(base+EL_AC,EL_AC_RESET);
DELAY(5);
outb(base+EL_AC,0);

/* 又把地址填回去,为什么?没有为什么,就是厂商规定的,一些端口填什么数据时会怎么样,只有厂商知道,我相信,
在同一厂商之间的网卡,交换机,路由器进行秘密通讯是非常可能的,他可以不返回到CPU层*/
for(j=0;j<ETHER_ADDR_LEN;j++)
outb(base+j,sc->arpcom.ac_enaddr[j]);
}

/* 连接该接口到核心数据结构.被调用时,我们已经知道该卡已经存在在给定的I/O
* 地址,我们还假定中断号是正确的.
*/
static int
el_attach(struct isa_device *idev)
{
struct el_softc *sc;
struct ifnet *ifp;/*该结构是一个巨大的结构,在STEVEN的书中有描述,我也写了一篇*/
u_short base;/*没用上,可以去掉*/

dprintf(("Attaching el%d...\n",idev->id_unit));

/* 放置一些指针. */
idev->id_ointr = elintr;/*放置中断例程指针,中断例程在下面*/
sc = &el_softc[idev->id_unit];/*定位本设备的softc结构指针*/
ifp = &sc->arpcom.ac_if;/*定位ifnet结构*/
base = sc->el_base;/*从程序来看,这一句可以去掉,根本没用,因为在该函数中没用到base*/

/* 重设板卡 */
dprintf(("Resetting board...\n"));
el_hardreset(sc);/*该程序在上面*/

/* 初始化ifnet结构,该结构的成员经常用来被ether网子程序,arp,bridge等调用 */
ifp->if_softc = sc;/*该网卡的IFP(通用接口结构)的专用结构指针(softc结构)*/
ifp->if_unit = idev->id_unit;/*第几块网卡*/
ifp->if_name = "el";/*网络卡的名称*/
ifp->if_mtu = ETHERMTU;/*1500*/
ifp->if_output = ether_output;/*以太网的输出子程序指针(不要搞错了,是向IP层
输出,按我们的理解是数据输入了,再转送到上一层协议)*/
ifp->if_start = el_start;/*把数据包从硬件接口输出去*/
ifp->if_ioctl = el_ioctl;/*控制网卡的函树指针*/
ifp->if_watchdog = el_watchdog;/*一般该函数用于包在一定时间内没发送出去,就调用他,在
本驱动程序中并不支持该函数,在我的rtl8139说明中有*/
ifp->if_init = el_init; /*不用说,是初始化,在probe,attach之后被调用*/
ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX);/*支持广播和单播*/

/* 调用通用以太网初始化例程 */
dprintf(("Attaching interface...\n"));
ether_ifattach(ifp, ETHER_BPF_SUPPORTED);
/*
在if_ethersubr.c中的ether_ifattach例程
void ether_ifattach(ifp, bpf) 调用时,ETHER_BPF_SUPPORTED是BSD的
包过滤器,如果在编译时设置文件没有
打开包过滤器,那么代表0,否则是1
register struct ifnet *ifp;
int bpf;
{
register struct ifaddr *ifa;
register struct sockaddr_dl *sdl;

if_attach(ifp); 此例程在if.c 中
ifp->if_type = IFT_ETHER;代表以太网
ifp->if_addrlen = 6;硬件地址长度是6
ifp->if_hdrlen = 14;包的头长度是6+6+2=14,其中2是协议类型
ifp->if_mtu = ETHERMTU; 为1500,多此一举,在前面你可看到,已
经填充了.
ifp->if_resolvemulti = ether_resolvemulti; 以太网解析多播例程指针
if (ifp->if_baudrate == 0) 波特率
ifp->if_baudrate = 10000000;
ifa = ifnet_addrs[ifp->if_index - 1];在ifnet_addrs[]数组中找到本地址指针
KASSERT(ifa != NULL, ("%s: no lladdr!\n", __FUNCTION__));
sdl = (struct sockaddr_dl *)ifa->ifa_addr; ifa->ifa_addr在此时指向的是sockaddr_dl结构.
sdl->sdl_type = IFT_ETHER;
sdl->sdl_alen = ifp->if_addrlen;
bcopy((IFP2AC(ifp))->ac_enaddr, LLADDR(sdl), ifp->if_addrlen);把硬件地址拷贝到sdl结构中
if (bpf) bpf为真,即加入了BSD包过滤
bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
if (ng_ether_attach_p != NULL)
(*ng_ether_attach_p)(ifp);
}

*/
printf("el%d: 3c501 address %6D\n",idev->id_unit,
sc->arpcom.ac_enaddr, ":");

dprintf(("el_attach() finished.\n"));
return(1);
}

/* 该例程重设接口. 在el_watchdog()中调用,因为watchdog不在本驱动程序中支持,所以从不被调用*/
static void
el_reset(xsc)/*上面的一个函数,重设硬件*/
void *xsc;
{
struct el_softc *sc = xsc;
int s;

dprintf(("elreset()\n"));
s = splimp();/*关网络中断*/
el_stop(sc);/*下面的一个函数*/
el_init(sc);/*重新初始化卡*/
splx(s);/*开网络中断*/
}
/*停止接口,在el_ioctl()和el_reset()中调用*/
static void el_stop(xsc)
void *xsc;
{
struct el_softc *sc = xsc;

outb(sc->el_base+EL_AC,0);/*用0写辅助命令寄存器*/
}

/* 初始化接口. */
static void
el_init(xsc)
void *xsc;
{
struct el_softc *sc = xsc;
struct ifnet *ifp;
int s;
u_short base;

ifp = &sc->arpcom.ac_if;/*定位ifnet结构*/
base = sc->el_base;/*网卡基本I/O地址*/

/* 如果地址不知道,什么也不做. */
if(TAILQ_EMPTY(&ifp->if_addrhead)) /* 在if.c中的if_attach例程
中已经填充,由el_attach调用
ether_attach时再调用if_attach */
return;

s = splimp();/*关网络中断*/

/* 重设板卡. */
dprintf(("Resetting board...\n"));
el_hardreset(sc);/*该函数在上面,重设硬件*/

/* 设置接收寄存器 rx */
dprintf(("Configuring rx...\n"));
if(ifp->if_flags & IFF_PROMISC) /*是混杂模式?EL_RXC是0X6接收命令寄存器*/
outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
else
outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
outb(base+EL_RBC,0);/*接收缓冲寄存器清0*/

/* 设置传输寄存器 TX */
dprintf(("Configuring tx...\n"));
outb(base+EL_TXC,0);

/* 开始接收 */
dprintf(("Starting reception...\n"));
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/*EL_AC_IRQE是IRQ enable(可用) EL_AC_RX为接收寄存器*/

/* 设置一些开始使用的标志 */
ifp->if_flags |= IFF_RUNNING;/*加上正在运行标志*/
ifp->if_flags &= ~IFF_OACTIVE;/*去掉正在传输标志*/

/* 调用输出. */
el_start(ifp);

splx(s);/*开网络中断*/
}



/* 开始在接口上输出.从队列中得到包并输出他们,在输出中,留出接收用一
部分时间,即打开中断再关闭中断,这样使接口接到的一些数据包不会丢失.

*/
static void
el_start(struct ifnet *ifp)
{
struct el_softc *sc;
u_short base;
struct mbuf *m, *m0;
int s, i, len, retries, done;

/* 定位softc结构的指针*/
sc = ifp->if_softc;
base = sc->el_base;/*基地址在输入输出指令时常要用到*/

dprintf(("el_start()...\n"));
s = splimp();/*因为下面涉及到if_flags的操作,所以要关闭网络中断*/

/* 如果输出正在进行,则退出 */
if(sc->arpcom.ac_if.if_flags & IFF_OACTIVE)
return;
sc->arpcom.ac_if.if_flags |= IFF_OACTIVE;/*加上输出正在进行传输标志*/

/* 主循环
*/
while(1) {/*唯一出口是准备传输的数据为空,即m0==NULL时*/
/* 从队列中移出下一数据包到m0中,请看头文件if_var.h
#define IF_DEQUEUE(ifq, m) { \
(m) = (ifq)->ifq_head; \ ifq是一mbuf指针队列,把第一个mbuf指针放到m中
if (m) { \
if (((ifq)->ifq_head = (m)->m_nextpkt) == 0) \重排队列
(ifq)->ifq_tail = 0; \
(m)->m_nextpkt = 0; \
(ifq)->ifq_len--; \
} \
}


*/
IF_DEQUEUE(&sc->arpcom.ac_if.if_snd,m0);/* &sc->arpcom.ac_if.if_snd指向发送队列,
该宏取出第一个mubf的指针放到m0中,看上面的说明.
这是数据结构的好教材*/

/* 如果发送缓冲区指针为空,即已经发送完,则退出,此是该无穷循环的唯一出口. */
if(m0 == NULL) {
sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE;/*出去前当然要去掉输出正在进行标志*/
splx(s);
return;
}

/* 关闭接收 */
outb(base+EL_AC,EL_AC_HOST);/*EL_AC_HOST为系统总线可访问缓冲,即系统总线网卡要用 */
outb(base+EL_RBC,0);/*接收缓冲寄存器清0*/

/* 拷贝mbuf中的数据到softc结构定义的成员el_pktbuf中,缓冲大小是EL_BUFSIZ即2048. */
len = 0;
for(m = m0; m != NULL; m = m->m_next) { /* m0是一mbuf指针,也是一mbuf链的第一个,此
次要发送的是一mbuf链,不是一单个mbuf*/
if(m->m_len == 0)
continue;
bcopy(mtod(m,caddr_t),sc->el_pktbuf+len,m->m_len);/*m->len是该mbuf链的数据长度,
sc->el_pktbuf是该卡的发送临时缓冲,要发
送的数据在这集中,然后传送到网卡上,太费
时间了,应该直接放置到网卡的存储器中.*/
len += m->m_len; /*len是这次要发送的总数*/
}
m_freem(m0); /*释放该mbuf链*/

len = max(len,ETHER_MIN_LEN); /*ETHER_MIN_LEN是发送的最小长度*/

/* 如果有BPF,就交给BPF验证 */
if(sc->arpcom.ac_if.if_bpf)
bpf_tap(&sc->arpcom.ac_if, sc->el_pktbuf, len);/*你当然可以在这写一点自己的验证过程*/

/* 传送数据包到板卡 */
dprintf(("el: xfr pkt length=%d...\n",len));
i = EL_BUFSIZ - len;/*EL_BUFSIZ=2048字节*/
outb(base+EL_GPBL,(i & 0xff)); /*告诉发送的长度*/
outb(base+EL_GPBH,((i>> 8) &0xff));
outsb(base+EL_BUF,sc->el_pktbuf,len);/*传输数据到板卡*/

/* 开始发送数据包 */
retries=0;/*下面做循环用的,在发不出去时,循环15次*/
done=0; /*done=1时发送成功了*/
while(!done) {
if(el_xmit(sc,len)) { /* 调用发送例程,其实只要传送base就可以了 */
done = -1;
break;
}
/* 检查输出后的状态,如果你要对watchdog支持,可以在这加上代码,即在5毫秒没发送出去,就调用el_watchdog() */
i = inb(base+EL_TXS);
dprintf(("tx status=0x%x\n",i));
if(!(i & EL_TXS_READY)) { /* 如果传输状态寄存器不是准备接受新帧就绪 */
dprintf(("el: err txs=%x\n",i)); /*那就是出错了*/
sc->arpcom.ac_if.if_oerrors++;
if(i & (EL_TXS_COLL|EL_TXS_COLL16)) {/*网络数据包碰撞*/
if((!(i & EL_TXC_DCOLL16)) && retries < 15) {/*做循环的目的是为了有错误时可重传15次*/
retries++;
outb(base+EL_AC,EL_AC_HOST);/*EL_AC_HOST为系统总线可访问缓冲 */
}
}
else
done = 1;
}
else {
sc->arpcom.ac_if.if_opackets++;/*统计用的,说明该卡成功发送一包*/
done = 1;
}
}
if(done == -1) /* 包没有传输(失败) */
continue;

/* 现在给板卡一个机会接收.注意:在linux中曾经ALEN先生批评此卡好恐怖(他说要进博物馆,哈哈),并说在传输时
会丢失进来的数据包,我查看了LINUX的驱动程序,确实是这样,但在FreeBSD中,给了一个机会,由此可证明他的
关于"丢失包的说法不一定成立",但关于一个缓冲和一次只能发送一数据包的说法确实是真的,还有多播方面也
不支持,我也希望大家最好不要去买这东西,和NE2000,PCI中的RTL8139芯片的网卡一样,性能太差了.*/
(void)inb(base+EL_AS);/*读辅助状态寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/* 用IRQ(中断)使能和接收 写辅助命令寄存器*/
splx(s);
/* 这有可能接收到中断(包到达) */
s = splimp();
}
}

/* 这是真正的传输包,由el_start()调用
*/
static int
el_xmit(struct el_softc *sc,int len)
{
int gpl;
int i;

gpl = EL_BUFSIZ - len;
dprintf(("el: xmit..."));
outb((sc->el_base)+EL_GPBL,(gpl & 0xff));
outb((sc->el_base)+EL_GPBH,((gpl>> 8) &0xff));
outb((sc->el_base)+EL_AC,EL_AC_TXFRX);/*真正的传送指令*/
i = 20000;
while((inb((sc->el_base)+EL_AS) & EL_AS_TXBUSY) && (i>0))/*如果传送还在忙,循环20000次等待*/
i--;
if(i == 0) {/*这里有一个bug,大家发现没有,i到了0时也有可能传送成功,解决办法是把(i>0)这条件放到前面*/
/*我稍微讲一下C,在编译C程序时,象while ( (a>b) && (i>0) )时,是这个样子
top:if a>b then
if i>0 then
执行体
endif
endif
goto top
也就是说,当i=0时候,inb((sc->el_base)+EL_AS)这指令还会执行,也有可能这时候传送完成了,而下面有给打出
一个什么"tx not ready"的东东,而且返回失败,有得重新传送一次.
*/
dprintf(("tx not ready\n"));
sc->arpcom.ac_if.if_oerrors++;
return(-1);
}
dprintf(("%d cycles.\n",(20000-i)));
return(0);/*成功*/
}

/* 传递包到更高一级协议处理,即ether_input()例程.由elintr()调用 */
static __inline void
elread(struct el_softc *sc,caddr_t buf,int len)
{
register struct ether_header *eh;
struct mbuf *m;

eh = (struct ether_header *)buf;/*从buf中分出以太网头部*/

/*
* elget函数是把包放入一mbuf缓冲链中
*/
m = elget(buf,len,&sc->arpcom.ac_if);
if(m == 0)/*出错了*/
return;

ether_input(&sc->arpcom.ac_if,eh,m);/*传输给上一层的包括ifnet结构,以太网头部,一mbuf*/
}

/* 中断例程 */
static void
elintr(int unit)
{
register struct el_softc *sc;
register int base;
int stat, rxstat, len, done;

/* 寻址softc和I/O基地址 */
sc = &el_softc[unit];
base = sc->el_base;

dprintf(("elintr: "));

/* 检查板卡状态 */
stat = inb(base+EL_AS);
if(stat & EL_AS_RXBUSY) {/*接收忙*/
(void)inb(base+EL_RXC);/*读接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/* 用IRQ(中断)使能和接收 写辅助命令寄存器*/
return;
}

done = 0;
while(!done) {
rxstat = inb(base+EL_RXS);
if(rxstat & EL_RXS_STALE) {/*EL_RXS_STALE代表接受状态没有改变*/
(void)inb(base+EL_RXC);/*读接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/* 用IRQ(中断)使能和接收 写辅助命令寄存器*/
return;
}

/* 如果这有一个溢出发生,重新初始化板卡. */
if(!(rxstat & EL_RXS_NOFLOW)) {
dprintf(("overflow.\n"));
el_hardreset(sc);
/* 使板卡回到接收模式 */
if(sc->arpcom.ac_if.if_flags & IFF_PROMISC)
outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
else
outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
(void)inb(base+EL_AS);/*读辅助状态寄存器*/
outb(base+EL_RBC,0);/*清除接收缓冲*/
(void)inb(base+EL_RXC);/*读接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/* 用IRQ(中断)使能和接收 写辅助命令寄存器*/
return;
}

/* 到这应该是进来了一数据包 */
len = inb(base+EL_RBL);
len |= inb(base+EL_RBH) << 8;/*包长度的高低位组合为该包的长度*/
dprintf(("receive len=%d rxstat=%x ",len,rxstat));
outb(base+EL_AC,EL_AC_HOST);/*EL_AC_HOST为系统总线可访问缓冲 */

/* 如果包太短或太长,回到接收模式并返回
*/
if((len <= sizeof(struct ether_header)) || (len > ETHER_MAX_LEN)) {/*如果包小于以太网头部的长度或大于最大长度*/
if(sc->arpcom.ac_if.if_flags & IFF_PROMISC)/*为重置硬件准备if_flags*/
outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
else
outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW));
(void)inb(base+EL_AS);/*读辅助状态寄存器*/
outb(base+EL_RBC,0);/*清除接收缓冲*/
(void)inb(base+EL_RXC);/*读接收命令寄存器*/
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));/* 用IRQ(中断)使能和接收 写辅助命令寄存器*/
return;
}

sc->arpcom.ac_if.if_ipackets++;/*统计使用,说明接收包总数*/

/* 拷贝数据到我们的缓冲 */
outb(base+EL_GPBL,0);
outb(base+EL_GPBH,0);
insb(base+EL_BUF,sc->el_pktbuf,len);/*从端口读一串数据到指定地址()*/
outb(base+EL_RBC,0);
outb(base+EL_AC,EL_AC_RX);
dprintf(("%6D-->",sc->el_pktbuf+6,":"));/*也放置到el_pktbuf中,发送也放在他中,在发送时有一个开中断接数据包的过程
不过那时候el_pktbuf中没有数据,不会相互影响.*/
dprintf(("%6D\n",sc->el_pktbuf,":"));

/* 把数据传递到上一层 */
len -= sizeof(struct ether_header);
elread(sc,(caddr_t)(sc->el_pktbuf),len);

/* 对状态? */
stat = inb(base+EL_AS);

/* 如果忙不为真则继续 */
if(!(stat & EL_AS_RXBUSY))
dprintf(("<rescan> "));
else
done = 1; /*退出循环*/
}

(void)inb(base+EL_RXC);
outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX));
return;
}

/*
* 从网卡上下载数据包,len是数据的长度,本地以太网头部被分开
*/
static struct mbuf *
elget(buf, totlen, ifp)/*由elread调用,buf是指向sc->el_pktbuf缓冲区,并且数据已经存在,
totlen是整个数据包长度-sizeof(struct ether_header)即以太网头部的长度*/
caddr_t buf;
int totlen;
struct ifnet *ifp;
{
struct mbuf *top, **mp, *m;
int len;
register caddr_t cp;
char *epkt;

buf += sizeof(struct ether_header);/*调用前buf指向...请看下图
|--------------------------------整个数据----------------------------------------------|
|--ether头部14字节----|--------IP或ARP或其他协议头部及数据-----------------------------|
^
调用前buf指向这
^
执行之后buf指向这
因为在向上传递数据的过程是一层一层的剥,在本次要剥掉ether_header即以太网头部
*/
cp = buf;/*放入寄存器中*/
epkt = cp + totlen;/*epkt在计算后指向数据的尾部*/

MGETHDR(m, M_DONTWAIT, MT_DATA);/*得到一标记了头部的mbuf*/
/*MGETHDR宏说明
#define MGETHDR(m, how, type) do { \
struct mbuf *_mm; \
int _mhow = (how); \
int _mtype = (type); \
int _ms = splimp(); \屏蔽中断
\
if (mmbfree == NULL) \mmbfree是内存管理初始化时的全局变量,意思是还有可用的内存块吗?
(void)m_mballoc(1, _mhow); \没有就分配一个,1代表分配一个MSIZE大小的块,该函数调用kmem_malloc
\核心内存分配函数,返回的可用mbuf指针在mmbfree中
_mm = mmbfree; \
if (_mm != NULL) { \
mmbfree = _mm->m_next; \如果上面的m_mballoc函数执行了,_mm->m_next肯定为NULL
mbtypes[MT_FREE]--; \
_mm->m_type = _mtype; \看上下文可知,_mtype是MT_DATA
mbtypes[_mtype]++; \
_mm->m_next = NULL; \从这开始是初始化mbuf一些指针
_mm->m_nextpkt = NULL; \
_mm->m_data = _mm->m_pktdat; \
_mm->m_flags = M_PKTHDR; \加入mbuf链首标志,即该链的第一个包,该宏和MGET的不同之处
_mm->m_pkthdr.rcvif = NULL; \
_mm->m_pkthdr.csum_flags = 0; \
_mm->m_pkthdr.aux = (struct mbuf *)NULL; \
(m) = _mm; \
splx(_ms); \恢复中断
} else { \
splx(_ms); \
_mm = m_retryhdr(_mhow, _mtype); \再来一次MGETHDR,不过m_retryhdr已经定义为空,防止死循环
if (_mm == NULL && _mhow == M_WAIT) \还为空
(m) = m_mballoc_wait(MGETHDR_C, _mtype); \强制用阻塞型
else \
(m) = _mm; \
} \
} while (0)

*/

if (m == 0)
return (0);
m->m_pkthdr.rcvif = ifp;/*指向接收该包的网络卡的ifp指针,后面好多协议要用到他*/
m->m_pkthdr.len = totlen;/*已经把以太网头部剥离,数据长度没算他了*/
m->m_len = MHLEN;/*该出是链首,所以该mbuf的长度是MHLEN,而不是MLEN*/
/* 这就是MHLEN
#define MSIZE 256 /* mbuf的大小 *
#define MLEN (MSIZE - sizeof(struct m_hdr)) /* 普通数据区的长度*
#define MHLEN (MLEN - sizeof(struct pkthdr)) /* 链首数据区的长度


*/
top = 0;
mp = &
while (totlen > 0) {
if (top) {/*如果不是链的第一个*/
MGET(m, M_DONTWAIT, MT_DATA);/*MGET和MGETHDR差不多,只不过少一个m_flags = M_PKTHDR*/
if (m == 0) {
m_freem(top);
return (0);
}
m->m_len = MLEN;/*非链首mbuf的长度为MLEN,这个if(top)就代表不是链首mbuf*/
}/*如果跳过了上面哪个if,那肯定是链的第一个mbuf,并且m已经在循环外就分配好了.*/
len = min(totlen, epkt - cp);/*epkt在计算后指向数据的尾部,cp指向首部*/
if (len >= MINCLSIZE) {/*#define MINCLSIZE (MHLEN + 1) 这意味着只要数据大于MHLEN,就要分配一个簇*/
MCLGET(m, M_DONTWAIT);/*看到宏展开后好恐怖,有空我再说一说*/
if (m->m_flags & M_EXT)/*在mbuf中注明是扩展型mbuf(即带有簇)*/
m->m_len = len = min(len, MCLBYTES);/*如果大于2048则先装2048吧,装的语句在下面*/
else
len = m->m_len;
} else {
/*
* 如果到这了,就意味着要么这个包小于MINCLSIZE,要么是后面一点尾巴且小于MINCLSIZE.
*/
if (len < m->m_len) {
if (top == 0 && len + max_linkhdr <= m->m_len)
m->m_data += max_linkhdr;
m->m_len = len;
} else
len = m->m_len;
}
bcopy(cp, mtod(m, caddr_t), (unsigned)len);/*第一次数据移动,费时的操作*/
cp += len;
*mp = m;
mp = &m->m_next;/*把mbuf链接起来*/
totlen -= len;
if (cp == epkt)
cp = buf;
}
return (top);/*返回装填数据的mbuf链首*/
}

总结

在该函数中,所做的事情非常费时,主要是做内存的申请,大批数据的拷贝,如果象NFS传送数据,会出现大量的簇的申请和大量
簇的数据的拷贝,一次循环需要拷贝2048个32位的双字.如果是发给本机的,那还行,如果是本机做为桥转发及防活墙,即数据不上传
到IP层处理,那么可以直接改写mbuf的分配方案,根据不同的网络流量可初始化一定数量的大容量的缓冲链(可以以一个以太网的整
页数来分配,如是100M以太网是1514字节,可分配2048字节,是有一点浪费,但性能可提高,sc->el_pktbuf可变为一队列,用来和其他
网卡的接收队列进行数据交换.这意味着光数据进入就少拷贝一次,性能将大大提高,目前我正在研究中.)*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* 
* 处理一个IOCTL请求.
*/
static int
el_ioctl(ifp, command, data)
register struct ifnet *ifp;
u_long command; /*IOCTL的命令*/
caddr_t data;
{
int s, error = 0;

s = splimp(); /*先关闭网络中断*/

switch (command) {
case SIOCSIFADDR:
case SIOCGIFADDR:
case SIOCSIFMTU:
error = ether_ioctl(ifp, command, data);
break;

case SIOCSIFFLAGS:
/*
* 如果接口已经DOWN但FLAG还有RUNNING, 那么先停止它
*/
if (((ifp->if_flags & IFF_UP) == 0) &&
(ifp->if_flags & IFF_RUNNING)) {
el_stop(ifp->if_softc);
ifp->if_flags &= ~IFF_RUNNING;/*在FALG中去掉IFF_RUNNING标志*/
} else {
/*
* 如果接口已经DOWN,FLAG没有RUNNING, 只要调用el_init例程
*/
if ((ifp->if_flags & IFF_UP) &&
((ifp->if_flags & IFF_RUNNING) == 0))
el_init(ifp->if_softc);
}
break;
default:
error = EINVAL;
}
(void) splx(s);
return (error);
}

/* 一般是数据在规定的时间内没有发出后被调用的程序,目前该驱动程序不支持 */
static void
el_watchdog(struct ifnet *ifp)
{
log(LOG_ERR,"el%d: device timeout\n", ifp->if_unit);
ifp->if_oerrors++;
el_reset(ifp->if_softc);
}

转:https://blog.csdn.net/h_cszc/article/details/7776116


freebsd网卡驱动程序详解
https://dnsnat.gitee.io/NETWORK/freebsd网卡驱动程序详解.html
作者
dnsnat
发布于
2022年3月22日
许可协议