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、下次寫代碼,這次只分析。