root/usr/src/uts/sun4u/lw2plus/io/lombus.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * The "lombus" driver provides access to the LOMlite2 virtual registers,
 * so that its clients (children) need not be concerned with the details
 * of the access mechanism, which in this case is implemented via a
 * packet-based protocol over a serial link connected to one of the serial
 * ports of the SuperIO (SIO) chip.
 *
 * On the other hand, this driver doesn't generally know what the virtual
 * registers signify - only the clients need this information.
 */


/*
 *  Header files
 */

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/intr.h>
#include <sys/kmem.h>
#include <sys/membar.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/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>

#include <sys/lombus.h>


#if     defined(NDI_ACC_HDL_V2)

/*
 * Compiling for Solaris 9+ with access handle enhancements
 */
#define HANDLE_TYPE             ndi_acc_handle_t
#define HANDLE_ADDR(hdlp)       (hdlp->ah_addr)
#define HANDLE_FAULT(hdlp)      (hdlp->ah_fault)
#define HANDLE_MAPLEN(hdlp)     (hdlp->ah_len)
#define HANDLE_PRIVATE(hdlp)    (hdlp->ah_bus_private)

#else

/*
 * Compatibility definitions for backport to Solaris 8
 */
#define HANDLE_TYPE             ddi_acc_impl_t
#define HANDLE_ADDR(hdlp)       (hdlp->ahi_common.ah_addr)
#define HANDLE_FAULT(hdlp)      (hdlp->ahi_fault)
#define HANDLE_MAPLEN(hdlp)     (hdlp->ahi_common.ah_len)
#define HANDLE_PRIVATE(hdlp)    (hdlp->ahi_common.ah_bus_private)

#define ddi_driver_major(dip)   ddi_name_to_major(ddi_binding_name(dip))

#endif  /* NDI_ACC_HDL_V2 */


/*
 * Local definitions
 */
#define MYNAME                  "lombus"
#define NOMAJOR                 (~(major_t)0)
#define DUMMY_VALUE             (~(int8_t)0)

#define LOMBUS_INST_TO_MINOR(i) (i)
#define LOMBUS_MINOR_TO_INST(m) (m)

#define LOMBUS_DUMMY_ADDRESS    ((caddr_t)0x0CADD1ED)
#define ADDR_TO_OFFSET(a, hdlp) ((caddr_t)(a) - HANDLE_ADDR(hdlp))
#define ADDR_TO_VREG(a)         ((caddr_t)(a) - LOMBUS_DUMMY_ADDRESS)
#define VREG_TO_ADDR(v)         (LOMBUS_DUMMY_ADDRESS + (v))


/*
 * The following definitions are taken from the datasheet
 * for the National Semiconductor PC87317 (SuperIO) chip.
 *
 * This chip implements UART functionality as logical device 6.
 * It provides all sorts of wierd modes and extensions, but we
 * have chosen to use only the 16550-compatible features
 * ("non-extended mode").
 *
 * Hardware: serial chip register numbers
 */
#define SIO_RXD                 0       /* read         */
#define SIO_TXD                 0       /* write        */
#define SIO_IER                 1
#define SIO_EIR                 2       /* read         */
#define SIO_FCR                 2       /* write        */
#define SIO_LCR                 3
#define SIO_BSR                 3       /* wierd        */
#define SIO_MCR                 4
#define SIO_LSR                 5
#define SIO_MSR                 6
#define SIO_SCR                 7

#define SIO_LBGDL               0       /* bank 1       */
#define SIO_LBGDH               1       /* bank 1       */

/*
 * Hardware: serial chip register bits
 */
#define SIO_IER_RXHDL_IE        0x01
#define SIO_IER_STD             0x00

#define SIO_EIR_IPF             0x01
#define SIO_EIR_IPR0            0x02
#define SIO_EIR_IPR1            0x04
#define SIO_EIR_RXFT            0x08
#define SIO_EIR_FEN0            0x40
#define SIO_EIR_FEN1            0x80

#define SIO_FCR_FIFO_EN         0x01
#define SIO_FCR_RXSR            0x02
#define SIO_FCR_TXSR            0x04
#define SIO_FCR_RXFTH0          0x40
#define SIO_FCR_RXFTH1          0x80
#define SIO_FCR_STD             (SIO_FCR_RXFTH0|SIO_FCR_FIFO_EN)

#define SIO_LCR_WLS0            0x01
#define SIO_LCR_WLS1            0x02
#define SIO_LCR_STB             0x04
#define SIO_LCR_PEN             0x08
#define SIO_LCR_EPS             0x10
#define SIO_LCR_STKP            0x20
#define SIO_LCR_SBRK            0x40
#define SIO_LCR_BKSE            0x80
#define SIO_LCR_8BIT            (SIO_LCR_WLS0|SIO_LCR_WLS1)
#define SIO_LCR_EPAR            (SIO_LCR_PEN|SIO_LCR_EPS)
#define SIO_LCR_STD             (SIO_LCR_8BIT|SIO_LCR_EPAR)

#define SIO_BSR_BANK0           (SIO_LCR_STD)
#define SIO_BSR_BANK1           (SIO_LCR_BKSE|SIO_LCR_STD)

#define SIO_MCR_DTR             0x01
#define SIO_MCR_RTS             0x02
#define SIO_MCR_ISEN            0x08
#define SIO_MCR_STD             (SIO_MCR_ISEN)

#define SIO_LSR_RXDA            0x01
#define SIO_LSR_OE              0x02
#define SIO_LSR_PE              0x04
#define SIO_LSR_FE              0x08
#define SIO_LSR_BRKE            0x10
#define SIO_LSR_TXRDY           0x20
#define SIO_LSR_TXEMP           0x40
#define SIO_LSR_ER_INF          0x80

#define SIO_MSR_DCTS            0x01
#define SIO_MSR_DDSR            0x02
#define SIO_MSR_TERI            0x04
#define SIO_MSR_DDCD            0x08
#define SIO_MSR_CTS             0x10
#define SIO_MSR_DSR             0x20
#define SIO_MSR_RI              0x40
#define SIO_MSR_DCD             0x80

/*
 * Min/max/default baud rates, and a macro to convert from a baud
 * rate to the number (divisor) to put in the baud rate registers
 */
#define SIO_BAUD_MIN            50
#define SIO_BAUD_MAX            115200
#define SIO_BAUD_DEFAULT        38400
#define SIO_BAUD_TO_DIVISOR(b)  (115200 / (b))


/*
 * Packet format ...
 */
#define LOMBUS_MASK             0xc0    /* Byte-type bits               */
#define LOMBUS_PARAM            0x00    /* Parameter byte: 0b0xxxxxxx   */
#define LOMBUS_LAST             0x80    /* Last byte of packet          */
#define LOMBUS_CMD              0x80    /* Command byte:   0b10###XWV   */
#define LOMBUS_STATUS           0xc0    /* Status  byte:   0b11###AEV   */

#define LOMBUS_SEQ              0x38    /* Sequence number bits         */
#define LOMBUS_SEQ_LSB          0x08    /* Sequence number LSB          */
#define LOMBUS_CMD_XADDR        0x04    /* Extended (2-byte) addressing */
#define LOMBUS_CMD_WRITE        0x02    /* Write command                */
#define LOMBUS_CMD_WMSB         0x01    /* Set MSB on Write             */
#define LOMBUS_CMD_READ         0x01    /* Read command                 */
#define LOMBUS_CMD_NOP          0x00    /* NOP command                  */

#define LOMBUS_STATUS_ASYNC     0x04    /* Asynchronous event pending   */
#define LOMBUS_STATUS_ERR       0x02    /* Error in command processing  */
#define LOMBUS_STATUS_MSB       0x01    /* MSB of Value read            */

#define LOMBUS_VREG_LO(x)       ((x) & ((1 << 7) - 1))
#define LOMBUS_VREG_HI(x)       ((x) >> 7)

#define LOMBUS_BUFSIZE          8


/*
 * Time periods, in nanoseconds
 *
 * Note that LOMBUS_ONE_SEC and some other time
 * periods are defined in <sys/lombus.h>
 */
#define LOMBUS_CMD_POLL         (LOMBUS_ONE_SEC/20)
#define LOMBUS_CTS_POLL         (LOMBUS_ONE_SEC/20)
#define LOMBUS_CTS_TIMEOUT      (LOMBUS_ONE_SEC*2)


/*
 * Local datatypes
 */
enum lombus_cmdstate {
        LOMBUS_CMDSTATE_IDLE,
        LOMBUS_CMDSTATE_BUSY,
        LOMBUS_CMDSTATE_WAITING,
        LOMBUS_CMDSTATE_READY,
        LOMBUS_CMDSTATE_ERROR
};


/*
 * This driver's soft-state structure
 */

struct lombus_state {
        /*
         * Configuration data, set during attach
         */
        dev_info_t *dip;
        major_t majornum;
        int instance;

        ddi_acc_handle_t sio_handle;
        uint8_t *sio_regs;
        ddi_softintr_t softid;
        ddi_periodic_t cycid; /* periodical callback */

        /*
         * Parameters derived from .conf properties
         */
        boolean_t allow_echo;
        int baud;
        uint32_t debug;
        boolean_t fake_cts;

        /*
         * Hardware mutex (initialised using <hw_iblk>),
         * used to prevent retriggering the softint while
         * it's still fetching data out of the chip FIFO.
         */
        kmutex_t hw_mutex[1];
        ddi_iblock_cookie_t hw_iblk;

        /*
         * Data protected by the hardware mutex: the watchdog-patting
         * protocol data (since the dog can be patted from a high-level
         * cyclic), and the interrupt-enabled flag.
         */
        hrtime_t hw_last_pat;
        boolean_t hw_int_enabled;

        /*
         * Flag to indicate that we've incurred a hardware fault on
         * accesses to the SIO; once this is set, we fake all further
         * accesses in order not to provoke additional bus errors.
         */
        boolean_t sio_fault;

        /*
         * Serial protocol state data, protected by lo_mutex
         * (which is initialised using <lo_iblk>)
         */
        kmutex_t lo_mutex[1];
        ddi_iblock_cookie_t lo_iblk;
        kcondvar_t lo_cv[1];

        volatile enum lombus_cmdstate cmdstate;
        clock_t deadline;
        uint8_t cmdbuf[LOMBUS_BUFSIZE];
        uint8_t reply[LOMBUS_BUFSIZE];
        uint8_t async;
        uint8_t index;
        uint8_t result;
        uint8_t sequence;
        uint32_t error;
};

/*
 * The auxiliary structure attached to each child
 * (the child's parent-private-data points to this).
 */
struct lombus_child_info {
        lombus_regspec_t *rsp;
        int nregs;
};


/*
 * Local data
 */

static void *lombus_statep;

static major_t lombus_major = NOMAJOR;

static ddi_device_acc_attr_t lombus_dev_acc_attr[1] =
{
        DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_LE_ACC,
        DDI_STRICTORDER_ACC
};


/*
 *  General utility routines ...
 */

