/* * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009 * The President and Fellows of Harvard College. * * 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY 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 UNIVERSITY 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. */ /* * Machine-independent LAMEbus code. */ #include #include #include #include #include #include #include /* Register offsets within each config region */ #define CFGREG_VID 0 /* Vendor ID */ #define CFGREG_DID 4 /* Device ID */ #define CFGREG_DRL 8 /* Device Revision Level */ /* LAMEbus controller private registers (offsets within its config region) */ #define CTLREG_RAMSZ 0x200 #define CTLREG_IRQS 0x204 #define CTLREG_PWR 0x208 #define CTLREG_IRQE 0x20c #define CTLREG_CPUS 0x210 #define CTLREG_CPUE 0x214 #define CTLREG_SELF 0x218 /* LAMEbus CPU control registers (offsets within each per-cpu region) */ #define CTLCPU_CIRQE 0x000 #define CTLCPU_CIPI 0x004 #define CTLCPU_CRAM 0x300 /* * Read a config register for the given slot. */ static inline uint32_t read_cfg_register(struct lamebus_softc *lb, int slot, uint32_t offset) { /* Note that lb might be NULL on some platforms in some contexts. */ offset += LB_CONFIG_SIZE*slot; return lamebus_read_register(lb, LB_CONTROLLER_SLOT, offset); } /* * Write a config register for a given slot. */ static inline void write_cfg_register(struct lamebus_softc *lb, int slot, uint32_t offset, uint32_t val) { offset += LB_CONFIG_SIZE*slot; lamebus_write_register(lb, LB_CONTROLLER_SLOT, offset, val); } /* * Read one of the bus controller's registers. */ static inline uint32_t read_ctl_register(struct lamebus_softc *lb, uint32_t offset) { /* Note that lb might be NULL on some platforms in some contexts. */ return read_cfg_register(lb, LB_CONTROLLER_SLOT, offset); } /* * Write one of the bus controller's registers. */ static inline void write_ctl_register(struct lamebus_softc *lb, uint32_t offset, uint32_t val) { write_cfg_register(lb, LB_CONTROLLER_SLOT, offset, val); } /* * Write one of the bus controller's CPU control registers. */ static inline void write_ctlcpu_register(struct lamebus_softc *lb, unsigned hw_cpunum, uint32_t offset, uint32_t val) { offset += LB_CTLCPU_OFFSET + hw_cpunum * LB_CTLCPU_SIZE; lamebus_write_register(lb, LB_CONTROLLER_SLOT, offset, val); } /* * Find and create secondary CPUs. */ void lamebus_find_cpus(struct lamebus_softc *lamebus) { uint32_t mainboard_vid, mainboard_did; uint32_t cpumask, self, bit, val; unsigned i, numcpus, bootcpu; unsigned hwnum[32]; mainboard_vid = read_cfg_register(lamebus, LB_CONTROLLER_SLOT, CFGREG_VID); mainboard_did = read_cfg_register(lamebus, LB_CONTROLLER_SLOT, CFGREG_DID); if (mainboard_vid == LB_VENDOR_CS161 && mainboard_did == LBCS161_UPBUSCTL) { /* Old uniprocessor mainboard; no cpu registers. */ lamebus->ls_uniprocessor = 1; return; } cpumask = read_ctl_register(lamebus, CTLREG_CPUS); self = read_ctl_register(lamebus, CTLREG_SELF); numcpus = 0; bootcpu = 0; for (i=0; i<32; i++) { bit = (uint32_t)1 << i; if ((cpumask & bit) != 0) { if (self & bit) { bootcpu = numcpus; curcpu->c_hardware_number = i; } hwnum[numcpus] = i; numcpus++; } } for (i=0; ils_uniprocessor) { return; } cpumask = read_ctl_register(lamebus, CTLREG_CPUS); self = read_ctl_register(lamebus, CTLREG_SELF); /* Poke in the startup address. */ cpunum = 1; for (i=0; i<32; i++) { bit = (uint32_t)1 << i; if ((cpumask & bit) != 0) { if (self & bit) { continue; } ctlcpuoffset = LB_CTLCPU_OFFSET + i * LB_CTLCPU_SIZE; cram = lamebus_map_area(lamebus, LB_CONTROLLER_SLOT, ctlcpuoffset + CTLCPU_CRAM); cram[0] = (uint32_t)cpu_start_secondary; cram[1] = cpunum++; } } /* Ensure all the above writes get flushed. */ membar_store_store(); /* Now, enable them all. */ write_ctl_register(lamebus, CTLREG_CPUE, cpumask); } /* * Probe function. * * Given a LAMEbus, look for a device that's not already been marked * in use, has the specified IDs, and has a device revision level at * least as high as the minimum specified. * * Returns the slot number found (0-31) or -1 if nothing suitable was * found. * * If VERSION_RET is not null, return the device version found. This * allows drivers to blacklist specific versions or otherwise conduct * more specific checks. */ int lamebus_probe(struct lamebus_softc *sc, uint32_t vendorid, uint32_t deviceid, uint32_t lowver, uint32_t *version_ret) { int slot; uint32_t val; /* * Because the slot information in sc is used when dispatching * interrupts, disable interrupts while working with it. */ spinlock_acquire(&sc->ls_lock); for (slot=0; slotls_slotsinuse & (1<ls_lock); return slot; } /* Found nothing */ spinlock_release(&sc->ls_lock); return -1; } /* * Mark that a slot is in use. * This prevents the probe routine from returning the same device over * and over again. */ void lamebus_mark(struct lamebus_softc *sc, int slot) { uint32_t mask = ((uint32_t)1) << slot; KASSERT(slot>=0 && slot < LB_NSLOTS); spinlock_acquire(&sc->ls_lock); if ((sc->ls_slotsinuse & mask)!=0) { panic("lamebus_mark: slot %d already in use\n", slot); } sc->ls_slotsinuse |= mask; spinlock_release(&sc->ls_lock); } /* * Mark that a slot is no longer in use. */ void lamebus_unmark(struct lamebus_softc *sc, int slot) { uint32_t mask = ((uint32_t)1) << slot; KASSERT(slot>=0 && slot < LB_NSLOTS); spinlock_acquire(&sc->ls_lock); if ((sc->ls_slotsinuse & mask)==0) { panic("lamebus_mark: slot %d not marked in use\n", slot); } sc->ls_slotsinuse &= ~mask; spinlock_release(&sc->ls_lock); } /* * Register a function (and a device context pointer) to be called * when a particular slot signals an interrupt. */ void lamebus_attach_interrupt(struct lamebus_softc *sc, int slot, void *devdata, void (*irqfunc)(void *devdata)) { uint32_t mask = ((uint32_t)1) << slot; KASSERT(slot>=0 && slot < LB_NSLOTS); spinlock_acquire(&sc->ls_lock); if ((sc->ls_slotsinuse & mask)==0) { panic("lamebus_attach_interrupt: slot %d not marked in use\n", slot); } KASSERT(sc->ls_devdata[slot]==NULL); KASSERT(sc->ls_irqfuncs[slot]==NULL); sc->ls_devdata[slot] = devdata; sc->ls_irqfuncs[slot] = irqfunc; spinlock_release(&sc->ls_lock); } /* * Unregister a function that was being called when a particular slot * signaled an interrupt. */ void lamebus_detach_interrupt(struct lamebus_softc *sc, int slot) { uint32_t mask = ((uint32_t)1) << slot; KASSERT(slot>=0 && slot < LB_NSLOTS); spinlock_acquire(&sc->ls_lock); if ((sc->ls_slotsinuse & mask)==0) { panic("lamebus_detach_interrupt: slot %d not marked in use\n", slot); } KASSERT(sc->ls_irqfuncs[slot]!=NULL); sc->ls_devdata[slot] = NULL; sc->ls_irqfuncs[slot] = NULL; spinlock_release(&sc->ls_lock); } /* * Mask/unmask an interrupt using the global IRQE register. */ void lamebus_mask_interrupt(struct lamebus_softc *lamebus, int slot) { uint32_t bits, mask = ((uint32_t)1) << slot; KASSERT(slot >= 0 && slot < LB_NSLOTS); spinlock_acquire(&lamebus->ls_lock); bits = read_ctl_register(lamebus, CTLREG_IRQE); bits &= ~mask; write_ctl_register(lamebus, CTLREG_IRQE, bits); spinlock_release(&lamebus->ls_lock); } void lamebus_unmask_interrupt(struct lamebus_softc *lamebus, int slot) { uint32_t bits, mask = ((uint32_t)1) << slot; KASSERT(slot >= 0 && slot < LB_NSLOTS); spinlock_acquire(&lamebus->ls_lock); bits = read_ctl_register(lamebus, CTLREG_IRQE); bits |= mask; write_ctl_register(lamebus, CTLREG_IRQE, bits); spinlock_release(&lamebus->ls_lock); } /* * LAMEbus interrupt handling function. (Machine-independent!) */ void lamebus_interrupt(struct lamebus_softc *lamebus) { /* * Note that despite the fact that "spl" stands for "set * priority level", we don't actually support interrupt * priorities. When an interrupt happens, we look through the * slots to find the first interrupting device and call its * interrupt routine, no matter what that device is. * * Note that the entire LAMEbus uses only one on-cpu interrupt line. * Thus, we do not use any on-cpu interrupt priority system either. */ int slot; uint32_t mask; uint32_t irqs; void (*handler)(void *); void *data; /* For keeping track of how many bogus things happen in a row. */ static int duds = 0; int duds_this_time = 0; /* and we better have a valid bus instance. */ KASSERT(lamebus != NULL); /* Lock the softc */ spinlock_acquire(&lamebus->ls_lock); /* * Read the LAMEbus controller register that tells us which * slots are asserting an interrupt condition. */ irqs = read_ctl_register(lamebus, CTLREG_IRQS); if (irqs == 0) { /* * Huh? None of them? Must be a glitch. */ kprintf("lamebus: stray interrupt on cpu %u\n", curcpu->c_number); duds++; duds_this_time++; /* * We could just return now, but instead we'll * continue ahead. Because irqs == 0, nothing in the * loop will execute, and passing through it gets us * to the code that checks how many duds we've * seen. This is important, because we just might get * a stray interrupt that latches itself on. If that * happens, we're pretty much toast, but it's better * to panic and hopefully reset the system than to * loop forever printing "stray interrupt". */ } /* * Go through the bits in the value we got back to see which * ones are set. */ for (mask=1, slot=0; slotls_slotsinuse & mask)==0) { /* * No device driver is using this slot. */ duds++; duds_this_time++; continue; } if (lamebus->ls_irqfuncs[slot]==NULL) { /* * The device driver hasn't installed an interrupt * handler. */ duds++; duds_this_time++; continue; } /* * Call the interrupt handler. Release the spinlock * while we do so, in case other CPUs are handling * interrupts on other devices. */ handler = lamebus->ls_irqfuncs[slot]; data = lamebus->ls_devdata[slot]; spinlock_release(&lamebus->ls_lock); handler(data); spinlock_acquire(&lamebus->ls_lock); /* * Reload the mask of pending IRQs - if we just called * hardclock, we might not have come back to this * context for some time, and it might have changed. */ irqs = read_ctl_register(lamebus, CTLREG_IRQS); } /* * If we get interrupts for a slot with no driver or no * interrupt handler, it's fairly serious. Because LAMEbus * uses level-triggered interrupts, if we don't shut off the * condition, we'll keep getting interrupted continuously and * the system will make no progress. But we don't know how to * do that if there's no driver or no interrupt handler. * * So, if we get too many dud interrupts, panic, since it's * better to panic and reset than to hang. * * If we get through here without seeing any duds this time, * the condition, whatever it was, has gone away. It might be * some stupid device we don't have a driver for, or it might * have been an electrical transient. In any case, warn and * clear the dud count. */ if (duds_this_time == 0 && duds > 0) { kprintf("lamebus: %d dud interrupts\n", duds); duds = 0; } if (duds > 10000) { panic("lamebus: too many (%d) dud interrupts\n", duds); } /* Unlock the softc */ spinlock_release(&lamebus->ls_lock); } /* * Have the bus controller power the system off. */ void lamebus_poweroff(struct lamebus_softc *lamebus) { /* * Write 0 to the power register to shut the system off. */ cpu_irqoff(); write_ctl_register(lamebus, CTLREG_PWR, 0); /* The power doesn't go off instantly... so halt the cpu. */ cpu_halt(); } /* * Ask the bus controller how much memory we have. */ uint32_t lamebus_ramsize(void) { /* * Note that this has to work before bus initialization. * On machines where lamebus_read_register doesn't work * before bus initialization, this function can't be used * for initial RAM size lookup. */ return read_ctl_register(NULL, CTLREG_RAMSZ); } /* * Turn on or off the interprocessor interrupt line for a given CPU. */ void lamebus_assert_ipi(struct lamebus_softc *lamebus, struct cpu *target) { if (lamebus->ls_uniprocessor) { return; } write_ctlcpu_register(lamebus, target->c_hardware_number, CTLCPU_CIPI, 1); } void lamebus_clear_ipi(struct lamebus_softc *lamebus, struct cpu *target) { if (lamebus->ls_uniprocessor) { return; } write_ctlcpu_register(lamebus, target->c_hardware_number, CTLCPU_CIPI, 0); } /* * Initial setup. * Should be called from mainbus_bootstrap(). */ struct lamebus_softc * lamebus_init(void) { struct lamebus_softc *lamebus; int i; /* Allocate space for lamebus data */ lamebus = kmalloc(sizeof(struct lamebus_softc)); if (lamebus==NULL) { panic("lamebus_init: Out of memory\n"); } spinlock_init(&lamebus->ls_lock); /* * Initialize the LAMEbus data structure. */ lamebus->ls_slotsinuse = 1 << LB_CONTROLLER_SLOT; for (i=0; ils_devdata[i] = NULL; lamebus->ls_irqfuncs[i] = NULL; } lamebus->ls_uniprocessor = 0; return lamebus; }