rockchip rk3566 android11 网口log报错: DMA engine initialization failed

问题描述:上电初始化前将网口插入,然后上电初始化网口能够正常使用,且能够找到PHY,ifconfig 查看能够有eth0 产生,网口正常使用且能够热插拔,但上电初始化时,不插入网口,就会报DMA engine initialization failed 错误,DMA 初始化的时候出错了。

分析:一般产生这个问题可以认为是GMAC 的工作时钟出问题了。先测量时钟引脚是否有时钟,时钟频率以及幅度等指标是否正常,主要确认以下几个方面:

1.IOMUX 出错,检查时钟脚寄存器值是否正确。

2.时钟方向以及配置与硬件不匹配。

3.检查 clock tree 和 CRU 寄存器,确认时钟频率大小和时钟是否有使能。

DMA:每一个网卡上都有一块FIFO存储器,对于NIC(Network Interface Controller),FIFO存储器是用来通过系统总线传送数据到系统存储器之前,缓存从LAN上接收到的数据。对与快速以太网还有一个直接内存存取(DMA:Directly Memory Access)控制器,用于提供对系统存储器的可靠访问。驱动为网卡分配一个环形缓冲区,在一段连续的物理内存中实现。网卡上存在一定大小的FIFO存储器,DMA缓冲区是由系统/驱动程序分配的一段连续的物理内存。

总结:网卡有一个循环缓冲区(通常叫做 DMA 环形缓冲区)建立在与处理器共享的内存中。每一个输入数据包被放置在环形缓冲区中下一个可用缓冲区,并且发出中断。然后驱动程序将网络数据包传给内核的其它部分处理,并在环形缓冲区中放置一个新的 DMA 缓冲区。DMA就是代码上的一个缓冲器buffer。DMA就是Direct Memory Access,意思是I/O设备直接存储器访问,几乎不消耗CPU的资源。在I/O设备和主存传递数据的时候,CPU可以处理其他事。

代码分析:通过查找,找到报错位置

文件路径:kernel/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c


/**
 * stmmac_hw_setup - setup mac in a usable state.
 *  @dev : pointer to the device structure.
 *  Description:
 *  this is the main function to setup the HW in a usable state because the
 *  dma engine is reset, the core registers are configured (e.g. AXI,
 *  Checksum features, timers). The DMA is ready to start receiving and
 *  transmitting.
 *  Return value:
 *  0 on success and an appropriate (-)ve integer as defined in errno.h
 *  file on failure.
 */
