root/sys/dev/pci/ips.c
/*      $NetBSD: ips.c,v 1.7 2024/01/08 18:38:25 chs Exp $      */
/*      $OpenBSD: ips.c,v 1.113 2016/08/14 04:08:03 dlg Exp $   */

/*-
 * Copyright (c) 2017 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (c) 2006, 2007, 2009 Alexander Yurchenko <grange@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * IBM (Adaptec) ServeRAID controllers driver.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ips.c,v 1.7 2024/01/08 18:38:25 chs Exp $");

#include "bio.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/buf.h>
#include <sys/endian.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/ioctl.h>
#include <sys/kthread.h>

#include <sys/bus.h>
#include <sys/intr.h>

#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsi_disk.h>
#include <dev/scsipi/scsipi_disk.h>
#include <dev/scsipi/scsiconf.h>

#include <dev/biovar.h>
#include <dev/sysmon/sysmonvar.h>
#include <sys/envsys.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

/* Debug levels */
#define IPS_D_ERR       0x0001  /* errors */
#define IPS_D_INFO      0x0002  /* information */
#define IPS_D_XFER      0x0004  /* transfers */

#ifdef IPS_DEBUG
#define DPRINTF(a, b)   do { if (ips_debug & (a)) printf b; } while (0)
int ips_debug = IPS_D_ERR;
#else
#define DPRINTF(a, b)
#endif

#define IPS_MAXDRIVES           8
#define IPS_MAXCHANS            4
#define IPS_MAXTARGETS          16
#define IPS_MAXCHUNKS           16
#define IPS_MAXCMDS             128

#define IPS_MAXFER              (64 * 1024)
#define IPS_MAXSGS              16
#define IPS_MAXCDB              12

#define IPS_SECSZ               512
#define IPS_NVRAMPGSZ           128
#define IPS_SQSZ                (IPS_MAXCMDS * sizeof(u_int32_t))

#define IPS_TIMEOUT             60000   /* ms */

/* Command codes */
#define IPS_CMD_READ            0x02
#define IPS_CMD_WRITE           0x03
#define IPS_CMD_DCDB            0x04
#define IPS_CMD_GETADAPTERINFO  0x05
#define IPS_CMD_FLUSH           0x0a
#define IPS_CMD_REBUILDSTATUS   0x0c
#define IPS_CMD_SETSTATE        0x10
#define IPS_CMD_REBUILD         0x16
#define IPS_CMD_ERRORTABLE      0x17
#define IPS_CMD_GETDRIVEINFO    0x19
#define IPS_CMD_RESETCHAN       0x1a
#define IPS_CMD_DOWNLOAD        0x20
#define IPS_CMD_RWBIOSFW        0x22
#define IPS_CMD_READCONF        0x38
#define IPS_CMD_GETSUBSYS       0x40
#define IPS_CMD_CONFIGSYNC      0x58
#define IPS_CMD_READ_SG         0x82
#define IPS_CMD_WRITE_SG        0x83
#define IPS_CMD_DCDB_SG         0x84
#define IPS_CMD_EDCDB           0x95
#define IPS_CMD_EDCDB_SG        0x96
#define IPS_CMD_RWNVRAMPAGE     0xbc
#define IPS_CMD_GETVERINFO      0xc6
#define IPS_CMD_FFDC            0xd7
#define IPS_CMD_SG              0x80
#define IPS_CMD_RWNVRAM         0xbc

/* DCDB attributes */
#define IPS_DCDB_DATAIN         0x01    /* data input */
#define IPS_DCDB_DATAOUT        0x02    /* data output */
#define IPS_DCDB_XFER64K        0x08    /* 64K transfer */
#define IPS_DCDB_TIMO10         0x10    /* 10 secs timeout */
#define IPS_DCDB_TIMO60         0x20    /* 60 secs timeout */
#define IPS_DCDB_TIMO20M        0x30    /* 20 mins timeout */
#define IPS_DCDB_NOAUTOREQSEN   0x40    /* no auto request sense */
#define IPS_DCDB_DISCON         0x80    /* disconnect allowed */

/* Register definitions */
#define IPS_REG_HIS             0x08    /* host interrupt status */
#define IPS_REG_HIS_SCE                 0x01    /* status channel enqueue */
#define IPS_REG_HIS_EN                  0x80    /* enable interrupts */
#define IPS_REG_CCSA            0x10    /* command channel system address */
#define IPS_REG_CCC             0x14    /* command channel control */
#define IPS_REG_CCC_SEM                 0x0008  /* semaphore */
#define IPS_REG_CCC_START               0x101a  /* start command */
#define IPS_REG_SQH             0x20    /* status queue head */
#define IPS_REG_SQT             0x24    /* status queue tail */
#define IPS_REG_SQE             0x28    /* status queue end */
#define IPS_REG_SQS             0x2c    /* status queue start */

#define IPS_REG_OIS             0x30    /* outbound interrupt status */
#define IPS_REG_OIS_PEND                0x0008  /* interrupt is pending */
#define IPS_REG_OIM             0x34    /* outbound interrupt mask */
#define IPS_REG_OIM_DS                  0x0008  /* disable interrupts */
#define IPS_REG_IQP             0x40    /* inbound queue port */
#define IPS_REG_OQP             0x44    /* outbound queue port */

/* Status word fields */
#define IPS_STAT_ID(x)          (((x) >> 8) & 0xff)     /* command id */
#define IPS_STAT_BASIC(x)       (((x) >> 16) & 0xff)    /* basic status */
#define IPS_STAT_EXT(x)         (((x) >> 24) & 0xff)    /* ext status */
#define IPS_STAT_GSC(x)         ((x) & 0x0f)

/* Basic status codes */
#define IPS_STAT_OK             0x00    /* success */
#define IPS_STAT_RECOV          0x01    /* recovered error */
#define IPS_STAT_INVOP          0x03    /* invalid opcode */
#define IPS_STAT_INVCMD         0x04    /* invalid command block */
#define IPS_STAT_INVPARM        0x05    /* invalid parameters block */
#define IPS_STAT_BUSY           0x08    /* busy */
#define IPS_STAT_CMPLERR        0x0c    /* completed with error */
#define IPS_STAT_LDERR          0x0d    /* logical drive error */
#define IPS_STAT_TIMO           0x0e    /* timeout */
#define IPS_STAT_PDRVERR        0x0f    /* physical drive error */

/* Extended status codes */
#define IPS_ESTAT_SELTIMO       0xf0    /* select timeout */
#define IPS_ESTAT_OURUN         0xf2    /* over/underrun */
#define IPS_ESTAT_HOSTRST       0xf7    /* host reset */
#define IPS_ESTAT_DEVRST        0xf8    /* device reset */
#define IPS_ESTAT_RECOV         0xfc    /* recovered error */
#define IPS_ESTAT_CKCOND        0xff    /* check condition */

#define IPS_IOSIZE              128     /* max space size to map */

/* Command frame */
struct ips_cmd {
        u_int8_t        code;
        u_int8_t        id;
        u_int8_t        drive;
        u_int8_t        sgcnt;
        u_int32_t       lba;
        u_int32_t       sgaddr;
        u_int16_t       seccnt;
        u_int8_t        seg4g;
        u_int8_t        esg;
        u_int32_t       ccsar;
        u_int32_t       cccr;
};

/* Direct CDB (SCSI pass-through) frame */
struct ips_dcdb {
        u_int8_t        device;
        u_int8_t        attr;
        u_int16_t       datalen;
        u_int32_t       sgaddr;
        u_int8_t        cdblen;
        u_int8_t        senselen;
        u_int8_t        sgcnt;
        u_int8_t        __reserved1;
        u_int8_t        cdb[IPS_MAXCDB];
        u_int8_t        sense[64];
        u_int8_t        status;
        u_int8_t        __reserved2[3];
};

/* Scatter-gather array element */
struct ips_sg {
        u_int32_t       addr;
        u_int32_t       size;
};

/* Command block */
struct ips_cmdb {
        struct ips_cmd  cmd;
        struct ips_dcdb dcdb;
        struct ips_sg   sg[IPS_MAXSGS];
};

/* Data frames */
struct ips_adapterinfo {
        u_int8_t        drivecnt;
        u_int8_t        miscflag;
        u_int8_t        sltflag;
        u_int8_t        bstflag;
        u_int8_t        pwrchgcnt;
        u_int8_t        wrongaddrcnt;
        u_int8_t        unidentcnt;
        u_int8_t        nvramdevchgcnt;
        u_int8_t        firmware[8];
        u_int8_t        bios[8];
        u_int32_t       drivesize[IPS_MAXDRIVES];
        u_int8_t        cmdcnt;
        u_int8_t        maxphysdevs;
        u_int16_t       flashrepgmcnt;
        u_int8_t        defunctdiskcnt;
        u_int8_t        rebuildflag;
        u_int8_t        offdrivecnt;
        u_int8_t        critdrivecnt;
        u_int16_t       confupdcnt;
        u_int8_t        blkflag;
        u_int8_t        __reserved;
        u_int16_t       deaddisk[IPS_MAXCHANS][IPS_MAXTARGETS];
};

