Merge branches 'acpi-cleanup', 'acpi-thermal', 'acpi-video' and 'acpi-dock'
[firefly-linux-kernel-4.4.55.git] / net / sched / act_api.c
index 72bdc71663458e8cc131ab392b6af7673f35207d..8a5ba5add4bcd60e59a9b2468df88812212012f4 100644 (file)
 #include <net/act_api.h>
 #include <net/netlink.h>
 
-void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
+void tcf_hash_destroy(struct tc_action *a)
 {
+       struct tcf_common *p = a->priv;
+       struct tcf_hashinfo *hinfo = a->ops->hinfo;
+
        spin_lock_bh(&hinfo->lock);
        hlist_del(&p->tcfc_head);
        spin_unlock_bh(&hinfo->lock);
@@ -42,18 +45,22 @@ void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
 }
 EXPORT_SYMBOL(tcf_hash_destroy);
 
-int tcf_hash_release(struct tcf_common *p, int bind,
-                    struct tcf_hashinfo *hinfo)
+int tcf_hash_release(struct tc_action *a, int bind)
 {
+       struct tcf_common *p = a->priv;
        int ret = 0;
 
        if (p) {
                if (bind)
                        p->tcfc_bindcnt--;
+               else if (p->tcfc_bindcnt > 0)
+                       return -EPERM;
 
                p->tcfc_refcnt--;
                if (p->tcfc_bindcnt <= 0 && p->tcfc_refcnt <= 0) {
-                       tcf_hash_destroy(p, hinfo);
+                       if (a->ops->cleanup)
+                               a->ops->cleanup(a, bind);
+                       tcf_hash_destroy(a);
                        ret = 1;
                }
        }
@@ -118,6 +125,7 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a)
        struct tcf_common *p;
        struct nlattr *nest;
        int i = 0, n_i = 0;
+       int ret = -EINVAL;
 
        nest = nla_nest_start(skb, a->order);
        if (nest == NULL)
@@ -127,10 +135,13 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a)
        for (i = 0; i < (hinfo->hmask + 1); i++) {
                head = &hinfo->htab[tcf_hash(i, hinfo->hmask)];
                hlist_for_each_entry_safe(p, n, head, tcfc_head) {
-                       if (ACT_P_DELETED == tcf_hash_release(p, 0, hinfo)) {
+                       a->priv = p;
+                       ret = tcf_hash_release(a, 0);
+                       if (ret == ACT_P_DELETED) {
                                module_put(a->ops->owner);
                                n_i++;
-                       }
+                       } else if (ret < 0)
+                               goto nla_put_failure;
                }
        }
        if (nla_put_u32(skb, TCA_FCNT, n_i))
@@ -140,7 +151,7 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a)
        return n_i;
 nla_put_failure:
        nla_nest_cancel(skb, nest);
-       return -EINVAL;
+       return ret;
 }
 
 static int tcf_generic_walker(struct sk_buff *skb, struct netlink_callback *cb,
@@ -198,7 +209,7 @@ int tcf_hash_search(struct tc_action *a, u32 index)
 }
 EXPORT_SYMBOL(tcf_hash_search);
 
-struct tcf_common *tcf_hash_check(u32 index, struct tc_action *a, int bind)
+int tcf_hash_check(u32 index, struct tc_action *a, int bind)
 {
        struct tcf_hashinfo *hinfo = a->ops->hinfo;
        struct tcf_common *p = NULL;
@@ -207,19 +218,30 @@ struct tcf_common *tcf_hash_check(u32 index, struct tc_action *a, int bind)
                        p->tcfc_bindcnt++;
                p->tcfc_refcnt++;
                a->priv = p;
+               return 1;
        }
-       return p;
+       return 0;
 }
 EXPORT_SYMBOL(tcf_hash_check);
 