static void
lombus_trace(struct lombus_state *ssp, char code, const char *caller,
        const char *fmt, ...)
{
        char buf[256];
        char *p;
        va_list va;

        if (ssp->debug & (1 << (code-'@'))) {
                p = buf;
                (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(ssp->majornum, ssp->instance, code, SL_TRACE,
                    buf);
        }
}

static struct lombus_state *
lombus_getstate(dev_info_t *dip, int instance, const char *caller)
{
        struct lombus_state *ssp = NULL;
        dev_info_t *sdip = NULL;
        major_t dmaj = NOMAJOR;

        if (dip != NULL) {
                /*
                 * Use the instance number from the <dip>; also,
                 * check that it really corresponds to this driver
                 */
                instance = ddi_get_instance(dip);
                dmaj = ddi_driver_major(dip);
                if (lombus_major == NOMAJOR && dmaj != NOMAJOR)
                        lombus_major = dmaj;
                else if (dmaj != lombus_major) {
                        cmn_err(CE_WARN,
                            "%s: major number mismatch (%d vs. %d) in %s(),"
                            "probably due to child misconfiguration",
                            MYNAME, lombus_major, dmaj, caller);
                        instance = -1;
                }
        }

        if (instance >= 0)
                ssp = ddi_get_soft_state(lombus_statep, instance);
        if (ssp != NULL) {
                sdip = ssp->dip;
                if (dip == NULL && sdip == NULL)
                        ssp = NULL;
                else if (dip != NULL && sdip != NULL && sdip != dip) {
                        cmn_err(CE_WARN,
                            "%s: devinfo mismatch (%p vs. %p) in %s(), "
                            "probably due to child misconfiguration",
                            MYNAME, (void *)dip, (void *)sdip, caller);
                        ssp = NULL;
                }
        }

        return (ssp);
}

/*
 * Lowest-level serial I/O chip register read/write
 */

static void
sio_put_reg(struct lombus_state *ssp, uint_t reg, uint8_t val)
{
        lombus_trace(ssp, 'P', "sio_put_reg", "REG[%d] <- $%02x", reg, val);

        if (ssp->sio_handle != NULL && !ssp->sio_fault) {
                /*
                 * The chip is mapped as "I/O" (e.g. with the side-effect
                 * bit on SPARC), therefore accesses are required to be
                 * in-order, with no value cacheing.  However, there can
                 * still be write-behind buffering, so it is not guaranteed
                 * that a write actually reaches the chip in a given time.
                 *
                 * To force the access right through to the chip, we follow
                 * the write with another write (to the SCRATCH register)
                 * and a read (of the value just written to the SCRATCH
                 * register).  The SCRATCH register is specifically provided
                 * for temporary data and has no effect on the SIO's own
                 * operation, making it ideal as a synchronising mechanism.
                 *
                 * If we didn't do this, it would be possible that the new
                 * value wouldn't reach the chip (and have the *intended*
                 * side-effects, such as disabling interrupts), for such a
                 * long time that the processor could execute a *lot* of
                 * instructions - including exiting the interrupt service
                 * routine and re-enabling interrupts.  This effect was
                 * observed to lead to spurious (unclaimed) interrupts in
                 * some circumstances.
                 *
                 * This will no longer be needed once "synchronous" access
                 * handles are available (see PSARC/2000/269 and 2000/531).
                 */
                ddi_put8(ssp->sio_handle, ssp->sio_regs + reg, val);
                ddi_put8(ssp->sio_handle, ssp->sio_regs + SIO_SCR, val);
                membar_sync();
                (void) ddi_get8(ssp->sio_handle, ssp->sio_regs + SIO_SCR);
        }
}

static uint8_t
sio_get_reg(struct lombus_state *ssp, uint_t reg)
{
        uint8_t val;

        if (ssp->sio_handle && !ssp->sio_fault)
                val = ddi_get8(ssp->sio_handle, ssp->sio_regs + reg);
        else
                val = DUMMY_VALUE;

        lombus_trace(ssp, 'G', "sio_get_reg", "$%02x <- REG[%d]", val, reg);

        return (val);
}

static void
sio_check_fault_status(struct lombus_state *ssp)
{
        ssp->sio_fault = ddi_check_acc_handle(ssp->sio_handle) != DDI_SUCCESS;
}

static boolean_t
sio_faulty(struct lombus_state *ssp)
{
        if (!ssp->sio_fault)
                sio_check_fault_status(ssp);
        return (ssp->sio_fault);
}


/*
 * Check for data ready.
 */
static boolean_t
sio_data_ready(struct lombus_state *ssp)
{
        uint8_t status;

        /*
         * Data is available if the RXDA bit in the LSR is nonzero
         * (if reading it didn't incur a fault).
         */
        status = sio_get_reg(ssp, SIO_LSR);
        return ((status & SIO_LSR_RXDA) != 0 && !sio_faulty(ssp));
}

/*
 * Check for LOM ready
 */
static boolean_t
sio_lom_ready(struct lombus_state *ssp)
{
        uint8_t status;
        boolean_t rslt;

        /*
         * The LOM is ready if the CTS bit in the MSR is 1, meaning
         * that the /CTS signal is being asserted (driven LOW) -
         * unless we incurred a fault in trying to read the MSR!
         *
         * For debugging, we force the result to TRUE if the FAKE flag is set
         */
        status = sio_get_reg(ssp, SIO_MSR);
        rslt = (status & SIO_MSR_CTS) != 0 && !sio_faulty(ssp);

        lombus_trace(ssp, 'R', "sio_lom_ready", "S $%02x R %d F %d",
            status, rslt, ssp->fake_cts);

        return (rslt || ssp->fake_cts);
}

#if     0
/*
 * Check for interrupt pending
 */
static boolean_t
sio_irq_pending(struct lombus_state *ssp)
{
        uint8_t status;
        boolean_t rslt;

        /*
         * An interrupt is pending if the IPF bit in the EIR is 0,
         * assuming we didn't incur a fault in trying to ready it.
         *
         * Note: we expect that every time we read this register
         * (which is only done from the interrupt service routine),
         * we will see $11001100 (RX FIFO timeout interrupt pending).
         */
        status = sio_get_reg(ssp, SIO_EIR);

        rslt = (status & SIO_EIR_IPF) == 0 && !sio_faulty(ssp);
        lombus_trace(ssp, 'I', "sio_irq_pending", "S $%02x R %d",
            status, rslt);

        /*
         * To investigate whether we're getting any abnormal interrupts
         * this code checks that the status value is as expected, and that
         * chip-level interrupts are supposed to be enabled at this time.
         * This will cause a PANIC (on a driver compiled with DEBUG) if
         * all is not as expected ...
         */
        ASSERT(status == 0xCC);
        ASSERT(ssp->hw_int_enabled);

        return (rslt);
}
#endif  /* 0 */

/*
 * Enable/disable interrupts
 */
static void
lombus_set_irq(struct lombus_state *ssp, boolean_t newstate)
{
        uint8_t val;

        val = newstate ? SIO_IER_RXHDL_IE : 0;
        sio_put_reg(ssp, SIO_IER, SIO_IER_STD | val);
        ssp->hw_int_enabled = newstate;
}

/*
 * Assert/deassert RTS
 */
static void
lombus_toggle_rts(struct lombus_state *ssp)
{
        uint8_t val;

        val = sio_get_reg(ssp, SIO_MCR);
        val &= SIO_MCR_RTS;
        val ^= SIO_MCR_RTS;
        val |= SIO_MCR_STD;
        sio_put_reg(ssp, SIO_MCR, val);
}


/*
 * High-level interrupt handler:
 *      Checks whether initialisation is complete (to avoid a race
 *      with mutex_init()), and whether chip interrupts are enabled.
 *      If not, the interrupt's not for us, so just return UNCLAIMED.
 *      Otherwise, disable the interrupt, trigger a softint, and return
 *      CLAIMED.  The softint handler will then do all the real work.
 *
 *      NOTE: the chip interrupt capability is only re-enabled once the
 *      receive code has run, but that can be called from a poll loop
 *      or cyclic callback as well as from the softint.  So it's *not*
 *      guaranteed that there really is a chip interrupt pending here,
 *      'cos the work may already have been done and the reason for the
 *      interrupt gone away before we get here.
 *
 *      OTOH, if we come through here twice without the receive code
 *      having run in between, that's definitely wrong.  In such an
 *      event, we would notice that chip interrupts haven't yet been
 *      re-enabled and return UNCLAIMED, allowing the system's jabber
 *      protect code (if any) to do its job.
 */
static uint_t
lombus_hi_intr(caddr_t arg)
{
        struct lombus_state *ssp = (void *)arg;
        uint_t claim;

        claim = DDI_INTR_UNCLAIMED;
        if (ssp->cycid != NULL) {
                mutex_enter(ssp->hw_mutex);
                if (ssp->hw_int_enabled) {
                        lombus_set_irq(ssp, B_FALSE);
                        ddi_trigger_softintr(ssp->softid);
                        claim = DDI_INTR_CLAIMED;
                }
                mutex_exit(ssp->hw_mutex);
        }

        return (claim);
}

/*
 * Packet receive handler
 *
 * This routine should be called from the low-level softint, or the
 * cyclic callback, or lombus_cmd() (for polled operation), with the
 * low-level mutex already held.
 */
static void
lombus_receive(struct lombus_state *ssp)
{
        boolean_t ready = B_FALSE;
        uint8_t data = 0;
        uint8_t rcvd = 0;
        uint8_t tmp;

        lombus_trace(ssp, 'S', "lombus_receive",
            "state %d; error $%x",
            ssp->cmdstate, ssp->error);

        /*
         * Check for access faults before starting the receive
         * loop (we don't want to cause bus errors or suchlike
         * unpleasantness in the event that the SIO has died).
         */
        if (!sio_faulty(ssp)) {
                /*
                 * Read bytes from the FIFO until they're all gone,
                 * or we find the 'END OF PACKET' set on one, or
                 * our buffer overflows (which must be an error)
                 */
                mutex_enter(ssp->hw_mutex);
                while (sio_data_ready(ssp)) {
                        data = sio_get_reg(ssp, SIO_RXD);
                        ssp->reply[rcvd = ssp->index] = data;
                        if (++rcvd >= LOMBUS_BUFSIZE)
                                break;
                        ssp->index = rcvd;
                        if (data & LOMBUS_LAST)
                                break;
                }
                lombus_set_irq(ssp, B_TRUE);
                mutex_exit(ssp->hw_mutex);
        }

        lombus_trace(ssp, 'S', "lombus_receive",
            "rcvd %d: $%02x $%02x $%02x $%02x $%02x $%02x $%02x $%02x",
            rcvd,
            ssp->reply[0], ssp->reply[1],
            ssp->reply[2], ssp->reply[3],
            ssp->reply[4], ssp->reply[5],
            ssp->reply[6], ssp->reply[7]);

        if (ssp->cmdstate != LOMBUS_CMDSTATE_WAITING) {
                /*
                 * We're not expecting any data in this state, so if
                 * we DID receive any data, we just throw it away by
                 * resetting the buffer index to 0.
                 */
                ssp->index = 0;
        } else if (rcvd == 0) {
                /*
                 * No bytes received this time through (though there
                 * might be a partial packet sitting in the buffer).
                 * If it seems the LOM is taking too long to respond,
                 * we'll assume it's died and return an error.
                 */
                if (ddi_get_lbolt() > ssp->deadline) {
                        ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
                        ssp->error = LOMBUS_ERR_TIMEOUT;
                        ready = B_TRUE;
                }
        } else if (rcvd >= LOMBUS_BUFSIZE) {
                /*
                 * Buffer overflow; discard the data & treat as an error
                 * (even if the last byte read did claim to terminate a
                 * packet, it can't be a valid one 'cos it's too long!)
                 */
                ssp->index = 0;
                ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
                ssp->error = LOMBUS_ERR_OFLOW;
                ready = B_TRUE;
        } else if ((data & LOMBUS_LAST) == 0) {
                /*
                 * Packet not yet complete; leave the partial packet in
                 * the buffer for later ...
                 */
                _NOTE(EMPTY)
                ;
        } else if ((data & LOMBUS_MASK) != LOMBUS_STATUS) {
                /*
                 * Invalid "status" byte - maybe an echo of the command?
                 *
                 * As a debugging feature, we allow for this, assuming
                 * that if the LOM has echoed the command byte, it has
                 * also echoed all the parameter bytes before starting
                 * command processing.  So, we dump out the buffer and
                 * then clear it, so we can go back to looking for the
                 * real reply.
                 *
                 * Otherwise, we just drop the data & flag an error.
                 */
                if (ssp->allow_echo) {
                        lombus_trace(ssp, 'E', "lombus_receive",
                            "echo $%02x $%02x $%02x $%02x "
                            "$%02x $%02x $%02x $%02x",
                            ssp->reply[0], ssp->reply[1],
                            ssp->reply[2], ssp->reply[3],
                            ssp->reply[4], ssp->reply[5],
                            ssp->reply[6], ssp->reply[7]);
                        ssp->index = 0;
                } else {
                        ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
                        ssp->error = LOMBUS_ERR_BADSTATUS;
                        ready = B_TRUE;
                }
        } else if ((data & LOMBUS_SEQ) != ssp->sequence) {
                /*
                 * Wrong sequence number!  Flag this as an error
                 */
                ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
                ssp->error = LOMBUS_ERR_SEQUENCE;
                ready = B_TRUE;
        } else {
                /*
                 * Finally, we know that's it's a valid reply to our
                 * last command.  Update the ASYNC status, derive the
                 * reply parameter (if any), and check the ERROR bit
                 * to find out what the parameter means.
                 *
                 * Note that not all the values read/assigned here
                 * are meaningful, but it doesn't matter; the waiting
                 * thread will know which one(s) it should check.
                 */
                ssp->async = (data & LOMBUS_STATUS_ASYNC) ? 1 : 0;
                tmp = ((data & LOMBUS_STATUS_MSB) ? 0x80 : 0) | ssp->reply[0];
                if (data & LOMBUS_STATUS_ERR) {
                        ssp->cmdstate = LOMBUS_CMDSTATE_ERROR;
                        ssp->error = tmp;
                } else {
                        ssp->cmdstate = LOMBUS_CMDSTATE_READY;
                        ssp->result = tmp;
                }
                ready = B_TRUE;
        }

        lombus_trace(ssp, 'T', "lombus_receive",
            "rcvd %d; last $%02x; state %d; error $%x; ready %d",
            rcvd, data, ssp->cmdstate, ssp->error, ready);

        if (ready)
                cv_broadcast(ssp->lo_cv);
}

/*
 * Low-level softint handler
 *
 * This routine should be triggered whenever there's a byte to be read
 */
static uint_t
lombus_softint(caddr_t arg)
{
        struct lombus_state *ssp = (void *)arg;

        mutex_enter(ssp->lo_mutex);
        lombus_receive(ssp);
        mutex_exit(ssp->lo_mutex);

        return (DDI_INTR_CLAIMED);
}

/*
 * Cyclic handler: just calls the receive routine, in case interrupts
 * are not being delivered and in order to handle command timeout
 */
static void
lombus_cyclic(void *arg)
{
        struct lombus_state *ssp = (void *)arg;

        mutex_enter(ssp->lo_mutex);
        lombus_receive(ssp);
        mutex_exit(ssp->lo_mutex);
}


/*
 * Serial protocol
 *
 * This routine builds a command and sets it in progress.
 */
static uint8_t
lombus_cmd(HANDLE_TYPE *hdlp, ptrdiff_t vreg, uint_t val, uint_t cmd)
{
        struct lombus_state *ssp;
        clock_t start;
        uint8_t *p;

        /*
         * First of all, wait for the interface to be available.
         *
         * NOTE: we blow through all the mutex/cv/state checking and
         * preempt any command in progress if the system is panicking!
         */
        ssp = HANDLE_PRIVATE(hdlp);
        mutex_enter(ssp->lo_mutex);
        while (ssp->cmdstate != LOMBUS_CMDSTATE_IDLE && !panicstr)
                cv_wait(ssp->lo_cv, ssp->lo_mutex);

        ssp->cmdstate = LOMBUS_CMDSTATE_BUSY;
        ssp->sequence = (ssp->sequence + LOMBUS_SEQ_LSB) & LOMBUS_SEQ;

        /*
         * We have exclusive ownership, so assemble the command (backwards):
         *
         * [byte 0]     Command:        modified by XADDR and/or WMSB bits
         * [Optional] Parameter:        Value to write (low 7 bits)
         * [Optional] Parameter:        Register number (high 7 bits)
         * [Optional] Parameter:        Register number (low 7 bits)
         */
        p = &ssp->cmdbuf[0];
        *p++ = LOMBUS_CMD | ssp->sequence | cmd;
        switch (cmd) {
        case LOMBUS_CMD_WRITE:
                *p++ = val & 0x7f;
                if (val >= 0x80)
                        ssp->cmdbuf[0] |= LOMBUS_CMD_WMSB;
                /*FALLTHRU*/
        case LOMBUS_CMD_READ:
                if (LOMBUS_VREG_HI(vreg) != 0) {
                        *p++ = LOMBUS_VREG_HI(vreg);
                        ssp->cmdbuf[0] |= LOMBUS_CMD_XADDR;
                }
                *p++ = LOMBUS_VREG_LO(vreg);
                /*FALLTHRU*/
        case LOMBUS_CMD_NOP:
                break;
        }

        /*
         * Check and update the SIO h/w fault status before accessing
         * the chip registers.  If there's a (new or previous) fault,
         * we'll run through the protocol but won't really touch the
         * hardware and all commands will timeout.  If a previously
         * discovered fault has now gone away (!), then we can (try to)
         * proceed with the new command (probably a probe).
         */
        sio_check_fault_status(ssp);

        /*
         * Wait up to LOMBUS_CTS_TIMEOUT (2 seconds) for the LOM to tell
         * us that it's ready for the next command.  If it doesn't, though,
         * we'll send it anyway, on the basis that the CTS signal might be
         * open- or short-circuited (or the LOM firmware forgot to set it,
         * or the LOM just got reset, or whatever ...)
         */
        start = ddi_get_lbolt();
        ssp->deadline = start + drv_usectohz(LOMBUS_CTS_TIMEOUT/1000);
        while (!sio_lom_ready(ssp)) {
                if (ddi_get_lbolt() > ssp->deadline)
                        break;

                (void) cv_reltimedwait(ssp->lo_cv, ssp->lo_mutex,
                    drv_usectohz(LOMBUS_CTS_POLL/1000), TR_CLOCK_TICK);
        }

        /*
         * Either the LOM is ready, or we timed out waiting for CTS.
         * In either case, we're going to send the command now by
         * stuffing the packet into the Tx FIFO, reversing it as we go.
         * We call lombus_receive() first to ensure there isn't any
         * garbage left in the Rx FIFO from an earlier command that
         * timed out (or was pre-empted by a PANIC!).  This also makes
         * sure that SIO interrupts are enabled so we'll see the reply
         * more quickly (the poll loop below will still work even if
         * interrupts aren't enabled, but it will take longer).
         */
        lombus_receive(ssp);
        mutex_enter(ssp->hw_mutex);
        while (p > ssp->cmdbuf)
                sio_put_reg(ssp, SIO_TXD, *--p);
        mutex_exit(ssp->hw_mutex);

        /*
         * Prepare for the reply (to be processed by the interrupt/cyclic
         * handler and/or polling loop below), then wait for a response
         * or timeout.
         */
        start = ddi_get_lbolt();
        ssp->deadline = start + drv_usectohz(LOMBUS_CMD_TIMEOUT/1000);
        ssp->error = 0;
        ssp->index = 0;
        ssp->result = DUMMY_VALUE;
        ssp->cmdstate = LOMBUS_CMDSTATE_WAITING;
        while (ssp->cmdstate == LOMBUS_CMDSTATE_WAITING) {
                if (cv_reltimedwait(ssp->lo_cv, ssp->lo_mutex,
                    drv_usectohz(LOMBUS_CMD_POLL/1000), TR_CLOCK_TICK) == -1)
                        lombus_receive(ssp);
        }

        /*
         * The return value may not be meaningful but retrieve it anyway
         */
        val = ssp->result;
        if (sio_faulty(ssp)) {
                val = DUMMY_VALUE;
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_SIOHW;
        } else if (ssp->cmdstate != LOMBUS_CMDSTATE_READY) {
                /*
                 * Some problem here ... transfer the error code from
                 * the per-instance state to the per-handle fault flag.
                 * The error code shouldn't be zero!
                 */
                if (ssp->error != 0)
                        HANDLE_FAULT(hdlp) = ssp->error;
                else
                        HANDLE_FAULT(hdlp) = LOMBUS_ERR_BADERRCODE;
        }

        /*
         * All done now!
         */
        ssp->index = 0;
        ssp->cmdstate = LOMBUS_CMDSTATE_IDLE;
        cv_broadcast(ssp->lo_cv);
        mutex_exit(ssp->lo_mutex);

        return (val);
}


/*
 * Space 0 - LOM virtual register access
 * Only 8-bit accesses are supported.
 */
static uint8_t
lombus_vreg_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
        ptrdiff_t offset;

        /*
         * Check the offset that the caller has added to the base address
         * against the length of the mapping originally requested.
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
                /*
                 * Invalid access - flag a fault and return a dummy value
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
                return (DUMMY_VALUE);
        }

        /*
         * Derive the virtual register number and run the command
         */
        return (lombus_cmd(hdlp, ADDR_TO_VREG(addr), 0, LOMBUS_CMD_READ));
}

