/*	$OpenBSD: qle.c,v 1.62 2022/03/11 18:00:51 mpi Exp $ */

/*
 * Copyright (c) 2013, 2014 Jonathan Matthew <jmatthew@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.
 */

#include "bio.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/sensors.h>
#include <sys/rwlock.h>
#include <sys/task.h>
#include <sys/timeout.h>

#include <machine/bus.h>

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

#ifdef __sparc64__
#include <dev/ofw/openfirm.h>
#endif

#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>

#include <dev/pci/qlereg.h>

#ifdef QLE_DEBUG
#define DPRINTF(m, f...) do { if ((qledebug & (m)) == (m)) printf(f); } \
    while (0)
#define QLE_D_MBOX		0x01
#define QLE_D_INTR		0x02
#define QLE_D_PORT		0x04
#define QLE_D_IO		0x08
#define QLE_D_IOCB		0x10
int qledebug = QLE_D_PORT;
#else
#define DPRINTF(m, f...)
#endif

#ifndef QLE_NOFIRMWARE
#include <dev/microcode/isp/asm_2400.h>
#include <dev/microcode/isp/asm_2500.h>
#endif

#define QLE_PCI_MEM_BAR		0x14
#define QLE_PCI_IO_BAR		0x10


#define QLE_DEFAULT_PORT_NAME		0x400000007F000003ULL /* from isp(4) */

#define QLE_WAIT_FOR_LOOP		10	/* seconds */
#define QLE_LOOP_SETTLE			200	/* ms */

/* rounded up range of assignable handles */
#define QLE_MAX_TARGETS			2048

/* maximum number of segments allowed for in a single io */
#define QLE_MAX_SEGS			32

enum qle_isp_gen {
	QLE_GEN_ISP24XX = 1,
	QLE_GEN_ISP25XX
};

enum qle_isp_type {
	QLE_ISP2422 = 1,
	QLE_ISP2432,
	QLE_ISP2512,
	QLE_ISP2522,
	QLE_ISP2532
};

/* port database things */
#define QLE_SCRATCH_SIZE		0x1000

enum qle_port_disp {
	QLE_PORT_DISP_NEW,
	QLE_PORT_DISP_GONE,
	QLE_PORT_DISP_SAME,
	QLE_PORT_DISP_CHANGED,
	QLE_PORT_DISP_MOVED,
	QLE_PORT_DISP_DUP
};

#define QLE_LOCATION_LOOP		(1 << 24)
#define QLE_LOCATION_FABRIC		(2 << 24)
#define QLE_LOCATION_LOOP_ID(l)		(l | QLE_LOCATION_LOOP)
#define QLE_LOCATION_PORT_ID(p)		(p | QLE_LOCATION_FABRIC)

struct qle_fc_port {
	TAILQ_ENTRY(qle_fc_port) ports;
	TAILQ_ENTRY(qle_fc_port) update;

	u_int64_t	node_name;
	u_int64_t	port_name;
	u_int32_t	location;	/* port id or loop id */

	int		flags;
#define QLE_PORT_FLAG_IS_TARGET		1
#define QLE_PORT_FLAG_NEEDS_LOGIN	2

	u_int32_t	portid;
	u_int16_t	loopid;
};


/* request/response queue stuff */
#define QLE_QUEUE_ENTRY_SIZE		64

struct qle_ccb {
	struct qle_softc 	*ccb_sc;
	int			ccb_id;
	struct scsi_xfer	*ccb_xs;

	bus_dmamap_t		ccb_dmamap;

	struct qle_iocb_seg	*ccb_segs;
	u_int64_t		ccb_seg_offset;

	SIMPLEQ_ENTRY(qle_ccb)	ccb_link;
};

SIMPLEQ_HEAD(qle_ccb_list, qle_ccb);

struct qle_dmamem {
	bus_dmamap_t		qdm_map;
	bus_dma_segment_t	qdm_seg;
	size_t			qdm_size;
	caddr_t			qdm_kva;
};
#define QLE_DMA_MAP(_qdm)	((_qdm)->qdm_map)
#define QLE_DMA_LEN(_qdm)	((_qdm)->qdm_size)
#define QLE_DMA_DVA(_qdm)	((u_int64_t)(_qdm)->qdm_map->dm_segs[0].ds_addr)
#define QLE_DMA_KVA(_qdm)	((void *)(_qdm)->qdm_kva)

struct qle_softc {
	struct device		sc_dev;

	pci_chipset_tag_t	sc_pc;
	pcitag_t		sc_tag;

	void			*sc_ih;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;
	bus_size_t		sc_ios;
	bus_dma_tag_t		sc_dmat;

	struct scsibus_softc	*sc_scsibus;

	enum qle_isp_type	sc_isp_type;
	enum qle_isp_gen	sc_isp_gen;
	int			sc_port;

	bus_space_handle_t	sc_mbox_ioh;
	u_int16_t		sc_mbox[QLE_MBOX_COUNT];
	int			sc_mbox_pending;
	struct mutex		sc_mbox_mtx;

	int			sc_loop_up;
	int			sc_topology;
	int			sc_loop_id;
	int			sc_port_id;
	int			sc_loop_max_id;
	u_int64_t		sc_sns_port_name;

	struct mutex		sc_port_mtx;
	TAILQ_HEAD(, qle_fc_port) sc_ports;
	TAILQ_HEAD(, qle_fc_port) sc_ports_new;
	TAILQ_HEAD(, qle_fc_port) sc_ports_gone;
	TAILQ_HEAD(, qle_fc_port) sc_ports_found;
	struct qle_fc_port	*sc_targets[QLE_MAX_TARGETS];

	struct taskq		*sc_update_taskq;
	struct task		sc_update_task;
	struct timeout		sc_update_timeout;
	int			sc_update;
	int			sc_update_tasks;
#define	QLE_UPDATE_TASK_CLEAR_ALL	0x00000001
#define QLE_UPDATE_TASK_SOFTRESET	0x00000002
#define QLE_UPDATE_TASK_UPDATE_TOPO	0x00000004
#define QLE_UPDATE_TASK_GET_PORT_LIST	0x00000008
#define QLE_UPDATE_TASK_PORT_LIST	0x00000010
#define QLE_UPDATE_TASK_SCAN_FABRIC	0x00000020
#define QLE_UPDATE_TASK_SCANNING_FABRIC	0x00000040
#define QLE_UPDATE_TASK_FABRIC_LOGIN	0x00000080
#define QLE_UPDATE_TASK_FABRIC_RELOGIN	0x00000100
#define QLE_UPDATE_TASK_DETACH_TARGET	0x00000200
#define QLE_UPDATE_TASK_ATTACH_TARGET	0x00000400

	int			sc_maxcmds;
	struct qle_dmamem	*sc_requests;
	struct qle_dmamem	*sc_responses;
	struct qle_dmamem	*sc_segments;
	struct qle_dmamem	*sc_pri_requests;
	struct qle_dmamem	*sc_scratch;
	struct qle_dmamem	*sc_fcp_cmnds;
	struct qle_ccb		*sc_ccbs;
	struct qle_ccb_list	sc_ccb_free;
	struct mutex		sc_ccb_mtx;
	struct mutex		sc_queue_mtx;
	struct scsi_iopool	sc_iopool;
	u_int32_t		sc_next_req_id;
	u_int32_t		sc_last_resp_id;
	int			sc_marker_required;
	int			sc_fabric_pending;
	u_int8_t		sc_fabric_response[QLE_QUEUE_ENTRY_SIZE];

	struct qle_nvram	sc_nvram;
	int			sc_nvram_valid;
};
#define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname)

int	qle_intr(void *);

int	qle_match(struct device *, void *, void *);
void	qle_attach(struct device *, struct device *, void *);
int	qle_detach(struct device *, int);

const struct cfattach qle_ca = {
	sizeof(struct qle_softc),
	qle_match,
	qle_attach,
	qle_detach
};

struct cfdriver qle_cd = {
	NULL,
	"qle",
	DV_DULL
};

void		qle_scsi_cmd(struct scsi_xfer *);
int		qle_scsi_probe(struct scsi_link *);


struct scsi_adapter qle_switch = {
	qle_scsi_cmd, NULL, qle_scsi_probe, NULL, NULL
};

u_int32_t	qle_read(struct qle_softc *, int);
void		qle_write(struct qle_softc *, int, u_int32_t);
void		qle_host_cmd(struct qle_softc *sc, u_int32_t);

int		qle_mbox(struct qle_softc *, int);
int		qle_ct_pass_through(struct qle_softc *sc,
		    u_int32_t port_handle, struct qle_dmamem *mem,
		    size_t req_size, size_t resp_size);
void		qle_mbox_putaddr(u_int16_t *, struct qle_dmamem *);
u_int16_t	qle_read_mbox(struct qle_softc *, int);
void		qle_write_mbox(struct qle_softc *, int, u_int16_t);

void		qle_handle_intr(struct qle_softc *, u_int16_t, u_int16_t);
void		qle_set_ints(struct qle_softc *, int);
int		qle_read_isr(struct qle_softc *, u_int16_t *, u_int16_t *);
void		qle_clear_isr(struct qle_softc *, u_int16_t);

void		qle_put_marker(struct qle_softc *, void *);
void		qle_put_cmd(struct qle_softc *, void *, struct scsi_xfer *,
		    struct qle_ccb *, u_int32_t);
struct qle_ccb *qle_handle_resp(struct qle_softc *, u_int32_t);
void		qle_sge(struct qle_iocb_seg *, u_int64_t, u_int32_t);

struct qle_fc_port *qle_next_fabric_port(struct qle_softc *, u_int32_t *,
		    u_int32_t *);
int		qle_get_port_db(struct qle_softc *, u_int16_t,
		    struct qle_dmamem *);
int		qle_get_port_name_list(struct qle_softc *sc, u_int32_t);
int		qle_add_loop_port(struct qle_softc *, struct qle_fc_port *);
int		qle_add_fabric_port(struct qle_softc *, struct qle_fc_port *);
int		qle_add_logged_in_port(struct qle_softc *, u_int16_t,
		    u_int32_t);
int		qle_classify_port(struct qle_softc *, u_int32_t, u_int64_t,
		    u_int64_t, struct qle_fc_port **);
int		qle_get_loop_id(struct qle_softc *sc, int);
void		qle_clear_port_lists(struct qle_softc *);
int		qle_softreset(struct qle_softc *);
void		qle_update_topology(struct qle_softc *);
int		qle_update_fabric(struct qle_softc *);
int		qle_fabric_plogx(struct qle_softc *, struct qle_fc_port *, int,
		    u_int32_t *);
int		qle_fabric_plogi(struct qle_softc *, struct qle_fc_port *);
void		qle_fabric_plogo(struct qle_softc *, struct qle_fc_port *);

void		qle_update_start(struct qle_softc *, int);
void		qle_update_defer(struct qle_softc *, int);
void		qle_update_cancel(struct qle_softc *);
void		qle_update_done(struct qle_softc *, int);
void		qle_do_update(void *);
void		qle_deferred_update(void *);
int		qle_async(struct qle_softc *, u_int16_t);

int		qle_load_fwchunk(struct qle_softc *,
		    struct qle_dmamem *, const u_int32_t *);
u_int32_t	qle_read_ram_word(struct qle_softc *, u_int32_t);
int		qle_verify_firmware(struct qle_softc *, u_int32_t);
int		qle_load_firmware_chunks(struct qle_softc *, const u_int32_t *);
int		qle_read_nvram(struct qle_softc *);

struct qle_dmamem *qle_dmamem_alloc(struct qle_softc *, size_t);
void		qle_dmamem_free(struct qle_softc *, struct qle_dmamem *);

int		qle_alloc_ccbs(struct qle_softc *);
void		qle_free_ccbs(struct qle_softc *);
void		*qle_get_ccb(void *);
void		qle_put_ccb(void *, void *);

void		qle_dump_stuff(struct qle_softc *, void *, int);
void		qle_dump_iocb(struct qle_softc *, void *);
void		qle_dump_iocb_segs(struct qle_softc *, void *, int);

static const struct pci_matchid qle_devices[] = {
	{ PCI_VENDOR_QLOGIC,	PCI_PRODUCT_QLOGIC_ISP2422 },
	{ PCI_VENDOR_QLOGIC,	PCI_PRODUCT_QLOGIC_ISP2432 },
	{ PCI_VENDOR_QLOGIC,	PCI_PRODUCT_QLOGIC_ISP2512 },
	{ PCI_VENDOR_QLOGIC,	PCI_PRODUCT_QLOGIC_ISP2522 },
	{ PCI_VENDOR_QLOGIC,	PCI_PRODUCT_QLOGIC_ISP2532 },
};

int
qle_match(struct device *parent, void *match, void *aux)
{
	return (pci_matchbyid(aux, qle_devices, nitems(qle_devices)));
}

