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 51 MALLOC_DECLARE(M_MIIPROXY); 52 MALLOC_DEFINE(M_MIIPROXY, "miiproxy", "miiproxy data structures"); 53 54 driver_t miiproxy_driver; 55 driver_t mdioproxy_driver; 56 57 struct miiproxy_softc { 58 device_t parent; 59 device_t proxy; 60 device_t mdio; 61 }; 62 63 struct 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 */ 73 struct rendezvous_entry; 74 75 enum rendezvous_op { 76 RENDEZVOUS_ATTACH, 77 RENDEZVOUS_DETACH 78 }; 79 80 typedef int (*rendezvous_callback_t)(enum rendezvous_op, 81 struct rendezvous_entry *); 82 83 static SLIST_HEAD(rendezvoushead, rendezvous_entry) rendezvoushead = 84 SLIST_HEAD_INITIALIZER(rendezvoushead); 85 86 struct rendezvous_endpoint { 87 device_t device; 88 const char *name; 89 rendezvous_callback_t callback; 90 }; 91 92 struct 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 */ 102 static int 103 rendezvous_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 */ 127 static int 128 rendezvous_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 */ 156 static int 157 rendezvous_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 */ 182 static int 183 rendezvous_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 */ 209 static int 210 rendezvous_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 238 static int 239 miiproxy_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 254 static int 255 miiproxy_probe(device_t dev) 256 { 257 device_set_desc(dev, "MII/MDIO proxy, MII side"); 258 259 return (BUS_PROBE_SPECIFIC); 260 } 261 262 static int 263 miiproxy_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 273 static int 274 miiproxy_detach(device_t dev) 275 { 276 277 rendezvous_unregister_proxy(dev); 278 bus_generic_detach(dev); 279 return (0); 280 } 281 282 static int 283 miiproxy_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 292 static int 293 miiproxy_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 302 static void 303 miiproxy_statchg(device_t dev) 304 { 305 306 MIIBUS_STATCHG(device_get_parent(dev)); 307 } 308 309 static void 310 miiproxy_linkchg(device_t dev) 311 { 312 313 MIIBUS_LINKCHG(device_get_parent(dev)); 314 } 315 316 static void 317 miiproxy_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 */ 326 static int 327 mdioproxy_rendezvous_callback(enum rendezvous_op op, struct rendezvous_entry *rendezvous) 328 { 329 return (0); 330 } 331 332 static void 333 mdioproxy_identify(driver_t *driver, device_t parent) 334 { 335 if (device_find_child(parent, driver->name, -1) == NULL) { 336 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