網絡設備與媒介層
/* 寄存器定義 */
#define DATA_REG 0x0004
#define CMD_REG 0x0008
/* 寄存器讀寫函數*/
static u16 xxx_readword(u32 base_addr,int portno)
{
/* 讀寄存器的值並返回 */
}
static void xxx_writeword(u32 base_addr,int portno,u16 value)
{
/* 向寄存器寫入數據 */
}
網絡設備驅動加載函數模板
int xxx_init_module(void)
{
...
/*分配net_device結構並對其成員賦值*/
xxx_dev = alloc_netdev(sizeof(struct xxx_priv),"sn%d",xxx_init);
if(xxx_dev == NULL)
/*分配net_device失敗*/
/*註冊net_device*/
if((result = register_netdev(xxx_dev)))
...
}
void xxx_cleanup(void)
{
...
/*註銷net_device*/
unregister_netdev(xxx_dev);
/*釋放net_device*/
free_netdev(xxx_dev);
}
網絡設備驅動的初始化函數模板
void xxx_init(struct net_device *dev)
{
/*設備的私有信息結構體*/
struct xxx_priv *priv;
/*檢查設備是否存在和設備所使用的硬件資源*/
xxx_hw_init();
/*初始化以太網設備公用成員*/
ether_setup(dev);
/*設置設備成員的函數指針*/
dev->open = xxx_open;
dev->stop = xxx_release;
dev->set_config = xxx_config;
dev->hard_start_xmit = xxx_tx;
dev->do_ioctl = xxx_ioctl;
dev->get_stats = xxx_stats;
dev->change_mtu = xxx_change_mtu;
dev->rebuild_header = xxx_rebuild_header;
dev->hard_header = xxx_header;
dev->tx_timeout = xxx_tx_timeout;
dev_>watchdog_timeo = timeout;
/*取得私有指針並初始化他 */
priv = netdev_priv(dev);
.../*初始化設備私有區數據*/
}
探測xxx網絡設備是否存在。方法類似於數學的反證法
探測設備的的具體硬件配置。對於同類的設備使用統一的驅動,需在初始化時探測設備的具體型號。另外即使是同一設備在硬件上的具體配置也可能不一樣,我們也可以探測設備所使用的硬件資源
申請設備所用的硬件資源,如用request_region()函數進行IO端口的申請,該過程可在xxx_open函數中完成
網絡設備的打開函數,需完成如下工作
使能設備使用的硬件資源,申請IO區域、中斷和DMA通道等
使用內核提供的netif_start_queue()激活發送哦隊列
網絡設備的關閉函數,需完成如下工作
使用內核提供的netif_stop_queue(),停止設備傳輸包
釋放所使用的IO區域,中斷和DMA資源
網絡設備打開和釋放函數模板
int xxx_open(struct net_device *dev)
{
/*申請端口、IRQ等*/
ret = request_irq(dev->irq,&xxx_interrupt,0,dev->name,dev);
...
netif_start_queue(dev);
...
ether_setup(dev);
}
void xxx_release(struct net_device *dev)
{
/*釋放端口、IRQ等*/
free_irq(dev->irq,dev);
...
netif_stop_queue(dev);
...
}
數據發送流程 :網絡設備驅動完成數據包發送流程如下
網絡設備驅動程序從上層協議傳遞協議傳遞過來的sk_buff參數獲得數據包的有效 數據和長度,將有效數據放入到臨時緩衝區
對於以特網,若有效數據的長度小於以太網衝突區檢測所要求的數據幀的最小長度ETH_ZLEN,則給臨時緩衝區的末尾寫0
設置硬的寄存器,驅動網絡設備進行數據發送操作。
網絡設備驅動程序的數據包發送函數模板
int xxx_tx(struct sk_buff *skb,struct net_device *dev)
{
int len;
char *data,shorttpkt[ETH_ZLEN];
if(xxx_send_avaiable(...)){/*發送隊列未滿可以發送*/
/*獲取有效數據指針和長度*/
data = skb->data;
len = skb_len;
if(len < ETH_ZLEN){
/*若幀小於以太網幀最小長度,補0*/
memset(shortpkt,0,ETH_ZLEN);
memcpy(shortpkt,skb->data,skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies;/*記錄發送時間戳*/
/*設置硬件寄存器讓硬件把數據包發送出去*/
xxx_hw_tx(data,len,dev);
...
}else{
netif_stop_queue(dev);
...
}
}
發送隊列爲滿或因其他原因來不及發送當前上層傳下來的包,則調用23行此函數阻止上層繼續向網絡設備驅動傳送數據包。當忙於發送的數據包被髮送完成後,TX結束的中斷處理中,應該調用netif_wake_queue()喚醒被阻塞的上層,以啓動它繼續向網絡設備發送數據。
當數據傳輸超時時,意味着當前的發送操作失敗或硬件陷入未知狀態,此時數據包發送超時函數xxx_tx_timeout()將被調用
網絡設備驅動程序的數據包發送超時函數模板
void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev);/*重新啓動發送隊列*/
}
數據接收流程,網絡設備接收數據的主要方法是由中斷引發設備的中斷處理函數,中斷處理函數判斷中斷類型,若爲接收中斷則讀取收到的數據,分配sk_buff數據結構和數據緩衝區,將接收到的數據複製到數據緩衝區,並調用netif_rx()函數將sk_buff傳遞給上層協議
網絡設備驅動中斷處理函數模板
static void xxx_interrupt(int irq,void *dev_id)
{
...
switch(status & ISQ_EVENT_MASK){
/*獲取數據包*/
xxx_rx(dev);
break;
/*其他類型的中斷*/
}
}
static void xxx_rx(struct xxx_device *dev)
{
...
length = get_rev_len(...);
/*分配新的套接字緩衝區*/
skb = dev_alloc_skb(length + 2);
skb_reserve(skb,2);/*對齊*/
skb->dev = dev;
/*讀取硬件上接收到的數據*/
insw(ioaddr + RX_FRAME_PORT,skb_put(skb,length),length >> 1);
if(length & 1)
skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
/*獲取上層協議類型*/
skb->protocol = eth_type_trans(skb,dev);
/*把數據包交給上層*/
netif_rx(skb);
/*記錄接收時間戳*/
dev->last_rx = jiffies;
...
}
接收數據順序判斷中斷類型
從硬件中讀取數據包有效長度
分配sk_buff
讀取硬件上接收到的數據並將數據放入到skb_buff
調用net_if()發將數據包傳遞給上層協議
通常情況下,網絡設備驅動以中斷方式接收數據包,而poll_controller()則採用中斷輪訓方式,另外一種接收數據方式是NAPI(New API)接收流程如下:接收中斷來臨,關閉接收中斷,以輪訓方式接收所有數據包直到收空,開啓接收中斷,接收中斷來臨。
如果收NAPI兼容的設備驅動,則可以通過poll方式接收數據包。這種情況下,需爲該設備驅動提供作爲netif_napi_add()參數的xxx_poll()函數,模板如下所示
static int xxx_poll(struct napi_struct *napi,int budget)
{
int npackets = 0;
struct sk_buff *skb;
struct xxx_priv *priv = container_of(napi,struct xxx_priv,napi);
struct xxx_packet *pkt;
while(npackets < budget && priv->rx_queue){
/* 從隊列中取出數據包*/
pkt = xxx_dequeue_buf(dev);
/*接下來的處理和中斷觸發的數據包一致*/
skb = dev_alloc_skb(pkt->datalen + 2);
...
skb_reserve(skb,2);
memcpy(skb_put(skb,pkt->datalen),pkt->data,pkt->datalen);
skb->dev = dev;
skb->protocol = eth_type_trans(skb,dev);
/*調用netif_receive_skb將數據包交給上層協議*/
netif_receive_skb(skb);
/*更改統計數據*/
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
xxx_release_buffer(pkt);
npackets++;
}
if(npackets <budget){
napi_complete(napi);
xxx_enable_rx_int(...)/*再次使能網絡設備的接收中斷*/
}
return npackets;
}
上述代碼中budget是在初始化階段分配給接口的weight值,poll函數每次只能接受最多budget數量的數據包。第八行的while循環讀取設備的接收緩衝區,讀取數據包並提交給上層。
雖然NPAI兼容的設備驅動以poll方式接收數據包,但是仍然需要首次數據包接收中斷來觸發poll過程。與數據包的終端接收方式不同的是,以輪訓方式接收數據包時,當第一次中斷髮生後,中斷處理程序要禁止設備的數據包接收中斷並調度NAPI
static void xxx_poll_interrupt(int irq,void *dev_id)
{
switch(status & ISQ_EVENT_MASK){
case ISQ_RECEIVER_EVENT:
.../*獲取數據包*/
xxx_disable_rx_int(...)/*禁止接收中斷*/
napi_schedule(&priv->napi);
break;
... /*其他類型的中斷*/
}
}
napi_schedule()函數被輪訓方式驅動的中斷程序調用,將設備的poll方法添加到網絡層的poll處理隊列中,排隊並且準備接收數據包,最終觸發一個NET_RX_SOFTIRQ軟中斷,通知網絡層接收數據包。
網絡連接狀態
網絡設備驅動可通過netif_carrier_on和netif_carrier函數改變設備的連接狀態,若驅動檢測到設備狀態發生變化,也應該以netif_carrier_on和netif_carrier函數顯示的通知內核
void netif_carrier_on(struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
/*向調用者返回鏈路上的載波信號是否存在*/
void netif_carrier_ok(struct net_device *dev);
網絡設備驅動程序中可採用一定手段檢測和報告鏈路連接器狀態,例如設置一個定時器來對鏈路狀態進行週期性檢測。當定時器到時後,在定時器處理函數中讀取物理設備的相關寄存器獲得載波狀態,從而更新設備連接器的狀態,如以下代碼清單
static void xxx_timer(unsigned long data)
{
struct net_device *dev = (struct net_device *data);
u16 link;
...
if(!(dev->flags & IFF_UP))
goto set_timer;
/*獲取物理上的連接狀態*/
if(link == xxx_chk_link(dev)){
if(!(dev->flags & IFF_RUNNING)){
netif_carrier_on(dev);
dev->flags |= IFF_RUNNING;
printk(KERN_DEBUG "%s:link up\n",dev->name);
}
}else{
if(dev->flags & IFF_RUNNING){
netif_carrier_off(dev);
dev->flags &= ~IFF_RUNNING;
printk(KERN_DEBUG "%s:link down\n",dev->name);
}
}
set_timer:
priv->timer.expries = jiffies +1*HZ;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; //timer handler
add_timer(&priv->timer);
}
9行xxx_chk_link用於讀取網絡適配器硬件相關寄存器以獲得相關鏈路的連接狀態,具體實現狀態由硬件決定。當鏈路連接上時netif_carrier_on顯式的通知內黑鏈路正常,反之netif_carrier_off通知內核鏈路失去連接。
上述代碼中,定時器處理函數不停的利用set_timer代碼啓動新的定時器以實現週期檢測的目的。最初啓動定時器適合在設備的打開函數中實現
static int xxx_open(struct net_device *dev)
{
struct xxx_priv *priv = (struct xxx_priv*)dev->priv;
...
priv->timer.expries = jiffies +3*HZ;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; //timer handler
add_timer(&priv->timer);
...
}
參數的設置和統計數據
用戶調用ioctl,並指定SIOCSIFHWADDR命令時,意味着要設置這個設備的MAC地址,設置MAC地址模板
static int set_mac_address(struct net_device *dev,void *addr)
{
if(netif_running(dev)) /*設備忙*/
return -EBUSY;
/*設置以太網MAC地址*/
xxx_set_mac(dev,addr);
return 0;
}
//當然要實現上述函數,需硬件支持,但是實際上很多硬件不支持
//#define netif_running(dev) (dev->flags & IFF_UP)
用戶調用ioctl,並指定SIOCSIFMAP(控制檯執行ifconfig就會引發這一調用)命令時,系統會調用驅動程序的set_config,系統會向set_config函數傳遞一個ifmap結構體,包含用戶欲設置的設備要使用的I/O地址、中斷等信息,實際上大多數設備並不宜包含set_config
網絡設備驅動的set_config函數模板
int xxx_config(struct net_device *dev,struct ifmap *map)
{
if(netif_running(dev)); /*不能設置一個正在運行狀態的設備*/
return -EBUSY;
/*假設不允許改變IO地址*/
if(map->base_addr != dev->base_addr){
printk(KERN_WARNING "xxx:can't change io address\n");
return -EOPNOTSUPP;
}
/*假設允許改變IRQ*/
if(map->irq != dev->irq)
dev->irq = map->irq;
return 0;
}