AM3354 CPSW网卡驱动

经过两周的调试,终于初步实现网卡的发送功能。

在这里参考了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_privcpsw_slave,然后是设置cpsw的基础寄存器的地址cpsw_base,然后调用calloc函数为这些结构体分配空间。

分配好后对priv结构体中的成员进行初始化,host_port=0表示主机端口号是0,然后成员的寄存器的偏移地址进行初始化。

host_port       = host_port_num;
regs        = regs;
host_port_regs  = regs + host_port_reg_ofs;
dma_regs        = regs + cpdma_reg_ofs;
ale_regs        = regs + ale_reg_ofs;
descs       = (void *)regs + bd_ram_ofs;

对每个salve进行初始化,这里采用for循环的意义在于可能有多个网卡,am335x支持双网卡。

for_each_slave(slave, priv) {
    cpsw_slave_setup(slave, idx, priv);
    idx = idx + 1;
}

以mdio方式对网卡配置进行初始化:主要是调用cpsw_phy_init函数进行初始化

eth_register(dev);
cpsw_mdio_init(name, mdio_base, mdio_div);
bus = miiphy_get_dev_by_name(name);
for_active_slave(slave, priv)
    cpsw_phy_init(dev, slave);

cpsw_phy_init函数定义:

static int cpsw_phy_init(struct eth_device *dev, struct cpsw_slave *slave)
{
    struct cpsw_priv *priv = (struct cpsw_priv *)priv;
    struct phy_device *phydev;
    u32 supported = PHY_GBIT_FEATURES;
    printf("cpsw_phy_init \n");
    printf("phy_addr:%d \n", phy_addr);
    phydev = phy_connect(bus, phy_addr,
            dev, phy_if);

    if (!phydev)
        return -1;

    phydev->supported= supported;
    phydev->advertising = phydev->supported;

    priv->phydev = phydev;
    phy_config(phydev);

    return 1;
}

该函数调用phy_connect函数连接网卡,返回的值如果合理就调用phy_config函数对该网卡进行配置,主要是配置网卡的速率和半双工。

首先分析phy_connect函数:

struct phy_device *phy_connect(struct mii_dev *bus, int addr,
        struct eth_device *dev, phy_interface_t interface)
{
    struct phy_device *phydev;
    phydev = phy_find_by_mask(bus, addr, interface);
    if (phydev)
        phy_connect_dev(phydev, dev);
    else
        printf("Could not get PHY for %s: addr %d\n", bus->name, addr);
    return phydev;
}

该函数首先调用phy_find_by_mask函数查询网卡设备,如果存在则调用phy_connect_dev函数连接,否则就打印出错信息

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函数实现:

static struct phy_device *get_phy_device_by_mask(struct mii_dev *bus,
        unsigned phy_mask, phy_interface_t interface)
{
    int i;
    struct phy_device *phydev;

    phydev = search_for_existing_phy(bus, phy_mask, interface);
    if (phydev)
        return phydev;
    /* Try Standard (ie Clause 22) access */
    /* Otherwise we have to try Clause 45 */
    for (i = 0; i < 5; i++) {
        phydev = create_phy_by_mask(bus, phy_mask,
                i ? i : MDIO_DEVAD_NONE, interface);
        if (IS_ERR(phydev))
            return NULL;
        if (phydev)
            return phydev;
    }
    printf("Phy %d not found\n", ffs(phy_mask) - 1);
    return phy_device_create(bus, ffs(phy_mask) - 1, 0xffffffff, interface);
}

该函数首先调用search_for_existing_phy函数查找当前存在的设备,如果存在则将该设备返回,不存在则调用create_phy_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;
}

create_phy_by_mask函数实现:

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函数实现:

int __weak get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id)
{
    int phy_reg;

    /* Grab the bits from PHYIR1, and put them
     * in the upper half */
    phy_reg = bus->read(bus, addr, devad, MII_PHYSID1);

    if (phy_reg < 0)
        return -EIO;

    *phy_id = (phy_reg & 0xffff) << 16;

    /* Grab the bits from PHYIR2, and put them in the lower half */
    phy_reg = bus-&gt;read(bus, addr, devad, MII_PHYSID2);

    if (phy_reg < 0)
        return -EIO;

    *phy_id |= (phy_reg & 0xffff);

    return 0;
}

