/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * The "lombus" driver provides access to the LOMlite2 virtual registers, * so that its clients (children) need not be concerned with the details * of the access mechanism, which in this case is implemented via a * packet-based protocol over a serial link connected to one of the serial * ports of the SuperIO (SIO) chip. * * On the other hand, this driver doesn't generally know what the virtual * registers signify - only the clients need this information. */ /* * Header files */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(NDI_ACC_HDL_V2) /* * Compiling for Solaris 9+ with access handle enhancements */ #define HANDLE_TYPE ndi_acc_handle_t #define HANDLE_ADDR(hdlp) (hdlp->ah_addr) #define HANDLE_FAULT(hdlp) (hdlp->ah_fault) #define HANDLE_MAPLEN(hdlp) (hdlp->ah_len) #define HANDLE_PRIVATE(hdlp) (hdlp->ah_bus_private) #else /* * Compatibility definitions for backport to Solaris 8 */ #define HANDLE_TYPE ddi_acc_impl_t #define HANDLE_ADDR(hdlp) (hdlp->ahi_common.ah_addr) #define HANDLE_FAULT(hdlp) (hdlp->ahi_fault) #define HANDLE_MAPLEN(hdlp) (hdlp->ahi_common.ah_len) #define HANDLE_PRIVATE(hdlp) (hdlp->ahi_common.ah_bus_private) #define ddi_driver_major(dip) ddi_name_to_major(ddi_binding_name(dip)) #endif /* NDI_ACC_HDL_V2 */ /* * Local definitions */ #define MYNAME "lombus" #define NOMAJOR (~(major_t)0) #define DUMMY_VALUE (~(int8_t)0) #define LOMBUS_INST_TO_MINOR(i) (i) #define LOMBUS_MINOR_TO_INST(m) (m) #define LOMBUS_DUMMY_ADDRESS ((caddr_t)0x0CADD1ED) #define ADDR_TO_OFFSET(a, hdlp) ((caddr_t)(a) - HANDLE_ADDR(hdlp)) #define ADDR_TO_VREG(a) ((caddr_t)(a) - LOMBUS_DUMMY_ADDRESS) #define VREG_TO_ADDR(v) (LOMBUS_DUMMY_ADDRESS + (v)) /* * The following definitions are taken from the datasheet * for the National Semiconductor PC87317 (SuperIO) chip. * * This chip implements UART functionality as logical device 6. * It provides all sorts of wierd modes and extensions, but we * have chosen to use only the 16550-compatible features * ("non-extended mode"). * * Hardware: serial chip register numbers */ #define SIO_RXD 0 /* read */ #define SIO_TXD 0 /* write */ #define SIO_IER 1 #define SIO_EIR 2 /* read */ #define SIO_FCR 2 /* write */ #define SIO_LCR 3 #define SIO_BSR 3 /* wierd */ #define SIO_MCR 4 #define SIO_LSR 5 #define SIO_MSR 6 #define SIO_SCR 7 #define SIO_LBGDL 0 /* bank 1 */ #define SIO_LBGDH 1 /* bank 1 */ /* * Hardware: serial chip register bits */ #define SIO_IER_RXHDL_IE 0x01 #define SIO_IER_STD 0x00 #define SIO_EIR_IPF 0x01 #define SIO_EIR_IPR0 0x02 #define SIO_EIR_IPR1 0x04 #define SIO_EIR_RXFT 0x08 #define SIO_EIR_FEN0 0x40 #define SIO_EIR_FEN1 0x80 #define SIO_FCR_FIFO_EN 0x01 #define SIO_FCR_RXSR 0x02 #define SIO_FCR_TXSR 0x04 #define SIO_FCR_RXFTH0 0x40 #define SIO_FCR_RXFTH1 0x80 #define SIO_FCR_STD (SIO_FCR_RXFTH0|SIO_FCR_FIFO_EN) #define SIO_LCR_WLS0 0x01 #define SIO_LCR_WLS1 0x02 #define SIO_LCR_STB 0x04 #define SIO_LCR_PEN 0x08 #define SIO_LCR_EPS 0x10 #define SIO_LCR_STKP 0x20 #define SIO_LCR_SBRK 0x40 #define SIO_LCR_BKSE 0x80 #define SIO_LCR_8BIT (SIO_LCR_WLS0|SIO_LCR_WLS1) #define SIO_LCR_EPAR (SIO_LCR_PEN|SIO_LCR_EPS) #define SIO_LCR_STD (SIO_LCR_8BIT|SIO_LCR_EPAR) #define SIO_BSR_BANK0 (SIO_LCR_STD) #define SIO_BSR_BANK1 (SIO_LCR_BKSE|SIO_LCR_STD) #define SIO_MCR_DTR 0x01 #define SIO_MCR_RTS 0x02 #define SIO_MCR_ISEN 0x08 #define SIO_MCR_STD (SIO_MCR_ISEN) #define SIO_LSR_RXDA 0x01 #define SIO_LSR_OE 0x02 #define SIO_LSR_PE 0x04 #define SIO_LSR_FE 0x08 #define SIO_LSR_BRKE 0x10 #define SIO_LSR_TXRDY 0x20 #define SIO_LSR_TXEMP 0x40 #define SIO_LSR_ER_INF 0x80 #define SIO_MSR_DCTS 0x01 #define SIO_MSR_DDSR 0x02 #define SIO_MSR_TERI 0x04 #define SIO_MSR_DDCD 0x08 #define SIO_MSR_CTS 0x10 #define SIO_MSR_DSR 0x20 #define SIO_MSR_RI 0x40 #define SIO_MSR_DCD 0x80 /* * Min/max/default baud rates, and a macro to convert from a baud * rate to the number (divisor) to put in the baud rate registers */ #define SIO_BAUD_MIN 50 #define SIO_BAUD_MAX 115200 #define SIO_BAUD_DEFAULT 38400 #define SIO_BAUD_TO_DIVISOR(b) (115200 / (b)) /* * Packet format ... */ #define LOMBUS_MASK 0xc0 /* Byte-type bits */ #define LOMBUS_PARAM 0x00 /* Parameter byte: 0b0xxxxxxx */ #define LOMBUS_LAST 0x80 /* Last byte of packet */ #define LOMBUS_CMD 0x80 /* Command byte: 0b10###XWV */ #define LOMBUS_STATUS 0xc0 /* Status byte: 0b11###AEV */ #define LOMBUS_SEQ 0x38 /* Sequence number bits */ #define LOMBUS_SEQ_LSB 0x08 /* Sequence number LSB */ #define LOMBUS_CMD_XADDR 0x04 /* Extended (2-byte) addressing */ #define LOMBUS_CMD_WRITE 0x02 /* Write command */ #define LOMBUS_CMD_WMSB 0x01 /* Set MSB on Write */ #define LOMBUS_CMD_READ 0x01 /* Read command */ #define LOMBUS_CMD_NOP 0x00 /* NOP command */ #define LOMBUS_STATUS_ASYNC 0x04 /* Asynchronous event pending */ #define LOMBUS_STATUS_ERR 0x02 /* Error in command processing */ #define LOMBUS_STATUS_MSB 0x01 /* MSB of Value read */ #define LOMBUS_VREG_LO(x) ((x) & ((1 << 7) - 1)) #define LOMBUS_VREG_HI(x) ((x) >> 7) #define LOMBUS_BUFSIZE 8 /* * Time periods, in nanoseconds * * Note that LOMBUS_ONE_SEC and some other time * periods are defined in */ #define LOMBUS_CMD_POLL (LOMBUS_ONE_SEC/20) #define LOMBUS_CTS_POLL (LOMBUS_ONE_SEC/20) #define LOMBUS_CTS_TIMEOUT (LOMBUS_ONE_SEC*2) /* * Local datatypes */ enum lombus_cmdstate { LOMBUS_CMDSTATE_IDLE, LOMBUS_CMDSTATE_BUSY, LOMBUS_CMDSTATE_WAITING, LOMBUS_CMDSTATE_READY, LOMBUS_CMDSTATE_ERROR }; /* * This driver's soft-state structure */ struct lombus_state { /* * Configuration data, set during attach */ dev_info_t *dip; major_t majornum; int instance; ddi_acc_handle_t sio_handle; uint8_t *sio_regs; ddi_softintr_t softid; ddi_periodic_t cycid; /* periodical callback */ /* * Parameters derived from .conf properties */ boolean_t allow_echo; int baud; uint32_t debug; boolean_t fake_cts; /* * Hardware mutex (initialised using ), * used to prevent retriggering the softint while * it's still fetching data out of the chip FIFO. */ kmutex_t hw_mutex[1]; ddi_iblock_cookie_t hw_iblk; /* * Data protected by the hardware mutex: the watchdog-patting * protocol data (since the dog can be patted from a high-level * cyclic), and the interrupt-enabled flag. */ hrtime_t hw_last_pat; boolean_t hw_int_enabled; /* * Flag to indicate that we've incurred a hardware fault on * accesses to the SIO; once this is set, we fake all further * accesses in order not to provoke additional bus errors. */ boolean_t sio_fault; /* * Serial protocol state data, protected by lo_mutex * (which is initialised using ) */ kmutex_t lo_mutex[1]; ddi_iblock_cookie_t lo_iblk; kcondvar_t lo_cv[1]; volatile enum lombus_cmdstate cmdstate; clock_t deadline; uint8_t cmdbuf[LOMBUS_BUFSIZE]; uint8_t reply[LOMBUS_BUFSIZE]; uint8_t async; uint8_t index; uint8_t result; uint8_t sequence; uint32_t error; }; /* * The auxiliary structure attached to each child * (the child's parent-private-data points to this). */ struct lombus_child_info { lombus_regspec_t *rsp; int nregs; }; /* * Local data */ static void *lombus_statep; static major_t lombus_major = NOMAJOR; static ddi_device_acc_attr_t lombus_dev_acc_attr[1] = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC }; /* * General utility routines ... */ static void lombus_trace(struct lombus_state *ssp, char code, const char *caller, const char *fmt, ...) { char buf[256]; char *p; va_list va; if (ssp->debug & (1 << (code-'@'))) { p = buf; snprintf(p, sizeof (buf) - (p - buf), "%s/%s: ", MYNAME, caller); p += strlen(p); va_start(va, fmt); vsnprintf(p, sizeof (buf) - (p - buf), fmt, va); va_end(va); buf[sizeof (buf) - 1] = '\0'; strlog(ssp->majornum, ssp->instance, code, SL_TRACE, buf); } } static struct lombus_state * lombus_getstate(dev_info_t *dip, int instance, const char *caller) { struct lombus_state *ssp = NULL; dev_info_t *sdip = NULL; major_t dmaj = NOMAJOR; if (dip != NULL) { /* * Use the instance number from the ; also, * check that it really corresponds to this driver */ instance = ddi_get_instance(dip); dmaj = ddi_driver_major(dip); if (lombus_major == NOMAJOR && dmaj != NOMAJOR) lombus_major = dmaj; else if (dmaj != lombus_major) { cmn_err(CE_WARN, "%s: major number mismatch (%d vs. %d) in %s()," "probably due to child misconfiguration", MYNAME, lombus_major, dmaj, caller); instance = -1; } } if (instance >= 0) ssp = ddi_get_soft_state(lombus_statep, instance); if (ssp != NULL) { sdip = ssp->dip; if (dip == NULL && sdip == NULL) ssp = NULL; else if (dip != NULL && sdip != NULL && sdip != dip) { cmn_err(CE_WARN, "%s: devinfo mismatch (%p vs. %p) in %s(), " "probably due to child misconfiguration", MYNAME, (void *)dip, (void *)sdip, caller); ssp = NULL; } } return (ssp); } /* * Lowest-level serial I/O chip register read/write */ static void sio_put_reg(struct lombus_state *ssp, uint_t reg, uint8_t val) { lombus_trace(ssp, 'P', "sio_put_reg", "REG[%d] <- $%02x", reg, val); if (ssp->sio_handle != NULL && !ssp->sio_fault) { /* * The chip is mapped as "I/O" (e.g. with the side-effect * bit on SPARC), therefore accesses are required to be * in-order, with no value cacheing. However, there can * still be write-behind buffering, so it is not guaranteed * that a write actually reaches the chip in a given time. * * To force the access right through to the chip, we follow * the write with another write (to the SCRATCH register) * and a read (of the value just written to the SCRATCH * register). The SCRATCH register is specifically provided * for temporary data and has no effect on the SIO's own * operation, making it ideal as a synchronising mechanism. * * If we didn't do this, it would be possible that the new * value wouldn't reach the chip (and have the *intended* * side-effects, such as disabling interrupts), for such a * long time that the processor could execute a *lot* of * instructions - including exiting the interrupt service * routine and re-enabling interrupts. This effect was * observed to lead to spurious (unclaimed) interrupts in * some circumstances. * * This will no longer be needed once "synchronous" access * handles are available (see PSARC/2000/269 and 2000/531). */ ddi_put8(ssp->sio_handle, ssp->sio_regs + reg, val); ddi_put8(ssp->sio_handle, ssp->sio_regs + SIO_SCR, val); membar_sync(); (void) ddi_get8(ssp->sio_handle, ssp->sio_regs + SIO_SCR); } } static uint8_t sio_get_reg(struct lombus_state *ssp, uint_t reg) { uint8_t val; if (ssp->sio_handle && !ssp->sio_fault) val = ddi_get8(ssp->sio_handle, ssp->sio_regs + reg); else val = DUMMY_VALUE; lombus_trace(ssp, 'G', "sio_get_reg", "$%02x <- REG[%d]", val, reg); return (val); } static void sio_check_fault_status(struct lombus_state *ssp) { ssp->sio_fault = ddi_check_acc_handle(ssp->sio_handle) != DDI_SUCCESS; } static boolean_t sio_faulty(struct lombus_state *ssp) { if (!ssp->sio_fault) sio_check_fault_status(ssp); return (ssp->sio_fault); } /* * Check for data ready. */ static boolean_t sio_data_ready(struct lombus_state *ssp) { uint8_t status; /* * Data is available if the RXDA bit in the LSR is nonzero * (if reading it didn't incur a fault). */ status = sio_get_reg(ssp, SIO_LSR); return ((status & SIO_LSR_RXDA) != 0 && !sio_faulty(ssp)); } /* * Check for LOM ready */ static boolean_t sio_lom_ready(struct lombus_state *ssp) { uint8_t status; boolean_t rslt; /* * The LOM is ready if the CTS bit in the MSR is 1, meaning * that the /CTS signal is being asserted (driven LOW) - * unless we incurred a fault in trying to read the MSR! * * For debugging, we force the result to TRUE if the FAKE flag is set */ status = sio_get_reg(ssp, SIO_MSR); rslt = (status & SIO_MSR_CTS) != 0 && !sio_faulty(ssp); lombus_trace(ssp, 'R', "sio_lom_ready", "S $%02x R %d F %d", status, rslt, ssp->fake_cts); return (rslt || ssp->fake_cts); } #if 0 /* * Check for interrupt pending */ static boolean_t sio_irq_pending(struct lombus_state *ssp) { uint8_t status; boolean_t rslt; /* * An interrupt is pending if the IPF bit in the EIR is 0, * assuming we didn't incur a fault in trying to ready it. * * Note: we expect that every time we read this register * (which is only done from the interrupt service routine), * we will see $11001100 (RX FIFO timeout interrupt pending). */ status = sio_get_reg(ssp, SIO_EIR); rslt = (status & SIO_EIR_IPF) == 0 && !sio_faulty(ssp); lombus_trace(ssp, 'I', "sio_irq_pending", "S $%02x R %d", status, rslt); /* * To investigate whether we're getting any abnormal interrupts * this code checks that the status value is as expected, and that * chip-level interrupts are supposed to be enabled at this time. * This will cause a PANIC (on a driver compiled with DEBUG) if * all is not as expected ... */ ASSERT(status == 0xCC); ASSERT(ssp->hw_int_enabled); return (rslt); } #endif /* 0 */ /* * Enable/disable interrupts */ static void lombus_set_irq(struct lombus_state *ssp, boolean_t newstate) { uint8_t val; val = newstate ? SIO_IER_RXHDL_IE : 0; sio_put_reg(ssp, SIO_IER, SIO_IER_STD | val); ssp->hw_int_enabled = newstate; } /* * Assert/deassert RTS */ static void lombus_toggle_rts(struct lombus_state *ssp) { uint8_t val; val = sio_get_reg(ssp, SIO_MCR); val &= SIO_MCR_RTS; val ^= SIO_MCR_RTS; val |= SIO_MCR_STD; sio_put_reg(ssp, SIO_MCR, val); } /* * High-level interrupt handler: * Checks whether initialisation is complete (to avoid a race * with mutex_init()), and whether chip interrupts are enabled. * If not, the interrupt's not for us, so just return UNCLAIMED. * Otherwise, disable the interrupt, trigger a softint, and return * CLAIMED. The softint handler will then do all the real work. * * NOTE: the chip interrupt capability is only re-enabled once the * receive code has run, but that can be called from a poll loop * or cyclic callback as well as from the softint. So it's *not* * guaranteed that there really is a chip interrupt pending here, * 'cos the work may already have been done and the reason for the * interrupt gone away before we get here. * * OTOH, if we come through here twice without the receive code * having run in between, that's definitely wrong. In such an * event, we would notice that chip interrupts haven't yet been * re-enabled and return UNCLAIMED, allowing the system's jabber * protect code (if any) to do its job. */ static uint_t lombus_hi_intr(caddr_t arg) { struct lombus_state *ssp = (void *)arg; uint_t claim; claim = DDI_INTR_UNCLAIMED; if (ssp->cycid != NULL) { mutex_enter(ssp->hw_mutex); if (ssp->hw_int_enabled) { lombus_set_irq(ssp, B_FALSE); ddi_trigger_softintr(ssp->softid); claim = DDI_INTR_CLAIMED; } mutex_exit(ssp->hw_mutex); } return (claim); } /* * Packet receive handler * * This routine should be called from the low-level softint, or the * cyclic callback, or lombus_cmd() (for polled operation), with the * low-level mutex already held. */ static void lombus_receive(struct lombus_state *ssp) { boolean_t ready = B_FALSE; uint8_t data = 0; uint8_t rcvd = 0; uint8_t tmp; lombus_trace(ssp, 'S', "lombus_receive", "state %d; error $%x", ssp->cmdstate, ssp->error); /* * Check for access faults before starting the receive * loop (we don't want to cause bus errors or suchlike * unpleasantness in the event that the SIO has died). */ if (!sio_faulty(ssp)) { /* * Read bytes from the FIFO until they're all gone, * or we find the 'END OF PACKET' set on one, or * our buffer overflows (which must be an error) */ mutex_enter(ssp->hw_mutex); while (sio_data_ready(ssp)) { data = sio_get_reg(ssp, SIO_RXD); ssp->reply[rcvd = ssp->index] = data; if (++rcvd >= LOMBUS_BUFSIZE) break; ssp->index = rcvd; if (data & LOMBUS_LAST) break; } lombus_set_irq(ssp, B_TRUE); mutex_exit(ssp->hw_mutex); } lombus_trace(ssp, 'S', "lombus_receive", "rcvd %d: $%02x $%02x $%02x $%02x $%02x $%02x $%02x $%02x", rcvd, ssp->reply[0], ssp->reply[1], ssp->reply[2], ssp->reply[3], ssp->reply[4], ssp->reply[5], ssp->reply[6], ssp->reply[7]); if (ssp->cmdstate != LOMBUS_CMDSTATE_WAITING) { /* * We're not expecting any data in this state, so if * we DID receive any data, we just throw it away by * resetting the buffer index to 0. */ ssp->index = 0; } else if (rcvd == 0) { /* * No bytes received this time through (though there * might be a partial packet sitting in the buffer). * If it seems the LOM is taking too long to respond, * we'll assume it's died and return an error. */ if (ddi_get_lbolt() > ssp->deadline) { ssp->cmdstate = LOMBUS_CMDSTATE_ERROR; ssp->error = LOMBUS_ERR_TIMEOUT; ready = B_TRUE; } } else if (rcvd >= LOMBUS_BUFSIZE) { /* * Buffer overflow; discard the data & treat as an error * (even if the last byte read did claim to terminate a * packet, it can't be a valid one 'cos it's too long!) */ ssp->index = 0; ssp->cmdstate = LOMBUS_CMDSTATE_ERROR; ssp->error = LOMBUS_ERR_OFLOW; ready = B_TRUE; } else if ((data & LOMBUS_LAST) == 0) { /* * Packet not yet complete; leave the partial packet in * the buffer for later ... */ _NOTE(EMPTY) ; } else if ((data & LOMBUS_MASK) != LOMBUS_STATUS) { /* * Invalid "status" byte - maybe an echo of the command? * * As a debugging feature, we allow for this, assuming * that if the LOM has echoed the command byte, it has * also echoed all the parameter bytes before starting * command processing. So, we dump out the buffer and * then clear it, so we can go back to looking for the * real reply. * * Otherwise, we just drop the data & flag an error. */ if (ssp->allow_echo) { lombus_trace(ssp, 'E', "lombus_receive", "echo $%02x $%02x $%02x $%02x " "$%02x $%02x $%02x $%02x", ssp->reply[0], ssp->reply[1], ssp->reply[2], ssp->reply[3], ssp->reply[4], ssp->reply[5], ssp->reply[6], ssp->reply[7]); ssp->index = 0; } else { ssp->cmdstate = LOMBUS_CMDSTATE_ERROR; ssp->error = LOMBUS_ERR_BADSTATUS; ready = B_TRUE; } } else if ((data & LOMBUS_SEQ) != ssp->sequence) { /* * Wrong sequence number! Flag this as an error */ ssp->cmdstate = LOMBUS_CMDSTATE_ERROR; ssp->error = LOMBUS_ERR_SEQUENCE; ready = B_TRUE; } else { /* * Finally, we know that's it's a valid reply to our * last command. Update the ASYNC status, derive the * reply parameter (if any), and check the ERROR bit * to find out what the parameter means. * * Note that not all the values read/assigned here * are meaningful, but it doesn't matter; the waiting * thread will know which one(s) it should check. */ ssp->async = (data & LOMBUS_STATUS_ASYNC) ? 1 : 0; tmp = ((data & LOMBUS_STATUS_MSB) ? 0x80 : 0) | ssp->reply[0]; if (data & LOMBUS_STATUS_ERR) { ssp->cmdstate = LOMBUS_CMDSTATE_ERROR; ssp->error = tmp; } else { ssp->cmdstate = LOMBUS_CMDSTATE_READY; ssp->result = tmp; } ready = B_TRUE; } lombus_trace(ssp, 'T', "lombus_receive", "rcvd %d; last $%02x; state %d; error $%x; ready %d", rcvd, data, ssp->cmdstate, ssp->error, ready); if (ready) cv_broadcast(ssp->lo_cv); } /* * Low-level softint handler * * This routine should be triggered whenever there's a byte to be read */ static uint_t lombus_softint(caddr_t arg) { struct lombus_state *ssp = (void *)arg; mutex_enter(ssp->lo_mutex); lombus_receive(ssp); mutex_exit(ssp->lo_mutex); return (DDI_INTR_CLAIMED); } /* * Cyclic handler: just calls the receive routine, in case interrupts * are not being delivered and in order to handle command timeout */ static void lombus_cyclic(void *arg) { struct lombus_state *ssp = (void *)arg; mutex_enter(ssp->lo_mutex); lombus_receive(ssp); mutex_exit(ssp->lo_mutex); } /* * Serial protocol * * This routine builds a command and sets it in progress. */ static uint8_t lombus_cmd(HANDLE_TYPE *hdlp, ptrdiff_t vreg, uint_t val, uint_t cmd) { struct lombus_state *ssp; clock_t start; uint8_t *p; /* * First of all, wait for the interface to be available. * * NOTE: we blow through all the mutex/cv/state checking and * preempt any command in progress if the system is panicking! */ ssp = HANDLE_PRIVATE(hdlp); mutex_enter(ssp->lo_mutex); while (ssp->cmdstate != LOMBUS_CMDSTATE_IDLE && !panicstr) cv_wait(ssp->lo_cv, ssp->lo_mutex); ssp->cmdstate = LOMBUS_CMDSTATE_BUSY; ssp->sequence = (ssp->sequence + LOMBUS_SEQ_LSB) & LOMBUS_SEQ; /* * We have exclusive ownership, so assemble the command (backwards): * * [byte 0] Command: modified by XADDR and/or WMSB bits * [Optional] Parameter: Value to write (low 7 bits) * [Optional] Parameter: Register number (high 7 bits) * [Optional] Parameter: Register number (low 7 bits) */ p = &ssp->cmdbuf[0]; *p++ = LOMBUS_CMD | ssp->sequence | cmd; switch (cmd) { case LOMBUS_CMD_WRITE: *p++ = val & 0x7f; if (val >= 0x80) ssp->cmdbuf[0] |= LOMBUS_CMD_WMSB; /*FALLTHRU*/ case LOMBUS_CMD_READ: if (LOMBUS_VREG_HI(vreg) != 0) { *p++ = LOMBUS_VREG_HI(vreg); ssp->cmdbuf[0] |= LOMBUS_CMD_XADDR; } *p++ = LOMBUS_VREG_LO(vreg); /*FALLTHRU*/ case LOMBUS_CMD_NOP: break; } /* * Check and update the SIO h/w fault status before accessing * the chip registers. If there's a (new or previous) fault, * we'll run through the protocol but won't really touch the * hardware and all commands will timeout. If a previously * discovered fault has now gone away (!), then we can (try to) * proceed with the new command (probably a probe). */ sio_check_fault_status(ssp); /* * Wait up to LOMBUS_CTS_TIMEOUT (2 seconds) for the LOM to tell * us that it's ready for the next command. If it doesn't, though, * we'll send it anyway, on the basis that the CTS signal might be * open- or short-circuited (or the LOM firmware forgot to set it, * or the LOM just got reset, or whatever ...) */ start = ddi_get_lbolt(); ssp->deadline = start + drv_usectohz(LOMBUS_CTS_TIMEOUT/1000); while (!sio_lom_ready(ssp)) { if (ddi_get_lbolt() > ssp->deadline) break; cv_reltimedwait(ssp->lo_cv, ssp->lo_mutex, drv_usectohz(LOMBUS_CTS_POLL/1000), TR_CLOCK_TICK); } /* * Either the LOM is ready, or we timed out waiting for CTS. * In either case, we're going to send the command now by * stuffing the packet into the Tx FIFO, reversing it as we go. * We call lombus_receive() first to ensure there isn't any * garbage left in the Rx FIFO from an earlier command that * timed out (or was pre-empted by a PANIC!). This also makes * sure that SIO interrupts are enabled so we'll see the reply * more quickly (the poll loop below will still work even if * interrupts aren't enabled, but it will take longer). */ lombus_receive(ssp); mutex_enter(ssp->hw_mutex); while (p > ssp->cmdbuf) sio_put_reg(ssp, SIO_TXD, *--p); mutex_exit(ssp->hw_mutex); /* * Prepare for the reply (to be processed by the interrupt/cyclic * handler and/or polling loop below), then wait for a response * or timeout. */ start = ddi_get_lbolt(); ssp->deadline = start + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000); ssp->error = 0; ssp->index = 0; ssp->result = DUMMY_VALUE; ssp->cmdstate = LOMBUS_CMDSTATE_WAITING; while (ssp->cmdstate == LOMBUS_CMDSTATE_WAITING) { if (cv_reltimedwait(ssp->lo_cv, ssp->lo_mutex, drv_usectohz(LOMBUS_CMD_POLL/1000), TR_CLOCK_TICK) == -1) lombus_receive(ssp); } /* * The return value may not be meaningful but retrieve it anyway */ val = ssp->result; if (sio_faulty(ssp)) { val = DUMMY_VALUE; HANDLE_FAULT(hdlp) = LOMBUS_ERR_SIOHW; } else if (ssp->cmdstate != LOMBUS_CMDSTATE_READY) { /* * Some problem here ... transfer the error code from * the per-instance state to the per-handle fault flag. * The error code shouldn't be zero! */ if (ssp->error != 0) HANDLE_FAULT(hdlp) = ssp->error; else HANDLE_FAULT(hdlp) = LOMBUS_ERR_BADERRCODE; } /* * All done now! */ ssp->index = 0; ssp->cmdstate = LOMBUS_CMDSTATE_IDLE; cv_broadcast(ssp->lo_cv); mutex_exit(ssp->lo_mutex); return (val); } /* * Space 0 - LOM virtual register access * Only 8-bit accesses are supported. */ static uint8_t lombus_vreg_get8(HANDLE_TYPE *hdlp, uint8_t *addr) { ptrdiff_t offset; /* * Check the offset that the caller has added to the base address * against the length of the mapping originally requested. */ offset = ADDR_TO_OFFSET(addr, hdlp); if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) { /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM; return (DUMMY_VALUE); } /* * Derive the virtual register number and run the command */ return (lombus_cmd(hdlp, ADDR_TO_VREG(addr), 0, LOMBUS_CMD_READ)); } static void lombus_vreg_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val) { ptrdiff_t offset; /* * Check the offset that the caller has added to the base address * against the length of the mapping originally requested. */ offset = ADDR_TO_OFFSET(addr, hdlp); if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) { /* * Invalid access - flag a fault and return */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM; return; } /* * Derive the virtual register number and run the command */ (void) lombus_cmd(hdlp, ADDR_TO_VREG(addr), val, LOMBUS_CMD_WRITE); } static void lombus_vreg_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) *host_addr++ = lombus_vreg_get8(hdlp, dev_addr); } static void lombus_vreg_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) lombus_vreg_put8(hdlp, dev_addr, *host_addr++); } /* * Space 1 - LOM watchdog pat register access * Only 8-bit accesses are supported. * * Reads have no effect and return 0. * * Writes pat the dog by toggling the RTS line iff enough time has * elapsed since last time we toggled it. * * Multi-byte reads (using ddi_rep_get8(9F)) are a fairly inefficient * way of zeroing the destination area ;-) and still won't pat the dog. * * Multi-byte writes (using ddi_rep_put8(9F)) will almost certainly * only count as a single pat, no matter how many bytes the caller * says to write, as the inter-pat time is VERY long compared with * the time it will take to read the memory source area. */ static uint8_t lombus_pat_get8(HANDLE_TYPE *hdlp, uint8_t *addr) { ptrdiff_t offset; /* * Check the offset that the caller has added to the base address * against the length of the mapping originally requested. */ offset = ADDR_TO_OFFSET(addr, hdlp); if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) { /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM; return (DUMMY_VALUE); } return (0); } static void lombus_pat_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val) { struct lombus_state *ssp; ptrdiff_t offset; hrtime_t now; _NOTE(ARGUNUSED(val)) /* * Check the offset that the caller has added to the base address * against the length of the mapping originally requested. */ offset = ADDR_TO_OFFSET(addr, hdlp); if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) { /* * Invalid access - flag a fault and return */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM; return; } ssp = HANDLE_PRIVATE(hdlp); mutex_enter(ssp->hw_mutex); now = gethrtime(); if ((now - ssp->hw_last_pat) >= LOMBUS_MIN_PAT) { lombus_toggle_rts(ssp); ssp->hw_last_pat = now; } mutex_exit(ssp->hw_mutex); } static void lombus_pat_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) *host_addr++ = lombus_pat_get8(hdlp, dev_addr); } static void lombus_pat_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) lombus_pat_put8(hdlp, dev_addr, *host_addr++); } /* * Space 2 - LOM async event flag register access * Only 16-bit accesses are supported. */ static uint16_t lombus_event_get16(HANDLE_TYPE *hdlp, uint16_t *addr) { struct lombus_state *ssp; ptrdiff_t offset; /* * Check the offset that the caller has added to the base address * against the length of the mapping orignally requested. */ offset = ADDR_TO_OFFSET(addr, hdlp); if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) { /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM; return (DUMMY_VALUE); } /* * Return the value of the asynchronous-event-pending flag * as passed back by the LOM at the end of the last command. */ ssp = HANDLE_PRIVATE(hdlp); return (ssp->async); } static void lombus_event_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val) { ptrdiff_t offset; _NOTE(ARGUNUSED(val)) /* * Check the offset that the caller has added to the base address * against the length of the mapping originally requested. */ offset = ADDR_TO_OFFSET(addr, hdlp); if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) { /* * Invalid access - flag a fault and return */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM; return; } /* * The user can't overwrite the asynchronous-event-pending flag! */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_RO; } static void lombus_event_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) *host_addr++ = lombus_event_get16(hdlp, dev_addr); } static void lombus_event_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) lombus_event_put16(hdlp, dev_addr, *host_addr++); } /* * All spaces - access handle fault information * Only 32-bit accesses are supported. */ static uint32_t lombus_meta_get32(HANDLE_TYPE *hdlp, uint32_t *addr) { struct lombus_state *ssp; ptrdiff_t offset; /* * Derive the offset that the caller has added to the base * address originally returned, and use it to determine * which meta-register is to be accessed ... */ offset = ADDR_TO_OFFSET(addr, hdlp); switch (offset) { case LOMBUS_FAULT_REG: /* * This meta-register provides a code for the most * recent virtual register access fault, if any. */ return (HANDLE_FAULT(hdlp)); case LOMBUS_PROBE_REG: /* * Reading this meta-register clears any existing fault * (at the virtual, not the hardware access layer), then * runs a NOP command and returns the fault code from that. */ HANDLE_FAULT(hdlp) = 0; lombus_cmd(hdlp, 0, 0, LOMBUS_CMD_NOP); return (HANDLE_FAULT(hdlp)); case LOMBUS_ASYNC_REG: /* * Obsolescent - but still supported for backwards * compatibility. This is an alias for the newer * LOMBUS_EVENT_REG, but doesn't require a separate * "reg" entry and ddi_regs_map_setup() call. * * It returns the value of the asynchronous-event-pending * flag as passed back by the LOM at the end of the last * completed command. */ ssp = HANDLE_PRIVATE(hdlp); return (ssp->async); default: /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return (DUMMY_VALUE); } } static void lombus_meta_put32(HANDLE_TYPE *hdlp, uint32_t *addr, uint32_t val) { ptrdiff_t offset; /* * Derive the offset that the caller has added to the base * address originally returned, and use it to determine * which meta-register is to be accessed ... */ offset = ADDR_TO_OFFSET(addr, hdlp); switch (offset) { case LOMBUS_FAULT_REG: /* * This meta-register contains a code for the most * recent virtual register access fault, if any. * It can be cleared simply by writing 0 to it. */ HANDLE_FAULT(hdlp) = val; return; case LOMBUS_PROBE_REG: /* * Writing this meta-register clears any existing fault * (at the virtual, not the hardware acess layer), then * runs a NOP command. The caller can check the fault * code later if required. */ HANDLE_FAULT(hdlp) = 0; lombus_cmd(hdlp, 0, 0, LOMBUS_CMD_NOP); return; default: /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return; } } static void lombus_meta_rep_get32(HANDLE_TYPE *hdlp, uint32_t *host_addr, uint32_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) *host_addr++ = lombus_meta_get32(hdlp, dev_addr); } static void lombus_meta_rep_put32(HANDLE_TYPE *hdlp, uint32_t *host_addr, uint32_t *dev_addr, size_t repcount, uint_t flags) { size_t inc; inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; for (; repcount--; dev_addr += inc) lombus_meta_put32(hdlp, dev_addr, *host_addr++); } /* * Finally, some dummy functions for all unsupported access * space/size/mode combinations ... */ static uint8_t lombus_no_get8(HANDLE_TYPE *hdlp, uint8_t *addr) { _NOTE(ARGUNUSED(addr)) /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return (DUMMY_VALUE); } static void lombus_no_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val) { _NOTE(ARGUNUSED(addr, val)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static void lombus_no_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static void lombus_no_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static uint16_t lombus_no_get16(HANDLE_TYPE *hdlp, uint16_t *addr) { _NOTE(ARGUNUSED(addr)) /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return (DUMMY_VALUE); } static void lombus_no_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val) { _NOTE(ARGUNUSED(addr, val)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static void lombus_no_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags) { _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static void lombus_no_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags) { _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static uint64_t lombus_no_get64(HANDLE_TYPE *hdlp, uint64_t *addr) { _NOTE(ARGUNUSED(addr)) /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return (DUMMY_VALUE); } static void lombus_no_put64(HANDLE_TYPE *hdlp, uint64_t *addr, uint64_t val) { _NOTE(ARGUNUSED(addr, val)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static void lombus_no_rep_get64(HANDLE_TYPE *hdlp, uint64_t *host_addr, uint64_t *dev_addr, size_t repcount, uint_t flags) { _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static void lombus_no_rep_put64(HANDLE_TYPE *hdlp, uint64_t *host_addr, uint64_t *dev_addr, size_t repcount, uint_t flags) { _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags)) /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; } static int lombus_acc_fault_check(HANDLE_TYPE *hdlp) { return (HANDLE_FAULT(hdlp) != 0); } /* * Hardware setup - put the SIO chip in the required operational * state, with all our favourite parameters programmed correctly. * This routine leaves all SIO interrupts disabled. */ static void lombus_hw_reset(struct lombus_state *ssp) { uint16_t divisor; /* * Disable interrupts, soft reset Tx and Rx circuitry, * reselect standard modes (bits/char, parity, etc). */ lombus_set_irq(ssp, B_FALSE); sio_put_reg(ssp, SIO_FCR, SIO_FCR_RXSR | SIO_FCR_TXSR); sio_put_reg(ssp, SIO_LCR, SIO_LCR_STD); /* * Select the proper baud rate; if the value is invalid * (presumably 0, i.e. not specified, but also if the * "baud" property is set to some silly value), we assume * the default. */ if (ssp->baud < SIO_BAUD_MIN || ssp->baud > SIO_BAUD_MAX) divisor = SIO_BAUD_TO_DIVISOR(SIO_BAUD_DEFAULT); else divisor = SIO_BAUD_TO_DIVISOR(ssp->baud); /* * According to the datasheet, it is forbidden for the divisor * register to be zero. So when loading the register in two * steps, we have to make sure that the temporary value formed * between loads is nonzero. However, we can't rely on either * half already having a nonzero value, as the datasheet also * says that these registers are indeterminate after a reset! * So, we explicitly set the low byte to a non-zero value first; * then we can safely load the high byte, and then the correct * value for the low byte, without the result ever being zero. */ sio_put_reg(ssp, SIO_BSR, SIO_BSR_BANK1); sio_put_reg(ssp, SIO_LBGDL, 0xff); sio_put_reg(ssp, SIO_LBGDH, divisor >> 8); sio_put_reg(ssp, SIO_LBGDL, divisor & 0xff); sio_put_reg(ssp, SIO_BSR, SIO_BSR_BANK0); /* * Program the remaining device registers as required */ sio_put_reg(ssp, SIO_MCR, SIO_MCR_STD); sio_put_reg(ssp, SIO_FCR, SIO_FCR_STD); } /* * Higher-level setup & teardown */ static void lombus_offline(struct lombus_state *ssp) { if (ssp->sio_handle != NULL) ddi_regs_map_free(&ssp->sio_handle); ssp->sio_handle = NULL; ssp->sio_regs = NULL; } static int lombus_online(struct lombus_state *ssp) { ddi_acc_handle_t h; caddr_t p; int nregs; int err; if (ddi_dev_nregs(ssp->dip, &nregs) != DDI_SUCCESS) nregs = 0; switch (nregs) { default: case 1: /* * regset 0 represents the SIO operating registers */ err = ddi_regs_map_setup(ssp->dip, 0, &p, 0, 0, lombus_dev_acc_attr, &h); lombus_trace(ssp, 'O', "online", "regmap 0 status %d addr $%p", err, p); if (err != DDI_SUCCESS) return (EIO); ssp->sio_handle = h; ssp->sio_regs = (void *)p; break; case 0: /* * If no registers are defined, succeed vacuously; * commands will be accepted, but we fake the accesses. */ break; } /* * Now that the registers are mapped, we can initialise the SIO h/w */ lombus_hw_reset(ssp); return (0); } /* * Nexus routines */ #if defined(NDI_ACC_HDL_V2) static const ndi_acc_fns_t lombus_vreg_acc_fns = { NDI_ACC_FNS_CURRENT, NDI_ACC_FNS_V1, lombus_vreg_get8, lombus_vreg_put8, lombus_vreg_rep_get8, lombus_vreg_rep_put8, lombus_no_get16, lombus_no_put16, lombus_no_rep_get16, lombus_no_rep_put16, lombus_meta_get32, lombus_meta_put32, lombus_meta_rep_get32, lombus_meta_rep_put32, lombus_no_get64, lombus_no_put64, lombus_no_rep_get64, lombus_no_rep_put64, lombus_acc_fault_check }; static const ndi_acc_fns_t lombus_pat_acc_fns = { NDI_ACC_FNS_CURRENT, NDI_ACC_FNS_V1, lombus_pat_get8, lombus_pat_put8, lombus_pat_rep_get8, lombus_pat_rep_put8, lombus_no_get16, lombus_no_put16, lombus_no_rep_get16, lombus_no_rep_put16, lombus_meta_get32, lombus_meta_put32, lombus_meta_rep_get32, lombus_meta_rep_put32, lombus_no_get64, lombus_no_put64, lombus_no_rep_get64, lombus_no_rep_put64, lombus_acc_fault_check }; static const ndi_acc_fns_t lombus_event_acc_fns = { NDI_ACC_FNS_CURRENT, NDI_ACC_FNS_V1, lombus_no_get8, lombus_no_put8, lombus_no_rep_get8, lombus_no_rep_put8, lombus_event_get16, lombus_event_put16, lombus_event_rep_get16, lombus_event_rep_put16, lombus_meta_get32, lombus_meta_put32, lombus_meta_rep_get32, lombus_meta_rep_put32, lombus_no_get64, lombus_no_put64, lombus_no_rep_get64, lombus_no_rep_put64, lombus_acc_fault_check }; static int lombus_map_handle(struct lombus_state *ssp, ddi_map_op_t op, int space, caddr_t vaddr, off_t len, ndi_acc_handle_t *hdlp, caddr_t *addrp) { switch (op) { default: return (DDI_ME_UNIMPLEMENTED); case DDI_MO_MAP_LOCKED: switch (space) { default: return (DDI_ME_REGSPEC_RANGE); case LOMBUS_VREG_SPACE: ndi_set_acc_fns(hdlp, &lombus_vreg_acc_fns); break; case LOMBUS_PAT_SPACE: ndi_set_acc_fns(hdlp, &lombus_pat_acc_fns); break; case LOMBUS_EVENT_SPACE: ndi_set_acc_fns(hdlp, &lombus_event_acc_fns); break; } hdlp->ah_addr = *addrp = vaddr; hdlp->ah_len = len; hdlp->ah_bus_private = ssp; return (DDI_SUCCESS); case DDI_MO_UNMAP: *addrp = NULL; hdlp->ah_bus_private = NULL; return (DDI_SUCCESS); } } #else static int lombus_map_handle(struct lombus_state *ssp, ddi_map_op_t op, int space, caddr_t vaddr, off_t len, ddi_acc_hdl_t *hdlp, caddr_t *addrp) { ddi_acc_impl_t *aip = hdlp->ah_platform_private; switch (op) { default: return (DDI_ME_UNIMPLEMENTED); case DDI_MO_MAP_LOCKED: switch (space) { default: return (DDI_ME_REGSPEC_RANGE); case LOMBUS_VREG_SPACE: aip->ahi_get8 = lombus_vreg_get8; aip->ahi_put8 = lombus_vreg_put8; aip->ahi_rep_get8 = lombus_vreg_rep_get8; aip->ahi_rep_put8 = lombus_vreg_rep_put8; aip->ahi_get16 = lombus_no_get16; aip->ahi_put16 = lombus_no_put16; aip->ahi_rep_get16 = lombus_no_rep_get16; aip->ahi_rep_put16 = lombus_no_rep_put16; aip->ahi_get32 = lombus_meta_get32; aip->ahi_put32 = lombus_meta_put32; aip->ahi_rep_get32 = lombus_meta_rep_get32; aip->ahi_rep_put32 = lombus_meta_rep_put32; aip->ahi_get64 = lombus_no_get64; aip->ahi_put64 = lombus_no_put64; aip->ahi_rep_get64 = lombus_no_rep_get64; aip->ahi_rep_put64 = lombus_no_rep_put64; aip->ahi_fault_check = lombus_acc_fault_check; break; case LOMBUS_PAT_SPACE: aip->ahi_get8 = lombus_pat_get8; aip->ahi_put8 = lombus_pat_put8; aip->ahi_rep_get8 = lombus_pat_rep_get8; aip->ahi_rep_put8 = lombus_pat_rep_put8; aip->ahi_get16 = lombus_no_get16; aip->ahi_put16 = lombus_no_put16; aip->ahi_rep_get16 = lombus_no_rep_get16; aip->ahi_rep_put16 = lombus_no_rep_put16; aip->ahi_get32 = lombus_meta_get32; aip->ahi_put32 = lombus_meta_put32; aip->ahi_rep_get32 = lombus_meta_rep_get32; aip->ahi_rep_put32 = lombus_meta_rep_put32; aip->ahi_get64 = lombus_no_get64; aip->ahi_put64 = lombus_no_put64; aip->ahi_rep_get64 = lombus_no_rep_get64; aip->ahi_rep_put64 = lombus_no_rep_put64; aip->ahi_fault_check = lombus_acc_fault_check; break; case LOMBUS_EVENT_SPACE: aip->ahi_get8 = lombus_no_get8; aip->ahi_put8 = lombus_no_put8; aip->ahi_rep_get8 = lombus_no_rep_get8; aip->ahi_rep_put8 = lombus_no_rep_put8; aip->ahi_get16 = lombus_event_get16; aip->ahi_put16 = lombus_event_put16; aip->ahi_rep_get16 = lombus_event_rep_get16; aip->ahi_rep_put16 = lombus_event_rep_put16; aip->ahi_get32 = lombus_meta_get32; aip->ahi_put32 = lombus_meta_put32; aip->ahi_rep_get32 = lombus_meta_rep_get32; aip->ahi_rep_put32 = lombus_meta_rep_put32; aip->ahi_get64 = lombus_no_get64; aip->ahi_put64 = lombus_no_put64; aip->ahi_rep_get64 = lombus_no_rep_get64; aip->ahi_rep_put64 = lombus_no_rep_put64; aip->ahi_fault_check = lombus_acc_fault_check; break; } hdlp->ah_addr = *addrp = vaddr; hdlp->ah_len = len; hdlp->ah_bus_private = ssp; return (DDI_SUCCESS); case DDI_MO_UNMAP: *addrp = NULL; hdlp->ah_bus_private = NULL; return (DDI_SUCCESS); } } #endif /* NDI_ACC_HDL_V2 */ static int lombus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t off, off_t len, caddr_t *addrp) { struct lombus_child_info *lcip; struct lombus_state *ssp; lombus_regspec_t *rsp; if ((ssp = lombus_getstate(dip, -1, "lombus_map")) == NULL) return (DDI_FAILURE); /* this "can't happen" */ /* * Validate mapping request ... */ if (mp->map_flags != DDI_MF_KERNEL_MAPPING) return (DDI_ME_UNSUPPORTED); if (mp->map_handlep == NULL) return (DDI_ME_UNSUPPORTED); if (mp->map_type != DDI_MT_RNUMBER) return (DDI_ME_UNIMPLEMENTED); if ((lcip = ddi_get_parent_data(rdip)) == NULL) return (DDI_ME_INVAL); if ((rsp = lcip->rsp) == NULL) return (DDI_ME_INVAL); if (mp->map_obj.rnumber >= lcip->nregs) return (DDI_ME_RNUMBER_RANGE); rsp += mp->map_obj.rnumber; if (off < 0 || off >= rsp->lombus_size) return (DDI_ME_INVAL); if (len == 0) len = rsp->lombus_size-off; if (len < 0) return (DDI_ME_INVAL); if (off+len < 0 || off+len > rsp->lombus_size) return (DDI_ME_INVAL); return (lombus_map_handle(ssp, mp->map_op, rsp->lombus_space, VREG_TO_ADDR(rsp->lombus_base+off), len, mp->map_handlep, addrp)); } static int lombus_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result) { struct lombus_child_info *lcip; struct lombus_state *ssp; lombus_regspec_t *rsp; dev_info_t *cdip; char addr[32]; uint_t nregs; uint_t rnum; int *regs; int limit; int err; int i; if ((ssp = lombus_getstate(dip, -1, "lombus_ctlops")) == NULL) return (DDI_FAILURE); /* this "can't happen" */ switch (op) { default: break; case DDI_CTLOPS_INITCHILD: /* * First, look up and validate the "reg" property. * * It must be a non-empty integer array containing a set * of triples. Once we've verified that, we can treat it * as an array of type lombus_regspec_t[], which defines * the meaning of the elements of each triple: * + the first element of each triple must be a valid space * + the second and third elements (base, size) of each * triple must define a valid subrange of that space * If it passes all the tests, we save it away for future * reference in the child's parent-private-data field. */ cdip = arg; err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS, "reg", ®s, &nregs); lombus_trace(ssp, 'C', "initchild", "prop status %d size %d", err, nregs); if (err != DDI_PROP_SUCCESS) return (DDI_FAILURE); err = (nregs <= 0 || (nregs % LOMBUS_REGSPEC_SIZE) != 0); nregs /= LOMBUS_REGSPEC_SIZE; rsp = (lombus_regspec_t *)regs; for (i = 0; i < nregs && !err; ++i) { switch (rsp[i].lombus_space) { default: limit = 0; err = 1; break; case LOMBUS_VREG_SPACE: limit = LOMBUS_MAX_REG+1; break; case LOMBUS_PAT_SPACE: limit = LOMBUS_PAT_REG+1; break; case LOMBUS_EVENT_SPACE: limit = LOMBUS_EVENT_REG+1; break; } err |= (rsp[i].lombus_base < 0); err |= (rsp[i].lombus_base >= limit); if (rsp[i].lombus_size == 0) rsp[i].lombus_size = limit-rsp[i].lombus_base; err |= (rsp[i].lombus_size < 0); err |= (rsp[i].lombus_base+rsp[i].lombus_size < 0); err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit); } if (err) { ddi_prop_free(regs); return (DDI_FAILURE); } lcip = kmem_zalloc(sizeof (*lcip), KM_SLEEP); lcip->nregs = nregs; lcip->rsp = rsp; ddi_set_parent_data(cdip, lcip); (void) snprintf(addr, sizeof (addr), "%x,%x", rsp[0].lombus_space, rsp[0].lombus_base); ddi_set_name_addr(cdip, addr); return (DDI_SUCCESS); case DDI_CTLOPS_UNINITCHILD: cdip = arg; ddi_set_name_addr(cdip, NULL); lcip = ddi_get_parent_data(cdip); ddi_set_parent_data(cdip, NULL); ddi_prop_free(lcip->rsp); kmem_free(lcip, sizeof (*lcip)); return (DDI_SUCCESS); case DDI_CTLOPS_REPORTDEV: if (rdip == NULL) return (DDI_FAILURE); cmn_err(CE_CONT, "?LOM device: %s@%s, %s#%d\n", ddi_node_name(rdip), ddi_get_name_addr(rdip), ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_SUCCESS); case DDI_CTLOPS_REGSIZE: if ((lcip = ddi_get_parent_data(rdip)) == NULL) return (DDI_FAILURE); if ((rnum = *(uint_t *)arg) >= lcip->nregs) return (DDI_FAILURE); *(off_t *)result = lcip->rsp[rnum].lombus_size; return (DDI_SUCCESS); case DDI_CTLOPS_NREGS: if ((lcip = ddi_get_parent_data(rdip)) == NULL) return (DDI_FAILURE); *(int *)result = lcip->nregs; return (DDI_SUCCESS); } return (ddi_ctlops(dip, rdip, op, arg, result)); } /* * Clean up on detach or failure of attach */ static int lombus_unattach(struct lombus_state *ssp, int instance) { if (ssp != NULL) { lombus_hw_reset(ssp); if (ssp->cycid != NULL) { ddi_periodic_delete(ssp->cycid); ssp->cycid = NULL; if (ssp->sio_handle != NULL) ddi_remove_intr(ssp->dip, 0, ssp->hw_iblk); ddi_remove_softintr(ssp->softid); cv_destroy(ssp->lo_cv); mutex_destroy(ssp->lo_mutex); mutex_destroy(ssp->hw_mutex); } lombus_offline(ssp); ddi_set_driver_private(ssp->dip, NULL); } ddi_soft_state_free(lombus_statep, instance); return (DDI_FAILURE); } /* * Autoconfiguration routines */ static int lombus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct lombus_state *ssp = NULL; int instance; int err; switch (cmd) { default: return (DDI_FAILURE); case DDI_ATTACH: break; } /* * Allocate the soft-state structure */ instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(lombus_statep, instance) != DDI_SUCCESS) return (DDI_FAILURE); if ((ssp = lombus_getstate(dip, instance, "lombus_attach")) == NULL) return (lombus_unattach(ssp, instance)); ddi_set_driver_private(dip, ssp); /* * Initialise devinfo-related fields */ ssp->dip = dip; ssp->majornum = ddi_driver_major(dip); ssp->instance = instance; /* * Set various options from .conf properties */ ssp->allow_echo = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "allow-lom-echo", 0) != 0; ssp->baud = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "baud-rate", 0); ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "debug", 0); ssp->fake_cts = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "fake-cts", 0) != 0; /* * Initialise current state & time */ ssp->cmdstate = LOMBUS_CMDSTATE_IDLE; ssp->hw_last_pat = gethrtime(); ssp->cycid = NULL; /* * Online the hardware ... */ err = lombus_online(ssp); if (err != 0) return (lombus_unattach(ssp, instance)); /* * Install soft and hard interrupt handler(s) * Initialise mutexes and cv * Start cyclic callbacks * Enable interrupts */ err = ddi_add_softintr(dip, DDI_SOFTINT_LOW, &ssp->softid, &ssp->lo_iblk, NULL, lombus_softint, (caddr_t)ssp); if (err != DDI_SUCCESS) return (lombus_unattach(ssp, instance)); if (ssp->sio_handle != NULL) err = ddi_add_intr(dip, 0, &ssp->hw_iblk, NULL, lombus_hi_intr, (caddr_t)ssp); mutex_init(ssp->hw_mutex, NULL, MUTEX_DRIVER, ssp->hw_iblk); mutex_init(ssp->lo_mutex, NULL, MUTEX_DRIVER, ssp->lo_iblk); cv_init(ssp->lo_cv, NULL, CV_DRIVER, NULL); /* * Register a periodical handler. */ ssp->cycid = ddi_periodic_add(lombus_cyclic, ssp, LOMBUS_ONE_SEC, DDI_IPL_1); /* * Final check before enabling h/w interrupts - did * we successfully install the h/w interrupt handler? */ if (err != DDI_SUCCESS) return (lombus_unattach(ssp, instance)); lombus_set_irq(ssp, B_TRUE); /* * All done, report success */ ddi_report_dev(dip); return (DDI_SUCCESS); } static int lombus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct lombus_state *ssp; int instance; switch (cmd) { default: return (DDI_FAILURE); case DDI_DETACH: break; } instance = ddi_get_instance(dip); if ((ssp = lombus_getstate(dip, instance, "lombus_detach")) == NULL) return (DDI_FAILURE); /* this "can't happen" */ (void) lombus_unattach(ssp, instance); return (DDI_SUCCESS); } static int lombus_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) { struct lombus_state *ssp; _NOTE(ARGUNUSED(cmd)) if ((ssp = lombus_getstate(dip, -1, "lombus_reset")) == NULL) return (DDI_FAILURE); lombus_hw_reset(ssp); return (DDI_SUCCESS); } /* * System interface structures */ static struct cb_ops lombus_cb_ops = { nodev, /* b/c open */ nodev, /* b/c close */ nodev, /* b strategy */ nodev, /* b print */ nodev, /* b dump */ nodev, /* c read */ nodev, /* c write */ nodev, /* c ioctl */ nodev, /* c devmap */ nodev, /* c mmap */ nodev, /* c segmap */ nochpoll, /* c poll */ ddi_prop_op, /* b/c prop_op */ NULL, /* c streamtab */ D_MP | D_NEW /* b/c flags */ }; static struct bus_ops lombus_bus_ops = { BUSO_REV, /* revision */ lombus_map, /* bus_map */ 0, /* get_intrspec */ 0, /* add_intrspec */ 0, /* remove_intrspec */ i_ddi_map_fault, /* map_fault */ ddi_no_dma_map, /* dma_map */ ddi_no_dma_allochdl, /* allocate DMA handle */ ddi_no_dma_freehdl, /* free DMA handle */ ddi_no_dma_bindhdl, /* bind DMA handle */ ddi_no_dma_unbindhdl, /* unbind DMA handle */ ddi_no_dma_flush, /* flush DMA */ ddi_no_dma_win, /* move DMA window */ ddi_no_dma_mctl, /* generic DMA control */ lombus_ctlops, /* generic control */ ddi_bus_prop_op, /* prop_op */ ndi_busop_get_eventcookie, /* get_eventcookie */ ndi_busop_add_eventcall, /* add_eventcall */ ndi_busop_remove_eventcall, /* remove_eventcall */ ndi_post_event, /* post_event */ 0, /* interrupt control */ 0, /* bus_config */ 0, /* bus_unconfig */ 0, /* bus_fm_init */ 0, /* bus_fm_fini */ 0, /* bus_fm_access_enter */ 0, /* bus_fm_access_exit */ 0, /* bus_power */ i_ddi_intr_ops /* bus_intr_op */ }; static struct dev_ops lombus_dev_ops = { DEVO_REV, 0, /* refcount */ ddi_no_info, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ lombus_attach, /* attach */ lombus_detach, /* detach */ lombus_reset, /* reset */ &lombus_cb_ops, /* driver operations */ &lombus_bus_ops, /* bus operations */ NULL, /* power */ ddi_quiesce_not_supported, /* devo_quiesce */ }; static struct modldrv modldrv = { &mod_driverops, "lombus driver", &lombus_dev_ops }; static struct modlinkage modlinkage = { MODREV_1, { &modldrv, NULL } }; /* * Dynamic loader interface code */ int _init(void) { int err; err = ddi_soft_state_init(&lombus_statep, sizeof (struct lombus_state), 0); if (err == DDI_SUCCESS) if ((err = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&lombus_statep); } return (err); } int _info(struct modinfo *mip) { return (mod_info(&modlinkage, mip)); } int _fini(void) { int err; if ((err = mod_remove(&modlinkage)) == 0) { ddi_soft_state_fini(&lombus_statep); lombus_major = NOMAJOR; } return (err); }