Merge tag 'mlx5-updates-2023-05-31' of git://git.kernel.org/pub/scm/linux/kernel...
authorJakub Kicinski <kuba@kernel.org>
Mon, 5 Jun 2023 22:42:22 +0000 (15:42 -0700)
committerJakub Kicinski <kuba@kernel.org>
Mon, 5 Jun 2023 22:42:22 +0000 (15:42 -0700)
Saeed Mahameed says:

====================
mlx5-updates-2023-05-31

net/mlx5: Support 4 ports VF LAG, part 1/2

This series continues the series[1] "Support 4 ports HCAs LAG mode"
by Mark Bloch. This series adds support for 4 ports VF LAG (single FDB
E-Switch).

This series of patches focuses on refactoring different sections of the
code that make assumptions about VF LAG supporting only two ports. For
instance, it assumes that each device can only have one peer.

Patches 1-5:
- Refactor ETH handling of TC rules of eswitches with peers.
Patch 6:
- Refactors peer miss group table.
Patches 7-9:
- Refactor single FDB E-Switch creation.
Patch 10:
- Refactor the DR layer.
Patches 11-14:
- Refactors devcom layer.

Next series will refactor LAG layer and enable 4 ports VF LAG.
This series specifically allows HCAs with 4 ports to create a VF LAG
with only 4 ports. It is not possible to create a VF LAG with 2 or 3
ports using HCAs that have 4 ports.

Currently, the Merged E-Switch feature only supports HCAs with 2 ports.
However, upcoming patches will introduce support for HCAs with 4 ports.

In order to activate VF LAG a user can execute:

devlink dev eswitch set pci/0000:08:00.0 mode switchdev
devlink dev eswitch set pci/0000:08:00.1 mode switchdev
devlink dev eswitch set pci/0000:08:00.2 mode switchdev
devlink dev eswitch set pci/0000:08:00.3 mode switchdev
ip link add name bond0 type bond
ip link set dev bond0 type bond mode 802.3ad
ip link set dev eth2 master bond0
ip link set dev eth3 master bond0
ip link set dev eth4 master bond0
ip link set dev eth5 master bond0

Where eth2, eth3, eth4 and eth5 are net-interfaces of pci/0000:08:00.0
pci/0000:08:00.1 pci/0000:08:00.2 pci/0000:08:00.3 respectively.

User can verify LAG state and type via debugfs:
/sys/kernel/debug/mlx5/0000\:08\:00.0/lag/state
/sys/kernel/debug/mlx5/0000\:08\:00.0/lag/type

[1]
https://lore.kernel.org/netdev/20220510055743.118828-1-saeedm@nvidia.com/

* tag 'mlx5-updates-2023-05-31' of git://git.kernel.org/pub/scm/linux/kernel/git/saeed/linux:
  net/mlx5: Devcom, extend mlx5_devcom_send_event to work with more than two devices
  net/mlx5: Devcom, introduce devcom_for_each_peer_entry
  net/mlx5: E-switch, mark devcom as not ready when all eswitches are unpaired
  net/mlx5: Devcom, Rename paired to ready
  net/mlx5: DR, handle more than one peer domain
  net/mlx5: E-switch, generalize shared FDB creation
  net/mlx5: E-switch, Handle multiple master egress rules
  net/mlx5: E-switch, refactor FDB miss rule add/remove
  net/mlx5: E-switch, enlarge peer miss group table
  net/mlx5e: Handle offloads flows per peer
  net/mlx5e: en_tc, re-factor query route port
  net/mlx5e: rep, store send to vport rules per peer
  net/mlx5e: tc, Refactor peer add/del flow
  net/mlx5e: en_tc, Extend peer flows to a list
====================

Link: https://lore.kernel.org/r/20230602191301.47004-1-saeed@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
34 files changed:
MAINTAINERS
drivers/net/dsa/sja1105/sja1105_mdio.c
drivers/net/ethernet/altera/Kconfig
drivers/net/ethernet/altera/altera_tse_main.c
drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
drivers/net/ethernet/stmicro/stmmac/Kconfig
drivers/net/ethernet/stmicro/stmmac/Makefile
drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c [deleted file]
drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h [deleted file]
drivers/net/ethernet/stmicro/stmmac/common.h
drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
drivers/net/mdio/Kconfig
drivers/net/mdio/Makefile
drivers/net/mdio/mdio-regmap.c [new file with mode: 0644]
drivers/net/pcs/Kconfig
drivers/net/pcs/Makefile
drivers/net/pcs/pcs-altera-tse.c [deleted file]
drivers/net/pcs/pcs-xpcs.c
include/linux/mdio/mdio-regmap.h [new file with mode: 0644]
include/linux/pcs-altera-tse.h [deleted file]
include/linux/pcs/pcs-xpcs.h
include/linux/phy.h
include/linux/ref_tracker.h
lib/ref_tracker.c
lib/test_ref_tracker.c
net/core/dev.c
net/core/net_namespace.c
net/ipv6/addrconf.c
tools/net/ynl/ynl-gen-c.py
tools/testing/selftests/drivers/net/mlxsw/egress_vid_classification.sh
tools/testing/selftests/drivers/net/mlxsw/ingress_rif_conf_1d.sh
tools/testing/selftests/net/forwarding/router_bridge_vlan.sh

index 288d9a5..081eb65 100644 (file)
@@ -909,13 +909,6 @@ L: netdev@vger.kernel.org
 S:     Maintained
 F:     drivers/net/ethernet/altera/
 
-ALTERA TSE PCS
-M:     Maxime Chevallier <maxime.chevallier@bootlin.com>
-L:     netdev@vger.kernel.org
-S:     Supported
-F:     drivers/net/pcs/pcs-altera-tse.c
-F:     include/linux/pcs-altera-tse.h
-
 ALTERA UART/JTAG UART SERIAL DRIVERS
 M:     Tobias Klauser <tklauser@distanz.ch>
 L:     linux-serial@vger.kernel.org
@@ -12844,6 +12837,13 @@ F:     Documentation/devicetree/bindings/net/ieee802154/mcr20a.txt
 F:     drivers/net/ieee802154/mcr20a.c
 F:     drivers/net/ieee802154/mcr20a.h
 
+MDIO REGMAP DRIVER
+M:     Maxime Chevallier <maxime.chevallier@bootlin.com>
+L:     netdev@vger.kernel.org
+S:     Maintained
+F:     drivers/net/mdio/mdio-regmap.c
+F:     include/linux/mdio/mdio-regmap.h
+
 MEASUREMENT COMPUTING CIO-DAC IIO DRIVER
 M:     William Breathitt Gray <william.gray@linaro.org>
 L:     linux-iio@vger.kernel.org
index 01f1cb7..833e55e 100644 (file)
@@ -400,7 +400,6 @@ static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
        }
 
        for (port = 0; port < ds->num_ports; port++) {
-               struct mdio_device *mdiodev;
                struct dw_xpcs *xpcs;
 
                if (dsa_is_unused_port(ds, port))
@@ -410,13 +409,7 @@ static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
                    priv->phy_mode[port] != PHY_INTERFACE_MODE_2500BASEX)
                        continue;
 
-               mdiodev = mdio_device_create(bus, port);
-               if (IS_ERR(mdiodev)) {
-                       rc = PTR_ERR(mdiodev);
-                       goto out_pcs_free;
-               }
-
-               xpcs = xpcs_create(mdiodev, priv->phy_mode[port]);
+               xpcs = xpcs_create_mdiodev(bus, port, priv->phy_mode[port]);
                if (IS_ERR(xpcs)) {
                        rc = PTR_ERR(xpcs);
                        goto out_pcs_free;
@@ -434,7 +427,6 @@ out_pcs_free:
                if (!priv->xpcs[port])
                        continue;
 
-               mdio_device_free(priv->xpcs[port]->mdiodev);
                xpcs_destroy(priv->xpcs[port]);
                priv->xpcs[port] = NULL;
        }
@@ -457,7 +449,6 @@ static void sja1105_mdiobus_pcs_unregister(struct sja1105_private *priv)
                if (!priv->xpcs[port])
                        continue;
 
-               mdio_device_free(priv->xpcs[port]->mdiodev);
                xpcs_destroy(priv->xpcs[port]);
                priv->xpcs[port] = NULL;
        }
index dd7fd41..93533ba 100644 (file)
@@ -5,6 +5,8 @@ config ALTERA_TSE
        select PHYLIB
        select PHYLINK
        select PCS_ALTERA_TSE
+       select MDIO_REGMAP
+       select REGMAP_MMIO
        help
          This driver supports the Altera Triple-Speed (TSE) Ethernet MAC.
 
index 190ff1b..d866c0f 100644 (file)
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mii.h>
+#include <linux/mdio/mdio-regmap.h>
 #include <linux/netdevice.h>
 #include <linux/of_device.h>
 #include <linux/of_mdio.h>
 #include <linux/of_net.h>
 #include <linux/of_platform.h>
-#include <linux/pcs-altera-tse.h>
+#include <linux/pcs-lynx.h>
 #include <linux/phy.h>
 #include <linux/platform_device.h>
+#include <linux/regmap.h>
 #include <linux/skbuff.h>
 #include <asm/cacheflush.h>
 
