Commit 4110b282 authored by David S. Miller's avatar David S. Miller

Merge branch 'switchdev-transaction-item-queue'

Jiri Pirko says:

====================
switchdev: transaction item queue and cleanup
====================
Acked-by: default avatarScott Feldman <sfeldma@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents aee2f545 f623ab7f
......@@ -369,3 +369,22 @@ The driver can monitor for updates to arp_tbl using the netevent notifier
NETEVENT_NEIGH_UPDATE. The device can be programmed with resolved nexthops
for the routes as arp_tbl updates. The driver implements ndo_neigh_destroy
to know when arp_tbl neighbor entries are purged from the port.
Transaction item queue
^^^^^^^^^^^^^^^^^^^^^^
For switchdev ops attr_set and obj_add, there is a 2 phase transaction model
used. First phase is to "prepare" anything needed, including various checks,
memory allocation, etc. The goal is to handle the stuff that is not unlikely
to fail here. The second phase is to "commit" the actual changes.
Switchdev provides an inftrastructure for sharing items (for example memory
allocations) between the two phases.
The object created by a driver in "prepare" phase and it is queued up by:
switchdev_trans_item_enqueue()
During the "commit" phase, the driver gets the object by:
switchdev_trans_item_dequeue()
If a transaction is aborted during "prepare" phase, switchdev code will handle
cleanup of the queued-up objects.
This diff is collapsed.
/*
* include/net/switchdev.h - 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
......@@ -13,16 +13,31 @@
#include <linux/netdevice.h>
#include <linux/notifier.h>
#include <linux/list.h>
#define SWITCHDEV_F_NO_RECURSE BIT(0)
enum switchdev_trans {
SWITCHDEV_TRANS_NONE,
SWITCHDEV_TRANS_PREPARE,
SWITCHDEV_TRANS_ABORT,
SWITCHDEV_TRANS_COMMIT,
struct switchdev_trans_item {
struct list_head list;
void *data;
void (*destructor)(const void *data);
};
struct switchdev_trans {
struct list_head item_list;
bool ph_prepare;
};
static inline bool switchdev_trans_ph_prepare(struct switchdev_trans *trans)
{
return trans && trans->ph_prepare;
}
static inline bool switchdev_trans_ph_commit(struct switchdev_trans *trans)
{
return trans && !trans->ph_prepare;
}
enum switchdev_attr_id {
SWITCHDEV_ATTR_UNDEFINED,
SWITCHDEV_ATTR_PORT_PARENT_ID,
......@@ -32,7 +47,6 @@ enum switchdev_attr_id {
struct switchdev_attr {
enum switchdev_attr_id id;
enum switchdev_trans trans;
u32 flags;
union {
struct netdev_phys_item_id ppid; /* PORT_PARENT_ID */
......@@ -52,7 +66,6 @@ enum switchdev_obj_id {
struct switchdev_obj {
enum switchdev_obj_id id;
enum switchdev_trans trans;
int (*cb)(struct net_device *dev, struct switchdev_obj *obj);
union {
struct switchdev_obj_vlan { /* PORT_VLAN */
......@@ -77,6 +90,11 @@ struct switchdev_obj {
} u;
};
void switchdev_trans_item_enqueue(struct switchdev_trans *trans,
void *data, void (*destructor)(void const *),
struct switchdev_trans_item *tritem);
void *switchdev_trans_item_dequeue(struct switchdev_trans *trans);
/**
* struct switchdev_ops - switchdev operations
*
......@@ -94,9 +112,11 @@ struct switchdev_ops {
int (*switchdev_port_attr_get)(struct net_device *dev,
struct switchdev_attr *attr);
int (*switchdev_port_attr_set)(struct net_device *dev,
struct switchdev_attr *attr);
struct switchdev_attr *attr,
struct switchdev_trans *trans);
int (*switchdev_port_obj_add)(struct net_device *dev,
struct switchdev_obj *obj);
struct switchdev_obj *obj,
struct switchdev_trans *trans);
int (*switchdev_port_obj_del)(struct net_device *dev,
struct switchdev_obj *obj);
int (*switchdev_port_obj_dump)(struct net_device *dev,
......
......@@ -242,7 +242,8 @@ static int dsa_bridge_check_vlan_range(struct dsa_switch *ds,
}
static int dsa_slave_port_vlan_add(struct net_device *dev,
struct switchdev_obj *obj)
struct switchdev_obj *obj,
struct switchdev_trans *trans)
{
struct switchdev_obj_vlan *vlan = &obj->u.vlan;
struct dsa_slave_priv *p = netdev_priv(dev);
......@@ -250,8 +251,7 @@ static int dsa_slave_port_vlan_add(struct net_device *dev,
u16 vid;
int err;
switch (obj->trans) {
case SWITCHDEV_TRANS_PREPARE:
if (switchdev_trans_ph_prepare(trans)) {
if (!ds->drv->port_vlan_add || !ds->drv->port_pvid_set)
return -EOPNOTSUPP;
......@@ -263,8 +263,7 @@ static int dsa_slave_port_vlan_add(struct net_device *dev,
vlan->vid_end);
if (err)
return err;
break;
case SWITCHDEV_TRANS_COMMIT:
} else {
for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
err = ds->drv->port_vlan_add(ds, p->port, vid,
vlan->flags &
......@@ -274,9 +273,6 @@ static int dsa_slave_port_vlan_add(struct net_device *dev,
if (err)
return err;
}
break;
default:
return -EOPNOTSUPP;
}
return 0;
......@@ -347,16 +343,17 @@ static int dsa_slave_port_vlan_dump(struct net_device *dev,
}
static int dsa_slave_port_fdb_add(struct net_device *dev,
struct switchdev_obj *obj)
struct switchdev_obj *obj,
struct switchdev_trans *trans)
{
struct switchdev_obj_fdb *fdb = &obj->u.fdb;
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
if (obj->trans == SWITCHDEV_TRANS_PREPARE)
if (switchdev_trans_ph_prepare(trans))
ret = ds->drv->port_fdb_add ? 0 : -EOPNOTSUPP;
else if (obj->trans == SWITCHDEV_TRANS_COMMIT)
else
ret = ds->drv->port_fdb_add(ds, p->port, fdb->addr, fdb->vid);
return ret;
......@@ -456,13 +453,14 @@ static int dsa_slave_stp_update(struct net_device *dev, u8 state)
}
static int dsa_slave_port_attr_set(struct net_device *dev,
struct switchdev_attr *attr)
struct switchdev_attr *attr,
struct switchdev_trans *trans)
{
int ret = 0;
switch (attr->id) {
case SWITCHDEV_ATTR_PORT_STP_STATE:
if (attr->trans == SWITCHDEV_TRANS_COMMIT)
if (switchdev_trans_ph_commit(trans))
ret = dsa_slave_stp_update(dev, attr->u.stp_state);
break;
default:
......@@ -474,7 +472,8 @@ static int dsa_slave_port_attr_set(struct net_device *dev,
}
static int dsa_slave_port_obj_add(struct net_device *dev,
struct switchdev_obj *obj)
struct switchdev_obj *obj,
struct switchdev_trans *trans)
{
int err;
......@@ -485,10 +484,10 @@ static int dsa_slave_port_obj_add(struct net_device *dev,
switch (obj->id) {
case SWITCHDEV_OBJ_PORT_FDB:
err = dsa_slave_port_fdb_add(dev, obj);
err = dsa_slave_port_fdb_add(dev, obj, trans);
break;
case SWITCHDEV_OBJ_PORT_VLAN:
err = dsa_slave_port_vlan_add(dev, obj);
err = dsa_slave_port_vlan_add(dev, obj, trans);
break;
default:
err = -EOPNOTSUPP;
......
/*
* 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,9 +16,82 @@
#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
*
......@@ -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)
struct switchdev_obj *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, 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, obj, trans);
if (err)
break;
}
......@@ -232,10 +309,13 @@ static int __switchdev_port_obj_add(struct net_device *dev,
*/
int switchdev_port_obj_add(struct net_device *dev, struct switchdev_obj *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 +323,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, 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 +342,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);
trans.ph_prepare = false;
err = __switchdev_port_obj_add(dev, obj, &trans);
WARN(err, "%s: Commit of object (id=%d) failed.\n", dev->name, obj->id);
switchdev_trans_items_warn_destroy(dev, &trans);
return err;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment