[PATCH v3 1/6] net: introduce DSA class for Ethernet switches

Vladimir Oltean olteanv at gmail.com
Sun Feb 9 14:10:16 CET 2020


Hi Alex,

On Tue, 3 Dec 2019 at 17:32, Alex Marginean <alexandru.marginean at nxp.com> wrote:
>
> DSA stands for Distributed Switch Architecture and it covers switches that
> are connected to the CPU through an Ethernet link and generally use frame
> tags to pass information about the source/destination ports to/from CPU.
> Front panel ports are presented as regular ethernet devices in U-Boot and
> they are expected to support the typical networking commands.
> DSA switches may be cascaded, DSA class code does not currently support
> this.
>
> Signed-off-by: Alex Marginean <alexandru.marginean at nxp.com>
> ---
>  drivers/net/Kconfig    |  13 ++
>  include/dm/uclass-id.h |   1 +
>  include/net/dsa.h      | 141 ++++++++++++++++
>  net/Makefile           |   1 +
>  net/dsa-uclass.c       | 369 +++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 525 insertions(+)
>  create mode 100644 include/net/dsa.h
>  create mode 100644 net/dsa-uclass.c
>
> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> index 4182897d89..a4157cb122 100644
> --- a/drivers/net/Kconfig
> +++ b/drivers/net/Kconfig
> @@ -37,6 +37,19 @@ config DM_MDIO_MUX
>           This is currently implemented in net/mdio-mux-uclass.c
>           Look in include/miiphy.h for details.
>
> +config DM_DSA
> +       bool "Enable Driver Model for DSA switches"
> +       depends on DM_ETH && DM_MDIO
> +       help
> +         Enable Driver Model for DSA switches
> +
> +         Adds UCLASS_DSA class supporting switches that follow the Distributed
> +         Switch Architecture (DSA).  These switches rely on the presence of a
> +         management switch port connected to an Ethernet controller capable of
> +         receiving frames from the switch.  This host Ethernet controller is
> +         called "master" and "cpu" in DSA terminology.
> +         This is currently implemented in net/dsa-uclass.c
> +
>  config MDIO_SANDBOX
>         depends on DM_MDIO && SANDBOX
>         default y
> diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
> index 0c563d898b..8f37a91488 100644
> --- a/include/dm/uclass-id.h
> +++ b/include/dm/uclass-id.h
> @@ -42,6 +42,7 @@ enum uclass_id {
>         UCLASS_DISPLAY,         /* Display (e.g. DisplayPort, HDMI) */
>         UCLASS_DSI_HOST,        /* Display Serial Interface host */
>         UCLASS_DMA,             /* Direct Memory Access */
> +       UCLASS_DSA,             /* Distributed (Ethernet) Switch Architecture */
>         UCLASS_EFI,             /* EFI managed devices */
>         UCLASS_ETH,             /* Ethernet device */
>         UCLASS_FIRMWARE,        /* Firmware */
> diff --git a/include/net/dsa.h b/include/net/dsa.h
> new file mode 100644
> index 0000000000..2387419b9d
> --- /dev/null
> +++ b/include/net/dsa.h
> @@ -0,0 +1,141 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (c) 2019 NXP
> + */
> +
> +#ifndef __DSA_H__
> +#define __DSA_H__
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <phy.h>
> +
> +/**
> + * DSA stands for Distributed Switch Architecture and it is infrastructure
> + * intended to support drivers for Switches that rely on an intermediary
> + * Ethernet device for I/O.  These switches may support cascading allowing
> + * them to be arranged as a tree.
> + * DSA is documented in detail in the Linux kernel documentation under
> + * Documentation/networking/dsa/dsa.txt
> + * The network layout of such a switch is shown below:
> + *
> + *         |---------------------------
> + *         | CPU network device (eth0)|
> + *         ----------------------------
> + *         | <tag added by switch     |
> + *         |                          |
> + *         |                          |
> + *         |        tag added by CPU> |
> + *     |--------------------------------------------|
> + *     | Switch driver                              |
> + *     |--------------------------------------------|
> + *         ||        ||         ||
> + *     |-------|  |-------|  |-------|
> + *     | sw0p0 |  | sw0p1 |  | sw0p2 |
> + *     |-------|  |-------|  |-------|
> + *
> + * In U-Boot the intent is to allow access to front panel ports (shown at the
> + * bottom of the picture) though the master Ethernet port (eth0 in the picture).
> + * Front panel ports are presented as regular Ethernet devices in U-Boot and
> + * they are expected to support the typical networking commands.
> + * In general DSA switches require the use of tags, extra headers added both by
> + * software on Tx and by the switch on Rx.  These tags carry at a minimum port
> + * information and switch information for cascaded set-ups.
> + * In U-Boot these tags are inserted and parsed by the DSA switch driver, the
> + * class code helps with headroom/tailroom for the extra headers.
> + *
> + * TODO:
> + * - handle switch cascading, for now U-Boot only supports stand-alone switches.
> + * - propagate the master Eth MAC address to switch ports, this is used in
> + * Linux to avoid using additional MAC addresses on master Eth.
> + * - Add support to probe DSA switches connected to a MDIO bus, this is needed
> + * to convert switch drivers that are now under drivers/net/phy.
> + */
> +
> +#define DSA_PORT_NAME_LENGTH   16
> +
> +/* Maximum number of ports each DSA device can have */
> +#define DSA_MAX_PORTS          12
> +/* Used to size internal buffers, no support for jumbo yet */
> +#define DSA_MAX_FRAME_SIZE     2048
> +
> +/**
> + * struct dsa_ops - DSA operations
> + *
> + * @port_enable:  Initialize a switch port for I/O
> + * @port_disable: Disable a port
> + * @xmit:         Insert the DSA tag for transmission
> + *                DSA drivers receive a copy of the packet with headroom and
> + *                tailroom reserved and set to 0.
> + *                Packet points to headroom and length is updated to include
> + *                both headroom and tailroom
> + * @rcv:          Process the DSA tag on reception
> + *                Packet and length describe the frame as received from master
> + *                including any additional headers
> + */
> +struct dsa_ops {
> +       int (*port_enable)(struct udevice *dev, int port,
> +                          struct phy_device *phy);
> +       void (*port_disable)(struct udevice *dev, int port,
> +                            struct phy_device *phy);
> +       int (*xmit)(struct udevice *dev, int port, void *packet, int length);
> +       int (*rcv)(struct udevice *dev, int *port, void *packet, int length);
> +};
> +
> +#define dsa_get_ops(dev) ((struct dsa_ops *)(dev)->driver->ops)
> +
> +/**
> + * struct dsa_port_platdata - DSA port platform data
> + *
> + * @dev :  Port u-device
> + *         Uclass code sets this field for all ports
> + * @phy:   PHY device associated with this port
> + *         Uclass code sets this field for all ports except CPU port, based on
> + *         DT information.  It may be NULL.
> + * @node:  Port DT node, if any.  Uclass code sets this field.
> + * @index: Port index in the DSA switch, set by class code.
> + * @name:  Name of the port Eth device.  If a label property is present in the
> + *         port DT node, it is used as name.  Drivers can use custom names by
> + *         populating this field, otherwise class code generates a default.
> + */
> +struct dsa_port_platdata {
> +       struct udevice *dev;
> +       struct phy_device *phy;
> +       ofnode node;
> +       int index;
> +       char name[DSA_PORT_NAME_LENGTH];
> +};
> +
> +/**
> + * struct dsa_perdev_platdata - Per-device platform data for DSA DM
> + *
> + * @num_ports:   Number of ports the device has, must be <= DSA_MAX_PORTS
> + *               All DSA drivers must set this at _bind
> + * @headroom:    Size, in bytes, of headroom needed for the DSA tag
> + *               All DSA drivers must set this at _bind or _probe
> + * @tailroom:    Size, in bytes, of tailroom needed for the DSA tag
> + *               DSA class code allocates headroom and tailroom on Tx before
> + *               calling DSA driver xmit function
> + *               All DSA drivers must set this at _bind or _probe
> + * @master_node: DT node of the master Ethernet.  DT is optional so this may be
> + *               null.
> + * @master_dev:  Ethernet device to be used as master.  Uclass code sets this
> + *               based on DT information if present, otherwise drivers must set
> + *               this field in _probe.
> + * @cpu_port:    Index of switch port linked to master Ethernet.
> + *               Uclass code sets this based on DT information if present,
> + *               otherwise drivers must set this field in _bind.
> + * @port:        per-port data
> + */
> +struct dsa_perdev_platdata {
> +       int num_ports;
> +       int headroom;
> +       int tailroom;
> +
> +       ofnode master_node;
> +       struct udevice *master_dev;
> +       int cpu_port;
> +       struct dsa_port_platdata port[DSA_MAX_PORTS];
> +};
> +
> +#endif /* __DSA_H__ */
> diff --git a/net/Makefile b/net/Makefile
> index 2a700c8401..fac8c8beb9 100644
> --- a/net/Makefile
> +++ b/net/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_CMD_SNTP) += sntp.o
>  obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o
>  obj-$(CONFIG_UDP_FUNCTION_FASTBOOT)  += fastboot.o
>  obj-$(CONFIG_CMD_WOL)  += wol.o
> +obj-$(CONFIG_DM_DSA)   += dsa-uclass.o
>
>  # Disable this warning as it is triggered by:
>  # sprintf(buf, index ? "foo%d" : "foo", index)
> diff --git a/net/dsa-uclass.c b/net/dsa-uclass.c
> new file mode 100644
> index 0000000000..3790a72841
> --- /dev/null
> +++ b/net/dsa-uclass.c
> @@ -0,0 +1,369 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * (C) Copyright 2019, NXP
> + */
> +
> +#include <net/dsa.h>
> +#include <dm/lists.h>
> +#include <dm/device-internal.h>
> +#include <dm/uclass-internal.h>
> +#include <miiphy.h>
> +
> +#define DSA_PORT_CHILD_DRV_NAME "dsa-port"
> +
> +/* helper that returns the DSA master Ethernet device. */
> +static struct udevice *dsa_port_get_master(struct udevice *pdev, bool probe)
> +{
> +       struct udevice *dev = dev_get_parent(pdev);
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +
> +       if (probe)
> +               device_probe(platdata->master_dev);
> +
> +       return platdata->master_dev;
> +}
> +
> +/*
> + * Start the desired port, the CPU port and the master Eth interface.
> + * TODO: if cascaded we may need to _start ports in other switches too
> + */
> +static int dsa_port_start(struct udevice *pdev)
> +{
> +       struct udevice *dev = dev_get_parent(pdev);
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       struct udevice *master = dsa_port_get_master(pdev, true);
> +       struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
> +       struct dsa_ops *ops = dsa_get_ops(dev);
> +       int err;
> +
> +       if (!ppriv || !platdata)
> +               return -EINVAL;
> +
> +       if (!master) {
> +               dev_err(pdev, "DSA master Ethernet device not found!\n");
> +               return -EINVAL;
> +       }
> +
> +       if (ops->port_enable) {
> +               err = ops->port_enable(dev, ppriv->index, ppriv->phy);
> +               if (err)
> +                       return err;
> +               err = ops->port_enable(dev, platdata->cpu_port,
> +                                      platdata->port[platdata->cpu_port].phy);
> +               if (err)
> +                       return err;
> +       }
> +
> +       return eth_get_ops(master)->start(master);
> +}
> +
> +/* Stop the desired port, the CPU port and the master Eth interface */
> +static void dsa_port_stop(struct udevice *pdev)
> +{
> +       struct udevice *dev = dev_get_parent(pdev);
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       struct udevice *master = dsa_port_get_master(pdev, false);
> +       struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
> +       struct dsa_ops *ops = dsa_get_ops(dev);
> +
> +       if (!ppriv || !platdata)
> +               return;
> +
> +       if (ops->port_disable) {
> +               ops->port_disable(dev, ppriv->index, ppriv->phy);
> +               ops->port_disable(dev, platdata->cpu_port,
> +                                 platdata->port[platdata->cpu_port].phy);
> +       }
> +
> +       /*
> +        * stop master only if it's active, don't probe it otherwise.
> +        * Under normal usage it would be active because we're using it, but
> +        * during tear-down it may have been removed ahead of us.
> +        */
> +       if (master && device_active(master))
> +               eth_get_ops(master)->stop(master);
> +}
> +
> +/*
> + * Insert a DSA tag and call master Ethernet send on the resulting packet
> + * We copy the frame to a stack buffer where we have reserved headroom and
> + * tailroom space.  Headroom and tailroom are set to 0.
> + */
> +static int dsa_port_send(struct udevice *pdev, void *packet, int length)
> +{
> +       struct udevice *dev = dev_get_parent(pdev);
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       struct udevice *master = dsa_port_get_master(pdev, true);
> +       struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
> +       struct dsa_ops *ops = dsa_get_ops(dev);
> +       uchar dsa_packet[DSA_MAX_FRAME_SIZE];
> +       int head = platdata->headroom, tail = platdata->tailroom;
> +       int err;
> +
> +       if (!master)
> +               return -EINVAL;
> +
> +       if (length + head + tail > DSA_MAX_FRAME_SIZE)
> +               return -EINVAL;
> +
> +       memset(dsa_packet, 0, head);
> +       memset(dsa_packet + head + length, 0, tail);
> +       memcpy(dsa_packet + head, packet, length);
> +       length += head + tail;
> +
> +       err = ops->xmit(dev, ppriv->index, dsa_packet, length);
> +       if (err)
> +               return err;
> +
> +       return eth_get_ops(master)->send(master, dsa_packet, length);
> +}
> +
> +/* Receive a frame from master Ethernet, process it and pass it on */
> +static int dsa_port_recv(struct udevice *pdev, int flags, uchar **packetp)
> +{
> +       struct udevice *dev = dev_get_parent(pdev);
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       struct udevice *master = dsa_port_get_master(pdev, true);
> +       struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
> +       struct dsa_ops *ops = dsa_get_ops(dev);
> +       int head = platdata->headroom, tail = platdata->tailroom;
> +       int length, port_index, err;
> +
> +       if (!master)
> +               return -EINVAL;
> +
> +       length = eth_get_ops(master)->recv(master, flags, packetp);
> +       if (length <= 0)
> +               return length;
> +
> +       /*
> +        * if we receive frames from a different port or frames that DSA driver
> +        * doesn't like we discard them here.
> +        * In case of discard we return with no frame and expect to be called
> +        * again instead of looping here, so upper layer can deal with timeouts
> +        * and ctrl-c
> +        */
> +       err = ops->rcv(dev, &port_index, *packetp, length);
> +       if (err || port_index != ppriv->index || (length <= head + tail)) {
> +               if (eth_get_ops(master)->free_pkt)
> +                       eth_get_ops(master)->free_pkt(master, *packetp, length);
> +               return -EAGAIN;
> +       }
> +
> +       /*
> +        * We move the pointer over headroom here to avoid a copy.  If free_pkt
> +        * gets called we move the pointer back before calling master free_pkt.
> +        */
> +       *packetp += head;
> +
> +       return length - head - tail;
> +}
> +
> +static int dsa_port_free_pkt(struct udevice *pdev, uchar *packet, int length)
> +{
> +       struct udevice *dev = dev_get_parent(pdev);
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       struct udevice *master = dsa_port_get_master(pdev, true);
> +
> +       if (!master)
> +               return -EINVAL;
> +
> +       if (eth_get_ops(master)->free_pkt) {
> +               /* return the original pointer and length to master Eth */
> +               packet -= platdata->headroom;
> +               length += platdata->headroom - platdata->tailroom;
> +
> +               return eth_get_ops(master)->free_pkt(master, packet, length);
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct eth_ops dsa_port_ops = {
> +       .start          = dsa_port_start,
> +       .send           = dsa_port_send,
> +       .recv           = dsa_port_recv,
> +       .stop           = dsa_port_stop,
> +       .free_pkt       = dsa_port_free_pkt,
> +};
> +
> +U_BOOT_DRIVER(dsa_port) = {
> +       .name   = DSA_PORT_CHILD_DRV_NAME,
> +       .id     = UCLASS_ETH,
> +       .ops    = &dsa_port_ops,
> +       .platdata_auto_alloc_size = sizeof(struct eth_pdata),
> +};
> +
> +/*
> + * reads the DT properties of the given DSA port.
> + * If the return value is != 0 then the port is skipped
> + */
> +static int dsa_port_parse_dt(struct udevice *dev, int port_index,
> +                            ofnode ports_node, bool *is_cpu)
> +{
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       struct dsa_port_platdata *port = &platdata->port[port_index];
> +       ofnode temp_node;
> +       u32 ethernet;
> +
> +       /*
> +        * if we don't have a DT we don't do anything here but the port is
> +        * registered normally
> +        */
> +       if (!ofnode_valid(ports_node))
> +               return 0;
> +
> +       ofnode_for_each_subnode(temp_node, ports_node) {
> +               const char *port_label;
> +               u32 reg;
> +
> +               if (ofnode_read_u32(temp_node, "reg", &reg) ||
> +                   reg != port_index)
> +                       continue;
> +
> +               /* if the port is explicitly disabled in DT skip it */
> +               if (!ofnode_is_available(temp_node))
> +                       return -ENODEV;
> +
> +               port->node = temp_node;
> +

I know I said that this is in principle good to go, but actually!
you should swap these 2, and set port->node = temp_node before you
actually skip ports with status = "disabled". The reason is that you
want the driver to be able to check for
ofnode_is_available(platdata->port[port].node) too, for whatever
reason.

> +               dev_dbg(dev, "port %d node %s\n", port->index,
> +                       ofnode_get_name(port->node));
> +
> +               /* Use 'label' if present in DT */
> +               port_label = ofnode_read_string(port->node, "label");
> +               if (port_label)
> +                       strncpy(port->name, port_label, DSA_PORT_NAME_LENGTH);
> +
> +               *is_cpu = !ofnode_read_u32(port->node, "ethernet",
> +                                          &ethernet);
> +
> +               if (*is_cpu) {
> +                       platdata->master_node =
> +                               ofnode_get_by_phandle(ethernet);
> +                       platdata->cpu_port = port_index;
> +
> +                       dev_dbg(dev, "master node %s on port %d\n",
> +                               ofnode_get_name(platdata->master_node),
> +                               port_index);
> +               }
> +               break;
> +       }
> +
> +       return 0;
> +}
> +
> +/**
> + * This function mostly deals with pulling information out of the device tree
> + * into the platdata structure.
> + * It goes through the list of switch ports, registers an Eth device for each
> + * front panel port and identifies the cpu port connected to master Eth device.
> + * TODO: support cascaded switches
> + */
> +static int dm_dsa_post_bind(struct udevice *dev)
> +{
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       ofnode ports_node = ofnode_null();
> +       int first_err = 0, err = 0, i;
> +
> +       if (!platdata) {
> +               dev_err(dev, "missing plaform data\n");
> +               return -EINVAL;
> +       }
> +
> +       if (platdata->num_ports <= 0 || platdata->num_ports > DSA_MAX_PORTS) {
> +               dev_err(dev, "unexpected num_ports value (%d)\n",
> +                       platdata->num_ports);
> +               return -EINVAL;
> +       }
> +
> +       platdata->master_node = ofnode_null();
> +
> +       if (!ofnode_valid(dev->node)) {
> +               dev_dbg(dev, "Device doesn't have a valid DT node!\n");
> +       } else {
> +               ports_node = ofnode_find_subnode(dev->node, "ports");
> +               if (!ofnode_valid(ports_node))
> +                       dev_dbg(dev,
> +                               "ports node is missing under DSA device!\n");
> +       }
> +
> +       for (i = 0; i < platdata->num_ports; i++) {
> +               struct dsa_port_platdata *port = &platdata->port[i];
> +               bool skip_port, is_cpu = false;
> +
> +               port->index = i;
> +
> +               /*
> +                * If the driver set up port names in _bind use those, otherwise
> +                * use default ones.
> +                * If present, DT label is used as name and overrides anything
> +                * we may have here.
> +                */
> +               if (!strlen(port->name))
> +                       snprintf(port->name, DSA_PORT_NAME_LENGTH, "%s@%d",
> +                                dev->name, i);
> +
> +               skip_port = !!dsa_port_parse_dt(dev, i, ports_node, &is_cpu);
> +
> +               /*
> +                * if this is the CPU port don't register it as an ETH device,
> +                * we skip it on purpose since I/O to/from it from the CPU
> +                * isn't useful
> +                * TODO: cpu port may have a PHY and we don't handle that yet.
> +                */
> +               if (is_cpu || skip_port)
> +                       continue;
> +
> +               err = device_bind_driver_to_node(dev, DSA_PORT_CHILD_DRV_NAME,
> +                                                port->name, port->node,
> +                                                &port->dev);
> +
> +               /* try to bind all ports but keep 1st error */
> +               if (err && !first_err)
> +                       first_err = err;
> +       }
> +
> +       if (!ofnode_valid(platdata->master_node))
> +               dev_dbg(dev, "DSA master Eth device is missing!\n");
> +
> +       return first_err;
> +}
> +
> +/**
> + * This function deals with additional devices around the switch as these should
> + * have been bound to drivers by now.
> + * TODO: pick up references to other switch devices here, if we're cascaded.
> + */
> +static int dm_dsa_pre_probe(struct udevice *dev)
> +{
> +       struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
> +       int i;
> +
> +       if (!platdata)
> +               return -EINVAL;
> +
> +       if (ofnode_valid(platdata->master_node))
> +               uclass_find_device_by_ofnode(UCLASS_ETH, platdata->master_node,
> +                                            &platdata->master_dev);
> +
> +       for (i = 0; i < platdata->num_ports; i++) {
> +               struct dsa_port_platdata *port = &platdata->port[i];
> +
> +               if (port->dev) {
> +                       port->dev->priv = port;
> +                       port->phy = dm_eth_phy_connect(port->dev);
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +UCLASS_DRIVER(dsa) = {
> +       .id = UCLASS_DSA,
> +       .name = "dsa",
> +       .post_bind  = dm_dsa_post_bind,
> +       .pre_probe = dm_dsa_pre_probe,
> +       .per_device_platdata_auto_alloc_size =
> +                       sizeof(struct dsa_perdev_platdata),
> +};
> --
> 2.17.1
>

Regards,
-Vladimir


More information about the U-Boot mailing list