1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2013 Luiz Otavio O Souza. 5 * Copyright (c) 2011-2012 Stefan Bethke. 6 * Copyright (c) 2012 Adrian Chadd. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * $FreeBSD$ 31 */ 32 33 #include "opt_platform.h" 34 35 #include <sys/param.h> 36 #include <sys/bus.h> 37 #include <sys/errno.h> 38 #include <sys/kernel.h> 39 #include <sys/lock.h> 40 #include <sys/malloc.h> 41 #include <sys/module.h> 42 #include <sys/mutex.h> 43 #include <sys/socket.h> 44 #include <sys/sockio.h> 45 #include <sys/sysctl.h> 46 #include <sys/systm.h> 47 #include <sys/types.h> 48 49 #include <net/if.h> 50 #include <net/ethernet.h> 51 #include <net/if_media.h> 52 #include <net/if_types.h> 53 #include <net/if_var.h> 54 55 #include <machine/bus.h> 56 #include <dev/mii/mii.h> 57 #include <dev/mii/miivar.h> 58 #include <dev/mdio/mdio.h> 59 60 #include <dev/etherswitch/etherswitch.h> 61 #include <dev/etherswitch/ip17x/ip17x_phy.h> 62 #include <dev/etherswitch/ip17x/ip17x_reg.h> 63 #include <dev/etherswitch/ip17x/ip17x_var.h> 64 #include <dev/etherswitch/ip17x/ip17x_vlans.h> 65 #include <dev/etherswitch/ip17x/ip175c.h> 66 #include <dev/etherswitch/ip17x/ip175d.h> 67 68 #ifdef FDT 69 #include <dev/fdt/fdt_common.h> 70 #include <dev/ofw/ofw_bus.h> 71 #include <dev/ofw/ofw_bus_subr.h> 72 #endif 73 74 #include "mdio_if.h" 75 #include "miibus_if.h" 76 #include "etherswitch_if.h" 77 78 MALLOC_DECLARE(M_IP17X); 79 MALLOC_DEFINE(M_IP17X, "ip17x", "ip17x data structures"); 80 81 static void ip17x_tick(void *); 82 static int ip17x_ifmedia_upd(struct ifnet *); 83 static void ip17x_ifmedia_sts(struct ifnet *, struct ifmediareq *); 84 85 static void 86 ip17x_identify(driver_t *driver, device_t parent) 87 { 88 if (device_find_child(parent, "ip17x", -1) == NULL) 89 BUS_ADD_CHILD(parent, 0, "ip17x", -1); 90 } 91 92 static int 93 ip17x_probe(device_t dev) 94 { 95 struct ip17x_softc *sc; 96 uint32_t oui, model, phy_id1, phy_id2; 97 #ifdef FDT 98 phandle_t ip17x_node; 99 pcell_t cell; 100 101 ip17x_node = fdt_find_compatible(OF_finddevice("/"), 102 "icplus,ip17x", 0); 103 104 if (ip17x_node == 0) 105 return (ENXIO); 106 #endif 107 108 sc = device_get_softc(dev); 109 110 /* Read ID from PHY 0. */ 111 phy_id1 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR1); 112 phy_id2 = MDIO_READREG(device_get_parent(dev), 0, MII_PHYIDR2); 113 114 oui = MII_OUI(phy_id1, phy_id2); 115 model = MII_MODEL(phy_id2); 116 /* We only care about IC+ devices. */ 117 if (oui != IP17X_OUI) { 118 device_printf(dev, 119 "Unsupported IC+ switch. Unknown OUI: %#x\n", oui); 120 return (ENXIO); 121 } 122 123 switch (model) { 124 case IP17X_IP175A: 125 sc->sc_switchtype = IP17X_SWITCH_IP175A; 126 break; 127 case IP17X_IP175C: 128 sc->sc_switchtype = IP17X_SWITCH_IP175C; 129 break; 130 default: 131 device_printf(dev, "Unsupported IC+ switch model: %#x\n", 132 model); 133 return (ENXIO); 134 } 135 136 /* IP175D has a specific ID register. */ 137 model = MDIO_READREG(device_get_parent(dev), IP175D_ID_PHY, 138 IP175D_ID_REG); 139 if (model == 0x175d) 140 sc->sc_switchtype = IP17X_SWITCH_IP175D; 141 else { 142 /* IP178 has more PHYs. Try it. */ 143 model = MDIO_READREG(device_get_parent(dev), 5, MII_PHYIDR1); 144 if (phy_id1 == model) 145 sc->sc_switchtype = IP17X_SWITCH_IP178C; 146 } 147 148 sc->miipoll = 1; 149 #ifdef FDT 150 if ((OF_getencprop(ip17x_node, "mii-poll", 151 &cell, sizeof(cell))) > 0) 152 sc->miipoll = cell ? 1 : 0; 153 #else 154 (void) resource_int_value(device_get_name(dev), device_get_unit(dev), 155 "mii-poll", &sc->miipoll); 156 #endif 157 device_set_desc_copy(dev, "IC+ IP17x switch driver"); 158 return (BUS_PROBE_DEFAULT); 159 } 160 161 static int 162 ip17x_attach_phys(struct ip17x_softc *sc) 163 { 164 int err, phy, port; 165 char name[IFNAMSIZ]; 166 167 port = err = 0; 168 169 /* PHYs need an interface, so we generate a dummy one */ 170 snprintf(name, IFNAMSIZ, "%sport", device_get_nameunit(sc->sc_dev)); 171 for (phy = 0; phy < MII_NPHY; phy++) { 172 if (((1 << phy) & sc->phymask) == 0) 173 continue; 174 sc->phyport[phy] = port; 175 sc->portphy[port] = phy; 176 sc->ifp[port] = if_alloc(IFT_ETHER); 177 if (sc->ifp[port] == NULL) { 178 device_printf(sc->sc_dev, "couldn't allocate ifnet structure\n"); 179 err = ENOMEM; 180 break; 181 } 182 183 sc->ifp[port]->if_softc = sc; 184 sc->ifp[port]->if_flags |= IFF_UP | IFF_BROADCAST | 185 IFF_DRV_RUNNING | IFF_SIMPLEX; 186 if_initname(sc->ifp[port], name, port); 187 sc->miibus[port] = malloc(sizeof(device_t), M_IP17X, 188 M_WAITOK | M_ZERO); 189 err = mii_attach(sc->sc_dev, sc->miibus[port], sc->ifp[port], 190 ip17x_ifmedia_upd, ip17x_ifmedia_sts, \ 191 BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0); 192 DPRINTF(sc->sc_dev, "%s attached to pseudo interface %s\n", 193 device_get_nameunit(*sc->miibus[port]), 194 sc->ifp[port]->if_xname); 195 if (err != 0) { 196 device_printf(sc->sc_dev, 197 "attaching PHY %d failed\n", 198 phy); 199 break; 200 } 201 sc->info.es_nports = port + 1; 202 if (++port >= sc->numports) 203 break; 204 } 205 return (err); 206 } 207 208 static int 209 ip17x_attach(device_t dev) 210 { 211 struct ip17x_softc *sc; 212 int err; 213 214 sc = device_get_softc(dev); 215 216 sc->sc_dev = dev; 217 mtx_init(&sc->sc_mtx, "ip17x", NULL, MTX_DEF); 218 strlcpy(sc->info.es_name, device_get_desc(dev), 219 sizeof(sc->info.es_name)); 220 221 /* XXX Defaults */ 222 sc->phymask = 0x0f; 223 sc->media = 100; 224 225 (void) resource_int_value(device_get_name(dev), device_get_unit(dev), 226 "phymask", &sc->phymask); 227 228 /* Number of vlans supported by the switch. */ 229 sc->info.es_nvlangroups = IP17X_MAX_VLANS; 230 231 /* Attach the switch related functions. */ 232 if (IP17X_IS_SWITCH(sc, IP175C)) 233 ip175c_attach(sc); 234 else if (IP17X_IS_SWITCH(sc, IP175D)) 235 ip175d_attach(sc); 236 else 237 /* We don't have support to all the models yet :-/ */ 238 return (ENXIO); 239 240 /* Always attach the cpu port. */ 241 sc->phymask |= (1 << sc->cpuport); 242 243 sc->ifp = malloc(sizeof(struct ifnet *) * sc->numports, M_IP17X, 244 M_WAITOK | M_ZERO); 245 sc->pvid = malloc(sizeof(uint32_t) * sc->numports, M_IP17X, 246 M_WAITOK | M_ZERO); 247 sc->miibus = malloc(sizeof(device_t *) * sc->numports, M_IP17X, 248 M_WAITOK | M_ZERO); 249 sc->portphy = malloc(sizeof(int) * sc->numports, M_IP17X, 250 M_WAITOK | M_ZERO); 251 252 /* Initialize the switch. */ 253 sc->hal.ip17x_reset(sc); 254 255 /* 256 * Attach the PHYs and complete the bus enumeration. 257 */ 258 err = ip17x_attach_phys(sc); 259 if (err != 0) 260 return (err); 261 262 /* 263 * Set the switch to port based vlans or disabled (if not supported 264 * on this model). 265 */ 266 sc->hal.ip17x_set_vlan_mode(sc, ETHERSWITCH_VLAN_PORT); 267 268 bus_generic_probe(dev); 269 bus_enumerate_hinted_children(dev); 270 err = bus_generic_attach(dev); 271 if (err != 0) 272 return (err); 273 274 if (sc->miipoll) { 275 callout_init(&sc->callout_tick, 0); 276 277 ip17x_tick(sc); 278 } 279 280 return (0); 281 } 282 283 static int 284 ip17x_detach(device_t dev) 285 { 286 struct ip17x_softc *sc; 287 int i, port; 288 289 sc = device_get_softc(dev); 290 if (sc->miipoll) 291 callout_drain(&sc->callout_tick); 292 293 for (i=0; i < MII_NPHY; i++) { 294 if (((1 << i) & sc->phymask) == 0) 295 continue; 296 port = sc->phyport[i]; 297 if (sc->miibus[port] != NULL) 298 device_delete_child(dev, (*sc->miibus[port])); 299 if (sc->ifp[port] != NULL) 300 if_free(sc->ifp[port]); 301 free(sc->miibus[port], M_IP17X); 302 } 303 304 free(sc->portphy, M_IP17X); 305 free(sc->miibus, M_IP17X); 306 free(sc->pvid, M_IP17X); 307 free(sc->ifp, M_IP17X); 308 309 /* Reset the switch. */ 310 sc->hal.ip17x_reset(sc); 311 312 bus_generic_detach(dev); 313 mtx_destroy(&sc->sc_mtx); 314 315 return (0); 316 } 317 318 static inline struct mii_data * 319 ip17x_miiforport(struct ip17x_softc *sc, int port) 320 { 321 322 if (port < 0 || port > sc->numports) 323 return (NULL); 324 return (device_get_softc(*sc->miibus[port])); 325 } 326 327 static inline struct ifnet * 328 ip17x_ifpforport(struct ip17x_softc *sc, int port) 329 { 330 331 if (port < 0 || port > sc->numports) 332 return (NULL); 333 return (sc->ifp[port]); 334 } 335 336 /* 337 * Poll the status for all PHYs. 338 */ 339 static void 340 ip17x_miipollstat(struct ip17x_softc *sc) 341 { 342 struct mii_softc *miisc; 343 struct mii_data *mii; 344 int i, port; 345 346 IP17X_LOCK_ASSERT(sc, MA_NOTOWNED); 347 348 for (i = 0; i < MII_NPHY; i++) { 349 if (((1 << i) & sc->phymask) == 0) 350 continue; 351 port = sc->phyport[i]; 352 if ((*sc->miibus[port]) == NULL) 353 continue; 354 mii = device_get_softc(*sc->miibus[port]); 355 LIST_FOREACH(miisc, &mii->mii_phys, mii_list) { 356 if (IFM_INST(mii->mii_media.ifm_cur->ifm_media) != 357 miisc->mii_inst) 358 continue; 359 ukphy_status(miisc); 360 mii_phy_update(miisc, MII_POLLSTAT); 361 } 362 } 363 } 364 365 static void 366 ip17x_tick(void *arg) 367 { 368 struct ip17x_softc *sc; 369 370 sc = arg; 371 ip17x_miipollstat(sc); 372 callout_reset(&sc->callout_tick, hz, ip17x_tick, sc); 373 } 374 375 static void 376 ip17x_lock(device_t dev) 377 { 378 struct ip17x_softc *sc; 379 380 sc = device_get_softc(dev); 381 IP17X_LOCK_ASSERT(sc, MA_NOTOWNED); 382 IP17X_LOCK(sc); 383 } 384 385 static void 386 ip17x_unlock(device_t dev) 387 { 388 struct ip17x_softc *sc; 389 390 sc = device_get_softc(dev); 391 IP17X_LOCK_ASSERT(sc, MA_OWNED); 392 IP17X_UNLOCK(sc); 393 } 394 395 static etherswitch_info_t * 396 ip17x_getinfo(device_t dev) 397 { 398 struct ip17x_softc *sc; 399 400 sc = device_get_softc(dev); 401 return (&sc->info); 402 } 403 404 static int 405 ip17x_getport(device_t dev, etherswitch_port_t *p) 406 { 407 struct ip17x_softc *sc; 408 struct ifmediareq *ifmr; 409 struct mii_data *mii; 410 int err, phy; 411 412 sc = device_get_softc(dev); 413 if (p->es_port < 0 || p->es_port >= sc->numports) 414 return (ENXIO); 415 416 phy = sc->portphy[p->es_port]; 417 418 /* Retrieve the PVID. */ 419 p->es_pvid = sc->pvid[phy]; 420 421 /* Port flags. */ 422 if (sc->addtag & (1 << phy)) 423 p->es_flags |= ETHERSWITCH_PORT_ADDTAG; 424 if (sc->striptag & (1 << phy)) 425 p->es_flags |= ETHERSWITCH_PORT_STRIPTAG; 426 427 ifmr = &p->es_ifmr; 428 429 /* No media settings ? */ 430 if (p->es_ifmr.ifm_count == 0) 431 return (0); 432 433 mii = ip17x_miiforport(sc, p->es_port); 434 if (mii == NULL) 435 return (ENXIO); 436 if (phy == sc->cpuport) { 437 /* fill in fixed values for CPU port */ 438 p->es_flags |= ETHERSWITCH_PORT_CPU; 439 ifmr->ifm_count = 0; 440 if (sc->media == 100) 441 ifmr->ifm_current = ifmr->ifm_active = 442 IFM_ETHER | IFM_100_TX | IFM_FDX; 443 else 444 ifmr->ifm_current = ifmr->ifm_active = 445 IFM_ETHER | IFM_1000_T | IFM_FDX; 446 ifmr->ifm_mask = 0; 447 ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID; 448 } else { 449 err = ifmedia_ioctl(mii->mii_ifp, &p->es_ifr, 450 &mii->mii_media, SIOCGIFMEDIA); 451 if (err) 452 return (err); 453 } 454 return (0); 455 } 456 457 static int 458 ip17x_setport(device_t dev, etherswitch_port_t *p) 459 { 460 struct ip17x_softc *sc; 461 struct ifmedia *ifm; 462 struct ifnet *ifp; 463 struct mii_data *mii; 464 int phy; 465 466 sc = device_get_softc(dev); 467 if (p->es_port < 0 || p->es_port >= sc->numports) 468 return (ENXIO); 469 470 phy = sc->portphy[p->es_port]; 471 ifp = ip17x_ifpforport(sc, p->es_port); 472 mii = ip17x_miiforport(sc, p->es_port); 473 if (ifp == NULL || mii == NULL) 474 return (ENXIO); 475 476 /* Port flags. */ 477 if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q) { 478 479 /* Set the PVID. */ 480 if (p->es_pvid != 0) { 481 if (IP17X_IS_SWITCH(sc, IP175C) && 482 p->es_pvid > IP175C_LAST_VLAN) 483 return (ENXIO); 484 sc->pvid[phy] = p->es_pvid; 485 } 486 487 /* Mutually exclusive. */ 488 if (p->es_flags & ETHERSWITCH_PORT_ADDTAG && 489 p->es_flags & ETHERSWITCH_PORT_STRIPTAG) 490 return (EINVAL); 491 492 /* Reset the settings for this port. */ 493 sc->addtag &= ~(1 << phy); 494 sc->striptag &= ~(1 << phy); 495 496 /* And then set it to the new value. */ 497 if (p->es_flags & ETHERSWITCH_PORT_ADDTAG) 498 sc->addtag |= (1 << phy); 499 if (p->es_flags & ETHERSWITCH_PORT_STRIPTAG) 500 sc->striptag |= (1 << phy); 501 } 502 503 /* Update the switch configuration. */ 504 if (sc->hal.ip17x_hw_setup(sc)) 505 return (ENXIO); 506 507 /* Do not allow media changes on CPU port. */ 508 if (phy == sc->cpuport) 509 return (0); 510 511 /* No media settings ? */ 512 if (p->es_ifmr.ifm_count == 0) 513 return (0); 514 515 ifm = &mii->mii_media; 516 return (ifmedia_ioctl(ifp, &p->es_ifr, ifm, SIOCSIFMEDIA)); 517 } 518 519 static void 520 ip17x_statchg(device_t dev) 521 { 522 523 DPRINTF(dev, "%s\n", __func__); 524 } 525 526 static int 527 ip17x_ifmedia_upd(struct ifnet *ifp) 528 { 529 struct ip17x_softc *sc; 530 struct mii_data *mii; 531 532 sc = ifp->if_softc; 533 DPRINTF(sc->sc_dev, "%s\n", __func__); 534 mii = ip17x_miiforport(sc, ifp->if_dunit); 535 if (mii == NULL) 536 return (ENXIO); 537 mii_mediachg(mii); 538 539 return (0); 540 } 541 542 static void 543 ip17x_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) 544 { 545 struct ip17x_softc *sc; 546 struct mii_data *mii; 547 548 sc = ifp->if_softc; 549 DPRINTF(sc->sc_dev, "%s\n", __func__); 550 mii = ip17x_miiforport(sc, ifp->if_dunit); 551 if (mii == NULL) 552 return; 553 mii_pollstat(mii); 554 ifmr->ifm_active = mii->mii_media_active; 555 ifmr->ifm_status = mii->mii_media_status; 556 } 557 558 static int 559 ip17x_readreg(device_t dev, int addr) 560 { 561 struct ip17x_softc *sc; 562 563 sc = device_get_softc(dev); 564 IP17X_LOCK_ASSERT(sc, MA_OWNED); 565 566 /* Not supported. */ 567 return (0); 568 } 569 570 static int 571 ip17x_writereg(device_t dev, int addr, int value) 572 { 573 struct ip17x_softc *sc; 574 575 sc = device_get_softc(dev); 576 IP17X_LOCK_ASSERT(sc, MA_OWNED); 577 578 /* Not supported. */ 579 return (0); 580 } 581 582 static int 583 ip17x_getconf(device_t dev, etherswitch_conf_t *conf) 584 { 585 struct ip17x_softc *sc; 586 587 sc = device_get_softc(dev); 588 589 /* Return the VLAN mode. */ 590 conf->cmd = ETHERSWITCH_CONF_VLAN_MODE; 591 conf->vlan_mode = sc->hal.ip17x_get_vlan_mode(sc); 592 593 return (0); 594 } 595 596 static int 597 ip17x_setconf(device_t dev, etherswitch_conf_t *conf) 598 { 599 struct ip17x_softc *sc; 600 601 sc = device_get_softc(dev); 602 603 /* Set the VLAN mode. */ 604 if (conf->cmd & ETHERSWITCH_CONF_VLAN_MODE) 605 sc->hal.ip17x_set_vlan_mode(sc, conf->vlan_mode); 606 607 return (0); 608 } 609 610 static device_method_t ip17x_methods[] = { 611 /* Device interface */ 612 DEVMETHOD(device_identify, ip17x_identify), 613 DEVMETHOD(device_probe, ip17x_probe), 614 DEVMETHOD(device_attach, ip17x_attach), 615 DEVMETHOD(device_detach, ip17x_detach), 616 617 /* bus interface */ 618 DEVMETHOD(bus_add_child, device_add_child_ordered), 619 620 /* MII interface */ 621 DEVMETHOD(miibus_readreg, ip17x_readphy), 622 DEVMETHOD(miibus_writereg, ip17x_writephy), 623 DEVMETHOD(miibus_statchg, ip17x_statchg), 624 625 /* MDIO interface */ 626 DEVMETHOD(mdio_readreg, ip17x_readphy), 627 DEVMETHOD(mdio_writereg, ip17x_writephy), 628 629 /* etherswitch interface */ 630 DEVMETHOD(etherswitch_lock, ip17x_lock), 631 DEVMETHOD(etherswitch_unlock, ip17x_unlock), 632 DEVMETHOD(etherswitch_getinfo, ip17x_getinfo), 633 DEVMETHOD(etherswitch_readreg, ip17x_readreg), 634 DEVMETHOD(etherswitch_writereg, ip17x_writereg), 635 DEVMETHOD(etherswitch_readphyreg, ip17x_readphy), 636 DEVMETHOD(etherswitch_writephyreg, ip17x_writephy), 637 DEVMETHOD(etherswitch_getport, ip17x_getport), 638 DEVMETHOD(etherswitch_setport, ip17x_setport), 639 DEVMETHOD(etherswitch_getvgroup, ip17x_getvgroup), 640 DEVMETHOD(etherswitch_setvgroup, ip17x_setvgroup), 641 DEVMETHOD(etherswitch_getconf, ip17x_getconf), 642 DEVMETHOD(etherswitch_setconf, ip17x_setconf), 643 644 DEVMETHOD_END 645 }; 646 647 DEFINE_CLASS_0(ip17x, ip17x_driver, ip17x_methods, 648 sizeof(struct ip17x_softc)); 649 static devclass_t ip17x_devclass; 650 651 DRIVER_MODULE(ip17x, mdio, ip17x_driver, ip17x_devclass, 0, 0); 652 DRIVER_MODULE(miibus, ip17x, miibus_driver, miibus_devclass, 0, 0); 653 DRIVER_MODULE(etherswitch, ip17x, etherswitch_driver, etherswitch_devclass, 0, 0); 654 MODULE_VERSION(ip17x, 1); 655 656 #ifdef FDT 657 MODULE_DEPEND(ip17x, mdio, 1, 1, 1); /* XXX which versions? */ 658 #else 659 DRIVER_MODULE(mdio, ip17x, mdio_driver, mdio_devclass, 0, 0); 660 MODULE_DEPEND(ip17x, miibus, 1, 1, 1); /* XXX which versions? */ 661 MODULE_DEPEND(ip17x, etherswitch, 1, 1, 1); /* XXX which versions? */ 662 #endif 663