[PATCH v3 03/12] net: add DSA framework to support basic switch functionality

Sascha Hauer sha at pengutronix.de
Thu Apr 7 06:56:57 PDT 2022


On Thu, Apr 07, 2022 at 11:15:55AM +0200, Oleksij Rempel wrote:
> Add DSA based port multiplexing functionality for barebox. With this
> framework we will be able to use different ports of as switch
> separately.
> 
> Signed-off-by: Oleksij Rempel <o.rempel at pengutronix.de>
> ---
>  drivers/net/Kconfig  |   4 +
>  drivers/net/Makefile |   1 +
>  drivers/net/dsa.c    | 460 +++++++++++++++++++++++++++++++++++++++++++
>  include/dsa.h        |  90 +++++++++
>  4 files changed, 555 insertions(+)
>  create mode 100644 drivers/net/dsa.c
>  create mode 100644 include/dsa.h
> 
> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> index 65c93bbe84..8d76fe66f2 100644
> --- a/drivers/net/Kconfig
> +++ b/drivers/net/Kconfig
> @@ -17,6 +17,10 @@ config HAS_MACB
>  config PHYLIB
>  	bool
>  
> +config DSA
> +	bool
> +	select PHYLIB
> +
>  menu "Network drivers"
>  	depends on NET
>  
> diff --git a/drivers/net/Makefile b/drivers/net/Makefile
> index 40045bab04..406e13f1a0 100644
> --- a/drivers/net/Makefile
> +++ b/drivers/net/Makefile
> @@ -1,4 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_DSA)			+= dsa.o
>  obj-$(CONFIG_PHYLIB)			+= phy/
>  obj-$(CONFIG_NET_USB)			+= usb/
>  
> diff --git a/drivers/net/dsa.c b/drivers/net/dsa.c
> new file mode 100644
> index 0000000000..2a93f0cd9f
> --- /dev/null
> +++ b/drivers/net/dsa.c
> @@ -0,0 +1,460 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <common.h>
> +#include <dma.h>
> +#include <dsa.h>
> +#include <of_net.h>
> +
> +u32 dsa_user_ports(struct dsa_switch *ds)
> +{
> +	u32 mask = 0;
> +	int i;
> +
> +	for (i = 0; i < ds->num_ports; i++) {
> +		if (ds->dp[i])
> +			mask |= BIT(ds->dp[i]->index);
> +	}
> +
> +	return mask;
> +}
> +
> +static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
> +{
> +	struct dsa_switch *ds = bus->priv;
> +
> +	if (ds->phys_mii_mask & BIT(addr))
> +		return ds->ops->phy_read(ds, addr, reg);
> +
> +	return 0xffff;
> +}
> +
> +static int dsa_slave_phy_write(struct mii_bus *bus, int addr, int reg, u16 val)
> +{
> +	struct dsa_switch *ds = bus->priv;
> +
> +	if (ds->phys_mii_mask & BIT(addr))
> +		return ds->ops->phy_write(ds, addr, reg, val);
> +
> +	return 0;
> +}
> +
> +static int dsa_slave_mii_bus_init(struct dsa_switch *ds)
> +{
> +	ds->slave_mii_bus = xzalloc(sizeof(*ds->slave_mii_bus));
> +	ds->slave_mii_bus->priv = (void *)ds;
> +	ds->slave_mii_bus->read = dsa_slave_phy_read;
> +	ds->slave_mii_bus->write = dsa_slave_phy_write;
> +	ds->slave_mii_bus->parent = ds->dev;
> +	ds->slave_mii_bus->phy_mask = ~ds->phys_mii_mask;
> +
> +	return mdiobus_register(ds->slave_mii_bus);
> +}
> +
> +static int dsa_port_probe(struct eth_device *edev)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +	const struct dsa_ops *ops = ds->ops;
> +	phy_interface_t interface;
> +	int ret;
> +
> +	if (ops->port_probe) {
> +		interface = of_get_phy_mode(dp->dev.device_node);
> +		ret = ops->port_probe(dp, dp->index, interface);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void dsa_port_set_ethaddr(struct eth_device *edev)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +
> +	if (is_valid_ether_addr(edev->ethaddr))
> +		return;
> +
> +	if (!is_valid_ether_addr(ds->edev_master->ethaddr))
> +		return;
> +
> +	eth_set_ethaddr(edev, ds->edev_master->ethaddr);
> +}
> +
> +static int dsa_port_start(struct eth_device *edev)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +	const struct dsa_ops *ops = ds->ops;
> +	phy_interface_t interface;
> +	int ret;
> +
> +	if (dp->enabled)
> +		return -EBUSY;
> +
> +	interface = of_get_phy_mode(dp->dev.device_node);
> +
> +	if (ops->port_pre_enable) {
> +		/* In case of RMII interface we need to enable RMII clock
> +		 * before talking to the PHY.
> +		 */
> +		ret = ops->port_pre_enable(dp, dp->index, interface);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = phy_device_connect(edev, ds->slave_mii_bus, dp->index, NULL, 0,
> +				 interface);
> +	if (ret)
> +		return ret;
> +
> +	dsa_port_set_ethaddr(edev);
> +
> +	if (ops->port_enable) {
> +		ret = ops->port_enable(dp, dp->index, dp->edev.phydev);

The test suggests that ops->port_enable is optional...

> +		if (ret)
> +			return ret;
> +	}
> +
> +	dp->enabled = true;
> +
> +	if (!ds->cpu_port_users) {
> +		struct dsa_port *dpc = ds->dp[ds->cpu_port];
> +
> +		ret = ops->port_enable(dpc, ds->cpu_port,
> +				       ds->cpu_port_fixed_phy);

...but it's called unconditionally here.

> +		if (ret)
> +			return ret;
> +		eth_open(ds->edev_master);
> +		ds->cpu_port_users++;

ds->cpu_port_users must be incremented on every dsa_port_start(), not
only the first one.

> +	}
> +
> +	return 0;
> +}
> +
> +/* Stop the desired port, the CPU port and the master Eth interface */
> +static void dsa_port_stop(struct eth_device *edev)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +	const struct dsa_ops *ops = ds->ops;
> +
> +	if (!dp->enabled)
> +		return;
> +
> +	if (ops->port_disable)
> +		ops->port_disable(dp, dp->index, dp->edev.phydev);
> +
> +	dp->enabled = false;
> +	ds->cpu_port_users--;
> +
> +	if (!ds->cpu_port_users) {
> +		struct dsa_port *dpc = ds->dp[ds->cpu_port];
> +
> +		ops->port_disable(dpc, ds->cpu_port, ds->cpu_port_fixed_phy);

Please decide if ops->port_disable is optional or not.

> +		eth_close(ds->edev_master);
> +	}
> +}
> +
> +static int dsa_port_send(struct eth_device *edev, void *packet, int length)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +	struct eth_device *edev_master;
> +	const struct dsa_ops *ops = ds->ops;
> +	void *tx_buf = ds->tx_buf;
> +	size_t full_length, stuff = 0;
> +	int ret;
> +
> +	if (length < 64)
> +		stuff = 64 - length;
> +
> +	full_length = length + ds->needed_headroom + ds->needed_tx_tailroom +
> +		      stuff;
> +
> +	if (full_length > DSA_PKTSIZE)
> +		return -ENOMEM;
> +
> +	memset(tx_buf + full_length - stuff, 0, stuff);
> +	memcpy(tx_buf + ds->needed_headroom, packet, length);
> +	ret = ops->xmit(dp, dp->index, tx_buf, full_length);
> +	if (ret)
> +		return ret;
> +
> +	edev_master = ds->edev_master;
> +
> +	return eth_send_raw(edev_master, tx_buf, full_length);

