1234861Sadrian/*-
2234861Sadrian * Copyright (c) 2011-2012 Stefan Bethke.
3234861Sadrian * All rights reserved.
4234861Sadrian *
5234861Sadrian * Redistribution and use in source and binary forms, with or without
6234861Sadrian * modification, are permitted provided that the following conditions
7234861Sadrian * are met:
8234861Sadrian * 1. Redistributions of source code must retain the above copyright
9234861Sadrian *    notice, this list of conditions and the following disclaimer.
10234861Sadrian * 2. Redistributions in binary form must reproduce the above copyright
11234861Sadrian *    notice, this list of conditions and the following disclaimer in the
12234861Sadrian *    documentation and/or other materials provided with the distribution.
13234861Sadrian *
14234861Sadrian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15234861Sadrian * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16234861Sadrian * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17234861Sadrian * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18234861Sadrian * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19234861Sadrian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20234861Sadrian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21234861Sadrian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22234861Sadrian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23234861Sadrian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24234861Sadrian * SUCH DAMAGE.
25234861Sadrian *
26234861Sadrian * $FreeBSD$
27234861Sadrian */
28234861Sadrian
29234861Sadrian#include <sys/param.h>
30234861Sadrian#include <sys/bus.h>
31234861Sadrian#include <sys/kernel.h>
32234861Sadrian#include <sys/module.h>
33234861Sadrian#include <sys/socket.h>
34234861Sadrian#include <sys/sockio.h>
35234861Sadrian#include <sys/systm.h>
36234861Sadrian
37234861Sadrian#include <net/if.h>
38234861Sadrian#include <net/if_arp.h>
39234861Sadrian#include <net/ethernet.h>
40234861Sadrian#include <net/if_dl.h>
41234861Sadrian#include <net/if_media.h>
42234861Sadrian#include <net/if_types.h>
43234861Sadrian
44234861Sadrian#include <dev/etherswitch/miiproxy.h>
45234861Sadrian#include <dev/mii/mii.h>
46234861Sadrian#include <dev/mii/miivar.h>
47234861Sadrian
48234861Sadrian#include "mdio_if.h"
49234861Sadrian#include "miibus_if.h"
50234861Sadrian
51234861Sadrian
52234861SadrianMALLOC_DECLARE(M_MIIPROXY);
53234861SadrianMALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
54234861Sadrian
55234861Sadriandriver_t miiproxy_driver;
56234861Sadriandriver_t mdioproxy_driver;
57234861Sadrian
58234861Sadrianstruct miiproxy_softc {
59234861Sadrian	device_t	parent;
60234861Sadrian	device_t	proxy;
61234861Sadrian	device_t	mdio;
62234861Sadrian};
63234861Sadrian
64234861Sadrianstruct mdioproxy_softc {
65234861Sadrian};
66234861Sadrian
67234861Sadrian/*
68234861Sadrian * The rendevous data structures and functions allow two device endpoints to
69234861Sadrian * match up, so that the proxy endpoint can be associated with a target
70234861Sadrian * endpoint.  The proxy has to know the device name of the target that it
71234861Sadrian * wants to associate with, for example through a hint.  The rendevous code
72234861Sadrian * makes no assumptions about the devices that want to meet.
73234861Sadrian */
74234861Sadrianstruct rendevous_entry;
75234861Sadrian
76234861Sadrianenum rendevous_op {
77234861Sadrian	RENDEVOUS_ATTACH,
78234861Sadrian	RENDEVOUS_DETACH
79234861Sadrian};
80234861Sadrian
81234861Sadriantypedef int (*rendevous_callback_t)(enum rendevous_op,
82234861Sadrian    struct rendevous_entry *);
83234861Sadrian
84234861Sadrianstatic SLIST_HEAD(rendevoushead, rendevous_entry) rendevoushead =
85234861Sadrian    SLIST_HEAD_INITIALIZER(rendevoushead);
86234861Sadrian
87234861Sadrianstruct rendevous_endpoint {
88234861Sadrian	device_t		device;
89234861Sadrian	const char		*name;
90234861Sadrian	rendevous_callback_t	callback;
91234861Sadrian};
92234861Sadrian
93234861Sadrianstruct rendevous_entry {
94234861Sadrian	SLIST_ENTRY(rendevous_entry)	entries;
95234861Sadrian	struct rendevous_endpoint	proxy;
96234861Sadrian	struct rendevous_endpoint	target;
97234861Sadrian};
98234861Sadrian
99234861Sadrian/*
100234861Sadrian * Call the callback routines for both the proxy and the target.  If either
101234861Sadrian * returns an error, undo the attachment.
102234861Sadrian */
103234861Sadrianstatic int
104234861Sadrianrendevous_attach(struct rendevous_entry *e, struct rendevous_endpoint *ep)
105234861Sadrian{
106234861Sadrian	int error;
107234861Sadrian
108234861Sadrian	error = e->proxy.callback(RENDEVOUS_ATTACH, e);
109234861Sadrian	if (error == 0) {
110234861Sadrian		error = e->target.callback(RENDEVOUS_ATTACH, e);
111234861Sadrian		if (error != 0) {
112234861Sadrian			e->proxy.callback(RENDEVOUS_DETACH, e);
113234861Sadrian			ep->device = NULL;
114234861Sadrian			ep->callback = NULL;
115234861Sadrian		}
116234861Sadrian	}
117234861Sadrian	return (error);
118234861Sadrian}
119234861Sadrian
120234861Sadrian/*
121234861Sadrian * Create an entry for the proxy in the rendevous list.  The name parameter
122234861Sadrian * indicates the name of the device that is the target endpoint for this
123234861Sadrian * rendevous.  The callback will be invoked as soon as the target is
124234861Sadrian * registered: either immediately if the target registered itself earlier,
125234861Sadrian * or once the target registers.  Returns ENXIO if the target has not yet
126234861Sadrian * registered.
127234861Sadrian */
128234861Sadrianstatic int
129234861Sadrianrendevous_register_proxy(device_t dev, const char *name,
130234861Sadrian    rendevous_callback_t callback)
131234861Sadrian{
132234861Sadrian	struct rendevous_entry *e;
133234861Sadrian
134234861Sadrian	KASSERT(callback != NULL, ("callback must be set"));
135234861Sadrian	SLIST_FOREACH(e, &rendevoushead, entries) {
136234861Sadrian		if (strcmp(name, e->target.name) == 0) {
137234861Sadrian			/* the target is already attached */
138234861Sadrian			e->proxy.name = device_get_nameunit(dev);
139234861Sadrian		    	e->proxy.device = dev;
140234861Sadrian		    	e->proxy.callback = callback;
141234861Sadrian			return (rendevous_attach(e, &e->proxy));
142234861Sadrian		}
143234861Sadrian	}
144234861Sadrian	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
145234861Sadrian	e->proxy.name = device_get_nameunit(dev);
146234861Sadrian    	e->proxy.device = dev;
147234861Sadrian    	e->proxy.callback = callback;
148234861Sadrian	e->target.name = name;
149234861Sadrian	SLIST_INSERT_HEAD(&rendevoushead, e, entries);
150234861Sadrian	return (ENXIO);
151234861Sadrian}
152234861Sadrian
153234861Sadrian/*
154234861Sadrian * Create an entry in the rendevous list for the target.
155234861Sadrian * Returns ENXIO if the proxy has not yet registered.
156234861Sadrian */
157234861Sadrianstatic int
158234861Sadrianrendevous_register_target(device_t dev, rendevous_callback_t callback)
159234861Sadrian{
160234861Sadrian	struct rendevous_entry *e;
161234861Sadrian	const char *name;
162234861Sadrian
163234861Sadrian	KASSERT(callback != NULL, ("callback must be set"));
164234861Sadrian	name = device_get_nameunit(dev);
165234861Sadrian	SLIST_FOREACH(e, &rendevoushead, entries) {
166234861Sadrian		if (strcmp(name, e->target.name) == 0) {
167234861Sadrian			e->target.device = dev;
168234861Sadrian			e->target.callback = callback;
169234861Sadrian			return (rendevous_attach(e, &e->target));
170234861Sadrian		}
171234861Sadrian	}
172234861Sadrian	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
173234861Sadrian	e->target.name = name;
174234861Sadrian    	e->target.device = dev;
175234861Sadrian	e->target.callback = callback;
176234861Sadrian	SLIST_INSERT_HEAD(&rendevoushead, e, entries);
177234861Sadrian	return (ENXIO);
178234861Sadrian}
179234861Sadrian
180234861Sadrian/*
181234861Sadrian * Remove the registration for the proxy.
182234861Sadrian */
183234861Sadrianstatic int
184234861Sadrianrendevous_unregister_proxy(device_t dev)
185234861Sadrian{
186234861Sadrian	struct rendevous_entry *e;
187234861Sadrian	int error = 0;
188234861Sadrian
189234861Sadrian	SLIST_FOREACH(e, &rendevoushead, entries) {
190234861Sadrian		if (e->proxy.device == dev) {
191234861Sadrian			if (e->target.device == NULL) {
192234861Sadrian				SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries);
193234861Sadrian				free(e, M_MIIPROXY);
194234861Sadrian				return (0);
195234861Sadrian			} else {
196234861Sadrian				e->proxy.callback(RENDEVOUS_DETACH, e);
197234861Sadrian				e->target.callback(RENDEVOUS_DETACH, e);
198234861Sadrian			}
199234861Sadrian			e->proxy.device = NULL;
200234861Sadrian			e->proxy.callback = NULL;
201234861Sadrian			return (error);
202234861Sadrian		}
203234861Sadrian	}
204234861Sadrian	return (ENOENT);
205234861Sadrian}
206234861Sadrian
207234861Sadrian/*
208234861Sadrian * Remove the registration for the target.
209234861Sadrian */
210234861Sadrianstatic int
211234861Sadrianrendevous_unregister_target(device_t dev)
212234861Sadrian{
213234861Sadrian	struct rendevous_entry *e;
214234861Sadrian	int error = 0;
215234861Sadrian
216234861Sadrian	SLIST_FOREACH(e, &rendevoushead, entries) {
217234861Sadrian		if (e->target.device == dev) {
218234861Sadrian			if (e->proxy.device == NULL) {
219234861Sadrian				SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries);
220234861Sadrian				free(e, M_MIIPROXY);
221234861Sadrian				return (0);
222234861Sadrian			} else {
223234861Sadrian				e->proxy.callback(RENDEVOUS_DETACH, e);
224234861Sadrian				e->target.callback(RENDEVOUS_DETACH, e);
225234861Sadrian			}
226234861Sadrian			e->target.device = NULL;
227234861Sadrian			e->target.callback = NULL;
228234861Sadrian			return (error);
229234861Sadrian		}
230234861Sadrian	}
231234861Sadrian	return (ENOENT);
232234861Sadrian}
233234861Sadrian
234234861Sadrian/*
235234861Sadrian * Functions of the proxy that is interposed between the ethernet interface
236234861Sadrian * driver and the miibus device.
237234861Sadrian */
238234861Sadrian
239234861Sadrianstatic int
240234861Sadrianmiiproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
241234861Sadrian{
242234861Sadrian	struct miiproxy_softc *sc = device_get_softc(rendevous->proxy.device);
243234861Sadrian
244234861Sadrian	switch (op) {
245234861Sadrian	case RENDEVOUS_ATTACH:
246234861Sadrian		sc->mdio = device_get_parent(rendevous->target.device);
247234861Sadrian		break;
248234861Sadrian	case RENDEVOUS_DETACH:
249234861Sadrian		sc->mdio = NULL;
250234861Sadrian		break;
251234861Sadrian	}
252234861Sadrian	return (0);
253234861Sadrian}
254234861Sadrian
255234861Sadrianstatic int
256234861Sadrianmiiproxy_probe(device_t dev)
257234861Sadrian{
258234861Sadrian	device_set_desc(dev, "MII/MDIO proxy, MII side");
259234861Sadrian
260234861Sadrian	return (BUS_PROBE_SPECIFIC);
261234861Sadrian}
262234861Sadrian
263234861Sadrianstatic int
264234861Sadrianmiiproxy_attach(device_t dev)
265234861Sadrian{
266234861Sadrian
267234861Sadrian	/*
268234861Sadrian	 * The ethernet interface needs to call mii_attach_proxy() to pass
269234861Sadrian	 * the relevant parameters for rendevous with the MDIO target.
270234861Sadrian	 */
271234861Sadrian	return (bus_generic_attach(dev));
272234861Sadrian}
273234861Sadrian
274234861Sadrianstatic int
275234861Sadrianmiiproxy_detach(device_t dev)
276234861Sadrian{
277234861Sadrian
278234861Sadrian	rendevous_unregister_proxy(dev);
279234861Sadrian	bus_generic_detach(dev);
280234861Sadrian	return (0);
281234861Sadrian}
282234861Sadrian
283234861Sadrianstatic int
284234861Sadrianmiiproxy_readreg(device_t dev, int phy, int reg)
285234861Sadrian{
286234861Sadrian	struct miiproxy_softc *sc = device_get_softc(dev);
287234861Sadrian
288234861Sadrian	if (sc->mdio != NULL)
289234861Sadrian		return (MDIO_READREG(sc->mdio, phy, reg));
290234861Sadrian	return (-1);
291234861Sadrian}
292234861Sadrian
293234861Sadrianstatic int
294234861Sadrianmiiproxy_writereg(device_t dev, int phy, int reg, int val)
295234861Sadrian{
296234861Sadrian	struct miiproxy_softc *sc = device_get_softc(dev);
297234861Sadrian
298234861Sadrian	if (sc->mdio != NULL)
299234861Sadrian		return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
300234861Sadrian	return (-1);
301234861Sadrian}
302234861Sadrian
303234861Sadrianstatic void
304234861Sadrianmiiproxy_statchg(device_t dev)
305234861Sadrian{
306234861Sadrian
307234861Sadrian	MIIBUS_STATCHG(device_get_parent(dev));
308234861Sadrian}
309234861Sadrian
310234861Sadrianstatic void
311234861Sadrianmiiproxy_linkchg(device_t dev)
312234861Sadrian{
313234861Sadrian
314234861Sadrian	MIIBUS_LINKCHG(device_get_parent(dev));
315234861Sadrian}
316234861Sadrian
317234861Sadrianstatic void
318234861Sadrianmiiproxy_mediainit(device_t dev)
319234861Sadrian{
320234861Sadrian
321234861Sadrian	MIIBUS_MEDIAINIT(device_get_parent(dev));
322234861Sadrian}
323234861Sadrian
324234861Sadrian/*
325234861Sadrian * Functions for the MDIO target device driver.
326234861Sadrian */
327234861Sadrianstatic int
328234861Sadrianmdioproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous)
329234861Sadrian{
330234861Sadrian	return (0);
331234861Sadrian}
332234861Sadrian
333234861Sadrianstatic void
334234861Sadrianmdioproxy_identify(driver_t *driver, device_t parent)
335234861Sadrian{
336234861Sadrian	device_t child;
337234861Sadrian
338234861Sadrian	if (device_find_child(parent, driver->name, -1) == NULL) {
339234861Sadrian		child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
340234861Sadrian	}
341234861Sadrian}
342234861Sadrian
343234861Sadrianstatic int
344234861Sadrianmdioproxy_probe(device_t dev)
345234861Sadrian{
346234861Sadrian	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
347234861Sadrian
348234861Sadrian	return (BUS_PROBE_SPECIFIC);
349234861Sadrian}
350234861Sadrian
351234861Sadrianstatic int
352234861Sadrianmdioproxy_attach(device_t dev)
353234861Sadrian{
354234861Sadrian
355234861Sadrian	rendevous_register_target(dev, mdioproxy_rendevous_callback);
356234861Sadrian	return (bus_generic_attach(dev));
357234861Sadrian}
358234861Sadrian
359234861Sadrianstatic int
360234861Sadrianmdioproxy_detach(device_t dev)
361234861Sadrian{
362234861Sadrian
363234861Sadrian	rendevous_unregister_target(dev);
364234861Sadrian	bus_generic_detach(dev);
365234861Sadrian	return (0);
366234861Sadrian}
367234861Sadrian
368234861Sadrian/*
369234861Sadrian * Attach this proxy in place of miibus.  The target MDIO must be attached
370234861Sadrian * already.  Returns NULL on error.
371234861Sadrian */
372234861Sadriandevice_t
373234861Sadrianmii_attach_proxy(device_t dev)
374234861Sadrian{
375234861Sadrian	struct miiproxy_softc *sc;
376234861Sadrian	int		error;
377234861Sadrian	const char	*name;
378234861Sadrian	device_t	miiproxy;
379234861Sadrian
380234861Sadrian	if (resource_string_value(device_get_name(dev),
381234861Sadrian	    device_get_unit(dev), "mdio", &name) != 0) {
382234861Sadrian	    	if (bootverbose)
383234861Sadrian			printf("mii_attach_proxy: not attaching, no mdio"
384234861Sadrian			    " device hint for %s\n", device_get_nameunit(dev));
385234861Sadrian		return (NULL);
386234861Sadrian	}
387234861Sadrian
388234861Sadrian	miiproxy = device_add_child(dev, miiproxy_driver.name, -1);
389234861Sadrian	error = bus_generic_attach(dev);
390234861Sadrian	if (error != 0) {
391234861Sadrian		device_printf(dev, "can't attach miiproxy\n");
392234861Sadrian		return (NULL);
393234861Sadrian	}
394234861Sadrian	sc = device_get_softc(miiproxy);
395234861Sadrian	sc->parent = dev;
396234861Sadrian	sc->proxy = miiproxy;
397234861Sadrian	if (rendevous_register_proxy(miiproxy, name, miiproxy_rendevous_callback) != 0) {
398234861Sadrian		device_printf(dev, "can't attach proxy\n");
399234861Sadrian		return (NULL);
400234861Sadrian	}
401234861Sadrian	device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio));
402234861Sadrian	return (miiproxy);
403234861Sadrian}
404234861Sadrian
405234861Sadrianstatic device_method_t miiproxy_methods[] = {
406234861Sadrian	/* device interface */
407234861Sadrian	DEVMETHOD(device_probe,		miiproxy_probe),
408234861Sadrian	DEVMETHOD(device_attach,	miiproxy_attach),
409234861Sadrian	DEVMETHOD(device_detach,	miiproxy_detach),
410234861Sadrian	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
411234861Sadrian
412234861Sadrian	/* MII interface */
413234861Sadrian	DEVMETHOD(miibus_readreg,	miiproxy_readreg),
414234861Sadrian	DEVMETHOD(miibus_writereg,	miiproxy_writereg),
415234861Sadrian	DEVMETHOD(miibus_statchg,	miiproxy_statchg),
416234861Sadrian	DEVMETHOD(miibus_linkchg,	miiproxy_linkchg),
417234861Sadrian	DEVMETHOD(miibus_mediainit,	miiproxy_mediainit),
418234861Sadrian
419234861Sadrian	DEVMETHOD_END
420234861Sadrian};
421234861Sadrian
422234861Sadrianstatic device_method_t mdioproxy_methods[] = {
423234861Sadrian	/* device interface */
424234861Sadrian	DEVMETHOD(device_identify,	mdioproxy_identify),
425234861Sadrian	DEVMETHOD(device_probe,		mdioproxy_probe),
426234861Sadrian	DEVMETHOD(device_attach,	mdioproxy_attach),
427234861Sadrian	DEVMETHOD(device_detach,	mdioproxy_detach),
428234861Sadrian	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
429234861Sadrian
430234861Sadrian	DEVMETHOD_END
431234861Sadrian};
432234861Sadrian
433234861SadrianDEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
434234861Sadrian    sizeof(struct miiproxy_softc));
435234861SadrianDEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
436234861Sadrian    sizeof(struct mdioproxy_softc));
437234861Sadrian
438234861Sadriandevclass_t miiproxy_devclass;
439234861Sadrianstatic devclass_t mdioproxy_devclass;
440234861Sadrian
441234861SadrianDRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0);
442234861SadrianDRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0);
443234861SadrianMODULE_VERSION(miiproxy, 1);
444234861SadrianMODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
445