ixgbe, mdio, and marvell ethernet switches
Adrian Chadd <[email protected]>
| Newsgroups | gmane.os.freebsd.devel.net |
|---|---|
| Message-ID | <CAJ-VmonZS--HHpHS4jZ_HFevgKGca=18byQLXcKn0Z5gDeU2Mg@mail.gmail.com> |
A few weeks ago I started poking at some netgate hardware to use as freebsd wifi test boxes in my little test lab here. The two I'm using are ADI (now Silicom) boxes that needed some fun deep dives into BIOS versions, interrupt storms and compatible mini-PCIe cards (some just don't actually get probed!) Anyway. I ended up stumbling across one of the Silicom OEM boxes that got turned into an AT&T open compute platform box back in 2018/2019 that has an integrated Marvell 88E6190X switch hanging off of the intel X553 backplane (the ixgbe driver.) However, it wasn't supported in FreeBSD, and the instructions to get the switch up in linux were hit and miss. People had gotten the etherswitch driver patched to work, but only against pfsense plus, which has the MDIO support for ixgbe. But, I couldn't find any diffs online to implement it. So I did what I normally do, I got distracted from wifi stuff and decided to get it going. Having a working amd64 etherswitch platform would be nice for general etherswitch tinkering outside of the arm/mips/ppc world, and then no-one would have any excuses getting involved in improving it! It took a few days, but I figured it out. Here's the fun deep dive. Firstly, DPDK introduced some support for the MDIO bus being exposed on the X553, but with a couple caveats: * it started as clause 22 (which is PHYs pre-2000) diff , but it eventually got turned into a clause 45 (PHYs post-2000) diff, and they .. just deleted the clause 22 code * the MDIO support is really focused on talking MDIO to the PHYs internally inside the X550 series hardware - clause 45, fixed PHY number, etc So it wasn't enough to really bring up the marvell switch, as these all seem to want clause 22 MDIO commands. Then i went digging into the X550 and X553 datasheets. * the X550 datasheet says that it only supports clause 45 * the X553 datasheet says it supports both clause 22 and clause 45, but makes a point of (a) telling you if you need to, you should speak into intel customer engineering, and (b) deleted the MDIO registers from their register programming guide Ok, so the X553 does do Clause 22, but .. how. Then I went through the DPDK and linux driver history and lo and behold, linux actually HAS the correct mdio bus support! specifically, they have a /separate/ mdio bus driver hiding in their X550/X553 support, which: * handles both clause 22 and clause 45 IO * does the correct semaphore handshaking, since MDIO is shared between multiple ethernet MACs, hardware and firmware * exposes only one MDIO bus on the X553, as it only has a single MDIO bus shared between the 1 to 4 MACs the chipset can expose Armed with that I committed some driver sins and got it working. The in progress absolutely disgusting hack diff is attached, so it won't get lost / forgotten. Anyway, on the etherswitch side I did a bunch of cleanup to support etherswitch/miiproxy/e6000sw as modules, and fix a bunch of e6000sw bugs, and they're in -HEAD: * https://reviews.freebsd.org/D50024 to https://reviews.freebsd.org/D50031 are all cleanups * https://reviews.freebsd.org/D50044 adds the 88E6190X support But then the next fun problem - none of the ports came up. Well, I lie. Only one of them came up. I went digging into the PHY registers and found that the marvell gige PHY code didn't actually power up the PHY if it was powered down, so I fixed that too: * https://reviews.freebsd.org/D50045 Then with some hints, I could see and use all of the switch ports on the AT&T ATT-150 CPE device: hint.mdio.0.at="ix0" hint.e6000sw.0.addr="0x10" hint.e6000sw.0.port0disabled="1" hint.e6000sw.0.is6190x=1 hint.e6000sw.0.port9cpu=1 hint.e6000sw.0.port10cpu=1 hint.e6000sw.0.port9speed=2500 hint.e6000sw.0.port10speed=2500 Then, I also acquired a wireguard M270 box, as it also apprently has a marvell switch attached to an X553 backplane, and none of the ethernet ports work at all out of the box. The above hints worked on that too - with addr set to "0x0" instead of "0x10". So, now i have two Atom C3558 boxes w/ X553 2.5gbit backplane ports hooked into an 8 port marvell gige switch working on -HEAD (save the ixgbe mdio diff that I attached; I'll sort that out soon). All it took was like $250 in hardware, staring at intel documentation and linux/dpdk driver code, and three evenings thinking to myself "surely this can't be THAT hard.
20250427-ixgbe-mdio-in-progress-1.diff
(application/octet-stream, 12.2 KB)
diff --git a/sys/dev/ixgbe/if_ix.c b/sys/dev/ixgbe/if_ix.c
index 959afa79e7da..8894cbe1ff56 100644
--- a/sys/dev/ixgbe/if_ix.c
+++ b/sys/dev/ixgbe/if_ix.c
@@ -36,10 +36,12 @@
#include "opt_rss.h"
#include "ixgbe.h"
+#include "mdio_if.h"
#include "ixgbe_sriov.h"
#include "ifdi_if.h"
#include <net/netmap.h>
+#include <dev/mdio/mdio.h>
#include <dev/netmap/netmap_kern.h>
/************************************************************************
@@ -254,6 +256,139 @@ static void ixgbe_handle_msf(void *);
static void ixgbe_handle_mod(void *);
static void ixgbe_handle_phy(void *);
+static s32
+ixgbe_read_phy_reg_mdi_22(struct ixgbe_hw *hw, u16 phy, u16 reg, u16 *phy_data)
+{
+ u32 i, data, command;
+
+ /* Setup and write the read command */
+ command = (reg << IXGBE_MSCA_DEV_TYPE_SHIFT) |
+ (phy << IXGBE_MSCA_PHY_ADDR_SHIFT) |
+ IXGBE_MSCA_OLD_PROTOCOL | IXGBE_MSCA_READ_AUTOINC |
+ IXGBE_MSCA_MDI_COMMAND;
+
+ IXGBE_WRITE_REG(hw, IXGBE_MSCA, command);
+
+ /* Check every 10 usec to see if the access completed.
+ * The MDI Command bit will clear when the operation is
+ * complete
+ */
+ for (i = 0; i < IXGBE_MDIO_COMMAND_TIMEOUT; i++) {
+ usec_delay(10);
+
+ command = IXGBE_READ_REG(hw, IXGBE_MSCA);
+ if (!(command & IXGBE_MSCA_MDI_COMMAND))
+ break;
+ }
+
+ if (command & IXGBE_MSCA_MDI_COMMAND) {
+ ERROR_REPORT1(IXGBE_ERROR_POLLING,
+ "PHY read command did not complete.\n");
+ return IXGBE_ERR_PHY;
+ }
+
+ /* Read operation is complete. Get the data from MSRWD */
+ data = IXGBE_READ_REG(hw, IXGBE_MSRWD);
+ data >>= IXGBE_MSRWD_READ_DATA_SHIFT;
+ *phy_data = (u16)data;
+
+ return IXGBE_SUCCESS;
+}
+
+
+static s32
+ixgbe_write_phy_reg_mdi_22(struct ixgbe_hw *hw, u16 phy, u16 reg, u16 phy_data)
+{
+ u32 i, command;
+
+ /* Put the data in the MDI single read and write data register*/
+ IXGBE_WRITE_REG(hw, IXGBE_MSRWD, (u32)phy_data);
+
+ /* Setup and write the write command */
+ command = (reg << IXGBE_MSCA_DEV_TYPE_SHIFT) |
+ (phy << IXGBE_MSCA_PHY_ADDR_SHIFT) |
+ IXGBE_MSCA_OLD_PROTOCOL | IXGBE_MSCA_WRITE |
+ IXGBE_MSCA_MDI_COMMAND;
+
+ IXGBE_WRITE_REG(hw, IXGBE_MSCA, command);
+
+ /* Check every 10 usec to see if the access completed.
+ * The MDI Command bit will clear when the operation is
+ * complete
+ */
+ for (i = 0; i < IXGBE_MDIO_COMMAND_TIMEOUT; i++) {
+ usec_delay(10);
+
+ command = IXGBE_READ_REG(hw, IXGBE_MSCA);
+ if (!(command & IXGBE_MSCA_MDI_COMMAND))
+ break;
+ }
+
+ if (command & IXGBE_MSCA_MDI_COMMAND) {
+ ERROR_REPORT1(IXGBE_ERROR_POLLING,
+ "PHY write cmd didn't complete\n");
+ return IXGBE_ERR_PHY;
+ }
+
+ return IXGBE_SUCCESS;
+}
+
+static int
+ixgbe_mdio_readreg(device_t dev, int phy, int reg)
+{
+ if_ctx_t ctx = device_get_softc(dev);
+ struct ixgbe_softc *sc = iflib_get_softc(ctx);
+ struct ixgbe_hw *hw = &sc->hw;
+ uint16_t val = 0;
+ int32_t ret = 0;
+
+#if 0
+ /* The interface takes a register, clause 45 devtype and data */
+ ret = hw->phy.ops.read_reg_mdi(hw, reg, phy, &val);
+#else
+ u32 gssr = hw->phy.phy_semaphore_mask | IXGBE_GSSR_PHY0_SM | IXGBE_GSSR_TOKEN_SM;
+
+ if (hw->mac.ops.acquire_swfw_sync(hw, gssr)) {
+ device_printf(dev, "%s: failed to acquire lock\n", __func__);
+ return (-1);
+ }
+ ret = ixgbe_read_phy_reg_mdi_22(hw, phy, reg, &val);
+ if (ret != IXGBE_SUCCESS) {
+ device_printf(dev, "%s: read_mdi_22 failed (%d)\n", __func__, ret);
+ }
+ hw->mac.ops.release_swfw_sync(hw, gssr);
+ (void) ret; // XXX return a suitable error if this is non-zero
+#endif
+ return (val);
+}
+
+static int
+ixgbe_mdio_writereg(device_t dev, int phy, int reg, int data)
+{
+ if_ctx_t ctx = device_get_softc(dev);
+ struct ixgbe_softc *sc = iflib_get_softc(ctx);
+ struct ixgbe_hw *hw = &sc->hw;
+ int32_t ret;
+
+#if 0
+ ret = hw->phy.ops.write_reg_mdi(hw, reg, phy, data);
+#else
+ u32 gssr = hw->phy.phy_semaphore_mask | IXGBE_GSSR_PHY0_SM | IXGBE_GSSR_TOKEN_SM;
+
+ if (hw->mac.ops.acquire_swfw_sync(hw, gssr)) {
+ device_printf(dev, "%s: failed to acquire lock\n", __func__);
+ return (-1);
+ }
+ ret = ixgbe_write_phy_reg_mdi_22(hw, phy, reg, data);
+ if (ret != IXGBE_SUCCESS) {
+ device_printf(dev, "%s: write_mdi_22 failed (%d)\n", __func__, ret);
+ }
+ hw->mac.ops.release_swfw_sync(hw, gssr);
+ (void) ret; // XXX return a suitable error if this is non-zero
+#endif
+ return (0);
+}
+
/************************************************************************
* FreeBSD Device Interface Entry Points
************************************************************************/
@@ -271,6 +406,13 @@ static device_method_t ix_methods[] = {
DEVMETHOD(pci_iov_uninit, iflib_device_iov_uninit),
DEVMETHOD(pci_iov_add_vf, iflib_device_iov_add_vf),
#endif /* PCI_IOV */
+
+ DEVMETHOD(bus_add_child, device_add_child_ordered),
+#if 1
+ DEVMETHOD(mdio_readreg, ixgbe_mdio_readreg),
+ DEVMETHOD(mdio_writereg, ixgbe_mdio_writereg),
+#endif
+
DEVMETHOD_END
};
@@ -279,10 +421,12 @@ static driver_t ix_driver = {
};
DRIVER_MODULE(ix, pci, ix_driver, 0, 0);
+DRIVER_MODULE(mdio, ix, mdio_driver, 0, 0);
IFLIB_PNP_INFO(pci, ix_driver, ixgbe_vendor_info_array);
MODULE_DEPEND(ix, pci, 1, 1, 1);
MODULE_DEPEND(ix, ether, 1, 1, 1);
MODULE_DEPEND(ix, iflib, 1, 1, 1);
+MODULE_DEPEND(ix, mdio, 1, 1, 1);
static device_method_t ixgbe_if_methods[] = {
DEVMETHOD(ifdi_attach_pre, ixgbe_if_attach_pre),
diff --git a/sys/dev/ixgbe/ixgbe_phy.c b/sys/dev/ixgbe/ixgbe_phy.c
index 2a735ead9a12..2bfb286c91c4 100644
--- a/sys/dev/ixgbe/ixgbe_phy.c
+++ b/sys/dev/ixgbe/ixgbe_phy.c
@@ -599,6 +599,8 @@ s32 ixgbe_read_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr, u32 device_type,
{
u32 i, data, command;
+ printf("%s: called; reg_addr=%d, type=%d, phy.addr=%d\n", __func__, reg_addr, device_type, hw->phy.addr);
+
/* Setup and write the address cycle command */
command = ((reg_addr << IXGBE_MSCA_NP_ADDR_SHIFT) |
(device_type << IXGBE_MSCA_DEV_TYPE_SHIFT) |
@@ -636,6 +638,7 @@ s32 ixgbe_read_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr, u32 device_type,
(hw->phy.addr << IXGBE_MSCA_PHY_ADDR_SHIFT) |
(IXGBE_MSCA_READ | IXGBE_MSCA_MDI_COMMAND));
+ printf("%s: writing 0x%08x -> MSCA\n", __func__, command);
IXGBE_WRITE_REG(hw, IXGBE_MSCA, command);
/*
@@ -652,6 +655,7 @@ s32 ixgbe_read_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr, u32 device_type,
}
if ((command & IXGBE_MSCA_MDI_COMMAND) != 0) {
+ printf("%s: timeout!\n", __func__);
ERROR_REPORT1(IXGBE_ERROR_POLLING, "PHY read command didn't complete\n");
DEBUGOUT("PHY read command didn't complete, returning IXGBE_ERR_PHY\n");
return IXGBE_ERR_PHY;
@@ -662,6 +666,7 @@ s32 ixgbe_read_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr, u32 device_type,
* from MSRWD
*/
data = IXGBE_READ_REG(hw, IXGBE_MSRWD);
+ printf("%s: read 0x%08x <- MSRWD\n", __func__, data);
data >>= IXGBE_MSRWD_READ_DATA_SHIFT;
*phy_data = (u16)(data);
@@ -707,6 +712,8 @@ s32 ixgbe_write_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr,
{
u32 i, command;
+ printf("%s: called; reg_addr=%d, type=%d, phy.addr=%d\n", __func__, reg_addr, device_type, hw->phy.addr);
+
/* Put the data in the MDI single read and write data register*/
IXGBE_WRITE_REG(hw, IXGBE_MSRWD, (u32)phy_data);
@@ -716,6 +723,7 @@ s32 ixgbe_write_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr,
(hw->phy.addr << IXGBE_MSCA_PHY_ADDR_SHIFT) |
(IXGBE_MSCA_ADDR_CYCLE | IXGBE_MSCA_MDI_COMMAND));
+ printf("%s: (1) writing 0x%08x -> MSCA\n", __func__, command);
IXGBE_WRITE_REG(hw, IXGBE_MSCA, command);
/*
@@ -732,6 +740,7 @@ s32 ixgbe_write_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr,
}
if ((command & IXGBE_MSCA_MDI_COMMAND) != 0) {
+ printf("%s: timeout (1)!\n", __func__);
ERROR_REPORT1(IXGBE_ERROR_POLLING, "PHY address cmd didn't complete\n");
return IXGBE_ERR_PHY;
}
@@ -745,6 +754,7 @@ s32 ixgbe_write_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr,
(hw->phy.addr << IXGBE_MSCA_PHY_ADDR_SHIFT) |
(IXGBE_MSCA_WRITE | IXGBE_MSCA_MDI_COMMAND));
+ printf("%s: (2) writing 0x%08x -> MSCA\n", __func__, command);
IXGBE_WRITE_REG(hw, IXGBE_MSCA, command);
/*
@@ -761,6 +771,7 @@ s32 ixgbe_write_phy_reg_mdi(struct ixgbe_hw *hw, u32 reg_addr,
}
if ((command & IXGBE_MSCA_MDI_COMMAND) != 0) {
+ printf("%s: timeout (2)!\n", __func__);
ERROR_REPORT1(IXGBE_ERROR_POLLING, "PHY write cmd didn't complete\n");
return IXGBE_ERR_PHY;
}
diff --git a/sys/dev/ixgbe/ixgbe_x550.c b/sys/dev/ixgbe/ixgbe_x550.c
index 7f07190f832c..5947d89fe11e 100644
--- a/sys/dev/ixgbe/ixgbe_x550.c
+++ b/sys/dev/ixgbe/ixgbe_x550.c
@@ -520,15 +520,38 @@ s32 ixgbe_shutdown_fw_phy(struct ixgbe_hw *hw)
static s32 ixgbe_read_phy_reg_x550em(struct ixgbe_hw *hw, u32 reg_addr,
u32 device_type, u16 *phy_data)
{
- UNREFERENCED_4PARAMETER(*hw, reg_addr, device_type, *phy_data);
- return IXGBE_NOT_IMPLEMENTED;
+ s32 status;
+ u32 mask = hw->phy.phy_semaphore_mask | IXGBE_GSSR_TOKEN_SM;
+
+ DEBUGFUNC("ixgbe_read_phy_reg_x550em");
+
+ if (hw->mac.ops.acquire_swfw_sync(hw, mask))
+ return IXGBE_ERR_SWFW_SYNC;
+
+ status = hw->phy.ops.read_reg_mdi(hw, reg_addr, device_type, phy_data);
+
+ hw->mac.ops.release_swfw_sync(hw, mask);
+
+ return status;
}
static s32 ixgbe_write_phy_reg_x550em(struct ixgbe_hw *hw, u32 reg_addr,
u32 device_type, u16 phy_data)
{
- UNREFERENCED_4PARAMETER(*hw, reg_addr, device_type, phy_data);
- return IXGBE_NOT_IMPLEMENTED;
+ s32 status;
+ u32 mask = hw->phy.phy_semaphore_mask | IXGBE_GSSR_TOKEN_SM;
+
+ DEBUGFUNC("ixgbe_write_phy_reg_x550em");
+
+ if (hw->mac.ops.acquire_swfw_sync(hw, mask) == IXGBE_SUCCESS) {
+ status = hw->phy.ops.write_reg_mdi(hw, reg_addr, device_type,
+ phy_data);
+ hw->mac.ops.release_swfw_sync(hw, mask);
+ } else {
+ status = IXGBE_ERR_SWFW_SYNC;
+ }
+
+ return status;
}
/**
@@ -2275,11 +2298,15 @@ s32 ixgbe_init_phy_ops_X550em(struct ixgbe_hw *hw)
/* Set functions pointers based on phy type */
switch (hw->phy.type) {
case ixgbe_phy_x550em_kx4:
+ printf("%s: x550em_kx4\n", __func__);
phy->ops.setup_link = NULL;
phy->ops.read_reg = ixgbe_read_phy_reg_x550em;
phy->ops.write_reg = ixgbe_write_phy_reg_x550em;
break;
case ixgbe_phy_x550em_kr:
+ printf("%s: x550em_kr\n", __func__);
+ phy->ops.read_reg_mdi = ixgbe_read_phy_reg_mdi;
+ phy->ops.write_reg_mdi = ixgbe_write_phy_reg_mdi;
phy->ops.setup_link = ixgbe_setup_kr_x550em;
phy->ops.read_reg = ixgbe_read_phy_reg_x550em;
phy->ops.write_reg = ixgbe_write_phy_reg_x550em;
@@ -2290,12 +2317,14 @@ s32 ixgbe_init_phy_ops_X550em(struct ixgbe_hw *hw)
phy->ops.reset = NULL;
break;
case ixgbe_phy_x550em_xfi:
+ printf("%s: x550em_xfi\n", __func__);
/* link is managed by HW */
phy->ops.setup_link = NULL;
phy->ops.read_reg = ixgbe_read_phy_reg_x550em;
phy->ops.write_reg = ixgbe_write_phy_reg_x550em;
break;
case ixgbe_phy_x550em_ext_t:
+ printf("%s: x550em_ext_t\n", __func__);
/* If internal link mode is XFI, then setup iXFI internal link,
* else setup KR now.
*/
@@ -2312,13 +2341,16 @@ s32 ixgbe_init_phy_ops_X550em(struct ixgbe_hw *hw)
phy->ops.reset = ixgbe_reset_phy_t_X550em;
break;
case ixgbe_phy_sgmii:
+ printf("%s: phy_sgmii\n", __func__);
phy->ops.setup_link = NULL;
break;
case ixgbe_phy_fw:
+ printf("%s: phy_fw\n", __func__);
phy->ops.setup_link = ixgbe_setup_fw_link;
phy->ops.reset = ixgbe_reset_phy_fw;
break;
default:
+ printf("%s: default\n", __func__);
break;
}
return ret_val;
@@ -2332,6 +2364,8 @@ static void ixgbe_set_mdio_speed(struct ixgbe_hw *hw)
{
u32 hlreg0;
+ printf("%s: called, device_id=%04x\n", __func__, hw->device_id);
+
switch (hw->device_id) {
case IXGBE_DEV_ID_X550EM_X_10G_T:
case IXGBE_DEV_ID_X550EM_A_SGMII:
@@ -2354,6 +2388,9 @@ static void ixgbe_set_mdio_speed(struct ixgbe_hw *hw)
default:
break;
}
+
+ printf("%s: HLREG0=0x%08x\n", __func__,
+ IXGBE_READ_REG(hw, IXGBE_HLREG0));
}
/**