net: dsa: add VLAN notifier
[linux-2.6-block.git] / net / dsa / switch.c
1 /*
2  * Handling of a single switch chip, part of a switch fabric
3  *
4  * Copyright (c) 2017 Savoir-faire Linux Inc.
5  *      Vivien Didelot <vivien.didelot@savoirfairelinux.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  */
12
13 #include <linux/netdevice.h>
14 #include <linux/notifier.h>
15 #include <net/switchdev.h>
16
17 #include "dsa_priv.h"
18
19 static unsigned int dsa_switch_fastest_ageing_time(struct dsa_switch *ds,
20                                                    unsigned int ageing_time)
21 {
22         int i;
23
24         for (i = 0; i < ds->num_ports; ++i) {
25                 struct dsa_port *dp = &ds->ports[i];
26
27                 if (dp->ageing_time && dp->ageing_time < ageing_time)
28                         ageing_time = dp->ageing_time;
29         }
30
31         return ageing_time;
32 }
33
34 static int dsa_switch_ageing_time(struct dsa_switch *ds,
35                                   struct dsa_notifier_ageing_time_info *info)
36 {
37         unsigned int ageing_time = info->ageing_time;
38         struct switchdev_trans *trans = info->trans;
39
40         /* Do not care yet about other switch chips of the fabric */
41         if (ds->index != info->sw_index)
42                 return 0;
43
44         if (switchdev_trans_ph_prepare(trans)) {
45                 if (ds->ageing_time_min && ageing_time < ds->ageing_time_min)
46                         return -ERANGE;
47                 if (ds->ageing_time_max && ageing_time > ds->ageing_time_max)
48                         return -ERANGE;
49                 return 0;
50         }
51
52         /* Program the fastest ageing time in case of multiple bridges */
53         ageing_time = dsa_switch_fastest_ageing_time(ds, ageing_time);
54
55         if (ds->ops->set_ageing_time)
56                 return ds->ops->set_ageing_time(ds, ageing_time);
57
58         return 0;
59 }
60
61 static int dsa_switch_bridge_join(struct dsa_switch *ds,
62                                   struct dsa_notifier_bridge_info *info)
63 {
64         if (ds->index == info->sw_index && ds->ops->port_bridge_join)
65                 return ds->ops->port_bridge_join(ds, info->port, info->br);
66
67         if (ds->index != info->sw_index && ds->ops->crosschip_bridge_join)
68                 return ds->ops->crosschip_bridge_join(ds, info->sw_index,
69                                                       info->port, info->br);
70
71         return 0;
72 }
73
74 static int dsa_switch_bridge_leave(struct dsa_switch *ds,
75                                    struct dsa_notifier_bridge_info *info)
76 {
77         if (ds->index == info->sw_index && ds->ops->port_bridge_leave)
78                 ds->ops->port_bridge_leave(ds, info->port, info->br);
79
80         if (ds->index != info->sw_index && ds->ops->crosschip_bridge_leave)
81                 ds->ops->crosschip_bridge_leave(ds, info->sw_index, info->port,
82                                                 info->br);
83
84         return 0;
85 }
86
87 static int dsa_switch_fdb_add(struct dsa_switch *ds,
88                               struct dsa_notifier_fdb_info *info)
89 {
90         const struct switchdev_obj_port_fdb *fdb = info->fdb;
91         struct switchdev_trans *trans = info->trans;
92
93         /* Do not care yet about other switch chips of the fabric */
94         if (ds->index != info->sw_index)
95                 return 0;
96
97         if (switchdev_trans_ph_prepare(trans)) {
98                 if (!ds->ops->port_fdb_prepare || !ds->ops->port_fdb_add)
99                         return -EOPNOTSUPP;
100
101                 return ds->ops->port_fdb_prepare(ds, info->port, fdb, trans);
102         }
103
104         ds->ops->port_fdb_add(ds, info->port, fdb, trans);
105
106         return 0;
107 }
108
109 static int dsa_switch_fdb_del(struct dsa_switch *ds,
110                               struct dsa_notifier_fdb_info *info)
111 {
112         const struct switchdev_obj_port_fdb *fdb = info->fdb;
113
114         /* Do not care yet about other switch chips of the fabric */
115         if (ds->index != info->sw_index)
116                 return 0;
117
118         if (!ds->ops->port_fdb_del)
119                 return -EOPNOTSUPP;
120
121         return ds->ops->port_fdb_del(ds, info->port, fdb);
122 }
123
124 static int dsa_switch_mdb_add(struct dsa_switch *ds,
125                               struct dsa_notifier_mdb_info *info)
126 {
127         const struct switchdev_obj_port_mdb *mdb = info->mdb;
128         struct switchdev_trans *trans = info->trans;
129
130         /* Do not care yet about other switch chips of the fabric */
131         if (ds->index != info->sw_index)
132                 return 0;
133
134         if (switchdev_trans_ph_prepare(trans)) {
135                 if (!ds->ops->port_mdb_prepare || !ds->ops->port_mdb_add)
136                         return -EOPNOTSUPP;
137
138                 return ds->ops->port_mdb_prepare(ds, info->port, mdb, trans);
139         }
140
141         ds->ops->port_mdb_add(ds, info->port, mdb, trans);
142
143         return 0;
144 }
145
146 static int dsa_switch_mdb_del(struct dsa_switch *ds,
147                               struct dsa_notifier_mdb_info *info)
148 {
149         const struct switchdev_obj_port_mdb *mdb = info->mdb;
150
151         /* Do not care yet about other switch chips of the fabric */
152         if (ds->index != info->sw_index)
153                 return 0;
154
155         if (!ds->ops->port_mdb_del)
156                 return -EOPNOTSUPP;
157
158         return ds->ops->port_mdb_del(ds, info->port, mdb);
159 }
160
161 static int dsa_switch_vlan_add(struct dsa_switch *ds,
162                                struct dsa_notifier_vlan_info *info)
163 {
164         const struct switchdev_obj_port_vlan *vlan = info->vlan;
165         struct switchdev_trans *trans = info->trans;
166
167         /* Do not care yet about other switch chips of the fabric */
168         if (ds->index != info->sw_index)
169                 return 0;
170
171         if (switchdev_trans_ph_prepare(trans)) {
172                 if (!ds->ops->port_vlan_prepare || !ds->ops->port_vlan_add)
173                         return -EOPNOTSUPP;
174
175                 return ds->ops->port_vlan_prepare(ds, info->port, vlan, trans);
176         }
177
178         ds->ops->port_vlan_add(ds, info->port, vlan, trans);
179
180         return 0;
181 }
182
183 static int dsa_switch_vlan_del(struct dsa_switch *ds,
184                                struct dsa_notifier_vlan_info *info)
185 {
186         const struct switchdev_obj_port_vlan *vlan = info->vlan;
187
188         /* Do not care yet about other switch chips of the fabric */
189         if (ds->index != info->sw_index)
190                 return 0;
191
192         if (!ds->ops->port_vlan_del)
193                 return -EOPNOTSUPP;
194
195         return ds->ops->port_vlan_del(ds, info->port, vlan);
196 }
197
198 static int dsa_switch_event(struct notifier_block *nb,
199                             unsigned long event, void *info)
200 {
201         struct dsa_switch *ds = container_of(nb, struct dsa_switch, nb);
202         int err;
203
204         switch (event) {
205         case DSA_NOTIFIER_AGEING_TIME:
206                 err = dsa_switch_ageing_time(ds, info);
207                 break;
208         case DSA_NOTIFIER_BRIDGE_JOIN:
209                 err = dsa_switch_bridge_join(ds, info);
210                 break;
211         case DSA_NOTIFIER_BRIDGE_LEAVE:
212                 err = dsa_switch_bridge_leave(ds, info);
213                 break;
214         case DSA_NOTIFIER_FDB_ADD:
215                 err = dsa_switch_fdb_add(ds, info);
216                 break;
217         case DSA_NOTIFIER_FDB_DEL:
218                 err = dsa_switch_fdb_del(ds, info);
219                 break;
220         case DSA_NOTIFIER_MDB_ADD:
221                 err = dsa_switch_mdb_add(ds, info);
222                 break;
223         case DSA_NOTIFIER_MDB_DEL:
224                 err = dsa_switch_mdb_del(ds, info);
225                 break;
226         case DSA_NOTIFIER_VLAN_ADD:
227                 err = dsa_switch_vlan_add(ds, info);
228                 break;
229         case DSA_NOTIFIER_VLAN_DEL:
230                 err = dsa_switch_vlan_del(ds, info);
231                 break;
232         default:
233                 err = -EOPNOTSUPP;
234                 break;
235         }
236
237         /* Non-switchdev operations cannot be rolled back. If a DSA driver
238          * returns an error during the chained call, switch chips may be in an
239          * inconsistent state.
240          */
241         if (err)
242                 dev_dbg(ds->dev, "breaking chain for DSA event %lu (%d)\n",
243                         event, err);
244
245         return notifier_from_errno(err);
246 }
247
248 int dsa_switch_register_notifier(struct dsa_switch *ds)
249 {
250         ds->nb.notifier_call = dsa_switch_event;
251
252         return raw_notifier_chain_register(&ds->dst->nh, &ds->nb);
253 }
254
255 void dsa_switch_unregister_notifier(struct dsa_switch *ds)
256 {
257         int err;
258
259         err = raw_notifier_chain_unregister(&ds->dst->nh, &ds->nb);
260         if (err)
261                 dev_err(ds->dev, "failed to unregister notifier (%d)\n", err);
262 }