struct ips_driveinfo {
        u_int8_t        drivecnt;
        u_int8_t        __reserved[3];
        struct ips_drive {
                u_int8_t        id;
                u_int8_t        __reserved;
                u_int8_t        raid;
                u_int8_t        state;
#define IPS_DS_FREE     0x00
#define IPS_DS_OFFLINE  0x02
#define IPS_DS_ONLINE   0x03
#define IPS_DS_DEGRADED 0x04
#define IPS_DS_SYS      0x06
#define IPS_DS_CRS      0x24

                u_int32_t       seccnt;
        }               drive[IPS_MAXDRIVES];
};

struct ips_conf {
        u_int8_t        ldcnt;
        u_int8_t        day;
        u_int8_t        month;
        u_int8_t        year;
        u_int8_t        initid[4];
        u_int8_t        hostid[12];
        u_int8_t        time[8];
        u_int32_t       useropt;
        u_int16_t       userfield;
        u_int8_t        rebuildrate;
        u_int8_t        __reserved1;

        struct ips_hw {
                u_int8_t        board[8];
                u_int8_t        cpu[8];
                u_int8_t        nchantype;
                u_int8_t        nhostinttype;
                u_int8_t        compression;
                u_int8_t        nvramtype;
                u_int32_t       nvramsize;
        }               hw;

        struct ips_ld {
                u_int16_t       userfield;
                u_int8_t        state;
                u_int8_t        raidcacheparam;
                u_int8_t        chunkcnt;
                u_int8_t        stripesize;
                u_int8_t        params;
                u_int8_t        __reserved;
                u_int32_t       size;

                struct ips_chunk {
                        u_int8_t        channel;
                        u_int8_t        target;
                        u_int16_t       __reserved;
                        u_int32_t       startsec;
                        u_int32_t       seccnt;
                }               chunk[IPS_MAXCHUNKS];
        }               ld[IPS_MAXDRIVES];

        struct ips_dev {
                u_int8_t        initiator;
                u_int8_t        params;
                u_int8_t        miscflag;
                u_int8_t        state;
#define IPS_DVS_STANDBY 0x01
#define IPS_DVS_REBUILD 0x02
#define IPS_DVS_SPARE   0x04
#define IPS_DVS_MEMBER  0x08
#define IPS_DVS_ONLINE  0x80
#define IPS_DVS_READY   (IPS_DVS_STANDBY | IPS_DVS_ONLINE)

                u_int32_t       seccnt;
                u_int8_t        devid[28];
        }               dev[IPS_MAXCHANS][IPS_MAXTARGETS];

        u_int8_t        reserved[512];
};

struct ips_rblstat {
        u_int8_t        __unknown[20];
        struct {
                u_int8_t        __unknown[4];
                u_int32_t       total;
                u_int32_t       remain;
        }               ld[IPS_MAXDRIVES];
};

struct ips_pg5 {
        u_int32_t       signature;
        u_int8_t        __reserved1;
        u_int8_t        slot;
        u_int16_t       type;
        u_int8_t        bioshi[4];
        u_int8_t        bioslo[4];
        u_int16_t       __reserved2;
        u_int8_t        __reserved3;
        u_int8_t        os;
        u_int8_t        driverhi[4];
        u_int8_t        driverlo[4];
        u_int8_t        __reserved4[100];
};

struct ips_info {
        struct ips_adapterinfo  adapter;
        struct ips_driveinfo    drive;
        struct ips_conf         conf;
        struct ips_rblstat      rblstat;
        struct ips_pg5          pg5;
};

/* Command control block */
struct ips_softc;
struct ips_ccb {
        struct ips_softc *      c_sc;           /* driver softc */
        int                     c_id;           /* command id */
        int                     c_flags;        /* SCSI_* flags */
        enum {
                IPS_CCB_FREE,
                IPS_CCB_QUEUED,
                IPS_CCB_DONE
        }                       c_state;        /* command state */

        void *                  c_cmdbva;       /* command block virt addr */
        paddr_t                 c_cmdbpa;       /* command block phys addr */
        bus_dmamap_t            c_dmam;         /* data buffer DMA map */

        struct scsipi_xfer *    c_xfer;         /* corresponding SCSI xfer */

        u_int8_t                c_stat;         /* status byte copy */
        u_int8_t                c_estat;        /* ext status byte copy */
        int                     c_error;        /* completion error */

        void                    (*c_done)(struct ips_softc *,   /* cmd done */
                                    struct ips_ccb *);          /* callback */

        SLIST_ENTRY(ips_ccb)    c_link;         /* queue link */
};

/* CCB queue */
SLIST_HEAD(ips_ccbq, ips_ccb);

/* DMA-able chunk of memory */
struct dmamem {
        bus_dma_tag_t           dm_tag;
        bus_dmamap_t            dm_map;
        bus_dma_segment_t       dm_seg;
        bus_size_t              dm_size;
        void *                  dm_vaddr;
#define dm_paddr dm_seg.ds_addr
};

struct ips_softc {
        device_t                sc_dev;

        /* SCSI mid-layer connection. */
        struct scsipi_adapter   sc_adapt;

        struct ips_pt {
                struct scsipi_channel   pt_chan;
                int                     pt_nchan;
                struct ips_softc *      pt_sc;

                int                     pt_proctgt;
                char                    pt_procdev[16];
        }                       sc_pt[IPS_MAXCHANS];

        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;
        bus_dma_tag_t           sc_dmat;

        const struct ips_chipset *sc_chip;

        struct ips_info *       sc_info;
        struct dmamem           sc_infom;

        int                     sc_nunits;

        struct dmamem           sc_cmdbm;

        struct ips_ccb *        sc_ccb;
        int                     sc_nccbs;
        struct ips_ccbq         sc_ccbq_free;
        struct kmutex           sc_ccb_mtx;

        struct dmamem           sc_sqm;
        paddr_t                 sc_sqtail;
        u_int32_t *             sc_sqbuf;
        int                     sc_sqidx;
};

int     ips_match(device_t, cfdata_t, void *);
void    ips_attach(device_t, device_t, void *);

void    ips_scsi_cmd(struct ips_ccb *);
void    ips_scsi_pt_cmd(struct scsipi_xfer *);
static void ips_scsipi_request(struct scsipi_channel *,
            scsipi_adapter_req_t, void *);
int     ips_scsi_ioctl(struct scsipi_channel *, u_long, void *,
            int, struct proc *);

#if NBIO > 0
int     ips_ioctl(device_t, u_long, void *);
int     ips_ioctl_inq(struct ips_softc *, struct bioc_inq *);
int     ips_ioctl_vol(struct ips_softc *, struct bioc_vol *);
int     ips_ioctl_disk(struct ips_softc *, struct bioc_disk *);
int     ips_ioctl_setstate(struct ips_softc *, struct bioc_setstate *);
#endif

int     ips_load_xs(struct ips_softc *, struct ips_ccb *, struct scsipi_xfer *);
void    ips_start_xs(struct ips_softc *, struct ips_ccb *, struct scsipi_xfer *);

int     ips_cmd(struct ips_softc *, struct ips_ccb *);
int     ips_poll(struct ips_softc *, struct ips_ccb *);
void    ips_done(struct ips_softc *, struct ips_ccb *);
void    ips_done_xs(struct ips_softc *, struct ips_ccb *);
void    ips_done_pt(struct ips_softc *, struct ips_ccb *);
void    ips_done_mgmt(struct ips_softc *, struct ips_ccb *);
int     ips_error(struct ips_softc *, struct ips_ccb *);
int     ips_error_xs(struct ips_softc *, struct ips_ccb *);
int     ips_intr(void *);
void    ips_timeout(void *);

int     ips_getadapterinfo(struct ips_softc *, int);
int     ips_getdriveinfo(struct ips_softc *, int);
int     ips_getconf(struct ips_softc *, int);
int     ips_getpg5(struct ips_softc *, int);

#if NBIO > 0
int     ips_getrblstat(struct ips_softc *, int);
int     ips_setstate(struct ips_softc *, int, int, int, int);
int     ips_rebuild(struct ips_softc *, int, int, int, int, int);
#endif

void    ips_copperhead_exec(struct ips_softc *, struct ips_ccb *);
void    ips_copperhead_intren(struct ips_softc *);
int     ips_copperhead_isintr(struct ips_softc *);
u_int32_t ips_copperhead_status(struct ips_softc *);

