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
52MALLOC_DECLARE(M_MIIPROXY);
53MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
54
55driver_t miiproxy_driver;
56driver_t mdioproxy_driver;
57
58struct miiproxy_softc {
59	device_t	parent;
60	device_t	proxy;
61	device_t	mdio;
62};
63
64struct 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 */
74struct rendevous_entry;
75
76enum rendevous_op {
77	RENDEVOUS_ATTACH,
78	RENDEVOUS_DETACH
79};
80
81typedef int (*rendevous_callback_t)(enum rendevous_op,
82    struct rendevous_entry *);
83
84static SLIST_HEAD(rendevoushead, rendevous_entry) rendevoushead =
85    SLIST_HEAD_INITIALIZER(rendevoushead);
86
87struct rendevous_endpoint {
88	device_t		device;
89	const char		*name;
90	rendevous_callback_t	callback;
91};
92
93struct 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 */
103static int
104rendevous_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 */
128static int
129rendevous_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 */
157static int
158rendevous_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 */
183static int
184rendevous_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 */
210static int
211rendevous_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
239static int
240miiproxy_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
255static int
256miiproxy_probe(device_t dev)
257{
258	device_set_desc(dev, "MII/MDIO proxy, MII side");
259
260	return (BUS_PROBE_SPECIFIC);
261}
262
263static int
264miiproxy_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
274static int
275miiproxy_detach(device_t dev)
276{
277
278	rendevous_unregister_proxy(dev);
279	bus_generic_detach(dev);
280	return (0);
281}
282
283static int
284miiproxy_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
293static int
294miiproxy_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
303static void
304miiproxy_statchg(device_t dev)
305{
306
307	MIIBUS_STATCHG(device_get_parent(dev));
308}
309
310static void
311miiproxy_linkchg(device_t dev)
312{
313
314	MIIBUS_LINKCHG(device_get_parent(dev));
315}
316
317static void
318miiproxy_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 */
327static int
328mdioproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
329{
330	return (0);
331}
332
333static void
334mdioproxy_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
343static int
344mdioproxy_probe(device_t dev)
345{
346	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
347
348	return (BUS_PROBE_SPECIFIC);
349}
350
351static int
352mdioproxy_attach(device_t dev)
353{
354
355	rendevous_register_target(dev, mdioproxy_rendevous_callback);
356	return (bus_generic_attach(dev));
357}
358
359static int
360mdioproxy_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 */
372device_t
373mii_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
405static 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
422static 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
433DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
434    sizeof(struct miiproxy_softc));
435DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
436    sizeof(struct mdioproxy_softc));
437
438devclass_t miiproxy_devclass;
439static devclass_t mdioproxy_devclass;
440
441DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0);
442DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0);
443MODULE_VERSION(miiproxy, 1);
444MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
445