linux網卡設備驅動(任意傳輸介質傳輸(與FPGA交互))

1 、底層數據分析

最近由於公司項目需求,需要實現需要把網絡數據包與DMA的交互,DMA後面連接的是FPGA,FPGA再和寬帶收發器相連接。
先不關心FPGA側的數據過程,以後會專門來說。首先分析下體現在ARM側的DMA數據流,(好吧,還得看下FPGA的部分結構,由於電腦沒裝vivado,用viso畫個簡單版本的,也便於專門搞ARM驅動的好理解)。第一幅是COPY Xilinx的,第二幅自己畫的。
在這裏插入圖片描述
在這裏插入圖片描述
圖畫的很LOW,下次一定帶上vivado。但是,可以看出來,整個硬件結構中有兩個DMA控制器是重點,我們需要在驅動程序中初始化DMA控制器,DMA控制器直接從DDR上面存取數據,那麼必定需要有信號來通知CPU,這件事情已經完成,中斷控制器就做了這個事情。
到現在總結下驅動底層要實現哪些接口:

  • DMA控制器初始化;
  • 中斷請求;
  • 讀寫接口;
    明白了流程,開始第二部看數據手冊:
    在這裏插入圖片描述
    欣賞一下Xilinx畫的圖吧,彌補對我畫的圖的不滿。看起來這個IP核有兩個功能,第一是S2MM第二是MM2S。(就是數據流方向的簡寫,設備到DDR和DDR到設備)

在這裏插入圖片描述
這個傢伙是我們配置DMA控制器的重點。往下翻翻手冊,看看怎麼操作吧。
在這裏插入圖片描述
不好意思,全是洋文。拙劣的翻譯一下吧。還是感謝下自己大學期間背了幾個月單詞,不然。。。

  • 打開DMA控制器的開關。
  • 想開DMA中斷嗎?想開你就開,不開拉倒。
  • 這是MM2S,所以你要設置你的內存地址,以便讓DMA來取數據。
  • 你要傳輸數據的長度<注意,寫完長度以後數據要開始傳輸!!!!>

現在,你可以編程了。
在這裏插入圖片描述
這個是地址映射表。我們先從vivado工程中找到設備的基地址。
在這裏插入圖片描述
這個是DMA的開關,按照上圖就是說,1開0關。
不說這個了,寫了半天好像是單片機編程,別說看,寫的都膩。

Linux網卡設備驅動的分析

在Linux中,想要寫個設備驅動,無非就是按照人家的套路來寫寫,(這也是當時學這個的原因,畫板子畫不好,算法至今只能大概知道小波是怎麼回事,也不會用。)好了,繼續前進:

1 、看看別人怎麼寫,自己又不會:

找到源碼,打開/driver/net/dm9000.c
她的硬件初始化不好看了,也用不上,還費時間。
直接看網絡設備初始化的部分,代碼太長了,不好意思全部貼出來。
精簡一下:

    	/* Init network device */
	ndev = alloc_etherdev(sizeof(struct board_info));
	if (!ndev) {
		dev_err(&pdev->dev, "could not allocate device.\n");
		return -ENOMEM;
	}


分配一個網絡設備的結構體,然後填充


    	/* fill in parameters for net-dev structure */
	ndev->base_addr = (unsigned long)db->io_addr;
	ndev->irq	= db->irq_res->start;

在這兒寫了網卡基地址和中斷號。


    ndev->netdev_ops	= &dm9000_netdev_ops;

這一句挺關鍵,裏面包含了網卡的傳輸函數。她拿了這個對象的指針


    static const struct net_device_ops dm9000_netdev_ops = {
	.ndo_open		= dm9000_open,
	.ndo_stop		= dm9000_stop,
	.ndo_start_xmit		= dm9000_start_xmit,
	.ndo_tx_timeout		= dm9000_timeout,
	.ndo_set_multicast_list	= dm9000_hash_table,
	.ndo_do_ioctl		= dm9000_ioctl,
	.ndo_change_mtu		= eth_change_mtu,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_set_mac_address	= eth_mac_addr,
};

是不是好像又是在寫流水燈程序?我都說了,Linux驅動這門算不上技術的技術一點意思都沒有,就是抄襲+流水燈。
但是,寫函數有了,那讀函數呢?不要忘了,學完流水燈還得學個按鍵。對於這種異步事件,要使用中斷。


 static irqreturn_t dm9000_wol_interrupt(int irq, void *dev_id)
{
	struct net_device *dev = dev_id;
	board_info_t *db = netdev_priv(dev);
	unsigned long flags;
	unsigned nsr, wcr;

	spin_lock_irqsave(&db->lock, flags);

	nsr = ior(db, DM9000_NSR);
	wcr = ior(db, DM9000_WCR);

	dev_dbg(db->dev, "%s: NSR=0x%02x, WCR=0x%02x\n", __func__, nsr, wcr);

	if (nsr & NSR_WAKEST) {
		/* clear, so we can avoid */
		iow(db, DM9000_NSR, NSR_WAKEST);

		if (wcr & WCR_LINKST)
			dev_info(db->dev, "wake by link status change\n");
		if (wcr & WCR_SAMPLEST)
			dev_info(db->dev, "wake by sample packet\n");
		if (wcr & WCR_MAGICST )
			dev_info(db->dev, "wake by magic packet\n");
		if (!(wcr & (WCR_LINKST | WCR_SAMPLEST | WCR_MAGICST)))
			dev_err(db->dev, "wake signalled with no reason? "
				"NSR=0x%02x, WSR=0x%02x\n", nsr, wcr);

	}

	spin_unlock_irqrestore(&db->lock, flags);

	return (nsr & NSR_WAKEST) ? IRQ_HANDLED : IRQ_NONE;
}