drop edev_master and use ds->edev_master directly.

> +}
> +
> +static int dsa_port_recv(struct eth_device *edev)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	int length;
> +
> +	if (!dp->rx_buf_length)
> +		return 0;
> +
> +	net_receive(edev, dp->rx_buf, dp->rx_buf_length);
> +	length = dp->rx_buf_length;
> +	dp->rx_buf_length = 0;
> +
> +	return length;
> +}
> +
> +static int dsa_ether_set_ethaddr(struct eth_device *edev,
> +				 const unsigned char *adr)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +	struct eth_device *edev_master;
> +
> +	edev_master = ds->edev_master;
> +
> +	return edev_master->set_ethaddr(edev_master, adr);
> +}
> +
> +static int dsa_ether_get_ethaddr(struct eth_device *edev, unsigned char *adr)
> +{
> +	struct dsa_port *dp = edev->priv;
> +	struct dsa_switch *ds = dp->ds;
> +	struct eth_device *edev_master;
> +
> +	edev_master = ds->edev_master;
> +
> +	return edev_master->get_ethaddr(edev_master, adr);
> +}
> +
> +static int dsa_switch_regiser_edev(struct dsa_switch *ds,
> +				   struct device_node *dn, int port)
> +{
> +	struct eth_device *edev;
> +	struct device_d *dev;
> +	struct dsa_port *dp;
> +	int ret;
> +
> +	ds->dp[port] = xzalloc(sizeof(*dp));
> +
> +	dp = ds->dp[port];
> +	dev = &dp->dev;
> +
> +	dev_set_name(dev, "dsa_port");
> +	dev->id = DEVICE_ID_DYNAMIC;
> +	dev->parent = ds->dev;
> +	dev->device_node = dn;
> +
> +	ret = register_device(dev);
> +	if (ret)
> +		return ret;
> +
> +	dp->rx_buf = xmalloc(DSA_PKTSIZE);
> +	dp->ds = ds;
> +	dp->index = port;
> +
> +	edev = &dp->edev;
> +	edev->priv = dp;
> +	edev->parent = dev;
> +	edev->init = dsa_port_probe;
> +	edev->open = dsa_port_start;
> +	edev->send = dsa_port_send;
> +	edev->recv = dsa_port_recv;
> +	edev->halt = dsa_port_stop;
> +	edev->get_ethaddr = dsa_ether_get_ethaddr;
> +	edev->set_ethaddr = dsa_ether_set_ethaddr;
> +
> +	return eth_register(edev);
> +}
> +
> +static int dsa_rx_preprocessor(struct eth_device *edev, unsigned char **packet,
> +			       int *length)
> +{
> +	struct dsa_switch *ds = edev->rx_preprocessor_priv;
> +	const struct dsa_ops *ops = ds->ops;
> +	struct dsa_port *dp;
> +	int ret, port;
> +
> +	ret = ops->rcv(ds, &port, *packet, *length);
> +	if (ret)
> +		return ret;
> +
> +	*length -= ds->needed_headroom;
> +	*packet += ds->needed_headroom;
> +	*length -= ds->needed_rx_tailroom;
> +
> +	if (port > DSA_MAX_PORTS)
> +		return -ERANGE;
> +
> +	dp = ds->dp[port];
> +	if (!dp)
> +		return 0;
> +
> +	if (*length > DSA_PKTSIZE)
> +		return -ENOMEM;
> +
> +	if (dp->rx_buf_length)
> +		return -EIO;
> +
> +	memcpy(dp->rx_buf, *packet, *length);
> +	dp->rx_buf_length = *length;
> +
> +	return -ENOMSG;
> +}
> +
> +static int dsa_switch_regiser_master(struct dsa_switch *ds,
> +				     struct device_node *np,
> +				     struct device_node *master, int port)