void    ips_morpheus_exec(struct ips_softc *, struct ips_ccb *);
void    ips_morpheus_intren(struct ips_softc *);
int     ips_morpheus_isintr(struct ips_softc *);
u_int32_t ips_morpheus_status(struct ips_softc *);

struct ips_ccb *ips_ccb_alloc(struct ips_softc *, int);
void    ips_ccb_free(struct ips_softc *, struct ips_ccb *, int);
struct ips_ccb *ips_ccb_get(struct ips_softc *);
void    ips_ccb_put(struct ips_softc *, struct ips_ccb *);

int     ips_dmamem_alloc(struct dmamem *, bus_dma_tag_t, bus_size_t);
void    ips_dmamem_free(struct dmamem *);

extern struct  cfdriver ips_cd;

CFATTACH_DECL_NEW(ips, sizeof(struct ips_softc),
    ips_match, ips_attach, NULL, NULL);

static struct ips_ident {
        pci_vendor_id_t vendor;
        pci_product_id_t product;
} const ips_ids[] = {
        { PCI_VENDOR_IBM,       PCI_PRODUCT_IBM_SERVERAID },
        { PCI_VENDOR_IBM,       PCI_PRODUCT_IBM_SERVERAID4 },
        { PCI_VENDOR_ADP2,      PCI_PRODUCT_ADP2_SERVERAID }
};

static const struct ips_chipset {
        enum {
                IPS_CHIP_COPPERHEAD = 0,
                IPS_CHIP_MORPHEUS
        }               ic_id;

        int             ic_bar;

        void            (*ic_exec)(struct ips_softc *, struct ips_ccb *);
        void            (*ic_intren)(struct ips_softc *);
        int             (*ic_isintr)(struct ips_softc *);
        u_int32_t       (*ic_status)(struct ips_softc *);
} ips_chips[] = {
        {
                IPS_CHIP_COPPERHEAD,
                0x14,
                ips_copperhead_exec,
                ips_copperhead_intren,
                ips_copperhead_isintr,
                ips_copperhead_status
        },
        {
                IPS_CHIP_MORPHEUS,
                0x10,
                ips_morpheus_exec,
                ips_morpheus_intren,
                ips_morpheus_isintr,
                ips_morpheus_status
        }
};

#define ips_exec(s, c)  (s)->sc_chip->ic_exec((s), (c))
#define ips_intren(s)   (s)->sc_chip->ic_intren((s))
#define ips_isintr(s)   (s)->sc_chip->ic_isintr((s))
#define ips_status(s)   (s)->sc_chip->ic_status((s))

static const char *ips_names[] = {
        NULL,
        NULL,
        "II",
        "onboard",
        "onboard",
        "3H",
        "3L",
        "4H",
        "4M",
        "4L",
        "4Mx",
        "4Lx",
        "5i",
        "5i",
        "6M",
        "6i",
        "7t",
        "7k",
        "7M"
};

/* Lookup supported device table */
static const struct ips_ident *
ips_lookup(const struct pci_attach_args *pa)
{
        const struct ips_ident *imp;
        int i;

        for (i = 0, imp = ips_ids; i < __arraycount(ips_ids); i++, imp++) {
                if (PCI_VENDOR(pa->pa_id) == imp->vendor &&
                    PCI_PRODUCT(pa->pa_id) == imp->product)
                        return imp;
        }
        return NULL;
}

int
ips_match(device_t parent, cfdata_t cfdata, void *aux)
{
        struct pci_attach_args *pa = aux;

        if (ips_lookup(pa) != NULL)
                return 1;

        return 0;
}

void
ips_attach(device_t parent, device_t self, void *aux)
{
        struct ips_softc *sc = device_private(self);
        struct pci_attach_args *pa = aux;
        struct ips_ccb ccb0;
        struct ips_adapterinfo *ai;
        struct ips_driveinfo *di;
        struct ips_pg5 *pg5;
        pcireg_t maptype;
        bus_size_t iosize;
        pci_intr_handle_t ih;
        const char *intrstr;
        int type, i;
        struct scsipi_adapter *adapt;
        struct scsipi_channel *chan;
        char intrbuf[PCI_INTRSTR_LEN];

        sc->sc_dev = self;
        sc->sc_dmat = pa->pa_dmat;

        /* Identify chipset */
        if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_IBM_SERVERAID)
                sc->sc_chip = &ips_chips[IPS_CHIP_COPPERHEAD];
        else
                sc->sc_chip = &ips_chips[IPS_CHIP_MORPHEUS];

        /* Map registers */
        // XXX check IPS_IOSIZE as old code used to do?
        maptype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, sc->sc_chip->ic_bar);
        if (pci_mapreg_map(pa, sc->sc_chip->ic_bar, maptype, 0, &sc->sc_iot,
            &sc->sc_ioh, NULL, &iosize)) {
                printf(": can't map regs\n");
                return;
        }

        /* Allocate command buffer */
        if (ips_dmamem_alloc(&sc->sc_cmdbm, sc->sc_dmat,
            IPS_MAXCMDS * sizeof(struct ips_cmdb))) {
                printf(": can't alloc cmd buffer\n");
                goto fail1;
        }

        /* Allocate info buffer */
        if (ips_dmamem_alloc(&sc->sc_infom, sc->sc_dmat,
            sizeof(struct ips_info))) {
                printf(": can't alloc info buffer\n");
                goto fail2;
        }
        sc->sc_info = sc->sc_infom.dm_vaddr;
        ai = &sc->sc_info->adapter;
        di = &sc->sc_info->drive;
        pg5 = &sc->sc_info->pg5;

        /* Allocate status queue for the Copperhead chipset */
        if (sc->sc_chip->ic_id == IPS_CHIP_COPPERHEAD) {
                if (ips_dmamem_alloc(&sc->sc_sqm, sc->sc_dmat, IPS_SQSZ)) {
                        printf(": can't alloc status queue\n");
                        goto fail3;
                }
                sc->sc_sqtail = sc->sc_sqm.dm_paddr;
                sc->sc_sqbuf = sc->sc_sqm.dm_vaddr;
                sc->sc_sqidx = 0;
                bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_SQS,
                    sc->sc_sqm.dm_paddr);
                bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_SQE,
                    sc->sc_sqm.dm_paddr + IPS_SQSZ);
                bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_SQH,
                    sc->sc_sqm.dm_paddr + sizeof(u_int32_t));
                bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_SQT,
                    sc->sc_sqm.dm_paddr);
        }

        /* Bootstrap CCB queue */
        sc->sc_nccbs = 1;
        sc->sc_ccb = &ccb0;
        bzero(&ccb0, sizeof(ccb0));
        ccb0.c_cmdbva = sc->sc_cmdbm.dm_vaddr;
        ccb0.c_cmdbpa = sc->sc_cmdbm.dm_paddr;
        SLIST_INIT(&sc->sc_ccbq_free);
        SLIST_INSERT_HEAD(&sc->sc_ccbq_free, &ccb0, c_link);
        mutex_init(&sc->sc_ccb_mtx, MUTEX_DEFAULT, IPL_BIO);

        /* Get adapter info */
        if (ips_getadapterinfo(sc, XS_CTL_NOSLEEP)) {
                printf(": can't get adapter info\n");
                goto fail4;
        }

        /* Get logical drives info */
        if (ips_getdriveinfo(sc, XS_CTL_NOSLEEP)) {
                printf(": can't get ld info\n");
                goto fail4;
        }
        sc->sc_nunits = di->drivecnt;

        /* Get configuration */
        if (ips_getconf(sc, XS_CTL_NOSLEEP)) {
                printf(": can't get config\n");
                goto fail4;
        }

        /* Read NVRAM page 5 for additional info */
        (void)ips_getpg5(sc, XS_CTL_NOSLEEP);

        /* Initialize CCB queue */
        sc->sc_nccbs = ai->cmdcnt;
        if ((sc->sc_ccb = ips_ccb_alloc(sc, sc->sc_nccbs)) == NULL) {
                printf(": can't alloc ccb queue\n");
                goto fail4;
        }
        SLIST_INIT(&sc->sc_ccbq_free);
        for (i = 0; i < sc->sc_nccbs; i++)
                SLIST_INSERT_HEAD(&sc->sc_ccbq_free,
                    &sc->sc_ccb[i], c_link);

        /* Install interrupt handler */
        if (pci_intr_map(pa, &ih)) {
                printf(": can't map interrupt\n");
                goto fail5;
        }
        intrstr = pci_intr_string(pa->pa_pc, ih, intrbuf, sizeof(intrbuf));
        if (pci_intr_establish_xname(pa->pa_pc, ih, IPL_BIO, ips_intr, sc,
                device_xname(sc->sc_dev)) == NULL) {
                printf(": can't establish interrupt");
                if (intrstr != NULL)
                        printf(" at %s", intrstr);
                printf("\n");
                goto fail5;
        }
        printf(": %s\n", intrstr);

        /* Display adapter info */
        device_printf(sc->sc_dev, "ServeRAID");
        type = htole16(pg5->type);
        if (type < sizeof(ips_names) / sizeof(ips_names[0]) && ips_names[type])
                printf(" %s", ips_names[type]);
        printf(", FW %c%c%c%c%c%c%c", ai->firmware[0], ai->firmware[1],
            ai->firmware[2], ai->firmware[3], ai->firmware[4], ai->firmware[5],
            ai->firmware[6]);
        printf(", BIOS %c%c%c%c%c%c%c", ai->bios[0], ai->bios[1], ai->bios[2],
            ai->bios[3], ai->bios[4], ai->bios[5], ai->bios[6]);
        printf(", %d cmds, %d LD%s", sc->sc_nccbs, sc->sc_nunits,
            (sc->sc_nunits == 1 ? "" : "s"));
        printf("\n");

        /*
         * Attach to scsipi.
         */
        adapt = &sc->sc_adapt;
        memset(adapt, 0, sizeof(*adapt));
        adapt->adapt_dev = self;
        adapt->adapt_nchannels = IPS_MAXCHANS;
        if (sc->sc_nunits > 0)
                adapt->adapt_openings = sc->sc_nccbs / sc->sc_nunits;
        adapt->adapt_max_periph = adapt->adapt_openings;
        adapt->adapt_request = ips_scsipi_request;
        adapt->adapt_minphys = minphys;
        adapt->adapt_ioctl = ips_scsi_ioctl;

        /* For each channel attach SCSI pass-through bus */
        for (i = 0; i < IPS_MAXCHANS; i++) {
                struct ips_pt *pt;
                int target, lastarget;

                pt = &sc->sc_pt[i];
                pt->pt_sc = sc;
                pt->pt_nchan = i;
                pt->pt_proctgt = -1;

                /* Check if channel has any devices besides disks */
                for (target = 0, lastarget = -1; target < IPS_MAXTARGETS;
                    target++) {
                        struct ips_dev *idev;
                        int dev_type;

                        idev = &sc->sc_info->conf.dev[i][target];
                        dev_type = idev->params & SID_TYPE;
                        if (idev->state && dev_type != T_DIRECT) {
                                lastarget = target;
                                if (type == T_PROCESSOR ||
                                    type == T_ENCLOSURE)
                                        /* remember enclosure address */
                                        pt->pt_proctgt = target;
                        }
                }
                if (lastarget == -1)
                        continue;

                chan = &pt->pt_chan;
                memset(chan, 0, sizeof(*chan));
                chan->chan_adapter = adapt;
                chan->chan_bustype = &scsi_bustype;
                chan->chan_channel = i;
                chan->chan_ntargets = IPS_MAXTARGETS;
                chan->chan_nluns = lastarget + 1;
                chan->chan_id = i;
                chan->chan_flags = SCSIPI_CHAN_NOSETTLE;
                config_found(self, chan, scsiprint, CFARGS_NONE);
        }

        /* Enable interrupts */
        ips_intren(sc);

