freebsd网卡驱动程序详解

来源 https://blog.csdn.net/h_cszc/article/details/7776116

/* 注释:xie_minix */ 
/*此处为BSD申明,略过... 最好是拷贝下来用C的开发工具来看比较好 :) */ 
/* 
当网络上一台计算机准备发送数据时,他的网卡开始工作了,首先网卡的芯片侦听在网络上是否有数据在 
流动,如果没有,他就把数据发送到网络上,在侦听和发送之间有一段极小的时间延迟,在这段时间内,也有 
可能在网络上有其他的计算机也准备发送数据,也侦听到网络上没有数据在流动,这就可能两台甚至多台 
的数据一起发送到网络上,产生数据的碰撞,发送数据的计算机的网卡芯片当然要在发送完成后再校验返回 
的数据,如果发现和发送的数据不一致,那就是说产生了碰撞,所以在一个以太网络中的计算机数量不宜过多, 
他不但会增加广播包在网络中的数量,也请也会增加数据包的碰撞次数. 
我们的计算机的网卡芯片在接收到一完整的数据包后,芯片的一引脚通知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以太网设备驱动程序 */ 
/*本段头文件是在编译核心时产生的*/

#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可变为一队列,用来和其他 
网卡的接收队列进行数据交换.这意味着光数据进入就少拷贝一次,性能将大大提高,目前我正在研究中.)*/

/* 
* 处理一个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); 
}

============== End

freebsd网卡驱动程序详解的更多相关文章

  1. 网卡配置文件详解 用户管理与文件权限篇 文件与目录权限 软连接 tar解压命令 killall命令 linux防火墙 dns解析设置 计划任务crond服务 软件包安装 阿里云 yum源 安装

    Linux系统基础优化及常用命令 Linux基础系统优化 引言没有,只有一张图. Linux的网络功能相当强悍,一时之间我们无法了解所有的网络命令,在配置服务器基础环境时,先了解下网络参数设定命令. ...

  2. Linux USB 鼠标驱动程序详解(转)

    Linux USB 鼠标驱动程序详解 USB 总线引出两个重要的链表!一个 USB 总线引出两个重要的链表,一个为 USB 设备链表,一个为 USB 驱动链表.设备链表包含各种系统中的 USB 设备以 ...

  3. 27.Linux-DM9000C网卡移植(详解)

    上一节 我们学习了:   网卡驱动介绍以及制作虚拟网卡驱动: http://www.cnblogs.com/lifexy/p/7763352.html 接下来本节,学习网卡芯片DM9000C,如何编写 ...

  4. Vmware虚拟机三种网卡模式详解

    由于Linux目前很热门,越来越多的人在学习linux,但是买一台服务放家里来学习,实在是很浪费.那么如何解决这个问题?虚拟机软件是很好的选择,常用的虚拟机软件有vmware workstations ...

  5. 26.Linux-网卡驱动介绍以及制作虚拟网卡驱动(详解)

    1.描述 网卡的驱动其实很简单,它还是与硬件相关,主要是负责收发网络的数据包,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送, 并将接收到的数据包传递给上层协议. 网卡设备与字符设备和块 ...

  6. Linux网卡bounding详解

    多块网卡绑在一起,作为一个网卡用,实现负载均衡和提高带宽   linux双网卡绑定一个IP地址,实质工作就是使用两块网卡虚拟为一块,使用同一个IP地址,是我们能够得到更好的更快的服务.其实这项技术在 ...

  7. vmware网卡设置详解

    转载请注明出处!本文连接及作者.不得用于商业用途! http://hi.baidu.com/quantumcloud/blog/item/9156a6c584996c179c163d5b.html B ...

  8. linux网卡设置详解

    centos7安装之后是需要在网卡配置文件中开始网络连接 onboot =yes 刚开始时网卡获取IP模式是dhcp 你会发现ifconfig不能用,猜测是废弃了,你要yum install net- ...

  9. VPS服务器下的centos网卡配置详解……

    自动激活网卡 安装了CENTOS 6.X后,每次启动了系统都需要手动激话网卡,以下方法可以在系统启动后自动激活网卡. cat /etc/sysconfig/network-scripts/ifcfg- ...

随机推荐

  1. MySQL-常用的存储引擎

    MySQL-常用的存储引擎 存储引擎 事务 锁粒度 主要应用 忌用 MyISAM 不支持 支持并发插入的表级锁 select,insert 读写操作频繁 MRG_MYISAM 不支持 支持并发插入的表 ...

  2. java基础必备单词讲解 day three

    if 如果 else 否则 switch 切换判断 case 实例 break 退出 return 返回 default 默认 variable array 数组 null 空的 无效的 pointe ...

  3. 在windows7上配置xampp虚拟主机

    在设置之前最好关闭xampp1.修改hosts文件进入C:\Windows\System32\drivers\etc目录,找到hosts文件.在# Localhost (DO NOT REMOVE) ...

  4. mybatis两级缓存原理剖析

    https://blog.csdn.net/zhurhyme/article/details/81064108 对于mybatis的缓存认识一直有一个误区,所以今天写一篇文章帮自己订正一下.mybat ...

  5. h5页面苹果端浮动问题

    最近在开发一个h5的app端,前端同事写好页面,我们后端java动态化页面,测试的时候发现安卓端什么浏览器都正常如下图1,可是苹果端无论什么浏览器都出现了底部菜单缺少了两个下图2图一:正常显示 图2, ...

  6. Python必学:使用哪款文本编辑器更好?

    Python的交互式命令行写程序,好处是一下就能得到结果,坏处是没法保存,下次还想运行的时候,还得再敲一遍. 所以,实际开发的时候,我们总是使用一个文本编辑器来写代码,写完了,保存为一个文件,这样,程 ...

  7. OC中block作方法参数时的用法

    方式一.在传参时直接声明block回调方法. 1. 定义方法: - (int)doTest:(NSString *)name para1:(int)temp1 para2:(int)temp2 suc ...

  8. 011---Djang的cookie和session

    -------------------------------------------------------------cookie与session------------------------- ...

  9. 15,redis基础学习

    redis Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件 yum安装redis 1.yum安装 #前提得配置好阿里云yum源,epel源 #查看 ...

  10. P3365 改造二叉树

    P3365 改造二叉树 链接 分析: 求出中序遍历后,然后使其变成上升子序列.过程:每个点减去坐标,然后nlogn求出最长不下降子序列,n-ans即答案. 做题时一直认为二叉树就是完全二叉树,然后一直 ...