On Wed, Feb 03, 2021 at 06:54:56PM +0200, Vadym Kochan wrote:
> +static struct prestera_lag *prestera_lag_by_dev(struct prestera_switch *sw,
> +                                             struct net_device *dev)
> +{
> +     struct prestera_lag *lag;
> +     u16 id;
> +
> +     for (id = 0; id < sw->lag_max; id++) {
> +             lag = &sw->lags[id];
> +             if (lag->dev == dev)
> +                     return lag;
> +     }
> +
> +     return NULL;
> +}
> +
> +static struct prestera_lag *prestera_lag_create(struct prestera_switch *sw,
> +                                             struct net_device *lag_dev)
> +{
> +     struct prestera_lag *lag;

You should initialize with NULL.

> +     u16 id;
> +
> +     for (id = 0; id < sw->lag_max; id++) {
> +             lag = &sw->lags[id];
> +             if (!lag->dev)
> +                     break;
> +     }
> +     if (lag) {
> +             INIT_LIST_HEAD(&lag->members);
> +             lag->dev = lag_dev;
> +     }
> +
> +     return lag;
> +}
> +
> +static void prestera_lag_destroy(struct prestera_switch *sw,
> +                              struct prestera_lag *lag)
> +{
> +     WARN_ON(!list_empty(&lag->members));
> +     lag->member_count = 0;
> +     lag->dev = NULL;
> +}
> +
> +static int prestera_lag_port_add(struct prestera_port *port,
> +                              struct net_device *lag_dev)
> +{
> +     struct prestera_switch *sw = port->sw;
> +     struct prestera_lag *lag;
> +     int err;
> +
> +     lag = prestera_lag_by_dev(sw, lag_dev);
> +     if (!lag) {
> +             lag = prestera_lag_create(sw, lag_dev);
> +             if (!lag)
> +                     return -ENOMEM;

I think ENOMEM is reserved for dynamic memory allocation. I think
-ENOSPC may be a better error code (here and everywhere else).
Maybe you would also like to propagate the netlink extack from the
changeupper event and say what went wrong?

> +     }
> +
> +     if (lag->member_count >= sw->lag_member_max)
> +             return -ENOMEM;
> +
> +     err = prestera_hw_lag_member_add(port, lag->lag_id);
> +     if (err) {
> +             if (!lag->member_count)
> +                     prestera_lag_destroy(sw, lag);
> +             return err;
> +     }
> +
> +     list_add(&port->lag_member, &lag->members);
> +     lag->member_count++;
> +     port->lag = lag;
> +
> +     return 0;
> +}
> +
> +static int prestera_lag_port_del(struct prestera_port *port)
> +{
> +     struct prestera_switch *sw = port->sw;
> +     struct prestera_lag *lag = port->lag;
> +     int err;
> +
> +     if (!lag || !lag->member_count)
> +             return -EINVAL;
> +
> +     err = prestera_hw_lag_member_del(port, lag->lag_id);
> +     if (err)
> +             return err;
> +
> +     list_del(&port->lag_member);
> +     lag->member_count--;
> +     port->lag = NULL;
> +
> +     if (netif_is_bridge_port(lag->dev)) {
> +             struct netdev_notifier_changeupper_info br_info;
> +
> +             br_info.upper_dev = netdev_master_upper_dev_get(lag->dev);
> +             br_info.linking = false;
> +
> +             prestera_bridge_port_event(lag->dev, port->dev,
> +                                        NETDEV_CHANGEUPPER, &br_info);
> +     }

I think it might be more intuitive if you just call
prestera_port_bridge_leave than simulate a notifier call.

> +
> +     if (!lag->member_count)
> +             prestera_lag_destroy(sw, lag);
> +
> +     return 0;
> +}
> +
> +bool prestera_port_is_lag_member(const struct prestera_port *port)
> +{
> +     return !!port->lag;
> +}
> +
> +u16 prestera_port_lag_id(const struct prestera_port *port)
> +{
> +     return port->lag->lag_id;
> +}
> +
> +static int prestera_lag_init(struct prestera_switch *sw)
> +{
> +     u16 id;
> +
> +     sw->lags = kcalloc(sw->lag_max, sizeof(*sw->lags), GFP_KERNEL);
> +     if (!sw->lags)
> +             return -ENOMEM;
> +
> +     for (id = 0; id < sw->lag_max; id++)
> +             sw->lags[id].lag_id = id;
> +
> +     return 0;
> +}
> +
> +static void prestera_lag_fini(struct prestera_switch *sw)
> +{
> +     u8 idx;
> +
> +     for (idx = 0; idx < sw->lag_max; idx++)
> +             WARN_ON(sw->lags[idx].member_count);
> +
> +     kfree(sw->lags);
> +}
> +
>  bool prestera_netdev_check(const struct net_device *dev)
>  {
>       return dev->netdev_ops == &prestera_netdev_ops;
> @@ -507,19 +654,54 @@ struct prestera_port 
> *prestera_port_dev_lower_find(struct net_device *dev)
>       return port;
>  }
>  
> -static int prestera_netdev_port_event(struct net_device *dev,
> +static int prestera_netdev_port_lower_event(struct net_device *dev,
> +                                         unsigned long event, void *ptr)
> +{
> +     struct netdev_notifier_changelowerstate_info *info = ptr;
> +     struct netdev_lag_lower_state_info *lower_state_info;
> +     struct prestera_port *port = netdev_priv(dev);
> +     bool enabled;
> +
> +     if (!netif_is_lag_port(dev))
> +             return 0;
> +     if (!prestera_port_is_lag_member(port))
> +             return 0;
> +
> +     lower_state_info = info->lower_state_info;
> +     enabled = lower_state_info->tx_enabled;

You also need to check for info->link_up, otherwise the ports won't get
rebalanced for bonding interfaces with "mode balance-xor miimon 1" and such.
There is also a comment in net/dsa/port.c with more details.

> +
> +     return prestera_hw_lag_member_enable(port, port->lag->lag_id, enabled);
> +}
> +
> +static bool prestera_lag_master_check(struct net_device *lag_dev,
> +                                   struct netdev_lag_upper_info *info,
> +                                   struct netlink_ext_ack *ext_ack)
> +{
> +     if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) {
> +             NL_SET_ERR_MSG_MOD(ext_ack, "Unsupported LAG Tx type");
> +             return false;
> +     }
> +
> +     return true;
> +}
> +
> +static int prestera_netdev_port_event(struct net_device *lower,
> +                                   struct net_device *dev,
>                                     unsigned long event, void *ptr)
>  {
>       struct netdev_notifier_changeupper_info *info = ptr;
> +     struct prestera_port *port = netdev_priv(dev);
>       struct netlink_ext_ack *extack;
>       struct net_device *upper;
> +     int err;
>  
>       extack = netdev_notifier_info_to_extack(&info->info);
>       upper = info->upper_dev;
>  
>       switch (event) {
>       case NETDEV_PRECHANGEUPPER:
> -             if (!netif_is_bridge_master(upper)) {
> +             if (!netif_is_bridge_master(upper) &&
> +                 !netif_is_lag_master(upper)) {

No 8021q uppers allowed on Marvell Prestera switch ports?

>                       NL_SET_ERR_MSG_MOD(extack, "Unknown upper device type");
>                       return -EINVAL;
>               }
> @@ -531,12 +713,60 @@ static int prestera_netdev_port_event(struct net_device 
> *dev,
>                       NL_SET_ERR_MSG_MOD(extack, "Upper device is already 
> enslaved");
>                       return -EINVAL;
>               }
> +
> +             if (netif_is_lag_master(upper) &&
> +                 !prestera_lag_master_check(upper, info->upper_info, extack))
> +                     return -EINVAL;

-EOPNOTSUPP maybe?
In DSA we had a discussion and convened to do software fallback for
bonding modes that can't be offloaded, and just print an extack and
return 0. What is your take on that?

> +             if (netif_is_lag_master(upper) && vlan_uses_dev(dev)) {
> +                     NL_SET_ERR_MSG_MOD(extack,
> +                                        "Master device is a LAG master and 
> port has a VLAN");
> +                     return -EINVAL;
> +             }
> +             if (netif_is_lag_port(dev) && is_vlan_dev(upper) &&
> +                 !netif_is_lag_master(vlan_dev_real_dev(upper))) {
> +                     NL_SET_ERR_MSG_MOD(extack,
> +                                        "Can not put a VLAN on a LAG port");
> +                     return -EINVAL;
> +             }
>               break;
>  
>       case NETDEV_CHANGEUPPER:
>               if (netif_is_bridge_master(upper))
> -                     return prestera_bridge_port_event(dev, event, ptr);
> +                     return prestera_bridge_port_event(lower, dev, event,
> +                                                       ptr);
> +
> +             if (netif_is_lag_master(upper)) {
> +                     if (info->linking) {
> +                             err = prestera_lag_port_add(port, upper);
> +                             if (err)
> +                                     return err;
> +                     } else {
> +                             prestera_lag_port_del(port);
> +                     }
> +             }
>               break;
> +
> +     case NETDEV_CHANGELOWERSTATE:
> +             return prestera_netdev_port_lower_event(dev, event, ptr);
> +     }
> +
> +     return 0;
> +}
> +
> +static int prestera_netdevice_lag_event(struct net_device *lag_dev,
> +                                     unsigned long event, void *ptr)
> +{
> +     struct net_device *dev;
> +     struct list_head *iter;
> +     int err;
> +
> +     netdev_for_each_lower_dev(lag_dev, dev, iter) {
> +             if (prestera_netdev_check(dev)) {
> +                     err = prestera_netdev_port_event(lag_dev, dev, event,
> +                                                      ptr);
> +                     if (err)
> +                             return err;
> +             }
>       }
>  
>       return 0;
> @@ -549,7 +779,9 @@ static int prestera_netdev_event_handler(struct 
> notifier_block *nb,
>       int err = 0;
>  
>       if (prestera_netdev_check(dev))
> -             err = prestera_netdev_port_event(dev, event, ptr);
> +             err = prestera_netdev_port_event(dev, dev, event, ptr);
> +     else if (netif_is_lag_master(dev))
> +             err = prestera_netdevice_lag_event(dev, event, ptr);
>  
>       return notifier_from_errno(err);
>  }
> @@ -603,6 +835,10 @@ static int prestera_switch_init(struct prestera_switch 
> *sw)
>       if (err)
>               goto err_dl_register;
>  
> +     err = prestera_lag_init(sw);
> +     if (err)
> +             goto err_lag_init;
> +
>       err = prestera_create_ports(sw);
>       if (err)
>               goto err_ports_create;
> @@ -610,6 +846,8 @@ static int prestera_switch_init(struct prestera_switch 
> *sw)
>       return 0;
>  
>  err_ports_create:
> +     prestera_lag_fini(sw);
> +err_lag_init:
>       prestera_devlink_unregister(sw);
>  err_dl_register:
>       prestera_event_handlers_unregister(sw);
> @@ -627,6 +865,7 @@ static int prestera_switch_init(struct prestera_switch 
> *sw)
>  static void prestera_switch_fini(struct prestera_switch *sw)
>  {
>       prestera_destroy_ports(sw);
> +     prestera_lag_fini(sw);
>       prestera_devlink_unregister(sw);
>       prestera_event_handlers_unregister(sw);
>       prestera_rxtx_switch_fini(sw);
> diff --git a/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c 
> b/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c
> index 7736d5f498c9..3750c66a550b 100644
> --- a/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c
> +++ b/drivers/net/ethernet/marvell/prestera/prestera_switchdev.c
> @@ -180,6 +180,45 @@ prestera_port_vlan_create(struct prestera_port *port, 
> u16 vid, bool untagged)
>       return ERR_PTR(err);
>  }
>  
> +static int prestera_fdb_add(struct prestera_port *port,
> +                         const unsigned char *mac, u16 vid, bool dynamic)
> +{
> +     if (prestera_port_is_lag_member(port))
> +             return prestera_hw_lag_fdb_add(port->sw, 
> prestera_port_lag_id(port),
> +                                           mac, vid, dynamic);
> +     else
> +             return prestera_hw_fdb_add(port, mac, vid, dynamic);
> +}

I think checkpatch tells you that "else" after "return" is not really
necessary.

> +
> +static int prestera_fdb_del(struct prestera_port *port,
> +                         const unsigned char *mac, u16 vid)
> +{
> +     if (prestera_port_is_lag_member(port))
> +             return prestera_hw_lag_fdb_del(port->sw, 
> prestera_port_lag_id(port),
> +                                           mac, vid);
> +     else
> +             return prestera_hw_fdb_del(port, mac, vid);
> +}
> +
> +static int prestera_fdb_flush_port_vlan(struct prestera_port *port, u16 vid,
> +                                     u32 mode)
> +{
> +     if (prestera_port_is_lag_member(port))
> +             return prestera_hw_fdb_flush_lag_vlan(port->sw, 
> prestera_port_lag_id(port),
> +                                                   vid, mode);
> +     else
> +             return prestera_hw_fdb_flush_port_vlan(port, vid, mode);
> +}
> +
> +static int prestera_fdb_flush_port(struct prestera_port *port, u32 mode)
> +{
> +     if (prestera_port_is_lag_member(port))
> +             return prestera_hw_fdb_flush_lag(port->sw, 
> prestera_port_lag_id(port),
> +                                              mode);
> +     else
> +             return prestera_hw_fdb_flush_port(port, mode);
> +}
> +
>  static void
>  prestera_port_vlan_bridge_leave(struct prestera_port_vlan *port_vlan)
>  {
> @@ -199,11 +238,11 @@ prestera_port_vlan_bridge_leave(struct 
> prestera_port_vlan *port_vlan)
>       last_port = port_count == 1;
>  
>       if (last_vlan)
> -             prestera_hw_fdb_flush_port(port, fdb_flush_mode);
> +             prestera_fdb_flush_port(port, fdb_flush_mode);
>       else if (last_port)
>               prestera_hw_fdb_flush_vlan(port->sw, vid, fdb_flush_mode);
>       else
> -             prestera_hw_fdb_flush_port_vlan(port, vid, fdb_flush_mode);
> +             prestera_fdb_flush_port_vlan(port, vid, fdb_flush_mode);
>  
>       list_del(&port_vlan->br_vlan_head);
>       prestera_bridge_vlan_put(br_vlan);
> @@ -394,9 +433,9 @@ prestera_bridge_port_add(struct prestera_bridge *bridge, 
> struct net_device *dev)
>  }
>  
>  static int
> -prestera_bridge_1d_port_join(struct prestera_bridge_port *br_port)
> +prestera_bridge_1d_port_join(struct prestera_bridge_port *br_port,
> +                          struct prestera_port *port)
>  {
> -     struct prestera_port *port = netdev_priv(br_port->dev);
>       struct prestera_bridge *bridge = br_port->bridge;
>       int err;
>  
> @@ -423,6 +462,7 @@ prestera_bridge_1d_port_join(struct prestera_bridge_port 
> *br_port)
>  }
>  
>  static int prestera_port_bridge_join(struct prestera_port *port,
> +                                  struct net_device *lower,
>                                    struct net_device *upper)
>  {
>       struct prestera_switchdev *swdev = port->sw->swdev;
> @@ -437,7 +477,7 @@ static int prestera_port_bridge_join(struct prestera_port 
> *port,
>                       return PTR_ERR(bridge);
>       }
>  
> -     br_port = prestera_bridge_port_add(bridge, port->dev);
> +     br_port = prestera_bridge_port_add(bridge, lower);
>       if (IS_ERR(br_port)) {
>               err = PTR_ERR(br_port);
>               goto err_brport_create;
> @@ -446,7 +486,7 @@ static int prestera_port_bridge_join(struct prestera_port 
> *port,
>       if (bridge->vlan_enabled)
>               return 0;
>  
> -     err = prestera_bridge_1d_port_join(br_port);
> +     err = prestera_bridge_1d_port_join(br_port, port);
>       if (err)
>               goto err_port_join;
>  
> @@ -459,19 +499,17 @@ static int prestera_port_bridge_join(struct 
> prestera_port *port,
>       return err;
>  }
>  
> -static void prestera_bridge_1q_port_leave(struct prestera_bridge_port 
> *br_port)
> +static void prestera_bridge_1q_port_leave(struct prestera_bridge_port 
> *br_port,
> +                                       struct prestera_port *port)
>  {
> -     struct prestera_port *port = netdev_priv(br_port->dev);
> -
> -     prestera_hw_fdb_flush_port(port, PRESTERA_FDB_FLUSH_MODE_ALL);
> +     prestera_fdb_flush_port(port, PRESTERA_FDB_FLUSH_MODE_ALL);
>       prestera_port_pvid_set(port, PRESTERA_DEFAULT_VID);
>  }
>  
> -static void prestera_bridge_1d_port_leave(struct prestera_bridge_port 
> *br_port)
> +static void prestera_bridge_1d_port_leave(struct prestera_bridge_port 
> *br_port,
> +                                       struct prestera_port *port)
>  {
> -     struct prestera_port *port = netdev_priv(br_port->dev);
> -
> -     prestera_hw_fdb_flush_port(port, PRESTERA_FDB_FLUSH_MODE_ALL);
> +     prestera_fdb_flush_port(port, PRESTERA_FDB_FLUSH_MODE_ALL);
>       prestera_hw_bridge_port_delete(port, br_port->bridge->bridge_id);
>  }
>  
> @@ -506,6 +544,7 @@ static int prestera_port_vid_stp_set(struct prestera_port 
> *port, u16 vid,
>  }
>  
>  static void prestera_port_bridge_leave(struct prestera_port *port,
> +                                    struct net_device *lower,
>                                      struct net_device *upper)
>  {
>       struct prestera_switchdev *swdev = port->sw->swdev;
> @@ -516,16 +555,16 @@ static void prestera_port_bridge_leave(struct 
> prestera_port *port,
>       if (!bridge)
>               return;
>  
> -     br_port = __prestera_bridge_port_by_dev(bridge, port->dev);
> +     br_port = __prestera_bridge_port_by_dev(bridge, lower);
>       if (!br_port)
>               return;
>  
>       bridge = br_port->bridge;
>  
>       if (bridge->vlan_enabled)
> -             prestera_bridge_1q_port_leave(br_port);
> +             prestera_bridge_1q_port_leave(br_port, port);
>       else
> -             prestera_bridge_1d_port_leave(br_port);
> +             prestera_bridge_1d_port_leave(br_port, port);
>  
>       prestera_hw_port_learning_set(port, false);
>       prestera_hw_port_flood_set(port, false);
> @@ -533,8 +572,8 @@ static void prestera_port_bridge_leave(struct 
> prestera_port *port,
>       prestera_bridge_port_put(br_port);
>  }
>  
> -int prestera_bridge_port_event(struct net_device *dev, unsigned long event,
> -                            void *ptr)
> +int prestera_bridge_port_event(struct net_device *lower, struct net_device 
> *dev,
> +                            unsigned long event, void *ptr)

It's odd that you have a net_device lower and a net_device dev.
You're only using "dev" to retrieve the struct prestera_port, can't you
just pass that as parameter? It will also help avoid possible mistakes
in the future between lower (which can be a LAG or a port) and which is
associated with a struct prestera_bridge_port, and dev which is only a
port, and is associated with struct prestera_port.

Reply via email to