#if NBIO > 0
        /* Install ioctl handler */
        if (bio_register(sc->sc_dev, ips_ioctl))
                device_printf(sc->sc_dev, "no ioctl support\n");
#endif

        return;
fail5:
        ips_ccb_free(sc, sc->sc_ccb, sc->sc_nccbs);
fail4:
        if (sc->sc_chip->ic_id == IPS_CHIP_COPPERHEAD)
                ips_dmamem_free(&sc->sc_sqm);
fail3:
        ips_dmamem_free(&sc->sc_infom);
fail2:
        ips_dmamem_free(&sc->sc_cmdbm);
fail1:
        bus_space_unmap(sc->sc_iot, sc->sc_ioh, iosize);
}

void
ips_scsi_cmd(struct ips_ccb *ccb)
{
        struct scsipi_xfer *xs = ccb->c_xfer;
        struct scsipi_periph *periph = xs->xs_periph;
        struct scsipi_channel *chan = periph->periph_channel;
        struct ips_softc *sc = device_private(chan->chan_adapter->adapt_dev);
        struct ips_driveinfo *di = &sc->sc_info->drive;
        struct ips_drive *drive;
        struct ips_cmd *cmd;
        int target = periph->periph_target;
        u_int32_t blkno, blkcnt;
        int code;

        DPRINTF(IPS_D_XFER, ("%s: ips_scsi_cmd: xs %p, target %d, "
            "opcode 0x%02x, flags 0x%x\n", device_xname(sc->sc_dev), xs, target,
            xs->cmd->opcode, xs->xs_control));

        if (target >= sc->sc_nunits || periph->periph_lun != 0) {
                DPRINTF(IPS_D_INFO, ("%s: ips_scsi_cmd: invalid params "
                    "target %d, lun %d\n", device_xname(sc->sc_dev),
                    target, periph->periph_lun));
                xs->error = XS_DRIVER_STUFFUP;
                ips_ccb_put(sc, ccb);
                scsipi_done(xs);
                return;
        }

        drive = &di->drive[target];
        xs->error = XS_NOERROR;

        /* Fake SCSI commands */
        switch (xs->cmd->opcode) {
        case READ_10:
        case SCSI_READ_6_COMMAND:
        case WRITE_10:
        case SCSI_WRITE_6_COMMAND: {
                struct scsi_rw_6 *rw;
                struct scsipi_rw_10 *rwb;

                if (xs->cmdlen == sizeof(struct scsi_rw_6)) {
                        rw = (void *)xs->cmd;
                        blkno = _3btol(rw->addr) &
                            (SRW_TOPADDR << 16 | 0xffff);
                        blkcnt = rw->length ? rw->length : 0x100;
                } else {
                        rwb = (void *)xs->cmd;
                        blkno = _4btol(rwb->addr);
                        blkcnt = _2btol(rwb->length);
                }

                if (blkno >= htole32(drive->seccnt) || blkno + blkcnt >
                    htole32(drive->seccnt)) {
                        DPRINTF(IPS_D_ERR, ("%s: ips_scsi_cmd: invalid params "
                            "blkno %u, blkcnt %u\n", device_xname(sc->sc_dev),
                            blkno, blkcnt));
                        xs->error = XS_DRIVER_STUFFUP;
                        break;
                }

                if (xs->xs_control & XS_CTL_DATA_IN)
                        code = IPS_CMD_READ;
                else
                        code = IPS_CMD_WRITE;

                cmd = ccb->c_cmdbva;
                cmd->code = code;
                cmd->drive = target;
                cmd->lba = htole32(blkno);
                cmd->seccnt = htole16(blkcnt);

                if (ips_load_xs(sc, ccb, xs)) {
                        DPRINTF(IPS_D_ERR, ("%s: ips_scsi_cmd: ips_load_xs "
                            "failed\n", device_xname(sc->sc_dev)));
                        xs->error = XS_DRIVER_STUFFUP;
                        ips_ccb_put(sc, ccb);
                        scsipi_done(xs);
                        return;
                }

                if (cmd->sgcnt > 0)
                        cmd->code |= IPS_CMD_SG;

                ccb->c_done = ips_done_xs;
                ips_start_xs(sc, ccb, xs);
                return;
        }
        case INQUIRY: {
                struct scsipi_inquiry_data inq;

                bzero(&inq, sizeof(inq));
                inq.device = T_DIRECT;
                inq.version = 2;
                inq.response_format = 2;
                inq.additional_length = 32;
                inq.flags3 |= SID_CmdQue;
                strlcpy(inq.vendor, "IBM", sizeof(inq.vendor));
                snprintf(inq.product, sizeof(inq.product),
                    "LD%d RAID%d", target, drive->raid);
                strlcpy(inq.revision, "1.0", sizeof(inq.revision));
                memcpy(xs->data, &inq, MIN(xs->datalen, sizeof(inq)));
                break;
        }
        case READ_CAPACITY_10: {
                struct scsipi_read_capacity_10_data rcd;

                bzero(&rcd, sizeof(rcd));
                _lto4b(htole32(drive->seccnt) - 1, rcd.addr);
                _lto4b(IPS_SECSZ, rcd.length);
                memcpy(xs->data, &rcd, MIN(xs->datalen, sizeof(rcd)));
                break;
        }
        case SCSI_REQUEST_SENSE: {
                struct scsi_sense_data sd;

                bzero(&sd, sizeof(sd));
                sd.response_code = SSD_RCODE_CURRENT;
                sd.flags = SKEY_NO_SENSE;
                memcpy(xs->data, &sd, MIN(xs->datalen, sizeof(sd)));
                break;
        }
        case SCSI_SYNCHRONIZE_CACHE_10:
                cmd = ccb->c_cmdbva;
                cmd->code = IPS_CMD_FLUSH;

                ccb->c_done = ips_done_xs;
                ips_start_xs(sc, ccb, xs);
                return;
        case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
        case START_STOP:
        case SCSI_TEST_UNIT_READY:
                break;
        default:
                DPRINTF(IPS_D_INFO, ("%s: unsupported scsi command 0x%02x\n",
                    device_xname(sc->sc_dev), xs->cmd->opcode));
                xs->error = XS_DRIVER_STUFFUP;
        }

        ips_ccb_put(sc, ccb);
        scsipi_done(xs);
}