@@ -1132,13 +1134,16 @@ static int request_and_map(struct platform_device *pdev, const char *name,
 static int altera_tse_probe(struct platform_device *pdev)
 {
        const struct of_device_id *of_id = NULL;
+       struct regmap_config pcs_regmap_cfg;
        struct altera_tse_private *priv;
+       struct mdio_regmap_config mrc;
        struct resource *control_port;
+       struct regmap *pcs_regmap;
        struct resource *dma_res;
        struct resource *pcs_res;
+       struct mii_bus *pcs_bus;
        struct net_device *ndev;
        void __iomem *descmap;
-       int pcs_reg_width = 2;
        int ret = -ENODEV;
 
        ndev = alloc_etherdev(sizeof(struct altera_tse_private));
@@ -1255,12 +1260,32 @@ static int altera_tse_probe(struct platform_device *pdev)
         * address space, but if it's not the case, we fallback to the mdiophy0
         * from the MAC's address space
         */
-       ret = request_and_map(pdev, "pcs", &pcs_res,
-                             &priv->pcs_base);
+       ret = request_and_map(pdev, "pcs", &pcs_res, &priv->pcs_base);
        if (ret) {
+               /* If we can't find a dedicated resource for the PCS, fallback
+                * to the internal PCS, that has a different address stride
+                */
                priv->pcs_base = priv->mac_dev + tse_csroffs(mdio_phy0);
-               pcs_reg_width = 4;
+               pcs_regmap_cfg.reg_bits = 32;
+               /* Values are MDIO-like values, on 16 bits */
+               pcs_regmap_cfg.val_bits = 16;
+               pcs_regmap_cfg.reg_shift = REGMAP_UPSHIFT(2);
+       } else {
+               pcs_regmap_cfg.reg_bits = 16;
+               pcs_regmap_cfg.val_bits = 16;
+               pcs_regmap_cfg.reg_shift = REGMAP_UPSHIFT(1);
+       }
+
+       /* Create a regmap for the PCS so that it can be used by the PCS driver */
+       pcs_regmap = devm_regmap_init_mmio(&pdev->dev, priv->pcs_base,
+                                          &pcs_regmap_cfg);
+       if (IS_ERR(pcs_regmap)) {
+               ret = PTR_ERR(pcs_regmap);
+               goto err_free_netdev;
        }
+       mrc.regmap = pcs_regmap;
+       mrc.parent = &pdev->dev;
+       mrc.valid_addr = 0x0;
 
        /* Rx IRQ */
        priv->rx_irq = platform_get_irq_byname(pdev, "rx_irq");
@@ -1384,7 +1409,18 @@ static int altera_tse_probe(struct platform_device *pdev)
                         (unsigned long) control_port->start, priv->rx_irq,
                         priv->tx_irq);
 
-       priv->pcs = alt_tse_pcs_create(ndev, priv->pcs_base, pcs_reg_width);
+       snprintf(mrc.name, MII_BUS_ID_SIZE, "%s-pcs-mii", ndev->name);
+       pcs_bus = devm_mdio_regmap_register(&pdev->dev, &mrc);
+       if (IS_ERR(pcs_bus)) {
+               ret = PTR_ERR(pcs_bus);
+               goto err_init_pcs;
+       }
+
+       priv->pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
+       if (IS_ERR(priv->pcs)) {
+               ret = PTR_ERR(priv->pcs);
+               goto err_init_pcs;
+       }
 
        priv->phylink_config.dev = &ndev->dev;
        priv->phylink_config.type = PHYLINK_NETDEV;
@@ -1407,12 +1443,13 @@ static int altera_tse_probe(struct platform_device *pdev)
        if (IS_ERR(priv->phylink)) {
                dev_err(&pdev->dev, "failed to create phylink\n");
                ret = PTR_ERR(priv->phylink);
-               goto err_init_phy;
+               goto err_init_phylink;
        }
 
        return 0;
-
-err_init_phy:
+err_init_phylink:
+       lynx_pcs_destroy(priv->pcs);
+err_init_pcs:
        unregister_netdev(ndev);
 err_register_netdev:
        netif_napi_del(&priv->napi);
@@ -1433,6 +1470,8 @@ static int altera_tse_remove(struct platform_device *pdev)
        altera_tse_mdio_destroy(ndev);
        unregister_netdev(ndev);
        phylink_destroy(priv->phylink);
+       lynx_pcs_destroy(priv->pcs);
+
        free_netdev(ndev);
 
        return 0;
index 4a73e2f..7304e8a 100644 (file)
@@ -96,8 +96,8 @@ struct mlxsw_sp_rif_subport {
 struct mlxsw_sp_rif_ipip_lb {
        struct mlxsw_sp_rif common;
        struct mlxsw_sp_rif_ipip_lb_config lb_config;
-       u16 ul_vr_id; /* Reserved for Spectrum-2. */
-       u16 ul_rif_id; /* Reserved for Spectrum. */
+       u16 ul_vr_id;   /* Spectrum-1. */
+       u16 ul_rif_id;  /* Spectrum-2+. */
 };
 
 struct mlxsw_sp_rif_params_ipip_lb {
@@ -748,10 +748,11 @@ static bool mlxsw_sp_vr_is_used(const struct mlxsw_sp_vr *vr)
 
 static struct mlxsw_sp_vr *mlxsw_sp_vr_find_unused(struct mlxsw_sp *mlxsw_sp)
 {
+       int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
        struct mlxsw_sp_vr *vr;
        int i;
 
-       for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
+       for (i = 0; i < max_vrs; i++) {
                vr = &mlxsw_sp->router->vrs[i];
                if (!mlxsw_sp_vr_is_used(vr))
                        return vr;
@@ -792,12 +793,13 @@ static u32 mlxsw_sp_fix_tb_id(u32 tb_id)
 static struct mlxsw_sp_vr *mlxsw_sp_vr_find(struct mlxsw_sp *mlxsw_sp,
                                            u32 tb_id)
 {
+       int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
        struct mlxsw_sp_vr *vr;
        int i;
 
        tb_id = mlxsw_sp_fix_tb_id(tb_id);
 
-       for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
+       for (i = 0; i < max_vrs; i++) {
                vr = &mlxsw_sp->router->vrs[i];
                if (mlxsw_sp_vr_is_used(vr) && vr->tb_id == tb_id)
                        return vr;
@@ -959,6 +961,7 @@ static int mlxsw_sp_vrs_lpm_tree_replace(struct mlxsw_sp *mlxsw_sp,
                                         struct mlxsw_sp_fib *fib,
                                         struct mlxsw_sp_lpm_tree *new_tree)
 {
+       int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
        enum mlxsw_sp_l3proto proto = fib->proto;
        struct mlxsw_sp_lpm_tree *old_tree;
        u8 old_id, new_id = new_tree->id;
@@ -968,7 +971,7 @@ static int mlxsw_sp_vrs_lpm_tree_replace(struct mlxsw_sp *mlxsw_sp,
        old_tree = mlxsw_sp->router->lpm.proto_trees[proto];
        old_id = old_tree->id;
 
-       for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
+       for (i = 0; i < max_vrs; i++) {
                vr = &mlxsw_sp->router->vrs[i];
                if (!mlxsw_sp_vr_lpm_tree_should_replace(vr, proto, old_id))
                        continue;
@@ -7298,9 +7301,10 @@ static void mlxsw_sp_vr_fib_flush(struct mlxsw_sp *mlxsw_sp,
 
 static void mlxsw_sp_router_fib_flush(struct mlxsw_sp *mlxsw_sp)
 {
+       int max_vrs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS);
        int i, j;
 
-       for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_VRS); i++) {
+       for (i = 0; i < max_vrs; i++) {
                struct mlxsw_sp_vr *vr = &mlxsw_sp->router->vrs[i];
 
                if (!mlxsw_sp_vr_is_used(vr))
@@ -7699,9 +7703,10 @@ static struct mlxsw_sp_rif *
 mlxsw_sp_rif_find_by_dev(const struct mlxsw_sp *mlxsw_sp,
                         const struct net_device *dev)
 {
+       int max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS);
        int i;
 
-       for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS); i++)
+       for (i = 0; i < max_rifs; i++)
                if (mlxsw_sp->router->rifs[i] &&
                    mlxsw_sp->router->rifs[i]->dev == dev)
                        return mlxsw_sp->router->rifs[i];
@@ -9724,7 +9729,7 @@ mlxsw_sp1_rif_ipip_lb_configure(struct mlxsw_sp_rif *rif,
        struct mlxsw_sp_vr *ul_vr;
        int err;
 
-       ul_vr = mlxsw_sp_vr_get(mlxsw_sp, ul_tb_id, NULL);
+       ul_vr = mlxsw_sp_vr_get(mlxsw_sp, ul_tb_id, extack);
        if (IS_ERR(ul_vr))
                return PTR_ERR(ul_vr);
 
@@ -9923,7 +9928,7 @@ mlxsw_sp2_rif_ipip_lb_configure(struct mlxsw_sp_rif *rif,
        struct mlxsw_sp_rif *ul_rif;
        int err;
 
-       ul_rif = mlxsw_sp_ul_rif_get(mlxsw_sp, ul_tb_id, NULL);
+       ul_rif = mlxsw_sp_ul_rif_get(mlxsw_sp, ul_tb_id, extack);
        if (IS_ERR(ul_rif))
                return PTR_ERR(ul_rif);
 
@@ -10041,11 +10046,12 @@ err_rifs_table_init:
 
 static void mlxsw_sp_rifs_fini(struct mlxsw_sp *mlxsw_sp)
 {
+       int max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS);
        struct devlink *devlink = priv_to_devlink(mlxsw_sp->core);
        int i;
 
        WARN_ON_ONCE(atomic_read(&mlxsw_sp->router->rifs_count));
-       for (i = 0; i < MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS); i++)
+       for (i = 0; i < max_rifs; i++)
                WARN_ON_ONCE(mlxsw_sp->router->rifs[i]);
 
        devl_resource_occ_get_unregister(devlink, MLXSW_SP_RESOURCE_RIFS);
index 5f5a997..5583f0b 100644 (file)
@@ -158,6 +158,9 @@ config DWMAC_SOCFPGA
        default ARCH_INTEL_SOCFPGA
        depends on OF && (ARCH_INTEL_SOCFPGA || COMPILE_TEST)
        select MFD_SYSCON
+       select MDIO_REGMAP
+       select REGMAP_MMIO
+       select PCS_LYNX
        help
          Support for ethernet controller on Altera SOCFPGA
 
index 8738fdb..7dd3d38 100644 (file)
@@ -35,7 +35,7 @@ obj-$(CONFIG_DWMAC_IMX8)      += dwmac-imx.o
 obj-$(CONFIG_DWMAC_TEGRA)      += dwmac-tegra.o
 obj-$(CONFIG_DWMAC_VISCONTI)   += dwmac-visconti.o
 stmmac-platform-objs:= stmmac_platform.o
-dwmac-altr-socfpga-objs := altr_tse_pcs.o dwmac-socfpga.o
+dwmac-altr-socfpga-objs := dwmac-socfpga.o
 
 obj-$(CONFIG_STMMAC_PCI)       += stmmac-pci.o
 obj-$(CONFIG_DWMAC_INTEL)      += dwmac-intel.o
diff --git a/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c b/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.c
deleted file mode 100644 (file)
index 00f6d34..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/* Copyright Altera Corporation (C) 2016. All rights reserved.
- *
- * Author: Tien Hock Loh <thloh@altera.com>
- */
-
-#include <linux/mfd/syscon.h>
-#include <linux/of.h>
-#include <linux/of_address.h>
-#include <linux/of_net.h>
-#include <linux/phy.h>
-#include <linux/regmap.h>
-#include <linux/reset.h>
-#include <linux/stmmac.h>
-
-#include "stmmac.h"
-#include "stmmac_platform.h"
-#include "altr_tse_pcs.h"
-
-#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII       0
-#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII          BIT(1)
-#define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII           BIT(2)
-#define SYSMGR_EMACGRP_CTRL_PHYSEL_WIDTH               2
-#define SYSMGR_EMACGRP_CTRL_PHYSEL_MASK                        GENMASK(1, 0)
-
-#define TSE_PCS_CONTROL_AN_EN_MASK                     BIT(12)
-#define TSE_PCS_CONTROL_REG                            0x00
-#define TSE_PCS_CONTROL_RESTART_AN_MASK                        BIT(9)
-#define TSE_PCS_CTRL_AUTONEG_SGMII                     0x1140
-#define TSE_PCS_IF_MODE_REG                            0x28
-#define TSE_PCS_LINK_TIMER_0_REG                       0x24
-#define TSE_PCS_LINK_TIMER_1_REG                       0x26
-#define TSE_PCS_SIZE                                   0x40
-#define TSE_PCS_STATUS_AN_COMPLETED_MASK               BIT(5)
-#define TSE_PCS_STATUS_LINK_MASK                       0x0004
-#define TSE_PCS_STATUS_REG                             0x02
-#define TSE_PCS_SGMII_SPEED_1000                       BIT(3)
-#define TSE_PCS_SGMII_SPEED_100                                BIT(2)
-#define TSE_PCS_SGMII_SPEED_10                         0x0
-#define TSE_PCS_SW_RST_MASK                            0x8000
-#define TSE_PCS_PARTNER_ABILITY_REG                    0x0A
-#define TSE_PCS_PARTNER_DUPLEX_FULL                    0x1000
-#define TSE_PCS_PARTNER_DUPLEX_HALF                    0x0000
-#define TSE_PCS_PARTNER_DUPLEX_MASK                    0x1000
-#define TSE_PCS_PARTNER_SPEED_MASK                     GENMASK(11, 10)
-#define TSE_PCS_PARTNER_SPEED_1000                     BIT(11)
-#define TSE_PCS_PARTNER_SPEED_100                      BIT(10)
-#define TSE_PCS_PARTNER_SPEED_10                       0x0000
-#define TSE_PCS_PARTNER_SPEED_1000                     BIT(11)
-#define TSE_PCS_PARTNER_SPEED_100                      BIT(10)
-#define TSE_PCS_PARTNER_SPEED_10                       0x0000
-#define TSE_PCS_SGMII_SPEED_MASK                       GENMASK(3, 2)
-#define TSE_PCS_SGMII_LINK_TIMER_0                     0x0D40
-#define TSE_PCS_SGMII_LINK_TIMER_1                     0x0003
-#define TSE_PCS_SW_RESET_TIMEOUT                       100
-#define TSE_PCS_USE_SGMII_AN_MASK                      BIT(1)
-#define TSE_PCS_USE_SGMII_ENA                          BIT(0)
-#define TSE_PCS_IF_USE_SGMII                           0x03
-
-#define AUTONEGO_LINK_TIMER                            20
-
-static int tse_pcs_reset(void __iomem *base, struct tse_pcs *pcs)
-{
-       int counter = 0;
-       u16 val;
-
-       val = readw(base + TSE_PCS_CONTROL_REG);
-       val |= TSE_PCS_SW_RST_MASK;
-       writew(val, base + TSE_PCS_CONTROL_REG);
-
-       while (counter < TSE_PCS_SW_RESET_TIMEOUT) {
-               val = readw(base + TSE_PCS_CONTROL_REG);
-               val &= TSE_PCS_SW_RST_MASK;
-               if (val == 0)
-                       break;
-               counter++;
-               udelay(1);
-       }
-       if (counter >= TSE_PCS_SW_RESET_TIMEOUT) {
-               dev_err(pcs->dev, "PCS could not get out of sw reset\n");
-               return -ETIMEDOUT;
-       }
-
-       return 0;
-}
-
-int tse_pcs_init(void __iomem *base, struct tse_pcs *pcs)
-{
-       int ret = 0;
-
-       writew(TSE_PCS_IF_USE_SGMII, base + TSE_PCS_IF_MODE_REG);
-
-       writew(TSE_PCS_CTRL_AUTONEG_SGMII, base + TSE_PCS_CONTROL_REG);
-
-       writew(TSE_PCS_SGMII_LINK_TIMER_0, base + TSE_PCS_LINK_TIMER_0_REG);
-       writew(TSE_PCS_SGMII_LINK_TIMER_1, base + TSE_PCS_LINK_TIMER_1_REG);
-
-       ret = tse_pcs_reset(base, pcs);
-       if (ret == 0)
-               writew(SGMII_ADAPTER_ENABLE,
-                      pcs->sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
-
-       return ret;
-}
-
-static void pcs_link_timer_callback(struct tse_pcs *pcs)
-{
-       u16 val = 0;
-       void __iomem *tse_pcs_base = pcs->tse_pcs_base;
-       void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base;
-
-       val = readw(tse_pcs_base + TSE_PCS_STATUS_REG);
-       val &= TSE_PCS_STATUS_LINK_MASK;
-
-       if (val != 0) {
-               dev_dbg(pcs->dev, "Adapter: Link is established\n");
-               writew(SGMII_ADAPTER_ENABLE,
-                      sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
-       } else {
-               mod_timer(&pcs->aneg_link_timer, jiffies +
-                         msecs_to_jiffies(AUTONEGO_LINK_TIMER));
-       }
-}
-
-static void auto_nego_timer_callback(struct tse_pcs *pcs)
-{
-       u16 val = 0;
-       u16 speed = 0;
-       u16 duplex = 0;
-       void __iomem *tse_pcs_base = pcs->tse_pcs_base;
-       void __iomem *sgmii_adapter_base = pcs->sgmii_adapter_base;
-
-       val = readw(tse_pcs_base + TSE_PCS_STATUS_REG);
-       val &= TSE_PCS_STATUS_AN_COMPLETED_MASK;
-
-       if (val != 0) {
-               dev_dbg(pcs->dev, "Adapter: Auto Negotiation is completed\n");
-               val = readw(tse_pcs_base + TSE_PCS_PARTNER_ABILITY_REG);
-               speed = val & TSE_PCS_PARTNER_SPEED_MASK;
-               duplex = val & TSE_PCS_PARTNER_DUPLEX_MASK;
-
-               if (speed == TSE_PCS_PARTNER_SPEED_10 &&
-                   duplex == TSE_PCS_PARTNER_DUPLEX_FULL)
-                       dev_dbg(pcs->dev,
-                               "Adapter: Link Partner is Up - 10/Full\n");
-               else if (speed == TSE_PCS_PARTNER_SPEED_100 &&
-                        duplex == TSE_PCS_PARTNER_DUPLEX_FULL)
-                       dev_dbg(pcs->dev,
-                               "Adapter: Link Partner is Up - 100/Full\n");
-               else if (speed == TSE_PCS_PARTNER_SPEED_1000 &&
-                        duplex == TSE_PCS_PARTNER_DUPLEX_FULL)
-                       dev_dbg(pcs->dev,
-                               "Adapter: Link Partner is Up - 1000/Full\n");
-               else if (speed == TSE_PCS_PARTNER_SPEED_10 &&
-                        duplex == TSE_PCS_PARTNER_DUPLEX_HALF)
-                       dev_err(pcs->dev,
-                               "Adapter does not support Half Duplex\n");
-               else if (speed == TSE_PCS_PARTNER_SPEED_100 &&
-                        duplex == TSE_PCS_PARTNER_DUPLEX_HALF)
-                       dev_err(pcs->dev,
-                               "Adapter does not support Half Duplex\n");
-               else if (speed == TSE_PCS_PARTNER_SPEED_1000 &&
-                        duplex == TSE_PCS_PARTNER_DUPLEX_HALF)
-                       dev_err(pcs->dev,
-                               "Adapter does not support Half Duplex\n");
-               else
-                       dev_err(pcs->dev,
-                               "Adapter: Invalid Partner Speed and Duplex\n");
-
-               if (duplex == TSE_PCS_PARTNER_DUPLEX_FULL &&
-                   (speed == TSE_PCS_PARTNER_SPEED_10 ||
-                    speed == TSE_PCS_PARTNER_SPEED_100 ||
-                    speed == TSE_PCS_PARTNER_SPEED_1000))
-                       writew(SGMII_ADAPTER_ENABLE,
-                              sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
-       } else {
-               val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
-               val |= TSE_PCS_CONTROL_RESTART_AN_MASK;
-               writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG);
-
-               tse_pcs_reset(tse_pcs_base, pcs);
-               mod_timer(&pcs->aneg_link_timer, jiffies +
-                         msecs_to_jiffies(AUTONEGO_LINK_TIMER));
-       }
-}
-
-static void aneg_link_timer_callback(struct timer_list *t)
-{
-       struct tse_pcs *pcs = from_timer(pcs, t, aneg_link_timer);
-
-       if (pcs->autoneg == AUTONEG_ENABLE)
-               auto_nego_timer_callback(pcs);
-       else if (pcs->autoneg == AUTONEG_DISABLE)
-               pcs_link_timer_callback(pcs);
-}
-
-void tse_pcs_fix_mac_speed(struct tse_pcs *pcs, struct phy_device *phy_dev,
-                          unsigned int speed)
-{
-       void __iomem *tse_pcs_base = pcs->tse_pcs_base;
-       u32 val;
-
-       pcs->autoneg = phy_dev->autoneg;
-
-       if (phy_dev->autoneg == AUTONEG_ENABLE) {
-               val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
-               val |= TSE_PCS_CONTROL_AN_EN_MASK;
-               writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG);
-
-               val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG);
-               val |= TSE_PCS_USE_SGMII_AN_MASK;
-               writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG);
-
-               val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
-               val |= TSE_PCS_CONTROL_RESTART_AN_MASK;
-
-               tse_pcs_reset(tse_pcs_base, pcs);
-
-               timer_setup(&pcs->aneg_link_timer, aneg_link_timer_callback,
-                           0);
-               mod_timer(&pcs->aneg_link_timer, jiffies +
-                         msecs_to_jiffies(AUTONEGO_LINK_TIMER));
-       } else if (phy_dev->autoneg == AUTONEG_DISABLE) {
-               val = readw(tse_pcs_base + TSE_PCS_CONTROL_REG);
-               val &= ~TSE_PCS_CONTROL_AN_EN_MASK;
-               writew(val, tse_pcs_base + TSE_PCS_CONTROL_REG);
-
-               val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG);
-               val &= ~TSE_PCS_USE_SGMII_AN_MASK;
-               writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG);
-
-               val = readw(tse_pcs_base + TSE_PCS_IF_MODE_REG);
-               val &= ~TSE_PCS_SGMII_SPEED_MASK;
-
-               switch (speed) {
-               case 1000:
-                       val |= TSE_PCS_SGMII_SPEED_1000;
-                       break;
-               case 100:
-                       val |= TSE_PCS_SGMII_SPEED_100;
-                       break;
-               case 10:
-                       val |= TSE_PCS_SGMII_SPEED_10;
-                       break;
-               default:
-                       return;
-               }
-               writew(val, tse_pcs_base + TSE_PCS_IF_MODE_REG);
-
-               tse_pcs_reset(tse_pcs_base, pcs);
-
-               timer_setup(&pcs->aneg_link_timer, aneg_link_timer_callback,
-                           0);
-               mod_timer(&pcs->aneg_link_timer, jiffies +
-                         msecs_to_jiffies(AUTONEGO_LINK_TIMER));
-       }
-}
diff --git a/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h b/drivers/net/ethernet/stmicro/stmmac/altr_tse_pcs.h
deleted file mode 100644 (file)
index 694ac25..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/* Copyright Altera Corporation (C) 2016. All rights reserved.
- *
- * Author: Tien Hock Loh <thloh@altera.com>
- */
-
-#ifndef __TSE_PCS_H__
-#define __TSE_PCS_H__
-
-#include <linux/phy.h>
-#include <linux/timer.h>
-
-#define SGMII_ADAPTER_CTRL_REG         0x00
-#define SGMII_ADAPTER_ENABLE           0x0000
-#define SGMII_ADAPTER_DISABLE          0x0001
-
-struct tse_pcs {
-       struct device *dev;
-       void __iomem *tse_pcs_base;
-       void __iomem *sgmii_adapter_base;
-       struct timer_list aneg_link_timer;
-       int autoneg;
-};
-
-int tse_pcs_init(void __iomem *base, struct tse_pcs *pcs);
-void tse_pcs_fix_mac_speed(struct tse_pcs *pcs, struct phy_device *phy_dev,
-                          unsigned int speed);
-
-#endif /* __TSE_PCS_H__ */
index 4ad692c..52c5ec5 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/stmmac.h>
 #include <linux/phy.h>
 #include <linux/pcs/pcs-xpcs.h>
+#include <linux/pcs-lynx.h>
 #include <linux/module.h>
 #if IS_ENABLED(CONFIG_VLAN_8021Q)
 #define STMMAC_VLAN_TAG_USED
@@ -519,6 +520,7 @@ struct mac_device_info {
        const struct stmmac_tc_ops *tc;
        const struct stmmac_mmc_ops *mmc;
        struct dw_xpcs *xpcs;
+       struct phylink_pcs *lynx_pcs; /* Lynx external PCS */
        struct mii_regs mii;    /* MII register Addresses */
        struct mac_link link;
        void __iomem *pcsr;     /* vpointer to device CSRs */
index 6ee0503..e399fcc 100644 (file)
 #include <linux/of_net.h>
 #include <linux/phy.h>
 #include <linux/regmap.h>
+#include <linux/mdio/mdio-regmap.h>
 #include <linux/reset.h>
 #include <linux/stmmac.h>
 
 #include "stmmac.h"
 #include "stmmac_platform.h"
 
-#include "altr_tse_pcs.h"
-
 #define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_GMII_MII 0x0
 #define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RGMII 0x1
 #define SYSMGR_EMACGRP_CTRL_PHYSEL_ENUM_RMII 0x2
 #define EMAC_SPLITTER_CTRL_SPEED_100           0x3
 #define EMAC_SPLITTER_CTRL_SPEED_1000          0x0
 
+#define SGMII_ADAPTER_CTRL_REG         0x00
+#define SGMII_ADAPTER_ENABLE           0x0000
+#define SGMII_ADAPTER_DISABLE          0x0001
+
 struct socfpga_dwmac;
 struct socfpga_dwmac_ops {
        int (*set_phy_mode)(struct socfpga_dwmac *dwmac_priv);
@@ -50,16 +53,18 @@ struct socfpga_dwmac {
        struct reset_control *stmmac_rst;
        struct reset_control *stmmac_ocp_rst;
        void __iomem *splitter_base;
+       void __iomem *tse_pcs_base;
+       void __iomem *sgmii_adapter_base;
        bool f2h_ptp_ref_clk;
-       struct tse_pcs pcs;
        const struct socfpga_dwmac_ops *ops;
+       struct mdio_device *pcs_mdiodev;
 };
 
 static void socfpga_dwmac_fix_mac_speed(void *priv, unsigned int speed)
 {
        struct socfpga_dwmac *dwmac = (struct socfpga_dwmac *)priv;
        void __iomem *splitter_base = dwmac->splitter_base;
-       void __iomem *sgmii_adapter_base = dwmac->pcs.sgmii_adapter_base;
+       void __iomem *sgmii_adapter_base = dwmac->sgmii_adapter_base;
        struct device *dev = dwmac->dev;
        struct net_device *ndev = dev_get_drvdata(dev);
        struct phy_device *phy_dev = ndev->phydev;
@@ -89,11 +94,9 @@ static void socfpga_dwmac_fix_mac_speed(void *priv, unsigned int speed)
                writel(val, splitter_base + EMAC_SPLITTER_CTRL_REG);
        }
 
-       if (phy_dev && sgmii_adapter_base) {
+       if (phy_dev && sgmii_adapter_base)
                writew(SGMII_ADAPTER_ENABLE,
                       sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
-               tse_pcs_fix_mac_speed(&dwmac->pcs, phy_dev, speed);
-       }
 }
 
 static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *dev)
@@ -183,11 +186,11 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *
                                goto err_node_put;
                        }
 
-                       dwmac->pcs.sgmii_adapter_base =
+                       dwmac->sgmii_adapter_base =
                            devm_ioremap_resource(dev, &res_sgmii_adapter);
 
-                       if (IS_ERR(dwmac->pcs.sgmii_adapter_base)) {
-                               ret = PTR_ERR(dwmac->pcs.sgmii_adapter_base);
+                       if (IS_ERR(dwmac->sgmii_adapter_base)) {
+                               ret = PTR_ERR(dwmac->sgmii_adapter_base);
                                goto err_node_put;
                        }
                }
@@ -205,11 +208,11 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device *
                                goto err_node_put;
                        }
 
-                       dwmac->pcs.tse_pcs_base =
+                       dwmac->tse_pcs_base =
                            devm_ioremap_resource(dev, &res_tse_pcs);
 
-                       if (IS_ERR(dwmac->pcs.tse_pcs_base)) {
-                               ret = PTR_ERR(dwmac->pcs.tse_pcs_base);
+                       if (IS_ERR(dwmac->tse_pcs_base)) {
+                               ret = PTR_ERR(dwmac->tse_pcs_base);
                                goto err_node_put;
                        }
                }
@@ -235,6 +238,13 @@ static int socfpga_get_plat_phymode(struct socfpga_dwmac *dwmac)
        return priv->plat->interface;
 }
 
+static void socfpga_sgmii_config(struct socfpga_dwmac *dwmac, bool enable)
+{
+       u16 val = enable ? SGMII_ADAPTER_ENABLE : SGMII_ADAPTER_DISABLE;
+
+       writew(val, dwmac->sgmii_adapter_base + SGMII_ADAPTER_CTRL_REG);
+}
+
 static int socfpga_set_phy_mode_common(int phymode, u32 *val)
 {
        switch (phymode) {
@@ -310,12 +320,8 @@ static int socfpga_gen5_set_phy_mode(struct socfpga_dwmac *dwmac)
         */
        reset_control_deassert(dwmac->stmmac_ocp_rst);
        reset_control_deassert(dwmac->stmmac_rst);
-       if (phymode == PHY_INTERFACE_MODE_SGMII) {
-               if (tse_pcs_init(dwmac->pcs.tse_pcs_base, &dwmac->pcs) != 0) {
-                       dev_err(dwmac->dev, "Unable to initialize TSE PCS");
-                       return -EINVAL;
-               }
-       }
+       if (phymode == PHY_INTERFACE_MODE_SGMII)
+               socfpga_sgmii_config(dwmac, true);
 
        return 0;
 }
@@ -367,12 +373,8 @@ static int socfpga_gen10_set_phy_mode(struct socfpga_dwmac *dwmac)
         */
        reset_control_deassert(dwmac->stmmac_ocp_rst);
        reset_control_deassert(dwmac->stmmac_rst);
-       if (phymode == PHY_INTERFACE_MODE_SGMII) {
-               if (tse_pcs_init(dwmac->pcs.tse_pcs_base, &dwmac->pcs) != 0) {
-                       dev_err(dwmac->dev, "Unable to initialize TSE PCS");
-                       return -EINVAL;
-               }
-       }
+       if (phymode == PHY_INTERFACE_MODE_SGMII)
+               socfpga_sgmii_config(dwmac, true);
        return 0;
 }
 
@@ -386,6 +388,7 @@ static int socfpga_dwmac_probe(struct platform_device *pdev)
        struct net_device       *ndev;
        struct stmmac_priv      *stpriv;
        const struct socfpga_dwmac_ops *ops;
+       struct regmap_config pcs_regmap_cfg;
 
        ops = device_get_match_data(&pdev->dev);
        if (!ops) {
@@ -443,6 +446,44 @@ static int socfpga_dwmac_probe(struct platform_device *pdev)
        if (ret)
                goto err_dvr_remove;
 
+       memset(&pcs_regmap_cfg, 0, sizeof(pcs_regmap_cfg));
+       pcs_regmap_cfg.reg_bits = 16;
+       pcs_regmap_cfg.val_bits = 16;
+       pcs_regmap_cfg.reg_shift = REGMAP_UPSHIFT(1);
+
+       /* Create a regmap for the PCS so that it can be used by the PCS driver,
+        * if we have such a PCS
+        */
+       if (dwmac->tse_pcs_base) {
+               struct mdio_regmap_config mrc;
+               struct regmap *pcs_regmap;
+               struct mii_bus *pcs_bus;
+
+               pcs_regmap = devm_regmap_init_mmio(&pdev->dev, dwmac->tse_pcs_base,
+                                                  &pcs_regmap_cfg);
+               if (IS_ERR(pcs_regmap)) {
+                       ret = PTR_ERR(pcs_regmap);
+                       goto err_dvr_remove;
+               }
+
+               mrc.regmap = pcs_regmap;
+               mrc.parent = &pdev->dev;
+               mrc.valid_addr = 0x0;
+
+               snprintf(mrc.name, MII_BUS_ID_SIZE, "%s-pcs-mii", ndev->name);
+               pcs_bus = devm_mdio_regmap_register(&pdev->dev, &mrc);
+               if (IS_ERR(pcs_bus)) {
+                       ret = PTR_ERR(pcs_bus);
+                       goto err_dvr_remove;
+               }
+
+               stpriv->hw->lynx_pcs = lynx_pcs_create_mdiodev(pcs_bus, 0);
+               if (IS_ERR(stpriv->hw->lynx_pcs)) {
+                       ret = PTR_ERR(stpriv->hw->lynx_pcs);
+                       goto err_dvr_remove;
+               }
+       }
+
        return 0;
 
 err_dvr_remove:
index 52cab9d..fa07b0d 100644 (file)
@@ -937,10 +937,13 @@ static struct phylink_pcs *stmmac_mac_select_pcs(struct phylink_config *config,
 {
        struct stmmac_priv *priv = netdev_priv(to_net_dev(config->dev));
 
-       if (!priv->hw->xpcs)
-               return NULL;
+       if (priv->hw->xpcs)
+               return &priv->hw->xpcs->pcs;
+
+       if (priv->hw->lynx_pcs)
+               return priv->hw->lynx_pcs;
 
-       return &priv->hw->xpcs->pcs;
+       return NULL;
 }
 
 static void stmmac_mac_config(struct phylink_config *config, unsigned int mode,
@@ -3813,7 +3816,8 @@ static int __stmmac_open(struct net_device *dev,
        if (priv->hw->pcs != STMMAC_PCS_TBI &&
            priv->hw->pcs != STMMAC_PCS_RTBI &&
            (!priv->hw->xpcs ||
-            xpcs_get_an_mode(priv->hw->xpcs, mode) != DW_AN_C73)) {
+            xpcs_get_an_mode(priv->hw->xpcs, mode) != DW_AN_C73) &&
+           !priv->hw->lynx_pcs) {
                ret = stmmac_init_phy(dev);
                if (ret) {
                        netdev_err(priv->dev,
index 3db1cb0..c784a67 100644 (file)
@@ -665,6 +665,9 @@ int stmmac_mdio_unregister(struct net_device *ndev)
        if (priv->hw->xpcs)
                xpcs_destroy(priv->hw->xpcs);
 
+       if (priv->hw->lynx_pcs)
+               lynx_pcs_destroy(priv->hw->lynx_pcs);
+
        mdiobus_unregister(priv->mii);
        priv->mii->priv = NULL;
        mdiobus_free(priv->mii);
index 9ff2e6f..4a7a303 100644 (file)
@@ -185,6 +185,17 @@ config MDIO_IPQ8064
          This driver supports the MDIO interface found in the network
          interface units of the IPQ8064 SoC
 
+config MDIO_REGMAP
+       tristate
+       help
+         This driver allows using MDIO devices that are not sitting on a
+         regular MDIO bus, but still exposes the standard 802.3 register
+         layout. It's regmap-based so that it can be used on integrated,
+         memory-mapped PHYs, SPI PHYs and so on. A new virtual MDIO bus is
+         created, and its read/write operations are mapped to the underlying
+         regmap. Users willing to use this driver must explicitly select
+         REGMAP.
+
 config MDIO_THUNDER
        tristate "ThunderX SOCs MDIO buses"
        depends on 64BIT
index 7d4cb4c..1015f0d 100644 (file)
@@ -19,6 +19,7 @@ obj-$(CONFIG_MDIO_MOXART)             += mdio-moxart.o
 obj-$(CONFIG_MDIO_MSCC_MIIM)           += mdio-mscc-miim.o
 obj-$(CONFIG_MDIO_MVUSB)               += mdio-mvusb.o
 obj-$(CONFIG_MDIO_OCTEON)              += mdio-octeon.o
+obj-$(CONFIG_MDIO_REGMAP)              += mdio-regmap.o
 obj-$(CONFIG_MDIO_SUN4I)               += mdio-sun4i.o
 obj-$(CONFIG_MDIO_THUNDER)             += mdio-thunder.o
 obj-$(CONFIG_MDIO_XGENE)               += mdio-xgene.o
diff --git a/drivers/net/mdio/mdio-regmap.c b/drivers/net/mdio/mdio-regmap.c
new file mode 100644 (file)
index 0000000..8a742a8
--- /dev/null
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Driver for MMIO-Mapped MDIO devices. Some IPs expose internal PHYs or PCS
+ * within the MMIO-mapped area
+ *
+ * Copyright (C) 2023 Maxime Chevallier <maxime.chevallier@bootlin.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mdio/mdio-regmap.h>
+
+#define DRV_NAME "mdio-regmap"
+
+struct mdio_regmap_priv {
+       struct regmap *regmap;
+       u8 valid_addr;
+};
+
+static int mdio_regmap_read_c22(struct mii_bus *bus, int addr, int regnum)
+{
+       struct mdio_regmap_priv *ctx = bus->priv;
+       unsigned int val;
+       int ret;
+
+       if (ctx->valid_addr != addr)
+               return -ENODEV;
+
+       ret = regmap_read(ctx->regmap, regnum, &val);
+       if (ret < 0)
+               return ret;
+
+       return val;
+}
+
+static int mdio_regmap_write_c22(struct mii_bus *bus, int addr, int regnum,
+                                u16 val)
+{
+       struct mdio_regmap_priv *ctx = bus->priv;
+
+       if (ctx->valid_addr != addr)
+               return -ENODEV;
+
+       return regmap_write(ctx->regmap, regnum, val);
+}
+
+struct mii_bus *devm_mdio_regmap_register(struct device *dev,
+                                         const struct mdio_regmap_config *config)
+{
+       struct mdio_regmap_priv *mr;
+       struct mii_bus *mii;
+       int rc;
+
+       if (!config->parent)
+               return ERR_PTR(-EINVAL);
+
+       mii = devm_mdiobus_alloc_size(config->parent, sizeof(*mr));
+       if (!mii)
+               return ERR_PTR(-ENOMEM);
+
+       mr = mii->priv;
+       mr->regmap = config->regmap;
+       mr->valid_addr = config->valid_addr;
+
+       mii->name = DRV_NAME;
+       strscpy(mii->id, config->name, MII_BUS_ID_SIZE);
+       mii->parent = config->parent;
+       mii->read = mdio_regmap_read_c22;
+       mii->write = mdio_regmap_write_c22;
+
+       if (config->autoscan)
+               mii->phy_mask = ~BIT(config->valid_addr);
+       else
+               mii->phy_mask = ~0;
+
+       rc = devm_mdiobus_register(dev, mii);
+       if (rc) {
+               dev_err(config->parent, "Cannot register MDIO bus![%s] (%d)\n", mii->id, rc);
+               return ERR_PTR(rc);
+       }
+
+       return mii;
+}
+EXPORT_SYMBOL_GPL(devm_mdio_regmap_register);
+
+MODULE_DESCRIPTION("MDIO API over regmap");
+MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
+MODULE_LICENSE("GPL");
index 7c34fb7..87cf308 100644 (file)
@@ -33,10 +33,4 @@ config PCS_RZN1_MIIC
          on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
          pass-through mode for MII.
 
-config PCS_ALTERA_TSE
-       tristate
-       help
-         This module provides helper functions for the Altera Triple Speed
-         Ethernet SGMII PCS, that can be found on the Intel Socfpga family.
-
 endmenu
index 9b9afd6..ea662a7 100644 (file)
@@ -7,4 +7,3 @@ obj-$(CONFIG_PCS_XPCS)          += pcs_xpcs.o
 obj-$(CONFIG_PCS_LYNX)         += pcs-lynx.o
 obj-$(CONFIG_PCS_MTK_LYNXI)    += pcs-mtk-lynxi.o
 obj-$(CONFIG_PCS_RZN1_MIIC)    += pcs-rzn1-miic.o
-obj-$(CONFIG_PCS_ALTERA_TSE)   += pcs-altera-tse.o
diff --git a/drivers/net/pcs/pcs-altera-tse.c b/drivers/net/pcs/pcs-altera-tse.c
deleted file mode 100644 (file)
index d616749..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2022 Bootlin
- *
- * Maxime Chevallier <maxime.chevallier@bootlin.com>
- */
-
-#include <linux/netdevice.h>
-#include <linux/phy.h>
-#include <linux/phylink.h>
-#include <linux/pcs-altera-tse.h>
-
-/* SGMII PCS register addresses
- */
-#define SGMII_PCS_LINK_TIMER_0 0x12
-#define SGMII_PCS_LINK_TIMER_1 0x13
-#define SGMII_PCS_IF_MODE      0x14
-#define   PCS_IF_MODE_SGMII_ENA                BIT(0)
-#define   PCS_IF_MODE_USE_SGMII_AN     BIT(1)
-#define   PCS_IF_MODE_SGMI_HALF_DUPLEX BIT(4)
-#define   PCS_IF_MODE_SGMI_PHY_AN      BIT(5)
-#define SGMII_PCS_SW_RESET_TIMEOUT 100 /* usecs */
-
-struct altera_tse_pcs {
-       struct phylink_pcs pcs;
-       void __iomem *base;
-       int reg_width;
-};
-
-static struct altera_tse_pcs *phylink_pcs_to_tse_pcs(struct phylink_pcs *pcs)
-{
-       return container_of(pcs, struct altera_tse_pcs, pcs);
-}
-
-static u16 tse_pcs_read(struct altera_tse_pcs *tse_pcs, int regnum)
-{
-       if (tse_pcs->reg_width == 4)
-               return readl(tse_pcs->base + regnum * 4);
-       else
-               return readw(tse_pcs->base + regnum * 2);
-}
-
-static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum,
-                         u16 value)
-{
-       if (tse_pcs->reg_width == 4)
-               writel(value, tse_pcs->base + regnum * 4);
-       else
-               writew(value, tse_pcs->base + regnum * 2);
-}
-
-static int tse_pcs_reset(struct altera_tse_pcs *tse_pcs)
-{
-       u16 bmcr;
-
-       /* Reset PCS block */
-       bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
-       bmcr |= BMCR_RESET;
-       tse_pcs_write(tse_pcs, MII_BMCR, bmcr);
-
-       return read_poll_timeout(tse_pcs_read, bmcr, (bmcr & BMCR_RESET),
-                                10, SGMII_PCS_SW_RESET_TIMEOUT, 1,
-                                tse_pcs, MII_BMCR);
-}
-
-static int alt_tse_pcs_validate(struct phylink_pcs *pcs,
-                               unsigned long *supported,
-                               const struct phylink_link_state *state)
-{
-       if (state->interface == PHY_INTERFACE_MODE_SGMII ||
-           state->interface == PHY_INTERFACE_MODE_1000BASEX)
-               return 1;
-
-       return -EINVAL;
-}
-
-static int alt_tse_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
-                             phy_interface_t interface,
-                             const unsigned long *advertising,
-                             bool permit_pause_to_mac)
-{
-       struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
-       u32 ctrl, if_mode;
-
-       ctrl = tse_pcs_read(tse_pcs, MII_BMCR);
-       if_mode = tse_pcs_read(tse_pcs, SGMII_PCS_IF_MODE);
-
-       /* Set link timer to 1.6ms, as per the MegaCore Function User Guide */
-       tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_0, 0x0D40);
-       tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_1, 0x03);
-
-       if (interface == PHY_INTERFACE_MODE_SGMII) {
-               if_mode |= PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA;
-       } else if (interface == PHY_INTERFACE_MODE_1000BASEX) {
-               if_mode &= ~(PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA);
-       }
-
-       ctrl |= (BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_ANENABLE);
-
-       tse_pcs_write(tse_pcs, MII_BMCR, ctrl);
-       tse_pcs_write(tse_pcs, SGMII_PCS_IF_MODE, if_mode);
-
-       return tse_pcs_reset(tse_pcs);
-}
-
-static void alt_tse_pcs_get_state(struct phylink_pcs *pcs,
-                                 struct phylink_link_state *state)
-{
-       struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
-       u16 bmsr, lpa;
-
-       bmsr = tse_pcs_read(tse_pcs, MII_BMSR);
-       lpa = tse_pcs_read(tse_pcs, MII_LPA);
-
-       phylink_mii_c22_pcs_decode_state(state, bmsr, lpa);
-}
-
-static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs)
-{
-       struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
-       u16 bmcr;
-
-       bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
-       bmcr |= BMCR_ANRESTART;
-       tse_pcs_write(tse_pcs, MII_BMCR, bmcr);
-
-       /* This PCS seems to require a soft reset to re-sync the AN logic */
-       tse_pcs_reset(tse_pcs);
-}
-
-static const struct phylink_pcs_ops alt_tse_pcs_ops = {
-       .pcs_validate = alt_tse_pcs_validate,
-       .pcs_get_state = alt_tse_pcs_get_state,
-       .pcs_config = alt_tse_pcs_config,
-       .pcs_an_restart = alt_tse_pcs_an_restart,
-};
-
-struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev,
-                                      void __iomem *pcs_base, int reg_width)
-{
-       struct altera_tse_pcs *tse_pcs;
-
-       if (reg_width != 4 && reg_width != 2)
-               return ERR_PTR(-EINVAL);
-
-       tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL);
-       if (!tse_pcs)
-               return ERR_PTR(-ENOMEM);
-
-       tse_pcs->pcs.ops = &alt_tse_pcs_ops;
-       tse_pcs->base = pcs_base;
-       tse_pcs->reg_width = reg_width;
-
-       return &tse_pcs->pcs;
-}
-EXPORT_SYMBOL_GPL(alt_tse_pcs_create);
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Altera TSE PCS driver");
-MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
index 1ba2144..23223f0 100644 (file)
@@ -1224,8 +1224,8 @@ static const struct phylink_pcs_ops xpcs_phylink_ops = {
        .pcs_link_up = xpcs_link_up,
 };
 
-struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
-                           phy_interface_t interface)
+static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
+                                  phy_interface_t interface)
 {
        struct dw_xpcs *xpcs;
        u32 xpcs_id;
@@ -1273,7 +1273,6 @@ out:
 
        return ERR_PTR(ret);
 }
-EXPORT_SYMBOL_GPL(xpcs_create);
 
 void xpcs_destroy(struct dw_xpcs *xpcs)
 {
diff --git a/include/linux/mdio/mdio-regmap.h b/include/linux/mdio/mdio-regmap.h
new file mode 100644 (file)
index 0000000..679d906
--- /dev/null
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Driver for MMIO-Mapped MDIO devices. Some IPs expose internal PHYs or PCS
+ * within the MMIO-mapped area
+ *
+ * Copyright (C) 2023 Maxime Chevallier <maxime.chevallier@bootlin.com>
+ */
+#ifndef MDIO_REGMAP_H
+#define MDIO_REGMAP_H
+
+#include <linux/phy.h>
+
+struct device;
+struct regmap;
+
+struct mdio_regmap_config {
+       struct device *parent;
+       struct regmap *regmap;
+       char name[MII_BUS_ID_SIZE];
+       u8 valid_addr;
+       bool autoscan;
+};
+
+struct mii_bus *devm_mdio_regmap_register(struct device *dev,
+                                         const struct mdio_regmap_config *config);
+
+#endif
diff --git a/include/linux/pcs-altera-tse.h b/include/linux/pcs-altera-tse.h
deleted file mode 100644 (file)
index 92ab9f0..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2022 Bootlin
- *
- * Maxime Chevallier <maxime.chevallier@bootlin.com>
- */
-
-#ifndef __LINUX_PCS_ALTERA_TSE_H
-#define __LINUX_PCS_ALTERA_TSE_H
-
-struct phylink_pcs;
-struct net_device;
-
-struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev,
-                                      void __iomem *pcs_base, int reg_width);
-
-#endif /* __LINUX_PCS_ALTERA_TSE_H */
index a99972a..914e387 100644 (file)
@@ -35,8 +35,6 @@ int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
 void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces);
 int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns,
                    int enable);
-struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
-                           phy_interface_t interface);
 struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr,
                                    phy_interface_t interface);
 void xpcs_destroy(struct dw_xpcs *xpcs);
index 7addde5..11c1e91 100644 (file)
@@ -1206,10 +1206,12 @@ static inline int phy_read(struct phy_device *phydev, u32 regnum)
 #define phy_read_poll_timeout(phydev, regnum, val, cond, sleep_us, \
                                timeout_us, sleep_before_read) \
 ({ \
-       int __ret = read_poll_timeout(phy_read, val, val < 0 || (cond), \
+       int __ret, __val; \
+       __ret = read_poll_timeout(__val = phy_read, val, \
+                                 __val < 0 || (cond), \
                sleep_us, timeout_us, sleep_before_read, phydev, regnum); \
-       if (val < 0) \
-               __ret = val; \
+       if (__val < 0) \
+               __ret = __val; \
        if (__ret) \
                phydev_err(phydev, "%s failed: %d\n", __func__, __ret); \
        __ret; \
@@ -1302,11 +1304,13 @@ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum);
 #define phy_read_mmd_poll_timeout(phydev, devaddr, regnum, val, cond, \
                                  sleep_us, timeout_us, sleep_before_read) \
 ({ \
-       int __ret = read_poll_timeout(phy_read_mmd, val, (cond) || val < 0, \
+       int __ret, __val; \
+       __ret = read_poll_timeout(__val = phy_read_mmd, val, \
+                                 __val < 0 || (cond), \
                                  sleep_us, timeout_us, sleep_before_read, \
                                  phydev, devaddr, regnum); \
-       if (val <  0) \
-               __ret = val; \
+       if (__val < 0) \
+               __ret = __val; \
        if (__ret) \
                phydev_err(phydev, "%s failed: %d\n", __func__, __ret); \
        __ret; \
index 9ca353a..8eac4f3 100644 (file)
@@ -17,12 +17,15 @@ struct ref_tracker_dir {
        bool                    dead;
        struct list_head        list; /* List of active trackers */
        struct list_head        quarantine; /* List of dead trackers */
+       char                    name[32];
 #endif
 };
 
 #ifdef CONFIG_REF_TRACKER
+
 static inline void ref_tracker_dir_init(struct ref_tracker_dir *dir,
-                                       unsigned int quarantine_count)
+                                       unsigned int quarantine_count,
+                                       const char *name)
 {
        INIT_LIST_HEAD(&dir->list);
        INIT_LIST_HEAD(&dir->quarantine);
@@ -31,14 +34,20 @@ static inline void ref_tracker_dir_init(struct ref_tracker_dir *dir,
        dir->dead = false;
        refcount_set(&dir->untracked, 1);
        refcount_set(&dir->no_tracker, 1);
+       strscpy(dir->name, name, sizeof(dir->name));
        stack_depot_init();
 }
 
 void ref_tracker_dir_exit(struct ref_tracker_dir *dir);
 
+void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir,
+                                 unsigned int display_limit);
+
 void ref_tracker_dir_print(struct ref_tracker_dir *dir,
                           unsigned int display_limit);
 
+int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size);
+
 int ref_tracker_alloc(struct ref_tracker_dir *dir,
                      struct ref_tracker **trackerp, gfp_t gfp);
 
@@ -48,7 +57,8 @@ int ref_tracker_free(struct ref_tracker_dir *dir,
 #else /* CONFIG_REF_TRACKER */
 
 static inline void ref_tracker_dir_init(struct ref_tracker_dir *dir,
-                                       unsigned int quarantine_count)
+                                       unsigned int quarantine_count,
+                                       const char *name)
 {
 }
 
@@ -56,11 +66,22 @@ static inline void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
 {
 }
 
+static inline void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir,
+                                               unsigned int display_limit)
+{
+}
+
 static inline void ref_tracker_dir_print(struct ref_tracker_dir *dir,
                                         unsigned int display_limit)
 {
 }
 
+static inline int ref_tracker_dir_snprint(struct ref_tracker_dir *dir,
+                                         char *buf, size_t size)
+{
+       return 0;
+}
+
 static inline int ref_tracker_alloc(struct ref_tracker_dir *dir,
                                    struct ref_tracker **trackerp,
                                    gfp_t gfp)
index dc7b14a..cf5609b 100644 (file)
@@ -1,11 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
+
+#define pr_fmt(fmt) "ref_tracker: " fmt
+
 #include <linux/export.h>
+#include <linux/list_sort.h>
 #include <linux/ref_tracker.h>
 #include <linux/slab.h>
 #include <linux/stacktrace.h>
 #include <linux/stackdepot.h>
 
 #define REF_TRACKER_STACK_ENTRIES 16
+#define STACK_BUF_SIZE 1024
 
 struct ref_tracker {
        struct list_head        head;   /* anchor into dir->list or dir->quarantine */
@@ -14,6 +19,141 @@ struct ref_tracker {
        depot_stack_handle_t    free_stack_handle;
 };
 
+struct ref_tracker_dir_stats {
+       int total;
+       int count;
+       struct {
+               depot_stack_handle_t stack_handle;
+               unsigned int count;
+       } stacks[];
+};
+
+static struct ref_tracker_dir_stats *
+ref_tracker_get_stats(struct ref_tracker_dir *dir, unsigned int limit)
+{
+       struct ref_tracker_dir_stats *stats;
+       struct ref_tracker *tracker;
+
+       stats = kmalloc(struct_size(stats, stacks, limit),
+                       GFP_NOWAIT | __GFP_NOWARN);
+       if (!stats)
+               return ERR_PTR(-ENOMEM);
+       stats->total = 0;
+       stats->count = 0;
+
+       list_for_each_entry(tracker, &dir->list, head) {
+               depot_stack_handle_t stack = tracker->alloc_stack_handle;
+               int i;
+
+               ++stats->total;
+               for (i = 0; i < stats->count; ++i)
+                       if (stats->stacks[i].stack_handle == stack)
+                               break;
+               if (i >= limit)
+                       continue;
+               if (i >= stats->count) {
+                       stats->stacks[i].stack_handle = stack;
+                       stats->stacks[i].count = 0;
+                       ++stats->count;
+               }
+               ++stats->stacks[i].count;
+       }
+
+       return stats;
+}
+
+struct ostream {
+       char *buf;
+       int size, used;
+};
+
+#define pr_ostream(stream, fmt, args...) \
+({ \
+       struct ostream *_s = (stream); \
+\
+       if (!_s->buf) { \
+               pr_err(fmt, ##args); \
+       } else { \
+               int ret, len = _s->size - _s->used; \
+               ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \
+               _s->used += min(ret, len); \
+       } \
+})
+
+static void
+__ref_tracker_dir_pr_ostream(struct ref_tracker_dir *dir,
+                            unsigned int display_limit, struct ostream *s)
+{
+       struct ref_tracker_dir_stats *stats;
+       unsigned int i = 0, skipped;
+       depot_stack_handle_t stack;
+       char *sbuf;
+
+       lockdep_assert_held(&dir->lock);
+
+       if (list_empty(&dir->list))
+               return;
+
+       stats = ref_tracker_get_stats(dir, display_limit);
+       if (IS_ERR(stats)) {
+               pr_ostream(s, "%s@%pK: couldn't get stats, error %pe\n",
+                          dir->name, dir, stats);
+               return;
+       }
+
+       sbuf = kmalloc(STACK_BUF_SIZE, GFP_NOWAIT | __GFP_NOWARN);
+
+       for (i = 0, skipped = stats->total; i < stats->count; ++i) {
+               stack = stats->stacks[i].stack_handle;
+               if (sbuf && !stack_depot_snprint(stack, sbuf, STACK_BUF_SIZE, 4))
+                       sbuf[0] = 0;
+               pr_ostream(s, "%s@%pK has %d/%d users at\n%s\n", dir->name, dir,
+                          stats->stacks[i].count, stats->total, sbuf);
+               skipped -= stats->stacks[i].count;
+       }
+
+       if (skipped)
+               pr_ostream(s, "%s@%pK skipped reports about %d/%d users.\n",
+                          dir->name, dir, skipped, stats->total);
+
+       kfree(sbuf);
+
+       kfree(stats);
+}
+
+void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir,
+                                 unsigned int display_limit)
+{
+       struct ostream os = {};
+
+       __ref_tracker_dir_pr_ostream(dir, display_limit, &os);
+}
+EXPORT_SYMBOL(ref_tracker_dir_print_locked);
+
+void ref_tracker_dir_print(struct ref_tracker_dir *dir,
+                          unsigned int display_limit)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&dir->lock, flags);
+       ref_tracker_dir_print_locked(dir, display_limit);
+       spin_unlock_irqrestore(&dir->lock, flags);
+}
+EXPORT_SYMBOL(ref_tracker_dir_print);
+
+int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size)
+{
+       struct ostream os = { .buf = buf, .size = size };
+       unsigned long flags;
+
+       spin_lock_irqsave(&dir->lock, flags);
+       __ref_tracker_dir_pr_ostream(dir, 16, &os);
+       spin_unlock_irqrestore(&dir->lock, flags);
+
+       return os.used;
+}
+EXPORT_SYMBOL(ref_tracker_dir_snprint);
+
 void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
 {
        struct ref_tracker *tracker, *n;
@@ -27,13 +167,13 @@ void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
                kfree(tracker);
                dir->quarantine_avail++;
        }