這不就是中斷服務函數嗎?在裏面判斷了中斷類型,如果是收到了數據,上報給TCP/IP協議棧。
再看看發送函數的細節:


    dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	unsigned long flags;
	board_info_t *db = netdev_priv(dev);

	dm9000_dbg(db, 3, "%s:\n", __func__);

	if (db->tx_pkt_cnt > 1)
		return NETDEV_TX_BUSY;

	spin_lock_irqsave(&db->lock, flags);

	/* Move data to DM9000 TX RAM */
	writeb(DM9000_MWCMD, db->io_addr);

	(db->outblk)(db->io_data, skb->data, skb->len);
	dev->stats.tx_bytes += skb->len;

	db->tx_pkt_cnt++;
	/* TX control: First packet immediately send, second packet queue */
	if (db->tx_pkt_cnt == 1) {
		dm9000_send_packet(dev, skb->ip_summed, skb->len);
	} else {
		/* Second packet */
		db->queue_pkt_len = skb->len;
		db->queue_ip_summed = skb->ip_summed;
		netif_stop_queue(dev);
	}

	spin_unlock_irqrestore(&db->lock, flags);

	/* free this SKB */
	dev_kfree_skb(skb);

	return NETDEV_TX_OK;
}

重要點分析

1、上來弄了個自旋鎖,還是中斷上下文的 spin_lock_irqsave(&db->lock, flags);,就是保護臨界數據,一會照抄就行了,先不管。
2、通知DM9000,趕緊寫數據吧 writeb(DM9000_MWCMD, db->io_addr);
3、寫入DM9000中的SARM (db->outblk)(db->io_data, skb->data, skb->len);
dev->stats.tx_bytes += skb->len;
4、 /* TX control: First packet immediately send, second packet queue /
if (db->tx_pkt_cnt == 1) {
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/
Second packet /
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
netif_stop_queue(dev);
}
這個是第一次直接發出去,以後用隊列。爲什麼?先留個疑問,寫代碼的時候看看。對於簡單的實現,起始無非就是mmcpy()。
5、解鎖,標誌着快完事了 。spin_unlock_irqrestore(&db->lock, flags);
6、釋放,還真完事了。dev_kfree_skb(skb);
讀取這麼簡單,看看所謂的中斷接收網絡數據,
/
Move data from DM9000 */
if (GoodPacket &&
((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {
skb_reserve(skb, 2);
rdptr = (u8 ) skb_put(skb, RxLen - 4);
/
Read received packet from RX SRAM */

		(db->inblk)(db->io_data, rdptr, RxLen);
		dev->stats.rx_bytes += RxLen;

		/* Pass to upper layer */
		skb->protocol = eth_type_trans(skb, dev);
		if (db->rx_csum) {
			if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)
				skb->ip_summed = CHECKSUM_UNNECESSARY;
			else
				skb_checksum_none_assert(skb);
		}
		netif_rx(skb);
		dev->stats.rx_packets++;

	} else {
		/* need to dump the packet's data */

		(db->dumpblk)(db->io_data, RxLen);
	}
} while (rxbyte & DM9000_PKT_RDY);

1、分配一個SKB,整個LINUX網絡協議棧中的,(她就好像人體肌肉中的ATP的殼子,高中生物也只剩下這點了。) ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) 。
2、既然想要運輸養料,光是個殼子不行,需要填充
skb_reserve(skb, 2);
rdptr = (u8 *) skb_put(skb, RxLen - 4);
這裏構造出了數據指針,接下來只需要mmcpy()一下不就行了。事實就會這樣。
(db->inblk)(db->io_data, rdptr, RxLen);
dev->stats.rx_bytes += RxLen;
她只不是寫到了專門的地方。

請注意。標題出現!

上面所說的兩次mmcpy,找不到把文章複製下來grep "mmcpy" ./meiwen.txt ,(乘機交給大家linux命令,大佬勿看)。
兩次mmcpy就是網卡與傳輸介質的接口。我們把數據通過這兩個接口和外部傳輸介質的數據相連接即可。
在一開始說了我們硬件的結構,以及最終抽象出來的接口,上一小節又說了linux最終給的兩個mmcpy,把他們接起來不就行了?

總結

1、致力於寫代碼的人,不要學Linux設備驅動,就是抄寫和流水燈+按鍵;
2、生活可以嬉皮笑臉,對於工作還是要有敬畏之心;
3、下次寫代碼,這次只分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章