/*
 * Start a SCSI command.
 */
static void
ips_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req,
                   void *arg)
{
        switch (req) {
        case ADAPTER_REQ_RUN_XFER: {
                struct ips_ccb *ccb;
                struct scsipi_xfer *xs;
                struct ips_softc *sc;

                sc = device_private(chan->chan_adapter->adapt_dev);
                xs = (struct scsipi_xfer *)arg;

                if ((ccb = ips_ccb_get(sc)) == NULL) {
                        xs->error = XS_RESOURCE_SHORTAGE;
                        scsipi_done(xs);
                        break;
                }

                ccb->c_xfer = xs;
                ips_scsi_cmd(ccb);

                break;
        }

        case ADAPTER_REQ_SET_XFER_MODE: {
                struct scsipi_xfer_mode *xm = arg;
                xm->xm_mode = PERIPH_CAP_TQING;
                xm->xm_period = 0;
                xm->xm_offset = 0;
                scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, xm);
                return;
        }

        case ADAPTER_REQ_GROW_RESOURCES:
                /*
                 * Not supported.
                 */
                break;
        }
}

int
ips_scsi_ioctl(struct scsipi_channel *chan, u_long cmd, void *data,
    int flag, struct proc *p)
{
#if NBIO > 0
        return (ips_ioctl(chan->chan_adapter->adapt_dev, cmd, data));
#else
        return (ENOTTY);
#endif
}

#if NBIO > 0
int
ips_ioctl(device_t dev, u_long cmd, void *data)
{
        struct ips_softc *sc = device_private(dev);

        DPRINTF(IPS_D_INFO, ("%s: ips_ioctl: cmd %lu\n",
            device_xname(sc->sc_dev), cmd));

        switch (cmd) {
        case BIOCINQ:
                return (ips_ioctl_inq(sc, (struct bioc_inq *)data));
        case BIOCVOL:
                return (ips_ioctl_vol(sc, (struct bioc_vol *)data));
        case BIOCDISK:
                return (ips_ioctl_disk(sc, (struct bioc_disk *)data));
        case BIOCSETSTATE:
                return (ips_ioctl_setstate(sc, (struct bioc_setstate *)data));
        default:
                return (ENOTTY);
        }
}

int
ips_ioctl_inq(struct ips_softc *sc, struct bioc_inq *bi)
{
        struct ips_conf *conf = &sc->sc_info->conf;
        int i;

        strlcpy(bi->bi_dev, device_xname(sc->sc_dev), sizeof(bi->bi_dev));
        bi->bi_novol = sc->sc_nunits;
        for (i = 0, bi->bi_nodisk = 0; i < sc->sc_nunits; i++)
                bi->bi_nodisk += conf->ld[i].chunkcnt;

        DPRINTF(IPS_D_INFO, ("%s: ips_ioctl_inq: novol %d, nodisk %d\n",
            bi->bi_dev, bi->bi_novol, bi->bi_nodisk));

        return (0);
}

int
ips_ioctl_vol(struct ips_softc *sc, struct bioc_vol *bv)
{
        struct ips_driveinfo *di = &sc->sc_info->drive;
        struct ips_conf *conf = &sc->sc_info->conf;
        struct ips_rblstat *rblstat = &sc->sc_info->rblstat;
        struct ips_ld *ld;
        int vid = bv->bv_volid;
        device_t dv;
        int error, rebuild = 0;
        u_int32_t total = 0, done = 0;

        if (vid >= sc->sc_nunits)
                return (EINVAL);
        if ((error = ips_getconf(sc, 0)))
                return (error);
        ld = &conf->ld[vid];

        switch (ld->state) {
        case IPS_DS_ONLINE:
                bv->bv_status = BIOC_SVONLINE;
                break;
        case IPS_DS_DEGRADED:
                bv->bv_status = BIOC_SVDEGRADED;
                rebuild++;
                break;
        case IPS_DS_OFFLINE:
                bv->bv_status = BIOC_SVOFFLINE;
                break;
        default:
                bv->bv_status = BIOC_SVINVALID;
        }

        if (rebuild && ips_getrblstat(sc, 0) == 0) {
                total = htole32(rblstat->ld[vid].total);
                done = total - htole32(rblstat->ld[vid].remain);
                if (total && total > done) {
                        bv->bv_status = BIOC_SVREBUILD;
                        bv->bv_percent = 100 * done / total;
                }
        }

        bv->bv_size = (uint64_t)htole32(ld->size) * IPS_SECSZ;
        bv->bv_level = di->drive[vid].raid;
        bv->bv_nodisk = ld->chunkcnt;

        /* Associate all unused and spare drives with first volume */
        if (vid == 0) {
                struct ips_dev *dev;
                int chan, target;

                for (chan = 0; chan < IPS_MAXCHANS; chan++)
                        for (target = 0; target < IPS_MAXTARGETS; target++) {
                                dev = &conf->dev[chan][target];
                                if (dev->state && !(dev->state &
                                    IPS_DVS_MEMBER) &&
                                    (dev->params & SID_TYPE) == T_DIRECT)
                                        bv->bv_nodisk++;
                        }
        }

        dv = sc->sc_dev;
        strlcpy(bv->bv_dev, device_xname(dv), sizeof(bv->bv_dev));
        strlcpy(bv->bv_vendor, "IBM", sizeof(bv->bv_vendor));

        DPRINTF(IPS_D_INFO, ("%s: ips_ioctl_vol: vid %d, state 0x%02x, "
            "total %u, done %u, size %llu, level %d, nodisk %d, dev %s\n",
            device_xname(sc->sc_dev), vid, ld->state, total, done, bv->bv_size,
            bv->bv_level, bv->bv_nodisk, bv->bv_dev));

        return (0);
}

int
ips_ioctl_disk(struct ips_softc *sc, struct bioc_disk *bd)
{
        struct ips_conf *conf = &sc->sc_info->conf;
        struct ips_ld *ld;
        struct ips_chunk *chunk;
        struct ips_dev *dev;
        int vid = bd->bd_volid, did = bd->bd_diskid;
        int chan, target, error, i;

        if (vid >= sc->sc_nunits)
                return (EINVAL);
        if ((error = ips_getconf(sc, 0)))
                return (error);
        ld = &conf->ld[vid];

        if (did >= ld->chunkcnt) {
                /* Probably unused or spare drives */
                if (vid != 0)
                        return (EINVAL);

                i = ld->chunkcnt;
                for (chan = 0; chan < IPS_MAXCHANS; chan++)
                        for (target = 0; target < IPS_MAXTARGETS; target++) {
                                dev = &conf->dev[chan][target];
                                if (dev->state && !(dev->state &
                                    IPS_DVS_MEMBER) &&
                                    (dev->params & SID_TYPE) == T_DIRECT)
                                        if (i++ == did)
                                                goto out;
                        }
        } else {
                chunk = &ld->chunk[did];
                chan = chunk->channel;
                target = chunk->target;
        }

out:
        if (chan >= IPS_MAXCHANS || target >= IPS_MAXTARGETS)
                return (EINVAL);
        dev = &conf->dev[chan][target];

        bd->bd_channel = chan;
        bd->bd_target = target;
        bd->bd_lun = 0;
        bd->bd_size = (uint64_t)htole32(dev->seccnt) * IPS_SECSZ;

        bzero(bd->bd_vendor, sizeof(bd->bd_vendor));
        memcpy(bd->bd_vendor, dev->devid, MIN(sizeof(bd->bd_vendor),
            sizeof(dev->devid)));
        strlcpy(bd->bd_procdev, sc->sc_pt[chan].pt_procdev,
            sizeof(bd->bd_procdev));

        if (dev->state & IPS_DVS_READY) {
                bd->bd_status = BIOC_SDUNUSED;
                if (dev->state & IPS_DVS_MEMBER)
                        bd->bd_status = BIOC_SDONLINE;
                if (dev->state & IPS_DVS_SPARE)
                        bd->bd_status = BIOC_SDHOTSPARE;
                if (dev->state & IPS_DVS_REBUILD)
                        bd->bd_status = BIOC_SDREBUILD;
        } else {
                bd->bd_status = BIOC_SDOFFLINE;
        }

        DPRINTF(IPS_D_INFO, ("%s: ips_ioctl_disk: vid %d, did %d, channel %d, "
            "target %d, size %llu, state 0x%02x\n", device_xname(sc->sc_dev),
            vid, did, bd->bd_channel, bd->bd_target, bd->bd_size, dev->state));

        return (0);
}

