rtems网络移植-实现网卡驱动
經過兩周的調試,終于初步實現網卡的發送功能。
在這里參考了uboot的beaglebone網卡驅動和《tcp/ip詳解卷二》
1、在前幾篇博文中,講解了網卡的mdio初始化過程,那么網卡lan8710a是如何與am335x處理器通信的呢?
首先看一張連接圖:
我們都知道osi七層協議,最底下的是物理層和數據鏈路層,也就是mac和phy。有一部分處理器是自帶mac層,也就是只需要pyh網卡一個外設就能實現網絡功能。還有一部分處理器沒有mac,也就是要外設一個mac和一個phy。beaglebone black的處理器是ti的am335x,是自帶mac層的,phy網卡采用的是smsc的lan8710A。
上圖很好的展現了網卡與處理器的通信:
首先處理器對于網卡寄存器的配置和控制方式采用的是MDIO通信
MDIO是由IEEE通過以太網標準IEEE 802.3的若干條款加以定義。MDIO是一種簡單的雙線串行接口,和spi類似,甚至不用時鐘同步。mdio控制的寄存器主要包括BMCR、BMSR,自適應等模式的配置。
然后是傳輸數據的通信方式采用MII接口:
MII共有14根線,包括發送和接收的八根線,還有時鐘線等。
再回到最上面的圖,能夠看到處理器控制mac然后以MII的通信方式進行數據傳輸,這里用到了DMA,好處是不用CPU的干預,傳輸速度很快。網卡直接把數據傳輸給
,然后DMA傳輸給內存DDR,而處理器對網卡的控制和配置則直接通過MDIO。下圖是LAN8710網卡官方文檔中的MII模式連接圖:
2、TI對于網卡設備的通用管理是CPSW方式,分為host和slave,如何配置?
這里給出的代碼分析是uboot中cpsw.c代碼
首先是cpsw設備的注冊:
int cpsw_register(struct cpsw_platform_data *data) {struct cpsw_priv *priv;struct cpsw_slave *slave;void *regs = (void *)data->cpsw_base;struct eth_device *dev;printf("cpsw_register \n");dev = calloc(sizeof(*dev), 1);if (!dev)return -ENOMEM;priv = calloc(sizeof(*priv), 1);if (!priv) {free(dev);return -ENOMEM;}priv->data = *data;priv->dev = dev;priv->slaves = malloc(sizeof(struct cpsw_slave) * data->slaves);if (!priv->slaves) {free(dev);free(priv);return -ENOMEM;}priv->host_port = data->host_port_num;priv->regs = regs;priv->host_port_regs = regs + data->host_port_reg_ofs;priv->dma_regs = regs + data->cpdma_reg_ofs;priv->ale_regs = regs + data->ale_reg_ofs;priv->descs = (void *)regs + data->bd_ram_ofs;int idx = 0;for_each_slave(slave, priv) {cpsw_slave_setup(slave, idx, priv);idx = idx + 1;}strcpy(dev->name, "cpsw");dev->iobase = 0;dev->init = cpsw_init;dev->halt = cpsw_halt;dev->send = cpsw_send;dev->recv = cpsw_recv;dev->priv = priv;eth_register(dev);cpsw_mdio_init(dev->name, data->mdio_base, data->mdio_div);priv->bus = miiphy_get_dev_by_name(dev->name);for_active_slave(slave, priv)cpsw_phy_init(dev, slave);return 1; }首先是聲明幾個結構體變量,其中包括cpsw的主:cpsw_priv和從:cpsw_slave,然后是設置cpsw的基礎寄存器的地址cpsw_base,然后調用calloc函數為這些結構體分配空間。
分配好后對priv結構體中的成員進行初始化,host_port=0表示主機端口號是0,然后成員的寄存器的偏移地址進行初始化。
priv->host_port = data->host_port_num;priv->regs = regs;priv->host_port_regs = regs + data->host_port_reg_ofs;priv->dma_regs = regs + data->cpdma_reg_ofs;priv->ale_regs = regs + data->ale_reg_ofs;priv->descs = (void *)regs + data->bd_ram_ofs;對每個salve進行初始化,這里采用for循環的意義在于可能有多個網卡,am335支持雙網卡。
for_each_slave(slave, priv) {cpsw_slave_setup(slave, idx, priv);idx = idx + 1;}
cpsw_phy_init函數定義: static int cpsw_phy_init(struct eth_device *dev, struct cpsw_slave *slave) {struct cpsw_priv *priv = (struct cpsw_priv *)dev->priv;struct phy_device *phydev;u32 supported = PHY_GBIT_FEATURES;printf("cpsw_phy_init \n");printf("phy_addr:%d \n",slave->data->phy_addr);phydev = phy_connect(priv->bus,slave->data->phy_addr,dev,slave->data->phy_if);if (!phydev)return -1;phydev->supported &= supported;phydev->advertising = phydev->supported;priv->phydev = phydev;phy_config(phydev);return 1; }
首先分析phy_connect函數:
phy_connect_dev函數實現:
struct phy_device *phy_find_by_mask(struct mii_dev *bus, unsigned phy_mask,phy_interface_t interface) {/* Reset the bus */if (bus->reset) {bus->reset(bus);/* Wait 15ms to make sure the PHY has come out of hard reset */udelay(15000);}return get_phy_device_by_mask(bus, phy_mask, interface); }該函數主要是調用get_phy_device_by_mask函數進行設備的查找,get_phy_device_by_mask函數的實現至關重要,包含了對于網卡的主要mdio通信
get_phy_device_by_mask函數實現:
search_for_existing_phy函數實現:
static struct phy_device *search_for_existing_phy(struct mii_dev *bus,unsigned phy_mask, phy_interface_t interface) {/* If we have one, return the existing device, with new interface */while (phy_mask) {int addr = ffs(phy_mask) - 1;if (bus->phymap[addr]) {bus->phymap[addr]->interface = interface;return bus->phymap[addr];}phy_mask &= ~(1 << addr);}return NULL; }
static struct phy_device *create_phy_by_mask(struct mii_dev *bus,unsigned phy_mask, int devad, phy_interface_t interface) {u32 phy_id = 0xffffffff;while (phy_mask) {int addr = ffs(phy_mask) - 1;int r = get_phy_id(bus, addr, devad, &phy_id);/* If the PHY ID is mostly f's, we didn't find anything */if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff)return phy_device_create(bus, addr, phy_id, interface);phy_mask &= ~(1 << addr);}return NULL; }
該函數調用get_phy_id函數讓處理器通過mdio總線查看網卡寄存器存儲的ID,如果ID都是f,說明沒有ID,就返回空,否則返回phy_device_create函數進行創建一個網卡設備。
get_phy_id函數實現:
cpsw_mdio_read實現:
static int cpsw_mdio_read(struct mii_dev *bus, int phy_id,int dev_addr, int phy_reg) {int data;u32 reg;printf("cpsw_mdio_read \n");printf("phy_id %d\n",phy_id);if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)return -EINVAL;wait_for_user_access();reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |(phy_id << 16));__raw_writel(reg, &mdio_regs->user[0].access);reg = wait_for_user_access();data = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1;return data; }該函數調用wait_for_user_access函數來等待能否讀取寄存器信號,當標志位表示能夠access時,將reg的值寫到&mdio_regs->user[0].access寄存器,reg包含了想要讀的寄存器的變量,然后繼續調用wait_for_user_access函數等待數據完成,將wait_for_user_access函數的返回值給reg,想要讀寄存器的data就等于(reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1; 然后返回data,就完成了mdio對于網卡寄存器的讀取。
看看wait_for_user_access函數的實現:
static inline u32 wait_for_user_access(void) {u32 reg = 0;int timeout = MDIO_TIMEOUT;while (timeout-- &&((reg = __raw_readl(&mdio_regs->user[0].access)) & USERACCESS_GO))udelay(10);if (timeout == -1) {printf("wait_for_user_access Timeout\n");return -ETIMEDOUT;}return reg; }可以看出,這里用了一個while循環,等待100個機器周期來讀取網卡寄存器的值。
以上就是cpsw的注冊和網卡設備的mdio連接。
接下來分析最重要的cpsw_init函數,包含了ALE、DMA、cpsw_slave的初始化和配置。
cpsw.c?
cpsw_init函數實現:
static int cpsw_init(struct eth_device *dev, bd_t *bis) {struct cpsw_priv *priv = dev->priv;struct cpsw_slave *slave;int i, ret; printf("cpsw_init func\n");/* soft reset the controller and initialize priv */setbit_and_wait_for_clear32(&priv->regs->soft_reset);/* initialize and reset the address lookup engine */cpsw_ale_enable(priv, 1);cpsw_ale_clear(priv, 1);cpsw_ale_vlan_aware(priv, 0); /* vlan unaware mode *//* setup host port priority mapping */__raw_writel(0x76543210, &priv->host_port_regs->cpdma_tx_pri_map);__raw_writel(0, &priv->host_port_regs->cpdma_rx_chan_map);/* disable priority elevation and enable statistics on all ports */__raw_writel(0, &priv->regs->ptype);/* enable statistics collection only on the host port */__raw_writel(BIT(priv->host_port), &priv->regs->stat_port_en);__raw_writel(0x7, &priv->regs->stat_port_en);printf("&priv->regs->stat_port_en:%x\n",&priv->regs->stat_port_en);printf("priv->regs->stat_port_en:%x\n",priv->regs->stat_port_en);cpsw_ale_port_state(priv, priv->host_port, ALE_PORT_STATE_FORWARD);//cpsw_ale_add_ucast(priv, priv->dev->enetaddr, priv->host_port, ALE_SECURE);//cpsw_ale_add_mcast(priv, net_bcast_ethaddr, 1 << priv->host_port);for_active_slave(slave, priv)cpsw_slave_init(slave, priv);cpsw_update_link(priv);/* init descriptor pool */for (i = 0; i < NUM_DESCS; i++) {desc_write(&priv->descs[i], hw_next,(i == (NUM_DESCS - 1)) ? 0 : &priv->descs[i+1]);}priv->desc_free = &priv->descs[0];printf("&priv->descs[0]:%x\n",&priv->descs[0]); printf("priv->dma_regs + CPDMA_RXHDP_VER2:%x\n",priv->dma_regs + CPDMA_RXHDP_VER2);/* initialize channels */if (priv->data.version == CPSW_CTRL_VERSION_2) {memset(&priv->rx_chan, 0, sizeof(struct cpdma_chan));priv->rx_chan.hdp = priv->dma_regs + CPDMA_RXHDP_VER2;priv->rx_chan.cp = priv->dma_regs + CPDMA_RXCP_VER2;priv->rx_chan.rxfree = priv->dma_regs + CPDMA_RXFREE;memset(&priv->tx_chan, 0, sizeof(struct cpdma_chan));priv->tx_chan.hdp = priv->dma_regs + CPDMA_TXHDP_VER2;priv->tx_chan.cp = priv->dma_regs + CPDMA_TXCP_VER2;} else {memset(&priv->rx_chan, 0, sizeof(struct cpdma_chan));priv->rx_chan.hdp = priv->dma_regs + CPDMA_RXHDP_VER1;priv->rx_chan.cp = priv->dma_regs + CPDMA_RXCP_VER1;priv->rx_chan.rxfree = priv->dma_regs + CPDMA_RXFREE;memset(&priv->tx_chan, 0, sizeof(struct cpdma_chan));priv->tx_chan.hdp = priv->dma_regs + CPDMA_TXHDP_VER1;priv->tx_chan.cp = priv->dma_regs + CPDMA_TXCP_VER1;}/* clear dma state */setbit_and_wait_for_clear32(priv->dma_regs + CPDMA_SOFTRESET);if (priv->data.version == CPSW_CTRL_VERSION_2) {for (i = 0; i < priv->data.channels; i++) {__raw_writel(0, priv->dma_regs + CPDMA_RXHDP_VER2 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_RXFREE + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_RXCP_VER2 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_TXHDP_VER2 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_TXCP_VER2 + 4* i);}} else {for (i = 0; i < priv->data.channels; i++) {__raw_writel(0, priv->dma_regs + CPDMA_RXHDP_VER1 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_RXFREE + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_RXCP_VER1 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_TXHDP_VER1 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_TXCP_VER1 + 4* i);}}__raw_writel(1, priv->dma_regs + CPDMA_TXCONTROL);__raw_writel(1, priv->dma_regs + CPDMA_RXCONTROL);/* submit rx descs */for (i = 0; i < PKTBUFSRX; i++) {ret = cpdma_submit(priv, &priv->rx_chan, net_rx_packets[i],PKTSIZE);if (ret < 0) {printf("error %d submitting rx desc\n", ret);break;}}return 0; }
ALE:address lookup engine 地址查詢引擎,是TI創造的一種對于雙網卡選擇的方式:
/* initialize and reset the address lookup engine */cpsw_ale_enable(priv, 1);cpsw_ale_clear(priv, 1);cpsw_ale_vlan_aware(priv, 0); /* vlan unaware mode */這三個函數的實現都在cpsw.c文件中,都是向相應的ale寄存器中寫值,目的是為了使能ale引擎,并開啟vlan(虛擬局域網)
貼出代碼,但不具體解釋:
#define cpsw_ale_enable(priv, val) cpsw_ale_control(priv, 31, val) #define cpsw_ale_clear(priv, val) cpsw_ale_control(priv, 30, val) #define cpsw_ale_vlan_aware(priv, val) cpsw_ale_control(priv, 2, val) static inline void cpsw_ale_control(struct cpsw_priv *priv, int bit, int val) {u32 tmp, mask = BIT(bit);tmp = __raw_readl(priv->ale_regs + ALE_CONTROL);tmp &= ~mask;tmp |= val ? mask : 0;__raw_writel(tmp, priv->ale_regs + ALE_CONTROL); }
接下來設置端口的初始mapping,也就是設置內存映射,將硬件DMA通道的發送和接收寄存器的硬件地址映射到內存空間,這樣就可通過訪問和修改內存地址內容來修改相應硬件配置:
接著,初始化cpsw的slave,也就是網卡部分,具體實現在cpsw_slave_init函數,待會再分析。
for_active_slave(slave, priv)cpsw_slave_init(slave, priv);
接著是cpsw_update_link函數,該函數是刷新與網卡的連接,確保能夠通信
該函數的實現主要是調用cpsw_slave_update_link函數對slave進行重新初始化連接:
函數實現:
static void cpsw_slave_update_link(struct cpsw_slave *slave,struct cpsw_priv *priv, int *link) {struct phy_device *phy;u32 mac_control = 0;phy = priv->phydev;if (!phy)return;phy_startup(phy);*link = phy->link;if (*link) { /* link up */mac_control = priv->data.mac_control;if (phy->speed == 1000)mac_control |= GIGABITEN;if (phy->duplex == DUPLEX_FULL)mac_control |= FULLDUPLEXEN;if (phy->speed == 100)mac_control |= MIIEN;}if (mac_control == slave->mac_control)return;if (mac_control) {printf("link up on port %d, speed %d, %s duplex\n",slave->slave_num, phy->speed,(phy->duplex == DUPLEX_FULL) ? "full" : "half");} else {printf("link down on port %d\n", slave->slave_num);}__raw_writel(mac_control, &slave->sliver->mac_control);slave->mac_control = mac_control; }該函數調用phy_startup(phy)進行設備的開啟和連接,然后獲得數據進行判斷,當link為真時,進入if,判斷網卡的工作速率是在10M還是100M,工作模式是雙工還是單工。并且通過printf打印信息。
對于phy_startup函數主要是調用phy.c文件下的genphy_update_link函數和genphy_parse_link函數。
genphy_update_link實現如下:
該函數首先調用phy_read函數來獲取BMSR寄存器的值
什么是BMSR,這是網卡的狀態寄存器,BMCR是網卡的控制寄存器,一般而言,BMSR供我們讀取數據進行判斷網卡的狀態,而BMCR一般是供我們寫入數據進行控制。
下圖是該網卡的寄存器表:
如果想要查看各寄存器的定義和每一位的定義,可以到SMSC官網下載文檔。
回到函數,當讀取到網卡的狀態寄存器的值后,開始進行一系列判斷
if ((mii_reg & BMSR_ANEGCAPABLE) && !(mii_reg & BMSR_ANEGCOMPLETE)) 這個判斷條件是判斷網卡是否完成自適應配置,如果完成,打印相應信息。不然的話,第二次讀取BMSR寄存器,重新判斷一次。
然后是genphy_config函數,該函數是對網卡的信息進行一個讀取,比如是否支持千兆網卡,是否支持10M/100M 單工/雙工。
函數實現:
int genphy_config(struct phy_device *phydev) {int val;u32 features;/* For now, I'll claim that the generic driver supports* all possible port types */features = (SUPPORTED_TP | SUPPORTED_MII| SUPPORTED_AUI | SUPPORTED_FIBRE |SUPPORTED_BNC);/* Do we support autonegotiation? */val = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);if (val < 0)return val;if (val & BMSR_ANEGCAPABLE)features |= SUPPORTED_Autoneg;if (val & BMSR_100FULL)features |= SUPPORTED_100baseT_Full;if (val & BMSR_100HALF)features |= SUPPORTED_100baseT_Half;if (val & BMSR_10FULL)features |= SUPPORTED_10baseT_Full;if (val & BMSR_10HALF)features |= SUPPORTED_10baseT_Half;if (val & BMSR_ESTATEN) {val = phy_read(phydev, MDIO_DEVAD_NONE, MII_ESTATUS);if (val < 0)return val;if (val & ESTATUS_1000_TFULL)features |= SUPPORTED_1000baseT_Full;if (val & ESTATUS_1000_THALF)features |= SUPPORTED_1000baseT_Half;if (val & ESTATUS_1000_XFULL)features |= SUPPORTED_1000baseX_Full;if (val & ESTATUS_1000_XHALF)features |= SUPPORTED_1000baseX_Half;}phydev->supported = features;phydev->advertising = features;genphy_config_aneg(phydev);return 0; }可以看出,該函數和genphy_update_link的實現風格很像,不再詳細說明
再回到cpsw_slave_update_link函數,這樣就完成了對于網卡的重新連接。
回到cpsw_init 函數,接著初始化DMA通道和DMA描述符
首先初始化描述符池
然后初始化DMA通道,am335有8個channel
memset(&priv->rx_chan, 0, sizeof(struct cpdma_chan));priv->rx_chan.hdp = priv->dma_regs + CPDMA_RXHDP_VER2;priv->rx_chan.cp = priv->dma_regs + CPDMA_RXCP_VER2;priv->rx_chan.rxfree = priv->dma_regs + CPDMA_RXFREE;memset(&priv->tx_chan, 0, sizeof(struct cpdma_chan));priv->tx_chan.hdp = priv->dma_regs + CPDMA_TXHDP_VER2;priv->tx_chan.cp = priv->dma_regs + CPDMA_TXCP_VER2;
分配好channel的內存地址后,初始化這些通道,方法也很簡單,寫0即可:
__raw_writel(0, priv->dma_regs + CPDMA_RXHDP_VER2 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_RXFREE + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_RXCP_VER2 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_TXHDP_VER2 + 4* i);__raw_writel(0, priv->dma_regs + CPDMA_TXCP_VER2 + 4* i);}
以上就完成了對cpsw設備的初始化,網卡的配置也基本完成
3、實現網卡的發送
網卡的發送函數主要是調用cpsw_send函數,比如當輸入ping命令時,經過一系列的裝包,最后調用cpsw_send函數進行發送,在發送ICMP包之前,會先調用arp發送arp地址解析協議,然后根據收到的rarp的包得知主機的mac地址。然后再發送icmp包,因此在這里首先要看如何實現arp包的發送。
首先了解arp協議的格式:
然后分析cpsw_send函數:
static int cpsw_send(struct eth_device *dev, void *packet, int length) {struct cpsw_priv *priv = dev->priv;void *buffer;int len;int timeout = CPDMA_TIMEOUT;flush_dcache_range((unsigned long)packet,(unsigned long)packet + length);/* first reap completed packets */while (timeout-- &&(cpdma_process(priv, &priv->tx_chan, &buffer, &len) >= 0));if (timeout == -1) {printf("cpdma_process timeout\n");return -ETIMEDOUT;}return cpdma_submit(priv, &priv->tx_chan, packet, length); }
該函數調用flush_dcache_range函數對數據緩存進行刷新,刷新的地址就是packet的地址,因為要保證緩存和內存的一致性,也就是一致性DMA。
然后調用cpdma_process和cpdma_submit函數將數據傳給DMA描述符,再傳送給DMA通道,DMA通過MII發送給網卡,網卡再將數據發送出去。
以上就是本人實現網卡驅動的大致過程,最后的描述可能有些不詳細,時間太晚了,有問題可以留言交流~。
轉載請說明出處。
總結
以上是生活随笔為你收集整理的rtems网络移植-实现网卡驱动的全部內容,希望文章能夠幫你解決所遇到的問題。