该函数就调用了bus->read总线读函数,来读取网卡寄存器的值,这里是读取寄存器存储的网卡ID,bus->read函数定义为cpsw_mdio_readcpsw_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_init函数实现:

static int cpsw_init(struct eth_device *dev, bd_t *bis)
{
    struct cpsw_priv    *priv = dev-&gt;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-&gt;dma_regs + CPDMA_SOFTRESET);

    if (priv-&gt;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的初始化:
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通道的发送和接收寄存器的硬件地址映射到内存空间,这样就可通过访问和修改内存地址内容来修改相应硬件配置:

/* 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);

接着,初始化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实现如下:

int genphy_update_link(struct phy_device *phydev)
{
    unsigned int mii_reg;

    /*
     * Wait if the link is up, and autonegotiation is in progress
     * (ie - we're capable and it's not done)
     */
    mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

    /*
     * If we already saw the link up, and it hasn't gone down, then
     * we don't need to wait for autoneg again
     */
    if (phydev->link && mii_reg & BMSR_LSTATUS)
        return 0;

    if ((mii_reg & BMSR_ANEGCAPABLE) && !(mii_reg & BMSR_ANEGCOMPLETE)) {
        int i = 0;

        printf("%s Waiting for PHY auto negotiation to complete",
            phydev->dev->name);
        while (!(mii_reg & BMSR_ANEGCOMPLETE)) {
            /*
             * Timeout reached ?
             */
            if (i > PHY_ANEG_TIMEOUT) {
                printf(" TIMEOUT !\n");
                phydev->link = 0;
                return 0;
            }

            if (ctrlc()) {
                puts("user interrupt!\n");
                phydev->link = 0;
                return -EINTR;
            }

            if ((i++ % 500) == 0)
                printf(".");

            udelay(1000);   /* 1 ms */
            mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
        }
        printf(" done\n");
        phydev->link = 1;
    } else {
        /* Read the link a second time to clear the latched state */
        mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

        if (mii_reg & BMSR_LSTATUS)
            phydev->link = 1;
        else
            phydev->link = 0;
    }

    return 0;
}

该函数首先调用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描述符
首先初始化描述符