int
ips_ioctl_setstate(struct ips_softc *sc, struct bioc_setstate *bs)
{
        struct ips_conf *conf = &sc->sc_info->conf;
        struct ips_dev *dev;
        int state, error;

        if (bs->bs_channel >= IPS_MAXCHANS || bs->bs_target >= IPS_MAXTARGETS)
                return (EINVAL);
        if ((error = ips_getconf(sc, 0)))
                return (error);
        dev = &conf->dev[bs->bs_channel][bs->bs_target];
        state = dev->state;

        switch (bs->bs_status) {
        case BIOC_SSONLINE:
                state |= IPS_DVS_READY;
                break;
        case BIOC_SSOFFLINE:
                state &= ~IPS_DVS_READY;
                break;
        case BIOC_SSHOTSPARE:
                state |= IPS_DVS_SPARE;
                break;
        case BIOC_SSREBUILD:
                return (ips_rebuild(sc, bs->bs_channel, bs->bs_target,
                    bs->bs_channel, bs->bs_target, 0));
        default:
                return (EINVAL);
        }

        return (ips_setstate(sc, bs->bs_channel, bs->bs_target, state, 0));
}
#endif  /* NBIO > 0 */

int
ips_load_xs(struct ips_softc *sc, struct ips_ccb *ccb, struct scsipi_xfer *xs)
{
        struct ips_cmdb *cmdb = ccb->c_cmdbva;
        struct ips_cmd *cmd = &cmdb->cmd;
        struct ips_sg *sg = cmdb->sg;
        int nsegs, i;

        if (xs->datalen == 0)
                return (0);

        /* Map data buffer into DMA segments */
        if (bus_dmamap_load(sc->sc_dmat, ccb->c_dmam, xs->data, xs->datalen,
            NULL, (xs->xs_control & XS_CTL_NOSLEEP ? BUS_DMA_NOWAIT : 0)))
                return (1);
        bus_dmamap_sync(sc->sc_dmat, ccb->c_dmam, 0,ccb->c_dmam->dm_mapsize,
            xs->xs_control & XS_CTL_DATA_IN ? BUS_DMASYNC_PREREAD :
            BUS_DMASYNC_PREWRITE);

        if ((nsegs = ccb->c_dmam->dm_nsegs) > IPS_MAXSGS)
                return (1);

        if (nsegs > 1) {
                cmd->sgcnt = nsegs;
                cmd->sgaddr = htole32(ccb->c_cmdbpa + offsetof(struct ips_cmdb,
                    sg));

                /* Fill in scatter-gather array */
                for (i = 0; i < nsegs; i++) {
                        sg[i].addr = htole32(ccb->c_dmam->dm_segs[i].ds_addr);
                        sg[i].size = htole32(ccb->c_dmam->dm_segs[i].ds_len);
                }
        } else {
                cmd->sgcnt = 0;
                cmd->sgaddr = htole32(ccb->c_dmam->dm_segs[0].ds_addr);
        }

        return (0);
}

void
ips_start_xs(struct ips_softc *sc, struct ips_ccb *ccb, struct scsipi_xfer *xs)
{
        ccb->c_flags = xs->xs_control;
        ccb->c_xfer = xs;
        int ispoll = xs->xs_control & XS_CTL_POLL;

        if (!ispoll) {
                int timeout = mstohz(xs->timeout);
                if (timeout == 0)
                        timeout = 1;

                callout_reset(&xs->xs_callout, timeout, ips_timeout, ccb);
        }

        /*
         * Return value not used here because ips_cmd() must complete
         * scsipi_xfer on any failure and SCSI layer will handle possible
         * errors.
         */
        ips_cmd(sc, ccb);
}

int
ips_cmd(struct ips_softc *sc, struct ips_ccb *ccb)
{
        struct ips_cmd *cmd = ccb->c_cmdbva;
        int s, error = 0;

        DPRINTF(IPS_D_XFER, ("%s: ips_cmd: id 0x%02x, flags 0x%x, xs %p, "
            "code 0x%02x, drive %d, sgcnt %d, lba %d, sgaddr 0x%08x, "
            "seccnt %d\n", device_xname(sc->sc_dev), ccb->c_id, ccb->c_flags,
            ccb->c_xfer, cmd->code, cmd->drive, cmd->sgcnt, htole32(cmd->lba),
            htole32(cmd->sgaddr), htole16(cmd->seccnt)));

        cmd->id = ccb->c_id;

        /* Post command to controller and optionally wait for completion */
        s = splbio();
        ips_exec(sc, ccb);
        ccb->c_state = IPS_CCB_QUEUED;
        if (ccb->c_flags & XS_CTL_POLL)
                error = ips_poll(sc, ccb);
        splx(s);

        return (error);
}

int
ips_poll(struct ips_softc *sc, struct ips_ccb *ccb)
{
        struct timeval tv;
        int error, timo;

        if (ccb->c_flags & XS_CTL_NOSLEEP) {
                /* busy-wait */
                DPRINTF(IPS_D_XFER, ("%s: ips_poll: busy-wait\n",
                    device_xname(sc->sc_dev)));

                for (timo = 10000; timo > 0; timo--) {
                        delay(100);
                        ips_intr(sc);
                        if (ccb->c_state == IPS_CCB_DONE)
                                break;
                }
        } else {
                /* sleep */
                timo = ccb->c_xfer ? ccb->c_xfer->timeout : IPS_TIMEOUT;
                tv.tv_sec = timo / 1000;
                tv.tv_usec = (timo % 1000) * 1000;
                timo = tvtohz(&tv);

                DPRINTF(IPS_D_XFER, ("%s: ips_poll: sleep %d hz\n",
                    device_xname(sc->sc_dev), timo));
                tsleep(ccb, PRIBIO + 1, "ipscmd", timo);
        }
        DPRINTF(IPS_D_XFER, ("%s: ips_poll: state %d\n",
            device_xname(sc->sc_dev),
            ccb->c_state));

        if (ccb->c_state != IPS_CCB_DONE)
                /*
                 * Command never completed. Fake hardware status byte
                 * to indicate timeout.
                 */
                ccb->c_stat = IPS_STAT_TIMO;

        ips_done(sc, ccb);
        error = ccb->c_error;

        return (error);
}

void
ips_done(struct ips_softc *sc, struct ips_ccb *ccb)
{
        DPRINTF(IPS_D_XFER, ("%s: ips_done: id 0x%02x, flags 0x%x, xs %p\n",
            device_xname(sc->sc_dev), ccb->c_id, ccb->c_flags, ccb->c_xfer));

        ccb->c_error = ips_error(sc, ccb);
        ccb->c_done(sc, ccb);
}

