From: Claudiu Manoil Date: Mon, 25 Jan 2021 12:23:53 +0000 (+0200) Subject: net: Introduce DSA class for Ethernet switches X-Git-Url: http://git.dujemihanovic.xyz/login.html?a=commitdiff_plain;h=fc054d563bfb676f808f26204dfe17c690eed1ba;p=u-boot.git net: Introduce DSA class for Ethernet switches 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 Signed-off-by: Claudiu Manoil Reviewed-by: Simon Glass Signed-off-by: Vladimir Oltean --- diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 971a572248..0e84c22b50 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -37,6 +37,21 @@ 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 + depends on PHY_FIXED + 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 the "master" Ethernet interface in DSA terminology. + This is currently implemented in net/dsa-uclass.c, refer to + include/net/dsa.h for API details. + 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 ae4425d7a5..d75de368c5 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -46,6 +46,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_ETH_PHY, /* Ethernet PHY device */ diff --git a/include/net.h b/include/net.h index 13da69b7c1..b95d6a6f60 100644 --- a/include/net.h +++ b/include/net.h @@ -499,7 +499,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 and/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..0f31a908c9 --- /dev/null +++ b/include/net/dsa.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2019-2021 NXP Semiconductors + */ + +#ifndef __DSA_H__ +#define __DSA_H__ + +#include +#include + +/** + * 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: + * + * |------| + * | eth0 | <--- master eth device (regular eth driver) + * |------| + * ^ | + * tag added by switch -->| | + * | | + * | |<-- tag added by DSA driver + * | v + * |--------------------------------------| + * | | CPU port | | <-- DSA (switch) device + * | ------------ | (DSA driver) + * | _________ _________ _________ | + * | | port0 | | port1 | ... | portn | | <-- ports as eth devices + * |-+-------+--+-------+-------+-------+-| ('dsa-port' eth driver) + * + * In U-Boot the intent is to allow access to front panel ports (shown at the + * bottom of the picture) through the master Ethernet dev (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 I/O for 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 head and tailroom. + * @rcv: Process the DSA tag on reception and return the port index + * from the h/w provided tag. Return the index via 'portp'. + * '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 *portp, void *packet, int length); +}; + +#define dsa_get_ops(dev) ((struct dsa_ops *)(dev)->driver->ops) + +/** + * struct dsa_port_pdata - DSA port platform data + * + * @phy: PHY device associated with this port. + * The uclass code attempts to set this field for all ports except CPU + * port, based on DT information. It may be NULL. + * @index: Port index in the DSA switch, set by the uclass code. + * @name: Name of the port Eth device. If a label property is present in the + * port DT node, it is used as name. + */ +struct dsa_port_pdata { + struct phy_device *phy; + u32 index; + char name[DSA_PORT_NAME_LENGTH]; +}; + +/** + * struct dsa_pdata - Per-device platform data for DSA DM + * + * @num_ports: Number of ports the device has, must be <= DSA_MAX_PORTS. + * This number is extracted from the DT 'ports' node of this + * DSA device, and it counts the CPU port and all the other + * port subnodes including the disabled ones. + * @cpu_port: Index of the switch port linked to the master Ethernet. + * The uclass code sets this based on DT information. + * @master_node: OF node of the host Ethernet controller. + * @cpu_port_node: DT node of the switch's CPU port. + */ +struct dsa_pdata { + int num_ports; + u32 cpu_port; + ofnode master_node; + ofnode cpu_port_node; +}; + +/** + * dsa_set_tagging() - Configure the headroom and/or tailroom sizes + * + * The DSA class code allocates headroom and tailroom on Tx before + * calling the DSA driver's xmit function. + * All drivers must call this at probe time. + * + * @dev: DSA device pointer + * @headroom: Size, in bytes, of headroom needed for the DSA tag. + * @tailroom: Size, in bytes, of tailroom needed for the DSA tag. + * Total headroom and tailroom size should not exceed + * DSA_MAX_OVR. + * @return 0 if OK, -ve on error + */ +int dsa_set_tagging(struct udevice *dev, ushort headroom, ushort tailroom); + +/* DSA helpers */ + +/** + * dsa_get_master() - Return a reference to the master Ethernet device + * + * Can be called at driver probe time or later. + * + * @dev: DSA device pointer + * @return Master Eth 'udevice' pointer if OK, NULL on error + */ +struct udevice *dsa_get_master(struct udevice *dev); + +/** + * dsa_port_get_pdata() - Helper that returns the platdata of an active + * (non-CPU) DSA port device. + * + * Can be called at driver probe time or later. + * + * @pdev: DSA port device pointer + * @return 'dsa_port_pdata' pointer if OK, NULL on error + */ +static inline struct dsa_port_pdata * + dsa_port_get_pdata(struct udevice *pdev) +{ + struct eth_pdata *eth = dev_get_plat(pdev); + + if (!eth) + return NULL; + + return eth->priv_pdata; +} + +#endif /* __DSA_H__ */ diff --git a/net/Makefile b/net/Makefile index 76527f704c..fb3eba840f 100644 --- a/net/Makefile +++ b/net/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_NET) += arp.o obj-$(CONFIG_CMD_BOOTP) += bootp.o obj-$(CONFIG_CMD_CDP) += cdp.o obj-$(CONFIG_CMD_DNS) += dns.o +obj-$(CONFIG_DM_DSA) += dsa-uclass.o ifdef CONFIG_DM_ETH obj-$(CONFIG_NET) += eth-uclass.o else diff --git a/net/dsa-uclass.c b/net/dsa-uclass.c new file mode 100644 index 0000000000..2ce9ddb90d --- /dev/null +++ b/net/dsa-uclass.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DSA_PORT_CHILD_DRV_NAME "dsa-port" + +/* per-device internal state structure */ +struct dsa_priv { + struct phy_device *cpu_port_fixed_phy; + struct udevice *master_dev; + int num_ports; + u32 cpu_port; + int headroom; + int tailroom; +}; + +/* external API */ +int dsa_set_tagging(struct udevice *dev, ushort headroom, ushort tailroom) +{ + struct dsa_priv *priv; + + if (!dev || !dev_get_uclass_priv(dev)) + return -ENODEV; + + if (headroom + tailroom > DSA_MAX_OVR) + return -EINVAL; + + priv = dev_get_uclass_priv(dev); + + if (headroom > 0) + priv->headroom = headroom; + if (tailroom > 0) + priv->tailroom = tailroom; + + return 0; +} + +/* returns the DSA master Ethernet device */ +struct udevice *dsa_get_master(struct udevice *dev) +{ + struct dsa_priv *priv = dev_get_uclass_priv(dev); + + if (!priv) + return NULL; + + return priv->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_priv *priv = dev_get_uclass_priv(dev); + struct udevice *master = dsa_get_master(dev); + struct dsa_ops *ops = dsa_get_ops(dev); + int err; + + if (!priv) + return -ENODEV; + + if (!master) { + dev_err(pdev, "DSA master Ethernet device not found!\n"); + return -EINVAL; + } + + if (ops->port_enable) { + struct dsa_port_pdata *port_pdata; + + port_pdata = dev_get_parent_plat(pdev); + err = ops->port_enable(dev, port_pdata->index, + port_pdata->phy); + if (err) + return err; + + err = ops->port_enable(dev, priv->cpu_port, + priv->cpu_port_fixed_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_priv *priv = dev_get_uclass_priv(dev); + struct udevice *master = dsa_get_master(dev); + struct dsa_ops *ops = dsa_get_ops(dev); + + if (!priv) + return; + + if (ops->port_disable) { + struct dsa_port_pdata *port_pdata; + + port_pdata = dev_get_parent_plat(pdev); + ops->port_disable(dev, port_pdata->index, port_pdata->phy); + ops->port_disable(dev, priv->cpu_port, NULL); + } + + /* + * 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_priv *priv = dev_get_uclass_priv(dev); + int head = priv->headroom, tail = priv->tailroom; + struct udevice *master = dsa_get_master(dev); + struct dsa_ops *ops = dsa_get_ops(dev); + uchar dsa_packet_tmp[PKTSIZE_ALIGN]; + struct dsa_port_pdata *port_pdata; + 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); + + port_pdata = dev_get_parent_plat(pdev); + err = ops->xmit(dev, port_pdata->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_priv *priv = dev_get_uclass_priv(dev); + int head = priv->headroom, tail = priv->tailroom; + struct udevice *master = dsa_get_master(dev); + struct dsa_ops *ops = dsa_get_ops(dev); + struct dsa_port_pdata *port_pdata; + 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. + */ + port_pdata = dev_get_parent_plat(pdev); + err = ops->rcv(dev, &port_index, *packetp, length); + if (err || port_index != port_pdata->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 udevice *master = dsa_get_master(dev); + struct dsa_priv *priv; + + if (!master) + return -EINVAL; + + priv = dev_get_uclass_priv(dev); + if (eth_get_ops(master)->free_pkt) { + /* return the original pointer and length to master Eth */ + packet -= priv->headroom; + length += priv->headroom - priv->tailroom; + + return eth_get_ops(master)->free_pkt(master, packet, length); + } + + return 0; +} + +static int dsa_port_of_to_pdata(struct udevice *pdev) +{ + struct dsa_port_pdata *port_pdata; + struct dsa_pdata *dsa_pdata; + struct eth_pdata *eth_pdata; + struct udevice *dev; + const char *label; + u32 index; + int err; + + if (!pdev) + return -ENODEV; + + err = ofnode_read_u32(dev_ofnode(pdev), "reg", &index); + if (err) + return err; + + dev = dev_get_parent(pdev); + dsa_pdata = dev_get_uclass_plat(dev); + + port_pdata = dev_get_parent_plat(pdev); + port_pdata->index = index; + + label = ofnode_read_string(dev_ofnode(pdev), "label"); + if (label) + strncpy(port_pdata->name, label, DSA_PORT_NAME_LENGTH); + + eth_pdata = dev_get_plat(pdev); + eth_pdata->priv_pdata = port_pdata; + + dev_dbg(pdev, "port %d node %s\n", port_pdata->index, + ofnode_get_name(dev_ofnode(pdev))); + + 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, +}; + +static int dsa_port_probe(struct udevice *pdev) +{ + struct udevice *dev = dev_get_parent(pdev); + struct eth_pdata *eth_pdata, *master_pdata; + unsigned char env_enetaddr[ARP_HLEN]; + struct dsa_port_pdata *port_pdata; + struct dsa_priv *dsa_priv; + struct udevice *master; + + port_pdata = dev_get_parent_plat(pdev); + dsa_priv = dev_get_uclass_priv(dev); + + port_pdata->phy = dm_eth_phy_connect(pdev); + if (!port_pdata->phy) + return -ENODEV; + + /* + * Inherit port's hwaddr from the DSA master, unless the port already + * has a unique MAC address specified in the environment. + */ + eth_env_get_enetaddr_by_index("eth", dev_seq(pdev), env_enetaddr); + if (!is_zero_ethaddr(env_enetaddr)) + return 0; + + master = dsa_get_master(dev); + if (!master) + return 0; + + master_pdata = dev_get_plat(master); + eth_pdata = dev_get_plat(pdev); + memcpy(eth_pdata->enetaddr, master_pdata->enetaddr, ARP_HLEN); + eth_env_set_enetaddr_by_index("eth", dev_seq(pdev), + master_pdata->enetaddr); + + return 0; +} + +static int dsa_port_remove(struct udevice *pdev) +{ + struct udevice *dev = dev_get_parent(pdev); + struct dsa_port_pdata *port_pdata; + struct dsa_priv *dsa_priv; + + port_pdata = dev_get_parent_plat(pdev); + dsa_priv = dev_get_uclass_priv(dev); + + port_pdata->phy = NULL; + + return 0; +} + +U_BOOT_DRIVER(dsa_port) = { + .name = DSA_PORT_CHILD_DRV_NAME, + .id = UCLASS_ETH, + .ops = &dsa_port_ops, + .probe = dsa_port_probe, + .remove = dsa_port_remove, + .of_to_plat = dsa_port_of_to_pdata, + .plat_auto = sizeof(struct eth_pdata), +}; + +/* + * This function mostly deals with pulling information out of the device tree + * into the pdata 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 dsa_post_bind(struct udevice *dev) +{ + struct dsa_pdata *pdata = dev_get_uclass_plat(dev); + ofnode node = dev_ofnode(dev), pnode; + int i, err, first_err = 0; + + if (!pdata || !ofnode_valid(node)) + return -ENODEV; + + pdata->master_node = ofnode_null(); + + node = ofnode_find_subnode(node, "ports"); + if (!ofnode_valid(node)) + node = ofnode_find_subnode(node, "ethernet-ports"); + if (!ofnode_valid(node)) { + dev_err(dev, "ports node is missing under DSA device!\n"); + return -EINVAL; + } + + pdata->num_ports = ofnode_get_child_count(node); + if (pdata->num_ports <= 0 || pdata->num_ports > DSA_MAX_PORTS) { + dev_err(dev, "invalid number of ports (%d)\n", + pdata->num_ports); + return -EINVAL; + } + + /* look for the CPU port */ + ofnode_for_each_subnode(pnode, node) { + u32 ethernet; + + if (ofnode_read_u32(pnode, "ethernet", ðernet)) + continue; + + pdata->master_node = ofnode_get_by_phandle(ethernet); + pdata->cpu_port_node = pnode; + break; + } + + if (!ofnode_valid(pdata->master_node)) { + dev_err(dev, "master eth node missing!\n"); + return -EINVAL; + } + + if (ofnode_read_u32(pnode, "reg", &pdata->cpu_port)) { + dev_err(dev, "CPU port node not valid!\n"); + return -EINVAL; + } + + dev_dbg(dev, "master node %s on port %d\n", + ofnode_get_name(pdata->master_node), pdata->cpu_port); + + for (i = 0; i < pdata->num_ports; i++) { + char name[DSA_PORT_NAME_LENGTH]; + struct udevice *pdev; + + /* + * 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. + */ + if (i == pdata->cpu_port) + continue; + + /* + * Set up default port names. If present, DT port labels + * will override the default port names. + */ + snprintf(name, DSA_PORT_NAME_LENGTH, "%s@%d", dev->name, i); + + ofnode_for_each_subnode(pnode, node) { + u32 reg; + + if (ofnode_read_u32(pnode, "reg", ®)) + continue; + + if (reg == i) + break; + } + + /* + * skip registration if port id not found or if the port + * is explicitly disabled in DT + */ + if (!ofnode_valid(pnode) || !ofnode_is_available(pnode)) + continue; + + err = device_bind_driver_to_node(dev, DSA_PORT_CHILD_DRV_NAME, + name, pnode, &pdev); + if (pdev) { + struct dsa_port_pdata *port_pdata; + + port_pdata = dev_get_parent_plat(pdev); + strncpy(port_pdata->name, name, DSA_PORT_NAME_LENGTH); + pdev->name = port_pdata->name; + } + + /* try to bind all ports but keep 1st error */ + if (err && !first_err) + first_err = err; + } + + if (first_err) + return first_err; + + dev_dbg(dev, "DSA ports successfully bound\n"); + + return 0; +} + +/** + * Initialize the uclass per device internal state structure (priv). + * TODO: pick up references to other switch devices here, if we're cascaded. + */ +static int dsa_pre_probe(struct udevice *dev) +{ + struct dsa_pdata *pdata = dev_get_uclass_plat(dev); + struct dsa_priv *priv = dev_get_uclass_priv(dev); + + if (!pdata || !priv) + return -ENODEV; + + priv->num_ports = pdata->num_ports; + priv->cpu_port = pdata->cpu_port; + priv->cpu_port_fixed_phy = fixed_phy_create(pdata->cpu_port_node); + if (!priv->cpu_port_fixed_phy) { + dev_err(dev, "Failed to register fixed-link for CPU port\n"); + return -ENODEV; + } + + uclass_find_device_by_ofnode(UCLASS_ETH, pdata->master_node, + &priv->master_dev); + return 0; +} + +UCLASS_DRIVER(dsa) = { + .id = UCLASS_DSA, + .name = "dsa", + .post_bind = dsa_post_bind, + .pre_probe = dsa_pre_probe, + .per_device_auto = sizeof(struct dsa_priv), + .per_device_plat_auto = sizeof(struct dsa_pdata), + .per_child_plat_auto = sizeof(struct dsa_port_pdata), + .flags = DM_UC_FLAG_SEQ_ALIAS, +};