1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2011-2012 Stefan Bethke.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/param.h>
30 #include <sys/bus.h>
31 #include <sys/kernel.h>
32 #include <sys/malloc.h>
33 #include <sys/module.h>
34 #include <sys/socket.h>
35 #include <sys/sockio.h>
36 #include <sys/systm.h>
37
38 #include <net/if.h>
39 #include <net/if_media.h>
40
41 #include <dev/etherswitch/miiproxy.h>
42 #include <dev/mii/mii.h>
43 #include <dev/mii/miivar.h>
44
45 #include "mdio_if.h"
46 #include "miibus_if.h"
47
48
49 MALLOC_DECLARE(M_MIIPROXY);
50 MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
51
52 driver_t miiproxy_driver;
53 driver_t mdioproxy_driver;
54
55 struct miiproxy_softc {
56 device_t parent;
57 device_t proxy;
58 device_t mdio;
59 };
60
61 struct mdioproxy_softc {
62 };
63
64 /*
65 * The rendezvous data structures and functions allow two device endpoints to
66 * match up, so that the proxy endpoint can be associated with a target
67 * endpoint. The proxy has to know the device name of the target that it
68 * wants to associate with, for example through a hint. The rendezvous code
69 * makes no assumptions about the devices that want to meet.
70 */
71 struct rendezvous_entry;
72
73 enum rendezvous_op {
74 RENDEZVOUS_ATTACH,
75 RENDEZVOUS_DETACH
76 };
77
78 typedef int (*rendezvous_callback_t)(enum rendezvous_op,
79 struct rendezvous_entry *);
80
81 static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead =
82 SLIST_HEAD_INITIALIZER(rendezvoushead);
83
84 struct rendezvous_endpoint {
85 device_t device;
86 const char *name;
87 rendezvous_callback_t callback;
88 };
89
90 struct rendezvous_entry {
91 SLIST_ENTRY(rendezvous_entry) entries;
92 struct rendezvous_endpoint proxy;
93 struct rendezvous_endpoint target;
94 };
95
96 /*
97 * Call the callback routines for both the proxy and the target. If either
98 * returns an error, undo the attachment.
99 */
100 static int
rendezvous_attach(struct rendezvous_entry * e,struct rendezvous_endpoint * ep)101 rendezvous_attach(struct rendezvous_entry *e, struct rendezvous_endpoint *ep)
102 {
103 int error;
104
105 error = e->proxy.callback(RENDEZVOUS_ATTACH, e);
106 if (error == 0) {
107 error = e->target.callback(RENDEZVOUS_ATTACH, e);
108 if (error != 0) {
109 e->proxy.callback(RENDEZVOUS_DETACH, e);
110 ep->device = NULL;
111 ep->callback = NULL;
112 }
113 }
114 return (error);
115 }
116
117 /*
118 * Create an entry for the proxy in the rendezvous list. The name parameter
119 * indicates the name of the device that is the target endpoint for this
120 * rendezvous. The callback will be invoked as soon as the target is
121 * registered: either immediately if the target registered itself earlier,
122 * or once the target registers. Returns ENXIO if the target has not yet
123 * registered.
124 */
125 static int
rendezvous_register_proxy(device_t dev,const char * name,rendezvous_callback_t callback)126 rendezvous_register_proxy(device_t dev, const char *name,
127 rendezvous_callback_t callback)
128 {
129 struct rendezvous_entry *e;
130
131 KASSERT(callback != NULL, ("callback must be set"));
132 SLIST_FOREACH(e, &rendezvoushead, entries) {
133 if (strcmp(name, e->target.name) == 0) {
134 /* the target is already attached */
135 e->proxy.name = device_get_nameunit(dev);
136 e->proxy.device = dev;
137 e->proxy.callback = callback;
138 return (rendezvous_attach(e, &e->proxy));
139 }
140 }
141 e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
142 e->proxy.name = device_get_nameunit(dev);
143 e->proxy.device = dev;
144 e->proxy.callback = callback;
145 e->target.name = name;
146 SLIST_INSERT_HEAD(&rendezvoushead, e, entries);
147 return (ENXIO);
148 }
149
150 /*
151 * Create an entry in the rendezvous list for the target.
152 * Returns ENXIO if the proxy has not yet registered.
153 */
154 static int
rendezvous_register_target(device_t dev,rendezvous_callback_t callback)155 rendezvous_register_target(device_t dev, rendezvous_callback_t callback)
156 {
157 struct rendezvous_entry *e;
158 const char *name;
159
160 KASSERT(callback != NULL, ("callback must be set"));
161 name = device_get_nameunit(dev);
162 SLIST_FOREACH(e, &rendezvoushead, entries) {
163 if (strcmp(name, e->target.name) == 0) {
164 e->target.device = dev;
165 e->target.callback = callback;
166 return (rendezvous_attach(e, &e->target));
167 }
168 }
169 e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
170 e->target.name = name;
171 e->target.device = dev;
172 e->target.callback = callback;
173 SLIST_INSERT_HEAD(&rendezvoushead, e, entries);
174 return (ENXIO);
175 }
176
177 /*
178 * Remove the registration for the proxy.
179 */
180 static int
rendezvous_unregister_proxy(device_t dev)181 rendezvous_unregister_proxy(device_t dev)
182 {
183 struct rendezvous_entry *e;
184 int error = 0;
185
186 SLIST_FOREACH(e, &rendezvoushead, entries) {
187 if (e->proxy.device == dev) {
188 if (e->target.device == NULL) {
189 SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries);
190 free(e, M_MIIPROXY);
191 return (0);
192 } else {
193 e->proxy.callback(RENDEZVOUS_DETACH, e);
194 e->target.callback(RENDEZVOUS_DETACH, e);
195 }
196 e->proxy.device = NULL;
197 e->proxy.callback = NULL;
198 return (error);
199 }
200 }
201 return (ENOENT);
202 }
203
204 /*
205 * Remove the registration for the target.
206 */
207 static int
rendezvous_unregister_target(device_t dev)208 rendezvous_unregister_target(device_t dev)
209 {
210 struct rendezvous_entry *e;
211 int error = 0;
212
213 SLIST_FOREACH(e, &rendezvoushead, entries) {
214 if (e->target.device == dev) {
215 if (e->proxy.device == NULL) {
216 SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries);
217 free(e, M_MIIPROXY);
218 return (0);
219 } else {
220 e->proxy.callback(RENDEZVOUS_DETACH, e);
221 e->target.callback(RENDEZVOUS_DETACH, e);
222 }
223 e->target.device = NULL;
224 e->target.callback = NULL;
225 return (error);
226 }
227 }
228 return (ENOENT);
229 }
230
231 /*
232 * Functions of the proxy that is interposed between the ethernet interface
233 * driver and the miibus device.
234 */
235
236 static int
miiproxy_rendezvous_callback(enum rendezvous_op op,struct rendezvous_entry * rendezvous)237 miiproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
238 {
239 struct miiproxy_softc *sc = device_get_softc(rendezvous->proxy.device);
240
241 switch (op) {
242 case RENDEZVOUS_ATTACH:
243 sc->mdio = device_get_parent(rendezvous->target.device);
244 break;
245 case RENDEZVOUS_DETACH:
246 sc->mdio = NULL;
247 break;
248 }
249 return (0);
250 }
251
252 static int
miiproxy_probe(device_t dev)253 miiproxy_probe(device_t dev)
254 {
255 device_set_desc(dev, "MII/MDIO proxy, MII side");
256
257 return (BUS_PROBE_SPECIFIC);
258 }
259
260 static int
miiproxy_attach(device_t dev)261 miiproxy_attach(device_t dev)
262 {
263
264 /*
265 * The ethernet interface needs to call mii_attach_proxy() to pass
266 * the relevant parameters for rendezvous with the MDIO target.
267 */
268 return (bus_generic_attach(dev));
269 }
270
271 static int
miiproxy_detach(device_t dev)272 miiproxy_detach(device_t dev)
273 {
274
275 rendezvous_unregister_proxy(dev);
276 bus_generic_detach(dev);
277 return (0);
278 }
279
280 static int
miiproxy_readreg(device_t dev,int phy,int reg)281 miiproxy_readreg(device_t dev, int phy, int reg)
282 {
283 struct miiproxy_softc *sc = device_get_softc(dev);
284
285 if (sc->mdio != NULL)
286 return (MDIO_READREG(sc->mdio, phy, reg));
287 return (-1);
288 }
289
290 static int
miiproxy_writereg(device_t dev,int phy,int reg,int val)291 miiproxy_writereg(device_t dev, int phy, int reg, int val)
292 {
293 struct miiproxy_softc *sc = device_get_softc(dev);
294
295 if (sc->mdio != NULL)
296 return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
297 return (-1);
298 }
299
300 static void
miiproxy_statchg(device_t dev)301 miiproxy_statchg(device_t dev)
302 {
303
304 MIIBUS_STATCHG(device_get_parent(dev));
305 }
306
307 static void
miiproxy_linkchg(device_t dev)308 miiproxy_linkchg(device_t dev)
309 {
310
311 MIIBUS_LINKCHG(device_get_parent(dev));
312 }
313
314 static void
miiproxy_mediainit(device_t dev)315 miiproxy_mediainit(device_t dev)
316 {
317
318 MIIBUS_MEDIAINIT(device_get_parent(dev));
319 }
320
321 /*
322 * Functions for the MDIO target device driver.
323 */
324 static int
mdioproxy_rendezvous_callback(enum rendezvous_op op,struct rendezvous_entry * rendezvous)325 mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
326 {
327 return (0);
328 }
329
330 static void
mdioproxy_identify(driver_t * driver,device_t parent)331 mdioproxy_identify(driver_t *driver, device_t parent)
332 {
333 if (device_find_child(parent, driver->name, -1) == NULL) {
334 BUS_ADD_CHILD(parent, 0, driver->name, -1);
335 }
336 }
337
338 static int
mdioproxy_probe(device_t dev)339 mdioproxy_probe(device_t dev)
340 {
341 device_set_desc(dev, "MII/MDIO proxy, MDIO side");
342
343 return (BUS_PROBE_SPECIFIC);
344 }
345
346 static int
mdioproxy_attach(device_t dev)347 mdioproxy_attach(device_t dev)
348 {
349
350 rendezvous_register_target(dev, mdioproxy_rendezvous_callback);
351 return (bus_generic_attach(dev));
352 }
353
354 static int
mdioproxy_detach(device_t dev)355 mdioproxy_detach(device_t dev)
356 {
357
358 rendezvous_unregister_target(dev);
359 bus_generic_detach(dev);
360 return (0);
361 }
362
363 /*
364 * Attach this proxy in place of miibus. The target MDIO must be attached
365 * already. Returns NULL on error.
366 */
367 device_t
mii_attach_proxy(device_t dev)368 mii_attach_proxy(device_t dev)
369 {
370 struct miiproxy_softc *sc;
371 int error;
372 const char *name;
373 device_t miiproxy;
374
375 if (resource_string_value(device_get_name(dev),
376 device_get_unit(dev), "mdio", &name) != 0) {
377 if (bootverbose)
378 printf("mii_attach_proxy: not attaching, no mdio"
379 " device hint for %s\n", device_get_nameunit(dev));
380 return (NULL);
381 }
382
383 miiproxy = device_add_child(dev, miiproxy_driver.name, -1);
384 error = bus_generic_attach(dev);
385 if (error != 0) {
386 device_printf(dev, "can't attach miiproxy\n");
387 return (NULL);
388 }
389 sc = device_get_softc(miiproxy);
390 sc->parent = dev;
391 sc->proxy = miiproxy;
392 if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) {
393 device_printf(dev, "can't attach proxy\n");
394 return (NULL);
395 }
396 device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio));
397 return (miiproxy);
398 }
399
400 static device_method_t miiproxy_methods[] = {
401 /* device interface */
402 DEVMETHOD(device_probe, miiproxy_probe),
403 DEVMETHOD(device_attach, miiproxy_attach),
404 DEVMETHOD(device_detach, miiproxy_detach),
405 DEVMETHOD(device_shutdown, bus_generic_shutdown),
406
407 /* MII interface */
408 DEVMETHOD(miibus_readreg, miiproxy_readreg),
409 DEVMETHOD(miibus_writereg, miiproxy_writereg),
410 DEVMETHOD(miibus_statchg, miiproxy_statchg),
411 DEVMETHOD(miibus_linkchg, miiproxy_linkchg),
412 DEVMETHOD(miibus_mediainit, miiproxy_mediainit),
413
414 DEVMETHOD_END
415 };
416
417 static device_method_t mdioproxy_methods[] = {
418 /* device interface */
419 DEVMETHOD(device_identify, mdioproxy_identify),
420 DEVMETHOD(device_probe, mdioproxy_probe),
421 DEVMETHOD(device_attach, mdioproxy_attach),
422 DEVMETHOD(device_detach, mdioproxy_detach),
423 DEVMETHOD(device_shutdown, bus_generic_shutdown),
424
425 DEVMETHOD_END
426 };
427
428 DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
429 sizeof(struct miiproxy_softc));
430 DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
431 sizeof(struct mdioproxy_softc));
432
433 DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, 0, 0);
434 DRIVER_MODULE(miibus, miiproxy, miibus_driver, 0, 0);
435 MODULE_VERSION(miiproxy, 1);
436 MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
437