/*- * Copyright (c) 2003 * Fraunhofer Institute for Open Communication Systems (FhG Fokus). * All rights reserved. * * Author: Hartmut Brandt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* known chips */ extern const struct utopia_chip utopia_chip_idt77155; extern const struct utopia_chip utopia_chip_idt77105; extern const struct utopia_chip utopia_chip_lite; extern const struct utopia_chip utopia_chip_ultra; extern const struct utopia_chip utopia_chip_622; /* * Global list of all registered interfaces */ static struct mtx utopia_list_mtx; static LIST_HEAD(, utopia) utopia_list = LIST_HEAD_INITIALIZER(utopia_list); #define UTP_RLOCK_LIST() mtx_lock(&utopia_list_mtx) #define UTP_RUNLOCK_LIST() mtx_unlock(&utopia_list_mtx) #define UTP_WLOCK_LIST() mtx_lock(&utopia_list_mtx) #define UTP_WUNLOCK_LIST() mtx_unlock(&utopia_list_mtx) #define UTP_LOCK(UTP) mtx_lock((UTP)->lock) #define UTP_UNLOCK(UTP) mtx_unlock((UTP)->lock) #define UTP_LOCK_ASSERT(UTP) mtx_assert((UTP)->lock, MA_OWNED) static struct proc *utopia_kproc; static void utopia_dump(struct utopia *) __unused; /* * Read a multi-register value. */ uint32_t utopia_update(struct utopia *utp, u_int reg, u_int nreg, uint32_t mask) { int err; u_int n; uint8_t regs[4]; uint32_t val; n = nreg; if ((err = UTP_READREGS(utp, reg, regs, &n)) != 0) { #ifdef DIAGNOSTIC printf("%s: register read error %s(%u,%u): %d\n", __func__, utp->chip->name, reg, nreg, err); #endif return (0); } if (n < nreg) { #ifdef DIAGNOSTIC printf("%s: got only %u regs %s(%u,%u): %d\n", __func__, n, utp->chip->name, reg, nreg, err); #endif return (0); } val = 0; for (n = nreg; n > 0; n--) { val <<= 8; val |= regs[n - 1]; } return (val & mask); } /* * Debugging - dump all registers. */ static void utopia_dump(struct utopia *utp) { uint8_t regs[256]; u_int n = 256, i; int err; if ((err = UTP_READREGS(utp, 0, regs, &n)) != 0) { printf("UTOPIA reg read error %d\n", err); return; } for (i = 0; i < n; i++) { if (i % 16 == 0) printf("%02x:", i); if (i % 16 == 8) printf(" "); printf(" %02x", regs[i]); if (i % 16 == 15) printf("\n"); } if (i % 16 != 0) printf("\n"); } /* * Update the carrier status */ void utopia_check_carrier(struct utopia *utp, u_int carr_ok) { int old; old = utp->carrier; if (carr_ok) { /* carrier */ utp->carrier = UTP_CARR_OK; if (old != UTP_CARR_OK) { if_printf(utp->ifatm->ifp, "carrier detected\n"); ATMEV_SEND_IFSTATE_CHANGED(utp->ifatm, 1); } } else { /* no carrier */ utp->carrier = UTP_CARR_LOST; if (old == UTP_CARR_OK) { if_printf(utp->ifatm->ifp, "carrier lost\n"); ATMEV_SEND_IFSTATE_CHANGED(utp->ifatm, 0); } } } static int unknown_inval(struct utopia *utp, int what __unused) { return (EINVAL); } static int unknown_reset(struct utopia *utp __unused) { return (EIO); } static int unknown_update_carrier(struct utopia *utp) { utp->carrier = UTP_CARR_UNKNOWN; return (0); } static int unknown_set_loopback(struct utopia *utp __unused, u_int mode __unused) { return (EINVAL); } static void unknown_intr(struct utopia *utp __unused) { } static void unknown_update_stats(struct utopia *utp __unused) { } static const struct utopia_chip utopia_chip_unknown = { UTP_TYPE_UNKNOWN, "unknown", 0, unknown_reset, unknown_inval, unknown_inval, unknown_inval, unknown_update_carrier, unknown_set_loopback, unknown_intr, unknown_update_stats, }; /* * Callbacks for the ifmedia infrastructure. */ static int utopia_media_change(struct ifnet *ifp) { struct ifatm *ifatm = IFP2IFATM(ifp); struct utopia *utp = ifatm->phy; int error = 0; UTP_LOCK(utp); if (utp->chip->type != UTP_TYPE_UNKNOWN && utp->state & UTP_ST_ACTIVE) { if (utp->media->ifm_media & IFM_ATM_SDH) { if (!(utp->state & UTP_ST_SDH)) error = utopia_set_sdh(utp, 1); } else { if (utp->state & UTP_ST_SDH) error = utopia_set_sdh(utp, 0); } if (utp->media->ifm_media & IFM_ATM_UNASSIGNED) { if (!(utp->state & UTP_ST_UNASS)) error = utopia_set_unass(utp, 1); } else { if (utp->state & UTP_ST_UNASS) error = utopia_set_unass(utp, 0); } if (utp->media->ifm_media & IFM_ATM_NOSCRAMB) { if (!(utp->state & UTP_ST_NOSCRAMB)) error = utopia_set_noscramb(utp, 1); } else { if (utp->state & UTP_ST_NOSCRAMB) error = utopia_set_noscramb(utp, 0); } } else error = EIO; UTP_UNLOCK(utp); return (error); } /* * Look at the carrier status. */ static void utopia_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) { struct utopia *utp = IFP2IFATM(ifp)->phy; UTP_LOCK(utp); if (utp->chip->type != UTP_TYPE_UNKNOWN && utp->state & UTP_ST_ACTIVE) { ifmr->ifm_active = IFM_ATM | utp->ifatm->mib.media; switch (utp->carrier) { case UTP_CARR_OK: ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; break; case UTP_CARR_LOST: ifmr->ifm_status = IFM_AVALID; break; default: ifmr->ifm_status = 0; break; } if (utp->state & UTP_ST_SDH) { ifmr->ifm_active |= IFM_ATM_SDH; ifmr->ifm_current |= IFM_ATM_SDH; } if (utp->state & UTP_ST_UNASS) { ifmr->ifm_active |= IFM_ATM_UNASSIGNED; ifmr->ifm_current |= IFM_ATM_UNASSIGNED; } if (utp->state & UTP_ST_NOSCRAMB) { ifmr->ifm_active |= IFM_ATM_NOSCRAMB; ifmr->ifm_current |= IFM_ATM_NOSCRAMB; } } else { ifmr->ifm_active = 0; ifmr->ifm_status = 0; } UTP_UNLOCK(utp); } /* * Initialize media from the mib */ void utopia_init_media(struct utopia *utp) { ifmedia_removeall(utp->media); ifmedia_add(utp->media, IFM_ATM | utp->ifatm->mib.media, 0, NULL); ifmedia_set(utp->media, IFM_ATM | utp->ifatm->mib.media); } /* * Reset all media */ void utopia_reset_media(struct utopia *utp) { ifmedia_removeall(utp->media); } /* * This is called by the driver as soon as the SUNI registers are accessible. * This may be either in the attach routine or the init routine of the driver. */ int utopia_start(struct utopia *utp) { uint8_t reg; int err; u_int n = 1; /* * Try to find out what chip we have */ if ((err = UTP_READREGS(utp, SUNI_REGO_MRESET, ®, &n)) != 0) return (err); switch (reg & SUNI_REGM_MRESET_TYPE) { case SUNI_REGM_MRESET_TYPE_622: utp->chip = &utopia_chip_622; break; case SUNI_REGM_MRESET_TYPE_LITE: /* this may be either a SUNI LITE or a IDT77155 * * Read register 0x70. The SUNI doesn't have it */ n = 1; if ((err = UTP_READREGS(utp, IDTPHY_REGO_RBER, ®, &n)) != 0) return (err); if ((reg & ~IDTPHY_REGM_RBER_RESV) == (IDTPHY_REGM_RBER_FAIL | IDTPHY_REGM_RBER_WARN)) utp->chip = &utopia_chip_idt77155; else utp->chip = &utopia_chip_lite; break; case SUNI_REGM_MRESET_TYPE_ULTRA: utp->chip = &utopia_chip_ultra; break; default: if (reg == (IDTPHY_REGM_MCR_DRIC | IDTPHY_REGM_MCR_EI)) utp->chip = &utopia_chip_idt77105; else { if_printf(utp->ifatm->ifp, "unknown ATM-PHY chip %#x\n", reg); utp->chip = &utopia_chip_unknown; } break; } utp->state |= UTP_ST_ACTIVE; return (0); } /* * Stop the chip */ void utopia_stop(struct utopia *utp) { utp->state &= ~UTP_ST_ACTIVE; } /* * Handle the sysctls */ static int utopia_sysctl_regs(SYSCTL_HANDLER_ARGS) { struct utopia *utp = (struct utopia *)arg1; int error; u_int n; uint8_t *val; uint8_t new[3]; if ((n = utp->chip->nregs) == 0) return (EIO); val = malloc(sizeof(uint8_t) * n, M_TEMP, M_WAITOK); UTP_LOCK(utp); error = UTP_READREGS(utp, 0, val, &n); UTP_UNLOCK(utp); if (error) { free(val, M_TEMP); return (error); } error = SYSCTL_OUT(req, val, sizeof(uint8_t) * n); free(val, M_TEMP); if (error != 0 || req->newptr == NULL) return (error); error = SYSCTL_IN(req, new, sizeof(new)); if (error) return (error); UTP_LOCK(utp); error = UTP_WRITEREG(utp, new[0], new[1], new[2]); UTP_UNLOCK(utp); return (error); } static int utopia_sysctl_stats(SYSCTL_HANDLER_ARGS) { struct utopia *utp = (struct utopia *)arg1; void *val; int error; val = malloc(sizeof(utp->stats), M_TEMP, M_WAITOK); UTP_LOCK(utp); bcopy(&utp->stats, val, sizeof(utp->stats)); if (req->newptr != NULL) bzero((char *)&utp->stats + sizeof(utp->stats.version), sizeof(utp->stats) - sizeof(utp->stats.version)); UTP_UNLOCK(utp); error = SYSCTL_OUT(req, val, sizeof(utp->stats)); if (error && req->newptr != NULL) bcopy(val, &utp->stats, sizeof(utp->stats)); free(val, M_TEMP); /* ignore actual new value */ return (error); } /* * Handle the loopback sysctl */ static int utopia_sysctl_loopback(SYSCTL_HANDLER_ARGS) { struct utopia *utp = (struct utopia *)arg1; int error; u_int loopback; error = SYSCTL_OUT(req, &utp->loopback, sizeof(u_int)); if (error != 0 || req->newptr == NULL) return (error); error = SYSCTL_IN(req, &loopback, sizeof(u_int)); if (error) return (error); UTP_LOCK(utp); error = utopia_set_loopback(utp, loopback); UTP_UNLOCK(utp); return (error); } /* * Handle the type sysctl */ static int utopia_sysctl_type(SYSCTL_HANDLER_ARGS) { struct utopia *utp = (struct utopia *)arg1; return (SYSCTL_OUT(req, &utp->chip->type, sizeof(utp->chip->type))); } /* * Handle the name sysctl */ static int utopia_sysctl_name(SYSCTL_HANDLER_ARGS) { struct utopia *utp = (struct utopia *)arg1; return (SYSCTL_OUT(req, utp->chip->name, strlen(utp->chip->name) + 1)); } /* * Initialize the state. This is called from the drivers attach * function. The mutex must be already initialized. */ int utopia_attach(struct utopia *utp, struct ifatm *ifatm, struct ifmedia *media, struct mtx *lock, struct sysctl_ctx_list *ctx, struct sysctl_oid_list *children, const struct utopia_methods *m) { bzero(utp, sizeof(*utp)); utp->ifatm = ifatm; utp->methods = m; utp->media = media; utp->lock = lock; utp->chip = &utopia_chip_unknown; utp->stats.version = 1; ifmedia_init(media, IFM_ATM_SDH | IFM_ATM_UNASSIGNED | IFM_ATM_NOSCRAMB, utopia_media_change, utopia_media_status); if (SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "phy_regs", CTLFLAG_RW | CTLTYPE_OPAQUE, utp, 0, utopia_sysctl_regs, "S", "phy registers") == NULL) return (-1); if (SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "phy_loopback", CTLFLAG_RW | CTLTYPE_UINT, utp, 0, utopia_sysctl_loopback, "IU", "phy loopback mode") == NULL) return (-1); if (SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "phy_type", CTLFLAG_RD | CTLTYPE_UINT, utp, 0, utopia_sysctl_type, "IU", "phy type") == NULL) return (-1); if (SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "phy_name", CTLFLAG_RD | CTLTYPE_STRING, utp, 0, utopia_sysctl_name, "A", "phy name") == NULL) return (-1); if (SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "phy_stats", CTLFLAG_RW | CTLTYPE_OPAQUE, utp, 0, utopia_sysctl_stats, "S", "phy statistics") == NULL) return (-1); if (SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "phy_state", CTLFLAG_RD, &utp->state, 0, "phy state") == NULL) return (-1); if (SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "phy_carrier", CTLFLAG_RD, &utp->carrier, 0, "phy carrier") == NULL) return (-1); UTP_WLOCK_LIST(); LIST_INSERT_HEAD(&utopia_list, utp, link); UTP_WUNLOCK_LIST(); utp->state |= UTP_ST_ATTACHED; return (0); } /* * Detach. We set a flag here, wakeup the daemon and let him do it. * Here we need the lock for synchronisation with the daemon. */ void utopia_detach(struct utopia *utp) { UTP_LOCK_ASSERT(utp); if (utp->state & UTP_ST_ATTACHED) { utp->state |= UTP_ST_DETACH; while (utp->state & UTP_ST_DETACH) { wakeup(&utopia_list); msleep(utp, utp->lock, PZERO, "utopia_detach", hz); } } } /* * The carrier state kernel proc for those adapters that do not interrupt. * * We assume, that utopia_attach can safely add a new utopia while we are going * through the list without disturbing us (we lock the list while getting * the address of the first element, adding is always done at the head). * Removing is entirely handled here. */ static void utopia_daemon(void *arg __unused) { struct utopia *utp, *next; UTP_RLOCK_LIST(); while (utopia_kproc != NULL) { utp = LIST_FIRST(&utopia_list); UTP_RUNLOCK_LIST(); while (utp != NULL) { mtx_lock(&Giant); /* XXX depend on MPSAFE */ UTP_LOCK(utp); next = LIST_NEXT(utp, link); if (utp->state & UTP_ST_DETACH) { LIST_REMOVE(utp, link); utp->state &= ~UTP_ST_DETACH; wakeup_one(utp); } else if (utp->state & UTP_ST_ACTIVE) { if (utp->flags & UTP_FL_POLL_CARRIER) utopia_update_carrier(utp); utopia_update_stats(utp); } UTP_UNLOCK(utp); mtx_unlock(&Giant); /* XXX depend on MPSAFE */ utp = next; } UTP_RLOCK_LIST(); msleep(&utopia_list, &utopia_list_mtx, PZERO, "*idle*", hz); } wakeup_one(&utopia_list); UTP_RUNLOCK_LIST(); kproc_exit(0); } /* * Module initialisation */ static int utopia_mod_init(module_t mod, int what, void *arg) { int err; struct proc *kp; switch (what) { case MOD_LOAD: mtx_init(&utopia_list_mtx, "utopia list mutex", NULL, MTX_DEF); err = kproc_create(utopia_daemon, NULL, &utopia_kproc, RFHIGHPID, 0, "utopia"); if (err != 0) { printf("cannot created utopia thread %d\n", err); return (err); } break; case MOD_UNLOAD: UTP_WLOCK_LIST(); if ((kp = utopia_kproc) != NULL) { utopia_kproc = NULL; wakeup_one(&utopia_list); PROC_LOCK(kp); UTP_WUNLOCK_LIST(); msleep(kp, &kp->p_mtx, PWAIT, "utopia_destroy", 0); PROC_UNLOCK(kp); } else UTP_WUNLOCK_LIST(); mtx_destroy(&utopia_list_mtx); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t utopia_mod = { "utopia", utopia_mod_init, 0 }; DECLARE_MODULE(utopia, utopia_mod, SI_SUB_INIT_IF, SI_ORDER_ANY); MODULE_VERSION(utopia, 1);