[U-Boot] [PATCH 1/6] net: introduce DSA class for Ethernet switches
Alex Marginean
alexandru.marginean at nxp.com
Fri Nov 22 01:36:54 UTC 2019
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/dsa.h | 132 +++++++++++++++
net/Makefile | 1 +
net/dsa-uclass.c | 369 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 516 insertions(+)
create mode 100644 include/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/dsa.h b/include/dsa.h
new file mode 100644
index 0000000000..72a2e6abaa
--- /dev/null
+++ b/include/dsa.h
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2019 NXP
+ */
+
+#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 acces 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.
+ */
+
+#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.
+ * #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];
+};
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..e5540c639d
--- /dev/null
+++ b/net/dsa-uclass.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2019, NXP
+ */
+
+#include <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 != 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;
+
+ 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",
+ ðernet);
+
+ 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
More information about the U-Boot
mailing list