]>
Commit | Line | Data |
---|---|---|
91da11f8 LB |
1 | /* |
2 | * net/dsa/dsa.c - Hardware switch handling | |
e84665c9 | 3 | * Copyright (c) 2008-2009 Marvell Semiconductor |
5e95329b | 4 | * Copyright (c) 2013 Florian Fainelli <florian@openwrt.org> |
91da11f8 LB |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | ||
51579c3f | 12 | #include <linux/device.h> |
91da11f8 | 13 | #include <linux/list.h> |
91da11f8 | 14 | #include <linux/platform_device.h> |
5a0e3ad6 | 15 | #include <linux/slab.h> |
3a9a231d | 16 | #include <linux/module.h> |
60724d4b | 17 | #include <linux/notifier.h> |
5e95329b FF |
18 | #include <linux/of.h> |
19 | #include <linux/of_mdio.h> | |
20 | #include <linux/of_platform.h> | |
769a0202 | 21 | #include <linux/of_net.h> |
cc30c163 | 22 | #include <linux/of_gpio.h> |
c6e970a0 | 23 | #include <linux/netdevice.h> |
51579c3f | 24 | #include <linux/sysfs.h> |
cbc5d90b | 25 | #include <linux/phy_fixed.h> |
85beabfe | 26 | #include <linux/gpio/consumer.h> |
a86d8bec | 27 | #include <linux/etherdevice.h> |
ea5dd34b | 28 | |
91da11f8 LB |
29 | #include "dsa_priv.h" |
30 | ||
39a7f2a4 AL |
31 | static struct sk_buff *dsa_slave_notag_xmit(struct sk_buff *skb, |
32 | struct net_device *dev) | |
33 | { | |
34 | /* Just return the original SKB */ | |
35 | return skb; | |
36 | } | |
37 | ||
38 | static const struct dsa_device_ops none_ops = { | |
39 | .xmit = dsa_slave_notag_xmit, | |
40 | .rcv = NULL, | |
41 | }; | |
42 | ||
43 | const struct dsa_device_ops *dsa_device_ops[DSA_TAG_LAST] = { | |
eb7b7211 AL |
44 | #ifdef CONFIG_NET_DSA_TAG_BRCM |
45 | [DSA_TAG_PROTO_BRCM] = &brcm_netdev_ops, | |
46 | #endif | |
b74b70c4 FF |
47 | #ifdef CONFIG_NET_DSA_TAG_BRCM_PREPEND |
48 | [DSA_TAG_PROTO_BRCM_PREPEND] = &brcm_prepend_netdev_ops, | |
49 | #endif | |
39a7f2a4 AL |
50 | #ifdef CONFIG_NET_DSA_TAG_DSA |
51 | [DSA_TAG_PROTO_DSA] = &dsa_netdev_ops, | |
52 | #endif | |
53 | #ifdef CONFIG_NET_DSA_TAG_EDSA | |
54 | [DSA_TAG_PROTO_EDSA] = &edsa_netdev_ops, | |
55 | #endif | |
8b8010fb WH |
56 | #ifdef CONFIG_NET_DSA_TAG_KSZ |
57 | [DSA_TAG_PROTO_KSZ] = &ksz_netdev_ops, | |
58 | #endif | |
eb7b7211 AL |
59 | #ifdef CONFIG_NET_DSA_TAG_LAN9303 |
60 | [DSA_TAG_PROTO_LAN9303] = &lan9303_netdev_ops, | |
39a7f2a4 | 61 | #endif |
eb7b7211 AL |
62 | #ifdef CONFIG_NET_DSA_TAG_MTK |
63 | [DSA_TAG_PROTO_MTK] = &mtk_netdev_ops, | |
cafdc45c JC |
64 | #endif |
65 | #ifdef CONFIG_NET_DSA_TAG_QCA | |
66 | [DSA_TAG_PROTO_QCA] = &qca_netdev_ops, | |
5cd8985a | 67 | #endif |
eb7b7211 AL |
68 | #ifdef CONFIG_NET_DSA_TAG_TRAILER |
69 | [DSA_TAG_PROTO_TRAILER] = &trailer_netdev_ops, | |
39a7f2a4 AL |
70 | #endif |
71 | [DSA_TAG_PROTO_NONE] = &none_ops, | |
72 | }; | |
91da11f8 | 73 | |
39a7f2a4 AL |
74 | const struct dsa_device_ops *dsa_resolve_tag_protocol(int tag_protocol) |
75 | { | |
76 | const struct dsa_device_ops *ops; | |
77 | ||
78 | if (tag_protocol >= DSA_TAG_LAST) | |
79 | return ERR_PTR(-EINVAL); | |
80 | ops = dsa_device_ops[tag_protocol]; | |
81 | ||
82 | if (!ops) | |
83 | return ERR_PTR(-ENOPROTOOPT); | |
84 | ||
85 | return ops; | |
86 | } | |
87 | ||
91da11f8 LB |
88 | static int dev_is_class(struct device *dev, void *class) |
89 | { | |
90 | if (dev->class != NULL && !strcmp(dev->class->name, class)) | |
91 | return 1; | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | static struct device *dev_find_class(struct device *parent, char *class) | |
97 | { | |
98 | if (dev_is_class(parent, class)) { | |
99 | get_device(parent); | |
100 | return parent; | |
101 | } | |
102 | ||
103 | return device_find_child(parent, class, dev_is_class); | |
104 | } | |
105 | ||
14b89f36 | 106 | struct net_device *dsa_dev_to_net_device(struct device *dev) |
91da11f8 LB |
107 | { |
108 | struct device *d; | |
109 | ||
110 | d = dev_find_class(dev, "net"); | |
111 | if (d != NULL) { | |
112 | struct net_device *nd; | |
113 | ||
114 | nd = to_net_dev(d); | |
115 | dev_hold(nd); | |
116 | put_device(d); | |
117 | ||
118 | return nd; | |
119 | } | |
120 | ||
121 | return NULL; | |
122 | } | |
14b89f36 | 123 | EXPORT_SYMBOL_GPL(dsa_dev_to_net_device); |
91da11f8 | 124 | |
3e8a72d1 | 125 | static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev, |
89e49506 | 126 | struct packet_type *pt, struct net_device *unused) |
3e8a72d1 | 127 | { |
2f657a60 | 128 | struct dsa_port *cpu_dp = dev->dsa_ptr; |
a86d8bec | 129 | struct sk_buff *nskb = NULL; |
5f6b4e14 | 130 | struct pcpu_sw_netstats *s; |
f613ed66 | 131 | struct dsa_slave_priv *p; |
3e8a72d1 | 132 | |
2f657a60 | 133 | if (unlikely(!cpu_dp)) { |
3e8a72d1 FF |
134 | kfree_skb(skb); |
135 | return 0; | |
136 | } | |
137 | ||
16c5dcb1 FF |
138 | skb = skb_unshare(skb, GFP_ATOMIC); |
139 | if (!skb) | |
140 | return 0; | |
141 | ||
2f657a60 | 142 | nskb = cpu_dp->rcv(skb, dev, pt); |
a86d8bec FF |
143 | if (!nskb) { |
144 | kfree_skb(skb); | |
145 | return 0; | |
146 | } | |
147 | ||
148 | skb = nskb; | |
f613ed66 | 149 | p = netdev_priv(skb->dev); |
a86d8bec FF |
150 | skb_push(skb, ETH_HLEN); |
151 | skb->pkt_type = PACKET_HOST; | |
152 | skb->protocol = eth_type_trans(skb, skb->dev); | |
153 | ||
5f6b4e14 FF |
154 | s = this_cpu_ptr(p->stats64); |
155 | u64_stats_update_begin(&s->syncp); | |
156 | s->rx_packets++; | |
157 | s->rx_bytes += skb->len; | |
158 | u64_stats_update_end(&s->syncp); | |
a86d8bec FF |
159 | |
160 | netif_receive_skb(skb); | |
161 | ||
162 | return 0; | |
3e8a72d1 FF |
163 | } |
164 | ||
ac2629a4 | 165 | #ifdef CONFIG_PM_SLEEP |
e7d53ad3 VD |
166 | static bool dsa_is_port_initialized(struct dsa_switch *ds, int p) |
167 | { | |
4a5b85ff | 168 | return dsa_is_user_port(ds, p) && ds->ports[p].slave; |
e7d53ad3 VD |
169 | } |
170 | ||
ac2629a4 FF |
171 | int dsa_switch_suspend(struct dsa_switch *ds) |
172 | { | |
173 | int i, ret = 0; | |
174 | ||
175 | /* Suspend slave network devices */ | |
176 | for (i = 0; i < ds->num_ports; i++) { | |
177 | if (!dsa_is_port_initialized(ds, i)) | |
178 | continue; | |
179 | ||
f8b8b1cd | 180 | ret = dsa_slave_suspend(ds->ports[i].slave); |
ac2629a4 FF |
181 | if (ret) |
182 | return ret; | |
183 | } | |
184 | ||
185 | if (ds->ops->suspend) | |
186 | ret = ds->ops->suspend(ds); | |
187 | ||
188 | return ret; | |
189 | } | |
190 | EXPORT_SYMBOL_GPL(dsa_switch_suspend); | |
191 | ||
192 | int dsa_switch_resume(struct dsa_switch *ds) | |
193 | { | |
194 | int i, ret = 0; | |
195 | ||
196 | if (ds->ops->resume) | |
197 | ret = ds->ops->resume(ds); | |
198 | ||
199 | if (ret) | |
200 | return ret; | |
201 | ||
202 | /* Resume slave network devices */ | |
203 | for (i = 0; i < ds->num_ports; i++) { | |
204 | if (!dsa_is_port_initialized(ds, i)) | |
205 | continue; | |
206 | ||
f8b8b1cd | 207 | ret = dsa_slave_resume(ds->ports[i].slave); |
ac2629a4 FF |
208 | if (ret) |
209 | return ret; | |
210 | } | |
211 | ||
212 | return 0; | |
213 | } | |
214 | EXPORT_SYMBOL_GPL(dsa_switch_resume); | |
215 | #endif | |
216 | ||
61b7363f | 217 | static struct packet_type dsa_pack_type __read_mostly = { |
3e8a72d1 FF |
218 | .type = cpu_to_be16(ETH_P_XDSA), |
219 | .func = dsa_switch_rcv, | |
220 | }; | |
221 | ||
c9eb3e0f AS |
222 | static struct workqueue_struct *dsa_owq; |
223 | ||
224 | bool dsa_schedule_work(struct work_struct *work) | |
225 | { | |
226 | return queue_work(dsa_owq, work); | |
227 | } | |
228 | ||
60724d4b FF |
229 | static ATOMIC_NOTIFIER_HEAD(dsa_notif_chain); |
230 | ||
231 | int register_dsa_notifier(struct notifier_block *nb) | |
232 | { | |
233 | return atomic_notifier_chain_register(&dsa_notif_chain, nb); | |
234 | } | |
235 | EXPORT_SYMBOL_GPL(register_dsa_notifier); | |
236 | ||
237 | int unregister_dsa_notifier(struct notifier_block *nb) | |
238 | { | |
239 | return atomic_notifier_chain_unregister(&dsa_notif_chain, nb); | |
240 | } | |
241 | EXPORT_SYMBOL_GPL(unregister_dsa_notifier); | |
242 | ||
243 | int call_dsa_notifiers(unsigned long val, struct net_device *dev, | |
244 | struct dsa_notifier_info *info) | |
245 | { | |
246 | info->dev = dev; | |
247 | return atomic_notifier_call_chain(&dsa_notif_chain, val, info); | |
248 | } | |
249 | EXPORT_SYMBOL_GPL(call_dsa_notifiers); | |
250 | ||
91da11f8 LB |
251 | static int __init dsa_init_module(void) |
252 | { | |
7df899c3 BH |
253 | int rc; |
254 | ||
c9eb3e0f AS |
255 | dsa_owq = alloc_ordered_workqueue("dsa_ordered", |
256 | WQ_MEM_RECLAIM); | |
257 | if (!dsa_owq) | |
258 | return -ENOMEM; | |
259 | ||
88e4f0ca VD |
260 | rc = dsa_slave_register_notifier(); |
261 | if (rc) | |
262 | return rc; | |
b73adef6 | 263 | |
a6a71f19 | 264 | rc = dsa_legacy_register(); |
7df899c3 BH |
265 | if (rc) |
266 | return rc; | |
267 | ||
3e8a72d1 FF |
268 | dev_add_pack(&dsa_pack_type); |
269 | ||
7df899c3 | 270 | return 0; |
91da11f8 LB |
271 | } |
272 | module_init(dsa_init_module); | |
273 | ||
274 | static void __exit dsa_cleanup_module(void) | |
275 | { | |
88e4f0ca | 276 | dsa_slave_unregister_notifier(); |
3e8a72d1 | 277 | dev_remove_pack(&dsa_pack_type); |
a6a71f19 | 278 | dsa_legacy_unregister(); |
c9eb3e0f | 279 | destroy_workqueue(dsa_owq); |
91da11f8 LB |
280 | } |
281 | module_exit(dsa_cleanup_module); | |
282 | ||
577d6a7c | 283 | MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); |
91da11f8 LB |
284 | MODULE_DESCRIPTION("Driver for Distributed Switch Architecture switch chips"); |
285 | MODULE_LICENSE("GPL"); | |
286 | MODULE_ALIAS("platform:dsa"); |