static void
lombus_vreg_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
        ptrdiff_t offset;

        /*
         * Check the offset that the caller has added to the base address
         * against the length of the mapping originally requested.
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
                /*
                 * Invalid access - flag a fault and return
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
                return;
        }

        /*
         * Derive the virtual register number and run the command
         */
        (void) lombus_cmd(hdlp, ADDR_TO_VREG(addr), val, LOMBUS_CMD_WRITE);
}

static void
lombus_vreg_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
        uint8_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                *host_addr++ = lombus_vreg_get8(hdlp, dev_addr);
}

static void
lombus_vreg_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
        uint8_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                lombus_vreg_put8(hdlp, dev_addr, *host_addr++);
}


/*
 * Space 1 - LOM watchdog pat register access
 * Only 8-bit accesses are supported.
 *
 * Reads have no effect and return 0.
 *
 * Writes pat the dog by toggling the RTS line iff enough time has
 * elapsed since last time we toggled it.
 *
 * Multi-byte reads (using ddi_rep_get8(9F)) are a fairly inefficient
 * way of zeroing the destination area ;-) and still won't pat the dog.
 *
 * Multi-byte writes (using ddi_rep_put8(9F)) will almost certainly
 * only count as a single pat, no matter how many bytes the caller
 * says to write, as the inter-pat time is VERY long compared with
 * the time it will take to read the memory source area.
 */