void
qle_attach(struct device *parent, struct device *self, void *aux)
{
	struct qle_softc *sc = (void *)self;
	struct pci_attach_args *pa = aux;
	pci_intr_handle_t ih;
	const char *intrstr;
	u_int32_t pcictl;
	struct scsibus_attach_args saa;
	struct qle_init_cb *icb;
	bus_size_t mbox_base;
	u_int32_t firmware_addr;
#ifndef QLE_NOFIRMWARE
	const u_int32_t *firmware = NULL;
#endif

	pcireg_t bars[] = { QLE_PCI_MEM_BAR, QLE_PCI_IO_BAR };
	pcireg_t memtype;
	int r, i, rv, loop_up;

	sc->sc_pc = pa->pa_pc;
	sc->sc_tag = pa->pa_tag;
	sc->sc_ih = NULL;
	sc->sc_dmat = pa->pa_dmat;
	sc->sc_ios = 0;

	for (r = 0; r < nitems(bars); r++) {
		memtype = pci_mapreg_type(sc->sc_pc, sc->sc_tag, bars[r]);
		if (pci_mapreg_map(pa, bars[r], memtype, 0,
		    &sc->sc_iot, &sc->sc_ioh, NULL, &sc->sc_ios, 0) == 0)
			break;

		sc->sc_ios = 0;
	}
	if (sc->sc_ios == 0) {
		printf(": unable to map registers\n");
		return;
	}

	if (pci_intr_map_msi(pa, &ih) != 0 && pci_intr_map(pa, &ih) != 0) {
		printf(": unable to map interrupt\n");
		goto unmap;
	}
	intrstr = pci_intr_string(sc->sc_pc, ih);
	sc->sc_ih = pci_intr_establish(sc->sc_pc, ih, IPL_BIO,
	    qle_intr, sc, DEVNAME(sc));
	if (sc->sc_ih == NULL) {
		printf(": unable to establish interrupt");
		if (intrstr != NULL)
			printf(" at %s", intrstr);
		printf("\n");
		goto deintr;
	}

	printf(": %s\n", intrstr);

	pcictl = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);
	pcictl |= PCI_COMMAND_INVALIDATE_ENABLE |
	    PCI_COMMAND_PARITY_ENABLE | PCI_COMMAND_SERR_ENABLE;
	pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, pcictl);

	pcictl = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG);
	pcictl &= ~(PCI_LATTIMER_MASK << PCI_LATTIMER_SHIFT);
	pcictl &= ~(PCI_CACHELINE_MASK << PCI_CACHELINE_SHIFT);
	pcictl |= (0x80 << PCI_LATTIMER_SHIFT);
	pcictl |= (0x10 << PCI_CACHELINE_SHIFT);
	pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG, pcictl);

	pcictl = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_ROM_REG);
	pcictl &= ~1;
	pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_ROM_REG, pcictl);

	switch (PCI_PRODUCT(pa->pa_id)) {
	case PCI_PRODUCT_QLOGIC_ISP2422:
		sc->sc_isp_type = QLE_ISP2422;
		sc->sc_isp_gen = QLE_GEN_ISP24XX;
		break;
	case PCI_PRODUCT_QLOGIC_ISP2432:
		sc->sc_isp_type = QLE_ISP2432;
		sc->sc_isp_gen = QLE_GEN_ISP24XX;
		break;
	case PCI_PRODUCT_QLOGIC_ISP2512:
		sc->sc_isp_type = QLE_ISP2512;
		sc->sc_isp_gen = QLE_GEN_ISP25XX;
		break;
	case PCI_PRODUCT_QLOGIC_ISP2522:
		sc->sc_isp_type = QLE_ISP2522;
		sc->sc_isp_gen = QLE_GEN_ISP25XX;
		break;
	case PCI_PRODUCT_QLOGIC_ISP2532:
		sc->sc_isp_type = QLE_ISP2532;
		sc->sc_isp_gen = QLE_GEN_ISP25XX;
		break;

	default:
		printf("unknown pci id %x", pa->pa_id);
		goto deintr;
	}

	/* these are the same for 24xx and 25xx but may vary later */
	mbox_base = QLE_MBOX_BASE_24XX;
	firmware_addr = QLE_2400_CODE_ORG;

	if (bus_space_subregion(sc->sc_iot, sc->sc_ioh, mbox_base,
	    sizeof(sc->sc_mbox), &sc->sc_mbox_ioh) != 0) {
		printf("%s: unable to map mbox registers\n", DEVNAME(sc));
		goto deintr;
	}

	sc->sc_port = pa->pa_function;

	TAILQ_INIT(&sc->sc_ports);
	TAILQ_INIT(&sc->sc_ports_new);
	TAILQ_INIT(&sc->sc_ports_gone);
	TAILQ_INIT(&sc->sc_ports_found);

	/* after reset, mbox regs 1 and 2 contain the string "ISP " */
	if (qle_read_mbox(sc, 1) != 0x4953 ||
	    qle_read_mbox(sc, 2) != 0x5020) {
		/* try releasing the risc processor */
		qle_host_cmd(sc, QLE_HOST_CMD_RELEASE);
	}

	qle_host_cmd(sc, QLE_HOST_CMD_PAUSE);
	if (qle_softreset(sc) != 0) {
		printf("softreset failed\n");
		goto deintr;
	}

	if (qle_read_nvram(sc) == 0)
		sc->sc_nvram_valid = 1;

#ifdef QLE_NOFIRMWARE
	if (qle_verify_firmware(sc, firmware_addr)) {
		printf("%s: no firmware loaded\n", DEVNAME(sc));
		goto deintr;
	}
#else
	switch (sc->sc_isp_gen) {
	case QLE_GEN_ISP24XX:
		firmware = isp_2400_risc_code;
		break;
	case QLE_GEN_ISP25XX:
		firmware = isp_2500_risc_code;
		break;
	default:
		printf("%s: no firmware to load?\n", DEVNAME(sc));
		goto deintr;
	}
	if (qle_load_firmware_chunks(sc, firmware)) {
		printf("%s: firmware load failed\n", DEVNAME(sc));
		goto deintr;
	}
#endif

	/* execute firmware */
	sc->sc_mbox[0] = QLE_MBOX_EXEC_FIRMWARE;
	sc->sc_mbox[1] = firmware_addr >> 16;
	sc->sc_mbox[2] = firmware_addr & 0xffff;
#ifdef QLE_NOFIRMWARE
	sc->sc_mbox[3] = 1;
#else
	sc->sc_mbox[3] = 0;
#endif
	sc->sc_mbox[4] = 0;
	if (qle_mbox(sc, 0x001f)) {
		printf("ISP couldn't exec firmware: %x\n", sc->sc_mbox[0]);
		goto deintr;
	}

	delay(250000);		/* from isp(4) */

	sc->sc_mbox[0] = QLE_MBOX_ABOUT_FIRMWARE;
	if (qle_mbox(sc, 0x0001)) {
		printf("ISP not talking after firmware exec: %x\n",
		    sc->sc_mbox[0]);
		goto deintr;
	}
	printf("%s: firmware rev %d.%d.%d, attrs 0x%x\n", DEVNAME(sc),
	    sc->sc_mbox[1], sc->sc_mbox[2], sc->sc_mbox[3], sc->sc_mbox[6]);

	sc->sc_maxcmds = 4096;

	/* reserve queue slots for markers and fabric ops */
	sc->sc_maxcmds -= 2;

	if (qle_alloc_ccbs(sc)) {
		/* error already printed */
		goto deintr;
	}
	sc->sc_scratch = qle_dmamem_alloc(sc, QLE_SCRATCH_SIZE);
	if (sc->sc_scratch == NULL) {
		printf("%s: unable to allocate scratch\n", DEVNAME(sc));
		goto free_ccbs;
	}

	/* build init buffer thing */
	icb = (struct qle_init_cb *)QLE_DMA_KVA(sc->sc_scratch);
	memset(icb, 0, sizeof(*icb));
	icb->icb_version = QLE_ICB_VERSION;
	if (sc->sc_nvram_valid) {
		icb->icb_max_frame_len = sc->sc_nvram.frame_payload_size;
		icb->icb_exec_throttle = sc->sc_nvram.execution_throttle;
		icb->icb_hardaddr = sc->sc_nvram.hard_address;
		icb->icb_portname = sc->sc_nvram.port_name;
		icb->icb_nodename = sc->sc_nvram.node_name;
		icb->icb_login_retry = sc->sc_nvram.login_retry;
		icb->icb_login_timeout = sc->sc_nvram.login_timeout;
		icb->icb_fwoptions1 = sc->sc_nvram.fwoptions1;
		icb->icb_fwoptions2 = sc->sc_nvram.fwoptions2;
		icb->icb_fwoptions3 = sc->sc_nvram.fwoptions3;
	} else {
		/* defaults copied from isp(4) */
		htolem16(&icb->icb_max_frame_len, 1024);
		htolem16(&icb->icb_exec_throttle, 16);
		icb->icb_portname = htobe64(QLE_DEFAULT_PORT_NAME);
		icb->icb_nodename = 0;
		icb->icb_login_retry = 3;

		htolem32(&icb->icb_fwoptions1, QLE_ICB_FW1_FAIRNESS |
		    QLE_ICB_FW1_HARD_ADDR | QLE_ICB_FW1_FULL_DUPLEX);
		htolem32(&icb->icb_fwoptions2, QLE_ICB_FW2_LOOP_PTP);
		htolem32(&icb->icb_fwoptions3, QLE_ICB_FW3_FCP_RSP_24_0 |
		    QLE_ICB_FW3_AUTONEG);
	}

	icb->icb_exchange_count = 0;

	icb->icb_req_out = 0;
	icb->icb_resp_in = 0;
	icb->icb_pri_req_out = 0;
	htolem16(&icb->icb_req_queue_len, sc->sc_maxcmds);
	htolem16(&icb->icb_resp_queue_len, sc->sc_maxcmds);
	htolem16(&icb->icb_pri_req_queue_len, 8); /* apparently the minimum */
	htolem32(&icb->icb_req_queue_addr_lo,
	    QLE_DMA_DVA(sc->sc_requests));
	htolem32(&icb->icb_req_queue_addr_hi,
	    QLE_DMA_DVA(sc->sc_requests) >> 32);
	htolem32(&icb->icb_resp_queue_addr_lo,
	    QLE_DMA_DVA(sc->sc_responses));
	htolem32(&icb->icb_resp_queue_addr_hi,
	    QLE_DMA_DVA(sc->sc_responses) >> 32);
	htolem32(&icb->icb_pri_req_queue_addr_lo,
	    QLE_DMA_DVA(sc->sc_pri_requests));
	htolem32(&icb->icb_pri_req_queue_addr_hi,
	    QLE_DMA_DVA(sc->sc_pri_requests) >> 32);

	htolem16(&icb->icb_link_down_nos, 200);
	icb->icb_int_delay = 0;
	icb->icb_login_timeout = 0;

	sc->sc_mbox[0] = QLE_MBOX_INIT_FIRMWARE;
	sc->sc_mbox[4] = 0;
	sc->sc_mbox[5] = 0;
	qle_mbox_putaddr(sc->sc_mbox, sc->sc_scratch);
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0,
	    sizeof(*icb), BUS_DMASYNC_PREWRITE);
	rv = qle_mbox(sc, 0x00fd);
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0,
	    sizeof(*icb), BUS_DMASYNC_POSTWRITE);

	if (rv != 0) {
		printf("%s: ISP firmware init failed: %x\n", DEVNAME(sc),
		    sc->sc_mbox[0]);
		goto free_scratch;
	}

	/* enable some more notifications */
	sc->sc_mbox[0] = QLE_MBOX_SET_FIRMWARE_OPTIONS;
	sc->sc_mbox[1] = QLE_FW_OPTION1_ASYNC_LIP_F8 |
	    QLE_FW_OPTION1_ASYNC_LIP_RESET |
	    QLE_FW_OPTION1_ASYNC_LIP_ERROR |
	    QLE_FW_OPTION1_ASYNC_LOGIN_RJT;
	sc->sc_mbox[2] = 0;
	sc->sc_mbox[3] = 0;
	if (qle_mbox(sc, 0x000f)) {
		printf("%s: setting firmware options failed: %x\n",
		    DEVNAME(sc), sc->sc_mbox[0]);
		goto free_scratch;
	}

	sc->sc_update_taskq = taskq_create(DEVNAME(sc), 1, IPL_BIO, 0);
	task_set(&sc->sc_update_task, qle_do_update, sc);
	timeout_set(&sc->sc_update_timeout, qle_deferred_update, sc);

	/* wait a bit for link to come up so we can scan and attach devices */
	for (i = 0; i < QLE_WAIT_FOR_LOOP * 1000; i++) {
		u_int16_t isr, info;

		if (sc->sc_loop_up) {
			if (++loop_up == QLE_LOOP_SETTLE)
				break;
		} else
			loop_up = 0;

		delay(1000);

		if (qle_read_isr(sc, &isr, &info) == 0)
			continue;

		qle_handle_intr(sc, isr, info);

	}

	if (sc->sc_loop_up) {
		qle_do_update(sc);
	} else {
		DPRINTF(QLE_D_PORT, "%s: loop still down, giving up\n",
		    DEVNAME(sc));
	}

	saa.saa_adapter = &qle_switch;
	saa.saa_adapter_softc = sc;
	saa.saa_adapter_target = SDEV_NO_ADAPTER_TARGET;
	saa.saa_adapter_buswidth = QLE_MAX_TARGETS;
	saa.saa_luns = 8;
	saa.saa_openings = sc->sc_maxcmds;
	saa.saa_pool = &sc->sc_iopool;
	if (sc->sc_nvram_valid) {
		saa.saa_wwpn = betoh64(sc->sc_nvram.port_name);
		saa.saa_wwnn = betoh64(sc->sc_nvram.node_name);
	} else {
		saa.saa_wwpn = QLE_DEFAULT_PORT_NAME;
		saa.saa_wwnn = 0;
	}
	if (saa.saa_wwnn == 0) {
		/*
		 * mask out the port number from the port name to get
		 * the node name.
		 */
		saa.saa_wwnn = saa.saa_wwpn;
		saa.saa_wwnn &= ~(0xfULL << 56);
	}
	saa.saa_quirks = saa.saa_flags = 0;

	sc->sc_scsibus = (struct scsibus_softc *)config_found(&sc->sc_dev,
	    &saa, scsiprint);

	return;

free_scratch:
	qle_dmamem_free(sc, sc->sc_scratch);
free_ccbs:
	qle_free_ccbs(sc);
deintr:
	pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
	sc->sc_ih = NULL;
