歡迎您光臨本站 註冊首頁

Linux網卡驅動程序詳解

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  

  當網路上一台計算機準備發送數據時,他的網卡開始工作了,首先網卡的晶元偵聽在網路上是否有數據在流動,如果沒有,他就把數據發送到網路上,在偵聽和發送之間有一段極小的時間延遲,在這段時間內,也有可能在網路上有其他的計算機也準備發送數據,也偵聽到網路上沒有數據在流動,這就可能兩台甚至多台的數據一起發送到網路上,產生數據的碰撞,發送數據的計算機的網卡晶元當然要在發送完成後再校驗返回的數據,如果發現和發送的數據不一致,那就是說產生了碰撞,所以在一個乙太網絡中的計算機數量不宜過多,他不但會增加廣播包在網路中的數量,也請也會增加數據包的碰撞次數.

  我們的計算機的網卡晶元在接收到一完整的數據包后,晶元的一引腳通知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 0x3f0n",
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 = inb(base+EL_EAW);/*EL_EAW是該卡的地址口,station_addr是函數內部變數,
下面判斷了生產廠家后就沒用的*/
}
dprintf(("Address is %6Dn",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 %6Dn",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>>&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%xn",i));
if(!(i & EL_TXS_READY)) { /* 如果傳輸狀態寄存器不是準備接受新幀就緒 */
dprintf(("el: err txs=%xn",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>>&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 readyn"));
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(("%6Dn",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 timeoutn", ifp->if_unit);
ifp->if_oerrors++;
el_reset(ifp->if_softc);
}

(責任編輯:A6)



[火星人 ] Linux網卡驅動程序詳解已經有422次圍觀

http://coctec.com/docs/service/show-post-73764.html