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