-       list_for_each_entry_safe(tracker, n, &dir->list, head) {
-               pr_err("leaked reference.\n");
-               if (tracker->alloc_stack_handle)
-                       stack_depot_print(tracker->alloc_stack_handle);
+       if (!list_empty(&dir->list)) {
+               ref_tracker_dir_print_locked(dir, 16);
                leak = true;
-               list_del(&tracker->head);
-               kfree(tracker);
+               list_for_each_entry_safe(tracker, n, &dir->list, head) {
+                       list_del(&tracker->head);
+                       kfree(tracker);
+               }
        }
        spin_unlock_irqrestore(&dir->lock, flags);
        WARN_ON_ONCE(leak);
@@ -42,28 +182,6 @@ void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
 }
 EXPORT_SYMBOL(ref_tracker_dir_exit);
 
-void ref_tracker_dir_print(struct ref_tracker_dir *dir,
-                          unsigned int display_limit)
-{
-       struct ref_tracker *tracker;
-       unsigned long flags;
-       unsigned int i = 0;
-
-       spin_lock_irqsave(&dir->lock, flags);
-       list_for_each_entry(tracker, &dir->list, head) {
-               if (i < display_limit) {
-                       pr_err("leaked reference.\n");
-                       if (tracker->alloc_stack_handle)
-                               stack_depot_print(tracker->alloc_stack_handle);
-                       i++;
-               } else {
-                       break;
-               }
-       }
-       spin_unlock_irqrestore(&dir->lock, flags);
-}
-EXPORT_SYMBOL(ref_tracker_dir_print);
-
 int ref_tracker_alloc(struct ref_tracker_dir *dir,
                      struct ref_tracker **trackerp,
                      gfp_t gfp)