void
ips_done_xs(struct ips_softc *sc, struct ips_ccb *ccb)
{
        struct scsipi_xfer *xs = ccb->c_xfer;

        if (!(xs->xs_control & XS_CTL_POLL))
                callout_stop(&xs->xs_callout);

        if (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) {
                bus_dmamap_sync(sc->sc_dmat, ccb->c_dmam, 0,
                    ccb->c_dmam->dm_mapsize, xs->xs_control & XS_CTL_DATA_IN ?
                    BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
                bus_dmamap_unload(sc->sc_dmat, ccb->c_dmam);
        }

        xs->resid = 0;
        xs->error = ips_error_xs(sc, ccb);
        ips_ccb_put(sc, ccb);
        scsipi_done(xs);
}

void
ips_done_pt(struct ips_softc *sc, struct ips_ccb *ccb)
{
        struct scsipi_xfer *xs = ccb->c_xfer;
        struct ips_cmdb *cmdb = ccb->c_cmdbva;
        struct ips_dcdb *dcdb = &cmdb->dcdb;
        int done = htole16(dcdb->datalen);

        if (!(xs->xs_control & XS_CTL_POLL))
                callout_stop(&xs->xs_callout);

        if (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) {
                bus_dmamap_sync(sc->sc_dmat, ccb->c_dmam, 0,
                    ccb->c_dmam->dm_mapsize, xs->xs_control & XS_CTL_DATA_IN ?
                    BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
                bus_dmamap_unload(sc->sc_dmat, ccb->c_dmam);
        }

        if (done && done < xs->datalen)
                xs->resid = xs->datalen - done;
        else
                xs->resid = 0;
        xs->error = ips_error_xs(sc, ccb);
        xs->status = dcdb->status;

        if (xs->error == XS_SENSE)
                memcpy(&xs->sense, dcdb->sense, MIN(sizeof(xs->sense),
                    sizeof(dcdb->sense)));

        if (xs->cmd->opcode == INQUIRY && xs->error == XS_NOERROR) {
                int type = ((struct scsipi_inquiry_data *)xs->data)->device &
                    SID_TYPE;

                if (type == T_DIRECT)
                        /* mask physical drives */
                        xs->error = XS_DRIVER_STUFFUP;
        }

        ips_ccb_put(sc, ccb);
        scsipi_done(xs);
}

void
ips_done_mgmt(struct ips_softc *sc, struct ips_ccb *ccb)
{
        if (ccb->c_flags & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT))
                bus_dmamap_sync(sc->sc_dmat, sc->sc_infom.dm_map, 0,
                    sc->sc_infom.dm_map->dm_mapsize,
                    ccb->c_flags & XS_CTL_DATA_IN ? BUS_DMASYNC_POSTREAD :
                    BUS_DMASYNC_POSTWRITE);

        ips_ccb_put(sc, ccb);
}

int
ips_error(struct ips_softc *sc, struct ips_ccb *ccb)
{
        struct ips_cmdb *cmdb = ccb->c_cmdbva;
        struct ips_cmd *cmd = &cmdb->cmd;
        struct ips_dcdb *dcdb = &cmdb->dcdb;
        struct scsipi_xfer *xs = ccb->c_xfer;
        u_int8_t gsc = IPS_STAT_GSC(ccb->c_stat);

        if (gsc == IPS_STAT_OK)
                return (0);

        DPRINTF(IPS_D_ERR, ("%s: ips_error: stat 0x%02x, estat 0x%02x, "
            "cmd code 0x%02x, drive %d, sgcnt %d, lba %u, seccnt %d",
            device_xname(sc->sc_dev), ccb->c_stat, ccb->c_estat, cmd->code,
            cmd->drive, cmd->sgcnt, htole32(cmd->lba), htole16(cmd->seccnt)));
        if (cmd->code == IPS_CMD_DCDB || cmd->code == IPS_CMD_DCDB_SG) {
                int i;

                DPRINTF(IPS_D_ERR, (", dcdb device 0x%02x, attr 0x%02x, "
                    "datalen %d, sgcnt %d, status 0x%02x",
                    dcdb->device, dcdb->attr, htole16(dcdb->datalen),
                    dcdb->sgcnt, dcdb->status));

                DPRINTF(IPS_D_ERR, (", cdb"));
                for (i = 0; i < dcdb->cdblen; i++)
                        DPRINTF(IPS_D_ERR, (" %x", dcdb->cdb[i]));
                if (ccb->c_estat == IPS_ESTAT_CKCOND) {
                        DPRINTF(IPS_D_ERR, (", sense"));
                        for (i = 0; i < dcdb->senselen; i++)
                                DPRINTF(IPS_D_ERR, (" %x", dcdb->sense[i]));
                }
        }               
        DPRINTF(IPS_D_ERR, ("\n"));

        switch (gsc) {
        case IPS_STAT_RECOV:
                return (0);
        case IPS_STAT_INVOP:
        case IPS_STAT_INVCMD:
        case IPS_STAT_INVPARM:
                return (EINVAL);
        case IPS_STAT_BUSY:
                return (EBUSY);
        case IPS_STAT_TIMO:
                return (ETIMEDOUT);
        case IPS_STAT_PDRVERR:
                switch (ccb->c_estat) {
                case IPS_ESTAT_SELTIMO:
                        return (ENODEV);
                case IPS_ESTAT_OURUN:
                        if (xs && htole16(dcdb->datalen) < xs->datalen)
                                /* underrun */
                                return (0);
                        break;
                case IPS_ESTAT_RECOV:
                        return (0);
                }
                break;
        }

        return (EIO);
}

int
ips_error_xs(struct ips_softc *sc, struct ips_ccb *ccb)
{
        struct ips_cmdb *cmdb = ccb->c_cmdbva;
        struct ips_dcdb *dcdb = &cmdb->dcdb;
        struct scsipi_xfer *xs = ccb->c_xfer;
        u_int8_t gsc = IPS_STAT_GSC(ccb->c_stat);

        /* Map hardware error codes to SCSI ones */
        switch (gsc) {
        case IPS_STAT_OK:
        case IPS_STAT_RECOV:
                return (XS_NOERROR);
        case IPS_STAT_BUSY:
                return (XS_BUSY);
        case IPS_STAT_TIMO:
                return (XS_TIMEOUT);
        case IPS_STAT_PDRVERR:
                switch (ccb->c_estat) {
                case IPS_ESTAT_SELTIMO:
                        return (XS_SELTIMEOUT);
                case IPS_ESTAT_OURUN:
                        if (xs && htole16(dcdb->datalen) < xs->datalen)
                                /* underrun */
                                return (XS_NOERROR);
                        break;
                case IPS_ESTAT_HOSTRST:
                case IPS_ESTAT_DEVRST:
                        return (XS_RESET);
                case IPS_ESTAT_RECOV:
                        return (XS_NOERROR);
                case IPS_ESTAT_CKCOND:
                        return (XS_SENSE);
                }
                break;
        }

        return (XS_DRIVER_STUFFUP);
}

int
ips_intr(void *arg)
{
        struct ips_softc *sc = arg;
        struct ips_ccb *ccb;
        u_int32_t status;
        int id;

        DPRINTF(IPS_D_XFER, ("%s: ips_intr", device_xname(sc->sc_dev)));
        if (!ips_isintr(sc)) {
                DPRINTF(IPS_D_XFER, (": not ours\n"));
                return (0);
        }
        DPRINTF(IPS_D_XFER, ("\n"));

        /* Process completed commands */
        while ((status = ips_status(sc)) != 0xffffffff) {
                DPRINTF(IPS_D_XFER, ("%s: ips_intr: status 0x%08x\n",
                    device_xname(sc->sc_dev), status));

                id = IPS_STAT_ID(status);
                if (id >= sc->sc_nccbs) {
                        DPRINTF(IPS_D_ERR, ("%s: ips_intr: invalid id %d\n",
                            device_xname(sc->sc_dev), id));
                        continue;
                }

                ccb = &sc->sc_ccb[id];
                if (ccb->c_state != IPS_CCB_QUEUED) {
                        DPRINTF(IPS_D_ERR, ("%s: ips_intr: cmd 0x%02x not "
                            "queued, state %d, status 0x%08x\n",
                            device_xname(sc->sc_dev), ccb->c_id, ccb->c_state,
                            status));
                        continue;
                }

                ccb->c_state = IPS_CCB_DONE;
                ccb->c_stat = IPS_STAT_BASIC(status);
                ccb->c_estat = IPS_STAT_EXT(status);

                if (ccb->c_flags & XS_CTL_POLL) {
                        wakeup(ccb);
                } else {
                        ips_done(sc, ccb);
                }
        }

        return (1);
}

void
ips_timeout(void *arg)
{
        struct ips_ccb *ccb = arg;
        struct ips_softc *sc = ccb->c_sc;
        struct scsipi_xfer *xs = ccb->c_xfer;
        int s;

        s = splbio();
        if (xs)
                scsi_print_addr(xs->xs_periph);
        else
                printf("%s: ", device_xname(sc->sc_dev));
        printf("timeout\n");

        /*
         * Command never completed. Fake hardware status byte
         * to indicate timeout.
         * XXX: need to remove command from controller.
         */
        ccb->c_stat = IPS_STAT_TIMO;
        ips_done(sc, ccb);
        splx(s);
}

int
ips_getadapterinfo(struct ips_softc *sc, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_DATA_IN | XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_GETADAPTERINFO;
        cmd->sgaddr = htole32(sc->sc_infom.dm_paddr + offsetof(struct ips_info,
            adapter));

        return (ips_cmd(sc, ccb));
}

int
ips_getdriveinfo(struct ips_softc *sc, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_DATA_IN | XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_GETDRIVEINFO;
        cmd->sgaddr = htole32(sc->sc_infom.dm_paddr + offsetof(struct ips_info,
            drive));

        return (ips_cmd(sc, ccb));
}

int
ips_getconf(struct ips_softc *sc, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_DATA_IN | XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_READCONF;
        cmd->sgaddr = htole32(sc->sc_infom.dm_paddr + offsetof(struct ips_info,
            conf));

        return (ips_cmd(sc, ccb));
}

int
ips_getpg5(struct ips_softc *sc, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_DATA_IN | XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_RWNVRAM;
        cmd->drive = 5;
        cmd->sgaddr = htole32(sc->sc_infom.dm_paddr + offsetof(struct ips_info,
            pg5));

        return (ips_cmd(sc, ccb));
}

#if NBIO > 0
int
ips_getrblstat(struct ips_softc *sc, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_DATA_IN | XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_REBUILDSTATUS;
        cmd->sgaddr = htole32(sc->sc_infom.dm_paddr + offsetof(struct ips_info,
            rblstat));

        return (ips_cmd(sc, ccb));
}

int
ips_setstate(struct ips_softc *sc, int chan, int target, int state, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_SETSTATE;
        cmd->drive = chan;
        cmd->sgcnt = target;
        cmd->seg4g = state;

        return (ips_cmd(sc, ccb));
}

int
ips_rebuild(struct ips_softc *sc, int chan, int target, int nchan,
    int ntarget, int flags)
{
        struct ips_ccb *ccb;
        struct ips_cmd *cmd;

        ccb = ips_ccb_get(sc);
        if (ccb == NULL)
                return (1);

        ccb->c_flags = XS_CTL_POLL | flags;
        ccb->c_done = ips_done_mgmt;

        cmd = ccb->c_cmdbva;
        cmd->code = IPS_CMD_REBUILD;
        cmd->drive = chan;
        cmd->sgcnt = target;
        cmd->seccnt = htole16(ntarget << 8 | nchan);

        return (ips_cmd(sc, ccb));
}
#endif  /* NBIO > 0 */

void
ips_copperhead_exec(struct ips_softc *sc, struct ips_ccb *ccb)
{
        u_int32_t reg;
        int timeout;

        for (timeout = 100; timeout-- > 0; delay(100)) {
                reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, IPS_REG_CCC);
                if ((reg & IPS_REG_CCC_SEM) == 0)
                        break;
        }
        if (timeout < 0) {
                device_printf(sc->sc_dev, "semaphore timeout\n");
                return;
        }

        bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_CCSA, ccb->c_cmdbpa);
        bus_space_write_2(sc->sc_iot, sc->sc_ioh, IPS_REG_CCC,
            IPS_REG_CCC_START);
}