unmap:
	bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);
	sc->sc_ios = 0;
}

int
qle_detach(struct device *self, int flags)
{
	struct qle_softc *sc = (struct qle_softc *)self;

	if (sc->sc_ih == NULL) {
		/* we didnt attach properly, so nothing to detach */
		return (0);
	}

	pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
	sc->sc_ih = NULL;

	bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);
	sc->sc_ios = 0;

	return (0);
}

int
qle_classify_port(struct qle_softc *sc, u_int32_t location,
    u_int64_t port_name, u_int64_t node_name, struct qle_fc_port **prev)
{
	struct qle_fc_port *port, *locmatch, *wwnmatch;
	locmatch = NULL;
	wwnmatch = NULL;

	/* make sure we don't try to add a port or location twice */
	TAILQ_FOREACH(port, &sc->sc_ports_new, update) {
		if ((port->port_name == port_name &&
		    port->node_name == node_name) ||
		    port->location == location) {
			*prev = port;
			return (QLE_PORT_DISP_DUP);
		}
	}

	/* if we're attaching, everything is new */
	if (sc->sc_scsibus == NULL) {
		*prev = NULL;
		return (QLE_PORT_DISP_NEW);
	}

	TAILQ_FOREACH(port, &sc->sc_ports, ports) {
		if (port->location == location)
			locmatch = port;

		if (port->port_name == port_name &&
		    port->node_name == node_name)
			wwnmatch = port;
	}

	if (locmatch == NULL && wwnmatch == NULL) {
		*prev = NULL;
		return (QLE_PORT_DISP_NEW);
	} else if (locmatch == wwnmatch) {
		*prev = locmatch;
		return (QLE_PORT_DISP_SAME);
	} else if (wwnmatch != NULL) {
		*prev = wwnmatch;
		return (QLE_PORT_DISP_MOVED);
	} else {
		*prev = locmatch;
		return (QLE_PORT_DISP_CHANGED);
	}
}

int
qle_get_loop_id(struct qle_softc *sc, int start)
{
	int i, last;

	i = QLE_MIN_HANDLE;
	last = QLE_MAX_HANDLE;
	if (i < start)
		i = start;

	for (; i <= last; i++) {
		if (sc->sc_targets[i] == NULL)
			return (i);
	}

	return (-1);
}

int
qle_get_port_db(struct qle_softc *sc, u_int16_t loopid, struct qle_dmamem *mem)
{
	sc->sc_mbox[0] = QLE_MBOX_GET_PORT_DB;
	sc->sc_mbox[1] = loopid;
	qle_mbox_putaddr(sc->sc_mbox, mem);
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0,
	    sizeof(struct qle_get_port_db), BUS_DMASYNC_PREREAD);
	if (qle_mbox(sc, 0x00cf)) {
		DPRINTF(QLE_D_PORT, "%s: get port db for %d failed: %x\n",
		    DEVNAME(sc), loopid, sc->sc_mbox[0]);
		return (1);
	}

	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0,
	    sizeof(struct qle_get_port_db), BUS_DMASYNC_POSTREAD);
	return (0);
}

int
qle_get_port_name_list(struct qle_softc *sc, u_int32_t match)
{
	struct qle_port_name_list *l;
	struct qle_fc_port *port;
	int i;

	sc->sc_mbox[0] = QLE_MBOX_GET_PORT_NAME_LIST;
	sc->sc_mbox[1] = 0;
	sc->sc_mbox[8] = QLE_DMA_LEN(sc->sc_scratch);
	sc->sc_mbox[9] = 0;
	qle_mbox_putaddr(sc->sc_mbox, sc->sc_scratch);
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0,
	    QLE_DMA_LEN(sc->sc_scratch), BUS_DMASYNC_PREREAD);
	if (qle_mbox(sc, 0x03cf)) {
		DPRINTF(QLE_D_PORT, "%s: get port name list failed: %x\n",
		    DEVNAME(sc), sc->sc_mbox[0]);
		return (1);
	}
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0,
	    sc->sc_mbox[1], BUS_DMASYNC_POSTREAD);

	i = 0;
	l = QLE_DMA_KVA(sc->sc_scratch);
	mtx_enter(&sc->sc_port_mtx);
	while (i * sizeof(*l) < sc->sc_mbox[1]) {
		u_int16_t loopid;
		u_int32_t loc;

		loopid = lemtoh16(&l[i].loopid) & 0xfff;
		/* skip special ports */
		switch (loopid) {
		case QLE_F_PORT_HANDLE:
		case QLE_SNS_HANDLE:
		case QLE_FABRIC_CTRL_HANDLE:
		case QLE_IP_BCAST_HANDLE:
			loc = 0;
			break;
		default:
			if (loopid <= sc->sc_loop_max_id) {
				loc = QLE_LOCATION_LOOP_ID(loopid);
			} else {
				/*
				 * we don't have the port id here, so just
				 * indicate it's a fabric port.
				 */
				loc = QLE_LOCATION_FABRIC;
			}
			break;
		}

		if (match & loc) {
			port = malloc(sizeof(*port), M_DEVBUF, M_ZERO |
			    M_NOWAIT);
			if (port == NULL) {
				printf("%s: failed to allocate port struct\n",
				    DEVNAME(sc));
				break;
			}
			port->location = loc;
			port->loopid = loopid;
			port->port_name = letoh64(l[i].port_name);
			DPRINTF(QLE_D_PORT, "%s: loop id %d, port name %llx\n",
			    DEVNAME(sc), port->loopid, port->port_name);
			TAILQ_INSERT_TAIL(&sc->sc_ports_found, port, update);
		}
		i++;
	}
	mtx_leave(&sc->sc_port_mtx);

	return (0);
}

int
qle_add_loop_port(struct qle_softc *sc, struct qle_fc_port *port)
{
	struct qle_get_port_db *pdb;
	struct qle_fc_port *pport;
	int disp;

	if (qle_get_port_db(sc, port->loopid, sc->sc_scratch) != 0) {
		return (1);
	}
	pdb = QLE_DMA_KVA(sc->sc_scratch);

	if (lemtoh16(&pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE)
		port->flags |= QLE_PORT_FLAG_IS_TARGET;

	port->port_name = betoh64(pdb->port_name);
	port->node_name = betoh64(pdb->node_name);
	port->portid = (pdb->port_id[0] << 16) | (pdb->port_id[1] << 8) |
	    pdb->port_id[2];

	mtx_enter(&sc->sc_port_mtx);
	disp = qle_classify_port(sc, port->location, port->port_name,
	    port->node_name, &pport);
	switch (disp) {
	case QLE_PORT_DISP_CHANGED:
	case QLE_PORT_DISP_MOVED:
	case QLE_PORT_DISP_NEW:
		TAILQ_INSERT_TAIL(&sc->sc_ports_new, port, update);
		sc->sc_targets[port->loopid] = port;
		break;
	case QLE_PORT_DISP_DUP:
		free(port, M_DEVBUF, sizeof *port);
		break;
	case QLE_PORT_DISP_SAME:
		TAILQ_REMOVE(&sc->sc_ports_gone, pport, update);
		free(port, M_DEVBUF, sizeof *port);
		break;
	}
	mtx_leave(&sc->sc_port_mtx);

	switch (disp) {
	case QLE_PORT_DISP_CHANGED:
	case QLE_PORT_DISP_MOVED:
	case QLE_PORT_DISP_NEW:
		DPRINTF(QLE_D_PORT, "%s: %s %d; name %llx\n",
		    DEVNAME(sc), ISSET(port->flags, QLE_PORT_FLAG_IS_TARGET) ?
		    "target" : "non-target", port->loopid,
		    betoh64(pdb->port_name));
		break;
	default:
		break;
	}
	return (0);
}

int
qle_add_fabric_port(struct qle_softc *sc, struct qle_fc_port *port)
{
	struct qle_get_port_db *pdb;

	if (qle_get_port_db(sc, port->loopid, sc->sc_scratch) != 0) {
		free(port, M_DEVBUF, sizeof *port);
		return (1);
	}
	pdb = QLE_DMA_KVA(sc->sc_scratch);

	if (lemtoh16(&pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE)
		port->flags |= QLE_PORT_FLAG_IS_TARGET;

	/*
	 * if we only know about this port because qle_get_port_name_list
	 * returned it, we don't have its port id or node name, so fill
	 * those in and update its location.
	 */
	if (port->location == QLE_LOCATION_FABRIC) {
		port->node_name = betoh64(pdb->node_name);
		port->port_name = betoh64(pdb->port_name);
		port->portid = (pdb->port_id[0] << 16) |
		    (pdb->port_id[1] << 8) | pdb->port_id[2];
		port->location = QLE_LOCATION_PORT_ID(port->portid);
	}

	mtx_enter(&sc->sc_port_mtx);
	TAILQ_INSERT_TAIL(&sc->sc_ports_new, port, update);
	sc->sc_targets[port->loopid] = port;
	mtx_leave(&sc->sc_port_mtx);

	DPRINTF(QLE_D_PORT, "%s: %s %d; name %llx\n",
	    DEVNAME(sc), ISSET(port->flags, QLE_PORT_FLAG_IS_TARGET) ?
	    "target" : "non-target", port->loopid, port->port_name);
	return (0);
}

int
qle_add_logged_in_port(struct qle_softc *sc, u_int16_t loopid,
    u_int32_t portid)
{
	struct qle_fc_port *port;
	struct qle_get_port_db *pdb;
	u_int64_t node_name, port_name;
	int flags, ret;

	ret = qle_get_port_db(sc, loopid, sc->sc_scratch);
	mtx_enter(&sc->sc_port_mtx);
	if (ret != 0) {
		/* put in a fake port to prevent use of this loop id */
		printf("%s: loop id %d used, but can't see what's using it\n",
		    DEVNAME(sc), loopid);
		node_name = 0;
		port_name = 0;
		flags = 0;
	} else {
		pdb = QLE_DMA_KVA(sc->sc_scratch);
		node_name = betoh64(pdb->node_name);
		port_name = betoh64(pdb->port_name);
		flags = 0;
		if (lemtoh16(&pdb->prli_svc_word3) & QLE_SVC3_TARGET_ROLE)
			flags |= QLE_PORT_FLAG_IS_TARGET;

		/* see if we've already found this port */
		TAILQ_FOREACH(port, &sc->sc_ports_found, update) {
			if ((port->node_name == node_name) &&
			    (port->port_name == port_name) &&
			    (port->portid == portid)) {
				mtx_leave(&sc->sc_port_mtx);
				DPRINTF(QLE_D_PORT, "%s: already found port "
				    "%06x\n", DEVNAME(sc), portid);
				return (0);
			}
		}
	}

	port = malloc(sizeof(*port), M_DEVBUF, M_ZERO | M_NOWAIT);
	if (port == NULL) {
		mtx_leave(&sc->sc_port_mtx);
		printf("%s: failed to allocate a port structure\n",
		    DEVNAME(sc));
		return (1);
	}
	port->location = QLE_LOCATION_PORT_ID(portid);
	port->port_name = port_name;
	port->node_name = node_name;
	port->loopid = loopid;
	port->portid = portid;
	port->flags = flags;

	TAILQ_INSERT_TAIL(&sc->sc_ports, port, ports);
	sc->sc_targets[port->loopid] = port;
	mtx_leave(&sc->sc_port_mtx);

	DPRINTF(QLE_D_PORT, "%s: added logged in port %06x at %d\n",
	    DEVNAME(sc), portid, loopid);
	return (0);
}

struct qle_ccb *
qle_handle_resp(struct qle_softc *sc, u_int32_t id)
{
	struct qle_ccb *ccb;
	struct qle_iocb_status *status;
	struct qle_iocb_req6 *req;
	struct scsi_xfer *xs;
	u_int32_t handle;
	u_int16_t completion;
	u_int8_t *entry;
	u_int8_t *data;

	ccb = NULL;
	entry = QLE_DMA_KVA(sc->sc_responses) + (id * QLE_QUEUE_ENTRY_SIZE);

	bus_dmamap_sync(sc->sc_dmat,
	    QLE_DMA_MAP(sc->sc_responses), id * QLE_QUEUE_ENTRY_SIZE,
	    QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTREAD);

	qle_dump_iocb(sc, entry);
	switch(entry[0]) {
	case QLE_IOCB_STATUS:
		status = (struct qle_iocb_status *)entry;
		handle = status->handle;
		if (handle > sc->sc_maxcmds) {
			panic("bad completed command handle: %d (> %d)",
			    handle, sc->sc_maxcmds);
		}

		ccb = &sc->sc_ccbs[handle];
		xs = ccb->ccb_xs;
		if (xs == NULL) {
			DPRINTF(QLE_D_IO, "%s: got status for inactive ccb %d\n",
			    DEVNAME(sc), handle);
			ccb = NULL;
			break;
		}
		if (xs->io != ccb) {
			panic("completed command handle doesn't match xs "
			    "(handle %d, ccb %p, xs->io %p)", handle, ccb,
			    xs->io);
		}

		if (xs->datalen > 0) {
			if (ccb->ccb_dmamap->dm_nsegs >
			    QLE_IOCB_SEGS_PER_CMD) {
				bus_dmamap_sync(sc->sc_dmat,
				    QLE_DMA_MAP(sc->sc_segments),
				    ccb->ccb_seg_offset,
				    sizeof(*ccb->ccb_segs) *
				    ccb->ccb_dmamap->dm_nsegs + 1,
				    BUS_DMASYNC_POSTWRITE);
			}

			bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmamap, 0,
			    ccb->ccb_dmamap->dm_mapsize,
			    (xs->flags & SCSI_DATA_IN) ? BUS_DMASYNC_POSTREAD :
			    BUS_DMASYNC_POSTWRITE);
			bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmamap);
		}

		xs->status = lemtoh16(&status->scsi_status) & 0xff;
		xs->resid = 0;
		completion = lemtoh16(&status->completion);
		switch (completion) {
		case QLE_IOCB_STATUS_DATA_UNDERRUN:
			xs->resid = lemtoh32(&status->resid);
			/* FALLTHROUGH */
		case QLE_IOCB_STATUS_DATA_OVERRUN:
		case QLE_IOCB_STATUS_COMPLETE:
			if (lemtoh16(&status->scsi_status) &
			    QLE_SCSI_STATUS_SENSE_VALID) {
				u_int32_t *pp;
				int sr;
				data = status->data +
				    lemtoh32(&status->fcp_rsp_len);
				sr = MIN(lemtoh32(&status->fcp_sense_len),
				    sizeof(xs->sense));
				memcpy(&xs->sense, data, sr);
				xs->error = XS_SENSE;
				pp = (u_int32_t *)&xs->sense;
				for (sr = 0; sr < sizeof(xs->sense)/4; sr++) {
					pp[sr] = swap32(pp[sr]);
				}
			} else {
				xs->error = XS_NOERROR;
			}
			break;

		case QLE_IOCB_STATUS_DMA_ERROR:
			DPRINTF(QLE_D_IO, "%s: dma error\n", DEVNAME(sc));
			/* set resid apparently? */
			break;

		case QLE_IOCB_STATUS_RESET:
			DPRINTF(QLE_D_IO, "%s: reset destroyed command\n",
			    DEVNAME(sc));
			sc->sc_marker_required = 1;
			xs->error = XS_RESET;
			break;

		case QLE_IOCB_STATUS_ABORTED:
			DPRINTF(QLE_D_IO, "%s: aborted\n", DEVNAME(sc));
			sc->sc_marker_required = 1;
			xs->error = XS_DRIVER_STUFFUP;
			break;

		case QLE_IOCB_STATUS_TIMEOUT:
			DPRINTF(QLE_D_IO, "%s: command timed out\n",
			    DEVNAME(sc));
			xs->error = XS_TIMEOUT;
			break;

		case QLE_IOCB_STATUS_QUEUE_FULL:
			DPRINTF(QLE_D_IO, "%s: queue full\n", DEVNAME(sc));
			xs->error = XS_BUSY;
			break;

		case QLE_IOCB_STATUS_PORT_UNAVAIL:
		case QLE_IOCB_STATUS_PORT_LOGGED_OUT:
		case QLE_IOCB_STATUS_PORT_CHANGED:
			DPRINTF(QLE_D_IO, "%s: dev gone\n", DEVNAME(sc));
			xs->error = XS_SELTIMEOUT;
			/* mark port as needing relogin? */
			break;

		default:
			DPRINTF(QLE_D_IO, "%s: unexpected completion status "
			    "%x\n", DEVNAME(sc), status->completion);
			xs->error = XS_DRIVER_STUFFUP;
			break;
		}
		break;

	case QLE_IOCB_STATUS_CONT:
		DPRINTF(QLE_D_IO, "%s: ignoring status continuation iocb\n",
		    DEVNAME(sc));
		break;

	case QLE_IOCB_PLOGX:
	case QLE_IOCB_CT_PASSTHROUGH:
		if (sc->sc_fabric_pending) {
			qle_dump_iocb(sc, entry);
			memcpy(sc->sc_fabric_response, entry,
			    QLE_QUEUE_ENTRY_SIZE);
			sc->sc_fabric_pending = 2;
			wakeup(sc->sc_scratch);
		} else {
			DPRINTF(QLE_D_IO, "%s: unexpected fabric response %x\n",
			    DEVNAME(sc), entry[0]);
		}
		break;

	case QLE_IOCB_MARKER:
		break;

	case QLE_IOCB_CMD_TYPE_6:
	case QLE_IOCB_CMD_TYPE_7:
		DPRINTF(QLE_D_IO, "%s: request bounced back\n", DEVNAME(sc));
		req = (struct qle_iocb_req6 *)entry;
		handle = req->req_handle;
		if (handle > sc->sc_maxcmds) {
			panic("bad bounced command handle: %d (> %d)",
			    handle, sc->sc_maxcmds);
		}

		ccb = &sc->sc_ccbs[handle];
		xs = ccb->ccb_xs;
		xs->error = XS_DRIVER_STUFFUP;
		break;
	default:
		DPRINTF(QLE_D_IO, "%s: unexpected response entry type %x\n",
		    DEVNAME(sc), entry[0]);
		break;
	}

	return (ccb);
}

