aboutsummaryrefslogtreecommitdiffstats
path: root/net/switchdev/switchdev.c
diff options
context:
space:
mode:
authorScott Feldman <sfeldma@gmail.com>2015-05-10 12:47:48 -0400
committerDavid S. Miller <davem@davemloft.net>2015-05-12 18:43:53 -0400
commit3094333d9089d43e8b8f0418676fa6ae06c27b51 (patch)
treee2796ab07d0356c4fba4b0040702f3ac2172a6dc /net/switchdev/switchdev.c
parent9d47c0a2d958e06322c88245749278633d333cca (diff)
switchdev: introduce get/set attrs ops
Add two new swdev ops for get/set switch port attributes. Most swdev interactions on a port are gets or sets on port attributes, so rather than adding ops for each attribute, let's define clean get/set ops for all attributes, and then we can have clear, consistent rules on how attributes propagate on stacked devs. Add the basic algorithms for get/set attr ops. Use the same recusive algo to walk lower devs we've used for STP updates, for example. For get, compare attr value for each lower dev and only return success if attr values match across all lower devs. For sets, set the same attr value for all lower devs. We'll use a two-phase prepare-commit transaction model for sets. In the first phase, the driver(s) are asked if attr set is OK. If all OK, the commit attr set in second phase. A driver would NACK the prepare phase if it can't set the attr due to lack of resources or support, within it's control. RTNL lock must be held across both phases because we'll recurse all lower devs first in prepare phase, and then recurse all lower devs again in commit phase. If any lower dev fails the prepare phase, we need to abort the transaction for all lower devs. If lower dev recusion isn't desired, allow a flag SWITCHDEV_F_NO_RECURSE to indicate get/set only work on port (lowest) device. Signed-off-by: Scott Feldman <sfeldma@gmail.com> Acked-by: Jiri Pirko <jiri@resnulli.us> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/switchdev/switchdev.c')
-rw-r--r--net/switchdev/switchdev.c169
1 files changed, 169 insertions, 0 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c
index b7f44a23def5..8f47187dc185 100644
--- a/net/switchdev/switchdev.c
+++ b/net/switchdev/switchdev.c
@@ -37,6 +37,175 @@ int switchdev_parent_id_get(struct net_device *dev,
37EXPORT_SYMBOL_GPL(switchdev_parent_id_get); 37EXPORT_SYMBOL_GPL(switchdev_parent_id_get);
38 38
39/** 39/**
40 * switchdev_port_attr_get - Get port attribute
41 *
42 * @dev: port device
43 * @attr: attribute to get
44 */
45int switchdev_port_attr_get(struct net_device *dev, struct switchdev_attr *attr)
46{
47 const struct switchdev_ops *ops = dev->switchdev_ops;
48 struct net_device *lower_dev;
49 struct list_head *iter;
50 struct switchdev_attr first = {
51 .id = SWITCHDEV_ATTR_UNDEFINED
52 };
53 int err = -EOPNOTSUPP;
54
55 if (ops && ops->switchdev_port_attr_get)
56 return ops->switchdev_port_attr_get(dev, attr);
57
58 if (attr->flags & SWITCHDEV_F_NO_RECURSE)
59 return err;
60
61 /* Switch device port(s) may be stacked under
62 * bond/team/vlan dev, so recurse down to get attr on
63 * each port. Return -ENODATA if attr values don't
64 * compare across ports.
65 */
66
67 netdev_for_each_lower_dev(dev, lower_dev, iter) {
68 err = switchdev_port_attr_get(lower_dev, attr);
69 if (err)
70 break;
71 if (first.id == SWITCHDEV_ATTR_UNDEFINED)
72 first = *attr;
73 else if (memcmp(&first, attr, sizeof(*attr)))
74 return -ENODATA;
75 }
76
77 return err;
78}
79EXPORT_SYMBOL_GPL(switchdev_port_attr_get);
80
81static int __switchdev_port_attr_set(struct net_device *dev,
82 struct switchdev_attr *attr)
83{
84 const struct switchdev_ops *ops = dev->switchdev_ops;
85 struct net_device *lower_dev;
86 struct list_head *iter;
87 int err = -EOPNOTSUPP;
88
89 if (ops && ops->switchdev_port_attr_set)
90 return ops->switchdev_port_attr_set(dev, attr);
91
92 if (attr->flags & SWITCHDEV_F_NO_RECURSE)
93 return err;
94
95 /* Switch device port(s) may be stacked under
96 * bond/team/vlan dev, so recurse down to set attr on
97 * each port.
98 */
99
100 netdev_for_each_lower_dev(dev, lower_dev, iter) {
101 err = __switchdev_port_attr_set(lower_dev, attr);
102 if (err)
103 break;
104 }
105
106 return err;
107}
108
109struct switchdev_attr_set_work {
110 struct work_struct work;
111 struct net_device *dev;
112 struct switchdev_attr attr;
113};
114
115static void switchdev_port_attr_set_work(struct work_struct *work)
116{
117 struct switchdev_attr_set_work *asw =
118 container_of(work, struct switchdev_attr_set_work, work);
119 int err;
120
121 rtnl_lock();
122 err = switchdev_port_attr_set(asw->dev, &asw->attr);
123 BUG_ON(err);
124 rtnl_unlock();
125
126 dev_put(asw->dev);
127 kfree(work);
128}
129
130static int switchdev_port_attr_set_defer(struct net_device *dev,
131 struct switchdev_attr *attr)
132{
133 struct switchdev_attr_set_work *asw;
134
135 asw = kmalloc(sizeof(*asw), GFP_ATOMIC);
136 if (!asw)
137 return -ENOMEM;
138
139 INIT_WORK(&asw->work, switchdev_port_attr_set_work);
140
141 dev_hold(dev);
142 asw->dev = dev;
143 memcpy(&asw->attr, attr, sizeof(asw->attr));
144
145 schedule_work(&asw->work);
146
147 return 0;
148}
149
150/**
151 * switchdev_port_attr_set - Set port attribute
152 *
153 * @dev: port device
154 * @attr: attribute to set
155 *
156 * Use a 2-phase prepare-commit transaction model to ensure
157 * system is not left in a partially updated state due to
158 * failure from driver/device.
159 */
160int switchdev_port_attr_set(struct net_device *dev, struct switchdev_attr *attr)
161{
162 int err;
163
164 if (!rtnl_is_locked()) {
165 /* Running prepare-commit transaction across stacked
166 * devices requires nothing moves, so if rtnl_lock is
167 * not held, schedule a worker thread to hold rtnl_lock
168 * while setting attr.
169 */
170
171 return switchdev_port_attr_set_defer(dev, attr);
172 }
173
174 /* Phase I: prepare for attr set. Driver/device should fail
175 * here if there are going to be issues in the commit phase,
176 * such as lack of resources or support. The driver/device
177 * should reserve resources needed for the commit phase here,
178 * but should not commit the attr.
179 */
180
181 attr->trans = SWITCHDEV_TRANS_PREPARE;
182 err = __switchdev_port_attr_set(dev, attr);
183 if (err) {
184 /* Prepare phase failed: abort the transaction. Any
185 * resources reserved in the prepare phase are
186 * released.
187 */
188
189 attr->trans = SWITCHDEV_TRANS_ABORT;
190 __switchdev_port_attr_set(dev, attr);
191
192 return err;
193 }
194
195 /* Phase II: commit attr set. This cannot fail as a fault
196 * of driver/device. If it does, it's a bug in the driver/device
197 * because the driver said everythings was OK in phase I.
198 */
199
200 attr->trans = SWITCHDEV_TRANS_COMMIT;
201 err = __switchdev_port_attr_set(dev, attr);
202 BUG_ON(err);
203
204 return err;
205}
206EXPORT_SYMBOL_GPL(switchdev_port_attr_set);
207
208/**
40 * switchdev_port_stp_update - Notify switch device port of STP 209 * switchdev_port_stp_update - Notify switch device port of STP
41 * state change 210 * state change
42 * @dev: port device 211 * @dev: port device