TI中文支持网
TI专业的中文技术问题搜集分享网站

[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 模式下无法运行。我没有对此进行调查,我很确定只需再加上一点东西,即可使其在此模式下正常运行。

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

Annie Liu:

下面是补丁:

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 forcpsw 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_HEADROOMALIGN(CPSW_HEADROOM_NA, sizeof(long))
+
+#define TST_FRAME_SIZE 512
+#define TST_PATTERN0xAA
+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;
+	intport = 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_infoxdp_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 旁路模式。

 * 恢复载波

赞(0)
未经允许不得转载:TI中文支持网 » [FAQ] CPSW linux Phy回环自检实现
分享到: 更多 (0)