static uint8_t
lombus_pat_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
        ptrdiff_t offset;

        /*
         * Check the offset that the caller has added to the base address
         * against the length of the mapping originally requested.
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
                /*
                 * Invalid access - flag a fault and return a dummy value
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
                return (DUMMY_VALUE);
        }

        return (0);
}

static void
lombus_pat_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
        struct lombus_state *ssp;
        ptrdiff_t offset;
        hrtime_t now;

        _NOTE(ARGUNUSED(val))

        /*
         * Check the offset that the caller has added to the base address
         * against the length of the mapping originally requested.
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        if (offset < 0 || offset >= HANDLE_MAPLEN(hdlp)) {
                /*
                 * Invalid access - flag a fault and return
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
                return;
        }

        ssp = HANDLE_PRIVATE(hdlp);
        mutex_enter(ssp->hw_mutex);
        now = gethrtime();
        if ((now - ssp->hw_last_pat) >= LOMBUS_MIN_PAT) {
                lombus_toggle_rts(ssp);
                ssp->hw_last_pat = now;
        }
        mutex_exit(ssp->hw_mutex);
}

static void
lombus_pat_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
        uint8_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                *host_addr++ = lombus_pat_get8(hdlp, dev_addr);
}

static void
lombus_pat_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
        uint8_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                lombus_pat_put8(hdlp, dev_addr, *host_addr++);
}


/*
 * Space 2 - LOM async event flag register access
 * Only 16-bit accesses are supported.
 */
static uint16_t
lombus_event_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
        struct lombus_state *ssp;
        ptrdiff_t offset;

        /*
         * Check the offset that the caller has added to the base address
         * against the length of the mapping orignally requested.
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
                /*
                 * Invalid access - flag a fault and return a dummy value
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
                return (DUMMY_VALUE);
        }

        /*
         * Return the value of the asynchronous-event-pending flag
         * as passed back by the LOM at the end of the last command.
         */
        ssp = HANDLE_PRIVATE(hdlp);
        return (ssp->async);
}

static void
lombus_event_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
        ptrdiff_t offset;

        _NOTE(ARGUNUSED(val))

        /*
         * Check the offset that the caller has added to the base address
         * against the length of the mapping originally requested.
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        if (offset < 0 || (offset%2) != 0 || offset >= HANDLE_MAPLEN(hdlp)) {
                /*
                 * Invalid access - flag a fault and return
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_NUM;
                return;
        }

        /*
         * The user can't overwrite the asynchronous-event-pending flag!
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_RO;
}

static void
lombus_event_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
        uint16_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                *host_addr++ = lombus_event_get16(hdlp, dev_addr);
}

static void
lombus_event_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
        uint16_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                lombus_event_put16(hdlp, dev_addr, *host_addr++);
}


/*
 * All spaces - access handle fault information
 * Only 32-bit accesses are supported.
 */
static uint32_t
lombus_meta_get32(HANDLE_TYPE *hdlp, uint32_t *addr)
{
        struct lombus_state *ssp;
        ptrdiff_t offset;

        /*
         * Derive the offset that the caller has added to the base
         * address originally returned, and use it to determine
         * which meta-register is to be accessed ...
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        switch (offset) {
        case LOMBUS_FAULT_REG:
                /*
                 * This meta-register provides a code for the most
                 * recent virtual register access fault, if any.
                 */
                return (HANDLE_FAULT(hdlp));

        case LOMBUS_PROBE_REG:
                /*
                 * Reading this meta-register clears any existing fault
                 * (at the virtual, not the hardware access layer), then
                 * runs a NOP command and returns the fault code from that.
                 */
                HANDLE_FAULT(hdlp) = 0;
                (void) lombus_cmd(hdlp, 0, 0, LOMBUS_CMD_NOP);
                return (HANDLE_FAULT(hdlp));

        case LOMBUS_ASYNC_REG:
                /*
                 * Obsolescent - but still supported for backwards
                 * compatibility.  This is an alias for the newer
                 * LOMBUS_EVENT_REG, but doesn't require a separate
                 * "reg" entry and ddi_regs_map_setup() call.
                 *
                 * It returns the value of the asynchronous-event-pending
                 * flag as passed back by the LOM at the end of the last
                 * completed command.
                 */
                ssp = HANDLE_PRIVATE(hdlp);
                return (ssp->async);

        default:
                /*
                 * Invalid access - flag a fault and return a dummy value
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
                return (DUMMY_VALUE);
        }
}

static void
lombus_meta_put32(HANDLE_TYPE *hdlp, uint32_t *addr, uint32_t val)
{
        ptrdiff_t offset;

        /*
         * Derive the offset that the caller has added to the base
         * address originally returned, and use it to determine
         * which meta-register is to be accessed ...
         */
        offset = ADDR_TO_OFFSET(addr, hdlp);
        switch (offset) {
        case LOMBUS_FAULT_REG:
                /*
                 * This meta-register contains a code for the most
                 * recent virtual register access fault, if any.
                 * It can be cleared simply by writing 0 to it.
                 */
                HANDLE_FAULT(hdlp) = val;
                return;

        case LOMBUS_PROBE_REG:
                /*
                 * Writing this meta-register clears any existing fault
                 * (at the virtual, not the hardware acess layer), then
                 * runs a NOP command.  The caller can check the fault
                 * code later if required.
                 */
                HANDLE_FAULT(hdlp) = 0;
                (void) lombus_cmd(hdlp, 0, 0, LOMBUS_CMD_NOP);
                return;

        default:
                /*
                 * Invalid access - flag a fault
                 */
                HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
                return;
        }
}

static void
lombus_meta_rep_get32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
        uint32_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                *host_addr++ = lombus_meta_get32(hdlp, dev_addr);
}

static void
lombus_meta_rep_put32(HANDLE_TYPE *hdlp, uint32_t *host_addr,
        uint32_t *dev_addr, size_t repcount, uint_t flags)
{
        size_t inc;

        inc = (flags & DDI_DEV_AUTOINCR) ? 1 : 0;
        for (; repcount--; dev_addr += inc)
                lombus_meta_put32(hdlp, dev_addr, *host_addr++);
}


/*
 * Finally, some dummy functions for all unsupported access
 * space/size/mode combinations ...
 */
static uint8_t
lombus_no_get8(HANDLE_TYPE *hdlp, uint8_t *addr)
{
        _NOTE(ARGUNUSED(addr))

        /*
         * Invalid access - flag a fault and return a dummy value
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
        return (DUMMY_VALUE);
}

static void
lombus_no_put8(HANDLE_TYPE *hdlp, uint8_t *addr, uint8_t val)
{
        _NOTE(ARGUNUSED(addr, val))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
lombus_no_rep_get8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
                uint8_t *dev_addr, size_t repcount, uint_t flags)
{
        _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
lombus_no_rep_put8(HANDLE_TYPE *hdlp, uint8_t *host_addr,
        uint8_t *dev_addr, size_t repcount, uint_t flags)
{
        _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static uint16_t
lombus_no_get16(HANDLE_TYPE *hdlp, uint16_t *addr)
{
        _NOTE(ARGUNUSED(addr))

        /*
         * Invalid access - flag a fault and return a dummy value
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
        return (DUMMY_VALUE);
}

static void
lombus_no_put16(HANDLE_TYPE *hdlp, uint16_t *addr, uint16_t val)
{
        _NOTE(ARGUNUSED(addr, val))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
lombus_no_rep_get16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
                uint16_t *dev_addr, size_t repcount, uint_t flags)
{
        _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
lombus_no_rep_put16(HANDLE_TYPE *hdlp, uint16_t *host_addr,
        uint16_t *dev_addr, size_t repcount, uint_t flags)
{
        _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static uint64_t
lombus_no_get64(HANDLE_TYPE *hdlp, uint64_t *addr)
{
        _NOTE(ARGUNUSED(addr))

        /*
         * Invalid access - flag a fault and return a dummy value
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
        return (DUMMY_VALUE);
}

static void
lombus_no_put64(HANDLE_TYPE *hdlp, uint64_t *addr, uint64_t val)
{
        _NOTE(ARGUNUSED(addr, val))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
lombus_no_rep_get64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
        uint64_t *dev_addr, size_t repcount, uint_t flags)
{
        _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static void
lombus_no_rep_put64(HANDLE_TYPE *hdlp, uint64_t *host_addr,
        uint64_t *dev_addr, size_t repcount, uint_t flags)
{
        _NOTE(ARGUNUSED(host_addr, dev_addr, repcount, flags))

        /*
         * Invalid access - flag a fault
         */
        HANDLE_FAULT(hdlp) = LOMBUS_ERR_REG_SIZE;
}

static int
lombus_acc_fault_check(HANDLE_TYPE *hdlp)
{
        return (HANDLE_FAULT(hdlp) != 0);
}


/*
 * Hardware setup - put the SIO chip in the required operational
 * state,  with all our favourite parameters programmed correctly.
 * This routine leaves all SIO interrupts disabled.
 */