void
qle_handle_intr(struct qle_softc *sc, u_int16_t isr, u_int16_t info)
{
	int i;
	u_int32_t rspin;
	struct qle_ccb *ccb;

	switch (isr) {
	case QLE_INT_TYPE_ASYNC:
		qle_async(sc, info);
		break;

	case QLE_INT_TYPE_IO:
		rspin = qle_read(sc, QLE_RESP_IN);
		if (rspin == sc->sc_last_resp_id)
			break;

		do {
			ccb = qle_handle_resp(sc, sc->sc_last_resp_id);
			if (ccb)
				scsi_done(ccb->ccb_xs);

			sc->sc_last_resp_id++;
			sc->sc_last_resp_id %= sc->sc_maxcmds;
		} while (sc->sc_last_resp_id != rspin);

		qle_write(sc, QLE_RESP_OUT, sc->sc_last_resp_id);
		break;

	case QLE_INT_TYPE_MBOX:
		mtx_enter(&sc->sc_mbox_mtx);
		if (sc->sc_mbox_pending) {
			for (i = 0; i < nitems(sc->sc_mbox); i++) {
				sc->sc_mbox[i] = qle_read_mbox(sc, i);
			}
			sc->sc_mbox_pending = 2;
			wakeup(sc->sc_mbox);
			mtx_leave(&sc->sc_mbox_mtx);
		} else {
			mtx_leave(&sc->sc_mbox_mtx);
			DPRINTF(QLE_D_INTR, "%s: unexpected mbox interrupt: "
			    "%x\n", DEVNAME(sc), info);
		}
		break;

	default:
		break;
	}

	qle_clear_isr(sc, isr);
}

int
qle_intr(void *xsc)
{
	struct qle_softc *sc = xsc;
	u_int16_t isr;
	u_int16_t info;

	if (qle_read_isr(sc, &isr, &info) == 0)
		return (0);

	qle_handle_intr(sc, isr, info);
	return (1);
}

int
qle_scsi_probe(struct scsi_link *link)
{
	struct qle_softc *sc = link->bus->sb_adapter_softc;
	int rv = 0;

	mtx_enter(&sc->sc_port_mtx);
	if (sc->sc_targets[link->target] == NULL)
		rv = ENXIO;
	else if (!ISSET(sc->sc_targets[link->target]->flags,
	    QLE_PORT_FLAG_IS_TARGET))
		rv = ENXIO;
	mtx_leave(&sc->sc_port_mtx);

	return (rv);
}

void
qle_scsi_cmd(struct scsi_xfer *xs)
{
	struct scsi_link	*link = xs->sc_link;
	struct qle_softc	*sc = link->bus->sb_adapter_softc;
	struct qle_ccb		*ccb;
	void			*iocb;
	struct qle_ccb_list	list;
	u_int16_t		req;
	u_int32_t		portid;
	int			offset, error, done;
	bus_dmamap_t		dmap;

	if (xs->cmdlen > 16) {
		DPRINTF(QLE_D_IO, "%s: cmd too big (%d)\n", DEVNAME(sc),
		    xs->cmdlen);
		memset(&xs->sense, 0, sizeof(xs->sense));
		xs->sense.error_code = SSD_ERRCODE_VALID | SSD_ERRCODE_CURRENT;
		xs->sense.flags = SKEY_ILLEGAL_REQUEST;
		xs->sense.add_sense_code = 0x20;
		xs->error = XS_SENSE;
		scsi_done(xs);
		return;
	}

	portid = 0xffffffff;
	mtx_enter(&sc->sc_port_mtx);
	if (sc->sc_targets[xs->sc_link->target] != NULL) {
		portid = sc->sc_targets[xs->sc_link->target]->portid;
	}
	mtx_leave(&sc->sc_port_mtx);
	if (portid == 0xffffffff) {
		xs->error = XS_DRIVER_STUFFUP;
		scsi_done(xs);
		return;
	}

	ccb = xs->io;
	dmap = ccb->ccb_dmamap;
	if (xs->datalen > 0) {
		error = bus_dmamap_load(sc->sc_dmat, dmap, xs->data,
		    xs->datalen, NULL, (xs->flags & SCSI_NOSLEEP) ?
		    BUS_DMA_NOWAIT : BUS_DMA_WAITOK);
		if (error) {
			xs->error = XS_DRIVER_STUFFUP;
			scsi_done(xs);
			return;
		}

		bus_dmamap_sync(sc->sc_dmat, dmap, 0,
		    dmap->dm_mapsize,
		    (xs->flags & SCSI_DATA_IN) ? BUS_DMASYNC_PREREAD :
		    BUS_DMASYNC_PREWRITE);
	}

	mtx_enter(&sc->sc_queue_mtx);

	/* put in a sync marker if required */
	if (sc->sc_marker_required) {
		req = sc->sc_next_req_id++;
		if (sc->sc_next_req_id == sc->sc_maxcmds)
			sc->sc_next_req_id = 0;

		DPRINTF(QLE_D_IO, "%s: writing marker at request %d\n",
		    DEVNAME(sc), req);
		offset = (req * QLE_QUEUE_ENTRY_SIZE);
		iocb = QLE_DMA_KVA(sc->sc_requests) + offset;
		bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests),
		    offset, QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE);
		qle_put_marker(sc, iocb);
		qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id);
		sc->sc_marker_required = 0;
	}

	req = sc->sc_next_req_id++;
	if (sc->sc_next_req_id == sc->sc_maxcmds)
		sc->sc_next_req_id = 0;

	offset = (req * QLE_QUEUE_ENTRY_SIZE);
	iocb = QLE_DMA_KVA(sc->sc_requests) + offset;
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset,
	    QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE);

	ccb->ccb_xs = xs;

	qle_put_cmd(sc, iocb, xs, ccb, portid);

	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset,
	    QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_PREREAD);
	qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id);

	if (!ISSET(xs->flags, SCSI_POLL)) {
		mtx_leave(&sc->sc_queue_mtx);
		return;
	}

	done = 0;
	SIMPLEQ_INIT(&list);
	do {
		u_int16_t isr, info;
		u_int32_t rspin;
		delay(100);

		if (qle_read_isr(sc, &isr, &info) == 0) {
			continue;
		}

		if (isr != QLE_INT_TYPE_IO) {
			qle_handle_intr(sc, isr, info);
			continue;
		}

		rspin = qle_read(sc, QLE_RESP_IN);
		while (rspin != sc->sc_last_resp_id) {
			ccb = qle_handle_resp(sc, sc->sc_last_resp_id);

			sc->sc_last_resp_id++;
			if (sc->sc_last_resp_id == sc->sc_maxcmds)
				sc->sc_last_resp_id = 0;

			if (ccb != NULL)
				SIMPLEQ_INSERT_TAIL(&list, ccb, ccb_link);
			if (ccb == xs->io)
				done = 1;
		}
		qle_write(sc, QLE_RESP_OUT, sc->sc_last_resp_id);
		qle_clear_isr(sc, isr);
	} while (done == 0);

	mtx_leave(&sc->sc_queue_mtx);

	while ((ccb = SIMPLEQ_FIRST(&list)) != NULL) {
		SIMPLEQ_REMOVE_HEAD(&list, ccb_link);
		scsi_done(ccb->ccb_xs);
	}
}

u_int32_t
qle_read(struct qle_softc *sc, int offset)
{
	u_int32_t v;
	v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset);
	bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 4,
	    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
	return (v);
}

void
qle_write(struct qle_softc *sc, int offset, u_int32_t value)
{
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, value);
	bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 4,
	    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
}

