diff options
Diffstat (limited to 'sys/dev/etherswitch/miiproxy.c')
-rw-r--r-- | sys/dev/etherswitch/miiproxy.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/sys/dev/etherswitch/miiproxy.c b/sys/dev/etherswitch/miiproxy.c new file mode 100644 index 000000000000..79342a9e8e03 --- /dev/null +++ b/sys/dev/etherswitch/miiproxy.c @@ -0,0 +1,434 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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. + */ + +#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. + */ + bus_attach_children(dev); + return (0); +} + +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) +{ + if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) == NULL) { + BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY); + } +} + +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); + bus_attach_children(dev); + return (0); +} + +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; + 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, DEVICE_UNIT_ANY); + bus_attach_children(dev); + 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)); + +DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, 0, 0); +DRIVER_MODULE(miibus, miiproxy, miibus_driver, 0, 0); +MODULE_VERSION(miiproxy, 1); +MODULE_DEPEND(miiproxy, miibus, 1, 1, 1); +MODULE_DEPEND(miiproxy, mdio, 1, 1, 1); |