static void
lombus_hw_reset(struct lombus_state *ssp)
{
        uint16_t divisor;

        /*
         * Disable interrupts, soft reset Tx and Rx circuitry,
         * reselect standard modes (bits/char, parity, etc).
         */
        lombus_set_irq(ssp, B_FALSE);
        sio_put_reg(ssp, SIO_FCR, SIO_FCR_RXSR | SIO_FCR_TXSR);
        sio_put_reg(ssp, SIO_LCR, SIO_LCR_STD);

        /*
         * Select the proper baud rate; if the value is invalid
         * (presumably 0, i.e. not specified, but also if the
         * "baud" property is set to some silly value), we assume
         * the default.
         */
        if (ssp->baud < SIO_BAUD_MIN || ssp->baud > SIO_BAUD_MAX)
                divisor = SIO_BAUD_TO_DIVISOR(SIO_BAUD_DEFAULT);
        else
                divisor = SIO_BAUD_TO_DIVISOR(ssp->baud);

        /*
         * According to the datasheet, it is forbidden for the divisor
         * register to be zero.  So when loading the register in two
         * steps, we have to make sure that the temporary value formed
         * between loads is nonzero.  However, we can't rely on either
         * half already having a nonzero value, as the datasheet also
         * says that these registers are indeterminate after a reset!
         * So, we explicitly set the low byte to a non-zero value first;
         * then we can safely load the high byte, and then the correct
         * value for the low byte, without the result ever being zero.
         */
        sio_put_reg(ssp, SIO_BSR, SIO_BSR_BANK1);
        sio_put_reg(ssp, SIO_LBGDL, 0xff);
        sio_put_reg(ssp, SIO_LBGDH, divisor >> 8);
        sio_put_reg(ssp, SIO_LBGDL, divisor & 0xff);
        sio_put_reg(ssp, SIO_BSR, SIO_BSR_BANK0);

        /*
         * Program the remaining device registers as required
         */
        sio_put_reg(ssp, SIO_MCR, SIO_MCR_STD);
        sio_put_reg(ssp, SIO_FCR, SIO_FCR_STD);
}


/*
 * Higher-level setup & teardown
 */

static void
lombus_offline(struct lombus_state *ssp)
{
        if (ssp->sio_handle != NULL)
                ddi_regs_map_free(&ssp->sio_handle);
        ssp->sio_handle = NULL;
        ssp->sio_regs = NULL;
}

static int
lombus_online(struct lombus_state *ssp)
{
        ddi_acc_handle_t h;
        caddr_t p;
        int nregs;
        int err;

        if (ddi_dev_nregs(ssp->dip, &nregs) != DDI_SUCCESS)
                nregs = 0;

        switch (nregs) {
        default:
        case 1:
                /*
                 *  regset 0 represents the SIO operating registers
                 */
                err = ddi_regs_map_setup(ssp->dip, 0, &p, 0, 0,
                    lombus_dev_acc_attr, &h);
                lombus_trace(ssp, 'O', "online",
                    "regmap 0 status %d addr $%p", err, p);
                if (err != DDI_SUCCESS)
                        return (EIO);

                ssp->sio_handle = h;
                ssp->sio_regs = (void *)p;
                break;

        case 0:
                /*
                 *  If no registers are defined, succeed vacuously;
                 *  commands will be accepted, but we fake the accesses.
                 */
                break;
        }

        /*
         * Now that the registers are mapped, we can initialise the SIO h/w
         */
        lombus_hw_reset(ssp);
        return (0);
}


/*
 *  Nexus routines
 */

#if     defined(NDI_ACC_HDL_V2)

static const ndi_acc_fns_t lombus_vreg_acc_fns = {
        NDI_ACC_FNS_CURRENT,
        NDI_ACC_FNS_V1,

        lombus_vreg_get8,
        lombus_vreg_put8,
        lombus_vreg_rep_get8,
        lombus_vreg_rep_put8,

        lombus_no_get16,
        lombus_no_put16,
        lombus_no_rep_get16,
        lombus_no_rep_put16,

        lombus_meta_get32,
        lombus_meta_put32,
        lombus_meta_rep_get32,
        lombus_meta_rep_put32,

        lombus_no_get64,
        lombus_no_put64,
        lombus_no_rep_get64,
        lombus_no_rep_put64,

        lombus_acc_fault_check
};

static const ndi_acc_fns_t lombus_pat_acc_fns = {
        NDI_ACC_FNS_CURRENT,
        NDI_ACC_FNS_V1,

        lombus_pat_get8,
        lombus_pat_put8,
        lombus_pat_rep_get8,
        lombus_pat_rep_put8,

        lombus_no_get16,
        lombus_no_put16,
        lombus_no_rep_get16,
        lombus_no_rep_put16,

        lombus_meta_get32,
        lombus_meta_put32,
        lombus_meta_rep_get32,
        lombus_meta_rep_put32,

        lombus_no_get64,
        lombus_no_put64,
        lombus_no_rep_get64,
        lombus_no_rep_put64,

        lombus_acc_fault_check
};

static const ndi_acc_fns_t lombus_event_acc_fns = {
        NDI_ACC_FNS_CURRENT,
        NDI_ACC_FNS_V1,

        lombus_no_get8,
        lombus_no_put8,
        lombus_no_rep_get8,
        lombus_no_rep_put8,

        lombus_event_get16,
        lombus_event_put16,
        lombus_event_rep_get16,
        lombus_event_rep_put16,

        lombus_meta_get32,
        lombus_meta_put32,
        lombus_meta_rep_get32,
        lombus_meta_rep_put32,

        lombus_no_get64,
        lombus_no_put64,
        lombus_no_rep_get64,
        lombus_no_rep_put64,

        lombus_acc_fault_check
};

static int
lombus_map_handle(struct lombus_state *ssp, ddi_map_op_t op,
        int space, caddr_t vaddr, off_t len,
        ndi_acc_handle_t *hdlp, caddr_t *addrp)
{
        switch (op) {
        default:
                return (DDI_ME_UNIMPLEMENTED);

        case DDI_MO_MAP_LOCKED:
                switch (space) {
                default:
                        return (DDI_ME_REGSPEC_RANGE);

                case LOMBUS_VREG_SPACE:
                        ndi_set_acc_fns(hdlp, &lombus_vreg_acc_fns);
                        break;

                case LOMBUS_PAT_SPACE:
                        ndi_set_acc_fns(hdlp, &lombus_pat_acc_fns);
                        break;

                case LOMBUS_EVENT_SPACE:
                        ndi_set_acc_fns(hdlp, &lombus_event_acc_fns);
                        break;
                }
                hdlp->ah_addr = *addrp = vaddr;
                hdlp->ah_len = len;
                hdlp->ah_bus_private = ssp;
                return (DDI_SUCCESS);

        case DDI_MO_UNMAP:
                *addrp = NULL;
                hdlp->ah_bus_private = NULL;
                return (DDI_SUCCESS);
        }
}

#else

