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/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 52 MALLOC_DECLARE(M_MIIPROXY); 53 MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures"); 54 55 driver_t miiproxy_driver; 56 driver_t mdioproxy_driver; 57 58 struct miiproxy_softc { 59 device_t parent; 60 device_t proxy; 61 device_t mdio; 62 }; 63 64 struct 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 */ 74 struct rendevous_entry; 75 76 enum rendevous_op { 77 RENDEVOUS_ATTACH, 78 RENDEVOUS_DETACH 79 }; 80 81 typedef int (*rendevous_callback_t)(enum rendevous_op, 82 struct rendevous_entry *); 83 84 static SLIST_HEAD(rendevoushead, rendevous_entry) rendevoushead = 85 SLIST_HEAD_INITIALIZER(rendevoushead); 86 87 struct rendevous_endpoint { 88 device_t device; 89 const char *name; 90 rendevous_callback_t callback; 91 }; 92 93 struct 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 */ 103 static int 104 rendevous_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 */ 128 static int 129 rendevous_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 */ 157 static int 158 rendevous_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 */ 183 static int 184 rendevous_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 */ 210 static int 211 rendevous_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 239 static int 240 miiproxy_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 255 static int 256 miiproxy_probe(device_t dev) 257 { 258 device_set_desc(dev, "MII/MDIO proxy, MII side"); 259 260 return (BUS_PROBE_SPECIFIC); 261 } 262 263 static int 264 miiproxy_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 274 static int 275 miiproxy_detach(device_t dev) 276 { 277 278 rendevous_unregister_proxy(dev); 279 bus_generic_detach(dev); 280 return (0); 281 } 282 283 static int 284 miiproxy_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 293 static int 294 miiproxy_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 303 static void 304 miiproxy_statchg(device_t dev) 305 { 306 307 MIIBUS_STATCHG(device_get_parent(dev)); 308 } 309 310 static void 311 miiproxy_linkchg(device_t dev) 312 { 313 314 MIIBUS_LINKCHG(device_get_parent(dev)); 315 } 316 317 static void 318 miiproxy_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 */ 327 static int 328 mdioproxy_rendevous_callback(enum rendevous_op op, struct rendevous_entry *rendevous) 329 { 330 return (0); 331 } 332 333 static void 334 mdioproxy_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 343 static int 344 mdioproxy_probe(device_t dev) 345 { 346 device_set_desc(dev, "MII/MDIO proxy, MDIO side"); 347 348 return (BUS_PROBE_SPECIFIC); 349 } 350 351 static int 352 mdioproxy_attach(device_t dev) 353 { 354 355 rendevous_register_target(dev, mdioproxy_rendevous_callback); 356 return (bus_generic_attach(dev)); 357 } 358 359 static int 360 mdioproxy_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 */ 372 device_t 373 mii_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 405 static 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 422 static 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 433 DEFINE_CLASS_0(miiproxy, miiproxy_driver, miiproxy_methods, 434 sizeof(struct miiproxy_softc)); 435 DEFINE_CLASS_0(mdioproxy, mdioproxy_driver, mdioproxy_methods, 436 sizeof(struct mdioproxy_softc)); 437 438 devclass_t miiproxy_devclass; 439 static devclass_t mdioproxy_devclass; 440 441 DRIVER_MODULE(mdioproxy, mdio, mdioproxy_driver, mdioproxy_devclass, 0, 0); 442 DRIVER_MODULE(miibus, miiproxy, miibus_driver, miibus_devclass, 0, 0); 443 MODULE_VERSION(miiproxy, 1); 444 MODULE_DEPEND(miiproxy, miibus, 1, 1, 1); 445