@@ -71,7 +189,7 @@ int ref_tracker_alloc(struct ref_tracker_dir *dir,
        unsigned long entries[REF_TRACKER_STACK_ENTRIES];
        struct ref_tracker *tracker;
        unsigned int nr_entries;
-       gfp_t gfp_mask = gfp;
+       gfp_t gfp_mask = gfp | __GFP_NOWARN;
        unsigned long flags;
 
        WARN_ON_ONCE(dir->dead);
@@ -119,7 +237,8 @@ int ref_tracker_free(struct ref_tracker_dir *dir,
                return -EEXIST;
        }
        nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1);
-       stack_handle = stack_depot_save(entries, nr_entries, GFP_ATOMIC);
+       stack_handle = stack_depot_save(entries, nr_entries,
+                                       GFP_NOWAIT | __GFP_NOWARN);
 
        spin_lock_irqsave(&dir->lock, flags);
        if (tracker->dead) {
index 19d7dec..49970a7 100644 (file)
@@ -64,7 +64,7 @@ static int __init test_ref_tracker_init(void)
 {
        int i;
 
-       ref_tracker_dir_init(&ref_dir, 100);
+       ref_tracker_dir_init(&ref_dir, 100, "selftest");
 
        timer_setup(&test_ref_tracker_timer, test_ref_tracker_timer_func, 0);
        mod_timer(&test_ref_tracker_timer, jiffies + 1);
index 99d99b2..8870eeb 100644 (file)
@@ -10635,7 +10635,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
        dev = PTR_ALIGN(p, NETDEV_ALIGN);
        dev->padded = (char *)dev - (char *)p;
 
-       ref_tracker_dir_init(&dev->refcnt_tracker, 128);
+       ref_tracker_dir_init(&dev->refcnt_tracker, 128, name);
 #ifdef CONFIG_PCPU_DEV_REFCNT
        dev->pcpu_refcnt = alloc_percpu(int);
        if (!dev->pcpu_refcnt)
index 3e3598c..f4183c4 100644 (file)
@@ -308,7 +308,7 @@ EXPORT_SYMBOL_GPL(get_net_ns_by_id);
 /* init code that must occur even if setup_net() is not called. */
 static __net_init void preinit_net(struct net *net)
 {
-       ref_tracker_dir_init(&net->notrefcnt_tracker, 128);
+       ref_tracker_dir_init(&net->notrefcnt_tracker, 128, "net notrefcnt");
 }
 
 /*
@@ -322,7 +322,7 @@ static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
        LIST_HEAD(net_exit_list);
 
        refcount_set(&net->ns.count, 1);
-       ref_tracker_dir_init(&net->refcnt_tracker, 128);
+       ref_tracker_dir_init(&net->refcnt_tracker, 128, "net refcnt");
 
        refcount_set(&net->passive, 1);
        get_random_bytes(&net->hash_mix, sizeof(u32));
index 3797917..5479da0 100644 (file)
@@ -3633,8 +3633,8 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
                                idev->if_flags |= IF_READY;
                        }
 
-                       pr_info("ADDRCONF(NETDEV_CHANGE): %s: link becomes ready\n",
-                               dev->name);
+                       pr_debug("ADDRCONF(NETDEV_CHANGE): %s: link becomes ready\n",
+                                dev->name);
 
                        run_pending = 1;
                }
index be66451..4c12c6f 100755 (executable)
@@ -170,6 +170,7 @@ class Type(SpecAttr):
         for line in lines:
             ri.cw.p(line)
         ri.cw.block_end()
+        return True
 
     def _setter_lines(self, ri, member, presence):
         raise Exception(f"Setter not implemented for class type {self.type}")
@@ -197,6 +198,12 @@ class TypeUnused(Type):
     def presence_type(self):
         return ''
 
+    def arg_member(self, ri):
+        return []
+
+    def _attr_get(self, ri, var):
+        return ['return MNL_CB_ERROR;'], None, None
+
     def _attr_typol(self):
         return '.type = YNL_PT_REJECT, '
 
@@ -208,8 +215,14 @@ class TypePad(Type):
     def presence_type(self):
         return ''
 
+    def arg_member(self, ri):
+        return []
+
     def _attr_typol(self):
-        return '.type = YNL_PT_REJECT, '
+        return '.type = YNL_PT_IGNORE, '
+
+    def attr_get(self, ri, var, first):
+        pass
 
     def attr_policy(self, cw):
         pass
@@ -411,7 +424,8 @@ class TypeNest(Type):
                             f"{self.enum_name}, &{var}->{self.c_name})")
 
     def _attr_get(self, ri, var):
-        get_lines = [f"{self.nested_render_name}_parse(&parg, attr);"]
+        get_lines = [f"if ({self.nested_render_name}_parse(&parg, attr))",
+                     "return MNL_CB_ERROR;"]
         init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",
                       f"parg.data = &{var}->{self.c_name};"]
         return get_lines, init_lines, None
@@ -812,7 +826,8 @@ class Family(SpecFamily):
                     inherit = set()
                     nested = spec['nested-attributes']
                     if nested not in self.root_sets:
-                        self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit)
+                        if nested not in self.pure_nested_structs:
+                            self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit)
                     if attr in rs_members['request']:
                         self.pure_nested_structs[nested].request = True
                     if attr in rs_members['reply']:
@@ -872,6 +887,12 @@ class Family(SpecFamily):
                     self.hooks[when][op_mode]['set'].add(name)
                     self.hooks[when][op_mode]['list'].append(name)
 
+    def has_notifications(self):
+        for op in self.ops.values():
+            if 'notify' in op or 'event' in op:
+                return True
+        return False
+
 
 class RenderInfo:
     def __init__(self, cw, family, ku_space, op, op_name, op_mode, attr_set=None):
@@ -883,11 +904,12 @@ class RenderInfo:
         self.op_mode = op_mode
 
         # 'do' and 'dump' response parsing is identical
-        if op_mode != 'do' and 'dump' in op and 'do' in op and 'reply' in op['do'] and \
-           op["do"]["reply"] == op["dump"]["reply"]:
-            self.type_consistent = True
-        else:
-            self.type_consistent = op_mode == 'event'
+        self.type_consistent = True
+        if op_mode != 'do' and 'dump' in op and 'do' in op:
+            if ('reply' in op['do']) != ('reply' in op["dump"]):
+                self.type_consistent = False
+            elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]:
+                self.type_consistent = False
 
         self.attr_set = attr_set
         if not self.attr_set:
@@ -1152,6 +1174,56 @@ def put_typol(cw, struct):
     cw.nl()
 
 
+def put_op_name_fwd(family, cw):
+    cw.write_func_prot('const char *', f'{family.name}_op_str', ['int op'], suffix=';')
+
+
+def put_op_name(family, cw):
+    map_name = f'{family.name}_op_strmap'
+    cw.block_start(line=f"static const char * const {map_name}[] =")
+    for op_name, op in family.msgs.items():
+        cw.p(f'[{op.enum_name}] = "{op_name}",')
+    cw.block_end(line=';')
+    cw.nl()
+
+    cw.write_func_prot('const char *', f'{family.name}_op_str', ['int op'])
+    cw.block_start()
+    cw.p(f'if (op < 0 || op >= (int)MNL_ARRAY_SIZE({map_name}))')
+    cw.p('return NULL;')
+    cw.p(f'return {map_name}[op];')
+    cw.block_end()
+    cw.nl()
+
+
+def put_enum_to_str_fwd(family, cw, enum):
+    args = [f'enum {enum.render_name} value']
+    if 'enum-name' in enum and not enum['enum-name']:
+        args = ['int value']
+    cw.write_func_prot('const char *', f'{enum.render_name}_str', args, suffix=';')
+
+
+def put_enum_to_str(family, cw, enum):
+    map_name = f'{enum.render_name}_strmap'
+    cw.block_start(line=f"static const char * const {map_name}[] =")
+    for entry in enum.entries.values():
+        cw.p(f'[{entry.value}] = "{entry.name}",')
+    cw.block_end(line=';')
+    cw.nl()
+
+    args = [f'enum {enum.render_name} value']
+    if 'enum-name' in enum and not enum['enum-name']:
+        args = ['int value']
+    cw.write_func_prot('const char *', f'{enum.render_name}_str', args)
+    cw.block_start()
+    if enum.type == 'flags':
+        cw.p('value = ffs(value) - 1;')
+    cw.p(f'if (value < 0 || value >= (int)MNL_ARRAY_SIZE({map_name}))')
+    cw.p('return NULL;')
+    cw.p(f'return {map_name}[value];')
+    cw.block_end()
+    cw.nl()
+
+
 def put_req_nested(ri, struct):
     func_args = ['struct nlmsghdr *nlh',
                  'unsigned int attr_type',
@@ -1211,8 +1283,9 @@ def _multi_parse(ri, struct, init_lines, local_vars):
 
     first = True
     for _, arg in struct.member_list():
-        arg.attr_get(ri, 'dst', first=first)
-        first = False
+        good = arg.attr_get(ri, 'dst', first=first)
+        # First may be 'unused' or 'pad', ignore those
+        first &= not good
 
     ri.cw.block_end()
     ri.cw.nl()
@@ -1304,13 +1377,13 @@ def print_req(ri):
     ret_err = '-1'
     direction = "request"
     local_vars = ['struct nlmsghdr *nlh;',
-                  'int len, err;']
+                  'int err;']
 
     if 'reply' in ri.op[ri.op_mode]:
         ret_ok = 'rsp'
         ret_err = 'NULL'
         local_vars += [f'{type_name(ri, rdir(direction))} *rsp;',
-                       'struct ynl_parse_arg yarg = { .ys = ys, };']
+                       'struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };']
 
     print_prototype(ri, direction, terminate=False)
     ri.cw.block_start()
@@ -1320,41 +1393,39 @@ def print_req(ri):
 
     ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
     if 'reply' in ri.op[ri.op_mode]:
-        ri.cw.p(f"yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
+        ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
     ri.cw.nl()
     for _, attr in ri.struct["request"].member_list():
         attr.attr_put(ri, "req")
     ri.cw.nl()
 
-    ri.cw.p('err = mnl_socket_sendto(ys->sock, nlh, nlh->nlmsg_len);')
-    ri.cw.p('if (err < 0)')
-    ri.cw.p(f"return {ret_err};")
-    ri.cw.nl()
-    ri.cw.p('len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);')
-    ri.cw.p('if (len < 0)')
-    ri.cw.p(f"return {ret_err};")
-    ri.cw.nl()
-
+    parse_arg = "NULL"
     if 'reply' in ri.op[ri.op_mode]:
         ri.cw.p('rsp = calloc(1, sizeof(*rsp));')
-        ri.cw.p('yarg.data = rsp;')
+        ri.cw.p('yrs.yarg.data = rsp;')
+        ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;")
+        if ri.op.value is not None:
+            ri.cw.p(f'yrs.rsp_cmd = {ri.op.enum_name};')
+        else:
+            ri.cw.p(f'yrs.rsp_cmd = {ri.op.rsp_value};')
         ri.cw.nl()
-        ri.cw.p(f"err = {ri.nl.parse_cb_run(op_prefix(ri, 'reply') + '_parse', '&yarg', False)};")
-        ri.cw.p('if (err < 0)')
+        parse_arg = '&yrs'
+    ri.cw.p(f"err = ynl_exec(ys, nlh, {parse_arg});")
+    ri.cw.p('if (err < 0)')
+    if 'reply' in ri.op[ri.op_mode]:
         ri.cw.p('goto err_free;')
-        ri.cw.nl()
-
-    ri.cw.p('err = ynl_recv_ack(ys, err);')
-    ri.cw.p('if (err)')
-    ri.cw.p('goto err_free;')
+    else:
+        ri.cw.p('return -1;')
     ri.cw.nl()
+
     ri.cw.p(f"return {ret_ok};")
     ri.cw.nl()
-    ri.cw.p('err_free:')
 
     if 'reply' in ri.op[ri.op_mode]:
+        ri.cw.p('err_free:')
         ri.cw.p(f"{call_free(ri, rdir(direction), 'rsp')}")
-    ri.cw.p(f"return {ret_err};")
+        ri.cw.p(f"return {ret_err};")
+
     ri.cw.block_end()
 
 
@@ -1364,7 +1435,7 @@ def print_dump(ri):
     ri.cw.block_start()
     local_vars = ['struct ynl_dump_state yds = {};',
                   'struct nlmsghdr *nlh;',
-                  'int len, err;']
+                  'int err;']
 
     for var in local_vars:
         ri.cw.p(f'{var}')
@@ -1373,6 +1444,10 @@ def print_dump(ri):
     ri.cw.p('yds.ys = ys;')
     ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});")
     ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;")
+    if ri.op.value is not None:
+        ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};')
+    else:
+        ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};')
     ri.cw.p(f"yds.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
     ri.cw.nl()
     ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")
@@ -1384,20 +1459,9 @@ def print_dump(ri):
             attr.attr_put(ri, "req")
     ri.cw.nl()
 
-    ri.cw.p('err = mnl_socket_sendto(ys->sock, nlh, nlh->nlmsg_len);')
-    ri.cw.p('if (err < 0)')
-    ri.cw.p('return NULL;')
-    ri.cw.nl()
-
-    ri.cw.block_start(line='do')
-    ri.cw.p('len = mnl_socket_recvfrom(ys->sock, ys->rx_buf, MNL_SOCKET_BUFFER_SIZE);')
-    ri.cw.p('if (len < 0)')
-    ri.cw.p('goto free_list;')
-    ri.cw.nl()
-    ri.cw.p(f"err = {ri.nl.parse_cb_run('ynl_dump_trampoline', '&yds', False, indent=2)};")
+    ri.cw.p('err = ynl_exec_dump(ys, nlh, &yds);')
     ri.cw.p('if (err < 0)')
     ri.cw.p('goto free_list;')
-    ri.cw.block_end(line='while (err > 0);')
     ri.cw.nl()
 
     ri.cw.p('return yds.first;')
@@ -1418,6 +1482,14 @@ def free_arg_name(direction):
     return 'obj'
 
 
+def print_alloc_wrapper(ri, direction):
+    name = op_prefix(ri, direction)
+    ri.cw.write_func_prot(f'static inline struct {name} *', f"{name}_alloc", [f"void"])
+    ri.cw.block_start()
+    ri.cw.p(f'return calloc(1, sizeof(struct {name}));')
+    ri.cw.block_end()
+
+
 def print_free_prototype(ri, direction, suffix=';'):
     name = op_prefix(ri, direction)
     arg = free_arg_name(direction)
@@ -1465,6 +1537,7 @@ def print_type_full(ri, struct):
 
 def print_type_helpers(ri, direction, deref=False):
     print_free_prototype(ri, direction)
+    ri.cw.nl()
 
     if ri.ku_space == 'user' and direction == 'request':
         for _, attr in ri.struct[direction].member_list():
@@ -1473,6 +1546,7 @@ def print_type_helpers(ri, direction, deref=False):
 
 
 def print_req_type_helpers(ri):
+    print_alloc_wrapper(ri, "request")
     print_type_helpers(ri, "request")
 
 
@@ -1496,6 +1570,12 @@ def print_req_type(ri):
     print_type(ri, "request")
 
 
+def print_req_free(ri):
+    if 'request' not in ri.op[ri.op_mode]:
+        return
+    _free_type(ri, 'request', ri.struct['request'])
+
+
 def print_rsp_type(ri):
     if (ri.op_mode == 'do' or ri.op_mode == 'dump') and 'reply' in ri.op[ri.op_mode]:
         direction = 'reply'
@@ -1513,6 +1593,7 @@ def print_wrapped_type(ri):
     elif ri.op_mode == 'notify' or ri.op_mode == 'event':
         ri.cw.p('__u16 family;')
         ri.cw.p('__u8 cmd;')
+        ri.cw.p('struct ynl_ntf_base_type *next;')
         ri.cw.p(f"void (*free)({type_name(ri, 'reply')} *ntf);")
     ri.cw.p(f"{type_name(ri, 'reply', deref=True)} obj __attribute__ ((aligned (8)));")
     ri.cw.block_end(line=';')
@@ -1564,7 +1645,7 @@ def print_dump_type_free(ri):
     ri.cw.block_start()
     ri.cw.p(f"{sub_type} *next = rsp;")
     ri.cw.nl()
-    ri.cw.block_start(line='while (next)')
+    ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)')
     _free_type_members_iter(ri, ri.struct['reply'])
     ri.cw.p('rsp = next;')
     ri.cw.p('next = rsp->next;')
@@ -2035,6 +2116,45 @@ def render_uapi(family, cw):
     cw.p(f'#endif /* {hdr_prot} */')
 
 
+def _render_user_ntf_entry(ri, op):
+    ri.cw.block_start(line=f"[{op.enum_name}] = ")
+    ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),")
+    ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,")
+    ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,")
+    ri.cw.p(f".free\t\t= (void *){op_prefix(ri, 'notify')}_free,")
+    ri.cw.block_end(line=',')
+
+
+def render_user_family(family, cw, prototype):
+    symbol = f'const struct ynl_family ynl_{family.c_name}_family'
+    if prototype:
+        cw.p(f'extern {symbol};')
+        return
+
+    ntf = family.has_notifications()
+    if ntf:
+        cw.block_start(line=f"static const struct ynl_ntf_info {family['name']}_ntf_info[] = ")
+        for ntf_op in sorted(family.all_notify.keys()):
+            op = family.ops[ntf_op]
+            ri = RenderInfo(cw, family, "user", op, ntf_op, "notify")
+            for ntf in op['notify']['cmds']:
+                _render_user_ntf_entry(ri, ntf)
+        for op_name, op in family.ops.items():
+            if 'event' not in op:
+                continue
+            ri = RenderInfo(cw, family, "user", op, op_name, "event")
+            _render_user_ntf_entry(ri, op)
+        cw.block_end(line=";")
+        cw.nl()
+
+    cw.block_start(f'{symbol} = ')
+    cw.p(f'.name\t\t= "{family.name}",')
+    if ntf:
+        cw.p(f".ntf_info\t= {family['name']}_ntf_info,")
+        cw.p(f".ntf_info_size\t= MNL_ARRAY_SIZE({family['name']}_ntf_info),")
+    cw.block_end(line=';')
+
+
 def find_kernel_root(full_path):
     sub_path = ''
     while True:
@@ -2103,6 +2223,13 @@ def main():
             cw.nl()
         headers = ['uapi/' + parsed.uapi_header]
     else:
+        cw.p('#include <stdlib.h>')
+        if args.header:
+            cw.p('#include <string.h>')
+            cw.p('#include <linux/types.h>')
+        else:
+            cw.p(f'#include "{parsed.name}-user.h"')
+            cw.p('#include "ynl.h"')
         headers = [parsed.uapi_header]
     for definition in parsed['definitions']:
         if 'header' in definition:
@@ -2123,6 +2250,8 @@ def main():
                 cw.p(f'#include "{one}"')
         else:
             cw.p('struct ynl_sock;')
+            cw.nl()
+            render_user_family(parsed, cw, True)
         cw.nl()
 
     if args.mode == "kernel":
@@ -2184,8 +2313,15 @@ def main():
             print_kernel_family_struct_src(parsed, cw)
 
     if args.mode == "user":
-        has_ntf = False
         if args.header:
+            cw.p('/* Enums */')
+            put_op_name_fwd(parsed, cw)
+
+            for name, const in parsed.consts.items():
+                if isinstance(const, EnumSet):
+                    put_enum_to_str_fwd(parsed, cw, const)
+            cw.nl()
+
             cw.p('/* Common nested types */')
             for attr_set, struct in sorted(parsed.pure_nested_structs.items()):
                 ri = RenderInfo(cw, parsed, args.mode, "", "", "", attr_set)
@@ -2221,9 +2357,8 @@ def main():
                 if 'notify' in op:
                     cw.p(f"/* {op.enum_name} - notify */")
                     ri = RenderInfo(cw, parsed, args.mode, op, op_name, 'notify')
-                    has_ntf = True
                     if not ri.type_consistent:
-                        raise Exception('Only notifications with consistent types supported')
+                        raise Exception(f'Only notifications with consistent types supported ({op.name})')
                     print_wrapped_type(ri)
 
                 if 'event' in op:
@@ -2233,11 +2368,19 @@ def main():
                     cw.nl()
                     print_wrapped_type(ri)
 
-            if has_ntf:
+            if parsed.has_notifications():
                 cw.p('/* --------------- Common notification parsing --------------- */')
                 print_ntf_parse_prototype(parsed, cw)
             cw.nl()
         else:
+            cw.p('/* Enums */')
+            put_op_name(parsed, cw)
+
+            for name, const in parsed.consts.items():
+                if isinstance(const, EnumSet):
+                    put_enum_to_str(parsed, cw, const)
+            cw.nl()
+
             cw.p('/* Policies */')
             for name, _ in parsed.attr_sets.items():
                 struct = Struct(parsed, name)
@@ -2263,6 +2406,7 @@ def main():
                 if 'do' in op and 'event' not in op:
                     cw.p(f"/* {op.enum_name} - do */")
                     ri = RenderInfo(cw, parsed, args.mode, op, op_name, "do")
+                    print_req_free(ri)
                     print_rsp_free(ri)
                     parse_rsp_msg(ri)
                     print_req(ri)
@@ -2280,14 +2424,12 @@ def main():
                 if 'notify' in op:
                     cw.p(f"/* {op.enum_name} - notify */")
                     ri = RenderInfo(cw, parsed, args.mode, op, op_name, 'notify')
-                    has_ntf = True
                     if not ri.type_consistent:
-                        raise Exception('Only notifications with consistent types supported')
+                        raise Exception(f'Only notifications with consistent types supported ({op.name})')
                     print_ntf_type_free(ri)
 
                 if 'event' in op:
                     cw.p(f"/* {op.enum_name} - event */")
-                    has_ntf = True
 
                     ri = RenderInfo(cw, parsed, args.mode, op, op_name, "do")
                     parse_rsp_msg(ri)
@@ -2295,10 +2437,13 @@ def main():
                     ri = RenderInfo(cw, parsed, args.mode, op, op_name, "event")
                     print_ntf_type_free(ri)
 
-            if has_ntf:
+            if parsed.has_notifications():
                 cw.p('/* --------------- Common notification parsing --------------- */')
                 print_ntf_type_parse(parsed, cw, args.mode)
 
+            cw.nl()
+            render_user_family(parsed, cw, False)
+
     if args.header:
         cw.p(f'#endif /* {hdr_prot} */')
 
index 0cf9e47..a5c2aec 100755 (executable)
 # +----------------|--+                   +--|-----------------+
 #                  |                         |
 # +----------------|-------------------------|-----------------+
-# | SW             |                         |                 |
+# | SW       $swp1 +                         + $swp2           |
+# |                |                         |                 |
 # | +--------------|-------------------------|---------------+ |
-# | |        $swp1 +                         + $swp2         | |
-# | |              |                         |               | |
 # | |     $swp1.10 +                         + $swp2.10      | |
 # | |                                                        | |
 # | |                           br0                          | |
index df2b099..7d7f862 100755 (executable)
 # +----------------|--+                   +--|-----------------+
 #                  |                         |
 # +----------------|-------------------------|-----------------+
-# | SW             |                         |                 |
+# | SW       $swp1 +                         + $swp2           |
+# |                |                         |                 |
 # | +--------------|-------------------------|---------------+ |
-# | |        $swp1 +                         + $swp2         | |
-# | |              |                         |               | |
 # | |     $swp1.10 +                         + $swp2.10      | |
 # | |                                                        | |
 # | |                           br0                          | |
index fa6a88c..de2b2d5 100755 (executable)
@@ -1,6 +1,28 @@
 #!/bin/bash
 # SPDX-License-Identifier: GPL-2.0
 
+# +------------------------+                           +----------------------+
+# | H1 (vrf)               |                           |             H2 (vrf) |
+# |    + $h1.555           |                           |  + $h2               |
+# |    | 192.0.2.1/28      |                           |  | 192.0.2.130/28    |
+# |    | 2001:db8:1::1/64  |                           |  | 2001:db8:2::2/64  |
+# |    |                   |                           |  |                   |
+# |    + $h1               |                           |  |                   |
+# +----|-------------------+                           +--|-------------------+
+#      |                                                  |
+# +----|--------------------------------------------------|-------------------+
+# | SW |                                                  |                   |
+# | +--|-------------------------------+                  + $swp2             |
+# | |  + $swp1                         |                    192.0.2.129/28    |
+# | |    vid 555                       |                    2001:db8:2::1/64  |
+# | |                                  |                                      |
+# | |  + BR1 (802.1q)                  |                                      |
+# | |    vid 555 pvid untagged         |                                      |
+# | |    192.0.2.2/28                  |                                      |
+# | |    2001:db8:1::2/64              |                                      |
+# | +----------------------------------+                                      |
+# +---------------------------------------------------------------------------+
+
 ALL_TESTS="
        ping_ipv4
        ping_ipv6
@@ -41,7 +63,7 @@ h2_destroy()
 
 router_create()
 {
-       ip link add name br1 type bridge vlan_filtering 1
+       ip link add name br1 type bridge vlan_filtering 1 vlan_default_pvid 0
        ip link set dev br1 up
 
        ip link set dev $swp1 master br1