diff options
Diffstat (limited to 'net/switchdev/switchdev.c')
-rw-r--r-- | net/switchdev/switchdev.c | 299 |
1 files changed, 183 insertions, 116 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c index fda38f830a10..fe82fab1d55c 100644 --- a/net/switchdev/switchdev.c +++ b/net/switchdev/switchdev.c @@ -1,6 +1,6 @@ /* * net/switchdev/switchdev.c - Switch device API - * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us> + * Copyright (c) 2014-2015 Jiri Pirko <jiri@resnulli.us> * Copyright (c) 2014-2015 Scott Feldman <sfeldma@gmail.com> * * This program is free software; you can redistribute it and/or modify @@ -16,10 +16,83 @@ #include <linux/notifier.h> #include <linux/netdevice.h> #include <linux/if_bridge.h> +#include <linux/list.h> #include <net/ip_fib.h> #include <net/switchdev.h> /** + * switchdev_trans_item_enqueue - Enqueue data item to transaction queue + * + * @trans: transaction + * @data: pointer to data being queued + * @destructor: data destructor + * @tritem: transaction item being queued + * + * Enqeueue data item to transaction queue. tritem is typically placed in + * cointainter pointed at by data pointer. Destructor is called on + * transaction abort and after successful commit phase in case + * the caller did not dequeue the item before. + */ +void switchdev_trans_item_enqueue(struct switchdev_trans *trans, + void *data, void (*destructor)(void const *), + struct switchdev_trans_item *tritem) +{ + tritem->data = data; + tritem->destructor = destructor; + list_add_tail(&tritem->list, &trans->item_list); +} +EXPORT_SYMBOL_GPL(switchdev_trans_item_enqueue); + +static struct switchdev_trans_item * +__switchdev_trans_item_dequeue(struct switchdev_trans *trans) +{ + struct switchdev_trans_item *tritem; + + if (list_empty(&trans->item_list)) + return NULL; + tritem = list_first_entry(&trans->item_list, + struct switchdev_trans_item, list); + list_del(&tritem->list); + return tritem; +} + +/** + * switchdev_trans_item_dequeue - Dequeue data item from transaction queue + * + * @trans: transaction + */ +void *switchdev_trans_item_dequeue(struct switchdev_trans *trans) +{ + struct switchdev_trans_item *tritem; + + tritem = __switchdev_trans_item_dequeue(trans); + BUG_ON(!tritem); + return tritem->data; +} +EXPORT_SYMBOL_GPL(switchdev_trans_item_dequeue); + +static void switchdev_trans_init(struct switchdev_trans *trans) +{ + INIT_LIST_HEAD(&trans->item_list); +} + +static void switchdev_trans_items_destroy(struct switchdev_trans *trans) +{ + struct switchdev_trans_item *tritem; + + while ((tritem = __switchdev_trans_item_dequeue(trans))) + tritem->destructor(tritem->data); +} + +static void switchdev_trans_items_warn_destroy(struct net_device *dev, + struct switchdev_trans *trans) +{ + WARN(!list_empty(&trans->item_list), "%s: transaction item queue is not empty.\n", + dev->name); + switchdev_trans_items_destroy(trans); +} + +/** * switchdev_port_attr_get - Get port attribute * * @dev: port device @@ -62,7 +135,8 @@ int switchdev_port_attr_get(struct net_device *dev, struct switchdev_attr *attr) EXPORT_SYMBOL_GPL(switchdev_port_attr_get); static int __switchdev_port_attr_set(struct net_device *dev, - struct switchdev_attr *attr) + struct switchdev_attr *attr, + struct switchdev_trans *trans) { const struct switchdev_ops *ops = dev->switchdev_ops; struct net_device *lower_dev; @@ -70,7 +144,7 @@ static int __switchdev_port_attr_set(struct net_device *dev, int err = -EOPNOTSUPP; if (ops && ops->switchdev_port_attr_set) - return ops->switchdev_port_attr_set(dev, attr); + return ops->switchdev_port_attr_set(dev, attr, trans); if (attr->flags & SWITCHDEV_F_NO_RECURSE) return err; @@ -81,7 +155,7 @@ static int __switchdev_port_attr_set(struct net_device *dev, */ netdev_for_each_lower_dev(dev, lower_dev, iter) { - err = __switchdev_port_attr_set(lower_dev, attr); + err = __switchdev_port_attr_set(lower_dev, attr, trans); if (err) break; } @@ -144,6 +218,7 @@ static int switchdev_port_attr_set_defer(struct net_device *dev, */ int switchdev_port_attr_set(struct net_device *dev, struct switchdev_attr *attr) { + struct switchdev_trans trans; int err; if (!rtnl_is_locked()) { @@ -156,6 +231,8 @@ int switchdev_port_attr_set(struct net_device *dev, struct switchdev_attr *attr) return switchdev_port_attr_set_defer(dev, attr); } + switchdev_trans_init(&trans); + /* Phase I: prepare for attr set. Driver/device should fail * here if there are going to be issues in the commit phase, * such as lack of resources or support. The driver/device @@ -163,18 +240,16 @@ int switchdev_port_attr_set(struct net_device *dev, struct switchdev_attr *attr) * but should not commit the attr. */ - attr->trans = SWITCHDEV_TRANS_PREPARE; - err = __switchdev_port_attr_set(dev, attr); + trans.ph_prepare = true; + err = __switchdev_port_attr_set(dev, attr, &trans); if (err) { /* Prepare phase failed: abort the transaction. Any * resources reserved in the prepare phase are * released. */ - if (err != -EOPNOTSUPP) { - attr->trans = SWITCHDEV_TRANS_ABORT; - __switchdev_port_attr_set(dev, attr); - } + if (err != -EOPNOTSUPP) + switchdev_trans_items_destroy(&trans); return err; } @@ -184,17 +259,19 @@ int switchdev_port_attr_set(struct net_device *dev, struct switchdev_attr *attr) * because the driver said everythings was OK in phase I. */ - attr->trans = SWITCHDEV_TRANS_COMMIT; - err = __switchdev_port_attr_set(dev, attr); + trans.ph_prepare = false; + err = __switchdev_port_attr_set(dev, attr, &trans); WARN(err, "%s: Commit of attribute (id=%d) failed.\n", dev->name, attr->id); + switchdev_trans_items_warn_destroy(dev, &trans); return err; } EXPORT_SYMBOL_GPL(switchdev_port_attr_set); static int __switchdev_port_obj_add(struct net_device *dev, - struct switchdev_obj *obj) + enum switchdev_obj_id id, const void *obj, + struct switchdev_trans *trans) { const struct switchdev_ops *ops = dev->switchdev_ops; struct net_device *lower_dev; @@ -202,7 +279,7 @@ static int __switchdev_port_obj_add(struct net_device *dev, int err = -EOPNOTSUPP; if (ops && ops->switchdev_port_obj_add) - return ops->switchdev_port_obj_add(dev, obj); + return ops->switchdev_port_obj_add(dev, id, obj, trans); /* Switch device port(s) may be stacked under * bond/team/vlan dev, so recurse down to add object on @@ -210,7 +287,7 @@ static int __switchdev_port_obj_add(struct net_device *dev, */ netdev_for_each_lower_dev(dev, lower_dev, iter) { - err = __switchdev_port_obj_add(lower_dev, obj); + err = __switchdev_port_obj_add(lower_dev, id, obj, trans); if (err) break; } @@ -222,6 +299,7 @@ static int __switchdev_port_obj_add(struct net_device *dev, * switchdev_port_obj_add - Add port object * * @dev: port device + * @id: object ID * @obj: object to add * * Use a 2-phase prepare-commit transaction model to ensure @@ -230,12 +308,16 @@ static int __switchdev_port_obj_add(struct net_device *dev, * * rtnl_lock must be held. */ -int switchdev_port_obj_add(struct net_device *dev, struct switchdev_obj *obj) +int switchdev_port_obj_add(struct net_device *dev, enum switchdev_obj_id id, + const void *obj) { + struct switchdev_trans trans; int err; ASSERT_RTNL(); + switchdev_trans_init(&trans); + /* Phase I: prepare for obj add. Driver/device should fail * here if there are going to be issues in the commit phase, * such as lack of resources or support. The driver/device @@ -243,18 +325,16 @@ int switchdev_port_obj_add(struct net_device *dev, struct switchdev_obj *obj) * but should not commit the obj. */ - obj->trans = SWITCHDEV_TRANS_PREPARE; - err = __switchdev_port_obj_add(dev, obj); + trans.ph_prepare = true; + err = __switchdev_port_obj_add(dev, id, obj, &trans); if (err) { /* Prepare phase failed: abort the transaction. Any * resources reserved in the prepare phase are * released. */ - if (err != -EOPNOTSUPP) { - obj->trans = SWITCHDEV_TRANS_ABORT; - __switchdev_port_obj_add(dev, obj); - } + if (err != -EOPNOTSUPP) + switchdev_trans_items_destroy(&trans); return err; } @@ -264,9 +344,10 @@ int switchdev_port_obj_add(struct net_device *dev, struct switchdev_obj *obj) * because the driver said everythings was OK in phase I. */ - obj->trans = SWITCHDEV_TRANS_COMMIT; - err = __switchdev_port_obj_add(dev, obj); - WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id); + trans.ph_prepare = false; + err = __switchdev_port_obj_add(dev, id, obj, &trans); + WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, id); + switchdev_trans_items_warn_destroy(dev, &trans); return err; } @@ -276,9 +357,11 @@ EXPORT_SYMBOL_GPL(switchdev_port_obj_add); * switchdev_port_obj_del - Delete port object * * @dev: port device + * @id: object ID * @obj: object to delete */ -int switchdev_port_obj_del(struct net_device *dev, struct switchdev_obj *obj) +int switchdev_port_obj_del(struct net_device *dev, enum switchdev_obj_id id, + const void *obj) { const struct switchdev_ops *ops = dev->switchdev_ops; struct net_device *lower_dev; @@ -286,7 +369,7 @@ int switchdev_port_obj_del(struct net_device *dev, struct switchdev_obj *obj) int err = -EOPNOTSUPP; if (ops && ops->switchdev_port_obj_del) - return ops->switchdev_port_obj_del(dev, obj); + return ops->switchdev_port_obj_del(dev, id, obj); /* Switch device port(s) may be stacked under * bond/team/vlan dev, so recurse down to delete object on @@ -294,7 +377,7 @@ int switchdev_port_obj_del(struct net_device *dev, struct switchdev_obj *obj) */ netdev_for_each_lower_dev(dev, lower_dev, iter) { - err = switchdev_port_obj_del(lower_dev, obj); + err = switchdev_port_obj_del(lower_dev, id, obj); if (err) break; } @@ -307,9 +390,12 @@ EXPORT_SYMBOL_GPL(switchdev_port_obj_del); * switchdev_port_obj_dump - Dump port objects * * @dev: port device + * @id: object ID * @obj: object to dump + * @cb: function to call with a filled object */ -int switchdev_port_obj_dump(struct net_device *dev, struct switchdev_obj *obj) +int switchdev_port_obj_dump(struct net_device *dev, enum switchdev_obj_id id, + void *obj, int (*cb)(void *obj)) { const struct switchdev_ops *ops = dev->switchdev_ops; struct net_device *lower_dev; @@ -317,7 +403,7 @@ int switchdev_port_obj_dump(struct net_device *dev, struct switchdev_obj *obj) int err = -EOPNOTSUPP; if (ops && ops->switchdev_port_obj_dump) - return ops->switchdev_port_obj_dump(dev, obj); + return ops->switchdev_port_obj_dump(dev, id, obj, cb); /* Switch device port(s) may be stacked under * bond/team/vlan dev, so recurse down to dump objects on @@ -325,7 +411,7 @@ int switchdev_port_obj_dump(struct net_device *dev, struct switchdev_obj *obj) */ netdev_for_each_lower_dev(dev, lower_dev, iter) { - err = switchdev_port_obj_dump(lower_dev, obj); + err = switchdev_port_obj_dump(lower_dev, id, obj, cb); break; } @@ -397,7 +483,7 @@ int call_switchdev_notifiers(unsigned long val, struct net_device *dev, EXPORT_SYMBOL_GPL(call_switchdev_notifiers); struct switchdev_vlan_dump { - struct switchdev_obj obj; + struct switchdev_obj_vlan vlan; struct sk_buff *skb; u32 filter_mask; u16 flags; @@ -405,8 +491,7 @@ struct switchdev_vlan_dump { u16 end; }; -static int switchdev_port_vlan_dump_put(struct net_device *dev, - struct switchdev_vlan_dump *dump) +static int switchdev_port_vlan_dump_put(struct switchdev_vlan_dump *dump) { struct bridge_vlan_info vinfo; @@ -436,12 +521,11 @@ static int switchdev_port_vlan_dump_put(struct net_device *dev, return 0; } -static int switchdev_port_vlan_dump_cb(struct net_device *dev, - struct switchdev_obj *obj) +static int switchdev_port_vlan_dump_cb(void *obj) { + struct switchdev_obj_vlan *vlan = obj; struct switchdev_vlan_dump *dump = - container_of(obj, struct switchdev_vlan_dump, obj); - struct switchdev_obj_vlan *vlan = &dump->obj.u.vlan; + container_of(vlan, struct switchdev_vlan_dump, vlan); int err = 0; if (vlan->vid_begin > vlan->vid_end) @@ -452,7 +536,7 @@ static int switchdev_port_vlan_dump_cb(struct net_device *dev, for (dump->begin = dump->end = vlan->vid_begin; dump->begin <= vlan->vid_end; dump->begin++, dump->end++) { - err = switchdev_port_vlan_dump_put(dev, dump); + err = switchdev_port_vlan_dump_put(dump); if (err) return err; } @@ -464,7 +548,7 @@ static int switchdev_port_vlan_dump_cb(struct net_device *dev, /* prepend */ dump->begin = vlan->vid_begin; } else { - err = switchdev_port_vlan_dump_put(dev, dump); + err = switchdev_port_vlan_dump_put(dump); dump->flags = vlan->flags; dump->begin = vlan->vid_begin; dump->end = vlan->vid_end; @@ -476,7 +560,7 @@ static int switchdev_port_vlan_dump_cb(struct net_device *dev, /* append */ dump->end = vlan->vid_end; } else { - err = switchdev_port_vlan_dump_put(dev, dump); + err = switchdev_port_vlan_dump_put(dump); dump->flags = vlan->flags; dump->begin = vlan->vid_begin; dump->end = vlan->vid_end; @@ -493,10 +577,6 @@ static int switchdev_port_vlan_fill(struct sk_buff *skb, struct net_device *dev, u32 filter_mask) { struct switchdev_vlan_dump dump = { - .obj = { - .id = SWITCHDEV_OBJ_PORT_VLAN, - .cb = switchdev_port_vlan_dump_cb, - }, .skb = skb, .filter_mask = filter_mask, }; @@ -504,12 +584,14 @@ static int switchdev_port_vlan_fill(struct sk_buff *skb, struct net_device *dev, if ((filter_mask & RTEXT_FILTER_BRVLAN) || (filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED)) { - err = switchdev_port_obj_dump(dev, &dump.obj); + err = switchdev_port_obj_dump(dev, SWITCHDEV_OBJ_PORT_VLAN, + &dump.vlan, + switchdev_port_vlan_dump_cb); if (err) goto err_out; if (filter_mask & RTEXT_FILTER_BRVLAN_COMPRESSED) /* last one */ - err = switchdev_port_vlan_dump_put(dev, &dump); + err = switchdev_port_vlan_dump_put(&dump); } err_out: @@ -617,14 +699,12 @@ static int switchdev_port_br_setlink_protinfo(struct net_device *dev, static int switchdev_port_br_afspec(struct net_device *dev, struct nlattr *afspec, int (*f)(struct net_device *dev, - struct switchdev_obj *obj)) + enum switchdev_obj_id id, + const void *obj)) { struct nlattr *attr; struct bridge_vlan_info *vinfo; - struct switchdev_obj obj = { - .id = SWITCHDEV_OBJ_PORT_VLAN, - }; - struct switchdev_obj_vlan *vlan = &obj.u.vlan; + struct switchdev_obj_vlan vlan = { 0 }; int rem; int err; @@ -634,30 +714,30 @@ static int switchdev_port_br_afspec(struct net_device *dev, if (nla_len(attr) != sizeof(struct bridge_vlan_info)) return -EINVAL; vinfo = nla_data(attr); - vlan->flags = vinfo->flags; + vlan.flags = vinfo->flags; if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { - if (vlan->vid_begin) + if (vlan.vid_begin) return -EINVAL; - vlan->vid_begin = vinfo->vid; + vlan.vid_begin = vinfo->vid; } else if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END) { - if (!vlan->vid_begin) + if (!vlan.vid_begin) return -EINVAL; - vlan->vid_end = vinfo->vid; - if (vlan->vid_end <= vlan->vid_begin) + vlan.vid_end = vinfo->vid; + if (vlan.vid_end <= vlan.vid_begin) return -EINVAL; - err = f(dev, &obj); + err = f(dev, SWITCHDEV_OBJ_PORT_VLAN, &vlan); if (err) return err; - memset(vlan, 0, sizeof(*vlan)); + memset(&vlan, 0, sizeof(vlan)); } else { - if (vlan->vid_begin) + if (vlan.vid_begin) return -EINVAL; - vlan->vid_begin = vinfo->vid; - vlan->vid_end = vinfo->vid; - err = f(dev, &obj); + vlan.vid_begin = vinfo->vid; + vlan.vid_end = vinfo->vid; + err = f(dev, SWITCHDEV_OBJ_PORT_VLAN, &vlan); if (err) return err; - memset(vlan, 0, sizeof(*vlan)); + memset(&vlan, 0, sizeof(vlan)); } } @@ -739,15 +819,12 @@ int switchdev_port_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], struct net_device *dev, const unsigned char *addr, u16 vid, u16 nlm_flags) { - struct switchdev_obj obj = { - .id = SWITCHDEV_OBJ_PORT_FDB, - .u.fdb = { - .addr = addr, - .vid = vid, - }, + struct switchdev_obj_fdb fdb = { + .addr = addr, + .vid = vid, }; - return switchdev_port_obj_add(dev, &obj); + return switchdev_port_obj_add(dev, SWITCHDEV_OBJ_PORT_FDB, &fdb); } EXPORT_SYMBOL_GPL(switchdev_port_fdb_add); @@ -766,30 +843,28 @@ int switchdev_port_fdb_del(struct ndmsg *ndm, struct nlattr *tb[], struct net_device *dev, const unsigned char *addr, u16 vid) { - struct switchdev_obj obj = { - .id = SWITCHDEV_OBJ_PORT_FDB, - .u.fdb = { - .addr = addr, - .vid = vid, - }, + struct switchdev_obj_fdb fdb = { + .addr = addr, + .vid = vid, }; - return switchdev_port_obj_del(dev, &obj); + return switchdev_port_obj_del(dev, SWITCHDEV_OBJ_PORT_FDB, &fdb); } EXPORT_SYMBOL_GPL(switchdev_port_fdb_del); struct switchdev_fdb_dump { - struct switchdev_obj obj; + struct switchdev_obj_fdb fdb; + struct net_device *dev; struct sk_buff *skb; struct netlink_callback *cb; int idx; }; -static int switchdev_port_fdb_dump_cb(struct net_device *dev, - struct switchdev_obj *obj) +static int switchdev_port_fdb_dump_cb(void *obj) { + struct switchdev_obj_fdb *fdb = obj; struct switchdev_fdb_dump *dump = - container_of(obj, struct switchdev_fdb_dump, obj); + container_of(fdb, struct switchdev_fdb_dump, fdb); u32 portid = NETLINK_CB(dump->cb->skb).portid; u32 seq = dump->cb->nlh->nlmsg_seq; struct nlmsghdr *nlh; @@ -809,13 +884,13 @@ static int switchdev_port_fdb_dump_cb(struct net_device *dev, ndm->ndm_pad2 = 0; ndm->ndm_flags = NTF_SELF; ndm->ndm_type = 0; - ndm->ndm_ifindex = dev->ifindex; - ndm->ndm_state = obj->u.fdb.ndm_state; + ndm->ndm_ifindex = dump->dev->ifindex; + ndm->ndm_state = fdb->ndm_state; - if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, obj->u.fdb.addr)) + if (nla_put(dump->skb, NDA_LLADDR, ETH_ALEN, fdb->addr)) goto nla_put_failure; - if (obj->u.fdb.vid && nla_put_u16(dump->skb, NDA_VLAN, obj->u.fdb.vid)) + if (fdb->vid && nla_put_u16(dump->skb, NDA_VLAN, fdb->vid)) goto nla_put_failure; nlmsg_end(dump->skb, nlh); @@ -845,16 +920,14 @@ int switchdev_port_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb, struct net_device *filter_dev, int idx) { struct switchdev_fdb_dump dump = { - .obj = { - .id = SWITCHDEV_OBJ_PORT_FDB, - .cb = switchdev_port_fdb_dump_cb, - }, + .dev = dev, .skb = skb, .cb = cb, .idx = idx, }; - switchdev_port_obj_dump(dev, &dump.obj); + switchdev_port_obj_dump(dev, SWITCHDEV_OBJ_PORT_FDB, &dump.fdb, + switchdev_port_fdb_dump_cb); return dump.idx; } EXPORT_SYMBOL_GPL(switchdev_port_fdb_dump); @@ -932,17 +1005,14 @@ static struct net_device *switchdev_get_dev_by_nhs(struct fib_info *fi) int switchdev_fib_ipv4_add(u32 dst, int dst_len, struct fib_info *fi, u8 tos, u8 type, u32 nlflags, u32 tb_id) { - struct switchdev_obj fib_obj = { - .id = SWITCHDEV_OBJ_IPV4_FIB, - .u.ipv4_fib = { - .dst = dst, - .dst_len = dst_len, - .fi = fi, - .tos = tos, - .type = type, - .nlflags = nlflags, - .tb_id = tb_id, - }, + struct switchdev_obj_ipv4_fib ipv4_fib = { + .dst = dst, + .dst_len = dst_len, + .fi = fi, + .tos = tos, + .type = type, + .nlflags = nlflags, + .tb_id = tb_id, }; struct net_device *dev; int err = 0; @@ -963,7 +1033,7 @@ int switchdev_fib_ipv4_add(u32 dst, int dst_len, struct fib_info *fi, if (!dev) return 0; - err = switchdev_port_obj_add(dev, &fib_obj); + err = switchdev_port_obj_add(dev, SWITCHDEV_OBJ_IPV4_FIB, &ipv4_fib); if (!err) fi->fib_flags |= RTNH_F_OFFLOAD; @@ -986,17 +1056,14 @@ EXPORT_SYMBOL_GPL(switchdev_fib_ipv4_add); int switchdev_fib_ipv4_del(u32 dst, int dst_len, struct fib_info *fi, u8 tos, u8 type, u32 tb_id) { - struct switchdev_obj fib_obj = { - .id = SWITCHDEV_OBJ_IPV4_FIB, - .u.ipv4_fib = { - .dst = dst, - .dst_len = dst_len, - .fi = fi, - .tos = tos, - .type = type, - .nlflags = 0, - .tb_id = tb_id, - }, + struct switchdev_obj_ipv4_fib ipv4_fib = { + .dst = dst, + .dst_len = dst_len, + .fi = fi, + .tos = tos, + .type = type, + .nlflags = 0, + .tb_id = tb_id, }; struct net_device *dev; int err = 0; @@ -1008,7 +1075,7 @@ int switchdev_fib_ipv4_del(u32 dst, int dst_len, struct fib_info *fi, if (!dev) return 0; - err = switchdev_port_obj_del(dev, &fib_obj); + err = switchdev_port_obj_del(dev, SWITCHDEV_OBJ_IPV4_FIB, &ipv4_fib); if (!err) fi->fib_flags &= ~RTNH_F_OFFLOAD; |