miiproxy.c revision 234861
1/*- 2 * Copyright (c) 2011-2012 Stefan Bethke. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: head/sys/dev/etherswitch/miiproxy.c 234861 2012-05-01 06:11:38Z adrian $ 27 */ 28 29#include <sys/param.h> 30#include <sys/bus.h> 31#include <sys/kernel.h> 32#include <sys/module.h> 33#include <sys/socket.h> 34#include <sys/sockio.h> 35#include <sys/systm.h> 36 37#include <net/if.h> 38#include <net/if_arp.h> 39#include <net/ethernet.h> 40#include <net/if_dl.h> 41#include <net/if_media.h> 42#include <net/if_types.h> 43 44#include <dev/etherswitch/miiproxy.h> 45#include <dev/mii/mii.h> 46#include <dev/mii/miivar.h> 47 48#include "mdio_if.h" 49#include "miibus_if.h" 50 51 52MALLOC_DECLARE(M_MIIPROXY); 53MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures"); 54 55driver_t miiproxy_driver; 56driver_t mdioproxy_driver; 57 58struct miiproxy_softc { 59 device_t parent; 60 device_t proxy; 61 device_t mdio; 62}; 63 64struct mdioproxy_softc { 65}; 66 67/* 68 * The rendevous data structures and functions allow two device endpoints to 69 * match up, so that the proxy endpoint can be associated with a target 70 * endpoint. The proxy has to know the device name of the target that it 71 * wants to associate with, for example through a hint. The rendevous code 72 * makes no assumptions about the devices that want to meet. 73 */ 74struct rendevous_entry; 75 76enum rendevous_op { 77 RENDEVOUS_ATTACH, 78 RENDEVOUS_DETACH 79}; 80 81typedef int (*rendevous_callback_t)(enum rendevous_op, 82 struct rendevous_entry *); 83 84static SLIST_HEAD(rendevoushead, rendevous_entry) rendevoushead = 85 SLIST_HEAD_INITIALIZER(rendevoushead); 86 87struct rendevous_endpoint { 88 device_t device; 89 const char *name; 90 rendevous_callback_t callback; 91}; 92 93struct rendevous_entry { 94 SLIST_ENTRY(rendevous_entry) entries; 95 struct rendevous_endpoint proxy; 96 struct rendevous_endpoint target; 97}; 98 99/* 100 * Call the callback routines for both the proxy and the target. If either 101 * returns an error, undo the attachment. 102 */ 103static int 104rendevous_attach(struct rendevous_entry *e, struct rendevous_endpoint *ep) 105{ 106 int error; 107 108 error = e->proxy.callback(RENDEVOUS_ATTACH, e); 109 if (error == 0) { 110 error = e->target.callback(RENDEVOUS_ATTACH, e); 111 if (error != 0) { 112 e->proxy.callback(RENDEVOUS_DETACH, e); 113 ep->device = NULL; 114 ep->callback = NULL; 115 } 116 } 117 return (error); 118} 119 120/* 121 * Create an entry for the proxy in the rendevous list. The name parameter 122 * indicates the name of the device that is the target endpoint for this 123 * rendevous. The callback will be invoked as soon as the target is 124 * registered: either immediately if the target registered itself earlier, 125 * or once the target registers. Returns ENXIO if the target has not yet 126 * registered. 127 */ 128static int 129rendevous_register_proxy(device_t dev, const char *name, 130 rendevous_callback_t callback) 131{ 132 struct rendevous_entry *e; 133 134 KASSERT(callback != NULL, ("callback must be set")); 135 SLIST_FOREACH(e, &rendevoushead, entries) { 136 if (strcmp(name, e->target.name) == 0) { 137 /* the target is already attached */ 138 e->proxy.name = device_get_nameunit(dev); 139 e->proxy.device = dev; 140 e->proxy.callback = callback; 141 return (rendevous_attach(e, &e->proxy)); 142 } 143 } 144 e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO); 145 e->proxy.name = device_get_nameunit(dev); 146 e->proxy.device = dev; 147 e->proxy.callback = callback; 148 e->target.name = name; 149 SLIST_INSERT_HEAD(&rendevoushead, e, entries); 150 return (ENXIO); 151} 152 153/* 154 * Create an entry in the rendevous list for the target. 155 * Returns ENXIO if the proxy has not yet registered. 156 */ 157static int 158rendevous_register_target(device_t dev, rendevous_callback_t callback) 159{ 160 struct rendevous_entry *e; 161 const char *name; 162 163 KASSERT(callback != NULL, ("callback must be set")); 164 name = device_get_nameunit(dev); 165 SLIST_FOREACH(e, &rendevoushead, entries) { 166 if (strcmp(name, e->target.name) == 0) { 167 e->target.device = dev; 168 e->target.callback = callback; 169 return (rendevous_attach(e, &e->target)); 170 } 171 } 172 e = malloc(sizeof(*e), M_MIIPROXY, M_WAITOK | M_ZERO); 173 e->target.name = name; 174 e->target.device = dev; 175 e->target.callback = callback; 176 SLIST_INSERT_HEAD(&rendevoushead, e, entries); 177 return (ENXIO); 178} 179 180/* 181 * Remove the registration for the proxy. 182 */ 183static int 184rendevous_unregister_proxy(device_t dev) 185{ 186 struct rendevous_entry *e; 187 int error = 0; 188 189 SLIST_FOREACH(e, &rendevoushead, entries) { 190 if (e->proxy.device == dev) { 191 if (e->target.device == NULL) { 192 SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries); 193 free(e, M_MIIPROXY); 194 return (0); 195 } else { 196 e->proxy.callback(RENDEVOUS_DETACH, e); 197 e->target.callback(RENDEVOUS_DETACH, e); 198 } 199 e->proxy.device = NULL; 200 e->proxy.callback = NULL; 201 return (error); 202 } 203 } 204 return (ENOENT); 205} 206 207/* 208 * Remove the registration for the target. 209 */ 210static int 211rendevous_unregister_target(device_t dev) 212{ 213 struct rendevous_entry *e; 214 int error = 0; 215 216 SLIST_FOREACH(e, &rendevoushead, entries) { 217 if (e->target.device == dev) { 218 if (e->proxy.device == NULL) { 219 SLIST_REMOVE(&rendevoushead, e, rendevous_entry, entries); 220 free(e, M_MIIPROXY); 221 return (0); 222 } else { 223 e->proxy.callback(RENDEVOUS_DETACH, e); 224 e->target.callback(RENDEVOUS_DETACH, e); 225 } 226 e->target.device = NULL; 227 e->target.callback = NULL; 228 return (error); 229 } 230 } 231 return (ENOENT); 232} 233 234/* 235 * Functions of the proxy that is interposed between the ethernet interface 236 * driver and the miibus device. 237 */ 238 239static int 240miiproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous) 241{ 242 struct miiproxy_softc *sc = device_get_softc(rendevous->proxy.device); 243 244 switch (op) { 245 case RENDEVOUS_ATTACH: 246 sc->mdio = device_get_parent(rendevous->target.device); 247 break; 248 case RENDEVOUS_DETACH: 249 sc->mdio = NULL; 250 break; 251 } 252 return (0); 253} 254 255static int 256miiproxy_probe(device_t dev) 257{ 258 device_set_desc(dev, "MII/MDIO proxy, MII side"); 259 260 return (BUS_PROBE_SPECIFIC); 261} 262 263static int 264miiproxy_attach(device_t dev) 265{ 266 267 /* 268 * The ethernet interface needs to call mii_attach_proxy() to pass 269 * the relevant parameters for rendevous with the MDIO target. 270 */ 271 return (bus_generic_attach(dev)); 272} 273 274static int 275miiproxy_detach(device_t dev) 276{ 277 278 rendevous_unregister_proxy(dev); 279 bus_generic_detach(dev); 280 return (0); 281} 282 283static int 284miiproxy_readreg(device_t dev, int phy, int reg) 285{ 286 struct miiproxy_softc *sc = device_get_softc(dev); 287 288 if (sc->mdio != NULL) 289 return (MDIO_READREG(sc->mdio, phy, reg)); 290 return (-1); 291} 292 293static int 294miiproxy_writereg(device_t dev, int phy, int reg, int val) 295{ 296 struct miiproxy_softc *sc = device_get_softc(dev); 297 298 if (sc->mdio != NULL) 299 return (MDIO_WRITEREG(sc->mdio, phy, reg, val)); 300 return (-1); 301} 302 303static void 304miiproxy_statchg(device_t dev) 305{ 306 307 MIIBUS_STATCHG(device_get_parent(dev)); 308} 309 310static void 311miiproxy_linkchg(device_t dev) 312{ 313 314 MIIBUS_LINKCHG(device_get_parent(dev)); 315} 316 317static void 318miiproxy_mediainit(device_t dev) 319{ 320 321 MIIBUS_MEDIAINIT(device_get_parent(dev)); 322} 323 324/* 325 * Functions for the MDIO target device driver. 326 */ 327static int 328mdioproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous) 329{ 330 return (0); 331} 332 333static void 334mdioproxy_identify(driver_t *driver, device_t parent) 335{ 336 device_t child; 337 338 if (device_find_child(parent, driver->name, -1) == NULL) { 339 child = BUS_ADD_CHILD(parent, 0, driver->name, -1); 340 } 341} 342 343static int 344mdioproxy_probe(device_t dev) 345{ 346 device_set_desc(dev, "MII/MDIO proxy, MDIO side"); 347 348 return (BUS_PROBE_SPECIFIC); 349} 350 351static int 352mdioproxy_attach(device_t dev) 353{ 354 355 rendevous_register_target(dev, mdioproxy_rendevous_callback); 356 return (bus_generic_attach(dev)); 357} 358 359static int 360mdioproxy_detach(device_t dev) 361{ 362 363 rendevous_unregister_target(dev); 364 bus_generic_detach(dev); 365 return (0); 366} 367 368/* 369 * Attach this proxy in place of miibus. The target MDIO must be attached 370 * already. Returns NULL on error. 371 */ 372device_t 373mii_attach_proxy(device_t dev) 374{ 375 struct miiproxy_softc *sc; 376 int error; 377 const char *name; 378 device_t miiproxy; 379 380 if (resource_string_value(device_get_name(dev), 381 device_get_unit(dev), "mdio", &name) != 0) { 382 if (bootverbose) 383 printf("mii_attach_proxy: not attaching, no mdio" 384 " device hint for %s\n", device_get_nameunit(dev)); 385 return (NULL); 386 } 387 388 miiproxy = device_add_child(dev, miiproxy_driver.name, -1); 389 error = bus_generic_attach(dev); 390 if (error != 0) { 391 device_printf(dev, "can't attach miiproxy\n"); 392 return (NULL); 393 } 394 sc = device_get_softc(miiproxy); 395 sc->parent = dev; 396 sc->proxy = miiproxy; 397 if (rendevous_register_proxy(miiproxy, name, miiproxy_rendevous_callback) != 0) { 398 device_printf(dev, "can't attach proxy\n"); 399 return (NULL); 400 } 401 device_printf(miiproxy, "attached to target %s\n", device_get_nameunit(sc->mdio)); 402 return (miiproxy); 403} 404 405static device_method_t miiproxy_methods[] = { 406 /* device interface */ 407 DEVMETHOD(device_probe, miiproxy_probe), 408 DEVMETHOD(device_attach, miiproxy_attach), 409 DEVMETHOD(device_detach, miiproxy_detach), 410 DEVMETHOD(device_shutdown, bus_generic_shutdown), 411 412 /* MII interface */ 413 DEVMETHOD(miibus_readreg, miiproxy_readreg), 414 DEVMETHOD(miibus_writereg, miiproxy_writereg), 415 DEVMETHOD(miibus_statchg, miiproxy_statchg), 416 DEVMETHOD(miibus_linkchg, miiproxy_linkchg), 417 DEVMETHOD(miibus_mediainit, miiproxy_mediainit), 418 419 DEVMETHOD_END 420}; 421 422static device_method_t mdioproxy_methods[] = { 423 /* device interface */ 424 DEVMETHOD(device_identify, mdioproxy_identify), 425 DEVMETHOD(device_probe, mdioproxy_probe), 426 DEVMETHOD(device_attach, mdioproxy_attach), 427 DEVMETHOD(device_detach, mdioproxy_detach), 428 DEVMETHOD(device_shutdown, bus_generic_shutdown), 429 430 DEVMETHOD_END 431}; 432 433DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods, 434 sizeof(struct miiproxy_softc)); 435DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods, 436 sizeof(struct mdioproxy_softc)); 437 438devclass_t miiproxy_devclass; 439static devclass_t mdioproxy_devclass; 440 441DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0); 442DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0); 443MODULE_VERSION(miiproxy, 1); 444MODULE_DEPEND(miiproxy, miibus, 1, 1, 1); 445