xref: /freebsd/sys/dev/etherswitch/miiproxy.c (revision 18250ec6c089c0c50cbd9fd87d78e03ff89916df)
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 	bus_attach_children(dev);
269 	return (0);
270 }
271 
272 static int
miiproxy_detach(device_t dev)273 miiproxy_detach(device_t dev)
274 {
275 
276 	rendezvous_unregister_proxy(dev);
277 	bus_generic_detach(dev);
278 	return (0);
279 }
280 
281 static int
miiproxy_readreg(device_t dev,int phy,int reg)282 miiproxy_readreg(device_t dev, int phy, int reg)
283 {
284 	struct miiproxy_softc *sc = device_get_softc(dev);
285 
286 	if (sc->mdio != NULL)
287 		return (MDIO_READREG(sc->mdio, phy, reg));
288 	return (-1);
289 }
290 
291 static int
miiproxy_writereg(device_t dev,int phy,int reg,int val)292 miiproxy_writereg(device_t dev, int phy, int reg, int val)
293 {
294 	struct miiproxy_softc *sc = device_get_softc(dev);
295 
296 	if (sc->mdio != NULL)
297 		return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
298 	return (-1);
299 }
300 
301 static void
miiproxy_statchg(device_t dev)302 miiproxy_statchg(device_t dev)
303 {
304 
305 	MIIBUS_STATCHG(device_get_parent(dev));
306 }
307 
308 static void
miiproxy_linkchg(device_t dev)309 miiproxy_linkchg(device_t dev)
310 {
311 
312 	MIIBUS_LINKCHG(device_get_parent(dev));
313 }
314 
315 static void
miiproxy_mediainit(device_t dev)316 miiproxy_mediainit(device_t dev)
317 {
318 
319 	MIIBUS_MEDIAINIT(device_get_parent(dev));
320 }
321 
322 /*
323  * Functions for the MDIO target device driver.
324  */
325 static int
mdioproxy_rendezvous_callback(enum rendezvous_op op,struct rendezvous_entry * rendezvous)326 mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
327 {
328 	return (0);
329 }
330 
331 static void
mdioproxy_identify(driver_t * driver,device_t parent)332 mdioproxy_identify(driver_t *driver, device_t parent)
333 {
334 	if (device_find_child(parent, driver->name, -1) == NULL) {
335 		BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY);
336 	}
337 }
338 
339 static int
mdioproxy_probe(device_t dev)340 mdioproxy_probe(device_t dev)
341 {
342 	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
343 
344 	return (BUS_PROBE_SPECIFIC);
345 }
346 
347 static int
mdioproxy_attach(device_t dev)348 mdioproxy_attach(device_t dev)
349 {
350 
351 	rendezvous_register_target(dev, mdioproxy_rendezvous_callback);
352 	bus_attach_children(dev);
353 	return (0);
354 }
355 
356 static int
mdioproxy_detach(device_t dev)357 mdioproxy_detach(device_t dev)
358 {
359 
360 	rendezvous_unregister_target(dev);
361 	bus_generic_detach(dev);
362 	return (0);
363 }
364 
365 /*
366  * Attach this proxy in place of miibus.  The target MDIO must be attached
367  * already.  Returns NULL on error.
368  */
369 device_t
mii_attach_proxy(device_t dev)370 mii_attach_proxy(device_t dev)
371 {
372 	struct miiproxy_softc *sc;
373 	const char	*name;
374 	device_t	miiproxy;
375 
376 	if (resource_string_value(device_get_name(dev),
377 	    device_get_unit(dev), "mdio", &name) != 0) {
378 	    	if (bootverbose)
379 			printf("mii_attach_proxy: not attaching, no mdio"
380 			    " device hint for %s\n", device_get_nameunit(dev));
381 		return (NULL);
382 	}
383 
384 	miiproxy = device_add_child(dev, miiproxy_driver.name, DEVICE_UNIT_ANY);
385 	bus_attach_children(dev);
386 	sc = device_get_softc(miiproxy);
387 	sc->parent = dev;
388 	sc->proxy = miiproxy;
389 	if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) {
390 		device_printf(dev, "can't attach proxy\n");
391 		return (NULL);
392 	}
393 	device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio));
394 	return (miiproxy);
395 }
396 
397 static device_method_t miiproxy_methods[] = {
398 	/* device interface */
399 	DEVMETHOD(device_probe,		miiproxy_probe),
400 	DEVMETHOD(device_attach,	miiproxy_attach),
401 	DEVMETHOD(device_detach,	miiproxy_detach),
402 	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
403 
404 	/* MII interface */
405 	DEVMETHOD(miibus_readreg,	miiproxy_readreg),
406 	DEVMETHOD(miibus_writereg,	miiproxy_writereg),
407 	DEVMETHOD(miibus_statchg,	miiproxy_statchg),
408 	DEVMETHOD(miibus_linkchg,	miiproxy_linkchg),
409 	DEVMETHOD(miibus_mediainit,	miiproxy_mediainit),
410 
411 	DEVMETHOD_END
412 };
413 
414 static device_method_t mdioproxy_methods[] = {
415 	/* device interface */
416 	DEVMETHOD(device_identify,	mdioproxy_identify),
417 	DEVMETHOD(device_probe,		mdioproxy_probe),
418 	DEVMETHOD(device_attach,	mdioproxy_attach),
419 	DEVMETHOD(device_detach,	mdioproxy_detach),
420 	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
421 
422 	DEVMETHOD_END
423 };
424 
425 DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
426     sizeof(struct miiproxy_softc));
427 DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
428     sizeof(struct mdioproxy_softc));
429 
430 DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, 0, 0);
431 DRIVER_MODULE(miibus, miiproxy, miibus_driver, 0, 0);
432 MODULE_VERSION(miiproxy, 1);
433 MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
434