diff options
Diffstat (limited to 'drivers/net/netdevsim/fib.c')
-rw-r--r-- | drivers/net/netdevsim/fib.c | 678 |
1 files changed, 495 insertions, 183 deletions
diff --git a/drivers/net/netdevsim/fib.c b/drivers/net/netdevsim/fib.c index 45d8a7790bd5..46fb414f7ca6 100644 --- a/drivers/net/netdevsim/fib.c +++ b/drivers/net/netdevsim/fib.c @@ -26,12 +26,13 @@ #include <net/fib_rules.h> #include <net/net_namespace.h> #include <net/nexthop.h> +#include <linux/debugfs.h> #include "netdevsim.h" struct nsim_fib_entry { u64 max; - u64 num; + atomic64_t num; }; struct nsim_per_fib_data { @@ -46,10 +47,15 @@ struct nsim_fib_data { struct nsim_fib_entry nexthops; struct rhashtable fib_rt_ht; struct list_head fib_rt_list; - spinlock_t fib_lock; /* Protects hashtable, list and accounting */ + struct mutex fib_lock; /* Protects hashtable and list */ struct notifier_block nexthop_nb; struct rhashtable nexthop_ht; struct devlink *devlink; + struct work_struct fib_event_work; + struct list_head fib_event_queue; + spinlock_t fib_event_queue_lock; /* Protects fib event queue list */ + struct dentry *ddir; + bool fail_route_offload; }; struct nsim_fib_rt_key { @@ -83,6 +89,22 @@ struct nsim_fib6_rt_nh { struct fib6_info *rt; }; +struct nsim_fib6_event { + struct fib6_info **rt_arr; + unsigned int nrt6; +}; + +struct nsim_fib_event { + struct list_head list; /* node in fib queue */ + union { + struct fib_entry_notifier_info fen_info; + struct nsim_fib6_event fib6_event; + }; + struct nsim_fib_data *data; + unsigned long event; + int family; +}; + static const struct rhashtable_params nsim_fib_rt_ht_params = { .key_offset = offsetof(struct nsim_fib_rt, key), .head_offset = offsetof(struct nsim_fib_rt, ht_node), @@ -128,7 +150,7 @@ u64 nsim_fib_get_val(struct nsim_fib_data *fib_data, return 0; } - return max ? entry->max : entry->num; + return max ? entry->max : atomic64_read(&entry->num); } static void nsim_fib_set_max(struct nsim_fib_data *fib_data, @@ -165,14 +187,12 @@ static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add, int err = 0; if (add) { - if (entry->num < entry->max) { - entry->num++; - } else { + if (!atomic64_add_unless(&entry->num, 1, entry->max)) { err = -ENOSPC; NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries"); } } else { - entry->num--; + atomic64_dec_if_positive(&entry->num); } return err; @@ -196,20 +216,15 @@ static int nsim_fib_rule_event(struct nsim_fib_data *data, return err; } -static int nsim_fib_account(struct nsim_fib_entry *entry, bool add, - struct netlink_ext_ack *extack) +static int nsim_fib_account(struct nsim_fib_entry *entry, bool add) { int err = 0; if (add) { - if (entry->num < entry->max) { - entry->num++; - } else { + if (!atomic64_add_unless(&entry->num, 1, entry->max)) err = -ENOSPC; - NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries"); - } } else { - entry->num--; + atomic64_dec_if_positive(&entry->num); } return err; @@ -254,7 +269,7 @@ nsim_fib4_rt_create(struct nsim_fib_data *data, { struct nsim_fib4_rt *fib4_rt; - fib4_rt = kzalloc(sizeof(*fib4_rt), GFP_ATOMIC); + fib4_rt = kzalloc(sizeof(*fib4_rt), GFP_KERNEL); if (!fib4_rt) return NULL; @@ -291,6 +306,25 @@ nsim_fib4_rt_lookup(struct rhashtable *fib_rt_ht, return container_of(fib_rt, struct nsim_fib4_rt, common); } +static void +nsim_fib4_rt_offload_failed_flag_set(struct net *net, + struct fib_entry_notifier_info *fen_info) +{ + u32 *p_dst = (u32 *)&fen_info->dst; + struct fib_rt_info fri; + + fri.fi = fen_info->fi; + fri.tb_id = fen_info->tb_id; + fri.dst = cpu_to_be32(*p_dst); + fri.dst_len = fen_info->dst_len; + fri.tos = fen_info->tos; + fri.type = fen_info->type; + fri.offload = false; + fri.trap = false; + fri.offload_failed = true; + fib_alias_hw_flags_set(net, &fri); +} + static void nsim_fib4_rt_hw_flags_set(struct net *net, const struct nsim_fib4_rt *fib4_rt, bool trap) @@ -307,55 +341,57 @@ static void nsim_fib4_rt_hw_flags_set(struct net *net, fri.type = fib4_rt->type; fri.offload = false; fri.trap = trap; + fri.offload_failed = false; fib_alias_hw_flags_set(net, &fri); } static int nsim_fib4_rt_add(struct nsim_fib_data *data, - struct nsim_fib4_rt *fib4_rt, - struct netlink_ext_ack *extack) + struct nsim_fib4_rt *fib4_rt) { struct net *net = devlink_net(data->devlink); int err; - err = nsim_fib_account(&data->ipv4.fib, true, extack); - if (err) - return err; - err = rhashtable_insert_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node, nsim_fib_rt_ht_params); - if (err) { - NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv4 route"); + if (err) goto err_fib_dismiss; - } + /* Simulate hardware programming latency. */ + msleep(1); nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); return 0; err_fib_dismiss: - nsim_fib_account(&data->ipv4.fib, false, extack); + /* Drop the accounting that was increased from the notification + * context when FIB_EVENT_ENTRY_REPLACE was triggered. + */ + nsim_fib_account(&data->ipv4.fib, false); return err; } static int nsim_fib4_rt_replace(struct nsim_fib_data *data, struct nsim_fib4_rt *fib4_rt, - struct nsim_fib4_rt *fib4_rt_old, - struct netlink_ext_ack *extack) + struct nsim_fib4_rt *fib4_rt_old) { struct net *net = devlink_net(data->devlink); int err; - /* We are replacing a route, so no need to change the accounting. */ + /* We are replacing a route, so need to remove the accounting which + * was increased when FIB_EVENT_ENTRY_REPLACE was triggered. + */ + err = nsim_fib_account(&data->ipv4.fib, false); + if (err) + return err; err = rhashtable_replace_fast(&data->fib_rt_ht, &fib4_rt_old->common.ht_node, &fib4_rt->common.ht_node, nsim_fib_rt_ht_params); - if (err) { - NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv4 route"); + if (err) return err; - } + msleep(1); nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); nsim_fib4_rt_hw_flags_set(net, fib4_rt_old, false); @@ -367,19 +403,27 @@ static int nsim_fib4_rt_replace(struct nsim_fib_data *data, static int nsim_fib4_rt_insert(struct nsim_fib_data *data, struct fib_entry_notifier_info *fen_info) { - struct netlink_ext_ack *extack = fen_info->info.extack; struct nsim_fib4_rt *fib4_rt, *fib4_rt_old; int err; + if (data->fail_route_offload) { + /* For testing purposes, user set debugfs fail_route_offload + * value to true. Simulate hardware programming latency and then + * fail. + */ + msleep(1); + return -EINVAL; + } + fib4_rt = nsim_fib4_rt_create(data, fen_info); if (!fib4_rt) return -ENOMEM; fib4_rt_old = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info); if (!fib4_rt_old) - err = nsim_fib4_rt_add(data, fib4_rt, extack); + err = nsim_fib4_rt_add(data, fib4_rt); else - err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old, extack); + err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old); if (err) nsim_fib4_rt_destroy(fib4_rt); @@ -390,31 +434,31 @@ static int nsim_fib4_rt_insert(struct nsim_fib_data *data, static void nsim_fib4_rt_remove(struct nsim_fib_data *data, const struct fib_entry_notifier_info *fen_info) { - struct netlink_ext_ack *extack = fen_info->info.extack; struct nsim_fib4_rt *fib4_rt; fib4_rt = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info); - if (WARN_ON_ONCE(!fib4_rt)) + if (!fib4_rt) return; rhashtable_remove_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node, nsim_fib_rt_ht_params); - nsim_fib_account(&data->ipv4.fib, false, extack); nsim_fib4_rt_destroy(fib4_rt); } static int nsim_fib4_event(struct nsim_fib_data *data, - struct fib_notifier_info *info, + struct fib_entry_notifier_info *fen_info, unsigned long event) { - struct fib_entry_notifier_info *fen_info; int err = 0; - fen_info = container_of(info, struct fib_entry_notifier_info, info); - switch (event) { case FIB_EVENT_ENTRY_REPLACE: err = nsim_fib4_rt_insert(data, fen_info); + if (err) { + struct net *net = devlink_net(data->devlink); + + nsim_fib4_rt_offload_failed_flag_set(net, fen_info); + } break; case FIB_EVENT_ENTRY_DEL: nsim_fib4_rt_remove(data, fen_info); @@ -445,7 +489,7 @@ static int nsim_fib6_rt_nh_add(struct nsim_fib6_rt *fib6_rt, { struct nsim_fib6_rt_nh *fib6_rt_nh; - fib6_rt_nh = kzalloc(sizeof(*fib6_rt_nh), GFP_ATOMIC); + fib6_rt_nh = kzalloc(sizeof(*fib6_rt_nh), GFP_KERNEL); if (!fib6_rt_nh) return -ENOMEM; @@ -457,33 +501,42 @@ static int nsim_fib6_rt_nh_add(struct nsim_fib6_rt *fib6_rt, return 0; } +#if IS_ENABLED(CONFIG_IPV6) +static void nsim_rt6_release(struct fib6_info *rt) +{ + fib6_info_release(rt); +} +#else +static void nsim_rt6_release(struct fib6_info *rt) +{ +} +#endif + static void nsim_fib6_rt_nh_del(struct nsim_fib6_rt *fib6_rt, const struct fib6_info *rt) { struct nsim_fib6_rt_nh *fib6_rt_nh; fib6_rt_nh = nsim_fib6_rt_nh_find(fib6_rt, rt); - if (WARN_ON_ONCE(!fib6_rt_nh)) + if (!fib6_rt_nh) return; fib6_rt->nhs--; list_del(&fib6_rt_nh->list); -#if IS_ENABLED(CONFIG_IPV6) - fib6_info_release(fib6_rt_nh->rt); -#endif + nsim_rt6_release(fib6_rt_nh->rt); kfree(fib6_rt_nh); } static struct nsim_fib6_rt * nsim_fib6_rt_create(struct nsim_fib_data *data, - struct fib6_entry_notifier_info *fen6_info) + struct fib6_info **rt_arr, unsigned int nrt6) { - struct fib6_info *iter, *rt = fen6_info->rt; + struct fib6_info *rt = rt_arr[0]; struct nsim_fib6_rt *fib6_rt; int i = 0; int err; - fib6_rt = kzalloc(sizeof(*fib6_rt), GFP_ATOMIC); + fib6_rt = kzalloc(sizeof(*fib6_rt), GFP_KERNEL); if (!fib6_rt) return ERR_PTR(-ENOMEM); @@ -497,32 +550,18 @@ nsim_fib6_rt_create(struct nsim_fib_data *data, */ INIT_LIST_HEAD(&fib6_rt->nh_list); - err = nsim_fib6_rt_nh_add(fib6_rt, rt); - if (err) - goto err_fib_rt_fini; - - if (!fen6_info->nsiblings) - return fib6_rt; - - list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) { - if (i == fen6_info->nsiblings) - break; - - err = nsim_fib6_rt_nh_add(fib6_rt, iter); + for (i = 0; i < nrt6; i++) { + err = nsim_fib6_rt_nh_add(fib6_rt, rt_arr[i]); if (err) goto err_fib6_rt_nh_del; - i++; } - WARN_ON_ONCE(i != fen6_info->nsiblings); return fib6_rt; err_fib6_rt_nh_del: - list_for_each_entry_continue_reverse(iter, &rt->fib6_siblings, - fib6_siblings) - nsim_fib6_rt_nh_del(fib6_rt, iter); - nsim_fib6_rt_nh_del(fib6_rt, rt); -err_fib_rt_fini: + for (i--; i >= 0; i--) { + nsim_fib6_rt_nh_del(fib6_rt, rt_arr[i]); + }; nsim_fib_rt_fini(&fib6_rt->common); kfree(fib6_rt); return ERR_PTR(err); @@ -555,127 +594,163 @@ nsim_fib6_rt_lookup(struct rhashtable *fib_rt_ht, const struct fib6_info *rt) } static int nsim_fib6_rt_append(struct nsim_fib_data *data, - struct fib6_entry_notifier_info *fen6_info) + struct nsim_fib6_event *fib6_event) { - struct fib6_info *iter, *rt = fen6_info->rt; + struct fib6_info *rt = fib6_event->rt_arr[0]; struct nsim_fib6_rt *fib6_rt; - int i = 0; - int err; + int i, err; + + if (data->fail_route_offload) { + /* For testing purposes, user set debugfs fail_route_offload + * value to true. Simulate hardware programming latency and then + * fail. + */ + msleep(1); + return -EINVAL; + } fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, rt); - if (WARN_ON_ONCE(!fib6_rt)) + if (!fib6_rt) return -EINVAL; - err = nsim_fib6_rt_nh_add(fib6_rt, rt); - if (err) - return err; - rt->trap = true; - - if (!fen6_info->nsiblings) - return 0; - - list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) { - if (i == fen6_info->nsiblings) - break; - - err = nsim_fib6_rt_nh_add(fib6_rt, iter); + for (i = 0; i < fib6_event->nrt6; i++) { + err = nsim_fib6_rt_nh_add(fib6_rt, fib6_event->rt_arr[i]); if (err) goto err_fib6_rt_nh_del; - iter->trap = true; - i++; + + fib6_event->rt_arr[i]->trap = true; } - WARN_ON_ONCE(i != fen6_info->nsiblings); return 0; err_fib6_rt_nh_del: - list_for_each_entry_continue_reverse(iter, &rt->fib6_siblings, - fib6_siblings) { - iter->trap = false; - nsim_fib6_rt_nh_del(fib6_rt, iter); + for (i--; i >= 0; i--) { + fib6_event->rt_arr[i]->trap = false; + nsim_fib6_rt_nh_del(fib6_rt, fib6_event->rt_arr[i]); } - rt->trap = false; - nsim_fib6_rt_nh_del(fib6_rt, rt); return err; } -static void nsim_fib6_rt_hw_flags_set(const struct nsim_fib6_rt *fib6_rt, +#if IS_ENABLED(CONFIG_IPV6) +static void nsim_fib6_rt_offload_failed_flag_set(struct nsim_fib_data *data, + struct fib6_info **rt_arr, + unsigned int nrt6) + +{ + struct net *net = devlink_net(data->devlink); + int i; + + for (i = 0; i < nrt6; i++) + fib6_info_hw_flags_set(net, rt_arr[i], false, false, true); +} +#else +static void nsim_fib6_rt_offload_failed_flag_set(struct nsim_fib_data *data, + struct fib6_info **rt_arr, + unsigned int nrt6) +{ +} +#endif + +#if IS_ENABLED(CONFIG_IPV6) +static void nsim_fib6_rt_hw_flags_set(struct nsim_fib_data *data, + const struct nsim_fib6_rt *fib6_rt, bool trap) { + struct net *net = devlink_net(data->devlink); struct nsim_fib6_rt_nh *fib6_rt_nh; list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) - fib6_info_hw_flags_set(fib6_rt_nh->rt, false, trap); + fib6_info_hw_flags_set(net, fib6_rt_nh->rt, false, trap, false); } +#else +static void nsim_fib6_rt_hw_flags_set(struct nsim_fib_data *data, + const struct nsim_fib6_rt *fib6_rt, + bool trap) +{ +} +#endif static int nsim_fib6_rt_add(struct nsim_fib_data *data, - struct nsim_fib6_rt *fib6_rt, - struct netlink_ext_ack *extack) + struct nsim_fib6_rt *fib6_rt) { int err; - err = nsim_fib_account(&data->ipv6.fib, true, extack); - if (err) - return err; - err = rhashtable_insert_fast(&data->fib_rt_ht, &fib6_rt->common.ht_node, nsim_fib_rt_ht_params); - if (err) { - NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv6 route"); + + if (err) goto err_fib_dismiss; - } - nsim_fib6_rt_hw_flags_set(fib6_rt, true); + msleep(1); + nsim_fib6_rt_hw_flags_set(data, fib6_rt, true); return 0; err_fib_dismiss: - nsim_fib_account(&data->ipv6.fib, false, extack); + /* Drop the accounting that was increased from the notification + * context when FIB_EVENT_ENTRY_REPLACE was triggered. + */ + nsim_fib_account(&data->ipv6.fib, false); return err; } static int nsim_fib6_rt_replace(struct nsim_fib_data *data, struct nsim_fib6_rt *fib6_rt, - struct nsim_fib6_rt *fib6_rt_old, - struct netlink_ext_ack *extack) + struct nsim_fib6_rt *fib6_rt_old) { int err; - /* We are replacing a route, so no need to change the accounting. */ + /* We are replacing a route, so need to remove the accounting which + * was increased when FIB_EVENT_ENTRY_REPLACE was triggered. + */ + err = nsim_fib_account(&data->ipv6.fib, false); + if (err) + return err; + err = rhashtable_replace_fast(&data->fib_rt_ht, &fib6_rt_old->common.ht_node, &fib6_rt->common.ht_node, nsim_fib_rt_ht_params); - if (err) { - NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv6 route"); + + if (err) return err; - } - nsim_fib6_rt_hw_flags_set(fib6_rt, true); + msleep(1); + nsim_fib6_rt_hw_flags_set(data, fib6_rt, true); - nsim_fib6_rt_hw_flags_set(fib6_rt_old, false); + nsim_fib6_rt_hw_flags_set(data, fib6_rt_old, false); nsim_fib6_rt_destroy(fib6_rt_old); return 0; } static int nsim_fib6_rt_insert(struct nsim_fib_data *data, - struct fib6_entry_notifier_info *fen6_info) + struct nsim_fib6_event *fib6_event) { - struct netlink_ext_ack *extack = fen6_info->info.extack; + struct fib6_info *rt = fib6_event->rt_arr[0]; struct nsim_fib6_rt *fib6_rt, *fib6_rt_old; int err; - fib6_rt = nsim_fib6_rt_create(data, fen6_info); + if (data->fail_route_offload) { + /* For testing purposes, user set debugfs fail_route_offload + * value to true. Simulate hardware programming latency and then + * fail. + */ + msleep(1); + return -EINVAL; + } + + fib6_rt = nsim_fib6_rt_create(data, fib6_event->rt_arr, + fib6_event->nrt6); if (IS_ERR(fib6_rt)) return PTR_ERR(fib6_rt); - fib6_rt_old = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt); + fib6_rt_old = nsim_fib6_rt_lookup(&data->fib_rt_ht, rt); if (!fib6_rt_old) - err = nsim_fib6_rt_add(data, fib6_rt, extack); + err = nsim_fib6_rt_add(data, fib6_rt); else - err = nsim_fib6_rt_replace(data, fib6_rt, fib6_rt_old, extack); + err = nsim_fib6_rt_replace(data, fib6_rt, fib6_rt_old); if (err) nsim_fib6_rt_destroy(fib6_rt); @@ -683,112 +758,276 @@ static int nsim_fib6_rt_insert(struct nsim_fib_data *data, return err; } -static void -nsim_fib6_rt_remove(struct nsim_fib_data *data, - const struct fib6_entry_notifier_info *fen6_info) +static void nsim_fib6_rt_remove(struct nsim_fib_data *data, + struct nsim_fib6_event *fib6_event) { - struct netlink_ext_ack *extack = fen6_info->info.extack; + struct fib6_info *rt = fib6_event->rt_arr[0]; struct nsim_fib6_rt *fib6_rt; + int i; /* Multipath routes are first added to the FIB trie and only then * notified. If we vetoed the addition, we will get a delete * notification for a route we do not have. Therefore, do not warn if * route was not found. */ - fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt); + fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, rt); if (!fib6_rt) return; /* If not all the nexthops are deleted, then only reduce the nexthop * group. */ - if (fen6_info->nsiblings + 1 != fib6_rt->nhs) { - nsim_fib6_rt_nh_del(fib6_rt, fen6_info->rt); + if (fib6_event->nrt6 != fib6_rt->nhs) { + for (i = 0; i < fib6_event->nrt6; i++) + nsim_fib6_rt_nh_del(fib6_rt, fib6_event->rt_arr[i]); return; } rhashtable_remove_fast(&data->fib_rt_ht, &fib6_rt->common.ht_node, nsim_fib_rt_ht_params); - nsim_fib_account(&data->ipv6.fib, false, extack); nsim_fib6_rt_destroy(fib6_rt); } -static int nsim_fib6_event(struct nsim_fib_data *data, - struct fib_notifier_info *info, - unsigned long event) +static int nsim_fib6_event_init(struct nsim_fib6_event *fib6_event, + struct fib6_entry_notifier_info *fen6_info) { - struct fib6_entry_notifier_info *fen6_info; - int err = 0; + struct fib6_info *rt = fen6_info->rt; + struct fib6_info **rt_arr; + struct fib6_info *iter; + unsigned int nrt6; + int i = 0; + + nrt6 = fen6_info->nsiblings + 1; + + rt_arr = kcalloc(nrt6, sizeof(struct fib6_info *), GFP_ATOMIC); + if (!rt_arr) + return -ENOMEM; - fen6_info = container_of(info, struct fib6_entry_notifier_info, info); + fib6_event->rt_arr = rt_arr; + fib6_event->nrt6 = nrt6; - if (fen6_info->rt->fib6_src.plen) { - NL_SET_ERR_MSG_MOD(info->extack, "IPv6 source-specific route is not supported"); + rt_arr[0] = rt; + fib6_info_hold(rt); + + if (!fen6_info->nsiblings) return 0; + + list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) { + if (i == fen6_info->nsiblings) + break; + + rt_arr[i + 1] = iter; + fib6_info_hold(iter); + i++; } + WARN_ON_ONCE(i != fen6_info->nsiblings); + + return 0; +} + +static void nsim_fib6_event_fini(struct nsim_fib6_event *fib6_event) +{ + int i; + + for (i = 0; i < fib6_event->nrt6; i++) + nsim_rt6_release(fib6_event->rt_arr[i]); + kfree(fib6_event->rt_arr); +} + +static int nsim_fib6_event(struct nsim_fib_data *data, + struct nsim_fib6_event *fib6_event, + unsigned long event) +{ + int err; + + if (fib6_event->rt_arr[0]->fib6_src.plen) + return 0; switch (event) { case FIB_EVENT_ENTRY_REPLACE: - err = nsim_fib6_rt_insert(data, fen6_info); + err = nsim_fib6_rt_insert(data, fib6_event); + if (err) + goto err_rt_offload_failed_flag_set; break; case FIB_EVENT_ENTRY_APPEND: - err = nsim_fib6_rt_append(data, fen6_info); + err = nsim_fib6_rt_append(data, fib6_event); + if (err) + goto err_rt_offload_failed_flag_set; break; case FIB_EVENT_ENTRY_DEL: - nsim_fib6_rt_remove(data, fen6_info); + nsim_fib6_rt_remove(data, fib6_event); break; default: break; } + return 0; + +err_rt_offload_failed_flag_set: + nsim_fib6_rt_offload_failed_flag_set(data, fib6_event->rt_arr, + fib6_event->nrt6); return err; } -static int nsim_fib_event(struct nsim_fib_data *data, - struct fib_notifier_info *info, unsigned long event) +static int nsim_fib_event(struct nsim_fib_event *fib_event) { int err = 0; - switch (info->family) { + switch (fib_event->family) { case AF_INET: - err = nsim_fib4_event(data, info, event); + nsim_fib4_event(fib_event->data, &fib_event->fen_info, + fib_event->event); + fib_info_put(fib_event->fen_info.fi); break; case AF_INET6: - err = nsim_fib6_event(data, info, event); + nsim_fib6_event(fib_event->data, &fib_event->fib6_event, + fib_event->event); + nsim_fib6_event_fini(&fib_event->fib6_event); + break; + } + + return err; +} + +static int nsim_fib4_prepare_event(struct fib_notifier_info *info, + struct nsim_fib_event *fib_event, + unsigned long event) +{ + struct nsim_fib_data *data = fib_event->data; + struct fib_entry_notifier_info *fen_info; + struct netlink_ext_ack *extack; + int err = 0; + + fen_info = container_of(info, struct fib_entry_notifier_info, + info); + fib_event->fen_info = *fen_info; + extack = info->extack; + + switch (event) { + case FIB_EVENT_ENTRY_REPLACE: + err = nsim_fib_account(&data->ipv4.fib, true); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries"); + return err; + } + break; + case FIB_EVENT_ENTRY_DEL: + nsim_fib_account(&data->ipv4.fib, false); + break; + } + + /* Take reference on fib_info to prevent it from being + * freed while event is queued. Release it afterwards. + */ + fib_info_hold(fib_event->fen_info.fi); + + return 0; +} + +static int nsim_fib6_prepare_event(struct fib_notifier_info *info, + struct nsim_fib_event *fib_event, + unsigned long event) +{ + struct nsim_fib_data *data = fib_event->data; + struct fib6_entry_notifier_info *fen6_info; + struct netlink_ext_ack *extack; + int err = 0; + + fen6_info = container_of(info, struct fib6_entry_notifier_info, + info); + + err = nsim_fib6_event_init(&fib_event->fib6_event, fen6_info); + if (err) + return err; + + extack = info->extack; + switch (event) { + case FIB_EVENT_ENTRY_REPLACE: + err = nsim_fib_account(&data->ipv6.fib, true); + if (err) { + NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries"); + goto err_fib6_event_fini; + } + break; + case FIB_EVENT_ENTRY_DEL: + nsim_fib_account(&data->ipv6.fib, false); break; } + return 0; + +err_fib6_event_fini: + nsim_fib6_event_fini(&fib_event->fib6_event); return err; } +static int nsim_fib_event_schedule_work(struct nsim_fib_data *data, + struct fib_notifier_info *info, + unsigned long event) +{ + struct nsim_fib_event *fib_event; + int err; + + if (info->family != AF_INET && info->family != AF_INET6) + /* netdevsim does not support 'RTNL_FAMILY_IP6MR' and + * 'RTNL_FAMILY_IPMR' and should ignore them. + */ + return NOTIFY_DONE; + + fib_event = kzalloc(sizeof(*fib_event), GFP_ATOMIC); + if (!fib_event) + return NOTIFY_BAD; + + fib_event->data = data; + fib_event->event = event; + fib_event->family = info->family; + + switch (info->family) { + case AF_INET: + err = nsim_fib4_prepare_event(info, fib_event, event); + break; + case AF_INET6: + err = nsim_fib6_prepare_event(info, fib_event, event); + break; + } + + if (err) + goto err_fib_prepare_event; + + /* Enqueue the event and trigger the work */ + spin_lock_bh(&data->fib_event_queue_lock); + list_add_tail(&fib_event->list, &data->fib_event_queue); + spin_unlock_bh(&data->fib_event_queue_lock); + schedule_work(&data->fib_event_work); + + return NOTIFY_DONE; + +err_fib_prepare_event: + kfree(fib_event); + return NOTIFY_BAD; +} + static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, void *ptr) { struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, fib_nb); struct fib_notifier_info *info = ptr; - int err = 0; - - /* IPv6 routes can be added via RAs from softIRQ. */ - spin_lock_bh(&data->fib_lock); + int err; switch (event) { case FIB_EVENT_RULE_ADD: case FIB_EVENT_RULE_DEL: err = nsim_fib_rule_event(data, info, event == FIB_EVENT_RULE_ADD); - break; - + return notifier_from_errno(err); case FIB_EVENT_ENTRY_REPLACE: case FIB_EVENT_ENTRY_APPEND: case FIB_EVENT_ENTRY_DEL: - err = nsim_fib_event(data, info, event); - break; + return nsim_fib_event_schedule_work(data, info, event); } - spin_unlock_bh(&data->fib_lock); - - return notifier_from_errno(err); + return NOTIFY_DONE; } static void nsim_fib4_rt_free(struct nsim_fib_rt *fib_rt, @@ -799,7 +1038,7 @@ static void nsim_fib4_rt_free(struct nsim_fib_rt *fib_rt, fib4_rt = container_of(fib_rt, struct nsim_fib4_rt, common); nsim_fib4_rt_hw_flags_set(devlink_net(devlink), fib4_rt, false); - nsim_fib_account(&data->ipv4.fib, false, NULL); + nsim_fib_account(&data->ipv4.fib, false); nsim_fib4_rt_destroy(fib4_rt); } @@ -809,8 +1048,8 @@ static void nsim_fib6_rt_free(struct nsim_fib_rt *fib_rt, struct nsim_fib6_rt *fib6_rt; fib6_rt = container_of(fib_rt, struct nsim_fib6_rt, common); - nsim_fib6_rt_hw_flags_set(fib6_rt, false); - nsim_fib_account(&data->ipv6.fib, false, NULL); + nsim_fib6_rt_hw_flags_set(data, fib6_rt, false); + nsim_fib_account(&data->ipv6.fib, false); nsim_fib6_rt_destroy(fib6_rt); } @@ -838,6 +1077,9 @@ static void nsim_fib_dump_inconsistent(struct notifier_block *nb) fib_nb); struct nsim_fib_rt *fib_rt, *fib_rt_tmp; + /* Flush the work to make sure there is no race with notifications. */ + flush_work(&data->fib_event_work); + /* The notifier block is still not registered, so we do not need to * take any locks here. */ @@ -847,8 +1089,8 @@ static void nsim_fib_dump_inconsistent(struct notifier_block *nb) nsim_fib_rt_free(fib_rt, data); } - data->ipv4.rules.num = 0ULL; - data->ipv6.rules.num = 0ULL; + atomic64_set(&data->ipv4.rules.num, 0ULL); + atomic64_set(&data->ipv6.rules.num, 0ULL); } static struct nsim_nexthop *nsim_nexthop_create(struct nsim_fib_data *data, @@ -860,7 +1102,7 @@ static struct nsim_nexthop *nsim_nexthop_create(struct nsim_fib_data *data, nexthop = kzalloc(sizeof(*nexthop), GFP_KERNEL); if (!nexthop) - return NULL; + return ERR_PTR(-ENOMEM); nexthop->id = info->id; @@ -868,15 +1110,20 @@ static struct nsim_nexthop *nsim_nexthop_create(struct nsim_fib_data *data, * occupy. */ - if (!info->is_grp) { + switch (info->type) { + case NH_NOTIFIER_INFO_TYPE_SINGLE: occ = 1; - goto out; + break; + case NH_NOTIFIER_INFO_TYPE_GRP: + for (i = 0; i < info->nh_grp->num_nh; i++) + occ += info->nh_grp->nh_entries[i].weight; + break; + default: + NL_SET_ERR_MSG_MOD(info->extack, "Unsupported nexthop type"); + kfree(nexthop); + return ERR_PTR(-EOPNOTSUPP); } - for (i = 0; i < info->nh_grp->num_nh; i++) - occ += info->nh_grp->nh_entries[i].weight; - -out: nexthop->occ = occ; return nexthop; } @@ -889,22 +1136,28 @@ static void nsim_nexthop_destroy(struct nsim_nexthop *nexthop) static int nsim_nexthop_account(struct nsim_fib_data *data, u64 occ, bool add, struct netlink_ext_ack *extack) { - int err = 0; + int i, err = 0; if (add) { - if (data->nexthops.num + occ <= data->nexthops.max) { - data->nexthops.num += occ; - } else { - err = -ENOSPC; - NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported nexthops"); - } + for (i = 0; i < occ; i++) + if (!atomic64_add_unless(&data->nexthops.num, 1, + data->nexthops.max)) { + err = -ENOSPC; + NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported nexthops"); + goto err_num_decrease; + } } else { - if (WARN_ON(occ > data->nexthops.num)) + if (WARN_ON(occ > atomic64_read(&data->nexthops.num))) return -EINVAL; - data->nexthops.num -= occ; + atomic64_sub(occ, &data->nexthops.num); } return err; + +err_num_decrease: + atomic64_sub(i, &data->nexthops.num); + return err; + } static int nsim_nexthop_add(struct nsim_fib_data *data, @@ -972,8 +1225,8 @@ static int nsim_nexthop_insert(struct nsim_fib_data *data, int err; nexthop = nsim_nexthop_create(data, info); - if (!nexthop) - return -ENOMEM; + if (IS_ERR(nexthop)) + return PTR_ERR(nexthop); nexthop_old = rhashtable_lookup_fast(&data->nexthop_ht, &info->id, nsim_nexthop_ht_params); @@ -1097,10 +1350,52 @@ static void nsim_fib_set_max_all(struct nsim_fib_data *data, } } +static void nsim_fib_event_work(struct work_struct *work) +{ + struct nsim_fib_data *data = container_of(work, struct nsim_fib_data, + fib_event_work); + struct nsim_fib_event *fib_event, *next_fib_event; + + LIST_HEAD(fib_event_queue); + + spin_lock_bh(&data->fib_event_queue_lock); + list_splice_init(&data->fib_event_queue, &fib_event_queue); + spin_unlock_bh(&data->fib_event_queue_lock); + + mutex_lock(&data->fib_lock); + list_for_each_entry_safe(fib_event, next_fib_event, &fib_event_queue, + list) { + nsim_fib_event(fib_event); + list_del(&fib_event->list); + kfree(fib_event); + cond_resched(); + } + mutex_unlock(&data->fib_lock); +} + +static int +nsim_fib_debugfs_init(struct nsim_fib_data *data, struct nsim_dev *nsim_dev) +{ + data->ddir = debugfs_create_dir("fib", nsim_dev->ddir); + if (IS_ERR(data->ddir)) + return PTR_ERR(data->ddir); + + data->fail_route_offload = false; + debugfs_create_bool("fail_route_offload", 0600, data->ddir, + &data->fail_route_offload); + return 0; +} + +static void nsim_fib_debugfs_exit(struct nsim_fib_data *data) +{ + debugfs_remove_recursive(data->ddir); +} + struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, struct netlink_ext_ack *extack) { struct nsim_fib_data *data; + struct nsim_dev *nsim_dev; int err; data = kzalloc(sizeof(*data), GFP_KERNEL); @@ -1108,16 +1403,25 @@ struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, return ERR_PTR(-ENOMEM); data->devlink = devlink; - err = rhashtable_init(&data->nexthop_ht, &nsim_nexthop_ht_params); + nsim_dev = devlink_priv(devlink); + err = nsim_fib_debugfs_init(data, nsim_dev); if (err) goto err_data_free; - spin_lock_init(&data->fib_lock); + err = rhashtable_init(&data->nexthop_ht, &nsim_nexthop_ht_params); + if (err) + goto err_debugfs_exit; + + mutex_init(&data->fib_lock); INIT_LIST_HEAD(&data->fib_rt_list); err = rhashtable_init(&data->fib_rt_ht, &nsim_fib_rt_ht_params); if (err) goto err_rhashtable_nexthop_destroy; + INIT_WORK(&data->fib_event_work, nsim_fib_event_work); + INIT_LIST_HEAD(&data->fib_event_queue); + spin_lock_init(&data->fib_event_queue_lock); + nsim_fib_set_max_all(data, devlink); data->nexthop_nb.notifier_call = nsim_nexthop_event_nb; @@ -1161,11 +1465,15 @@ struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, err_nexthop_nb_unregister: unregister_nexthop_notifier(devlink_net(devlink), &data->nexthop_nb); err_rhashtable_fib_destroy: + flush_work(&data->fib_event_work); rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free, data); err_rhashtable_nexthop_destroy: rhashtable_free_and_destroy(&data->nexthop_ht, nsim_nexthop_free, data); + mutex_destroy(&data->fib_lock); +err_debugfs_exit: + nsim_fib_debugfs_exit(data); err_data_free: kfree(data); return ERR_PTR(err); @@ -1185,10 +1493,14 @@ void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data) NSIM_RESOURCE_IPV4_FIB); unregister_fib_notifier(devlink_net(devlink), &data->fib_nb); unregister_nexthop_notifier(devlink_net(devlink), &data->nexthop_nb); + flush_work(&data->fib_event_work); rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free, data); rhashtable_free_and_destroy(&data->nexthop_ht, nsim_nexthop_free, data); + WARN_ON_ONCE(!list_empty(&data->fib_event_queue)); WARN_ON_ONCE(!list_empty(&data->fib_rt_list)); + mutex_destroy(&data->fib_lock); + nsim_fib_debugfs_exit(data); kfree(data); } |