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: releng/11.0/sys/dev/etherswitch/miiproxy.c 281686 2015-04-18 07:34:39Z rpaulo $
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
49MALLOC_DECLARE(M_MIIPROXY);
50MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
51
52driver_t miiproxy_driver;
53driver_t mdioproxy_driver;
54
55struct miiproxy_softc {
56	device_t	parent;
57	device_t	proxy;
58	device_t	mdio;
59};
60
61struct 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 */
71struct rendezvous_entry;
72
73enum rendezvous_op {
74	RENDEZVOUS_ATTACH,
75	RENDEZVOUS_DETACH
76};
77
78typedef int (*rendezvous_callback_t)(enum rendezvous_op,
79    struct rendezvous_entry *);
80
81static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead =
82    SLIST_HEAD_INITIALIZER(rendezvoushead);
83
84struct rendezvous_endpoint {
85	device_t		device;
86	const char		*name;
87	rendezvous_callback_t	callback;
88};
89
90struct 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 */
100static int
101rendezvous_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 */
125static int
126rendezvous_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 */
154static int
155rendezvous_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 */
180static int
181rendezvous_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 */
207static int
208rendezvous_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
236static int
237miiproxy_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
252static int
253miiproxy_probe(device_t dev)
254{
255	device_set_desc(dev, "MII/MDIO proxy, MII side");
256
257	return (BUS_PROBE_SPECIFIC);
258}
259
260static int
261miiproxy_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
271static int
272miiproxy_detach(device_t dev)
273{
274
275	rendezvous_unregister_proxy(dev);
276	bus_generic_detach(dev);
277	return (0);
278}
279
280static int
281miiproxy_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
290static int
291miiproxy_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
300static void
301miiproxy_statchg(device_t dev)
302{
303
304	MIIBUS_STATCHG(device_get_parent(dev));
305}
306
307static void
308miiproxy_linkchg(device_t dev)
309{
310
311	MIIBUS_LINKCHG(device_get_parent(dev));
312}
313
314static void
315miiproxy_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 */
324static int
325mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
326{
327	return (0);
328}
329
330static void
331mdioproxy_identify(driver_t *driver, device_t parent)
332{
333	device_t child;
334
335	if (device_find_child(parent, driver->name, -1) == NULL) {
336		child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
337	}
338}
339
340static int
341mdioproxy_probe(device_t dev)
342{
343	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
344
345	return (BUS_PROBE_SPECIFIC);
346}
347
348static int
349mdioproxy_attach(device_t dev)
350{
351
352	rendezvous_register_target(dev, mdioproxy_rendezvous_callback);
353	return (bus_generic_attach(dev));
354}
355
356static int
357mdioproxy_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 */
369device_t
370mii_attach_proxy(device_t dev)
371{
372	struct miiproxy_softc *sc;
373	int		error;
374	const char	*name;
375	device_t	miiproxy;
376
377	if (resource_string_value(device_get_name(dev),
378	    device_get_unit(dev), "mdio", &name) != 0) {
379	    	if (bootverbose)
380			printf("mii_attach_proxy: not attaching, no mdio"
381			    " device hint for %s\n", device_get_nameunit(dev));
382		return (NULL);
383	}
384
385	miiproxy = device_add_child(dev, miiproxy_driver.name, -1);
386	error = bus_generic_attach(dev);
387	if (error != 0) {
388		device_printf(dev, "can't attach miiproxy\n");
389		return (NULL);
390	}
391	sc = device_get_softc(miiproxy);
392	sc->parent = dev;
393	sc->proxy = miiproxy;
394	if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) {
395		device_printf(dev, "can't attach proxy\n");
396		return (NULL);
397	}
398	device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio));
399	return (miiproxy);
400}
401
402static device_method_t miiproxy_methods[] = {
403	/* device interface */
404	DEVMETHOD(device_probe,		miiproxy_probe),
405	DEVMETHOD(device_attach,	miiproxy_attach),
406	DEVMETHOD(device_detach,	miiproxy_detach),
407	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
408
409	/* MII interface */
410	DEVMETHOD(miibus_readreg,	miiproxy_readreg),
411	DEVMETHOD(miibus_writereg,	miiproxy_writereg),
412	DEVMETHOD(miibus_statchg,	miiproxy_statchg),
413	DEVMETHOD(miibus_linkchg,	miiproxy_linkchg),
414	DEVMETHOD(miibus_mediainit,	miiproxy_mediainit),
415
416	DEVMETHOD_END
417};
418
419static device_method_t mdioproxy_methods[] = {
420	/* device interface */
421	DEVMETHOD(device_identify,	mdioproxy_identify),
422	DEVMETHOD(device_probe,		mdioproxy_probe),
423	DEVMETHOD(device_attach,	mdioproxy_attach),
424	DEVMETHOD(device_detach,	mdioproxy_detach),
425	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
426
427	DEVMETHOD_END
428};
429
430DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
431    sizeof(struct miiproxy_softc));
432DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
433    sizeof(struct mdioproxy_softc));
434
435devclass_t miiproxy_devclass;
436static devclass_t mdioproxy_devclass;
437
438DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0);
439DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0);
440MODULE_VERSION(miiproxy, 1);
441MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
442