-struct tcf_common *tcf_hash_create(u32 index, struct nlattr *est,
-                                  struct tc_action *a, int size, int bind)
+void tcf_hash_cleanup(struct tc_action *a, struct nlattr *est)
+{
+       struct tcf_common *pc = a->priv;
+       if (est)
+               gen_kill_estimator(&pc->tcfc_bstats,
+                                  &pc->tcfc_rate_est);
+       kfree_rcu(pc, tcfc_rcu);
+}
+EXPORT_SYMBOL(tcf_hash_cleanup);
+
+int tcf_hash_create(u32 index, struct nlattr *est, struct tc_action *a,
+                   int size, int bind)
 {
        struct tcf_hashinfo *hinfo = a->ops->hinfo;
        struct tcf_common *p = kzalloc(size, GFP_KERNEL);
 
        if (unlikely(!p))
-               return ERR_PTR(-ENOMEM);
+               return -ENOMEM;
        p->tcfc_refcnt = 1;
        if (bind)
                p->tcfc_bindcnt = 1;
@@ -234,17 +256,19 @@ struct tcf_common *tcf_hash_create(u32 index, struct nlattr *est,
                                            &p->tcfc_lock, est);
                if (err) {
                        kfree(p);
-                       return ERR_PTR(err);
+                       return err;
                }
        }
 
        a->priv = (void *) p;
-       return p;
+       return 0;
 }
 EXPORT_SYMBOL(tcf_hash_create);
 
