/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2011-2012 Stefan Bethke. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include <sys/param.h> #include <sys/bus.h> #include <sys/kernel.h> #include <sys/malloc.h> #include <sys/module.h> #include <sys/socket.h> #include <sys/sockio.h> #include <sys/systm.h> #include <net/if.h> #include <net/if_media.h> #include <dev/etherswitch/miiproxy.h> #include <dev/mii/mii.h> #include <dev/mii/miivar.h> #include "mdio_if.h" #include "miibus_if.h" MALLOC_DECLARE(M_MIIPROXY); MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures"); driver_t miiproxy_driver; driver_t mdioproxy_driver; struct miiproxy_softc { device_t parent; device_t proxy; device_t mdio; }; struct mdioproxy_softc { }; /* * The rendezvous data structures and functions allow two device endpoints to * match up, so that the proxy endpoint can be associated with a target * endpoint. The proxy has to know the device name of the target that it * wants to associate with, for example through a hint. The rendezvous code * makes no assumptions about the devices that want to meet. */ struct rendezvous_entry; enum rendezvous_op { RENDEZVOUS_ATTACH, RENDEZVOUS_DETACH }; typedef int (*rendezvous_callback_t)(enum rendezvous_op, struct rendezvous_entry *); static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead = SLIST_HEAD_INITIALIZER(rendezvoushead); struct rendezvous_endpoint { device_t device; const char *name; rendezvous_callback_t callback; }; struct rendezvous_entry { SLIST_ENTRY(rendezvous_entry) entries; struct rendezvous_endpoint proxy; struct rendezvous_endpoint target; }; /* * Call the callback routines for both the proxy and the target. If either * returns an error, undo the attachment. */ static int rendezvous_attach(struct rendezvous_entry *e, struct rendezvous_endpoint *ep) { int error; error = e->proxy.callback(RENDEZVOUS_ATTACH, e); if (error == 0) { error = e->target.callback(RENDEZVOUS_ATTACH, e); if (error != 0) { e->proxy.callback(RENDEZVOUS_DETACH, e); ep->device = NULL; ep->callback = NULL; } } return (error); } /* * Create an entry for the proxy in the rendezvous list. The name parameter * indicates the name of the device that is the target endpoint for this * rendezvous. The callback will be invoked as soon as the target is * registered: either immediately if the target registered itself earlier, * or once the target registers. Returns ENXIO if the target has not yet * registered. */ static int rendezvous_register_proxy(device_t dev, const char *name, rendezvous_callback_t callback) { struct rendezvous_entry *e; KASSERT(callback != NULL, ("callback must be set")); SLIST_FOREACH(e, &rendezvoushead, entries) { if (strcmp(name, e->target.name) == 0) { /* the target is already attached */ e->proxy.name = device_get_nameunit(dev); e->proxy.device = dev; e->proxy.callback = callback; return (rendezvous_attach(e, &e->proxy)); } } e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO); e->proxy.name = device_get_nameunit(dev); e->proxy.device = dev; e->proxy.callback = callback; e->target.name = name; SLIST_INSERT_HEAD(&rendezvoushead, e, entries); return (ENXIO); } /* * Create an entry in the rendezvous list for the target. * Returns ENXIO if the proxy has not yet registered. */ static int rendezvous_register_target(device_t dev, rendezvous_callback_t callback) { struct rendezvous_entry *e; const char *name; KASSERT(callback != NULL, ("callback must be set")); name = device_get_nameunit(dev); SLIST_FOREACH(e, &rendezvoushead, entries) { if (strcmp(name, e->target.name) == 0) { e->target.device = dev; e->target.callback = callback; return (rendezvous_attach(e, &e->target)); } } e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO); e->target.name = name; e->target.device = dev; e->target.callback = callback; SLIST_INSERT_HEAD(&rendezvoushead, e, entries); return (ENXIO); } /* * Remove the registration for the proxy. */ static int rendezvous_unregister_proxy(device_t dev) { struct rendezvous_entry *e; int error = 0; SLIST_FOREACH(e, &rendezvoushead, entries) { if (e->proxy.device == dev) { if (e->target.device == NULL) { SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries); free(e, M_MIIPROXY); return (0); } else { e->proxy.callback(RENDEZVOUS_DETACH, e); e->target.callback(RENDEZVOUS_DETACH, e); } e->proxy.device = NULL; e->proxy.callback = NULL; return (error); } } return (ENOENT); } /* * Remove the registration for the target. */ static int rendezvous_unregister_target(device_t dev) { struct rendezvous_entry *e; int error = 0; SLIST_FOREACH(e, &rendezvoushead, entries) { if (e->target.device == dev) { if (e->proxy.device == NULL) { SLIST_REMOVE(&rendezvoushead, e, rendezvous_entry, entries); free(e, M_MIIPROXY); return (0); } else { e->proxy.callback(RENDEZVOUS_DETACH, e); e->target.callback(RENDEZVOUS_DETACH, e); } e->target.device = NULL; e->target.callback = NULL; return (error); } } return (ENOENT); } /* * Functions of the proxy that is interposed between the ethernet interface * driver and the miibus device. */ static int miiproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous) { struct miiproxy_softc *sc = device_get_softc(rendezvous->proxy.device); switch (op) { case RENDEZVOUS_ATTACH: sc->mdio = device_get_parent(rendezvous->target.device); break; case RENDEZVOUS_DETACH: sc->mdio = NULL; break; } return (0); } static int miiproxy_probe(device_t dev) { device_set_desc(dev, "MII/MDIO proxy, MII side"); return (BUS_PROBE_SPECIFIC); } static int miiproxy_attach(device_t dev) { /* * The ethernet interface needs to call mii_attach_proxy() to pass * the relevant parameters for rendezvous with the MDIO target. */ return (bus_generic_attach(dev)); } static int miiproxy_detach(device_t dev) { rendezvous_unregister_proxy(dev); bus_generic_detach(dev); return (0); } static int miiproxy_readreg(device_t dev, int phy, int reg) { struct miiproxy_softc *sc = device_get_softc(dev); if (sc->mdio != NULL) return (MDIO_READREG(sc->mdio, phy, reg)); return (-1); } static int miiproxy_writereg(device_t dev, int phy, int reg, int val) { struct miiproxy_softc *sc = device_get_softc(dev); if (sc->mdio != NULL) return (MDIO_WRITEREG(sc->mdio, phy, reg, val)); return (-1); } static void miiproxy_statchg(device_t dev) { MIIBUS_STATCHG(device_get_parent(dev)); } static void miiproxy_linkchg(device_t dev) { MIIBUS_LINKCHG(device_get_parent(dev)); } static void miiproxy_mediainit(device_t dev) { MIIBUS_MEDIAINIT(device_get_parent(dev)); } /* * Functions for the MDIO target device driver. */ static int mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous) { return (0); } static void mdioproxy_identify(driver_t *driver, device_t parent) { device_t child; if (device_find_child(parent, driver->name, -1) == NULL) { child = BUS_ADD_CHILD(parent, 0, driver->name, -1); } } static int mdioproxy_probe(device_t dev) { device_set_desc(dev, "MII/MDIO proxy, MDIO side"); return (BUS_PROBE_SPECIFIC); } static int mdioproxy_attach(device_t dev) { rendezvous_register_target(dev, mdioproxy_rendezvous_callback); return (bus_generic_attach(dev)); } static int mdioproxy_detach(device_t dev) { rendezvous_unregister_target(dev); bus_generic_detach(dev); return (0); } /* * Attach this proxy in place of miibus. The target MDIO must be attached * already. Returns NULL on error. */ device_t mii_attach_proxy(device_t dev) { struct miiproxy_softc *sc; int error; const char *name; device_t miiproxy; if (resource_string_value(device_get_name(dev), device_get_unit(dev), "mdio", &name) != 0) { if (bootverbose) printf("mii_attach_proxy: not attaching, no mdio" " device hint for %s\n", device_get_nameunit(dev)); return (NULL); } miiproxy = device_add_child(dev, miiproxy_driver.name, -1); error = bus_generic_attach(dev); if (error != 0) { device_printf(dev, "can't attach miiproxy\n"); return (NULL); } sc = device_get_softc(miiproxy); sc->parent = dev; sc->proxy = miiproxy; if (rendezvous_register_proxy(miiproxy, name, miiproxy_rendezvous_callback) != 0) { device_printf(dev, "can't attach proxy\n"); return (NULL); } device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio)); return (miiproxy); } static device_method_t miiproxy_methods[] = { /* device interface */ DEVMETHOD(device_probe, miiproxy_probe), DEVMETHOD(device_attach, miiproxy_attach), DEVMETHOD(device_detach, miiproxy_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* MII interface */ DEVMETHOD(miibus_readreg, miiproxy_readreg), DEVMETHOD(miibus_writereg, miiproxy_writereg), DEVMETHOD(miibus_statchg, miiproxy_statchg), DEVMETHOD(miibus_linkchg, miiproxy_linkchg), DEVMETHOD(miibus_mediainit, miiproxy_mediainit), DEVMETHOD_END }; static device_method_t mdioproxy_methods[] = { /* device interface */ DEVMETHOD(device_identify, mdioproxy_identify), DEVMETHOD(device_probe, mdioproxy_probe), DEVMETHOD(device_attach, mdioproxy_attach), DEVMETHOD(device_detach, mdioproxy_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD_END }; DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods, sizeof(struct miiproxy_softc)); DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods, sizeof(struct mdioproxy_softc)); devclass_t miiproxy_devclass; static devclass_t mdioproxy_devclass; DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0); DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0); MODULE_VERSION(miiproxy, 1); MODULE_DEPEND(miiproxy, miibus, 1, 1, 1);