u_int16_t
qle_read_mbox(struct qle_softc *sc, int mbox)
{
	u_int16_t v;
	bus_size_t offset = mbox * 2;
	v = bus_space_read_2(sc->sc_iot, sc->sc_mbox_ioh, offset);
	bus_space_barrier(sc->sc_iot, sc->sc_mbox_ioh, offset, 2,
	    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
	return (v);
}

void
qle_write_mbox(struct qle_softc *sc, int mbox, u_int16_t value)
{
	bus_size_t offset = (mbox * 2);
	bus_space_write_2(sc->sc_iot, sc->sc_mbox_ioh, offset, value);
	bus_space_barrier(sc->sc_iot, sc->sc_mbox_ioh, offset, 2,
	    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
}

void
qle_host_cmd(struct qle_softc *sc, u_int32_t cmd)
{
	qle_write(sc, QLE_HOST_CMD_CTRL, cmd << QLE_HOST_CMD_SHIFT);
}

#define MBOX_COMMAND_TIMEOUT	400000

int
qle_mbox(struct qle_softc *sc, int maskin)
{
	int i;
	int result = 0;
	int rv;

	for (i = 0; i < nitems(sc->sc_mbox); i++) {
		if (maskin & (1 << i)) {
			qle_write_mbox(sc, i, sc->sc_mbox[i]);
		}
	}
	qle_host_cmd(sc, QLE_HOST_CMD_SET_HOST_INT);

	if (sc->sc_scsibus != NULL) {
		mtx_enter(&sc->sc_mbox_mtx);
		sc->sc_mbox_pending = 1;
		while (sc->sc_mbox_pending == 1) {
			msleep_nsec(sc->sc_mbox, &sc->sc_mbox_mtx, PRIBIO,
			    "qlembox", INFSLP);
		}
		result = sc->sc_mbox[0];
		sc->sc_mbox_pending = 0;
		mtx_leave(&sc->sc_mbox_mtx);
		return (result == QLE_MBOX_COMPLETE ? 0 : result);
	}

	for (i = 0; i < MBOX_COMMAND_TIMEOUT && result == 0; i++) {
		u_int16_t isr, info;

		delay(100);

		if (qle_read_isr(sc, &isr, &info) == 0)
			continue;

		switch (isr) {
		case QLE_INT_TYPE_MBOX:
			result = info;
			break;

		default:
			qle_handle_intr(sc, isr, info);
			break;
		}
	}

	if (result == 0) {
		/* timed out; do something? */
		DPRINTF(QLE_D_MBOX, "%s: mbox timed out\n", DEVNAME(sc));
		rv = 1;
	} else {
		for (i = 0; i < nitems(sc->sc_mbox); i++) {
			sc->sc_mbox[i] = qle_read_mbox(sc, i);
		}
		rv = (result == QLE_MBOX_COMPLETE ? 0 : result);
	}

	qle_clear_isr(sc, QLE_INT_TYPE_MBOX);
	return (rv);
}

void
qle_mbox_putaddr(u_int16_t *mbox, struct qle_dmamem *mem)
{
	mbox[2] = (QLE_DMA_DVA(mem) >> 16) & 0xffff;
	mbox[3] = (QLE_DMA_DVA(mem) >> 0) & 0xffff;
	mbox[6] = (QLE_DMA_DVA(mem) >> 48) & 0xffff;
	mbox[7] = (QLE_DMA_DVA(mem) >> 32) & 0xffff;
}

void
qle_set_ints(struct qle_softc *sc, int enabled)
{
	u_int32_t v = enabled ? QLE_INT_CTRL_ENABLE : 0;
	qle_write(sc, QLE_INT_CTRL, v);
}

int
qle_read_isr(struct qle_softc *sc, u_int16_t *isr, u_int16_t *info)
{
	u_int32_t v;

	switch (sc->sc_isp_gen) {
	case QLE_GEN_ISP24XX:
	case QLE_GEN_ISP25XX:
		if ((qle_read(sc, QLE_INT_STATUS) & QLE_RISC_INT_REQ) == 0)
			return (0);

		v = qle_read(sc, QLE_RISC_STATUS);

		switch (v & QLE_INT_STATUS_MASK) {
		case QLE_24XX_INT_ROM_MBOX:
		case QLE_24XX_INT_ROM_MBOX_FAIL:
		case QLE_24XX_INT_MBOX:
		case QLE_24XX_INT_MBOX_FAIL:
			*isr = QLE_INT_TYPE_MBOX;
			break;

		case QLE_24XX_INT_ASYNC:
			*isr = QLE_INT_TYPE_ASYNC;
			break;

		case QLE_24XX_INT_RSPQ:
			*isr = QLE_INT_TYPE_IO;
			break;

		default:
			*isr = QLE_INT_TYPE_OTHER;
			break;
		}

		*info = (v >> QLE_INT_INFO_SHIFT);
		return (1);

	default:
		return (0);
	}
}

void
qle_clear_isr(struct qle_softc *sc, u_int16_t isr)
{
	qle_host_cmd(sc, QLE_HOST_CMD_CLR_RISC_INT);
}

void
qle_update_done(struct qle_softc *sc, int task)
{
	atomic_clearbits_int(&sc->sc_update_tasks, task);
}

void
qle_update_cancel(struct qle_softc *sc)
{
	atomic_swap_uint(&sc->sc_update_tasks, 0);
	timeout_del(&sc->sc_update_timeout);
	task_del(sc->sc_update_taskq, &sc->sc_update_task);
}

void
qle_update_start(struct qle_softc *sc, int task)
{
	atomic_setbits_int(&sc->sc_update_tasks, task);
	if (!timeout_pending(&sc->sc_update_timeout))
		task_add(sc->sc_update_taskq, &sc->sc_update_task);
}

void
qle_update_defer(struct qle_softc *sc, int task)
{
	atomic_setbits_int(&sc->sc_update_tasks, task);
	timeout_del(&sc->sc_update_timeout);
	task_del(sc->sc_update_taskq, &sc->sc_update_task);
	timeout_add_msec(&sc->sc_update_timeout, QLE_LOOP_SETTLE);
}

void
qle_clear_port_lists(struct qle_softc *sc)
{
	struct qle_fc_port *port;
	while (!TAILQ_EMPTY(&sc->sc_ports_found)) {
		port = TAILQ_FIRST(&sc->sc_ports_found);
		TAILQ_REMOVE(&sc->sc_ports_found, port, update);
		free(port, M_DEVBUF, sizeof *port);
	}

	while (!TAILQ_EMPTY(&sc->sc_ports_new)) {
		port = TAILQ_FIRST(&sc->sc_ports_new);
		TAILQ_REMOVE(&sc->sc_ports_new, port, update);
		free(port, M_DEVBUF, sizeof *port);
	}

	while (!TAILQ_EMPTY(&sc->sc_ports_gone)) {
		port = TAILQ_FIRST(&sc->sc_ports_gone);
		TAILQ_REMOVE(&sc->sc_ports_gone, port, update);
	}
}

int
qle_softreset(struct qle_softc *sc)
{
	int i;
	qle_set_ints(sc, 0);

	/* set led control bits, stop dma */
	qle_write(sc, QLE_GPIO_DATA, 0);
	qle_write(sc, QLE_CTRL_STATUS, QLE_CTRL_DMA_SHUTDOWN);
	while (qle_read(sc, QLE_CTRL_STATUS) & QLE_CTRL_DMA_ACTIVE) {
		DPRINTF(QLE_D_IO, "%s: dma still active\n", DEVNAME(sc));
		delay(100);
	}

	/* reset */
	qle_write(sc, QLE_CTRL_STATUS, QLE_CTRL_RESET | QLE_CTRL_DMA_SHUTDOWN);
	delay(100);
	/* clear data and control dma engines? */

	/* wait for soft reset to clear */
	for (i = 0; i < 1000; i++) {
		if (qle_read_mbox(sc, 0) == 0x0000)
			break;

		delay(100);
	}

	if (i == 1000) {
		printf("%s: reset mbox didn't clear\n", DEVNAME(sc));
		qle_set_ints(sc, 0);
		return (ENXIO);
	}

	for (i = 0; i < 500000; i++) {
		if ((qle_read(sc, QLE_CTRL_STATUS) & QLE_CTRL_RESET) == 0)
			break;
		delay(5);
	}
	if (i == 500000) {
		printf("%s: reset status didn't clear\n", DEVNAME(sc));
		return (ENXIO);
	}

	/* reset risc processor */
	qle_host_cmd(sc, QLE_HOST_CMD_RESET);
	qle_host_cmd(sc, QLE_HOST_CMD_RELEASE);
	qle_host_cmd(sc, QLE_HOST_CMD_CLEAR_RESET);

	/* wait for reset to clear */
	for (i = 0; i < 1000; i++) {
		if (qle_read_mbox(sc, 0) == 0x0000)
			break;
		delay(100);
	}
	if (i == 1000) {
		printf("%s: risc not ready after reset\n", DEVNAME(sc));
		return (ENXIO);
	}

	/* reset queue pointers */
	qle_write(sc, QLE_REQ_IN, 0);
	qle_write(sc, QLE_REQ_OUT, 0);
	qle_write(sc, QLE_RESP_IN, 0);
	qle_write(sc, QLE_RESP_OUT, 0);

	qle_set_ints(sc, 1);

	/* do a basic mailbox operation to check we're alive */
	sc->sc_mbox[0] = QLE_MBOX_NOP;
	if (qle_mbox(sc, 0x0001)) {
		printf("ISP not responding after reset\n");
		return (ENXIO);
	}

	return (0);
}

void
qle_update_topology(struct qle_softc *sc)
{
	sc->sc_mbox[0] = QLE_MBOX_GET_ID;
	if (qle_mbox(sc, 0x0001)) {
		DPRINTF(QLE_D_PORT, "%s: unable to get loop id\n", DEVNAME(sc));
		sc->sc_topology = QLE_TOPO_N_PORT_NO_TARGET;
	} else {
		sc->sc_topology = sc->sc_mbox[6];
		sc->sc_loop_id = sc->sc_mbox[1];

		switch (sc->sc_topology) {
		case QLE_TOPO_NL_PORT:
		case QLE_TOPO_N_PORT:
			DPRINTF(QLE_D_PORT, "%s: loop id %d\n", DEVNAME(sc),
			    sc->sc_loop_id);
			break;

		case QLE_TOPO_FL_PORT:
		case QLE_TOPO_F_PORT:
			sc->sc_port_id = sc->sc_mbox[2] |
			    (sc->sc_mbox[3] << 16);
			DPRINTF(QLE_D_PORT, "%s: fabric port id %06x\n",
			    DEVNAME(sc), sc->sc_port_id);
			break;

		case QLE_TOPO_N_PORT_NO_TARGET:
		default:
			DPRINTF(QLE_D_PORT, "%s: not useful\n", DEVNAME(sc));
			break;
		}

		switch (sc->sc_topology) {
		case QLE_TOPO_NL_PORT:
		case QLE_TOPO_FL_PORT:
			sc->sc_loop_max_id = 126;
			break;

		case QLE_TOPO_N_PORT:
			sc->sc_loop_max_id = 2;
			break;

		default:
			sc->sc_loop_max_id = 0;
			break;
		}
	}
}

int
qle_update_fabric(struct qle_softc *sc)
{
	/*struct qle_sns_rft_id *rft;*/

	switch (sc->sc_topology) {
	case QLE_TOPO_F_PORT:
	case QLE_TOPO_FL_PORT:
		break;

	default:
		return (0);
	}

	/* get the name server's port db entry */
	sc->sc_mbox[0] = QLE_MBOX_GET_PORT_DB;
	sc->sc_mbox[1] = QLE_F_PORT_HANDLE;
	qle_mbox_putaddr(sc->sc_mbox, sc->sc_scratch);
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0,
	    sizeof(struct qle_get_port_db), BUS_DMASYNC_PREREAD);
	if (qle_mbox(sc, 0x00cf)) {
		DPRINTF(QLE_D_PORT, "%s: get port db for SNS failed: %x\n",
		    DEVNAME(sc), sc->sc_mbox[0]);
		sc->sc_sns_port_name = 0;
	} else {
		struct qle_get_port_db *pdb;
		bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_scratch), 0,
		    sizeof(struct qle_get_port_db), BUS_DMASYNC_POSTREAD);
		pdb = QLE_DMA_KVA(sc->sc_scratch);
		DPRINTF(QLE_D_PORT, "%s: SNS port name %llx\n", DEVNAME(sc),
		    betoh64(pdb->port_name));
		sc->sc_sns_port_name = betoh64(pdb->port_name);
	}

	/*
	 * register fc4 types with the fabric
	 * some switches do this automatically, but apparently
	 * some don't.
	 */
	/*
	rft = QLE_DMA_KVA(sc->sc_scratch);
	memset(rft, 0, sizeof(*rft) + sizeof(struct qle_sns_req_hdr));
	htolem16(&rft->subcmd, QLE_SNS_RFT_ID);
	htolem16(&rft->max_word, sizeof(struct qle_sns_req_hdr) / 4);
	htolem32(&rft->port_id, sc->sc_port_id);
	rft->fc4_types[0] = (1 << QLE_FC4_SCSI);
	if (qle_sns_req(sc, sc->sc_scratch, sizeof(*rft))) {
		printf("%s: RFT_ID failed\n", DEVNAME(sc));
		/ * we might be able to continue after this fails * /
	}
	*/

	return (1);
}

int
qle_ct_pass_through(struct qle_softc *sc, u_int32_t port_handle,
    struct qle_dmamem *mem, size_t req_size, size_t resp_size)
{
	struct qle_iocb_ct_passthrough *iocb;
	u_int16_t req;
	u_int64_t offset;
	int rv;

	mtx_enter(&sc->sc_queue_mtx);

	req = sc->sc_next_req_id++;
	if (sc->sc_next_req_id == sc->sc_maxcmds)
		sc->sc_next_req_id = 0;

	offset = (req * QLE_QUEUE_ENTRY_SIZE);
	iocb = QLE_DMA_KVA(sc->sc_requests) + offset;
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset,
	    QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE);

	memset(iocb, 0, QLE_QUEUE_ENTRY_SIZE);
	iocb->entry_type = QLE_IOCB_CT_PASSTHROUGH;
	iocb->entry_count = 1;

	iocb->req_handle = 9;
	htolem16(&iocb->req_nport_handle, port_handle);
	htolem16(&iocb->req_dsd_count, 1);
	htolem16(&iocb->req_resp_dsd_count, 1);
	htolem32(&iocb->req_cmd_byte_count, req_size);
	htolem32(&iocb->req_resp_byte_count, resp_size);
	qle_sge(&iocb->req_cmd_seg, QLE_DMA_DVA(mem), req_size);
	qle_sge(&iocb->req_resp_seg, QLE_DMA_DVA(mem) + req_size, resp_size);

	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, QLE_DMA_LEN(mem),
	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
	qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id);
	sc->sc_fabric_pending = 1;
	mtx_leave(&sc->sc_queue_mtx);

	/* maybe put a proper timeout on this */
	rv = 0;
	while (sc->sc_fabric_pending == 1) {
		if (sc->sc_scsibus == NULL) {
			u_int16_t isr, info;

			delay(100);
			if (qle_read_isr(sc, &isr, &info) != 0)
				qle_handle_intr(sc, isr, info);
		} else {
			tsleep_nsec(sc->sc_scratch, PRIBIO, "qle_fabric",
			    SEC_TO_NSEC(1));
		}
	}
	if (rv == 0)
		bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0,
		    QLE_DMA_LEN(mem), BUS_DMASYNC_POSTREAD |
		    BUS_DMASYNC_POSTWRITE);

	sc->sc_fabric_pending = 0;

	return (rv);
}

struct qle_fc_port *
qle_next_fabric_port(struct qle_softc *sc, u_int32_t *firstport,
    u_int32_t *lastport)
{
	struct qle_ct_ga_nxt_req *ga;
	struct qle_ct_ga_nxt_resp *gar;
	struct qle_fc_port *fport;
	int result;

	/* get the next port from the fabric nameserver */
	ga = QLE_DMA_KVA(sc->sc_scratch);
	memset(ga, 0, sizeof(*ga) + sizeof(*gar));
	ga->header.ct_revision = 0x01;
	ga->header.ct_gs_type = 0xfc;
	ga->header.ct_gs_subtype = 0x02;
	ga->subcmd = htobe16(QLE_SNS_GA_NXT);
	ga->max_word = htobe16((sizeof(*gar) - 16) / 4);
	ga->port_id = htobe32(*lastport);
	result = qle_ct_pass_through(sc, QLE_SNS_HANDLE, sc->sc_scratch,
	    sizeof(*ga), sizeof(*gar));
	if (result) {
		DPRINTF(QLE_D_PORT, "%s: GA_NXT %06x failed: %x\n", DEVNAME(sc),
		    *lastport, result);
		*lastport = 0xffffffff;
		return (NULL);
	}

	gar = (struct qle_ct_ga_nxt_resp *)(ga + 1);
	/* if the response is all zeroes, try again */
	if (gar->port_type_id == 0 && gar->port_name == 0 &&
	    gar->node_name == 0) {
		DPRINTF(QLE_D_PORT, "%s: GA_NXT returned junk\n", DEVNAME(sc));
		return (NULL);
	}

	/* are we back at the start? */
	*lastport = betoh32(gar->port_type_id) & 0xffffff;
	if (*lastport == *firstport) {
		*lastport = 0xffffffff;
		return (NULL);
	}
	if (*firstport == 0xffffffff)
		*firstport = *lastport;

	DPRINTF(QLE_D_PORT, "%s: GA_NXT: port id: %06x, wwpn %llx, wwnn %llx\n",
	    DEVNAME(sc), *lastport, betoh64(gar->port_name),
	    betoh64(gar->node_name));

	/* don't try to log in to ourselves */
	if (*lastport == sc->sc_port_id) {
		return (NULL);
	}

	fport = malloc(sizeof(*fport), M_DEVBUF, M_ZERO | M_NOWAIT);
	if (fport == NULL) {
		printf("%s: failed to allocate a port struct\n",
		    DEVNAME(sc));
		*lastport = 0xffffffff;
		return (NULL);
	}
	fport->port_name = betoh64(gar->port_name);
	fport->node_name = betoh64(gar->node_name);
	fport->location = QLE_LOCATION_PORT_ID(*lastport);
	fport->portid = *lastport;
	return (fport);
}

int
qle_fabric_plogx(struct qle_softc *sc, struct qle_fc_port *port, int flags,
    u_int32_t *info)
{
	struct qle_iocb_plogx *iocb;
	u_int16_t req;
	u_int64_t offset;
	int rv;

	mtx_enter(&sc->sc_queue_mtx);

	req = sc->sc_next_req_id++;
	if (sc->sc_next_req_id == sc->sc_maxcmds)
		sc->sc_next_req_id = 0;

	offset = (req * QLE_QUEUE_ENTRY_SIZE);
	iocb = QLE_DMA_KVA(sc->sc_requests) + offset;
	bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(sc->sc_requests), offset,
	    QLE_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE);

	memset(iocb, 0, QLE_QUEUE_ENTRY_SIZE);
	iocb->entry_type = QLE_IOCB_PLOGX;
	iocb->entry_count = 1;

	iocb->req_handle = 7;
	htolem16(&iocb->req_nport_handle, port->loopid);
	htolem16(&iocb->req_port_id_lo, port->portid);
	iocb->req_port_id_hi = port->portid >> 16;
	htolem16(&iocb->req_flags, flags);

	DPRINTF(QLE_D_PORT, "%s: plogx loop id %d port %06x, flags %x\n",
	    DEVNAME(sc), port->loopid, port->portid, flags);
	qle_dump_iocb(sc, iocb);

	qle_write(sc, QLE_REQ_IN, sc->sc_next_req_id);
	sc->sc_fabric_pending = 1;
	mtx_leave(&sc->sc_queue_mtx);

	/* maybe put a proper timeout on this */
	rv = 0;
	while (sc->sc_fabric_pending == 1) {
		if (sc->sc_scsibus == NULL) {
			u_int16_t isr, info;

			delay(100);
			if (qle_read_isr(sc, &isr, &info) != 0)
				qle_handle_intr(sc, isr, info);
		} else {
			tsleep_nsec(sc->sc_scratch, PRIBIO, "qle_fabric",
			    SEC_TO_NSEC(1));
		}
	}
	sc->sc_fabric_pending = 0;

	iocb = (struct qle_iocb_plogx *)&sc->sc_fabric_response;
	rv = lemtoh16(&iocb->req_status);
	if (rv == QLE_PLOGX_ERROR) {
		rv = lemtoh32(&iocb->req_ioparms[0]);
		*info = lemtoh32(&iocb->req_ioparms[1]);
	}

	return (rv);
}

int
qle_fabric_plogi(struct qle_softc *sc, struct qle_fc_port *port)
{
	u_int32_t info;
	int err, loopid;

	loopid = 0;
retry:
	if (port->loopid == 0) {

		mtx_enter(&sc->sc_port_mtx);
		loopid = qle_get_loop_id(sc, loopid);
		mtx_leave(&sc->sc_port_mtx);
		if (loopid == -1) {
			printf("%s: ran out of loop ids\n", DEVNAME(sc));
			return (1);
		}

		port->loopid = loopid;
	}

	err = qle_fabric_plogx(sc, port, QLE_PLOGX_LOGIN, &info);
	switch (err) {
	case 0:
		DPRINTF(QLE_D_PORT, "%s: logged in to %06x as %d\n",
		    DEVNAME(sc), port->portid, port->loopid);
		port->flags &= ~QLE_PORT_FLAG_NEEDS_LOGIN;
		return (0);

	case QLE_PLOGX_ERROR_PORT_ID_USED:
		DPRINTF(QLE_D_PORT, "%s: already logged in to %06x as %d\n",
		    DEVNAME(sc), port->portid, info);
		port->loopid = info;
		port->flags &= ~QLE_PORT_FLAG_NEEDS_LOGIN;
		return (0);

	case QLE_PLOGX_ERROR_HANDLE_USED:
		if (qle_add_logged_in_port(sc, loopid, info)) {
			return (1);
		}
		port->loopid = 0;
		loopid++;
		goto retry;

	default:
		DPRINTF(QLE_D_PORT, "%s: error %x logging in to port %06x\n",
		    DEVNAME(sc), err, port->portid);
		port->loopid = 0;
		return (1);
	}
}

void
qle_fabric_plogo(struct qle_softc *sc, struct qle_fc_port *port)
{
	int err;
	u_int32_t info;

	/*
	 * we only log out if we can't see the port any more, so we always
	 * want to do an explicit logout and free the n-port handle.
	 */
	err = qle_fabric_plogx(sc, port, QLE_PLOGX_LOGOUT |
	    QLE_PLOGX_LOGOUT_EXPLICIT | QLE_PLOGX_LOGOUT_FREE_HANDLE, &info);
	if (err == 0) {
		DPRINTF(QLE_D_PORT, "%s: logged out of port %06x\n",
		    DEVNAME(sc), port->portid);
	} else {
		DPRINTF(QLE_D_PORT, "%s: failed to log out of port %06x: "
		    "%x %x\n", DEVNAME(sc), port->portid, err, info);
	}
}

void
qle_deferred_update(void *xsc)
{
	struct qle_softc *sc = xsc;
	task_add(sc->sc_update_taskq, &sc->sc_update_task);
}

void
qle_do_update(void *xsc)
{
	struct qle_softc *sc = xsc;
	int firstport, lastport;
	struct qle_fc_port *port, *fport;

	DPRINTF(QLE_D_PORT, "%s: updating\n", DEVNAME(sc));
	while (sc->sc_update_tasks != 0) {
		if (sc->sc_update_tasks & QLE_UPDATE_TASK_CLEAR_ALL) {
			TAILQ_HEAD(, qle_fc_port) detach;
			DPRINTF(QLE_D_PORT, "%s: detaching everything\n",
			    DEVNAME(sc));

			mtx_enter(&sc->sc_port_mtx);
			qle_clear_port_lists(sc);
			TAILQ_INIT(&detach);
			TAILQ_CONCAT(&detach, &sc->sc_ports, ports);
			mtx_leave(&sc->sc_port_mtx);

			while (!TAILQ_EMPTY(&detach)) {
				port = TAILQ_FIRST(&detach);
				TAILQ_REMOVE(&detach, port, ports);
				if (port->flags & QLE_PORT_FLAG_IS_TARGET) {
					scsi_detach_target(sc->sc_scsibus,
					    port->loopid, DETACH_FORCE |
					    DETACH_QUIET);
					sc->sc_targets[port->loopid] = NULL;
				}
				if (port->location & QLE_LOCATION_FABRIC)
					qle_fabric_plogo(sc, port);

				free(port, M_DEVBUF, sizeof *port);
			}

			qle_update_done(sc, QLE_UPDATE_TASK_CLEAR_ALL);
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_SOFTRESET) {
			DPRINTF(QLE_D_IO, "%s: attempting softreset\n",
			    DEVNAME(sc));
			if (qle_softreset(sc) != 0) {
				DPRINTF(QLE_D_IO, "%s: couldn't softreset\n",
				    DEVNAME(sc));
			}
			qle_update_done(sc, QLE_UPDATE_TASK_SOFTRESET);
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_UPDATE_TOPO) {
			DPRINTF(QLE_D_PORT, "%s: updating topology\n",
			    DEVNAME(sc));
			qle_update_topology(sc);
			qle_update_done(sc, QLE_UPDATE_TASK_UPDATE_TOPO);
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_GET_PORT_LIST) {
			DPRINTF(QLE_D_PORT, "%s: getting port name list\n",
			    DEVNAME(sc));
			mtx_enter(&sc->sc_port_mtx);
			qle_clear_port_lists(sc);
			mtx_leave(&sc->sc_port_mtx);

			qle_get_port_name_list(sc, QLE_LOCATION_LOOP |
			    QLE_LOCATION_FABRIC);
			mtx_enter(&sc->sc_port_mtx);
			TAILQ_FOREACH(port, &sc->sc_ports, ports) {
				TAILQ_INSERT_TAIL(&sc->sc_ports_gone, port,
				    update);
				if (port->location & QLE_LOCATION_FABRIC) {
					port->flags |=
					    QLE_PORT_FLAG_NEEDS_LOGIN;
				}
			}

			/* take care of ports that haven't changed first */
			TAILQ_FOREACH(fport, &sc->sc_ports_found, update) {
				port = sc->sc_targets[fport->loopid];
				if (port == NULL || fport->port_name !=
				    port->port_name) {
					/* new or changed port, handled later */
					continue;
				}

				/*
				 * the port hasn't been logged out, which
				 * means we don't need to log in again, and,
				 * for loop ports, that the port still exists
				 */
				port->flags &= ~QLE_PORT_FLAG_NEEDS_LOGIN;
				if (port->location & QLE_LOCATION_LOOP)
					TAILQ_REMOVE(&sc->sc_ports_gone,
					    port, update);

				fport->location = 0;
			}
			mtx_leave(&sc->sc_port_mtx);
			qle_update_start(sc, QLE_UPDATE_TASK_PORT_LIST);
			qle_update_done(sc, QLE_UPDATE_TASK_GET_PORT_LIST);
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_PORT_LIST) {
			mtx_enter(&sc->sc_port_mtx);
			fport = TAILQ_FIRST(&sc->sc_ports_found);
			if (fport != NULL) {
				TAILQ_REMOVE(&sc->sc_ports_found, fport,
				    update);
			}
			mtx_leave(&sc->sc_port_mtx);

			if (fport == NULL) {
				DPRINTF(QLE_D_PORT, "%s: done with ports\n",
				    DEVNAME(sc));
				qle_update_done(sc,
				    QLE_UPDATE_TASK_PORT_LIST);
				qle_update_start(sc,
				    QLE_UPDATE_TASK_SCAN_FABRIC);
			} else if (fport->location & QLE_LOCATION_LOOP) {
				DPRINTF(QLE_D_PORT, "%s: loop port %04x\n",
				    DEVNAME(sc), fport->loopid);
				if (qle_add_loop_port(sc, fport) != 0)
					free(fport, M_DEVBUF, sizeof *port);
			} else if (fport->location & QLE_LOCATION_FABRIC) {
				qle_add_fabric_port(sc, fport);
			} else {
				/* already processed */
				free(fport, M_DEVBUF, sizeof *port);
			}
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCAN_FABRIC) {
			DPRINTF(QLE_D_PORT, "%s: starting fabric scan\n",
			    DEVNAME(sc));
			lastport = sc->sc_port_id;
			firstport = 0xffffffff;
			if (qle_update_fabric(sc))
				qle_update_start(sc,
				    QLE_UPDATE_TASK_SCANNING_FABRIC);
			else
				qle_update_start(sc,
				    QLE_UPDATE_TASK_ATTACH_TARGET |
				    QLE_UPDATE_TASK_DETACH_TARGET);

			qle_update_done(sc, QLE_UPDATE_TASK_SCAN_FABRIC);
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_SCANNING_FABRIC) {
			fport = qle_next_fabric_port(sc, &firstport, &lastport);
			if (fport != NULL) {
				int disp;

				mtx_enter(&sc->sc_port_mtx);
				disp = qle_classify_port(sc, fport->location,
				    fport->port_name, fport->node_name, &port);
				switch (disp) {
				case QLE_PORT_DISP_CHANGED:
				case QLE_PORT_DISP_MOVED:
					/* we'll log out the old port later */
				case QLE_PORT_DISP_NEW:
					DPRINTF(QLE_D_PORT, "%s: new port "
					    "%06x\n", DEVNAME(sc),
					    fport->portid);
					TAILQ_INSERT_TAIL(&sc->sc_ports_found,
					    fport, update);
					break;
				case QLE_PORT_DISP_DUP:
					free(fport, M_DEVBUF, sizeof *port);
					break;
				case QLE_PORT_DISP_SAME:
					DPRINTF(QLE_D_PORT, "%s: existing port "
					    " %06x\n", DEVNAME(sc),
					    fport->portid);
					TAILQ_REMOVE(&sc->sc_ports_gone, port,
					    update);
					free(fport, M_DEVBUF, sizeof *port);
					break;
				}
				mtx_leave(&sc->sc_port_mtx);
			}
			if (lastport == 0xffffffff) {
				DPRINTF(QLE_D_PORT, "%s: finished\n",
				    DEVNAME(sc));
				qle_update_done(sc,
				    QLE_UPDATE_TASK_SCANNING_FABRIC);
				qle_update_start(sc,
				    QLE_UPDATE_TASK_FABRIC_LOGIN);
			}
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_FABRIC_LOGIN) {
			mtx_enter(&sc->sc_port_mtx);
			port = TAILQ_FIRST(&sc->sc_ports_found);
			if (port != NULL) {
				TAILQ_REMOVE(&sc->sc_ports_found, port, update);
			}
			mtx_leave(&sc->sc_port_mtx);

			if (port != NULL) {
				DPRINTF(QLE_D_PORT, "%s: found port %06x\n",
				    DEVNAME(sc), port->portid);
				if (qle_fabric_plogi(sc, port) == 0) {
					qle_add_fabric_port(sc, port);
				} else {
					DPRINTF(QLE_D_PORT, "%s: plogi %06x "
					    "failed\n", DEVNAME(sc),
					    port->portid);
					free(port, M_DEVBUF, sizeof *port);
				}
			} else {
				DPRINTF(QLE_D_PORT, "%s: done with logins\n",
				    DEVNAME(sc));
				qle_update_done(sc,
				    QLE_UPDATE_TASK_FABRIC_LOGIN);
				qle_update_start(sc,
				    QLE_UPDATE_TASK_ATTACH_TARGET |
				    QLE_UPDATE_TASK_DETACH_TARGET);
			}
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_FABRIC_RELOGIN) {
			TAILQ_FOREACH(port, &sc->sc_ports, ports) {
				if (port->flags & QLE_PORT_FLAG_NEEDS_LOGIN) {
					qle_fabric_plogi(sc, port);
					break;
				}
			}

			if (port == NULL)
				qle_update_done(sc,
				    QLE_UPDATE_TASK_FABRIC_RELOGIN);
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_DETACH_TARGET) {
			mtx_enter(&sc->sc_port_mtx);
			port = TAILQ_FIRST(&sc->sc_ports_gone);
			if (port != NULL) {
				sc->sc_targets[port->loopid] = NULL;
				TAILQ_REMOVE(&sc->sc_ports_gone, port, update);
				TAILQ_REMOVE(&sc->sc_ports, port, ports);
			}
			mtx_leave(&sc->sc_port_mtx);

			if (port != NULL) {
				DPRINTF(QLE_D_PORT, "%s: detaching port %06x\n",
				    DEVNAME(sc), port->portid);
				if (sc->sc_scsibus != NULL)
					scsi_detach_target(sc->sc_scsibus,
					    port->loopid, DETACH_FORCE |
					    DETACH_QUIET);

				if (port->location & QLE_LOCATION_FABRIC)
					qle_fabric_plogo(sc, port);

				free(port, M_DEVBUF, sizeof *port);
			} else {
				DPRINTF(QLE_D_PORT, "%s: nothing to detach\n",
				    DEVNAME(sc));
				qle_update_done(sc,
				    QLE_UPDATE_TASK_DETACH_TARGET);
			}
			continue;
		}

		if (sc->sc_update_tasks & QLE_UPDATE_TASK_ATTACH_TARGET) {
			mtx_enter(&sc->sc_port_mtx);
			port = TAILQ_FIRST(&sc->sc_ports_new);
			if (port != NULL) {
				TAILQ_REMOVE(&sc->sc_ports_new, port, update);
				TAILQ_INSERT_TAIL(&sc->sc_ports, port, ports);
			}
			mtx_leave(&sc->sc_port_mtx);

			if (port != NULL) {
				if (sc->sc_scsibus != NULL)
					scsi_probe_target(sc->sc_scsibus,
					    port->loopid);
			} else {
				qle_update_done(sc,
				    QLE_UPDATE_TASK_ATTACH_TARGET);
			}
			continue;
		}

	}

	DPRINTF(QLE_D_PORT, "%s: done updating\n", DEVNAME(sc));
}