void
ips_copperhead_intren(struct ips_softc *sc)
{
        bus_space_write_1(sc->sc_iot, sc->sc_ioh, IPS_REG_HIS, IPS_REG_HIS_EN);
}

int
ips_copperhead_isintr(struct ips_softc *sc)
{
        u_int8_t reg;

        reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, IPS_REG_HIS);
        bus_space_write_1(sc->sc_iot, sc->sc_ioh, IPS_REG_HIS, reg);
        if (reg != 0xff && (reg & IPS_REG_HIS_SCE))
                return (1);

        return (0);
}

u_int32_t
ips_copperhead_status(struct ips_softc *sc)
{
        u_int32_t sqhead, sqtail, status;

        sqhead = bus_space_read_4(sc->sc_iot, sc->sc_ioh, IPS_REG_SQH);
        DPRINTF(IPS_D_XFER, ("%s: sqhead 0x%08x, sqtail 0x%08x\n",
            device_xname(sc->sc_dev), sqhead, sc->sc_sqtail));

        sqtail = sc->sc_sqtail + sizeof(u_int32_t);
        if (sqtail == sc->sc_sqm.dm_paddr + IPS_SQSZ)
                sqtail = sc->sc_sqm.dm_paddr;
        if (sqtail == sqhead)
                return (0xffffffff);

        sc->sc_sqtail = sqtail;
        if (++sc->sc_sqidx == IPS_MAXCMDS)
                sc->sc_sqidx = 0;
        status = htole32(sc->sc_sqbuf[sc->sc_sqidx]);
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_SQT, sqtail);

        return (status);
}

void
ips_morpheus_exec(struct ips_softc *sc, struct ips_ccb *ccb)
{
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_IQP, ccb->c_cmdbpa);
}

void
ips_morpheus_intren(struct ips_softc *sc)
{
        u_int32_t reg;

        reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, IPS_REG_OIM);
        reg &= ~IPS_REG_OIM_DS;
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, IPS_REG_OIM, reg);
}

int
ips_morpheus_isintr(struct ips_softc *sc)
{
        return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, IPS_REG_OIS) &
            IPS_REG_OIS_PEND);
}

u_int32_t
ips_morpheus_status(struct ips_softc *sc)
{
        u_int32_t reg;

        reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh, IPS_REG_OQP);
        DPRINTF(IPS_D_XFER, ("%s: status 0x%08x\n", device_xname(sc->sc_dev),
            reg));

        return (reg);
}

struct ips_ccb *
ips_ccb_alloc(struct ips_softc *sc, int n)
{
        struct ips_ccb *ccb;
        int i;

        ccb = malloc(n * sizeof(*ccb), M_DEVBUF, M_WAITOK | M_ZERO);
        for (i = 0; i < n; i++) {
                ccb[i].c_sc = sc;
                ccb[i].c_id = i;
                ccb[i].c_cmdbva = (char *)sc->sc_cmdbm.dm_vaddr +
                    i * sizeof(struct ips_cmdb);
                ccb[i].c_cmdbpa = sc->sc_cmdbm.dm_paddr +
                    i * sizeof(struct ips_cmdb);
                if (bus_dmamap_create(sc->sc_dmat, IPS_MAXFER, IPS_MAXSGS,
                    IPS_MAXFER, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
                    &ccb[i].c_dmam))
                        goto fail;
        }

        return (ccb);
fail:
        for (; i > 0; i--)
                bus_dmamap_destroy(sc->sc_dmat, ccb[i - 1].c_dmam);
        free(ccb, M_DEVBUF);
        return (NULL);
}

void
ips_ccb_free(struct ips_softc *sc, struct ips_ccb *ccb, int n)
{
        int i;

        for (i = 0; i < n; i++)
                bus_dmamap_destroy(sc->sc_dmat, ccb[i - 1].c_dmam);
        free(ccb, M_DEVBUF);
}

struct ips_ccb *
ips_ccb_get(struct ips_softc *sc)
{
        struct ips_ccb *ccb;

        mutex_enter(&sc->sc_ccb_mtx);
        if ((ccb = SLIST_FIRST(&sc->sc_ccbq_free)) != NULL) {
                SLIST_REMOVE_HEAD(&sc->sc_ccbq_free, c_link);
                ccb->c_flags = 0;
                ccb->c_xfer = NULL;
                bzero(ccb->c_cmdbva, sizeof(struct ips_cmdb));
        }
        mutex_exit(&sc->sc_ccb_mtx);

        return (ccb);
}

void
ips_ccb_put(struct ips_softc *sc, struct ips_ccb *ccb)
{
        ccb->c_state = IPS_CCB_FREE;
        mutex_enter(&sc->sc_ccb_mtx);
        SLIST_INSERT_HEAD(&sc->sc_ccbq_free, ccb, c_link);
        mutex_exit(&sc->sc_ccb_mtx);
}

int
ips_dmamem_alloc(struct dmamem *dm, bus_dma_tag_t tag, bus_size_t size)
{
        int nsegs;

        dm->dm_tag = tag;
        dm->dm_size = size;

        if (bus_dmamap_create(tag, size, 1, size, 0,
            BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &dm->dm_map))
                return (1);
        if (bus_dmamem_alloc(tag, size, 0, 0, &dm->dm_seg, 1, &nsegs,
            BUS_DMA_NOWAIT))
                goto fail1;
        if (bus_dmamem_map(tag, &dm->dm_seg, 1, size, &dm->dm_vaddr,
            BUS_DMA_NOWAIT))
                goto fail2;
        if (bus_dmamap_load(tag, dm->dm_map, dm->dm_vaddr, size, NULL,
            BUS_DMA_NOWAIT))
                goto fail3;

        return (0);

fail3:
        bus_dmamem_unmap(tag, dm->dm_vaddr, size);
fail2:
        bus_dmamem_free(tag, &dm->dm_seg, 1);
fail1:
        bus_dmamap_destroy(tag, dm->dm_map);
        return (1);
}

void
ips_dmamem_free(struct dmamem *dm)
{
        bus_dmamap_unload(dm->dm_tag, dm->dm_map);
        bus_dmamem_unmap(dm->dm_tag, dm->dm_vaddr, dm->dm_size);
        bus_dmamem_free(dm->dm_tag, &dm->dm_seg, 1);
        bus_dmamap_destroy(dm->dm_tag, dm->dm_map);
}