diff options
Diffstat (limited to 'usr/src')
30 files changed, 14184 insertions, 14 deletions
diff --git a/usr/src/uts/common/io/bscbus.c b/usr/src/uts/common/io/bscbus.c new file mode 100644 index 0000000000..2a69eae390 --- /dev/null +++ b/usr/src/uts/common/io/bscbus.c @@ -0,0 +1,2641 @@ +/* + * 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 2005 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. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/note.h> +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/debug.h> +#include <sys/errno.h> +#include <sys/file.h> + +#if defined(__sparc) +#include <sys/intr.h> +#include <sys/membar.h> +#endif + +#include <sys/kmem.h> +#include <sys/modctl.h> +#include <sys/note.h> +#include <sys/open.h> +#include <sys/poll.h> +#include <sys/spl.h> +#include <sys/stat.h> +#include <sys/strlog.h> +#include <sys/atomic.h> + +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> + +#include <sys/bscbus.h> + +#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 <sys/lombus.h> + */ +#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 <lo_iblk>) + */ + 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 <dip>; 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; + clock_t tick; + 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); + + tick = ddi_get_lbolt() + csp->poll_hz; + if ((cv_timedwait(csp->lo_cv, csp->lo_mutex, 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 */ +}; + +static struct modldrv modldrv = +{ + &mod_driverops, + "bscbus driver, v%I%", + &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_add_32_nv(&ssp->cmd_log_idx, 1); + 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 */ diff --git a/usr/src/uts/common/io/bscv.c b/usr/src/uts/common/io/bscv.c new file mode 100644 index 0000000000..9353af7323 --- /dev/null +++ b/usr/src/uts/common/io/bscv.c @@ -0,0 +1,6408 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * bscv.c - multi-threaded lom driver for the Stiletto platform. + */ + +/* + * Included files. + */ + +#include <sys/note.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/uio.h> +#include <sys/open.h> +#include <sys/cred.h> +#include <sys/stream.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/cyclic.h> +#include <sys/reboot.h> +#include <sys/modctl.h> +#include <sys/mkdev.h> +#include <sys/errno.h> +#include <sys/debug.h> +#include <sys/kmem.h> +#include <sys/consdev.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/disp.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/stream.h> +#include <sys/strlog.h> +#include <sys/log.h> +#include <sys/utsname.h> +#include <sys/callb.h> +#include <sys/sysevent.h> +#include <sys/nvpair.h> +#include <sys/sysevent/eventdefs.h> +#include <sys/sysevent/domain.h> +#include <sys/sysevent/env.h> +#include <sys/sysevent/dr.h> + +#include <sys/lom_io.h> +#include <sys/bscbus.h> +#include <sys/bscv_impl.h> + +/* + * Variables defined here and visible internally only + */ + +static void *bscv_statep = NULL; + +/* + * Forward declarations + */ + +static int bscv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); +static int bscv_attach(dev_info_t *, ddi_attach_cmd_t); +static int bscv_detach(dev_info_t *, ddi_detach_cmd_t); +static int bscv_reset(dev_info_t *, ddi_reset_cmd_t); +static int bscv_map_regs(bscv_soft_state_t *); +static void bscv_unmap_regs(bscv_soft_state_t *); +static void bscv_map_chan_logical_physical(bscv_soft_state_t *); + +static int bscv_open(dev_t *, int, int, cred_t *); +static int bscv_close(dev_t, int, int, cred_t *); +static void bscv_full_stop(bscv_soft_state_t *); + +static void bscv_enter(bscv_soft_state_t *); +static void bscv_exit(bscv_soft_state_t *); +#ifdef DEBUG +static int bscv_held(bscv_soft_state_t *); +#endif /* DEBUG */ + +static void bscv_put8(bscv_soft_state_t *, int, bscv_addr_t, uint8_t); +static void bscv_put16(bscv_soft_state_t *, int, bscv_addr_t, uint16_t); +static void bscv_put32(bscv_soft_state_t *, int, bscv_addr_t, uint32_t); +static uint8_t bscv_get8(bscv_soft_state_t *, int, bscv_addr_t); +static uint16_t bscv_get16(bscv_soft_state_t *, int, bscv_addr_t); +static uint32_t bscv_get32(bscv_soft_state_t *, int, bscv_addr_t); +static void bscv_setclear8(bscv_soft_state_t *, int, + bscv_addr_t, uint8_t, uint8_t); +static void bscv_setclear8_volatile(bscv_soft_state_t *, int, + bscv_addr_t, uint8_t, uint8_t); +static void bscv_rep_rw8(bscv_soft_state_t *, int, + uint8_t *, bscv_addr_t, size_t, uint_t, boolean_t); +static uint8_t bscv_get8_cached(bscv_soft_state_t *, bscv_addr_t); + +static uint8_t bscv_get8_locked(bscv_soft_state_t *, int, bscv_addr_t, int *); +static void bscv_rep_get8_locked(bscv_soft_state_t *, int, + uint8_t *, bscv_addr_t, size_t, uint_t, int *); + +static boolean_t bscv_faulty(bscv_soft_state_t *); +static void bscv_clear_fault(bscv_soft_state_t *); +static void bscv_set_fault(bscv_soft_state_t *); +static boolean_t bscv_session_error(bscv_soft_state_t *); +static int bscv_retcode(bscv_soft_state_t *); +static int bscv_should_retry(bscv_soft_state_t *); +static void bscv_locked_result(bscv_soft_state_t *, int *); + +static void bscv_put8_once(bscv_soft_state_t *, int, bscv_addr_t, uint8_t); +static uint8_t bscv_get8_once(bscv_soft_state_t *, int, bscv_addr_t); +static uint32_t bscv_probe(bscv_soft_state_t *, int, uint32_t *); +static void bscv_resync_comms(bscv_soft_state_t *, int); + +static boolean_t bscv_window_setup(bscv_soft_state_t *); +static int bscv_eerw(bscv_soft_state_t *, uint32_t, uint8_t *, + unsigned, boolean_t); + +static int bscv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); +static int bscv_ioc_dogstate(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_psustate(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_fanstate(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_fledstate(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_ledstate(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_info(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_mread(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_volts(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_stats(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_temp(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_cons(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_eventlog2(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_info2(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_test(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_mprog2(bscv_soft_state_t *, intptr_t, int); +static int bscv_ioc_mread2(bscv_soft_state_t *, intptr_t, int); + +static void bscv_event_daemon(void *); +static void bscv_start_event_daemon(bscv_soft_state_t *); +static int bscv_stop_event_daemon(bscv_soft_state_t *); +static int bscv_pause_event_daemon(bscv_soft_state_t *); +static void bscv_resume_event_daemon(bscv_soft_state_t *); +static void bscv_event_process(bscv_soft_state_t *ssp, boolean_t); +static int bscv_event_validate(bscv_soft_state_t *, uint32_t, uint8_t); +static void bscv_event_process_one(bscv_soft_state_t *, lom_event_t *); +static void bscv_build_eventstring(bscv_soft_state_t *, + lom_event_t *, char *, char *); +static int bscv_level_of_event(lom_event_t *); +static void bscv_status(bscv_soft_state_t *, uint8_t, uint8_t); +char *bscv_get_label(char [][MAX_LOM2_NAME_STR], int, int); +static void bscv_generic_sysevent(bscv_soft_state_t *, char *, char *, char *, + char *, int32_t, char *); +static void bscv_sysevent(bscv_soft_state_t *, lom_event_t *); + +static int bscv_prog(bscv_soft_state_t *, intptr_t, int); +static int bscv_prog_image(bscv_soft_state_t *, boolean_t, + uint8_t *, int, uint32_t); +static int bscv_prog_receive_image(bscv_soft_state_t *, lom_prog_t *, + uint8_t *, int); +static void bscv_leave_programming_mode(bscv_soft_state_t *, boolean_t); +static int bscv_prog_stop_lom(bscv_soft_state_t *); +static int bscv_prog_start_lom(bscv_soft_state_t *); + +static int bscv_attach_common(bscv_soft_state_t *); +static int bscv_cleanup(bscv_soft_state_t *); +static void bscv_setup_capability(bscv_soft_state_t *); +static int bscv_probe_check(bscv_soft_state_t *); +static void bscv_setup_hostname(bscv_soft_state_t *); +static void bscv_read_hostname(bscv_soft_state_t *, char *); +static void bscv_write_hostname(bscv_soft_state_t *, char *, uint8_t); +static void bscv_setup_static_info(bscv_soft_state_t *); +static uint8_t bscv_read_env_name(bscv_soft_state_t *, uint8_t, + uint8_t, uint8_t, char [][MAX_LOM2_NAME_STR], int); +static void bscv_setup_events(bscv_soft_state_t *); + +static void bscv_trace(bscv_soft_state_t *, char, const char *, + const char *, ...); + +#ifdef __sparc +static void bscv_idi_init(); +static void bscv_idi_fini(); +static void bscv_idi_new_instance(dev_info_t *dip); +static void bscv_idi_clear_err(); +void bscv_idi_set(struct bscv_idi_info info); +static boolean_t bscv_idi_err(); +static boolean_t bscv_nodename_set(struct bscv_idi_info info); +static boolean_t bscv_sig_set(struct bscv_idi_info info); +static boolean_t bscv_wdog_pat(struct bscv_idi_info info); +static boolean_t bscv_wdog_cfg(struct bscv_idi_info info); +static void bscv_write_sig(bscv_soft_state_t *ssp, bscv_sig_t s); +#endif /* __sparc */ + +static void bscv_setup_watchdog(bscv_soft_state_t *ssp); +static void bscv_write_wdog_cfg(bscv_soft_state_t *, + uint_t, boolean_t, uint8_t); + +#if defined(__i386) || defined(__amd64) +static void bscv_inform_bsc(bscv_soft_state_t *, uint32_t); +static void bscv_watchdog_pat_request(void *); +static void bscv_watchdog_cfg_request(bscv_soft_state_t *, uint8_t); +static uint_t bscv_set_watchdog_timer(bscv_soft_state_t *, uint_t); +static void bscv_clear_watchdog_timer(bscv_soft_state_t *); + +static boolean_t bscv_panic_callback(void *, int); +static void bscv_watchdog_cyclic_add(bscv_soft_state_t *); +static void bscv_watchdog_cyclic_remove(bscv_soft_state_t *); + +extern kmutex_t cpu_lock; /* needed for cyclics */ +static uint8_t wdog_reset_on_timeout = 1; + +#define WDOG_ON 1 +#define WDOG_OFF 0 +#define CLK_WATCHDOG_DEFAULT 10 /* 10 seconds */ +#define WATCHDOG_PAT_INTERVAL 1000000000 /* 1 second */ + +static int bscv_watchdog_enable; +static int bscv_watchdog_available; +static int watchdog_activated; +static uint_t bscv_watchdog_timeout_seconds; +#endif /* __i386 || __amd64 */ + +#ifdef __sparc +struct bscv_idi_callout bscv_idi_callout_table[] = { + {BSCV_IDI_NODENAME, &bscv_nodename_set }, + {BSCV_IDI_SIG, &bscv_sig_set }, + {BSCV_IDI_WDOG_PAT, &bscv_wdog_pat }, + {BSCV_IDI_WDOG_CFG, &bscv_wdog_cfg }, + {BSCV_IDI_NULL, NULL } +}; + +static struct bscv_idi_callout_mgr bscv_idi_mgr; +#endif /* __sparc */ + +/* + * Local Definitions + */ +#define STATUS_READ_LIMIT 8 /* Read up to 8 status changes at a time */ +#define MYNAME "bscv" +#define BSCV_INST_TO_MINOR(i) (i) +#define BSCV_MINOR_TO_INST(m) (m) +#define ddi_driver_major(dip) ddi_name_to_major(ddi_binding_name(dip)) + +/* + * Strings for daemon event reporting + */ + +static char *eventSubsysStrings[] = +{ "", /* 00 */ + "Alarm ", /* 01 */ + "temperature sensor ", /* 02 */ + "overheat sensor ", /* 03 */ + "Fan ", /* 04 */ + "supply rail ", /* 05 */ + "circuit breaker ", /* 06 */ + "PSU ", /* 07 */ + "user ", /* 08 */ + "phonehome ", /* 09; unutilized */ + "LOM ", /* 0a */ + "host ", /* 0b */ + "event log ", /* 0c */ + "", /* 0d; EVENT_SUBSYS_EXTRA unutilized */ + "LED ", /* 0e */ +}; + +static char *eventTypeStrings[] = +{ + "[null event]", /* 00 */ + "ON", /* 01 */ + "OFF", /* 02 */ + "state change", /* 03 */ + "power on", /* 04 */ + "power off", /* 05 */ + "powered off unexpectedly", /* 06 */ + "reset unexpectedly", /* 07 */ + "booted", /* 08 */ + "watchdog enabled", /* 09 */ + "watchdog disabled", /* 0a */ + "watchdog triggered", /* 0b */ + "failed", /* 0c */ + "recovered", /* 0d */ + "reset", /* 0e */ + "XIR reset", /* 0f */ + "console selected", /* 10 */ + "time reference", /* 11 */ + "script failure", /* 12 */ + "modem access failure", /* 13 */ + "modem dialing failure", /* 14 */ + "bad checksum", /* 15 */ + "added", /* 16 */ + "removed", /* 17 */ + "changed", /* 18 */ + "login", /* 19 */ + "password changed", /* 1a */ + "login failed", /* 1b */ + "logout", /* 1c */ + "flash download", /* 1d */ + "data lost", /* 1e */ + "device busy", /* 1f */ + "fault led state", /* 20 */ + "overheat", /* 21 */ + "severe overheat", /* 22 */ + "no overheat", /* 23 */ + "SCC", /* 24 */ + "device inaccessible", /* 25 */ + "Hostname change", /* 26 */ + "CPU signature timeout", /* 27 */ + "Bootmode change", /* 28 */ + "Watchdog change policy", /* 29 */ + "Watchdog change timeout", /* 2a */ +}; + +/* + * These store to mapping between the logical service, e.g. chan_prog for + * programming, and the actual Xbus channel which carries that traffic. + * Any services can be shared on the same channel apart from chan_wdogpat. + */ +static int chan_general; /* General Traffic */ +static int chan_wdogpat; /* Watchdog Patting */ +static int chan_cpusig; /* CPU signatures */ +static int chan_eeprom; /* EEPROM I/O */ +static int chan_prog; /* Programming */ + +/* + * cb_ops structure defining the driver entry points + */ + +static struct cb_ops bscv_cb_ops = { + bscv_open, /* open */ + bscv_close, /* close */ + nodev, /* strategy */ + nodev, /* print */ + nodev, /* dump */ + nodev, /* read */ + nodev, /* write */ + bscv_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + nochpoll, /* poll */ + ddi_prop_op, /* prop op */ + NULL, /* ! STREAMS */ + D_NEW | D_MP /* MT/MP Safe */ +}; + +/* + * dev_ops structure defining autoconfiguration driver autoconfiguration + * routines + */ + +static struct dev_ops bscv_dev_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + bscv_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + bscv_attach, /* devo_attach */ + bscv_detach, /* devo_detach */ + bscv_reset, /* devo_reset */ + &bscv_cb_ops, /* devo_cb_ops */ + (struct bus_ops *)0 /* devo_bus_ops */ +}; + +/* + * module configuration section + */ + +#ifdef DEBUG +#define BSCV_VERSION_STRING "bscv driver - Debug v%I%" +#else /* DEBUG */ +#define BSCV_VERSION_STRING "bscv driver v%I%" +#endif /* DEBUG */ + +static struct modldrv modldrv = { + &mod_driverops, + BSCV_VERSION_STRING, + &bscv_dev_ops, +}; + +static struct modlinkage modlinkage = { + MODREV_1, + &modldrv, + NULL +}; + +/* + * kernel accessible routines. These routines are necessarily global so the + * driver can be loaded, and unloaded successfully + */ + +/* + * function - _init + * description - initializes the driver state structure and installs the + * driver module into the kernel + * inputs - none + * outputs - success or failure of module installation + */ + +int +_init(void) +{ + register int e; + + if ((e = ddi_soft_state_init(&bscv_statep, + sizeof (bscv_soft_state_t), 1)) != 0) { + return (e); + } + + if ((e = mod_install(&modlinkage)) != 0) { + ddi_soft_state_fini(&bscv_statep); + } + +#ifdef __sparc + if (e == 0) bscv_idi_init(); +#endif /* __sparc */ + return (e); +} + +/* + * function - _info + * description - provide information about a kernel loaded module + * inputs - module infomation + * outputs - success or failure of information request + */ + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* + * function - _fini + * description - removes a module from the kernel and frees the driver soft + * state memory + * inputs - none + * outputs - success or failure of module removal + */ + +int +_fini(void) +{ + register int e; + + if ((e = mod_remove(&modlinkage)) != 0) { + return (e); + } + +#ifdef __sparc + bscv_idi_fini(); +#endif /* __sparc */ + ddi_soft_state_fini(&bscv_statep); + + return (e); +} + +/* + * function - bscv_getinfo + * description - routine used to provide information on the driver + * inputs - device information structure, command, command arg, storage + * area for the result + * outputs - DDI_SUCCESS or DDI_FAILURE + */ + +/*ARGSUSED*/ +static int +bscv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) +{ + bscv_soft_state_t *ssp; + dev_t dev = (dev_t)arg; + int instance; + int error; + + instance = DEVICETOINSTANCE(dev); + + switch (cmd) { + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)(uintptr_t)instance; + error = DDI_SUCCESS; + break; + + case DDI_INFO_DEVT2DEVINFO: + ssp = ddi_get_soft_state(bscv_statep, instance); + if (ssp == NULL) + return (DDI_FAILURE); + *result = (void *) ssp->dip; + error = DDI_SUCCESS; + break; + + default: + error = DDI_FAILURE; + break; + } + + return (error); +} + +#ifdef __sparc +void +bscv_idi_init() +{ + bscv_idi_mgr.valid_inst = (uint32_t)~0; /* No valid instances */ + bscv_idi_mgr.tbl = bscv_idi_callout_table; + bscv_idi_mgr.errs = 0; + + /* + * Now that all fields are initialized, set the magic flag. This is + * a kind of integrity check for the data structure. + */ + bscv_idi_mgr.magic = BSCV_IDI_CALLOUT_MAGIC; +} + +static void +bscv_idi_clear_err() +{ + ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC); + + bscv_idi_mgr.errs = 0; +} + +/* + * function - bscv_idi_err + * description - error messaging service which throttles the number of error + * messages to avoid overflowing storage + * inputs - none + * returns - boolean to indicate whether a message should be reported + * side-effects - updates the error number counter + */ +static boolean_t +bscv_idi_err() +{ + ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC); + + bscv_idi_mgr.errs++; + + if (bscv_idi_mgr.errs++ < BSCV_IDI_ERR_MSG_THRESHOLD) + return (B_TRUE); + + return (B_FALSE); +} + +void +bscv_idi_new_instance(dev_info_t *dip) +{ + ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC); + + /* + * We don't care how many instances we have, or their value, so long + * as we have at least one valid value. This is so service routines + * can get any required locks via a soft state pointer. + */ + if (bscv_idi_mgr.valid_inst == (uint32_t)~0) { + bscv_idi_mgr.valid_inst = ddi_get_instance(dip); + } +} + +void +bscv_idi_fini() +{ + bscv_idi_mgr.valid_inst = (uint32_t)~0; /* No valid instances */ + bscv_idi_mgr.tbl = NULL; +} +#endif /* __sparc */ + +/* + * function - bscv_attach + * description - this routine is responsible for setting aside memory for the + * driver data structures, initialising the mutexes and creating + * the device minor nodes. Additionally, this routine calls the + * the callback routine. + * inputs - device information structure, DDI_ATTACH command + * outputs - DDI_SUCCESS or DDI_FAILURE + */ + +int +bscv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + bscv_soft_state_t *ssp; + int instance; + + switch (cmd) { + case DDI_ATTACH: + + instance = ddi_get_instance(dip); + + if (ddi_soft_state_zalloc(bscv_statep, instance) != + DDI_SUCCESS) { + return (DDI_FAILURE); + } + + + ssp = ddi_get_soft_state(bscv_statep, instance); + + ssp->progress = 0; + + ssp->dip = dip; + ssp->instance = instance; + ssp->event_waiting = B_FALSE; + ssp->status_change = B_FALSE; + ssp->nodename_change = B_FALSE; + ssp->cap0 = 0; + ssp->cap1 = 0; + ssp->cap2 = 0; + ssp->prog_mode_only = B_FALSE; + ssp->programming = B_FALSE; + ssp->cssp_prog = B_FALSE; + ssp->task_flags = 0; + ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "debug", 0); + ssp->majornum = ddi_driver_major(dip); + ssp->minornum = BSCV_INST_TO_MINOR(instance); +#if defined(__i386) || defined(__amd64) + ssp->last_nodename[0] = '\0'; +#endif /* __i386 || __amd64 */ + + /* + * initialise the mutexes + */ + + mutex_init(&ssp->cmd_mutex, NULL, MUTEX_DRIVER, NULL); + + mutex_init(&ssp->task_mu, NULL, MUTEX_DRIVER, NULL); + cv_init(&ssp->task_cv, NULL, CV_DRIVER, NULL); + cv_init(&ssp->task_evnt_cv, NULL, CV_DRIVER, NULL); + mutex_init(&ssp->prog_mu, NULL, MUTEX_DRIVER, NULL); + ssp->progress |= BSCV_LOCKS; + + bscv_trace(ssp, 'A', "bscv_attach", + "bscv_attach: mutexes and condition vars initialised"); + + /* Map in physical communication channels */ + + if (bscv_map_regs(ssp) != DDI_SUCCESS) { + (void) bscv_cleanup(ssp); + return (DDI_FAILURE); + } + ssp->progress |= BSCV_MAPPED_REGS; + + /* Associate logical channels to physical channels */ + + bscv_map_chan_logical_physical(ssp); + + bscv_enter(ssp); + + bscv_leave_programming_mode(ssp, B_FALSE); + + if (bscv_attach_common(ssp) == DDI_FAILURE) { + bscv_exit(ssp); + (void) bscv_cleanup(ssp); + return (DDI_FAILURE); + } + +#ifdef __sparc + /* + * At this point the inter-driver-interface is made available. + * The IDI uses the event thread service which + * bscv_attach_common() sets up. + */ + bscv_idi_new_instance(dip); +#endif /* __sparc */ + + bscv_exit(ssp); + + /* + * now create the minor nodes + */ + if (ddi_create_minor_node(ssp->dip, "lom", S_IFCHR, + BSCV_INST_TO_MINOR(instance), + DDI_PSEUDO, 0) != DDI_SUCCESS) { + (void) bscv_cleanup(ssp); + return (DDI_FAILURE); + } + bscv_trace(ssp, 'A', "bscv_attach", + "bscv_attach: device minor nodes created"); + ssp->progress |= BSCV_NODES; + + if (!ssp->prog_mode_only) + bscv_start_event_daemon(ssp); + +#if defined(__i386) || defined(__amd64) + bscv_watchdog_enable = 1; + bscv_watchdog_available = 1; + watchdog_activated = 0; + bscv_watchdog_timeout_seconds = CLK_WATCHDOG_DEFAULT; + + if (bscv_watchdog_enable && (boothowto & RB_DEBUG)) { + bscv_watchdog_available = 0; + cmn_err(CE_WARN, "bscv: kernel debugger " + "detected: hardware watchdog disabled"); + } + + /* + * Before we enable the watchdog - register the panic + * callback so that we get called to stop the watchdog + * in the case of a panic. + */ + ssp->callb_id = callb_add(bscv_panic_callback, + (void *)ssp, CB_CL_PANIC, ""); + + if (bscv_watchdog_available) { + (void) bscv_set_watchdog_timer(ssp, + CLK_WATCHDOG_DEFAULT); + bscv_enter(ssp); + bscv_setup_watchdog(ssp); /* starts cyclic callback */ + bscv_exit(ssp); + } +#endif /* __i386 || __amd64 */ + ddi_report_dev(dip); + return (DDI_SUCCESS); + default: + return (DDI_FAILURE); + } +} + +/* + * function - bscv_detach + * description - routine that prepares a module to be unloaded. It undoes all + * the work done by the bscv_attach)() routine. This is + * facilitated by the use of the progress indicator + * inputs - device information structure, DDI_DETACH command + * outputs - DDI_SUCCESS or DDI_FAILURE + */ + +/*ARGSUSED*/ +static int +bscv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + return (DDI_FAILURE); +} + +/* + * function - bscv_reset + * description - routine called when system is being stopped - used to disable + * the watchdog. + * inputs - device information structure, DDI_RESET command + * outputs - DDI_SUCCESS or DDI_FAILURE + */ +static int +bscv_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) +{ + bscv_soft_state_t *ssp; + int instance; + + switch (cmd) { + case DDI_RESET_FORCE: + + instance = ddi_get_instance(dip); + ssp = ddi_get_soft_state(bscv_statep, instance); + if (ssp == NULL) { + return (DDI_FAILURE); + } + bscv_full_stop(ssp); + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } +} + +/* + * cb_ops routines + */ + +/* + * function - bscv_open + * description - routine to provide association between user fd and device + * minor number. This routine is necessarily simple since a + * read/write interface is not provided. Additionally, the + * driver does not enforce exclusive access (FEXCL) or + * non-blocking during an open (FNDELAY). Deferred attach is + * supported. + * inputs - device number, flag specifying open type, device type, + * permissions + * outputs - success or failure of operation + */ + +/*ARGSUSED*/ +static int +bscv_open(dev_t *devp, int flag, int otype, cred_t *cred) +{ + bscv_soft_state_t *ssp; + int instance; + + instance = DEVICETOINSTANCE(*devp); + ssp = ddi_get_soft_state(bscv_statep, instance); + if (ssp == NULL) { + return (ENXIO); /* not attached yet */ + } + bscv_trace(ssp, 'O', "bscv_open", "instance 0x%x", instance); + + if (otype != OTYP_CHR) { + return (EINVAL); + } + + return (0); +} + +/* + * function - bscv_close + * description - routine to perform the final close on the device. As per the + * open routine, neither FEXCL or FNDELAY accesses are enforced + * by the driver. + * inputs - device number,flag specifying open type, device type, + * permissions + * outputs - success or failure of operation + */ + +/*ARGSUSED1*/ +static int +bscv_close(dev_t dev, int flag, int otype, cred_t *cred) +{ + bscv_soft_state_t *ssp; + int instance; + + instance = DEVICETOINSTANCE(dev); + ssp = ddi_get_soft_state(bscv_statep, instance); + if (ssp == NULL) { + return (ENXIO); + } + bscv_trace(ssp, 'O', "bscv_close", "instance 0x%x", instance); + + return (0); +} + +static int +bscv_map_regs(bscv_soft_state_t *ssp) +{ + int i; + int retval; + int *props; + unsigned int nelements; + + ASSERT(ssp); + + ssp->nchannels = 0; + + /* + * Work out how many channels are available by looking at the number + * of elements of the regs property array. + */ + retval = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ssp->dip, + DDI_PROP_DONTPASS, "reg", &props, &nelements); + + /* We don't need props anymore. Free memory if it was allocated */ + if (retval == DDI_PROP_SUCCESS) + ddi_prop_free(props); + + /* Check for sanity of nelements */ + if (retval != DDI_PROP_SUCCESS) { + bscv_trace(ssp, 'A', "bscv_map_regs", "lookup reg returned" + " 0x%x", retval); + goto cleanup_exit; + } else if (nelements % LOMBUS_REGSPEC_SIZE != 0) { + bscv_trace(ssp, 'A', "bscv_map_regs", "nelements %d not" + " a multiple of %d", nelements, LOMBUS_REGSPEC_SIZE); + goto cleanup_exit; + } else if (nelements > BSCV_MAXCHANNELS * LOMBUS_REGSPEC_SIZE) { + bscv_trace(ssp, 'A', "bscv_map_regs", "nelements %d too large" + ", probably a misconfiguration", nelements); + goto cleanup_exit; + } else if (nelements < BSCV_MINCHANNELS * LOMBUS_REGSPEC_SIZE) { + bscv_trace(ssp, 'A', "bscv_map_regs", "nelements %d too small" + ", need to have at least a general and a wdog channel", + nelements); + goto cleanup_exit; + } + + ssp->nchannels = nelements / LOMBUS_REGSPEC_SIZE; + + ssp->attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; + ssp->attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; + ssp->attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; + + for (i = 0; i < ssp->nchannels; i++) { + retval = ddi_regs_map_setup(ssp->dip, i, + (caddr_t *)&ssp->channel[i].regs, + 0, 0, &ssp->attr, &ssp->channel[i].handle); + if (retval != DDI_SUCCESS) { + bscv_trace(ssp, 'A', "bscv_map_regs", "map failure" + " 0x%x on space %d", retval, i); + + /* Rewind all current mappings - avoiding failed one */ + i--; + for (; i >= 0; i--) { + ddi_regs_map_free(&ssp->channel[i].handle); + } + + goto cleanup_exit; + } + } + + return (DDI_SUCCESS); + +cleanup_exit: + /* + * It is important to set nchannels to 0 even if, say, only one of + * the two required handles was mapped. If we cannot achieve our + * minimum config its not safe to do any IO; this keeps our failure + * mode handling simpler. + */ + ssp->nchannels = 0; + return (DDI_FAILURE); +} + +static void +bscv_unmap_regs(bscv_soft_state_t *ssp) +{ + int i; + + ASSERT(ssp); + + for (i = 0; i < ssp->nchannels; i++) { + ddi_regs_map_free(&ssp->channel[i].handle); + } +} + +/* + * Map logical services onto physical XBus channels. + */ +static void +bscv_map_chan_logical_physical(bscv_soft_state_t *ssp) +{ + ASSERT(ssp); + + /* + * We can assert that there will always be at least two channels, + * to allow watchdog pats to be segregated from all other traffic. + */ + chan_general = 0; + chan_wdogpat = 1; + + /* + * By default move all other services onto the generic channel unless + * the hardware supports additional channels. + */ + + chan_cpusig = chan_eeprom = chan_prog = chan_general; + + if (ssp->nchannels > 2) + chan_cpusig = 2; + if (ssp->nchannels > 3) + chan_eeprom = 3; + if (ssp->nchannels > 4) + chan_prog = 4; +} + + +/* + * function - bscv_full_stop + * description - gracefully shut the lom down during panic or reboot. + * Disables the watchdog, setup up serial event reporting + * and stops the event daemon running. + * inputs - soft state pointer + * outputs - none + */ +void +bscv_full_stop(bscv_soft_state_t *ssp) +{ + uint8_t bits2set = 0; + uint8_t bits2clear = 0; + + bscv_trace(ssp, 'W', "bscv_full_stop", + "turning off watchdog"); + + if (!ddi_in_panic()) { + /* Stop the event daemon if we are not panicking. */ + (void) bscv_pause_event_daemon(ssp); + } + + bscv_enter(ssp); + +#if defined(__i386) || defined(__amd64) + if (ddi_in_panic()) { + bscv_inform_bsc(ssp, BSC_INFORM_PANIC); + } else { + bscv_inform_bsc(ssp, BSC_INFORM_OFFLINE); + } +#endif /* __i386 || __amd64 */ + + /* set serial event reporting */ + switch (ssp->serial_reporting) { + case LOM_SER_EVENTS_ON: + case LOM_SER_EVENTS_DEF: + /* Make sure serial event reporting is on */ + bits2clear = EBUS_ALARM_NOEVENTS; + break; + case LOM_SER_EVENTS_OFF: + /* Make sure serial event reporting is on */ + bits2set = EBUS_ALARM_NOEVENTS; + break; + default: + break; + } + bscv_setclear8_volatile(ssp, chan_general, + EBUS_IDX_ALARM, bits2set, bits2clear); + + bscv_exit(ssp); +} + +/* + * LOM I/O routines. + * + * locking + * + * Two sets of routines are provided: + * normal - must be called after acquiring an appropriate lock. + * locked - perform all the locking required and return any error + * code in the supplied 'res' argument. If there is no + * error 'res' is not changed. + * The locked routines are designed for use in ioctl commands where + * only a single operation needs to be performed and the overhead of + * locking and result checking adds significantly to code complexity. + * + * locking primitives + * + * bscv_enter() - acquires an I/O lock for the calling thread. + * bscv_exit() - releases an I/O lock acquired by bscv_enter(). + * bscv_held() - used to assert ownership of an I/O lock. + * + * normal I/O routines + * + * Note bscv_{put|get}{16|32} routines are big-endian. This assumes that + * the firmware works that way too. + * + * bscv_put8(), bscv_put16, bscv_put32 - write values to the LOM + * and handle any retries if necessary. + * 16 and 32 bit values are big-endian. + * bscv_get8(), bscv_get16, bscv_get32 - read values from the LOM + * and handle any retries if necessary. + * 16 and 32 bit values are big-endian. + * bscv_setclear8() - set or clear the specified bits in the register + * at the supplied address. + * bscv_setclear8_volatile() - set or clear the specified bits in the + * register at the supplied address. If the lom reports + * that the registers has changed since the last read + * re-read and apply the set or clear to the new bits. + * bscv_get8_cached() - Return a cached register value (addr < 0x80). + * Does not access the hardware. A read of the hardware + * automatically updates this cache. + * + * locked I/O routines + * + * bscv_get8_locked(), bscv_rep_get8_locked(). + * + * Call the indicated function from above, but wrapping it with + * bscv_enter()/bscv_exit(). + * + * + * Fault management + * + * LOM communications fault are grouped into three categories: + * 1) Faulty - the LOM is not responding and no attempt to communicate + * with it should be made. + * 2) Transient fault - something which might recover after a retry + * but which doesn't affect our ability to perform other + * commands. + * 3) Command error - an inappropriate command was executed. A retry + * will not fix it but the command failed. + * + * The current implementation of the bscv driver is not very good at + * noticing command errors due to the structure of the original code + * that it is based on. It is possible to extend the driver to do this + * and would probably involve having a concept of a "session error" + * which is less severe than a fault but means that a sequence of + * commands had some fault which cannot be recovered. + * + * + * faults + * + * bscv_faulty() - returns B_TRUE if the LOM (communications) have been + * declared faulty. + * bscv_clear_fault() - marks the LOM as not faulty. + * bscv_set_fault() - marks the LOM as being faulty. + * + * bscv_clear_fault and bscv_set_fault should generally not be called + * directly. + * + * command errors/transient faults + * + * bscv_retcode() - returns the actual error code of the last operation. + * bscv_should_retry() - determines if last operation may suceed if + * retried. + * bscv_locked_result() - Set the result of a locked register access. + * + * low level I/O primitives + * + * These are generally not called directly. These perform a single + * access to the LOM device. They do not handle retries. + * + * bscv_put8_once() + * bscv_get8_once() + * bscv_probe() - perform a probe (NOP) operation to check out lom comms. + * bscv_resync_comms() - resynchronise communications after a transient fault. + */ + +static void +bscv_enter(bscv_soft_state_t *ssp) +{ + bscv_trace(ssp, '@', "bscv_enter", ""); + mutex_enter(&ssp->cmd_mutex); + ssp->had_session_error = B_FALSE; +} + +static void +bscv_exit(bscv_soft_state_t *ssp) +{ + mutex_exit(&ssp->cmd_mutex); + bscv_trace(ssp, '@', "bscv_exit", ""); +} + +#ifdef DEBUG +static int +bscv_held(bscv_soft_state_t *ssp) +{ + return (mutex_owned(&ssp->cmd_mutex)); +} +#endif /* DEBUG */ + +static void +bscv_put8(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint8_t val) +{ + boolean_t needretry; + int num_failures; + + ASSERT(bscv_held(ssp)); + + if (bscv_faulty(ssp)) { + return; + } + + bscv_trace(ssp, '@', "bscv_put8", + "addr 0x%x.%02x <= 0x%02x", addr >> 8, addr & 0xff, val); + + for (num_failures = 0; + num_failures < BSC_FAILURE_RETRY_LIMIT; + num_failures++) { + bscv_put8_once(ssp, chan, addr, val); + needretry = bscv_should_retry(ssp); + if (!needretry) { + break; + } + } + if (ssp->command_error != 0) { + ssp->had_session_error = B_TRUE; + } + + if (needretry) { + /* Failure - we ran out of retries */ + cmn_err(CE_WARN, "bscv_put8: addr 0x%x.%02x retried " + "write %d times, giving up", + addr >> 8, addr & 0xff, num_failures); + bscv_set_fault(ssp); + } else if (num_failures > 0) { + bscv_trace(ssp, 'R', "bscv_put8", + "addr 0x%x.%02x retried write %d times, succeeded", + addr >> 8, addr & 0xff, num_failures); + } +} + +static void +bscv_put16(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint16_t val) +{ + ASSERT(bscv_held(ssp)); + bscv_trace(ssp, '@', "bscv_put16", + "addr 0x%x.%02x <= %04x", addr >> 8, addr & 0xff, val); + bscv_put8(ssp, chan, addr, val >> 8); + bscv_put8(ssp, chan, addr + 1, val & 0xff); +} + +static void +bscv_put32(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint32_t val) +{ + ASSERT(bscv_held(ssp)); + bscv_trace(ssp, '@', "bscv_put32", + "addr 0x%x.%02x <= %08x", addr >> 8, addr & 0xff, val); + bscv_put8(ssp, chan, addr, (val >> 24) & 0xff); + bscv_put8(ssp, chan, addr + 1, (val >> 16) & 0xff); + bscv_put8(ssp, chan, addr + 2, (val >> 8) & 0xff); + bscv_put8(ssp, chan, addr + 3, val & 0xff); +} + +static uint8_t +bscv_get8(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr) +{ + uint8_t retval; + boolean_t needretry; + int num_failures; + + ASSERT(bscv_held(ssp)); + + if (bscv_faulty(ssp)) { + return (0); + } + + for (num_failures = 0; + num_failures < BSC_FAILURE_RETRY_LIMIT; + num_failures++) { + retval = bscv_get8_once(ssp, chan, addr); + needretry = bscv_should_retry(ssp); + if (!needretry) { + break; + } + } + if (ssp->command_error != 0) { + ssp->had_session_error = B_TRUE; + } + + if (needretry) { + /* Failure */ + cmn_err(CE_WARN, "bscv_get8: addr 0x%x.%02x retried " + "read %d times, giving up", + addr >> 8, addr & 0xff, num_failures); + bscv_set_fault(ssp); + } else if (num_failures > 0) { + bscv_trace(ssp, 'R', "bscv_get8", + "addr 0x%x.%02x retried read %d times, succeeded", + addr >> 8, addr & 0xff, num_failures); + } + + bscv_trace(ssp, '@', "bscv_get8", + "addr 0x%x.%02x => %02x", addr >> 8, addr & 0xff, retval); + return (retval); +} + +static uint16_t +bscv_get16(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr) +{ + uint16_t retval; + + ASSERT(bscv_held(ssp)); + + retval = bscv_get8(ssp, chan, addr) << 8; + retval |= bscv_get8(ssp, chan, addr + 1); + + bscv_trace(ssp, '@', "bscv_get16", + "addr 0x%x.%02x => %04x", addr >> 8, addr & 0xff, retval); + return (retval); +} + +static uint32_t +bscv_get32(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr) +{ + uint32_t retval; + + ASSERT(bscv_held(ssp)); + + retval = bscv_get8(ssp, chan, addr) << 24; + retval |= bscv_get8(ssp, chan, addr + 1) << 16; + retval |= bscv_get8(ssp, chan, addr + 2) << 8; + retval |= bscv_get8(ssp, chan, addr + 3); + + bscv_trace(ssp, '@', "bscv_get32", + "addr 0x%x.%02x => %08x", addr >> 8, addr & 0xff, retval); + return (retval); +} + +static void +bscv_setclear8(bscv_soft_state_t *ssp, int chan, + bscv_addr_t addr, uint8_t set, uint8_t clear) +{ + uint8_t val; + + ASSERT(bscv_held(ssp)); + ASSERT(addr < BSC_ADDR_CACHE_LIMIT); + + val = ssp->lom_regs[addr] | set; + val &= ~clear; + + bscv_trace(ssp, '@', "bscv_setclear8", + "addr 0x%x.%02x, set %02x, clear %02x => %02x", + addr >> 8, addr & 0xff, + set, clear, val); + + bscv_put8(ssp, chan, addr, val); +} + +static void +bscv_setclear8_volatile(bscv_soft_state_t *ssp, int chan, + bscv_addr_t addr, uint8_t set, uint8_t clear) +{ + uint8_t val; + boolean_t needretry; + int num_failures; + + ASSERT(bscv_held(ssp)); + ASSERT(addr < BSC_ADDR_CACHE_LIMIT); + + if (bscv_faulty(ssp)) { + return; + } + + bscv_trace(ssp, '@', "bscv_setclear8_volatile", + "addr 0x%x.%02x => set %02x clear %02x", + addr >> 8, addr & 0xff, set, clear); + + val = bscv_get8_cached(ssp, addr); + for (num_failures = 0; + num_failures < BSC_FAILURE_RETRY_LIMIT; + num_failures++) { + val |= set; + val &= ~clear; + bscv_put8_once(ssp, chan, addr, val); + if (ssp->command_error == EBUS_ERROR_STALEDATA) { + /* Re-read the stale register from the lom */ + val = bscv_get8_once(ssp, chan, addr); + needretry = 1; + } else { + needretry = bscv_should_retry(ssp); + if (!needretry) { + break; + } + } + } + if (ssp->command_error != 0) { + ssp->had_session_error = B_TRUE; + } + + if (needretry) { + /* Failure */ + cmn_err(CE_WARN, "bscv_setclear8_volatile: addr 0x%x.%02x " + "retried write %d times, giving up", + addr >> 8, addr & 0xff, num_failures); + if (ssp->command_error != EBUS_ERROR_STALEDATA) { + bscv_set_fault(ssp); + } + } else if (num_failures > 0) { + bscv_trace(ssp, 'R', "bscv_setclear8_volatile", + "addr 0x%x.%02x retried write %d times, succeeded", + addr >> 8, addr & 0xff, num_failures); + } +} + +static void +bscv_rep_rw8(bscv_soft_state_t *ssp, int chan, uint8_t *host_addr, + bscv_addr_t dev_addr, size_t repcount, uint_t flags, + boolean_t is_write) +{ + size_t inc; + + ASSERT(bscv_held(ssp)); + + inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0; + for (; repcount--; dev_addr += inc) { + if (flags & DDI_DEV_AUTOINCR) { + if (is_write) { + bscv_put8(ssp, chan, dev_addr, *host_addr++); + } else { + *host_addr++ = bscv_get8(ssp, chan, dev_addr); + } + } else { + if (is_write) { + bscv_put8_once(ssp, chan, + dev_addr, *host_addr++); + } else { + *host_addr++ = bscv_get8_once(ssp, chan, + dev_addr); + } + /* We need this because _once routines don't do it */ + if (ssp->command_error != 0) { + ssp->had_session_error = B_TRUE; + } + } + if (bscv_faulty(ssp) || bscv_session_error(ssp)) { + /* + * No retry here. If we were AUTOINCR then get/put + * will have retried. For NO_AUTOINCR we cannot retry + * because the data would be corrupted. + */ + break; + } + } +} + +static uint8_t +bscv_get8_cached(bscv_soft_state_t *ssp, bscv_addr_t addr) +{ + ASSERT(addr < BSC_ADDR_CACHE_LIMIT); + /* Can be called with or without the lock held */ + + return (ssp->lom_regs[addr]); +} + +static uint8_t +bscv_get8_locked(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, int *res) +{ + uint8_t retval; + + ASSERT(addr < BSC_ADDR_CACHE_LIMIT); + bscv_enter(ssp); + retval = bscv_get8(ssp, chan, addr); + bscv_locked_result(ssp, res); + bscv_exit(ssp); + bscv_trace(ssp, '@', "bscv_get8_locked", + "addr 0x%x.%02x => %02x", addr >> 8, addr & 0xff, retval); + return (retval); +} + +static void +bscv_rep_get8_locked(bscv_soft_state_t *ssp, int chan, uint8_t *host_addr, + bscv_addr_t dev_addr, size_t repcount, uint_t flags, int *res) +{ + bscv_enter(ssp); + bscv_rep_rw8(ssp, chan, host_addr, dev_addr, repcount, + flags, B_FALSE /* read */); + bscv_locked_result(ssp, res); + bscv_exit(ssp); +} + +static boolean_t +bscv_faulty(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + return (ssp->had_fault); +} + +static void +bscv_clear_fault(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + bscv_trace(ssp, 'J', "bscv_clear_fault", "clearing fault flag"); + ssp->had_fault = B_FALSE; + ssp->had_session_error = B_FALSE; +} + +static void +bscv_set_fault(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + bscv_trace(ssp, 'J', "bscv_set_fault", "setting fault flag"); + ssp->had_fault = B_TRUE; +} + +static boolean_t +bscv_session_error(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + return (ssp->had_session_error); +} + +static int +bscv_retcode(bscv_soft_state_t *ssp) +{ + bscv_trace(ssp, '@', "bscv_retcode", + "code 0x%x", ssp->command_error); + return (ssp->command_error); +} + +static int +bscv_should_retry(bscv_soft_state_t *ssp) +{ + if ((ssp->command_error == EBUS_ERROR_DEVICEFAIL) || + (ssp->command_error >= LOMBUS_ERR_BASE)) { + /* This command is due to an I/O fault - retry might fix */ + return (1); + } else { + /* + * The command itself was bad - there is no point in fixing + * Note. Whatever happens we should know that if we were + * doing EBUS_IDX_SELFTEST0..EBUS_IDX_SELFTEST7 and we + * had 0x80 set then this is a test error not a retry + * error. + */ + return (0); + } +} + +static void +bscv_locked_result(bscv_soft_state_t *ssp, int *res) +{ + if (bscv_faulty(ssp) || (bscv_retcode(ssp) != 0)) { + *res = EIO; + } +} + +static void +bscv_put8_once(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr, uint8_t val) +{ + uint32_t fault; + + ASSERT(bscv_held(ssp)); + + ssp->command_error = 0; + + if (bscv_faulty(ssp)) { + /* Bail out things are not working */ + return; + } else if (ssp->nchannels == 0) { + /* Didn't manage to map handles so ddi_{get,put}* broken */ + bscv_trace(ssp, '@', "bscv_put8_once", + "nchannels is 0x0 so cannot do IO"); + return; + } + + /* Clear any pending fault */ + ddi_put32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG), 0); + + /* Do the access and get fault code - may take a long time */ + ddi_put8(ssp->channel[chan].handle, + &ssp->channel[chan].regs[addr], val); + fault = ddi_get32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG)); + + ssp->command_error = fault; + + if (fault == 0) { + /* Things were ok - update cache entry */ + if (addr < BSC_ADDR_CACHE_LIMIT) { + /* Store cacheable entries */ + ssp->lom_regs[addr] = val; + } + } else if (fault >= LOMBUS_ERR_BASE) { + /* lombus problem - do a resync session */ + cmn_err(CE_WARN, "!bscv_put8_once: Had comms fault " + "for address 0x%x.%02x - data 0x%x, fault 0x%x", + addr >> 8, addr & 0xff, val, fault); + /* Attempt to resync with the lom */ + bscv_resync_comms(ssp, chan); + /* + * Note: we do not set fault status here. That + * is done if our caller decides to give up talking to + * the lom. The observant might notice that this means + * that if we mend things on the last attempt we still + * get the fault set - we just live with that! + */ + } + + bscv_trace(ssp, '@', "bscv_put8_once", + "addr 0x%x.%02x <= 0x%02x", addr >> 8, addr & 0xff, val); +} + +static uint8_t +bscv_get8_once(bscv_soft_state_t *ssp, int chan, bscv_addr_t addr) +{ + uint8_t val; + uint32_t fault; + + ASSERT(bscv_held(ssp)); + + ssp->command_error = 0; + + if (bscv_faulty(ssp)) { + /* Bail out things are not working */ + return (0xff); + } else if (ssp->nchannels == 0) { + /* Didn't manage to map handles so ddi_{get,put}* broken */ + bscv_trace(ssp, '@', "bscv_get8_once", + "nchannels is 0x0 so cannot do IO"); + return (0xff); + } + + /* Clear any pending fault */ + ddi_put32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG), 0); + + /* Do the access and get fault code - may take a long time */ + val = ddi_get8(ssp->channel[chan].handle, + &ssp->channel[chan].regs[addr]); + fault = ddi_get32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG)); + ssp->command_error = fault; + + if (fault >= LOMBUS_ERR_BASE) { + /* lombus problem - do a resync session */ + cmn_err(CE_WARN, "!bscv_get8_once: Had comms fault " + "for address 0x%x.%02x - data 0x%x, fault 0x%x", + addr >> 8, addr & 0xff, val, fault); + /* Attempt to resync with the lom */ + bscv_resync_comms(ssp, chan); + /* + * Note: we do not set fault status here. That + * is done if our caller decides to give up talking to + * the lom. The observant might notice that this means + * that if we mend things on the last attempt we still + * get the fault set - we just live with that! + */ + } + /* + * FIXME - should report error if you get + * EBUS_ERROR_DEVICEFAIL reported from the BSC. That gets + * logged as a failure in bscv_should_retry and may contribute + * to a permanent failure. Reference issues seen by Mitac. + */ + + if (!bscv_faulty(ssp)) { + if (addr < BSC_ADDR_CACHE_LIMIT) { + /* Store cacheable entries */ + ssp->lom_regs[addr] = val; + } + } + + bscv_trace(ssp, '@', "bscv_get8_once", + "addr 0x%x.%02x => 0x%02x", addr >> 8, addr & 0xff, val); + return (val); +} + +static uint32_t +bscv_probe(bscv_soft_state_t *ssp, int chan, uint32_t *fault) +{ + uint32_t async_reg; + + if (ssp->nchannels == 0) { + /* + * Failed to map handles, so cannot do any IO. Set the + * fault indicator and return a dummy value. + */ + bscv_trace(ssp, '@', "bscv_probe", + "nchannels is 0x0 so cannot do any IO"); + *fault = LOMBUS_ERR_REG_NUM; + return ((~(int8_t)0)); + } + + /* Clear faults */ + ddi_put32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_FAULT_REG), 0); + /* Probe and Check faults */ + *fault = ddi_get32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_PROBE_REG)); + /* Read status */ + async_reg = ddi_get32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, LOMBUS_ASYNC_REG)); + + bscv_trace(ssp, '@', "bscv_probe", + "async status 0x%x, fault 0x%x", async_reg, *fault); + return (async_reg); +} + +static void +bscv_resync_comms(bscv_soft_state_t *ssp, int chan) +{ + int try; + uint32_t command_error = ssp->command_error; + uint32_t fault = 0; + + if (ssp->nchannels == 0) { + /* + * Didn't manage to map handles so ddi_{get,put}* broken. + * Therefore, there is no way to resync comms. + */ + bscv_trace(ssp, '@', "bscv_resync_comms", + "nchannels is 0x0 so not possible to resync comms"); + return; + } + if (command_error >= LOMBUS_ERR_BASE && + command_error != LOMBUS_ERR_REG_NUM && + command_error != LOMBUS_ERR_REG_SIZE && + command_error != LOMBUS_ERR_TIMEOUT) { + /* Resync here to make sure that the lom is talking */ + cmn_err(CE_WARN, "!bscv_resync_comms: " + "Attempting comms resync after comms fault 0x%x", + command_error); + for (try = 1; try <= 8; try++) { + /* Probe */ + fault = ddi_get32(ssp->channel[chan].handle, + (uint32_t *)BSC_NEXUS_ADDR(ssp, chan, 0, + LOMBUS_PROBE_REG)); + + if (fault == 0) { + break; + } else { + cmn_err(CE_WARN, "!bscv_resync_comms: " + "comms resync (probing) - try 0x%x " + "had fault 0x%x", try, fault); + } + } + if (fault != 0) { + cmn_err(CE_WARN, "!bscv_resync_comms: " + "Failed to resync comms - giving up"); + ssp->bad_resync++; + } else { + cmn_err(CE_WARN, "!bscv_resync_comms: " + "resync comms after 0x%x tries", try); + ssp->bad_resync = 0; + } + } + +} + + +/* + * LOMLite configuration/event eeprom access routines + * + * bscv_window_setup() - Read/Sanity check the eeprom parameters. + * This must be called prior to calling bscv_eerw(). + * bscv_eerw() - Read/write data from/to the eeprom. + */ + +/* + * function - bscv_window_setup + * description - this routine reads the eeprom parameters and sanity + * checks them to ensure that the lom is talking sense. + * inputs - soft state ptr + * outputs - B_TRUE if the eeprom is ok, B_FALSE if the eeprom is not OK. + */ +static boolean_t +bscv_window_setup(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + + if (ssp->eeinfo_valid) { + /* Already have good cached values */ + return (ssp->eeinfo_valid); + } + ssp->eeprom_size = + bscv_get8(ssp, chan_general, EBUS_IDX_EEPROM_SIZE_KB) * 1024; + ssp->eventlog_start = bscv_get16(ssp, chan_general, + EBUS_IDX_LOG_START_HI); + + /* + * The log does not run to the end of the EEPROM because it is a + * logical partition. The last 8K partition is reserved for FRUID + * usage. + */ + ssp->eventlog_size = EBUS_LOG_END - ssp->eventlog_start; + + bscv_trace(ssp, 'I', "bscv_window_setup", "eeprom size 0x%x log_start" + " 0x%x log_size 0x%x", ssp->eeprom_size, ssp->eventlog_start, + ssp->eventlog_size); + + if (bscv_faulty(ssp) || bscv_session_error(ssp)) { + ssp->eeinfo_valid = B_FALSE; + } else if ((ssp->eeprom_size == 0) || + (ssp->eventlog_start >= ssp->eeprom_size)) { + /* Sanity check values */ + cmn_err(CE_WARN, + "!bscv_window_setup: read invalid eeprom parameters"); + ssp->eeinfo_valid = B_FALSE; + } else { + ssp->eeinfo_valid = B_TRUE; + } + + bscv_trace(ssp, 'I', "bscv_window_setup", "returning eeinfo_valid %s", + ssp->eeinfo_valid ? "true" : "false"); + return (ssp->eeinfo_valid); +} + +/* + * function - bscv_eerw + * description - this routine reads/write data from/to the eeprom. + * It takes care of setting the window on the eeprom correctly. + * inputs - soft state ptr, eeprom offset, data buffer, size, read/write + * outputs - B_TRUE if the eeprom is ok, B_FALSE if the eeprom is not OK. + */ +static int +bscv_eerw(bscv_soft_state_t *ssp, uint32_t eeoffset, uint8_t *buf, + unsigned size, boolean_t is_write) +{ + uint32_t blk_addr = eeoffset; + unsigned remaining = size; + uint8_t page_idx; + uint8_t this_page; + uint8_t blk_size; + int res = 0; + + while (remaining > 0) { + page_idx = blk_addr & 0xff; + if ((page_idx + remaining) > 0x100) { + blk_size = 0x100 - page_idx; + } else { + blk_size = remaining; + } + + /* Select correct eeprom page */ + this_page = blk_addr >> 8; + bscv_put8(ssp, chan_eeprom, EBUS_IDX_EEPROM_PAGESEL, this_page); + + bscv_trace(ssp, 'M', "lom_eerw", + "%s data @0x%x.%02x, size 0x%x, 0x%x bytes remaining", + is_write ? "writing" : "reading", + this_page, page_idx, blk_size, remaining - blk_size); + + bscv_rep_rw8(ssp, chan_eeprom, + buf, BSCVA(EBUS_CMD_SPACE_EEPROM, page_idx), + blk_size, DDI_DEV_AUTOINCR, is_write); + + if (bscv_faulty(ssp) || bscv_session_error(ssp)) { + res = EIO; + break; + } + + remaining -= blk_size; + blk_addr += blk_size; + buf += blk_size; + } + + return (res); +} + +static boolean_t +bscv_is_null_event(bscv_soft_state_t *ssp, lom_event_t *e) +{ + ASSERT(e != NULL); + + if (EVENT_DECODE_SUBSYS(e->ev_subsys) == EVENT_SUBSYS_NONE && + e->ev_event == EVENT_NONE) { + /* + * This marks a NULL event. + */ + bscv_trace(ssp, 'E', "bscv_is_null_event", + "EVENT_SUBSYS_NONE/EVENT_NONE null event"); + return (B_TRUE); + } else if (e->ev_subsys == 0xff && e->ev_event == 0xff) { + /* + * Under some circumstances, we've seen all 1s to represent + * a manually cleared event log at the BSC prompt. Only + * a test/diagnosis environment is likely to show this. + */ + bscv_trace(ssp, 'E', "bscv_is_null_event", "0xffff null event"); + return (B_TRUE); + } else { + /* + * Not a NULL event. + */ + bscv_trace(ssp, 'E', "bscv_is_null_event", "returning False"); + return (B_FALSE); + } +} + +/* + * ********************************************************************* + * IOCTL Processing + * ********************************************************************* + */ + +/* + * function - bscv_ioctl + * description - routine that acts as a high level manager for ioctls. It + * calls the appropriate handler for ioctls on the alarm:mon and + * alarm:ctl minor nodes respectively + * + * Unsupported ioctls (now deprecated) + * LOMIOCALCTL + * LOMIOCALSTATE + * LOMIOCCLEARLOG + * LOMIOCCTL + * LOMIOCCTL2 + * LOMIOCDAEMON + * LOMIOCDMON + * LOMIOCDOGCTL, TSIOCDOGCTL + * LOMIOCDOGPAT, TSIOCDOGPAT + * LOMIOCDOGTIME, TSIOCDOGTIME + * LOMIOCEVENTLOG + * LOMIOCEVNT + * LOMIOCGETMASK + * LOMIOCMPROG + * LOMIOCNBMON, TSIOCNBMON + * LOMIOCSLEEP + * LOMIOCUNLOCK, TSIOCUNLOCK + * LOMIOCWTMON, TSIOCWTMON + * + * Supported ioctls + * LOMIOCDOGSTATE, TSIOCDOGSTATE + * LOMIOCPROG + * LOMIOCPSUSTATE + * LOMIOCFANSTATE + * LOMIOCFLEDSTATE + * LOMIOCINFO + * LOMIOCMREAD + * LOMIOCVOLTS + * LOMIOCSTATS + * LOMIOCTEMP + * LOMIOCCONS + * LOMIOCEVENTLOG2 + * LOMIOCINFO2 + * LOMIOCTEST + * LOMIOCMPROG2 + * LOMIOCMREAD2 + * + * inputs - device number, command, user space arg, filemode, user + * credentials, return value + * outputs - the return value propagated back by the lower level routines. + */ + +/*ARGSUSED*/ +static int +bscv_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cred, int *rvalp) +{ + bscv_soft_state_t *ssp; + int instance; + int res = 0; + + instance = DEVICETOINSTANCE(dev); + ssp = ddi_get_soft_state(bscv_statep, instance); + if (ssp == NULL) { + return (ENXIO); + } + + /* + * The Combined Switch and Service Processor takes care of configuration + * and control. The CSSP tells the BSC chip about it; therefore the + * bscv driver doesn't send such configuration and control to the BSC. + * Additionally Watchdog configuration is no longer done from userland + * lom. + */ + switch (cmd) { + case LOMIOCALCTL: + case LOMIOCALSTATE: + case LOMIOCCLEARLOG: + case LOMIOCCTL: + case LOMIOCCTL2: + case LOMIOCDAEMON: + case LOMIOCDMON: + case LOMIOCDOGCTL: + case LOMIOCDOGPAT: + case LOMIOCDOGTIME: + case LOMIOCEVENTLOG: + case LOMIOCEVNT: + case LOMIOCGETMASK: + case LOMIOCMPROG: + case LOMIOCNBMON: + case LOMIOCSLEEP: + case LOMIOCUNLOCK: + case LOMIOCWTMON: + return (ENOTSUP); + } + + /* + * set the default result. + */ + + *rvalp = 0; + + if (ssp->cssp_prog) { + return (ENXIO); + } else if ((ssp->prog_mode_only || ssp->programming) && + cmd != LOMIOCPROG) { + return (ENXIO); + } + + /* + * Check that the caller has appropriate access permissions + * (FWRITE set in mode) for those ioctls which change lom + * state + */ + if (!(mode & FWRITE)) { + switch (cmd) { + case LOMIOCMPROG2: + case LOMIOCMREAD2: + case LOMIOCPROG: + case LOMIOCTEST: + return (EACCES); + /* NOTREACHED */ + default: + /* Does not require write access */ + break; + } + } + + switch (cmd) { + + case LOMIOCDOGSTATE: + res = bscv_ioc_dogstate(ssp, arg, mode); + break; + + case LOMIOCPROG: + res = bscv_prog(ssp, arg, mode); + break; + + case LOMIOCPSUSTATE: + res = bscv_ioc_psustate(ssp, arg, mode); + break; + + case LOMIOCFANSTATE: + res = bscv_ioc_fanstate(ssp, arg, mode); + break; + + case LOMIOCFLEDSTATE: + res = bscv_ioc_fledstate(ssp, arg, mode); + break; + + case LOMIOCLEDSTATE: + res = bscv_ioc_ledstate(ssp, arg, mode); + break; + + case LOMIOCINFO: + res = bscv_ioc_info(ssp, arg, mode); + break; + + case LOMIOCMREAD: + res = bscv_ioc_mread(ssp, arg, mode); + break; + + case LOMIOCVOLTS: + res = bscv_ioc_volts(ssp, arg, mode); + break; + + case LOMIOCSTATS: + res = bscv_ioc_stats(ssp, arg, mode); + break; + + case LOMIOCTEMP: + res = bscv_ioc_temp(ssp, arg, mode); + break; + + case LOMIOCCONS: + res = bscv_ioc_cons(ssp, arg, mode); + break; + + case LOMIOCEVENTLOG2: + res = bscv_ioc_eventlog2(ssp, arg, mode); + break; + + case LOMIOCINFO2: + res = bscv_ioc_info2(ssp, arg, mode); + break; + + case LOMIOCTEST: + res = bscv_ioc_test(ssp, arg, mode); + break; + + case LOMIOCMPROG2: + res = bscv_ioc_mprog2(ssp, arg, mode); + break; + + case LOMIOCMREAD2: + res = bscv_ioc_mread2(ssp, arg, mode); + break; + + default: + bscv_trace(ssp, 'I', "bscv_ioctl", "Invalid IOCTL 0x%x", cmd); + res = EINVAL; + } + return (res); +} + +/* + * LOMIOCDOGSTATE + * TSIOCDOGSTATE - indicate whether the alarm watchdog and reset + * circuitry is enabled or not. + */ +static int +bscv_ioc_dogstate(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_dogstate_t dogstate; + uint8_t dogval; + int res = 0; + + dogval = bscv_get8_locked(ssp, chan_general, EBUS_IDX_WDOG_CTRL, &res); + dogstate.dog_enable = (dogval & EBUS_WDOG_ENABLE) ? 1 : 0; + dogstate.reset_enable = (dogval & EBUS_WDOG_RST) ? 1 : 0; + dogstate.dog_timeout = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_WDOG_TIME, &res); + + if ((res == 0) && + (ddi_copyout((caddr_t)&dogstate, + (caddr_t)arg, sizeof (dogstate), mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCPSUSTATE - returns full information for 4 PSUs. All this + * information is available from two bytes of LOMlite RAM, but if + * on the first read it is noticed that two or more of the PSUs are + * not present only 1 byte will be read subsequently. + */ +static int +bscv_ioc_psustate(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_psudata_t psudata; + uint8_t psustat; + int i; + int res = 0; + + for (i = 0; i < MAX_PSUS; i++) { + psustat = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_PSU1_STAT + i, &res); + psudata.fitted[i] = psustat & EBUS_PSU_PRESENT; + psudata.output[i] = psustat & EBUS_PSU_OUTPUT; + psudata.supplyb[i] = psustat & EBUS_PSU_INPUTB; + psudata.supplya[i] = psustat & EBUS_PSU_INPUTA; + psudata.standby[i] = psustat & EBUS_PSU_STANDBY; + } + + if (ddi_copyout((caddr_t)&psudata, (caddr_t)arg, sizeof (psudata), + mode) < 0) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCFANSTATE - returns full information including speed for 4 + * fans and the minimum and maximum operating speeds for each fan as + * stored in the READ ONLY EEPROM data. As this EEPROM data is set + * at manufacture time, this data should only be read by the driver + * once and stored locally. + */ +static int +bscv_ioc_fanstate(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_fandata_t fandata; + int numfans; + int i; + int res = 0; + + bzero(&fandata, sizeof (lom_fandata_t)); + numfans = EBUS_CONFIG_NFAN_DEC(bscv_get8_locked(ssp, + chan_general, EBUS_IDX_CONFIG, &res)); + for (i = 0; (i < numfans) && (res == 0); i++) { + if (ssp->fanspeed[i] != LOM_FAN_NOT_PRESENT) { + fandata.fitted[i] = 1; + fandata.speed[i] = ssp->fanspeed[i]; + fandata.minspeed[i] = bscv_get8_cached(ssp, + EBUS_IDX_FAN1_LOW + i); + } + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&fandata, (caddr_t)arg, sizeof (fandata), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCFLEDSTATE - returns the state of the fault LED + */ +static int +bscv_ioc_fledstate(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_fled_info_t fled_info; + uint8_t fledstate; + int res = 0; + + fledstate = bscv_get8_locked(ssp, chan_general, EBUS_IDX_ALARM, &res); + + /* Decode of 0x0F is off and 0x00-0x07 is on. */ + if (EBUS_ALARM_LED_DEC(fledstate) == 0x0F) { + fled_info.on = 0; + } else { + /* has +1 here - not 2 as in the info ioctl */ + fled_info.on = EBUS_ALARM_LED_DEC(fledstate) + 1; + } + if ((res == 0) && + (ddi_copyout((caddr_t)&fled_info, (caddr_t)arg, + sizeof (fled_info), mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCLEDSTATE - returns the state of the requested LED + */ +static int +bscv_ioc_ledstate(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_led_state_t led_state; + int fw_led_state; + int res = 0; + + /* copy in arguments supplied */ + if (ddi_copyin((caddr_t)arg, (caddr_t)&led_state, + sizeof (lom_led_state_t), mode) < 0) { + return (EFAULT); + } + + /* + * check if led index is -1, if so set it to max value for + * this implementation. + */ + if (led_state.index == -1) { + led_state.index = MAX_LED_ID; + } + + /* is the index in a valid range */ + if ((led_state.index > MAX_LED_ID) || (led_state.index < 0)) { + led_state.state = LOM_LED_OUTOFRANGE; + } else { + /* read the relevant led info */ + fw_led_state = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_LED1_STATUS + led_state.index, &res); + + /* set the state values accordingly */ + switch (fw_led_state) { + case LOM_LED_STATE_OFF: + led_state.state = LOM_LED_OFF; + led_state.colour = LOM_LED_COLOUR_ANY; + break; + case LOM_LED_STATE_ON_STEADY: + led_state.state = LOM_LED_ON; + led_state.colour = LOM_LED_COLOUR_ANY; + break; + case LOM_LED_STATE_ON_FLASHING: + case LOM_LED_STATE_ON_SLOWFLASH: + led_state.state = LOM_LED_BLINKING; + led_state.colour = LOM_LED_COLOUR_ANY; + break; + case LOM_LED_STATE_NOT_PRESENT: + led_state.state = LOM_LED_NOT_IMPLEMENTED; + led_state.colour = LOM_LED_COLOUR_NONE; + break; + case LOM_LED_STATE_INACCESSIBLE: + case LOM_LED_STATE_STANDBY: + default: + led_state.state = LOM_LED_ACCESS_ERROR; + led_state.colour = LOM_LED_COLOUR_NONE; + break; + } + + /* set the label info */ + (void) strcpy(led_state.label, + ssp->led_names[led_state.index]); + } + + /* copy out lom_state */ + if ((res == 0) && + (ddi_copyout((caddr_t)&led_state, (caddr_t)arg, + sizeof (lom_led_state_t), mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCINFO - returns with a structure containing any information + * stored on the LOMlite which a user should not need to access but + * may be useful for diagnostic problems. The structure contains: the + * serial escape character, alarm3 mode, version and checksum read from + * RAM and the Product revision and ID read from EEPROM. + */ +static int +bscv_ioc_info(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_info_t info; + int i; + uint16_t csum; + int res = 0; + + info.ser_char = bscv_get8_locked(ssp, chan_general, EBUS_IDX_ESCAPE, + &res); + info.a3mode = WATCHDOG; + info.fver = bscv_get8_locked(ssp, chan_general, EBUS_IDX_FW_REV, &res); + csum = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_HI, &res) + << 8; + csum |= bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_LO, &res); + info.fchksum = csum; + info.prod_rev = bscv_get8_locked(ssp, chan_general, EBUS_IDX_MODEL_REV, + &res); + for (i = 0; i < sizeof (info.prod_id); i++) { + info.prod_id[i] = bscv_get8_locked(ssp, + chan_general, EBUS_IDX_MODEL_ID1 + i, &res); + } + if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_ALARM, &res) & + EBUS_ALARM_NOEVENTS) { + info.events = OFF; + } else { + info.events = ON; + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&info, (caddr_t)arg, sizeof (info), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCMREAD - used to query the LOMlite configuration parameters + */ +static int +bscv_ioc_mread(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_mprog_t mprog; + int i; + int fanz; + int res = 0; + + for (i = 0; i < sizeof (mprog.mod_id); i++) { + mprog.mod_id[i] = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_MODEL_ID1 + i, &res); + } + mprog.mod_rev = bscv_get8_locked(ssp, chan_general, EBUS_IDX_MODEL_REV, + &res); + mprog.config = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CONFIG, + &res); + + /* Read the fan calibration values */ + fanz = sizeof (mprog.fanhz) / sizeof (mprog.fanhz[0]); + for (i = 0; i < fanz; i++) { + mprog.fanhz[i] = bscv_get8_cached(ssp, + EBUS_IDX_FAN1_CAL + i); + mprog.fanmin[i] = bscv_get8_cached(ssp, + EBUS_IDX_FAN1_LOW + i); + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&mprog, (caddr_t)arg, sizeof (mprog), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCVOLTS + */ +static int +bscv_ioc_volts(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + int i; + uint16_t supply; + int res = 0; + + supply = (bscv_get8_locked(ssp, chan_general, EBUS_IDX_SUPPLY_HI, &res) + << 8) | bscv_get8_locked(ssp, chan_general, EBUS_IDX_SUPPLY_LO, + &res); + + for (i = 0; i < ssp->volts.num; i++) { + ssp->volts.status[i] = (supply >> i) & 1; + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&ssp->volts, (caddr_t)arg, + sizeof (ssp->volts), mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCSTATS + */ +static int +bscv_ioc_stats(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + int i; + uint8_t status; + int res = 0; + + status = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CBREAK_STATUS, + &res); + for (i = 0; i < ssp->sflags.num; i++) { + ssp->sflags.status[i] = (int)((status >> i) & 1); + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&ssp->sflags, (caddr_t)arg, + sizeof (ssp->sflags), mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCTEMP + */ +static int +bscv_ioc_temp(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + int i; + int idx; + uint8_t status_ov; + lom_temp_t temps; + int res = 0; + + bzero(&temps, sizeof (temps)); + idx = 0; + for (i = 0; i < ssp->temps.num; i++) { + if (ssp->temps.temp[i] != LOM_TEMP_STATE_NOT_PRESENT) { + temps.temp[idx] = ssp->temps.temp[i]; + bcopy(ssp->temps.name[i], temps.name[idx], + sizeof (temps.name[idx])); + temps.warning[idx] = ssp->temps.warning[i]; + temps.shutdown[idx] = ssp->temps.shutdown[i]; + idx++; + } + } + temps.num = idx; + + bcopy(ssp->temps.name_ov, temps.name_ov, sizeof (temps.name_ov)); + temps.num_ov = ssp->temps.num_ov; + status_ov = bscv_get8_locked(ssp, chan_general, EBUS_IDX_OTEMP_STATUS, + &res); + for (i = 0; i < ssp->temps.num_ov; i++) { + ssp->temps.status_ov[i] = (status_ov >> i) & 1; + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&temps, (caddr_t)arg, sizeof (temps), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCCONS + */ +static int +bscv_ioc_cons(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_cbuf_t cbuf; + int datasize; + int res = 0; + + bzero(&cbuf, sizeof (cbuf)); + datasize = EBUS_IDX1_CONS_BUF_END - EBUS_IDX1_CONS_BUF_START + 1; + /* Ensure that we do not overfill cbuf and that it is NUL terminated */ + if (datasize > (sizeof (cbuf) - 1)) { + datasize = sizeof (cbuf) - 1; + } + bscv_rep_get8_locked(ssp, chan_general, (uint8_t *)cbuf.lrbuf, + BSCVA(EBUS_CMD_SPACE1, (EBUS_IDX1_CONS_BUF_END - datasize + 1)), + datasize, DDI_DEV_AUTOINCR, &res); + /* This is always within the array due to the checks above */ + cbuf.lrbuf[datasize] = '\0'; + + if ((res == 0) && + (ddi_copyout((caddr_t)&cbuf, (caddr_t)arg, sizeof (cbuf), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCEVENTLOG2 + */ +static int +bscv_ioc_eventlog2(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_eventlog2_t *eventlog2; + int events_recorded; + int level; + uint16_t next_offset; + lom_event_t event; + int res = 0; + + eventlog2 = (lom_eventlog2_t *)kmem_zalloc(sizeof (*eventlog2), + KM_SLEEP); + + /* + * First get number of events and level requested. + */ + + if (ddi_copyin((caddr_t)arg, (caddr_t)eventlog2, + sizeof (lom_eventlog2_t), mode) < 0) { + kmem_free((void *)eventlog2, sizeof (*eventlog2)); + return (EFAULT); + } + + bscv_enter(ssp); + + /* + * OK we have full private access to the LOM now so loop + * over the eventlog addr spaces until we get the required + * number of events. + */ + + if (!bscv_window_setup(ssp)) { + res = EIO; + bscv_exit(ssp); + kmem_free((void *)eventlog2, sizeof (*eventlog2)); + return (res); + } + + /* + * Read count, next event ptr MSB,LSB. Note a read of count + * is necessary to latch values for the next event ptr + */ + (void) bscv_get8(ssp, chan_general, EBUS_IDX_UNREAD_EVENTS); + next_offset = bscv_get16(ssp, chan_general, EBUS_IDX_LOG_PTR_HI); + bscv_trace(ssp, 'I', "bscv_ioc_eventlog2", "log_ptr_hi 0x%x", + next_offset); + + events_recorded = 0; + + while (events_recorded < eventlog2->num) { + /* + * Working backwards - read an event at a time. + * next_offset is one event on from where we want to be! + * Decrement next_offset and maybe wrap to the end of the + * buffer. + * Note the unsigned arithmetic, so check values first! + */ + if (next_offset <= ssp->eventlog_start) { + /* Wrap to the end of the buffer */ + next_offset = ssp->eventlog_start + ssp->eventlog_size; + bscv_trace(ssp, 'I', "bscv_ioc_eventlog2", "wrapping" + " around to end of buffer; next_offset 0x%x", + next_offset); + } + next_offset -= sizeof (event); + + if (bscv_eerw(ssp, next_offset, (uint8_t *)&event, + sizeof (event), B_FALSE /* read */) != 0) { + /* Fault reading data - stop */ + bscv_trace(ssp, 'I', "bscv_ioc_eventlog2", "read" + " failure for offset 0x%x", next_offset); + res = EIO; + break; + } + + if (bscv_is_null_event(ssp, &event)) { + /* + * No more events in this log so give up. + */ + bscv_trace(ssp, 'I', "bscv_ioc_eventlog2", "no more" + " events left at offset 0x%x", next_offset); + break; + } + + /* + * Are we interested in this event + */ + + level = bscv_level_of_event(&event); + if (level <= eventlog2->level) { + /* Arggh why the funny byte ordering 3, 2, 0, 1 */ + eventlog2->code[events_recorded] = + ((unsigned)event.ev_event | + ((unsigned)event.ev_subsys << 8) | + ((unsigned)event.ev_resource << 16) | + ((unsigned)event.ev_detail << 24)); + + eventlog2->time[events_recorded] = + ((unsigned)event.ev_data[0] | + ((unsigned)event.ev_data[1] << 8) | + ((unsigned)event.ev_data[3] << 16) | + ((unsigned)event.ev_data[2] << 24)); + + bscv_build_eventstring(ssp, + &event, eventlog2->string[events_recorded], + eventlog2->string[events_recorded] + + sizeof (eventlog2->string[events_recorded])); + events_recorded++; + } + } + + eventlog2->num = events_recorded; + + bscv_exit(ssp); + + if ((res == 0) && + (ddi_copyout((caddr_t)eventlog2, (caddr_t)arg, + sizeof (lom_eventlog2_t), mode) < 0)) { + res = EFAULT; + } + + kmem_free((void *)eventlog2, sizeof (lom_eventlog2_t)); + return (res); +} + +/* + * LOMIOCINFO2 + */ +static int +bscv_ioc_info2(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom2_info_t info2; + int i; + uint16_t csum; + int res = 0; + + bzero(&info2, sizeof (info2)); + + (void) strncpy(info2.escape_chars, ssp->escape_chars, + sizeof (info2.escape_chars)); + info2.serial_events = ssp->reporting_level | ssp->serial_reporting; + info2.a3mode = WATCHDOG; + + info2.fver = bscv_get8_locked(ssp, chan_general, EBUS_IDX_FW_REV, &res); + csum = bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_HI, &res) + << 8; + csum |= bscv_get8_locked(ssp, chan_general, EBUS_IDX_CHECK_LO, &res); + info2.fchksum = csum; + info2.prod_rev = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_MODEL_REV, &res); + for (i = 0; i < sizeof (info2.prod_id); i++) { + info2.prod_id[i] = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_MODEL_ID1 + i, &res); + } + info2.serial_config = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_SER_TIMEOUT, &res); + if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_CONFIG_MISC, &res) & + EBUS_CONFIG_MISC_SECURITY_ENABLED) { + info2.serial_config |= LOM_SER_SECURITY; + } + if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_CONFIG_MISC, &res) & + EBUS_CONFIG_MISC_AUTO_CONSOLE) { + info2.serial_config |= LOM_SER_RETURN; + } + if (bscv_get8_locked(ssp, chan_general, EBUS_IDX_WDOG_CTRL, &res) & + EBUS_WDOG_BREAK_DISABLE) { + info2.serial_config |= LOM_DISABLE_WDOG_BREAK; + } + info2.baud_rate = bscv_get8_locked(ssp, chan_general, + EBUS_IDX_SER_BAUD, &res); + info2.serial_hw_config = + ((int)bscv_get8_locked(ssp, chan_general, + EBUS_IDX_SER_CHARMODE, &res) | + ((int)bscv_get8_locked(ssp, chan_general, + EBUS_IDX_SER_FLOWCTL, &res) << 8) | + ((int)bscv_get8_locked(ssp, chan_general, + EBUS_IDX_SER_MODEMTYPE, &res) << 16)); + + /* + * There is no phone home support on the blade platform. We hardcode + * FALSE and NUL for config and script respectively. + */ + info2.phone_home_config = B_FALSE; + info2.phone_home_script[0] = '\0'; + + for (i = 0; i < ssp->num_fans; i++) { + (void) strcpy(info2.fan_names[i], ssp->fan_names[i]); + } + + if ((res == 0) && + (ddi_copyout((caddr_t)&info2, (caddr_t)arg, sizeof (info2), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCTEST + */ +static int +bscv_ioc_test(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + uint32_t test; + uint8_t testnum; + uint8_t testarg; + int res = 0; + + if (ddi_copyin((caddr_t)arg, (caddr_t)&test, sizeof (test), + mode) < 0) { + return (EFAULT); + } + + /* + * Extract num iterations. + */ + + testarg = (test & 0xff00) >> 8; + testnum = test & 0xff; + + bscv_trace(ssp, 'F', "bscv_ioc_test", + "LOMIOCTEST data 0x%x (test 0x%x, arg 0x%x)", + test, (EBUS_IDX_SELFTEST0 + testnum), testarg); + + switch (testnum + EBUS_IDX_SELFTEST0) { + default: + /* Invalid test */ + res = EINVAL; + break; + + case EBUS_IDX_SELFTEST0: /* power on self-test result */ + case EBUS_IDX_SELFTEST1: /* not used currently */ + case EBUS_IDX_SELFTEST2: /* not used currently */ + case EBUS_IDX_SELFTEST3: /* not used currently */ + case EBUS_IDX_SELFTEST4: /* not used currently */ + case EBUS_IDX_SELFTEST5: /* not used currently */ + case EBUS_IDX_SELFTEST6: /* LED self-test */ + case EBUS_IDX_SELFTEST7: /* platform-specific tests */ + /* Run the test */ + + /* Stop other things and then run the test */ + bscv_enter(ssp); + + /* + * Then we simply write the argument to the relevant register + * and wait for the return code. + */ + bscv_put8(ssp, chan_general, + EBUS_IDX_SELFTEST0 + testnum, testarg); + if (bscv_faulty(ssp)) { + res = EIO; + } else { + /* Get hold of the SunVTS error code */ + test = bscv_retcode(ssp); + } + + bscv_exit(ssp); + break; + } + + bscv_trace(ssp, 'F', "bscv_ioc_test", + "LOMIOCTEST status 0x%x, res 0x%x", test, res); + if ((res == 0) && + (ddi_copyout((caddr_t)&test, (caddr_t)arg, sizeof (test), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +/* + * LOMIOCMPROG2 + */ +static int +bscv_ioc_mprog2(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom2_mprog_t mprog2; + uint32_t base_addr; + uint32_t data_size; + uint32_t eeprom_size; + int res = 0; + + if (ddi_copyin((caddr_t)arg, (caddr_t)&mprog2, sizeof (mprog2), + mode) < 0) { + return (EFAULT); + } + + /* + * Note that originally this was accessed as 255 byte pages + * in address spaces 240-255. We have to emulate this behaviour. + */ + if ((mprog2.addr_space < 240) || (mprog2.addr_space > 255)) { + return (EINVAL); + } + + bscv_enter(ssp); + + /* Calculate required data location */ + data_size = 255; + base_addr = (mprog2.addr_space - 240) * data_size; + + eeprom_size = bscv_get8(ssp, chan_general, EBUS_IDX_EEPROM_SIZE_KB) * + 1024; + + if (bscv_faulty(ssp)) { + bscv_exit(ssp); + return (EIO); + } else if ((base_addr + data_size) > eeprom_size) { + bscv_trace(ssp, 'M', "bscv_ioc_mprog2", + "Request extends past end of eeprom"); + bscv_exit(ssp); + return (ENXIO); + } + + bscv_put8(ssp, chan_general, EBUS_IDX_CMD_RES, EBUS_CMD_UNLOCK1); + if (bscv_faulty(ssp)) { + bscv_trace(ssp, 'M', "bscv_ioc_mprog2", "ML1 Write failed"); + bscv_exit(ssp); + return (EIO); + } + + bscv_put8(ssp, chan_general, EBUS_IDX_CMD_RES, EBUS_CMD_UNLOCK2); + if (bscv_faulty(ssp)) { + bscv_trace(ssp, 'M', "bscv_ioc_mprog2", "ML2 Write failed"); + bscv_exit(ssp); + return (EIO); + } + + if (bscv_eerw(ssp, base_addr, &mprog2.data[0], + data_size, B_TRUE /* write */) != 0) { + res = EIO; + } + + /* Read a probe key to release the lock. */ + (void) bscv_get8(ssp, chan_general, EBUS_IDX_PROBEAA); + + if (bscv_faulty(ssp)) { + res = EIO; + } + bscv_exit(ssp); + + return (res); +} + +/* + * LOMIOCMREAD2 + */ +static int +bscv_ioc_mread2(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom2_mprog_t mprog2; + uint32_t base_addr; + uint32_t data_size; + uint32_t eeprom_size; + int res = 0; + + if (ddi_copyin((caddr_t)arg, (caddr_t)&mprog2, sizeof (mprog2), + mode) < 0) { + return (EFAULT); + } + + /* + * Need to stop the queue and then just read + * the bytes blind to the relevant addresses. + * Note that originally this was accessed as 255 byte pages + * in address spaces 240-255. We have to emulate this behaviour. + */ + if ((mprog2.addr_space < 240) || (mprog2.addr_space > 255)) { + return (EINVAL); + } + + bscv_enter(ssp); + + /* Calculate required data location */ + data_size = 255; + base_addr = (mprog2.addr_space - 240) * data_size; + eeprom_size = bscv_get8(ssp, chan_general, EBUS_IDX_EEPROM_SIZE_KB) * + 1024; + + if (bscv_faulty(ssp)) { + bscv_exit(ssp); + return (EIO); + } else if ((base_addr + data_size) > eeprom_size) { + bscv_trace(ssp, 'M', "bscv_ioc_mread2", + "Request extends past end of eeprom"); + bscv_exit(ssp); + return (ENXIO); + } + + if (bscv_eerw(ssp, base_addr, &mprog2.data[0], + data_size, B_FALSE /* read */) != 0) { + res = EIO; + } + + if (bscv_faulty(ssp)) { + res = EIO; + } + bscv_exit(ssp); + + if ((res == 0) && + (ddi_copyout((caddr_t)&mprog2, (caddr_t)arg, sizeof (mprog2), + mode) < 0)) { + res = EFAULT; + } + return (res); +} + +static void +bscv_get_state_changes(bscv_soft_state_t *ssp) +{ + int i = STATUS_READ_LIMIT; + uint8_t change; + uint8_t detail; + + ASSERT(bscv_held(ssp)); + + while (i-- && !ssp->cssp_prog) { + /* Are there any changes to process? */ + change = bscv_get8(ssp, chan_general, EBUS_IDX_STATE_CHNG); + change &= EBUS_STATE_MASK; + if (!change) + break; + + /* Clarify the pending change */ + detail = bscv_get8(ssp, chan_general, EBUS_IDX_EVENT_DETAIL); + + bscv_status(ssp, change, detail); + } + + bscv_trace(ssp, 'D', "bscv_get_state_changes", + "loop index %d ssp->cssp_prog 0x%x", i, ssp->cssp_prog); +} + +/* + * ********************************************************************* + * Event Processing + * ********************************************************************* + */ + +/* + * function - bscv_event_daemon + * description - Perform periodic lom tasks in a separate thread. + * inputs - LOM soft state structure pointer + * outputs - none. + */ +static void +bscv_event_daemon(void *arg) +{ + bscv_soft_state_t *ssp = (void *)arg; + boolean_t do_events; + boolean_t do_status; + boolean_t do_nodename; + boolean_t do_watchdog; + uint32_t async_reg; + uint32_t fault; + clock_t poll_period = BSC_EVENT_POLL_NORMAL; + int fault_cnt = 0; + + bscv_trace(ssp, 'D', "bscv_event_daemon", + "bscv_event_daemon: started"); + + /* Acquire task daemon lock. */ + mutex_enter(&ssp->task_mu); + + ssp->task_flags |= TASK_ALIVE_FLG; + + for (;;) { + if ((ssp->task_flags & TASK_STOP_FLG) != 0) { + /* Stop request seen - terminate */ + break; + } + if ((ssp->task_flags & TASK_PAUSE_FLG) == 0) { + /* Poll for events reported to the nexus */ + mutex_exit(&ssp->task_mu); + /* Probe and Check faults */ + bscv_enter(ssp); + async_reg = bscv_probe(ssp, chan_general, &fault); + bscv_trace(ssp, 'D', "bscv_event_daemon", + "process event: async_reg 0x%x, fault 0x%x", + async_reg, fault); + + if (!fault) { + /* Treat non-fault conditions */ + + if (ssp->cssp_prog || ssp->prog_mode_only) { + /* + * The BSC has become available again. + */ + fault_cnt = 0; + ssp->cssp_prog = B_FALSE; + ssp->prog_mode_only = B_FALSE; + (void) bscv_attach_common(ssp); + } else if (fault_cnt > 0) { + /* Previous fault has cleared */ + bscv_clear_fault(ssp); + fault_cnt = 0; + cmn_err(CE_WARN, + "!bscv_event_daemon previous fault " + "cleared."); + } else if (bscv_faulty(ssp)) { + /* Previous fault has cleared */ + bscv_clear_fault(ssp); + /* Sleep to avoid busy waiting */ + ssp->event_sleep = B_TRUE; + } + poll_period = BSC_EVENT_POLL_NORMAL; + + if (async_reg) { + ssp->status_change = B_TRUE; + ssp->event_waiting = B_TRUE; + } + } else if (ssp->cssp_prog) { + /* + * Expect radio silence or error values + * when the CSSP is upgrading the BSC firmware + * so throw away any fault indication. + */ + fault = B_FALSE; + } else if (fault_cnt == BSC_PROBE_FAULT_LIMIT) { + /* Count previous faults and maybe fail */ + /* Declare the lom broken */ + bscv_set_fault(ssp); + poll_period = BSC_EVENT_POLL_FAULTY; + cmn_err(CE_WARN, + "!bscv_event_daemon had faults probing " + "lom - marking it as faulty."); + /* + * Increment fault_cnt to ensure that + * next time we do not report a message + * i.e. we drop out of the bottom + */ + fault_cnt = BSC_PROBE_FAULT_LIMIT + 1; + ssp->event_sleep = B_TRUE; + } else if (fault_cnt < BSC_PROBE_FAULT_LIMIT) { + if (bscv_faulty(ssp)) { + poll_period = BSC_EVENT_POLL_FAULTY; + /* + * No recovery messages in this case + * because there was never a fault + * message here. + */ + fault_cnt = 0; + } else { + /* Getting ready to explode */ + fault_cnt++; + cmn_err(CE_WARN, + "!bscv_event_daemon had fault 0x%x", + fault); + } + ssp->event_sleep = B_TRUE; + } + bscv_exit(ssp); + mutex_enter(&ssp->task_mu); + } + +#if defined(__i386) || defined(__amd64) + /* + * we have no platmod hook on Solaris x86 to report + * a change to the nodename so we keep a copy so + * we can detect a change and request that the bsc + * be updated when appropriate. + */ + if (strcmp(ssp->last_nodename, utsname.nodename) != 0) { + + bscv_trace(ssp, 'X', "bscv_event_daemon", + "utsname.nodename='%s' possible change detected", + utsname.nodename); + ssp->nodename_change = B_TRUE; + (void) strncpy(ssp->last_nodename, utsname.nodename, + sizeof (ssp->last_nodename)); + /* enforce null termination */ + ssp->last_nodename[sizeof (ssp->last_nodename) - 1] = + '\0'; + } +#endif /* __i386 || __amd64 */ + + if (((ssp->task_flags & TASK_PAUSE_FLG) == 0) && + fault_cnt == 0 && ssp->cssp_prog == B_FALSE && + (ssp->event_waiting || ssp->status_change || + ssp->nodename_change || ssp->watchdog_change)) { + + do_events = ssp->event_waiting; + ssp->event_waiting = B_FALSE; + ssp->task_flags |= do_events ? + TASK_EVENT_PENDING_FLG : 0; + do_status = ssp->status_change; + ssp->status_change = B_FALSE; + do_nodename = ssp->nodename_change; + ssp->nodename_change = B_FALSE; + do_watchdog = ssp->watchdog_change; + if (ssp->watchdog_change) { + ssp->watchdog_change = B_FALSE; + } + + mutex_exit(&ssp->task_mu); + /* + * We must not hold task_mu whilst processing + * events because this can lead to priority + * inversion and hence our interrupts getting + * locked out. + */ + bscv_enter(ssp); + if (do_events) { + bscv_event_process(ssp, do_events); + } + if (do_nodename) { + bscv_trace(ssp, 'D', "bscv_event_daemon", + "do_nodename task"); + bscv_setup_hostname(ssp); + } + if (do_watchdog) { + bscv_trace(ssp, 'D', "bscv_event_daemon", + "do_watchdog task"); + bscv_setup_watchdog(ssp); + } + /* + * Pending status changes are dealt with last because + * if we see that the BSC is about to be programmed, + * then it will expect us to to quiescent in the + * first second so it can cleanly tear down its comms + * protocols; this takes ~100 ms. + */ + if (do_status) { + bscv_get_state_changes(ssp); + } + if (bscv_session_error(ssp)) { + /* + * Had fault during event session. We always + * sleep after one of these because there + * may be a problem with the lom which stops + * us doing useful work in the event daemon. + * If we don't sleep then we may livelock. + */ + bscv_trace(ssp, 'D', "bscv_event_daemon", + "had session error - sleeping"); + ssp->event_sleep = B_TRUE; + } + bscv_exit(ssp); + + mutex_enter(&ssp->task_mu); + + if (ssp->task_flags & TASK_EVENT_PENDING_FLG) { + /* + * We have read any events which were + * pending. Let the consumer continue. + * Ignore the race condition with new events + * arriving - just let the consumer have + * whatever was pending when they asked. + */ + ssp->event_active_count++; + ssp->task_flags &= ~(TASK_EVENT_PENDING_FLG | + TASK_EVENT_CONSUMER_FLG); + cv_broadcast(&ssp->task_evnt_cv); + } + } else { + /* There was nothing to do - sleep */ + ssp->event_sleep = B_TRUE; + } + + if (ssp->event_sleep) { + ssp->task_flags |= TASK_SLEEPING_FLG; + /* Sleep until there is something to do */ + (void) cv_timedwait(&ssp->task_cv, + &ssp->task_mu, + poll_period + ddi_get_lbolt()); + ssp->task_flags &= ~TASK_SLEEPING_FLG; + ssp->event_sleep = B_FALSE; + } + } + + if (ssp->task_flags & TASK_EVENT_CONSUMER_FLG) { + /* + * We are going away so wake up any event consumer. + * Pretend that any pending events have been processed. + */ + ssp->event_active_count += 2; + cv_broadcast(&ssp->task_evnt_cv); + } + + ASSERT(!(ssp->task_flags & TASK_EVENT_PENDING_FLG)); + ssp->task_flags &= + ~(TASK_STOP_FLG | TASK_ALIVE_FLG | TASK_EVENT_CONSUMER_FLG); + mutex_exit(&ssp->task_mu); + + bscv_trace(ssp, 'D', "bscv_event_daemon", + "exiting."); +} + +/* + * function - bscv_start_event_daemon + * description - Create the event daemon thread. + * inputs - LOM soft state structure pointer + * outputs - none + */ +static void +bscv_start_event_daemon(bscv_soft_state_t *ssp) +{ + if (ssp->progress & BSCV_THREAD) + return; + + /* Start the event thread after the queue has started */ + (void) thread_create(NULL, 0, (void (*)())bscv_event_daemon, ssp, + 0, &p0, TS_RUN, minclsyspri); + + ssp->progress |= BSCV_THREAD; +} + +/* + * function - bscv_stop_event_daemon + * description - Attempt to stop the event daemon thread. + * inputs - LOM soft state structure pointer + * outputs - DDI_SUCCESS OR DDI_FAILURE + */ +static int +bscv_stop_event_daemon(bscv_soft_state_t *ssp) +{ + int try; + int res = DDI_SUCCESS; + + mutex_enter(&ssp->task_mu); + + /* Wait for task daemon to stop running. */ + for (try = 0; + ((ssp->task_flags & TASK_ALIVE_FLG) && try < 10); + try++) { + /* Signal that the task daemon should stop */ + ssp->task_flags |= TASK_STOP_FLG; + cv_signal(&ssp->task_cv); + /* Release task daemon lock. */ + mutex_exit(&ssp->task_mu); + /* + * TODO - when the driver is modified to support + * system suspend or if this routine gets called + * during panic we should use drv_usecwait() rather + * than delay in those circumstances. + */ + delay(drv_usectohz(1000000)); + mutex_enter(&ssp->task_mu); + } + + if (ssp->task_flags & TASK_ALIVE_FLG) { + res = DDI_FAILURE; + } + mutex_exit(&ssp->task_mu); + + return (res); +} + +/* + * function - bscv_pause_event_daemon + * description - Attempt to pause the event daemon thread. + * inputs - LOM soft state structure pointer + * outputs - DDI_SUCCESS OR DDI_FAILURE + */ +static int +bscv_pause_event_daemon(bscv_soft_state_t *ssp) +{ + int try; + + if (!(ssp->progress & BSCV_THREAD)) { + /* Nothing to do */ + return (BSCV_SUCCESS); + } + + bscv_trace(ssp, 'D', "bscv_pause_event_daemon", + "Attempting to pause event daemon"); + + mutex_enter(&ssp->task_mu); + /* Signal that the task daemon should pause */ + ssp->task_flags |= TASK_PAUSE_FLG; + + /* Wait for task daemon to pause. */ + for (try = 0; + (!(ssp->task_flags & TASK_SLEEPING_FLG) && + (ssp->task_flags & TASK_ALIVE_FLG) && + try < 10); + try++) { + /* Paranoia */ + ssp->task_flags |= TASK_PAUSE_FLG; + cv_signal(&ssp->task_cv); + /* Release task daemon lock. */ + mutex_exit(&ssp->task_mu); + delay(drv_usectohz(1000000)); + mutex_enter(&ssp->task_mu); + } + if ((ssp->task_flags & TASK_SLEEPING_FLG) || + !(ssp->task_flags & TASK_ALIVE_FLG)) { + mutex_exit(&ssp->task_mu); + bscv_trace(ssp, 'D', "bscv_pause_event_daemon", + "Pause event daemon - success"); + return (BSCV_SUCCESS); + } + mutex_exit(&ssp->task_mu); + bscv_trace(ssp, 'D', "bscv_pause_event_daemon", + "Pause event daemon - failed"); + return (BSCV_FAILURE); +} + +/* + * function - bscv_resume_event_daemon + * description - Resumethe event daemon thread. + * inputs - LOM soft state structure pointer + * outputs - None. + */ +static void +bscv_resume_event_daemon(bscv_soft_state_t *ssp) +{ + if (!(ssp->progress & BSCV_THREAD)) { + /* Nothing to do */ + return; + } + + mutex_enter(&ssp->task_mu); + /* Allow the task daemon to resume event processing */ + ssp->task_flags &= ~TASK_PAUSE_FLG; + cv_signal(&ssp->task_cv); + mutex_exit(&ssp->task_mu); + + bscv_trace(ssp, 'D', "bscv_pause_event_daemon", + "Event daemon resumed"); +} + +/* + * function - bscv_event_process + * description - process (report) events + * inputs - Soft state ptr, process event request + * outputs - none + */ +static void +bscv_event_process(bscv_soft_state_t *ssp, boolean_t do_events) +{ + uint32_t currptr; + unsigned int count; + + /* Raw values read from the lom */ + uint8_t evcount; + uint16_t logptr; + + lom_event_t event; + + if (do_events) { + /* + * Read count, next event ptr MSB,LSB. Note a read of count + * latches values for the next event ptr + */ + evcount = bscv_get8(ssp, chan_general, EBUS_IDX_UNREAD_EVENTS); + logptr = bscv_get16(ssp, chan_general, EBUS_IDX_LOG_PTR_HI); + + /* Sanity check the values from the lom */ + count = bscv_event_validate(ssp, logptr, evcount); + + if (count == -1) { + /* + * Nothing to do - or badly configured event log. + * We really do not want to touch the lom in this + * case because any data that we access may be bad! + * This differs from zero because if we have zero + * to read the lom probably things that unread is + * non-zero and we want that to be set to zero! + * Signal event fault to make the thread wait + * before attempting to re-read the log. + */ + ssp->event_sleep = B_TRUE; + + goto logdone; + } + if (ssp->event_fault_reported) { + /* Clear down any old status - things are fixed */ + cmn_err(CE_NOTE, "Event pointer fault recovered."); + ssp->event_fault_reported = B_FALSE; + } + + /* Compute the first entry that we need to read. */ + currptr = logptr - ssp->eventlog_start; + currptr += ssp->eventlog_size; + currptr -= (count * sizeof (event)); + currptr %= ssp->eventlog_size; + currptr += ssp->eventlog_start; + + bscv_trace(ssp, 'E', "bscv_event_process", + "processing %d events from 0x%x in 0x%x:0x%x", + count, currptr, + ssp->eventlog_start, + ssp->eventlog_start + ssp->eventlog_size); + + for (; count > 0; count--) { + /* Ensure window is positioned correctly */ + if (bscv_eerw(ssp, currptr, (uint8_t *)&event, + sizeof (event), B_FALSE /* read */) != 0) { + /* Fault reading data - stop */ + break; + } + + bscv_event_process_one(ssp, &event); + bscv_sysevent(ssp, &event); + + currptr += sizeof (event); + if (currptr >= ssp->eventlog_start + + ssp->eventlog_size) { + currptr = ssp->eventlog_start; + } + } + /* + * Clear event count - write the evcount value to remove that + * many from the unread total. + * Adjust the value to reflect how many we have left to + * read just in case we had a failure reading events. + */ + if (count == 0) { + /*EMPTY*/ + ASSERT(logptr == currptr); + } else if (count > evcount) { + evcount = 0; + } else { + evcount -= count; + } + bscv_put8(ssp, chan_general, EBUS_IDX_UNREAD_EVENTS, evcount); + /* Remember where we were for next time */ + ssp->oldeeptr = currptr; + ssp->oldeeptr_valid = B_TRUE; +logdone: + ; + } +} + +/* + * function - bscv_event_validate + * description - validate the event data supplied by the lom and determine + * how many (if any) events to read. + * This function performs complex checks to ensure that + * events are not lost due to lom resets or host resets. + * A combination of lom reset and host reset (i.e. power fail) + * may cause some events to not be reported. + * inputs - Soft state ptr, next event pointer, number of unread events. + * outputs - the number of events to read. -1 on error. + * zero is a valid value because it forces the loms unread + * count to be cleared. + */ +static int +bscv_event_validate(bscv_soft_state_t *ssp, uint32_t newptr, uint8_t unread) +{ + uint32_t oldptr; + unsigned int count; + + if (!bscv_window_setup(ssp)) { + /* Problem with lom eeprom setup we cannot do anything */ + return (-1); + } + + /* Sanity check the event pointers */ + if ((newptr < ssp->eventlog_start) || + (newptr >= (ssp->eventlog_start + ssp->eventlog_size))) { + if (!ssp->event_fault_reported) { + cmn_err(CE_WARN, "Event pointer out of range. " + "Cannot read events."); + ssp->event_fault_reported = B_TRUE; + } + return (-1); + } + oldptr = ssp->oldeeptr; + /* Now sanity check log pointer against count */ + if (newptr < oldptr) { + /* + * Must have wrapped add eventlog_size to get the + * correct relative values - this makes the checks + * below work! + */ + newptr += ssp->eventlog_size; + } + if (!ssp->oldeeptr_valid) { + /* We have just started up - we have to trust lom */ + count = unread; + } else if ((unread == 0) && (newptr == oldptr)) { + /* Nothing to do - we were just polling */ + return (-1); + } else if (oldptr + (unread * sizeof (lom_event_t)) == newptr) { + /* Ok - got as many events as we expected */ + count = unread; + } else if (oldptr + (unread * sizeof (lom_event_t)) > newptr) { + /* + * Errrm more messages than there should have been. + * Possible causes: + * 1. the event log has filled - we have been + * away for a long time + * 2. software bug in lom or driver. + * 3. something that I haven't thought of! + * Always warn about this we should really never + * see it! + */ + count = (newptr - oldptr) / sizeof (lom_event_t); + bscv_trace(ssp, 'E', "bscv_event_process", + "bscv_event_process: lom reported " + "more events (%d) than expected (%d).", + unread, count); + cmn_err(CE_CONT, "only processing %d events", count); + } else { + /* Less messages - perhaps the lom has been reset */ + count = (newptr - oldptr) / sizeof (lom_event_t); + bscv_trace(ssp, 'E', "bscv_event_process", + "lom reported less events (%d) than expected (%d)" + " - the lom may have been reset", + unread, count); + } + /* Whatever happens only read a maximum of 255 entries */ + if ((count >= 0xff)) { + cmn_err(CE_WARN, + "bscv_event_process: too many events (%d) to " + "process - some may have been lost", count); + count = 0xff; + } + return (count); +} + +/* + * function - bscv_event_process_one + * description - reports on state changes to the host. + * + * inputs - LOM soft state structure pointer. + * + * outputs - none. + */ + +static void +bscv_event_process_one(bscv_soft_state_t *ssp, lom_event_t *event) +{ + int level; + char eventstr[100]; + int msg_type = 0; + + if (bscv_is_null_event(ssp, event)) { + /* Cleared entry - do not report it */ + return; + } + + level = bscv_level_of_event(event); + + switch (level) { + default: + msg_type = CE_NOTE; + break; + + case EVENT_LEVEL_FATAL: + case EVENT_LEVEL_FAULT: + msg_type = CE_WARN; + break; + } + + bscv_build_eventstring(ssp, event, eventstr, eventstr + + sizeof (eventstr)); + + if (level <= ssp->reporting_level) { + /* + * The message is important enough to be shown on the console + * as well as the log. + */ + cmn_err(msg_type, "%s", eventstr); + } else { + /* + * The message goes only to the log. + */ + cmn_err(msg_type, "!%s", eventstr); + } +} + +/* + * time formats + * + * The BSC represents times as seconds since epoch 1970. Currently it gives + * us 32 bits, unsigned. In the future this might change to a 64-bit count, + * to allow a greater range. + * + * Timestamp values below BSC_TIME_SANITY do not represent an absolute time, + * but instead represent an offset from the last reset. This must be + * borne in mind by output routines. + */ + +typedef uint32_t bsctime_t; + +#define BSC_TIME_SANITY 1000000000 + +/* + * render a formatted time for display + */ + +static size_t +bscv_event_snprintgmttime(char *buf, size_t bufsz, todinfo_t t) +{ + int year; + + /* tod_year is base 1900 so this code needs to adjust */ + year = 1900 + t.tod_year; + + return (snprintf(buf, bufsz, "%04d-%02d-%02d %02d:%02d:%02dZ", + year, t.tod_month, t.tod_day, t.tod_hour, + t.tod_min, t.tod_sec)); +} + +/* + * function - bscv_build_eventstring + * description - reports on state changes to the host. + * + * inputs - LOM soft state structure pointer. + * + * outputs - none. + */ + +static void +bscv_build_eventstring(bscv_soft_state_t *ssp, lom_event_t *event, + char *buf, char *bufend) +{ + uint8_t subsystem; + uint8_t eventtype; + bsctime_t bsctm; + + bscv_trace(ssp, 'S', "bscv_build_eventstring", "event %2x%2x%2x%2x", + event->ev_subsys, event->ev_event, + event->ev_resource, event->ev_detail); + bscv_trace(ssp, 'S', "bscv_build_eventstring", "time %2x%2x%2x%2x", + event->ev_data[0], event->ev_data[1], + event->ev_data[2], event->ev_data[3]); + + /* + * We accept bad subsystems and event type codes here. + * The code decodes as much as possible and then produces + * suitable output. + */ + subsystem = EVENT_DECODE_SUBSYS(event->ev_subsys); + eventtype = event->ev_event; + + /* time */ + bsctm = (((uint32_t)event->ev_data[0]) << 24) | + (((uint32_t)event->ev_data[1]) << 16) | + (((uint32_t)event->ev_data[2]) << 8) | + ((uint32_t)event->ev_data[3]); + if (bsctm < BSC_TIME_SANITY) { + /* offset */ + buf += snprintf(buf, bufend-buf, "+P%dd%02dh%02dm%02ds", + (int)(bsctm/86400), (int)(bsctm/3600%24), + (int)(bsctm/60%60), (int)(bsctm%60)); + } else { + /* absolute time */ + mutex_enter(&tod_lock); + buf += bscv_event_snprintgmttime(buf, bufend-buf, + utc_to_tod(bsctm)); + mutex_exit(&tod_lock); + } + buf += snprintf(buf, bufend-buf, " "); + + /* subsysp */ + if (subsystem < + (sizeof (eventSubsysStrings)/sizeof (*eventSubsysStrings))) { + buf += snprintf(buf, bufend - buf, "%s", + eventSubsysStrings[subsystem]); + } else { + buf += snprintf(buf, bufend - buf, + "unknown subsystem %d ", subsystem); + } + + /* resource */ + switch (subsystem) { + case EVENT_SUBSYS_ALARM: + case EVENT_SUBSYS_TEMP: + case EVENT_SUBSYS_OVERTEMP: + case EVENT_SUBSYS_FAN: + case EVENT_SUBSYS_SUPPLY: + case EVENT_SUBSYS_BREAKER: + case EVENT_SUBSYS_PSU: + buf += snprintf(buf, bufend - buf, "%d ", event->ev_resource); + break; + case EVENT_SUBSYS_LED: + buf += snprintf(buf, bufend - buf, "%s ", bscv_get_label( + ssp->led_names, MAX_LED_ID, event->ev_resource - 1)); + break; + default: + break; + } + + /* fatal */ + if (event->ev_subsys & EVENT_MASK_FAULT) { + if (event->ev_subsys & EVENT_MASK_FATAL) { + buf += snprintf(buf, bufend - buf, "FATAL FAULT: "); + } else { + buf += snprintf(buf, bufend - buf, "FAULT: "); + } + } + + /* eventp */ + if (eventtype < + (sizeof (eventTypeStrings)/sizeof (*eventTypeStrings))) { + buf += snprintf(buf, bufend - buf, "%s", + eventTypeStrings[eventtype]); + } else { + buf += snprintf(buf, bufend - buf, + "unknown event 0x%02x%02x%02x%02x", + event->ev_subsys, event->ev_event, + event->ev_resource, event->ev_detail); + } + + /* detail */ + switch (subsystem) { + case EVENT_SUBSYS_TEMP: + if ((eventtype != EVENT_RECOVERED) && + eventtype != EVENT_DEVICE_INACCESSIBLE) { + buf += snprintf(buf, bufend - buf, " - %d degC", + (int8_t)event->ev_detail); + } + break; + case EVENT_SUBSYS_FAN: + if (eventtype == EVENT_FAILED) { + buf += snprintf(buf, bufend - buf, + " %d%%", event->ev_detail); + } + break; + case EVENT_SUBSYS_LOM: + switch (eventtype) { + case EVENT_FLASH_DOWNLOAD: + buf += snprintf(buf, bufend - buf, + ": v%d.%d to v%d.%d", + (event->ev_resource >> 4), + (event->ev_resource & 0x0f), + (event->ev_detail >> 4), + (event->ev_detail & 0x0f)); + break; + case EVENT_WATCHDOG_TRIGGER: + buf += snprintf(buf, bufend - buf, + event->ev_detail ? "- soft" : " - hard"); + break; + case EVENT_UNEXPECTED_RESET: + if (event->ev_detail & + LOM_UNEXPECTEDRESET_MASK_BADTRAP) { + buf += snprintf(buf, bufend - buf, + " - unclaimed exception 0x%x", + event->ev_detail & + ~LOM_UNEXPECTEDRESET_MASK_BADTRAP); + } + break; + case EVENT_RESET: + switch (event->ev_detail) { + case LOM_RESET_DETAIL_BYUSER: + buf += snprintf(buf, bufend - buf, " by user"); + break; + case LOM_RESET_DETAIL_REPROGRAMMING: + buf += snprintf(buf, bufend - buf, + " after flash download"); + break; + default: + buf += snprintf(buf, bufend - buf, + " - unknown reason"); + break; + } + break; + default: + break; + } + break; + case EVENT_SUBSYS_LED: + switch (event->ev_detail) { + case LOM_LED_STATE_OFF: + buf += snprintf(buf, bufend - buf, ": OFF"); + break; + case LOM_LED_STATE_ON_STEADY: + buf += snprintf(buf, bufend - buf, ": ON"); + break; + case LOM_LED_STATE_ON_FLASHING: + case LOM_LED_STATE_ON_SLOWFLASH: + buf += snprintf(buf, bufend - buf, ": BLINKING"); + break; + case LOM_LED_STATE_INACCESSIBLE: + buf += snprintf(buf, bufend - buf, ": inaccessible"); + break; + case LOM_LED_STATE_STANDBY: + buf += snprintf(buf, bufend - buf, ": standby"); + break; + case LOM_LED_STATE_NOT_PRESENT: + buf += snprintf(buf, bufend - buf, ": not present"); + break; + default: + buf += snprintf(buf, bufend - buf, ": 0x%x", + event->ev_resource); + break; + } + break; + case EVENT_SUBSYS_USER: + switch (eventtype) { + case EVENT_USER_ADDED: + case EVENT_USER_REMOVED: + case EVENT_USER_PERMSCHANGED: + case EVENT_USER_LOGIN: + case EVENT_USER_PASSWORD_CHANGE: + case EVENT_USER_LOGINFAIL: + case EVENT_USER_LOGOUT: + buf += snprintf(buf, bufend - buf, " %d", + event->ev_resource); + default: + break; + } + break; + case EVENT_SUBSYS_PSU: + if (event->ev_detail & LOM_PSU_NOACCESS) { + buf += snprintf(buf, bufend - buf, " - inaccessible"); + } else if ((event->ev_detail & LOM_PSU_STATUS_MASK) + == LOM_PSU_STATUS_MASK) { + buf += snprintf(buf, bufend - buf, " - OK"); + } else { + buf += snprintf(buf, bufend - buf, " -"); + /* + * If both inputs are seen to have failed then simply + * indicate that the PSU input has failed + */ + if (!(event->ev_detail & + (LOM_PSU_INPUT_A_OK | LOM_PSU_INPUT_B_OK))) { + buf += snprintf(buf, bufend - buf, " Input"); + } else { + /* At least one input is ok */ + if (!(event->ev_detail & LOM_PSU_INPUT_A_OK)) { + buf += snprintf(buf, bufend - buf, + " InA"); + } + if (!(event->ev_detail & LOM_PSU_INPUT_B_OK)) { + buf += snprintf(buf, bufend - buf, + " InB"); + } + /* + * Only flag an output error if an input is + * still present + */ + if (!(event->ev_detail & LOM_PSU_OUTPUT_OK)) { + buf += snprintf(buf, bufend - buf, + " Output"); + } + } + buf += snprintf(buf, bufend - buf, " failed"); + } + break; + case EVENT_SUBSYS_NONE: + if (eventtype == EVENT_FAULT_LED) { + switch (event->ev_detail) { + case 0: + buf += snprintf(buf, bufend - buf, " - ON"); + break; + case 255: + buf += snprintf(buf, bufend - buf, " - OFF"); + break; + default: + buf += snprintf(buf, bufend - buf, + " - %dHz", event->ev_detail); + break; + } + } + break; + case EVENT_SUBSYS_HOST: + if (eventtype == EVENT_BOOTMODE_CHANGE) { + switch (event->ev_detail & + ~EBUS_BOOTMODE_FORCE_CONSOLE) { + case EBUS_BOOTMODE_FORCE_NOBOOT: + buf += snprintf(buf, bufend - buf, + " - no boot"); + break; + case EBUS_BOOTMODE_RESET_DEFAULT: + buf += snprintf(buf, bufend - buf, + " - reset defaults"); + break; + case EBUS_BOOTMODE_FULLDIAG: + buf += snprintf(buf, bufend - buf, + " - full diag"); + break; + case EBUS_BOOTMODE_SKIPDIAG: + buf += snprintf(buf, bufend - buf, + " - skip diag"); + break; + default: + break; + } + } + if (eventtype == EVENT_SCC_STATUS) { + switch (event->ev_detail) { + case 0: + buf += snprintf(buf, bufend - buf, + " - inserted"); + break; + case 1: + buf += snprintf(buf, bufend - buf, + " - removed"); + break; + default: + break; + } + } + break; + + default: + break; + } + + /* shutd */ + if (event->ev_subsys & EVENT_MASK_SHUTDOWN_REQD) { + buf += snprintf(buf, bufend - buf, " - shutdown req'd"); + } + + buf += snprintf(buf, bufend - buf, "\n"); + + if (buf >= bufend) { + /* Ensure newline at end of string */ + bufend[-2] = '\n'; + bufend[-1] = '\0'; +#ifdef DEBUG + cmn_err(CE_WARN, "!bscv_build_eventstring: buffer too small!"); +#endif /* DEBUG */ + } +} + +/* + * function - bscv_level_of_event + * description - This routine determines which level an event should be + * reported at. + * inputs - lom event structure pointer + * outputs - event level. + */ +static int +bscv_level_of_event(lom_event_t *event) +{ + int level; + /* + * This is the same criteria that the firmware uses except we + * log the fault led on as being EVENT_LEVEL_FAULT + */ + if (EVENT_DECODE_SUBSYS(event->ev_subsys) == EVENT_SUBSYS_USER) { + level = EVENT_LEVEL_USER; + } else if ((EVENT_DECODE_SUBSYS(event->ev_subsys) == + EVENT_SUBSYS_ALARM) && (event->ev_event == EVENT_STATE_ON)) { + level = EVENT_LEVEL_FAULT; + } else if ((EVENT_DECODE_SUBSYS(event->ev_subsys) == + EVENT_SUBSYS_NONE) && + (event->ev_event == EVENT_FAULT_LED) && + (event->ev_detail != 0xff)) { + level = EVENT_LEVEL_FAULT; + } else if ((EVENT_DECODE_SUBSYS(event->ev_subsys) == + EVENT_SUBSYS_LOM) && event->ev_event == EVENT_TIME_REFERENCE) { + level = EVENT_LEVEL_NOTICE; + } else if (event->ev_event == EVENT_RECOVERED) { + /* + * All recovery messages need to be reported to the console + * because during boot, the faults which occurred whilst + * Solaris was not running are relayed to the console. There + * is a case whereby a fatal fault (eg. over temp) could + * have occurred and then recovered. The recovery condition + * needs to be reported so the user doesn't think that the + * failure (over temp) is still present. + */ + level = EVENT_LEVEL_FAULT; + } else if (EVENT_DECODE_FAULT(event->ev_subsys) == 0) { + /* None of FAULT, FATAL or SHUTDOWN REQD are set */ + level = EVENT_LEVEL_NOTICE; + } else if (EVENT_DECODE_FAULT(event->ev_subsys) == EVENT_MASK_FAULT) { + /* Only FAULT set i.e not FATAL or SHUTDOWN REQD */ + level = EVENT_LEVEL_FAULT; + } else { + level = EVENT_LEVEL_FATAL; + } + + return (level); +} + +/* + * function - bscv_status + * description - This routine is called when any change in the LOMlite2 status + * is indicated by the status registers. + * + * inputs - LOM soft state structure pointer + * + * outputs - none. + */ +static void +bscv_status(bscv_soft_state_t *ssp, uint8_t state_chng, uint8_t dev_no) +{ + int8_t temp; + uint8_t fanspeed; + + ASSERT(bscv_held(ssp)); + + bscv_trace(ssp, 'D', "bscv_status", "state_chng 0x%x dev_no 0x%x", + state_chng, dev_no); + + /* + * The device that has changed is given by the state change + * register and the event detail register so react + * accordingly. + */ + + if (state_chng == EBUS_STATE_NOTIFY) { + /* + * The BSC is indicating a self state change + */ + if (dev_no == EBUS_DETAIL_FLASH) { + ssp->cssp_prog = B_TRUE; + bscv_trace(ssp, 'D', "bscv_status", + "ssp->cssp_prog changed to 0x%x", + ssp->cssp_prog); + /* + * It takes the BSC at least 100 ms to + * clear down the comms protocol. + * We back-off from talking to the + * BSC during this period. + */ + delay(BSC_EVENT_POLL_NORMAL); + bscv_trace(ssp, 'D', "bscv_status", + "completed delay"); + } else if (dev_no == EBUS_DETAIL_RESET) { + /* + * The bsc has reset + */ + bscv_trace(ssp, 'D', "bscv_status", + "BSC reset occured, re-synching"); + (void) bscv_attach_common(ssp); + bscv_trace(ssp, 'D', "bscv_status", + "completed attach_common"); + } + + } + + if ((state_chng & EBUS_STATE_FAN) && ((dev_no - 1) < MAX_FANS)) { + fanspeed = bscv_get8(ssp, chan_general, + EBUS_IDX_FAN1_SPEED + dev_no - 1); + /* + * Only remember fanspeeds which are real values or + * NOT PRESENT values. + */ + if ((fanspeed <= LOM_FAN_MAX_SPEED) || + (fanspeed == LOM_FAN_NOT_PRESENT)) { + ssp->fanspeed[dev_no - 1] = fanspeed; + } + } + + if ((state_chng & EBUS_STATE_PSU) && ((dev_no - 1) < MAX_PSUS)) { + (void) bscv_get8(ssp, chan_general, + EBUS_IDX_PSU1_STAT + dev_no - 1); + } + + if (state_chng & EBUS_STATE_GP) { + (void) bscv_get8(ssp, chan_general, EBUS_IDX_GPIP); + } + + if (state_chng & EBUS_STATE_CB) { + (void) bscv_get8(ssp, chan_general, EBUS_IDX_CBREAK_STATUS); + } + + if ((state_chng & EBUS_STATE_TEMPERATURE) && + ((dev_no - 1) < MAX_TEMPS)) { + temp = bscv_get8(ssp, chan_general, + EBUS_IDX_TEMP1 + dev_no - 1); + /* + * Only remember temperatures which are real values or + * a NOT PRESENT value. + */ + if ((temp <= LOM_TEMP_MAX_VALUE) || + (temp == LOM_TEMP_STATE_NOT_PRESENT)) { + ssp->temps.temp[dev_no - 1] = temp; + } + } + + if (state_chng & EBUS_STATE_RAIL) { + (void) bscv_get8(ssp, chan_general, EBUS_IDX_SUPPLY_LO); + (void) bscv_get8(ssp, chan_general, EBUS_IDX_SUPPLY_HI); + } +} + +char * +bscv_get_label(char labels[][MAX_LOM2_NAME_STR], int limit, int index) +{ + + if (labels == NULL) + return (""); + + if (limit < 0 || index < 0 || index > limit) + return ("-"); + + return (labels[index]); +} + +static void +bscv_generic_sysevent(bscv_soft_state_t *ssp, char *class, char *subclass, + char *fru_id, char *res_id, int32_t fru_state, char *msg) +{ + int rv; + nvlist_t *attr_list; + + bscv_trace(ssp, 'E', "bscv_generic_sysevent", "%s/%s:(%s,%s,%d) %s", + class, subclass, fru_id, res_id, fru_state, msg); + + + if (nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, KM_SLEEP)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist alloc failure"); + return; + } + if (nvlist_add_uint32(attr_list, ENV_VERSION, 1)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist ENV_VERSION failure"); + nvlist_free(attr_list); + return; + } + if (nvlist_add_string(attr_list, ENV_FRU_ID, fru_id)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist ENV_FRU_ID failure"); + nvlist_free(attr_list); + return; + } + if (nvlist_add_string(attr_list, ENV_FRU_RESOURCE_ID, res_id)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist ENV_FRU_RESOURCE_ID failure"); + nvlist_free(attr_list); + return; + } + if (nvlist_add_string(attr_list, ENV_FRU_DEVICE, ENV_RESERVED_ATTR)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist ENV_FRU_DEVICE failure"); + nvlist_free(attr_list); + return; + } + if (nvlist_add_int32(attr_list, ENV_FRU_STATE, fru_state)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist ENV_FRU_STATE failure"); + nvlist_free(attr_list); + return; + } + if (nvlist_add_string(attr_list, ENV_MSG, msg)) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", + "nvlist ENV_MSG failure"); + nvlist_free(attr_list); + return; + } + + rv = ddi_log_sysevent(ssp->dip, DDI_VENDOR_SUNW, class, + subclass, attr_list, NULL, DDI_SLEEP); + + if (rv == DDI_SUCCESS) { + bscv_trace(ssp, 'E', "bscv_generic_sysevent", "sent sysevent"); + } else { + cmn_err(CE_WARN, "!cannot deliver sysevent"); + } + + nvlist_free(attr_list); +} + +/* + * function - bscv_sysevent + * description - send out a sysevent on the given change if needed + * inputs - soft state pointer, event to report + * outputs - none + */ + +static void +bscv_sysevent(bscv_soft_state_t *ssp, lom_event_t *event) +{ + char *class = NULL; + char *subclass = NULL; + char *fru_id = "Blade"; /* The blade is only one FRU */ + char *res_id; + int32_t fru_state = 0; + + bscv_trace(ssp, 'E', "bscv_sysevent", "processing event"); + + ASSERT(event != NULL); + + /* Map ev_subsys to sysevent class/sub-class */ + + switch (EVENT_DECODE_SUBSYS(event->ev_subsys)) { + case EVENT_SUBSYS_NONE: + break; + case EVENT_SUBSYS_ALARM: + break; + case EVENT_SUBSYS_TEMP: + class = EC_ENV, subclass = ESC_ENV_TEMP; + res_id = bscv_get_label(ssp->temps.name, ssp->temps.num, + event->ev_resource - 1); + switch (event->ev_event) { + case EVENT_SEVERE_OVERHEAT: + fru_state = ENV_FAILED; + break; + case EVENT_OVERHEAT: + fru_state = ENV_WARNING; + break; + case EVENT_NO_OVERHEAT: + fru_state = ENV_OK; + break; + default: + return; + } + break; + case EVENT_SUBSYS_OVERTEMP: + break; + case EVENT_SUBSYS_FAN: + class = EC_ENV, subclass = ESC_ENV_FAN; + res_id = bscv_get_label(ssp->fan_names, ssp->num_fans, + event->ev_resource - 1); + switch (event->ev_event) { + case EVENT_FAILED: + fru_state = ENV_FAILED; + break; + case EVENT_RECOVERED: + fru_state = ENV_OK; + break; + default: + return; + } + break; + case EVENT_SUBSYS_SUPPLY: + class = EC_ENV, subclass = ESC_ENV_POWER; + res_id = bscv_get_label(ssp->sflags.name, ssp->sflags.num, + event->ev_resource - 1); + switch (event->ev_event) { + case EVENT_FAILED: + fru_state = ENV_FAILED; + break; + case EVENT_RECOVERED: + fru_state = ENV_OK; + break; + default: + return; + } + break; + case EVENT_SUBSYS_BREAKER: + break; + case EVENT_SUBSYS_PSU: + break; + case EVENT_SUBSYS_USER: + break; + case EVENT_SUBSYS_PHONEHOME: + break; + case EVENT_SUBSYS_LOM: + break; + case EVENT_SUBSYS_HOST: + break; + case EVENT_SUBSYS_EVENTLOG: + break; + case EVENT_SUBSYS_EXTRA: + break; + case EVENT_SUBSYS_LED: + if (event->ev_event != EVENT_FAULT_LED && + event->ev_event != EVENT_STATE_CHANGE) + return; + /* + * There are 3 LEDs : Power, Service, Ready-to-Remove on a + * JBOS blade. We'll never report the Power since Solaris + * won't be running when it is _switched_ ON. Ready-to-Remove + * will only be lit when we're powered down which also means + * Solaris won't be running. We don't want to report it + * during system testing / Sun VTS exercising the LEDs. + * + * Therefore, we only report the Service Required LED. + */ + class = EC_ENV, subclass = ESC_ENV_LED; + res_id = bscv_get_label(ssp->led_names, MAX_LED_ID, + event->ev_resource - 1); + + switch (event->ev_detail) { + case LOM_LED_STATE_ON_STEADY: + fru_state = ENV_LED_ON; + break; + case LOM_LED_STATE_ON_FLASHING: + case LOM_LED_STATE_ON_SLOWFLASH: + fru_state = ENV_LED_BLINKING; + break; + case LOM_LED_STATE_OFF: + fru_state = ENV_LED_OFF; + break; + case LOM_LED_STATE_INACCESSIBLE: + fru_state = ENV_LED_INACCESSIBLE; + break; + case LOM_LED_STATE_STANDBY: + fru_state = ENV_LED_STANDBY; + break; + case LOM_LED_STATE_NOT_PRESENT: + fru_state = ENV_LED_NOT_PRESENT; + break; + default: + fru_state = ENV_LED_INACCESSIBLE; + break; + } + break; + default : + break; + } + + if (class == NULL || subclass == NULL) { + bscv_trace(ssp, 'E', "bscv_sysevent", "class/subclass NULL"); + return; + } + + bscv_generic_sysevent(ssp, class, subclass, fru_id, res_id, fru_state, + ENV_RESERVED_ATTR); +} + +/* + * ********************************************************************* + * Firmware download (programming) + * ********************************************************************* + */ + +/* + * function - bscv_prog + * description - LOMlite2 flash programming code. + * + * bscv_prog_image - download a complete image to the lom. + * bscv_prog_receive_image - receive data to build up a + * complete image. + * bscv_prog_stop_lom - pause the event daemon and prepare + * lom for firmware upgrade. + * bscv_prog_start_lom - reinit the driver/lom after upgrade + * and restart the event daemon + * + * inputs - soft state pointer, arg ptr, ioctl mode + * outputs - status + */ + +static int +bscv_prog(bscv_soft_state_t *ssp, intptr_t arg, int mode) +{ + lom_prog_t *prog; + int res = 0; + + /* + * We will get repeatedly called with bits of data first for + * loader, then for main image. + */ + prog = (lom_prog_t *)kmem_alloc(sizeof (lom_prog_t), KM_SLEEP); + + if (ddi_copyin((caddr_t)arg, (caddr_t)prog, sizeof (*prog), + mode) < 0) { + kmem_free((void *)prog, sizeof (*prog)); + return (EFAULT); + } + + bscv_trace(ssp, 'U', "bscv_prog", + "index 0x%x size 0x%x", prog->index, prog->size); + + mutex_enter(&ssp->prog_mu); + if (prog->size == 0) { + if (prog->index == 2) { + /* + * This is the initial request for the chip type so we + * know what we are programming. + * The type will have been read in at init so just + * return it in data[0]. + */ + prog->data[0] = bscv_get8_cached(ssp, + EBUS_IDX_CPU_IDENT); + + if (ddi_copyout((caddr_t)prog, (caddr_t)arg, + sizeof (lom_prog_t), mode) < 0) { + res = EFAULT; + } + } else if (prog->index == 0) { + res = bscv_prog_stop_lom(ssp); + } else if (prog->index == 1) { + res = bscv_prog_start_lom(ssp); + } else { + res = EINVAL; + } + } else { + if (ssp->image == NULL) { + ssp->image = (uint8_t *)kmem_zalloc( + BSC_IMAGE_MAX_SIZE, KM_SLEEP); + } + res = bscv_prog_receive_image(ssp, prog, + ssp->image, BSC_IMAGE_MAX_SIZE); + } + mutex_exit(&ssp->prog_mu); + kmem_free((void *)prog, sizeof (lom_prog_t)); + + return (res); +} + +static int +bscv_check_loader_config(bscv_soft_state_t *ssp, boolean_t is_image2) +{ + bscv_trace(ssp, 'U', "bscv_check_loader_config", + "loader_running %d, is_image2 %d", + ssp->loader_running, is_image2); + + /* + * loader_running TRUE means that we have told the microcontroller to + * JUMP into the loader code which has been downloaded into its RAM. + * At this point its an error to try and download another loader. We + * should be downloading the actual image at this point. + * Conversely, it is an error to download an image when the loader is + * not already downloaded and the microcontroller hasn't JUMPed into it. + * is_image2 TRUE means the image is being downloaded. + * is_image2 FALSE means the loader is being downloaded. + */ + if (ssp->loader_running && !is_image2) { + cmn_err(CE_WARN, "Attempt to download loader image " + "with loader image already active"); + cmn_err(CE_CONT, "This maybe an attempt to restart a " + "failed firmware download - ignoring download attempt"); + return (B_FALSE); + } else if (!ssp->loader_running && is_image2) { + cmn_err(CE_WARN, "Attempt to download firmware image " + "without loader image active"); + return (B_FALSE); + + } + + return (B_TRUE); +} + +static uint32_t +bscv_get_pagesize(bscv_soft_state_t *ssp) +{ + uint32_t pagesize; + + ASSERT(bscv_held(ssp)); + + pagesize = bscv_get32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PAGE0)); + + bscv_trace(ssp, 'U', "bscv_get_pagesize", "pagesize 0x%x", pagesize); + + return (pagesize); +} + +/* + * Sets the pagesize, returning the old value. + */ +static uint32_t +bscv_set_pagesize(bscv_soft_state_t *ssp, uint32_t pagesize) +{ + uint32_t old_pagesize; + + ASSERT(bscv_held(ssp)); + + old_pagesize = bscv_get_pagesize(ssp); + + /* + * The microcontroller remembers this value until until someone + * changes it. + */ + bscv_put32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PSIZ0), pagesize); + + return (old_pagesize); +} + +static uint8_t +bscv_enter_programming_mode(bscv_soft_state_t *ssp) +{ + uint8_t retval; + + ASSERT(bscv_held(ssp)); + + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR), + EBUS_PROGRAM_PCR_PRGMODE_ON); + + retval = bscv_get8(ssp, chan_prog, BSCVA(EBUS_CMD_SPACE_PROGRAM, + EBUS_PROGRAM_PCSR)); + + return (retval); +} + +static void +bscv_leave_programming_mode(bscv_soft_state_t *ssp, boolean_t with_jmp) +{ + uint8_t reg; + ASSERT(bscv_held(ssp)); + + if (with_jmp) { + reg = EBUS_PROGRAM_PCR_PROGOFF_JUMPTOADDR; + bscv_trace(ssp, 'U', "bscv_leave_programming_mode", + "jumptoaddr"); + } else { + reg = EBUS_PROGRAM_PCR_PRGMODE_OFF; + bscv_trace(ssp, 'U', "bscv_leave_programming_mode", + "prgmode_off"); + } + + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR), reg); +} + + +static void +bscv_set_jump_to_addr(bscv_soft_state_t *ssp, uint32_t loadaddr) +{ + ASSERT(bscv_held(ssp)); + + bscv_put32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PADR0), loadaddr); + + bscv_trace(ssp, 'U', "bscv_set_jump_to_addr", + "set jump to loadaddr 0x%x", loadaddr); +} + +static uint8_t +bscv_erase_once(bscv_soft_state_t *ssp, uint32_t loadaddr, uint32_t image_size) +{ + uint8_t retval; + + ASSERT(bscv_held(ssp)); + + /* + * write PADR, PSIZ to define area to be erased + * We do not send erase for zero size because the current + * downloader gets this wrong + */ + + /* + * start at 0 + */ + bscv_trace(ssp, 'U', "bscv_erase_once", "sending erase command"); + + bscv_put32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PADR0), + loadaddr); + + /* set PSIZ to full size of image to be programmed */ + bscv_put32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PSIZ0), + image_size); + + /* write ERASE to PCSR */ + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR), + EBUS_PROGRAM_PCR_ERASE); + + /* read PCSR to check status */ + retval = bscv_get8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR)); + return (retval); +} + +static uint8_t +bscv_do_erase(bscv_soft_state_t *ssp, uint32_t loadaddr, uint32_t image_size, + boolean_t is_image2) +{ + int retryable = BSC_ERASE_RETRY_LIMIT; + uint8_t retval; + + while (retryable--) { + retval = bscv_erase_once(ssp, loadaddr, image_size); + if (PSR_SUCCESS(retval)) + break; + else + cmn_err(CE_WARN, "erase error 0x%x, attempt %d" + ", base 0x%x, size 0x%x, %s image", + retval, BSC_ERASE_RETRY_LIMIT - retryable, + loadaddr, image_size, + is_image2 ? "main" : "loader"); + } + + return (retval); +} + +static uint8_t +bscv_set_page(bscv_soft_state_t *ssp, uint32_t addr) +{ + uint32_t retval; + int retryable = BSC_PAGE_RETRY_LIMIT; + + ASSERT(bscv_held(ssp)); + + while (retryable--) { + + /* + * Write the page address and read it back for confirmation. + */ + bscv_put32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PADR0), + addr); + retval = bscv_get32(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PADR0)); + + if (retval == addr) + break; + else { + cmn_err(CE_WARN, "programmming error, attempt %d, " + "set page 0x%x, read back 0x%x", + BSC_PAGE_RETRY_LIMIT - retryable, + addr, retval); + } + } + return ((addr == retval) ? EBUS_PROGRAM_PSR_SUCCESS : + EBUS_PROGRAM_PSR_INVALID_OPERATION); +} + +static uint8_t +bscv_do_page_data_once(bscv_soft_state_t *ssp, uint32_t index, + uint32_t image_size, uint32_t pagesize, uint8_t *imagep, + uint16_t *calcd_chksum) +{ + uint32_t size; + uint16_t chksum; + int i; + uint8_t retval; + + ASSERT(bscv_held(ssp)); + + bscv_trace(ssp, 'P', "bscv_do_page_data_once", "index 0x%x", index); + + /* write PSIZ bytes to PDAT */ + if (index + pagesize < image_size) { + bscv_rep_rw8(ssp, chan_prog, imagep + index, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_DATA), + pagesize, DDI_DEV_NO_AUTOINCR, B_TRUE /* write */); + size = pagesize; + } else { + bscv_trace(ssp, 'P', "bscv_do_page_once", + "Sending last block, last 0x%x bytes", + (image_size % pagesize)); + size = (image_size - index); + bscv_rep_rw8(ssp, chan_prog, imagep + index, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_DATA), + size, DDI_DEV_NO_AUTOINCR, B_TRUE /* write */); + /* Now pad the rest of the page with zeros */ + for (i = size; i < pagesize; i++) { + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, + EBUS_PROGRAM_DATA), + 0); + } + } + + /* write the checksum to PCSM */ + chksum = 0; + for (i = 0; i < size; i++) { + chksum = ((chksum << 3) | (chksum >> 13)) ^ + *(imagep + index + i); + } + /* Cope with non-pagesize sized bufers */ + for (; i < pagesize; i++) { + chksum = ((chksum << 3) | (chksum >> 13)) ^ 0; + } + bscv_put16(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSM0), chksum); + + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR), + EBUS_PROGRAM_PCR_PROGRAM); + + retval = bscv_get8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR)); + + *calcd_chksum = chksum; + return (retval); +} + +static uint8_t bscv_do_page(bscv_soft_state_t *ssp, uint32_t loadaddr, + uint32_t index, uint32_t image_size, uint32_t pagesize, uint8_t *imagep, + boolean_t is_image2) +{ + int retryable = BSC_PAGE_RETRY_LIMIT; + uint8_t retval; + uint16_t checksum; + + bscv_trace(ssp, 'P', "bscv_do_page", "index 0x%x", index); + + while (retryable--) { + /* + * Set the page address (with retries). If this is not + * successful, then there is no point carrying on and sending + * the page's data since that could cause random memory + * corruption in the microcontroller. + */ + retval = bscv_set_page(ssp, loadaddr + index); + if (!PSR_SUCCESS(retval)) { + cmn_err(CE_WARN, "programming error 0x%x, " + "could not setup page address 0x%x, %s image", + retval, loadaddr + index, + is_image2 ? "main" : "loader"); + break; + } + + /* + * Send down the data for the page + */ + + bscv_trace(ssp, 'P', "bscv_do_page", "sending data for page"); + + retval = bscv_do_page_data_once(ssp, index, image_size, + pagesize, imagep, &checksum); + if (PSR_SUCCESS(retval)) + break; + else + cmn_err(CE_WARN, "programming error 0x%x," + " attempt %d, index 0x%x, checksum 0x%x, %s image", + retval, BSC_PAGE_RETRY_LIMIT - retryable, + index, checksum, is_image2 ? "main" : "loader"); + } + + bscv_trace(ssp, 'U', "bscv_do_page", "Returning 0x%x for index 0x%x," + " checksum 0x%x, %s image", retval, index, checksum, + is_image2 ? "main" : "loader"); + + return (retval); +} + +static uint8_t +bscv_do_pages(bscv_soft_state_t *ssp, uint32_t loadaddr, uint32_t image_size, + uint32_t pagesize, uint8_t *imagep, boolean_t is_image2) +{ + uint8_t retval; + uint32_t index; + + bscv_trace(ssp, 'P', "bscv_do_pages", "entered"); + + for (index = 0; index < image_size; index += pagesize) { + retval = bscv_do_page(ssp, loadaddr, index, image_size, + pagesize, imagep, is_image2); + if (bscv_faulty(ssp) || !PSR_SUCCESS(retval)) { + bscv_trace(ssp, 'U', "bscv_do_pages", + "Failed to program lom (status 0x%x)", retval); + break; + } + } + + return (retval); +} + +static int +bscv_prog_image(bscv_soft_state_t *ssp, boolean_t is_image2, + uint8_t *imagep, int image_size, uint32_t loadaddr) +{ + uint32_t pagesize; + int res = 0; + uint8_t retval; + + bscv_trace(ssp, 'U', "bscv_prog_image", + "image 0x%x, imagep %p, size 0x%x", + is_image2 ? 2 : 1, imagep, image_size); + + if (!bscv_check_loader_config(ssp, is_image2)) + /* + * Return no error to allow userland to continue on with + * downloading the image. + */ + return (0); + + bscv_enter(ssp); + + pagesize = bscv_get_pagesize(ssp); + + retval = bscv_enter_programming_mode(ssp); + if (bscv_faulty(ssp) || !PSR_PROG(retval)) { + cmn_err(CE_WARN, "lom: Failed to enter program mode, error 0x%x" + ", %s image", retval, is_image2 ? "main" : "loader"); + res = EIO; + goto BSCV_PROG_IMAGE_END; + } + bscv_trace(ssp, 'U', "bscv_prog_image", "entered programming mode"); + + /* + * Only issue an erase if we are downloading the image. The loader + * does not need this step. + */ + if (is_image2 && (image_size != 0)) { + retval = bscv_do_erase(ssp, loadaddr, image_size, is_image2); + if (bscv_faulty(ssp) || !PSR_SUCCESS(retval)) { + cmn_err(CE_WARN, + "lom: Erase failed during programming, status 0x%x", + retval); + res = EIO; + goto BSCV_PROG_IMAGE_END; + } else { + bscv_trace(ssp, 'U', "bscv_prog_image", + "erase complete - programming..."); + + } + } + + (void) bscv_set_pagesize(ssp, pagesize); + + retval = bscv_do_pages(ssp, loadaddr, image_size, pagesize, imagep, + is_image2); + if (bscv_faulty(ssp) || !PSR_SUCCESS(retval)) { + bscv_trace(ssp, 'U', "bscv_prog_image", + "Failed to program lom (status 0x%x)", retval); + res = EIO; + goto BSCV_PROG_IMAGE_END; + } + +BSCV_PROG_IMAGE_END: + if (res == 0 && !is_image2) { + /* + * We've downloaded the loader successfully. Now make the + * microcontroller jump to it. + */ + bscv_set_jump_to_addr(ssp, loadaddr); + ssp->loader_running = B_TRUE; + bscv_leave_programming_mode(ssp, B_TRUE); + } else { + /* + * We've just downloaded either the loader which failed, or + * the image (which may or may not have been successful). + */ + bscv_set_jump_to_addr(ssp, 0); + + if (res != 0) { + bscv_trace(ssp, 'U', "bscv_prog_image", + "got error 0x%x - leaving programming mode", + res); + cmn_err(CE_WARN, "programming error 0x%x, %s image", + res, is_image2 ? "main" : "loader"); + } else { + bscv_trace(ssp, 'U', "bscv_prog_image", + "programming complete - leaving programming mode"); + } + + bscv_leave_programming_mode(ssp, B_FALSE); + ssp->loader_running = B_FALSE; + } + + bscv_exit(ssp); + + return (res); +} + + +static int +bscv_prog_receive_image(bscv_soft_state_t *ssp, lom_prog_t *prog, + uint8_t *imagep, int max_size) +{ + int res = 0; + uint_t size; + int32_t loadaddr; + lom_prog_data_t *prog_data; + + if ((prog->index & 0x7FFF) != ssp->prog_index) { + bscv_trace(ssp, 'U', "bscv_prog_receive_image", + "Got wrong buffer 0x%x, expected 0x%x", + prog->index & 0x7fff, ssp->prog_index); + return (EINVAL); + } + + /* + * We want to get the whole image and then do the download. + * It is assumed the device is now in programming mode. + */ + + if ((prog->index & 0x7fff) == 0) { + /* Starting a new image */ + ssp->image_ptr = 0; + } + + if ((ssp->image_ptr + prog->size) > max_size) { + cmn_err(CE_WARN, + "lom image exceeded maximum size: got 0x%x, maximum 0x%x", + (ssp->image_ptr + prog->size), max_size); + return (EFAULT); + } + bcopy(prog->data, &imagep[ssp->image_ptr], prog->size); + ssp->image_ptr += prog->size; + + ssp->prog_index++; + + if (prog->index & 0x8000) { + /* + * OK we have the whole image so synch up and start download. + */ + prog_data = (lom_prog_data_t *)imagep; + if (prog_data->header.magic != PROG_MAGIC) { + /* Old style programming data */ + /* Take care image may not fill all of structure */ + + /* sign extend loadaddr from 16 to 32 bits */ + loadaddr = (int16_t)((uint16_t)((imagep[2] << 8) + + imagep[3])); + + size = (imagep[0] << 8) + imagep[1]; + if (size != (ssp->image_ptr - 4)) { + cmn_err(CE_WARN, "Image size mismatch:" + " expected 0x%x, got 0x%x", + size, (ssp->image_ptr - 1)); + } + + res = bscv_prog_image(ssp, + ssp->image2_processing, + imagep + 4, ssp->image_ptr - 4, loadaddr); + + /* + * Done the loading so set the flag to say we are doing + * the other image. + */ + ssp->image2_processing = !ssp->image2_processing; + } else if ((ssp->image_ptr < sizeof (*prog_data)) || + (prog_data->platform.bscv.size != + (ssp->image_ptr - sizeof (*prog_data)))) { + /* Image too small for new style image */ + cmn_err(CE_WARN, "image too small"); + res = EINVAL; + } else { + /* New style programming image */ + switch (prog_data->platmagic) { + case PROG_PLAT_BSCV_IMAGE: + res = bscv_prog_image(ssp, B_TRUE, + imagep + sizeof (*prog_data), + prog_data->platform.bscv.size, + prog_data->platform.bscv.loadaddr); + ssp->image2_processing = B_FALSE; + break; + case PROG_PLAT_BSCV_LOADER: + res = bscv_prog_image(ssp, B_FALSE, + imagep + sizeof (*prog_data), + prog_data->platform.bscv.size, + prog_data->platform.bscv.loadaddr); + ssp->image2_processing = B_TRUE; + break; + default: + cmn_err(CE_WARN, "unknown platmagic 0x%x", + prog_data->platmagic); + res = EINVAL; + break; + } + } + ssp->prog_index = 0; + ssp->image_ptr = 0; + } + return (res); +} + +static int +bscv_prog_stop_lom(bscv_soft_state_t *ssp) +{ + if (ssp->programming) { + /* + * Already programming - this may be a retry of a failed + * programming attempt or just a software error! + */ + goto queue_stopped; + } + + if (bscv_pause_event_daemon(ssp) == BSCV_FAILURE) { + bscv_trace(ssp, 'Q', "bscv_prog_stop_lom", + "failed to pause event daemon thread"); + return (EAGAIN); + } + + bscv_enter(ssp); + + ssp->programming = B_TRUE; + + bscv_exit(ssp); + +queue_stopped: + + ssp->prog_index = 0; + ssp->image2_processing = B_FALSE; + + return (0); +} + +static int +bscv_prog_start_lom(bscv_soft_state_t *ssp) +{ + int res = 0; + + if (!ssp->programming) { + /* Not programming so this is not a valid command */ + return (EINVAL); + } + + if (ssp->image != NULL) { + kmem_free((void *)ssp->image, BSC_IMAGE_MAX_SIZE); + ssp->image = NULL; + } + + /* + * OK we are out of reset now so: + * Probe the firmware and set everything up. + */ + + bscv_enter(ssp); + + /* Explicit clear fault because things may have been mended now */ + bscv_clear_fault(ssp); + + if (ssp->loader_running) { + cmn_err(CE_WARN, "Firmware upgrade failed to exit loader - " + "performing forced exit"); + /* Must try to restart the lom here. */ + /* Ensure prog mode entry to enable PRGMODE_OFF */ + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR), + EBUS_PROGRAM_PCR_PRGMODE_ON); + bscv_put8(ssp, chan_prog, + BSCVA(EBUS_CMD_SPACE_PROGRAM, EBUS_PROGRAM_PCSR), + EBUS_PROGRAM_PCR_PRGMODE_OFF); + ssp->loader_running = B_FALSE; + /* give the lom chance to recover */ + delay(drv_usectohz(5000000)); /* 5 seconds */ + } + + ssp->prog_mode_only = B_FALSE; + ssp->programming = B_FALSE; + + if (bscv_attach_common(ssp) == DDI_FAILURE) { + ssp->prog_mode_only = B_TRUE; + res = EIO; + } + + bscv_exit(ssp); + + if (!ssp->prog_mode_only) { + /* + * Start the event thread after the queue has started + * + * Not sure if this is entirely correct because + * the other code at the end of bscv_attach() + * does not get run here. + */ + bscv_start_event_daemon(ssp); + bscv_resume_event_daemon(ssp); + } + + return (res); +} + + +/* + * ********************************************************************* + * Attach processing + * ********************************************************************* + */ + +/* + * function - bscv_attach_common + * description - this routine co-ordinates the initialisation of the + * driver both at attach time and after firmware programming. + * sequence - bscv_setup_capability - read LOMlite2 capabilities + * bscv_probe_check - test comms and setup register cache + * bscv_setup_hostname - sync stored name in lom with nodename. + * bscv_setup_static_info - read device names etc. + * bscv_setup_events - start event daemon etc. + * + * inputs - device information structure, DDI_ATTACH command + * outputs - DDI_SUCCESS or DDI_FAILURE + */ + +static int +bscv_attach_common(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + + bscv_trace(ssp, 'A', "bscv_attach_common:", ""); + + /* + * Set the threshold for reporting messages to the console to + * Warnings or higher. + */ + ssp->reporting_level = 2; + + /* + * When the system is not running the Operating System, make + * the microcontroller print event messages straight onto the + * console. + */ + ssp->serial_reporting = LOM_SER_EVENTS_DEF; + + /* Setup capabilities */ + bscv_setup_capability(ssp); + + if (bscv_probe_check(ssp) == DDI_FAILURE) { + cmn_err(CE_WARN, "BSC chip not responding"); + /* + * We want lom -G to talk to this driver upon broken firmware + * so we prematurely return success here. + */ + return (DDI_SUCCESS); + } + + bscv_setup_hostname(ssp); + bscv_setup_static_info(ssp); + bscv_setup_events(ssp); + +#if defined(__i386) || defined(__amd64) + bscv_inform_bsc(ssp, BSC_INFORM_ONLINE); +#endif /* __i386 || __amd64 */ + /* + * Watchdog configuration and CPU signatures are sent asynchronously + * with respect to attach so only inform the BSC if we've already + * sent the data in the past. + */ + + if (ssp->progress & BSCV_WDOG_CFG) + bscv_setup_watchdog(ssp); + +#ifdef __sparc + if (ssp->progress & BSCV_SIG_SENT) + bscv_write_sig(ssp, ssp->last_sig); +#endif /* __sparc */ + + return (DDI_SUCCESS); +} + +/* + * function - bscv_cleanup + * description - routine that does the necessary tidying up if the attach + * request fails or the driver is to be detached. + * If the event thread has been started we may fail to + * stop it (because it is busy) so we fail the cleanup + * and hence the detach. All other calls to bscv_cleanup + * are done before the event daemon is started. + * inputs - soft state structure address. + * outputs - DDI_SUCCESS or DDI_FAILURE. + */ + +static int +bscv_cleanup(bscv_soft_state_t *ssp) +{ + int instance; + uint8_t bits2set; + uint8_t bits2clear; + + instance = ssp->instance; + + if (ssp->progress & BSCV_LOCKS) { + bscv_enter(ssp); + } + + if (ssp->progress & BSCV_THREAD) { + if (bscv_stop_event_daemon(ssp) == DDI_FAILURE) { + /* Fail the cleanup - may be able to cleanup later */ + if (ssp->progress & BSCV_LOCKS) { + bscv_exit(ssp); + } + return (DDI_FAILURE); + } + } + + if (ssp->progress & BSCV_NODES) { + ddi_remove_minor_node(ssp->dip, NULL); + } + + if (ssp->progress & BSCV_MAPPED_REGS) { + /* + * switch back on serial event reporting - cover all configs. + */ + bits2set = 0; + bits2clear = 0; + if (ssp->serial_reporting == LOM_SER_EVENTS_ON) { + bits2clear |= EBUS_ALARM_NOEVENTS; + } else if (ssp->serial_reporting == LOM_SER_EVENTS_OFF) { + bits2set |= EBUS_ALARM_NOEVENTS; + } else if (ssp->serial_reporting == LOM_SER_EVENTS_DEF) { + bits2clear |= EBUS_ALARM_NOEVENTS; + } + bscv_setclear8_volatile(ssp, chan_general, EBUS_IDX_ALARM, + bits2set, bits2clear); + + /* + * disable the reset function if we have enabled + * it. We don't want any nasty surprises like system + * rebooting unexpectedly. If we timeout on the busy + * flag we just have to carry on. + */ + + bscv_trace(ssp, 'W', "bscv_cleanup", + "bscv_cleanup - disable wdog"); + if (bscv_get8_cached(ssp, EBUS_IDX_WDOG_CTRL) & + EBUS_WDOG_ENABLE) { + bscv_setclear8(ssp, chan_general, EBUS_IDX_WDOG_CTRL, + 0, EBUS_WDOG_RST | EBUS_WDOG_ENABLE); + } + } + + /* + * unmap registers + */ + + if (ssp->progress & BSCV_MAPPED_REGS) { + bscv_unmap_regs(ssp); + } + + /* + * release any memory allocated for mutexes and condition + * variables before deallocating the structures containing them + */ + + if (ssp->progress & BSCV_LOCKS) { + bscv_exit(ssp); + cv_destroy(&ssp->task_cv); + cv_destroy(&ssp->task_evnt_cv); + mutex_destroy(&ssp->task_mu); + mutex_destroy(&ssp->prog_mu); + mutex_destroy(&ssp->cmd_mutex); + } + + if (ssp->image != NULL) { + kmem_free((void *)ssp->image, BSC_IMAGE_MAX_SIZE); + } + +#if defined(__i386) || defined(__amd64) + mutex_enter(&cpu_lock); + bscv_watchdog_cyclic_remove(ssp); + mutex_exit(&cpu_lock); +#endif /* __i386 || __amd64 */ + ddi_soft_state_free(bscv_statep, instance); + + return (DDI_SUCCESS); +} + +/* + * function - bscv_setup_capability + * description - probe the lom find what capabilities are present for + * us to use. + * inputs - soft state ptr + * outputs - returns DDI_SUCCESS or DDI_FAILURE + */ +static void bscv_setup_capability(bscv_soft_state_t *ssp) +{ + ASSERT(bscv_held(ssp)); + + if (ssp->prog_mode_only) { + /* Turn off all capabilities */ + ssp->cap0 = 0; + ssp->cap1 = 0; + ssp->cap2 = 0; + return; + } + + ssp->cap0 = bscv_get8(ssp, chan_general, EBUS_IDX_CAP0); + ssp->cap1 = bscv_get8(ssp, chan_general, EBUS_IDX_CAP1); + ssp->cap2 = bscv_get8(ssp, chan_general, EBUS_IDX_CAP2); + if (!bscv_faulty(ssp)) { + bscv_trace(ssp, 'A', "bscv_setup_capability", + "Capability flags cap0=0x%x cap1=0x%x, cap2=0x%x", + ssp->cap0, ssp->cap1, ssp->cap2); + } else { + cmn_err(CE_WARN, "!Could not read capability flags"); + ssp->cap0 = 0; ssp->cap1 = 0; ssp->cap2 = 0; + } +} + +/* + * function - bscv_probe_check + * description - probe the lom to check for correct operation + * has a side effect of setting up the cached registers and + * updates ssp->prog_mode_only. + * inputs - soft state ptr + * outputs - returns DDI_SUCCESS or DDI_FAILURE + */ + +static int bscv_probe_check(bscv_soft_state_t *ssp) +{ + int i; + uint8_t probeval; + + ASSERT(bscv_held(ssp)); + + bscv_trace(ssp, 'A', "bscv_probe_check", ""); + + if (!ssp->prog_mode_only) { + /* + * Make sure probe location is OK so that we are + * in sync. + * We want to make sure that this is not faulty so we + * do a bscv_clear_fault to clear any existing + * fault records down. + */ + bscv_clear_fault(ssp); + probeval = bscv_get8(ssp, chan_general, EBUS_IDX_PROBEAA); + if (bscv_faulty(ssp)) { + ssp->prog_mode_only = B_TRUE; + } else if (probeval != 0xAA) { + bscv_trace(ssp, 'A', "bscv_probe_check", + "LOMlite out of sync"); + + /* + * It may be that the LOMlite was out of + * sync so lets try the read again. + */ + probeval = bscv_get8(ssp, chan_general, + EBUS_IDX_PROBEAA); + if (bscv_faulty(ssp)) { + bscv_trace(ssp, 'A', "bscv_probe_check", + "Init readAA1 failed"); + ssp->prog_mode_only = B_TRUE; + } else if (probeval != 0xAA) { + /* + * OK that is twice we are out so I + * guess the LOMlite is in trouble + */ + bscv_trace(ssp, 'A', "bscv_probe_check", + "Init readAA probe failed - got 0x%x", + probeval); + ssp->prog_mode_only = B_TRUE; + } + } + } + + /* + * Read in all page zero lom registers. + * Read state change 1st so we dont miss anything and clear it. + * Note: we discard the values because we rely on bscv_get8 to + * setup the cache of register values. + */ + + if (!ssp->prog_mode_only) { + (void) bscv_get8(ssp, chan_general, EBUS_IDX_STATE_CHNG); + if (bscv_faulty(ssp)) { + bscv_trace(ssp, 'A', "bscv_probe_check", + "Read of state change register failed"); + ssp->prog_mode_only = B_TRUE; + } + } + + if (!ssp->prog_mode_only) { + for (i = 1; i < 0x80; i++) { + switch (i) { + case EBUS_IDX_STATE_CHNG: + case EBUS_IDX_CMD_RES: + case EBUS_IDX_HNAME_CHAR: + /* + * Should not read these - they have side + * effects. + */ + break; + default: + (void) bscv_get8(ssp, chan_general, i); + break; + } + if (bscv_faulty(ssp)) { + bscv_trace(ssp, 'A', "bscv_probe_check", + "Initial read or register %2x failed", i); + ssp->prog_mode_only = B_TRUE; + /* Might as well give up now! */ + break; + } + } + } + + /* + * Check the probe keys so we know the lom is OK + */ + + if (!ssp->prog_mode_only) { + if ((bscv_get8_cached(ssp, EBUS_IDX_PROBE55) != 0x55) || + (bscv_get8_cached(ssp, EBUS_IDX_PROBEAA) != 0xAA)) { + + bscv_trace(ssp, 'A', "bscv_probe_check", + "LOMlite Probe failed"); + for (i = 0; i < 0x8; i++) { + bscv_trace(ssp, 'A', "bscv_probe_check", + "%2x %2x %2x %2x %2x %2x %2x %2x %2x " + "%2x %2x %2x %2x %2x %2x %2x %2x %2x", + bscv_get8_cached(ssp, i), + bscv_get8_cached(ssp, i + 1), + bscv_get8_cached(ssp, i + 2), + bscv_get8_cached(ssp, i + 3), + bscv_get8_cached(ssp, i + 4), + bscv_get8_cached(ssp, i + 5), + bscv_get8_cached(ssp, i + 6), + bscv_get8_cached(ssp, i + 7), + bscv_get8_cached(ssp, i + 8), + bscv_get8_cached(ssp, i + 9), + bscv_get8_cached(ssp, i + 10), + bscv_get8_cached(ssp, i + 11), + bscv_get8_cached(ssp, i + 12), + bscv_get8_cached(ssp, i + 13), + bscv_get8_cached(ssp, i + 14), + bscv_get8_cached(ssp, i + 15)); + } + ssp->prog_mode_only = B_TRUE; + } + } + + return ((ssp->prog_mode_only == B_FALSE) ? DDI_SUCCESS : DDI_FAILURE); +} + +#ifdef __sparc +/* + * function - bscv_idi_set + * description - bscv inter driver interface set function + * inputs - structure which defines type of service required and data + * ouputs - none + * + * This is the Entry Point function for the platmod driver. It works out which + * X Bus channel ought to deliver the service requested. + */ +void +bscv_idi_set(struct bscv_idi_info info) +{ + struct bscv_idi_callout *tbl; + boolean_t retval; + + ASSERT(bscv_idi_mgr.magic == BSCV_IDI_CALLOUT_MAGIC); + + if (bscv_idi_mgr.tbl == NULL) { + if (bscv_idi_err()) + cmn_err(CE_WARN, "!bscv_idi_set : cannot find " + "bscv_callout_table"); + return; + } else if (bscv_idi_mgr.valid_inst == (uint32_t)~0) { + if (bscv_idi_err()) + /* + * This error message can appear in the context of + * another driver, say platmod or todblade. We want + * to clearly indicate the culprit driver so put in + * the driver name. + */ + cmn_err(CE_WARN, "!bscv_idi_set : no valid " + "driver instance of " + MYNAME); + return; + } + + tbl = bscv_idi_mgr.tbl; + + while (tbl->type != BSCV_IDI_NULL) { + if (tbl->type == info.type) { + /* + * We service the request with a valid instance number + * for the driver. + */ + retval = ((tbl->fn) (info)); + + /* + * If the request was serviced, clear any accumulated + * error counters so future warnings will be reported if + * seen. + */ + if (retval == B_TRUE) + bscv_idi_clear_err(); + return; + } else { + tbl++; + } + } + + if (bscv_idi_err()) + cmn_err(CE_WARN, "!bscv_idi_set : cannot match info.type %d", + info.type); +} + +/* + * function - bscv_nodename_set + * description - notify the event thread that a nodename change has occurred. + * inputs - data from client driver + * outputs - none. + * side-effects - the event thread will schedule an update to the lom firmware. + */ +/*ARGSUSED*/ +static boolean_t +bscv_nodename_set(struct bscv_idi_info info) +{ + bscv_soft_state_t *ssp; + + ssp = ddi_get_soft_state(bscv_statep, bscv_idi_mgr.valid_inst); + + if (ssp == NULL) { + if (bscv_idi_err()) + cmn_err(CE_WARN, "!blade_nodename_set: cannot get ssp"); + return (B_FALSE); + } + + /* Get a lock on the SSP, notify our change, then exit */ + mutex_enter(&ssp->task_mu); + ssp->nodename_change = B_TRUE; + cv_signal(&ssp->task_cv); + mutex_exit(&ssp->task_mu); + + return (B_TRUE); +} + +/* + * function - bscv_sig_set + * description - write a signature + * inputs - data from client driver + * outputs - none. + */ +static boolean_t +bscv_sig_set(struct bscv_idi_info info) +{ + bscv_soft_state_t *ssp; + bscv_sig_t sig; + + ssp = ddi_get_soft_state(bscv_statep, bscv_idi_mgr.valid_inst); + + if (ssp == NULL) { + if (bscv_idi_err()) + cmn_err(CE_WARN, "!blade_nodename_set: cannot get ssp"); + return (B_FALSE); + } + + /* Service the request */ + bcopy(info.data, &sig, sizeof (sig)); + bscv_enter(ssp); + bscv_write_sig(ssp, sig); + bscv_exit(ssp); + + return (B_TRUE); +} +#endif /* __sparc */ + +static void +bscv_wdog_do_pat(bscv_soft_state_t *ssp) +{ + uint8_t pat; + + /* + * The value of the dog pat is a sequence number which wraps around, + * bounded by BSCV_WDOG_PAT_SEQ_MASK. + */ + pat = ssp->pat_seq++; + pat &= EBUS_WDOG_NB_PAT_SEQ_MASK; + + /* Set top nibble to indicate a pat */ + pat |= EBUS_WDOG_NB_PAT; + + /* + * Now pat the dog. This exercises a special protocol in the + * bus nexus that offers : non-blocking IO, and timely delivery, + * callable from high-level interrupt context. The requirement + * on us is that the channel is not shared for any other use. + * This means for chan_wdogpat, nothing may use channel[chan].regs + * or channel.[chan].handle. + */ + + ddi_put8(ssp->channel[chan_wdogpat].handle, + ssp->channel[chan_wdogpat].regs, pat); + + bscv_trace(ssp, 'W', "bscv_wdog_pat", "patted the dog with seq %d", + pat); +} + +#ifdef __sparc +/* + * function - bscv_wdog_pat + * description - pat the watchdog + * inputs - data from client driver + * outputs - none. + */ +/*ARGSUSED*/ +static boolean_t +bscv_wdog_pat(struct bscv_idi_info info) +{ + /* + * This function remembers if it has ever been called with the + * configure option set. + */ + bscv_soft_state_t *ssp; + + ssp = ddi_get_soft_state(bscv_statep, bscv_idi_mgr.valid_inst); + + if (ssp == NULL) { + if (bscv_idi_err()) + cmn_err(CE_WARN, "!bscv_wdog_pat: cannot get ssp"); + return (B_FALSE); + } else if (ssp->nchannels == 0) { + /* Didn't manage to map handles so ddi_{get,put}* broken */ + if (bscv_idi_err()) + cmn_err(CE_WARN, "!bscv_wdog_pat: handle not mapped"); + return (B_FALSE); + } + + bscv_wdog_do_pat(ssp); + return (B_TRUE); +} + +/* + * function - bscv_wdog_cfg + * description - configure the watchdog + * inputs - data from client driver + * outputs - none. + */ +static boolean_t +bscv_wdog_cfg(struct bscv_idi_info info) +{ + bscv_soft_state_t *ssp; + + ssp = ddi_get_soft_state(bscv_statep, bscv_idi_mgr.valid_inst); + + if (ssp == NULL) { + if (bscv_idi_err()) + cmn_err(CE_WARN, "!bscv_wdog_cfg: cannot get ssp"); + return (B_FALSE); + } else if (ssp->nchannels == 0) { + /* Didn't manage to map handles so ddi_{get,put}* broken */ + if (bscv_idi_err()) + cmn_err(CE_WARN, "!bscv_wdog_cfg: handle not mapped"); + return (B_FALSE); + } + + if (sizeof (bscv_wdog_t) != info.size) { + bscv_trace(ssp, 'W', "bscv_wdog_set", "data passed in is size" + " %d instead of %d", info.size, + sizeof (bscv_wdog_t)); + return (B_FALSE); + } + + bscv_trace(ssp, 'W', "bscv_wdog_cfg", "enable_wdog %s, " + "wdog_timeout_s %d, reset_system_on_timeout %s", + ((bscv_wdog_t *)info.data)->enable_wdog ? "enabled" : "disabled", + ((bscv_wdog_t *)info.data)->wdog_timeout_s, + ((bscv_wdog_t *)info.data)->reset_system_on_timeout ? "yes" : "no"); + bscv_write_wdog_cfg(ssp, + ((bscv_wdog_t *)info.data)->wdog_timeout_s, + ((bscv_wdog_t *)info.data)->enable_wdog, + ((bscv_wdog_t *)info.data)->reset_system_on_timeout); + return (B_TRUE); +} +#endif /* __sparc */ + +static void +bscv_write_wdog_cfg(bscv_soft_state_t *ssp, + uint_t wdog_timeout_s, + boolean_t enable_wdog, + uint8_t reset_system_on_timeout) +{ + uint8_t cfg = EBUS_WDOG_NB_CFG; + + /* + * Configure the timeout value (1 to 127 seconds). + * Note that a policy is implemented at the bsc/ssp which bounds + * the value further. The bounding here is to fit the timeout value + * into the 7 bits the bsc uses. + */ + if (wdog_timeout_s < 1) + ssp->watchdog_timeout = 1; + else if (wdog_timeout_s > 127) + ssp->watchdog_timeout = 127; + else + ssp->watchdog_timeout = wdog_timeout_s; + + /* + * Configure the watchdog on or off. + */ + if (enable_wdog) + cfg |= EBUS_WDOG_NB_CFG_ENB; + else + cfg &= ~EBUS_WDOG_NB_CFG_ENB; + + /* + * Configure whether the microcontroller should reset the system when + * the watchdog expires. + */ + ssp->watchdog_reset_on_timeout = reset_system_on_timeout; + + ddi_put8(ssp->channel[chan_wdogpat].handle, + ssp->channel[chan_wdogpat].regs, cfg); + + /* have the event daemon set the timeout value and whether to reset */ + ssp->watchdog_change = B_TRUE; + + bscv_trace(ssp, 'W', "bscv_wdog_cfg", + "configured the dog with cfg 0x%x", cfg); +} + +/* + * function - bscv_setup_watchdog + * description - setup the bsc watchdog + * inputs - soft state ptr + * outputs - + */ +static void bscv_setup_watchdog(bscv_soft_state_t *ssp) +{ + uint8_t set = 0; + uint8_t clear = 0; +#ifdef __sparc + extern int watchdog_activated; +#endif /* __sparc */ + + ASSERT(bscv_held(ssp)); + + /* Set the timeout */ + bscv_put8(ssp, chan_general, + EBUS_IDX_WDOG_TIME, ssp->watchdog_timeout); + + /* Set whether to reset the system on timeout */ + if (ssp->watchdog_reset_on_timeout) { + set |= EBUS_WDOG_RST; + } else { + clear |= EBUS_WDOG_RST; + } + + if (watchdog_activated) { + set |= EBUS_WDOG_ENABLE; + } else { + clear |= EBUS_WDOG_ENABLE; + } + + /* Set other host defaults */ + clear |= (EBUS_WDOG_BREAK_DISABLE | EBUS_WDOG_AL3_FANPSU + | EBUS_WDOG_AL3_WDOG); + + bscv_setclear8_volatile(ssp, chan_general, EBUS_IDX_WDOG_CTRL, + set, clear); + +#if defined(__i386) || defined(__amd64) + /* start the cyclic based watchdog patter */ + mutex_enter(&cpu_lock); + bscv_watchdog_cyclic_add(ssp); + mutex_exit(&cpu_lock); +#endif /* __i386 || __amd64 */ + ssp->progress |= BSCV_WDOG_CFG; +} + + +/* + * function - bscv_setup_hostname + * description - setup the lom hostname if different from the nodename + * inputs - soft state ptr + * outputs - none + */ + +static void bscv_setup_hostname(bscv_soft_state_t *ssp) +{ + char host_nodename[128]; + char lom_nodename[128]; + size_t hostlen; + size_t nodelen; + + ASSERT(bscv_held(ssp)); + + /* + * Check machine label is the same as the + * system nodename. + */ + (void) strncpy(host_nodename, utsname.nodename, + sizeof (host_nodename)); + + /* read in lom hostname */ + bscv_read_hostname(ssp, lom_nodename); + + /* Enforce null termination */ + host_nodename[sizeof (host_nodename) - 1] = '\0'; + lom_nodename[sizeof (lom_nodename) - 1] = '\0'; + + hostlen = (size_t)bscv_get8(ssp, chan_general, EBUS_IDX_HNAME_LENGTH); + nodelen = (size_t)strlen(host_nodename); + if ((nodelen > 0) && + ((hostlen != nodelen) || (strcmp((const char *)&lom_nodename, + (const char *)&host_nodename)) || + (hostlen == 0))) { + bscv_trace(ssp, 'A', "bscv_setup_hostname", + "nodename(%s,%d) != bsc label(%s,%d)", + host_nodename, nodelen, lom_nodename, hostlen); + + /* Write new label into LOM EEPROM */ + bscv_write_hostname(ssp, + host_nodename, + (uint8_t)strlen(host_nodename)); + } + + ssp->progress |= BSCV_HOSTNAME_DONE; +} + +/* + * function - bscv_read_hostname + * description - read the current hostname from the lom + * inputs - soft state pointer and buffer to store the hostname in. + * outputs - none + */ + +static void +bscv_read_hostname(bscv_soft_state_t *ssp, char *lom_nodename) +{ + int num_failures; + boolean_t needretry; + int length; + int i; + + ASSERT(bscv_held(ssp)); + + /* + * We have a special failure case here because a retry of a read + * causes data to be lost. Thus we handle the retries ourselves + * and are also responsible for detemining if the lom is faulty + */ + for (num_failures = 0; + num_failures < BSC_FAILURE_RETRY_LIMIT; + num_failures++) { + bscv_clear_fault(ssp); + length = bscv_get8(ssp, chan_general, EBUS_IDX_HNAME_LENGTH); + if (bscv_faulty(ssp)) { + needretry = 1; + } else { + needretry = 0; + for (i = 0; i < length; i++) { + lom_nodename[i] = bscv_get8_once(ssp, + chan_general, EBUS_IDX_HNAME_CHAR); + /* Retry on any error */ + if (bscv_retcode(ssp) != 0) { + needretry = 1; + break; + } + } + /* null terminate for strcmp later */ + lom_nodename[length] = '\0'; + } + if (!needretry) { + break; + } + /* Force the nodename to be empty */ + lom_nodename[0] = '\0'; + } + + if (needretry) { + /* Failure - we ran out of retries */ + cmn_err(CE_WARN, + "bscv_read_hostname: retried %d times, giving up", + num_failures); + ssp->had_fault = B_TRUE; + } else if (num_failures > 0) { + bscv_trace(ssp, 'R', "bscv_read_hostname", + "retried %d times, succeeded", num_failures); + } +} + +/* + * function - bscv_write_hostname + * description - write a new hostname to the lom + * inputs - soft state pointer, pointer to new name, name length + * outputs - none + */ +static void +bscv_write_hostname(bscv_soft_state_t *ssp, + char *host_nodename, uint8_t length) +{ + int num_failures; + boolean_t needretry; + int i; + + ASSERT(bscv_held(ssp)); + + /* + * We have a special failure case here because a retry of a read + * causes data to be lost. Thus we handle the retries ourselves + * and are also responsible for detemining if the lom is faulty + */ + for (num_failures = 0; + num_failures < BSC_FAILURE_RETRY_LIMIT; + num_failures++) { + bscv_clear_fault(ssp); + bscv_put8(ssp, chan_general, EBUS_IDX_HNAME_LENGTH, length); + if (bscv_faulty(ssp)) { + needretry = 1; + } else { + needretry = 0; + for (i = 0; i < length; i++) { + bscv_put8_once(ssp, chan_general, + EBUS_IDX_HNAME_CHAR, host_nodename[i]); + /* Retry on any error */ + if (bscv_retcode(ssp) != 0) { + needretry = 1; + break; + } + } + } + if (!needretry) { + break; + } + } + + if (needretry) { + /* Failure - we ran out of retries */ + cmn_err(CE_WARN, + "bscv_write_hostname: retried %d times, giving up", + num_failures); + ssp->had_fault = B_TRUE; + } else if (num_failures > 0) { + bscv_trace(ssp, 'R', "bscv_write_hostname", + "retried %d times, succeeded", num_failures); + } +} + +/* + * function - bscv_setup_static_info + * description - read in static information from the lom at attach time. + * inputs - soft state ptr + * outputs - none + */ + +static void +bscv_setup_static_info(bscv_soft_state_t *ssp) +{ + uint8_t addr_space_ptr; + uint16_t mask; + uint8_t fanspeed; + int oldtemps[MAX_TEMPS]; + int8_t temp; + int i; + + ASSERT(bscv_held(ssp)); + + /* + * Finally read in some static info like device names, + * shutdown enabled, etc before the queue starts. + */ + + /* + * To get the volts static info we need address space 2 + */ + bzero(&ssp->volts, sizeof (lom_volts_t)); + ssp->volts.num = EBUS_CONFIG2_NSUPPLY_DEC( + bscv_get8(ssp, chan_general, EBUS_IDX_CONFIG2)); + if (ssp->volts.num > MAX_VOLTS) { + cmn_err(CE_WARN, + "lom: firmware reported too many voltage lines. "); + cmn_err(CE_CONT, "Reported %d, maximum is %d", + ssp->volts.num, MAX_VOLTS); + ssp->volts.num = MAX_VOLTS; + } + + bscv_trace(ssp, 'A', "bscv_setup_static_info", + "num volts %d", ssp->volts.num); + (void) bscv_read_env_name(ssp, + EBUS_CMD_SPACE2, + EBUS_IDX2_SUPPLY_NAME_START, + EBUS_IDX2_SUPPLY_NAME_END, + ssp->volts.name, + ssp->volts.num); + + mask = bscv_get8(ssp, chan_general, BSCVA(EBUS_CMD_SPACE2, + EBUS_IDX2_SUPPLY_FATAL_MASK1)) << 8; + mask |= bscv_get8(ssp, chan_general, BSCVA(EBUS_CMD_SPACE2, + EBUS_IDX2_SUPPLY_FATAL_MASK2)); + + for (i = 0; i < ssp->volts.num; i++) { + ssp->volts.shutdown_enabled[i] = + (((mask >> i) & 1) == 0) ? 0 : 1; + } + + /* + * Get the temperature static info and populate initial temperatures. + * Do not destroy old temperature values if the new value is not + * known i.e. if the device is inaccessible. + */ + bcopy(ssp->temps.temp, oldtemps, sizeof (oldtemps)); + + bzero(&ssp->temps, sizeof (lom_temp_t)); + ssp->temps.num = EBUS_CONFIG2_NTEMP_DEC( + bscv_get8(ssp, chan_general, EBUS_IDX_CONFIG2)); + if (ssp->temps.num > MAX_TEMPS) { + cmn_err(CE_WARN, + "lom: firmware reported too many temperatures being " + "monitored."); + cmn_err(CE_CONT, "Reported %d, maximum is %d", + ssp->temps.num, MAX_TEMPS); + ssp->temps.num = MAX_TEMPS; + } + ssp->temps.num_ov = EBUS_CONFIG3_NOTEMP_DEC( + bscv_get8(ssp, chan_general, EBUS_IDX_CONFIG3)); + if (ssp->temps.num_ov > MAX_TEMPS) { + cmn_err(CE_WARN, + "lom: firmware reported too many over temperatures being " + "monitored."); + cmn_err(CE_CONT, "Reported %d, maximum is %d", + ssp->temps.num_ov, MAX_TEMPS); + ssp->temps.num_ov = MAX_TEMPS; + } + bscv_trace(ssp, 'A', "bscv_setup_static_info", + "num temps %d, over temps %d", + ssp->temps.num, ssp->temps.num_ov); + + addr_space_ptr = bscv_read_env_name(ssp, + EBUS_CMD_SPACE4, + EBUS_IDX4_TEMP_NAME_START, + EBUS_IDX4_TEMP_NAME_END, + ssp->temps.name, + ssp->temps.num); + + for (i = 0; i < ssp->temps.num; i++) { + ssp->temps.warning[i] = (int8_t)bscv_get8(ssp, chan_general, + BSCVA(EBUS_CMD_SPACE4, EBUS_IDX4_TEMP_WARN1 + i)); + + /* + * If shutdown is not enabled then set it as zero so + * it is not displayed by the utility. + */ + if ((bscv_get8(ssp, chan_general, BSCVA(EBUS_CMD_SPACE4, + EBUS_IDX4_TEMP_FATAL_MASK)) >> i) & 0x01) { + ssp->temps.shutdown[i] = (int8_t)bscv_get8(ssp, + chan_general, + BSCVA(EBUS_CMD_SPACE4, EBUS_IDX4_TEMP_SDOWN1 + i)); + } else { + ssp->temps.shutdown[i] = 0; + } + } + + for (i = 0; i < ssp->temps.num; i++) { + temp = bscv_get8(ssp, chan_general, EBUS_IDX_TEMP1 + i); + if ((temp <= LOM_TEMP_MAX_VALUE) || + (temp == LOM_TEMP_STATE_NOT_PRESENT)) { + ssp->temps.temp[i] = temp; + } else { + /* New value is not known - use old value */ + ssp->temps.temp[i] = oldtemps[i]; + } + } + + /* + * Check for and skip a single 0xff character between the + * temperature and over temperature names + */ + if (bscv_get8(ssp, chan_general, + BSCVA(EBUS_CMD_SPACE4, addr_space_ptr)) == 0xff) { + addr_space_ptr++; + } + + (void) bscv_read_env_name(ssp, + EBUS_CMD_SPACE4, + addr_space_ptr, + EBUS_IDX4_TEMP_NAME_END, + ssp->temps.name_ov, + ssp->temps.num_ov); + + /* + * To get the CB static info we need address space 3 + */ + bzero(&ssp->sflags, sizeof (lom_sflags_t)); + ssp->sflags.num = EBUS_CONFIG3_NBREAKERS_DEC(bscv_get8(ssp, + chan_general, EBUS_IDX_CONFIG3)); + if (ssp->sflags.num > MAX_STATS) { + cmn_err(CE_WARN, + "lom: firmware reported too many status flags."); + cmn_err(CE_CONT, + "Reported %d, maximum is %d", + ssp->sflags.num, MAX_STATS); + ssp->sflags.num = MAX_STATS; + } + bscv_trace(ssp, 'A', "bscv_setup_static_info", + "num sflags %d", ssp->sflags.num); + + (void) bscv_read_env_name(ssp, + EBUS_CMD_SPACE3, + EBUS_IDX3_BREAKER_NAME_START, + EBUS_IDX3_BREAKER_NAME_END, + ssp->sflags.name, + ssp->sflags.num); + + + /* + * To get the fan static info we need address space 5 + */ + ssp->num_fans = EBUS_CONFIG_NFAN_DEC( + bscv_get8(ssp, chan_general, EBUS_IDX_CONFIG)); + if (ssp->num_fans > MAX_FANS) { + cmn_err(CE_WARN, + "lom: firmware reported too many fans. "); + cmn_err(CE_CONT, + "Reported %d, maximum is %d", + ssp->num_fans, MAX_FANS); + ssp->num_fans = MAX_FANS; + } + + for (i = 0; i < ssp->num_fans; i++) { + fanspeed = bscv_get8(ssp, chan_general, + EBUS_IDX_FAN1_SPEED + i); + if ((fanspeed <= LOM_FAN_MAX_SPEED) || + (fanspeed == LOM_FAN_NOT_PRESENT)) { + /* + * Do not destroy previous values unless the + * value is definitive. + */ + ssp->fanspeed[i] = fanspeed; + } + } + + bscv_trace(ssp, 'A', "bscv_setup_static_info", + "num fans %d", ssp->num_fans); + + (void) bscv_read_env_name(ssp, + EBUS_CMD_SPACE5, + EBUS_IDX5_FAN_NAME_START, + EBUS_IDX5_FAN_NAME_END, + ssp->fan_names, + ssp->num_fans); + + /* Get led static information from address space 10 */ + + (void) bscv_read_env_name(ssp, + EBUS_CMD_SPACE_LEDS, + EBUS_IDX10_LED_NAME_START, + EBUS_IDX10_LED_NAME_END, + ssp->led_names, + MAX_LED_ID); +} + +/* + * function - bscv_read_env_name + * description - read in static environment names + * warning changes address space and the caller relies + * on this behaviour. + * inputs - soft state ptr, chosen address space, + * start of name data, end of name data, + * name storage, number of names. + * outputs - next address for reading name data. + */ + +static uint8_t +bscv_read_env_name(bscv_soft_state_t *ssp, + uint8_t addr_space, + uint8_t addr_start, + uint8_t addr_end, + char namebuf[][MAX_LOM2_NAME_STR], + int numnames) +{ + int i; + int nameidx; + int namemax; + unsigned int addr_space_ptr; + uint8_t this_char; + + ASSERT(bscv_held(ssp)); + + bscv_trace(ssp, 'A', "bscv_read_env_name", + "bscv_read_env_name, space %d, start 0x%x, end 0x%x, numnames %d", + addr_space, addr_start, addr_end, numnames); + + addr_space_ptr = addr_start; + + for (i = 0; i < numnames; i++) { + nameidx = 0; + namemax = sizeof (namebuf[i]); + bzero(namebuf[i], namemax); + + while (addr_space_ptr <= addr_end) { + /* + * Read the current character. + */ + this_char = bscv_get8(ssp, chan_general, + BSCVA(addr_space, addr_space_ptr)); + + if (this_char == 0xff) { + /* + * Ran out of names - this must + * be the end of the name. + * This is really an error because + * we have just seen either a non-NUL + * terminated string or the number of + * strings did not match what was + * reported. + */ + break; + } + /* + * We increment the buffer pointer now so that + * it is ready for the next read + */ + addr_space_ptr++; + + if (this_char == '\0') { + /* Found end of string - done */ + break; + } + if (nameidx < (namemax - 1)) { + /* + * Buffer not full - record character + * NOTE we always leave room for the NUL + * terminator. + */ + namebuf[i][nameidx++] = this_char; + } + } + /* Ensure null termination */ + namebuf[i][nameidx] = '\0'; + } + /* Clamp addr_space_ptr to 0xff because we return uint8_t */ + if (addr_space_ptr > 0xff) { + addr_space_ptr = 0xff; + } + return (addr_space_ptr); +} + +/* + * function - bscv_setup_events + * description - initialise the event reporting code + * inputs - soft state ptr + * outputs - DDI_SUCCESS or DDI_FAILURE + */ + +static void +bscv_setup_events(bscv_soft_state_t *ssp) +{ + uint8_t bits2set; + uint8_t bits2clear; + + ASSERT(bscv_held(ssp)); + + /* + * deal with event reporting - cover all cases + */ + + bits2set = 0; + bits2clear = 0; + if (ssp->serial_reporting == LOM_SER_EVENTS_ON) { + bits2clear |= EBUS_ALARM_NOEVENTS; + } else if (ssp->serial_reporting == LOM_SER_EVENTS_OFF) { + bits2set |= EBUS_ALARM_NOEVENTS; + } else if (ssp->serial_reporting == LOM_SER_EVENTS_DEF) { + bits2set |= EBUS_ALARM_NOEVENTS; + } + bscv_setclear8_volatile(ssp, chan_general, EBUS_IDX_ALARM, + bits2set, bits2clear); +} + +#ifdef __sparc +/* + * function - bscv_write_sig + * description - write out a signature, taking care to deal with any strange + * values for CPU ID + * inputs - soft state ptr, signature + * outputs - none + */ +static void +bscv_write_sig(bscv_soft_state_t *ssp, bscv_sig_t s) +{ + ASSERT(bscv_held(ssp)); + + /* Upload the signature */ + bscv_put32(ssp, chan_cpusig, + BSCVA(EBUS_CMD_SPACE_CPUSIG, EBUS_IDX11_CPU_SIG_MSB), + s.sig_info.signature); + + /* + * We always write the CPU ID last because this tells the firmware + * that the signature is fully uploaded and therefore to consume the + * data. This is required since the signature is > 1 byte in size + * and we transmit data in single bytes. + */ + if (s.cpu == ~0) { + /* ~0 means the signature applies to any CPU. */ + bscv_put8(ssp, chan_cpusig, + BSCVA(EBUS_CMD_SPACE_CPUSIG, EBUS_IDX11_CPU_ID), + EBUS_ANY_CPU_ID); + } else { + if (s.cpu > 255) { + /* + * The CPU ID supplied is unexpectedly large. Lets + * just use the bottom bits, in case other high order + * bits are being used for special meaning. + */ + cmn_err(CE_WARN, "CPU Signature ID 0x%x > 255", s.cpu); + s.cpu %= 256; + cmn_err(CE_CONT, "using ID 0x%x instead ", s.cpu); + } + bscv_put8(ssp, chan_cpusig, + BSCVA(EBUS_CMD_SPACE_CPUSIG, EBUS_IDX11_CPU_ID), + (uint8_t)s.cpu); + } + + ssp->last_sig = s; + ssp->progress |= BSCV_SIG_SENT; +} +#endif /* __sparc */ + +#if defined(__i386) || defined(__amd64) + +/* + * function - bscv_inform_bsc + * description - inform bsc of driver state for logging purposes + * inputs - driver soft state, state + * outputs - none + * + */ +static void +bscv_inform_bsc(bscv_soft_state_t *ssp, uint32_t state) +{ + ASSERT(bscv_held(ssp)); + + bscv_trace(ssp, 'X', "bscv_inform_bsc", + "bscv_inform_bsc: state=%d", state); + + bscv_put32(ssp, chan_general, + BSCVA(EBUS_CMD_SPACE_CPUSIG, EBUS_IDX11_CPU_SIG_MSB), state); + bscv_put8(ssp, chan_cpusig, + BSCVA(EBUS_CMD_SPACE_CPUSIG, EBUS_IDX11_CPU_ID), EBUS_ANY_CPU_ID); +} + +/* + * function - bscv_watchdog_pat_request + * description - request a heartbeat pat + * inputs - timeout value in seconds + * outputs - none + */ +static void +bscv_watchdog_pat_request(void *arg) +{ + bscv_soft_state_t *ssp = (bscv_soft_state_t *)arg; + + bscv_wdog_do_pat(ssp); +} + +/* + * function - bscv_watchdog_cfg_request + * description - request configuration of the bsc hardware watchdog + * inputs - new state (0=disabled, 1=enabled) + * outputs - one if successful, zero if unsuccesful + */ +static void +bscv_watchdog_cfg_request(bscv_soft_state_t *ssp, uint8_t new_state) +{ + ASSERT(new_state == WDOG_ON || new_state == WDOG_OFF); + + watchdog_activated = new_state; + bscv_trace(ssp, 'X', "bscv_watchdog_cfg_request", + "watchdog_activated=%d", watchdog_activated); + bscv_write_wdog_cfg(ssp, + bscv_watchdog_timeout_seconds, + new_state, + wdog_reset_on_timeout); +} + +/* + * function - bscv_set_watchdog_timer + * description - setup the heartbeat timeout value + * inputs - timeout value in seconds + * outputs - zero if the value was not changed + * otherwise the current value + */ +static uint_t +bscv_set_watchdog_timer(bscv_soft_state_t *ssp, uint_t timeoutval) +{ + bscv_trace(ssp, 'X', "bscv_set_watchdog_timer:", + "timeout=%d", timeoutval); + + /* + * We get started during bscv_attach only + * if bscv_watchdog_enable is set. + */ + if (bscv_watchdog_available && (!watchdog_activated || + (watchdog_activated && + (timeoutval != bscv_watchdog_timeout_seconds)))) { + bscv_watchdog_timeout_seconds = timeoutval; + bscv_watchdog_cfg_request(ssp, WDOG_ON); + return (bscv_watchdog_timeout_seconds); + } + return (0); +} + +/* + * function - bscv_clear_watchdog_timer + * description - add the watchdog patter cyclic + * inputs - driver soft state + * outputs - value of watchdog timeout in seconds + * + * This function is a copy of the SPARC implementation + * in the todblade clock driver. + */ +static void +bscv_clear_watchdog_timer(bscv_soft_state_t *ssp) +{ + bscv_trace(ssp, 'X', "bscv_clear_watchdog_timer", ""); + + if (bscv_watchdog_available && watchdog_activated) { + bscv_watchdog_enable = 0; + bscv_watchdog_cfg_request(ssp, WDOG_OFF); + } +} + +/* + * function - bscv_panic_callback + * description - called when we panic so we can disabled the watchdog + * inputs - driver soft state pointer + * outputs - DDI_SUCCESS + */ +/*ARGSUSED1*/ +static boolean_t +bscv_panic_callback(void *arg, int code) +{ + bscv_soft_state_t *ssp = (bscv_soft_state_t *)arg; + + bscv_trace(ssp, 'X', "bscv_panic_callback", + "disabling watchdog"); + + bscv_clear_watchdog_timer(ssp); + /* + * We dont get interrupts during the panic callback. But bscbus + * takes care of all this + */ + bscv_full_stop(ssp); + return (DDI_SUCCESS); +} + +/* + * function - bscv_watchdog_cyclic_add + * description - add the watchdog patter cyclic + * inputs - driver soft state + * outputs - none + */ +static void +bscv_watchdog_cyclic_add(bscv_soft_state_t *ssp) +{ + cyc_handler_t hdlr; + cyc_time_t when; + + ASSERT(MUTEX_HELD(&cpu_lock)); /* for cyclic_add */ + + if (ssp->cyclic_id != CYCLIC_NONE) { + return; + } + + hdlr.cyh_level = CY_LOCK_LEVEL; + hdlr.cyh_func = (cyc_func_t)bscv_watchdog_pat_request; + hdlr.cyh_arg = (void *)ssp; + + when.cyt_when = 0; + when.cyt_interval = WATCHDOG_PAT_INTERVAL; + + ssp->cyclic_id = cyclic_add(&hdlr, &when); + + bscv_trace(ssp, 'X', "bscv_watchdog_cyclic_add:", + "cyclic added"); +} + +/* + * function - bscv_watchdog_cyclic_remove + * description - remove the watchdog patter cyclic + * inputs - soft state ptr + * outputs - none + */ +static void +bscv_watchdog_cyclic_remove(bscv_soft_state_t *ssp) +{ + ASSERT(MUTEX_HELD(&cpu_lock)); /* for cyclic_remove */ + + if (ssp->cyclic_id == CYCLIC_NONE) { + return; + } + + cyclic_remove(ssp->cyclic_id); + ssp->cyclic_id = CYCLIC_NONE; + bscv_trace(ssp, 'X', "bscv_watchdog_cyclic_remove:", + "cyclic removed"); +} +#endif /* __i386 || __amd64 */ + + +/* + * General utility routines ... + */ + +#ifdef DEBUG + +static void +bscv_trace(bscv_soft_state_t *ssp, char code, const char *caller, + const char *fmt, ...) +{ + char buf[256]; + char *p; + va_list va; + + if (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((short)ssp->majornum, (short)ssp->minornum, code, + SL_TRACE, buf); + } +} + +#else /* DEBUG */ + +_NOTE(ARGSUSED(0)) +static void +bscv_trace(bscv_soft_state_t *ssp, char code, const char *caller, + const char *fmt, ...) +{ +} + +#endif /* DEBUG */ diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile index 6b1b5bb8c3..eb3a21935c 100644 --- a/usr/src/uts/common/sys/Makefile +++ b/usr/src/uts/common/sys/Makefile @@ -594,6 +594,8 @@ AUDIOHDRS= \ g711.h BSCHDRS= \ + bscbus.h \ + bscv_impl.h \ lom_ebuscodes.h \ lom_io.h \ lom_priv.h \ diff --git a/usr/src/uts/common/sys/bscbus.h b/usr/src/uts/common/sys/bscbus.h new file mode 100644 index 0000000000..0c57cb93f3 --- /dev/null +++ b/usr/src/uts/common/sys/bscbus.h @@ -0,0 +1,63 @@ +/* + * 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 2001-2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_BSCBUS_H +#define _SYS_BSCBUS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The bscbus nexus driver provides the same client interface as the lombus + * nexus driver. + */ +#include <sys/lombus.h> + +/* + * Register spaces (as lombus.h but spaces now have a channel + * value encoded in it too) + * + * Space* Size Range Meaning + * (bits) + * + * xx00 8 [0 .. 16383] LOM virtual registers + * xx01 8 [0] Watchdog pat (on write) + * xx02 16 [0] Async event info (read only) + * All 32 [-4 .. -12] Access handle fault info + * * xx is the channel number. + */ + +#define LOMBUS_SPACE_TO_REGSET(rsp) ((rsp) & 0xff) +#define LOMBUS_SPACE_TO_CHANNEL(rsp) (((rsp) & 0xff00) >> 8) +#define LOMBUS_SPACE(regset, channel) ((regset) | ((channel) << 8)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_BSCBUS_H */ diff --git a/usr/src/uts/common/sys/bscv_impl.h b/usr/src/uts/common/sys/bscv_impl.h new file mode 100644 index 0000000000..da4654393d --- /dev/null +++ b/usr/src/uts/common/sys/bscv_impl.h @@ -0,0 +1,369 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_BSCV_IMPL_H +#define _SYS_BSCV_IMPL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Implementation private header file for bscv driver. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/lom_priv.h> + + +/* + * Local #defines + */ + +#define BSCV_SUCCESS DDI_SUCCESS +#define BSCV_FAILURE DDI_FAILURE + +/* + * The following are used as progress indicators in bscv_attach() + */ + +#define BSCV_LOCKS 0x01 +#define BSCV_MAPPED_REGS 0x02 +#define BSCV_NODES 0x04 +#define BSCV_THREAD 0x08 +#define BSCV_HOSTNAME_DONE 0x10 +#define BSCV_WDOG_CFG 0x20 +#define BSCV_SIG_SENT 0x40 + +/* + * macros to encode device minors and provide mapping to device instances. + * The following is designed to get around the problem of a 32-bit app not + * supporting a 32-bit minor number on an LP64 model system. + */ + +#ifdef NBITSMINOR +#undef NBITSMINOR +#define NBITSMINOR 18 +#endif + +#define BSCV_MONITOR_NODE 0 +#define BSCV_CONTROL_NODE (1 << (NBITSMINOR - 1)) + +#define DEVICETOINSTANCE(x) ((getminor(x)) & (~BSCV_CONTROL_NODE)); + +/* + * The maximum number of leds which are supported by this lom implementation. + */ +#define MAX_LED_ID 7 + +/* + * general driver configuration constants which may be changed to improve + * performance/efficiency. + */ + +#define INIT_BUSY_WAIT 10 /* 10 microsecs */ + +#define MAX_WDOGTIMEOUT 127 /* maximum wdog timout - 127s */ + + +/* + * Event processing task status flags. + */ +#define TASK_ALIVE_FLG 0x01 +#define TASK_STOP_FLG 0x02 +#define TASK_SLEEPING_FLG 0x04 +#define TASK_PAUSE_FLG 0x08 +#define TASK_EVENT_PENDING_FLG 0x10 +#define TASK_EVENT_CONSUMER_FLG 0x20 + +/* + * strace(1M) prints out the debug data once the debug value is set in + * the bscv.conf file and the debug driver is installed. + * + * Debug flags + * + * '@' - Register (@)ccess + * 'A' - (A)ttach + * 'B' - (B)lom1 attach extra + * 'C' - lom1 (C)allback + * 'D' - (D)aemon + * 'E' - (E)vents + * 'F' - Sel(F)test + * 'I' - (I)octl + * 'L' - TSa(L)arms + * 'M' - (M)odel parameters + * 'N' - I(N)terrupt Service Routine + * 'O' - (O)pen/Close + * 'P' - (P)rogramming + * 'Q' - (Q)ueue things + * 'R' - Read/Write (R)etry summary. + * 'S' - Event (S)trings + * 'U' - Programming ioctls + * 'V' - ??? + * 'W' - (W)atchdog + * 'X' - additional X86 functional calls + * 'Z' - Temporary - just log things + */ + +/* + * Debug tips : + * + * strace(1M) prints out the debug data. + * A nice way to work out the debug value set in bscv.conf is to use mdb + * Say we want to show 'D' Daemon and 'I' IOCTL processing, + * you calculate the debug value with the following mdb session : + * # mdb + * > 1<<('D'-'@') | 1<<('I'-'@') = X + * 210 + * > $q + * When you insert "debug=0x210;" into bscv.conf, it causes the next + * reboot with the debug driver to trace Daemon and IOCTL functionality. + */ + +/* + * Xbus channel access data + */ + +struct xbus_channel { + ddi_acc_handle_t handle; + uint8_t *regs; +}; + +#define BSCV_MINCHANNELS 2 +#define BSCV_MAXCHANNELS 16 + +/* + * soft state structure + */ + +typedef +struct { + /* + * Hardware instance variables + */ + uint64_t debug; /* debugging turned on */ + major_t majornum; /* debugging - major number */ + minor_t minornum; /* debugging - minor number */ + + dev_info_t *dip; /* pointer to device info tree */ + int instance; /* instance number for the device */ + ddi_device_acc_attr_t attr; /* device access attributes */ + + struct xbus_channel channel[BSCV_MAXCHANNELS]; + int nchannels; + + int progress; /* progress indicator for attach */ + + int bad_resync; /* Number of bad resyncs */ + + /* + * lom data variables/arrays + */ + uint8_t lom_regs[0x80]; /* registers on the lomlite */ + int serial_reporting; + int reporting_level; + + /* + * lom2 static information. + * setup at driver attach and restart after programming. + */ + int num_fans; + char fan_names[MAX_FANS][MAX_LOM2_NAME_STR]; + uint8_t fanspeed[MAX_FANS]; + char led_names[MAX_LED_ID][MAX_LOM2_NAME_STR]; + lom_volts_t volts; /* keep a static copy of this so */ + /* dont have to re-read names */ + lom_temp_t temps; /* keep a static copy of this so */ + /* dont have to re-read names */ + lom_sflags_t sflags; /* keep a static copy of this so */ + /* dont have to re-read names */ + char escape_chars[6]; /* local copy */ + + uint_t watchdog_timeout; + uint8_t watchdog_reset_on_timeout; + + /* + * lom2 firmware communication + */ + + /* + * cmd_mutex protects the lom2 command progress variables. + * These should only be read/updated with the mutex held. + * + * command_error - acts as a return code and may be read + * without the mutex held if a command is not in progress. + * Note a read only returns failure if the lom does not respond. + * So you might need to check the error code to see if things really + * did work! + * + * addr_mu is used to protect stopping and starting of the queue. + * BUT when programming it has different semantics and relies + * on only the programming thread being in the ioctl routine + * whilst programming is in progress. The event queue must also + * be paused at this time. + */ + kmutex_t cmd_mutex; /* LOM command mutual exclusion */ + + int command_error; /* error code from last command */ + /* valid until the next command */ + /* starts. */ + + boolean_t had_fault; /* Current command sequence faulted */ + boolean_t had_session_error; /* Current session had error */ + + uint8_t pat_seq; /* Watchdog patting sequence number */ + uint8_t cap0; /* capability byte */ + uint8_t cap1; /* capability byte */ + uint8_t cap2; /* capability byte */ + + /* + * Programming variables + */ + kmutex_t prog_mu; /* Programming mutex. - lom 2 */ + boolean_t prog_mode_only; /* If true we can only reprogram */ + /* the lom */ + boolean_t programming; /* TRUE is actually programming */ + /* the BSC */ + boolean_t cssp_prog; /* TRUE is CSSP programming the BSC */ + + int prog_index; /* data buffer number - bit */ + /* 0x8000 set if last buffer */ + int image_ptr; /* ptr to next byte in image buffer */ + /* for programming */ + uint8_t *image; /* ptr to image buffer for */ + /* programming */ + boolean_t image2_processing; /* boolean to say which of */ + /* 2 BSC images being processed */ + boolean_t loader_running; /* Still have the loader running */ + + /* + * LOM eeprom window access state + * Access under bscv_enter/bscv_exit protection. + */ + boolean_t eeinfo_valid; + uint32_t eeprom_size; + uint32_t eventlog_start; + uint32_t eventlog_size; + boolean_t oldeeptr_valid; + uint16_t oldeeptr; + + /* + * Communication with the event processing thread + * + * Change these variables with task_mu held and signal task_cv + * if an event/task needs processing. + */ + kmutex_t task_mu; /* mutex for wait on event thread */ + kcondvar_t task_cv; /* cv for wait on event thread */ + kcondvar_t task_evnt_cv; /* cv for lom2 wait on event */ + int task_flags; /* To monitor/stop the event thread */ + volatile int event_active_count; /* Count of event thread runs */ + boolean_t event_waiting; /* New events are waiting in the lom */ + boolean_t status_change; /* A status change is waiting */ + boolean_t nodename_change; /* Nodename has changed */ + boolean_t event_sleep; /* Error reading events - wait a bit */ + boolean_t event_fault_reported; /* Event fault reported */ + boolean_t watchdog_change; /* Watchdog config has changed */ +#ifdef __sparc + bscv_sig_t last_sig; /* Record of last signature sent */ +#endif /* __sparc */ + uint8_t last_event[8]; /* last event read and reported */ +#if defined(__i386) || defined(__amd64) + cyclic_id_t cyclic_id; /* watchdog patter cyclic timer */ + callb_id_t callb_id; /* Need to store the ID so we can */ + /* unschedule the panic callback */ + char last_nodename[128]; /* copy of last utsname.nodename */ +#endif /* __i386 || __amd64 */ +} bscv_soft_state_t; + +struct bscv_idi_callout { + enum bscv_idi_type type; /* Type of service */ + boolean_t (*fn)(struct bscv_idi_info); /* Function's address */ +}; + +#define BSCV_IDI_CALLOUT_MAGIC 0xb5c1ca11 +#define BSCV_IDI_ERR_MSG_THRESHOLD 10 +struct bscv_idi_callout_mgr { + /* + * To allow for sanity check. + */ + uint32_t magic; + + /* + * The instance number of "an" instance of the driver. This is assigned + * during driver attach. + */ + uint32_t valid_inst; + + /* + * Table of services offered via the idi interface. + */ + struct bscv_idi_callout *tbl; + + /* + * Error message count since last successful use of the idi interface. + */ + uint64_t errs; +}; + + + +#define BSC_IMAGE_MAX_SIZE (0x20000 + sizeof (lom_prog_data_t)) + +#define BSC_PROBE_FAULT_LIMIT 8 /* Tries before declaring lom dead */ +#define BSC_EVENT_POLL_NORMAL (drv_usectohz(1000000)) /* 1 second */ +#define BSC_EVENT_POLL_FAULTY (drv_usectohz(10000000)) /* 10 second */ + +#define BSC_FAILURE_RETRY_LIMIT 5 /* Access retries before giving up */ +#define BSC_ERASE_RETRY_LIMIT 5 /* Erase retries */ +#define BSC_PAGE_RETRY_LIMIT 5 /* Page write retries */ + +#define BSC_ADDR_CACHE_LIMIT \ + (sizeof (((bscv_soft_state_t *)NULL)->lom_regs)) +#define BSC_INFORM_ONLINE 0x4f530100 +#define BSC_INFORM_OFFLINE 0x4f530201 +#define BSC_INFORM_PANIC 0x4f530204 + +#include <sys/lom_ebuscodes.h> + +typedef uint32_t bscv_addr_t; + +#define BSC_NEXUS_ADDR(ssp, chan, as, index) \ + (&((ssp)->channel[chan].regs[((as) * 256) + (index)])) + +#define BSC_NEXUS_OFFSET(as, index) (((as) * 256) + (index)) + +#define BSCVA(as, index) (((as) * 256) + (index)) + +#define PSR_SUCCESS(status) (((status) & EBUS_PROGRAM_PSR_STATUS_MASK) == \ + EBUS_PROGRAM_PSR_SUCCESS) + +#define PSR_PROG(status) (((status) & EBUS_PROGRAM_PSR_PROG_MODE) != 0) +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_BSCV_IMPL_H */ diff --git a/usr/src/uts/intel/Makefile.files b/usr/src/uts/intel/Makefile.files index 5d010e8498..0cb64d3672 100644 --- a/usr/src/uts/intel/Makefile.files +++ b/usr/src/uts/intel/Makefile.files @@ -121,6 +121,8 @@ AMD64GART_OBJS += amd64_gart.o ATA_OBJS += $(GHD_OBJS) ata_blacklist.o ata_common.o ata_disk.o \ ata_dma.o atapi.o atapi_fsm.o ata_debug.o \ sil3xxx.o +BSCBUS_OBJS += bscbus.o +BSCV_OBJS += bscv.o CMDK_OBJS += cmdk.o CMLB_OBJS += cmlb.o DADK_OBJS += dadk.o diff --git a/usr/src/uts/intel/Makefile.intel.shared b/usr/src/uts/intel/Makefile.intel.shared index 2a80b43646..c195cac44c 100644 --- a/usr/src/uts/intel/Makefile.intel.shared +++ b/usr/src/uts/intel/Makefile.intel.shared @@ -218,6 +218,8 @@ DRV_KMODS += audioixp DRV_KMODS += bl DRV_KMODS += bge DRV_KMODS += bofi +DRV_KMODS += bscbus +DRV_KMODS += bscv DRV_KMODS += clone DRV_KMODS += cmdk DRV_KMODS += cn @@ -323,8 +325,6 @@ $(CLOSED_BUILD)DRV_KMODS += dca $(CLOSED_BUILD)CLOSED_DRV_KMODS += audioens $(CLOSED_BUILD)CLOSED_DRV_KMODS += audiovia823x $(CLOSED_BUILD)CLOSED_DRV_KMODS += bmc -$(CLOSED_BUILD)CLOSED_DRV_KMODS += bscbus -$(CLOSED_BUILD)CLOSED_DRV_KMODS += bscv $(CLOSED_BUILD)CLOSED_DRV_KMODS += elxl $(CLOSED_BUILD)CLOSED_DRV_KMODS += glm $(CLOSED_BUILD)CLOSED_DRV_KMODS += iprb diff --git a/usr/src/uts/intel/bscbus/Makefile b/usr/src/uts/intel/bscbus/Makefile new file mode 100644 index 0000000000..1e2944f362 --- /dev/null +++ b/usr/src/uts/intel/bscbus/Makefile @@ -0,0 +1,93 @@ +# +# 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 +# +# +# uts/intel/bscbus/Makefile +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of the bscbus driver +# +# intel architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = bscbus +OBJECTS = $(BSCBUS_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(BSCBUS_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/intel/io + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets +# +ALL_TARGET = $(BINARY) $(CONFMOD) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# For now, disable these lint checks; maintainers should endeavor +# to investigate and remove these for maximum lint coverage. +# Please do not carry these forward to new Makefiles. +# +LINTTAGS += -erroff=E_PTRDIFF_OVERFLOW +LINTTAGS += -erroff=E_ASSIGN_NARROW_CONV +LINTTAGS += -erroff=E_SUSPICIOUS_COMPARISON + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ diff --git a/usr/src/uts/intel/bscv/Makefile b/usr/src/uts/intel/bscv/Makefile new file mode 100644 index 0000000000..f01bd4e914 --- /dev/null +++ b/usr/src/uts/intel/bscv/Makefile @@ -0,0 +1,94 @@ +# +# 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 +# +# +# uts/intel/bscv/Makefile +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of the bscv driver +# +# intel architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = bscv +OBJECTS = $(BSCV_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(BSCV_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/intel/io + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets +# +ALL_TARGET = $(BINARY) $(CONFMOD) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# For now, disable these lint checks; maintainers should endeavor +# to investigate and remove these for maximum lint coverage. +# Please do not carry these forward to new Makefiles. +# +LINTTAGS += -erroff=E_BAD_PTR_CAST_ALIGN +LINTTAGS += -erroff=E_PTRDIFF_OVERFLOW +LINTTAGS += -erroff=E_ASSIGN_NARROW_CONV +LINTTAGS += -erroff=E_SUSPICIOUS_COMPARISON + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ diff --git a/usr/src/uts/intel/io/bscbus.conf b/usr/src/uts/intel/io/bscbus.conf new file mode 100644 index 0000000000..c783e2619d --- /dev/null +++ b/usr/src/uts/intel/io/bscbus.conf @@ -0,0 +1,31 @@ +# +# 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 2003 Sun Microsystems, Inc. +# All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# Configuration file for "bscbus" driver +# + +interrupt-priorities = 8, 8; diff --git a/usr/src/uts/intel/io/bscv.conf b/usr/src/uts/intel/io/bscv.conf new file mode 100644 index 0000000000..20f96109e4 --- /dev/null +++ b/usr/src/uts/intel/io/bscv.conf @@ -0,0 +1,38 @@ +# +# 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 2003 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# Configuration file for bscv driver +# + +ddi-forceattach=1; + +# +# The DEBUG version of the bscv driver supports a debug option value. +# +debug=0x0; + +parent="bscbus" name="bscv" instance=0; +reg=0x0, 0x0, 0x4000, 0x201, 0x0, 0x1; diff --git a/usr/src/uts/sun4u/Makefile.files b/usr/src/uts/sun4u/Makefile.files index 96cadbd9c8..04564949c8 100644 --- a/usr/src/uts/sun4u/Makefile.files +++ b/usr/src/uts/sun4u/Makefile.files @@ -20,7 +20,7 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -133,6 +133,9 @@ PMUBUS_OBJS += pmubus.o PMUGPIO_OBJS += pmugpio.o PMC_OBJS += pmc.o TRAPSTAT_OBJS += trapstat.o +I2BSC_OBJS += i2bsc.o +GPTWOCFG_OBJS += gptwocfg.o +GPTWO_CPU_OBJS += gptwo_cpu.o WRSM_OBJS += wci_common.o \ wrsm_barrier.o \ wrsm_cf.o \ @@ -182,8 +185,10 @@ TODMOSTEK_OBJS += todmostek.o TODDS1287_OBJS += todds1287.o TODDS1337_OBJS += todds1337.o TODSTARFIRE_OBJS += todstarfire.o +TODSTARCAT_OBJS += todstarcat.o TODBLADE_OBJS += todblade.o TODM5819_OBJS += todm5819.o +TODM5819P_RMC_OBJS += todm5819p_rmc.o TODBQ4802_OBJS += todbq4802.o TODSG_OBJS += todsg.o TODOPL_OBJS = todopl.o diff --git a/usr/src/uts/sun4u/Makefile.sun4u.shared b/usr/src/uts/sun4u/Makefile.sun4u.shared index c77a378664..618b59d3d2 100644 --- a/usr/src/uts/sun4u/Makefile.sun4u.shared +++ b/usr/src/uts/sun4u/Makefile.sun4u.shared @@ -405,9 +405,9 @@ DRV_KMODS += rmclomv DRV_KMODS += wrsmd DRV_KMODS += sf DRV_KMODS += nxge +DRV_KMODS += i2bsc $(CLOSED_BUILD)CLOSED_DRV_KMODS += ctsmc -$(CLOSED_BUILD)CLOSED_DRV_KMODS += i2bsc $(CLOSED_BUILD)CLOSED_DRV_KMODS += m1535ppm $(CLOSED_BUILD)CLOSED_DRV_KMODS += memtest $(CLOSED_BUILD)CLOSED_DRV_KMODS += mi2cv @@ -448,14 +448,13 @@ MISC_KMODS += sbd MISC_KMODS += opl_cfg MISC_KMODS += kmech_krb5 MISC_KMODS += zuluvm +MISC_KMODS += gptwo_cpu gptwocfg # # Brand modules # BRAND_KMODS += sn1_brand -$(CLOSED_BUILD)CLOSED_MISC_KMODS += gptwo_cpu gptwocfg - # # Software Cryptographic Providers (/kernel/crypto): # @@ -491,8 +490,9 @@ CPU_KMODS += cheetah cheetahplus jalapeno serrano spitfire hummingbird # TOD_KMODS += todds1287 todds1337 todmostek todstarfire TOD_KMODS += todm5819 todblade todbq4802 todsg todopl +TOD_KMODS += todm5819p_rmc todstarcat -$(CLOSED_BUILD)CLOSED_TOD_KMODS += todm5819p_rmc todstarcat todm5823 +$(CLOSED_BUILD)CLOSED_TOD_KMODS += todm5823 # # Performance Counter BackEnd Modules (/usr/kernel/pcbe): diff --git a/usr/src/uts/sun4u/blade/Makefile.blade.shared b/usr/src/uts/sun4u/blade/Makefile.blade.shared index 6e24b19f6f..a89230bde3 100644 --- a/usr/src/uts/sun4u/blade/Makefile.blade.shared +++ b/usr/src/uts/sun4u/blade/Makefile.blade.shared @@ -21,7 +21,7 @@ # #ident "%Z%%M% %I% %E% SMI" # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Global definitions for sun4u jbos-blade implementation specific modules. @@ -89,8 +89,8 @@ include $(UTSTREE)/sun4u/Makefile.sun4u # Define modules (must come after Makefile.sun4u, for CLOSED_BUILD). # BLADE_KMODS = platmod -$(CLOSED_BUILD)CLOSED_BLADE_KMODS += bscbus -$(CLOSED_BUILD)CLOSED_BLADE_KMODS += bscv +BLADE_KMODS += bscbus +BLADE_KMODS += bscv LINTS_DIR = $(OBJS_DIR) LINT_LIB_DIR = $(UTSBASE)/$(PLATFORM)/blade/lint-libs/$(OBJS_DIR) diff --git a/usr/src/uts/sun4u/blade/Makefile.files b/usr/src/uts/sun4u/blade/Makefile.files index d834db1711..e28eaae1e6 100644 --- a/usr/src/uts/sun4u/blade/Makefile.files +++ b/usr/src/uts/sun4u/blade/Makefile.files @@ -2,9 +2,8 @@ # CDDL HEADER START # # The contents of this file are subject to the terms of the -# Common Development and Distribution License, Version 1.0 only -# (the "License"). You may not use this file except in compliance -# with the License. +# 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. @@ -22,7 +21,7 @@ # #pragma ident "%Z%%M% %I% %E% SMI" # -# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # This Makefile defines all file modules for the directory @@ -33,6 +32,8 @@ # # Object lists # +BSCBUS_OBJS = bscbus.o +BSCV_OBJS = bscv.o # # Miscellaneous diff --git a/usr/src/uts/sun4u/blade/bscbus/Makefile b/usr/src/uts/sun4u/blade/bscbus/Makefile new file mode 100644 index 0000000000..916901075b --- /dev/null +++ b/usr/src/uts/sun4u/blade/bscbus/Makefile @@ -0,0 +1,100 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# uts/sun4u/blade/bscbus/Makefile +# +# This makefile drives the production of the bscbus driver kernel +# module in the sun4u blade systems +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../../.. + +# +# Define the module and object file sets. +# +MODULE = bscbus +OBJECTS = $(BSCBUS_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(BSCBUS_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_BLADE_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/sun4u/blade/io + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/blade/Makefile.blade + +# +# Define targets +# +ALL_TARGET = $(BINARY) $(SRC_CONFILE) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# Overrides +# +ALL_BUILDS = $(ALL_BUILDSONLY64) +DEF_BUILDS = $(DEF_BUILDSONLY64) +CLEANLINTFILES += $(LINT32_FILES) + +# +# lint pass one enforcement +# +CFLAGS += $(CCVERBOSE) + +# +# Turn on doubleword alignment for 64 bit registers +# +CFLAGS += -dalign + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sun4u/blade/Makefile.targ diff --git a/usr/src/uts/sun4u/blade/bscv/Makefile b/usr/src/uts/sun4u/blade/bscv/Makefile new file mode 100644 index 0000000000..6c95f9e137 --- /dev/null +++ b/usr/src/uts/sun4u/blade/bscv/Makefile @@ -0,0 +1,94 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of the sun4u "bscv" driver module. +# +# sun4u implementation architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../../.. + +# +# Define the module and object file sets. +# +MODULE = bscv +OBJECTS = $(BSCV_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(BSCV_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_BLADE_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/sun4u/blade/io + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/blade/Makefile.blade + +# +# Overrides +# +ALL_BUILDS = $(ALL_BUILDSONLY64) +DEF_BUILDS = $(DEF_BUILDSONLY64) +CLEANLINTFILES += $(LINT32_FILES) + +# +# Define targets +# +ALL_TARGET = $(BINARY) $(SRC_CONFFILE) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# lint pass one enforcement +# +CFLAGS += $(CCVERBOSE) + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sun4u/blade/Makefile.targ diff --git a/usr/src/uts/sun4u/blade/io/bscbus.conf b/usr/src/uts/sun4u/blade/io/bscbus.conf new file mode 100644 index 0000000000..a00c7f80f6 --- /dev/null +++ b/usr/src/uts/sun4u/blade/io/bscbus.conf @@ -0,0 +1,31 @@ +# +# 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 2002 Sun Microsystems, Inc. +# All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# Configuration file for "bscbus" driver +# + +interrupt-priorities = 8, 8, 8, 8; diff --git a/usr/src/uts/sun4u/blade/io/bscv.conf b/usr/src/uts/sun4u/blade/io/bscv.conf new file mode 100644 index 0000000000..238e0ff7f3 --- /dev/null +++ b/usr/src/uts/sun4u/blade/io/bscv.conf @@ -0,0 +1,33 @@ +# +# 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 2001-2002 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# Configuration file for bscv driver +# + +# +# The DEBUG version of the bscv driver supports a debug option value. +# +debug=0x0; diff --git a/usr/src/uts/sun4u/gptwo_cpu/Makefile b/usr/src/uts/sun4u/gptwo_cpu/Makefile new file mode 100644 index 0000000000..06bb109a42 --- /dev/null +++ b/usr/src/uts/sun4u/gptwo_cpu/Makefile @@ -0,0 +1,115 @@ +# +# 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 +# +# +# uts/sun4u/gptwo_cpu/Makefile +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of the cpu portion of +# of the Safari Configurator. +# +# sun4u implementation architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = gptwo_cpu +OBJECTS = $(GPTWO_CPU_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(GPTWO_CPU_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_PSM_MISC_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/Makefile.sun4u + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + + +# Turn this on once compiler understands v9 in it's backend +#INLINES += $(UTSBASE)/sun4u/io/gptwo_cpu.il + +# +# lint pass one enforcement +# +CFLAGS += $(CCVERBOSE) + +# +# Turn on doubleword alignment for 64 bit registers +# +CFLAGS += -dalign + +# +# Pick up defines in cheetahregs.h. +# +CFLAGS += -DCHEETAH_PLUS +LINTFLAGS += -DCHEETAH_PLUS + +# +# module dependencies +# +LDFLAGS += -dy -Nmisc/gptwocfg + +# +# For now, disable these lint checks; maintainers should endeavor +# to investigate and remove these for maximum lint coverage. +# Please do not carry these forward to new Makefiles. +# +LINTTAGS += -erroff=E_STATIC_UNUSED + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sun4u/Makefile.targ diff --git a/usr/src/uts/sun4u/gptwocfg/Makefile b/usr/src/uts/sun4u/gptwocfg/Makefile new file mode 100644 index 0000000000..5fc69800a4 --- /dev/null +++ b/usr/src/uts/sun4u/gptwocfg/Makefile @@ -0,0 +1,95 @@ +# +# 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 +# +# +# uts/sun4u/gptwocfg/Makefile +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of the gptwocfg Safari Configurator +# +# sun4u implementation architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = gptwocfg +OBJECTS = $(GPTWOCFG_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(GPTWOCFG_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_PSM_MISC_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/Makefile.sun4u + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# Turn this on once compiler understands v9 in it's backend +#INLINES += $(UTSBASE)/sun4u/io/gptwocfg.il + +# +# lint pass one enforcement +# +CFLAGS += $(CCVERBOSE) + +# +# Turn on doubleword alignment for 64 bit registers +# +CFLAGS += -dalign + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sun4u/Makefile.targ diff --git a/usr/src/uts/sun4u/i2bsc/Makefile b/usr/src/uts/sun4u/i2bsc/Makefile new file mode 100644 index 0000000000..0c984165b1 --- /dev/null +++ b/usr/src/uts/sun4u/i2bsc/Makefile @@ -0,0 +1,112 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" + +# This makefile drives the production of the i2bsc nexus driver. +# +# Path to the base of the uts directory tree (usually /usr/src/uts). + +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = i2bsc +OBJECTS = $(I2BSC_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(I2BSC_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_PSM_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/sun4u/io/i2c/nexus + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/Makefile.sun4u + +# +# lint pass one enforcement +# +CFLAGS += $(CCVERBOSE) -I$(UTSBASE)/sun4u + +LDFLAGS += -dy -N misc/i2c_svc + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# For now, disable these lint checks; maintainers should endeavor +# to investigate and remove these for maximum lint coverage. +# Please do not carry these forward to new Makefiles. +# +LINTTAGS += -erroff=E_BAD_PTR_CAST_ALIGN +LINTTAGS += -erroff=E_PTRDIFF_OVERFLOW +LINTTAGS += -erroff=E_ASSIGN_NARROW_CONV + +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Defines for local commands. +# +WLCC = wlcc +TOUCH = touch +WARLOCK = warlock + +# +# Warlock targets +# + +I2BSC = $(I2BSC_OBJS:%.o=%.ll) + +warlock: $(MODULE).ok + +%.ok: $(I2BSC_FILES) + $(TOUCH) $@ + +%.ll: $(UTSBASE)/sun4u/io/i2c/nexus/i2bsc/%.c + $(WLCC) $(CFLAGS) $(CPPFLAGS) -DDEBUG -o $@ $< + +# +# Include common targets +# +include $(UTSBASE)/sun4u/Makefile.targ diff --git a/usr/src/uts/sun4u/io/gptwo_cpu.c b/usr/src/uts/sun4u/io/gptwo_cpu.c new file mode 100644 index 0000000000..c6b87a99b2 --- /dev/null +++ b/usr/src/uts/sun4u/io/gptwo_cpu.c @@ -0,0 +1,1023 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * CPU functions to the Safari Configurator (gptwo_cpu) + */ + +#include <sys/types.h> +#include <sys/cred.h> +#include <sys/mman.h> +#include <sys/kmem.h> +#include <sys/conf.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/modctl.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/autoconf.h> +#include <sys/ksynch.h> +#include <sys/promif.h> +#include <sys/ndi_impldefs.h> +#include <sys/ddi_impldefs.h> +#include <sys/machsystm.h> +#include <sys/gp2cfg.h> +#include <sys/gptwo_cpu.h> +#include <sys/cheetahregs.h> + +#ifdef DEBUG +int gptwo_cpu_debug = 0; + +static void debug(char *, uintptr_t, uintptr_t, + uintptr_t, uintptr_t, uintptr_t); + +#define GPTWO_DEBUG0(level, flag, s) if (gptwo_cpu_debug >= level) \ + cmn_err(flag, s) +#define GPTWO_DEBUG1(level, flag, fmt, a1) if (gptwo_cpu_debug >= level) \ + debug(fmt, (uintptr_t)(a1), 0, 0, 0, 0); +#define GPTWO_DEBUG2(level, flag, fmt, a1, a2) if (gptwo_cpu_debug >= level) \ + debug(fmt, (uintptr_t)(a1), (uintptr_t)(a2), 0, 0, 0); +#define GPTWO_DEBUG3(level, flag, fmt, a1, a2, a3) \ + if (gptwo_cpu_debug >= level) \ + debug(fmt, (uintptr_t)(a1), (uintptr_t)(a2), (uintptr_t)(a3), 0, 0); +#else +#define GPTWO_DEBUG0(level, flag, s) +#define GPTWO_DEBUG1(level, flag, fmt, a1) +#define GPTWO_DEBUG2(level, flag, fmt, a1, a2) +#define GPTWO_DEBUG3(level, flag, fmt, a1, a2, a3) +#endif + +/* + * Devinfo branch create arg + */ +struct bca { + spcd_t *pcd; + uint_t portid; + uint_t cpuid; + uint_t coreid; + uint_t impl; + dev_info_t *new_child; +}; + +static dev_info_t *gptwocfg_create_cpu_node(dev_info_t *, spcd_t *, + uint_t, uint_t, uint_t, uint_t); +static dev_info_t *gptwocfg_create_mc_node(dev_info_t *, spcd_t *, uint_t); +static dev_info_t *gptwocfg_create_cmp_node(dev_info_t *, spcd_t *, uint_t); +static int gptwocfg_create_core_node(dev_info_t *, spcd_t *, uint_t, uint_t); +static int set_mc_props(dev_info_t *new_child, void *arg, uint_t flags); +static int set_cmp_props(dev_info_t *new_child, void *arg, uint_t flags); +static int set_cpu_props(dev_info_t *new_child, void *arg, uint_t flags); +static int set_cpu_common_props(dev_info_t *new_child, struct bca *bcp); +static int set_cpu_us3_props(dev_info_t *new_child, struct bca *bcp); +static int set_cpu_us4_props(dev_info_t *new_child, struct bca *bcp); +static void get_new_child(dev_info_t *rdip, void *arg, uint_t flags); + + +/* + * Module linkage information for the kernel. + */ + +extern struct mod_ops mod_miscops; + +static struct modlmisc modlmisc = { + &mod_miscops, /* Type of module */ + "gptwo->cpu configurator %I%", +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modlmisc, NULL +}; + +int +_init(void) +{ + int err = 0; + + /* register device with the configurator */ + gptwocfg_register_ops(SAFPTYPE_CPU, gptwocfg_configure_cpu, NULL); + + if ((err = mod_install(&modlinkage)) != 0) { + GPTWO_DEBUG1(1, CE_WARN, "gptwo_cpu (CPU/MC Functions) " + "failed to load, error=%d\n", err); + gptwocfg_unregister_ops(SAFPTYPE_CPU); + } else { + GPTWO_DEBUG0(1, CE_WARN, "gptwo_cpu (CPU/MC Functions) " + "has been loaded.\n"); + } + return (err); +} + +int +_fini(void) +{ + /* cleanup/freeup structs with configurator */ + gptwocfg_unregister_ops(SAFPTYPE_CPU); + return (mod_remove(&modlinkage)); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +gptwo_new_nodes_t * +gptwocfg_configure_cpu(dev_info_t *ap, spcd_t *pcd, uint_t portid) +{ + dev_info_t *cpu_node[AGENTS_PER_PORT], *mc_node[AGENTS_PER_PORT]; + dev_info_t *cmp_node = NULL; + gptwo_new_nodes_t *new_nodes; + int nodes = 0; + int i, j = 0; + uint_t implementation; + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_configure_cpu: portid=%x pcd=%lx\n", + portid, pcd); + + for (i = 0; i < AGENTS_PER_PORT; i++) { + cpu_node[i] = NULL; + mc_node[i] = NULL; + } + + implementation = (pcd->spcd_ver_reg >> 32) & 0x000000000000ffff; + + switch (implementation) { + case CHEETAH_IMPL: + case CHEETAH_PLUS_IMPL: + case JAGUAR_IMPL: + case PANTHER_IMPL: + break; + default: + cmn_err(CE_WARN, "Unsupported cpu implementation=0x%x : " + "skipping configure of portid=0x%x", implementation, + portid); + ASSERT(0); + return (NULL); + } + + if (CPU_IMPL_IS_CMP(implementation)) { + if (cmp_node = gptwocfg_create_cmp_node(ap, pcd, portid)) + nodes++; + else + return (NULL); + } + + for (i = 0; i < AGENTS_PER_PORT; i++) { + if (pcd->spcd_agent[i] != SPCD_RSV_PASS) + continue; + + if (cpu_node[i] = gptwocfg_create_cpu_node(cmp_node ? + cmp_node : ap, pcd, portid, pcd->spcd_cpuid[i], i, + implementation)) { + /* + * If the CPU is a CMP, the entire branch is + * manipulated using just the top node. Thus, + * the dips of the individual cores do not need + * to be held or stored in the new node list. + */ + if (cmp_node) { + e_ddi_branch_rele(cpu_node[i]); + } else { + nodes++; + } + } + } + + /* current implementations have 1 MC node per Safari port */ + if (pcd->spcd_prsv == SPCD_RSV_PASS && + (mc_node[0] = gptwocfg_create_mc_node(ap, pcd, portid))) + nodes++; + + new_nodes = gptwocfg_allocate_node_list(nodes); + + j = 0; + for (i = 0; i < AGENTS_PER_PORT; i++) { + if ((cpu_node[i] != NULL) && (!CPU_IMPL_IS_CMP(implementation))) + new_nodes->gptwo_nodes[j++] = cpu_node[i]; + if (mc_node[i] != NULL) + new_nodes->gptwo_nodes[j++] = mc_node[i]; + } + + if (cmp_node) + new_nodes->gptwo_nodes[j++] = cmp_node; + + return (new_nodes); +} + + +static dev_info_t * +gptwocfg_create_cmp_node(dev_info_t *ap, spcd_t *pcd, uint_t portid) +{ + struct bca arg; + devi_branch_t b; + + arg.pcd = pcd; + arg.portid = portid; + arg.cpuid = 0; + arg.coreid = 0; + arg.new_child = NULL; + + b.arg = &arg; + b.type = DEVI_BRANCH_SID; + b.create.sid_branch_create = set_cmp_props; + b.devi_branch_callback = get_new_child; + + if (e_ddi_branch_create(ap, &b, NULL, 0)) + return (NULL); + + return (arg.new_child); +} + +/*ARGSUSED*/ +static int +set_cmp_props(dev_info_t *new_child, void *arg, uint_t flags) +{ + struct bca *bap = (struct bca *)arg; + gptwo_regspec_t reg; + spcd_t *pcd; + uint_t portid; + + pcd = bap->pcd; + portid = bap->portid; + + GPTWO_DEBUG2(1, CE_CONT, "set_cmp_props: portid=%x pcd=%lx\n", + portid, pcd); + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "name", "cmp") != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cmp_props: failed to " + "create name property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "portid", portid) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cmp_props: failed to " + "create portid property\n"); + return (DDI_WALK_ERROR); + } + + reg.gptwo_phys_hi = 0x400 | (portid >> 9); + reg.gptwo_phys_low = (portid << 23); + reg.gptwo_size_hi = 0; + reg.gptwo_size_low = 0x10000; + + if (ndi_prop_update_int_array(DDI_DEV_T_NONE, + new_child, "reg", (int *)®, + sizeof (gptwo_regspec_t) / sizeof (int)) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cmp_props: failed to " + "create reg property\n"); + return (DDI_WALK_ERROR); + } + + return (DDI_WALK_TERMINATE); +} + +static dev_info_t * +gptwocfg_create_cpu_node(dev_info_t *ap, spcd_t *pcd, uint_t portid, + uint_t cpuid, uint_t coreid, uint_t impl) +{ + struct bca arg; + devi_branch_t b = {0}; + + arg.pcd = pcd; + arg.portid = portid; + arg.cpuid = cpuid; + arg.coreid = coreid; + arg.impl = impl; + arg.new_child = NULL; + + b.arg = &arg; + b.type = DEVI_BRANCH_SID; + b.create.sid_branch_create = set_cpu_props; + b.devi_branch_callback = get_new_child; + + if (e_ddi_branch_create(ap, &b, NULL, 0)) + return (NULL); + + return (arg.new_child); +} + +/*ARGSUSED*/ +static int +set_cpu_props(dev_info_t *new_child, void *arg, uint_t flags) +{ + struct bca *bcp = arg; + uint_t impl = bcp->impl; + int rc; + + if (set_cpu_common_props(new_child, bcp) != DDI_WALK_CONTINUE) + return (DDI_WALK_ERROR); + + switch (impl) { + case CHEETAH_IMPL: + case CHEETAH_PLUS_IMPL: + rc = set_cpu_us3_props(new_child, bcp); + break; + case JAGUAR_IMPL: + case PANTHER_IMPL: + rc = set_cpu_us4_props(new_child, bcp); + break; + default: + ASSERT(0); + return (DDI_WALK_ERROR); + } + + return (rc); +} + +/* + * Set properties common to cpu (non-CMP) and core (CMP) nodes. + * + * cpuid + * device_type + * manufacturer# + * implementation# + * mask# + * sparc-version + * clock-frequency + * #dtlb-entries + * #itlb-entries + */ +static int +set_cpu_common_props(dev_info_t *new_child, struct bca *bcp) +{ + uint_t cpuid, impl; + spcd_t *pcd; + int mask, manufacturer; + + cpuid = bcp->cpuid; + pcd = bcp->pcd; + impl = bcp->impl; + + mask = (pcd->spcd_ver_reg >> 24) & 0x00000000000000ff; + manufacturer = (pcd->spcd_ver_reg >> 48) & 0x000000000000ffff; + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "cpuid", cpuid) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create cpuid property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "device_type", "cpu") != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create device_type property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, "manufacturer#", + manufacturer) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create manufacturer# property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, "implementation#", + impl) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create implementation# property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, "mask#", + mask) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create mask# property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "sparc-version", 9) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create sparc-version property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "clock-frequency", (pcd->spcd_afreq * 1000000)) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create clock-frequency property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "#dtlb-entries", 0x10) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create #dtlb-entries property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "#itlb-entries", 0x10) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_common_props: failed " + "to create #itlb-entries property\n"); + return (DDI_WALK_ERROR); + } + + return (DDI_WALK_CONTINUE); +} + +/* + * Set cpu node properties for Cheetah and Cheetah+. + * + * name + * portid + * reg + * icache-size + * icache-line-size + * icache-associativity + * dcache-size + * dcache-line-size + * dcache-associativity + * ecache-size + * ecache-line-size + * ecache-associativity + */ +static int +set_cpu_us3_props(dev_info_t *new_child, struct bca *bcp) +{ + char *node_name; + gptwo_regspec_t reg; + int ecache_size, ecache_line_size; + int dimms, ecache_assoc; + spcd_t *pcd; + uint_t portid, impl; + + pcd = bcp->pcd; + portid = bcp->portid; + impl = bcp->impl; + + ASSERT(IS_CHEETAH(impl) || IS_CHEETAH_PLUS(impl)); + + switch (impl) { + case CHEETAH_IMPL: + ecache_assoc = CH_ECACHE_NWAY; + node_name = "SUNW,UltraSPARC-III"; + break; + case CHEETAH_PLUS_IMPL: + /* + * Hard coding the ecache-associativity to 2 for Cheetah+. + * We probably should add this to the PCD. + */ + ecache_assoc = CHP_ECACHE_NWAY; + node_name = "SUNW,UltraSPARC-III+"; + break; + default: + GPTWO_DEBUG1(1, CE_CONT, "set_cpu_us3_props: invalid " + "implementation=0x%x\n", impl); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "name", node_name) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create name property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "portid", portid) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create portid property\n"); + return (DDI_WALK_ERROR); + } + + reg.gptwo_phys_hi = 0x400 | (portid >> 9); + reg.gptwo_phys_low = (portid << 23); + reg.gptwo_size_hi = 0; + reg.gptwo_size_low = 0x10000; + + if (ndi_prop_update_int_array(DDI_DEV_T_NONE, + new_child, "reg", (int *)®, + sizeof (gptwo_regspec_t) / sizeof (int)) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create reg property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "icache-size", CH_ICACHE_SIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create icache-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "icache-line-size", CH_ICACHE_LSIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create icache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "icache-associativity", CH_ICACHE_NWAY) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create icache-associativity property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "dcache-size", CH_DCACHE_SIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create dcache-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "dcache-line-size", CH_DCACHE_LSIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create dcache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "dcache-associativity", CH_DCACHE_NWAY) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create dcache-associativity property\n"); + return (DDI_WALK_ERROR); + } + + /* + * Get the External Cache Size from the Common PCD. + */ + ecache_size = pcd->spcd_cache * 0x100000; + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "ecache-size", ecache_size) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create ecache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + switch (ecache_size) { + case CH_ECACHE_1M_SIZE: + ecache_line_size = 64; + break; + case CH_ECACHE_4M_SIZE: + ecache_line_size = 256; + break; + case CH_ECACHE_8M_SIZE: + ecache_line_size = 512; + break; + default: + GPTWO_DEBUG1(1, CE_CONT, "set_cpu_us3_props: invalid " + "ecache-size 0x%x\b", ecache_size); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "ecache-line-size", ecache_line_size) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create ecache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "ecache-associativity", ecache_assoc) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us3_props: failed " + "to create ecache-associativity property\n"); + return (DDI_WALK_ERROR); + } + + /* + * Create the ecache-dimm-label property. + */ + dimms = 0; + + while ((pcd->sprd_ecache_dimm_label[dimms] != NULL) && + (dimms < MAX_DIMMS_PER_PORT)) + dimms++; + + if (dimms) { + (void) ndi_prop_update_string_array(DDI_DEV_T_NONE, new_child, + "ecache-dimm-label", (char **)pcd->sprd_ecache_dimm_label, + dimms); + } + + return (DDI_WALK_TERMINATE); +} + +/* + * Set cmp core node properties for Jaguar and Panther. + * + * name + * compatible + * reg + * l1-icache-size + * l1-icache-line-size + * l1-icache-associativity + * l1-dcache-size + * l1-dcache-line-size + * l1-dcache-associativity + * l2-cache-size + * l2-cache-line-size + * l2-cache-associativity + * l2-cache-sharing + * l3-cache-size + * l3-cache-line-size + * l3-cache-associativity + * l3-cache-sharing + */ +static int +set_cpu_us4_props(dev_info_t *new_child, struct bca *bcp) +{ + uint_t l1_icache_size, l1_icache_line_size; + uint_t l2_cache_size, l2_cache_line_size, l2_cache_assoc; + uint_t l2_cache_share; + uint_t pcd_cache_size; + uint_t coreid, impl; + spcd_t *pcd; + char *compatible; + int dimms; + int i; + + pcd = bcp->pcd; + coreid = bcp->coreid; + impl = bcp->impl; + + ASSERT(IS_JAGUAR(impl) || IS_PANTHER(impl)); + + /* + * Get the External Cache Size from the Common PCD. + */ + pcd_cache_size = pcd->spcd_cache * 0x100000; + + switch (impl) { + case JAGUAR_IMPL: + compatible = "SUNW,UltraSPARC-IV"; + l1_icache_size = CH_ICACHE_SIZE; + l1_icache_line_size = CH_ICACHE_LSIZE; + l2_cache_assoc = CHP_ECACHE_NWAY; + + /* + * Jaguar has no logical sharing of L2 cache, so the sharing + * bit-map will represent this core only. + */ + l2_cache_share = coreid ? 0x2 : 0x1; + + /* + * Jaguar has a split ecache, so the total ecache must be + * divided in half to get the ecache for the individual core. + */ + l2_cache_size = pcd_cache_size / 2; + + switch (l2_cache_size) { + case JG_ECACHE_4M_SIZE: + l2_cache_line_size = 64; + break; + case JG_ECACHE_8M_SIZE: + l2_cache_line_size = 128; + break; + default: + GPTWO_DEBUG1(1, CE_CONT, "set_cpu_us4_props: " + "invalid l2_cache-size 0x%x\n", l2_cache_size); + return (DDI_WALK_ERROR); + } + break; + case PANTHER_IMPL: + ASSERT(pcd_cache_size == PN_L3_SIZE); + compatible = "SUNW,UltraSPARC-IV+"; + l1_icache_size = PN_ICACHE_SIZE; + l1_icache_line_size = PN_ICACHE_LSIZE; + l2_cache_size = PN_L2_SIZE; + l2_cache_line_size = PN_L2_LINESIZE; + l2_cache_assoc = PN_ECACHE_NWAY; + + /* + * For Panther, the L2 and L3 caches are logically shared by + * all enabled cores, so the sharing bit-map will represent + * all enabled cores. Panther split-mode is still considered + * shared. + * + * Check the PCD status to determine enabled cores. + */ + ASSERT(pcd->spcd_ptype == SAFPTYPE_CPU); + l2_cache_share = 0; + for (i = 0; i < AGENTS_PER_PORT; i++) { + if (pcd->spcd_agent[i] == SPCD_RSV_PASS) { + l2_cache_share |= (1 << i); + } + } + + break; + default: + GPTWO_DEBUG1(1, CE_CONT, "set_cpu_us4_props: invalid " + "implementation=0x%x\n", impl); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "name", "cpu") != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create name property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "compatible", compatible) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create compatible property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "reg", coreid) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create reg property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l1-icache-size", l1_icache_size) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l1-icache-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l1-icache-line-size", l1_icache_line_size) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create icache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l1-icache-associativity", CH_ICACHE_NWAY) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l1-icache-associativity property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l1-dcache-size", CH_DCACHE_SIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l1-dcache-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l1-dcache-line-size", CH_DCACHE_LSIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create dcache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l1-dcache-associativity", CH_DCACHE_NWAY) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l1-dcache-associativity property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l2-cache-size", l2_cache_size) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l2-cache-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l2-cache-line-size", l2_cache_line_size) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l2_cache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l2-cache-associativity", l2_cache_assoc) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l2-cache-associativity property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l2-cache-sharing", l2_cache_share) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: failed " + "to create l2-cache-sharing property\n"); + return (DDI_WALK_ERROR); + } + + /* + * Create the ecache-dimm-label property. + */ + dimms = 0; + + while ((pcd->sprd_ecache_dimm_label[dimms] != NULL) && + (dimms < MAX_DIMMS_PER_PORT)) + dimms++; + + if (dimms) { + (void) ndi_prop_update_string_array(DDI_DEV_T_NONE, new_child, + "ecache-dimm-label", (char **)pcd->sprd_ecache_dimm_label, + dimms); + } + + if (IS_PANTHER(impl)) { + int l3_cache_share = l2_cache_share; + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l3-cache-size", PN_L3_SIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: " + "failed to create l3-cache-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l3-cache-line-size", PN_L3_LINESIZE) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: " + "failed to create l3-cache-line-size property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l3-cache-associativity", PN_ECACHE_NWAY) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: " + "failed to create l3-cache-associativity " + "property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "l3-cache-sharing", l3_cache_share) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_cpu_us4_props: " + "failed to create l3-cache-sharing property\n"); + return (DDI_WALK_ERROR); + } + } + + return (DDI_WALK_TERMINATE); +} + +static dev_info_t * +gptwocfg_create_mc_node(dev_info_t *ap, spcd_t *pcd, uint_t portid) +{ + struct bca arg; + devi_branch_t b = {0}; + + arg.pcd = pcd; + arg.portid = portid; + arg.cpuid = portid; + arg.new_child = NULL; + + b.arg = &arg; + b.type = DEVI_BRANCH_SID; + b.create.sid_branch_create = set_mc_props; + b.devi_branch_callback = get_new_child; + + if (e_ddi_branch_create(ap, &b, NULL, 0)) + return (NULL); + + return (arg.new_child); +} + +/*ARGSUSED*/ +static int +set_mc_props(dev_info_t *new_child, void *arg, uint_t flags) +{ + struct bca *bcp = arg; + gptwo_regspec_t reg; + int banks, dimms; + spcd_t *pcd = bcp->pcd; + uint_t portid = bcp->portid; + uint_t cpuid = bcp->cpuid; + + GPTWO_DEBUG3(1, CE_CONT, "set_mc_props: ap=0x%lx portid=0x%x " + "cpuid=0x%x\n", ddi_get_parent(new_child), portid, cpuid); + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "name", "memory-controller") != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create name property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "compatible", "SUNW,UltraSPARC-III,mc") != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create compatible property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_string(DDI_DEV_T_NONE, new_child, + "device_type", "memory-controller") != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create device_type property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "portid", portid) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create portid property\n"); + return (DDI_WALK_ERROR); + } + + if (ndi_prop_update_int(DDI_DEV_T_NONE, new_child, + "cpuid", cpuid) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create cpuid property\n"); + return (DDI_WALK_ERROR); + } + + reg.gptwo_phys_hi = 0x400 | (portid >> 9); + reg.gptwo_phys_low = (portid << 23) | 0x400000; + reg.gptwo_size_hi = 0; + reg.gptwo_size_low = 0x48; + + if (ndi_prop_update_int_array(DDI_DEV_T_NONE, + new_child, "reg", (int *)®, + sizeof (gptwo_regspec_t) / sizeof (int)) != DDI_SUCCESS) { + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create reg property\n"); + return (DDI_WALK_ERROR); + } + + if (pcd->memory_layout) { + if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, + new_child, "memory-layout", (uchar_t *)pcd->memory_layout, + pcd->memory_layout_size) != DDI_SUCCESS) { + + GPTWO_DEBUG0(1, CE_CONT, "set_mc_props: failed " + "to create memory-layout property\n"); + + return (DDI_WALK_ERROR); + } + } + + /* + * Create the bank-status property. + */ + banks = 0; + + while ((pcd->sprd_bank_rsv[banks] != NULL) && + (banks < MAX_BANKS_PER_PORT)) + banks++; + + if (banks) { + (void) ndi_prop_update_string_array(DDI_DEV_T_NONE, new_child, + "bank-status", (char **)pcd->sprd_bank_rsv, banks); + } + + /* + * Create the dimm-status property. + */ + dimms = 0; + + while ((pcd->sprd_dimm[dimms] != NULL) && + (dimms < MAX_DIMMS_PER_PORT)) + dimms++; + + if (dimms) { + (void) ndi_prop_update_string_array(DDI_DEV_T_NONE, new_child, + "dimm-status", (char **)pcd->sprd_dimm, dimms); + } + + + return (DDI_WALK_TERMINATE); +} + +/*ARGSUSED*/ +static void +get_new_child(dev_info_t *rdip, void *arg, uint_t flags) +{ + struct bca *bcp = arg; + + bcp->new_child = rdip; + +} + +#ifdef DEBUG +static void +debug(char *fmt, uintptr_t a1, uintptr_t a2, uintptr_t a3, + uintptr_t a4, uintptr_t a5) +{ + cmn_err(CE_CONT, fmt, a1, a2, a3, a4, a5); +} +#endif diff --git a/usr/src/uts/sun4u/io/gptwocfg.c b/usr/src/uts/sun4u/io/gptwocfg.c new file mode 100644 index 0000000000..e1a2bb6347 --- /dev/null +++ b/usr/src/uts/sun4u/io/gptwocfg.c @@ -0,0 +1,683 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Safari Configurator (gptwocfg) + * + */ + +#include <sys/types.h> +#include <sys/cred.h> +#include <sys/mman.h> +#include <sys/kmem.h> +#include <sys/conf.h> +#include <sys/cmn_err.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/modctl.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <sys/autoconf.h> +#include <sys/ksynch.h> +#include <sys/promif.h> +#include <sys/ndi_impldefs.h> +#include <sys/ddi_impldefs.h> +#include <sys/gp2cfg.h> +#include <sys/machsystm.h> +#include <sys/platform_module.h> +#pragma weak starcat_dr_name + +#ifdef DEBUG +int gptwocfg_debug = 0; + +static void debug(char *, uintptr_t, uintptr_t, + uintptr_t, uintptr_t, uintptr_t); + +#define GPTWO_DEBUG0(level, flag, s) if (gptwocfg_debug >= level) \ + cmn_err(flag, s) +#define GPTWO_DEBUG1(level, flag, fmt, a1) if (gptwocfg_debug >= level) \ + debug(fmt, (uintptr_t)(a1), 0, 0, 0, 0); +#define GPTWO_DEBUG2(level, flag, fmt, a1, a2) if (gptwocfg_debug >= level) \ + debug(fmt, (uintptr_t)(a1), (uintptr_t)(a2), 0, 0, 0); +#define GPTWO_DEBUG3(level, flag, fmt, a1, a2, a3) \ + if (gptwocfg_debug >= level) \ + debug(fmt, (uintptr_t)(a1), (uintptr_t)(a2), (uintptr_t)(a3), 0, 0); +#else +#define GPTWO_DEBUG0(level, flag, s) +#define GPTWO_DEBUG1(level, flag, fmt, a1) +#define GPTWO_DEBUG2(level, flag, fmt, a1, a2) +#define GPTWO_DEBUG3(level, flag, fmt, a1, a2, a3) +#endif + +kmutex_t gptwo_handle_list_lock; +gptwocfg_handle_list_t *gptwocfg_handle_list; + +static kmutex_t gptwo_config_list_lock; +static gptwocfg_config_t *gptwo_config_list; + +static gptwo_new_nodes_t * + gptwocfg_get_obp_created_nodes(dev_info_t *, uint_t); + +void (*gptwocfg_unclaim_address)(uint_t); + +extern caddr_t efcode_vaddr; +extern int efcode_size; + +#define GPTWO_NUMBER_OF_DEVICE_TYPES 6 + +static kmutex_t gptwocfg_ops_table_lock; +gptwocfg_ops_t *gptwocfg_ops_table[GPTWO_NUMBER_OF_DEVICE_TYPES]; + +/* + * Module linkage information for the kernel. + */ + +extern struct mod_ops mod_miscops; + +static struct modlmisc modlmisc = { + &mod_miscops, /* Type of module */ + "gptwo configurator %I%", +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modlmisc, NULL +}; + +int +_init(void) +{ + unsigned int i; + + GPTWO_DEBUG0(1, CE_WARN, "gptwocfg (Safari Configurator) " + "has been loaded\n"); + + mutex_init(&gptwo_config_list_lock, NULL, MUTEX_DRIVER, NULL); + mutex_init(&gptwocfg_ops_table_lock, NULL, MUTEX_DRIVER, NULL); + gptwo_config_list = NULL; + + mutex_init(&gptwo_handle_list_lock, NULL, MUTEX_DRIVER, NULL); + gptwocfg_handle_list = NULL; + + for (i = 0; i < GPTWO_NUMBER_OF_DEVICE_TYPES; i++) + gptwocfg_ops_table[i] = NULL; + + return (mod_install(&modlinkage)); +} + +int +_fini(void) +{ + int error; + + error = mod_remove(&modlinkage); + if (error != 0) { + return (error); + } + mutex_destroy(&gptwo_config_list_lock); + mutex_destroy(&gptwocfg_ops_table_lock); + mutex_destroy(&gptwo_handle_list_lock); + + return (0); +} + +int +_info(modinfop) +struct modinfo *modinfop; +{ + return (mod_info(&modlinkage, modinfop)); +} + +gptwo_new_nodes_t * +gptwocfg_allocate_node_list(int number_of_nodes) +{ + gptwo_new_nodes_t *gptwo_new_nodes; + int size; + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_allocate_node_list- %d nodes", + number_of_nodes); + + size = sizeof (gptwo_new_nodes_t) + + ((number_of_nodes -1) * sizeof (dev_info_t *)); + + gptwo_new_nodes = kmem_zalloc(size, KM_SLEEP); + + gptwo_new_nodes->gptwo_number_of_nodes = number_of_nodes; + gptwo_new_nodes->gptwo_version = GP2_VERSION; + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_allocate_node_list- returned %p\n", + gptwo_new_nodes); + + return (gptwo_new_nodes); +} + +void +gptwocfg_free_node_list(gptwo_new_nodes_t *gptwo_new_nodes) +{ + int size; + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_free_node_list- %p %d nodes", + gptwo_new_nodes, gptwo_new_nodes->gptwo_number_of_nodes); + + size = sizeof (gptwo_new_nodes_t) + + ((gptwo_new_nodes->gptwo_number_of_nodes - 1) * + sizeof (dev_info_t *)); + + kmem_free(gptwo_new_nodes, size); +} + +void +gptwocfg_register_ops(uint_t type, gptwo_cfgfunc_t *cfg_func, + gptwo_uncfgfunc_t *uncfg_func) +{ + /* KM_SLEEP guarantees success */ + gptwocfg_ops_t *ops = kmem_zalloc(sizeof (gptwocfg_ops_t), KM_SLEEP); + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_register_ops: type=%x ops=%lx\n", + type, ops); + ASSERT(type < GPTWO_NUMBER_OF_DEVICE_TYPES); + ops->gptwocfg_type = type; + ops->gptwocfg_version = GPTWOCFG_OPS_VERSION; + ops->gptwocfg_configure = cfg_func; + ops->gptwocfg_unconfigure = uncfg_func; + + mutex_enter(&gptwocfg_ops_table_lock); + gptwocfg_ops_table[type] = ops; + mutex_exit(&gptwocfg_ops_table_lock); +} + + + +void +gptwocfg_unregister_ops(uint_t type) +{ + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_unregister_ops: type=%x\n", type); + + ASSERT(type < GPTWO_NUMBER_OF_DEVICE_TYPES); + + mutex_enter(&gptwocfg_ops_table_lock); + kmem_free(gptwocfg_ops_table[type], sizeof (gptwocfg_ops_t)); + gptwocfg_ops_table[type] = NULL; + mutex_exit(&gptwocfg_ops_table_lock); +} + +gptwocfg_cookie_t +gptwocfg_configure(dev_info_t *ap, spcd_t *pcd, gptwo_aid_t id) +{ + gptwo_new_nodes_t *new_nodes = NULL; + gptwocfg_config_t *config; + gptwocfg_ops_t *ops; + + GPTWO_DEBUG3(1, CE_CONT, "gptwocfg_configure: ap=0x%p pcd=%p id=%x\n", + ap, pcd, id); + + /* + * Look to see if the port is already configured. + */ + mutex_enter(&gptwo_config_list_lock); + config = gptwo_config_list; + while (config != NULL) { + if (&starcat_dr_name) { + if (starcat_dr_name(ddi_node_name(ap)) < 0) { + config = config->gptwo_next; + continue; + } + } + if (config->gptwo_portid == id) { + cmn_err(CE_WARN, "gptwocfg: gptwocfg_configure: " + "0x%x Port already configured\n", id); + mutex_exit(&gptwo_config_list_lock); + return (NULL); + } + config = config->gptwo_next; + } + mutex_exit(&gptwo_config_list_lock); + + if (pcd == NULL) { + GPTWO_DEBUG0(1, CE_CONT, "gptwocfg_configure: pcd=NULL\n"); + return (NULL); + } + + if ((pcd->spcd_magic != PCD_MAGIC) || + (pcd->spcd_version != PCD_VERSION)) { + cmn_err(CE_WARN, "gptwocfg: Invalid Port " + "Configuration Descriptor\n"); + return (NULL); + } + + if (pcd->spcd_ptype >= GPTWO_NUMBER_OF_DEVICE_TYPES) { + cmn_err(CE_WARN, + "gptwocfg: Invalid device type %x", pcd->spcd_ptype); + return (NULL); + } + + if (pcd->spcd_prsv != SPCD_RSV_PASS) { + cmn_err(CE_WARN, + "gptwocfg: Agent at ID %x has not passed test(s)\n", id); + return (NULL); + } + + mutex_enter(&gptwocfg_ops_table_lock); + + ops = gptwocfg_ops_table[pcd->spcd_ptype]; + + if (ops == NULL) { + cmn_err(CE_WARN, "gptwocfg: Ops for type %x have not been " + "registered\n", pcd->spcd_ptype); + mutex_exit(&gptwocfg_ops_table_lock); + return (NULL); + } + + if (ops->gptwocfg_configure == NULL) { + cmn_err(CE_WARN, "gptwocfg: no configure routine registered " + "for sfaari type %x\n", pcd->spcd_ptype); + mutex_exit(&gptwocfg_ops_table_lock); + return (NULL); + } + + new_nodes = ops->gptwocfg_configure(ap, pcd, id); + + mutex_exit(&gptwocfg_ops_table_lock); + + if (new_nodes != NULL) { + config = kmem_zalloc(sizeof (gptwocfg_config_t), KM_SLEEP); + config->gptwo_version = GP2_VERSION; + config->gptwo_ap = ap; + config->gptwo_portid = id; + config->gptwo_nodes = new_nodes; + config->gptwo_ops = ops; + + /* + * put config on config list + */ + mutex_enter(&gptwo_config_list_lock); + config->gptwo_next = gptwo_config_list; + gptwo_config_list = config; + mutex_exit(&gptwo_config_list_lock); + } else { + config = NULL; + } + + return ((gptwocfg_cookie_t)config); +} + +gptwocfg_cookie_t +gptwocfg_unconfigure(dev_info_t *ap, gptwo_aid_t id) +{ + int i, circ; + int failure = 0; + dev_info_t *saf_dip; + gptwocfg_config_t *config, *temp; + gptwo_new_nodes_t *obp_nodes; + gptwocfg_ops_t *ops; + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_unconfigure: ap=0x%p id=0x%lx\n", + ap, id); + + mutex_enter(&gptwo_config_list_lock); + config = gptwo_config_list; + while (config != NULL) { + if (config->gptwo_portid == id) { + break; + } + config = config->gptwo_next; + } + mutex_exit(&gptwo_config_list_lock); + + if (config == NULL) { + /* + * There is no config structure associated with this agent id + * so it was probably built by firmware at start of day. We + * need to create a config structure before we can continue. + */ + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_unconfigure: id=0x%lx " + "No config structure - Need to build one\n", id); + + obp_nodes = gptwocfg_get_obp_created_nodes(ap, id); + + if (obp_nodes != NULL) { + config = kmem_zalloc(sizeof (gptwocfg_config_t), + KM_SLEEP); + config->gptwo_version = GP2_VERSION; + config->gptwo_ap = ap; + config->gptwo_portid = id; + config->gptwo_nodes = obp_nodes; + + /* + * put config on config list + */ + mutex_enter(&gptwo_config_list_lock); + config->gptwo_next = gptwo_config_list; + gptwo_config_list = config; + mutex_exit(&gptwo_config_list_lock); + } else { + cmn_err(CE_WARN, "gptwocfg: gptwocfg_unconfigure: " + "No OBP created nodes for ap=0x%lx agent id=0x%x", + (long)ap, id); + return (NULL); + } + } + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_unconfigure config=0x%lx\n", + config); + + ops = config->gptwo_ops; + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_unconfigure: ops=%lx\n", ops); + + ndi_devi_enter(ap, &circ); + + for (i = 0; i < config->gptwo_nodes->gptwo_number_of_nodes; i++) { + dev_info_t *fdip = NULL; + + saf_dip = config->gptwo_nodes->gptwo_nodes[i]; + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_unconfigure saf_dip=0x%lx\n", + saf_dip); + + if (saf_dip == NULL) { + GPTWO_DEBUG0(1, CE_CONT, "gptwocfg_unconfigure: " + "skipping NULLL saf device\n"); + + continue; + } + + config->gptwo_nodes->gptwo_nodes[i] = NULL; + + if (ops) { + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_configure " + "ops->gptwocfg_configure=%lx\n", + ops->gptwocfg_configure); + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_unconfigure " + "ops->gptwocfg_unconfigure=%lx\n", + ops->gptwocfg_unconfigure); + + if (ops->gptwocfg_unconfigure != NULL) { + config->gptwo_nodes->gptwo_nodes[i] = + ops->gptwocfg_unconfigure(saf_dip); + + } + } + + GPTWO_DEBUG1(1, CE_CONT, "e_ddi_branch_destroy <%s>\n", + ddi_get_name(saf_dip)); + + ASSERT(e_ddi_branch_held(saf_dip)); + + /* + * Don't hold parent busy when calling + * e_ddi_branch_unconfigure/destroy/referenced() + */ + ndi_devi_exit(ap, circ); + if (e_ddi_branch_destroy(saf_dip, &fdip, 0)) { + char *path = kmem_alloc(MAXPATHLEN, KM_SLEEP); + + /* + * If non-NULL, fdip is held and must be released. + */ + if (fdip != NULL) { + (void) ddi_pathname(fdip, path); + ddi_release_devi(fdip); + } else { + (void) ddi_pathname(saf_dip, path); + } + + cmn_err(CE_WARN, "saf node removal failed: %s (%p)", + path, fdip ? (void *)fdip : (void *)saf_dip); + + kmem_free(path, MAXPATHLEN); + + config->gptwo_nodes->gptwo_nodes[i] = saf_dip; + failure = 1; + } + ndi_devi_enter(ap, &circ); + } + + ndi_devi_exit(ap, circ); + + if (!failure) { + gptwocfg_free_node_list(config->gptwo_nodes); + + mutex_enter(&gptwo_config_list_lock); + if (gptwo_config_list == config) { + gptwo_config_list = config->gptwo_next; + } else { + temp = gptwo_config_list; + while (temp->gptwo_next != config) { + temp = temp->gptwo_next; + } + temp->gptwo_next = config->gptwo_next; + } + mutex_exit(&gptwo_config_list_lock); + + kmem_free(config, sizeof (gptwocfg_config_t)); + config = NULL; + } + + return (config); +} + +int +gptwocfg_next_node(gptwocfg_cookie_t c, dev_info_t *previous, dev_info_t **next) +{ + gptwocfg_config_t *cookie; + int i, j; + + GPTWO_DEBUG3(1, CE_WARN, "gptwocfg_next_node" + "(c=0x%lx, previous=0x%lx, next=0x%lx)\n", c, previous, next); + + cookie = (gptwocfg_config_t *)c; + + for (i = 0; i < cookie->gptwo_nodes->gptwo_number_of_nodes; i++) { + GPTWO_DEBUG1(1, CE_WARN, "0x%lx\n", + cookie->gptwo_nodes->gptwo_nodes[i]); + } + + if (previous == NULL) { + for (i = 0; i < cookie->gptwo_nodes->gptwo_number_of_nodes; + i++) { + if (cookie->gptwo_nodes->gptwo_nodes[i]) { + *next = cookie->gptwo_nodes->gptwo_nodes[i]; + GPTWO_DEBUG1(1, CE_WARN, "returned 0x%lx\n", + *next); + return (1); + } + } + return (0); + } + + for (i = 0; i < cookie->gptwo_nodes->gptwo_number_of_nodes; i++) { + if (cookie->gptwo_nodes->gptwo_nodes[i] == previous) { + for (j = i + 1; + j < cookie->gptwo_nodes->gptwo_number_of_nodes; + j++) { + if (cookie->gptwo_nodes->gptwo_nodes[j]) { + *next = + cookie->gptwo_nodes->gptwo_nodes[j]; + GPTWO_DEBUG1(1, CE_WARN, + "returned 0x%lx\n", *next); + return (1); + } + } + *next = NULL; + GPTWO_DEBUG1(1, CE_WARN, "returned 0x%lx\n", + *next); + return (1); + } + } + + /* + * previous is probably an invalid dev_info. + */ + return (0); +} + +static gptwo_new_nodes_t * +gptwocfg_get_obp_created_nodes(dev_info_t *ap, uint_t id) +{ + gptwo_new_nodes_t *obp_nodes; + dev_info_t *saf_dev; + int i = 0, nodes = 0; + int circular_count; + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_get_obp_created_nodes - ap=0x%lx " + "id=0x%x\n", ap, id); + + ndi_devi_enter(ap, &circular_count); + + /* + * First go through all the children of the attachment point + * to count matching safari agent ids + */ + saf_dev = ddi_get_child(ap); + while (saf_dev != NULL) { + if (ddi_getprop(DDI_DEV_T_ANY, saf_dev, DDI_PROP_DONTPASS, + "portid", -1) == id) { + if (&starcat_dr_name) { + if (starcat_dr_name(ddi_node_name(saf_dev)) + < 0) { + saf_dev = ddi_get_next_sibling(saf_dev); + continue; + } + } + nodes++; + } + saf_dev = ddi_get_next_sibling(saf_dev); + } + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_get_obp_created_nodes - %d nodes " + "found\n", nodes); + + obp_nodes = gptwocfg_allocate_node_list(nodes); + + /* + * Then fill in the nodes structure. + */ + saf_dev = ddi_get_child(ap); + while ((saf_dev != NULL) && (i < nodes)) { + if (ddi_getprop(DDI_DEV_T_ANY, saf_dev, DDI_PROP_DONTPASS, + "portid", -1) == id) { + if (&starcat_dr_name) { + if (starcat_dr_name(ddi_node_name(saf_dev)) + < 0) { + saf_dev = ddi_get_next_sibling(saf_dev); + continue; + } + } + /* + * Branch rooted at this dip must have been + * held by the DR driver. + */ + ASSERT(e_ddi_branch_held(saf_dev)); + obp_nodes->gptwo_nodes[i++] = saf_dev; + } + saf_dev = ddi_get_next_sibling(saf_dev); + } + + ndi_devi_exit(ap, circular_count); + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_get_obp_created_nodes - " + "Returning 0x%lx\n", obp_nodes); + + return (obp_nodes); +} + +void +gptwocfg_save_handle(dev_info_t *dip, fco_handle_t fco_handle) +{ + gptwocfg_handle_list_t *h; + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_save_handle - " + "dip=%lx fco_handle=%lx\n", dip, fco_handle); + + h = kmem_zalloc(sizeof (gptwocfg_handle_list_t), KM_SLEEP); + + mutex_enter(&gptwo_handle_list_lock); + + h->next = gptwocfg_handle_list; + h->dip = dip; + h->fco_handle = fco_handle; + gptwocfg_handle_list = h; + + mutex_exit(&gptwo_handle_list_lock); +} + +fco_handle_t +gptwocfg_get_handle(dev_info_t *dip) +{ + gptwocfg_handle_list_t *h, *last; + fco_handle_t fco_handle; + + mutex_enter(&gptwo_handle_list_lock); + + h = last = gptwocfg_handle_list; + + while (h != NULL) { + if (h->dip == dip) { + if (h == gptwocfg_handle_list) + gptwocfg_handle_list = h->next; + else + last->next = h->next; + + mutex_exit(&gptwo_handle_list_lock); + + fco_handle = h->fco_handle; + + kmem_free(h, sizeof (gptwocfg_handle_list_t)); + + GPTWO_DEBUG2(1, CE_CONT, "gptwocfg_get_handle - " + "dip=%lx fco_handle=%lx\n", dip, fco_handle); + + return (fco_handle); + } + last = h; + h = h->next; + } + + mutex_exit(&gptwo_handle_list_lock); + + GPTWO_DEBUG1(1, CE_CONT, "gptwocfg_get_handle - dip=%lx NO HANDLE\n", + dip); + + return (0); +} + +void +gptwocfg_devi_attach_to_parent(dev_info_t *dip) +{ + (void) i_ndi_config_node(dip, DS_LINKED, 0); +} + +#ifdef DEBUG +static void +debug(char *fmt, uintptr_t a1, uintptr_t a2, uintptr_t a3, + uintptr_t a4, uintptr_t a5) +{ + cmn_err(CE_CONT, fmt, a1, a2, a3, a4, a5); +} +#endif diff --git a/usr/src/uts/sun4u/io/i2c/nexus/i2bsc.c b/usr/src/uts/sun4u/io/i2c/nexus/i2bsc.c new file mode 100644 index 0000000000..969b8baa9e --- /dev/null +++ b/usr/src/uts/sun4u/io/i2c/nexus/i2bsc.c @@ -0,0 +1,1257 @@ +/* + * 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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * i2bsc.c is the nexus driver i2c traffic against devices hidden behind the + * Blade Support Chip (BSC). It supports both interrupt and polled + * mode operation, but defaults to interrupt. + */ + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/open.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/modctl.h> +#include <sys/stat.h> +#include <sys/kmem.h> +#include <sys/platform_module.h> +#include <sys/stream.h> +#include <sys/strlog.h> +#include <sys/log.h> +#include <sys/debug.h> +#include <sys/note.h> + +#include <sys/bscbus.h> +#include <sys/lom_ebuscodes.h> + +#include <sys/i2c/clients/i2c_client.h> +#include <sys/i2c/misc/i2c_svc.h> +#include <sys/i2c/misc/i2c_svc_impl.h> +#include <sys/i2c/nexus/i2bsc_impl.h> + +/* + * static function declarations + */ +static void i2bsc_resume(dev_info_t *dip); +static void i2bsc_suspend(dev_info_t *dip); +static int i2bsc_bus_ctl(dev_info_t *dip, dev_info_t *rdip, + ddi_ctl_enum_t op, void *arg, void *result); +static void i2bsc_acquire(i2bsc_t *, dev_info_t *dip, + i2c_transfer_t *tp); +static void i2bsc_release(i2bsc_t *); +static int i2bsc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static int i2bsc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); +static int i2bsc_open(dev_t *devp, int flag, int otyp, + cred_t *cred_p); +static int i2bsc_close(dev_t dev, int flag, int otyp, + cred_t *cred_p); +static int i2bsc_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); +static int i2bsc_initchild(dev_info_t *dip, dev_info_t *cdip); +static int i2bsc_uninitchild(dev_info_t *dip, dev_info_t *cdip); +static int i2bsc_setup_regs(i2bsc_t *); +static void i2bsc_start_session(i2bsc_t *); +static void i2bsc_fail_session(i2bsc_t *); +static int i2bsc_end_session(i2bsc_t *); +static void i2bsc_free_regs(i2bsc_t *); +static int i2bsc_reportdev(dev_info_t *dip, dev_info_t *rdip); +int i2bsc_transfer(dev_info_t *dip, i2c_transfer_t *tp); +static void i2bsc_trace(i2bsc_t *, char, const char *, + const char *, ...); +static int i2bsc_notify_max_transfer_size(i2bsc_t *); +static int i2bsc_discover_capability(i2bsc_t *); +static void i2bsc_put8(i2bsc_t *, uint8_t, uint8_t, uint8_t); +static uint8_t i2bsc_get8(i2bsc_t *, uint8_t, uint8_t); +static int i2bsc_safe_upload(i2bsc_t *, i2c_transfer_t *); +static boolean_t i2bsc_is_firmware_broken(i2bsc_t *); + +static struct bus_ops i2bsc_busops = { + BUSO_REV, + nullbusmap, /* bus_map */ + NULL, /* bus_get_intrspec */ + NULL, /* bus_add_intrspec */ + NULL, /* bus_remove_intrspec */ + NULL, /* bus_map_fault */ + ddi_no_dma_map, /* bus_dma_map */ + ddi_no_dma_allochdl, /* bus_dma_allochdl */ + ddi_no_dma_freehdl, /* bus_dma_freehdl */ + ddi_no_dma_bindhdl, /* bus_dma_bindhdl */ + ddi_no_dma_unbindhdl, /* bus_unbindhdl */ + ddi_no_dma_flush, /* bus_dma_flush */ + ddi_no_dma_win, /* bus_dma_win */ + ddi_no_dma_mctl, /* bus_dma_ctl */ + i2bsc_bus_ctl, /* bus_ctl */ + ddi_bus_prop_op, /* bus_prop_op */ + NULL, /* bus_get_eventcookie */ + NULL, /* bus_add_eventcall */ + NULL, /* bus_remove_eventcall */ + NULL, /* bus_post_event */ + 0, /* bus_intr_ctl */ + 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 */ + +}; + +struct cb_ops i2bsc_cb_ops = { + i2bsc_open, /* open */ + i2bsc_close, /* close */ + nodev, /* strategy */ + nodev, /* print */ + nodev, /* dump */ + nodev, /* read */ + nodev, /* write */ + i2bsc_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + nochpoll, /* poll */ + ddi_prop_op, /* cb_prop_op */ + 0, /* streamtab */ + D_MP | D_NEW /* Driver compatibility flag */ +}; + +static struct dev_ops i2bsc_ops = { + DEVO_REV, + 0, + ddi_getinfo_1to1, + nulldev, + nulldev, + i2bsc_attach, + i2bsc_detach, + nodev, + &i2bsc_cb_ops, + &i2bsc_busops +}; + +#ifdef DEBUG +#define I2BSC_VERSION_STRING "i2bsc driver - Debug v%I%" +#else +#define I2BSC_VERSION_STRING "i2bsc driver v%I%" +#endif + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + I2BSC_VERSION_STRING, /* Name of the module. */ + &i2bsc_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + &modldrv, + NULL +}; + +/* + * i2bsc soft state + */ +static void *i2bsc_state; + +i2c_nexus_reg_t i2bsc_regvec = { + I2C_NEXUS_REV, + i2bsc_transfer, +}; + +int +_init(void) +{ + int status; + + status = ddi_soft_state_init(&i2bsc_state, sizeof (i2bsc_t), + I2BSC_INITIAL_SOFT_SPACE); + if (status != 0) { + return (status); + } + + if ((status = mod_install(&modlinkage)) != 0) { + ddi_soft_state_fini(&i2bsc_state); + } + + return (status); +} + +int +_fini(void) +{ + int status; + + if ((status = mod_remove(&modlinkage)) == 0) { + ddi_soft_state_fini(&i2bsc_state); + } + + return (status); +} + +/* + * The loadable-module _info(9E) entry point + */ +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +static void +i2bsc_dodetach(dev_info_t *dip) +{ + i2bsc_t *i2c; + int instance = ddi_get_instance(dip); + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + + if ((i2c->i2bsc_attachflags & IMUTEX) != 0) { + mutex_destroy(&i2c->i2bsc_imutex); + cv_destroy(&i2c->i2bsc_icv); + } + if ((i2c->i2bsc_attachflags & SETUP_REGS) != 0) { + i2bsc_free_regs(i2c); + } + if ((i2c->i2bsc_attachflags & NEXUS_REGISTER) != 0) { + i2c_nexus_unregister(dip); + } + if ((i2c->i2bsc_attachflags & MINOR_NODE) != 0) { + ddi_remove_minor_node(dip, NULL); + } + + ddi_soft_state_free(i2bsc_state, instance); +} + +static int +i2bsc_doattach(dev_info_t *dip) +{ + i2bsc_t *i2c; + int instance = ddi_get_instance(dip); + + /* + * Allocate soft state structure. + */ + if (ddi_soft_state_zalloc(i2bsc_state, instance) != DDI_SUCCESS) { + return (DDI_FAILURE); + } + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + + i2c->majornum = ddi_driver_major(dip); + i2c->minornum = instance; + i2c->i2bsc_dip = dip; + i2c->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "debug", 0); + + (void) snprintf(i2c->i2bsc_name, sizeof (i2c->i2bsc_name), + "%s_%d", ddi_node_name(dip), instance); + + if (i2bsc_setup_regs(i2c) != DDI_SUCCESS) { + goto bad; + } + + i2c->i2bsc_attachflags |= SETUP_REGS; + + mutex_init(&i2c->i2bsc_imutex, NULL, MUTEX_DRIVER, + (void *) 0); + cv_init(&i2c->i2bsc_icv, NULL, CV_DRIVER, NULL); + i2c->i2bsc_attachflags |= IMUTEX; + + i2c_nexus_register(dip, &i2bsc_regvec); + i2c->i2bsc_attachflags |= NEXUS_REGISTER; + + if (ddi_create_minor_node(dip, "devctl", S_IFCHR, instance, + DDI_NT_NEXUS, 0) == DDI_FAILURE) { + cmn_err(CE_WARN, "%s ddi_create_minor_node failed", + i2c->i2bsc_name); + goto bad; + } + + i2c->i2bsc_attachflags |= MINOR_NODE; + + /* + * Now actually start talking to the microcontroller. The first + * thing to check is whether the firmware is broken. + */ + if (i2bsc_is_firmware_broken(i2c)) { + cmn_err(CE_WARN, "Underlying BSC hardware not communicating;" + " shutting down my i2c services"); + goto bad; + } + + i2c->i2bsc_attachflags |= FIRMWARE_ALIVE; + + /* + * Now see if the BSC chip supports the i2c service we rely upon. + */ + (void) i2bsc_discover_capability(i2c); + + if (i2bsc_notify_max_transfer_size(i2c) == DDI_SUCCESS) + i2c->i2bsc_attachflags |= TRANSFER_SZ; + + i2bsc_trace(i2c, 'A', "i2bsc_doattach", "attachflags %d", + i2c->i2bsc_attachflags); + + return (DDI_SUCCESS); + +bad: + i2bsc_dodetach(dip); + + return (DDI_FAILURE); +} + +static int +i2bsc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + switch (cmd) { + case DDI_ATTACH: + return (i2bsc_doattach(dip)); + + case DDI_RESUME: + i2bsc_resume(dip); + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } +} + +static int +i2bsc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + switch (cmd) { + case DDI_DETACH: + i2bsc_dodetach(dip); + return (DDI_SUCCESS); + + case DDI_SUSPEND: + i2bsc_suspend(dip); + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } +} + +/*ARGSUSED*/ +static int +i2bsc_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) +{ + int instance; + i2bsc_t *i2c; + + /* + * Make sure the open is for the right file type + */ + if (otyp != OTYP_CHR) + return (EINVAL); + + instance = getminor(*devp); + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + if (i2c == NULL) + return (ENXIO); + + /* + * Enforce exclusive access + */ + mutex_enter(&i2c->i2bsc_imutex); + if (i2c->i2bsc_open) { + mutex_exit(&i2c->i2bsc_imutex); + return (EBUSY); + } else + i2c->i2bsc_open = 1; + + mutex_exit(&i2c->i2bsc_imutex); + + return (0); +} + +/*ARGSUSED*/ +static int +i2bsc_close(dev_t dev, int flag, int otyp, cred_t *cred_p) +{ + int instance; + i2bsc_t *i2c; + + /* + * Make sure the close is for the right file type + */ + if (otyp != OTYP_CHR) + return (EINVAL); + + instance = getminor(dev); + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + if (i2c == NULL) + return (ENXIO); + + mutex_enter(&i2c->i2bsc_imutex); + i2c->i2bsc_open = 0; + mutex_exit(&i2c->i2bsc_imutex); + + return (0); +} + +/*ARGSUSED*/ +static int +i2bsc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + i2bsc_t *i2c; + dev_info_t *self; + dev_info_t *child; + struct devctl_iocdata *dcp; + int rv; + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, getminor(dev)); + + if (i2c == NULL) + return (ENXIO); + + self = (dev_info_t *)i2c->i2bsc_dip; + + /* + * read devctl ioctl data + */ + if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS) { + return (EFAULT); + } + + switch (cmd) { + case DEVCTL_BUS_DEV_CREATE: + rv = ndi_dc_devi_create(dcp, self, 0, NULL); + break; + + case DEVCTL_DEVICE_REMOVE: + if (ndi_dc_getname(dcp) == NULL || + ndi_dc_getaddr(dcp) == NULL) { + rv = EINVAL; + break; + } + + /* + * lookup and hold child device + */ + child = ndi_devi_find(self, + ndi_dc_getname(dcp), ndi_dc_getaddr(dcp)); + if (child == NULL) { + rv = ENXIO; + break; + } + + if ((rv = ndi_devi_offline(child, NDI_DEVI_REMOVE)) != + NDI_SUCCESS) { + rv = (rv == NDI_BUSY) ? EBUSY : EIO; + } + + break; + + default: + rv = ENOTSUP; + } + + ndi_dc_freehdl(dcp); + + return (rv); +} + +static int +i2bsc_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, + void *arg, void *result) +{ + i2bsc_t *i2c; + int instance = ddi_get_instance(dip); + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + + i2bsc_trace(i2c, 'A', "i2bsc_bus_ctl", "dip/rdip,op/arg" + " %p/%p,%d/%p", dip, rdip, (int)op, arg); + + switch (op) { + case DDI_CTLOPS_INITCHILD: + return (i2bsc_initchild(dip, (dev_info_t *)arg)); + + case DDI_CTLOPS_UNINITCHILD: + return (i2bsc_uninitchild(dip, (dev_info_t *)arg)); + + case DDI_CTLOPS_REPORTDEV: + return (i2bsc_reportdev(dip, rdip)); + + case DDI_CTLOPS_DMAPMAPC: + case DDI_CTLOPS_POKE: + case DDI_CTLOPS_PEEK: + case DDI_CTLOPS_IOMIN: + case DDI_CTLOPS_REPORTINT: + case DDI_CTLOPS_SIDDEV: + case DDI_CTLOPS_SLAVEONLY: + case DDI_CTLOPS_AFFINITY: + case DDI_CTLOPS_PTOB: + case DDI_CTLOPS_BTOP: + case DDI_CTLOPS_BTOPR: + case DDI_CTLOPS_DVMAPAGESIZE: + return (DDI_FAILURE); + + default: + return (ddi_ctlops(dip, rdip, op, arg, result)); + } +} + +/* + * i2bsc_suspend() is called before the system suspends. Existing + * transfer in progress or waiting will complete, but new transfers are + * effectively blocked by "acquiring" the bus. + */ +static void +i2bsc_suspend(dev_info_t *dip) +{ + i2bsc_t *i2c; + int instance; + + instance = ddi_get_instance(dip); + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + + i2bsc_acquire(i2c, NULL, NULL); +} + +/* + * i2bsc_resume() is called when the system resumes from CPR. It releases + * the hold that was placed on the i2c bus, which allows any real + * transfers to continue. + */ +static void +i2bsc_resume(dev_info_t *dip) +{ + i2bsc_t *i2c; + int instance; + + instance = ddi_get_instance(dip); + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, instance); + + i2bsc_release(i2c); +} + +/* + * i2bsc_acquire() is called by a thread wishing to "own" the I2C bus. + * It should not be held across multiple transfers. + */ +static void +i2bsc_acquire(i2bsc_t *i2c, dev_info_t *dip, i2c_transfer_t *tp) +{ + mutex_enter(&i2c->i2bsc_imutex); + while (i2c->i2bsc_busy) { + cv_wait(&i2c->i2bsc_icv, &i2c->i2bsc_imutex); + } + i2c->i2bsc_busy = 1; + i2c->i2bsc_cur_tran = tp; + i2c->i2bsc_cur_dip = dip; + mutex_exit(&i2c->i2bsc_imutex); +} + +/* + * i2bsc_release() is called to release a hold made by i2bsc_acquire(). + */ +static void +i2bsc_release(i2bsc_t *i2c) +{ + mutex_enter(&i2c->i2bsc_imutex); + i2c->i2bsc_busy = 0; + i2c->i2bsc_cur_tran = NULL; + cv_signal(&i2c->i2bsc_icv); + mutex_exit(&i2c->i2bsc_imutex); +} + +static int +i2bsc_initchild(dev_info_t *dip, dev_info_t *cdip) +{ + i2bsc_t *i2c; + int32_t address_cells; + int len; + int32_t regs[3]; + int err; + i2bsc_ppvt_t *ppvt; + char name[30]; + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, ddi_get_instance(dip)); + + i2bsc_trace(i2c, 'A', "i2bsc_initchild", "dip/cdip %p/%p", dip, cdip); + + ppvt = kmem_alloc(sizeof (i2bsc_ppvt_t), KM_SLEEP); + + len = sizeof (address_cells); + + err = ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip, + DDI_PROP_CANSLEEP, "#address-cells", + (caddr_t)&address_cells, &len); + if (err != DDI_PROP_SUCCESS || len != sizeof (address_cells)) { + return (DDI_FAILURE); + } + + len = sizeof (regs); + err = ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip, + DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, + "reg", (caddr_t)regs, &len); + if (err != DDI_PROP_SUCCESS) + return (DDI_FAILURE); + + if (address_cells == 1) { + ppvt->i2bsc_ppvt_bus = I2BSC_DEFAULT_BUS; + ppvt->i2bsc_ppvt_addr = regs[0]; + (void) sprintf(name, "%x", regs[0]); + i2bsc_trace(i2c, 'A', "i2bsc_initchild", "#address-cells = 1" + " regs[0] = %d", regs[0]); + } else if (address_cells == 2) { + ppvt->i2bsc_ppvt_bus = regs[0]; + ppvt->i2bsc_ppvt_addr = regs[1]; + (void) sprintf(name, "%x,%x", regs[0], regs[1]); + i2bsc_trace(i2c, 'A', "i2bsc_initchild", "#address-cells = 2" + " regs[0] = %d, regs[1] = %d", regs[0], regs[1]); + } else { + return (DDI_FAILURE); + } + + /* + * Attach the parent's private data structure to the child's devinfo + * node, and store the child's address on the nexus in the child's + * devinfo node. + */ + ddi_set_parent_data(cdip, ppvt); + ddi_set_name_addr(cdip, name); + + i2bsc_trace(i2c, 'A', "i2bsc_initchild", "success(%s)", + ddi_node_name(cdip)); + + return (DDI_SUCCESS); +} + +static int +i2bsc_uninitchild(dev_info_t *dip, dev_info_t *cdip) +{ + i2bsc_t *i2c; + i2bsc_ppvt_t *ppvt; + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, ddi_get_instance(dip)); + + i2bsc_trace(i2c, 'D', "i2bsc_uninitchild", "dip/cdip %p/%p", dip, cdip); + + ppvt = ddi_get_parent_data(cdip); + kmem_free(ppvt, sizeof (i2bsc_ppvt_t)); + + ddi_set_parent_data(cdip, NULL); + ddi_set_name_addr(cdip, NULL); + + i2bsc_trace(i2c, 'D', "i2bsc_uninitchild", "success(%s)", + ddi_node_name(cdip)); + + return (DDI_SUCCESS); +} + +/* + * i2bsc_setup_regs() is called to map in registers specific to + * the i2bsc. + */ +static int +i2bsc_setup_regs(i2bsc_t *i2c) +{ + int nregs; + + i2c->bscbus_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; + i2c->bscbus_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; + i2c->bscbus_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; + + if (ddi_dev_nregs(i2c->i2bsc_dip, &nregs) != DDI_SUCCESS) { + return (DDI_FAILURE); + } + + if (nregs < 1) { + return (DDI_FAILURE); + } + + if (ddi_regs_map_setup(i2c->i2bsc_dip, 0, + (caddr_t *)&i2c->bscbus_regs, 0, 0, &i2c->bscbus_attr, + &i2c->bscbus_handle) != DDI_SUCCESS) { + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* + * i2bsc_free_regs() frees any registers previously + * allocated. + */ +static void +i2bsc_free_regs(i2bsc_t *i2c) +{ + if (i2c->bscbus_regs != NULL) { + ddi_regs_map_free(&i2c->bscbus_handle); + } +} + +/*ARGSUSED*/ +static int +i2bsc_reportdev(dev_info_t *dip, dev_info_t *rdip) +{ + if (rdip == (dev_info_t *)0) + return (DDI_FAILURE); + + cmn_err(CE_CONT, "?i2bsc-device: %s@%s, %s%d\n", + ddi_node_name(rdip), ddi_get_name_addr(rdip), ddi_driver_name(rdip), + ddi_get_instance(rdip)); + + return (DDI_SUCCESS); +} + +/* + * I/O Functions + * + * i2bsc_{put,get}8_once are wrapper functions to ddi_{get,put}8. + * i2bsc_{put,get}8 are equivalent functions but with retry code. + * i2bsc_bscbus_state determines underlying bus error status. + * i2bsc_clear_acc_fault clears the underlying bus error status. + * + * I/O Flags + * + * bscbus_fault - Error register in underlying bus for last IO operation. + * session_failure - Set by any failed IO command. This is a sticky flag + * reset explicitly using i2bsc_start_session + * + * Session Management + * + * i2bsc_{start,end}_session need to be used to detect an error across multiple + * gets/puts rather than having to test for an error on each get/put. + */ + +static int i2bsc_bscbus_state(i2bsc_t *i2c) +{ + uint32_t retval; + + retval = ddi_get32(i2c->bscbus_handle, + (uint32_t *)I2BSC_NEXUS_ADDR(i2c, EBUS_CMD_SPACE_GENERIC, + LOMBUS_FAULT_REG)); + i2c->bscbus_fault = retval; + + return ((retval == 0) ? DDI_SUCCESS : DDI_FAILURE); +} + +static void i2bsc_clear_acc_fault(i2bsc_t *i2c) +{ + i2bsc_trace(i2c, '@', "i2bsc_clear_acc_fault", "clearing acc fault"); + ddi_put32(i2c->bscbus_handle, + (uint32_t *)I2BSC_NEXUS_ADDR(i2c, EBUS_CMD_SPACE_GENERIC, + LOMBUS_FAULT_REG), 0); +} + +static void +i2bsc_start_session(i2bsc_t *i2c) +{ + i2bsc_trace(i2c, 'S', "i2bsc_start_session", "session started"); + i2c->bscbus_session_failure = 0; +} + +static void +i2bsc_fail_session(i2bsc_t *i2c) +{ + i2bsc_trace(i2c, 'S', "i2bsc_fail_session", "session failed"); + i2c->bscbus_session_failure = 1; +} + +static int +i2bsc_end_session(i2bsc_t *i2c) +{ + /* + * The ONLY way to get the session status is to end the session. + * If clients of the session interface ever wanted the status mid-way + * then they are really working with multiple contigious sessions. + */ + i2bsc_trace(i2c, 'S', "i2bsc_end_session", "session ended with %d", + i2c->bscbus_session_failure); + return ((i2c->bscbus_session_failure) ? DDI_FAILURE : DDI_SUCCESS); +} + +static boolean_t +i2bsc_is_firmware_broken(i2bsc_t *i2c) +{ + int i; + int niterations = I2BSC_SHORT_RETRY_LIMIT; + + i2bsc_trace(i2c, 'A', "i2bsc_is_firmware_broken", "called"); + + for (i = 0; i < niterations; i++) { + (void) ddi_get8(i2c->bscbus_handle, + I2BSC_NEXUS_ADDR(i2c, EBUS_CMD_SPACE_I2C, + EBUS_IDX12_RESULT)); + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) { + i2bsc_clear_acc_fault(i2c); + continue; + } else { + /* + * Firmware communication succeeded. + */ + i2bsc_trace(i2c, 'A', "i2bsc_is_firmware_broken", + "firmware communications okay"); + return (B_FALSE); + } + } + + /* + * Firmware is not communicative. Some possible causes : + * Broken hardware + * BSC held in reset + * Corrupt BSC image + * OBP incompatiblity preventing drivers loading properly + */ + i2bsc_trace(i2c, 'A', "i2bsc_is_firmware_broken", "%d read fails", + niterations); + return (B_TRUE); +} + +static void +i2bsc_put8(i2bsc_t *i2c, uint8_t space, uint8_t index, uint8_t value) +{ + int retryable = I2BSC_RETRY_LIMIT; + + i2bsc_trace(i2c, '@', "i2bsc_put8", "(space,index)<-val (%d,%d)<-%d", + space, index, value); + + i2bsc_clear_acc_fault(i2c); + + /* + * If a session failure has already occurred, reduce the level of + * retries to a minimum. This is a optimization of the failure + * recovery strategy. + */ + if (i2c->bscbus_session_failure) + retryable = 1; + + while (retryable--) { + ddi_put8(i2c->bscbus_handle, + I2BSC_NEXUS_ADDR(i2c, space, index), value); + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) { + i2bsc_clear_acc_fault(i2c); + } else + break; + } + + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) + i2bsc_fail_session(i2c); + + i2bsc_trace(i2c, '@', "i2bsc_put8", "tried %d time(s)", + I2BSC_RETRY_LIMIT - retryable); +} + +static uint8_t +i2bsc_get8(i2bsc_t *i2c, uint8_t space, uint8_t index) +{ + uint8_t value; + int retryable = I2BSC_RETRY_LIMIT; + + i2bsc_clear_acc_fault(i2c); + + /* + * If a session failure has already occurred, reduce the level of + * retries to a minimum. This is a optimization of the failure + * recovery strategy. + */ + if (i2c->bscbus_session_failure) + retryable = 1; + + while (retryable--) { + value = ddi_get8(i2c->bscbus_handle, + I2BSC_NEXUS_ADDR(i2c, space, index)); + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) { + i2bsc_clear_acc_fault(i2c); + } else + break; + } + + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) + i2bsc_fail_session(i2c); + + i2bsc_trace(i2c, '@', "i2bsc_get8", "tried %d time(s)", + I2BSC_RETRY_LIMIT - retryable); + + i2bsc_trace(i2c, '@', "i2bsc_get8", "(space,index)->val (%d,%d)->%d", + space, index, value); + return (value); +} + +static void +i2bsc_put8_once(i2bsc_t *i2c, uint8_t space, uint8_t index, uint8_t value) +{ + i2bsc_trace(i2c, '@', "i2bsc_put8_once", + "(space,index)<-val (%d,%d)<-%d", space, index, value); + + i2bsc_clear_acc_fault(i2c); + + ddi_put8(i2c->bscbus_handle, + I2BSC_NEXUS_ADDR(i2c, space, index), value); + + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) + i2bsc_fail_session(i2c); +} + +static uint8_t +i2bsc_get8_once(i2bsc_t *i2c, uint8_t space, uint8_t index) +{ + uint8_t value; + + i2bsc_clear_acc_fault(i2c); + + value = ddi_get8(i2c->bscbus_handle, + I2BSC_NEXUS_ADDR(i2c, space, index)); + + if (i2bsc_bscbus_state(i2c) != DDI_SUCCESS) + i2bsc_fail_session(i2c); + + i2bsc_trace(i2c, '@', "i2bsc_get8_once", + "(space,index)->val (%d,%d)->%d", space, index, value); + + return (value); +} + +static int +i2bsc_notify_max_transfer_size(i2bsc_t *i2c) +{ + /* + * If the underlying hardware does not support the i2c service and + * we are not running in fake_mode, then we cannot set the + * MAX_TRANSFER_SZ. + */ + if (i2c->i2c_proxy_support == 0) + return (DDI_FAILURE); + + i2bsc_start_session(i2c); + + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_MAX_TRANSFER_SZ, + I2BSC_MAX_TRANSFER_SZ); + + if (i2bsc_end_session(i2c) != DDI_SUCCESS) + return (DDI_FAILURE); + + return (DDI_SUCCESS); +} + +/* + * Discover if the microcontroller implements the I2C Proxy Service this + * driver requires. If it does not, i2c transactions will abort with + * I2C_FAILURE, unless fake_mode is being used. + */ +static int +i2bsc_discover_capability(i2bsc_t *i2c) +{ + i2bsc_start_session(i2c); + + i2c->i2c_proxy_support = i2bsc_get8(i2c, EBUS_CMD_SPACE_GENERIC, + EBUS_IDX_CAP0); + i2c->i2c_proxy_support &= EBUS_CAP0_I2C_PROXY; + + if (i2bsc_end_session(i2c) != DDI_SUCCESS) + return (DDI_FAILURE); + + return (DDI_SUCCESS); +} + +static int +i2bsc_upload_preamble(i2bsc_t *i2c, i2c_transfer_t *tp) +{ + i2bsc_ppvt_t *ppvt; + int wr_rd; + + ppvt = ddi_get_parent_data(i2c->i2bsc_cur_dip); + + /* Get a lock on the i2c devices owned by the microcontroller */ + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_TRANSACTION_LOCK, 1); + if (!i2bsc_get8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_TRANSACTION_LOCK)) { + /* + * i2c client driver must timeout retry, NOT this nexus + */ + tp->i2c_result = I2C_INCOMPLETE; + i2bsc_trace(i2c, 'U', "i2bsc_upload_preamble", + "Couldn't get transaction lock"); + return (tp->i2c_result); + } + + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_BUS_ADDRESS, + ppvt->i2bsc_ppvt_bus); + + /* + * The Solaris architecture for I2C uses 10-bit I2C addresses where + * bit-0 is zero (the read/write bit). The microcontroller uses 7 bit + * I2C addresses (read/write bit excluded). Hence we need to convert + * the address by bit-shifting. + */ + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_CLIENT_ADDRESS, + ppvt->i2bsc_ppvt_addr >> 1); + + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_TRANSFER_TYPE, + tp->i2c_flags); + + /* + * We have only one register used for data input and output. When + * a WR_RD is issued, this means we want to do a Random-Access-Read. + * First a series of bytes are written which define the address to + * read from. In hardware this sets an address pointer. Then a series + * of bytes are read. The read/write boundary tells you how many + * bytes are to be written before reads will be issued. + */ + if (tp->i2c_flags == I2C_WR_RD) + wr_rd = tp->i2c_wlen; + else + wr_rd = 0; + + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_WR_RD_BOUNDARY, wr_rd); + + return (I2C_SUCCESS); +} + +/* + * Function i2bsc_upload + * + * Description This function runs the i2c transfer protocol down to the + * microcontroller. Its goal is to be as reliable as possible. + * This is achieved by making all the state-less aspects + * re-tryable. For stateful aspects, we take care to ensure the + * counters are decremented only when data transfer has been + * successful. + */ +static int +i2bsc_upload(i2bsc_t *i2c, i2c_transfer_t *tp) +{ + int quota = I2BSC_MAX_TRANSFER_SZ; + uint8_t res; + int residual; + + /* + * Total amount of data outstanding + */ + residual = tp->i2c_w_resid + tp->i2c_r_resid; + + /* + * Anything in this session *could* be re-tried without side-effects. + * Therefore, error exit codes are I2C_INCOMPLETE rather than + * I2C_FAILURE. + */ + i2bsc_start_session(i2c); + if (i2bsc_upload_preamble(i2c, tp) != I2C_SUCCESS) + return (I2C_INCOMPLETE); + if (i2bsc_end_session(i2c) != DDI_SUCCESS) + return (I2C_INCOMPLETE); + + /* The writes done here are not retryable */ + while (tp->i2c_w_resid && quota) { + i2bsc_put8_once(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_DATA_INOUT, + tp->i2c_wbuf[tp->i2c_wlen - tp->i2c_w_resid]); + if (i2bsc_bscbus_state(i2c) == DDI_SUCCESS) { + tp->i2c_w_resid--; + quota--; + residual--; + } else { + i2bsc_trace(i2c, 'T', "i2bsc_upload", "write failed"); + return (tp->i2c_result = I2C_INCOMPLETE); + } + } + + /* The reads done here are not retryable */ + while (tp->i2c_r_resid && quota) { + tp->i2c_rbuf[tp->i2c_rlen - tp->i2c_r_resid] = + i2bsc_get8_once(i2c, EBUS_CMD_SPACE_I2C, + EBUS_IDX12_DATA_INOUT); + if (i2bsc_bscbus_state(i2c) == DDI_SUCCESS) { + tp->i2c_r_resid--; + quota--; + residual--; + } else { + i2bsc_trace(i2c, 'T', "i2bsc_upload", "read failed"); + return (tp->i2c_result = I2C_INCOMPLETE); + } + } + + i2bsc_start_session(i2c); + + /* + * A possible future enhancement would be to allow early breakout of the + * loops seen above. In such circumstances, "residual" would be non- + * zero. This may be useful if we want to support the interruption of + * transfer part way through an i2c_transfer_t. + */ + i2bsc_put8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_RESIDUAL_DATA, residual); + res = i2bsc_get8(i2c, EBUS_CMD_SPACE_I2C, EBUS_IDX12_RESULT); + if (i2bsc_end_session(i2c) != DDI_SUCCESS) + return (tp->i2c_result = I2C_INCOMPLETE); + + switch (res) { + case EBUS_I2C_SUCCESS: + tp->i2c_result = I2C_SUCCESS; + break; + case EBUS_I2C_FAILURE: + /* + * This is rare but possible. A retry may still fix this + * so lets allow that by returning I2C_INCOMPLETE. + * "hifTxRing still contains 1 bytes" is reported by the + * microcontroller when this return value is seen. + */ + i2bsc_trace(i2c, 'T', "i2bsc_upload", "EBUS_I2C_FAILURE" + " but returning I2C_INCOMPLETE for possible re-try"); + tp->i2c_result = I2C_INCOMPLETE; + break; + case EBUS_I2C_INCOMPLETE: + tp->i2c_result = I2C_INCOMPLETE; + break; + default: + tp->i2c_result = I2C_FAILURE; + } + + return (tp->i2c_result); +} + +/* + * Function i2bsc_safe_upload + * + * Description This function is called "safe"-upload because it attempts to + * do transaction re-tries for cases where state is not spoiled + * by a transaction-level retry. + */ +static int +i2bsc_safe_upload(i2bsc_t *i2c, i2c_transfer_t *tp) +{ + int retryable = I2BSC_RETRY_LIMIT; + int result; + + i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", "Transaction %s", + (tp->i2c_flags == I2C_WR_RD) ? "retryable" : "single-shot"); + + /* + * The only re-tryable transaction type is I2C_WR_RD. If we don't + * have this we can only use session-based recovery offered by + * i2bsc_upload. + */ + if (tp->i2c_flags != I2C_WR_RD) + return (i2bsc_upload(i2c, tp)); + + while (retryable--) { + result = i2bsc_upload(i2c, tp); + if (result == I2C_INCOMPLETE) { + /* Have another go */ + tp->i2c_r_resid = tp->i2c_rlen; + tp->i2c_w_resid = tp->i2c_wlen; + tp->i2c_result = I2C_SUCCESS; + i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", + "Retried (%d)", I2BSC_RETRY_LIMIT - retryable); + continue; + } else { + i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", + "Exiting while loop on result %d", result); + return (result); + } + } + + i2bsc_trace(i2c, 'T', "i2bsc_safe_upload", "Exiting on %d", result); + return (result); +} + +/* + * Function i2bsc_transfer + * + * Description This is the entry-point that clients use via the Solaris i2c + * framework. It kicks off the servicing of i2c transfer requests. + */ +int +i2bsc_transfer(dev_info_t *dip, i2c_transfer_t *tp) +{ + i2bsc_t *i2c; + + i2c = (i2bsc_t *)ddi_get_soft_state(i2bsc_state, + ddi_get_instance(ddi_get_parent(dip))); + + i2bsc_acquire(i2c, dip, tp); + + tp->i2c_r_resid = tp->i2c_rlen; + tp->i2c_w_resid = tp->i2c_wlen; + tp->i2c_result = I2C_SUCCESS; + + i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Transaction i2c_version/flags" + " %d/%d", tp->i2c_version, tp->i2c_flags); + i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Transaction buffer rlen/wlen" + " %d/%d", tp->i2c_rlen, tp->i2c_wlen); + i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Transaction ptrs wbuf/rbuf" + " %p/%p", tp->i2c_wbuf, tp->i2c_rbuf); + + if (i2c->i2c_proxy_support) + (void) i2bsc_safe_upload(i2c, tp); + else + tp->i2c_result = I2C_FAILURE; + + i2bsc_trace(i2c, 'T', "i2bsc_transfer", "Residual writes/reads" + " %d/%d", tp->i2c_w_resid, tp->i2c_r_resid); + i2bsc_trace(i2c, 'T', "i2bsc_transfer", "i2c_result" + " %d", tp->i2c_result); + + i2bsc_release(i2c); + + return (tp->i2c_result); +} + +/* + * General utility routines ... + */ + +#ifdef DEBUG + +static void +i2bsc_trace(i2bsc_t *ssp, char code, const char *caller, + const char *fmt, ...) +{ + char buf[256]; + char *p; + va_list va; + + if (ssp->debug & (1 << (code-'@'))) { + p = buf; + (void) snprintf(p, sizeof (buf) - (p - buf), + "%s/%s: ", ssp->i2bsc_name, 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(ssp->majornum, ssp->minornum, code, SL_TRACE, + buf); + } +} + +#else /* DEBUG */ + +_NOTE(ARGSUSED(0)) +static void +i2bsc_trace(i2bsc_t *ssp, char code, const char *caller, + const char *fmt, ...) +{ +} + +#endif /* DEBUG */ diff --git a/usr/src/uts/sun4u/io/i2c/nexus/i2bsc.conf b/usr/src/uts/sun4u/io/i2c/nexus/i2bsc.conf new file mode 100644 index 0000000000..6608ada6b9 --- /dev/null +++ b/usr/src/uts/sun4u/io/i2c/nexus/i2bsc.conf @@ -0,0 +1,31 @@ +# +# 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 2001-2002 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# Configuration file for i2bsc driver +# + +# 64-bit Debug bit-vector; requires DEBUG driver to be installed +debug=0; diff --git a/usr/src/uts/sun4u/io/todm5819p_rmc.c b/usr/src/uts/sun4u/io/todm5819p_rmc.c new file mode 100644 index 0000000000..e9bda93105 --- /dev/null +++ b/usr/src/uts/sun4u/io/todm5819p_rmc.c @@ -0,0 +1,409 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * tod driver module for ALI M5819P part + */ + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/open.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> + +#include <sys/todm5819p.h> +#include <sys/rmc_comm_dp.h> +#include <sys/rmc_comm_drvintf.h> +#include <sys/modctl.h> +#include <sys/stat.h> +#include <sys/clock.h> +#include <sys/reboot.h> +#include <sys/machsystm.h> + +static timestruc_t todm5819p_rmc_get(void); +static void todm5819p_rmc_set(timestruc_t); +static uint_t todm5819p_rmc_set_watchdog_timer(uint_t); +static uint_t todm5819p_rmc_clear_watchdog_timer(void); +static void todm5819p_rmc_set_power_alarm(timestruc_t); +static void todm5819p_rmc_clear_power_alarm(void); +static uint64_t todm5819p_rmc_get_cpufrequency(void); + +extern uint64_t find_cpufrequency(volatile uint8_t *); + +/* + * External variables + */ +extern int watchdog_enable; +extern int watchdog_available; +extern int boothowto; + +/* + * Global variables + */ +int m5819p_debug_flags; + +/* + * Module linkage information for the kernel. + */ +static struct modlmisc modlmisc = { + &mod_miscops, "tod module for ALI M5819P" +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modlmisc, NULL +}; + +static todinfo_t rtc_to_tod(struct rtc_t *); +static void read_rtc(struct rtc_t *); +static void write_rtc_time(struct rtc_t *); +static void write_rtc_alarm(struct rtc_t *); + + +int +_init(void) +{ + if (strcmp(tod_module_name, "todm5819p_rmc") == 0) { + M5819P_ADDR_REG = RTC_B; + M5819P_DATA_REG = (RTC_DM | RTC_HM); + + tod_ops.tod_get = todm5819p_rmc_get; + tod_ops.tod_set = todm5819p_rmc_set; + + tod_ops.tod_set_watchdog_timer = + todm5819p_rmc_set_watchdog_timer; + tod_ops.tod_clear_watchdog_timer = + todm5819p_rmc_clear_watchdog_timer; + tod_ops.tod_set_power_alarm = todm5819p_rmc_set_power_alarm; + tod_ops.tod_clear_power_alarm = todm5819p_rmc_clear_power_alarm; + tod_ops.tod_get_cpufrequency = todm5819p_rmc_get_cpufrequency; + if (boothowto & RB_DEBUG) { + cmn_err(CE_WARN, "todm5819p_rmc: kernel debugger " + "detected: hardware watchdog disabled"); + } + } + + return (mod_install(&modlinkage)); +} + +int +_fini(void) +{ + if (strcmp(tod_module_name, "todm5819p_rmc") == 0) + return (EBUSY); + + return (mod_remove(&modlinkage)); +} + +/* + * The loadable-module _info(9E) entry point + */ +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + + +/* + * Read the current time from the clock chip and convert to UNIX form. + * Assumes that the year in the clock chip is valid. + * Must be called with tod_lock held. + */ +static timestruc_t +todm5819p_rmc_get(void) +{ + int i; + int s; + timestruc_t ts; + struct rtc_t rtc; + + ASSERT(MUTEX_HELD(&tod_lock)); + + /* set the hw watchdog timer if it's been activated */ + if (watchdog_activated) { + int ret = 0; + ret = tod_ops.tod_set_watchdog_timer(0); + /* + * The empty set_watchdog routine returns a 0. So if a + * coded routine fails we will look for a -1 for a failure. + */ + if (ret == -1) + cmn_err(CE_WARN, "todm5819p: failed to set hardware " + "watchdog timer."); + } + + /* + * Read current time from the tod. If the tod isn't accessible, wait and + * retry. + * Run critical in the time critical section to avoid being interrupted + */ + for (i = 0; i < TODM5819_UIP_RETRY_THRESH; i++) { + s = ddi_enter_critical(); + M5819P_ADDR_REG = RTC_A; + if (!(M5819P_DATA_REG & RTC_UIP)) { + read_rtc(&rtc); + ddi_exit_critical(s); + break; + } + ddi_exit_critical(s); + drv_usecwait(TODM5819_UIP_WAIT_USEC); + } + if (i == TODM5819_UIP_RETRY_THRESH) { + /* + * tod is inaccessible: just return current software time + */ + tod_fault_reset(); + return (hrestime); + } + + ts.tv_sec = tod_to_utc(rtc_to_tod(&rtc)); + ts.tv_nsec = 0; + return (ts); +} + +static todinfo_t +rtc_to_tod(struct rtc_t *rtc) +{ + todinfo_t tod; + + /* + * tod_year is base 1900 so this code needs to adjust the true year + * retrieved from the rtc's century and year fields. + */ + tod.tod_year = rtc->rtc_year + (rtc->rtc_century * 100) - 1900; + tod.tod_month = rtc->rtc_mon; + tod.tod_day = rtc->rtc_dom; + tod.tod_dow = rtc->rtc_dow; + tod.tod_hour = rtc->rtc_hrs; + tod.tod_min = rtc->rtc_min; + tod.tod_sec = rtc->rtc_sec; + + return (tod); +} + +static void +read_rtc(struct rtc_t *rtc) +{ + M5819P_ADDR_REG = RTC_SEC; + rtc->rtc_sec = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_ASEC; + rtc->rtc_asec = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_MIN; + rtc->rtc_min = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_AMIN; + rtc->rtc_amin = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_HRS; + rtc->rtc_hrs = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_AHRS; + rtc->rtc_ahrs = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_DOW; + rtc->rtc_dow = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_DOM; + rtc->rtc_dom = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_MON; + rtc->rtc_mon = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_YEAR; + rtc->rtc_year = M5819P_DATA_REG; + M5819P_ADDR_REG = RTC_CENTURY; + rtc->rtc_century = M5819P_DATA_REG; + + /* Read date alarm */ + M5819P_ADDR_REG = RTC_ADOM_REG; + rtc->rtc_adom = (M5819P_DATA_REG) & RTC_ADOM; +} + +/* + * Write the specified time into the clock chip. + * Must be called with tod_lock held. + */ +static void +todm5819p_rmc_set(timestruc_t ts) +{ + struct rtc_t rtc; + todinfo_t tod = utc_to_tod(ts.tv_sec); + int year; + rmc_comm_msg_t request; + dp_set_date_time_t set_time_msg; + + ASSERT(MUTEX_HELD(&tod_lock)); + + /* tod_year is base 1900 so this code needs to adjust */ + year = 1900 + tod.tod_year; + rtc.rtc_year = year % 100; + rtc.rtc_century = year / 100; + rtc.rtc_mon = (uint8_t)tod.tod_month; + rtc.rtc_dom = (uint8_t)tod.tod_day; + rtc.rtc_dow = (uint8_t)tod.tod_dow; + rtc.rtc_hrs = (uint8_t)tod.tod_hour; + rtc.rtc_min = (uint8_t)tod.tod_min; + rtc.rtc_sec = (uint8_t)tod.tod_sec; + + write_rtc_time(&rtc); + + set_time_msg.year = year - 1900; + set_time_msg.month = tod.tod_month - 1; + set_time_msg.day = tod.tod_day; + set_time_msg.hour = tod.tod_hour; + set_time_msg.minute = tod.tod_min; + set_time_msg.second = tod.tod_sec; + + request.msg_type = DP_SET_DATE_TIME; + request.msg_len = sizeof (set_time_msg); + request.msg_buf = (caddr_t)&set_time_msg; + + (void) rmc_comm_request_nowait(&request, 0); +} + +void +write_rtc_time(struct rtc_t *rtc) +{ + uint8_t regb; + + /* + * Freeze + */ + M5819P_ADDR_REG = RTC_B; + regb = M5819P_DATA_REG; + M5819P_DATA_REG = (regb | RTC_SET); + + M5819P_ADDR_REG = RTC_SEC; + M5819P_DATA_REG = rtc->rtc_sec; + M5819P_ADDR_REG = RTC_MIN; + M5819P_DATA_REG = rtc->rtc_min; + M5819P_ADDR_REG = RTC_HRS; + M5819P_DATA_REG = rtc->rtc_hrs; + M5819P_ADDR_REG = RTC_DOW; + M5819P_DATA_REG = rtc->rtc_dow; + M5819P_ADDR_REG = RTC_DOM; + M5819P_DATA_REG = rtc->rtc_dom; + M5819P_ADDR_REG = RTC_MON; + M5819P_DATA_REG = rtc->rtc_mon; + M5819P_ADDR_REG = RTC_YEAR; + M5819P_DATA_REG = rtc->rtc_year; + M5819P_ADDR_REG = RTC_CENTURY; + M5819P_DATA_REG = rtc->rtc_century; + + /* + * Unfreeze + */ + M5819P_ADDR_REG = RTC_B; + M5819P_DATA_REG = regb; +} + +void +write_rtc_alarm(struct rtc_t *rtc) +{ + M5819P_ADDR_REG = RTC_ASEC; + M5819P_DATA_REG = rtc->rtc_asec; + M5819P_ADDR_REG = RTC_AMIN; + M5819P_DATA_REG = rtc->rtc_amin; + M5819P_ADDR_REG = RTC_AHRS; + M5819P_DATA_REG = rtc->rtc_ahrs; + + M5819P_ADDR_REG = RTC_ADOM_REG; + M5819P_DATA_REG = rtc->rtc_adom; +} + +/* + * program the rtc registers for alarm to go off at the specified time + */ +static void +todm5819p_rmc_set_power_alarm(timestruc_t ts) +{ + todinfo_t tod; + uint8_t regb; + struct rtc_t rtc; + + ASSERT(MUTEX_HELD(&tod_lock)); + tod = utc_to_tod(ts.tv_sec); + + /* + * disable alarms and clear AF flag by reading reg C + */ + M5819P_ADDR_REG = RTC_B; + regb = M5819P_DATA_REG; + M5819P_DATA_REG = regb & ~RTC_AIE; + M5819P_ADDR_REG = RTC_C; + (void) M5819P_DATA_REG; + + rtc.rtc_asec = (uint8_t)tod.tod_sec; + rtc.rtc_amin = (uint8_t)tod.tod_min; + rtc.rtc_ahrs = (uint8_t)tod.tod_hour; + rtc.rtc_adom = (uint8_t)tod.tod_day; + + /* + * Write alarm values and enable alarm + */ + write_rtc_alarm(&rtc); + + M5819P_ADDR_REG = RTC_B; + M5819P_DATA_REG = regb | RTC_AIE; +} + +/* + * clear alarm interrupt + */ +static void +todm5819p_rmc_clear_power_alarm(void) +{ + uint8_t regb; + + ASSERT(MUTEX_HELD(&tod_lock)); + + M5819P_ADDR_REG = RTC_B; + regb = M5819P_DATA_REG; + M5819P_DATA_REG = regb & ~RTC_AIE; +} + +/* + * Determine the cpu frequency by watching the TOD chip rollover twice. + * Cpu clock rate is determined by computing the ticks added (in tick register) + * during one second interval on TOD. + */ +uint64_t +todm5819p_rmc_get_cpufrequency(void) +{ + ASSERT(MUTEX_HELD(&tod_lock)); + M5819P_ADDR_REG = RTC_SEC; + return (find_cpufrequency(v_rtc_data_reg)); +} + +/*ARGSUSED*/ +static uint_t +todm5819p_rmc_set_watchdog_timer(uint_t timeoutval) +{ + ASSERT(MUTEX_HELD(&tod_lock)); + return (0); +} + +static uint_t +todm5819p_rmc_clear_watchdog_timer(void) +{ + ASSERT(MUTEX_HELD(&tod_lock)); + return (0); +} diff --git a/usr/src/uts/sun4u/io/todstarcat.c b/usr/src/uts/sun4u/io/todstarcat.c new file mode 100644 index 0000000000..a203ee27e6 --- /dev/null +++ b/usr/src/uts/sun4u/io/todstarcat.c @@ -0,0 +1,263 @@ +/* + * 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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * tod driver module for Starcat + * This module implements a soft tod since + * starcat has no tod part. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/sysmacros.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/modctl.h> +#include <sys/autoconf.h> +#include <sys/debug.h> +#include <sys/clock.h> +#include <sys/cmn_err.h> +#include <sys/promif.h> +#include <sys/cpuvar.h> +#include <sys/sunddi.h> +#include <sys/iosramio.h> +#include <sys/domaind.h> + +#define abs(x) ((x) < 0 ? -(x) : (x)) + +#define TODSC_SET_THRESHOLD 30 + +static timestruc_t todsc_get(void); +static void todsc_set(timestruc_t); +static uint_t todsc_set_watchdog_timer(uint_t); +static uint_t todsc_clear_watchdog_timer(void); +static void todsc_set_power_alarm(timestruc_t); +static void todsc_clear_power_alarm(void); +static uint64_t todsc_get_cpufrequency(void); + +/* + * Module linkage information for the kernel. + */ +static struct modlmisc modlmisc = { + &mod_miscops, "Soft tod module for Sun Fire 15000" +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modlmisc, NULL +}; + +static uint32_t heartbeat = 0; + +int +_init(void) +{ + if (strcmp(tod_module_name, "todstarcat") == 0) { + uint32_t ssc_time32 = 0; + char obp_string[40]; + + /* + * To obtain the initial start of day time, we use an + * OBP callback; this is because the iosram is not yet + * accessible from the OS at this early stage of startup. + */ + + /* + * Set the string to pass to OBP + * for now, we assume we always get a 32bit value + */ + (void) sprintf(obp_string, "h# %p unix-gettod", + (void *) &ssc_time32); + + prom_interpret(obp_string, 0, 0, 0, 0, 0); + + hrestime.tv_sec = (time_t)ssc_time32; + + /* + * A date of zero causes the root filesystem driver + * to try to set the date from the last shutdown. + */ + + /* + * Check for a zero date. + */ + if (ssc_time32 == 0) { + cmn_err(CE_WARN, "Initial date is invalid."); + cmn_err(CE_CONT, "Attempting to set the date and time " + "based on the last shutdown.\n"); + cmn_err(CE_CONT, "Please inspect the date and time and " + "correct if necessary.\n"); + } + + /* + * Check that the date has not overflowed a 32-bit integer. + */ + if (TIMESPEC_OVERFLOW(&hrestime)) { + cmn_err(CE_WARN, "Date overflow detected."); + cmn_err(CE_CONT, "Attempting to set the date and time " + "based on the last shutdown.\n"); + cmn_err(CE_CONT, "Please inspect the date and time and " + "correct if necessary.\n"); + + hrestime.tv_sec = (time_t)0; + } + + tod_ops.tod_get = todsc_get; + tod_ops.tod_set = todsc_set; + tod_ops.tod_set_watchdog_timer = todsc_set_watchdog_timer; + tod_ops.tod_clear_watchdog_timer = todsc_clear_watchdog_timer; + tod_ops.tod_set_power_alarm = todsc_set_power_alarm; + tod_ops.tod_clear_power_alarm = todsc_clear_power_alarm; + tod_ops.tod_get_cpufrequency = todsc_get_cpufrequency; + + /* + * Flag warning if user tried to use hardware watchdog + */ + if (watchdog_enable) { + cmn_err(CE_WARN, "Hardware watchdog unavailable"); + } + } + + return (mod_install(&modlinkage)); +} + +int +_fini(void) +{ + if (strcmp(tod_module_name, "todstarcat") == 0) + return (EBUSY); + else + return (mod_remove(&modlinkage)); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + + +/* + * Starcat tod_get is simplified to return hrestime and to + * update the domain heartbeat. + * Must be called with tod_lock held. + */ +static timestruc_t +todsc_get(void) +{ + ASSERT(MUTEX_HELD(&tod_lock)); + + heartbeat++; + (void) iosram_wr(DOMD_MAGIC, DOMD_HEARTBEAT_OFFSET, + sizeof (uint32_t), (caddr_t)&heartbeat); + return (hrestime); +} + +/* + * Must be called with tod_lock held. + * + * When running NTP, tod_set is called at least once per second in order + * to update the hardware clock - for Starcat, we don't want to sync + * the non-existent hardware clock, and only want to record significant + * time changes on the SC (i.e. when date(1M) is run). So, we have a + * threshold requirement before recording the time change. + */ +/* ARGSUSED */ +static void +todsc_set(timestruc_t ts) +{ + char obp_string[40]; + + ASSERT(MUTEX_HELD(&tod_lock)); + + heartbeat++; + (void) iosram_wr(DOMD_MAGIC, DOMD_HEARTBEAT_OFFSET, + sizeof (uint32_t), (caddr_t)&heartbeat); + + if (abs(hrestime.tv_sec - ts.tv_sec) > TODSC_SET_THRESHOLD) { + /* + * Update the SSC with the new UTC domain time + */ + (void) sprintf(obp_string, "h# %x unix-settod", + (int)ts.tv_sec); + + prom_interpret(obp_string, 0, 0, 0, 0, 0); +#ifdef DEBUG + cmn_err(CE_NOTE, "todsc_set: new domain time 0x%lx\n", + ts.tv_sec); +#endif + } +} + +/* + * No watchdog function. + */ +/* ARGSUSED */ +static uint_t +todsc_set_watchdog_timer(uint_t timeoutval) +{ + ASSERT(MUTEX_HELD(&tod_lock)); + return (0); +} + +/* + * No watchdog function + */ +static uint_t +todsc_clear_watchdog_timer(void) +{ + ASSERT(MUTEX_HELD(&tod_lock)); + return (0); +} + +/* + * Null function. + */ +/* ARGSUSED */ +static void +todsc_set_power_alarm(timestruc_t ts) +{ + ASSERT(MUTEX_HELD(&tod_lock)); +} + +/* + * Null function + */ +static void +todsc_clear_power_alarm() +{ + ASSERT(MUTEX_HELD(&tod_lock)); +} + +/* + * Get clock freq from the cpunode. This function is only called + * when use_stick = 0, otherwise, system_clock_freq gets used instead. + */ +uint64_t +todsc_get_cpufrequency(void) +{ + return (cpunodes[CPU->cpu_id].clock_freq); +} diff --git a/usr/src/uts/sun4u/todm5819p_rmc/Makefile b/usr/src/uts/sun4u/todm5819p_rmc/Makefile new file mode 100644 index 0000000000..7725458687 --- /dev/null +++ b/usr/src/uts/sun4u/todm5819p_rmc/Makefile @@ -0,0 +1,89 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of todm5819p_rmc kernel module. +# +# sun4u implementation architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = todm5819p_rmc +OBJECTS = $(TODM5819P_RMC_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(TODM5819P_RMC_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_PSM_TOD_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/Makefile.sun4u + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# lint pass one enforcement +# +CFLAGS += $(CCVERBOSE) +LDFLAGS += -dy -Ndrv/rmc_comm -Ndrv/pmugpio + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sun4u/Makefile.targ + + diff --git a/usr/src/uts/sun4u/todstarcat/Makefile b/usr/src/uts/sun4u/todstarcat/Makefile new file mode 100644 index 0000000000..f7fd26cc89 --- /dev/null +++ b/usr/src/uts/sun4u/todstarcat/Makefile @@ -0,0 +1,88 @@ +# +# 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 +# +# +# uts/sun4u/todstarcat/Makefile +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of the todstarcat +# kernel module. +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = todstarcat +OBJECTS = $(TODSTARCAT_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(TODSTARCAT_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_PSM_TOD_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sun4u/Makefile.sun4u + +INC_PATH += -I$(UTSBASE)/sun4u/starcat + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# module dependencies +# +LDFLAGS += -dy -Ndrv/sbbc -Ndrv/iosram + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sun4u/Makefile.targ |