问题背景:
手头上的任务是将eboot中添加ping外部网的功能,所以就直接从uboot上把ping命令那部分拿过来调试,am335x内部自带mac,ksz9031是phy,目前主板可以设置ksz9031,也就是说mdio是没问题的,然后用的是通用的cpsw网卡驱动,用的cpdma传输,目前死活ping不通,10/100/1000速度下都试过,自动协商也没问题,感觉卡在cpdma那边,没有输出传出去的感觉,想不用dma直接输出数据,不知道如何下手。经过几个星期的折腾终于解决了,问题出在大小端函数和实虚地址上面。
分析:
AM335x硬件资源上可以支持两个双网口,网口是GMII,RGMII,RMII接口的10/100/1000以太网端口;
首先来看下它的网络子系统结构图
图1 AM335x网络子系统结构图
网络子系统的核心就是这个CPSW_3G的模块。它是个是么结构呢,我的理解他就是的多路选择器,这个模块有3个接口,分别为PORT0,PORT1,PORT2。其中PORT0接口的接口类型是CPPI型,它连接内部DMA,构成网络特有的DMA通道叫做CPDMA。还有两个接口PORT1和PORT2是对外的,他们的接口类型可以选择为GMII,RGMII,RMII这三种中的任意一个,这个有相应的寄存器可以配置的。简单的描述下就是下面这个结构
图2 AM335x网络子系统简化图
再接着看图1,在uboot中并没有用到中断,采用的是轮询的方式,所以中断int部分暂时不用。系统时钟PRCM的配置,千兆网模式下的时钟是125MHZ,百兆网口需要25MHZ,十兆需要2.5MHZ。Control Moudle中的GMII_SEL,就是图中左下方那块的作用是选择接口模式,也就是将PORT1和PORT2选择为GMII,RGMII,RMII其中一种类型。再往下SCR就是各种配置寄存器了,这个对着TI手册查看即可,再往下有一个特别重要的点,MDIO模块。这里我们要阐述下网卡的概念,网卡是由两部分构成的,一部分是mac,一部分是phy,mac提供mac地址和链路接口,phy用于物理信号的生成接收。一般的像常用的DM9000网卡中这两部分都是集成的。也有些芯片像AM335x,它内部自带mac部分(上面分析的部分),这个时候外部就只需要接phy芯片就行,接口方式就是上面说的GMII,RGMII,RMII。我用的phy芯片是KSZ9031,用的接口方式是RGMII。 phy芯片是需要配置的,而这个配置的专用线就是MDIO,它和IIC比较类似,一根CLK,一根DATA。但它据说比IIC快,具体为什么,没有分析,可能他是专用吧,IIC比较通用。因此它们的连接简图如图3所示:
图3 AM335x网络连接简图
ok,进入正题之前先对网络系统进行初始化:
初始化过程1,对于phy芯片的初始化Uboot中在board/ti/am335x/board.c或者evm.c中会对phy进行初始化最终通用的phy.c(通用的都是phy,也有些特定的,按情况而定)中的一些初始化配置。
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);
初始化过程2,对于am335x内部网络模块的初始化。这部分内容在ti的通用网络驱动文件cpsw.c中,Cpsw.c是一个通用的网络驱动,所有的配置,收发的最终实现都在这里面对于AM335X内部网络模块的初始化分为两部分,一部分是mac模块的配置(对照寄存器表一一配置)。另外一部分是对于DMA配置。
初始化过程有个博客讲解的比较好,我直接复制过来了(也添加了一点自己的分析),我有点懒:我会接着他后面详解这个发送流程。
原博客地址是
https://blog.csdn.net/hahachenchen789/article/details/53339181
转载开始:(一直到下面转载结束)
static int cpsw_init(struct eth_device *dev, bd_t *bis)
{
struct cpsw_priv *priv = dev->priv;
struct cpsw_slave *slave;
int i, ret;
/* 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);
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, NetBcastAddr, 1 << priv->host_port);
for_each_slave(slave, priv)
cpsw_slave_init(slave, priv);
cpsw_update_link(priv);
/* init descriptor pool */ 提交DMA描述符,实际上就是提供描述符链表,先把这个链表构造在这,每有一次数据传输就往里面填值。
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];
/* 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, NetRxPackets[i],
PKTSIZE);
if (ret < 0) {
printf("error %d submitting rx desc\n", ret);
break;
}
}
return 0;
}
首先是声明几个结构体变量,其中包括cpsw的主:cpsw_priv和cpsw_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->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_read,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_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的初始化:
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协议的格式:
图4
然后分析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
UBOOT中对于网络的收发机制是基于描述符DMA的,它的每一次传输或者接收都有相应的一个描述符,这个描述符里包含的信息如下图5是发送描述符,图6是接收描述符。一个描述符包含了一次传输的所有信息,看图中可以看出报货,下一个描述符的地址,数据buffer的地址,数据的长度,SOP,EOP,Owner,EOQ等等传输状态信息,这些状态信息,我们人为 在传输前的设置初值,然后传输过程中,这些值会由芯片改变,我们去读取这些值,可以判断有没有传输完成,有没有收完。
Start of Packet(SOP) Flag
若置位,表示描述符指针指向一个新报文的缓冲区。如是单个帧报文,sop和结束帧eop标志置位。否则,描述符指针指向上一个报文缓冲区,此报文EOP标志置位。此位通过软件应用置位,不允许EMAC修改。
End of Packet(EOP)Flag
若置位,表示描述符指针指向当下报文的最后。若是一个单个的帧报文,sop和eop同时置位。否则,描述符指针指向设置eop标志的报文的包缓冲区最后。软件应该置位此位,EMAC不允许修改。
Ownership
若置位,表示当下包(从sop到eop)的所有描述符被EMAC拥有。此标志在增加描述符到发送队列之前由软件在sop包描述符上设定。对于单个分片包,sop/eop/OWNER标志同时置位。此标志被EMAC清除,一旦当下包的所有描述符完成之后。注:此标志仅当SOP描述符上有效。
EOQ
若置位,表示有问题的描述符是当下发送通道的发送队列中的上一个描述符,发送器停止。软件应用在增加描述符到发送队列前清楚此标志。当EMAC识别出一个描述符是当下报文的最后一个描述符时,置位此标志。(eop标志置位)没有其它描述符在发送队列。(下一个描述符指针为NULL)
图5
图6
这些描述符存放在固定的内存中,从参考手册中可以看到这块区域叫做CPPI_RAM,地址是在0x4A102000,这是ti专门为网络模块dma所设置的一块区域,从上面的收发描述符可以看出它是一个链表(每一个描述符占据48个字节,上图中只显示了32个字节,实际上在程序中定义的描述符结构体如下所示)。
struct cpdma_desc {
/* hardware fields */
u32 hw_next;
u32 hw_buffer;
u32 hw_len;
u32 hw_mode;
/* software fields */
u32 sw_buffer;
u32 sw_len;
};
以ping命令进行情景分析:
分析第二步的ping_send(),ping_send调用cpsw_send
static int cpdma_process(struct cpsw_priv *priv, struct cpdma_chan *chan,
void **buffer, int *len)
{
struct cpdma_desc *desc = chan->head;
u32 status;
return -ENOENT;
//发送过程只会执行到这,因为它预先没有提交描述符信息,需要直接返回去调用cpdma_submit,从
//CPSW_init中可以看出了注册的时候只注册了接受描述符,发送描述符,是在每一次发送前注册的,原因也很
//简单,你发送的时候总归是知道你要发送了,但是接收你不知道什么时候接受,你得准备好位子去接收。
}
static int cpdma_submit(struct cpsw_priv *priv, struct cpdma_chan *chan,
void *buffer, int len)
{
struct cpdma_desc *desc, *prev;
u32 mode;
u32 status;
desc = cpdma_desc_alloc(priv); //分配一个描述符
if (!desc)
return -ENOMEM;
if (len < PKT_MIN)
len = PKT_MIN;
status = desc_read(desc, hw_mode);
mode = CPDMA_DESC_OWNER | CPDMA_DESC_SOP | CPDMA_DESC_EOP;//填充描述符
desc_write(desc, hw_next, 0);//填充描述符
desc_write(desc, hw_buffer, buffer);//填充描述符
desc_write(desc, hw_len, len);//填充描述符
desc_write(desc, hw_mode, mode | len);//填充描述符
desc_write(desc, sw_buffer, buffer);//填充描述符
desc_write(desc, sw_len, len);//填充描述符
if (!chan->head) { //第一个包的话会构建队列
/* simple case - first packet enqueued */
chan->head = desc;
chan->tail = desc;
chan_write(chan, hdp, desc); //当把描述符地址填入描述符地址指针寄存器的时候这个发送就
//开始了,重点就在这,这个地址在eboot中是虚拟地址,但是一档我们向这个寄存器写入这个地址,后面的传
//输都是自动完成的,而这是个DMA模块,这边的操作必须是实际物理地址,所以必须在这边换成物理地址,但
//是只能改写入寄存器的这部分地址为物理地址,不能影响程序的运行,这里面有些相互关联的地方,要仔细
//的一一改错去。
goto done;
}
非第一个包的队列操作
/* not the first packet - enqueue at the tail */
prev = chan->tail;
desc_write(prev, hw_next, desc);
chan->tail = desc;
/* next check if EOQ has been triggered already */
if (desc_read(prev, hw_mode) & CPDMA_DESC_EOQ)
chan_write(chan, hdp, desc); //当把描述符地址填入描述符地址指针寄存器的时候这个发送就开始了
done:
if (chan->rxfree)
chan_write(chan, rxfree, 1);
return 0;
}
AM335X内部机制,当一切初始化好的时候,将描述符地址填入描述符地址指针寄存器,这个时候发送就开始了。
然后我们来看接收部分:
eth_rx();函数接收数据包最终调用的是cpsw_recv
static int cpsw_recv(struct eth_device *dev)
{
while (cpdma_process(priv, &priv->rx_chan, &buffer, &len) >= 0) {//读取状态,有收到包则把数据包读出来
NetReceive(buffer, len);//处理数据包//如果收到的是arp回复包则在里面再送icmp(ping请求)
cpdma_submit(priv, &priv->rx_chan, buffer, PKTSIZE);//构造新的等待接收的接收描述符信息
}
}
static int cpdma_process(struct cpsw_priv *priv, struct cpdma_chan *chan,
void **buffer, int *len)
{
struct cpdma_desc *desc = chan->head;
u32 status;
return -ENOENT;
//接收描述符是初始化的时候就填好的,描述符中的状态是硬件自动改变的,根据这些状态可以知道接收成功没有,发送成功没有
status = desc_read(desc, hw_mode);
//读取描述符中的状态
if (len)
*len = status & 0x7ff;
//读取包长度
if (buffer)
*buffer = desc_read_ptr(desc, sw_buffer);
//读取数据buffer
if (status & CPDMA_DESC_OWNER) { //如果描述符得这一位还是CPDMA_DESC_OWNER说明没有收到数据,该位没有被改变
if (chan_read(chan, hdp) == 0) {
if (desc_read(desc, hw_mode) & CPDMA_DESC_OWNER)
chan_write(chan, hdp, desc);
}
return -EBUSY;
}
chan->head = desc_read_ptr(desc, hw_next);//运行到这说明接收完成,就把下一个描述符放到队列头部
chan_write(chan, cp, desc); //写入传输完成指针寄存器,表示传输完成
cpdma_desc_free(priv, desc);
return 0;
}
总结一下分析网络问题的心路历程:利用wireshark捕捉不到arp包,进行如下分析
图7 IEEE定义的phy芯片的寄存器
总而言之,脑子中的思路是:外部能不能收到包->有没有发出去->Phy配置对不对->Cpsw配置对不对->DMA配置对不对->实虚地址对不对->时钟时序对不对。
以下附上我的实虚地址改了哪些地方:我的实际地址 0x4A102000对应的虚拟地址是0xB0502000,这得根据你自己的改。修改以下三个函数就可以了。
static void cpdma_desc_free(struct cpsw_priv *priv, struct cpdma_desc *desc)
{
unsigned int phsic_desc = 0;
unsigned int viture = 0;
struct cpdma_desc *viture_desc = NULL;
unsigned int *pp = &desc;
if((*pp >= 0x4A102000)&&(*pp <= 0x4A103FFF))
{
phsic_desc = *pp;
viture = *pp + 0x66400000;
viture_desc = (struct cpdma_desc *)viture;
}
if((*pp >= 0xB0502000)&&(*pp <= 0xB0503FFF))
{
phsic_desc = *pp - 0x66400000;
viture = *pp;
viture_desc = (struct cpdma_desc *)viture;
}
if (desc) {
desc_write(viture_desc, hw_next, priv->desc_free);
priv->desc_free = phsic_desc;
}
}
static int cpdma_submit(struct cpsw_priv *priv, struct cpdma_chan *chan,
void *buffer, int len)
{
struct cpdma_desc *desc, *prev;
u32 mode;
int i=0;
unsigned char *buf = (unsigned char *)buffer;
desc = cpdma_desc_alloc(priv);
unsigned int phsic_desc = 0;
unsigned int viture = 0;
struct cpdma_desc *viture_desc = NULL;
unsigned int *pp = &desc;
if((*pp >= 0x4A102000)&&(*pp <= 0x4A103FFF))
{
phsic_desc = *pp;
viture = *pp + 0x66400000;
viture_desc = (struct cpdma_desc *)viture;
}
if((*pp >= 0xB0502000)&&(*pp <= 0xB0503FFF))
{
phsic_desc = *pp - 0x66400000;
viture = *pp;
viture_desc = (struct cpdma_desc *)viture;
}
printf("### cpdma_submit \n");
if (!desc)
return -ENOMEM;
if (len < PKT_MIN)
len = PKT_MIN;
for(i=0; ihw_mode, desc->hw_buffer, desc->hw_len, desc->hw_next, desc->sw_buffer, desc->sw_len);
if (!chan->head) {
/* simple case - first packet enqueued */
chan->head = viture_desc;
chan->tail = viture_desc;
chan_write(chan, hdp, phsic_desc);
goto done;
}
/* not the first packet - enqueue at the tail */
prev = chan->tail;
desc_write(prev, hw_next, phsic_desc);
chan->tail = viture_desc;
/* next check if EOQ has been triggered already */
if (desc_read(prev, hw_mode) & CPDMA_DESC_EOQ)
chan_write(chan, hdp, phsic_desc);
printf("### chanhdp %x \n", &(chan->hdp));
done:
if (chan->rxfree)
chan_write(chan, rxfree, 1);
printf("### cpdma_submit \n");
return 0;
}
static int cpdma_process(struct cpsw_priv *priv, struct cpdma_chan *chan,
void **buffer, int *len)
{
struct cpdma_desc *desc = chan->head;
u32 status;
unsigned int phsic_desc = 0;
unsigned int viture = 0;
struct cpdma_desc *viture_desc = NULL;
unsigned int *pp = &desc;
if((*pp >= 0x4A102000)&&(*pp <= 0x4A103FFF))
{
phsic_desc = *pp;
viture = *pp + 0x66400000;
viture_desc = (struct cpdma_desc *)viture;
}
if((*pp >= 0xB0502000)&&(*pp <= 0xB0503FFF))
{
phsic_desc = *pp - 0x66400000;
viture = *pp;
viture_desc = (struct cpdma_desc *)viture;
}
printf("### desc %x\n", desc);
if (!desc)
return -ENOENT;
status = desc_read(viture_desc, hw_mode);
printf("### status %x\n", status);
if (len)
*len = status & 0x7ff;
if (buffer)
*buffer = desc_read_ptr(viture_desc, sw_buffer);
if (status & CPDMA_DESC_OWNER) {
printf("### CPDMA_DESC_OWNER \n");
if (chan_read(chan, hdp) == 0) {
if (desc_read(viture_desc, hw_mode) & CPDMA_DESC_OWNER)
chan_write(chan, hdp, phsic_desc);
}
return -EBUSY;
}
printf("### desc2 %x\n", desc);
printf("### status2 %x\n", status);
chan->head = desc_read_ptr(viture_desc, hw_next);
chan_write(chan, cp, phsic_desc);
cpdma_desc_free(priv, viture_desc);
return 0;
}
转载请注明出处
https://blog.csdn.net/qq_20753873/article/details/89139365