/* 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];

然后初始化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地址解析协议,然后根据收到的arp的包得知主机的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发送给网卡,网卡再将数据发送出去。
以上就是本人实现网卡驱动的大致过程,最后的描述可能有些不详细,时间太晚了,有问题可以留言交流~。
转载请说明出处。
https://blog.csdn.net/hahachenchen789/article/details/53339181

  • 10
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
韦东山老师为啥要录升级版嵌入式视频?200x年左右,嵌入式Linux在全世界、在中国刚刚兴起。我记得我2005年进入中兴时,全部门的人正在努力学习Linux。在2008年,我写了一本书《嵌入式Linux应用开发完全手册》。它的大概内容是:裸机、U-boot、Linux内核、Linux设备驱动。那时还没有这样讲解整个系统的书,芯片厂家Linux开发包也还不完善,从bootloader到内核,再到设备驱动都不完善。有全系统开发能力的人也很少。于是这书也就恰逢其时,变成了畅销书。我也根据这个思路录制了视频:裸机、U-boot、Linux内核、Linux设备驱动。收获些许名声,带领很多人进入Linux世界。11年过去了,嵌入式Linux世界发生了翻天覆地的变化① 基本系统能用芯片厂家都会提供完整的U-boot、Linux内核、芯片上硬件资源的驱动。方案厂家会做一些定制,比如加上某个WIFI模块,会添加这个WIFI模块的驱动。你可以使用厂家的原始方案,或是使用/借鉴方案商的方案,做出一个“能用”的产品。② 基础驱动弱化;高级驱动专业化基础的驱动,比如GPIO、UART、SPI、I2C、LCD、MMC等,有了太多的书籍、视频、示例代码,修修改改总是可以用的。很多所谓的驱动工程师,实际上就是“调参工程师”。我们群里有名的火哥,提出了一个概念:这些驱动就起一个“hardware enable”的作用。高级的驱动,比如USB、PCIE、HDMI、MIPI、GPU、WIFI、蓝牙、摄像头、声卡。体系非常复杂,很少有人能讲清楚,很多时候只是一笔带过。配置一下应用层工具就了事,能用就成。这些高级驱动,工作中需要专门的人来负责,非常专业。他们是某一块的专家,比如摄像头专家、音频专家。③ 项目为王你到一个公司,目的是把产品做出来,会涉及APP到内核到驱动全流程。中小公司玩不起华为中兴的配置,需要的是全面手。大公司里,只负责很小很小一块的镙丝钉,位置也不太稳固啊。所以,如果你不是立志成为某方面的专家,那就做一个全栈工程师吧。④ 调试很重要都说代码是3分写7分调,各种调试调优技术,可以为你的升职加薪加一把火。基于上述4点,我录制的全新视频将有这些特点:1. 快速入门,2. 实战项目,3. 驱动大全,4. 专题,5. 授人以渔,6. 要做任务另外,我们会使用多款芯片同时录制,先讲通用的原理,再单独讲各个板子的操作。这些芯片涵盖主流芯片公司的主流芯片,让你学习工作无缝对接。1.快速入门入门讲究的是快速,入门之后再慢慢深入,特别是对于急着找工作的学生,对于业余时间挑灯夜读的工作了的人,一定要快!再从裸机、U-boot、内核、驱动这样的路线学习就不适合了,时间就拉得太长了。搞不好学了后面忘了前面。并且实际工作中并不需要你去弄懂U-boot,会用就行:U-boot比驱动还复杂。讲哪些内容?怎么讲呢?混着讲比如先讲LED APP,知道APP怎么调用驱动,再讲LED硬件原理和裸机,最后讲驱动的编写。这样可以快速掌握嵌入式Linux的整套开发流程,不必像以前那样光学习裸机就花上1、2个月。而里面的裸机课程,也会让你在掌握硬件操作的同时,把单片机也学会了。讲基础技能中断、休眠-唤醒、异步通知、阻塞、内存映射等等机制,会配合驱动和APP来讲解。这些技能是嵌入式Linux开发的基础。而这些驱动,只会涉及LED、按制、LCD等几个驱动。掌握了这些输入、输出的驱动和对应的APP后,你已经具备基本的开发能力了。讲配置我们从厂家、从方案公司基本上都可以拿到一套完整的开发环境,怎么去配置它?需要懂shell和python等配置脚本。效果效率优先以前我都是现场写代码、现场写文档,字写得慢,降低了学习效率。这次,效果与效率统一考虑,不再追求所有东西都现场写。容易的地方可先写好代码文档,难的地方现场写。2.实战项目会讲解这样的涉及linux网关/服务器相关项目(不限于,请多提建议):                   定位为:快速掌握项目开发经验,丰满简历。涉及的每一部分都会讲,比如如果涉及蓝牙,在这里只会讲怎么使用,让你能写出程序;如果要深入,可以看后面的蓝牙专题。3. 驱动大全包括基础驱动、高级驱动。这些驱动都是独立成章,深入讲解。虽然基础驱动弱化了,但是作为Linux系统开发人员,这是必备技能,并且从驱动去理解内核是一个好方法。在讲解这些驱动时,会把驱动的运行环境,比如内核调度,进程线程等概念也讲出来,这样就可以搭建一个知识体系。没有这些知识体系的话,对驱动的理解就太肤浅了,等于在Linux框架下写裸机,一叶障目,不见泰山。定位为:工具、字典,用到再学习。4. 专题想深入学习的任何内容,都可独立为专题。比如U-boot专题、内核内存管理专题、systemtap调试专题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值