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