/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* The "bscbus" driver provides access to the LOMlite2 virtual registers,
* so that its clients (children) need not be concerned with the details
* of the access mechanism, which in this case is implemented via a
* packet-based protocol over a Xbus (similar to ebus) parallel link to the
* H8 host interface registers.
*
* On the other hand, this driver doesn't generally know what the virtual
* registers signify - only the clients need this information.
*/
#include <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;
uint8_t status;
/*
* First of all, wait for the interface to be available.
*
* NOTE: we blow through all the mutex/cv/state checking and
* preempt any command in progress if the system is panicking!
*/
csp = HANDLE_PRIVATE(hdlp);
mutex_enter(csp->lo_mutex);
while (csp->cmdstate != BSCBUS_CMDSTATE_IDLE && !ddi_in_panic())
cv_wait(csp->lo_cv, csp->lo_mutex);
csp->cmdstate = BSCBUS_CMDSTATE_BUSY;
csp->sequence = (csp->sequence + BSCBUS_SEQ_LSB) & BSCBUS_SEQ;
/*
* We have exclusive ownership, so assemble the command (backwards):
*
* [byte 0] Command: modified by XADDR and/or WMSB bits
* [Optional] Parameter: Value to write (low 7 bits)
* [Optional] Parameter: Register number (high 7 bits)
* [Optional] Parameter: Register number (low 7 bits)
*/
csp->cmdp = &csp->cmdbuf[0];
*csp->cmdp++ = BSCBUS_CMD | csp->sequence | cmd;
switch (cmd) {
case BSCBUS_CMD_WRITE:
*csp->cmdp++ = val & 0x7f;
if (val >= 0x80)
csp->cmdbuf[0] |= BSCBUS_CMD_WMSB;
/*FALLTHRU*/
case BSCBUS_CMD_READ:
if (BSCBUS_VREG_HI(vreg) != 0) {
*csp->cmdp++ = BSCBUS_VREG_HI(vreg);
csp->cmdbuf[0] |= BSCBUS_CMD_XADDR;
}
*csp->cmdp++ = BSCBUS_VREG_LO(vreg);
/*FALLTHRU*/
case BSCBUS_CMD_NOP:
break;
}
/*
* Check and update the H8 h/w fault status before accessing
* the chip registers. If there's a (new or previous) fault,
* we'll run through the protocol but won't really touch the
* hardware and all commands will timeout. If a previously
* discovered fault has now gone away (!), then we can (try to)
* proceed with the new command (probably a probe).
*/
bscbus_check_fault_status(csp);
/*
* Prepare for the command (to be processed by the interrupt
* handler and/or polling loop below), and wait for a response
* or timeout.
*/
start = ddi_get_lbolt();
csp->deadline = start + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000);
csp->error = 0;
csp->index = 0;
csp->result = DUMMY_VALUE;
status = bscbus_get_reg(csp, H8_STR);
if (status & H8_STR_BUSY) {
bscbus_cmd_log(csp, BSC_CMD_BUSY, status, 0xfd);
/*
* Must ensure that the busy state has cleared before
* sending the command
*/
csp->cmdstate = BSCBUS_CMDSTATE_CLEARING;
bscbus_trace(csp, 'P', "bscbus_cmd",
"h8 reporting status (%x) busy - clearing", status);
} else {
/* It is clear to send the command immediately */
csp->cmdstate = BSCBUS_CMDSTATE_SENDING;
bscbus_trace(csp, 'P', "bscbus_cmd",
"sending first byte of command, status %x", status);
bscbus_poll(csp);
}
csp->poll_hz = drv_usectohz(
(csp->interrupt_failed ?
BSCBUS_CMD_POLLNOINTS : BSCBUS_CMD_POLL) / 1000);
while ((csp->cmdstate != BSCBUS_CMDSTATE_READY) &&
(csp->cmdstate != BSCBUS_CMDSTATE_ERROR)) {
ASSERT(csp->cmdstate != BSCBUS_CMDSTATE_IDLE);
if ((cv_reltimedwait(csp->lo_cv, csp->lo_mutex,
csp->poll_hz, TR_CLOCK_TICK) == -1) &&
csp->cmdstate != BSCBUS_CMDSTATE_READY &&
csp->cmdstate != BSCBUS_CMDSTATE_ERROR) {
if (!csp->interrupt_failed) {
bscbus_trace(csp, 'I', "bscbus_cmd:",
"interrupt_failed channel %d", csp->chno);
csp->interrupt_failed = B_TRUE;
csp->poll_hz = drv_usectohz(
BSCBUS_CMD_POLLNOINTS / 1000);
}
bscbus_poll(csp);
}
}
/*
* The return value may not be meaningful but retrieve it anyway
*/
val = csp->result;
if (bscbus_faulty(csp)) {
val = DUMMY_VALUE;
HANDLE_FAULT(hdlp) = LOMBUS_ERR_SIOHW;
} else if (csp->cmdstate != BSCBUS_CMDSTATE_READY) {
/*
* Some problem here ... transfer the error code from
* the per-instance state to the per-handle fault flag.
* The error code shouldn't be zero!
*/
if (csp->error != 0)
HANDLE_FAULT(hdlp) = csp->error;
else
HANDLE_FAULT(hdlp) = LOMBUS_ERR_BADERRCODE;
}
/*
* All done now!
*/
csp->index = 0;
csp->cmdstate = BSCBUS_CMDSTATE_IDLE;
cv_broadcast(csp->lo_cv);
mutex_exit(csp->lo_mutex);
return (val);
}
/*
* Space 0 - LOM virtual register access
* Only 8-bit accesses are supported.
*/
static uint8_t
bscbus_vreg_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return (DUMMY_VALUE);
}
/*
* Derive the virtual register number and run the command
*/
return (bscbus_cmd(hdlp, ADDR_TO_VREG(addr), 0, BSCBUS_CMD_READ));
}
static void
bscbus_vreg_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return;
}
/*
* Derive the virtual register number and run the command
*/
(void) bscbus_cmd(hdlp, ADDR_TO_VREG(addr), val, BSCBUS_CMD_WRITE);
}
static void
bscbus_vreg_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = bscbus_vreg_get8(hdlp, dev_addr);
}
static void
bscbus_vreg_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
bscbus_vreg_put8(hdlp, dev_addr, *host_addr++);
}
/*
* Space 1 - LOM watchdog pat register access
* Only 8-bit accesses are supported.
*
* Reads have no effect and return 0.
*
* Multi-byte reads (using ddi_rep_get8(9F)) are a fairly inefficient
* way of zeroing the destination area ;-) and still won't pat the dog.
*
* Multi-byte writes (using ddi_rep_put8(9F)) will almost certainly
* only count as a single pat, no matter how many bytes the caller
* says to write, as the inter-pat time is VERY long compared with
* the time it will take to read the memory source area.
*/
static uint8_t
bscbus_pat_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return (DUMMY_VALUE);
}
return (0);
}
static void
bscbus_pat_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
struct bscbus_channel_state *csp;
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return;
}
csp = HANDLE_PRIVATE(hdlp);
mutex_enter(csp->dog_mutex);
bscbus_pat_dog(csp, val);
mutex_exit(csp->dog_mutex);
}
static void
bscbus_pat_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = bscbus_pat_get8(hdlp, dev_addr);
}
static void
bscbus_pat_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
bscbus_pat_put8(hdlp, dev_addr, *host_addr++);
}
/*
* Space 2 - LOM async event flag register access
* Only 16-bit accesses are supported.
*/
static uint16_t
bscbus_event_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
struct bscbus_channel_state *csp;
ptrdiff_t offset;
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping orignally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return (DUMMY_VALUE);
}
/*
* Return the value of the asynchronous-event-pending flag
* as passed back by the LOM at the end of the last command.
*/
csp = HANDLE_PRIVATE(hdlp);
return (csp->async);
}
static void
bscbus_event_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
ptrdiff_t offset;
_NOTE(ARGUNUSED(val))
/*
* Check the offset that the caller has added to the base address
* against the length of the mapping originally requested.
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
/*
* Invalid access - flag a fault and return
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
return;
}
/*
* The user can't overwrite the asynchronous-event-pending flag!
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_RO;
}
static void
bscbus_event_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = bscbus_event_get16(hdlp, dev_addr);
}
static void
bscbus_event_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
bscbus_event_put16(hdlp, dev_addr, *host_addr++);
}
/*
* All spaces - access handle fault information
* Only 32-bit accesses are supported.
*/
static uint32_t
bscbus_meta_get32(HANDLE_TYPE *hdlp, uint32_t *addr)
{
struct bscbus_channel_state *csp;
ptrdiff_t offset;
/*
* Derive the offset that the caller has added to the base
* address originally returned, and use it to determine
* which meta-register is to be accessed ...
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
switch (offset) {
case LOMBUS_FAULT_REG:
/*
* This meta-register provides a code for the most
* recent virtual register access fault, if any.
*/
return (HANDLE_FAULT(hdlp));
case LOMBUS_PROBE_REG:
/*
* Reading this meta-register clears any existing fault
* (at the virtual, not the hardware access layer), then
* runs a NOP command and returns the fault code from that.
*/
HANDLE_FAULT(hdlp) = 0;
(void) bscbus_cmd(hdlp, 0, 0, BSCBUS_CMD_NOP);
return (HANDLE_FAULT(hdlp));
case LOMBUS_ASYNC_REG:
/*
* Obsolescent - but still supported for backwards
* compatibility. This is an alias for the newer
* LOMBUS_EVENT_REG, but doesn't require a separate
* "reg" entry and ddi_regs_map_setup() call.
*
* It returns the value of the asynchronous-event-pending
* flag as passed back by the BSC at the end of the last
* completed command.
*/
csp = HANDLE_PRIVATE(hdlp);
return (csp->async);
default:
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
}
static void
bscbus_meta_put32(HANDLE_TYPE *hdlp, uint32_t *addr, uint32_t val)
{
ptrdiff_t offset;
/*
* Derive the offset that the caller has added to the base
* address originally returned, and use it to determine
* which meta-register is to be accessed ...
*/
offset = ADDR_TO_OFFSET(addr, hdlp);
switch (offset) {
case LOMBUS_FAULT_REG:
/*
* This meta-register contains a code for the most
* recent virtual register access fault, if any.
* It can be cleared simply by writing 0 to it.
*/
HANDLE_FAULT(hdlp) = val;
return;
case LOMBUS_PROBE_REG:
/*
* Writing this meta-register clears any existing fault
* (at the virtual, not the hardware acess layer), then
* runs a NOP command. The caller can check the fault
* code later if required.
*/
HANDLE_FAULT(hdlp) = 0;
(void) bscbus_cmd(hdlp, 0, 0, BSCBUS_CMD_NOP);
return;
default:
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return;
}
}
static void
bscbus_meta_rep_get32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
*host_addr++ = bscbus_meta_get32(hdlp, dev_addr);
}
static void
bscbus_meta_rep_put32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
size_t inc;
inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
for (; repcount--; dev_addr += inc)
bscbus_meta_put32(hdlp, dev_addr, *host_addr++);
}
/*
* Finally, some dummy functions for all unsupported access
* space/size/mode combinations ...
*/
static uint8_t
bscbus_no_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
_NOTE(ARGUNUSED(addr))
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
static void
bscbus_no_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
_NOTE(ARGUNUSED(addr, val))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
bscbus_no_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
bscbus_no_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static uint16_t
bscbus_no_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
_NOTE(ARGUNUSED(addr))
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
static void
bscbus_no_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
_NOTE(ARGUNUSED(addr, val))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
bscbus_no_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
bscbus_no_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static uint64_t
bscbus_no_get64(HANDLE_TYPE *hdlp, uint64_t *addr)
{
_NOTE(ARGUNUSED(addr))
/*
* Invalid access - flag a fault and return a dummy value
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
return (DUMMY_VALUE);
}
static void
bscbus_no_put64(HANDLE_TYPE *hdlp, uint64_t *addr, uint64_t val)
{
_NOTE(ARGUNUSED(addr, val))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
bscbus_no_rep_get64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static void
bscbus_no_rep_put64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
_NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))
/*
* Invalid access - flag a fault
*/
HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}
static int
bscbus_acc_fault_check(HANDLE_TYPE *hdlp)
{
return (HANDLE_FAULT(hdlp) != 0);
}
/*
* Hardware setup - ensure that there are no pending transactions and
* hence no pending interrupts. We do this be ensuring that the BSC is
* not reporting a busy condition and that it does not have any data
* pending in its output buffer.
* This is important because if we have pending interrupts at attach
* time Solaris will hang due to bugs in ddi_get_iblock_cookie.
*/
static void
bscbus_hw_reset(struct bscbus_channel_state *csp)
{
int64_t timeout;
uint8_t status;
if (csp->map_count == 0) {
/* No-one using this instance - no need to reset hardware */
return;
}
bscbus_trace(csp, 'R', "bscbus_hw_reset",
"resetting channel %d", csp->chno);
status = bscbus_get_reg(csp, H8_STR);
if (status & H8_STR_BUSY) {
/*
* Give the h8 time to complete a reply.
* In practice we should never worry about this
* because whenever we get here it will have been
* long enough for the h8 to complete a reply
*/
bscbus_cmd_log(csp, BSC_CMD_BUSY, status, 0);
bscbus_trace(csp, 'R', "bscbus_hw_reset",
"h8 reporting status (%x) busy - waiting", status);
if (ddi_in_panic()) {
drv_usecwait(BSCBUS_HWRESET_POLL/1000);
} else {
delay(drv_usectohz(BSCBUS_HWRESET_POLL/1000));
}
}
/* Reply should be completed by now. Try to clear busy status */
status = bscbus_get_reg(csp, H8_STR);
if (status & (H8_STR_BUSY | H8_STR_OBF)) {
bscbus_trace(csp, 'R', "bscbus_hw_reset",
"clearing busy status for channel %d", csp->chno);
for (timeout = BSCBUS_HWRESET_TIMEOUT;
(timeout > 0);
timeout -= BSCBUS_HWRESET_POLL) {
if (status & H8_STR_OBF) {
(void) bscbus_get_reg(csp, H8_ODR);
if (!(status & H8_STR_BUSY)) {
/* We are done */
break;
}
}
if (ddi_in_panic()) {
drv_usecwait(BSCBUS_HWRESET_POLL/1000);
} else {
delay(drv_usectohz(BSCBUS_HWRESET_POLL/1000));
}
status = bscbus_get_reg(csp, H8_STR);
}
if (timeout <= 0) {
cmn_err(CE_WARN, "bscbus_hw_reset: timed out "
"clearing busy status");
}
}
/*
* We read ODR just in case there is a pending interrupt with
* no data. This is potentially dangerous because we could get
* out of sync due to race conditions BUT at this point the
* channel should be idle so it is safe.
*/
(void) bscbus_get_reg(csp, H8_ODR);
}
/*
* Higher-level setup & teardown
*/
static void
bscbus_offline(struct bscbus_state *ssp)
{
if (ssp->h8_handle != NULL)
ddi_regs_map_free(&ssp->h8_handle);
ssp->h8_handle = NULL;
ssp->h8_regs = NULL;
}
static int
bscbus_online(struct bscbus_state *ssp)
{
ddi_acc_handle_t h;
caddr_t p;
int nregs;
int err;
ssp->h8_handle = NULL;
ssp->h8_regs = (void *)NULL;
ssp->per_channel_regs = B_FALSE;
if (ddi_dev_nregs(ssp->dip, &nregs) != DDI_SUCCESS)
nregs = 0;
switch (nregs) {
case 1:
/*
* regset 0 represents the H8 interface registers
*/
err = ddi_regs_map_setup(ssp->dip, 0, &p, 0, 0,
bscbus_dev_acc_attr, &h);
if (err != DDI_SUCCESS)
return (EIO);
ssp->h8_handle = h;
ssp->h8_regs = (void *)p;
break;
case 0:
/*
* If no registers are defined, succeed vacuously;
* commands will be accepted, but we fake the accesses.
*/
break;
default:
/*
* Remember that we are using the new register scheme.
* reg set 0 is chan 0
* reg set 1 is chan 1 ...
* Interrupts are specified in that order but later
* channels may not have interrupts.
* We map the regs later on a per channel basis.
*/
ssp->per_channel_regs = B_TRUE;
break;
}
return (0);
}
static int
bscbus_claim_channel(struct bscbus_channel_state *csp, boolean_t map_dog)
{
int err;
mutex_enter(csp->ssp->ch_mutex);
csp->map_count++;
bscbus_trace(csp, 'C', "bscbus_claim_channel",
"claim channel for channel %d, count %d",
csp->chno, csp->map_count);
if (csp->map_count == 1) {
/* No-one is using this channel - initialise it */
bscbus_trace(csp, 'C', "bscbus_claim_channel",
"initialise channel %d, count %d",
csp->chno, csp->map_count);
mutex_init(csp->dog_mutex, NULL, MUTEX_DRIVER,
(void *)(uintptr_t)__ipltospl(SPL7 - 1));
csp->map_dog = map_dog;
csp->interrupt_failed = B_FALSE;
csp->cmdstate = BSCBUS_CMDSTATE_IDLE;
csp->pat_retry_count = 0;
csp->pat_fail_count = 0;
/* Map appropriate register set for this channel */
if (csp->ssp->per_channel_regs == B_TRUE) {
ddi_acc_handle_t h;
caddr_t p;
err = ddi_regs_map_setup(csp->ssp->dip, csp->chno,
&p, 0, 0, bscbus_dev_acc_attr, &h);
if (err != DDI_SUCCESS) {
goto failed1;
}
csp->ch_handle = h;
csp->ch_regs = (void *)p;
bscbus_trace(csp, 'C', "bscbus_claim_channel",
"mapped chno=%d ch_handle=%d ch_regs=%p",
csp->chno, h, p);
} else {
/*
* if using the old reg property scheme use the
* common mapping.
*/
csp->ch_handle = csp->ssp->h8_handle;
csp->ch_regs =
csp->ssp->h8_regs +
BSCBUS_CHANNEL_TO_OFFSET(csp->chno);
}
/* Ensure no interrupts pending prior to getting iblk cookie */
bscbus_hw_reset(csp);
if (csp->map_dog == 1) {
/*
* we don't want lo_mutex to be initialised
* with an iblock cookie if we are the wdog,
* because we don't use interrupts.
*/
mutex_init(csp->lo_mutex, NULL,
MUTEX_DRIVER, NULL);
cv_init(csp->lo_cv, NULL,
CV_DRIVER, NULL);
csp->unclaimed_count = 0;
} else {
int ninterrupts;
/*
* check that there is an interrupt for this
* this channel. If we fail to setup interrupts we
* must unmap the registers and fail.
*/
err = ddi_dev_nintrs(csp->ssp->dip, &ninterrupts);
if (err != DDI_SUCCESS) {
ninterrupts = 0;
}
if (ninterrupts <= csp->chno) {
cmn_err(CE_WARN,
"no interrupt available for "
"bscbus channel %d", csp->chno);
goto failed2;
}
if (ddi_intr_hilevel(csp->ssp->dip, csp->chno) != 0) {
cmn_err(CE_WARN,
"bscbus interrupts are high "
"level - channel not usable.");
goto failed2;
} else {
err = ddi_get_iblock_cookie(csp->ssp->dip,
csp->chno, &csp->lo_iblk);
if (err != DDI_SUCCESS) {
goto failed2;
}
mutex_init(csp->lo_mutex, NULL,
MUTEX_DRIVER, csp->lo_iblk);
cv_init(csp->lo_cv, NULL,
CV_DRIVER, NULL);
csp->unclaimed_count = 0;
err = ddi_add_intr(csp->ssp->dip, csp->chno,
&csp->lo_iblk, NULL,
bscbus_hwintr, (caddr_t)csp);
if (err != DDI_SUCCESS) {
cv_destroy(csp->lo_cv);
mutex_destroy(csp->lo_mutex);
goto failed2;
}
}
}
/*
* The channel is now live and may
* receive interrupts
*/
} else if (csp->map_dog != map_dog) {
bscbus_trace(csp, 'C', "bscbus_claim_channel",
"request conflicts with previous mapping. old %x, new %x.",
csp->map_dog, map_dog);
goto failed1;
}
mutex_exit(csp->ssp->ch_mutex);
return (1);
failed2:
/* unmap regs for failed channel */
if (csp->ssp->per_channel_regs == B_TRUE) {
ddi_regs_map_free(&csp->ch_handle);
}
csp->ch_handle = NULL;
csp->ch_regs = (void *)NULL;
failed1:
csp->map_count--;
mutex_exit(csp->ssp->ch_mutex);
return (0);
}
static void
bscbus_release_channel(struct bscbus_channel_state *csp)
{
mutex_enter(csp->ssp->ch_mutex);
if (csp->map_count == 1) {
/* No-one is now using this channel - shutdown channel */
bscbus_trace(csp, 'C', "bscbus_release_channel",
"shutdown channel %d, count %d",
csp->chno, csp->map_count);
if (csp->map_dog == 0) {
ASSERT(!ddi_intr_hilevel(csp->ssp->dip, csp->chno));
ddi_remove_intr(csp->ssp->dip, csp->chno, csp->lo_iblk);
}
cv_destroy(csp->lo_cv);
mutex_destroy(csp->lo_mutex);
mutex_destroy(csp->dog_mutex);
bscbus_hw_reset(csp);
/* unmap registers if using the new register scheme */
if (csp->ssp->per_channel_regs == B_TRUE) {
ddi_regs_map_free(&csp->ch_handle);
}
csp->ch_handle = NULL;
csp->ch_regs = (void *)NULL;
}
csp->map_count--;
bscbus_trace(csp, 'C', "bscbus_release_channel",
"release channel %d, count %d",
csp->chno, csp->map_count);
mutex_exit(csp->ssp->ch_mutex);
}
/*
* Nexus routines
*/
#if defined(NDI_ACC_HDL_V2)
static const ndi_acc_fns_t bscbus_vreg_acc_fns = {
NDI_ACC_FNS_CURRENT,
NDI_ACC_FNS_V1,
bscbus_vreg_get8,
bscbus_vreg_put8,
bscbus_vreg_rep_get8,
bscbus_vreg_rep_put8,
bscbus_no_get16,
bscbus_no_put16,
bscbus_no_rep_get16,
bscbus_no_rep_put16,
bscbus_meta_get32,
bscbus_meta_put32,
bscbus_meta_rep_get32,
bscbus_meta_rep_put32,
bscbus_no_get64,
bscbus_no_put64,
bscbus_no_rep_get64,
bscbus_no_rep_put64,
bscbus_acc_fault_check
};
static const ndi_acc_fns_t bscbus_pat_acc_fns = {
NDI_ACC_FNS_CURRENT,
NDI_ACC_FNS_V1,
bscbus_pat_get8,
bscbus_pat_put8,
bscbus_pat_rep_get8,
bscbus_pat_rep_put8,
bscbus_no_get16,
bscbus_no_put16,
bscbus_no_rep_get16,
bscbus_no_rep_put16,
bscbus_meta_get32,
bscbus_meta_put32,
bscbus_meta_rep_get32,
bscbus_meta_rep_put32,
bscbus_no_get64,
bscbus_no_put64,
bscbus_no_rep_get64,
bscbus_no_rep_put64,
bscbus_acc_fault_check
};
static const ndi_acc_fns_t bscbus_event_acc_fns = {
NDI_ACC_FNS_CURRENT,
NDI_ACC_FNS_V1,
bscbus_no_get8,
bscbus_no_put8,
bscbus_no_rep_get8,
bscbus_no_rep_put8,
bscbus_event_get16,
bscbus_event_put16,
bscbus_event_rep_get16,
bscbus_event_rep_put16,
bscbus_meta_get32,
bscbus_meta_put32,
bscbus_meta_rep_get32,
bscbus_meta_rep_put32,
bscbus_no_get64,
bscbus_no_put64,
bscbus_no_rep_get64,
bscbus_no_rep_put64,
bscbus_acc_fault_check
};
static int
bscbus_map_handle(struct bscbus_channel_state *csp, ddi_map_op_t op,
int space, caddr_t vaddr, off_t len,
ndi_acc_handle_t *hdlp, caddr_t *addrp)
{
switch (op) {
default:
return (DDI_ME_UNIMPLEMENTED);
case DDI_MO_MAP_LOCKED:
if (bscbus_claim_channel(csp,
(space == LOMBUS_PAT_SPACE)) == 0) {
return (DDI_ME_GENERIC);
}
switch (space) {
default:
return (DDI_ME_REGSPEC_RANGE);
case LOMBUS_VREG_SPACE:
ndi_set_acc_fns(hdlp, &bscbus_vreg_acc_fns);
break;
case LOMBUS_PAT_SPACE:
ndi_set_acc_fns(hdlp, &bscbus_pat_acc_fns);
break;
case LOMBUS_EVENT_SPACE:
ndi_set_acc_fns(hdlp, &bscbus_event_acc_fns);
break;
}
hdlp->ah_addr = *addrp = vaddr;
hdlp->ah_len = len;
hdlp->ah_bus_private = csp;
return (DDI_SUCCESS);
case DDI_MO_UNMAP:
*addrp = NULL;
hdlp->ah_bus_private = NULL;
bscbus_release_channel(csp);
return (DDI_SUCCESS);
}
}
#else
static int
bscbus_map_handle(struct bscbus_channel_state *csp, ddi_map_op_t op,
int space, caddr_t vaddr, off_t len,
ddi_acc_hdl_t *hdlp, caddr_t *addrp)
{
ddi_acc_impl_t *aip = hdlp->ah_platform_private;
switch (op) {
default:
return (DDI_ME_UNIMPLEMENTED);
case DDI_MO_MAP_LOCKED:
if (bscbus_claim_channel(csp,
(space == LOMBUS_PAT_SPACE)) == 0) {
return (DDI_ME_GENERIC);
}
switch (space) {
default:
return (DDI_ME_REGSPEC_RANGE);
case LOMBUS_VREG_SPACE:
aip->ahi_get8 = bscbus_vreg_get8;
aip->ahi_put8 = bscbus_vreg_put8;
aip->ahi_rep_get8 = bscbus_vreg_rep_get8;
aip->ahi_rep_put8 = bscbus_vreg_rep_put8;
aip->ahi_get16 = bscbus_no_get16;
aip->ahi_put16 = bscbus_no_put16;
aip->ahi_rep_get16 = bscbus_no_rep_get16;
aip->ahi_rep_put16 = bscbus_no_rep_put16;
aip->ahi_get32 = bscbus_meta_get32;
aip->ahi_put32 = bscbus_meta_put32;
aip->ahi_rep_get32 = bscbus_meta_rep_get32;
aip->ahi_rep_put32 = bscbus_meta_rep_put32;
aip->ahi_get64 = bscbus_no_get64;
aip->ahi_put64 = bscbus_no_put64;
aip->ahi_rep_get64 = bscbus_no_rep_get64;
aip->ahi_rep_put64 = bscbus_no_rep_put64;
aip->ahi_fault_check = bscbus_acc_fault_check;
break;
case LOMBUS_PAT_SPACE:
aip->ahi_get8 = bscbus_pat_get8;
aip->ahi_put8 = bscbus_pat_put8;
aip->ahi_rep_get8 = bscbus_pat_rep_get8;
aip->ahi_rep_put8 = bscbus_pat_rep_put8;
aip->ahi_get16 = bscbus_no_get16;
aip->ahi_put16 = bscbus_no_put16;
aip->ahi_rep_get16 = bscbus_no_rep_get16;
aip->ahi_rep_put16 = bscbus_no_rep_put16;
aip->ahi_get32 = bscbus_meta_get32;
aip->ahi_put32 = bscbus_meta_put32;
aip->ahi_rep_get32 = bscbus_meta_rep_get32;
aip->ahi_rep_put32 = bscbus_meta_rep_put32;
aip->ahi_get64 = bscbus_no_get64;
aip->ahi_put64 = bscbus_no_put64;
aip->ahi_rep_get64 = bscbus_no_rep_get64;
aip->ahi_rep_put64 = bscbus_no_rep_put64;
aip->ahi_fault_check = bscbus_acc_fault_check;
break;
case LOMBUS_EVENT_SPACE:
aip->ahi_get8 = bscbus_no_get8;
aip->ahi_put8 = bscbus_no_put8;
aip->ahi_rep_get8 = bscbus_no_rep_get8;
aip->ahi_rep_put8 = bscbus_no_rep_put8;
aip->ahi_get16 = bscbus_event_get16;
aip->ahi_put16 = bscbus_event_put16;
aip->ahi_rep_get16 = bscbus_event_rep_get16;
aip->ahi_rep_put16 = bscbus_event_rep_put16;
aip->ahi_get32 = bscbus_meta_get32;
aip->ahi_put32 = bscbus_meta_put32;
aip->ahi_rep_get32 = bscbus_meta_rep_get32;
aip->ahi_rep_put32 = bscbus_meta_rep_put32;
aip->ahi_get64 = bscbus_no_get64;
aip->ahi_put64 = bscbus_no_put64;
aip->ahi_rep_get64 = bscbus_no_rep_get64;
aip->ahi_rep_put64 = bscbus_no_rep_put64;
aip->ahi_fault_check = bscbus_acc_fault_check;
break;
}
hdlp->ah_addr = *addrp = vaddr;
hdlp->ah_len = len;
hdlp->ah_bus_private = csp;
return (DDI_SUCCESS);
case DDI_MO_UNMAP:
*addrp = NULL;
hdlp->ah_bus_private = NULL;
bscbus_release_channel(csp);
return (DDI_SUCCESS);
}
}
#endif /* NDI_ACC_HDL_V2 */
static int
bscbus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp)
{
struct bscbus_child_info *lcip;
struct bscbus_state *ssp;
lombus_regspec_t *rsp;
if ((ssp = bscbus_getstate(dip, -1, "bscbus_map")) == NULL)
return (DDI_FAILURE); /* this "can't happen" */
/*
* Validate mapping request ...
*/
if (mp->map_flags != DDI_MF_KERNEL_MAPPING)
return (DDI_ME_UNSUPPORTED);
if (mp->map_handlep == NULL)
return (DDI_ME_UNSUPPORTED);
if (mp->map_type != DDI_MT_RNUMBER)
return (DDI_ME_UNIMPLEMENTED);
if ((lcip = ddi_get_parent_data(rdip)) == NULL)
return (DDI_ME_INVAL);
if ((rsp = lcip->rsp) == NULL)
return (DDI_ME_INVAL);
if (mp->map_obj.rnumber >= lcip->nregs)
return (DDI_ME_RNUMBER_RANGE);
rsp += mp->map_obj.rnumber;
if (off < 0 || off >= rsp->lombus_size)
return (DDI_ME_INVAL);
if (len == 0)
len = rsp->lombus_size-off;
if (len < 0)
return (DDI_ME_INVAL);
if (off+len < 0 || off+len > rsp->lombus_size)
return (DDI_ME_INVAL);
return (bscbus_map_handle(
&ssp->channel[LOMBUS_SPACE_TO_CHANNEL(rsp->lombus_space)],
mp->map_op, LOMBUS_SPACE_TO_REGSET(rsp->lombus_space),
VREG_TO_ADDR(rsp->lombus_base+off), len, mp->map_handlep, addrp));
}
static int
bscbus_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
void *arg, void *result)
{
struct bscbus_child_info *lcip;
lombus_regspec_t *rsp;
dev_info_t *cdip;
char addr[32];
uint_t nregs;
uint_t rnum;
int *regs;
int limit;
int err;
int i;
if (bscbus_getstate(dip, -1, "bscbus_ctlops") == NULL)
return (DDI_FAILURE); /* this "can't happen" */
switch (op) {
default:
break;
case DDI_CTLOPS_INITCHILD:
/*
* First, look up and validate the "reg" property.
*
* It must be a non-empty integer array containing a set
* of triples. Once we've verified that, we can treat it
* as an array of type lombus_regspec_t[], which defines
* the meaning of the elements of each triple:
* + the first element of each triple must be a valid space
* + the second and third elements (base, size) of each
* triple must define a valid subrange of that space
* If it passes all the tests, we save it away for future
* reference in the child's parent-private-data field.
*/
cdip = arg;
err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
DDI_PROP_DONTPASS, "reg", &regs, &nregs);
if (err != DDI_PROP_SUCCESS)
return (DDI_FAILURE);
err = (nregs <= 0 || (nregs % LOMBUS_REGSPEC_SIZE) != 0);
nregs /= LOMBUS_REGSPEC_SIZE;
rsp = (lombus_regspec_t *)regs;
for (i = 0; i < nregs && !err; ++i) {
switch (LOMBUS_SPACE_TO_REGSET(rsp[i].lombus_space)) {
default:
limit = 0;
err = 1;
cmn_err(CE_WARN,
"child(%p): unknown reg space %d",
(void *)cdip, rsp[i].lombus_space);
break;
case LOMBUS_VREG_SPACE:
limit = LOMBUS_MAX_REG+1;
break;
case LOMBUS_PAT_SPACE:
limit = LOMBUS_PAT_REG+1;
break;
case LOMBUS_EVENT_SPACE:
limit = LOMBUS_EVENT_REG+1;
break;
}
err |= (rsp[i].lombus_base < 0);
err |= (rsp[i].lombus_base >= limit);
if (rsp[i].lombus_size == 0)
rsp[i].lombus_size = limit-rsp[i].lombus_base;
err |= (rsp[i].lombus_size < 0);
err |= (rsp[i].lombus_base+rsp[i].lombus_size < 0);
err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit);
err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit);
}
if (err) {
ddi_prop_free(regs);
return (DDI_FAILURE);
}
lcip = kmem_zalloc(sizeof (*lcip), KM_SLEEP);
lcip->nregs = nregs;
lcip->rsp = rsp;
ddi_set_parent_data(cdip, lcip);
(void) snprintf(addr, sizeof (addr),
"%x,%x", rsp[0].lombus_space, rsp[0].lombus_base);
ddi_set_name_addr(cdip, addr);
return (DDI_SUCCESS);
case DDI_CTLOPS_UNINITCHILD:
cdip = arg;
ddi_set_name_addr(cdip, NULL);
lcip = ddi_get_parent_data(cdip);
ddi_set_parent_data(cdip, NULL);
ddi_prop_free(lcip->rsp);
kmem_free(lcip, sizeof (*lcip));
return (DDI_SUCCESS);
case DDI_CTLOPS_REPORTDEV:
if (rdip == NULL)
return (DDI_FAILURE);
cmn_err(CE_CONT, "?BSC device: %s@%s, %s#%d\n",
ddi_node_name(rdip), ddi_get_name_addr(rdip),
ddi_driver_name(dip), ddi_get_instance(dip));
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
if ((lcip = ddi_get_parent_data(rdip)) == NULL)
return (DDI_FAILURE);
if ((rnum = *(uint_t *)arg) >= lcip->nregs)
return (DDI_FAILURE);
*(off_t *)result = lcip->rsp[rnum].lombus_size;
return (DDI_SUCCESS);
case DDI_CTLOPS_NREGS:
if ((lcip = ddi_get_parent_data(rdip)) == NULL)
return (DDI_FAILURE);
*(int *)result = lcip->nregs;
return (DDI_SUCCESS);
}
return (ddi_ctlops(dip, rdip, op, arg, result));
}
/*
* This nexus does not support passing interrupts to leaf drivers, so
* all the intrspec-related operations just fail as cleanly as possible.
*/
/*ARGSUSED*/
static int
bscbus_intr_op(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
#if defined(__sparc)
return (i_ddi_intr_ops(dip, rdip, op, hdlp, result));
#else
_NOTE(ARGUNUSED(dip, rdip, op, hdlp, result))
return (DDI_FAILURE);
#endif
}
/*
* Clean up on detach or failure of attach
*/
static int
bscbus_unattach(struct bscbus_state *ssp, int instance)
{
int chno;
if (ssp != NULL) {
for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) {
ASSERT(ssp->channel[chno].map_count == 0);
}
bscbus_offline(ssp);
ddi_set_driver_private(ssp->dip, NULL);
mutex_destroy(ssp->ch_mutex);
}
#ifdef BSCBUS_LOGSTATUS
if (ssp->cmd_log_size != 0) {
kmem_free(ssp->cmd_log,
ssp->cmd_log_size * sizeof (bsc_cmd_log_t));
}
#endif /* BSCBUS_LOGSTATUS */
ddi_soft_state_free(bscbus_statep, instance);
return (DDI_FAILURE);
}
/*
* Autoconfiguration routines
*/
static int
bscbus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct bscbus_state *ssp = NULL;
int chno;
int instance;
int err;
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_ATTACH:
break;
}
/*
* Allocate the soft-state structure
*/
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(bscbus_statep, instance) != DDI_SUCCESS)
return (DDI_FAILURE);
if ((ssp = bscbus_getstate(dip, instance, "bscbus_attach")) == NULL)
return (bscbus_unattach(ssp, instance));
ddi_set_driver_private(dip, ssp);
/*
* Initialise devinfo-related fields
*/
ssp->dip = dip;
ssp->majornum = ddi_driver_major(dip);
ssp->instance = instance;
/*
* Set various options from .conf properties
*/
ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "debug", 0);
mutex_init(ssp->ch_mutex, NULL, MUTEX_DRIVER, NULL);
#ifdef BSCBUS_LOGSTATUS
ssp->cmd_log_size = bscbus_cmd_log_size;
if (ssp->cmd_log_size != 0) {
ssp->cmd_log_idx = 0;
ssp->cmd_log = kmem_zalloc(ssp->cmd_log_size *
sizeof (bsc_cmd_log_t), KM_SLEEP);
}
#endif /* BSCBUS_LOGSTATUS */
/*
* Online the hardware ...
*/
err = bscbus_online(ssp);
if (err != 0)
return (bscbus_unattach(ssp, instance));
for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) {
struct bscbus_channel_state *csp = &ssp->channel[chno];
/*
* Initialise state
* The hardware/interrupts are setup at map time to
* avoid claiming hardware that OBP is using
*/
csp->ssp = ssp;
csp->chno = chno;
csp->map_count = 0;
csp->map_dog = B_FALSE;
}
/*
* All done, report success
*/
ddi_report_dev(dip);
return (DDI_SUCCESS);
}
static int
bscbus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct bscbus_state *ssp;
int instance;
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_DETACH:
break;
}
instance = ddi_get_instance(dip);
if ((ssp = bscbus_getstate(dip, instance, "bscbus_detach")) == NULL)
return (DDI_FAILURE); /* this "can't happen" */
(void) bscbus_unattach(ssp, instance);
return (DDI_SUCCESS);
}
static int
bscbus_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
struct bscbus_state *ssp;
int chno;
_NOTE(ARGUNUSED(cmd))
if ((ssp = bscbus_getstate(dip, -1, "bscbus_reset")) == NULL)
return (DDI_FAILURE);
for (chno = 0; chno < BSCBUS_MAX_CHANNELS; chno++) {
bscbus_hw_reset(&ssp->channel[chno]);
}
return (DDI_SUCCESS);
}
/*
* System interface structures
*/
static struct cb_ops bscbus_cb_ops =
{
nodev, /* b/c open */
nodev, /* b/c close */
nodev, /* b strategy */
nodev, /* b print */
nodev, /* b dump */
nodev, /* c read */
nodev, /* c write */
nodev, /* c ioctl */
nodev, /* c devmap */
nodev, /* c mmap */
nodev, /* c segmap */
nochpoll, /* c poll */
ddi_prop_op, /* b/c prop_op */
NULL, /* c streamtab */
D_MP | D_NEW /* b/c flags */
};
static struct bus_ops bscbus_bus_ops =
{
BUSO_REV, /* revision */
bscbus_map, /* bus_map */
0, /* get_intrspec */
0, /* add_intrspec */
0, /* remove_intrspec */
i_ddi_map_fault, /* map_fault */
ddi_no_dma_map, /* dma_map */
ddi_no_dma_allochdl, /* allocate DMA handle */
ddi_no_dma_freehdl, /* free DMA handle */
ddi_no_dma_bindhdl, /* bind DMA handle */
ddi_no_dma_unbindhdl, /* unbind DMA handle */
ddi_no_dma_flush, /* flush DMA */
ddi_no_dma_win, /* move DMA window */
ddi_no_dma_mctl, /* generic DMA control */
bscbus_ctlops, /* generic control */
ddi_bus_prop_op, /* prop_op */
ndi_busop_get_eventcookie, /* get_eventcookie */
ndi_busop_add_eventcall, /* add_eventcall */
ndi_busop_remove_eventcall, /* remove_eventcall */
ndi_post_event, /* post_event */
0, /* interrupt control */
0, /* bus_config */
0, /* bus_unconfig */
0, /* bus_fm_init */
0, /* bus_fm_fini */
0, /* bus_fm_access_enter */
0, /* bus_fm_access_exit */
0, /* bus_power */
bscbus_intr_op /* bus_intr_op */
};
static struct dev_ops bscbus_dev_ops =
{
DEVO_REV,
0, /* refcount */
ddi_no_info, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
bscbus_attach, /* attach */
bscbus_detach, /* detach */
bscbus_reset, /* reset */
&bscbus_cb_ops, /* driver operations */
&bscbus_bus_ops, /* bus operations */
NULL, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
static struct modldrv modldrv =
{
&mod_driverops,
"bscbus driver",
&bscbus_dev_ops
};
static struct modlinkage modlinkage =
{
MODREV_1,
{
&modldrv,
NULL
}
};
/*
* Dynamic loader interface code
*/
int
_init(void)
{
int err;
err = ddi_soft_state_init(&bscbus_statep,
sizeof (struct bscbus_state), 0);
if (err == DDI_SUCCESS)
if ((err = mod_install(&modlinkage)) != DDI_SUCCESS) {
ddi_soft_state_fini(&bscbus_statep);
}
return (err);
}
int
_info(struct modinfo *mip)
{
return (mod_info(&modlinkage, mip));
}
int
_fini(void)
{
int err;
if ((err = mod_remove(&modlinkage)) == DDI_SUCCESS) {
ddi_soft_state_fini(&bscbus_statep);
bscbus_major = NOMAJOR;
}
return (err);
}
#ifdef BSCBUS_LOGSTATUS
void bscbus_cmd_log(struct bscbus_channel_state *csp, bsc_cmd_stamp_t cat,
uint8_t status, uint8_t data)
{
int idx;
bsc_cmd_log_t *logp;
struct bscbus_state *ssp;
if ((csp) == NULL)
return;
if ((ssp = (csp)->ssp) == NULL)
return;
if (ssp->cmd_log_size == 0)
return;
if ((bscbus_cmd_log_flags & (1 << cat)) == 0)
return;
idx = atomic_inc_32_nv(&ssp->cmd_log_idx);
logp = &ssp->cmd_log[idx % ssp->cmd_log_size];
logp->bcl_seq = idx;
logp->bcl_cat = cat;
logp->bcl_now = gethrtime();
logp->bcl_chno = csp->chno;
logp->bcl_cmdstate = csp->cmdstate;
logp->bcl_status = status;
logp->bcl_data = data;
}
#endif /* BSCBUS_LOGSTATUS */