1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 * $FreeBSD$
29 */
30
31#include <sys/param.h>
32#include <sys/bus.h>
33#include <sys/kernel.h>
34#include <sys/malloc.h>
35#include <sys/module.h>
36#include <sys/socket.h>
37#include <sys/sockio.h>
38#include <sys/systm.h>
39
40#include <net/if.h>
41#include <net/if_media.h>
42
43#include <dev/etherswitch/miiproxy.h>
44#include <dev/mii/mii.h>
45#include <dev/mii/miivar.h>
46
47#include "mdio_if.h"
48#include "miibus_if.h"
49
50
51MALLOC_DECLARE(M_MIIPROXY);
52MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures");
53
54driver_t miiproxy_driver;
55driver_t mdioproxy_driver;
56
57struct miiproxy_softc {
58	device_t	parent;
59	device_t	proxy;
60	device_t	mdio;
61};
62
63struct mdioproxy_softc {
64};
65
66/*
67 * The rendezvous data structures and functions allow two device endpoints to
68 * match up, so that the proxy endpoint can be associated with a target
69 * endpoint.  The proxy has to know the device name of the target that it
70 * wants to associate with, for example through a hint.  The rendezvous code
71 * makes no assumptions about the devices that want to meet.
72 */
73struct rendezvous_entry;
74
75enum rendezvous_op {
76	RENDEZVOUS_ATTACH,
77	RENDEZVOUS_DETACH
78};
79
80typedef int (*rendezvous_callback_t)(enum rendezvous_op,
81    struct rendezvous_entry *);
82
83static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead =
84    SLIST_HEAD_INITIALIZER(rendezvoushead);
85
86struct rendezvous_endpoint {
87	device_t		device;
88	const char		*name;
89	rendezvous_callback_t	callback;
90};
91
92struct rendezvous_entry {
93	SLIST_ENTRY(rendezvous_entry)	entries;
94	struct rendezvous_endpoint	proxy;
95	struct rendezvous_endpoint	target;
96};
97
98/*
99 * Call the callback routines for both the proxy and the target.  If either
100 * returns an error, undo the attachment.
101 */
102static int
103rendezvous_attach(struct rendezvous_entry *e, struct rendezvous_endpoint *ep)
104{
105	int error;
106
107	error = e->proxy.callback(RENDEZVOUS_ATTACH, e);
108	if (error == 0) {
109		error = e->target.callback(RENDEZVOUS_ATTACH, e);
110		if (error != 0) {
111			e->proxy.callback(RENDEZVOUS_DETACH, e);
112			ep->device = NULL;
113			ep->callback = NULL;
114		}
115	}
116	return (error);
117}
118
119/*
120 * Create an entry for the proxy in the rendezvous list.  The name parameter
121 * indicates the name of the device that is the target endpoint for this
122 * rendezvous.  The callback will be invoked as soon as the target is
123 * registered: either immediately if the target registered itself earlier,
124 * or once the target registers.  Returns ENXIO if the target has not yet
125 * registered.
126 */
127static int
128rendezvous_register_proxy(device_t dev, const char *name,
129    rendezvous_callback_t callback)
130{
131	struct rendezvous_entry *e;
132
133	KASSERT(callback != NULL, ("callback must be set"));
134	SLIST_FOREACH(e, &rendezvoushead, entries) {
135		if (strcmp(name, e->target.name) == 0) {
136			/* the target is already attached */
137			e->proxy.name = device_get_nameunit(dev);
138		    	e->proxy.device = dev;
139		    	e->proxy.callback = callback;
140			return (rendezvous_attach(e, &e->proxy));
141		}
142	}
143	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
144	e->proxy.name = device_get_nameunit(dev);
145    	e->proxy.device = dev;
146    	e->proxy.callback = callback;
147	e->target.name = name;
148	SLIST_INSERT_HEAD(&rendezvoushead, e, entries);
149	return (ENXIO);
150}
151
152/*
153 * Create an entry in the rendezvous list for the target.
154 * Returns ENXIO if the proxy has not yet registered.
155 */
156static int
157rendezvous_register_target(device_t dev, rendezvous_callback_t callback)
158{
159	struct rendezvous_entry *e;
160	const char *name;
161
162	KASSERT(callback != NULL, ("callback must be set"));
163	name = device_get_nameunit(dev);
164	SLIST_FOREACH(e, &rendezvoushead, entries) {
165		if (strcmp(name, e->target.name) == 0) {
166			e->target.device = dev;
167			e->target.callback = callback;
168			return (rendezvous_attach(e, &e->target));
169		}
170	}
171	e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO);
172	e->target.name = name;
173    	e->target.device = dev;
174	e->target.callback = callback;
175	SLIST_INSERT_HEAD(&rendezvoushead, e, entries);
176	return (ENXIO);
177}
178
179/*
180 * Remove the registration for the proxy.
181 */
182static int
183rendezvous_unregister_proxy(device_t dev)
184{
185	struct rendezvous_entry *e;
186	int error = 0;
187
188	SLIST_FOREACH(e, &rendezvoushead, entries) {
189		if (e->proxy.device == dev) {
190			if (e->target.device == NULL) {
191				SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries);
192				free(e, M_MIIPROXY);
193				return (0);
194			} else {
195				e->proxy.callback(RENDEZVOUS_DETACH, e);
196				e->target.callback(RENDEZVOUS_DETACH, e);
197			}
198			e->proxy.device = NULL;
199			e->proxy.callback = NULL;
200			return (error);
201		}
202	}
203	return (ENOENT);
204}
205
206/*
207 * Remove the registration for the target.
208 */
209static int
210rendezvous_unregister_target(device_t dev)
211{
212	struct rendezvous_entry *e;
213	int error = 0;
214
215	SLIST_FOREACH(e, &rendezvoushead, entries) {
216		if (e->target.device == dev) {
217			if (e->proxy.device == NULL) {
218				SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries);
219				free(e, M_MIIPROXY);
220				return (0);
221			} else {
222				e->proxy.callback(RENDEZVOUS_DETACH, e);
223				e->target.callback(RENDEZVOUS_DETACH, e);
224			}
225			e->target.device = NULL;
226			e->target.callback = NULL;
227			return (error);
228		}
229	}
230	return (ENOENT);
231}
232
233/*
234 * Functions of the proxy that is interposed between the ethernet interface
235 * driver and the miibus device.
236 */
237
238static int
239miiproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
240{
241	struct miiproxy_softc *sc = device_get_softc(rendezvous->proxy.device);
242
243	switch (op) {
244	case RENDEZVOUS_ATTACH:
245		sc->mdio = device_get_parent(rendezvous->target.device);
246		break;
247	case RENDEZVOUS_DETACH:
248		sc->mdio = NULL;
249		break;
250	}
251	return (0);
252}
253
254static int
255miiproxy_probe(device_t dev)
256{
257	device_set_desc(dev, "MII/MDIO proxy, MII side");
258
259	return (BUS_PROBE_SPECIFIC);
260}
261
262static int
263miiproxy_attach(device_t dev)
264{
265
266	/*
267	 * The ethernet interface needs to call mii_attach_proxy() to pass
268	 * the relevant parameters for rendezvous with the MDIO target.
269	 */
270	return (bus_generic_attach(dev));
271}
272
273static int
274miiproxy_detach(device_t dev)
275{
276
277	rendezvous_unregister_proxy(dev);
278	bus_generic_detach(dev);
279	return (0);
280}
281
282static int
283miiproxy_readreg(device_t dev, int phy, int reg)
284{
285	struct miiproxy_softc *sc = device_get_softc(dev);
286
287	if (sc->mdio != NULL)
288		return (MDIO_READREG(sc->mdio, phy, reg));
289	return (-1);
290}
291
292static int
293miiproxy_writereg(device_t dev, int phy, int reg, int val)
294{
295	struct miiproxy_softc *sc = device_get_softc(dev);
296
297	if (sc->mdio != NULL)
298		return (MDIO_WRITEREG(sc->mdio, phy, reg, val));
299	return (-1);
300}
301
302static void
303miiproxy_statchg(device_t dev)
304{
305
306	MIIBUS_STATCHG(device_get_parent(dev));
307}
308
309static void
310miiproxy_linkchg(device_t dev)
311{
312
313	MIIBUS_LINKCHG(device_get_parent(dev));
314}
315
316static void
317miiproxy_mediainit(device_t dev)
318{
319
320	MIIBUS_MEDIAINIT(device_get_parent(dev));
321}
322
323/*
324 * Functions for the MDIO target device driver.
325 */
326static int
327mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous)
328{
329	return (0);
330}
331
332static void
333mdioproxy_identify(driver_t *driver, device_t parent)
334{
335	device_t child;
336
337	if (device_find_child(parent, driver->name, -1) == NULL) {
338		child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
339	}
340}
341
342static int
343mdioproxy_probe(device_t dev)
344{
345	device_set_desc(dev, "MII/MDIO proxy, MDIO side");
346
347	return (BUS_PROBE_SPECIFIC);
348}
349
350static int
351mdioproxy_attach(device_t dev)
352{
353
354	rendezvous_register_target(dev, mdioproxy_rendezvous_callback);
355	return (bus_generic_attach(dev));
356}
357
358static int
359mdioproxy_detach(device_t dev)
360{
361
362	rendezvous_unregister_target(dev);
363	bus_generic_detach(dev);
364	return (0);
365}
366
367/*
368 * Attach this proxy in place of miibus.  The target MDIO must be attached
369 * already.  Returns NULL on error.
370 */
371device_t
372mii_attach_proxy(device_t dev)
373{
374	struct miiproxy_softc *sc;
375	int		error;
376	const char	*name;
377	device_t	miiproxy;
378
379	if (resource_string_value(device_get_name(dev),
380	    device_get_unit(dev), "mdio", &name) != 0) {
381	    	if (bootverbose)
382			printf("mii_attach_proxy: not attaching, no mdio"
383			    " device hint for %s\n", device_get_nameunit(dev));
384		return (NULL);
385	}
386
387	miiproxy = device_add_child(dev, miiproxy_driver.name, -1);
388	error = bus_generic_attach(dev);
389	if (error != 0) {
390		device_printf(dev, "can't attach miiproxy\n");
391		return (NULL);
392	}
393	sc = device_get_softc(miiproxy);
394	sc->parent = dev;
395	sc->proxy = miiproxy;
396	if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) {
397		device_printf(dev, "can't attach proxy\n");
398		return (NULL);
399	}
400	device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio));
401	return (miiproxy);
402}
403
404static device_method_t miiproxy_methods[] = {
405	/* device interface */
406	DEVMETHOD(device_probe,		miiproxy_probe),
407	DEVMETHOD(device_attach,	miiproxy_attach),
408	DEVMETHOD(device_detach,	miiproxy_detach),
409	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
410
411	/* MII interface */
412	DEVMETHOD(miibus_readreg,	miiproxy_readreg),
413	DEVMETHOD(miibus_writereg,	miiproxy_writereg),
414	DEVMETHOD(miibus_statchg,	miiproxy_statchg),
415	DEVMETHOD(miibus_linkchg,	miiproxy_linkchg),
416	DEVMETHOD(miibus_mediainit,	miiproxy_mediainit),
417
418	DEVMETHOD_END
419};
420
421static device_method_t mdioproxy_methods[] = {
422	/* device interface */
423	DEVMETHOD(device_identify,	mdioproxy_identify),
424	DEVMETHOD(device_probe,		mdioproxy_probe),
425	DEVMETHOD(device_attach,	mdioproxy_attach),
426	DEVMETHOD(device_detach,	mdioproxy_detach),
427	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
428
429	DEVMETHOD_END
430};
431
432DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods,
433    sizeof(struct miiproxy_softc));
434DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods,
435    sizeof(struct mdioproxy_softc));
436
437devclass_t miiproxy_devclass;
438static devclass_t mdioproxy_devclass;
439
440DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0);
441DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0);
442MODULE_VERSION(miiproxy, 1);
443MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);
444