| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
| /* Copyright (C) 2018 Netronome Systems, Inc. */ |
| |
| #include "main.h" |
| |
| /* LAG group config flags. */ |
| #define NFP_FL_LAG_LAST BIT(1) |
| #define NFP_FL_LAG_FIRST BIT(2) |
| #define NFP_FL_LAG_DATA BIT(3) |
| #define NFP_FL_LAG_XON BIT(4) |
| #define NFP_FL_LAG_SYNC BIT(5) |
| #define NFP_FL_LAG_SWITCH BIT(6) |
| #define NFP_FL_LAG_RESET BIT(7) |
| |
| /* LAG port state flags. */ |
| #define NFP_PORT_LAG_LINK_UP BIT(0) |
| #define NFP_PORT_LAG_TX_ENABLED BIT(1) |
| #define NFP_PORT_LAG_CHANGED BIT(2) |
| |
| enum nfp_fl_lag_batch { |
| NFP_FL_LAG_BATCH_FIRST, |
| NFP_FL_LAG_BATCH_MEMBER, |
| NFP_FL_LAG_BATCH_FINISHED |
| }; |
| |
| /** |
| * struct nfp_flower_cmsg_lag_config - control message payload for LAG config |
| * @ctrl_flags: Configuration flags |
| * @reserved: Reserved for future use |
| * @ttl: Time to live of packet - host always sets to 0xff |
| * @pkt_number: Config message packet number - increment for each message |
| * @batch_ver: Batch version of messages - increment for each batch of messages |
| * @group_id: Group ID applicable |
| * @group_inst: Group instance number - increment when group is reused |
| * @members: Array of 32-bit words listing all active group members |
| */ |
| struct nfp_flower_cmsg_lag_config { |
| u8 ctrl_flags; |
| u8 reserved[2]; |
| u8 ttl; |
| __be32 pkt_number; |
| __be32 batch_ver; |
| __be32 group_id; |
| __be32 group_inst; |
| __be32 members[]; |
| }; |
| |
| /** |
| * struct nfp_fl_lag_group - list entry for each LAG group |
| * @group_id: Assigned group ID for host/kernel sync |
| * @group_inst: Group instance in case of ID reuse |
| * @list: List entry |
| * @master_ndev: Group master Netdev |
| * @dirty: Marked if the group needs synced to HW |
| * @offloaded: Marked if the group is currently offloaded to NIC |
| * @to_remove: Marked if the group should be removed from NIC |
| * @to_destroy: Marked if the group should be removed from driver |
| * @slave_cnt: Number of slaves in group |
| */ |
| struct nfp_fl_lag_group { |
| unsigned int group_id; |
| u8 group_inst; |
| struct list_head list; |
| struct net_device *master_ndev; |
| bool dirty; |
| bool offloaded; |
| bool to_remove; |
| bool to_destroy; |
| unsigned int slave_cnt; |
| }; |
| |
| #define NFP_FL_LAG_PKT_NUMBER_MASK GENMASK(30, 0) |
| #define NFP_FL_LAG_VERSION_MASK GENMASK(22, 0) |
| #define NFP_FL_LAG_HOST_TTL 0xff |
| |
| /* Use this ID with zero members to ack a batch config */ |
| #define NFP_FL_LAG_SYNC_ID 0 |
| #define NFP_FL_LAG_GROUP_MIN 1 /* ID 0 reserved */ |
| #define NFP_FL_LAG_GROUP_MAX 31 /* IDs 1 to 31 are valid */ |
| |
| /* wait for more config */ |
| #define NFP_FL_LAG_DELAY (msecs_to_jiffies(2)) |
| |
| #define NFP_FL_LAG_RETRANS_LIMIT 100 /* max retrans cmsgs to store */ |
| |
| static unsigned int nfp_fl_get_next_pkt_number(struct nfp_fl_lag *lag) |
| { |
| lag->pkt_num++; |
| lag->pkt_num &= NFP_FL_LAG_PKT_NUMBER_MASK; |
| |
| return lag->pkt_num; |
| } |
| |
| static void nfp_fl_increment_version(struct nfp_fl_lag *lag) |
| { |
| /* LSB is not considered by firmware so add 2 for each increment. */ |
| lag->batch_ver += 2; |
| lag->batch_ver &= NFP_FL_LAG_VERSION_MASK; |
| |
| /* Zero is reserved by firmware. */ |
| if (!lag->batch_ver) |
| lag->batch_ver += 2; |
| } |
| |
| static struct nfp_fl_lag_group * |
| nfp_fl_lag_group_create(struct nfp_fl_lag *lag, struct net_device *master) |
| { |
| struct nfp_fl_lag_group *group; |
| struct nfp_flower_priv *priv; |
| int id; |
| |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| |
| id = ida_alloc_range(&lag->ida_handle, NFP_FL_LAG_GROUP_MIN, |
| NFP_FL_LAG_GROUP_MAX, GFP_KERNEL); |
| if (id < 0) { |
| nfp_flower_cmsg_warn(priv->app, |
| "No more bonding groups available\n"); |
| return ERR_PTR(id); |
| } |
| |
| group = kmalloc(sizeof(*group), GFP_KERNEL); |
| if (!group) { |
| ida_free(&lag->ida_handle, id); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| group->group_id = id; |
| group->master_ndev = master; |
| group->dirty = true; |
| group->offloaded = false; |
| group->to_remove = false; |
| group->to_destroy = false; |
| group->slave_cnt = 0; |
| group->group_inst = ++lag->global_inst; |
| list_add_tail(&group->list, &lag->group_list); |
| |
| return group; |
| } |
| |
| static struct nfp_fl_lag_group * |
| nfp_fl_lag_find_group_for_master_with_lag(struct nfp_fl_lag *lag, |
| struct net_device *master) |
| { |
| struct nfp_fl_lag_group *entry; |
| |
| if (!master) |
| return NULL; |
| |
| list_for_each_entry(entry, &lag->group_list, list) |
| if (entry->master_ndev == master) |
| return entry; |
| |
| return NULL; |
| } |
| |
| static int nfp_fl_lag_get_group_info(struct nfp_app *app, |
| struct net_device *netdev, |
| __be16 *group_id, |
| u8 *batch_ver, |
| u8 *group_inst) |
| { |
| struct nfp_flower_priv *priv = app->priv; |
| struct nfp_fl_lag_group *group = NULL; |
| __be32 temp_vers; |
| |
| mutex_lock(&priv->nfp_lag.lock); |
| group = nfp_fl_lag_find_group_for_master_with_lag(&priv->nfp_lag, |
| netdev); |
| if (!group) { |
| mutex_unlock(&priv->nfp_lag.lock); |
| return -ENOENT; |
| } |
| |
| if (group_id) |
| *group_id = cpu_to_be16(group->group_id); |
| |
| if (batch_ver) { |
| temp_vers = cpu_to_be32(priv->nfp_lag.batch_ver << |
| NFP_FL_PRE_LAG_VER_OFF); |
| memcpy(batch_ver, &temp_vers, 3); |
| } |
| |
| if (group_inst) |
| *group_inst = group->group_inst; |
| |
| mutex_unlock(&priv->nfp_lag.lock); |
| |
| return 0; |
| } |
| |
| int nfp_flower_lag_populate_pre_action(struct nfp_app *app, |
| struct net_device *master, |
| struct nfp_fl_pre_lag *pre_act, |
| struct netlink_ext_ack *extack) |
| { |
| if (nfp_fl_lag_get_group_info(app, master, &pre_act->group_id, |
| pre_act->lag_version, |
| &pre_act->instance)) { |
| NL_SET_ERR_MSG_MOD(extack, "invalid entry: group does not exist for LAG action"); |
| return -ENOENT; |
| } |
| |
| return 0; |
| } |
| |
| void nfp_flower_lag_get_info_from_netdev(struct nfp_app *app, |
| struct net_device *netdev, |
| struct nfp_tun_neigh_lag *lag) |
| { |
| nfp_fl_lag_get_group_info(app, netdev, NULL, |
| lag->lag_version, &lag->lag_instance); |
| } |
| |
| int nfp_flower_lag_get_output_id(struct nfp_app *app, struct net_device *master) |
| { |
| struct nfp_flower_priv *priv = app->priv; |
| struct nfp_fl_lag_group *group = NULL; |
| int group_id = -ENOENT; |
| |
| mutex_lock(&priv->nfp_lag.lock); |
| group = nfp_fl_lag_find_group_for_master_with_lag(&priv->nfp_lag, |
| master); |
| if (group) |
| group_id = group->group_id; |
| mutex_unlock(&priv->nfp_lag.lock); |
| |
| return group_id; |
| } |
| |
| static int |
| nfp_fl_lag_config_group(struct nfp_fl_lag *lag, struct nfp_fl_lag_group *group, |
| struct net_device **active_members, |
| unsigned int member_cnt, enum nfp_fl_lag_batch *batch) |
| { |
| struct nfp_flower_cmsg_lag_config *cmsg_payload; |
| struct nfp_flower_priv *priv; |
| unsigned long int flags; |
| unsigned int size, i; |
| struct sk_buff *skb; |
| |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| size = sizeof(*cmsg_payload) + sizeof(__be32) * member_cnt; |
| skb = nfp_flower_cmsg_alloc(priv->app, size, |
| NFP_FLOWER_CMSG_TYPE_LAG_CONFIG, |
| GFP_KERNEL); |
| if (!skb) |
| return -ENOMEM; |
| |
| cmsg_payload = nfp_flower_cmsg_get_data(skb); |
| flags = 0; |
| |
| /* Increment batch version for each new batch of config messages. */ |
| if (*batch == NFP_FL_LAG_BATCH_FIRST) { |
| flags |= NFP_FL_LAG_FIRST; |
| nfp_fl_increment_version(lag); |
| *batch = NFP_FL_LAG_BATCH_MEMBER; |
| } |
| |
| /* If it is a reset msg then it is also the end of the batch. */ |
| if (lag->rst_cfg) { |
| flags |= NFP_FL_LAG_RESET; |
| *batch = NFP_FL_LAG_BATCH_FINISHED; |
| } |
| |
| /* To signal the end of a batch, both the switch and last flags are set |
| * and the reserved SYNC group ID is used. |
| */ |
| if (*batch == NFP_FL_LAG_BATCH_FINISHED) { |
| flags |= NFP_FL_LAG_SWITCH | NFP_FL_LAG_LAST; |
| lag->rst_cfg = false; |
| cmsg_payload->group_id = cpu_to_be32(NFP_FL_LAG_SYNC_ID); |
| cmsg_payload->group_inst = 0; |
| } else { |
| cmsg_payload->group_id = cpu_to_be32(group->group_id); |
| cmsg_payload->group_inst = cpu_to_be32(group->group_inst); |
| } |
| |
| cmsg_payload->reserved[0] = 0; |
| cmsg_payload->reserved[1] = 0; |
| cmsg_payload->ttl = NFP_FL_LAG_HOST_TTL; |
| cmsg_payload->ctrl_flags = flags; |
| cmsg_payload->batch_ver = cpu_to_be32(lag->batch_ver); |
| cmsg_payload->pkt_number = cpu_to_be32(nfp_fl_get_next_pkt_number(lag)); |
| |
| for (i = 0; i < member_cnt; i++) |
| cmsg_payload->members[i] = |
| cpu_to_be32(nfp_repr_get_port_id(active_members[i])); |
| |
| nfp_ctrl_tx(priv->app->ctrl, skb); |
| return 0; |
| } |
| |
| static void nfp_fl_lag_do_work(struct work_struct *work) |
| { |
| enum nfp_fl_lag_batch batch = NFP_FL_LAG_BATCH_FIRST; |
| struct nfp_fl_lag_group *entry, *storage; |
| struct delayed_work *delayed_work; |
| struct nfp_flower_priv *priv; |
| struct nfp_fl_lag *lag; |
| int err; |
| |
| delayed_work = to_delayed_work(work); |
| lag = container_of(delayed_work, struct nfp_fl_lag, work); |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| |
| mutex_lock(&lag->lock); |
| list_for_each_entry_safe(entry, storage, &lag->group_list, list) { |
| struct net_device *iter_netdev, **acti_netdevs; |
| struct nfp_flower_repr_priv *repr_priv; |
| int active_count = 0, slaves = 0; |
| struct nfp_repr *repr; |
| unsigned long *flags; |
| |
| if (entry->to_remove) { |
| /* Active count of 0 deletes group on hw. */ |
| err = nfp_fl_lag_config_group(lag, entry, NULL, 0, |
| &batch); |
| if (!err) { |
| entry->to_remove = false; |
| entry->offloaded = false; |
| } else { |
| nfp_flower_cmsg_warn(priv->app, |
| "group delete failed\n"); |
| schedule_delayed_work(&lag->work, |
| NFP_FL_LAG_DELAY); |
| continue; |
| } |
| |
| if (entry->to_destroy) { |
| ida_free(&lag->ida_handle, entry->group_id); |
| list_del(&entry->list); |
| kfree(entry); |
| } |
| continue; |
| } |
| |
| acti_netdevs = kmalloc_array(entry->slave_cnt, |
| sizeof(*acti_netdevs), GFP_KERNEL); |
| if (!acti_netdevs) { |
| schedule_delayed_work(&lag->work, |
| NFP_FL_LAG_DELAY); |
| continue; |
| } |
| |
| /* Include sanity check in the loop. It may be that a bond has |
| * changed between processing the last notification and the |
| * work queue triggering. If the number of slaves has changed |
| * or it now contains netdevs that cannot be offloaded, ignore |
| * the group until pending notifications are processed. |
| */ |
| rcu_read_lock(); |
| for_each_netdev_in_bond_rcu(entry->master_ndev, iter_netdev) { |
| if (!nfp_netdev_is_nfp_repr(iter_netdev)) { |
| slaves = 0; |
| break; |
| } |
| |
| repr = netdev_priv(iter_netdev); |
| |
| if (repr->app != priv->app) { |
| slaves = 0; |
| break; |
| } |
| |
| slaves++; |
| if (slaves > entry->slave_cnt) |
| break; |
| |
| /* Check the ports for state changes. */ |
| repr_priv = repr->app_priv; |
| flags = &repr_priv->lag_port_flags; |
| |
| if (*flags & NFP_PORT_LAG_CHANGED) { |
| *flags &= ~NFP_PORT_LAG_CHANGED; |
| entry->dirty = true; |
| } |
| |
| if ((*flags & NFP_PORT_LAG_TX_ENABLED) && |
| (*flags & NFP_PORT_LAG_LINK_UP)) |
| acti_netdevs[active_count++] = iter_netdev; |
| } |
| rcu_read_unlock(); |
| |
| if (slaves != entry->slave_cnt || !entry->dirty) { |
| kfree(acti_netdevs); |
| continue; |
| } |
| |
| err = nfp_fl_lag_config_group(lag, entry, acti_netdevs, |
| active_count, &batch); |
| if (!err) { |
| entry->offloaded = true; |
| entry->dirty = false; |
| } else { |
| nfp_flower_cmsg_warn(priv->app, |
| "group offload failed\n"); |
| schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY); |
| } |
| |
| kfree(acti_netdevs); |
| } |
| |
| /* End the config batch if at least one packet has been batched. */ |
| if (batch == NFP_FL_LAG_BATCH_MEMBER) { |
| batch = NFP_FL_LAG_BATCH_FINISHED; |
| err = nfp_fl_lag_config_group(lag, NULL, NULL, 0, &batch); |
| if (err) |
| nfp_flower_cmsg_warn(priv->app, |
| "group batch end cmsg failed\n"); |
| } |
| |
| mutex_unlock(&lag->lock); |
| } |
| |
| static int |
| nfp_fl_lag_put_unprocessed(struct nfp_fl_lag *lag, struct sk_buff *skb) |
| { |
| struct nfp_flower_cmsg_lag_config *cmsg_payload; |
| |
| cmsg_payload = nfp_flower_cmsg_get_data(skb); |
| if (be32_to_cpu(cmsg_payload->group_id) > NFP_FL_LAG_GROUP_MAX) |
| return -EINVAL; |
| |
| /* Drop cmsg retrans if storage limit is exceeded to prevent |
| * overloading. If the fw notices that expected messages have not been |
| * received in a given time block, it will request a full resync. |
| */ |
| if (skb_queue_len(&lag->retrans_skbs) >= NFP_FL_LAG_RETRANS_LIMIT) |
| return -ENOSPC; |
| |
| __skb_queue_tail(&lag->retrans_skbs, skb); |
| |
| return 0; |
| } |
| |
| static void nfp_fl_send_unprocessed(struct nfp_fl_lag *lag) |
| { |
| struct nfp_flower_priv *priv; |
| struct sk_buff *skb; |
| |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| |
| while ((skb = __skb_dequeue(&lag->retrans_skbs))) |
| nfp_ctrl_tx(priv->app->ctrl, skb); |
| } |
| |
| bool nfp_flower_lag_unprocessed_msg(struct nfp_app *app, struct sk_buff *skb) |
| { |
| struct nfp_flower_cmsg_lag_config *cmsg_payload; |
| struct nfp_flower_priv *priv = app->priv; |
| struct nfp_fl_lag_group *group_entry; |
| unsigned long int flags; |
| bool store_skb = false; |
| int err; |
| |
| cmsg_payload = nfp_flower_cmsg_get_data(skb); |
| flags = cmsg_payload->ctrl_flags; |
| |
| /* Note the intentional fall through below. If DATA and XON are both |
| * set, the message will stored and sent again with the rest of the |
| * unprocessed messages list. |
| */ |
| |
| /* Store */ |
| if (flags & NFP_FL_LAG_DATA) |
| if (!nfp_fl_lag_put_unprocessed(&priv->nfp_lag, skb)) |
| store_skb = true; |
| |
| /* Send stored */ |
| if (flags & NFP_FL_LAG_XON) |
| nfp_fl_send_unprocessed(&priv->nfp_lag); |
| |
| /* Resend all */ |
| if (flags & NFP_FL_LAG_SYNC) { |
| /* To resend all config: |
| * 1) Clear all unprocessed messages |
| * 2) Mark all groups dirty |
| * 3) Reset NFP group config |
| * 4) Schedule a LAG config update |
| */ |
| |
| __skb_queue_purge(&priv->nfp_lag.retrans_skbs); |
| |
| mutex_lock(&priv->nfp_lag.lock); |
| list_for_each_entry(group_entry, &priv->nfp_lag.group_list, |
| list) |
| group_entry->dirty = true; |
| |
| err = nfp_flower_lag_reset(&priv->nfp_lag); |
| if (err) |
| nfp_flower_cmsg_warn(priv->app, |
| "mem err in group reset msg\n"); |
| mutex_unlock(&priv->nfp_lag.lock); |
| |
| schedule_delayed_work(&priv->nfp_lag.work, 0); |
| } |
| |
| return store_skb; |
| } |
| |
| static void |
| nfp_fl_lag_schedule_group_remove(struct nfp_fl_lag *lag, |
| struct nfp_fl_lag_group *group) |
| { |
| group->to_remove = true; |
| |
| schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY); |
| } |
| |
| static void |
| nfp_fl_lag_schedule_group_delete(struct nfp_fl_lag *lag, |
| struct net_device *master) |
| { |
| struct nfp_fl_lag_group *group; |
| struct nfp_flower_priv *priv; |
| |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| |
| if (!netif_is_bond_master(master)) |
| return; |
| |
| mutex_lock(&lag->lock); |
| group = nfp_fl_lag_find_group_for_master_with_lag(lag, master); |
| if (!group) { |
| mutex_unlock(&lag->lock); |
| nfp_warn(priv->app->cpp, "untracked bond got unregistered %s\n", |
| netdev_name(master)); |
| return; |
| } |
| |
| group->to_remove = true; |
| group->to_destroy = true; |
| mutex_unlock(&lag->lock); |
| |
| schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY); |
| } |
| |
| static int |
| nfp_fl_lag_changeupper_event(struct nfp_fl_lag *lag, |
| struct netdev_notifier_changeupper_info *info) |
| { |
| struct net_device *upper = info->upper_dev, *iter_netdev; |
| struct netdev_lag_upper_info *lag_upper_info; |
| struct nfp_fl_lag_group *group; |
| struct nfp_flower_priv *priv; |
| unsigned int slave_count = 0; |
| bool can_offload = true; |
| struct nfp_repr *repr; |
| |
| if (!netif_is_lag_master(upper)) |
| return 0; |
| |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| |
| rcu_read_lock(); |
| for_each_netdev_in_bond_rcu(upper, iter_netdev) { |
| if (!nfp_netdev_is_nfp_repr(iter_netdev)) { |
| can_offload = false; |
| break; |
| } |
| repr = netdev_priv(iter_netdev); |
| |
| /* Ensure all ports are created by the same app/on same card. */ |
| if (repr->app != priv->app) { |
| can_offload = false; |
| break; |
| } |
| |
| slave_count++; |
| } |
| rcu_read_unlock(); |
| |
| lag_upper_info = info->upper_info; |
| |
| /* Firmware supports active/backup and L3/L4 hash bonds. */ |
| if (lag_upper_info && |
| lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_ACTIVEBACKUP && |
| (lag_upper_info->tx_type != NETDEV_LAG_TX_TYPE_HASH || |
| (lag_upper_info->hash_type != NETDEV_LAG_HASH_L34 && |
| lag_upper_info->hash_type != NETDEV_LAG_HASH_E34 && |
| lag_upper_info->hash_type != NETDEV_LAG_HASH_UNKNOWN))) { |
| can_offload = false; |
| nfp_flower_cmsg_warn(priv->app, |
| "Unable to offload tx_type %u hash %u\n", |
| lag_upper_info->tx_type, |
| lag_upper_info->hash_type); |
| } |
| |
| mutex_lock(&lag->lock); |
| group = nfp_fl_lag_find_group_for_master_with_lag(lag, upper); |
| |
| if (slave_count == 0 || !can_offload) { |
| /* Cannot offload the group - remove if previously offloaded. */ |
| if (group && group->offloaded) |
| nfp_fl_lag_schedule_group_remove(lag, group); |
| |
| mutex_unlock(&lag->lock); |
| return 0; |
| } |
| |
| if (!group) { |
| group = nfp_fl_lag_group_create(lag, upper); |
| if (IS_ERR(group)) { |
| mutex_unlock(&lag->lock); |
| return PTR_ERR(group); |
| } |
| } |
| |
| group->dirty = true; |
| group->slave_cnt = slave_count; |
| |
| /* Group may have been on queue for removal but is now offloadable. */ |
| group->to_remove = false; |
| mutex_unlock(&lag->lock); |
| |
| schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY); |
| return 0; |
| } |
| |
| static void |
| nfp_fl_lag_changels_event(struct nfp_fl_lag *lag, struct net_device *netdev, |
| struct netdev_notifier_changelowerstate_info *info) |
| { |
| struct netdev_lag_lower_state_info *lag_lower_info; |
| struct nfp_flower_repr_priv *repr_priv; |
| struct nfp_flower_priv *priv; |
| struct nfp_repr *repr; |
| unsigned long *flags; |
| |
| if (!netif_is_lag_port(netdev) || !nfp_netdev_is_nfp_repr(netdev)) |
| return; |
| |
| lag_lower_info = info->lower_state_info; |
| if (!lag_lower_info) |
| return; |
| |
| priv = container_of(lag, struct nfp_flower_priv, nfp_lag); |
| repr = netdev_priv(netdev); |
| |
| /* Verify that the repr is associated with this app. */ |
| if (repr->app != priv->app) |
| return; |
| |
| repr_priv = repr->app_priv; |
| flags = &repr_priv->lag_port_flags; |
| |
| mutex_lock(&lag->lock); |
| if (lag_lower_info->link_up) |
| *flags |= NFP_PORT_LAG_LINK_UP; |
| else |
| *flags &= ~NFP_PORT_LAG_LINK_UP; |
| |
| if (lag_lower_info->tx_enabled) |
| *flags |= NFP_PORT_LAG_TX_ENABLED; |
| else |
| *flags &= ~NFP_PORT_LAG_TX_ENABLED; |
| |
| *flags |= NFP_PORT_LAG_CHANGED; |
| mutex_unlock(&lag->lock); |
| |
| schedule_delayed_work(&lag->work, NFP_FL_LAG_DELAY); |
| } |
| |
| int nfp_flower_lag_netdev_event(struct nfp_flower_priv *priv, |
| struct net_device *netdev, |
| unsigned long event, void *ptr) |
| { |
| struct nfp_fl_lag *lag = &priv->nfp_lag; |
| int err; |
| |
| switch (event) { |
| case NETDEV_CHANGEUPPER: |
| err = nfp_fl_lag_changeupper_event(lag, ptr); |
| if (err) |
| return NOTIFY_BAD; |
| return NOTIFY_OK; |
| case NETDEV_CHANGELOWERSTATE: |
| nfp_fl_lag_changels_event(lag, netdev, ptr); |
| return NOTIFY_OK; |
| case NETDEV_UNREGISTER: |
| nfp_fl_lag_schedule_group_delete(lag, netdev); |
| return NOTIFY_OK; |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| int nfp_flower_lag_reset(struct nfp_fl_lag *lag) |
| { |
| enum nfp_fl_lag_batch batch = NFP_FL_LAG_BATCH_FIRST; |
| |
| lag->rst_cfg = true; |
| return nfp_fl_lag_config_group(lag, NULL, NULL, 0, &batch); |
| } |
| |
| void nfp_flower_lag_init(struct nfp_fl_lag *lag) |
| { |
| INIT_DELAYED_WORK(&lag->work, nfp_fl_lag_do_work); |
| INIT_LIST_HEAD(&lag->group_list); |
| mutex_init(&lag->lock); |
| ida_init(&lag->ida_handle); |
| |
| __skb_queue_head_init(&lag->retrans_skbs); |
| |
| /* 0 is a reserved batch version so increment to first valid value. */ |
| nfp_fl_increment_version(lag); |
| } |
| |
| void nfp_flower_lag_cleanup(struct nfp_fl_lag *lag) |
| { |
| struct nfp_fl_lag_group *entry, *storage; |
| |
| cancel_delayed_work_sync(&lag->work); |
| |
| __skb_queue_purge(&lag->retrans_skbs); |
| |
| /* Remove all groups. */ |
| mutex_lock(&lag->lock); |
| list_for_each_entry_safe(entry, storage, &lag->group_list, list) { |
| list_del(&entry->list); |
| kfree(entry); |
| } |
| mutex_unlock(&lag->lock); |
| mutex_destroy(&lag->lock); |
| ida_destroy(&lag->ida_handle); |
| } |