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