static int
lombus_map_handle(struct lombus_state *ssp, ddi_map_op_t op,
        int space, caddr_t vaddr, off_t len,
        ddi_acc_hdl_t *hdlp, caddr_t *addrp)
{
        ddi_acc_impl_t *aip = hdlp->ah_platform_private;

        switch (op) {
        default:
                return (DDI_ME_UNIMPLEMENTED);

        case DDI_MO_MAP_LOCKED:
                switch (space) {
                default:
                        return (DDI_ME_REGSPEC_RANGE);

                case LOMBUS_VREG_SPACE:
                        aip->ahi_get8 = lombus_vreg_get8;
                        aip->ahi_put8 = lombus_vreg_put8;
                        aip->ahi_rep_get8 = lombus_vreg_rep_get8;
                        aip->ahi_rep_put8 = lombus_vreg_rep_put8;

                        aip->ahi_get16 = lombus_no_get16;
                        aip->ahi_put16 = lombus_no_put16;
                        aip->ahi_rep_get16 = lombus_no_rep_get16;
                        aip->ahi_rep_put16 = lombus_no_rep_put16;

                        aip->ahi_get32 = lombus_meta_get32;
                        aip->ahi_put32 = lombus_meta_put32;
                        aip->ahi_rep_get32 = lombus_meta_rep_get32;
                        aip->ahi_rep_put32 = lombus_meta_rep_put32;

                        aip->ahi_get64 = lombus_no_get64;
                        aip->ahi_put64 = lombus_no_put64;
                        aip->ahi_rep_get64 = lombus_no_rep_get64;
                        aip->ahi_rep_put64 = lombus_no_rep_put64;

                        aip->ahi_fault_check = lombus_acc_fault_check;
                        break;

                case LOMBUS_PAT_SPACE:
                        aip->ahi_get8 = lombus_pat_get8;
                        aip->ahi_put8 = lombus_pat_put8;
                        aip->ahi_rep_get8 = lombus_pat_rep_get8;
                        aip->ahi_rep_put8 = lombus_pat_rep_put8;

                        aip->ahi_get16 = lombus_no_get16;
                        aip->ahi_put16 = lombus_no_put16;
                        aip->ahi_rep_get16 = lombus_no_rep_get16;
                        aip->ahi_rep_put16 = lombus_no_rep_put16;

                        aip->ahi_get32 = lombus_meta_get32;
                        aip->ahi_put32 = lombus_meta_put32;
                        aip->ahi_rep_get32 = lombus_meta_rep_get32;
                        aip->ahi_rep_put32 = lombus_meta_rep_put32;

                        aip->ahi_get64 = lombus_no_get64;
                        aip->ahi_put64 = lombus_no_put64;
                        aip->ahi_rep_get64 = lombus_no_rep_get64;
                        aip->ahi_rep_put64 = lombus_no_rep_put64;

                        aip->ahi_fault_check = lombus_acc_fault_check;
                        break;

                case LOMBUS_EVENT_SPACE:
                        aip->ahi_get8 = lombus_no_get8;
                        aip->ahi_put8 = lombus_no_put8;
                        aip->ahi_rep_get8 = lombus_no_rep_get8;
                        aip->ahi_rep_put8 = lombus_no_rep_put8;

                        aip->ahi_get16 = lombus_event_get16;
                        aip->ahi_put16 = lombus_event_put16;
                        aip->ahi_rep_get16 = lombus_event_rep_get16;
                        aip->ahi_rep_put16 = lombus_event_rep_put16;

                        aip->ahi_get32 = lombus_meta_get32;
                        aip->ahi_put32 = lombus_meta_put32;
                        aip->ahi_rep_get32 = lombus_meta_rep_get32;
                        aip->ahi_rep_put32 = lombus_meta_rep_put32;

                        aip->ahi_get64 = lombus_no_get64;
                        aip->ahi_put64 = lombus_no_put64;
                        aip->ahi_rep_get64 = lombus_no_rep_get64;
                        aip->ahi_rep_put64 = lombus_no_rep_put64;

                        aip->ahi_fault_check = lombus_acc_fault_check;
                        break;
                }
                hdlp->ah_addr = *addrp = vaddr;
                hdlp->ah_len = len;
                hdlp->ah_bus_private = ssp;
                return (DDI_SUCCESS);

        case DDI_MO_UNMAP:
                *addrp = NULL;
                hdlp->ah_bus_private = NULL;
                return (DDI_SUCCESS);
        }
}

#endif  /* NDI_ACC_HDL_V2 */

static int
lombus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
        off_t off, off_t len, caddr_t *addrp)
{
        struct lombus_child_info *lcip;
        struct lombus_state *ssp;
        lombus_regspec_t *rsp;

        if ((ssp = lombus_getstate(dip, -1, "lombus_map")) == NULL)
                return (DDI_FAILURE);   /* this "can't happen" */

        /*
         * Validate mapping request ...
         */

        if (mp->map_flags != DDI_MF_KERNEL_MAPPING)
                return (DDI_ME_UNSUPPORTED);
        if (mp->map_handlep == NULL)
                return (DDI_ME_UNSUPPORTED);
        if (mp->map_type != DDI_MT_RNUMBER)
                return (DDI_ME_UNIMPLEMENTED);
        if ((lcip = ddi_get_parent_data(rdip)) == NULL)
                return (DDI_ME_INVAL);
        if ((rsp = lcip->rsp) == NULL)
                return (DDI_ME_INVAL);
        if (mp->map_obj.rnumber >= lcip->nregs)
                return (DDI_ME_RNUMBER_RANGE);
        rsp += mp->map_obj.rnumber;
        if (off < 0 || off >= rsp->lombus_size)
                return (DDI_ME_INVAL);
        if (len == 0)
                len = rsp->lombus_size-off;
        if (len < 0)
                return (DDI_ME_INVAL);
        if (off+len < 0 || off+len > rsp->lombus_size)
                return (DDI_ME_INVAL);

        return (lombus_map_handle(ssp, mp->map_op,
            rsp->lombus_space, VREG_TO_ADDR(rsp->lombus_base+off), len,
            mp->map_handlep, addrp));
}

static int
lombus_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op,
        void *arg, void *result)
{
        struct lombus_child_info *lcip;
        struct lombus_state *ssp;
        lombus_regspec_t *rsp;
        dev_info_t *cdip;
        char addr[32];
        uint_t nregs;
        uint_t rnum;
        int *regs;
        int limit;
        int err;
        int i;

        if ((ssp = lombus_getstate(dip, -1, "lombus_ctlops")) == NULL)
                return (DDI_FAILURE);   /* this "can't happen" */

        switch (op) {
        default:
                break;

        case DDI_CTLOPS_INITCHILD:
                /*
                 * First, look up and validate the "reg" property.
                 *
                 * It must be a non-empty integer array containing a set
                 * of triples.  Once we've verified that, we can treat it
                 * as an array of type lombus_regspec_t[], which defines
                 * the meaning of the elements of each triple:
                 * +  the first element of each triple must be a valid space
                 * +  the second and third elements (base, size) of each
                 *      triple must define a valid subrange of that space
                 * If it passes all the tests, we save it away for future
                 * reference in the child's parent-private-data field.
                 */
                cdip = arg;
                err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
                    DDI_PROP_DONTPASS, "reg", &regs, &nregs);
                lombus_trace(ssp, 'C', "initchild",
                    "prop status %d size %d", err, nregs);
                if (err != DDI_PROP_SUCCESS)
                        return (DDI_FAILURE);

                err = (nregs <= 0 || (nregs % LOMBUS_REGSPEC_SIZE) != 0);
                nregs /= LOMBUS_REGSPEC_SIZE;
                rsp = (lombus_regspec_t *)regs;
                for (i = 0; i < nregs && !err; ++i) {
                        switch (rsp[i].lombus_space) {
                        default:
                                limit = 0;
                                err = 1;
                                break;

                        case LOMBUS_VREG_SPACE:
                                limit = LOMBUS_MAX_REG+1;
                                break;

                        case LOMBUS_PAT_SPACE:
                                limit = LOMBUS_PAT_REG+1;
                                break;

                        case LOMBUS_EVENT_SPACE:
                                limit = LOMBUS_EVENT_REG+1;
                                break;
                        }

                        err |= (rsp[i].lombus_base < 0);
                        err |= (rsp[i].lombus_base >= limit);

                        if (rsp[i].lombus_size == 0)
                                rsp[i].lombus_size = limit-rsp[i].lombus_base;
                        err |= (rsp[i].lombus_size < 0);

                        err |= (rsp[i].lombus_base+rsp[i].lombus_size < 0);
                        err |= (rsp[i].lombus_base+rsp[i].lombus_size > limit);
                }

                if (err) {
                        ddi_prop_free(regs);
                        return (DDI_FAILURE);
                }

                lcip = kmem_zalloc(sizeof (*lcip), KM_SLEEP);
                lcip->nregs = nregs;
                lcip->rsp = rsp;
                ddi_set_parent_data(cdip, lcip);

                (void) snprintf(addr, sizeof (addr),
                    "%x,%x", rsp[0].lombus_space, rsp[0].lombus_base);
                ddi_set_name_addr(cdip, addr);

                return (DDI_SUCCESS);

        case DDI_CTLOPS_UNINITCHILD:
                cdip = arg;
                ddi_set_name_addr(cdip, NULL);
                lcip = ddi_get_parent_data(cdip);
                ddi_set_parent_data(cdip, NULL);
                ddi_prop_free(lcip->rsp);
                kmem_free(lcip, sizeof (*lcip));
                return (DDI_SUCCESS);

        case DDI_CTLOPS_REPORTDEV:
                if (rdip == NULL)
                        return (DDI_FAILURE);

                cmn_err(CE_CONT, "?LOM device: %s@%s, %s#%d\n",
                    ddi_node_name(rdip), ddi_get_name_addr(rdip),
                    ddi_driver_name(dip), ddi_get_instance(dip));

                return (DDI_SUCCESS);

        case DDI_CTLOPS_REGSIZE:
                if ((lcip = ddi_get_parent_data(rdip)) == NULL)
                        return (DDI_FAILURE);
                if ((rnum = *(uint_t *)arg) >= lcip->nregs)
                        return (DDI_FAILURE);
                *(off_t *)result = lcip->rsp[rnum].lombus_size;
                return (DDI_SUCCESS);

        case DDI_CTLOPS_NREGS:
                if ((lcip = ddi_get_parent_data(rdip)) == NULL)
                        return (DDI_FAILURE);
                *(int *)result = lcip->nregs;
                return (DDI_SUCCESS);
        }

        return (ddi_ctlops(dip, rdip, op, arg, result));
}


/*
 *  Clean up on detach or failure of attach
 */
static int
lombus_unattach(struct lombus_state *ssp, int instance)
{
        if (ssp != NULL) {
                lombus_hw_reset(ssp);
                if (ssp->cycid != NULL) {
                        ddi_periodic_delete(ssp->cycid);
                        ssp->cycid = NULL;
                        if (ssp->sio_handle != NULL)
                                ddi_remove_intr(ssp->dip, 0, ssp->hw_iblk);
                        ddi_remove_softintr(ssp->softid);
                        cv_destroy(ssp->lo_cv);
                        mutex_destroy(ssp->lo_mutex);
                        mutex_destroy(ssp->hw_mutex);
                }
                lombus_offline(ssp);
                ddi_set_driver_private(ssp->dip, NULL);
        }

        ddi_soft_state_free(lombus_statep, instance);
        return (DDI_FAILURE);
}

/*
 *  Autoconfiguration routines
 */

static int
lombus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        struct lombus_state *ssp = NULL;
        int instance;
        int err;

        switch (cmd) {
        default:
                return (DDI_FAILURE);

        case DDI_ATTACH:
                break;
        }

        /*
         *  Allocate the soft-state structure
         */
        instance = ddi_get_instance(dip);
        if (ddi_soft_state_zalloc(lombus_statep, instance) != DDI_SUCCESS)
                return (DDI_FAILURE);
        if ((ssp = lombus_getstate(dip, instance, "lombus_attach")) == NULL)
                return (lombus_unattach(ssp, instance));
        ddi_set_driver_private(dip, ssp);

        /*
         *  Initialise devinfo-related fields
         */
        ssp->dip = dip;
        ssp->majornum = ddi_driver_major(dip);
        ssp->instance = instance;

        /*
         *  Set various options from .conf properties
         */
        ssp->allow_echo = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "allow-lom-echo", 0) != 0;
        ssp->baud = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "baud-rate", 0);
        ssp->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "debug", 0);
        ssp->fake_cts = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "fake-cts", 0) != 0;

        /*
         * Initialise current state & time
         */
        ssp->cmdstate = LOMBUS_CMDSTATE_IDLE;
        ssp->hw_last_pat = gethrtime();
        ssp->cycid = NULL;

        /*
         *  Online the hardware ...
         */
        err = lombus_online(ssp);
        if (err != 0)
                return (lombus_unattach(ssp, instance));

        /*
         * Install soft and hard interrupt handler(s)
         * Initialise mutexes and cv
         * Start cyclic callbacks
         * Enable interrupts
         */
        err = ddi_add_softintr(dip, DDI_SOFTINT_LOW, &ssp->softid,
            &ssp->lo_iblk, NULL, lombus_softint, (caddr_t)ssp);
        if (err != DDI_SUCCESS)
                return (lombus_unattach(ssp, instance));

        if (ssp->sio_handle != NULL)
                err = ddi_add_intr(dip, 0, &ssp->hw_iblk, NULL,
                    lombus_hi_intr, (caddr_t)ssp);

        mutex_init(ssp->hw_mutex, NULL, MUTEX_DRIVER, ssp->hw_iblk);
        mutex_init(ssp->lo_mutex, NULL, MUTEX_DRIVER, ssp->lo_iblk);
        cv_init(ssp->lo_cv, NULL, CV_DRIVER, NULL);

        /*
         * Register a periodical handler.
         */
        ssp->cycid = ddi_periodic_add(lombus_cyclic, ssp, LOMBUS_ONE_SEC,
            DDI_IPL_1);

        /*
         * Final check before enabling h/w interrupts - did
         * we successfully install the h/w interrupt handler?
         */
        if (err != DDI_SUCCESS)
                return (lombus_unattach(ssp, instance));

        lombus_set_irq(ssp, B_TRUE);

        /*
         *  All done, report success
         */
        ddi_report_dev(dip);
        return (DDI_SUCCESS);
}


static int
lombus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        struct lombus_state *ssp;
        int instance;

        switch (cmd) {
        default:
                return (DDI_FAILURE);

        case DDI_DETACH:
                break;
        }

        instance = ddi_get_instance(dip);
        if ((ssp = lombus_getstate(dip, instance, "lombus_detach")) == NULL)
                return (DDI_FAILURE);   /* this "can't happen" */

        (void) lombus_unattach(ssp, instance);
        return (DDI_SUCCESS);
}

static int
lombus_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
        struct lombus_state *ssp;

        _NOTE(ARGUNUSED(cmd))

        if ((ssp = lombus_getstate(dip, -1, "lombus_reset")) == NULL)
                return (DDI_FAILURE);

        lombus_hw_reset(ssp);
        return (DDI_SUCCESS);
}


/*
 * System interface structures
 */

static struct cb_ops lombus_cb_ops =
{
        nodev,                  /* b/c open     */
        nodev,                  /* b/c close    */
        nodev,                  /* b   strategy */
        nodev,                  /* b   print    */
        nodev,                  /* b   dump     */
        nodev,                  /* c   read     */
        nodev,                  /* c   write    */
        nodev,                  /* c   ioctl    */
        nodev,                  /* c   devmap   */
        nodev,                  /* c   mmap     */
        nodev,                  /* c   segmap   */
        nochpoll,               /* c   poll     */
        ddi_prop_op,            /* b/c prop_op  */
        NULL,                   /* c   streamtab */
        D_MP | D_NEW            /* b/c flags    */
};

static struct bus_ops lombus_bus_ops =
{
        BUSO_REV,                       /* revision             */
        lombus_map,                     /* bus_map              */
        0,                              /* get_intrspec         */
        0,                              /* add_intrspec         */
        0,                              /* remove_intrspec      */
        i_ddi_map_fault,                /* map_fault            */
        ddi_no_dma_map,                 /* dma_map              */
        ddi_no_dma_allochdl,            /* allocate DMA handle  */
        ddi_no_dma_freehdl,             /* free DMA handle      */
        ddi_no_dma_bindhdl,             /* bind DMA handle      */
        ddi_no_dma_unbindhdl,           /* unbind DMA handle    */
        ddi_no_dma_flush,               /* flush DMA            */
        ddi_no_dma_win,                 /* move DMA window      */
        ddi_no_dma_mctl,                /* generic DMA control  */
        lombus_ctlops,                  /* generic control      */
        ddi_bus_prop_op,                /* prop_op              */
        ndi_busop_get_eventcookie,      /* get_eventcookie      */
        ndi_busop_add_eventcall,        /* add_eventcall        */
        ndi_busop_remove_eventcall,     /* remove_eventcall     */
        ndi_post_event,                 /* post_event           */
        0,                              /* interrupt control    */
        0,                              /* bus_config           */
        0,                              /* bus_unconfig         */
        0,                              /* bus_fm_init          */
        0,                              /* bus_fm_fini          */
        0,                              /* bus_fm_access_enter  */
        0,                              /* bus_fm_access_exit   */
        0,                              /* bus_power            */
        i_ddi_intr_ops                  /* bus_intr_op          */
};

static struct dev_ops lombus_dev_ops =
{
        DEVO_REV,
        0,                              /* refcount             */
        ddi_no_info,                    /* getinfo              */
        nulldev,                        /* identify             */
        nulldev,                        /* probe                */
        lombus_attach,                  /* attach               */
        lombus_detach,                  /* detach               */
        lombus_reset,                   /* reset                */
        &lombus_cb_ops,                 /* driver operations    */
        &lombus_bus_ops,                /* bus operations       */
        NULL,                           /* power                */
        ddi_quiesce_not_supported,      /* devo_quiesce         */
};

static struct modldrv modldrv =
{
        &mod_driverops,
        "lombus driver",
        &lombus_dev_ops
};

static struct modlinkage modlinkage =
{
        MODREV_1,
        {
                &modldrv,
                NULL
        }
};


/*
 *  Dynamic loader interface code
 */

int
_init(void)
{
        int err;

        err = ddi_soft_state_init(&lombus_statep,
            sizeof (struct lombus_state), 0);
        if (err == DDI_SUCCESS)
                if ((err = mod_install(&modlinkage)) != 0) {
                        ddi_soft_state_fini(&lombus_statep);
                }

        return (err);
}

int
_info(struct modinfo *mip)
{
        return (mod_info(&modlinkage, mip));
}

int
_fini(void)
{
        int err;

        if ((err = mod_remove(&modlinkage)) == 0) {
                ddi_soft_state_fini(&lombus_statep);
                lombus_major = NOMAJOR;
        }

        return (err);
}