static int stmmac_hw_setup(struct net_device *dev, bool init_ptp)
{
	struct stmmac_priv *priv = netdev_priv(dev);
	u32 rx_cnt = priv->plat->rx_queues_to_use;
	u32 tx_cnt = priv->plat->tx_queues_to_use;
	u32 chan;
	int ret;

	/* DMA initialization and SW reset */
	ret = stmmac_init_dma_engine(priv); //该函数返回值为-16 DMA 软件初始化失败。
	if (ret < 0) {
		netdev_err(priv->dev, "%s: DMA engine initialization failed\n",
			   __func__);
		return ret;
	}

	/* Copy the MAC addr into the HW  */
	stmmac_set_umac_addr(priv, priv->hw, dev->dev_addr, 0);
    .........
    .........


继续追代码。。。。。。

 stmmac_init_dma_engine - DMA init.
 * @priv: driver private structure
 * Description:
 * It inits the DMA invoking the specific MAC/GMAC callback.
 * Some DMA parameters can be passed from the platform;
 * in case of these are not passed a default is kept for the MAC or GMAC.
 */
static int stmmac_init_dma_engine(struct stmmac_priv *priv)
{
	u32 rx_channels_count = priv->plat->rx_queues_to_use;
	u32 tx_channels_count = priv->plat->tx_queues_to_use;
	u32 dma_csr_ch = max(rx_channels_count, tx_channels_count);
	struct stmmac_rx_queue *rx_q;
	struct stmmac_tx_queue *tx_q;
	u32 chan = 0;
	int atds = 0;
	int ret = 0;

	if (!priv->plat->dma_cfg || !priv->plat->dma_cfg->pbl) {
		dev_err(priv->device, "Invalid DMA configuration\n");
		return -EINVAL;
	}

	if (priv->extend_desc && (priv->mode == STMMAC_RING_MODE))
		atds = 1;

	ret = stmmac_reset(priv, priv->ioaddr);//该函数返回-16
	if (ret) {
		dev_err(priv->device, "Failed to reset the dma\n");
		return ret;
	}
    后面初始化DMA 相关的操作都没有执行了
	/* DMA Configuration */
	stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg, atds);

stmmac_reset 该函数返回错误的原因主要是由于PHY初始化失败造成的。

好像没有办法继续跟踪代码,我们先来看一下相关的配置。

&gmac1 {
	phy-mode = "rgmii"; //使用的接口,一般千兆网使用的是该接口,我的项目是一个千兆网口
	clock_in_out = "input"; //这里是使用的input,意思是使用PHY所产生的时钟供給Gmac.而不是使用的RK的内部时钟。
	snps,reset-gpio = <&gpio0 RK_PC2 GPIO_ACTIVE_LOW>; //复位引脚
	snps,reset-active-low;//低电平有效
	/* Reset time is 20ms, 100ms for rtl8211f */
	snps,reset-delays-us = <0 20000 100000>;//复位时序
	assigned-clocks = <&cru SCLK_GMAC1_RX_TX>, <&cru SCLK_GMAC1>, <&cru CLK_MAC1_OUT>;
	assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>, <&gmac1_clkin>;
	assigned-clock-rates = <0>, <125000000>, <25000000>;
	pinctrl-names = "default";
	pinctrl-0 = <&gmac1m1_miim
		     &gmac1m1_tx_bus2
		     &gmac1m1_rx_bus2
		     &gmac1m1_rgmii_clk
		     &gmac1m1_rgmii_bus
		     /*&eth1m1_pins*/
		     &gmac1m1_clkinout>;
	tx_delay = <0x28>;
	rx_delay = <0x11>;
	phy-handle = <&rgmii_phy1>;
	status = "okay";
};
&mdio1 {
	status = "okay";
	rgmii_phy1: phy@4 {
		compatible = "ethernet-phy-ieee802.3-c22";
		reg = <0x4>;
		clocks = <&cru CLK_MAC1_OUT>;
	};
};

配置解读:RGMII 上分别有 TX_CLK 和 RX_CLK 两个时钟,这两个时钟分别由 MAC 和 PHY 产生,这两个时钟频率的大小和网速的大小相关,千兆网速的时候,时钟频率为 125MHz,百兆为 25MHz, 十兆为 2.5MHz。TX_CLK 可以由 RK 内部的 PLL 分频产生,也可以由外部的时钟输入经过分频后产生。目前我们使用的是由外部输入的时钟,这样的时钟相对于内部 PLL 产生的时钟更加独立,不受 RK 内部分频策略的影响,因此更加稳定。而对于 PHY 来说,本身就需要一个25M 的晶振作为时钟源,因此 RX_CLK 正是由这个时钟源倍频或分频得到的。绝大多数 PHY 还有这样的一个输出管脚,可以输出一个时钟给 MAC,也就是上面描述的相对于 MAC 来说的外部时钟,这个时钟大小为 125MHz,作为 MAC 端 TX_CLK 的时钟源。时钟方向正是指的是用内部时钟 output 或是外部时钟 input(这个见代码中的注释)。

分析:进一步的来分析问题,由于我们上电初始化的时候插入网线可以初始化成功,且可以正常使用且能够进行热插拔,没有报错,网口的速率也能够达到千兆网的要求,所以我们的配置,以及驱动代码是没有问题的。当我们没有查入网线进行上电时,网口会初始化失败,报上面的错误,而我们上面分析到了DMA是什么,这里不在讲,所以可以确定的是时钟出现了问题,而我经过查阅资料,PHY内部与网口之间,会有一个自动协商的机制,就是当我们插入网线与不插入网线的硬件状态是不同的,目前我怀疑的是供給GMAC的时钟在插入网线的后,由于这种机制导致了其时候达到了125M,达到了GMAC的初始化要求,而不插入网线时其,PHY默认供给GMAC的时钟只有25M,其导致不能够初始化成功。使用 100M PHY 时,其频率是 50M,使用 1000M PHY 时,其频率是 125M。

接下来我们来继续分析PHY的数据手册,我这里用的是AR8035。

我又叫硬件工程师量了一下时钟速率:

这是不接网线时上电初始化的的RX_CLK的时钟速率。只有2.5M很明显是不符合要求的。

 这是初始化上电时插入网线时的一个时钟速率为125M。(该状态下网口可以初始化成功,并正常使用)

由于可以看到PHY的一个输出时钟是可以控制的,默认的它是一个25M的一个时钟供给GMAC。而GMAC需要的是一个125M的时钟,所以会导致GMAC初始化失败,也就验证了我们之前的分析想法。

        原因找到了,接下来就是实践操作的时候了。show time ~~~~~~  我们现在做的就是需要在不t插入网线的时候,让PHY输出给GMAC为125M,(或许大于25M就行,我这里没有去试)

 // To enable AR8035 ouput a 125MHz clk from CLK_25M 
       phy_write(phydev, 0xd, 0x7);
       phy_write(phydev, 0xe, 0x8016);
       phy_write(phydev, 0xd, 0x4007);
       val = phy_read(phydev, 0xe);
       val &= 0xffe3;
       val |= 0x18;
       phy_write(phydev, 0xe, val);

添加:

有如下报错:

[   26.033159] rk_gmac-dwmac fe010000.ethernet: Failed to reset the dma
[   26.033174] rk_gmac-dwmac fe010000.ethernet eth0: stmmac_hw_setup: DMA engine initialization failed
[   26.033197] rk_gmac-dwmac fe010000.ethernet eth0: stmmac_open: Hw setup failed

分析:Failed to reset the dma

目前是 DMA HW reset 失败了。!!

而其DMA init 初始化成功了。先看一下代码:

   /*ret = stmmac_reset(priv, priv->ioaddr);
	if (ret) {
		dev_err(priv->device, "Failed to reset the dma\n");
		return ret;
	}

    经过好一阵的查找,发现该函数其实是通过结构体函数指针赋值的,其赋值的函数如下:
const struct stmmac_dma_ops dwmac4_dma_ops = {
	.reset = dwmac4_dma_reset,
	.init = dwmac4_dma_init,
	.init_chan = dwmac4_dma_init_channel,
	.init_rx_chan = dwmac4_dma_init_rx_chan,
	.init_tx_chan = dwmac4_dma_init_tx_chan,
    ...................


int dwmac4_dma_reset(void __iomem *ioaddr)
{
	u32 value = readl(ioaddr + DMA_BUS_MODE);
	int limit;

	/* DMA SW reset */
	value |= DMA_BUS_MODE_SFT_RESET;
	writel(value, ioaddr + DMA_BUS_MODE);
	limit = 10;
	while (limit--) {
		if (!(readl(ioaddr + DMA_BUS_MODE) & DMA_BUS_MODE_SFT_RESET))
			break;
		mdelay(10);
	}

	if (limit < 0)
		return -EBUSY; //DMA 复位失败,最终返回-16 表示设备忙!!

	return 0;
}

   分析:它这段代码其实就是写读,ioaddr 相关的值,我目前也不知道它相关寄存器的手册,不知道它相关寄存器位表示什么,由于我的项目是DMA 复位失败了 ,但是DMA 是初始化成功了的,

stmmac_dma_init(priv, priv->ioaddr, priv->plat->dma_cfg, atds); //该函数执行成功。

具体DMA 复位的操作是什么作用。

        我这里的处理方法是直接将DMA rest 注释掉了,不让它DMA复位。

/*Removing DMA reset does not affect the use of functions */
	/*ret = stmmac_reset(priv, priv->ioaddr);
	if (ret) {
		dev_err(priv->device, "Failed to reset the dma\n");
		return ret;
	}*/

然后再测试网口功能,上电插入初始化等,都可以正常使用,以及热插拔,以及网速的相关测试都正常。目前我能想到的办法是这个,由于无法查看相关寄存器的值代表什么意思,所以无法追查,如您有更好的方法,欢迎留言分享。

总结:遇见问题不要慌,不要做无头的苍蝇,首先根据现象来推算我们的哪里错了,哪里没有问题,然后去推测问题的所在,再去仔细的查看,通过硬件状态来分析。

  • 18
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值