diff --git a/_targets/Makefile.armv7a7-imx6ull b/_targets/Makefile.armv7a7-imx6ull index 58b2b6dc..5ee39d5a 100644 --- a/_targets/Makefile.armv7a7-imx6ull +++ b/_targets/Makefile.armv7a7-imx6ull @@ -9,4 +9,4 @@ NET_DRIVERS_SUPPORTED := enet tuntap NET_DRIVERS ?= $(NET_DRIVERS_SUPPORTED) -DRIVERS_SRCS_enet = imx-enet.c ephy.c imx6ull-gpio.c $(DRIVERS_SRCS_UTIL) hw-debug.c \ No newline at end of file +DRIVERS_SRCS_enet = imx-enet.c ephy.c imx6ull-gpio.c physelftest.c $(DRIVERS_SRCS_UTIL) hw-debug.c \ No newline at end of file diff --git a/_targets/Makefile.armv7m7-imxrt106x b/_targets/Makefile.armv7m7-imxrt106x index ba5f87a0..3d1cfe15 100644 --- a/_targets/Makefile.armv7m7-imxrt106x +++ b/_targets/Makefile.armv7m7-imxrt106x @@ -14,4 +14,4 @@ endif NET_DRIVERS ?= $(NET_DRIVERS_SUPPORTED) PPPOS_MODEM ?= huawei -DRIVERS_SRCS_enet = imx-enet.c ephy.c imxrt-gpio.c $(DRIVERS_SRCS_UTIL) +DRIVERS_SRCS_enet = imx-enet.c ephy.c imxrt-gpio.c physelftest.c $(DRIVERS_SRCS_UTIL) diff --git a/_targets/Makefile.armv7m7-imxrt117x b/_targets/Makefile.armv7m7-imxrt117x index 41009270..2a393591 100644 --- a/_targets/Makefile.armv7m7-imxrt117x +++ b/_targets/Makefile.armv7m7-imxrt117x @@ -10,4 +10,4 @@ NET_DRIVERS_SUPPORTED := pppou pppos enet NET_DRIVERS ?= $(NET_DRIVERS_SUPPORTED) PPPOS_MODEM ?= huawei -DRIVERS_SRCS_enet = imx-enet.c ephy.c imxrt-gpio.c $(DRIVERS_SRCS_UTIL) hw-debug.c +DRIVERS_SRCS_enet = imx-enet.c ephy.c imxrt-gpio.c physelftest.c $(DRIVERS_SRCS_UTIL) hw-debug.c diff --git a/_targets/Makefile.ia32-generic b/_targets/Makefile.ia32-generic index a2efb2cb..7e7690a4 100644 --- a/_targets/Makefile.ia32-generic +++ b/_targets/Makefile.ia32-generic @@ -10,4 +10,4 @@ NET_DRIVERS_SUPPORTED := rtl tuntap NET_DRIVERS ?= $(NET_DRIVERS_SUPPORTED) DRIVERS_SRCS_PCI_IA32 := pci.c pci-x86.c -DRIVERS_SRCS_rtl = rtl8139cp.c $(DRIVERS_SRCS_PCI_IA32) $(DRIVERS_SRCS_UTIL) +DRIVERS_SRCS_rtl = rtl8139cp.c physelftest.c $(DRIVERS_SRCS_PCI_IA32) $(DRIVERS_SRCS_UTIL) diff --git a/_targets/Makefile.riscv64-gr765 b/_targets/Makefile.riscv64-gr765 index ffbe0522..b0ca4c86 100644 --- a/_targets/Makefile.riscv64-gr765 +++ b/_targets/Makefile.riscv64-gr765 @@ -9,4 +9,4 @@ NET_DRIVERS_SUPPORTED := greth NET_DRIVERS ?= $(NET_DRIVERS_SUPPORTED) -DRIVERS_SRCS_greth = greth.c ephy.c null-gpio.c $(DRIVERS_SRCS_UTIL) +DRIVERS_SRCS_greth = greth.c ephy.c null-gpio.c physelftest.c $(DRIVERS_SRCS_UTIL) diff --git a/_targets/Makefile.sparcv8leon-gr740 b/_targets/Makefile.sparcv8leon-gr740 index fd4ce243..329a90eb 100644 --- a/_targets/Makefile.sparcv8leon-gr740 +++ b/_targets/Makefile.sparcv8leon-gr740 @@ -9,4 +9,4 @@ NET_DRIVERS_SUPPORTED := greth NET_DRIVERS ?= $(NET_DRIVERS_SUPPORTED) -DRIVERS_SRCS_greth = greth.c ephy.c null-gpio.c $(DRIVERS_SRCS_UTIL) +DRIVERS_SRCS_greth = greth.c ephy.c null-gpio.c physelftest.c $(DRIVERS_SRCS_UTIL) diff --git a/drivers/ephy.c b/drivers/ephy.c index 5cf1b5d6..c4400b18 100644 --- a/drivers/ephy.c +++ b/drivers/ephy.c @@ -13,6 +13,7 @@ #include "netif-driver.h" #include +#include #include #include #include @@ -236,10 +237,27 @@ static uint32_t ephy_readPhyId(const eth_phy_state_t *phy) } +static inline const char *ephy_duplexToString(int duplex) +{ + switch (duplex) { + case DUPLEX_HALF: + return "Half"; + + case DUPLEX_FULL: + return "Full"; + + case DUPLEX_UNKNOWN: + default: + return "unknown"; + } +} + + static void ephy_setLinkState(const eth_phy_state_t *phy) { uint16_t bctl, bstat, adv, lpa, pc1, pc2; - int speed, full_duplex = 0; + int speed, duplex = 0; + char linkstatus_str[32] = { 0 }; bctl = ephy_regRead(phy, EPHY_COMMON_00_BMCR); /* @@ -252,9 +270,15 @@ static void ephy_setLinkState(const eth_phy_state_t *phy) adv = ephy_regRead(phy, EPHY_COMMON_04_ANAR); lpa = ephy_regRead(phy, EPHY_COMMON_05_ANLPAR); - speed = ephy_linkSpeed(phy, &full_duplex); + bool linkup = (bstat & (1U << 2)) != 0; + speed = ephy_linkSpeed(phy, &duplex); - int linkup = (bstat & (1U << 2)) != 0; + if (linkup) { + snprintf(linkstatus_str, sizeof(linkstatus_str), "link is UP %d/%s", speed, ephy_duplexToString(duplex)); + } + else { + snprintf(linkstatus_str, sizeof(linkstatus_str), "link is DOWN"); + } if (phy->link_state_callback != NULL) { phy->link_state_callback(phy->link_state_callback_arg, linkup); @@ -266,20 +290,20 @@ static void ephy_setLinkState(const eth_phy_state_t *phy) case ephy_ksz8081rnd: pc1 = ephy_regRead(phy, EPHY_KSZ8081_1E_PHYCR1); pc2 = ephy_regRead(phy, EPHY_KSZ8081_1F_PHYCR2); - ephy_printf(phy, "link is %s %uMbps/%s (ctl %04x, status %04x, adv %04x, lpa %04x, pctl %04x,%04x)", - (linkup != 0) ? "UP " : "DOWN", speed, (full_duplex != 0) ? "Full" : "Half", bctl, bstat, adv, lpa, pc1, pc2); + ephy_printf(phy, "%s (ctl %04x, status %04x, adv %04x, lpa %04x, pctl %04x,%04x)", + linkstatus_str, bctl, bstat, adv, lpa, pc1, pc2); break; case ephy_ksz9031mnx: case ephy_dp83867is: case ephy_rtl8201fi: - ephy_printf(phy, "link is %s %uMbps/%s (ctl %04x, status %04x, adv %04x, lpa %04x)", - (linkup != 0) ? "UP " : "DOWN", speed, (full_duplex != 0) ? "Full" : "Half", bctl, bstat, adv, lpa); + ephy_printf(phy, "%s (ctl %04x, status %04x, adv %04x, lpa %04x)", + linkstatus_str, bctl, bstat, adv, lpa); break; case ephy_rtl8211fdi: pc1 = ephy_regRead(phy, EPHY_RTL8211_18_PHYCR1); pc2 = ephy_regRead(phy, EPHY_RTL8211_19_PHYCR2); - ephy_printf(phy, "link is %s %uMbps/%s (ctl %04x, status %04x, adv %04x, lpa %04x, pctl %04x,%04x)", - linkup ? "UP " : "DOWN", speed, full_duplex ? "Full" : "Half", bctl, bstat, adv, lpa, pc1, pc2); + ephy_printf(phy, "%s (ctl %04x, status %04x, adv %04x, lpa %04x, pctl %04x,%04x)", + linkstatus_str, bctl, bstat, adv, lpa, pc1, pc2); break; default: /* unreachable */ @@ -288,51 +312,57 @@ static void ephy_setLinkState(const eth_phy_state_t *phy) } -static inline int ephy_ksz8081rnx_linkSpeed(const eth_phy_state_t *phy, int *full_duplex) +static inline int ephy_ksz8081rnx_linkSpeed(const eth_phy_state_t *phy, int *duplex) { uint16_t pc1 = ephy_regRead(phy, EPHY_KSZ8081_1E_PHYCR1); if ((pc1 & 0x7) == 0) { /* PHY still in auto-negotiation */ - return 0; + if (duplex != NULL) { + *duplex = DUPLEX_UNKNOWN; + } + return SPEED_UNKNOWN; } - if (full_duplex != NULL) { - *full_duplex = ((pc1 & (1U << 2)) != 0) ? 1 : 0; + if (duplex != NULL) { + *duplex = ((pc1 & (1U << 2)) != 0) ? DUPLEX_FULL : DUPLEX_HALF; } - return ((pc1 & 0x1) != 0) ? 10 : 100; + return ((pc1 & 0x1) != 0) ? SPEED_10 : SPEED_100; } -static inline int ephy_ksz9031mnx_linkSpeed(const eth_phy_state_t *phy, int *full_duplex) +static inline int ephy_ksz9031mnx_linkSpeed(const eth_phy_state_t *phy, int *duplex) { uint16_t st = ephy_regRead(phy, EPHY_COMMON_01_BMSR); - /* PHY still in auto-negotiation */ - if ((st & (1U << 5)) == 0) { - return 0; + + if ((st & (1U << 5)) == 0) { /* PHY still in auto-negotiation */ + if (duplex != NULL) { + *duplex = DUPLEX_UNKNOWN; + } + return SPEED_UNKNOWN; } uint16_t pc = ephy_regRead(phy, EPHY_KSZ9031_1F_PHYCR); - if (full_duplex != NULL) { - *full_duplex = ((pc & (1U << 3)) != 0) ? 1 : 0; + if (duplex != NULL) { + *duplex = ((pc & (1U << 3)) != 0) ? DUPLEX_FULL : DUPLEX_HALF; } if ((pc & (1U << 4)) != 0) { - return 10; + return SPEED_10; } else if ((pc & (1U << 5)) != 0) { - return 100; + return SPEED_100; } else if ((pc & (1U << 6)) != 0) { - return 1000; + return SPEED_1000; } else { - return 0; + return SPEED_UNKNOWN; } } -static inline int ephy_dp83867is_linkSpeed(const eth_phy_state_t *phy, int *full_duplex) +static inline int ephy_dp83867is_linkSpeed(const eth_phy_state_t *phy, int *duplex) { uint16_t st = ephy_regRead(phy, EPHY_COMMON_01_BMSR); uint16_t sts = ephy_regRead(phy, EPHY_DP83867IS_11_PHYSTS); @@ -340,78 +370,81 @@ static inline int ephy_dp83867is_linkSpeed(const eth_phy_state_t *phy, int *full /* PHY still in auto-negotiation */ if ((st & (1U << 5)) == 0) { - return 0; + if (duplex != NULL) { + *duplex = DUPLEX_UNKNOWN; + } + return SPEED_UNKNOWN; } - if (full_duplex != NULL) { - *full_duplex = ((sts & (1U << 13)) != 0) ? 1 : 0; + if (duplex != NULL) { + *duplex = ((sts & (1U << 13)) != 0) ? DUPLEX_FULL : DUPLEX_HALF; } speed = (sts >> 14) & 0x3; switch (speed) { case 0x0: - return 10; + return SPEED_10; case 0x1: - return 100; + return SPEED_100; case 0x2: - return 1000; + return SPEED_1000; default: - return 0; + return SPEED_UNKNOWN; } } -static inline int ephy_rtl8201fi_linkSpeed(const eth_phy_state_t *phy, int *full_duplex) +static inline int ephy_rtl8201fi_linkSpeed(const eth_phy_state_t *phy, int *duplex) { uint16_t bmcr = ephy_regRead(phy, EPHY_COMMON_00_BMCR); - if (full_duplex != NULL) { - *full_duplex = ((bmcr & (1U << 8)) != 0) ? 1 : 0; + if (duplex != NULL) { + *duplex = ((bmcr & (1U << 8)) != 0) ? DUPLEX_FULL : DUPLEX_HALF; } - return ((bmcr & (1U << 13)) == 0) ? 10 : 100; + return ((bmcr & (1U << 13)) == 0) ? SPEED_10 : SPEED_100; } -static inline int ephy_rtl8211fdi_linkSpeed(const eth_phy_state_t *phy, int *full_duplex) +static inline int ephy_rtl8211fdi_linkSpeed(const eth_phy_state_t *phy, int *duplex) { uint16_t physr = ephy_regRead(phy, EPHY_RTL8211_1A_PHYSR); uint16_t speed; - if (full_duplex != NULL) { - *full_duplex = ((physr & (1U << 3)) != 0) ? 1 : 0; + if (duplex != NULL) { + *duplex = ((physr & (1U << 3)) != 0) ? DUPLEX_FULL : DUPLEX_HALF; } speed = (physr >> 4) & 0x3; switch (speed) { case 0x0: - return 10; + return SPEED_10; case 0x1: - return 100; + return SPEED_100; case 0x2: - return 1000; + return SPEED_1000; default: - return 0; + return SPEED_UNKNOWN; } } -int ephy_linkSpeed(const eth_phy_state_t *phy, int *full_duplex) +int ephy_linkSpeed(const eth_phy_state_t *phy, int *duplex) { switch (phy->model) { case ephy_ksz8081rna: case ephy_ksz8081rnb: case ephy_ksz8081rnd: - return ephy_ksz8081rnx_linkSpeed(phy, full_duplex); + return ephy_ksz8081rnx_linkSpeed(phy, duplex); case ephy_ksz9031mnx: - return ephy_ksz9031mnx_linkSpeed(phy, full_duplex); + return ephy_ksz9031mnx_linkSpeed(phy, duplex); case ephy_dp83867is: - return ephy_dp83867is_linkSpeed(phy, full_duplex); + return ephy_dp83867is_linkSpeed(phy, duplex); case ephy_rtl8201fi: - return ephy_rtl8201fi_linkSpeed(phy, full_duplex); + return ephy_rtl8201fi_linkSpeed(phy, duplex); case ephy_rtl8211fdi: - return ephy_rtl8211fdi_linkSpeed(phy, full_duplex); + return ephy_rtl8211fdi_linkSpeed(phy, duplex); default: /* unreachable */ return 0; @@ -641,11 +674,17 @@ static int ephy_config(eth_phy_state_t *phy, char *cfg) } -int ephy_enableLoopback(const eth_phy_state_t *phy, bool enable) +int ephy_getLoopback(const eth_phy_state_t *phy) { - uint16_t bmcr = ephy_regRead(phy, EPHY_COMMON_00_BMCR); - bool loopback_enabled = bmcr & (1U << 14); + return (ephy_regRead(phy, EPHY_COMMON_00_BMCR) >> 14) & 0x1; +} + +/* toggle MACPHY internal loopback for test mode */ +int ephy_setLoopback(const eth_phy_state_t *phy, bool enable) +{ + uint16_t bmcr = ephy_regRead(phy, EPHY_COMMON_00_BMCR); + bool loopback_enabled = (bmcr & (1U << 14)) != 0; if (loopback_enabled == enable) { return 0; } @@ -656,8 +695,15 @@ int ephy_enableLoopback(const eth_phy_state_t *phy, bool enable) /* full-duplex */ bmcr |= 1U << 8; bmcr |= ephy_bmcrMaxSpeedMask(phy); + /* loopback */ + bmcr |= 1U << 14; + } + else { + bmcr &= ~(1U << 14); } + ephy_regWrite(phy, EPHY_COMMON_00_BMCR, bmcr); + switch (phy->model) { case ephy_ksz8081rna: case ephy_ksz8081rnb: @@ -671,16 +717,16 @@ int ephy_enableLoopback(const eth_phy_state_t *phy, bool enable) ephy_regWrite(phy, EPHY_KSZ8081_1F_PHYCR2, phycr2); break; } + case ephy_rtl8201fi: + case ephy_rtl8211fdi: + if (enable) { + usleep(phy->reset_release_time_us); + } + break; default: break; } - /* loopback */ - bmcr = enable ? - (bmcr | (1U << 14)) : - (bmcr & ~(1U << 14)); - ephy_regWrite(phy, EPHY_COMMON_00_BMCR, bmcr); - if (!enable) { ephy_restartAN(phy); } @@ -690,6 +736,27 @@ int ephy_enableLoopback(const eth_phy_state_t *phy, bool enable) } +int ephy_getAN(const eth_phy_state_t *phy) +{ + int autoneg = (ephy_regRead(phy, EPHY_COMMON_00_BMCR) >> 12) & 0x1; + return (autoneg != 0) ? AUTONEG_ENABLE : AUTONEG_DISABLE; +} + + +#define EPHY_ADVERTISED_SPEEDS (ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full | ADVERTISED_100baseT_Half | ADVERTISED_100baseT_Full) +#define EPHY_ADVERTISED_INTERFACES (ADVERTISED_Autoneg) +#define EPHY_ADVERTISED_FEATURES (ADVERTISED_MII) +#define EPHY_ADVERTISED (EPHY_ADVERTISED_SPEEDS | EPHY_ADVERTISED_INTERFACES | EPHY_ADVERTISED_FEATURES) +int ephy_getAdv(const eth_phy_state_t *phy) +{ + int adv = EPHY_ADVERTISED_SPEEDS | EPHY_ADVERTISED_INTERFACES | EPHY_ADVERTISED_FEATURES; + if (phy->model == ephy_rtl8211fdi || phy->model == ephy_ksz9031mnx || phy->model == ephy_dp83867is) { + adv |= ADVERTISED_1000baseT_Full; + } + return adv; +} + + /* Try to set alternative MAC PHY config (alternative configurations within the same PHY ID). * returns: * > 0 if alternative config has been set diff --git a/drivers/ephy.h b/drivers/ephy.h index a6dfd075..0ee9351c 100644 --- a/drivers/ephy.h +++ b/drivers/ephy.h @@ -13,9 +13,11 @@ #include "gpio.h" +#include #include #include + typedef void (*link_state_cb_t)(void *arg, int state); typedef struct { @@ -48,12 +50,17 @@ typedef struct { int ephy_init(eth_phy_state_t *phy, char *conf, uint8_t board_rev, link_state_cb_t cb, void *cb_arg); -int ephy_linkSpeed(const eth_phy_state_t *phy, int *full_duplex); +int ephy_linkSpeed(const eth_phy_state_t *phy, int *duplex); /* called by the MAC driver if it handles the PHY IRQ */ void ephy_macInterrupt(const eth_phy_state_t *phy); -/* toggle MACPHY internal loopback for test mode */ -int ephy_enableLoopback(const eth_phy_state_t *phy, bool enable); +/* ethtool interface */ +int ephy_getLoopback(const eth_phy_state_t *phy); +int ephy_setLoopback(const eth_phy_state_t *phy, bool enable); +int ephy_getAN(const eth_phy_state_t *phy); + +/* get advertised: speeds, interfaces, features */ +int ephy_getAdv(const eth_phy_state_t *phy); #endif /* NET_EPHY_H_ */ diff --git a/drivers/greth.c b/drivers/greth.c index b06f8a80..e6d9ea50 100644 --- a/drivers/greth.c +++ b/drivers/greth.c @@ -35,6 +35,8 @@ #include #include +#include "physelftest.h" + #define greth_printf(state, fmt, ...) printf("lwip: greth@%p: " fmt "\n", (void *)state->dev_phys_addr, ##__VA_ARGS__) #define GRETH_MAC_IAB UINT64_C(0x0050C275A000) /* Gaisler research AB */ @@ -42,6 +44,19 @@ #define GRETH_TX_RING_SIZE 8 #define GRETH_MAX_PKT_SZ 1514 +#define GRETH_SELFTEST 1 + +#define GRETH_SUPPORTED_SPEEDS ( \ + SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | \ + SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | \ + SUPPORTED_1000baseT_Full) +#define GRETH_SUPPORTED_INTERFACES (SUPPORTED_MII) +#define GRETH_SUPPORTED_FEATURES (SUPPORTED_Autoneg) +#define GRETH_SUPPORTED (GRETH_SUPPORTED_SPEEDS | GRETH_SUPPORTED_INTERFACES | GRETH_SUPPORTED_FEATURES) +#define GRETH_PORT (PORT_MII) +#define GRETH_MDIO_SUPPORTED(addr) ((((addr & NETDEV_MDIO_CLAUSE45) != 0) ? ETH_MDIO_SUPPORTS_C45 : 0) | ETH_MDIO_SUPPORTS_C22) +#define GRETH_LINK_MODE_MASKS_NWORDS (1) + #ifndef GRETH_EDCL #define GRETH_EDCL 0 #endif @@ -626,12 +641,177 @@ const char *greth_media(struct netif *netif) } +#if GRETH_SELFTEST +static int greth_selftestSetup(void *arg) +{ + greth_state_t *state = arg; + + /* setup self-test (local loopback mode & force linkup) */ + if (ephy_setLoopback(&state->phy, true) < 0) { + ephy_setLoopback(&state->phy, false); + return -1; + } + + /* enable promisicious mode to allow invalid MAC in pseudo-ETH test packet */ + state->mmio->CTRL |= GRETH_CTRL_PM; + + return 0; +} + + +static int greth_selftestTeardown(void *arg) +{ + greth_state_t *state = arg; + + state->mmio->CTRL &= ~GRETH_CTRL_PM; + + (void)ephy_setLoopback(&state->phy, false); + + return 0; +} +#endif + + +static int greth_ethtoolIoctl(struct netif *netif, void *data) +{ + greth_state_t *state = netif->state; + uint32_t cmd = *((uint32_t *)data); + int err; + + switch (cmd) { + case ETHTOOL_GSET: { + struct ethtool_cmd *ecmd = data; + int duplex; + int speed = ephy_linkSpeed(&state->phy, &duplex); + + memset(ecmd, 0, sizeof(*ecmd)); + ecmd->supported = GRETH_SUPPORTED; + ecmd->advertising = ephy_getAdv(&state->phy); + ecmd->speed = speed; + ecmd->duplex = duplex; + ecmd->port = GRETH_PORT; + ecmd->phy_address = state->phy.addr; + ecmd->autoneg = ephy_getAN(&state->phy); + ecmd->mdio_support = GRETH_MDIO_SUPPORTED(state->phy.addr); + /* FIXME: MDI info */ + + return EOK; + } + + case ETHTOOL_GSSET_INFO: { + struct ethtool_sset_info *info = data; + uint64_t outmask = 0; + + if ((info->sset_mask & (1ull << ETH_SS_TEST)) != 0) { + info->data[ETH_SS_TEST] = 0; /* number_of_strings * ETH_GSTRING_LEN */ + outmask |= 1ull << ETH_SS_TEST; + } + + info->sset_mask = outmask; + + return EOK; + } + + case ETHTOOL_GSTRINGS: { + struct ethtool_gstrings *strings = data; + + switch (strings->string_set) { + case ETH_SS_TEST: + strings->len = 0; + break; + + default: + return -EINVAL; + } + + return EOK; + } + + case ETHTOOL_TEST: { + struct ethtool_test *test_params = data; +#if GRETH_SELFTEST + if ((test_params->flags & ETH_TEST_FL_OFFLINE) != 0) { + const struct selftest_params params = { + .module = "greth", + .netif = netif, + .crcStripped = true, + .verbose = GRETH_DEBUG != 0, + .setup = greth_selftestSetup, + .teardown = greth_selftestTeardown + }; + err = physelftest(¶ms); + if (err < 0) { + test_params->flags |= ETH_TEST_FL_FAILED; + } + return EOK; + } +#endif + return -EOPNOTSUPP; + } + + case ETHTOOL_GLOOPBACK: { + struct ethtool_value *value = data; + + value->data = ephy_getLoopback(&state->phy); + + return EOK; + } + + case ETHTOOL_SLOOPBACK: { + struct ethtool_value *value = data; + + int err = ephy_setLoopback(&state->phy, value->data); + if (err < 0) { + value->data = ETH_PHY_LOOPBACK_SET_FAILED; + } + + return EOK; + } + + case ETHTOOL_GLINKSETTINGS: { + struct ethtool_link_settings *link_settings = data; + + /* the caller expects us to return the real value */ + if (link_settings->link_mode_masks_nwords != GRETH_LINK_MODE_MASKS_NWORDS) { + link_settings->link_mode_masks_nwords = -GRETH_LINK_MODE_MASKS_NWORDS; + return EOK; + } + + int duplex; + uint64_t speed = ephy_linkSpeed(&state->phy, &duplex); + + link_settings->speed = speed & 0xffffffff; + link_settings->duplex = duplex; + link_settings->port = GRETH_PORT; + link_settings->phy_address = state->phy.addr; + link_settings->autoneg = ephy_getAN(&state->phy); + link_settings->mdio_support = GRETH_MDIO_SUPPORTED(state->phy.addr); + /* FIXME: MDI info */ + + /* layout of link_mode_masks: + * uint32_t map_supported[link_mode_masks_nwords]; + * uint32_t map_advertising[link_mode_masks_nwords]; + * uint32_t map_lp_advertising[link_mode_masks_nwords]; + */ + link_settings->link_mode_masks[0] = GRETH_SUPPORTED; + link_settings->link_mode_masks[1] = ephy_getAdv(&state->phy); + + return EOK; + } + + default: + return -EOPNOTSUPP; + } +} + + static netif_driver_t greth_drv = { .init = greth_netifInit, .state_sz = sizeof(greth_state_t), .state_align = _Alignof(greth_state_t), .name = "greth", .media = greth_media, + .do_ethtool_ioctl = greth_ethtoolIoctl, }; diff --git a/drivers/imx-enet.c b/drivers/imx-enet.c index aedcc1f4..34c7ab73 100644 --- a/drivers/imx-enet.c +++ b/drivers/imx-enet.c @@ -14,10 +14,12 @@ #include "netif-driver.h" #include "bdring.h" #include "ephy.h" +#include "physelftest.h" #include "physmmap.h" #include "res-create.h" #include "imx-enet-regs.h" +#include #include #include #include @@ -31,9 +33,10 @@ #include #include -#define ENET_DEBUG 0 -#define MDIO_DEBUG 0 -#define ENET_SELFTEST 0 +#define ENET_DEBUG 0 +#define MDIO_DEBUG 0 + +#define ENET_SELFTEST 1 #define ENET_MAX_PKT_SZ 1984 /* DMA aligned */ #define ENET_USE_ENHANCED_DESCRIPTORS 0 @@ -64,6 +67,10 @@ #define ENET_RX_RING_SIZE 8 #define ENET_TX_RING_SIZE 8 +#define ENET_SUPPORTED_SPEEDS ( \ + SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | \ + SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full) + #elif defined(__CPU_IMXRT117X) #include @@ -85,6 +92,11 @@ #define ENET_RX_RING_SIZE 4 #define ENET_TX_RING_SIZE 4 +#define ENET_SUPPORTED_SPEEDS ( \ + SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | \ + SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | \ + SUPPORTED_1000baseT_Half | SUPPORTED_1000baseT_Full) + #elif defined(__CPU_IMX6ULL) #include @@ -105,10 +117,22 @@ #define ENET_RX_RING_SIZE 64 #define ENET_TX_RING_SIZE 64 +#define ENET_SUPPORTED_SPEEDS ( \ + SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | \ + SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full) + #else #error "Unsupported TARGET" #endif + +#define ENET_SUPPORTED_INTERFACES (SUPPORTED_MII) +#define ENET_SUPPORTED_FEATURES (SUPPORTED_Autoneg) +#define ENET_SUPPORTED (ENET_SUPPORTED_SPEEDS | ENET_SUPPORTED_INTERFACES | ENET_SUPPORTED_FEATURES) +#define ENET_PORT (PORT_MII) +#define ENET_MDIO_SUPPORTED(addr) ((((addr & NETDEV_MDIO_CLAUSE45) != 0) ? ETH_MDIO_SUPPORTS_C45 : 0) | ETH_MDIO_SUPPORTS_C22) +#define ENET_LINK_MODE_MASKS_NWORDS (1) + #if ENET_USE_ENHANCED_DESCRIPTORS typedef enet_enhanced_desc_t enet_buf_desc_t; #else @@ -138,15 +162,6 @@ typedef struct { eth_phy_state_t phy; -#if ENET_SELFTEST - struct { -#define SELFTEST_RESOURCES(s) &(s)->selfTest.rx_lock, 2, ~0x1 - handle_t rx_lock; - handle_t rx_cond; - unsigned int rx_valid; /* -1: received invalid packet, 0: no packet received, 1: received valid packet */ - } selfTest; -#endif - uint32_t irq_stack[1024] __attribute__((aligned(16))); } enet_state_t; @@ -1408,168 +1423,6 @@ static void enet_setLinkState(void *arg, int state) } -#if ENET_SELFTEST -#define _TP_DST "dddddd" -#define _TP_SRC "ssssss" -#define _TP_ETHTYPE "\x05\xDD" /* eth frame type 0x05DD is undefined */ -#define _TP_10DIG "0123456789" -#define TEST_PACKET _TP_DST _TP_SRC _TP_ETHTYPE \ - _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG -#define TEST_PACKET_LEN (sizeof((TEST_PACKET)) - 1) - - -/* self-test RX input function */ -static err_t enet_testNetifInput(struct pbuf *p, struct netif *netif) -{ - uint8_t buf[TEST_PACKET_LEN]; /* used only if pbuf is fragmented (should not happen) */ - enet_state_t *state = netif->state; - - bool is_valid_pkt = true; - - /* verify contents */ - if (p->len != (TEST_PACKET_LEN + ETH_PAD_SIZE)) { - enet_debug_printf(state, "self-test RX: invalid packet length"); - enet_debug_printf(state, "expected: %uB", (TEST_PACKET_LEN + ETH_PAD_SIZE)); - enet_debug_printf(state, "actual: %uB", p->len); - is_valid_pkt = false; - } - uint8_t *data = pbuf_get_contiguous(p, buf, sizeof(buf), TEST_PACKET_LEN, ETH_PAD_SIZE); - if (data == NULL || memcmp(TEST_PACKET, data, TEST_PACKET_LEN) != 0) { -#if ENET_DEBUG - if (data == NULL) { - data = p->payload; - } - enet_printf(state, "self-test RX: invalid packet contents"); - - enet_printf(state, "expected:"); - for (int i = 0; i < TEST_PACKET_LEN; i++) { - if (i != 0 && i % 16 == 0) { - printf("\n"); - } - printf("%02x ", TEST_PACKET[i]); - } - printf("\n"); - - enet_printf(state, "actual:"); - for (int i = 0; i < p->len; i++) { - if (i != 0 && i % 16 == 0) { - printf("\n"); - } - printf("%02x ", data[i]); - } - printf("\n"); -#endif - is_valid_pkt = false; - } - pbuf_free(p); - - mutexLock(state->selfTest.rx_lock); - state->selfTest.rx_valid = is_valid_pkt ? 1 : -1; - mutexUnlock(state->selfTest.rx_lock); - condBroadcast(state->selfTest.rx_cond); - - return ERR_OK; -} - - -/* MACPHY self-test procedure (internal loopback) */ -static int enet_phySelfTest(struct netif *netif) -{ - enet_state_t *state = netif->state; - int err; - bool was_addins_set; - - enet_printf(state, "Start enet phy tx/rx selftest"); - - err = create_mutexcond_bulk(SELFTEST_RESOURCES(state)); - if (err != 0) { - return err; - } - - /* setup self-test (local loopback mode & force linkup) */ - if (ephy_enableLoopback(&state->phy, true) < 0) { - ephy_enableLoopback(&state->phy, false); - resourceDestroy(state->selfTest.rx_cond); - resourceDestroy(state->selfTest.rx_lock); - return -1; - } - - /* enable promisicious mode to allow invalid MAC in pseudo-ETH test packet */ - state->mmio->RCR |= ENET_RCR_PROM; - - /* disable MAC address addition on TX */ - was_addins_set = (state->mmio->TCR & ENET_TCR_ADDINS) != 0; - state->mmio->TCR &= ~ENET_TCR_ADDINS; - - /* enable MIB counters (mmio->stats) + clear stats */ - state->mmio->MIBC = 0; - state->mmio->MIBC |= ENET_MIBC_MIB_CLEAR; - state->mmio->MIBC &= ~ENET_MIBC_MIB_CLEAR; - - /* override netif->input */ - netif_input_fn old_input = netif->input; - netif->input = &enet_testNetifInput; - - int ret = 0; - do { - struct pbuf *p = pbuf_alloc(PBUF_RAW, TEST_PACKET_LEN + ETH_PAD_SIZE, PBUF_RAM); - memset(p->payload, 0, ETH_PAD_SIZE); - pbuf_take_at(p, TEST_PACKET, TEST_PACKET_LEN, ETH_PAD_SIZE); - - /* try to send and receive packets */ - mutexLock(state->selfTest.rx_lock); - state->selfTest.rx_valid = 0; - if (enet_netifOutput(netif, p) != ERR_OK) { /* frees pbuf internally */ - enet_printf(state, "failed to send test packet"); - ret = -1; - mutexUnlock(state->selfTest.rx_lock); - break; - } - - err = 0; - while ((err != -ETIME) && (state->selfTest.rx_valid == 0)) { - /* TX -> RX takes ~4ms, wait for 100ms just to be sure */ - err = condWait(state->selfTest.rx_cond, state->selfTest.rx_lock, 100 * 1000); - } - mutexUnlock(state->selfTest.rx_lock); - - enet_debug_printf(state, "stats: TX: PACKETS=%u CRC_ALIGN=%u OK=%u", - state->mmio->stats.RMON_T_PACKETS, - state->mmio->stats.RMON_T_CRC_ALIGN, - state->mmio->stats.IEEE_T_FRAME_OK); - - enet_debug_printf(state, "stats: RX: PACKETS=%u CRC_ALIGN=%u OK=%u", - state->mmio->stats.RMON_R_PACKETS, - state->mmio->stats.RMON_R_CRC_ALIGN, - state->mmio->stats.IEEE_R_FRAME_OK); - - if ((err < 0) || (state->selfTest.rx_valid != 1)) { - enet_debug_printf(state, "Test failed: state->selfTest.rx_valid=%d, %s (%d)", - state->selfTest.rx_valid, strerror(-err), err); - ret = -1; - } - - /* successfully received */ - } while (0); - - /* restore normal mode */ - netif->input = old_input; - state->mmio->RCR &= ~ENET_RCR_PROM; - if (was_addins_set) { - state->mmio->TCR |= ENET_TCR_ADDINS; - } - state->mmio->MIBC = ENET_MIBC_MIB_DIS; - ephy_enableLoopback(&state->phy, false); - - /* destroy selftest resources */ - resourceDestroy(state->selfTest.rx_cond); - resourceDestroy(state->selfTest.rx_lock); - - return ret; -} -#endif - - /* ARGS: enet:base:irq[:no-mdio][:PHY:[model:][bus.]addr[:config]] */ static int enet_netifInit(struct netif *netif, char *cfg) { @@ -1645,16 +1498,6 @@ static int enet_netifInit(struct netif *netif, char *cfg) physunmap(ocotp_mem, 0x1000); return err; } - -#if ENET_SELFTEST - err = enet_phySelfTest(netif); - if (err < 0) { - enet_printf(state, "WARN: PHY autotest failed"); - } - else { - enet_printf(state, "PHY selftest passed successfully"); - } -#endif } physunmap(ocotp_mem, 0x1000); @@ -1663,40 +1506,234 @@ static int enet_netifInit(struct netif *netif, char *cfg) } -const char *enet_media(struct netif *netif) +static const char *enet_media(struct netif *netif) { - int full_duplex, speed; + int duplex, speed; enet_state_t *state; state = netif->state; - speed = ephy_linkSpeed(&state->phy, &full_duplex); + speed = ephy_linkSpeed(&state->phy, &duplex); switch (speed) { - case 0: + case SPEED_UNKNOWN: return "unspecified"; - case 10: - if (full_duplex != 0) { - return "10Mbps/full-duplex"; + case SPEED_10: + switch (duplex) { + case DUPLEX_HALF: + return "10Mbps/half-duplex"; + case DUPLEX_FULL: + return "10Mbps/full-duplex"; + default: + return "unrecognized"; } - else { - return "10Mbps/half-duplex"; + case SPEED_100: + switch (duplex) { + case DUPLEX_HALF: + return "100Mbps/half-duplex"; + case DUPLEX_FULL: + return "100Mbps/full-duplex"; + default: + return "unrecognized"; } - case 100: - if (full_duplex != 0) { - return "100Mbps/full-duplex"; + case SPEED_1000: + switch (duplex) { + case DUPLEX_HALF: + return "1000Mbps/half-duplex"; + case DUPLEX_FULL: + return "1000Mbps/full-duplex"; + default: + return "unrecognized"; } - else { - return "100Mbps/half-duplex"; + default: + return "unrecognized"; + } +} + + +#if ENET_SELFTEST +static bool was_addins_set; + + +static int enet_selftestSetup(void *arg) +{ + enet_state_t *state = arg; + + /* setup self-test (local loopback mode & force linkup) */ + if (ephy_setLoopback(&state->phy, true) < 0) { + ephy_setLoopback(&state->phy, false); + return -1; + } + + /* enable promisicious mode to allow invalid MAC in pseudo-ETH test packet */ + state->mmio->RCR |= ENET_RCR_PROM; + + /* disable MAC address addition on TX */ + was_addins_set = (state->mmio->TCR & ENET_TCR_ADDINS) != 0; + state->mmio->TCR &= ~ENET_TCR_ADDINS; + + /* enable MIB counters (mmio->stats) + clear stats */ + state->mmio->MIBC = 0; + state->mmio->MIBC |= ENET_MIBC_MIB_CLEAR; + state->mmio->MIBC &= ~ENET_MIBC_MIB_CLEAR; + + return 0; +} + + +static int enet_selftestTeardown(void *arg) +{ + enet_state_t *state = arg; + + enet_debug_printf(state, "stats: TX: PACKETS=%u CRC_ALIGN=%u OK=%u", + state->mmio->stats.RMON_T_PACKETS, + state->mmio->stats.RMON_T_CRC_ALIGN, + state->mmio->stats.IEEE_T_FRAME_OK); + enet_debug_printf(state, "stats: RX: PACKETS=%u CRC_ALIGN=%u OK=%u", + state->mmio->stats.RMON_R_PACKETS, + state->mmio->stats.RMON_R_CRC_ALIGN, + state->mmio->stats.IEEE_R_FRAME_OK); + + state->mmio->RCR &= ~ENET_RCR_PROM; + if (was_addins_set) { + state->mmio->TCR |= ENET_TCR_ADDINS; + } + state->mmio->MIBC = ENET_MIBC_MIB_DIS; + (void)ephy_setLoopback(&state->phy, false); + + return 0; +} +#endif + + +static int enet_ethtoolIoctl(struct netif *netif, void *data) +{ + enet_state_t *state = netif->state; + uint32_t cmd = *((uint32_t *)data); + int err; + + switch (cmd) { + case ETHTOOL_GSET: { + struct ethtool_cmd *ecmd = data; + int duplex; + int speed = ephy_linkSpeed(&state->phy, &duplex); + + memset(ecmd, 0, sizeof(*ecmd)); + ecmd->supported = ENET_SUPPORTED; + ecmd->advertising = ephy_getAdv(&state->phy); + ecmd->speed = speed; + ecmd->duplex = duplex; + ecmd->port = ENET_PORT; + ecmd->phy_address = state->phy.addr; + ecmd->autoneg = ephy_getAN(&state->phy); + ecmd->mdio_support = ENET_MDIO_SUPPORTED(state->phy.addr); + /* FIXME: MDI info */ + + return EOK; + } + + case ETHTOOL_GSSET_INFO: { + struct ethtool_sset_info *info = data; + uint64_t outmask = 0; + + if ((info->sset_mask & (1ull << ETH_SS_TEST)) != 0) { + info->data[ETH_SS_TEST] = 0; /* number_of_strings * ETH_GSTRING_LEN */ + outmask |= 1ull << ETH_SS_TEST; } - case 1000: - if (full_duplex != 0) { - return "1000Mbps/full-duplex"; + + info->sset_mask = outmask; + + return EOK; + } + + case ETHTOOL_GSTRINGS: { + struct ethtool_gstrings *strings = data; + + switch (strings->string_set) { + case ETH_SS_TEST: + strings->len = 0; + break; + + default: + return -EINVAL; } - else { - return "1000Mbps/half-duplex"; + + return EOK; + } + + case ETHTOOL_TEST: { + struct ethtool_test *test_params = data; +#if ENET_SELFTEST + if ((test_params->flags & ETH_TEST_FL_OFFLINE) != 0) { + const struct selftest_params params = { + .module = "enet", + .netif = netif, + .crcStripped = true, + .verbose = ENET_DEBUG != 0, + .setup = enet_selftestSetup, + .teardown = enet_selftestTeardown + }; + err = physelftest(¶ms); + if (err < 0) { + test_params->flags |= ETH_TEST_FL_FAILED; + } + return EOK; } +#endif + return -EOPNOTSUPP; + } + + case ETHTOOL_GLOOPBACK: { + struct ethtool_value *value = data; + + value->data = ephy_getLoopback(&state->phy); + + return EOK; + } + + case ETHTOOL_SLOOPBACK: { + struct ethtool_value *value = data; + + int err = ephy_setLoopback(&state->phy, value->data); + if (err < 0) { + value->data = ETH_PHY_LOOPBACK_SET_FAILED; + } + + return EOK; + } + + case ETHTOOL_GLINKSETTINGS: { + struct ethtool_link_settings *link_settings = data; + + /* the caller expects us to return the real value */ + if (link_settings->link_mode_masks_nwords != ENET_LINK_MODE_MASKS_NWORDS) { + link_settings->link_mode_masks_nwords = -ENET_LINK_MODE_MASKS_NWORDS; + return EOK; + } + + int duplex; + uint64_t speed = ephy_linkSpeed(&state->phy, &duplex); + + link_settings->speed = speed & 0xffffffff; + link_settings->duplex = duplex; + link_settings->port = ENET_PORT; + link_settings->phy_address = state->phy.addr; + link_settings->autoneg = ephy_getAN(&state->phy); + link_settings->mdio_support = ENET_MDIO_SUPPORTED(state->phy.addr); + /* FIXME: MDI info */ + + /* layout of link_mode_masks: + * uint32_t map_supported[link_mode_masks_nwords]; + * uint32_t map_advertising[link_mode_masks_nwords]; + * uint32_t map_lp_advertising[link_mode_masks_nwords]; + */ + link_settings->link_mode_masks[0] = ENET_SUPPORTED; + link_settings->link_mode_masks[1] = ephy_getAdv(&state->phy); + + return EOK; + } + default: - return "unrecognized"; + return -EOPNOTSUPP; } } @@ -1707,6 +1744,7 @@ static netif_driver_t enet_drv = { .state_align = _Alignof(enet_state_t), .name = "enet", .media = enet_media, + .do_ethtool_ioctl = enet_ethtoolIoctl, }; diff --git a/drivers/physelftest.c b/drivers/physelftest.c new file mode 100644 index 00000000..974b2f67 --- /dev/null +++ b/drivers/physelftest.c @@ -0,0 +1,170 @@ +/* + * Phoenix-RTOS --- networking stack + * + * PHY selftest routine + * + * Copyright 2025 Phoenix Systems + * Author: Marek Białowąs, Julian Uziembło + * + * %LICENSE% + */ +#include +#include +#include + +#include "lwip/err.h" +#include "lwip/pbuf.h" +#include "lwip/netif.h" + +#include "physelftest.h" +#include "res-create.h" + +#define _TP_DST "dddddd" +#define _TP_SRC "ssssss" +#define _TP_ETHTYPE "\x05\xDD" /* eth frame type 0x05DD is undefined */ +#define _TP_10DIG "0123456789" +#define TEST_PACKET _TP_DST _TP_SRC _TP_ETHTYPE \ + _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG _TP_10DIG +#define TEST_PACKET_LEN (sizeof((TEST_PACKET)) - 1) +#define CRC_LEN 4 + +#define PHYSELFTEST_RESOURCES(s) &(s).rx_lock, 2, ~0x1 + +#define physelftest_printf(common, fmt, ...) printf("lwip: %s: " fmt "\n", (common).params->module, ##__VA_ARGS__) +#define physelftest_debug_printf(common, fmt, ...) \ + do { \ + if ((common).params->verbose) { \ + physelftest_printf(common, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + + +static struct { + handle_t rx_lock; + handle_t rx_cond; + enum { rx_invalid = -1, + rx_no_packet, + rx_success } rx_status; + const struct selftest_params *params; +} test_common; + + +/* self-test RX input function */ +static err_t physelftest_netifInput(struct pbuf *p, struct netif *netif) +{ + uint8_t buf[TEST_PACKET_LEN]; /* used only if pbuf is fragmented (should not happen) */ + bool is_valid_pkt = true; + const size_t expected_len = TEST_PACKET_LEN + ETH_PAD_SIZE + (test_common.params->crcStripped ? 0 : CRC_LEN); + + /* verify contents */ + if (p->len != expected_len) { + physelftest_debug_printf(test_common, "self-test RX: invalid packet length"); + physelftest_debug_printf(test_common, "expected: %zuB", expected_len); + physelftest_debug_printf(test_common, "actual: %uB", p->len); + is_valid_pkt = false; + } + uint8_t *data = pbuf_get_contiguous(p, buf, sizeof(buf), TEST_PACKET_LEN, ETH_PAD_SIZE); + if (data == NULL || memcmp(TEST_PACKET, data, TEST_PACKET_LEN) != 0) { + if (test_common.params->verbose) { + if (data == NULL) { + data = p->payload; + } + physelftest_printf(test_common, "self-test RX: invalid packet contents"); + + physelftest_printf(test_common, "expected:"); + for (int i = 0; i < TEST_PACKET_LEN; i++) { + if (i != 0 && i % 16 == 0) { + printf("\n"); + } + printf("%02x ", (uint8_t)TEST_PACKET[i]); + } + printf("\n"); + + physelftest_printf(test_common, "actual:"); + for (int i = 0; i < p->len; i++) { + if (i != 0 && i % 16 == 0) { + printf("\n"); + } + printf("%02x ", data[i]); + } + printf("\n"); + } + is_valid_pkt = false; + } + pbuf_free(p); + + mutexLock(test_common.rx_lock); + test_common.rx_status = is_valid_pkt ? rx_success : rx_invalid; + mutexUnlock(test_common.rx_lock); + condBroadcast(test_common.rx_cond); + + return ERR_OK; +} + + +/* MACPHY self-test procedure (internal loopback) */ +int physelftest(const struct selftest_params *params) +{ + test_common.params = params; + int err = params->setup(params->netif->state); + if (err < 0) { + return err; + } + + err = create_mutexcond_bulk(PHYSELFTEST_RESOURCES(test_common)); + if (err != 0) { + return err; + } + + /* override netif->input */ + netif_input_fn old_input = params->netif->input; + params->netif->input = physelftest_netifInput; + + int ret = 0; + do { + struct pbuf *p = pbuf_alloc(PBUF_RAW, TEST_PACKET_LEN + ETH_PAD_SIZE, PBUF_RAM); + memset(p->payload, 0, ETH_PAD_SIZE); + pbuf_take_at(p, TEST_PACKET, TEST_PACKET_LEN, ETH_PAD_SIZE); + + /* try to send and receive packets */ + mutexLock(test_common.rx_lock); + test_common.rx_status = rx_no_packet; + if (params->netif->linkoutput(params->netif, p) != ERR_OK) { /* frees pbuf internally */ + physelftest_printf(test_common, "failed to send test packet"); + ret = -1; + mutexUnlock(test_common.rx_lock); + break; + } + + err = 0; + while ((err != -ETIME) && (test_common.rx_status == rx_no_packet)) { + /* TX -> RX takes ~4ms, wait for 100ms just to be sure */ + err = condWait(test_common.rx_cond, test_common.rx_lock, 100 * 1000); + } + mutexUnlock(test_common.rx_lock); + + if ((err < 0) || (test_common.rx_status != 1)) { + physelftest_debug_printf(test_common, "Test failed: state->selfTest.rx_valid=%d, %s (%d)", + test_common.rx_status, strerror(-err), err); + ret = -1; + } + else { + physelftest_debug_printf(test_common, "Test passed"); + } + /* successfully received */ + } while (0); + + /* destroy selftest resources */ + (void)resourceDestroy(test_common.rx_cond); + (void)resourceDestroy(test_common.rx_lock); + + /* restore normal mode */ + params->netif->input = old_input; + + err = params->teardown(params->netif->state); + if (err < 0) { + ret = err; + } + + return ret; +} diff --git a/drivers/physelftest.h b/drivers/physelftest.h new file mode 100644 index 00000000..ae217f96 --- /dev/null +++ b/drivers/physelftest.h @@ -0,0 +1,31 @@ +/* + * Phoenix-RTOS --- networking stack + * + * PHY selftest routine + * + * Copyright 2025 Phoenix Systems + * Author: Marek Białowąs, Julian Uziembło + * + * %LICENSE% + */ +#ifndef NET_PHYSELFTEST_H_ +#define NET_PHYSELFTEST_H_ + +#include +#include + + +struct selftest_params { + const char *module; + struct netif *netif; + int (*setup)(void *arg); + int (*teardown)(void *arg); + bool verbose; + bool crcStripped; +}; + + +int physelftest(const struct selftest_params *params); + + +#endif /* NET_PHYSELFTEST_H_ */ diff --git a/drivers/rtl8139cp.c b/drivers/rtl8139cp.c index 1f6fb044..50bace53 100644 --- a/drivers/rtl8139cp.c +++ b/drivers/rtl8139cp.c @@ -11,6 +11,7 @@ #include "arch/cc.h" #include "lwip/etharp.h" #include "netif-driver.h" +#include "physelftest.h" #include "physmmap.h" #include "bdring.h" #include "pci.h" @@ -27,6 +28,8 @@ #include +#include +#include #include #include #include @@ -42,6 +45,9 @@ #define RTL_TX_RING_SIZE 64 #define DEBUG_RINGS 0 +#define RTL_DEBUG 0 +#define RTL_SELFTEST 1 + typedef struct { volatile struct rtl_regs *mmio; @@ -393,11 +399,165 @@ static int rtl_netifInit(struct netif *netif, char *cfg) } +static int rtl_getLoopback(rtl_priv_t *state) +{ + uint32_t tcr = state->mmio->TCR; + + switch ((tcr >> 17) & 0x3) { + case 0x0: + return ETH_PHY_LOOPBACK_DISABLED; + + case 0x3: + return ETH_PHY_LOOPBACK_ENABLED; + + default: + return -EIO; + } +} + + +static int rtl_setLoopback(rtl_priv_t *state, bool enable) +{ + uint32_t tcr = state->mmio->TCR; + + if (enable) { + tcr |= (0x3 << 17); + } + else { + tcr &= ~(0x3 << 17); + } + + state->mmio->TCR = tcr; + + if (state->mmio->TCR != tcr) { + return -1; + } + + return 0; +} + + +#if RTL_SELFTEST +static uint32_t old_rcr; + + +static int rtl_selftestSetup(void *arg) +{ + rtl_priv_t *state = arg; + + if (rtl_setLoopback(state, true) < 0) { + (void)rtl_setLoopback(state, false); + return -1; + } + + old_rcr = state->mmio->RCR; + /* accept faulty frames */ + state->mmio->RCR |= (1u << 16) | 0x3f; + + return 0; +} + + +static int rtl_selftestTeardown(void *arg) +{ + rtl_priv_t *state = arg; + + state->mmio->RCR = old_rcr; + + return rtl_setLoopback(state, false); +} +#endif + + +static int rtl_ethtoolIoctl(struct netif *netif, void *data) +{ + int err; + rtl_priv_t *state = netif->state; + uint32_t cmd = *(uint32_t *)data; + + switch (cmd) { + case ETHTOOL_GSSET_INFO: { + struct ethtool_sset_info *info = data; + uint64_t outmask = 0; + + if ((info->sset_mask & (1ull << ETH_SS_TEST)) != 0) { + info->data[ETH_SS_TEST] = 0; /* number_of_strings * ETH_GSTRING_LEN */ + outmask |= 1ull << ETH_SS_TEST; + } + + info->sset_mask = outmask; + + return EOK; + } + + case ETHTOOL_GSTRINGS: { + struct ethtool_gstrings *strings = data; + + switch (strings->string_set) { + case ETH_SS_TEST: + strings->len = 0; + break; + + default: + return -EINVAL; + } + + return EOK; + } + + case ETHTOOL_TEST: { +#if RTL_SELFTEST + struct ethtool_test *test = data; + if ((test->flags & ETH_TEST_FL_OFFLINE) != 0) { + const struct selftest_params params = { + .module = "rtl", + .netif = netif, + .crcStripped = false, + .verbose = RTL_DEBUG != 0, + .setup = rtl_selftestSetup, + .teardown = rtl_selftestTeardown, + }; + err = physelftest(¶ms); + if (err < 0) { + test->flags |= ETH_TEST_FL_FAILED; + } + return EOK; + } +#endif + return -EOPNOTSUPP; + } + + case ETHTOOL_GLOOPBACK: { + struct ethtool_value *value = data; + err = rtl_getLoopback(state); + if (err < 0) { + return err; + } + value->data = err; + return EOK; + } + + case ETHTOOL_SLOOPBACK: { + struct ethtool_value *value = data; + err = rtl_setLoopback(state, value->data); + if (err < 0) { + value->data = ETH_PHY_LOOPBACK_SET_FAILED; + } + return EOK; + } + + default: + return -EOPNOTSUPP; + } +} + + static netif_driver_t rtl_drv = { .init = rtl_netifInit, .state_sz = sizeof(rtl_priv_t), .state_align = _Alignof(rtl_priv_t), .name = "rtl", + .do_ethtool_ioctl = rtl_ethtoolIoctl }; diff --git a/include/netif-driver.h b/include/netif-driver.h index 2a1c0357..bf21e3c3 100644 --- a/include/netif-driver.h +++ b/include/netif-driver.h @@ -34,6 +34,7 @@ typedef struct netif_driver_ { size_t state_sz, state_align; const char *name; const char *(*media)(struct netif *netif); + int (*do_ethtool_ioctl)(struct netif *netif, void *data); } netif_driver_t; diff --git a/lib-lwip b/lib-lwip index 2c200127..fba0dace 160000 --- a/lib-lwip +++ b/lib-lwip @@ -1 +1 @@ -Subproject commit 2c2001272f5c4f7c0a4b658bece9cf03b3344ad2 +Subproject commit fba0dacedaaf8e9c2c408da02df120d858686824 diff --git a/port/sockets.c b/port/sockets.c index 5f7c5a01..4ea60551 100644 --- a/port/sockets.c +++ b/port/sockets.c @@ -42,6 +42,7 @@ #endif #include "netif.h" +#include "netif-driver.h" #include "route.h" #include "ipsec-api.h" @@ -583,35 +584,39 @@ static int socket_ioctl(int sock, unsigned long request, const void *in_data, vo return -EOPNOTSUPP; case SIOCGIFCONF: { - struct ifconf *ifconf = (struct ifconf *)out_data; - int maxlen = ifconf->ifc_len; - struct ifreq *ifreq = ifconf->ifc_req; + struct ifconf *ifconf = out_data; + struct ifreq *ifreq = NULL; struct netif *netif; + const int maxlen = (ifconf->ifc_buf != NULL) ? ifconf->ifc_len : 0; - ifconf->ifc_len = 0; - if (ifreq == NULL) { - /* WARN: it is legal to pass NULL here (we should return the length sufficient for whole response) */ - return -EFAULT; + if (maxlen != 0) { + ifreq = (struct ifreq *)((char *)out_data + sizeof(struct ifconf)); + ifconf->ifc_req = ifreq; } - memset(ifreq, 0, maxlen); + ifconf->ifc_len = 0; for (netif = netif_list; netif != NULL; netif = netif->next) { - if (ifconf->ifc_len + sizeof(struct ifreq) > maxlen) { + if (maxlen != 0 && ifconf->ifc_len + sizeof(struct ifreq) > maxlen) { break; } + struct ifreq current_ifr = { 0 }; /* LWiP name is only 2 chars, we have to manually add the number */ - snprintf(ifreq->ifr_name, IFNAMSIZ, "%c%c%d", netif->name[0], netif->name[1], netif->num); + snprintf(current_ifr.ifr_name, IFNAMSIZ, "%c%c%d", netif->name[0], netif->name[1], netif->num); - struct sockaddr_in *sin = (struct sockaddr_in *)&ifreq->ifr_addr; + struct sockaddr_in *sin = (struct sockaddr_in *)¤t_ifr.ifr_addr; inet_addr_from_ip4addr(&sin->sin_addr, netif_ip4_addr(netif)); ifconf->ifc_len += sizeof(struct ifreq); - ifreq += 1; + if (ifreq != NULL) { + memcpy(ifreq, ¤t_ifr, sizeof(struct ifreq)); + ifreq += 1; + } } return EOK; } + /** ROUTING * net and host routing is supported and multiple gateways with ethernet interfaces * TODO: support metric @@ -623,31 +628,55 @@ static int socket_ioctl(int sock, unsigned long request, const void *in_data, vo return -EFAULT; } - struct netif *interface = netif_find(rt->rt_dev); - int ret = EOK; + char *rt_dev = (char *)in_data + sizeof(struct rtentry); + if (rt_dev == NULL) { + return -EINVAL; + } + struct netif *interface = netif_find(rt_dev); + int ret = EOK; if (interface == NULL) { - free(rt->rt_dev); - free(rt); return -ENXIO; } switch (request) { - case SIOCADDRT: ret = route_add(interface, rt); break; + case SIOCDELRT: ret = route_del(interface, rt); break; - } - free(rt->rt_dev); - free(rt); + default: + break; + } return ret; } + case SIOCETHTOOL: { + struct ifreq *ifr = out_data; + void *ethtool_data = (char *)out_data + sizeof(struct ifreq); + + struct netif *interface = netif_find(ifr->ifr_name); + if (interface == NULL) { + return -ENXIO; + } + + if (strncmp(interface->name, "lo", 2) == 0) { + /* loopback doesn't have driver */ + return -EOPNOTSUPP; + } + + netif_driver_t *drv = netif_driver(interface); + if (drv == NULL || drv->do_ethtool_ioctl == NULL) { + return -EOPNOTSUPP; + } + + return drv->do_ethtool_ioctl(interface, ethtool_data); + } + #if LWIP_IPV6 case SIOCGIFDSTADDR_IN6: case SIOCGIFNETMASK_IN6: