diff options
Diffstat (limited to 'net/sched')
49 files changed, 1050 insertions, 617 deletions
diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 4d33a50a8a6d..52622a3d2517 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -99,7 +99,7 @@ int __tcf_idr_release(struct tc_action *p, bool bind, bool strict) p->tcfa_refcnt--; if (p->tcfa_bindcnt <= 0 && p->tcfa_refcnt <= 0) { if (p->ops->cleanup) - p->ops->cleanup(p, bind); + p->ops->cleanup(p); tcf_idr_remove(p->idrinfo, p); ret = ACT_P_DELETED; } diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c index 5ef8ce8c83d4..b3f2c15affa7 100644 --- a/net/sched/act_bpf.c +++ b/net/sched/act_bpf.c @@ -357,7 +357,7 @@ out: return ret; } -static void tcf_bpf_cleanup(struct tc_action *act, int bind) +static void tcf_bpf_cleanup(struct tc_action *act) { struct tcf_bpf_cfg tmp; @@ -401,16 +401,14 @@ static __net_init int bpf_init_net(struct net *net) return tc_action_net_init(tn, &act_bpf_ops); } -static void __net_exit bpf_exit_net(struct net *net) +static void __net_exit bpf_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, bpf_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, bpf_net_id); } static struct pernet_operations bpf_net_ops = { .init = bpf_init_net, - .exit = bpf_exit_net, + .exit_batch = bpf_exit_net, .id = &bpf_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_connmark.c b/net/sched/act_connmark.c index 10b7a8855a6c..2b15ba84e0c8 100644 --- a/net/sched/act_connmark.c +++ b/net/sched/act_connmark.c @@ -209,16 +209,14 @@ static __net_init int connmark_init_net(struct net *net) return tc_action_net_init(tn, &act_connmark_ops); } -static void __net_exit connmark_exit_net(struct net *net) +static void __net_exit connmark_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, connmark_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, connmark_net_id); } static struct pernet_operations connmark_net_ops = { .init = connmark_init_net, - .exit = connmark_exit_net, + .exit_batch = connmark_exit_net, .id = &connmark_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_csum.c b/net/sched/act_csum.c index d836f998117b..af4b8ec60d9a 100644 --- a/net/sched/act_csum.c +++ b/net/sched/act_csum.c @@ -635,16 +635,14 @@ static __net_init int csum_init_net(struct net *net) return tc_action_net_init(tn, &act_csum_ops); } -static void __net_exit csum_exit_net(struct net *net) +static void __net_exit csum_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, csum_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, csum_net_id); } static struct pernet_operations csum_net_ops = { .init = csum_init_net, - .exit = csum_exit_net, + .exit_batch = csum_exit_net, .id = &csum_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_gact.c b/net/sched/act_gact.c index a0ac42b3ed06..b56986d41c87 100644 --- a/net/sched/act_gact.c +++ b/net/sched/act_gact.c @@ -235,16 +235,14 @@ static __net_init int gact_init_net(struct net *net) return tc_action_net_init(tn, &act_gact_ops); } -static void __net_exit gact_exit_net(struct net *net) +static void __net_exit gact_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, gact_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, gact_net_id); } static struct pernet_operations gact_net_ops = { .init = gact_init_net, - .exit = gact_exit_net, + .exit_batch = gact_exit_net, .id = &gact_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 3007cb1310ea..5954e992685a 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -387,7 +387,7 @@ out_nlmsg_trim: } /* under ife->tcf_lock */ -static void _tcf_ife_cleanup(struct tc_action *a, int bind) +static void _tcf_ife_cleanup(struct tc_action *a) { struct tcf_ife_info *ife = to_ife(a); struct tcf_meta_info *e, *n; @@ -405,13 +405,13 @@ static void _tcf_ife_cleanup(struct tc_action *a, int bind) } } -static void tcf_ife_cleanup(struct tc_action *a, int bind) +static void tcf_ife_cleanup(struct tc_action *a) { struct tcf_ife_info *ife = to_ife(a); struct tcf_ife_params *p; spin_lock_bh(&ife->tcf_lock); - _tcf_ife_cleanup(a, bind); + _tcf_ife_cleanup(a); spin_unlock_bh(&ife->tcf_lock); p = rcu_dereference_protected(ife->params, 1); @@ -546,7 +546,7 @@ metadata_parse_err: if (exists) tcf_idr_release(*a, bind); if (ret == ACT_P_CREATED) - _tcf_ife_cleanup(*a, bind); + _tcf_ife_cleanup(*a); if (exists) spin_unlock_bh(&ife->tcf_lock); @@ -567,7 +567,7 @@ metadata_parse_err: err = use_all_metadata(ife); if (err) { if (ret == ACT_P_CREATED) - _tcf_ife_cleanup(*a, bind); + _tcf_ife_cleanup(*a); if (exists) spin_unlock_bh(&ife->tcf_lock); @@ -858,16 +858,14 @@ static __net_init int ife_init_net(struct net *net) return tc_action_net_init(tn, &act_ife_ops); } -static void __net_exit ife_exit_net(struct net *net) +static void __net_exit ife_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, ife_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, ife_net_id); } static struct pernet_operations ife_net_ops = { .init = ife_init_net, - .exit = ife_exit_net, + .exit_batch = ife_exit_net, .id = &ife_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_ipt.c b/net/sched/act_ipt.c index d9e399a7e3d5..06e380ae0928 100644 --- a/net/sched/act_ipt.c +++ b/net/sched/act_ipt.c @@ -77,7 +77,7 @@ static void ipt_destroy_target(struct xt_entry_target *t) module_put(par.target->me); } -static void tcf_ipt_release(struct tc_action *a, int bind) +static void tcf_ipt_release(struct tc_action *a) { struct tcf_ipt *ipt = to_ipt(a); ipt_destroy_target(ipt->tcfi_t); @@ -337,16 +337,14 @@ static __net_init int ipt_init_net(struct net *net) return tc_action_net_init(tn, &act_ipt_ops); } -static void __net_exit ipt_exit_net(struct net *net) +static void __net_exit ipt_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, ipt_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, ipt_net_id); } static struct pernet_operations ipt_net_ops = { .init = ipt_init_net, - .exit = ipt_exit_net, + .exit_batch = ipt_exit_net, .id = &ipt_net_id, .size = sizeof(struct tc_action_net), }; @@ -387,16 +385,14 @@ static __net_init int xt_init_net(struct net *net) return tc_action_net_init(tn, &act_xt_ops); } -static void __net_exit xt_exit_net(struct net *net) +static void __net_exit xt_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, xt_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, xt_net_id); } static struct pernet_operations xt_net_ops = { .init = xt_init_net, - .exit = xt_exit_net, + .exit_batch = xt_exit_net, .id = &xt_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_mirred.c b/net/sched/act_mirred.c index 08b61849c2a2..e6ff88f72900 100644 --- a/net/sched/act_mirred.c +++ b/net/sched/act_mirred.c @@ -29,7 +29,6 @@ #include <net/tc_act/tc_mirred.h> static LIST_HEAD(mirred_list); -static DEFINE_SPINLOCK(mirred_list_lock); static bool tcf_mirred_is_act_redirect(int action) { @@ -50,18 +49,15 @@ static bool tcf_mirred_act_wants_ingress(int action) } } -static void tcf_mirred_release(struct tc_action *a, int bind) +static void tcf_mirred_release(struct tc_action *a) { struct tcf_mirred *m = to_mirred(a); struct net_device *dev; - /* We could be called either in a RCU callback or with RTNL lock held. */ - spin_lock_bh(&mirred_list_lock); list_del(&m->tcfm_list); - dev = rcu_dereference_protected(m->tcfm_dev, 1); + dev = rtnl_dereference(m->tcfm_dev); if (dev) dev_put(dev); - spin_unlock_bh(&mirred_list_lock); } static const struct nla_policy mirred_policy[TCA_MIRRED_MAX + 1] = { @@ -139,8 +135,6 @@ static int tcf_mirred_init(struct net *net, struct nlattr *nla, m->tcf_action = parm->action; m->tcfm_eaction = parm->eaction; if (dev != NULL) { - m->tcfm_ifindex = parm->ifindex; - m->net = net; if (ret != ACT_P_CREATED) dev_put(rcu_dereference_protected(m->tcfm_dev, 1)); dev_hold(dev); @@ -149,9 +143,7 @@ static int tcf_mirred_init(struct net *net, struct nlattr *nla, } if (ret == ACT_P_CREATED) { - spin_lock_bh(&mirred_list_lock); list_add(&m->tcfm_list, &mirred_list); - spin_unlock_bh(&mirred_list_lock); tcf_idr_insert(tn, *a); } @@ -247,13 +239,14 @@ static int tcf_mirred_dump(struct sk_buff *skb, struct tc_action *a, int bind, { unsigned char *b = skb_tail_pointer(skb); struct tcf_mirred *m = to_mirred(a); + struct net_device *dev = rtnl_dereference(m->tcfm_dev); struct tc_mirred opt = { .index = m->tcf_index, .action = m->tcf_action, .refcnt = m->tcf_refcnt - ref, .bindcnt = m->tcf_bindcnt - bind, .eaction = m->tcfm_eaction, - .ifindex = m->tcfm_ifindex, + .ifindex = dev ? dev->ifindex : 0, }; struct tcf_t t; @@ -294,7 +287,6 @@ static int mirred_device_event(struct notifier_block *unused, ASSERT_RTNL(); if (event == NETDEV_UNREGISTER) { - spin_lock_bh(&mirred_list_lock); list_for_each_entry(m, &mirred_list, tcfm_list) { if (rcu_access_pointer(m->tcfm_dev) == dev) { dev_put(dev); @@ -304,7 +296,6 @@ static int mirred_device_event(struct notifier_block *unused, RCU_INIT_POINTER(m->tcfm_dev, NULL); } } - spin_unlock_bh(&mirred_list_lock); } return NOTIFY_DONE; @@ -318,7 +309,7 @@ static struct net_device *tcf_mirred_get_dev(const struct tc_action *a) { struct tcf_mirred *m = to_mirred(a); - return __dev_get_by_index(m->net, m->tcfm_ifindex); + return rtnl_dereference(m->tcfm_dev); } static struct tc_action_ops act_mirred_ops = { @@ -343,16 +334,14 @@ static __net_init int mirred_init_net(struct net *net) return tc_action_net_init(tn, &act_mirred_ops); } -static void __net_exit mirred_exit_net(struct net *net) +static void __net_exit mirred_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, mirred_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, mirred_net_id); } static struct pernet_operations mirred_net_ops = { .init = mirred_init_net, - .exit = mirred_exit_net, + .exit_batch = mirred_exit_net, .id = &mirred_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_nat.c b/net/sched/act_nat.c index c365d01b99c8..98c6a4b2f523 100644 --- a/net/sched/act_nat.c +++ b/net/sched/act_nat.c @@ -310,16 +310,14 @@ static __net_init int nat_init_net(struct net *net) return tc_action_net_init(tn, &act_nat_ops); } -static void __net_exit nat_exit_net(struct net *net) +static void __net_exit nat_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, nat_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, nat_net_id); } static struct pernet_operations nat_net_ops = { .init = nat_init_net, - .exit = nat_exit_net, + .exit_batch = nat_exit_net, .id = &nat_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c index 491fe5deb09e..349beaffb29e 100644 --- a/net/sched/act_pedit.c +++ b/net/sched/act_pedit.c @@ -216,7 +216,7 @@ static int tcf_pedit_init(struct net *net, struct nlattr *nla, return ret; } -static void tcf_pedit_cleanup(struct tc_action *a, int bind) +static void tcf_pedit_cleanup(struct tc_action *a) { struct tcf_pedit *p = to_pedit(a); struct tc_pedit_key *keys = p->tcfp_keys; @@ -453,16 +453,14 @@ static __net_init int pedit_init_net(struct net *net) return tc_action_net_init(tn, &act_pedit_ops); } -static void __net_exit pedit_exit_net(struct net *net) +static void __net_exit pedit_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, pedit_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, pedit_net_id); } static struct pernet_operations pedit_net_ops = { .init = pedit_init_net, - .exit = pedit_exit_net, + .exit_batch = pedit_exit_net, .id = &pedit_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_police.c b/net/sched/act_police.c index 3bb2ebf9e9ae..95d3c9097b25 100644 --- a/net/sched/act_police.c +++ b/net/sched/act_police.c @@ -118,13 +118,13 @@ static int tcf_act_police_init(struct net *net, struct nlattr *nla, police = to_police(*a); if (parm->rate.rate) { err = -ENOMEM; - R_tab = qdisc_get_rtab(&parm->rate, tb[TCA_POLICE_RATE]); + R_tab = qdisc_get_rtab(&parm->rate, tb[TCA_POLICE_RATE], NULL); if (R_tab == NULL) goto failure; if (parm->peakrate.rate) { P_tab = qdisc_get_rtab(&parm->peakrate, - tb[TCA_POLICE_PEAKRATE]); + tb[TCA_POLICE_PEAKRATE], NULL); if (P_tab == NULL) goto failure; } @@ -334,16 +334,14 @@ static __net_init int police_init_net(struct net *net) return tc_action_net_init(tn, &act_police_ops); } -static void __net_exit police_exit_net(struct net *net) +static void __net_exit police_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, police_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, police_net_id); } static struct pernet_operations police_net_ops = { .init = police_init_net, - .exit = police_exit_net, + .exit_batch = police_exit_net, .id = &police_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_sample.c b/net/sched/act_sample.c index 9438969290a6..1ba0df238756 100644 --- a/net/sched/act_sample.c +++ b/net/sched/act_sample.c @@ -96,7 +96,7 @@ static int tcf_sample_init(struct net *net, struct nlattr *nla, return ret; } -static void tcf_sample_cleanup(struct tc_action *a, int bind) +static void tcf_sample_cleanup(struct tc_action *a) { struct tcf_sample *s = to_sample(a); struct psample_group *psample_group; @@ -236,16 +236,14 @@ static __net_init int sample_init_net(struct net *net) return tc_action_net_init(tn, &act_sample_ops); } -static void __net_exit sample_exit_net(struct net *net) +static void __net_exit sample_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, sample_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, sample_net_id); } static struct pernet_operations sample_net_ops = { .init = sample_init_net, - .exit = sample_exit_net, + .exit_batch = sample_exit_net, .id = &sample_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_simple.c b/net/sched/act_simple.c index e7b57e5071a3..425eac11f6da 100644 --- a/net/sched/act_simple.c +++ b/net/sched/act_simple.c @@ -47,7 +47,7 @@ static int tcf_simp(struct sk_buff *skb, const struct tc_action *a, return d->tcf_action; } -static void tcf_simp_release(struct tc_action *a, int bind) +static void tcf_simp_release(struct tc_action *a) { struct tcf_defact *d = to_defact(a); kfree(d->tcfd_defdata); @@ -204,16 +204,14 @@ static __net_init int simp_init_net(struct net *net) return tc_action_net_init(tn, &act_simp_ops); } -static void __net_exit simp_exit_net(struct net *net) +static void __net_exit simp_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, simp_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, simp_net_id); } static struct pernet_operations simp_net_ops = { .init = simp_init_net, - .exit = simp_exit_net, + .exit_batch = simp_exit_net, .id = &simp_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_skbedit.c b/net/sched/act_skbedit.c index 59949d61f20d..5a3f691bb545 100644 --- a/net/sched/act_skbedit.c +++ b/net/sched/act_skbedit.c @@ -241,16 +241,14 @@ static __net_init int skbedit_init_net(struct net *net) return tc_action_net_init(tn, &act_skbedit_ops); } -static void __net_exit skbedit_exit_net(struct net *net) +static void __net_exit skbedit_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, skbedit_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, skbedit_net_id); } static struct pernet_operations skbedit_net_ops = { .init = skbedit_init_net, - .exit = skbedit_exit_net, + .exit_batch = skbedit_exit_net, .id = &skbedit_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_skbmod.c b/net/sched/act_skbmod.c index b642ad3d39dd..fa975262dbac 100644 --- a/net/sched/act_skbmod.c +++ b/net/sched/act_skbmod.c @@ -184,7 +184,7 @@ static int tcf_skbmod_init(struct net *net, struct nlattr *nla, return ret; } -static void tcf_skbmod_cleanup(struct tc_action *a, int bind) +static void tcf_skbmod_cleanup(struct tc_action *a) { struct tcf_skbmod *d = to_skbmod(a); struct tcf_skbmod_params *p; @@ -266,16 +266,14 @@ static __net_init int skbmod_init_net(struct net *net) return tc_action_net_init(tn, &act_skbmod_ops); } -static void __net_exit skbmod_exit_net(struct net *net) +static void __net_exit skbmod_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, skbmod_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, skbmod_net_id); } static struct pernet_operations skbmod_net_ops = { .init = skbmod_init_net, - .exit = skbmod_exit_net, + .exit_batch = skbmod_exit_net, .id = &skbmod_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_tunnel_key.c b/net/sched/act_tunnel_key.c index 30c96274c638..0e23aac09ad6 100644 --- a/net/sched/act_tunnel_key.c +++ b/net/sched/act_tunnel_key.c @@ -201,7 +201,7 @@ err_out: return ret; } -static void tunnel_key_release(struct tc_action *a, int bind) +static void tunnel_key_release(struct tc_action *a) { struct tcf_tunnel_key *t = to_tunnel_key(a); struct tcf_tunnel_key_params *params; @@ -325,16 +325,14 @@ static __net_init int tunnel_key_init_net(struct net *net) return tc_action_net_init(tn, &act_tunnel_key_ops); } -static void __net_exit tunnel_key_exit_net(struct net *net) +static void __net_exit tunnel_key_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, tunnel_key_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, tunnel_key_net_id); } static struct pernet_operations tunnel_key_net_ops = { .init = tunnel_key_init_net, - .exit = tunnel_key_exit_net, + .exit_batch = tunnel_key_exit_net, .id = &tunnel_key_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/act_vlan.c b/net/sched/act_vlan.c index 97f717a13ad5..e1a1b3f3983a 100644 --- a/net/sched/act_vlan.c +++ b/net/sched/act_vlan.c @@ -219,7 +219,7 @@ static int tcf_vlan_init(struct net *net, struct nlattr *nla, return ret; } -static void tcf_vlan_cleanup(struct tc_action *a, int bind) +static void tcf_vlan_cleanup(struct tc_action *a) { struct tcf_vlan *v = to_vlan(a); struct tcf_vlan_params *p; @@ -301,16 +301,14 @@ static __net_init int vlan_init_net(struct net *net) return tc_action_net_init(tn, &act_vlan_ops); } -static void __net_exit vlan_exit_net(struct net *net) +static void __net_exit vlan_exit_net(struct list_head *net_list) { - struct tc_action_net *tn = net_generic(net, vlan_net_id); - - tc_action_net_exit(tn); + tc_action_net_exit(net_list, vlan_net_id); } static struct pernet_operations vlan_net_ops = { .init = vlan_init_net, - .exit = vlan_exit_net, + .exit_batch = vlan_exit_net, .id = &vlan_net_id, .size = sizeof(struct tc_action_net), }; diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index b9d63d2246e6..6708b6953bfa 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -217,8 +217,12 @@ static void tcf_chain_flush(struct tcf_chain *chain) static void tcf_chain_destroy(struct tcf_chain *chain) { + struct tcf_block *block = chain->block; + list_del(&chain->list); kfree(chain); + if (list_empty(&block->chain_list)) + kfree(block); } static void tcf_chain_hold(struct tcf_chain *chain) @@ -277,20 +281,24 @@ static void tcf_block_offload_unbind(struct tcf_block *block, struct Qdisc *q, } int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q, - struct tcf_block_ext_info *ei) + struct tcf_block_ext_info *ei, + struct netlink_ext_ack *extack) { struct tcf_block *block = kzalloc(sizeof(*block), GFP_KERNEL); struct tcf_chain *chain; int err; - if (!block) + if (!block) { + NL_SET_ERR_MSG(extack, "Memory allocation for block failed"); return -ENOMEM; + } INIT_LIST_HEAD(&block->chain_list); INIT_LIST_HEAD(&block->cb_list); /* Create chain 0 by default, it has to be always present. */ chain = tcf_chain_create(block, 0); if (!chain) { + NL_SET_ERR_MSG(extack, "Failed to create new tcf chain"); err = -ENOMEM; goto err_chain_create; } @@ -317,7 +325,8 @@ static void tcf_chain_head_change_dflt(struct tcf_proto *tp_head, void *priv) } int tcf_block_get(struct tcf_block **p_block, - struct tcf_proto __rcu **p_filter_chain, struct Qdisc *q) + struct tcf_proto __rcu **p_filter_chain, struct Qdisc *q, + struct netlink_ext_ack *extack) { struct tcf_block_ext_info ei = { .chain_head_change = tcf_chain_head_change_dflt, @@ -325,53 +334,38 @@ int tcf_block_get(struct tcf_block **p_block, }; WARN_ON(!p_filter_chain); - return tcf_block_get_ext(p_block, q, &ei); + return tcf_block_get_ext(p_block, q, &ei, extack); } EXPORT_SYMBOL(tcf_block_get); -static void tcf_block_put_final(struct work_struct *work) -{ - struct tcf_block *block = container_of(work, struct tcf_block, work); - struct tcf_chain *chain, *tmp; - - rtnl_lock(); - - /* At this point, all the chains should have refcnt == 1. */ - list_for_each_entry_safe(chain, tmp, &block->chain_list, list) - tcf_chain_put(chain); - rtnl_unlock(); - kfree(block); -} - /* XXX: Standalone actions are not allowed to jump to any chain, and bound * actions should be all removed after flushing. */ void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q, struct tcf_block_ext_info *ei) { - struct tcf_chain *chain; + struct tcf_chain *chain, *tmp; - if (!block) - return; - /* Hold a refcnt for all chains, except 0, so that they don't disappear + /* Hold a refcnt for all chains, so that they don't disappear * while we are iterating. */ + if (!block) + return; list_for_each_entry(chain, &block->chain_list, list) - if (chain->index) - tcf_chain_hold(chain); + tcf_chain_hold(chain); list_for_each_entry(chain, &block->chain_list, list) tcf_chain_flush(chain); tcf_block_offload_unbind(block, q, ei); - INIT_WORK(&block->work, tcf_block_put_final); - /* Wait for existing RCU callbacks to cool down, make sure their works - * have been queued before this. We can not flush pending works here - * because we are holding the RTNL lock. - */ - rcu_barrier(); - tcf_queue_work(&block->work); + /* At this point, all the chains should have refcnt >= 1. */ + list_for_each_entry_safe(chain, tmp, &block->chain_list, list) + tcf_chain_put(chain); + + /* Finally, put chain 0 and allow block to be freed. */ + chain = list_first_entry(&block->chain_list, struct tcf_chain, list); + tcf_chain_put(chain); } EXPORT_SYMBOL(tcf_block_put_ext); @@ -806,7 +800,7 @@ replay: } /* And the last stroke */ - block = cops->tcf_block(q, cl); + block = cops->tcf_block(q, cl, extack); if (!block) { err = -EINVAL; goto errout; @@ -1053,7 +1047,7 @@ static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb) if (cl == 0) goto out; } - block = cops->tcf_block(q, cl); + block = cops->tcf_block(q, cl, NULL); if (!block) goto out; diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c index 543a3e875d05..6132a7317efa 100644 --- a/net/sched/cls_flower.c +++ b/net/sched/cls_flower.c @@ -166,6 +166,7 @@ static int fl_classify(struct sk_buff *skb, const struct tcf_proto *tp, * so do it rather here. */ skb_key.basic.n_proto = skb->protocol; + skb_flow_dissect_tunnel_info(skb, &head->dissector, &skb_key); skb_flow_dissect(skb, &head->dissector, &skb_key, 0); fl_set_masked_key(&skb_mkey, &skb_key, &head->mask); diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 0f1eab99ff4e..8a04c36e579f 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -393,13 +393,16 @@ static __u8 __detect_linklayer(struct tc_ratespec *r, __u32 *rtab) static struct qdisc_rate_table *qdisc_rtab_list; struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, - struct nlattr *tab) + struct nlattr *tab, + struct netlink_ext_ack *extack) { struct qdisc_rate_table *rtab; if (tab == NULL || r->rate == 0 || r->cell_log == 0 || - nla_len(tab) != TC_RTAB_SIZE) + nla_len(tab) != TC_RTAB_SIZE) { + NL_SET_ERR_MSG(extack, "Invalid rate table parameters for searching"); return NULL; + } for (rtab = qdisc_rtab_list; rtab; rtab = rtab->next) { if (!memcmp(&rtab->rate, r, sizeof(struct tc_ratespec)) && @@ -418,6 +421,8 @@ struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, r->linklayer = __detect_linklayer(r, rtab->data); rtab->next = qdisc_rtab_list; qdisc_rtab_list = rtab; + } else { + NL_SET_ERR_MSG(extack, "Failed to allocate new qdisc rate table"); } return rtab; } @@ -449,7 +454,8 @@ static const struct nla_policy stab_policy[TCA_STAB_MAX + 1] = { [TCA_STAB_DATA] = { .type = NLA_BINARY }, }; -static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt) +static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt, + struct netlink_ext_ack *extack) { struct nlattr *tb[TCA_STAB_MAX + 1]; struct qdisc_size_table *stab; @@ -458,23 +464,29 @@ static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt) u16 *tab = NULL; int err; - err = nla_parse_nested(tb, TCA_STAB_MAX, opt, stab_policy, NULL); + err = nla_parse_nested(tb, TCA_STAB_MAX, opt, stab_policy, extack); if (err < 0) return ERR_PTR(err); - if (!tb[TCA_STAB_BASE]) + if (!tb[TCA_STAB_BASE]) { + NL_SET_ERR_MSG(extack, "Size table base attribute is missing"); return ERR_PTR(-EINVAL); + } s = nla_data(tb[TCA_STAB_BASE]); if (s->tsize > 0) { - if (!tb[TCA_STAB_DATA]) + if (!tb[TCA_STAB_DATA]) { + NL_SET_ERR_MSG(extack, "Size table data attribute is missing"); return ERR_PTR(-EINVAL); + } tab = nla_data(tb[TCA_STAB_DATA]); tsize = nla_len(tb[TCA_STAB_DATA]) / sizeof(u16); } - if (tsize != s->tsize || (!tab && tsize > 0)) + if (tsize != s->tsize || (!tab && tsize > 0)) { + NL_SET_ERR_MSG(extack, "Invalid size of size table"); return ERR_PTR(-EINVAL); + } list_for_each_entry(stab, &qdisc_stab_list, list) { if (memcmp(&stab->szopts, s, sizeof(*s))) @@ -669,7 +681,7 @@ int qdisc_class_hash_init(struct Qdisc_class_hash *clhash) unsigned int size = 4; clhash->hash = qdisc_class_hash_alloc(size); - if (clhash->hash == NULL) + if (!clhash->hash) return -ENOMEM; clhash->hashsize = size; clhash->hashmask = size - 1; @@ -795,11 +807,11 @@ static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid, tcm->tcm_info = refcount_read(&q->refcnt); if (nla_put_string(skb, TCA_KIND, q->ops->id)) goto nla_put_failure; - if (nla_put_u8(skb, TCA_HW_OFFLOAD, !!(q->flags & TCQ_F_OFFLOADED))) - goto nla_put_failure; if (q->ops->dump && q->ops->dump(q, skb) < 0) goto nla_put_failure; - qlen = q->q.qlen; + if (nla_put_u8(skb, TCA_HW_OFFLOAD, !!(q->flags & TCQ_F_OFFLOADED))) + goto nla_put_failure; + qlen = qdisc_qlen_sum(q); stab = rtnl_dereference(q->stab); if (stab && qdisc_dump_stab(skb, stab) < 0) @@ -898,7 +910,8 @@ static void notify_and_destroy(struct net *net, struct sk_buff *skb, static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, struct sk_buff *skb, struct nlmsghdr *n, u32 classid, - struct Qdisc *new, struct Qdisc *old) + struct Qdisc *new, struct Qdisc *old, + struct netlink_ext_ack *extack) { struct Qdisc *q = old; struct net *net = dev_net(dev); @@ -913,8 +926,10 @@ static int qdisc_graft(struct net_device *dev, struct Qdisc *parent, (new && new->flags & TCQ_F_INGRESS)) { num_q = 1; ingress = 1; - if (!dev_ingress_queue(dev)) + if (!dev_ingress_queue(dev)) { + NL_SET_ERR_MSG(extack, "Device does not have an ingress queue"); return -ENOENT; + } } if (dev->flags & IFF_UP) @@ -956,14 +971,22 @@ skip: } else { const struct Qdisc_class_ops *cops = parent->ops->cl_ops; + /* Only support running class lockless if parent is lockless */ + if (new && (new->flags & TCQ_F_NOLOCK) && + parent && !(parent->flags & TCQ_F_NOLOCK)) + new->flags &= ~TCQ_F_NOLOCK; + err = -EOPNOTSUPP; if (cops && cops->graft) { unsigned long cl = cops->find(parent, classid); - if (cl) - err = cops->graft(parent, cl, new, &old); - else + if (cl) { + err = cops->graft(parent, cl, new, &old, + extack); + } else { + NL_SET_ERR_MSG(extack, "Specified class not found"); err = -ENOENT; + } } if (!err) notify_and_destroy(net, skb, n, classid, old, new); @@ -984,7 +1007,8 @@ static struct lock_class_key qdisc_rx_lock; static struct Qdisc *qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue, struct Qdisc *p, u32 parent, u32 handle, - struct nlattr **tca, int *errp) + struct nlattr **tca, int *errp, + struct netlink_ext_ack *extack) { int err; struct nlattr *kind = tca[TCA_KIND]; @@ -1022,10 +1046,12 @@ static struct Qdisc *qdisc_create(struct net_device *dev, #endif err = -ENOENT; - if (ops == NULL) + if (!ops) { + NL_SET_ERR_MSG(extack, "Specified qdisc not found"); goto err_out; + } - sch = qdisc_alloc(dev_queue, ops); + sch = qdisc_alloc(dev_queue, ops, extack); if (IS_ERR(sch)) { err = PTR_ERR(sch); goto err_out2; @@ -1062,54 +1088,64 @@ static struct Qdisc *qdisc_create(struct net_device *dev, netdev_info(dev, "Caught tx_queue_len zero misconfig\n"); } - if (!ops->init || (err = ops->init(sch, tca[TCA_OPTIONS])) == 0) { - if (qdisc_is_percpu_stats(sch)) { - sch->cpu_bstats = - netdev_alloc_pcpu_stats(struct gnet_stats_basic_cpu); - if (!sch->cpu_bstats) - goto err_out4; + if (ops->init) { + err = ops->init(sch, tca[TCA_OPTIONS], extack); + if (err != 0) + goto err_out5; + } + + if (qdisc_is_percpu_stats(sch)) { + sch->cpu_bstats = + netdev_alloc_pcpu_stats(struct gnet_stats_basic_cpu); + if (!sch->cpu_bstats) + goto err_out4; - sch->cpu_qstats = alloc_percpu(struct gnet_stats_queue); - if (!sch->cpu_qstats) - goto err_out4; + sch->cpu_qstats = alloc_percpu(struct gnet_stats_queue); + if (!sch->cpu_qstats) + goto err_out4; + } + + if (tca[TCA_STAB]) { + stab = qdisc_get_stab(tca[TCA_STAB], extack); + if (IS_ERR(stab)) { + err = PTR_ERR(stab); + goto err_out4; } + rcu_assign_pointer(sch->stab, stab); + } + if (tca[TCA_RATE]) { + seqcount_t *running; - if (tca[TCA_STAB]) { - stab = qdisc_get_stab(tca[TCA_STAB]); - if (IS_ERR(stab)) { - err = PTR_ERR(stab); - goto err_out4; - } - rcu_assign_pointer(sch->stab, stab); + err = -EOPNOTSUPP; + if (sch->flags & TCQ_F_MQROOT) { + NL_SET_ERR_MSG(extack, "Cannot attach rate estimator to a multi-queue root qdisc"); + goto err_out4; } - if (tca[TCA_RATE]) { - seqcount_t *running; - - err = -EOPNOTSUPP; - if (sch->flags & TCQ_F_MQROOT) - goto err_out4; - - if ((sch->parent != TC_H_ROOT) && - !(sch->flags & TCQ_F_INGRESS) && - (!p || !(p->flags & TCQ_F_MQROOT))) - running = qdisc_root_sleeping_running(sch); - else - running = &sch->running; - - err = gen_new_estimator(&sch->bstats, - sch->cpu_bstats, - &sch->rate_est, - NULL, - running, - tca[TCA_RATE]); - if (err) - goto err_out4; + + if (sch->parent != TC_H_ROOT && + !(sch->flags & TCQ_F_INGRESS) && + (!p || !(p->flags & TCQ_F_MQROOT))) + running = qdisc_root_sleeping_running(sch); + else + running = &sch->running; + + err = gen_new_estimator(&sch->bstats, + sch->cpu_bstats, + &sch->rate_est, + NULL, + running, + tca[TCA_RATE]); + if (err) { + NL_SET_ERR_MSG(extack, "Failed to generate new estimator"); + goto err_out4; } + } - qdisc_hash_add(sch, false); + qdisc_hash_add(sch, false); - return sch; - } + return sch; + +err_out5: /* ops->init() failed, we call ->destroy() like qdisc_create_dflt() */ if (ops->destroy) ops->destroy(sch); @@ -1135,21 +1171,24 @@ err_out4: goto err_out3; } -static int qdisc_change(struct Qdisc *sch, struct nlattr **tca) +static int qdisc_change(struct Qdisc *sch, struct nlattr **tca, + struct netlink_ext_ack *extack) { struct qdisc_size_table *ostab, *stab = NULL; int err = 0; if (tca[TCA_OPTIONS]) { - if (sch->ops->change == NULL) + if (!sch->ops->change) { + NL_SET_ERR_MSG(extack, "Change operation not supported by specified qdisc"); return -EINVAL; - err = sch->ops->change(sch, tca[TCA_OPTIONS]); + } + err = sch->ops->change(sch, tca[TCA_OPTIONS], extack); if (err) return err; } if (tca[TCA_STAB]) { - stab = qdisc_get_stab(tca[TCA_STAB]); + stab = qdisc_get_stab(tca[TCA_STAB], extack); if (IS_ERR(stab)) return PTR_ERR(stab); } @@ -1247,8 +1286,10 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, if (clid != TC_H_ROOT) { if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) { p = qdisc_lookup(dev, TC_H_MAJ(clid)); - if (!p) + if (!p) { + NL_SET_ERR_MSG(extack, "Failed to find qdisc with specified classid"); return -ENOENT; + } q = qdisc_leaf(p, clid); } else if (dev_ingress_queue(dev)) { q = dev_ingress_queue(dev)->qdisc_sleeping; @@ -1256,26 +1297,38 @@ static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, } else { q = dev->qdisc; } - if (!q) + if (!q) { + NL_SET_ERR_MSG(extack, "Cannot find specified qdisc on specified device"); return -ENOENT; + } - if (tcm->tcm_handle && q->handle != tcm->tcm_handle) + if (tcm->tcm_handle && q->handle != tcm->tcm_handle) { + NL_SET_ERR_MSG(extack, "Invalid handle"); return -EINVAL; + } } else { q = qdisc_lookup(dev, tcm->tcm_handle); - if (!q) + if (!q) { + NL_SET_ERR_MSG(extack, "Failed to find qdisc with specified handle"); return -ENOENT; + } } - if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) + if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) { + NL_SET_ERR_MSG(extack, "Invalid qdisc name"); return -EINVAL; + } if (n->nlmsg_type == RTM_DELQDISC) { - if (!clid) + if (!clid) { + NL_SET_ERR_MSG(extack, "Classid cannot be zero"); return -EINVAL; - if (q->handle == 0) + } + if (q->handle == 0) { + NL_SET_ERR_MSG(extack, "Cannot delete qdisc with handle of zero"); return -ENOENT; - err = qdisc_graft(dev, p, skb, n, clid, NULL, q); + } + err = qdisc_graft(dev, p, skb, n, clid, NULL, q, extack); if (err != 0) return err; } else { @@ -1321,8 +1374,10 @@ replay: if (clid != TC_H_ROOT) { if (clid != TC_H_INGRESS) { p = qdisc_lookup(dev, TC_H_MAJ(clid)); - if (!p) + if (!p) { + NL_SET_ERR_MSG(extack, "Failed to find specified qdisc"); return -ENOENT; + } q = qdisc_leaf(p, clid); } else if (dev_ingress_queue_create(dev)) { q = dev_ingress_queue(dev)->qdisc_sleeping; @@ -1337,20 +1392,31 @@ replay: if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) { if (tcm->tcm_handle) { - if (q && !(n->nlmsg_flags & NLM_F_REPLACE)) + if (q && !(n->nlmsg_flags & NLM_F_REPLACE)) { + NL_SET_ERR_MSG(extack, "NLM_F_REPLACE needed to override"); return -EEXIST; - if (TC_H_MIN(tcm->tcm_handle)) + } + if (TC_H_MIN(tcm->tcm_handle)) { + NL_SET_ERR_MSG(extack, "Invalid minor handle"); return -EINVAL; + } q = qdisc_lookup(dev, tcm->tcm_handle); if (!q) goto create_n_graft; - if (n->nlmsg_flags & NLM_F_EXCL) + if (n->nlmsg_flags & NLM_F_EXCL) { + NL_SET_ERR_MSG(extack, "Exclusivity flag on, cannot override"); return -EEXIST; - if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) + } + if (tca[TCA_KIND] && + nla_strcmp(tca[TCA_KIND], q->ops->id)) { + NL_SET_ERR_MSG(extack, "Invalid qdisc name"); return -EINVAL; + } if (q == p || - (p && check_loop(q, p, 0))) + (p && check_loop(q, p, 0))) { + NL_SET_ERR_MSG(extack, "Qdisc parent/child loop detected"); return -ELOOP; + } qdisc_refcount_inc(q); goto graft; } else { @@ -1385,33 +1451,45 @@ replay: } } } else { - if (!tcm->tcm_handle) + if (!tcm->tcm_handle) { + NL_SET_ERR_MSG(extack, "Handle cannot be zero"); return -EINVAL; + } q = qdisc_lookup(dev, tcm->tcm_handle); } /* Change qdisc parameters */ - if (q == NULL) + if (!q) { + NL_SET_ERR_MSG(extack, "Specified qdisc not found"); return -ENOENT; - if (n->nlmsg_flags & NLM_F_EXCL) + } + if (n->nlmsg_flags & NLM_F_EXCL) { + NL_SET_ERR_MSG(extack, "Exclusivity flag on, cannot modify"); return -EEXIST; - if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) + } + if (tca[TCA_KIND] && nla_strcmp(tca[TCA_KIND], q->ops->id)) { + NL_SET_ERR_MSG(extack, "Invalid qdisc name"); return -EINVAL; - err = qdisc_change(q, tca); + } + err = qdisc_change(q, tca, extack); if (err == 0) qdisc_notify(net, skb, n, clid, NULL, q); return err; create_n_graft: - if (!(n->nlmsg_flags & NLM_F_CREATE)) + if (!(n->nlmsg_flags & NLM_F_CREATE)) { + NL_SET_ERR_MSG(extack, "Qdisc not found. To create specify NLM_F_CREATE flag"); return -ENOENT; + } if (clid == TC_H_INGRESS) { - if (dev_ingress_queue(dev)) + if (dev_ingress_queue(dev)) { q = qdisc_create(dev, dev_ingress_queue(dev), p, tcm->tcm_parent, tcm->tcm_parent, - tca, &err); - else + tca, &err, extack); + } else { + NL_SET_ERR_MSG(extack, "Cannot find ingress queue for specified device"); err = -ENOENT; + } } else { struct netdev_queue *dev_queue; @@ -1424,7 +1502,7 @@ create_n_graft: q = qdisc_create(dev, dev_queue, p, tcm->tcm_parent, tcm->tcm_handle, - tca, &err); + tca, &err, extack); } if (q == NULL) { if (err == -EAGAIN) @@ -1433,7 +1511,7 @@ create_n_graft: } graft: - err = qdisc_graft(dev, p, skb, n, clid, q, NULL); + err = qdisc_graft(dev, p, skb, n, clid, q, NULL, extack); if (err) { if (q) qdisc_destroy(q); @@ -1685,7 +1763,7 @@ static void tc_bind_tclass(struct Qdisc *q, u32 portid, u32 clid, cl = cops->find(q, portid); if (!cl) return; - block = cops->tcf_block(q, cl); + block = cops->tcf_block(q, cl, NULL); if (!block) return; list_for_each_entry(chain, &block->chain_list, list) { @@ -1832,7 +1910,7 @@ static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, new_cl = cl; err = -EOPNOTSUPP; if (cops->change) - err = cops->change(q, clid, portid, tca, &new_cl); + err = cops->change(q, clid, portid, tca, &new_cl, extack); if (err == 0) { tclass_notify(net, skb, n, q, new_cl, RTM_NEWTCLASS); /* We just create a new class, need to do reverse binding. */ diff --git a/net/sched/sch_atm.c b/net/sched/sch_atm.c index 2dbd249c0b2f..cd49afca9617 100644 --- a/net/sched/sch_atm.c +++ b/net/sched/sch_atm.c @@ -82,7 +82,8 @@ static inline struct atm_flow_data *lookup_flow(struct Qdisc *sch, u32 classid) } static int atm_tc_graft(struct Qdisc *sch, unsigned long arg, - struct Qdisc *new, struct Qdisc **old) + struct Qdisc *new, struct Qdisc **old, + struct netlink_ext_ack *extack) { struct atm_qdisc_data *p = qdisc_priv(sch); struct atm_flow_data *flow = (struct atm_flow_data *)arg; @@ -191,7 +192,8 @@ static const struct nla_policy atm_policy[TCA_ATM_MAX + 1] = { }; static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent, - struct nlattr **tca, unsigned long *arg) + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) { struct atm_qdisc_data *p = qdisc_priv(sch); struct atm_flow_data *flow = (struct atm_flow_data *)*arg; @@ -281,13 +283,15 @@ static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent, goto err_out; } - error = tcf_block_get(&flow->block, &flow->filter_list, sch); + error = tcf_block_get(&flow->block, &flow->filter_list, sch, + extack); if (error) { kfree(flow); goto err_out; } - flow->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid); + flow->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid, + extack); if (!flow->q) flow->q = &noop_qdisc; pr_debug("atm_tc_change: qdisc %p\n", flow->q); @@ -356,7 +360,8 @@ static void atm_tc_walk(struct Qdisc *sch, struct qdisc_walker *walker) } } -static struct tcf_block *atm_tc_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *atm_tc_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct atm_qdisc_data *p = qdisc_priv(sch); struct atm_flow_data *flow = (struct atm_flow_data *)cl; @@ -531,7 +536,8 @@ static struct sk_buff *atm_tc_peek(struct Qdisc *sch) return p->link.q->ops->peek(p->link.q); } -static int atm_tc_init(struct Qdisc *sch, struct nlattr *opt) +static int atm_tc_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct atm_qdisc_data *p = qdisc_priv(sch); int err; @@ -541,12 +547,13 @@ static int atm_tc_init(struct Qdisc *sch, struct nlattr *opt) INIT_LIST_HEAD(&p->link.list); list_add(&p->link.list, &p->flows); p->link.q = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, sch->handle); + &pfifo_qdisc_ops, sch->handle, extack); if (!p->link.q) p->link.q = &noop_qdisc; pr_debug("atm_tc_init: link (%p) qdisc %p\n", &p->link, p->link.q); - err = tcf_block_get(&p->link.block, &p->link.filter_list, sch); + err = tcf_block_get(&p->link.block, &p->link.filter_list, sch, + extack); if (err) return err; diff --git a/net/sched/sch_cbq.c b/net/sched/sch_cbq.c index 525eb3a6d625..f42025d53cfe 100644 --- a/net/sched/sch_cbq.c +++ b/net/sched/sch_cbq.c @@ -1132,7 +1132,8 @@ static const struct nla_policy cbq_policy[TCA_CBQ_MAX + 1] = { [TCA_CBQ_POLICE] = { .len = sizeof(struct tc_cbq_police) }, }; -static int cbq_init(struct Qdisc *sch, struct nlattr *opt) +static int cbq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct cbq_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_CBQ_MAX + 1]; @@ -1143,22 +1144,27 @@ static int cbq_init(struct Qdisc *sch, struct nlattr *opt) hrtimer_init(&q->delay_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED); q->delay_timer.function = cbq_undelay; - if (!opt) + if (!opt) { + NL_SET_ERR_MSG(extack, "CBQ options are required for this operation"); return -EINVAL; + } - err = nla_parse_nested(tb, TCA_CBQ_MAX, opt, cbq_policy, NULL); + err = nla_parse_nested(tb, TCA_CBQ_MAX, opt, cbq_policy, extack); if (err < 0) return err; - if (tb[TCA_CBQ_RTAB] == NULL || tb[TCA_CBQ_RATE] == NULL) + if (!tb[TCA_CBQ_RTAB] || !tb[TCA_CBQ_RATE]) { + NL_SET_ERR_MSG(extack, "Rate specification missing or incomplete"); return -EINVAL; + } r = nla_data(tb[TCA_CBQ_RATE]); - if ((q->link.R_tab = qdisc_get_rtab(r, tb[TCA_CBQ_RTAB])) == NULL) + q->link.R_tab = qdisc_get_rtab(r, tb[TCA_CBQ_RTAB], extack); + if (!q->link.R_tab) return -EINVAL; - err = tcf_block_get(&q->link.block, &q->link.filter_list, sch); + err = tcf_block_get(&q->link.block, &q->link.filter_list, sch, extack); if (err) goto put_rtab; @@ -1170,7 +1176,7 @@ static int cbq_init(struct Qdisc *sch, struct nlattr *opt) q->link.common.classid = sch->handle; q->link.qdisc = sch; q->link.q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - sch->handle); + sch->handle, NULL); if (!q->link.q) q->link.q = &noop_qdisc; else @@ -1369,13 +1375,13 @@ cbq_dump_class_stats(struct Qdisc *sch, unsigned long arg, } static int cbq_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct cbq_class *cl = (struct cbq_class *)arg; if (new == NULL) { - new = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, cl->common.classid); + new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + cl->common.classid, extack); if (new == NULL) return -ENOBUFS; } @@ -1450,7 +1456,7 @@ static void cbq_destroy(struct Qdisc *sch) static int cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **tca, - unsigned long *arg) + unsigned long *arg, struct netlink_ext_ack *extack) { int err; struct cbq_sched_data *q = qdisc_priv(sch); @@ -1460,29 +1466,37 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t struct cbq_class *parent; struct qdisc_rate_table *rtab = NULL; - if (opt == NULL) + if (!opt) { + NL_SET_ERR_MSG(extack, "Mandatory qdisc options missing"); return -EINVAL; + } - err = nla_parse_nested(tb, TCA_CBQ_MAX, opt, cbq_policy, NULL); + err = nla_parse_nested(tb, TCA_CBQ_MAX, opt, cbq_policy, extack); if (err < 0) return err; - if (tb[TCA_CBQ_OVL_STRATEGY] || tb[TCA_CBQ_POLICE]) + if (tb[TCA_CBQ_OVL_STRATEGY] || tb[TCA_CBQ_POLICE]) { + NL_SET_ERR_MSG(extack, "Neither overlimit strategy nor policing attributes can be used for changing class params"); return -EOPNOTSUPP; + } if (cl) { /* Check parent */ if (parentid) { if (cl->tparent && - cl->tparent->common.classid != parentid) + cl->tparent->common.classid != parentid) { + NL_SET_ERR_MSG(extack, "Invalid parent id"); return -EINVAL; - if (!cl->tparent && parentid != TC_H_ROOT) + } + if (!cl->tparent && parentid != TC_H_ROOT) { + NL_SET_ERR_MSG(extack, "Parent must be root"); return -EINVAL; + } } if (tb[TCA_CBQ_RATE]) { rtab = qdisc_get_rtab(nla_data(tb[TCA_CBQ_RATE]), - tb[TCA_CBQ_RTAB]); + tb[TCA_CBQ_RTAB], extack); if (rtab == NULL) return -EINVAL; } @@ -1494,6 +1508,7 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t qdisc_root_sleeping_running(sch), tca[TCA_RATE]); if (err) { + NL_SET_ERR_MSG(extack, "Failed to replace specified rate estimator"); qdisc_put_rtab(rtab); return err; } @@ -1532,19 +1547,23 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t if (parentid == TC_H_ROOT) return -EINVAL; - if (tb[TCA_CBQ_WRROPT] == NULL || tb[TCA_CBQ_RATE] == NULL || - tb[TCA_CBQ_LSSOPT] == NULL) + if (!tb[TCA_CBQ_WRROPT] || !tb[TCA_CBQ_RATE] || !tb[TCA_CBQ_LSSOPT]) { + NL_SET_ERR_MSG(extack, "One of the following attributes MUST be specified: WRR, rate or link sharing"); return -EINVAL; + } - rtab = qdisc_get_rtab(nla_data(tb[TCA_CBQ_RATE]), tb[TCA_CBQ_RTAB]); + rtab = qdisc_get_rtab(nla_data(tb[TCA_CBQ_RATE]), tb[TCA_CBQ_RTAB], + extack); if (rtab == NULL) return -EINVAL; if (classid) { err = -EINVAL; if (TC_H_MAJ(classid ^ sch->handle) || - cbq_class_lookup(q, classid)) + cbq_class_lookup(q, classid)) { + NL_SET_ERR_MSG(extack, "Specified class not found"); goto failure; + } } else { int i; classid = TC_H_MAKE(sch->handle, 0x8000); @@ -1556,8 +1575,10 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t break; } err = -ENOSR; - if (i >= 0x8000) + if (i >= 0x8000) { + NL_SET_ERR_MSG(extack, "Unable to generate classid"); goto failure; + } classid = classid|q->hgenerator; } @@ -1565,8 +1586,10 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t if (parentid) { parent = cbq_class_lookup(q, parentid); err = -EINVAL; - if (parent == NULL) + if (!parent) { + NL_SET_ERR_MSG(extack, "Failed to find parentid"); goto failure; + } } err = -ENOBUFS; @@ -1574,7 +1597,7 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t if (cl == NULL) goto failure; - err = tcf_block_get(&cl->block, &cl->filter_list, sch); + err = tcf_block_get(&cl->block, &cl->filter_list, sch, extack); if (err) { kfree(cl); return err; @@ -1586,6 +1609,7 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t qdisc_root_sleeping_running(sch), tca[TCA_RATE]); if (err) { + NL_SET_ERR_MSG(extack, "Couldn't create new estimator"); tcf_block_put(cl->block); kfree(cl); goto failure; @@ -1594,7 +1618,8 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t cl->R_tab = rtab; rtab = NULL; - cl->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid); + cl->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, classid, + NULL); if (!cl->q) cl->q = &noop_qdisc; else @@ -1678,7 +1703,8 @@ static int cbq_delete(struct Qdisc *sch, unsigned long arg) return 0; } -static struct tcf_block *cbq_tcf_block(struct Qdisc *sch, unsigned long arg) +static struct tcf_block *cbq_tcf_block(struct Qdisc *sch, unsigned long arg, + struct netlink_ext_ack *extack) { struct cbq_sched_data *q = qdisc_priv(sch); struct cbq_class *cl = (struct cbq_class *)arg; diff --git a/net/sched/sch_cbs.c b/net/sched/sch_cbs.c index 7a72980c1509..cdd96b9a27bc 100644 --- a/net/sched/sch_cbs.c +++ b/net/sched/sch_cbs.c @@ -219,14 +219,17 @@ static void cbs_disable_offload(struct net_device *dev, } static int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q, - const struct tc_cbs_qopt *opt) + const struct tc_cbs_qopt *opt, + struct netlink_ext_ack *extack) { const struct net_device_ops *ops = dev->netdev_ops; struct tc_cbs_qopt_offload cbs = { }; int err; - if (!ops->ndo_setup_tc) + if (!ops->ndo_setup_tc) { + NL_SET_ERR_MSG(extack, "Specified device does not support cbs offload"); return -EOPNOTSUPP; + } cbs.queue = q->queue; @@ -237,8 +240,10 @@ static int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q, cbs.sendslope = opt->sendslope; err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs); - if (err < 0) + if (err < 0) { + NL_SET_ERR_MSG(extack, "Specified device failed to setup cbs hardware offload"); return err; + } q->enqueue = cbs_enqueue_offload; q->dequeue = cbs_dequeue_offload; @@ -246,7 +251,8 @@ static int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q, return 0; } -static int cbs_change(struct Qdisc *sch, struct nlattr *opt) +static int cbs_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct cbs_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); @@ -254,12 +260,14 @@ static int cbs_change(struct Qdisc *sch, struct nlattr *opt) struct tc_cbs_qopt *qopt; int err; - err = nla_parse_nested(tb, TCA_CBS_MAX, opt, cbs_policy, NULL); + err = nla_parse_nested(tb, TCA_CBS_MAX, opt, cbs_policy, extack); if (err < 0) return err; - if (!tb[TCA_CBS_PARMS]) + if (!tb[TCA_CBS_PARMS]) { + NL_SET_ERR_MSG(extack, "Missing CBS parameter which are mandatory"); return -EINVAL; + } qopt = nla_data(tb[TCA_CBS_PARMS]); @@ -276,7 +284,7 @@ static int cbs_change(struct Qdisc *sch, struct nlattr *opt) cbs_disable_offload(dev, q); } else { - err = cbs_enable_offload(dev, q, qopt); + err = cbs_enable_offload(dev, q, qopt, extack); if (err < 0) return err; } @@ -291,13 +299,16 @@ static int cbs_change(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int cbs_init(struct Qdisc *sch, struct nlattr *opt) +static int cbs_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct cbs_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); - if (!opt) + if (!opt) { + NL_SET_ERR_MSG(extack, "Missing CBS qdisc options which are mandatory"); return -EINVAL; + } q->queue = sch->dev_queue - netdev_get_tx_queue(dev, 0); @@ -306,7 +317,7 @@ static int cbs_init(struct Qdisc *sch, struct nlattr *opt) qdisc_watchdog_init(&q->watchdog, sch); - return cbs_change(sch, opt); + return cbs_change(sch, opt, extack); } static void cbs_destroy(struct Qdisc *sch) diff --git a/net/sched/sch_choke.c b/net/sched/sch_choke.c index 531250fceb9e..eafc0d17d174 100644 --- a/net/sched/sch_choke.c +++ b/net/sched/sch_choke.c @@ -344,7 +344,8 @@ static void choke_free(void *addr) kvfree(addr); } -static int choke_change(struct Qdisc *sch, struct nlattr *opt) +static int choke_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct choke_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_CHOKE_MAX + 1]; @@ -431,9 +432,10 @@ static int choke_change(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int choke_init(struct Qdisc *sch, struct nlattr *opt) +static int choke_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { - return choke_change(sch, opt); + return choke_change(sch, opt, extack); } static int choke_dump(struct Qdisc *sch, struct sk_buff *skb) diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c index c518a1efcb9d..17cd81f84b5d 100644 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c @@ -130,7 +130,8 @@ static const struct nla_policy codel_policy[TCA_CODEL_MAX + 1] = { [TCA_CODEL_CE_THRESHOLD]= { .type = NLA_U32 }, }; -static int codel_change(struct Qdisc *sch, struct nlattr *opt) +static int codel_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct codel_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_CODEL_MAX + 1]; @@ -184,7 +185,8 @@ static int codel_change(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int codel_init(struct Qdisc *sch, struct nlattr *opt) +static int codel_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct codel_sched_data *q = qdisc_priv(sch); @@ -196,7 +198,7 @@ static int codel_init(struct Qdisc *sch, struct nlattr *opt) q->params.mtu = psched_mtu(qdisc_dev(sch)); if (opt) { - int err = codel_change(sch, opt); + int err = codel_change(sch, opt, extack); if (err) return err; diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 5bbcef3dcd8c..e0b0cf8a9939 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -64,7 +64,8 @@ static const struct nla_policy drr_policy[TCA_DRR_MAX + 1] = { }; static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, - struct nlattr **tca, unsigned long *arg) + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) { struct drr_sched *q = qdisc_priv(sch); struct drr_class *cl = (struct drr_class *)*arg; @@ -73,17 +74,21 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, u32 quantum; int err; - if (!opt) + if (!opt) { + NL_SET_ERR_MSG(extack, "DRR options are required for this operation"); return -EINVAL; + } - err = nla_parse_nested(tb, TCA_DRR_MAX, opt, drr_policy, NULL); + err = nla_parse_nested(tb, TCA_DRR_MAX, opt, drr_policy, extack); if (err < 0) return err; if (tb[TCA_DRR_QUANTUM]) { quantum = nla_get_u32(tb[TCA_DRR_QUANTUM]); - if (quantum == 0) + if (quantum == 0) { + NL_SET_ERR_MSG(extack, "Specified DRR quantum cannot be zero"); return -EINVAL; + } } else quantum = psched_mtu(qdisc_dev(sch)); @@ -94,8 +99,10 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, NULL, qdisc_root_sleeping_running(sch), tca[TCA_RATE]); - if (err) + if (err) { + NL_SET_ERR_MSG(extack, "Failed to replace estimator"); return err; + } } sch_tree_lock(sch); @@ -113,7 +120,8 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, cl->common.classid = classid; cl->quantum = quantum; cl->qdisc = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, classid); + &pfifo_qdisc_ops, classid, + NULL); if (cl->qdisc == NULL) cl->qdisc = &noop_qdisc; else @@ -125,6 +133,7 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid, qdisc_root_sleeping_running(sch), tca[TCA_RATE]); if (err) { + NL_SET_ERR_MSG(extack, "Failed to replace estimator"); qdisc_destroy(cl->qdisc); kfree(cl); return err; @@ -172,12 +181,15 @@ static unsigned long drr_search_class(struct Qdisc *sch, u32 classid) return (unsigned long)drr_find_class(sch, classid); } -static struct tcf_block *drr_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *drr_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct drr_sched *q = qdisc_priv(sch); - if (cl) + if (cl) { + NL_SET_ERR_MSG(extack, "DRR classid must be zero"); return NULL; + } return q->block; } @@ -201,13 +213,14 @@ static void drr_unbind_tcf(struct Qdisc *sch, unsigned long arg) } static int drr_graft_class(struct Qdisc *sch, unsigned long arg, - struct Qdisc *new, struct Qdisc **old) + struct Qdisc *new, struct Qdisc **old, + struct netlink_ext_ack *extack) { struct drr_class *cl = (struct drr_class *)arg; if (new == NULL) { - new = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, cl->common.classid); + new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + cl->common.classid, NULL); if (new == NULL) new = &noop_qdisc; } @@ -408,12 +421,13 @@ out: return NULL; } -static int drr_init_qdisc(struct Qdisc *sch, struct nlattr *opt) +static int drr_init_qdisc(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct drr_sched *q = qdisc_priv(sch); int err; - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; err = qdisc_class_hash_init(&q->clhash); diff --git a/net/sched/sch_dsmark.c b/net/sched/sch_dsmark.c index fb4fb71c68cf..049714c57075 100644 --- a/net/sched/sch_dsmark.c +++ b/net/sched/sch_dsmark.c @@ -61,7 +61,8 @@ static inline int dsmark_valid_index(struct dsmark_qdisc_data *p, u16 index) /* ------------------------- Class/flow operations ------------------------- */ static int dsmark_graft(struct Qdisc *sch, unsigned long arg, - struct Qdisc *new, struct Qdisc **old) + struct Qdisc *new, struct Qdisc **old, + struct netlink_ext_ack *extack) { struct dsmark_qdisc_data *p = qdisc_priv(sch); @@ -70,7 +71,7 @@ static int dsmark_graft(struct Qdisc *sch, unsigned long arg, if (new == NULL) { new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - sch->handle); + sch->handle, NULL); if (new == NULL) new = &noop_qdisc; } @@ -112,7 +113,8 @@ static const struct nla_policy dsmark_policy[TCA_DSMARK_MAX + 1] = { }; static int dsmark_change(struct Qdisc *sch, u32 classid, u32 parent, - struct nlattr **tca, unsigned long *arg) + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) { struct dsmark_qdisc_data *p = qdisc_priv(sch); struct nlattr *opt = tca[TCA_OPTIONS]; @@ -184,7 +186,8 @@ ignore: } } -static struct tcf_block *dsmark_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *dsmark_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct dsmark_qdisc_data *p = qdisc_priv(sch); @@ -330,7 +333,8 @@ static struct sk_buff *dsmark_peek(struct Qdisc *sch) return p->q->ops->peek(p->q); } -static int dsmark_init(struct Qdisc *sch, struct nlattr *opt) +static int dsmark_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct dsmark_qdisc_data *p = qdisc_priv(sch); struct nlattr *tb[TCA_DSMARK_MAX + 1]; @@ -344,7 +348,7 @@ static int dsmark_init(struct Qdisc *sch, struct nlattr *opt) if (!opt) goto errout; - err = tcf_block_get(&p->block, &p->filter_list, sch); + err = tcf_block_get(&p->block, &p->filter_list, sch, extack); if (err) return err; @@ -377,7 +381,8 @@ static int dsmark_init(struct Qdisc *sch, struct nlattr *opt) p->default_index = default_index; p->set_tc_index = nla_get_flag(tb[TCA_DSMARK_SET_TC_INDEX]); - p->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, sch->handle); + p->q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, sch->handle, + NULL); if (p->q == NULL) p->q = &noop_qdisc; else diff --git a/net/sched/sch_fifo.c b/net/sched/sch_fifo.c index 1e37247656f8..24893d3b5d22 100644 --- a/net/sched/sch_fifo.c +++ b/net/sched/sch_fifo.c @@ -55,7 +55,8 @@ static int pfifo_tail_enqueue(struct sk_buff *skb, struct Qdisc *sch, return NET_XMIT_CN; } -static int fifo_init(struct Qdisc *sch, struct nlattr *opt) +static int fifo_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { bool bypass; bool is_bfifo = sch->ops == &bfifo_qdisc_ops; @@ -157,7 +158,7 @@ int fifo_set_limit(struct Qdisc *q, unsigned int limit) nla->nla_len = nla_attr_size(sizeof(struct tc_fifo_qopt)); ((struct tc_fifo_qopt *)nla_data(nla))->limit = limit; - ret = q->ops->change(q, nla); + ret = q->ops->change(q, nla, NULL); kfree(nla); } return ret; @@ -165,12 +166,14 @@ int fifo_set_limit(struct Qdisc *q, unsigned int limit) EXPORT_SYMBOL(fifo_set_limit); struct Qdisc *fifo_create_dflt(struct Qdisc *sch, struct Qdisc_ops *ops, - unsigned int limit) + unsigned int limit, + struct netlink_ext_ack *extack) { struct Qdisc *q; int err = -ENOMEM; - q = qdisc_create_dflt(sch->dev_queue, ops, TC_H_MAKE(sch->handle, 1)); + q = qdisc_create_dflt(sch->dev_queue, ops, TC_H_MAKE(sch->handle, 1), + extack); if (q) { err = fifo_set_limit(q, limit); if (err < 0) { diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index 263d16e3219e..a366e4c9413a 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -685,7 +685,8 @@ static const struct nla_policy fq_policy[TCA_FQ_MAX + 1] = { [TCA_FQ_LOW_RATE_THRESHOLD] = { .type = NLA_U32 }, }; -static int fq_change(struct Qdisc *sch, struct nlattr *opt) +static int fq_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct fq_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_FQ_MAX + 1]; @@ -788,7 +789,8 @@ static void fq_destroy(struct Qdisc *sch) qdisc_watchdog_cancel(&q->watchdog); } -static int fq_init(struct Qdisc *sch, struct nlattr *opt) +static int fq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct fq_sched_data *q = qdisc_priv(sch); int err; @@ -811,7 +813,7 @@ static int fq_init(struct Qdisc *sch, struct nlattr *opt) qdisc_watchdog_init(&q->watchdog, sch); if (opt) - err = fq_change(sch, opt); + err = fq_change(sch, opt, extack); else err = fq_resize(sch, q->fq_trees_log); diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index 0305d791ea94..22fa13cf5d8b 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -377,7 +377,8 @@ static const struct nla_policy fq_codel_policy[TCA_FQ_CODEL_MAX + 1] = { [TCA_FQ_CODEL_MEMORY_LIMIT] = { .type = NLA_U32 }, }; -static int fq_codel_change(struct Qdisc *sch, struct nlattr *opt) +static int fq_codel_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct fq_codel_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_FQ_CODEL_MAX + 1]; @@ -458,7 +459,8 @@ static void fq_codel_destroy(struct Qdisc *sch) kvfree(q->flows); } -static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt) +static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct fq_codel_sched_data *q = qdisc_priv(sch); int i; @@ -477,12 +479,12 @@ static int fq_codel_init(struct Qdisc *sch, struct nlattr *opt) q->cparams.mtu = psched_mtu(qdisc_dev(sch)); if (opt) { - int err = fq_codel_change(sch, opt); + int err = fq_codel_change(sch, opt, extack); if (err) return err; } - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; @@ -595,7 +597,8 @@ static void fq_codel_unbind(struct Qdisc *q, unsigned long cl) { } -static struct tcf_block *fq_codel_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *fq_codel_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct fq_codel_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 661c7144b53a..a883c501d5ec 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -26,11 +26,13 @@ #include <linux/list.h> #include <linux/slab.h> #include <linux/if_vlan.h> +#include <linux/skb_array.h> #include <linux/if_macvlan.h> #include <net/sch_generic.h> #include <net/pkt_sched.h> #include <net/dst.h> #include <trace/events/qdisc.h> +#include <net/xfrm.h> /* Qdisc to use by default */ const struct Qdisc_ops *default_qdisc_ops = &pfifo_fast_ops; @@ -47,17 +49,115 @@ EXPORT_SYMBOL(default_qdisc_ops); * - updates to tree and tree walking are only done under the rtnl mutex. */ -static inline int dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) +static inline struct sk_buff *__skb_dequeue_bad_txq(struct Qdisc *q) +{ + const struct netdev_queue *txq = q->dev_queue; + spinlock_t *lock = NULL; + struct sk_buff *skb; + + if (q->flags & TCQ_F_NOLOCK) { + lock = qdisc_lock(q); + spin_lock(lock); + } + + skb = skb_peek(&q->skb_bad_txq); + if (skb) { + /* check the reason of requeuing without tx lock first */ + txq = skb_get_tx_queue(txq->dev, skb); + if (!netif_xmit_frozen_or_stopped(txq)) { + skb = __skb_dequeue(&q->skb_bad_txq); + if (qdisc_is_percpu_stats(q)) { + qdisc_qstats_cpu_backlog_dec(q, skb); + qdisc_qstats_cpu_qlen_dec(q); + } else { + qdisc_qstats_backlog_dec(q, skb); + q->q.qlen--; + } + } else { + skb = NULL; + } + } + + if (lock) + spin_unlock(lock); + + return skb; +} + +static inline struct sk_buff *qdisc_dequeue_skb_bad_txq(struct Qdisc *q) +{ + struct sk_buff *skb = skb_peek(&q->skb_bad_txq); + + if (unlikely(skb)) + skb = __skb_dequeue_bad_txq(q); + + return skb; +} + +static inline void qdisc_enqueue_skb_bad_txq(struct Qdisc *q, + struct sk_buff *skb) +{ + spinlock_t *lock = NULL; + + if (q->flags & TCQ_F_NOLOCK) { + lock = qdisc_lock(q); + spin_lock(lock); + } + + __skb_queue_tail(&q->skb_bad_txq, skb); + + if (lock) + spin_unlock(lock); +} + +static inline int __dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) +{ + while (skb) { + struct sk_buff *next = skb->next; + + __skb_queue_tail(&q->gso_skb, skb); + q->qstats.requeues++; + qdisc_qstats_backlog_inc(q, skb); + q->q.qlen++; /* it's still part of the queue */ + + skb = next; + } + __netif_schedule(q); + + return 0; +} + +static inline int dev_requeue_skb_locked(struct sk_buff *skb, struct Qdisc *q) { - q->gso_skb = skb; - q->qstats.requeues++; - qdisc_qstats_backlog_inc(q, skb); - q->q.qlen++; /* it's still part of the queue */ + spinlock_t *lock = qdisc_lock(q); + + spin_lock(lock); + while (skb) { + struct sk_buff *next = skb->next; + + __skb_queue_tail(&q->gso_skb, skb); + + qdisc_qstats_cpu_requeues_inc(q); + qdisc_qstats_cpu_backlog_inc(q, skb); + qdisc_qstats_cpu_qlen_inc(q); + + skb = next; + } + spin_unlock(lock); + __netif_schedule(q); return 0; } +static inline int dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) +{ + if (q->flags & TCQ_F_NOLOCK) + return dev_requeue_skb_locked(skb, q); + else + return __dev_requeue_skb(skb, q); +} + static void try_bulk_dequeue_skb(struct Qdisc *q, struct sk_buff *skb, const struct netdev_queue *txq, @@ -95,9 +195,15 @@ static void try_bulk_dequeue_skb_slow(struct Qdisc *q, if (!nskb) break; if (unlikely(skb_get_queue_mapping(nskb) != mapping)) { - q->skb_bad_txq = nskb; - qdisc_qstats_backlog_inc(q, nskb); - q->q.qlen++; + qdisc_enqueue_skb_bad_txq(q, nskb); + + if (qdisc_is_percpu_stats(q)) { + qdisc_qstats_cpu_backlog_inc(q, nskb); + qdisc_qstats_cpu_qlen_inc(q); + } else { + qdisc_qstats_backlog_inc(q, nskb); + q->q.qlen++; + } break; } skb->next = nskb; @@ -113,40 +219,62 @@ static void try_bulk_dequeue_skb_slow(struct Qdisc *q, static struct sk_buff *dequeue_skb(struct Qdisc *q, bool *validate, int *packets) { - struct sk_buff *skb = q->gso_skb; const struct netdev_queue *txq = q->dev_queue; + struct sk_buff *skb = NULL; *packets = 1; - if (unlikely(skb)) { + if (unlikely(!skb_queue_empty(&q->gso_skb))) { + spinlock_t *lock = NULL; + + if (q->flags & TCQ_F_NOLOCK) { + lock = qdisc_lock(q); + spin_lock(lock); + } + + skb = skb_peek(&q->gso_skb); + + /* skb may be null if another cpu pulls gso_skb off in between + * empty check and lock. + */ + if (!skb) { + if (lock) + spin_unlock(lock); + goto validate; + } + /* skb in gso_skb were already validated */ *validate = false; + if (xfrm_offload(skb)) + *validate = true; /* check the reason of requeuing without tx lock first */ txq = skb_get_tx_queue(txq->dev, skb); if (!netif_xmit_frozen_or_stopped(txq)) { - q->gso_skb = NULL; - qdisc_qstats_backlog_dec(q, skb); - q->q.qlen--; - } else + skb = __skb_dequeue(&q->gso_skb); + if (qdisc_is_percpu_stats(q)) { + qdisc_qstats_cpu_backlog_dec(q, skb); + qdisc_qstats_cpu_qlen_dec(q); + } else { + qdisc_qstats_backlog_dec(q, skb); + q->q.qlen--; + } + } else { skb = NULL; - goto trace; - } - *validate = true; - skb = q->skb_bad_txq; - if (unlikely(skb)) { - /* check the reason of requeuing without tx lock first */ - txq = skb_get_tx_queue(txq->dev, skb); - if (!netif_xmit_frozen_or_stopped(txq)) { - q->skb_bad_txq = NULL; - qdisc_qstats_backlog_dec(q, skb); - q->q.qlen--; - goto bulk; } - skb = NULL; + if (lock) + spin_unlock(lock); goto trace; } - if (!(q->flags & TCQ_F_ONETXQUEUE) || - !netif_xmit_frozen_or_stopped(txq)) - skb = q->dequeue(q); +validate: + *validate = true; + + if ((q->flags & TCQ_F_ONETXQUEUE) && + netif_xmit_frozen_or_stopped(txq)) + return skb; + + skb = qdisc_dequeue_skb_bad_txq(q); + if (unlikely(skb)) + goto bulk; + skb = q->dequeue(q); if (skb) { bulk: if (qdisc_may_bulk(q)) @@ -165,21 +293,33 @@ trace: * only one CPU can execute this function. * * Returns to the caller: - * 0 - queue is empty or throttled. - * >0 - queue is not empty. + * false - hardware queue frozen backoff + * true - feel free to send more pkts */ -int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, - struct net_device *dev, struct netdev_queue *txq, - spinlock_t *root_lock, bool validate) +bool sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, + struct net_device *dev, struct netdev_queue *txq, + spinlock_t *root_lock, bool validate) { int ret = NETDEV_TX_BUSY; + bool again = false; /* And release qdisc */ - spin_unlock(root_lock); + if (root_lock) + spin_unlock(root_lock); /* Note that we validate skb (GSO, checksum, ...) outside of locks */ if (validate) - skb = validate_xmit_skb_list(skb, dev); + skb = validate_xmit_skb_list(skb, dev, &again); + +#ifdef CONFIG_XFRM_OFFLOAD + if (unlikely(again)) { + if (root_lock) + spin_lock(root_lock); + + dev_requeue_skb(skb, q); + return false; + } +#endif if (likely(skb)) { HARD_TX_LOCK(dev, txq, smp_processor_id()); @@ -188,27 +328,28 @@ int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, HARD_TX_UNLOCK(dev, txq); } else { - spin_lock(root_lock); - return qdisc_qlen(q); + if (root_lock) + spin_lock(root_lock); + return true; } - spin_lock(root_lock); - if (dev_xmit_complete(ret)) { - /* Driver sent out skb successfully or skb was consumed */ - ret = qdisc_qlen(q); - } else { + if (root_lock) + spin_lock(root_lock); + + if (!dev_xmit_complete(ret)) { /* Driver returned NETDEV_TX_BUSY - requeue skb */ if (unlikely(ret != NETDEV_TX_BUSY)) net_warn_ratelimited("BUG %s code %d qlen %d\n", dev->name, ret, q->q.qlen); - ret = dev_requeue_skb(skb, q); + dev_requeue_skb(skb, q); + return false; } if (ret && netif_xmit_frozen_or_stopped(txq)) - ret = 0; + return false; - return ret; + return true; } /* @@ -230,20 +371,22 @@ int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q, * >0 - queue is not empty. * */ -static inline int qdisc_restart(struct Qdisc *q, int *packets) +static inline bool qdisc_restart(struct Qdisc *q, int *packets) { + spinlock_t *root_lock = NULL; struct netdev_queue *txq; struct net_device *dev; - spinlock_t *root_lock; struct sk_buff *skb; bool validate; /* Dequeue packet */ skb = dequeue_skb(q, &validate, packets); if (unlikely(!skb)) - return 0; + return false; + + if (!(q->flags & TCQ_F_NOLOCK)) + root_lock = qdisc_lock(q); - root_lock = qdisc_lock(q); dev = qdisc_dev(q); txq = skb_get_tx_queue(dev, skb); @@ -267,8 +410,6 @@ void __qdisc_run(struct Qdisc *q) break; } } - - qdisc_run_end(q); } unsigned long dev_trans_start(struct net_device *dev) @@ -437,7 +578,8 @@ struct Qdisc noop_qdisc = { }; EXPORT_SYMBOL(noop_qdisc); -static int noqueue_init(struct Qdisc *qdisc, struct nlattr *opt) +static int noqueue_init(struct Qdisc *qdisc, struct nlattr *opt, + struct netlink_ext_ack *extack) { /* register_qdisc() assigns a default of noop_enqueue if unset, * but __dev_queue_xmit() treats noqueue only as such @@ -468,93 +610,99 @@ static const u8 prio2band[TC_PRIO_MAX + 1] = { /* * Private data for a pfifo_fast scheduler containing: - * - queues for the three band - * - bitmap indicating which of the bands contain skbs + * - rings for priority bands */ struct pfifo_fast_priv { - u32 bitmap; - struct qdisc_skb_head q[PFIFO_FAST_BANDS]; + struct skb_array q[PFIFO_FAST_BANDS]; }; -/* - * Convert a bitmap to the first band number where an skb is queued, where: - * bitmap=0 means there are no skbs on any band. - * bitmap=1 means there is an skb on band 0. - * bitmap=7 means there are skbs on all 3 bands, etc. - */ -static const int bitmap2band[] = {-1, 0, 1, 0, 2, 0, 1, 0}; - -static inline struct qdisc_skb_head *band2list(struct pfifo_fast_priv *priv, - int band) +static inline struct skb_array *band2list(struct pfifo_fast_priv *priv, + int band) { - return priv->q + band; + return &priv->q[band]; } static int pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc *qdisc, struct sk_buff **to_free) { - if (qdisc->q.qlen < qdisc_dev(qdisc)->tx_queue_len) { - int band = prio2band[skb->priority & TC_PRIO_MAX]; - struct pfifo_fast_priv *priv = qdisc_priv(qdisc); - struct qdisc_skb_head *list = band2list(priv, band); - - priv->bitmap |= (1 << band); - qdisc->q.qlen++; - return __qdisc_enqueue_tail(skb, qdisc, list); - } + int band = prio2band[skb->priority & TC_PRIO_MAX]; + struct pfifo_fast_priv *priv = qdisc_priv(qdisc); + struct skb_array *q = band2list(priv, band); + int err; + + err = skb_array_produce(q, skb); - return qdisc_drop(skb, qdisc, to_free); + if (unlikely(err)) + return qdisc_drop_cpu(skb, qdisc, to_free); + + qdisc_qstats_cpu_qlen_inc(qdisc); + qdisc_qstats_cpu_backlog_inc(qdisc, skb); + return NET_XMIT_SUCCESS; } static struct sk_buff *pfifo_fast_dequeue(struct Qdisc *qdisc) { struct pfifo_fast_priv *priv = qdisc_priv(qdisc); - int band = bitmap2band[priv->bitmap]; + struct sk_buff *skb = NULL; + int band; - if (likely(band >= 0)) { - struct qdisc_skb_head *qh = band2list(priv, band); - struct sk_buff *skb = __qdisc_dequeue_head(qh); + for (band = 0; band < PFIFO_FAST_BANDS && !skb; band++) { + struct skb_array *q = band2list(priv, band); - if (likely(skb != NULL)) { - qdisc_qstats_backlog_dec(qdisc, skb); - qdisc_bstats_update(qdisc, skb); - } + if (__skb_array_empty(q)) + continue; - qdisc->q.qlen--; - if (qh->qlen == 0) - priv->bitmap &= ~(1 << band); - - return skb; + skb = skb_array_consume_bh(q); + } + if (likely(skb)) { + qdisc_qstats_cpu_backlog_dec(qdisc, skb); + qdisc_bstats_cpu_update(qdisc, skb); + qdisc_qstats_cpu_qlen_dec(qdisc); } - return NULL; + return skb; } static struct sk_buff *pfifo_fast_peek(struct Qdisc *qdisc) { struct pfifo_fast_priv *priv = qdisc_priv(qdisc); - int band = bitmap2band[priv->bitmap]; + struct sk_buff *skb = NULL; + int band; - if (band >= 0) { - struct qdisc_skb_head *qh = band2list(priv, band); + for (band = 0; band < PFIFO_FAST_BANDS && !skb; band++) { + struct skb_array *q = band2list(priv, band); - return qh->head; + skb = __skb_array_peek(q); } - return NULL; + return skb; } static void pfifo_fast_reset(struct Qdisc *qdisc) { - int prio; + int i, band; struct pfifo_fast_priv *priv = qdisc_priv(qdisc); - for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) - __qdisc_reset_queue(band2list(priv, prio)); + for (band = 0; band < PFIFO_FAST_BANDS; band++) { + struct skb_array *q = band2list(priv, band); + struct sk_buff *skb; - priv->bitmap = 0; - qdisc->qstats.backlog = 0; - qdisc->q.qlen = 0; + /* NULL ring is possible if destroy path is due to a failed + * skb_array_init() in pfifo_fast_init() case. + */ + if (!q->ring.queue) + continue; + + while ((skb = skb_array_consume_bh(q)) != NULL) + kfree_skb(skb); + } + + for_each_possible_cpu(i) { + struct gnet_stats_queue *q = per_cpu_ptr(qdisc->cpu_qstats, i); + + q->backlog = 0; + q->qlen = 0; + } } static int pfifo_fast_dump(struct Qdisc *qdisc, struct sk_buff *skb) @@ -570,19 +718,51 @@ nla_put_failure: return -1; } -static int pfifo_fast_init(struct Qdisc *qdisc, struct nlattr *opt) +static int pfifo_fast_init(struct Qdisc *qdisc, struct nlattr *opt, + struct netlink_ext_ack *extack) { - int prio; + unsigned int qlen = qdisc_dev(qdisc)->tx_queue_len; struct pfifo_fast_priv *priv = qdisc_priv(qdisc); + int prio; + + /* guard against zero length rings */ + if (!qlen) + return -EINVAL; - for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) - qdisc_skb_head_init(band2list(priv, prio)); + for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) { + struct skb_array *q = band2list(priv, prio); + int err; + + err = skb_array_init(q, qlen, GFP_KERNEL); + if (err) + return -ENOMEM; + } /* Can by-pass the queue discipline */ qdisc->flags |= TCQ_F_CAN_BYPASS; return 0; } +static void pfifo_fast_destroy(struct Qdisc *sch) +{ + struct pfifo_fast_priv *priv = qdisc_priv(sch); + int prio; + + for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) { + struct skb_array *q = band2list(priv, prio); + + /* NULL ring is possible if destroy path is due to a failed + * skb_array_init() in pfifo_fast_init() case. + */ + if (!q->ring.queue) + continue; + /* Destroy ring but no need to kfree_skb because a call to + * pfifo_fast_reset() has already done that work. + */ + ptr_ring_cleanup(&q->ring, NULL); + } +} + struct Qdisc_ops pfifo_fast_ops __read_mostly = { .id = "pfifo_fast", .priv_size = sizeof(struct pfifo_fast_priv), @@ -590,9 +770,11 @@ struct Qdisc_ops pfifo_fast_ops __read_mostly = { .dequeue = pfifo_fast_dequeue, .peek = pfifo_fast_peek, .init = pfifo_fast_init, + .destroy = pfifo_fast_destroy, .reset = pfifo_fast_reset, .dump = pfifo_fast_dump, .owner = THIS_MODULE, + .static_flags = TCQ_F_NOLOCK | TCQ_F_CPUSTATS, }; EXPORT_SYMBOL(pfifo_fast_ops); @@ -600,7 +782,8 @@ static struct lock_class_key qdisc_tx_busylock; static struct lock_class_key qdisc_running_key; struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, - const struct Qdisc_ops *ops) + const struct Qdisc_ops *ops, + struct netlink_ext_ack *extack) { void *p; struct Qdisc *sch; @@ -609,6 +792,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, struct net_device *dev; if (!dev_queue) { + NL_SET_ERR_MSG(extack, "No device queue given"); err = -EINVAL; goto errout; } @@ -630,9 +814,24 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p); sch->padded = (char *) sch - (char *) p; } + __skb_queue_head_init(&sch->gso_skb); + __skb_queue_head_init(&sch->skb_bad_txq); qdisc_skb_head_init(&sch->q); spin_lock_init(&sch->q.lock); + if (ops->static_flags & TCQ_F_CPUSTATS) { + sch->cpu_bstats = + netdev_alloc_pcpu_stats(struct gnet_stats_basic_cpu); + if (!sch->cpu_bstats) + goto errout1; + + sch->cpu_qstats = alloc_percpu(struct gnet_stats_queue); + if (!sch->cpu_qstats) { + free_percpu(sch->cpu_bstats); + goto errout1; + } + } + spin_lock_init(&sch->busylock); lockdep_set_class(&sch->busylock, dev->qdisc_tx_busylock ?: &qdisc_tx_busylock); @@ -642,6 +841,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, dev->qdisc_running_key ?: &qdisc_running_key); sch->ops = ops; + sch->flags = ops->static_flags; sch->enqueue = ops->enqueue; sch->dequeue = ops->dequeue; sch->dev_queue = dev_queue; @@ -649,27 +849,32 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, refcount_set(&sch->refcnt, 1); return sch; +errout1: + kfree(p); errout: return ERR_PTR(err); } struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue, const struct Qdisc_ops *ops, - unsigned int parentid) + unsigned int parentid, + struct netlink_ext_ack *extack) { struct Qdisc *sch; - if (!try_module_get(ops->owner)) + if (!try_module_get(ops->owner)) { + NL_SET_ERR_MSG(extack, "Failed to increase module reference counter"); return NULL; + } - sch = qdisc_alloc(dev_queue, ops); + sch = qdisc_alloc(dev_queue, ops, extack); if (IS_ERR(sch)) { module_put(ops->owner); return NULL; } sch->parent = parentid; - if (!ops->init || ops->init(sch, NULL) == 0) + if (!ops->init || ops->init(sch, NULL, extack) == 0) return sch; qdisc_destroy(sch); @@ -682,17 +887,21 @@ EXPORT_SYMBOL(qdisc_create_dflt); void qdisc_reset(struct Qdisc *qdisc) { const struct Qdisc_ops *ops = qdisc->ops; + struct sk_buff *skb, *tmp; if (ops->reset) ops->reset(qdisc); - kfree_skb(qdisc->skb_bad_txq); - qdisc->skb_bad_txq = NULL; + skb_queue_walk_safe(&qdisc->gso_skb, skb, tmp) { + __skb_unlink(skb, &qdisc->gso_skb); + kfree_skb_list(skb); + } - if (qdisc->gso_skb) { - kfree_skb_list(qdisc->gso_skb); - qdisc->gso_skb = NULL; + skb_queue_walk_safe(&qdisc->skb_bad_txq, skb, tmp) { + __skb_unlink(skb, &qdisc->skb_bad_txq); + kfree_skb_list(skb); } + qdisc->q.qlen = 0; qdisc->qstats.backlog = 0; } @@ -711,6 +920,7 @@ static void qdisc_free(struct Qdisc *qdisc) void qdisc_destroy(struct Qdisc *qdisc) { const struct Qdisc_ops *ops = qdisc->ops; + struct sk_buff *skb, *tmp; if (qdisc->flags & TCQ_F_BUILTIN || !refcount_dec_and_test(&qdisc->refcnt)) @@ -730,8 +940,16 @@ void qdisc_destroy(struct Qdisc *qdisc) module_put(ops->owner); dev_put(qdisc_dev(qdisc)); - kfree_skb_list(qdisc->gso_skb); - kfree_skb(qdisc->skb_bad_txq); + skb_queue_walk_safe(&qdisc->gso_skb, skb, tmp) { + __skb_unlink(skb, &qdisc->gso_skb); + kfree_skb_list(skb); + } + + skb_queue_walk_safe(&qdisc->skb_bad_txq, skb, tmp) { + __skb_unlink(skb, &qdisc->skb_bad_txq); + kfree_skb_list(skb); + } + qdisc_free(qdisc); } EXPORT_SYMBOL(qdisc_destroy); @@ -746,10 +964,6 @@ struct Qdisc *dev_graft_qdisc(struct netdev_queue *dev_queue, root_lock = qdisc_lock(oqdisc); spin_lock_bh(root_lock); - /* Prune old scheduler */ - if (oqdisc && refcount_read(&oqdisc->refcnt) <= 1) - qdisc_reset(oqdisc); - /* ... and graft new one */ if (qdisc == NULL) qdisc = &noop_qdisc; @@ -772,7 +986,7 @@ static void attach_one_default_qdisc(struct net_device *dev, if (dev->priv_flags & IFF_NO_QUEUE) ops = &noqueue_qdisc_ops; - qdisc = qdisc_create_dflt(dev_queue, ops, TC_H_ROOT); + qdisc = qdisc_create_dflt(dev_queue, ops, TC_H_ROOT, NULL); if (!qdisc) { netdev_info(dev, "activation failed\n"); return; @@ -795,7 +1009,7 @@ static void attach_default_qdiscs(struct net_device *dev) dev->qdisc = txq->qdisc_sleeping; qdisc_refcount_inc(dev->qdisc); } else { - qdisc = qdisc_create_dflt(txq, &mq_qdisc_ops, TC_H_ROOT); + qdisc = qdisc_create_dflt(txq, &mq_qdisc_ops, TC_H_ROOT, NULL); if (qdisc) { dev->qdisc = qdisc; qdisc->ops->attach(qdisc); @@ -885,14 +1099,18 @@ static bool some_qdisc_is_busy(struct net_device *dev) dev_queue = netdev_get_tx_queue(dev, i); q = dev_queue->qdisc_sleeping; - root_lock = qdisc_lock(q); - spin_lock_bh(root_lock); + if (q->flags & TCQ_F_NOLOCK) { + val = test_bit(__QDISC_STATE_SCHED, &q->state); + } else { + root_lock = qdisc_lock(q); + spin_lock_bh(root_lock); - val = (qdisc_is_running(q) || - test_bit(__QDISC_STATE_SCHED, &q->state)); + val = (qdisc_is_running(q) || + test_bit(__QDISC_STATE_SCHED, &q->state)); - spin_unlock_bh(root_lock); + spin_unlock_bh(root_lock); + } if (val) return true; @@ -900,6 +1118,16 @@ static bool some_qdisc_is_busy(struct net_device *dev) return false; } +static void dev_qdisc_reset(struct net_device *dev, + struct netdev_queue *dev_queue, + void *none) +{ + struct Qdisc *qdisc = dev_queue->qdisc_sleeping; + + if (qdisc) + qdisc_reset(qdisc); +} + /** * dev_deactivate_many - deactivate transmissions on several devices * @head: list of devices to deactivate @@ -910,7 +1138,6 @@ static bool some_qdisc_is_busy(struct net_device *dev) void dev_deactivate_many(struct list_head *head) { struct net_device *dev; - bool sync_needed = false; list_for_each_entry(dev, head, close_list) { netdev_for_each_tx_queue(dev, dev_deactivate_queue, @@ -920,20 +1147,25 @@ void dev_deactivate_many(struct list_head *head) &noop_qdisc); dev_watchdog_down(dev); - sync_needed |= !dev->dismantle; } /* Wait for outstanding qdisc-less dev_queue_xmit calls. * This is avoided if all devices are in dismantle phase : * Caller will call synchronize_net() for us */ - if (sync_needed) - synchronize_net(); + synchronize_net(); /* Wait for outstanding qdisc_run calls. */ - list_for_each_entry(dev, head, close_list) + list_for_each_entry(dev, head, close_list) { while (some_qdisc_is_busy(dev)) yield(); + /* The new qdisc is assigned at this point so we can safely + * unwind stale skb lists and qdisc statistics + */ + netdev_for_each_tx_queue(dev, dev_qdisc_reset, NULL); + if (dev_ingress_queue(dev)) + dev_qdisc_reset(dev, dev_ingress_queue(dev), NULL); + } } void dev_deactivate(struct net_device *dev) @@ -954,6 +1186,8 @@ static void dev_init_scheduler_queue(struct net_device *dev, rcu_assign_pointer(dev_queue->qdisc, qdisc); dev_queue->qdisc_sleeping = qdisc; + __skb_queue_head_init(&qdisc->gso_skb); + __skb_queue_head_init(&qdisc->skb_bad_txq); } void dev_init_scheduler(struct net_device *dev) diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c index bc30f9186ac6..cbe4831f46f4 100644 --- a/net/sched/sch_gred.c +++ b/net/sched/sch_gred.c @@ -306,12 +306,13 @@ static inline int gred_change_table_def(struct Qdisc *sch, struct nlattr *dps) struct tc_gred_sopt *sopt; int i; - if (dps == NULL) + if (!dps) return -EINVAL; sopt = nla_data(dps); - if (sopt->DPs > MAX_DPs || sopt->DPs == 0 || sopt->def_DP >= sopt->DPs) + if (sopt->DPs > MAX_DPs || sopt->DPs == 0 || + sopt->def_DP >= sopt->DPs) return -EINVAL; sch_tree_lock(sch); @@ -391,7 +392,8 @@ static const struct nla_policy gred_policy[TCA_GRED_MAX + 1] = { [TCA_GRED_LIMIT] = { .type = NLA_U32 }, }; -static int gred_change(struct Qdisc *sch, struct nlattr *opt) +static int gred_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct gred_sched *table = qdisc_priv(sch); struct tc_gred_qopt *ctl; @@ -465,12 +467,13 @@ errout: return err; } -static int gred_init(struct Qdisc *sch, struct nlattr *opt) +static int gred_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct nlattr *tb[TCA_GRED_MAX + 1]; int err; - if (opt == NULL) + if (!opt) return -EINVAL; err = nla_parse_nested(tb, TCA_GRED_MAX, opt, gred_policy, NULL); diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index d04068a97d81..3ae9877ea205 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -921,7 +921,8 @@ static const struct nla_policy hfsc_policy[TCA_HFSC_MAX + 1] = { static int hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid, - struct nlattr **tca, unsigned long *arg) + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) { struct hfsc_sched *q = qdisc_priv(sch); struct hfsc_class *cl = (struct hfsc_class *)*arg; @@ -1033,7 +1034,7 @@ hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid, if (cl == NULL) return -ENOBUFS; - err = tcf_block_get(&cl->block, &cl->filter_list, sch); + err = tcf_block_get(&cl->block, &cl->filter_list, sch, extack); if (err) { kfree(cl); return err; @@ -1061,8 +1062,8 @@ hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid, cl->cl_common.classid = classid; cl->sched = q; cl->cl_parent = parent; - cl->qdisc = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, classid); + cl->qdisc = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + classid, NULL); if (cl->qdisc == NULL) cl->qdisc = &noop_qdisc; else @@ -1176,7 +1177,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) static int hfsc_graft_class(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct hfsc_class *cl = (struct hfsc_class *)arg; @@ -1184,7 +1185,7 @@ hfsc_graft_class(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, return -EINVAL; if (new == NULL) { new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - cl->cl_common.classid); + cl->cl_common.classid, NULL); if (new == NULL) new = &noop_qdisc; } @@ -1246,7 +1247,8 @@ hfsc_unbind_tcf(struct Qdisc *sch, unsigned long arg) cl->filter_cnt--; } -static struct tcf_block *hfsc_tcf_block(struct Qdisc *sch, unsigned long arg) +static struct tcf_block *hfsc_tcf_block(struct Qdisc *sch, unsigned long arg, + struct netlink_ext_ack *extack) { struct hfsc_sched *q = qdisc_priv(sch); struct hfsc_class *cl = (struct hfsc_class *)arg; @@ -1388,7 +1390,8 @@ hfsc_schedule_watchdog(struct Qdisc *sch) } static int -hfsc_init_qdisc(struct Qdisc *sch, struct nlattr *opt) +hfsc_init_qdisc(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct hfsc_sched *q = qdisc_priv(sch); struct tc_hfsc_qopt *qopt; @@ -1396,7 +1399,7 @@ hfsc_init_qdisc(struct Qdisc *sch, struct nlattr *opt) qdisc_watchdog_init(&q->watchdog, sch); - if (opt == NULL || nla_len(opt) < sizeof(*qopt)) + if (!opt || nla_len(opt) < sizeof(*qopt)) return -EINVAL; qopt = nla_data(opt); @@ -1406,14 +1409,14 @@ hfsc_init_qdisc(struct Qdisc *sch, struct nlattr *opt) return err; q->eligible = RB_ROOT; - err = tcf_block_get(&q->root.block, &q->root.filter_list, sch); + err = tcf_block_get(&q->root.block, &q->root.filter_list, sch, extack); if (err) return err; q->root.cl_common.classid = sch->handle; q->root.sched = q; q->root.qdisc = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - sch->handle); + sch->handle, NULL); if (q->root.qdisc == NULL) q->root.qdisc = &noop_qdisc; else @@ -1429,7 +1432,8 @@ hfsc_init_qdisc(struct Qdisc *sch, struct nlattr *opt) } static int -hfsc_change_qdisc(struct Qdisc *sch, struct nlattr *opt) +hfsc_change_qdisc(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct hfsc_sched *q = qdisc_priv(sch); struct tc_hfsc_qopt *qopt; diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index 73a53c08091b..bce2632212d3 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -504,7 +504,8 @@ static const struct nla_policy hhf_policy[TCA_HHF_MAX + 1] = { [TCA_HHF_NON_HH_WEIGHT] = { .type = NLA_U32 }, }; -static int hhf_change(struct Qdisc *sch, struct nlattr *opt) +static int hhf_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct hhf_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_HHF_MAX + 1]; @@ -571,7 +572,8 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int hhf_init(struct Qdisc *sch, struct nlattr *opt) +static int hhf_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct hhf_sched_data *q = qdisc_priv(sch); int i; @@ -589,7 +591,7 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt) q->hhf_non_hh_weight = 2; if (opt) { - int err = hhf_change(sch, opt); + int err = hhf_change(sch, opt, extack); if (err) return err; diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c index fa0380730ff0..1ea9846cc6ce 100644 --- a/net/sched/sch_htb.c +++ b/net/sched/sch_htb.c @@ -1017,7 +1017,8 @@ static void htb_work_func(struct work_struct *work) rcu_read_unlock(); } -static int htb_init(struct Qdisc *sch, struct nlattr *opt) +static int htb_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct htb_sched *q = qdisc_priv(sch); struct nlattr *tb[TCA_HTB_MAX + 1]; @@ -1031,7 +1032,7 @@ static int htb_init(struct Qdisc *sch, struct nlattr *opt) if (!opt) return -EINVAL; - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; @@ -1171,7 +1172,7 @@ htb_dump_class_stats(struct Qdisc *sch, unsigned long arg, struct gnet_dump *d) } static int htb_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct htb_class *cl = (struct htb_class *)arg; @@ -1179,7 +1180,7 @@ static int htb_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, return -EINVAL; if (new == NULL && (new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - cl->common.classid)) == NULL) + cl->common.classid, extack)) == NULL) return -ENOBUFS; *old = qdisc_replace(sch, new, &cl->un.leaf.q); @@ -1289,7 +1290,8 @@ static int htb_delete(struct Qdisc *sch, unsigned long arg) if (!cl->level && htb_parent_last_child(cl)) { new_q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - cl->parent->common.classid); + cl->parent->common.classid, + NULL); last_child = 1; } @@ -1326,7 +1328,7 @@ static int htb_delete(struct Qdisc *sch, unsigned long arg) static int htb_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **tca, - unsigned long *arg) + unsigned long *arg, struct netlink_ext_ack *extack) { int err = -EINVAL; struct htb_sched *q = qdisc_priv(sch); @@ -1356,10 +1358,12 @@ static int htb_change_class(struct Qdisc *sch, u32 classid, /* Keeping backward compatible with rate_table based iproute2 tc */ if (hopt->rate.linklayer == TC_LINKLAYER_UNAWARE) - qdisc_put_rtab(qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB])); + qdisc_put_rtab(qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB], + NULL)); if (hopt->ceil.linklayer == TC_LINKLAYER_UNAWARE) - qdisc_put_rtab(qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB])); + qdisc_put_rtab(qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB], + NULL)); if (!cl) { /* new class */ struct Qdisc *new_q; @@ -1394,7 +1398,7 @@ static int htb_change_class(struct Qdisc *sch, u32 classid, if (!cl) goto failure; - err = tcf_block_get(&cl->block, &cl->filter_list, sch); + err = tcf_block_get(&cl->block, &cl->filter_list, sch, extack); if (err) { kfree(cl); goto failure; @@ -1423,8 +1427,8 @@ static int htb_change_class(struct Qdisc *sch, u32 classid, * so that can't be used inside of sch_tree_lock * -- thanks to Karlis Peisenieks */ - new_q = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, classid); + new_q = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + classid, NULL); sch_tree_lock(sch); if (parent && !parent->level) { unsigned int qlen = parent->un.leaf.q->q.qlen; @@ -1524,7 +1528,8 @@ failure: return err; } -static struct tcf_block *htb_tcf_block(struct Qdisc *sch, unsigned long arg) +static struct tcf_block *htb_tcf_block(struct Qdisc *sch, unsigned long arg, + struct netlink_ext_ack *extack) { struct htb_sched *q = qdisc_priv(sch); struct htb_class *cl = (struct htb_class *)arg; diff --git a/net/sched/sch_ingress.c b/net/sched/sch_ingress.c index fc1286f499c1..7ca2be20dd6f 100644 --- a/net/sched/sch_ingress.c +++ b/net/sched/sch_ingress.c @@ -48,7 +48,8 @@ static void ingress_walk(struct Qdisc *sch, struct qdisc_walker *walker) { } -static struct tcf_block *ingress_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *ingress_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct ingress_sched_data *q = qdisc_priv(sch); @@ -62,7 +63,8 @@ static void clsact_chain_head_change(struct tcf_proto *tp_head, void *priv) mini_qdisc_pair_swap(miniqp, tp_head); } -static int ingress_init(struct Qdisc *sch, struct nlattr *opt) +static int ingress_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct ingress_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); @@ -76,7 +78,7 @@ static int ingress_init(struct Qdisc *sch, struct nlattr *opt) q->block_info.chain_head_change = clsact_chain_head_change; q->block_info.chain_head_change_priv = &q->miniqp; - err = tcf_block_get_ext(&q->block, sch, &q->block_info); + err = tcf_block_get_ext(&q->block, sch, &q->block_info, extack); if (err) return err; @@ -153,7 +155,8 @@ static unsigned long clsact_bind_filter(struct Qdisc *sch, return clsact_find(sch, classid); } -static struct tcf_block *clsact_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *clsact_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct clsact_sched_data *q = qdisc_priv(sch); @@ -167,7 +170,8 @@ static struct tcf_block *clsact_tcf_block(struct Qdisc *sch, unsigned long cl) } } -static int clsact_init(struct Qdisc *sch, struct nlattr *opt) +static int clsact_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct clsact_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); @@ -182,7 +186,8 @@ static int clsact_init(struct Qdisc *sch, struct nlattr *opt) q->ingress_block_info.chain_head_change = clsact_chain_head_change; q->ingress_block_info.chain_head_change_priv = &q->miniqp_ingress; - err = tcf_block_get_ext(&q->ingress_block, sch, &q->ingress_block_info); + err = tcf_block_get_ext(&q->ingress_block, sch, &q->ingress_block_info, + extack); if (err) return err; @@ -192,7 +197,8 @@ static int clsact_init(struct Qdisc *sch, struct nlattr *opt) q->egress_block_info.chain_head_change = clsact_chain_head_change; q->egress_block_info.chain_head_change_priv = &q->miniqp_egress; - err = tcf_block_get_ext(&q->egress_block, sch, &q->egress_block_info); + err = tcf_block_get_ext(&q->egress_block, sch, &q->egress_block_info, + extack); if (err) return err; diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c index 213b586a06a0..f062a18e9162 100644 --- a/net/sched/sch_mq.c +++ b/net/sched/sch_mq.c @@ -17,6 +17,7 @@ #include <linux/skbuff.h> #include <net/netlink.h> #include <net/pkt_sched.h> +#include <net/sch_generic.h> struct mq_sched { struct Qdisc **qdiscs; @@ -35,7 +36,8 @@ static void mq_destroy(struct Qdisc *sch) kfree(priv->qdiscs); } -static int mq_init(struct Qdisc *sch, struct nlattr *opt) +static int mq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct net_device *dev = qdisc_dev(sch); struct mq_sched *priv = qdisc_priv(sch); @@ -59,7 +61,8 @@ static int mq_init(struct Qdisc *sch, struct nlattr *opt) dev_queue = netdev_get_tx_queue(dev, ntx); qdisc = qdisc_create_dflt(dev_queue, get_default_qdisc_ops(dev, ntx), TC_H_MAKE(TC_H_MAJ(sch->handle), - TC_H_MIN(ntx + 1))); + TC_H_MIN(ntx + 1)), + extack); if (!qdisc) return -ENOMEM; priv->qdiscs[ntx] = qdisc; @@ -97,23 +100,42 @@ static int mq_dump(struct Qdisc *sch, struct sk_buff *skb) struct net_device *dev = qdisc_dev(sch); struct Qdisc *qdisc; unsigned int ntx; + __u32 qlen = 0; sch->q.qlen = 0; memset(&sch->bstats, 0, sizeof(sch->bstats)); memset(&sch->qstats, 0, sizeof(sch->qstats)); + /* MQ supports lockless qdiscs. However, statistics accounting needs + * to account for all, none, or a mix of locked and unlocked child + * qdiscs. Percpu stats are added to counters in-band and locking + * qdisc totals are added at end. + */ for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { qdisc = netdev_get_tx_queue(dev, ntx)->qdisc_sleeping; spin_lock_bh(qdisc_lock(qdisc)); - sch->q.qlen += qdisc->q.qlen; - sch->bstats.bytes += qdisc->bstats.bytes; - sch->bstats.packets += qdisc->bstats.packets; - sch->qstats.backlog += qdisc->qstats.backlog; - sch->qstats.drops += qdisc->qstats.drops; - sch->qstats.requeues += qdisc->qstats.requeues; - sch->qstats.overlimits += qdisc->qstats.overlimits; + + if (qdisc_is_percpu_stats(qdisc)) { + qlen = qdisc_qlen_sum(qdisc); + __gnet_stats_copy_basic(NULL, &sch->bstats, + qdisc->cpu_bstats, + &qdisc->bstats); + __gnet_stats_copy_queue(&sch->qstats, + qdisc->cpu_qstats, + &qdisc->qstats, qlen); + } else { + sch->q.qlen += qdisc->q.qlen; + sch->bstats.bytes += qdisc->bstats.bytes; + sch->bstats.packets += qdisc->bstats.packets; + sch->qstats.backlog += qdisc->qstats.backlog; + sch->qstats.drops += qdisc->qstats.drops; + sch->qstats.requeues += qdisc->qstats.requeues; + sch->qstats.overlimits += qdisc->qstats.overlimits; + } + spin_unlock_bh(qdisc_lock(qdisc)); } + return 0; } @@ -134,7 +156,7 @@ static struct netdev_queue *mq_select_queue(struct Qdisc *sch, } static int mq_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct netdev_queue *dev_queue = mq_queue_get(sch, cl); struct net_device *dev = qdisc_dev(sch); diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c index b85885a9d8a1..0e9d761cdd80 100644 --- a/net/sched/sch_mqprio.c +++ b/net/sched/sch_mqprio.c @@ -132,7 +132,8 @@ static int parse_attr(struct nlattr *tb[], int maxtype, struct nlattr *nla, return 0; } -static int mqprio_init(struct Qdisc *sch, struct nlattr *opt) +static int mqprio_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct net_device *dev = qdisc_dev(sch); struct mqprio_sched *priv = qdisc_priv(sch); @@ -229,7 +230,7 @@ static int mqprio_init(struct Qdisc *sch, struct nlattr *opt) qdisc = qdisc_create_dflt(dev_queue, get_default_qdisc_ops(dev, i), TC_H_MAKE(TC_H_MAJ(sch->handle), - TC_H_MIN(i + 1))); + TC_H_MIN(i + 1)), extack); if (!qdisc) return -ENOMEM; @@ -319,7 +320,7 @@ static struct netdev_queue *mqprio_queue_get(struct Qdisc *sch, } static int mqprio_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct net_device *dev = qdisc_dev(sch); struct netdev_queue *dev_queue = mqprio_queue_get(sch, cl); @@ -388,22 +389,40 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb) struct nlattr *nla = (struct nlattr *)skb_tail_pointer(skb); struct tc_mqprio_qopt opt = { 0 }; struct Qdisc *qdisc; - unsigned int i; + unsigned int ntx, tc; sch->q.qlen = 0; memset(&sch->bstats, 0, sizeof(sch->bstats)); memset(&sch->qstats, 0, sizeof(sch->qstats)); - for (i = 0; i < dev->num_tx_queues; i++) { - qdisc = rtnl_dereference(netdev_get_tx_queue(dev, i)->qdisc); + /* MQ supports lockless qdiscs. However, statistics accounting needs + * to account for all, none, or a mix of locked and unlocked child + * qdiscs. Percpu stats are added to counters in-band and locking + * qdisc totals are added at end. + */ + for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { + qdisc = netdev_get_tx_queue(dev, ntx)->qdisc_sleeping; spin_lock_bh(qdisc_lock(qdisc)); - sch->q.qlen += qdisc->q.qlen; - sch->bstats.bytes += qdisc->bstats.bytes; - sch->bstats.packets += qdisc->bstats.packets; - sch->qstats.backlog += qdisc->qstats.backlog; - sch->qstats.drops += qdisc->qstats.drops; - sch->qstats.requeues += qdisc->qstats.requeues; - sch->qstats.overlimits += qdisc->qstats.overlimits; + + if (qdisc_is_percpu_stats(qdisc)) { + __u32 qlen = qdisc_qlen_sum(qdisc); + + __gnet_stats_copy_basic(NULL, &sch->bstats, + qdisc->cpu_bstats, + &qdisc->bstats); + __gnet_stats_copy_queue(&sch->qstats, + qdisc->cpu_qstats, + &qdisc->qstats, qlen); + } else { + sch->q.qlen += qdisc->q.qlen; + sch->bstats.bytes += qdisc->bstats.bytes; + sch->bstats.packets += qdisc->bstats.packets; + sch->qstats.backlog += qdisc->qstats.backlog; + sch->qstats.drops += qdisc->qstats.drops; + sch->qstats.requeues += qdisc->qstats.requeues; + sch->qstats.overlimits += qdisc->qstats.overlimits; + } + spin_unlock_bh(qdisc_lock(qdisc)); } @@ -411,9 +430,9 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb) memcpy(opt.prio_tc_map, dev->prio_tc_map, sizeof(opt.prio_tc_map)); opt.hw = priv->hw_offload; - for (i = 0; i < netdev_get_num_tc(dev); i++) { - opt.count[i] = dev->tc_to_txq[i].count; - opt.offset[i] = dev->tc_to_txq[i].offset; + for (tc = 0; tc < netdev_get_num_tc(dev); tc++) { + opt.count[tc] = dev->tc_to_txq[tc].count; + opt.offset[tc] = dev->tc_to_txq[tc].offset; } if (nla_put(skb, TCA_OPTIONS, NLA_ALIGN(sizeof(opt)), &opt)) @@ -495,7 +514,6 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, if (cl >= TC_H_MIN_PRIORITY) { int i; __u32 qlen = 0; - struct Qdisc *qdisc; struct gnet_stats_queue qstats = {0}; struct gnet_stats_basic_packed bstats = {0}; struct net_device *dev = qdisc_dev(sch); @@ -511,18 +529,26 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, for (i = tc.offset; i < tc.offset + tc.count; i++) { struct netdev_queue *q = netdev_get_tx_queue(dev, i); + struct Qdisc *qdisc = rtnl_dereference(q->qdisc); + struct gnet_stats_basic_cpu __percpu *cpu_bstats = NULL; + struct gnet_stats_queue __percpu *cpu_qstats = NULL; - qdisc = rtnl_dereference(q->qdisc); spin_lock_bh(qdisc_lock(qdisc)); - qlen += qdisc->q.qlen; - bstats.bytes += qdisc->bstats.bytes; - bstats.packets += qdisc->bstats.packets; - qstats.backlog += qdisc->qstats.backlog; - qstats.drops += qdisc->qstats.drops; - qstats.requeues += qdisc->qstats.requeues; - qstats.overlimits += qdisc->qstats.overlimits; + if (qdisc_is_percpu_stats(qdisc)) { + cpu_bstats = qdisc->cpu_bstats; + cpu_qstats = qdisc->cpu_qstats; + } + + qlen = qdisc_qlen_sum(qdisc); + __gnet_stats_copy_basic(NULL, &sch->bstats, + cpu_bstats, &qdisc->bstats); + __gnet_stats_copy_queue(&sch->qstats, + cpu_qstats, + &qdisc->qstats, + qlen); spin_unlock_bh(qdisc_lock(qdisc)); } + /* Reclaim root sleeping lock before completing stats */ if (d->lock) spin_lock_bh(d->lock); diff --git a/net/sched/sch_multiq.c b/net/sched/sch_multiq.c index 012216386c0b..1da7ea8de0ad 100644 --- a/net/sched/sch_multiq.c +++ b/net/sched/sch_multiq.c @@ -180,7 +180,8 @@ multiq_destroy(struct Qdisc *sch) kfree(q->queues); } -static int multiq_tune(struct Qdisc *sch, struct nlattr *opt) +static int multiq_tune(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct multiq_sched_data *q = qdisc_priv(sch); struct tc_multiq_qopt *qopt; @@ -215,7 +216,7 @@ static int multiq_tune(struct Qdisc *sch, struct nlattr *opt) child = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, TC_H_MAKE(sch->handle, - i + 1)); + i + 1), extack); if (child) { sch_tree_lock(sch); old = q->queues[i]; @@ -236,17 +237,18 @@ static int multiq_tune(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int multiq_init(struct Qdisc *sch, struct nlattr *opt) +static int multiq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct multiq_sched_data *q = qdisc_priv(sch); int i, err; q->queues = NULL; - if (opt == NULL) + if (!opt) return -EINVAL; - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; @@ -258,7 +260,7 @@ static int multiq_init(struct Qdisc *sch, struct nlattr *opt) for (i = 0; i < q->max_bands; i++) q->queues[i] = &noop_qdisc; - return multiq_tune(sch, opt); + return multiq_tune(sch, opt, extack); } static int multiq_dump(struct Qdisc *sch, struct sk_buff *skb) @@ -281,7 +283,7 @@ nla_put_failure: } static int multiq_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct multiq_sched_data *q = qdisc_priv(sch); unsigned long band = arg - 1; @@ -369,7 +371,8 @@ static void multiq_walk(struct Qdisc *sch, struct qdisc_walker *arg) } } -static struct tcf_block *multiq_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *multiq_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct multiq_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index dd70924cbcdf..7bbc13b8ca47 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c @@ -893,7 +893,8 @@ static int parse_attr(struct nlattr *tb[], int maxtype, struct nlattr *nla, } /* Parse netlink message to set options */ -static int netem_change(struct Qdisc *sch, struct nlattr *opt) +static int netem_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct netem_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_NETEM_MAX + 1]; @@ -984,7 +985,8 @@ static int netem_change(struct Qdisc *sch, struct nlattr *opt) return ret; } -static int netem_init(struct Qdisc *sch, struct nlattr *opt) +static int netem_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct netem_sched_data *q = qdisc_priv(sch); int ret; @@ -995,7 +997,7 @@ static int netem_init(struct Qdisc *sch, struct nlattr *opt) return -EINVAL; q->loss_model = CLG_RANDOM; - ret = netem_change(sch, opt); + ret = netem_change(sch, opt, extack); if (ret) pr_info("netem: change failed\n"); return ret; @@ -1157,7 +1159,7 @@ static int netem_dump_class(struct Qdisc *sch, unsigned long cl, } static int netem_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct netem_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_pie.c b/net/sched/sch_pie.c index 776c694c77c7..18d30bb86881 100644 --- a/net/sched/sch_pie.c +++ b/net/sched/sch_pie.c @@ -181,7 +181,8 @@ static const struct nla_policy pie_policy[TCA_PIE_MAX + 1] = { [TCA_PIE_BYTEMODE] = {.type = NLA_U32}, }; -static int pie_change(struct Qdisc *sch, struct nlattr *opt) +static int pie_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct pie_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_PIE_MAX + 1]; @@ -439,7 +440,8 @@ static void pie_timer(struct timer_list *t) } -static int pie_init(struct Qdisc *sch, struct nlattr *opt) +static int pie_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct pie_sched_data *q = qdisc_priv(sch); @@ -451,7 +453,7 @@ static int pie_init(struct Qdisc *sch, struct nlattr *opt) timer_setup(&q->adapt_timer, pie_timer, 0); if (opt) { - int err = pie_change(sch, opt); + int err = pie_change(sch, opt, extack); if (err) return err; diff --git a/net/sched/sch_plug.c b/net/sched/sch_plug.c index 1c6cbab3e7b9..5619d2eb17b6 100644 --- a/net/sched/sch_plug.c +++ b/net/sched/sch_plug.c @@ -123,7 +123,8 @@ static struct sk_buff *plug_dequeue(struct Qdisc *sch) return qdisc_dequeue_head(sch); } -static int plug_init(struct Qdisc *sch, struct nlattr *opt) +static int plug_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct plug_sched_data *q = qdisc_priv(sch); @@ -158,7 +159,8 @@ static int plug_init(struct Qdisc *sch, struct nlattr *opt) * command is received (just act as a pass-thru queue). * TCQ_PLUG_LIMIT: Increase/decrease queue size */ -static int plug_change(struct Qdisc *sch, struct nlattr *opt) +static int plug_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct plug_sched_data *q = qdisc_priv(sch); struct tc_plug_qopt *msg; diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c index 2c79559a0d31..fe1510eb111f 100644 --- a/net/sched/sch_prio.c +++ b/net/sched/sch_prio.c @@ -153,7 +153,8 @@ prio_destroy(struct Qdisc *sch) qdisc_destroy(q->queues[prio]); } -static int prio_tune(struct Qdisc *sch, struct nlattr *opt) +static int prio_tune(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct prio_sched_data *q = qdisc_priv(sch); struct Qdisc *queues[TCQ_PRIO_BANDS]; @@ -175,7 +176,8 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt) /* Before commit, make sure we can allocate all new qdiscs */ for (i = oldbands; i < qopt->bands; i++) { queues[i] = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, - TC_H_MAKE(sch->handle, i + 1)); + TC_H_MAKE(sch->handle, i + 1), + extack); if (!queues[i]) { while (i > oldbands) qdisc_destroy(queues[--i]); @@ -205,7 +207,8 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int prio_init(struct Qdisc *sch, struct nlattr *opt) +static int prio_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct prio_sched_data *q = qdisc_priv(sch); int err; @@ -213,11 +216,11 @@ static int prio_init(struct Qdisc *sch, struct nlattr *opt) if (!opt) return -EINVAL; - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; - return prio_tune(sch, opt); + return prio_tune(sch, opt, extack); } static int prio_dump(struct Qdisc *sch, struct sk_buff *skb) @@ -240,7 +243,7 @@ nla_put_failure: } static int prio_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct prio_sched_data *q = qdisc_priv(sch); unsigned long band = arg - 1; @@ -327,7 +330,8 @@ static void prio_walk(struct Qdisc *sch, struct qdisc_walker *arg) } } -static struct tcf_block *prio_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *prio_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct prio_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index 6962b37a3ad3..bb1a9c11fc54 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -402,7 +402,8 @@ static int qfq_change_agg(struct Qdisc *sch, struct qfq_class *cl, u32 weight, } static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, - struct nlattr **tca, unsigned long *arg) + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) { struct qfq_sched *q = qdisc_priv(sch); struct qfq_class *cl = (struct qfq_class *)*arg; @@ -479,8 +480,8 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, cl->common.classid = classid; cl->deficit = lmax; - cl->qdisc = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, classid); + cl->qdisc = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + classid, NULL); if (cl->qdisc == NULL) cl->qdisc = &noop_qdisc; @@ -564,7 +565,8 @@ static unsigned long qfq_search_class(struct Qdisc *sch, u32 classid) return (unsigned long)qfq_find_class(sch, classid); } -static struct tcf_block *qfq_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *qfq_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct qfq_sched *q = qdisc_priv(sch); @@ -593,13 +595,14 @@ static void qfq_unbind_tcf(struct Qdisc *sch, unsigned long arg) } static int qfq_graft_class(struct Qdisc *sch, unsigned long arg, - struct Qdisc *new, struct Qdisc **old) + struct Qdisc *new, struct Qdisc **old, + struct netlink_ext_ack *extack) { struct qfq_class *cl = (struct qfq_class *)arg; if (new == NULL) { - new = qdisc_create_dflt(sch->dev_queue, - &pfifo_qdisc_ops, cl->common.classid); + new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, + cl->common.classid, NULL); if (new == NULL) new = &noop_qdisc; } @@ -1413,14 +1416,15 @@ static void qfq_qlen_notify(struct Qdisc *sch, unsigned long arg) qfq_deactivate_class(q, cl); } -static int qfq_init_qdisc(struct Qdisc *sch, struct nlattr *opt) +static int qfq_init_qdisc(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct qfq_sched *q = qdisc_priv(sch); struct qfq_group *grp; int i, j, err; u32 max_cl_shift, maxbudg_shift, max_classes; - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; diff --git a/net/sched/sch_red.c b/net/sched/sch_red.c index f0747eb87dc4..0af1c1254e0b 100644 --- a/net/sched/sch_red.c +++ b/net/sched/sch_red.c @@ -157,7 +157,6 @@ static int red_offload(struct Qdisc *sch, bool enable) .handle = sch->handle, .parent = sch->parent, }; - int err; if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) return -EOPNOTSUPP; @@ -172,14 +171,7 @@ static int red_offload(struct Qdisc *sch, bool enable) opt.command = TC_RED_DESTROY; } - err = dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, &opt); - - if (!err && enable) - sch->flags |= TCQ_F_OFFLOADED; - else - sch->flags &= ~TCQ_F_OFFLOADED; - - return err; + return dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, &opt); } static void red_destroy(struct Qdisc *sch) @@ -197,7 +189,8 @@ static const struct nla_policy red_policy[TCA_RED_MAX + 1] = { [TCA_RED_MAX_P] = { .type = NLA_U32 }, }; -static int red_change(struct Qdisc *sch, struct nlattr *opt) +static int red_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct red_sched_data *q = qdisc_priv(sch); struct nlattr *tb[TCA_RED_MAX + 1]; @@ -224,7 +217,8 @@ static int red_change(struct Qdisc *sch, struct nlattr *opt) return -EINVAL; if (ctl->limit > 0) { - child = fifo_create_dflt(sch, &bfifo_qdisc_ops, ctl->limit); + child = fifo_create_dflt(sch, &bfifo_qdisc_ops, ctl->limit, + extack); if (IS_ERR(child)) return PTR_ERR(child); } @@ -272,14 +266,15 @@ static inline void red_adaptative_timer(struct timer_list *t) spin_unlock(root_lock); } -static int red_init(struct Qdisc *sch, struct nlattr *opt) +static int red_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct red_sched_data *q = qdisc_priv(sch); q->qdisc = &noop_qdisc; q->sch = sch; timer_setup(&q->adapt_timer, red_adaptative_timer, 0); - return red_change(sch, opt); + return red_change(sch, opt, extack); } static int red_dump_offload_stats(struct Qdisc *sch, struct tc_red_qopt *opt) @@ -294,12 +289,22 @@ static int red_dump_offload_stats(struct Qdisc *sch, struct tc_red_qopt *opt) .stats.qstats = &sch->qstats, }, }; + int err; - if (!(sch->flags & TCQ_F_OFFLOADED)) + sch->flags &= ~TCQ_F_OFFLOADED; + + if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) return 0; - return dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, - &hw_stats); + err = dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, + &hw_stats); + if (err == -EOPNOTSUPP) + return 0; + + if (!err) + sch->flags |= TCQ_F_OFFLOADED; + + return err; } static int red_dump(struct Qdisc *sch, struct sk_buff *skb) @@ -339,32 +344,24 @@ static int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d) { struct red_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); - struct tc_red_xstats st = { - .early = q->stats.prob_drop + q->stats.forced_drop, - .pdrop = q->stats.pdrop, - .other = q->stats.other, - .marked = q->stats.prob_mark + q->stats.forced_mark, - }; + struct tc_red_xstats st = {0}; if (sch->flags & TCQ_F_OFFLOADED) { - struct red_stats hw_stats = {0}; struct tc_red_qopt_offload hw_stats_request = { .command = TC_RED_XSTATS, .handle = sch->handle, .parent = sch->parent, { - .xstats = &hw_stats, + .xstats = &q->stats, }, }; - if (!dev->netdev_ops->ndo_setup_tc(dev, - TC_SETUP_QDISC_RED, - &hw_stats_request)) { - st.early += hw_stats.prob_drop + hw_stats.forced_drop; - st.pdrop += hw_stats.pdrop; - st.other += hw_stats.other; - st.marked += hw_stats.prob_mark + hw_stats.forced_mark; - } + dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, + &hw_stats_request); } + st.early = q->stats.prob_drop + q->stats.forced_drop; + st.pdrop = q->stats.pdrop; + st.other = q->stats.other; + st.marked = q->stats.prob_mark + q->stats.forced_mark; return gnet_stats_copy_app(d, &st, sizeof(st)); } @@ -380,7 +377,7 @@ static int red_dump_class(struct Qdisc *sch, unsigned long cl, } static int red_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct red_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_sfb.c b/net/sched/sch_sfb.c index 0678debdd856..7cbdad8419b7 100644 --- a/net/sched/sch_sfb.c +++ b/net/sched/sch_sfb.c @@ -488,7 +488,8 @@ static const struct tc_sfb_qopt sfb_default_ops = { .penalty_burst = 20, }; -static int sfb_change(struct Qdisc *sch, struct nlattr *opt) +static int sfb_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct sfb_sched_data *q = qdisc_priv(sch); struct Qdisc *child; @@ -512,7 +513,7 @@ static int sfb_change(struct Qdisc *sch, struct nlattr *opt) if (limit == 0) limit = qdisc_dev(sch)->tx_queue_len; - child = fifo_create_dflt(sch, &pfifo_qdisc_ops, limit); + child = fifo_create_dflt(sch, &pfifo_qdisc_ops, limit, extack); if (IS_ERR(child)) return PTR_ERR(child); @@ -549,17 +550,18 @@ static int sfb_change(struct Qdisc *sch, struct nlattr *opt) return 0; } -static int sfb_init(struct Qdisc *sch, struct nlattr *opt) +static int sfb_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct sfb_sched_data *q = qdisc_priv(sch); int err; - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; q->qdisc = &noop_qdisc; - return sfb_change(sch, opt); + return sfb_change(sch, opt, extack); } static int sfb_dump(struct Qdisc *sch, struct sk_buff *skb) @@ -615,7 +617,7 @@ static int sfb_dump_class(struct Qdisc *sch, unsigned long cl, } static int sfb_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct sfb_sched_data *q = qdisc_priv(sch); @@ -643,7 +645,8 @@ static void sfb_unbind(struct Qdisc *sch, unsigned long arg) } static int sfb_change_class(struct Qdisc *sch, u32 classid, u32 parentid, - struct nlattr **tca, unsigned long *arg) + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) { return -ENOSYS; } @@ -665,7 +668,8 @@ static void sfb_walk(struct Qdisc *sch, struct qdisc_walker *walker) } } -static struct tcf_block *sfb_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *sfb_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct sfb_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index 930e5bd26d3d..2f2678197760 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -721,7 +721,8 @@ static void sfq_destroy(struct Qdisc *sch) kfree(q->red_parms); } -static int sfq_init(struct Qdisc *sch, struct nlattr *opt) +static int sfq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct sfq_sched_data *q = qdisc_priv(sch); int i; @@ -730,7 +731,7 @@ static int sfq_init(struct Qdisc *sch, struct nlattr *opt) q->sch = sch; timer_setup(&q->perturb_timer, sfq_perturbation, TIMER_DEFERRABLE); - err = tcf_block_get(&q->block, &q->filter_list, sch); + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); if (err) return err; @@ -836,7 +837,8 @@ static void sfq_unbind(struct Qdisc *q, unsigned long cl) { } -static struct tcf_block *sfq_tcf_block(struct Qdisc *sch, unsigned long cl) +static struct tcf_block *sfq_tcf_block(struct Qdisc *sch, unsigned long cl, + struct netlink_ext_ack *extack) { struct sfq_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_tbf.c b/net/sched/sch_tbf.c index 120f4f365967..83e76d046993 100644 --- a/net/sched/sch_tbf.c +++ b/net/sched/sch_tbf.c @@ -302,7 +302,8 @@ static const struct nla_policy tbf_policy[TCA_TBF_MAX + 1] = { [TCA_TBF_PBURST] = { .type = NLA_U32 }, }; -static int tbf_change(struct Qdisc *sch, struct nlattr *opt) +static int tbf_change(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { int err; struct tbf_sched_data *q = qdisc_priv(sch); @@ -326,11 +327,13 @@ static int tbf_change(struct Qdisc *sch, struct nlattr *opt) qopt = nla_data(tb[TCA_TBF_PARMS]); if (qopt->rate.linklayer == TC_LINKLAYER_UNAWARE) qdisc_put_rtab(qdisc_get_rtab(&qopt->rate, - tb[TCA_TBF_RTAB])); + tb[TCA_TBF_RTAB], + NULL)); if (qopt->peakrate.linklayer == TC_LINKLAYER_UNAWARE) qdisc_put_rtab(qdisc_get_rtab(&qopt->peakrate, - tb[TCA_TBF_PTAB])); + tb[TCA_TBF_PTAB], + NULL)); buffer = min_t(u64, PSCHED_TICKS2NS(qopt->buffer), ~0U); mtu = min_t(u64, PSCHED_TICKS2NS(qopt->mtu), ~0U); @@ -383,7 +386,8 @@ static int tbf_change(struct Qdisc *sch, struct nlattr *opt) if (err) goto done; } else if (qopt->limit > 0) { - child = fifo_create_dflt(sch, &bfifo_qdisc_ops, qopt->limit); + child = fifo_create_dflt(sch, &bfifo_qdisc_ops, qopt->limit, + extack); if (IS_ERR(child)) { err = PTR_ERR(child); goto done; @@ -421,19 +425,20 @@ done: return err; } -static int tbf_init(struct Qdisc *sch, struct nlattr *opt) +static int tbf_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct tbf_sched_data *q = qdisc_priv(sch); qdisc_watchdog_init(&q->watchdog, sch); q->qdisc = &noop_qdisc; - if (opt == NULL) + if (!opt) return -EINVAL; q->t_c = ktime_get_ns(); - return tbf_change(sch, opt); + return tbf_change(sch, opt, extack); } static void tbf_destroy(struct Qdisc *sch) @@ -494,7 +499,7 @@ static int tbf_dump_class(struct Qdisc *sch, unsigned long cl, } static int tbf_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, - struct Qdisc **old) + struct Qdisc **old, struct netlink_ext_ack *extack) { struct tbf_sched_data *q = qdisc_priv(sch); diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 9fe6b427afed..93f04cf5cac1 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -167,7 +167,8 @@ teql_destroy(struct Qdisc *sch) } } -static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt) +static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) { struct net_device *dev = qdisc_dev(sch); struct teql_master *m = (struct teql_master *)sch->ops; |