This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[FAQ] CPSW linux Phy回环自检实现

我已在ti-linux-kernel (branch=ti-rt-linux-5.4.y ; commit=3be7c8fa970782c88c2a188f7310ccc7256121f7) drivers/net/ethernet/ti/cpsw.c 驱动器的 Git 存储库中实施了 Phy 环回模式自检过程。它目前在我的 am5718-idk 板上工作,具有两个 cpsw 接口(gmac 配置为双 gmac 模式)。

目前我有以下限制:

  • 如果我们同时在两个接口上运行测试,测试可能会失败(添加锁应该可以解决问题)
  • 该测试在单 emac 模式下无法运行。我没有对此进行调查,我很确定只需再加上一点东西,即可使其在此模式下正常运行。

如果您有兴趣,我可以为您提供补丁以供审核,并且(当然,如果它足够好)在内核中集成。

  • 下面是补丁:

    From 7526fa471a712cfd783edcd0bcc97a4ec62a4f41 Mon Sep 17 00:00:00 2001
    From: Kamel Hacene <kamel.hacene@safrangroup.com>
    Date: Mon, 7 Jun 2021 08:13:47 +0000
    Subject: [PATCH] net: ethernet: ti: Add phy loopback ethtool self-test for
     cpsw driver
    
    ---
     drivers/net/ethernet/ti/cpsw.c          |   3 +-
     drivers/net/ethernet/ti/cpsw_ethtool.c  | 339 ++++++++++++++++++++++++++++++++
     drivers/net/ethernet/ti/cpsw_priv.h     |   9 +
     drivers/net/ethernet/ti/davinci_cpdma.c |  10 +
     drivers/net/ethernet/ti/davinci_cpdma.h |   2 +
     5 files changed, 362 insertions(+), 1 deletion(-)
    
    diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c
    index 5146685eddd8..8544bb193b71 100644
    --- a/drivers/net/ethernet/ti/cpsw.c
    +++ b/drivers/net/ethernet/ti/cpsw.c
    @@ -1058,7 +1058,7 @@ static void cpsw_fifo_shp_on(struct cpsw_priv *priv, int fifo, int on)
     	writel_relaxed(val, &cpsw->regs->ptype);
     }
     
    -static void _cpsw_adjust_link(struct cpsw_slave *slave,
    +void _cpsw_adjust_link(struct cpsw_slave *slave,
     			      struct cpsw_priv *priv, bool *link)
     {
     	struct phy_device	*phy = slave->phy;
    @@ -2497,6 +2497,7 @@ static const struct ethtool_ops cpsw_ethtool_ops = {
     	.nway_reset	= cpsw_nway_reset,
     	.get_ringparam = cpsw_get_ringparam,
     	.set_ringparam = cpsw_set_ringparam,
    +	.self_test = cpsw_self_test,
     };
     
     static int cpsw_probe_dt(struct cpsw_platform_data *data,
    diff --git a/drivers/net/ethernet/ti/cpsw_ethtool.c b/drivers/net/ethernet/ti/cpsw_ethtool.c
    index 31248a6cc642..843d3f1ad321 100644
    --- a/drivers/net/ethernet/ti/cpsw_ethtool.c
    +++ b/drivers/net/ethernet/ti/cpsw_ethtool.c
    @@ -16,11 +16,23 @@
     #include <linux/skbuff.h>
     
     #include "cpsw.h"
    +#include "cpsw_sl.h"
     #include "cpts.h"
     #include "cpsw_ale.h"
     #include "cpsw_priv.h"
     #include "davinci_cpdma.h"
     
    +#define CPSW_XMETA_OFFSET	ALIGN(sizeof(struct xdp_frame), sizeof(long))
    +struct __aligned(sizeof(long)) cpsw_meta_xdp {
    +	struct net_device *ndev;
    +	int ch;
    +};
    +#define CPSW_HEADROOM_NA (max(XDP_PACKET_HEADROOM, NET_SKB_PAD) + NET_IP_ALIGN)
    +#define CPSW_HEADROOM  ALIGN(CPSW_HEADROOM_NA, sizeof(long))
    +
    +#define TST_FRAME_SIZE 512
    +#define TST_PATTERN    0xAA
    +
     struct cpsw_hw_stats {
     	u32	rxgoodframes;
     	u32	rxbroadcastframes;
    @@ -217,6 +229,14 @@ int cpsw_set_coalesce(struct net_device *ndev, struct ethtool_coalesce *coal)
     	return 0;
     }
     
    +enum cpsw_diagnostics_result {
    +	TEST_LOOP
    +};
    +static const char cpsw_gstrings_test[][ETH_GSTRING_LEN] = {
    +	[TEST_LOOP] = "Loopback test (offline)"
    +};
    +#define CPSW_TEST_LEN (sizeof(cpsw_gstrings_test) / ETH_GSTRING_LEN)
    +
     int cpsw_get_sset_count(struct net_device *ndev, int sset)
     {
     	struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
    @@ -226,6 +246,8 @@ int cpsw_get_sset_count(struct net_device *ndev, int sset)
     		return (CPSW_STATS_COMMON_LEN +
     		       (cpsw->rx_ch_num + cpsw->tx_ch_num) *
     		       CPSW_STATS_CH_LEN);
    +	case ETH_SS_TEST:
    +		return CPSW_TEST_LEN;
     	default:
     		return -EOPNOTSUPP;
     	}
    @@ -744,3 +766,320 @@ int cpsw_get_ts_info(struct net_device *ndev, struct ethtool_ts_info *info)
     	return 0;
     }
     #endif
    +
    +static void cpsw_loopback_rx_handler(void *token, int len, int status)
    +{
    +	struct page *page = token;
    +	void *pa = page_address(page);
    +	struct cpsw_meta_xdp	*xmeta = pa + CPSW_XMETA_OFFSET;
    +	struct cpsw_common	*cpsw = ndev_to_cpsw(xmeta->ndev);
    +	struct net_device	*ndev = xmeta->ndev;
    +	int			port = 0;
    +	unsigned int success = 1;
    +
    +	struct cpsw_priv *priv;
    +	struct cpsw_test *test;
    +	int i;
    +	unsigned char data;
    +	unsigned char* pointer = pa;
    +	int length;
    +
    +	/* Dual emac mode ? */
    +	if (cpsw->data.dual_emac && status >= 0) {
    +		/* Refresh ndev if port is not 0 */
    +		port = CPDMA_RX_SOURCE_PORT(status);
    +		if (port)
    +			ndev = cpsw->slaves[--port].ndev;
    +
    +		/* Ignore hw headroom and vlan fields */
    +		pointer += CPSW_HEADROOM;
    +		pointer += CPSW_RX_VLAN_ENCAP_HDR_SIZE;
    +		length = len - (CPSW_HEADROOM + CPSW_RX_VLAN_ENCAP_HDR_SIZE);
    +	}
    +	else
    +	{
    +		/* Single emac -> Ignore hw headroom only */
    +		pointer += CPSW_HEADROOM;
    +		length = len - CPSW_HEADROOM;
    +	}
    +
    +	/* Retrieve slave private structures */
    +	priv = netdev_priv(ndev);
    +	test = &(priv->test);
    +
    +	netdev_info(ndev, "Loopback test rx handler call (length:%d, pattern:%#x)\n", length, TST_PATTERN);
    +
    +	/* (Dual emac) Verify RX port == TX port */
    +	if(success == 1)
    +	{
    +		if (cpsw->data.dual_emac && status >= 0) {
    +			if (port != test->port)
    +			{
    +				netdev_err(ndev, "Loopback test: Invalid RX port (%d). Expected same as TX port (%d).\n", port, test->port);
    +				success = 0;
    +			}
    +		}
    +	}
    +
    +	/* Verify frame size is correct */
    +	if(success == 1)
    +	{
    +		if(length != (TST_FRAME_SIZE - CPSW_HEADROOM))
    +		{
    +			netdev_err(ndev, "Loopback test: Invalid length %d (expected: %d)\n", length, TST_FRAME_SIZE - CPSW_HEADROOM);
    +			success = 0;
    +		}
    +	}
    +
    +	/* Verify frame data is correct */
    +	if(success == 1)
    +	{
    +		for(i = 0; i < length; i++)
    +		{
    +			data = *((unsigned char*)(pointer) + i);
    +			if(data != TST_PATTERN)
    +			{
    +				netdev_err(ndev, "Loopback test : bad data(%d) = %#x. Expected pattern %#x\n", i, data, TST_PATTERN);
    +				success = 0;
    +			}
    +		}
    +	}
    +
    +	/* Set global result */
    +	test->success = success;
    +	netdev_info(ndev, "Loopback test rx handler call result: %d\n", success);
    +
    +	/* Call RX handler to drop the frame to network stack */
    +	(*test->handler)((void *)token, len, status);
    +}
    +
    +static int cpsw_loopback(struct net_device *ndev)
    +{
    +	struct cpsw_priv *priv = netdev_priv(ndev);
    +	struct cpsw_common *cpsw = priv->cpsw;
    +	struct cpsw_test *test = &(priv->test);
    +	struct sk_buff *skb = NULL;
    +	unsigned int frame_size;
    +	struct cpdma_chan *txch;
    +	struct cpdma_chan *rxch;
    +	int ret;
    +	int retry;
    +	int success;
    +
    +	/* Allocate test skb */
    +	skb = netdev_alloc_skb(ndev, TST_FRAME_SIZE);
    +	if (!skb)
    +	{
    +		netdev_err(ndev, "Could not allocate socket buffer\n");
    +		return -1;
    +	}
    +
    +	/* Fill */
    +	skb_put(skb, TST_FRAME_SIZE);
    +	frame_size = skb->len;
    +	memset(skb->data, TST_PATTERN, frame_size);
    +
    +	/* Use txqueue 0 */
    +	txch = cpsw->txv[0].ch;
    +	rxch = cpsw->rxv[0].ch;
    +
    +	/* Backup and et RX handler */
    +	test->handler = cpdma_chan_get_handler(rxch);
    +	cpdma_chan_set_handler(rxch, cpsw_loopback_rx_handler);
    +
    +	/* Reset TX channel */
    +	ret = cpdma_chan_stop(txch);
    +	if (unlikely(ret != 0)) {
    +		netdev_err(ndev, "Could not stop tx channel 0\n");
    +	}
    +	ret = cpdma_chan_start(txch);
    +	if (unlikely(ret != 0)) {
    +		netdev_err(ndev, "Could not start tx channel 0\n");
    +	}
    +
    +	skb_tx_timestamp(skb);
    +
    +	/* Set TX port so we can test it in RX handler */
    +	test->port = priv->emac_port;
    +
    +	/* Default succes to false */
    +	success = 0;
    +
    +	ret = cpdma_chan_submit(txch, skb, skb->data, skb->len,
    +				priv->emac_port + cpsw->data.dual_emac);
    +	if (unlikely(ret != 0)) {
    +		netdev_err(ndev, "Could not send packet. Err: %x\n", ret);
    +	}
    +	else
    +	{
    +		for(retry = 0; retry < 5; retry++)
    +		{
    +			/* Wait for packet to go from Tx to Rx */
    +			msleep(200);
    +
    +			/* Try to read a frame, ret return the number of rode frames. In case
    +			 * the value is not 0, it means our RX handler has been called and we
    +			 * can retrieve the test result.
    +			 */
    +			ret = cpdma_chan_process(rxch, cpsw->rxv[0].budget);
    +			if(ret != 0)
    +			{
    +				/* Retrieve result */
    +				success = test->success;
    +				break;
    +			}
    +
    +			/* Dbg */
    +			netdev_info(ndev, "Retry:%d ; TXstat:%ld ; RXstat:%ld \n",
    +					retry, ndev->stats.tx_bytes, ndev->stats.rx_bytes);
    +		}
    +	}
    +
    +	/* Restore RX handler */
    +	cpdma_chan_set_handler(rxch, test->handler);
    +
    +	return success;
    +}
    +
    +static int cpsw_loopback_test(struct cpsw_slave *slave)
    +{
    +	struct phy_device *phy = slave->phy;
    +	struct net_device *ndev = slave->ndev;
    +	struct cpsw_priv  *priv = netdev_priv(ndev);
    +	struct cpsw_common *cpsw = priv->cpsw;
    +	bool link;
    +	int ctrl1000;
    +	int bypass;
    +	int slave_port;
    +	int i;
    +	int success = 0;
    +	int bmcr;
    +
    +	netdev_info(ndev, "Starting loopback test\n");
    +	slave_port = cpsw_get_slave_port(slave->slave_num);
    +
    +	/* Disable CPSW interrupts */
    +	cpsw_intr_disable(cpsw);
    +
    +	/* Backup phy BMCR configuration */
    +	bmcr = phy_read(phy, MII_BMCR);
    +
    +	/* Set loopback, 1Gbit, full duplex, disable autoneg */
    +	phy_modify(phy, MII_BMCR,
    +			BMCR_LOOPBACK | BMCR_ANENABLE | BMCR_FULLDPLX | BMCR_SPEED1000 | BMCR_SPEED100,
    +			BMCR_LOOPBACK | 0             | BMCR_FULLDPLX | BMCR_SPEED1000 | 0);
    +
    +	/* Enable CPSW_SL 1Gbit/Full */
    +	slave->mac_control = CPSW_SL_CTL_GMII_EN | CPSW_SL_CTL_GIG | CPSW_SL_CTL_FULLDUPLEX;
    +	cpsw_sl_ctl_set(slave->mac_sl, slave->mac_control);
    +
    +	/* Enable ALE forwarding */
    +	cpsw_ale_control_set(cpsw->ale, slave_port,
    +				   ALE_PORT_STATE,
    +				   priv->port_state[slave_port]);
    +
    +	/* Enable master-slave manual configuration (as slave) */
    +	ctrl1000 = phy_read(phy, MII_CTRL1000);
    +	phy_modify(phy, MII_CTRL1000,
    +			CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER,
    +			CTL1000_ENABLE_MASTER | 0);
    +
    +	/* Wait for phy to be up */
    +	for(i = 0; (i < 10) && !(phy_read(phy, MII_BMSR) & BMSR_LSTATUS); i++)
    +	{
    +		msleep(100);
    +		netdev_info(ndev, "Waiting for phy to be up (BMSR=%#x) (check bit: %d)\n",
    +				phy_read(phy, MII_BMSR), BMSR_LSTATUS);
    +	}
    +
    +	/* Enable ALE bypass mode */
    +	bypass = cpsw_ale_control_get(cpsw->ale, 0, ALE_BYPASS);
    +	if(!bypass)
    +	{
    +		cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, 1);
    +	}
    +
    +	/* Loopback test */
    +	success = cpsw_loopback(ndev);
    +
    +	/* Restore ALE bypass */
    +	if(!bypass)
    +	{
    +		cpsw_ale_control_set(cpsw->ale, 0, ALE_BYPASS, 0);
    +	}
    +
    +	/* Restore phy BMCR */
    +	phy_write(phy, MII_BMCR, bmcr);
    +
    +	/* Restore phy CTRL1000 */
    +	phy_write(phy, MII_CTRL1000, ctrl1000);
    +
    +	/* Set back speed/duplex and ALE enable */
    +	_cpsw_adjust_link(slave, priv, &link);
    +
    +	/* Enable interrupts */
    +	cpsw_intr_enable(cpsw);
    +
    +	return success;
    +}
    +
    +void cpsw_self_test(struct net_device *ndev,
    +    struct ethtool_test *etest, u64 *data)
    +{
    +	struct cpsw_priv *priv = netdev_priv(ndev);
    +	struct cpsw_common *cpsw = priv->cpsw;
    +	int slave_no = cpsw_slave_index(cpsw, priv);
    +	bool if_running = netif_running(ndev);
    +	int other_slave = slave_no == 1 ? 0 : 1;
    +
    +	/* Check requested tests */
    +	if(etest->flags != ETH_TEST_FL_OFFLINE)
    +	{
    +		netdev_err(ndev, "Only offline tests are supported\n");
    +		etest->flags |= ETH_TEST_FL_FAILED;
    +		return;
    +	}
    +
    +	/* Interface needs to be up */
    +	if(!if_running)
    +	{
    +		dev_open(ndev, NULL);
    +	}
    +
    +	/* No carrier */
    +	netif_carrier_off(ndev);
    +
    +	/* Stop other slave */
    +	if (cpsw->data.dual_emac)
    +	{
    +		if(netif_running(cpsw->slaves[other_slave].ndev))
    +		{
    +			dev_close(cpsw->slaves[other_slave].ndev);
    +		}
    +	}
    +
    +	/* Phy loopback test */
    +	if (cpsw->slaves[slave_no].phy)
    +	{
    +		data[TEST_LOOP] = cpsw_loopback_test(&cpsw->slaves[slave_no]);
    +		if(0 == data[TEST_LOOP])
    +		{
    +			etest->flags |= ETH_TEST_FL_FAILED;
    +		}
    +	}
    +	else
    +	{
    +		/* Need a phy for loopback test */
    +		netdev_err(ndev, "This test requires a phy\n");
    +		etest->flags |= ETH_TEST_FL_FAILED;
    +		return;
    +	}
    +
    +	/* Restore carrier */
    +	netif_carrier_on(ndev);
    +	if (cpsw->data.dual_emac)
    +	{
    +		dev_open(cpsw->slaves[other_slave].ndev, NULL);
    +	}
    +}
    +
    diff --git a/drivers/net/ethernet/ti/cpsw_priv.h b/drivers/net/ethernet/ti/cpsw_priv.h
    index 665bcaff61e2..d3e7d01134a0 100644
    --- a/drivers/net/ethernet/ti/cpsw_priv.h
    +++ b/drivers/net/ethernet/ti/cpsw_priv.h
    @@ -350,6 +350,12 @@ struct cpsw_common {
     	struct page_pool		*page_pool[CPSW_MAX_QUEUES];
     };
     
    +struct cpsw_test {
    +	u32 success;
    +	u32 port;
    +	cpdma_handler_fn handler;
    +};
    +
     struct cpsw_priv {
     	struct net_device		*ndev;
     	struct device			*dev;
    @@ -365,6 +371,7 @@ struct cpsw_priv {
     	struct bpf_prog			*xdp_prog;
     	struct xdp_rxq_info		xdp_rxq[CPSW_MAX_QUEUES];
     	struct xdp_attachment_info	xdpi;
    +	struct cpsw_test test;
     
     	u32 emac_port;
     	struct cpsw_common *cpsw;
    @@ -434,5 +441,7 @@ int cpsw_set_channels_common(struct net_device *ndev,
     			     struct ethtool_channels *chs,
     			     cpdma_handler_fn rx_handler);
     int cpsw_get_ts_info(struct net_device *ndev, struct ethtool_ts_info *info);
    +void cpsw_self_test(struct net_device *ndev, struct ethtool_test *eth_test, u64 *data);
    +void _cpsw_adjust_link(struct cpsw_slave *slave, struct cpsw_priv *priv, bool *link);
     
     #endif /* DRIVERS_NET_ETHERNET_TI_CPSW_PRIV_H_ */
    diff --git a/drivers/net/ethernet/ti/davinci_cpdma.c b/drivers/net/ethernet/ti/davinci_cpdma.c
    index 6614fa3089b2..0653dfe421c8 100644
    --- a/drivers/net/ethernet/ti/davinci_cpdma.c
    +++ b/drivers/net/ethernet/ti/davinci_cpdma.c
    @@ -873,6 +873,16 @@ u32 cpdma_chan_get_rate(struct cpdma_chan *ch)
     	return rate;
     }
     
    +cpdma_handler_fn cpdma_chan_get_handler(struct cpdma_chan *ch)
    +{
    +	return ch->handler;
    +}
    +
    +void cpdma_chan_set_handler(struct cpdma_chan *ch, cpdma_handler_fn handler)
    +{
    +	ch->handler = handler;
    +}
    +
     struct cpdma_chan *cpdma_chan_create(struct cpdma_ctlr *ctlr, int chan_num,
     				     cpdma_handler_fn handler, int rx_type)
     {
    diff --git a/drivers/net/ethernet/ti/davinci_cpdma.h b/drivers/net/ethernet/ti/davinci_cpdma.h
    index d3cfe234d16a..b5dd2e68e024 100644
    --- a/drivers/net/ethernet/ti/davinci_cpdma.h
    +++ b/drivers/net/ethernet/ti/davinci_cpdma.h
    @@ -97,6 +97,8 @@ int cpdma_chan_set_weight(struct cpdma_chan *ch, int weight);
     int cpdma_chan_set_rate(struct cpdma_chan *ch, u32 rate);
     u32 cpdma_chan_get_rate(struct cpdma_chan *ch);
     u32 cpdma_chan_get_min_rate(struct cpdma_ctlr *ctlr);
    +cpdma_handler_fn cpdma_chan_get_handler(struct cpdma_chan *ch);
    +void cpdma_chan_set_handler(struct cpdma_chan *ch, cpdma_handler_fn handler);
     
     enum cpdma_control {
     	CPDMA_TX_RLIM,			/* read-write */
    -- 
    2.11.0

    稍微解释一下:

    文件 cpsw.c:

    - 将回调 cpsw_self_test 添加到 ethtool_ops,以便我们可以通过用户空间中的“ethtool -t <iface>”调用它

    - 将 _cpsw_adjust_link 设置为非静态,因此我们可以在 cpsw_ethtool.c 中调用它

    文件 davinci_cpdma.c:

    - 为处理程序添加 getter/setter。在自动测试期间,我将用包装程序替换 rx 处理程序,以检查接收到的帧是否与发出的帧一致

    文件 cpsw_ethtool.c:

    - 实现 cpsw_self_test 函数,以执行以下操作:

     * 将测试的从接口设置为向上,停止另一个接口(双 EMAC)以防止来自另一个从机的帧干扰测试(在我的测试期间从未发生过,因为我无法 100% 确定这不会发生)。

     * 禁用中断

     * 在 phy BMCR 寄存器中设置 phy 环回模式、1Gbit、全双工(不带自动协商)

     * 启用 cpsw_sl 1Gbit/全

     * 启用 ALE 转发

     * 在 phy MII_CTRL1000 寄存器中启用主/从

     * 启用 ALE 旁路

     * 用图案填充缓冲器 (0xAA)

     * 替换 RX 处理器

     * 复位 TX 通道

     * 发送帧

    // 在这里,RX 处理程序应该接收帧。它会先检查帧的大小和数据,然后在从器件的私有结构中设置结果

     * 等待系统执行 RX 处理程序,然后检索结果。

     * 恢复 RX 处理程序、ALE、phy 寄存器...

     * 调用 _cpsw_adjust_link 以根据之前的值恢复 phy 配置和 cpsw 旁路模式。

     * 恢复载波