基於stm32f429的uclinux-W5500網路裝置核心驅動
之前那篇寫w5500驅動只是單純的應用程式驅動,雖然可以實現一定的目的,但是沒有充分利用到linux的核心,在一些應用場合就顯得不合時宜,於是就進行w5500網路裝置核心驅動的學習,幸運的是w5500網路裝置驅動的檔案是在4.8版本的linux核心中找到,但是與我現在使用的2.6.33版本的核心在有些函式和資料結構等都有一定程度上缺失,為此花了很久的一段時間去修修補補這個驅動C檔案,終於修補到了編譯無錯誤的程度了,但也經過一定時間的除錯才能達到想要的效果,但是通過這樣也學習到linux的網路裝置方面的知識
這裡只是對w5500網路裝置驅動的一些重要的函式及資料結構做簡單的分析與總結
W5500網路裝置驅動主要包括在兩個檔案:
w5100-spi.c構建spi裝置驅動以使用spi介面方式來設定w5500,設定w5500的基本讀寫函式,w5100.c主要構建linux核心的網路裝置驅動,設定網路傳送接收skb等函式,注意兩者緊密相連,缺一不可!修改核心檔案中的Kconfig和makefile檔案編譯這兩個C檔案。
基本流程為:
在w5100-spi.c主要是用spi的方式來設定w5500的讀、寫函式,通過其中的w5100_spi_probe函式最後轉入到w5100.c中的w5100_probe函式,下面重點關注w5100_spi_probe()這個函式:
int w5100_probe(struct device *dev, const struct w5100_ops *ops,int sizeof_ops_priv, const void *mac_addr, int irq,int link_gpio)
1、 進入這個函式後,首先分配註冊網路裝置
ndev = alloc_etherdev(alloc_size);
err = register_netdev(ndev);
2、 填充網路裝置檔案操作結構體
ndev->netdev_ops = &w5100_netdev_ops;
static const struct net_device_ops w5100_netdev_ops = {
.ndo_open = w5100_open,
.ndo_stop = w5100_stop,
.ndo_start_xmit = w5100_start_tx,
.ndo_tx_timeout = w5100_tx_timeout,
.ndo_set_rx_mode = w5100_set_rx_mode,
.ndo_set_mac_address = w5100_set_macaddr,
.ndo_validate_addr = eth_validate_addr,
.ndo_change_mtu = eth_change_mtu,
};
填充網路裝置檔案操作,open、stop、.ndo_start_xmit等函式,可見網路裝置資料傳送函式為w5100_start_tx
3、 填充ethtool_ops結構體
ndev->ethtool_ops = &w5100_ethtool_ops;
static const struct ethtool_ops w5100_ethtool_ops = {
.get_drvinfo = w5100_get_drvinfo,
.get_msglevel = w5100_get_msglevel,
.set_msglevel = w5100_set_msglevel,
.get_link = w5100_get_link,
.get_regs_len = w5100_get_regs_len,
.get_regs = w5100_get_regs,
};
ethtool_ops成員函式與使用者空間ethool工具的各個命令選項對應,ethtool提供了網絡卡及網絡卡驅動管理能力,能夠為linux網路開發人員和管理人員提供對網絡卡硬體、驅動程式和網路協議棧的設定、檢視以及除錯等功能。
4、 netif_napi_add(ndev, &priv->napi, w5100_napi_poll, 16);
該函式用於初始化一個NAPI,netif_napi_add()的poll引數是NAPI要排程執行的輪詢函式
資料接收流程:
如果是NAPI相容的裝置驅動,則通過poll方式接收資料包,在這種情況下,我們需要為該裝置驅動提供作為netif_napi_add()引數的w5100_napi_poll函式:
static int w5100_napi_poll(struct napi_struct *napi, int budget)
{
struct w5100_priv *priv = container_of(napi, struct w5100_priv, napi);
int rx_count;
for (rx_count = 0; rx_count < budget; rx_count++) {
struct sk_buff *skb = w5100_rx_skb(priv->ndev); //取得接收skb的資料
if (skb)
netif_receive_skb(skb);//分析網路接收緩衝區的資料
else
break;
}
if (rx_count < budget) {
napi_complete(napi);//將napi裝置從輪詢列表中刪除
w5100_enable_intr(priv); //使能中斷
}
return rx_count;
}
資料接收的方式是NAPI(New API),簡單來說,NAPI是綜合中斷方式與輪詢方式的技術
其資料接收流程為:
接收中斷來臨——》關閉接收中斷——》以輪詢方式接收所有資料包直到收空——》開啟接收中斷——》接收中斷來臨
註冊中斷函式:
err = request_irq(priv->irq, w5100_interrupt,IRQF_TRIGGER_LOW, ndev->name, ndev);
priv->irq:要申請的硬體中斷號
w5100_interrupt:向系統註冊的中斷處理函式,是一個回撥函式,中斷髮生時,系統呼叫這個函式,ndev引數將被傳遞給它
IRQF_TRIGGER_LOW:指定中斷觸發型別:低電平有效
ndev->name:裝置驅動程式的名稱
ndev:中斷名稱可作為共享中斷時的中斷區別引數,也可以用來指定中斷服務函式需要參考的資料地址
中斷函式:
static irqreturn_t w5100_interrupt(int irq, void *ndev_instance)
{
struct net_device *ndev = ndev_instance;
struct w5100_priv *priv = netdev_priv(ndev);
int ir = w5100_read(priv, W5100_S0_IR(priv));
if (!ir)
return IRQ_NONE;
w5100_write(priv, W5100_S0_IR(priv), ir);
if (ir & S0_IR_SENDOK) {
// netif_dbg(priv, tx_done, ndev, "tx done\n");
netif_wake_queue(ndev);
}
if (ir & S0_IR_RECV) {
w5100_disable_intr(priv);
if (priv->ops->may_sleep)
queue_work(priv->xfer_wq, &priv->rx_work); //排程執行一個指定workqueue中的任務。輸入引數:
@ workqueue_struct:指定的workqueue指標
@work_struct:具體任務物件指標
else if (napi_schedule_prep(&priv->napi)) //檢查是否可以進行napi排程
__napi_schedule(&priv->napi);// 在網絡卡驅動的中斷處理函式中呼叫napi_schedule()來使用NAPI
}
return IRQ_HANDLED;
}
5、 建立網絡卡工作佇列
priv->xfer_wq = alloc_workqueue(netdev_name(ndev), WQ_MEM_RECLAIM, 0);
新增四個任務到工作佇列
INIT_WORK(&priv->rx_work, w5100_rx_work);
INIT_WORK(&priv->tx_work, w5100_tx_work);
INIT_WORK(&priv->setrx_work, w5100_setrx_work);
INIT_WORK(&priv->restart_work, w5100_restart_work);
INIT_WORK會在你定義的_work工作佇列裡面增加一個工作任務,該任務就是_func。_func這個任務會需要一些資料作為引數,這個引數就是通過_data傳遞的。
⑴w5100_rx_work()函式:
w5100_rx_skb():
①skb = netdev_alloc_skb_ip_align(ndev, rx_len);
netdev_alloc_skb_ip_align會申請一個sk_buff結構,同時申請存放報文資料的buffer空間,並將他們關聯起來
②skb_put(skb, rx_len);
skb_put()修改指向資料區末尾的指標tail,使之往下移len位元組,即使資料區向下擴大len位元組,並更新資料區長度len,通常在裝置驅動的接收資料處理中會呼叫此函式。
③skb->protocol = eth_type_trans(skb, ndev);
Linux kernel使用eth_type_trans來判斷資料幀的型別,及協議型別
w5100_enable_intr(priv):使能中斷
⑵w5100_tx_work()函式:w5100_tx_skb,傳送寫入skb的函式,最後dev_kfree_skb(skb)釋放套接字緩衝區。
⑶w5100_setrx_work(struct work_struct *work)
w5100_hw_start(priv):w5500硬體啟動
u8 mode = S0_MR_MACRAW;//將w5500設定為原始套接字,此處關鍵!
⑷w5100_restart_work
w5100_restart:
①netif_stop_queue(ndev):
呼叫linux核心提供的netif_stop_queue()函式,停止裝置傳輸包。
②w5100_hw_reset(priv);
函式內對w5500設定mac地址,進行socket0的初始化
③netif_wake_queue(ndev);
當忙於傳送的資料包被髮送完成後,在以TX結束的中斷處理中,應該呼叫netif_wake_queue(ndev)喚醒被阻塞的上層,以啟動它繼續向網路裝置驅動傳送資料包。
6、 資料傳送函式
w5100_netdev_ops函式中.ndo_start_xmit = w5100_start_tx,所以w5100_start_tx()為網絡卡資料傳送函式
static int w5100_start_tx(struct sk_buff *skb, struct net_device *ndev)
{
struct w5100_priv *priv = netdev_priv(ndev);
netif_stop_queue(ndev);// 停止裝置傳輸包
if (priv->ops->may_sleep) {
WARN_ON(priv->tx_skb);
priv->tx_skb = skb;
queue_work(priv->xfer_wq, &priv->tx_work);//開啟資料傳送工作任務
} else {
w5100_tx_skb(ndev, skb);
}
return NETDEV_TX_OK;
}
static void w5100_tx_skb(struct net_device *ndev, struct sk_buff *skb)
{
struct w5100_priv *priv = netdev_priv(ndev);
u16 offset;
offset = w5100_read16(priv, W5100_S0_TX_WR(priv));//讀w5500的socket0傳送緩衝區偏移
w5100_writebuf(priv, offset, skb->data, skb->len);
w5100_write16(priv, W5100_S0_TX_WR(priv), offset + skb->len);//寫資料到w5500
ndev->stats.tx_bytes += skb->len;
ndev->stats.tx_packets++; //記錄發包數量
dev_kfree_skb(skb);
w5100_command(priv, S0_CR_SEND);
}
最後執行的效果:
設定w5500網絡卡ip地址為192.168.1.88,用電腦主機去ping,可以ping通,若在linux核心中加了tcp/ip協議棧的話,則可以與電腦主機伺服器進行tcp和udp連線,但是這樣卻沒有使用w5500原本自帶的硬體tcp/ip協議棧,而是直接繞過去了,我們注意到的是w5500的網路裝置驅動程式是設定為原始套接字的形式,所以以後要關注的原始套接字在網路協議中的傳輸!!