s/regiser/register/

> +{
> +	struct device_node *phy_node;
> +	struct phy_device *phydev;
> +	struct dsa_port *dp;
> +	int ret;
> +
> +	of_device_ensure_probed(master);
> +
> +	if (ds->edev_master) {
> +		dev_err(ds->dev, "master was already registered!\n");
> +		return -EINVAL;
> +	}
> +
> +	ds->edev_master = of_find_eth_device_by_node(master);
> +	if (!ds->edev_master) {
> +		dev_err(ds->dev, "can't find ethernet master device\n");
> +		return -ENODEV;
> +	}
> +
> +	ds->edev_master->rx_preprocessor = dsa_rx_preprocessor;
> +	ds->edev_master->rx_preprocessor_priv = ds;
> +
> +	ret = dev_set_param(&ds->edev_master->dev, "mode", "disabled");
> +	if (ret)
> +		dev_warn(ds->dev, "Can't set disable master Ethernet device\n");
> +
> +	phydev = phy_device_create(NULL, 0, 0);
> +
> +	phydev->registered = 1;
> +	phydev->link = 1;
> +
> +	phy_node = of_get_child_by_name(np, "fixed-link");
> +	if (!phy_node)
> +		return -ENODEV;
> +
> +	if (of_property_read_u32(phy_node, "speed", &phydev->speed))
> +		return -ENODEV;
> +
> +	phydev->duplex = of_property_read_bool(phy_node, "full-duplex");
> +	phydev->pause = of_property_read_bool(phy_node, "pause");
> +	phydev->asym_pause = of_property_read_bool(phy_node, "asym-pause");
> +	phydev->interface = of_get_phy_mode(np);
> +
> +	ds->dp[port] = xzalloc(sizeof(*dp));
> +	dp = ds->dp[port];
> +	dp->ds = ds;
> +
> +	ds->cpu_port = port;
> +	ds->cpu_port_fixed_phy = phydev;
> +
> +	return 0;
> +}
> +
> +static int dsa_switch_parse_ports_of(struct dsa_switch *ds,
> +				     struct device_node *dn)
> +{
> +	struct device_node *ports, *port;
> +	int ret = 0;
> +	u32 reg;
> +
> +	ports = of_get_child_by_name(dn, "ports");
> +	if (!ports) {
> +		/* The second possibility is "ethernet-ports" */
> +		ports = of_get_child_by_name(dn, "ethernet-ports");
> +		if (!ports) {
> +			dev_err(ds->dev, "no ports child node found\n");
> +			return -EINVAL;
> +		}
> +	}
> +
> +	for_each_available_child_of_node(ports, port) {
> +		struct device_node *master;
> +
> +		ret = of_property_read_u32(port, "reg", &reg);
> +		if (ret) {
> +			dev_err(ds->dev, "No or too many ports are configured\n");
> +			goto out_put_node;
> +		}
> +
> +		if (reg >= ds->num_ports) {
> +			dev_err(ds->dev, "port %pOF index %u exceeds num_ports (%u)\n",
> +				port, reg, ds->num_ports);
> +			ret = -EINVAL;
> +			goto out_put_node;
> +		}
> +
> +		master = of_parse_phandle(port, "ethernet", 0);
> +		if (master)
> +			dsa_switch_regiser_master(ds, port, master, reg);
> +	}
> +
> +	for_each_available_child_of_node(ports, port) {
> +		struct device_node *master;
> +
> +		ret = of_property_read_u32(port, "reg", &reg);
> +		if (ret) {
> +			dev_err(ds->dev, "No or too many ports are configured\n");
> +			goto out_put_node;
> +		}

You won't hit this case here, it is already caught above.

I'm stopping here after I realized that I'm repeating all the things I
said to v1 already.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



More information about the barebox mailing list