Message ID | 1588700588-8587-2-git-send-email-claudiu.manoil@nxp.com |
---|---|
State | New |
Headers | show |
Series | Introduce DSA Ethernet switch class and Felix driver | expand |
>-----Original Message----- >From: U-Boot <u-boot-bounces@lists.denx.de> On Behalf Of Claudiu Manoil >Sent: Tuesday, May 5, 2020 11:13 PM >To: u-boot@lists.denx.de >Cc: joe.hershberger@ni.com; Alexandru Marginean ><alexandru.marginean@nxp.com>; Vladimir Oltean ><vladimir.oltean@nxp.com> >Subject: [PATCH v4 1/6] net: introduce DSA class for Ethernet switches > >From: Alex Marginean <alexandru.marginean@nxp.com> > >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@nxp.com> >Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> >Signed-off-by: Claudiu Manoil <claudiu.manoil@nxp.com> >--- > drivers/net/Kconfig | 13 ++ > include/dm/uclass-id.h | 1 + > include/net.h | 6 + > include/net/dsa.h | 137 ++++++++++++++ > net/Makefile | 1 + > net/dsa-uclass.c | 395 +++++++++++++++++++++++++++++++++++++++++ > 6 files changed, 553 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 >4d1013c984..863314284b 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 >598f65ea7a..6fc52b72aa 100644 >--- a/include/dm/uclass-id.h >+++ b/include/dm/uclass-id.h >@@ -44,6 +44,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.h b/include/net.h index 82500eeb30..155f49196e >100644 >--- a/include/net.h >+++ b/include/net.h >@@ -489,7 +489,13 @@ struct icmp_hdr { > * maximum packet size and multiple of 32 bytes = 1536 > */ > #define PKTSIZE 1522 >+#ifndef CONFIG_DM_DSA > #define PKTSIZE_ALIGN 1536 >+#else >+/* Maximum DSA tagging overhead (headroom or tailroom) */ >+#define DSA_MAX_OVR 256 >+#define PKTSIZE_ALIGN (1536 + DSA_MAX_OVR) >+#endif > > /* > * Maximum receive ring size; that is, the number of packets diff --git >a/include/net/dsa.h b/include/net/dsa.h new file mode 100644 index >0000000000..45f1d42e7e >--- /dev/null >+++ b/include/net/dsa.h >@@ -0,0 +1,137 @@ >+/* SPDX-License-Identifier: GPL-2.0+ */ >+/* >+ * Copyright 2019-2020 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. >+ * - 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 >+ >+/** >+ * 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 fef71b940a..32f5a7824e 100644 >--- a/net/Makefile >+++ b/net/Makefile >@@ -27,6 +27,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..32573fe45c >--- /dev/null >+++ b/net/dsa-uclass.c >@@ -0,0 +1,395 @@ >+// SPDX-License-Identifier: GPL-2.0+ >+/* >+ * Copyright 2019-2020 NXP >+ */ >+ >+#include <net/dsa.h> >+#include <dm/lists.h> >+#include <dm/device_compat.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) { >+ struct udevice *dev = dev_get_parent(pdev); >+ struct dsa_perdev_platdata *platdata = dev_get_platdata(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); >+ 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); >+ 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); >+ int head = platdata->headroom, tail = platdata->tailroom; >+ struct udevice *master = dsa_port_get_master(pdev); >+ struct dsa_port_platdata *ppriv = dev_get_priv(pdev); >+ struct dsa_ops *ops = dsa_get_ops(dev); >+ uchar dsa_packet_tmp[PKTSIZE_ALIGN]; >+ int err; >+ >+ if (!master) >+ return -EINVAL; >+ >+ if (length + head + tail > PKTSIZE_ALIGN) >+ return -EINVAL; >+ >+ memset(dsa_packet_tmp, 0, head); >+ memset(dsa_packet_tmp + head + length, 0, tail); >+ memcpy(dsa_packet_tmp + head, packet, length); >+ length += head + tail; >+ /* copy back to preserve original buffer alignment */ >+ memcpy(packet, dsa_packet_tmp, length); >+ >+ err = ops->xmit(dev, ppriv->index, packet, length); >+ if (err) >+ return err; >+ >+ return eth_get_ops(master)->send(master, 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); >+ 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); >+ >+ 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 int dsa_port_probe(struct udevice *pdev) { >+ struct udevice *master = dsa_port_get_master(pdev); >+ unsigned char env_enetaddr[ARP_HLEN]; >+ >+ /* If there is no MAC address in the environment, inherit it >+ * from the DSA master. >+ */ >+ eth_env_get_enetaddr_by_index("eth", pdev->seq, env_enetaddr); >+ if (!is_zero_ethaddr(env_enetaddr)) >+ return 0; >+ >+ if (master) { >+ struct eth_pdata *meth = dev_get_platdata(master); >+ struct eth_pdata *peth = dev_get_platdata(pdev); >+ >+ memcpy(peth->enetaddr, meth->enetaddr, ARP_HLEN); >+ eth_env_set_enetaddr_by_index("eth", pdev->seq, >+ meth->enetaddr); >+ } >+ >+ 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, >+ .probe = dsa_port_probe, >+ .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; >+ >+ port->node = temp_node; >+ /* if the port is explicitly disabled in DT skip it */ >+ if (!ofnode_is_available(temp_node)) >+ return -ENODEV; >+ >+ 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]; >+ >+ /* non-cpu ports only */ >+ if (!port->dev) >+ continue; >+ >+ 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 Please help to rebase the patch series on top of master branch. Regards Priyanka
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 4d1013c984..863314284b 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 598f65ea7a..6fc52b72aa 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -44,6 +44,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.h b/include/net.h index 82500eeb30..155f49196e 100644 --- a/include/net.h +++ b/include/net.h @@ -489,7 +489,13 @@ struct icmp_hdr { * maximum packet size and multiple of 32 bytes = 1536 */ #define PKTSIZE 1522 +#ifndef CONFIG_DM_DSA #define PKTSIZE_ALIGN 1536 +#else +/* Maximum DSA tagging overhead (headroom or tailroom) */ +#define DSA_MAX_OVR 256 +#define PKTSIZE_ALIGN (1536 + DSA_MAX_OVR) +#endif /* * Maximum receive ring size; that is, the number of packets diff --git a/include/net/dsa.h b/include/net/dsa.h new file mode 100644 index 0000000000..45f1d42e7e --- /dev/null +++ b/include/net/dsa.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2019-2020 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. + * - 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 + +/** + * 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 fef71b940a..32f5a7824e 100644 --- a/net/Makefile +++ b/net/Makefile @@ -27,6 +27,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..32573fe45c --- /dev/null +++ b/net/dsa-uclass.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019-2020 NXP + */ + +#include <net/dsa.h> +#include <dm/lists.h> +#include <dm/device_compat.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) +{ + struct udevice *dev = dev_get_parent(pdev); + struct dsa_perdev_platdata *platdata = dev_get_platdata(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); + 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); + 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); + int head = platdata->headroom, tail = platdata->tailroom; + struct udevice *master = dsa_port_get_master(pdev); + struct dsa_port_platdata *ppriv = dev_get_priv(pdev); + struct dsa_ops *ops = dsa_get_ops(dev); + uchar dsa_packet_tmp[PKTSIZE_ALIGN]; + int err; + + if (!master) + return -EINVAL; + + if (length + head + tail > PKTSIZE_ALIGN) + return -EINVAL; + + memset(dsa_packet_tmp, 0, head); + memset(dsa_packet_tmp + head + length, 0, tail); + memcpy(dsa_packet_tmp + head, packet, length); + length += head + tail; + /* copy back to preserve original buffer alignment */ + memcpy(packet, dsa_packet_tmp, length); + + err = ops->xmit(dev, ppriv->index, packet, length); + if (err) + return err; + + return eth_get_ops(master)->send(master, 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); + 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); + + 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 int dsa_port_probe(struct udevice *pdev) +{ + struct udevice *master = dsa_port_get_master(pdev); + unsigned char env_enetaddr[ARP_HLEN]; + + /* If there is no MAC address in the environment, inherit it + * from the DSA master. + */ + eth_env_get_enetaddr_by_index("eth", pdev->seq, env_enetaddr); + if (!is_zero_ethaddr(env_enetaddr)) + return 0; + + if (master) { + struct eth_pdata *meth = dev_get_platdata(master); + struct eth_pdata *peth = dev_get_platdata(pdev); + + memcpy(peth->enetaddr, meth->enetaddr, ARP_HLEN); + eth_env_set_enetaddr_by_index("eth", pdev->seq, + meth->enetaddr); + } + + 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, + .probe = dsa_port_probe, + .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; + + port->node = temp_node; + /* if the port is explicitly disabled in DT skip it */ + if (!ofnode_is_available(temp_node)) + return -ENODEV; + + 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]; + + /* non-cpu ports only */ + if (!port->dev) + continue; + + 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), +};