rtems网络移植-实现网卡驱动
經(jīng)過兩周的調(diào)試,終于初步實現(xiàn)網(wǎng)卡的發(fā)送功能。
在這里參考了uboot的beaglebone網(wǎng)卡驅(qū)動和《tcp/ip詳解卷二》
1、在前幾篇博文中,講解了網(wǎng)卡的mdio初始化過程,那么網(wǎng)卡lan8710a是如何與am335x處理器通信的呢?
首先看一張連接圖:
我們都知道osi七層協(xié)議,最底下的是物理層和數(shù)據(jù)鏈路層,也就是mac和phy。有一部分處理器是自帶mac層,也就是只需要pyh網(wǎng)卡一個外設(shè)就能實現(xiàn)網(wǎng)絡(luò)功能。還有一部分處理器沒有mac,也就是要外設(shè)一個mac和一個phy。beaglebone black的處理器是ti的am335x,是自帶mac層的,phy網(wǎng)卡采用的是smsc的lan8710A。
上圖很好的展現(xiàn)了網(wǎng)卡與處理器的通信:
首先處理器對于網(wǎng)卡寄存器的配置和控制方式采用的是MDIO通信
MDIO是由IEEE通過以太網(wǎng)標(biāo)準(zhǔn)IEEE 802.3的若干條款加以定義。MDIO是一種簡單的雙線串行接口,和spi類似,甚至不用時鐘同步。mdio控制的寄存器主要包括BMCR、BMSR,自適應(yīng)等模式的配置。
然后是傳輸數(shù)據(jù)的通信方式采用MII接口:
MII共有14根線,包括發(fā)送和接收的八根線,還有時鐘線等。
再回到最上面的圖,能夠看到處理器控制mac然后以MII的通信方式進(jìn)行數(shù)據(jù)傳輸,這里用到了DMA,好處是不用CPU的干預(yù),傳輸速度很快。網(wǎng)卡直接把數(shù)據(jù)傳輸給
,然后DMA傳輸給內(nèi)存DDR,而處理器對網(wǎng)卡的控制和配置則直接通過MDIO。下圖是LAN8710網(wǎng)卡官方文檔中的MII模式連接圖:
2、TI對于網(wǎng)卡設(shè)備的通用管理是CPSW方式,分為host和slave,如何配置?
這里給出的代碼分析是uboot中cpsw.c代碼
首先是cpsw設(shè)備的注冊:
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; }首先是聲明幾個結(jié)構(gòu)體變量,其中包括cpsw的主:cpsw_priv和從:cpsw_slave,然后是設(shè)置cpsw的基礎(chǔ)寄存器的地址cpsw_base,然后調(diào)用calloc函數(shù)為這些結(jié)構(gòu)體分配空間。
分配好后對priv結(jié)構(gòu)體中的成員進(jìn)行初始化,host_port=0表示主機(jī)端口號是0,然后成員的寄存器的偏移地址進(jìn)行初始化。
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進(jìn)行初始化,這里采用for循環(huán)的意義在于可能有多個網(wǎng)卡,am335支持雙網(wǎng)卡。
for_each_slave(slave, priv) {cpsw_slave_setup(slave, idx, priv);idx = idx + 1;}
cpsw_phy_init函數(shù)定義: 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函數(shù):
phy_connect_dev函數(shù)實現(xiàn):
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); }該函數(shù)主要是調(diào)用get_phy_device_by_mask函數(shù)進(jìn)行設(shè)備的查找,get_phy_device_by_mask函數(shù)的實現(xiàn)至關(guān)重要,包含了對于網(wǎng)卡的主要mdio通信
get_phy_device_by_mask函數(shù)實現(xiàn):
search_for_existing_phy函數(shù)實現(xiàn):
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; }
該函數(shù)調(diào)用get_phy_id函數(shù)讓處理器通過mdio總線查看網(wǎng)卡寄存器存儲的ID,如果ID都是f,說明沒有ID,就返回空,否則返回phy_device_create函數(shù)進(jìn)行創(chuàng)建一個網(wǎng)卡設(shè)備。
get_phy_id函數(shù)實現(xiàn):
cpsw_mdio_read實現(xiàn):
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; }該函數(shù)調(diào)用wait_for_user_access函數(shù)來等待能否讀取寄存器信號,當(dāng)標(biāo)志位表示能夠access時,將reg的值寫到&mdio_regs->user[0].access寄存器,reg包含了想要讀的寄存器的變量,然后繼續(xù)調(diào)用wait_for_user_access函數(shù)等待數(shù)據(jù)完成,將wait_for_user_access函數(shù)的返回值給reg,想要讀寄存器的data就等于(reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -1; 然后返回data,就完成了mdio對于網(wǎng)卡寄存器的讀取。
看看wait_for_user_access函數(shù)的實現(xiàn):
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循環(huán),等待100個機(jī)器周期來讀取網(wǎng)卡寄存器的值。
以上就是cpsw的注冊和網(wǎng)卡設(shè)備的mdio連接。
接下來分析最重要的cpsw_init函數(shù),包含了ALE、DMA、cpsw_slave的初始化和配置。
cpsw.c?
cpsw_init函數(shù)實現(xiàn):
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創(chuàng)造的一種對于雙網(wǎng)卡選擇的方式:
/* 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 */這三個函數(shù)的實現(xiàn)都在cpsw.c文件中,都是向相應(yīng)的ale寄存器中寫值,目的是為了使能ale引擎,并開啟vlan(虛擬局域網(wǎng))
貼出代碼,但不具體解釋:
#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); }
接下來設(shè)置端口的初始mapping,也就是設(shè)置內(nèi)存映射,將硬件DMA通道的發(fā)送和接收寄存器的硬件地址映射到內(nèi)存空間,這樣就可通過訪問和修改內(nèi)存地址內(nèi)容來修改相應(yīng)硬件配置:
接著,初始化cpsw的slave,也就是網(wǎng)卡部分,具體實現(xiàn)在cpsw_slave_init函數(shù),待會再分析。
for_active_slave(slave, priv)cpsw_slave_init(slave, priv);
接著是cpsw_update_link函數(shù),該函數(shù)是刷新與網(wǎng)卡的連接,確保能夠通信
該函數(shù)的實現(xiàn)主要是調(diào)用cpsw_slave_update_link函數(shù)對slave進(jìn)行重新初始化連接:
函數(shù)實現(xiàn):
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; }該函數(shù)調(diào)用phy_startup(phy)進(jìn)行設(shè)備的開啟和連接,然后獲得數(shù)據(jù)進(jìn)行判斷,當(dāng)link為真時,進(jìn)入if,判斷網(wǎng)卡的工作速率是在10M還是100M,工作模式是雙工還是單工。并且通過printf打印信息。
對于phy_startup函數(shù)主要是調(diào)用phy.c文件下的genphy_update_link函數(shù)和genphy_parse_link函數(shù)。
genphy_update_link實現(xiàn)如下:
該函數(shù)首先調(diào)用phy_read函數(shù)來獲取BMSR寄存器的值
什么是BMSR,這是網(wǎng)卡的狀態(tài)寄存器,BMCR是網(wǎng)卡的控制寄存器,一般而言,BMSR供我們讀取數(shù)據(jù)進(jìn)行判斷網(wǎng)卡的狀態(tài),而BMCR一般是供我們寫入數(shù)據(jù)進(jìn)行控制。
下圖是該網(wǎng)卡的寄存器表:
如果想要查看各寄存器的定義和每一位的定義,可以到SMSC官網(wǎng)下載文檔。
回到函數(shù),當(dāng)讀取到網(wǎng)卡的狀態(tài)寄存器的值后,開始進(jìn)行一系列判斷
if ((mii_reg & BMSR_ANEGCAPABLE) && !(mii_reg & BMSR_ANEGCOMPLETE)) 這個判斷條件是判斷網(wǎng)卡是否完成自適應(yīng)配置,如果完成,打印相應(yīng)信息。不然的話,第二次讀取BMSR寄存器,重新判斷一次。
然后是genphy_config函數(shù),該函數(shù)是對網(wǎng)卡的信息進(jìn)行一個讀取,比如是否支持千兆網(wǎng)卡,是否支持10M/100M 單工/雙工。
函數(shù)實現(xiàn):
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; }可以看出,該函數(shù)和genphy_update_link的實現(xiàn)風(fēng)格很像,不再詳細(xì)說明
再回到cpsw_slave_update_link函數(shù),這樣就完成了對于網(wǎng)卡的重新連接。
回到cpsw_init 函數(shù),接著初始化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的內(nèi)存地址后,初始化這些通道,方法也很簡單,寫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設(shè)備的初始化,網(wǎng)卡的配置也基本完成
3、實現(xiàn)網(wǎng)卡的發(fā)送
網(wǎng)卡的發(fā)送函數(shù)主要是調(diào)用cpsw_send函數(shù),比如當(dāng)輸入ping命令時,經(jīng)過一系列的裝包,最后調(diào)用cpsw_send函數(shù)進(jìn)行發(fā)送,在發(fā)送ICMP包之前,會先調(diào)用arp發(fā)送arp地址解析協(xié)議,然后根據(jù)收到的rarp的包得知主機(jī)的mac地址。然后再發(fā)送icmp包,因此在這里首先要看如何實現(xiàn)arp包的發(fā)送。
首先了解arp協(xié)議的格式:
然后分析cpsw_send函數(shù):
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); }
該函數(shù)調(diào)用flush_dcache_range函數(shù)對數(shù)據(jù)緩存進(jìn)行刷新,刷新的地址就是packet的地址,因為要保證緩存和內(nèi)存的一致性,也就是一致性DMA。
然后調(diào)用cpdma_process和cpdma_submit函數(shù)將數(shù)據(jù)傳給DMA描述符,再傳送給DMA通道,DMA通過MII發(fā)送給網(wǎng)卡,網(wǎng)卡再將數(shù)據(jù)發(fā)送出去。
以上就是本人實現(xiàn)網(wǎng)卡驅(qū)動的大致過程,最后的描述可能有些不詳細(xì),時間太晚了,有問題可以留言交流~。
轉(zhuǎn)載請說明出處。
總結(jié)
以上是生活随笔為你收集整理的rtems网络移植-实现网卡驱动的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。