aboutsummaryrefslogtreecommitdiffstats
path: root/net/dsa/dsa.c
diff options
context:
space:
mode:
authorLennert Buytenhek <buytenh@wantstofly.org>2008-10-07 09:44:02 -0400
committerDavid S. Miller <davem@davemloft.net>2008-10-08 20:15:19 -0400
commit91da11f870f00a3322b81c73042291d7f0be5a17 (patch)
tree670fedb54ee3c8fa403e9095f6d7e95ee560f346 /net/dsa/dsa.c
parent176eaa589b3d242f25f24e472883fcce5f196777 (diff)
net: Distributed Switch Architecture protocol support
Distributed Switch Architecture is a protocol for managing hardware switch chips. It consists of a set of MII management registers and commands to configure the switch, and an ethernet header format to signal which of the ports of the switch a packet was received from or is intended to be sent to. The switches that this driver supports are typically embedded in access points and routers, and a typical setup with a DSA switch looks something like this: +-----------+ +-----------+ | | RGMII | | | +-------+ +------ 1000baseT MDI ("WAN") | | | 6-port +------ 1000baseT MDI ("LAN1") | CPU | | ethernet +------ 1000baseT MDI ("LAN2") | |MIImgmt| switch +------ 1000baseT MDI ("LAN3") | +-------+ w/5 PHYs +------ 1000baseT MDI ("LAN4") | | | | +-----------+ +-----------+ The switch driver presents each port on the switch as a separate network interface to Linux, polls the switch to maintain software link state of those ports, forwards MII management interface accesses to those network interfaces (e.g. as done by ethtool) to the switch, and exposes the switch's hardware statistics counters via the appropriate Linux kernel interfaces. This initial patch supports the MII management interface register layout of the Marvell 88E6123, 88E6161 and 88E6165 switch chips, and supports the "Ethertype DSA" packet tagging format. (There is no officially registered ethertype for the Ethertype DSA packet format, so we just grab a random one. The ethertype to use is programmed into the switch, and the switch driver uses the value of ETH_P_EDSA for this, so this define can be changed at any time in the future if the one we chose is allocated to another protocol or if Ethertype DSA gets its own officially registered ethertype, and everything will continue to work.) Signed-off-by: Lennert Buytenhek <buytenh@marvell.com> Tested-by: Nicolas Pitre <nico@marvell.com> Tested-by: Byron Bradley <byron.bbradley@gmail.com> Tested-by: Tim Ellis <tim.ellis@mac.com> Tested-by: Peter van Valderen <linux@ddcrew.com> Tested-by: Dirk Teurlings <dirk@upexia.nl> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/dsa/dsa.c')
-rw-r--r--net/dsa/dsa.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
new file mode 100644
index 000000000000..6cc5be2ec7f1
--- /dev/null
+++ b/net/dsa/dsa.c
@@ -0,0 +1,369 @@
1/*
2 * net/dsa/dsa.c - Hardware switch handling
3 * Copyright (c) 2008 Marvell Semiconductor
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 */
10
11#include <linux/list.h>
12#include <linux/netdevice.h>
13#include <linux/platform_device.h>
14#include <net/dsa.h>
15#include "dsa_priv.h"
16
17char dsa_driver_version[] = "0.1";
18
19
20/* switch driver registration ***********************************************/
21static DEFINE_MUTEX(dsa_switch_drivers_mutex);
22static LIST_HEAD(dsa_switch_drivers);
23
24void register_switch_driver(struct dsa_switch_driver *drv)
25{
26 mutex_lock(&dsa_switch_drivers_mutex);
27 list_add_tail(&drv->list, &dsa_switch_drivers);
28 mutex_unlock(&dsa_switch_drivers_mutex);
29}
30
31void unregister_switch_driver(struct dsa_switch_driver *drv)
32{
33 mutex_lock(&dsa_switch_drivers_mutex);
34 list_del_init(&drv->list);
35 mutex_unlock(&dsa_switch_drivers_mutex);
36}
37
38static struct dsa_switch_driver *
39dsa_switch_probe(struct mii_bus *bus, int sw_addr, char **_name)
40{
41 struct dsa_switch_driver *ret;
42 struct list_head *list;
43 char *name;
44
45 ret = NULL;
46 name = NULL;
47
48 mutex_lock(&dsa_switch_drivers_mutex);
49 list_for_each(list, &dsa_switch_drivers) {
50 struct dsa_switch_driver *drv;
51
52 drv = list_entry(list, struct dsa_switch_driver, list);
53
54 name = drv->probe(bus, sw_addr);
55 if (name != NULL) {
56 ret = drv;
57 break;
58 }
59 }
60 mutex_unlock(&dsa_switch_drivers_mutex);
61
62 *_name = name;
63
64 return ret;
65}
66
67
68/* basic switch operations **************************************************/
69static struct dsa_switch *
70dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
71 struct mii_bus *bus, struct net_device *dev)
72{
73 struct dsa_switch *ds;
74 int ret;
75 struct dsa_switch_driver *drv;
76 char *name;
77 int i;
78
79 /*
80 * Probe for switch model.
81 */
82 drv = dsa_switch_probe(bus, pd->sw_addr, &name);
83 if (drv == NULL) {
84 printk(KERN_ERR "%s: could not detect attached switch\n",
85 dev->name);
86 return ERR_PTR(-EINVAL);
87 }
88 printk(KERN_INFO "%s: detected a %s switch\n", dev->name, name);
89
90
91 /*
92 * Allocate and initialise switch state.
93 */
94 ds = kzalloc(sizeof(*ds) + drv->priv_size, GFP_KERNEL);
95 if (ds == NULL)
96 return ERR_PTR(-ENOMEM);
97
98 ds->pd = pd;
99 ds->master_netdev = dev;
100 ds->master_mii_bus = bus;
101
102 ds->drv = drv;
103 ds->tag_protocol = drv->tag_protocol;
104
105
106 /*
107 * Validate supplied switch configuration.
108 */
109 ds->cpu_port = -1;
110 for (i = 0; i < DSA_MAX_PORTS; i++) {
111 char *name;
112
113 name = pd->port_names[i];
114 if (name == NULL)
115 continue;
116
117 if (!strcmp(name, "cpu")) {
118 if (ds->cpu_port != -1) {
119 printk(KERN_ERR "multiple cpu ports?!\n");
120 ret = -EINVAL;
121 goto out;
122 }
123 ds->cpu_port = i;
124 } else {
125 ds->valid_port_mask |= 1 << i;
126 }
127 }
128
129 if (ds->cpu_port == -1) {
130 printk(KERN_ERR "no cpu port?!\n");
131 ret = -EINVAL;
132 goto out;
133 }
134
135
136 /*
137 * If we use a tagging format that doesn't have an ethertype
138 * field, make sure that all packets from this point on get
139 * sent to the tag format's receive function. (Which will
140 * discard received packets until we set ds->ports[] below.)
141 */
142 wmb();
143 dev->dsa_ptr = (void *)ds;
144
145
146 /*
147 * Do basic register setup.
148 */
149 ret = drv->setup(ds);
150 if (ret < 0)
151 goto out;
152
153 ret = drv->set_addr(ds, dev->dev_addr);
154 if (ret < 0)
155 goto out;
156
157 ds->slave_mii_bus = mdiobus_alloc();
158 if (ds->slave_mii_bus == NULL) {
159 ret = -ENOMEM;
160 goto out;
161 }
162 dsa_slave_mii_bus_init(ds);
163
164 ret = mdiobus_register(ds->slave_mii_bus);
165 if (ret < 0)
166 goto out_free;
167
168
169 /*
170 * Create network devices for physical switch ports.
171 */
172 wmb();
173 for (i = 0; i < DSA_MAX_PORTS; i++) {
174 struct net_device *slave_dev;
175
176 if (!(ds->valid_port_mask & (1 << i)))
177 continue;
178
179 slave_dev = dsa_slave_create(ds, parent, i, pd->port_names[i]);
180 if (slave_dev == NULL) {
181 printk(KERN_ERR "%s: can't create dsa slave "
182 "device for port %d(%s)\n",
183 dev->name, i, pd->port_names[i]);
184 continue;
185 }
186
187 ds->ports[i] = slave_dev;
188 }
189
190 return ds;
191
192out_free:
193 mdiobus_free(ds->slave_mii_bus);
194out:
195 dev->dsa_ptr = NULL;
196 kfree(ds);
197 return ERR_PTR(ret);
198}
199
200static void dsa_switch_destroy(struct dsa_switch *ds)
201{
202}
203
204
205/* link polling *************************************************************/
206static void dsa_link_poll_work(struct work_struct *ugly)
207{
208 struct dsa_switch *ds;
209
210 ds = container_of(ugly, struct dsa_switch, link_poll_work);
211
212 ds->drv->poll_link(ds);
213 mod_timer(&ds->link_poll_timer, round_jiffies(jiffies + HZ));
214}
215
216static void dsa_link_poll_timer(unsigned long _ds)
217{
218 struct dsa_switch *ds = (void *)_ds;
219
220 schedule_work(&ds->link_poll_work);
221}
222
223
224/* platform driver init and cleanup *****************************************/
225static int dev_is_class(struct device *dev, void *class)
226{
227 if (dev->class != NULL && !strcmp(dev->class->name, class))
228 return 1;
229
230 return 0;
231}
232
233static struct device *dev_find_class(struct device *parent, char *class)
234{
235 if (dev_is_class(parent, class)) {
236 get_device(parent);
237 return parent;
238 }
239
240 return device_find_child(parent, class, dev_is_class);
241}
242
243static struct mii_bus *dev_to_mii_bus(struct device *dev)
244{
245 struct device *d;
246
247 d = dev_find_class(dev, "mdio_bus");
248 if (d != NULL) {
249 struct mii_bus *bus;
250
251 bus = to_mii_bus(d);
252 put_device(d);
253
254 return bus;
255 }
256
257 return NULL;
258}
259
260static struct net_device *dev_to_net_device(struct device *dev)
261{
262 struct device *d;
263
264 d = dev_find_class(dev, "net");
265 if (d != NULL) {
266 struct net_device *nd;
267
268 nd = to_net_dev(d);
269 dev_hold(nd);
270 put_device(d);
271
272 return nd;
273 }
274
275 return NULL;
276}
277
278static int dsa_probe(struct platform_device *pdev)
279{
280 static int dsa_version_printed;
281 struct dsa_platform_data *pd = pdev->dev.platform_data;
282 struct net_device *dev;
283 struct mii_bus *bus;
284 struct dsa_switch *ds;
285
286 if (!dsa_version_printed++)
287 printk(KERN_NOTICE "Distributed Switch Architecture "
288 "driver version %s\n", dsa_driver_version);
289
290 if (pd == NULL || pd->mii_bus == NULL || pd->netdev == NULL)
291 return -EINVAL;
292
293 bus = dev_to_mii_bus(pd->mii_bus);
294 if (bus == NULL)
295 return -EINVAL;
296
297 dev = dev_to_net_device(pd->netdev);
298 if (dev == NULL)
299 return -EINVAL;
300
301 if (dev->dsa_ptr != NULL) {
302 dev_put(dev);
303 return -EEXIST;
304 }
305
306 ds = dsa_switch_setup(&pdev->dev, pd, bus, dev);
307 if (IS_ERR(ds)) {
308 dev_put(dev);
309 return PTR_ERR(ds);
310 }
311
312 if (ds->drv->poll_link != NULL) {
313 INIT_WORK(&ds->link_poll_work, dsa_link_poll_work);
314 init_timer(&ds->link_poll_timer);
315 ds->link_poll_timer.data = (unsigned long)ds;
316 ds->link_poll_timer.function = dsa_link_poll_timer;
317 ds->link_poll_timer.expires = round_jiffies(jiffies + HZ);
318 add_timer(&ds->link_poll_timer);
319 }
320
321 platform_set_drvdata(pdev, ds);
322
323 return 0;
324}
325
326static int dsa_remove(struct platform_device *pdev)
327{
328 struct dsa_switch *ds = platform_get_drvdata(pdev);
329
330 if (ds->drv->poll_link != NULL)
331 del_timer_sync(&ds->link_poll_timer);
332
333 flush_scheduled_work();
334
335 dsa_switch_destroy(ds);
336
337 return 0;
338}
339
340static void dsa_shutdown(struct platform_device *pdev)
341{
342}
343
344static struct platform_driver dsa_driver = {
345 .probe = dsa_probe,
346 .remove = dsa_remove,
347 .shutdown = dsa_shutdown,
348 .driver = {
349 .name = "dsa",
350 .owner = THIS_MODULE,
351 },
352};
353
354static int __init dsa_init_module(void)
355{
356 return platform_driver_register(&dsa_driver);
357}
358module_init(dsa_init_module);
359
360static void __exit dsa_cleanup_module(void)
361{
362 platform_driver_unregister(&dsa_driver);
363}
364module_exit(dsa_cleanup_module);
365
366MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>")
367MODULE_DESCRIPTION("Driver for Distributed Switch Architecture switch chips");
368MODULE_LICENSE("GPL");
369MODULE_ALIAS("platform:dsa");