/* * 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 "bscbus" 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 Xbus (similar to ebus) parallel link to the * H8 host interface registers. * * On the other hand, this driver doesn't generally know what the virtual * registers signify - only the clients need this information. */ #include #include #include #include #include #include #if defined(__sparc) #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(NDI_ACC_HDL_V2) /* * Compiling for Solaris 10+ 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/9 */ #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 "bscbus" #define NOMAJOR (~(major_t)0) #define DUMMY_VALUE (~(int8_t)0) #define BSCBUS_INST_TO_MINOR(i) (i) #define BSCBUS_MINOR_TO_INST(m) (m) #define BSCBUS_MAX_CHANNELS (4) #define BSCBUS_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) - BSCBUS_DUMMY_ADDRESS) #define VREG_TO_ADDR(v) (BSCBUS_DUMMY_ADDRESS + (v)) #ifdef DEBUG #define BSCBUS_LOGSTATUS #endif /* DEBUG */ #ifdef BSCBUS_LOGSTATUS /* * BSC command logging routines. * Record the data passing to and from the BSC */ typedef enum { BSC_CMD_BUSY = 1, /* bsc reports busy */ BSC_CMD_CLEARING = 2, /* clearing bsc busy */ BSC_CMD_CLEARED = 3, /* cleared bsc busy */ BSC_CMD_SENDING = 4, /* sending next byte */ BSC_CMD_SENT = 5, /* sending last byte */ BSC_CMD_PENDING = 6, /* got sent byte ack */ BSC_CMD_REPLY = 7, /* got reply byte */ BSC_CMD_COMPLETE = 8, /* command complete */ BSC_CMD_ERROR_SEQ = 9, /* error status */ BSC_CMD_ERROR_STATUS = 10, /* error status */ BSC_CMD_ERROR_OFLOW = 11, /* error status */ BSC_CMD_ERROR_TOUT = 12, /* error status */ BSC_CMD_PROCESS = 13, /* async intr */ BSC_CMD_V1INTR = 14, /* v1 intr */ BSC_CMD_V1INTRUNCL = 15, /* v1 intr unclaim */ BSC_CMD_DOGPAT = 17 /* watchdog pat */ } bsc_cmd_stamp_t; typedef struct { hrtime_t bcl_now; int bcl_seq; bsc_cmd_stamp_t bcl_cat; uint8_t bcl_chno; uint8_t bcl_cmdstate; uint8_t bcl_status; uint8_t bcl_data; } bsc_cmd_log_t; uint32_t bscbus_cmd_log_size = 1024; uint32_t bscbus_cmd_log_flags = 0xffffffff; #endif /* BSCBUS_LOGSTATUS */ /* * The following definitions are taken from the Hardware Manual for * the Hitachi H8S/2148 in conjunction with the hardware specification * for the Stiletto blade. * * Each instance of the host interface has 3 registers on the H8: * IDRn - Input Data Register - write-only for Solaris. * writes to this can be done via two * addresses - control and data. * The H8 can determine which address was * written by examining the C/D bit in * the status register. * ODRn - Output Data Register - read-only for Solaris. * A read has the side effect of acknowledging * interrupts. * STRn - Status Register - read-only for Solaris. * * * * In terms of host access to this the Input and Output data registers are * mapped at the same address. */ #define H8_IDRD 0 #define H8_IDRC 1 #define H8_ODR 0 #define H8_STR 1 #define H8_STR_OBF 0x01 /* data available in ODR */ #define H8_STR_IBF 0x02 /* data for H8 in IDR */ #define H8_STR_IDRC 0x08 /* last write to IDR was to IDRC */ /* 0=data, 1=command */ #define H8_STR_BUSY 0x04 /* H8 busy processing command */ #define H8_STR_TOKENPROTOCOL 0x80 /* token-passing protocol */ /* * Packet format ... */ #define BSCBUS_MASK 0xc0 /* Byte-type bits */ #define BSCBUS_PARAM 0x00 /* Parameter byte: 0b0xxxxxxx */ #define BSCBUS_LAST 0x80 /* Last byte of packet */ #define BSCBUS_CMD 0x80 /* Command byte: 0b10###XWV */ #define BSCBUS_STATUS 0xc0 /* Status byte: 0b11###AEV */ #define BSCBUS_SEQ 0x38 /* Sequence number bits */ #define BSCBUS_SEQ_LSB 0x08 /* Sequence number LSB */ #define BSCBUS_CMD_XADDR 0x04 /* Extended (2-byte) addressing */ #define BSCBUS_CMD_WRITE 0x02 /* Write command */ #define BSCBUS_CMD_WMSB 0x01 /* Set MSB on Write */ #define BSCBUS_CMD_READ 0x01 /* Read command */ #define BSCBUS_CMD_NOP 0x00 /* NOP command */ #define BSCBUS_STATUS_ASYNC 0x04 /* Asynchronous event pending */ #define BSCBUS_STATUS_ERR 0x02 /* Error in command processing */ #define BSCBUS_STATUS_MSB 0x01 /* MSB of Value read */ #define BSCBUS_VREG_LO(x) ((x) & ((1 << 7) - 1)) #define BSCBUS_VREG_HI(x) ((x) >> 7) #define BSCBUS_BUFSIZE 8 #define BSCBUS_CHANNEL_TO_OFFSET(chno) ((chno) * 2) /* Register offset */ /* * Time periods, in nanoseconds * * Note that LOMBUS_ONE_SEC and some other time * periods are defined in */ #define BSCBUS_CMD_POLL (LOMBUS_ONE_SEC) #define BSCBUS_CMD_POLLNOINTS (LOMBUS_ONE_SEC/20) #define BSCBUS_HWRESET_POLL (LOMBUS_ONE_SEC/20) #define BSCBUS_HWRESET_TIMEOUT (LOMBUS_ONE_SEC*2) #define BSCBUS_DOG_PAT_POLL_LIMIT (1000) #define BSCBUS_DOG_PAT_POLL (1) #define BSCBUS_PAT_RETRY_LIMIT 5 /* * Local datatypes */ enum bscbus_cmdstate { BSCBUS_CMDSTATE_IDLE, /* No transaction in progress */ BSCBUS_CMDSTATE_BUSY, /* Setting up command */ BSCBUS_CMDSTATE_CLEARING, /* Clearing firmware busy status */ BSCBUS_CMDSTATE_SENDING, /* Waiting to send data to f/w */ BSCBUS_CMDSTATE_PENDING, /* Waiting for ack from f/w */ BSCBUS_CMDSTATE_WAITING, /* Waiting for status from f/w */ BSCBUS_CMDSTATE_READY, /* Status received/command done */ BSCBUS_CMDSTATE_ERROR /* Command failed with error */ }; struct bscbus_channel_state { /* Changes to these are protected by the instance ch_mutex mutex */ struct bscbus_state *ssp; uint8_t *ch_regs; ddi_acc_handle_t ch_handle; /* per channel access handle */ unsigned int chno; unsigned int map_count; /* Number of mappings to channel */ boolean_t map_dog; /* channel is mapped for watchdog */ /* * Flag to indicate that we've incurred a hardware fault on * accesses to the H8; once this is set, we fake all further * accesses in order not to provoke additional bus errors. */ boolean_t xio_fault; /* * Data protected by the dog_mutex: the watchdog-patting * protocol data (since the dog can be patted from a high-level * cyclic), and the interrupt-enabled flag. */ kmutex_t dog_mutex[1]; unsigned int pat_retry_count; unsigned int pat_fail_count; /* * 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]; int unclaimed_count; volatile enum bscbus_cmdstate cmdstate; clock_t deadline; clock_t poll_hz; boolean_t interrupt_failed; uint8_t cmdbuf[BSCBUS_BUFSIZE]; uint8_t *cmdp; /* Points to last tx'd in cmdbuf */ uint8_t reply[BSCBUS_BUFSIZE]; uint8_t async; uint8_t index; uint8_t result; uint8_t sequence; uint32_t error; }; #define BSCBUS_TX_PENDING(csp) ((csp)->cmdp > (csp)->cmdbuf) /* * This driver's soft-state structure */ struct bscbus_state { /* * Configuration data, set during attach */ dev_info_t *dip; major_t majornum; int instance; ddi_acc_handle_t h8_handle; uint8_t *h8_regs; /* * Parameters derived from .conf properties */ uint32_t debug; /* * Flag to indicate that we are using per channel * mapping of the register sets and interrupts. * reg set 0 is chan 0 * reg set 1 is chan 1 ... * * Interrupts are specified in that order but later * channels may not have interrupts. */ boolean_t per_channel_regs; /* * channel state data, protected by ch_mutex * channel claim/release requests are protected by this mutex. */ kmutex_t ch_mutex[1]; struct bscbus_channel_state channel[BSCBUS_MAX_CHANNELS]; #ifdef BSCBUS_LOGSTATUS /* * Command logging buffer for recording transactions with the * BSC. This is useful for debugging failed transactions and other * such funnies. */ bsc_cmd_log_t *cmd_log; uint32_t cmd_log_idx; uint32_t cmd_log_size; uint32_t cmd_log_flags; #endif /* BSCBUS_LOGSTATUS */ }; /* * The auxiliary structure attached to each child * (the child's parent-private-data points to this). */ struct bscbus_child_info { lombus_regspec_t *rsp; int nregs; }; #ifdef BSCBUS_LOGSTATUS void bscbus_cmd_log(struct bscbus_channel_state *, bsc_cmd_stamp_t, uint8_t, uint8_t); #else /* BSCBUS_LOGSTATUS */ #define bscbus_cmd_log(state, stamp, status, data) #endif /* BSCBUS_LOGSTATUS */ /* * Local data */ static void *bscbus_statep; static major_t bscbus_major = NOMAJOR; static ddi_device_acc_attr_t bscbus_dev_acc_attr[1] = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC }; /* * General utility routines ... */ #ifdef DEBUG static void bscbus_trace(struct bscbus_channel_state *csp, char code, const char *caller, const char *fmt, ...) { char buf[256]; char *p; va_list va; if (csp->ssp->debug & (1 << (code-'@'))) { p = buf; (void) snprintf(p, sizeof (buf) - (p - buf), "%s/%s: ", MYNAME, caller); p += strlen(p); va_start(va, fmt); (void) vsnprintf(p, sizeof (buf) - (p - buf), fmt, va); va_end(va); buf[sizeof (buf) - 1] = '\0'; (void) strlog(csp->ssp->majornum, csp->ssp->instance, code, SL_TRACE, buf); } } #else /* DEBUG */ #define bscbus_trace #endif /* DEBUG */ static struct bscbus_state * bscbus_getstate(dev_info_t *dip, int instance, const char *caller) { struct bscbus_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 (bscbus_major == NOMAJOR && dmaj != NOMAJOR) bscbus_major = dmaj; else if (dmaj != bscbus_major) { cmn_err(CE_WARN, "%s: major number mismatch (%d vs. %d) in %s()," "probably due to child misconfiguration", MYNAME, bscbus_major, dmaj, caller); instance = -1; } } if (instance >= 0) ssp = ddi_get_soft_state(bscbus_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 I/O register read/write */ static void bscbus_put_reg(struct bscbus_channel_state *csp, uint_t reg, uint8_t val) { if (csp->ch_handle != NULL && !csp->xio_fault) { ddi_put8(csp->ch_handle, csp->ch_regs + reg, val); } } static uint8_t bscbus_get_reg(struct bscbus_channel_state *csp, uint_t reg) { uint8_t val; if (csp->ch_handle != NULL && !csp->xio_fault) val = ddi_get8(csp->ch_handle, csp->ch_regs + reg); else val = DUMMY_VALUE; return (val); } static void bscbus_check_fault_status(struct bscbus_channel_state *csp) { csp->xio_fault = ddi_check_acc_handle(csp->ch_handle) != DDI_SUCCESS; } static boolean_t bscbus_faulty(struct bscbus_channel_state *csp) { if (!csp->xio_fault) bscbus_check_fault_status(csp); return (csp->xio_fault); } /* * Write data into h8 registers */ static void bscbus_pat_dog(struct bscbus_channel_state *csp, uint8_t val) { uint8_t status; uint32_t doglimit = BSCBUS_DOG_PAT_POLL_LIMIT; bscbus_trace(csp, 'W', "bscbus_pat_dog:", ""); bscbus_cmd_log(csp, BSC_CMD_DOGPAT, 0, val); status = bscbus_get_reg(csp, H8_STR); while (status & H8_STR_IBF) { if (csp->pat_retry_count > BSCBUS_PAT_RETRY_LIMIT) { /* * Previous attempts to contact BSC have failed. * Do not bother waiting for it to eat previous * data. * Pat anyway just in case the BSC is really alive * and the IBF bit is lying. */ bscbus_put_reg(csp, H8_IDRC, val); bscbus_trace(csp, 'W', "bscbus_pat_dog:", "retry count exceeded"); return; } if (--doglimit == 0) { /* The BSC is not responding - give up */ csp->pat_fail_count++; csp->pat_retry_count++; /* Pat anyway just in case the BSC is really alive */ bscbus_put_reg(csp, H8_IDRC, val); bscbus_trace(csp, 'W', "bscbus_pat_dog:", "poll limit exceeded"); return; } drv_usecwait(BSCBUS_DOG_PAT_POLL); status = bscbus_get_reg(csp, H8_STR); } bscbus_put_reg(csp, H8_IDRC, val); csp->pat_retry_count = 0; } /* * State diagrams for how bscbus_process works. * BSCBUS_CMDSTATE_IDLE No transaction in progress * BSCBUS_CMDSTATE_BUSY Setting up command * BSCBUS_CMDSTATE_CLEARING Clearing firmware busy status * BSCBUS_CMDSTATE_SENDING Waiting to send data to f/w * BSCBUS_CMDSTATE_PENDING Waiting for ack from f/w * BSCBUS_CMDSTATE_WAITING Waiting for status from f/w * BSCBUS_CMDSTATE_READY Status received/command done * BSCBUS_CMDSTATE_ERROR Command failed with error * * +----------+ * | | * | IDLE/BUSY| * | (0/1) | abnormal * +----------+ state * | \ detected * | \------>------+ +----<---+ * bsc | | | | * is | V V | * ready| +----------+ | * | | | ^ * | | CLEARING | | * | | (2) | | * | +----------+ | * | cleared / | \ | more to clear * | / | \-->--+ * | +-------<-------/ V * | | | * V V |timeout * +----------+ timeout | * | |------>---------+--------+ * | SENDING | | * | (3) |------<-------+ | * +----------+ | V * sent| \ send ^ack | * last| \ next |received | * | \ +----------+ | * | \ | | | * | \------>| PENDING |-->-+ * | | (4) | | * | +----------+ |timeout * | +---<----+ | * | | | | * V V | | * +----------+ | | * | | | | * | WAITING | ^ | * | (5) | | | * +----------+ | | * | | |more | | * | V |required| | * done| | +--->----+ | * | +--->--------------+ +---<---+ * | error/timeout | | * V V V * +----------+ +----------+ * | | | | * | READY | | ERROR | * | (7) | | (6) | * +----------+ +----------+ * | | * V V * | | * +------>---+---<------+ * | * | * Back to * Idle */ static void bscbus_process_sending(struct bscbus_channel_state *csp, uint8_t status) { /* * When we get here we actually expect H8_STR_IBF to * be clear but we check just in case of problems. */ ASSERT(BSCBUS_TX_PENDING(csp)); if (!(status & H8_STR_IBF)) { bscbus_put_reg(csp, H8_IDRD, *--csp->cmdp); bscbus_trace(csp, 'P', "bscbus_process_sending", "state %d; val $%x", csp->cmdstate, *csp->cmdp); if (!BSCBUS_TX_PENDING(csp)) { bscbus_cmd_log(csp, BSC_CMD_SENT, status, *csp->cmdp); /* No more pending - move to waiting state */ bscbus_trace(csp, 'P', "bscbus_process_sending", "moving to waiting"); csp->cmdstate = BSCBUS_CMDSTATE_WAITING; /* Extend deadline because time has moved on */ csp->deadline = ddi_get_lbolt() + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000); } else { /* Wait for ack of this byte */ bscbus_cmd_log(csp, BSC_CMD_SENDING, status, *csp->cmdp); csp->cmdstate = BSCBUS_CMDSTATE_PENDING; bscbus_trace(csp, 'P', "bscbus_process_sending", "moving to pending"); } } } static void bscbus_process_clearing(struct bscbus_channel_state *csp, uint8_t status, uint8_t data) { /* * We only enter this state if H8_STR_BUSY was set when * we started the transaction. We just ignore all received * data until we see OBF set AND BUSY cleared. * It is not good enough to see BUSY clear on its own */ if ((status & H8_STR_OBF) && !(status & H8_STR_BUSY)) { bscbus_cmd_log(csp, BSC_CMD_CLEARED, status, data); csp->cmdstate = BSCBUS_CMDSTATE_SENDING; /* Throw away any data received up until now */ bscbus_trace(csp, 'P', "bscbus_process_clearing", "busy cleared"); /* * Send the next byte immediately. * At this stage we should clear the OBF flag because that * data has been used. IBF is still valid so do not clear that. */ status &= ~(H8_STR_OBF); bscbus_process_sending(csp, status); } else { if (status & H8_STR_OBF) { bscbus_cmd_log(csp, BSC_CMD_CLEARING, status, data); } } } static void bscbus_process_pending(struct bscbus_channel_state *csp, uint8_t status) { /* We are waiting for an acknowledgement of a byte */ if (status & H8_STR_OBF) { bscbus_cmd_log(csp, BSC_CMD_PENDING, status, *csp->cmdp); bscbus_trace(csp, 'P', "bscbus_process_pending", "moving to sending"); csp->cmdstate = BSCBUS_CMDSTATE_SENDING; /* * Send the next byte immediately. * At this stage we should clear the OBF flag because that * data has been used. IBF is still valid so do not clear that. */ status &= ~(H8_STR_OBF); bscbus_process_sending(csp, status); } } static boolean_t bscbus_process_waiting(struct bscbus_channel_state *csp, uint8_t status, uint8_t data) { uint8_t rcvd = 0; boolean_t ready = B_FALSE; uint8_t tmp; if (status & H8_STR_OBF) { csp->reply[rcvd = csp->index] = data; if (++rcvd < BSCBUS_BUFSIZE) csp->index = rcvd; bscbus_trace(csp, 'D', "bscbus_process_waiting", "rcvd %d: $%02x $%02x $%02x $%02x $%02x $%02x $%02x $%02x", rcvd, csp->reply[0], csp->reply[1], csp->reply[2], csp->reply[3], csp->reply[4], csp->reply[5], csp->reply[6], csp->reply[7]); } if (rcvd == 0) { /* * No bytes received this time through (though there * might be a partial packet sitting in the buffer). */ /* EMPTY */ ; } else if (rcvd >= BSCBUS_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!) */ bscbus_cmd_log(csp, BSC_CMD_ERROR_OFLOW, status, data); csp->index = 0; csp->cmdstate = BSCBUS_CMDSTATE_ERROR; csp->error = LOMBUS_ERR_OFLOW; ready = B_TRUE; } else if ((data & BSCBUS_LAST) == 0) { /* * Packet not yet complete; leave the partial packet in * the buffer for later ... */ bscbus_cmd_log(csp, BSC_CMD_REPLY, status, data); } else if ((data & BSCBUS_MASK) != BSCBUS_STATUS) { /* Invalid "status" byte - maybe an echo of the command? */ bscbus_cmd_log(csp, BSC_CMD_ERROR_STATUS, status, data); csp->cmdstate = BSCBUS_CMDSTATE_ERROR; csp->error = LOMBUS_ERR_BADSTATUS; ready = B_TRUE; } else if ((data & BSCBUS_SEQ) != csp->sequence) { /* Wrong sequence number! Flag this as an error */ bscbus_cmd_log(csp, BSC_CMD_ERROR_SEQ, status, data); csp->cmdstate = BSCBUS_CMDSTATE_ERROR; csp->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. */ bscbus_cmd_log(csp, BSC_CMD_COMPLETE, status, data); csp->async = (data & BSCBUS_STATUS_ASYNC) ? 1 : 0; tmp = ((data & BSCBUS_STATUS_MSB) ? 0x80 : 0) | csp->reply[0]; if (data & BSCBUS_STATUS_ERR) { csp->cmdstate = BSCBUS_CMDSTATE_ERROR; csp->error = tmp; } else { csp->cmdstate = BSCBUS_CMDSTATE_READY; csp->result = tmp; } ready = B_TRUE; } return (ready); } /* * Packet receive handler * * This routine should be called from the low-level softint, * or bscbus_cmd() (for polled operation), with the * low-level mutex already held. */ static void bscbus_process(struct bscbus_channel_state *csp, uint8_t status, uint8_t data) { boolean_t ready = B_FALSE; ASSERT(mutex_owned(csp->lo_mutex)); if ((status & H8_STR_OBF) || (status & H8_STR_IBF)) { bscbus_trace(csp, 'D', "bscbus_process", "state %d; error $%x", csp->cmdstate, csp->error); } switch (csp->cmdstate) { case BSCBUS_CMDSTATE_CLEARING: bscbus_process_clearing(csp, status, data); break; case BSCBUS_CMDSTATE_SENDING: bscbus_process_sending(csp, status); break; case BSCBUS_CMDSTATE_PENDING: bscbus_process_pending(csp, status); break; case BSCBUS_CMDSTATE_WAITING: ready = bscbus_process_waiting(csp, status, data); break; default: /* Nothing to do */ break; } /* * Check for timeouts - but only if the command has not yet * completed (ready is true when command completes in this * call to bscbus_process OR cmdstate is READY or ERROR if * this is a spurious call to bscbus_process i.e. a spurious * interrupt) */ if (!ready && ((ddi_get_lbolt() - csp->deadline) > 0) && csp->cmdstate != BSCBUS_CMDSTATE_READY && csp->cmdstate != BSCBUS_CMDSTATE_ERROR) { bscbus_trace(csp, 'P', "bscbus_process", "timeout previous state %d; error $%x", csp->cmdstate, csp->error); bscbus_cmd_log(csp, BSC_CMD_ERROR_TOUT, status, data); if (csp->cmdstate == BSCBUS_CMDSTATE_CLEARING) { /* Move onto sending because busy might be stuck */ csp->cmdstate = BSCBUS_CMDSTATE_SENDING; /* Extend timeout relative to original start time */ csp->deadline += drv_usectohz(LOMBUS_CMD_TIMEOUT/1000); } else if (csp->cmdstate != BSCBUS_CMDSTATE_IDLE) { csp->cmdstate = BSCBUS_CMDSTATE_ERROR; csp->error = LOMBUS_ERR_TIMEOUT; } ready = B_TRUE; } if ((status & H8_STR_OBF) || (status & H8_STR_IBF) || ready) { bscbus_trace(csp, 'D', "bscbus_process", "last $%02x; state %d; error $%x; ready %d", data, csp->cmdstate, csp->error, ready); } if (ready) cv_broadcast(csp->lo_cv); } static uint_t bscbus_hwintr(caddr_t arg) { struct bscbus_channel_state *csp = (void *)arg; uint8_t status; uint8_t data = 0xb0 /* Dummy value */; mutex_enter(csp->lo_mutex); /* * Read the registers to ensure that the interrupt is cleared. * Status must be read first because reading data changes the * status. * We always read the data because that clears the interrupt down. * This is horrible hardware semantics but we have to do it! */ status = bscbus_get_reg(csp, H8_STR); data = bscbus_get_reg(csp, H8_ODR); if (!(status & H8_STR_OBF)) { bscbus_cmd_log(csp, BSC_CMD_V1INTRUNCL, status, data); csp->unclaimed_count++; } else { bscbus_cmd_log(csp, BSC_CMD_V1INTR, status, data); } if (status & H8_STR_TOKENPROTOCOL) { bscbus_process(csp, status, data); if (csp->interrupt_failed) { bscbus_trace(csp, 'I', "bscbus_hwintr:", "interrupt fault cleared channel %d", csp->chno); csp->interrupt_failed = B_FALSE; csp->poll_hz = drv_usectohz(BSCBUS_CMD_POLL / 1000); } } mutex_exit(csp->lo_mutex); return (DDI_INTR_CLAIMED); } void bscbus_poll(struct bscbus_channel_state *csp) { /* * This routine is only called if we timeout in userland * waiting for an interrupt. This generally means that we have * lost interrupt capabilities or that something has gone * wrong. In this case we are allowed to access the hardware * and read the data register if necessary. * If interrupts return then recovery actions should mend us! */ uint8_t status; uint8_t data = 0xfa; /* Dummy value */ ASSERT(mutex_owned(csp->lo_mutex)); /* Should look for data to receive */ status = bscbus_get_reg(csp, H8_STR); if (status & H8_STR_OBF) { /* There is data available */ data = bscbus_get_reg(csp, H8_ODR); bscbus_cmd_log(csp, BSC_CMD_PROCESS, status, data); } bscbus_process(csp, status, data); } /* * Serial protocol * * This routine builds a command and sets it in progress. */ static uint8_t bscbus_cmd(HANDLE_TYPE *hdlp, ptrdiff_t vreg, uint_t val, uint_t cmd) { struct bscbus_channel_state *csp; clock_t start; uint8_t status; /* * 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! */ csp = HANDLE_PRIVATE(hdlp); mutex_enter(csp->lo_mutex); while (csp->cmdstate != BSCBUS_CMDSTATE_IDLE && !ddi_in_panic()) cv_wait(csp->lo_cv, csp->lo_mutex); csp->cmdstate = BSCBUS_CMDSTATE_BUSY; csp->sequence = (csp->sequence + BSCBUS_SEQ_LSB) & BSCBUS_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) */ csp->cmdp = &csp->cmdbuf[0]; *csp->cmdp++ = BSCBUS_CMD | csp->sequence | cmd; switch (cmd) { case BSCBUS_CMD_WRITE: *csp->cmdp++ = val & 0x7f; if (val >= 0x80) csp->cmdbuf[0] |= BSCBUS_CMD_WMSB; /*FALLTHRU*/ case BSCBUS_CMD_READ: if (BSCBUS_VREG_HI(vreg) != 0) { *csp->cmdp++ = BSCBUS_VREG_HI(vreg); csp->cmdbuf[0] |= BSCBUS_CMD_XADDR; } *csp->cmdp++ = BSCBUS_VREG_LO(vreg); /*FALLTHRU*/ case BSCBUS_CMD_NOP: break; } /* * Check and update the H8 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). */ bscbus_check_fault_status(csp); /* * Prepare for the command (to be processed by the interrupt * handler and/or polling loop below), and wait for a response * or timeout. */ start = ddi_get_lbolt(); csp->deadline = start + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000); csp->error = 0; csp->index = 0; csp->result = DUMMY_VALUE; status = bscbus_get_reg(csp, H8_STR); if (status & H8_STR_BUSY) { bscbus_cmd_log(csp, BSC_CMD_BUSY, status, 0xfd); /* * Must ensure that the busy state has cleared before * sending the command */ csp->cmdstate = BSCBUS_CMDSTATE_CLEARING; bscbus_trace(csp, 'P', "bscbus_cmd", "h8 reporting status (%x) busy - clearing", status); } else { /* It is clear to send the command immediately */ csp->cmdstate = BSCBUS_CMDSTATE_SENDING; bscbus_trace(csp, 'P', "bscbus_cmd", "sending first byte of command, status %x", status); bscbus_poll(csp); } csp->poll_hz = drv_usectohz( (csp->interrupt_failed ? BSCBUS_CMD_POLLNOINTS : BSCBUS_CMD_POLL) / 1000); while ((csp->cmdstate != BSCBUS_CMDSTATE_READY) && (csp->cmdstate != BSCBUS_CMDSTATE_ERROR)) { ASSERT(csp->cmdstate != BSCBUS_CMDSTATE_IDLE); if ((cv_reltimedwait(csp->lo_cv, csp->lo_mutex, csp->poll_hz, TR_CLOCK_TICK) == -1) && csp->cmdstate != BSCBUS_CMDSTATE_READY && csp->cmdstate != BSCBUS_CMDSTATE_ERROR) { if (!csp->interrupt_failed) { bscbus_trace(csp, 'I', "bscbus_cmd:", "interrupt_failed channel %d", csp->chno); csp->interrupt_failed = B_TRUE; csp->poll_hz = drv_usectohz( BSCBUS_CMD_POLLNOINTS / 1000); } bscbus_poll(csp); } } /* * The return value may not be meaningful but retrieve it anyway */ val = csp->result; if (bscbus_faulty(csp)) { val = DUMMY_VALUE; HANDLE_FAULT(hdlp) = LOMBUS_ERR_SIOHW; } else if (csp->cmdstate != BSCBUS_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 (csp->error != 0) HANDLE_FAULT(hdlp) = csp->error; else HANDLE_FAULT(hdlp) = LOMBUS_ERR_BADERRCODE; } /* * All done now! */ csp->index = 0; csp->cmdstate = BSCBUS_CMDSTATE_IDLE; cv_broadcast(csp->lo_cv); mutex_exit(csp->lo_mutex); return (val); } /* * Space 0 - LOM virtual register access * Only 8-bit accesses are supported. */ static uint8_t bscbus_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 (bscbus_cmd(hdlp, ADDR_TO_VREG(addr), 0, BSCBUS_CMD_READ)); } static void bscbus_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) bscbus_cmd(hdlp, ADDR_TO_VREG(addr), val, BSCBUS_CMD_WRITE); } static void bscbus_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++ = bscbus_vreg_get8(hdlp, dev_addr); } static void bscbus_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) bscbus_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. * * 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 bscbus_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 bscbus_pat_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val) { struct bscbus_channel_state *csp; 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; } csp = HANDLE_PRIVATE(hdlp); mutex_enter(csp->dog_mutex); bscbus_pat_dog(csp, val); mutex_exit(csp->dog_mutex); } static void bscbus_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++ = bscbus_pat_get8(hdlp, dev_addr); } static void bscbus_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) bscbus_pat_put8(hdlp, dev_addr, *host_addr++); } /* * Space 2 - LOM async event flag register access * Only 16-bit accesses are supported. */ static uint16_t bscbus_event_get16(HANDLE_TYPE *hdlp, uint16_t *addr) { struct bscbus_channel_state *csp; 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. */ csp = HANDLE_PRIVATE(hdlp); return (csp->async); } static void bscbus_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 bscbus_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++ = bscbus_event_get16(hdlp, dev_addr); } static void bscbus_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) bscbus_event_put16(hdlp, dev_addr, *host_addr++); } /* * All spaces - access handle fault information * Only 32-bit accesses are supported. */ static uint32_t bscbus_meta_get32(HANDLE_TYPE *hdlp, uint32_t *addr) { struct bscbus_channel_state *csp; 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; (void) bscbus_cmd(hdlp, 0, 0, BSCBUS_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 BSC at the end of the last * completed command. */ csp = HANDLE_PRIVATE(hdlp); return (csp->async); default: /* * Invalid access - flag a fault and return a dummy value */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return (DUMMY_VALUE); } } static void bscbus_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; (void) bscbus_cmd(hdlp, 0, 0, BSCBUS_CMD_NOP); return; default: /* * Invalid access - flag a fault */ HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE; return; } } static void bscbus_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++ = bscbus_meta_get32(hdlp, dev_addr); } static void bscbus_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) bscbus_meta_put32(hdlp, dev_addr, *host_addr++); } /* * Finally, some dummy functions for all unsupported access * space/size/mode combinations ... */ static uint8_t bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_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 bscbus_acc_fault_check(HANDLE_TYPE *hdlp) { return (HANDLE_FAULT(hdlp) != 0); } /* * Hardware setup - ensure that there are no pending transactions and * hence no pending interrupts. We do this be ensuring that the BSC is * not reporting a busy condition and that it does not have any data * pending in its output buffer. * This is important because if we have pending interrupts at attach * time Solaris will hang due to bugs in ddi_get_iblock_cookie. */ static void bscbus_hw_reset(struct bscbus_channel_state *csp) { int64_t timeout; uint8_t status; if (csp->map_count == 0) { /* No-one using this instance - no need to reset hardware */ return; } bscbus_trace(csp, 'R', "bscbus_hw_reset", "resetting channel %d", csp->chno); status = bscbus_get_reg(csp, H8_STR); if (status & H8_STR_BUSY) { /* * Give the h8 time to complete a reply. * In practice we should never worry about this * because whenever we get here it will have been * long enough for the h8 to complete a reply */ bscbus_cmd_log(csp, BSC_CMD_BUSY, status, 0); bscbus_trace(csp, 'R', "bscbus_hw_reset", "h8 reporting status (%x) busy - waiting", status); if (ddi_in_panic()) { drv_usecwait(BSCBUS_HWRESET_POLL/1000); } else { delay(drv_usectohz(BSCBUS_HWRESET_POLL/1000)); } } /* Reply should be completed by now. Try to clear busy status */ status = bscbus_get_reg(csp, H8_STR); if (status & (H8_STR_BUSY | H8_STR_OBF)) { bscbus_trace(csp, 'R', "bscbus_hw_reset", "clearing busy status for channel %d", csp->chno); for (timeout = BSCBUS_HWRESET_TIMEOUT; (timeout > 0); timeout -= BSCBUS_HWRESET_POLL) { if (status & H8_STR_OBF) { (void) bscbus_get_reg(csp, H8_ODR); if (!(status & H8_STR_BUSY)) { /* We are done */ break; } } if (ddi_in_panic()) { drv_usecwait(BSCBUS_HWRESET_POLL/1000); } else { delay(drv_usectohz(BSCBUS_HWRESET_POLL/1000)); } status = bscbus_get_reg(csp, H8_STR); } if (timeout <= 0) { cmn_err(CE_WARN, "bscbus_hw_reset: timed out " "clearing busy status"); } } /* * We read ODR just in case there is a pending interrupt with * no data. This is potentially dangerous because we could get * out of sync due to race conditions BUT at this point the * channel should be idle so it is safe. */ (void) bscbus_get_reg(csp, H8_ODR); } /* * Higher-level setup & teardown */ static void bscbus_offline(struct bscbus_state *ssp) { if (ssp->h8_handle != NULL) ddi_regs_map_free(&ssp->h8_handle); ssp->h8_handle = NULL; ssp->h8_regs = NULL; } static int bscbus_online(struct bscbus_state *ssp) { ddi_acc_handle_t h; caddr_t p; int nregs; int err; ssp->h8_handle = NULL; ssp->h8_regs = (void *)NULL; ssp->per_channel_regs = B_FALSE; if (ddi_dev_nregs(ssp->dip, &nregs) != DDI_SUCCESS) nregs = 0; switch (nregs) { case 1: /* * regset 0 represents the H8 interface registers */ err = ddi_regs_map_setup(ssp->dip, 0, &p, 0, 0, bscbus_dev_acc_attr, &h); if (err != DDI_SUCCESS) return (EIO); ssp->h8_handle = h; ssp->h8_regs = (void *)p; break; case 0: /* * If no registers are defined, succeed vacuously; * commands will be accepted, but we fake the accesses. */ break; default: /* * Remember that we are using the new register scheme. * reg set 0 is chan 0 * reg set 1 is chan 1 ... * Interrupts are specified in that order but later * channels may not have interrupts. * We map the regs later on a per channel basis. */ ssp->per_channel_regs = B_TRUE; break; } return (0); } static int bscbus_claim_channel(struct bscbus_channel_state *csp, boolean_t map_dog) { int err; mutex_enter(csp->ssp->ch_mutex); csp->map_count++; bscbus_trace(csp, 'C', "bscbus_claim_channel", "claim channel for channel %d, count %d", csp->chno, csp->map_count); if (csp->map_count == 1) { /* No-one is using this channel - initialise it */ bscbus_trace(csp, 'C', "bscbus_claim_channel", "initialise channel %d, count %d", csp->chno, csp->map_count); mutex_init(csp->dog_mutex, NULL, MUTEX_DRIVER, (void *)(uintptr_t)__ipltospl(SPL7 - 1)); csp->map_dog = map_dog; csp->interrupt_failed = B_FALSE; csp->cmdstate = BSCBUS_CMDSTATE_IDLE; csp->pat_retry_count = 0; csp->pat_fail_count = 0; /* Map appropriate register set for this channel */ if (csp->ssp->per_channel_regs == B_TRUE) { ddi_acc_handle_t h; caddr_t p; err = ddi_regs_map_setup(csp->ssp->dip, csp->chno, &p, 0, 0, bscbus_dev_acc_attr, &h); if (err != DDI_SUCCESS) { goto failed1; } csp->ch_handle = h; csp->ch_regs = (void *)p; bscbus_trace(csp, 'C', "bscbus_claim_channel", "mapped chno=%d ch_handle=%d ch_regs=%p", csp->chno, h, p); } else { /* * if using the old reg property scheme use the * common mapping. */ csp->ch_handle = csp->ssp->h8_handle; csp->ch_regs = csp->ssp->h8_regs + BSCBUS_CHANNEL_TO_OFFSET(csp->chno); } /* Ensure no interrupts pending prior to getting iblk cookie */ bscbus_hw_reset(csp); if (csp->map_dog == 1) { /* * we don't want lo_mutex to be initialised * with an iblock cookie if we are the wdog, * because we don't use interrupts. */ mutex_init(csp->lo_mutex, NULL, MUTEX_DRIVER, NULL); cv_init(csp->lo_cv, NULL, CV_DRIVER, NULL); csp->unclaimed_count = 0; } else { int ninterrupts; /* * check that there is an interrupt for this * this channel. If we fail to setup interrupts we * must unmap the registers and fail. */ err = ddi_dev_nintrs(csp->ssp->dip, &ninterrupts); if (err != DDI_SUCCESS) { ninterrupts = 0; } if (ninterrupts <= csp->chno) { cmn_err(CE_WARN, "no interrupt available for " "bscbus channel %d", csp->chno); goto failed2; } if (ddi_intr_hilevel(csp->ssp->dip, csp->chno) != 0) { cmn_err(CE_WARN, "bscbus interrupts are high " "level - channel not usable."); goto failed2; } else { err = ddi_get_iblock_cookie(csp->ssp->dip, csp->chno, &csp->lo_iblk); if (err != DDI_SUCCESS) { goto failed2; } mutex_init(csp->lo_mutex, NULL, MUTEX_DRIVER, csp->lo_iblk); cv_init(csp->lo_cv, NULL, CV_DRIVER, NULL); csp->unclaimed_count = 0; err = ddi_add_intr(csp->ssp->dip, csp->chno, &csp->lo_iblk, NULL, bscbus_hwintr, (caddr_t)csp); if (err != DDI_SUCCESS) { cv_destroy(csp->lo_cv); mutex_destroy(csp->lo_mutex); goto failed2; } } } /* * The channel is now live and may * receive interrupts */ } else if (csp->map_dog != map_dog) { bscbus_trace(csp, 'C', "bscbus_claim_channel", "request conflicts with previous mapping. old %x, new %x.", csp->map_dog, map_dog); goto failed1; } mutex_exit(csp->ssp->ch_mutex); return (1); failed2: /* unmap regs for failed channel */ if (csp->ssp->per_channel_regs == B_TRUE) { ddi_regs_map_free(&csp->ch_handle); } csp->ch_handle = NULL; csp->ch_regs = (void *)NULL; failed1: csp->map_count--; mutex_exit(csp->ssp->ch_mutex); return (0); } static void bscbus_release_channel(struct bscbus_channel_state *csp) { mutex_enter(csp->ssp->ch_mutex); if (csp->map_count == 1) { /* No-one is now using this channel - shutdown channel */ bscbus_trace(csp, 'C', "bscbus_release_channel", "shutdown channel %d, count %d", csp->chno, csp->map_count); if (csp->map_dog == 0) { ASSERT(!ddi_intr_hilevel(csp->ssp->dip, csp->chno)); ddi_remove_intr(csp->ssp->dip, csp->chno, csp->lo_iblk); } cv_destroy(csp->lo_cv); mutex_destroy(csp->lo_mutex); mutex_destroy(csp->dog_mutex); bscbus_hw_reset(csp); /* unmap registers if using the new register scheme */ if (csp->ssp->per_channel_regs == B_TRUE) { ddi_regs_map_free(&csp->ch_handle); } csp->ch_handle = NULL; csp->ch_regs = (void *)NULL; } csp->map_count--; bscbus_trace(csp, 'C', "bscbus_release_channel", "release channel %d, count %d", csp->chno, csp->map_count); mutex_exit(csp->ssp->ch_mutex); } /* * Nexus routines */ #if defined(NDI_ACC_HDL_V2) static const ndi_acc_fns_t bscbus_vreg_acc_fns = { NDI_ACC_FNS_CURRENT, NDI_ACC_FNS_V1, bscbus_vreg_get8, bscbus_vreg_put8, bscbus_vreg_rep_get8, bscbus_vreg_rep_put8, bscbus_no_get16, bscbus_no_put16, bscbus_no_rep_get16, bscbus_no_rep_put16, bscbus_meta_get32, bscbus_meta_put32, bscbus_meta_rep_get32, bscbus_meta_rep_put32, bscbus_no_get64, bscbus_no_put64, bscbus_no_rep_get64, bscbus_no_rep_put64, bscbus_acc_fault_check }; static const ndi_acc_fns_t bscbus_pat_acc_fns = { NDI_ACC_FNS_CURRENT, NDI_ACC_FNS_V1, bscbus_pat_get8, bscbus_pat_put8, bscbus_pat_rep_get8, bscbus_pat_rep_put8, bscbus_no_get16, bscbus_no_put16, bscbus_no_rep_get16, bscbus_no_rep_put16, bscbus_meta_get32, bscbus_meta_put32, bscbus_meta_rep_get32, bscbus_meta_rep_put32, bscbus_no_get64, bscbus_no_put64, bscbus_no_rep_get64, bscbus_no_rep_put64, bscbus_acc_fault_check }; static const ndi_acc_fns_t bscbus_event_acc_fns = { NDI_ACC_FNS_CURRENT, NDI_ACC_FNS_V1, bscbus_no_get8, bscbus_no_put8, bscbus_no_rep_get8, bscbus_no_rep_put8, bscbus_event_get16, bscbus_event_put16, bscbus_event_rep_get16, bscbus_event_rep_put16, bscbus_meta_get32, bscbus_meta_put32, bscbus_meta_rep_get32, bscbus_meta_rep_put32, bscbus_no_get64, bscbus_no_put64, bscbus_no_rep_get64, bscbus_no_rep_put64, bscbus_acc_fault_check }; static int bscbus_map_handle(struct bscbus_channel_state *csp, 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: if (bscbus_claim_channel(csp, (space == LOMBUS_PAT_SPACE)) == 0) { return (DDI_ME_GENERIC); } switch (space) { default: return (DDI_ME_REGSPEC_RANGE); case LOMBUS_VREG_SPACE: ndi_set_acc_fns(hdlp, &bscbus_vreg_acc_fns); break; case LOMBUS_PAT_SPACE: ndi_set_acc_fns(hdlp, &bscbus_pat_acc_fns); break; case LOMBUS_EVENT_SPACE: ndi_set_acc_fns(hdlp, &bscbus_event_acc_fns); break; } hdlp->ah_addr = *addrp = vaddr; hdlp->ah_len = len; hdlp->ah_bus_private = csp; return (DDI_SUCCESS); case DDI_MO_UNMAP: *addrp = NULL; hdlp->ah_bus_private = NULL; bscbus_release_channel(csp); return (DDI_SUCCESS); } } #else static int bscbus_map_handle(struct bscbus_channel_state *csp, 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: if (bscbus_claim_channel(csp, (space == LOMBUS_PAT_SPACE)) == 0) { return (DDI_ME_GENERIC); } switch (space) { default: return (DDI_ME_REGSPEC_RANGE); case LOMBUS_VREG_SPACE: aip->ahi_get8 = bscbus_vreg_get8; aip->ahi_put8 = bscbus_vreg_put8; aip->ahi_rep_get8 = bscbus_vreg_rep_get8; aip->ahi_rep_put8 = bscbus_vreg_rep_put8; aip->ahi_get16 = bscbus_no_get16; aip->ahi_put16 = bscbus_no_put16; aip->ahi_rep_get16 = bscbus_no_rep_get16; aip->ahi_rep_put16 = bscbus_no_rep_put16; aip->ahi_get32 = bscbus_meta_get32; aip->ahi_put32 = bscbus_meta_put32; aip->ahi_rep_get32 = bscbus_meta_rep_get32; aip->ahi_rep_put32 = bscbus_meta_rep_put32; aip->ahi_get64 = bscbus_no_get64; aip->ahi_put64 = bscbus_no_put64; aip->ahi_rep_get64 = bscbus_no_rep_get64; aip->ahi_rep_put64 = bscbus_no_rep_put64; aip->ahi_fault_check = bscbus_acc_fault_check; break; case LOMBUS_PAT_SPACE: aip->ahi_get8 = bscbus_pat_get8; aip->ahi_put8 = bscbus_pat_put8; aip->ahi_rep_get8 = bscbus_pat_rep_get8; aip->ahi_rep_put8 = bscbus_pat_rep_put8; aip->ahi_get16 = bscbus_no_get16; aip->ahi_put16 = bscbus_no_put16; aip->ahi_rep_get16 = bscbus_no_rep_get16; aip->ahi_rep_put16 = bscbus_no_rep_put16; aip->ahi_get32 = bscbus_meta_get32; aip->ahi_put32 = bscbus_meta_put32; aip->ahi_rep_get32 = bscbus_meta_rep_get32; aip->ahi_rep_put32 = bscbus_meta_rep_put32; aip->ahi_get64 = bscbus_no_get64; aip->ahi_put64 = bscbus_no_put64; aip->ahi_rep_get64 = bscbus_no_rep_get64; aip->ahi_rep_put64 = bscbus_no_rep_put64; aip->ahi_fault_check = bscbus_acc_fault_check; break; case LOMBUS_EVENT_SPACE: aip->ahi_get8 = bscbus_no_get8; aip->ahi_put8 = bscbus_no_put8; aip->ahi_rep_get8 = bscbus_no_rep_get8; aip->ahi_rep_put8 = bscbus_no_rep_put8; aip->ahi_get16 = bscbus_event_get16; aip->ahi_put16 = bscbus_event_put16; aip->ahi_rep_get16 = bscbus_event_rep_get16; aip->ahi_rep_put16 = bscbus_event_rep_put16; aip->ahi_get32 = bscbus_meta_get32; aip->ahi_put32 = bscbus_meta_put32; aip->ahi_rep_get32 = bscbus_meta_rep_get32; aip->ahi_rep_put32 = bscbus_meta_rep_put32; aip->ahi_get64 = bscbus_no_get64; aip->ahi_put64 = bscbus_no_put64; aip->ahi_rep_get64 = bscbus_no_rep_get64; aip->ahi_rep_put64 = bscbus_no_rep_put64; aip->ahi_fault_check = bscbus_acc_fault_check; break; } hdlp->ah_addr = *addrp = vaddr; hdlp->ah_len = len; hdlp->ah_bus_private = csp; return (DDI_SUCCESS); case DDI_MO_UNMAP: *addrp = NULL; hdlp->ah_bus_private = NULL; bscbus_release_channel(csp); return (DDI_SUCCESS); } } #endif /* NDI_ACC_HDL_V2 */ static int bscbus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t off, off_t len, caddr_t *addrp) { struct bscbus_child_info *lcip; struct bscbus_state *ssp; lombus_regspec_t *rsp; if ((ssp = bscbus_getstate(dip, -1, "bscbus_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 (bscbus_map_handle( &ssp->channel[LOMBUS_SPACE_TO_CHANNEL(rsp->lombus_space)], mp->map_op, LOMBUS_SPACE_TO_REGSET(rsp->lombus_space), VREG_TO_ADDR(rsp->lombus_base+off), len, mp->map_handlep, addrp)); } static int bscbus_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result) { struct bscbus_child_info *lcip; 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 (bscbus_getstate(dip, -1, "bscbus_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); 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 (LOMBUS_SPACE_TO_REGSET(rsp[i].lombus_space)) { default: limit = 0; err = 1; cmn_err(CE_WARN, "child(%p): unknown reg space %d", (void *)cdip, rsp[i].lombus_space); 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); 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, "?BSC 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)); } /* * This nexus does not support passing interrupts to leaf drivers, so * all the intrspec-related operations just fail as cleanly as possible. */ /*ARGSUSED*/ static int bscbus_intr_op(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op, ddi_intr_handle_impl_t *hdlp, void *result) { #if defined(__sparc) return (i_ddi_intr_ops(dip, rdip, op, hdlp, result)); #else _NOTE(ARGUNUSED(dip, rdip, op, hdlp, result)) return (DDI_FAILURE); #endif } /* * Clean up on detach or failure of attach */ static int bscbus_unattach(struct bscbus_state *ssp, int instance) { int chno; if (ssp != NULL) { for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) { ASSERT(ssp->channel[chno].map_count == 0); } bscbus_offline(ssp); ddi_set_driver_private(ssp->dip, NULL); mutex_destroy(ssp->ch_mutex); } #ifdef BSCBUS_LOGSTATUS if (ssp->cmd_log_size != 0) { kmem_free(ssp->cmd_log, ssp->cmd_log_size * sizeof (bsc_cmd_log_t)); } #endif /* BSCBUS_LOGSTATUS */ ddi_soft_state_free(bscbus_statep, instance); return (DDI_FAILURE); } /* * Autoconfiguration routines */ static int bscbus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct bscbus_state *ssp = NULL; int chno; 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(bscbus_statep, instance) != DDI_SUCCESS) return (DDI_FAILURE); if ((ssp = bscbus_getstate(dip, instance, "bscbus_attach")) == NULL) return (bscbus_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->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "debug", 0); mutex_init(ssp->ch_mutex, NULL, MUTEX_DRIVER, NULL); #ifdef BSCBUS_LOGSTATUS ssp->cmd_log_size = bscbus_cmd_log_size; if (ssp->cmd_log_size != 0) { ssp->cmd_log_idx = 0; ssp->cmd_log = kmem_zalloc(ssp->cmd_log_size * sizeof (bsc_cmd_log_t), KM_SLEEP); } #endif /* BSCBUS_LOGSTATUS */ /* * Online the hardware ... */ err = bscbus_online(ssp); if (err != 0) return (bscbus_unattach(ssp, instance)); for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) { struct bscbus_channel_state *csp = &ssp->channel[chno]; /* * Initialise state * The hardware/interrupts are setup at map time to * avoid claiming hardware that OBP is using */ csp->ssp = ssp; csp->chno = chno; csp->map_count = 0; csp->map_dog = B_FALSE; } /* * All done, report success */ ddi_report_dev(dip); return (DDI_SUCCESS); } static int bscbus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct bscbus_state *ssp; int instance; switch (cmd) { default: return (DDI_FAILURE); case DDI_DETACH: break; } instance = ddi_get_instance(dip); if ((ssp = bscbus_getstate(dip, instance, "bscbus_detach")) == NULL) return (DDI_FAILURE); /* this "can't happen" */ (void) bscbus_unattach(ssp, instance); return (DDI_SUCCESS); } static int bscbus_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) { struct bscbus_state *ssp; int chno; _NOTE(ARGUNUSED(cmd)) if ((ssp = bscbus_getstate(dip, -1, "bscbus_reset")) == NULL) return (DDI_FAILURE); for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) { bscbus_hw_reset(&ssp->channel[chno]); } return (DDI_SUCCESS); } /* * System interface structures */ static struct cb_ops bscbus_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 bscbus_bus_ops = { BUSO_REV, /* revision */ bscbus_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 */ bscbus_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 */ bscbus_intr_op /* bus_intr_op */ }; static struct dev_ops bscbus_dev_ops = { DEVO_REV, 0, /* refcount */ ddi_no_info, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ bscbus_attach, /* attach */ bscbus_detach, /* detach */ bscbus_reset, /* reset */ &bscbus_cb_ops, /* driver operations */ &bscbus_bus_ops, /* bus operations */ NULL, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; static struct modldrv modldrv = { &mod_driverops, "bscbus driver", &bscbus_dev_ops }; static struct modlinkage modlinkage = { MODREV_1, { &modldrv, NULL } }; /* * Dynamic loader interface code */ int _init(void) { int err; err = ddi_soft_state_init(&bscbus_statep, sizeof (struct bscbus_state), 0); if (err == DDI_SUCCESS) if ((err = mod_install(&modlinkage)) != DDI_SUCCESS) { ddi_soft_state_fini(&bscbus_statep); } return (err); } int _info(struct modinfo *mip) { return (mod_info(&modlinkage, mip)); } int _fini(void) { int err; if ((err = mod_remove(&modlinkage)) == DDI_SUCCESS) { ddi_soft_state_fini(&bscbus_statep); bscbus_major = NOMAJOR; } return (err); } #ifdef BSCBUS_LOGSTATUS void bscbus_cmd_log(struct bscbus_channel_state *csp, bsc_cmd_stamp_t cat, uint8_t status, uint8_t data) { int idx; bsc_cmd_log_t *logp; struct bscbus_state *ssp; if ((csp) == NULL) return; if ((ssp = (csp)->ssp) == NULL) return; if (ssp->cmd_log_size == 0) return; if ((bscbus_cmd_log_flags & (1 << cat)) == 0) return; idx = atomic_inc_32_nv(&ssp->cmd_log_idx); logp = &ssp->cmd_log[idx % ssp->cmd_log_size]; logp->bcl_seq = idx; logp->bcl_cat = cat; logp->bcl_now = gethrtime(); logp->bcl_chno = csp->chno; logp->bcl_cmdstate = csp->cmdstate; logp->bcl_status = status; logp->bcl_data = data; } #endif /* BSCBUS_LOGSTATUS */