-void tcf_hash_insert(struct tcf_common *p, struct tcf_hashinfo *hinfo)
+void tcf_hash_insert(struct tc_action *a)
 {
+       struct tcf_common *p = a->priv;
+       struct tcf_hashinfo *hinfo = a->ops->hinfo;
        unsigned int h = tcf_hash(p->tcfc_index, hinfo->hmask);
 
        spin_lock_bh(&hinfo->lock);
@@ -256,12 +280,13 @@ EXPORT_SYMBOL(tcf_hash_insert);
 static LIST_HEAD(act_base);
 static DEFINE_RWLOCK(act_mod_lock);
 
-int tcf_register_action(struct tc_action_ops *act)
+int tcf_register_action(struct tc_action_ops *act, unsigned int mask)
 {
        struct tc_action_ops *a;
+       int err;
 
-       /* Must supply act, dump, cleanup and init */
-       if (!act->act || !act->dump || !act->cleanup || !act->init)
+       /* Must supply act, dump and init */
+       if (!act->act || !act->dump || !act->init)
                return -EINVAL;
 
        /* Supply defaults */
@@ -270,10 +295,21 @@ int tcf_register_action(struct tc_action_ops *act)
        if (!act->walk)
                act->walk = tcf_generic_walker;
 
+       act->hinfo = kmalloc(sizeof(struct tcf_hashinfo), GFP_KERNEL);
+       if (!act->hinfo)
+               return -ENOMEM;
+       err = tcf_hashinfo_init(act->hinfo, mask);
+       if (err) {
+               kfree(act->hinfo);
+               return err;
+       }
+
        write_lock(&act_mod_lock);
        list_for_each_entry(a, &act_base, head) {
                if (act->type == a->type || (strcmp(act->kind, a->kind) == 0)) {
                        write_unlock(&act_mod_lock);
+                       tcf_hashinfo_destroy(act->hinfo);
+                       kfree(act->hinfo);
                        return -EEXIST;
                }
        }
@@ -292,6 +328,8 @@ int tcf_unregister_action(struct tc_action_ops *act)
        list_for_each_entry(a, &act_base, head) {
                if (a == act) {
                        list_del(&act->head);
+                       tcf_hashinfo_destroy(act->hinfo);
+                       kfree(act->hinfo);
                        err = 0;
                        break;
                }
@@ -368,16 +406,21 @@ exec_done:
 }
 EXPORT_SYMBOL(tcf_action_exec);
 
-void tcf_action_destroy(struct list_head *actions, int bind)
+int tcf_action_destroy(struct list_head *actions, int bind)
 {
        struct tc_action *a, *tmp;
+       int ret = 0;
 
        list_for_each_entry_safe(a, tmp, actions, list) {
-               if (a->ops->cleanup(a, bind) == ACT_P_DELETED)
+               ret = tcf_hash_release(a, bind);
+               if (ret == ACT_P_DELETED)
                        module_put(a->ops->owner);
+               else if (ret < 0)
+                       return ret;
                list_del(&a->list);
                kfree(a);
        }
+       return ret;
 }
 
 int
@@ -642,6 +685,20 @@ act_get_notify(struct net *net, u32 portid, struct nlmsghdr *n,
        return rtnl_unicast(skb, net, portid);
 }
 
+static struct tc_action *create_a(int i)
+{
+       struct tc_action *act;
+
+       act = kzalloc(sizeof(*act), GFP_KERNEL);
+       if (act == NULL) {
+               pr_debug("create_a: failed to alloc!\n");
+               return NULL;
+       }
+       act->order = i;
+       INIT_LIST_HEAD(&act->list);
+       return act;
+}
+
 static struct tc_action *
 tcf_action_get_1(struct nlattr *nla, struct nlmsghdr *n, u32 portid)
 {
@@ -661,11 +718,10 @@ tcf_action_get_1(struct nlattr *nla, struct nlmsghdr *n, u32 portid)
        index = nla_get_u32(tb[TCA_ACT_INDEX]);
 
        err = -ENOMEM;
-       a = kzalloc(sizeof(struct tc_action), GFP_KERNEL);
+       a = create_a(0);
        if (a == NULL)
                goto err_out;
 
-       INIT_LIST_HEAD(&a->list);
        err = -EINVAL;
        a->ops = tc_lookup_action(tb[TCA_ACT_KIND]);
        if (a->ops == NULL) /* could happen in batch of actions */
@@ -695,20 +751,6 @@ static void cleanup_a(struct list_head *actions)
        }
 }
 
-static struct tc_action *create_a(int i)
-{
-       struct tc_action *act;
-
-       act = kzalloc(sizeof(*act), GFP_KERNEL);
-       if (act == NULL) {
-               pr_debug("create_a: failed to alloc!\n");
-               return NULL;
-       }
-       act->order = i;
-       INIT_LIST_HEAD(&act->list);
-       return act;
-}
-
 static int tca_action_flush(struct net *net, struct nlattr *nla,
                            struct nlmsghdr *n, u32 portid)
 {
@@ -720,18 +762,12 @@ static int tca_action_flush(struct net *net, struct nlattr *nla,
        struct nlattr *nest;
        struct nlattr *tb[TCA_ACT_MAX + 1];
        struct nlattr *kind;
-       struct tc_action *a = create_a(0);
+       struct tc_action a;
        int err = -ENOMEM;
 
-       if (a == NULL) {
-               pr_debug("tca_action_flush: couldnt create tc_action\n");
-               return err;
-       }
-
        skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
        if (!skb) {
                pr_debug("tca_action_flush: failed skb alloc\n");
-               kfree(a);
                return err;
        }
 
@@ -743,8 +779,10 @@ static int tca_action_flush(struct net *net, struct nlattr *nla,
 
        err = -EINVAL;
        kind = tb[TCA_ACT_KIND];
-       a->ops = tc_lookup_action(kind);
-       if (a->ops == NULL) /*some idjot trying to flush unknown action */
+       memset(&a, 0, sizeof(struct tc_action));
+       INIT_LIST_HEAD(&a.list);
+       a.ops = tc_lookup_action(kind);
+       if (a.ops == NULL) /*some idjot trying to flush unknown action */
                goto err_out;
 
        nlh = nlmsg_put(skb, portid, n->nlmsg_seq, RTM_DELACTION, sizeof(*t), 0);
@@ -759,7 +797,7 @@ static int tca_action_flush(struct net *net, struct nlattr *nla,
        if (nest == NULL)
                goto out_module_put;
 
-       err = a->ops->walk(skb, &dcb, RTM_DELACTION, a);
+       err = a.ops->walk(skb, &dcb, RTM_DELACTION, &a);
        if (err < 0)
                goto out_module_put;
        if (err == 0)
@@ -769,8 +807,7 @@ static int tca_action_flush(struct net *net, struct nlattr *nla,
 
        nlh->nlmsg_len = skb_tail_pointer(skb) - b;
        nlh->nlmsg_flags |= NLM_F_ROOT;
-       module_put(a->ops->owner);
-       kfree(a);
+       module_put(a.ops->owner);
        err = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
                             n->nlmsg_flags & NLM_F_ECHO);
        if (err > 0)
@@ -779,11 +816,10 @@ static int tca_action_flush(struct net *net, struct nlattr *nla,
        return err;
 
 out_module_put:
-       module_put(a->ops->owner);
+       module_put(a.ops->owner);
 err_out:
 noflush_out:
        kfree_skb(skb);
-       kfree(a);
        return err;
 }
 
@@ -805,7 +841,11 @@ tcf_del_notify(struct net *net, struct nlmsghdr *n, struct list_head *actions,
        }
 
        /* now do the delete */
-       tcf_action_destroy(actions, 0);
+       ret = tcf_action_destroy(actions, 0);
+       if (ret < 0) {
+               kfree_skb(skb);
+               return ret;
+       }
 
        ret = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
                             n->nlmsg_flags & NLM_F_ECHO);