int
qle_async(struct qle_softc *sc, u_int16_t info)
{
	switch (info) {
	case QLE_ASYNC_SYSTEM_ERROR:
		qle_update_start(sc, QLE_UPDATE_TASK_SOFTRESET);
		break;

	case QLE_ASYNC_REQ_XFER_ERROR:
		qle_update_start(sc, QLE_UPDATE_TASK_SOFTRESET);
		break;

	case QLE_ASYNC_RSP_XFER_ERROR:
		qle_update_start(sc, QLE_UPDATE_TASK_SOFTRESET);
		break;

	case QLE_ASYNC_LIP_OCCURRED:
		DPRINTF(QLE_D_INTR, "%s: lip occurred\n", DEVNAME(sc));
		break;

	case QLE_ASYNC_LOOP_UP:
		DPRINTF(QLE_D_PORT, "%s: loop up\n", DEVNAME(sc));
		sc->sc_loop_up = 1;
		sc->sc_marker_required = 1;
		qle_update_defer(sc, QLE_UPDATE_TASK_UPDATE_TOPO |
		    QLE_UPDATE_TASK_GET_PORT_LIST);
		break;

	case QLE_ASYNC_LOOP_DOWN:
		DPRINTF(QLE_D_PORT, "%s: loop down\n", DEVNAME(sc));
		sc->sc_loop_up = 0;
		qle_update_cancel(sc);
		qle_update_start(sc, QLE_UPDATE_TASK_CLEAR_ALL);
		break;

	case QLE_ASYNC_LIP_RESET:
		DPRINTF(QLE_D_PORT, "%s: lip reset\n", DEVNAME(sc));
		sc->sc_marker_required = 1;
		qle_update_defer(sc, QLE_UPDATE_TASK_FABRIC_RELOGIN);
		break;

	case QLE_ASYNC_PORT_DB_CHANGE:
		DPRINTF(QLE_D_PORT, "%s: port db changed %x\n", DEVNAME(sc),
		    qle_read_mbox(sc, 1));
		qle_update_start(sc, QLE_UPDATE_TASK_GET_PORT_LIST);
		break;

	case QLE_ASYNC_CHANGE_NOTIFY:
		DPRINTF(QLE_D_PORT, "%s: name server change (%02x:%02x)\n",
		    DEVNAME(sc), qle_read_mbox(sc, 1), qle_read_mbox(sc, 2));
		qle_update_start(sc, QLE_UPDATE_TASK_GET_PORT_LIST);
		break;

	case QLE_ASYNC_LIP_F8:
		DPRINTF(QLE_D_INTR, "%s: lip f8\n", DEVNAME(sc));
		break;

	case QLE_ASYNC_LOOP_INIT_ERROR:
		DPRINTF(QLE_D_PORT, "%s: loop initialization error: %x\n",
		    DEVNAME(sc), qle_read_mbox(sc, 1));
		break;

	case QLE_ASYNC_POINT_TO_POINT:
		DPRINTF(QLE_D_PORT, "%s: connected in point-to-point mode\n",
		    DEVNAME(sc));
		break;

	case QLE_ASYNC_ZIO_RESP_UPDATE:
		/* shouldn't happen, we don't do zio */
		break;

	default:
		DPRINTF(QLE_D_INTR, "%s: unknown async %x\n", DEVNAME(sc), info);
		break;
	}
	return (1);
}

void
qle_dump_stuff(struct qle_softc *sc, void *buf, int n)
{
#ifdef QLE_DEBUG
	u_int8_t *d = buf;
	int l;

	if ((qledebug & QLE_D_IOCB) == 0)
		return;

	printf("%s: stuff\n", DEVNAME(sc));
	for (l = 0; l < n; l++) {
		printf(" %2.2x", d[l]);
		if (l % 16 == 15)
			printf("\n");
	}
	if (n % 16 != 0)
		printf("\n");
#endif
}

void
qle_dump_iocb(struct qle_softc *sc, void *buf)
{
#ifdef QLE_DEBUG
	u_int8_t *iocb = buf;
	int l;
	int b;

	if ((qledebug & QLE_D_IOCB) == 0)
		return;

	printf("%s: iocb:\n", DEVNAME(sc));
	for (l = 0; l < 4; l++) {
		for (b = 0; b < 16; b++) {
			printf(" %2.2x", iocb[(l*16)+b]);
		}
		printf("\n");
	}
#endif
}

void
qle_dump_iocb_segs(struct qle_softc *sc, void *segs, int n)
{
#ifdef QLE_DEBUG
	u_int8_t *buf = segs;
	int s, b;

	if ((qledebug & QLE_D_IOCB) == 0)
		return;

	printf("%s: iocb segs:\n", DEVNAME(sc));
	for (s = 0; s < n; s++) {
		for (b = 0; b < sizeof(struct qle_iocb_seg); b++) {
			printf(" %2.2x", buf[(s*(sizeof(struct qle_iocb_seg)))
			    + b]);
		}
		printf("\n");
	}
#endif
}

void
qle_put_marker(struct qle_softc *sc, void *buf)
{
	struct qle_iocb_marker *marker = buf;

	marker->entry_type = QLE_IOCB_MARKER;
	marker->entry_count = 1;
	marker->seqno = 0;
	marker->flags = 0;

	/* could be more specific here; isp(4) isn't */
	marker->target = 0;
	marker->modifier = QLE_IOCB_MARKER_SYNC_ALL;
}

void
qle_sge(struct qle_iocb_seg *seg, u_int64_t addr, u_int32_t len)
{
	htolem32(&seg->seg_addr_lo, addr);
	htolem32(&seg->seg_addr_hi, addr >> 32);
	htolem32(&seg->seg_len, len);
}

void
qle_put_cmd(struct qle_softc *sc, void *buf, struct scsi_xfer *xs,
    struct qle_ccb *ccb, u_int32_t target_port)
{
	bus_dmamap_t dmap = ccb->ccb_dmamap;
	struct qle_iocb_req6 *req = buf;
	struct qle_fcp_cmnd *cmnd;
	u_int64_t fcp_cmnd_offset;
	u_int32_t fcp_dl;
	int seg;
	int target = xs->sc_link->target;
	int lun = xs->sc_link->lun;
	u_int16_t flags;

	memset(req, 0, sizeof(*req));
	req->entry_type = QLE_IOCB_CMD_TYPE_6;
	req->entry_count = 1;

	req->req_handle = ccb->ccb_id;
	htolem16(&req->req_nport_handle, target);

	/*
	 * timeout is in seconds.  make sure it's at least 1 if a timeout
	 * was specified in xs
	 */
	if (xs->timeout != 0)
		htolem16(&req->req_timeout, MAX(1, xs->timeout/1000));

	if (xs->datalen > 0) {
		flags = (xs->flags & SCSI_DATA_IN) ?
		    QLE_IOCB_CTRL_FLAG_READ : QLE_IOCB_CTRL_FLAG_WRITE;
		if (dmap->dm_nsegs == 1) {
			qle_sge(&req->req_data_seg, dmap->dm_segs[0].ds_addr,
			    dmap->dm_segs[0].ds_len);
		} else {
			flags |= QLE_IOCB_CTRL_FLAG_EXT_SEG;
			for (seg = 0; seg < dmap->dm_nsegs; seg++) {
				qle_sge(&ccb->ccb_segs[seg],
				    dmap->dm_segs[seg].ds_addr,
				    dmap->dm_segs[seg].ds_len);
			}
			qle_sge(&ccb->ccb_segs[seg++], 0, 0);

			bus_dmamap_sync(sc->sc_dmat,
			    QLE_DMA_MAP(sc->sc_segments), ccb->ccb_seg_offset,
			    seg * sizeof(*ccb->ccb_segs),
			    BUS_DMASYNC_PREWRITE);

			qle_sge(&req->req_data_seg,
			    QLE_DMA_DVA(sc->sc_segments) + ccb->ccb_seg_offset,
			    seg * sizeof(struct qle_iocb_seg));
		}

		htolem16(&req->req_data_seg_count, dmap->dm_nsegs);
		htolem32(&req->req_data_len, xs->datalen);
		htolem16(&req->req_ctrl_flags, flags);
	}

	htobem16(&req->req_fcp_lun[0], lun);
	htobem16(&req->req_fcp_lun[1], lun >> 16);
	htolem32(&req->req_target_id, target_port & 0xffffff);

