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