From: Raju Lakkaraju <raju.lakkar...@microchip.com> Add the PHY Netlink interface driver to implement the cable diagnostics of Microsemi Ethernet PHYs.
Signed-off-by: Raju Lakkaraju <raju.lakkar...@microchip.com> --- drivers/net/phy/Kconfig | 6 ++ drivers/net/phy/Makefile | 1 + drivers/net/phy/phy.c | 17 ++++ drivers/net/phy/phy_netlink.c | 226 ++++++++++++++++++++++++++++++++++++++++++ include/linux/phy.h | 88 ++++++++++++++++ include/linux/phy_netlink.h | 48 +++++++++ 6 files changed, 386 insertions(+) create mode 100644 drivers/net/phy/phy_netlink.c create mode 100644 include/linux/phy_netlink.h diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index db5645b..a105058 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -406,6 +406,12 @@ config MICROCHIP_T1_PHY ---help--- Supports the LAN87XX PHYs. +config PHY_NETLINK + tristate "PHY Netlink Interface" + depends on NET + ---help--- + Supports the PHY netlink interface. + config MICROSEMI_PHY tristate "Microsemi PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index bac339e..96aad9b 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o obj-$(CONFIG_MICREL_PHY) += micrel.o obj-$(CONFIG_MICROCHIP_PHY) += microchip.o obj-$(CONFIG_MICROCHIP_T1_PHY) += microchip_t1.o +obj-$(CONFIG_PHY_NETLINK) += phy_netlink.o obj-$(CONFIG_MICROSEMI_PHY) += mscc.o obj-$(CONFIG_NATIONAL_PHY) += national.o obj-$(CONFIG_NXP_TJA11XX_PHY) += nxp-tja11xx.o diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d915076..29b2921 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1251,3 +1251,20 @@ int phy_ethtool_nway_reset(struct net_device *ndev) return phy_restart_aneg(phydev); } EXPORT_SYMBOL(phy_ethtool_nway_reset); + +int phy_cabdiag_request(struct net_device *ndev, struct phy_cabdiag_req *cfg) +{ + struct phy_device *phydev = ndev->phydev; + + if (!phydev) + return -ENODEV; + + if (!phydev->drv) + return -EIO; + + if (phydev->drv->request_cable_diag) + return phydev->drv->request_cable_diag(phydev, cfg); + + return -EOPNOTSUPP; +} +EXPORT_SYMBOL(phy_cabdiag_request); diff --git a/drivers/net/phy/phy_netlink.c b/drivers/net/phy/phy_netlink.c new file mode 100644 index 0000000..896c1b2 --- /dev/null +++ b/drivers/net/phy/phy_netlink.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Microchip Technology + +#include <linux/bitmap.h> +#include <linux/rtnetlink.h> +#include <net/sock.h> +#include <linux/phy.h> +#include <linux/netdevice.h> +#include <net/genetlink.h> +#include <net/sock.h> +#include <linux/phy_netlink.h> + +static struct genl_family phynl_genl_family; + +static const struct nla_policy cabdiag_op_policy[CABDIAG_OP_ATTR_MAX + 1] = { + [CABDIAG_OP_ATTR_NOOP] = { .type = NLA_UNSPEC }, + [CABDIAG_OP_ATTR_REQUEST] = { .type = NLA_NESTED }, +}; + +static const struct nla_policy +cabdiag_pair_sta_policy[CABDIAG_PAIR_STA_ATTR_MAX + 1] = { + [CABDIAG_PAIR_STA_ATTR_NOOP] = { .type = NLA_UNSPEC }, + [CABDIAG_PAIR_STA_ATTR_STATUS] = { .type = NLA_U8 }, + [CABDIAG_PAIR_STA_ATTR_LENGTH] = { .type = NLA_U8 }, +}; + +static const struct nla_policy cabdiag_req_policy[CABDIAG_REQ_ATTR_MAX + 1] = { + [CABDIAG_REQ_ATTR_NOOP] = { .type = NLA_UNSPEC }, + [CABDIAG_REQ_ATTR_IFNAME] = { .type = NLA_STRING }, + [CABDIAG_REQ_ATTR_OP_STA] = { .type = NLA_U8 }, + [CABDIAG_REQ_ATTR_PAIRS_MASK] = { .type = NLA_U8 }, + [CABDIAG_REQ_ATTR_TIMEOUT] = { .type = NLA_U8 }, + [CABDIAG_REQ_ATTR_STATUS] = + NLA_POLICY_NESTED_ARRAY(cabdiag_pair_sta_policy), +}; + +static int phynl_interface_check(struct genl_info *info, const char *ifname) +{ + struct net *net = genl_info_net(info); + struct net_device *netdev; + int ret = 0; + + netdev = dev_get_by_name(net, ifname); + if (netdev) { + if (netdev->phydev) { + if (!netdev->phydev->drv) { + pr_err("PHYNL: netdev->phydev->drv == NULL\n"); + ret = -EINVAL; + goto out_dev_put; + } + } else { + pr_err("PHYNL: netdev->phydev == NULL\n"); + ret = -EINVAL; + goto out_dev_put; + } + } else { + pr_err("PHYNL: can't find net device\n"); + return -ENODEV; + } + +out_dev_put: + dev_put(netdev); + + return ret; +} + +static int phynl_cabdiag_request(struct genl_info *info, struct nlattr *nest) +{ + struct nlattr *tb[CABDIAG_REQ_ATTR_MAX + 1]; + struct net *net = genl_info_net(info); + struct phy_cabdiag_req cfg; + struct net_device *netdev; + struct sk_buff *msg; + __be64 val_64; + char *ifname; + void *hdr; + int ret; + + if (!nest) { + pr_err("PHYNL: nelink message error\n"); + return -EINVAL; + } + + memset(&cfg, 0, sizeof(struct phy_cabdiag_req)); + ret = nla_parse_nested(tb, CABDIAG_REQ_ATTR_MAX, nest, + cabdiag_req_policy, info->extack); + if (ret < 0) + return ret; + + ifname = (char *)nla_data(tb[CABDIAG_REQ_ATTR_IFNAME]); + if (tb[CABDIAG_REQ_ATTR_IFNAME]) { + ret = phynl_interface_check(info, ifname); + if (ret < 0) + return ret; + } + + if (tb[CABDIAG_REQ_ATTR_PAIRS_MASK]) + cfg.pairs_bitmask = + nla_get_u8(tb[CABDIAG_REQ_ATTR_PAIRS_MASK]); + + if (tb[CABDIAG_REQ_ATTR_TIMEOUT]) + cfg.timeout_cnt = nla_get_u8(tb[CABDIAG_REQ_ATTR_TIMEOUT]); + + netdev = dev_get_by_name(net, ifname); + + /* Initialize to default status */ + cfg.pairs[CABDIAG_PAIR_A].length = CD_LENGTH_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_B].length = CD_LENGTH_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_C].length = CD_LENGTH_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_D].length = CD_LENGTH_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_A].status = CD_STATUS_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_B].status = CD_STATUS_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_C].status = CD_STATUS_NOT_SUPPORTED; + cfg.pairs[CABDIAG_PAIR_D].status = CD_STATUS_NOT_SUPPORTED; + ret = phy_cabdiag_request(netdev, &cfg); + if (ret < 0) + goto out_dev_put; + + /* Reply back */ + msg = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) { + ret = -ENOMEM; + goto out_dev_put; + } + + hdr = genlmsg_put_reply(msg, info, &phynl_genl_family, 0, + PHYNL_CMD_CABDIAG); + + nest = nla_nest_start(msg, CABDIAG_OP_ATTR_REQUEST | NLA_F_NESTED); + nla_put_u8(msg, CABDIAG_REQ_ATTR_OP_STA, cfg.op_status); + nla_put_u8(msg, CABDIAG_REQ_ATTR_PAIRS_MASK, cfg.pairs_bitmask); + nla_put_u8(msg, CABDIAG_REQ_ATTR_TIMEOUT, cfg.timeout_cnt); + val_64 = ((__be64)cfg.pairs[CABDIAG_PAIR_D].length << 56 | + (__be64)cfg.pairs[CABDIAG_PAIR_D].status << 48 | + (__be64)cfg.pairs[CABDIAG_PAIR_C].length << 40 | + (__be64)cfg.pairs[CABDIAG_PAIR_C].status << 32 | + (__be64)cfg.pairs[CABDIAG_PAIR_B].length << 24 | + (__be64)cfg.pairs[CABDIAG_PAIR_B].status << 16 | + (__be64)cfg.pairs[CABDIAG_PAIR_A].length << 8 | + cfg.pairs[CABDIAG_PAIR_A].status); + nla_put_be64(msg, CABDIAG_REQ_ATTR_STATUS, val_64, 4); + nla_nest_end(msg, nest); + + genlmsg_end(msg, hdr); + genlmsg_reply(msg, info); + +out_dev_put: + if (netdev) + dev_put(netdev); + + return ret; +} + +static int phynl_cabdiag_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *tb[CABDIAG_OP_ATTR_MAX + 1]; + int ret; + + ret = genlmsg_parse(info->nlhdr, &phynl_genl_family, tb, + CABDIAG_OP_ATTR_MAX, cabdiag_op_policy, + info ? info->extack : NULL); + if (ret < 0) { + pr_err("PHYNL: genlmsg_parse returns:%d\n", ret); + return ret; + } + + if (tb[CABDIAG_OP_ATTR_REQUEST]) { + ret = phynl_cabdiag_request(info, tb[CABDIAG_OP_ATTR_REQUEST]); + if (ret < 0) + return ret; + } + + return 0; +} + +#define PHYNL_GENL_NAME "phynl" +#define PHYNL_GENL_VERSION 1 +#define PHYNL_MCGRP_MONITOR "phy_monitor" +static struct genl_multicast_group phynl_genl_mcgroups[] = { + { + .name = PHYNL_MCGRP_MONITOR + }, +}; + +static const struct genl_ops phynl_genl_ops[] = { + { + .cmd = PHYNL_CMD_CABDIAG, + .doit = phynl_cabdiag_doit, + }, +}; + +static struct genl_family phynl_genl_family = { + .hdrsize = 0, + .name = PHYNL_GENL_NAME, + .version = PHYNL_GENL_VERSION, + .netnsok = true, + .parallel_ops = false, + .ops = phynl_genl_ops, + .n_ops = ARRAY_SIZE(phynl_genl_ops), + .mcgrps = phynl_genl_mcgroups, + .n_mcgrps = ARRAY_SIZE(phynl_genl_mcgroups), +}; + +static int __init phynl_genl_init(void) +{ + int ret; + + ret = genl_register_family(&phynl_genl_family); + if (ret < 0) + panic("PHYNL: Could not register genetlink family\n"); + + return 0; +} + +static void __exit phynl_genl_exit(void) +{ + genl_unregister_family(&phynl_genl_family); +} + +module_init(phynl_genl_init); +module_exit(phynl_genl_exit); + +MODULE_DESCRIPTION("PHY Netlink Interface driver"); +MODULE_AUTHOR("Nagaraju Lakkaraju"); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/phy.h b/include/linux/phy.h index d0af7d3..b045ddf 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -320,6 +320,91 @@ struct phy_c45_device_ids { u32 device_ids[8]; }; +#define CABDIAG_PAIR_A_MASK 0x0001 +#define CABDIAG_PAIR_B_MASK 0x0002 +#define CABDIAG_PAIR_C_MASK 0x0004 +#define CABDIAG_PAIR_D_MASK 0x0008 +enum phy_cabdiag_pairs { + CABDIAG_PAIR_A, + CABDIAG_PAIR_B, + CABDIAG_PAIR_C, + CABDIAG_PAIR_D, + + CABDIAG_PAIR_CNT, +}; + +/** + * phy_cabdiag_op_sta - Cable diagnostics operational status + * CD_REQ_NONE - Unknown + * CD_REQ_INVALID_PAIR_MASK - Invalid pair mask + * CD_REQ_INVALID_TIMEOUT - Invalid Timeout counter + * CD_REQ_REJECTED_BUSY - Previous command execution busy + * CD_STATUS_SUCCESS - Operation success + * CD_STATUS_FAILED_TIMEOUT - Failed due to Timeout + * CD_STATUS_FAILED_INVALID - Status results invalid + */ +enum phy_cabdiag_op_sta { + CD_REQ_NONE, + CD_REQ_INVALID_PAIR_MASK, + CD_REQ_INVALID_TIMEOUT, + CD_REQ_REJECTED_BUSY, + CD_STATUS_SUCCESS, + CD_STATUS_FAILED_TIMEOUT, + CD_STATUS_FAILED_INVALID +}; + +#define CD_LENGTH_NOT_SUPPORTED 0xFF +#define CD_STATUS_NOT_SUPPORTED 0xFF + +/** + * phy_cabdiag_sta_code - Cable diagnostics fault codes + * b0000 - 0100: Individual cable pair fault codes + * b10xx : Cross pair short to pair 'xx' + * b11xx : Abnormal Cross pair coupling with pair 'xx' + * xx : b00 - Pair-A, + * : b01 - Pair-B, + * : b10 - Pair-C, + * : b11 - Pair-D + */ +enum phy_cabdiag_sta_code { + CD_NORMAL_PAIR = 0x0, + CD_OPEN_PAIR = 0x1, + CD_SHORTED_PAIR = 0x2, + CD_ABNORMAL_TERMINATION = 0x4, + CD_X_PAIR_SHORT_TO_PAIR_A = 0x8, + CD_X_PAIR_SHORT_TO_PAIR_B = 0x9, + CD_X_PAIR_SHORT_TO_PAIR_C = 0xA, + CD_X_PAIR_SHORT_TO_PAIR_D = 0xB, + CD_ABNORMAL_X_PAIR_A = 0xC, + CD_ABNORMAL_X_PAIR_B = 0xD, + CD_ABNORMAL_X_PAIR_C = 0xE, + CD_ABNORMAL_X_PAIR_D = 0xF +}; + +/** + * struct phy_cabdiag_pair_sta - PHY diagnostics pair status + * @status: Fault codes + * @length: Length in meters + */ +struct phy_cabdiag_pair_sta { + enum phy_cabdiag_sta_code status; + u8 length; +}; + +/** + * struct phy_cabdiag_req - PHY diagnostics request/status command + * @op_status: Cable Diagnostics Operational status + * @pairs_bitmask: Allows settings diag request just for a pair + * @timeout_cnt: Timeout count (i.e. Multiples of driver wait time) + * @pairs[CABDIAG_PAIR_CNT]: Status of 4 pairs of cable + */ +struct phy_cabdiag_req { + enum phy_cabdiag_op_sta op_status; + u8 pairs_bitmask; + u8 timeout_cnt; + struct phy_cabdiag_pair_sta pairs[CABDIAG_PAIR_CNT]; +}; + /* phy_device: An instance of a PHY * * drv: Pointer to the driver for this PHY instance @@ -621,6 +706,8 @@ struct phy_driver { struct ethtool_tunable *tuna, const void *data); int (*set_loopback)(struct phy_device *dev, bool enable); + int (*request_cable_diag)(struct phy_device *dev, + struct phy_cabdiag_req *cfg); }; #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \ struct phy_driver, mdiodrv) @@ -1170,6 +1257,7 @@ int phy_ethtool_get_link_ksettings(struct net_device *ndev, int phy_ethtool_set_link_ksettings(struct net_device *ndev, const struct ethtool_link_ksettings *cmd); int phy_ethtool_nway_reset(struct net_device *ndev); +int phy_cabdiag_request(struct net_device *ndev, struct phy_cabdiag_req *cfg); #if IS_ENABLED(CONFIG_PHYLIB) int __init mdio_bus_init(void); diff --git a/include/linux/phy_netlink.h b/include/linux/phy_netlink.h new file mode 100644 index 0000000..2823d67 --- /dev/null +++ b/include/linux/phy_netlink.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +#ifndef __PHY_NETLINK_H +#define __PHY_NETLINK_H + +#include <linux/rtnetlink.h> +#include <linux/phy.h> +#include <linux/netdevice.h> +#include <net/genetlink.h> + +/* Genetlink setup */ +enum { + PHYNL_CMD_NOOP, + PHYNL_CMD_CABDIAG, + + __PHYNL_CMD_CNT, + PHYNL_CMD_MAX = (__PHYNL_CMD_CNT - 1) +}; + +enum { + CABDIAG_OP_ATTR_NOOP, + CABDIAG_OP_ATTR_REQUEST, + + __CABDIAG_OP_ATTR_CNT, + CABDIAG_OP_ATTR_MAX = (__CABDIAG_OP_ATTR_CNT - 1) +}; + +enum { + CABDIAG_PAIR_STA_ATTR_NOOP, + CABDIAG_PAIR_STA_ATTR_STATUS, + CABDIAG_PAIR_STA_ATTR_LENGTH, + + __CABDIAG_PAIR_STA_ATTR_CNT, + CABDIAG_PAIR_STA_ATTR_MAX = (__CABDIAG_PAIR_STA_ATTR_CNT - 1) +}; + +enum { + CABDIAG_REQ_ATTR_NOOP, + CABDIAG_REQ_ATTR_IFNAME, + CABDIAG_REQ_ATTR_OP_STA, + CABDIAG_REQ_ATTR_PAIRS_MASK, + CABDIAG_REQ_ATTR_TIMEOUT, + CABDIAG_REQ_ATTR_STATUS, + + __CABDIAG_REQ_ATTR_CNT, + CABDIAG_REQ_ATTR_MAX = (__CABDIAG_REQ_ATTR_CNT - 1) +}; + +#endif /* __PHY_NETLINK_H */ -- 2.7.4