/* 开始在接口上输出.从队列中得到包并输出他们,在输出中,留出接收用一
部分时间,即打开中断再关闭中断,这样使接口接到的一些数据包不会丢失.
*/
static void
el_start(struct ifnet *ifp)
{
struct el_softc *sc;
u_short base;
struct mbuf *m, *m0;
int s, i, len, retr
IEs, 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);
}
上一篇: FreeBSD下的内存文件系统
下一篇: FreeBSD网站平台建设全过程(1、系统安装)