	fcp_cmnd_offset = ccb->ccb_id * sizeof(*cmnd);
	/* set up FCP_CMND */
	cmnd = (struct qle_fcp_cmnd *)QLE_DMA_KVA(sc->sc_fcp_cmnds) +
	    ccb->ccb_id;

	memset(cmnd, 0, sizeof(*cmnd));
	htobem16(&cmnd->fcp_lun[0], lun);
	htobem16(&cmnd->fcp_lun[1], lun >> 16);
	/* cmnd->fcp_task_attr = TSK_SIMPLE; */
	/* cmnd->fcp_task_mgmt = 0; */
	memcpy(cmnd->fcp_cdb, &xs->cmd, xs->cmdlen);

	/* FCP_DL goes after the cdb */
	fcp_dl = htobe32(xs->datalen);
	if (xs->cmdlen > 16) {
		htolem16(&req->req_fcp_cmnd_len, 12 + xs->cmdlen + 4);
		cmnd->fcp_add_cdb_len = xs->cmdlen - 16;
		memcpy(cmnd->fcp_cdb + xs->cmdlen, &fcp_dl, sizeof(fcp_dl));
	} else {
		htolem16(&req->req_fcp_cmnd_len, 12 + 16 + 4);
		cmnd->fcp_add_cdb_len = 0;
		memcpy(cmnd->fcp_cdb + 16, &fcp_dl, sizeof(fcp_dl));
	}
	if (xs->datalen > 0)
		cmnd->fcp_add_cdb_len |= (xs->flags & SCSI_DATA_IN) ? 2 : 1;

	bus_dmamap_sync(sc->sc_dmat,
	    QLE_DMA_MAP(sc->sc_fcp_cmnds), fcp_cmnd_offset,
	    sizeof(*cmnd), BUS_DMASYNC_PREWRITE);

	/* link req to cmnd */
	fcp_cmnd_offset += QLE_DMA_DVA(sc->sc_fcp_cmnds);
	htolem32(&req->req_fcp_cmnd_addr_lo, fcp_cmnd_offset);
	htolem32(&req->req_fcp_cmnd_addr_hi, fcp_cmnd_offset >> 32);
}

int
qle_load_fwchunk(struct qle_softc *sc, struct qle_dmamem *mem,
    const u_int32_t *src)
{
	u_int32_t dest, done, total;
	int i;

	dest = src[2];
	done = 0;
	total = src[3];

	while (done < total) {
		u_int32_t *copy;
		u_int32_t words;

		/* limit transfer size otherwise it just doesn't work */
		words = MIN(total - done, 1 << 10);
		copy = QLE_DMA_KVA(mem);
		for (i = 0; i < words; i++) {
			htolem32(&copy[i], src[done++]);
		}
		bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, words * 4,
		    BUS_DMASYNC_PREWRITE);

		sc->sc_mbox[0] = QLE_MBOX_LOAD_RISC_RAM;
		sc->sc_mbox[1] = dest;
		sc->sc_mbox[4] = words >> 16;
		sc->sc_mbox[5] = words & 0xffff;
		sc->sc_mbox[8] = dest >> 16;
		qle_mbox_putaddr(sc->sc_mbox, mem);
		if (qle_mbox(sc, 0x01ff)) {
			printf("firmware load failed\n");
			return (1);
		}
		bus_dmamap_sync(sc->sc_dmat, QLE_DMA_MAP(mem), 0, words * 4,
		    BUS_DMASYNC_POSTWRITE);

		dest += words;
	}

	return (qle_verify_firmware(sc, src[2]));
}

int
qle_load_firmware_chunks(struct qle_softc *sc, const u_int32_t *fw)
{
	struct qle_dmamem *mem;
	int res = 0;

	mem = qle_dmamem_alloc(sc, 65536);
	for (;;) {
		if (qle_load_fwchunk(sc, mem, fw)) {
			res = 1;
			break;
		}
		if (fw[1] == 0)
			break;
		fw += fw[3];
	}

	qle_dmamem_free(sc, mem);
	return (res);
}

u_int32_t
qle_read_ram_word(struct qle_softc *sc, u_int32_t addr)
{
	sc->sc_mbox[0] = QLE_MBOX_READ_RISC_RAM;
	sc->sc_mbox[1] = addr & 0xffff;
	sc->sc_mbox[8] = addr >> 16;
	if (qle_mbox(sc, 0x0103)) {
		return (0);
	}
	return ((sc->sc_mbox[3] << 16) | sc->sc_mbox[2]);
}

int
qle_verify_firmware(struct qle_softc *sc, u_int32_t addr)
{
	/*
	 * QLE_MBOX_VERIFY_CSUM requires at least the firmware header
	 * to be correct, otherwise it wanders all over ISP memory and
	 * gets lost.  Check that chunk address (addr+2) is right and
	 * size (addr+3) is plausible first.
	 */
	if ((qle_read_ram_word(sc, addr+2) != addr) ||
	    (qle_read_ram_word(sc, addr+3) > 0xffff)) {
		return (1);
	}

	sc->sc_mbox[0] = QLE_MBOX_VERIFY_CSUM;
	sc->sc_mbox[1] = addr >> 16;
	sc->sc_mbox[2] = addr;
	if (qle_mbox(sc, 0x0007)) {
		return (1);
	}
	return (0);
}

int
qle_read_nvram(struct qle_softc *sc)
{
	u_int32_t data[sizeof(sc->sc_nvram) / 4];
	u_int32_t csum, tmp, v;
	int i, base, l;

	switch (sc->sc_isp_gen) {
	case QLE_GEN_ISP24XX:
		base = 0x7ffe0080;
		break;
	case QLE_GEN_ISP25XX:
		base = 0x7ff48080;
		break;
	}
	base += sc->sc_port * 0x100;

	csum = 0;
	for (i = 0; i < nitems(data); i++) {
		data[i] = 0xffffffff;
		qle_write(sc, QLE_FLASH_NVRAM_ADDR, base + i);
		for (l = 0; l < 5000; l++) {
			delay(10);
			tmp = qle_read(sc, QLE_FLASH_NVRAM_ADDR);
			if (tmp & (1U << 31)) {
				v = qle_read(sc, QLE_FLASH_NVRAM_DATA);
				csum += v;
				data[i] = letoh32(v);
				break;
			}
		}
	}

	bcopy(data, &sc->sc_nvram, sizeof(sc->sc_nvram));
	/* id field should be 'ISP' */
	if (sc->sc_nvram.id[0] != 'I' || sc->sc_nvram.id[1] != 'S' ||
	    sc->sc_nvram.id[2] != 'P' || csum != 0) {
		printf("%s: nvram corrupt\n", DEVNAME(sc));
		return (1);
	}
	return (0);
}

struct qle_dmamem *
qle_dmamem_alloc(struct qle_softc *sc, size_t size)
{
	struct qle_dmamem *m;
	int nsegs;

	m = malloc(sizeof(*m), M_DEVBUF, M_NOWAIT | M_ZERO);
	if (m == NULL)
		return (NULL);

	m->qdm_size = size;

	if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
	    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &m->qdm_map) != 0)
		goto qdmfree;

	if (bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, &m->qdm_seg, 1,
	    &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0)
		goto destroy;

	if (bus_dmamem_map(sc->sc_dmat, &m->qdm_seg, nsegs, size, &m->qdm_kva,
	    BUS_DMA_NOWAIT) != 0)
		goto free;

	if (bus_dmamap_load(sc->sc_dmat, m->qdm_map, m->qdm_kva, size, NULL,
	    BUS_DMA_NOWAIT) != 0)
		goto unmap;

	return (m);

unmap:
	bus_dmamem_unmap(sc->sc_dmat, m->qdm_kva, m->qdm_size);
free:
	bus_dmamem_free(sc->sc_dmat, &m->qdm_seg, 1);
destroy:
	bus_dmamap_destroy(sc->sc_dmat, m->qdm_map);
qdmfree:
	free(m, M_DEVBUF, sizeof *m);

	return (NULL);
}

void
qle_dmamem_free(struct qle_softc *sc, struct qle_dmamem *m)
{
	bus_dmamap_unload(sc->sc_dmat, m->qdm_map);
	bus_dmamem_unmap(sc->sc_dmat, m->qdm_kva, m->qdm_size);
	bus_dmamem_free(sc->sc_dmat, &m->qdm_seg, 1);
	bus_dmamap_destroy(sc->sc_dmat, m->qdm_map);
	free(m, M_DEVBUF, sizeof *m);
}

int
qle_alloc_ccbs(struct qle_softc *sc)
{
	struct qle_ccb		*ccb;
	u_int8_t		*cmd;
	int			i;

	SIMPLEQ_INIT(&sc->sc_ccb_free);
	mtx_init(&sc->sc_ccb_mtx, IPL_BIO);
	mtx_init(&sc->sc_queue_mtx, IPL_BIO);
	mtx_init(&sc->sc_port_mtx, IPL_BIO);
	mtx_init(&sc->sc_mbox_mtx, IPL_BIO);

	sc->sc_ccbs = mallocarray(sc->sc_maxcmds, sizeof(struct qle_ccb),
	    M_DEVBUF, M_WAITOK | M_CANFAIL | M_ZERO);
	if (sc->sc_ccbs == NULL) {
		printf("%s: unable to allocate ccbs\n", DEVNAME(sc));
		return (1);
	}

	sc->sc_requests = qle_dmamem_alloc(sc, sc->sc_maxcmds *
	    QLE_QUEUE_ENTRY_SIZE);
	if (sc->sc_requests == NULL) {
		printf("%s: unable to allocate ccb dmamem\n", DEVNAME(sc));
		goto free_ccbs;
	}
	sc->sc_responses = qle_dmamem_alloc(sc, sc->sc_maxcmds *
	    QLE_QUEUE_ENTRY_SIZE);
	if (sc->sc_responses == NULL) {
		printf("%s: unable to allocate rcb dmamem\n", DEVNAME(sc));
		goto free_req;
	}
	sc->sc_pri_requests = qle_dmamem_alloc(sc, 8 * QLE_QUEUE_ENTRY_SIZE);
	if (sc->sc_pri_requests == NULL) {
		printf("%s: unable to allocate pri ccb dmamem\n", DEVNAME(sc));
		goto free_res;
	}
	sc->sc_segments = qle_dmamem_alloc(sc, sc->sc_maxcmds * QLE_MAX_SEGS *
	    sizeof(struct qle_iocb_seg));
	if (sc->sc_segments == NULL) {
		printf("%s: unable to allocate iocb segments\n", DEVNAME(sc));
		goto free_pri;
	}

	sc->sc_fcp_cmnds = qle_dmamem_alloc(sc, sc->sc_maxcmds *
	    sizeof(struct qle_fcp_cmnd));
	if (sc->sc_fcp_cmnds == NULL) {
		printf("%s: unable to allocate FCP_CMNDs\n", DEVNAME(sc));
		goto free_seg;
	}

	cmd = QLE_DMA_KVA(sc->sc_requests);
	memset(cmd, 0, QLE_QUEUE_ENTRY_SIZE * sc->sc_maxcmds);
	for (i = 0; i < sc->sc_maxcmds; i++) {
		ccb = &sc->sc_ccbs[i];

		if (bus_dmamap_create(sc->sc_dmat, MAXPHYS,
		    QLE_MAX_SEGS-1, MAXPHYS, 0,
		    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
		    &ccb->ccb_dmamap) != 0) {
			printf("%s: unable to create dma map\n", DEVNAME(sc));
			goto free_maps;
		}

		ccb->ccb_sc = sc;
		ccb->ccb_id = i;

		ccb->ccb_seg_offset = i * QLE_MAX_SEGS *
		    sizeof(struct qle_iocb_seg);
		ccb->ccb_segs = QLE_DMA_KVA(sc->sc_segments) +
		    ccb->ccb_seg_offset;

		qle_put_ccb(sc, ccb);
	}

	scsi_iopool_init(&sc->sc_iopool, sc, qle_get_ccb, qle_put_ccb);
	return (0);

free_maps:
	while ((ccb = qle_get_ccb(sc)) != NULL)
		bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap);

	qle_dmamem_free(sc, sc->sc_fcp_cmnds);
free_seg:
	qle_dmamem_free(sc, sc->sc_segments);
free_pri:
	qle_dmamem_free(sc, sc->sc_pri_requests);
free_res:
	qle_dmamem_free(sc, sc->sc_responses);
free_req:
	qle_dmamem_free(sc, sc->sc_requests);
free_ccbs:
	free(sc->sc_ccbs, M_DEVBUF, 0);

	return (1);
}

void
qle_free_ccbs(struct qle_softc *sc)
{
	struct qle_ccb		*ccb;

	scsi_iopool_destroy(&sc->sc_iopool);
	while ((ccb = qle_get_ccb(sc)) != NULL)
		bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap);
	qle_dmamem_free(sc, sc->sc_segments);
	qle_dmamem_free(sc, sc->sc_responses);
	qle_dmamem_free(sc, sc->sc_requests);
	free(sc->sc_ccbs, M_DEVBUF, 0);
}

void *
qle_get_ccb(void *xsc)
{
	struct qle_softc 	*sc = xsc;
	struct qle_ccb		*ccb;

	mtx_enter(&sc->sc_ccb_mtx);
	ccb = SIMPLEQ_FIRST(&sc->sc_ccb_free);
	if (ccb != NULL) {
		SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_free, ccb_link);
	}
	mtx_leave(&sc->sc_ccb_mtx);
	return (ccb);
}

void
qle_put_ccb(void *xsc, void *io)
{
	struct qle_softc	*sc = xsc;
	struct qle_ccb		*ccb = io;

	ccb->ccb_xs = NULL;
	mtx_enter(&sc->sc_ccb_mtx);
	SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_free, ccb, ccb_link);
	mtx_leave(&sc->sc_ccb_mtx);
}
