/*	$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);
}
