#include <sys/param.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <sys/rwlock.h>
#include <sys/sensors.h>
#include <sys/queue.h>
#include <machine/bus.h>
#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>
#include <dev/ic/qlareg.h>
#include <dev/ic/qlavar.h>
#ifdef QLA_DEBUG
#define DPRINTF(m, f...) do { if ((qladebug & (m)) == (m)) printf(f); } \
while (0)
#define QLA_D_MBOX 0x01
#define QLA_D_INTR 0x02
#define QLA_D_PORT 0x04
#define QLA_D_IO 0x08
#define QLA_D_IOCB 0x10
int qladebug = QLA_D_PORT;
#else
#define DPRINTF(m, f...)
#endif
#ifndef ISP_NOFIRMWARE
#include <dev/microcode/isp/asm_2100.h>
#include <dev/microcode/isp/asm_2200.h>
#include <dev/microcode/isp/asm_2300.h>
#endif
struct cfdriver qla_cd = {
NULL,
"qla",
DV_DULL
};
void qla_scsi_cmd(struct scsi_xfer *);
int qla_scsi_probe(struct scsi_link *);
u_int16_t qla_read(struct qla_softc *, bus_size_t);
void qla_write(struct qla_softc *, bus_size_t, u_int16_t);
void qla_host_cmd(struct qla_softc *sc, u_int16_t);
u_int16_t qla_read_queue_2100(struct qla_softc *, bus_size_t);
int qla_mbox(struct qla_softc *, int);
int qla_sns_req(struct qla_softc *, struct qla_dmamem *, int);
void qla_mbox_putaddr(u_int16_t *, struct qla_dmamem *);
u_int16_t qla_read_mbox(struct qla_softc *, int);
void qla_write_mbox(struct qla_softc *, int, u_int16_t);
void qla_handle_intr(struct qla_softc *, u_int16_t, u_int16_t);
void qla_set_ints(struct qla_softc *, int);
int qla_read_isr_1G(struct qla_softc *, u_int16_t *, u_int16_t *);
int qla_read_isr_2G(struct qla_softc *, u_int16_t *, u_int16_t *);
void qla_clear_isr(struct qla_softc *, u_int16_t);
void qla_update_start(struct qla_softc *, int);
void qla_update_done(struct qla_softc *, int);
void qla_do_update(void *);
void qla_put_marker(struct qla_softc *, void *);
void qla_put_cmd(struct qla_softc *, void *, struct scsi_xfer *,
struct qla_ccb *);
struct qla_ccb *qla_handle_resp(struct qla_softc *, u_int16_t);
int qla_get_port_name_list(struct qla_softc *, u_int32_t);
struct qla_fc_port *qla_next_fabric_port(struct qla_softc *, u_int32_t *,
u_int32_t *);
int qla_get_port_db(struct qla_softc *c, u_int16_t,
struct qla_dmamem *);
int qla_add_loop_port(struct qla_softc *, struct qla_fc_port *);
int qla_add_fabric_port(struct qla_softc *, struct qla_fc_port *);
int qla_add_logged_in_port(struct qla_softc *, int, u_int32_t);
int qla_classify_port(struct qla_softc *, u_int32_t, u_int64_t,
u_int64_t, struct qla_fc_port **);
int qla_get_loop_id(struct qla_softc *sc, int);
void qla_clear_port_lists(struct qla_softc *);
int qla_softreset(struct qla_softc *);
void qla_update_topology(struct qla_softc *);
int qla_update_fabric(struct qla_softc *);
int qla_fabric_plogi(struct qla_softc *, struct qla_fc_port *);
void qla_fabric_plogo(struct qla_softc *, struct qla_fc_port *);
void qla_update_start(struct qla_softc *, int);
int qla_async(struct qla_softc *, u_int16_t);
int qla_verify_firmware(struct qla_softc *sc, u_int16_t);
int qla_load_firmware_words(struct qla_softc *, const u_int16_t *,
u_int16_t);
int qla_load_firmware_2100(struct qla_softc *);
int qla_load_firmware_2200(struct qla_softc *);
int qla_load_fwchunk_2300(struct qla_softc *,
struct qla_dmamem *, const u_int16_t *, u_int32_t);
int qla_load_firmware_2300(struct qla_softc *);
int qla_load_firmware_2322(struct qla_softc *);
int qla_read_nvram(struct qla_softc *);
struct qla_dmamem *qla_dmamem_alloc(struct qla_softc *, size_t);
void qla_dmamem_free(struct qla_softc *, struct qla_dmamem *);
int qla_alloc_ccbs(struct qla_softc *);
void qla_free_ccbs(struct qla_softc *);
void *qla_get_ccb(void *);
void qla_put_ccb(void *, void *);
void qla_dump_iocb(struct qla_softc *, void *);
void qla_dump_iocb_segs(struct qla_softc *, void *, int);
static const struct qla_regs qla_regs_2100 = {
qla_read_queue_2100,
qla_read_isr_1G,
QLA_MBOX_BASE_2100 + 0x8,
QLA_MBOX_BASE_2100 + 0x8,
QLA_MBOX_BASE_2100 + 0xa,
QLA_MBOX_BASE_2100 + 0xa
};
static const struct qla_regs qla_regs_2200 = {
qla_read,
qla_read_isr_1G,
QLA_MBOX_BASE_2200 + 0x8,
QLA_MBOX_BASE_2200 + 0x8,
QLA_MBOX_BASE_2200 + 0xa,
QLA_MBOX_BASE_2200 + 0xa
};
static const struct qla_regs qla_regs_23XX = {
qla_read,
qla_read_isr_2G,
QLA_REQ_IN,
QLA_REQ_OUT,
QLA_RESP_IN,
QLA_RESP_OUT
};
#define qla_queue_read(_sc, _r) ((*(_sc)->sc_regs->read)((_sc), (_r)))
#define qla_queue_write(_sc, _r, _v) qla_write((_sc), (_r), (_v))
#define qla_read_isr(_sc, _isr, _info) \
((*(_sc)->sc_regs->read_isr)((_sc), (_isr), (_info)))
const struct scsi_adapter qla_switch = {
qla_scsi_cmd, NULL, qla_scsi_probe, NULL, NULL
};
int
qla_classify_port(struct qla_softc *sc, u_int32_t location,
u_int64_t port_name, u_int64_t node_name, struct qla_fc_port **prev)
{
struct qla_fc_port *port, *locmatch, *wwnmatch;
locmatch = NULL;
wwnmatch = NULL;
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 (QLA_PORT_DISP_DUP);
}
}
if (sc->sc_scsibus == NULL) {
*prev = NULL;
return (QLA_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 (QLA_PORT_DISP_NEW);
} else if (locmatch == wwnmatch) {
*prev = locmatch;
return (QLA_PORT_DISP_SAME);
} else if (wwnmatch != NULL) {
*prev = wwnmatch;
return (QLA_PORT_DISP_MOVED);
} else {
*prev = locmatch;
return (QLA_PORT_DISP_CHANGED);
}
}
int
qla_get_loop_id(struct qla_softc *sc, int start)
{
int i, last;
if (sc->sc_2k_logins) {
i = QLA_2KL_MIN_HANDLE;
last = QLA_2KL_MAX_HANDLE;
} else {
i = QLA_MIN_HANDLE;
last = QLA_MAX_HANDLE;
}
if (i < start)
i = start;
for (; i <= last; i++) {
if (sc->sc_targets[i] == NULL)
return (i);
}
return (-1);
}
int
qla_get_port_db(struct qla_softc *sc, u_int16_t loopid, struct qla_dmamem *mem)
{
sc->sc_mbox[0] = QLA_MBOX_GET_PORT_DB;
if (sc->sc_2k_logins) {
sc->sc_mbox[1] = loopid;
} else {
sc->sc_mbox[1] = loopid << 8;
}
memset(QLA_DMA_KVA(mem), 0, sizeof(struct qla_get_port_db));
qla_mbox_putaddr(sc->sc_mbox, mem);
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0,
sizeof(struct qla_get_port_db), BUS_DMASYNC_PREREAD);
if (qla_mbox(sc, 0x00cf)) {
DPRINTF(QLA_D_PORT, "%s: get port db %d failed: %x\n",
DEVNAME(sc), loopid, sc->sc_mbox[0]);
return (1);
}
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0,
sizeof(struct qla_get_port_db), BUS_DMASYNC_POSTREAD);
return (0);
}
int
qla_add_loop_port(struct qla_softc *sc, struct qla_fc_port *port)
{
struct qla_get_port_db *pdb;
struct qla_fc_port *pport = NULL;
int disp;
if (qla_get_port_db(sc, port->loopid, sc->sc_scratch)) {
return (1);
}
pdb = QLA_DMA_KVA(sc->sc_scratch);
if (letoh16(pdb->prli_svc_word3) & QLA_SVC3_TARGET_ROLE)
port->flags |= QLA_PORT_FLAG_IS_TARGET;
port->port_name = betoh64(pdb->port_name);
port->node_name = betoh64(pdb->node_name);
port->portid = (letoh16(pdb->port_id[0]) << 16) |
letoh16(pdb->port_id[1]);
mtx_enter(&sc->sc_port_mtx);
disp = qla_classify_port(sc, port->location, port->port_name,
port->node_name, &pport);
switch (disp) {
case QLA_PORT_DISP_CHANGED:
case QLA_PORT_DISP_MOVED:
case QLA_PORT_DISP_NEW:
TAILQ_INSERT_TAIL(&sc->sc_ports_new, port, update);
sc->sc_targets[port->loopid] = port;
break;
case QLA_PORT_DISP_DUP:
free(port, M_DEVBUF, sizeof *port);
break;
case QLA_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 QLA_PORT_DISP_CHANGED:
case QLA_PORT_DISP_MOVED:
case QLA_PORT_DISP_NEW:
DPRINTF(QLA_D_PORT, "%s: %s %d; name %llx, port %06x\n",
DEVNAME(sc), ISSET(port->flags, QLA_PORT_FLAG_IS_TARGET) ?
"target" : "non-target", port->loopid, port->port_name,
port->portid);
break;
}
return (0);
}
int
qla_add_fabric_port(struct qla_softc *sc, struct qla_fc_port *port)
{
struct qla_get_port_db *pdb;
if (qla_get_port_db(sc, port->loopid, sc->sc_scratch)) {
return (1);
}
pdb = QLA_DMA_KVA(sc->sc_scratch);
if (letoh16(pdb->prli_svc_word3) & QLA_SVC3_TARGET_ROLE)
port->flags |= QLA_PORT_FLAG_IS_TARGET;
if (port->location == QLA_LOCATION_FABRIC) {
port->node_name = betoh64(pdb->node_name);
port->port_name = betoh64(pdb->port_name);
port->portid = (letoh16(pdb->port_id[0]) << 16) |
letoh16(pdb->port_id[1]);
port->location = QLA_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(QLA_D_PORT, "%s: %s %d; name %llx\n",
DEVNAME(sc), ISSET(port->flags, QLA_PORT_FLAG_IS_TARGET) ?
"target" : "non-target", port->loopid, port->port_name);
return (0);
}
int
qla_add_logged_in_port(struct qla_softc *sc, int loopid, u_int32_t portid)
{
struct qla_fc_port *port;
struct qla_get_port_db *pdb;
u_int64_t node_name, port_name;
int flags, ret;
ret = qla_get_port_db(sc, loopid, sc->sc_scratch);
mtx_enter(&sc->sc_port_mtx);
if (ret != 0) {
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 = QLA_DMA_KVA(sc->sc_scratch);
node_name = betoh64(pdb->node_name);
port_name = betoh64(pdb->port_name);
flags = 0;
if (letoh16(pdb->prli_svc_word3) & QLA_SVC3_TARGET_ROLE)
flags |= QLA_PORT_FLAG_IS_TARGET;
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(QLA_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 = QLA_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(QLA_D_PORT, "%s: added logged in port %06x at %d\n",
DEVNAME(sc), portid, loopid);
return (0);
}
int
qla_attach(struct qla_softc *sc)
{
struct scsibus_attach_args saa;
struct qla_init_cb *icb;
#ifndef ISP_NOFIRMWARE
int (*loadfirmware)(struct qla_softc *) = NULL;
#endif
u_int16_t firmware_addr = 0;
u_int64_t dva;
int i, rv;
TAILQ_INIT(&sc->sc_ports);
TAILQ_INIT(&sc->sc_ports_new);
TAILQ_INIT(&sc->sc_ports_gone);
TAILQ_INIT(&sc->sc_ports_found);
switch (sc->sc_isp_gen) {
case QLA_GEN_ISP2100:
sc->sc_mbox_base = QLA_MBOX_BASE_2100;
sc->sc_regs = &qla_regs_2100;
#ifndef ISP_NOFIRMWARE
loadfirmware = qla_load_firmware_2100;
#endif
firmware_addr = QLA_2100_CODE_ORG;
break;
case QLA_GEN_ISP2200:
sc->sc_mbox_base = QLA_MBOX_BASE_2200;
sc->sc_regs = &qla_regs_2200;
#ifndef ISP_NOFIRMWARE
loadfirmware = qla_load_firmware_2200;
#endif
firmware_addr = QLA_2200_CODE_ORG;
break;
case QLA_GEN_ISP23XX:
sc->sc_mbox_base = QLA_MBOX_BASE_23XX;
sc->sc_regs = &qla_regs_23XX;
#ifndef ISP_NOFIRMWARE
if (sc->sc_isp_type != QLA_ISP2322)
loadfirmware = qla_load_firmware_2300;
#endif
firmware_addr = QLA_2300_CODE_ORG;
break;
default:
printf("unknown isp type\n");
return (ENXIO);
}
if (qla_read_mbox(sc, 1) != 0x4953 ||
qla_read_mbox(sc, 2) != 0x5020 ||
qla_read_mbox(sc, 3) != 0x2020) {
qla_host_cmd(sc, QLA_HOST_CMD_RELEASE);
}
qla_host_cmd(sc, QLA_HOST_CMD_PAUSE);
if (qla_softreset(sc) != 0) {
printf("softreset failed\n");
return (ENXIO);
}
if (qla_read_nvram(sc) == 0) {
sc->sc_nvram_valid = 1;
if (sc->sc_port_name == 0)
sc->sc_port_name = betoh64(sc->sc_nvram.port_name);
if (sc->sc_node_name == 0)
sc->sc_node_name = betoh64(sc->sc_nvram.node_name);
}
if (sc->sc_port_name == 0)
sc->sc_port_name = QLA_DEFAULT_PORT_NAME;
#ifdef ISP_NOFIRMWARE
if (qla_verify_firmware(sc, firmware_addr)) {
printf("%s: no firmware loaded\n", DEVNAME(sc));
return (ENXIO);
}
#else
if (loadfirmware && (loadfirmware)(sc)) {
printf("%s: firmware load failed\n", DEVNAME(sc));
return (ENXIO);
}
#endif
sc->sc_mbox[0] = QLA_MBOX_EXEC_FIRMWARE;
sc->sc_mbox[1] = firmware_addr;
#ifdef ISP_NOFIRMWARE
sc->sc_mbox[2] = 1;
#else
if (loadfirmware)
sc->sc_mbox[2] = 0;
else
sc->sc_mbox[2] = 1;
#endif
if (qla_mbox(sc, 0x0007)) {
printf("ISP couldn't exec firmware: %x\n", sc->sc_mbox[0]);
return (ENXIO);
}
delay(250000);
sc->sc_mbox[0] = QLA_MBOX_ABOUT_FIRMWARE;
if (qla_mbox(sc, 0x0001)) {
printf("ISP not talking after firmware exec: %x\n",
sc->sc_mbox[0]);
return (ENXIO);
}
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]);
if (sc->sc_mbox[6] & QLA_FW_ATTR_EXPANDED_LUN)
sc->sc_expanded_lun = 1;
if (sc->sc_mbox[6] & QLA_FW_ATTR_FABRIC)
sc->sc_fabric = 1;
if (sc->sc_mbox[6] & QLA_FW_ATTR_2K_LOGINS)
sc->sc_2k_logins = 1;
sc->sc_mbox[0] = QLA_MBOX_GET_FIRMWARE_STATUS;
if (qla_mbox(sc, 0x0001)) {
printf("couldn't get firmware status: %x\n", sc->sc_mbox[0]);
return (ENXIO);
}
sc->sc_maxcmds = sc->sc_mbox[2];
if (qla_alloc_ccbs(sc)) {
return (ENOMEM);
}
sc->sc_scratch = qla_dmamem_alloc(sc, QLA_SCRATCH_SIZE);
if (sc->sc_scratch == NULL) {
printf("%s: unable to allocate scratch\n", DEVNAME(sc));
goto free_ccbs;
}
icb = (struct qla_init_cb *)QLA_DMA_KVA(sc->sc_scratch);
memset(icb, 0, sizeof(*icb));
icb->icb_version = QLA_ICB_VERSION;
htobem32(&icb->icb_portname_hi, sc->sc_port_name >> 32);
htobem32(&icb->icb_portname_lo, sc->sc_port_name);
htobem32(&icb->icb_nodename_hi, sc->sc_node_name >> 32);
htobem32(&icb->icb_nodename_lo, sc->sc_node_name);
if (sc->sc_nvram_valid) {
icb->icb_fw_options = sc->sc_nvram.fw_options;
icb->icb_max_frame_len = sc->sc_nvram.frame_payload_size;
icb->icb_max_alloc = sc->sc_nvram.max_iocb_allocation;
icb->icb_exec_throttle = sc->sc_nvram.execution_throttle;
icb->icb_retry_count = sc->sc_nvram.retry_count;
icb->icb_retry_delay = sc->sc_nvram.retry_delay;
icb->icb_hardaddr = sc->sc_nvram.hard_address;
icb->icb_inquiry_data = sc->sc_nvram.inquiry_data;
icb->icb_login_timeout = sc->sc_nvram.login_timeout;
icb->icb_xfwoptions = sc->sc_nvram.add_fw_options;
icb->icb_zfwoptions = sc->sc_nvram.special_options;
} else {
icb->icb_retry_count = 3;
icb->icb_retry_delay = 5;
icb->icb_exec_throttle = htole16(16);
icb->icb_max_alloc = htole16(256);
icb->icb_max_frame_len = htole16(1024);
icb->icb_fw_options = htole16(QLA_ICB_FW_FAIRNESS |
QLA_ICB_FW_ENABLE_PDB_CHANGED | QLA_ICB_FW_HARD_ADDR |
QLA_ICB_FW_FULL_DUPLEX);
}
icb->icb_lun_enables = 0;
icb->icb_cmd_count = 0;
icb->icb_notify_count = 0;
icb->icb_lun_timeout = 0;
icb->icb_int_delaytimer = 0;
icb->icb_req_out = 0;
icb->icb_resp_in = 0;
htolem16(&icb->icb_req_queue_len, sc->sc_maxcmds);
htolem16(&icb->icb_resp_queue_len, sc->sc_maxcmds);
dva = QLA_DMA_DVA(sc->sc_requests);
htolem32(&icb->icb_req_queue_addr_lo, dva);
htolem32(&icb->icb_req_queue_addr_hi, dva >> 32);
dva = QLA_DMA_DVA(sc->sc_responses);
htolem32(&icb->icb_resp_queue_addr_lo, dva);
htolem32(&icb->icb_resp_queue_addr_hi, dva >> 32);
icb->icb_fw_options |= htole16(QLA_ICB_FW_EXTENDED_INIT_CB);
icb->icb_fw_options &= ~htole16(QLA_ICB_FW_FAST_POST);
sc->sc_mbox[0] = QLA_MBOX_INIT_FIRMWARE;
sc->sc_mbox[4] = 0;
sc->sc_mbox[5] = 0;
qla_mbox_putaddr(sc->sc_mbox, sc->sc_scratch);
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0,
sizeof(*icb), BUS_DMASYNC_PREWRITE);
rv = qla_mbox(sc, 0x00fd);
bus_dmamap_sync(sc->sc_dmat, QLA_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;
}
sc->sc_mbox[0] = QLA_MBOX_SET_FIRMWARE_OPTIONS;
sc->sc_mbox[1] = QLA_FW_OPTION1_ASYNC_LIP_F8 |
QLA_FW_OPTION1_ASYNC_LIP_RESET |
QLA_FW_OPTION1_ASYNC_LIP_ERROR |
QLA_FW_OPTION1_ASYNC_LOGIN_RJT;
sc->sc_mbox[2] = 0;
sc->sc_mbox[3] = 0;
if (qla_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, qla_do_update, sc);
for (i = 0; i < QLA_WAIT_FOR_LOOP * 10000; i++) {
u_int16_t isr, info;
delay(100);
if (qla_read_isr(sc, &isr, &info) == 0)
continue;
qla_handle_intr(sc, isr, info);
if (sc->sc_loop_up)
break;
}
if (sc->sc_loop_up) {
qla_do_update(sc);
} else {
DPRINTF(QLA_D_PORT, "%s: loop still down, giving up\n",
DEVNAME(sc));
}
saa.saa_adapter = &qla_switch;
saa.saa_adapter_softc = sc;
if (sc->sc_2k_logins) {
saa.saa_adapter_buswidth = QLA_2KL_BUSWIDTH;
} else {
saa.saa_adapter_buswidth = QLA_BUSWIDTH;
}
saa.saa_adapter_target = SDEV_NO_ADAPTER_TARGET;
saa.saa_luns = 8;
saa.saa_openings = sc->sc_maxcmds;
saa.saa_pool = &sc->sc_iopool;
saa.saa_wwpn = sc->sc_port_name;
saa.saa_wwnn = sc->sc_node_name;
if (saa.saa_wwnn == 0) {
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(0);
free_scratch:
qla_dmamem_free(sc, sc->sc_scratch);
free_ccbs:
qla_free_ccbs(sc);
return (ENXIO);
}
int
qla_detach(struct qla_softc *sc, int flags)
{
return (0);
}
struct qla_ccb *
qla_handle_resp(struct qla_softc *sc, u_int16_t id)
{
struct qla_ccb *ccb;
struct qla_iocb_status *status;
struct scsi_xfer *xs;
u_int32_t handle;
u_int8_t *entry;
ccb = NULL;
entry = QLA_DMA_KVA(sc->sc_responses) + (id * QLA_QUEUE_ENTRY_SIZE);
bus_dmamap_sync(sc->sc_dmat,
QLA_DMA_MAP(sc->sc_responses), id * QLA_QUEUE_ENTRY_SIZE,
QLA_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTREAD);
qla_dump_iocb(sc, entry);
switch (entry[0]) {
case QLA_IOCB_STATUS:
status = (struct qla_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(QLA_D_INTR, "%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 >
QLA_IOCB_SEGS_PER_CMD) {
bus_dmamap_sync(sc->sc_dmat,
QLA_DMA_MAP(sc->sc_segments),
ccb->ccb_seg_offset,
sizeof(*ccb->ccb_t4segs) *
ccb->ccb_dmamap->dm_nsegs,
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 = letoh16(status->scsi_status);
switch (letoh16(status->completion)) {
case QLA_IOCB_STATUS_COMPLETE:
if (letoh16(status->scsi_status) &
QLA_SCSI_STATUS_SENSE_VALID) {
memcpy(&xs->sense, status->sense_data,
sizeof(xs->sense));
xs->error = XS_SENSE;
} else {
xs->error = XS_NOERROR;
}
xs->resid = 0;
break;
case QLA_IOCB_STATUS_DMA_ERROR:
DPRINTF(QLA_D_INTR, "%s: dma error\n", DEVNAME(sc));
break;
case QLA_IOCB_STATUS_RESET:
DPRINTF(QLA_D_IO, "%s: reset destroyed command\n",
DEVNAME(sc));
sc->sc_marker_required = 1;
xs->error = XS_RESET;
break;
case QLA_IOCB_STATUS_ABORTED:
DPRINTF(QLA_D_IO, "%s: aborted\n", DEVNAME(sc));
sc->sc_marker_required = 1;
xs->error = XS_DRIVER_STUFFUP;
break;
case QLA_IOCB_STATUS_TIMEOUT:
DPRINTF(QLA_D_IO, "%s: command timed out\n",
DEVNAME(sc));
xs->error = XS_TIMEOUT;
break;
case QLA_IOCB_STATUS_DATA_OVERRUN:
case QLA_IOCB_STATUS_DATA_UNDERRUN:
xs->resid = letoh32(status->resid);
xs->error = XS_NOERROR;
break;
case QLA_IOCB_STATUS_QUEUE_FULL:
DPRINTF(QLA_D_IO, "%s: queue full\n", DEVNAME(sc));
xs->error = XS_BUSY;
break;
case QLA_IOCB_STATUS_PORT_UNAVAIL:
case QLA_IOCB_STATUS_PORT_LOGGED_OUT:
case QLA_IOCB_STATUS_PORT_CHANGED:
DPRINTF(QLA_D_IO, "%s: dev gone\n", DEVNAME(sc));
xs->error = XS_SELTIMEOUT;
break;
default:
DPRINTF(QLA_D_INTR, "%s: unexpected completion"
" status %x\n", DEVNAME(sc), status->completion);
xs->error = XS_DRIVER_STUFFUP;
break;
}
break;
case QLA_IOCB_STATUS_CONT:
DPRINTF(QLA_D_INTR, "%s: ignoring status continuation iocb\n",
DEVNAME(sc));
break;
default:
DPRINTF(QLA_D_INTR, "%s: unexpected response entry type %x\n",
DEVNAME(sc), entry[0]);
break;
}
return (ccb);
}
void
qla_handle_intr(struct qla_softc *sc, u_int16_t isr, u_int16_t info)
{
int i;
u_int16_t rspin;
struct qla_ccb *ccb;
switch (isr) {
case QLA_INT_TYPE_ASYNC:
qla_async(sc, info);
break;
case QLA_INT_TYPE_IO:
rspin = qla_queue_read(sc, sc->sc_regs->res_in);
if (rspin == sc->sc_last_resp_id) {
if (sc->sc_mbox_pending) {
DPRINTF(QLA_D_MBOX, "%s: ignoring premature"
" mbox int\n", DEVNAME(sc));
return;
}
break;
}
if (sc->sc_responses == NULL)
break;
DPRINTF(QLA_D_IO, "%s: response queue %x=>%x\n",
DEVNAME(sc), sc->sc_last_resp_id, rspin);
do {
ccb = qla_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);
qla_queue_write(sc, sc->sc_regs->res_out, rspin);
break;
case QLA_INT_TYPE_MBOX:
mtx_enter(&sc->sc_mbox_mtx);
if (sc->sc_mbox_pending) {
DPRINTF(QLA_D_MBOX, "%s: mbox response %x\n",
DEVNAME(sc), info);
for (i = 0; i < nitems(sc->sc_mbox); i++) {
sc->sc_mbox[i] = qla_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(QLA_D_MBOX, "%s: unexpected mbox interrupt:"
" %x\n", DEVNAME(sc), info);
}
break;
default:
break;
}
qla_clear_isr(sc, isr);
}
int
qla_intr(void *xsc)
{
struct qla_softc *sc = xsc;
u_int16_t isr;
u_int16_t info;
if (qla_read_isr(sc, &isr, &info) == 0)
return (0);
qla_handle_intr(sc, isr, info);
return (1);
}
int
qla_scsi_probe(struct scsi_link *link)
{
struct qla_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,
QLA_PORT_FLAG_IS_TARGET))
rv = ENXIO;
else {
link->port_wwn = sc->sc_targets[link->target]->port_name;
link->node_wwn = sc->sc_targets[link->target]->node_name;
}
mtx_leave(&sc->sc_port_mtx);
return (rv);
}
void
qla_scsi_cmd(struct scsi_xfer *xs)
{
struct scsi_link *link = xs->sc_link;
struct qla_softc *sc = link->bus->sb_adapter_softc;
struct qla_ccb *ccb;
struct qla_iocb_req34 *iocb;
struct qla_ccb_list list;
u_int16_t req, rspin;
int offset, error, done;
bus_dmamap_t dmap;
if (xs->cmdlen > sizeof(iocb->req_cdb)) {
DPRINTF(QLA_D_IO, "%s: cdb 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;
}
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);
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(QLA_D_IO, "%s: writing marker at request %d\n",
DEVNAME(sc), req);
offset = (req * QLA_QUEUE_ENTRY_SIZE);
iocb = QLA_DMA_KVA(sc->sc_requests) + offset;
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_requests),
offset, QLA_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE);
qla_put_marker(sc, iocb);
qla_queue_write(sc, sc->sc_regs->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 * QLA_QUEUE_ENTRY_SIZE);
iocb = QLA_DMA_KVA(sc->sc_requests) + offset;
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_requests), offset,
QLA_QUEUE_ENTRY_SIZE, BUS_DMASYNC_POSTWRITE);
ccb->ccb_xs = xs;
DPRINTF(QLA_D_IO, "%s: writing cmd at request %d\n", DEVNAME(sc), req);
qla_put_cmd(sc, iocb, xs, ccb);
qla_queue_write(sc, sc->sc_regs->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;
delay(100);
if (qla_read_isr(sc, &isr, &info) == 0) {
continue;
}
if (isr != QLA_INT_TYPE_IO) {
qla_handle_intr(sc, isr, info);
continue;
}
rspin = qla_queue_read(sc, sc->sc_regs->res_in);
while (rspin != sc->sc_last_resp_id) {
ccb = qla_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;
}
qla_queue_write(sc, sc->sc_regs->res_out, rspin);
qla_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_int16_t
qla_read(struct qla_softc *sc, bus_size_t offset)
{
u_int16_t v;
v = bus_space_read_2(sc->sc_iot, sc->sc_ioh, offset);
bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 2,
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
return (v);
}
void
qla_write(struct qla_softc *sc, bus_size_t offset, u_int16_t value)
{
bus_space_write_2(sc->sc_iot, sc->sc_ioh, offset, value);
bus_space_barrier(sc->sc_iot, sc->sc_ioh, offset, 2,
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
}
u_int16_t
qla_read_mbox(struct qla_softc *sc, int mbox)
{
return (qla_read(sc, sc->sc_mbox_base + (mbox * 2)));
}
void
qla_write_mbox(struct qla_softc *sc, int mbox, u_int16_t value)
{
qla_write(sc, sc->sc_mbox_base + (mbox * 2), value);
}
void
qla_host_cmd(struct qla_softc *sc, u_int16_t cmd)
{
qla_write(sc, QLA_HOST_CMD_CTRL, cmd << QLA_HOST_CMD_SHIFT);
}
#define MBOX_COMMAND_TIMEOUT 4000
int
qla_mbox(struct qla_softc *sc, int maskin)
{
int i;
int result = 0;
int rv;
sc->sc_mbox_pending = 1;
for (i = 0; i < nitems(sc->sc_mbox); i++) {
if (maskin & (1 << i)) {
qla_write_mbox(sc, i, sc->sc_mbox[i]);
}
}
qla_host_cmd(sc, QLA_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,
"qlambox", INFSLP);
}
result = sc->sc_mbox[0];
sc->sc_mbox_pending = 0;
mtx_leave(&sc->sc_mbox_mtx);
return (result == QLA_MBOX_COMPLETE ? 0 : result);
}
for (i = 0; i < MBOX_COMMAND_TIMEOUT && result == 0; i++) {
u_int16_t isr, info;
delay(100);
if (qla_read_isr(sc, &isr, &info) == 0)
continue;
switch (isr) {
case QLA_INT_TYPE_MBOX:
result = info;
break;
default:
qla_handle_intr(sc, isr, info);
break;
}
}
if (result == 0) {
DPRINTF(QLA_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] = qla_read_mbox(sc, i);
}
rv = (result == QLA_MBOX_COMPLETE ? 0 : result);
}
qla_clear_isr(sc, QLA_INT_TYPE_MBOX);
sc->sc_mbox_pending = 0;
return (rv);
}
void
qla_mbox_putaddr(u_int16_t *mbox, struct qla_dmamem *mem)
{
mbox[2] = (QLA_DMA_DVA(mem) >> 16) & 0xffff;
mbox[3] = (QLA_DMA_DVA(mem) >> 0) & 0xffff;
mbox[6] = (QLA_DMA_DVA(mem) >> 48) & 0xffff;
mbox[7] = (QLA_DMA_DVA(mem) >> 32) & 0xffff;
}
int
qla_sns_req(struct qla_softc *sc, struct qla_dmamem *mem, int reqsize)
{
struct qla_sns_req_hdr *header;
uint64_t dva;
int rv;
memset(&sc->sc_mbox, 0, sizeof(sc->sc_mbox));
sc->sc_mbox[0] = QLA_MBOX_SEND_SNS;
sc->sc_mbox[1] = reqsize / 2;
qla_mbox_putaddr(sc->sc_mbox, mem);
header = QLA_DMA_KVA(mem);
htolem16(&header->resp_len, (QLA_DMA_LEN(mem) - reqsize) / 2);
dva = QLA_DMA_DVA(mem) + reqsize;
htolem32(&header->resp_addr_lo, dva);
htolem32(&header->resp_addr_hi, dva >> 32);
header->subcmd_len = htole16((reqsize - sizeof(*header)) / 2);
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, QLA_DMA_LEN(mem),
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
rv = qla_mbox(sc, 0x00cf);
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, QLA_DMA_LEN(mem),
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
return (rv);
}
void
qla_set_ints(struct qla_softc *sc, int enabled)
{
u_int16_t v = enabled ? (QLA_INT_REQ | QLA_RISC_INT_REQ) : 0;
qla_write(sc, QLA_INT_CTRL, v);
}
int
qla_read_isr_1G(struct qla_softc *sc, u_int16_t *isr, u_int16_t *info)
{
u_int16_t int_status;
if (qla_read(sc, QLA_SEMA) & QLA_SEMA_LOCK) {
*info = qla_read_mbox(sc, 0);
if (*info & QLA_MBOX_HAS_STATUS)
*isr = QLA_INT_TYPE_MBOX;
else
*isr = QLA_INT_TYPE_ASYNC;
} else {
int_status = qla_read(sc, QLA_INT_STATUS);
if ((int_status & QLA_INT_REQ) == 0)
return (0);
*isr = QLA_INT_TYPE_IO;
}
return (1);
}
int
qla_read_isr_2G(struct qla_softc *sc, u_int16_t *isr, u_int16_t *info)
{
u_int32_t v;
if ((qla_read(sc, QLA_INT_STATUS) & QLA_INT_REQ) == 0)
return (0);
v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, QLA_RISC_STATUS_LOW);
bus_space_barrier(sc->sc_iot, sc->sc_ioh, QLA_RISC_STATUS_LOW,
4, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
switch (v & QLA_INT_STATUS_MASK) {
case QLA_23XX_INT_ROM_MBOX:
case QLA_23XX_INT_ROM_MBOX_FAIL:
case QLA_23XX_INT_MBOX:
case QLA_23XX_INT_MBOX_FAIL:
*isr = QLA_INT_TYPE_MBOX;
break;
case QLA_23XX_INT_ASYNC:
*isr = QLA_INT_TYPE_ASYNC;
break;
case QLA_23XX_INT_RSPQ:
*isr = QLA_INT_TYPE_IO;
break;
default:
*isr = QLA_INT_TYPE_OTHER;
break;
}
*info = (v >> QLA_INT_INFO_SHIFT);
return (1);
}
void
qla_clear_isr(struct qla_softc *sc, u_int16_t isr)
{
qla_host_cmd(sc, QLA_HOST_CMD_CLR_RISC_INT);
switch (isr) {
case QLA_INT_TYPE_MBOX:
case QLA_INT_TYPE_ASYNC:
qla_write(sc, QLA_SEMA, 0);
break;
default:
break;
}
}
u_int16_t
qla_read_queue_2100(struct qla_softc *sc, bus_size_t queue)
{
u_int16_t a, b, i;
for (i = 0; i < 1000; i++) {
a = qla_read(sc, queue);
b = qla_read(sc, queue);
if (a == b)
return (a);
}
DPRINTF(QLA_D_INTR, "%s: queue ptr unstable\n", DEVNAME(sc));
return (a);
}
int
qla_softreset(struct qla_softc *sc)
{
int i;
qla_set_ints(sc, 0);
qla_write(sc, QLA_CTRL_STATUS, QLA_CTRL_RESET);
delay(100);
for (i = 0; i < 1000; i++) {
if ((qla_read(sc, QLA_CTRL_STATUS) & QLA_CTRL_RESET) == 0)
break;
delay(100);
}
if (i == 1000) {
DPRINTF(QLA_D_INTR, "%s: reset didn't clear\n", DEVNAME(sc));
qla_set_ints(sc, 0);
return (ENXIO);
}
qla_write(sc, QLA_CTRL_STATUS, QLA_CTRL_FPM0_REGS);
qla_write(sc, QLA_FPM_DIAG, QLA_FPM_RESET);
qla_write(sc, QLA_FPM_DIAG, 0);
qla_write(sc, QLA_CTRL_STATUS, QLA_CTRL_RISC_REGS);
qla_host_cmd(sc, QLA_HOST_CMD_RESET);
delay(100);
qla_write(sc, QLA_SEMA, 0);
qla_host_cmd(sc, QLA_HOST_CMD_MASK_PARITY);
qla_host_cmd(sc, QLA_HOST_CMD_RELEASE);
qla_queue_write(sc, sc->sc_regs->req_in, 0);
qla_queue_write(sc, sc->sc_regs->req_out, 0);
qla_queue_write(sc, sc->sc_regs->res_in, 0);
qla_queue_write(sc, sc->sc_regs->res_out, 0);
qla_set_ints(sc, 1);
sc->sc_mbox[0] = QLA_MBOX_NOP;
if (qla_mbox(sc, 0x0001)) {
DPRINTF(QLA_D_INTR, "%s: ISP not responding after reset\n",
DEVNAME(sc));
return (ENXIO);
}
return (0);
}
void
qla_update_topology(struct qla_softc *sc)
{
sc->sc_mbox[0] = QLA_MBOX_GET_LOOP_ID;
if (qla_mbox(sc, 0x0001)) {
DPRINTF(QLA_D_PORT, "%s: unable to get loop id\n", DEVNAME(sc));
sc->sc_topology = QLA_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 QLA_TOPO_NL_PORT:
case QLA_TOPO_N_PORT:
DPRINTF(QLA_D_PORT, "%s: loop id %d\n", DEVNAME(sc),
sc->sc_loop_id);
break;
case QLA_TOPO_FL_PORT:
case QLA_TOPO_F_PORT:
sc->sc_port_id = sc->sc_mbox[2] |
(sc->sc_mbox[3] << 16);
DPRINTF(QLA_D_PORT, "%s: fabric port id %06x\n",
DEVNAME(sc), sc->sc_port_id);
break;
case QLA_TOPO_N_PORT_NO_TARGET:
default:
DPRINTF(QLA_D_PORT, "%s: not connected\n", DEVNAME(sc));
break;
}
switch (sc->sc_topology) {
case QLA_TOPO_NL_PORT:
case QLA_TOPO_FL_PORT:
sc->sc_loop_max_id = 126;
break;
case QLA_TOPO_N_PORT:
sc->sc_loop_max_id = 2;
break;
default:
sc->sc_loop_max_id = 0;
break;
}
}
}
int
qla_update_fabric(struct qla_softc *sc)
{
struct qla_sns_rft_id *rft;
if (sc->sc_fabric == 0)
return (0);
switch (sc->sc_topology) {
case QLA_TOPO_F_PORT:
case QLA_TOPO_FL_PORT:
break;
default:
return (0);
}
sc->sc_mbox[0] = QLA_MBOX_GET_PORT_DB;
if (sc->sc_2k_logins) {
sc->sc_mbox[1] = QLA_F_PORT_HANDLE;
} else {
sc->sc_mbox[1] = QLA_F_PORT_HANDLE << 8;
}
qla_mbox_putaddr(sc->sc_mbox, sc->sc_scratch);
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0,
sizeof(struct qla_get_port_db), BUS_DMASYNC_PREREAD);
if (qla_mbox(sc, 0x00cf)) {
DPRINTF(QLA_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 qla_get_port_db *pdb;
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0,
sizeof(struct qla_get_port_db), BUS_DMASYNC_POSTREAD);
pdb = QLA_DMA_KVA(sc->sc_scratch);
DPRINTF(QLA_D_PORT, "%s: SNS port name %llx\n", DEVNAME(sc),
betoh64(pdb->port_name));
sc->sc_sns_port_name = betoh64(pdb->port_name);
}
rft = QLA_DMA_KVA(sc->sc_scratch);
memset(rft, 0, sizeof(*rft) + sizeof(struct qla_sns_req_hdr));
rft->subcmd = htole16(QLA_SNS_RFT_ID);
rft->max_word = htole16(sizeof(struct qla_sns_req_hdr) / 4);
rft->port_id = htole32(sc->sc_port_id);
rft->fc4_types[0] = htole32(1 << QLA_FC4_SCSI);
if (qla_sns_req(sc, sc->sc_scratch, sizeof(*rft))) {
DPRINTF(QLA_D_PORT, "%s: RFT_ID failed\n", DEVNAME(sc));
}
return (1);
}
int
qla_get_port_name_list(struct qla_softc *sc, u_int32_t match)
{
int i;
struct qla_port_name_list *l;
struct qla_fc_port *port;
sc->sc_mbox[0] = QLA_MBOX_GET_PORT_NAME_LIST;
sc->sc_mbox[1] = 0x08;
if (match & QLA_LOCATION_FABRIC)
sc->sc_mbox[1] |= 0x02;
qla_mbox_putaddr(sc->sc_mbox, sc->sc_scratch);
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0,
QLA_DMA_LEN(sc->sc_scratch), BUS_DMASYNC_PREREAD);
if (qla_mbox(sc, 0x04f)) {
DPRINTF(QLA_D_PORT, "%s: get port name list failed: %x\n",
DEVNAME(sc), sc->sc_mbox[0]);
return (1);
}
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(sc->sc_scratch), 0,
QLA_DMA_LEN(sc->sc_scratch), BUS_DMASYNC_PREREAD);
i = 0;
l = QLA_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 = letoh16(l[i].loop_id);
switch (loopid) {
case QLA_F_PORT_HANDLE:
case QLA_SNS_HANDLE:
case QLA_FABRIC_CTRL_HANDLE:
loc = 0;
break;
default:
if (loopid <= sc->sc_loop_max_id) {
loc = QLA_LOCATION_LOOP_ID(loopid);
} else {
loc = QLA_LOCATION_FABRIC;
}
}
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(QLA_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);
}
struct qla_fc_port *
qla_next_fabric_port(struct qla_softc *sc, u_int32_t *firstport,
u_int32_t *lastport)
{
struct qla_sns_ga_nxt *ga;
struct qla_sns_ga_nxt_resp *gar;
struct qla_fc_port *fport;
int result;
ga = QLA_DMA_KVA(sc->sc_scratch);
memset(ga, 0, sizeof(*ga) + sizeof(*gar));
ga->subcmd = htole16(QLA_SNS_GA_NXT);
ga->max_word = htole16(sizeof(*gar) / 4);
ga->port_id = htole32(*lastport);
result = qla_sns_req(sc, sc->sc_scratch, sizeof(*ga));
if (result) {
DPRINTF(QLA_D_PORT, "%s: GA_NXT %06x failed: %x\n", DEVNAME(sc),
*lastport, result);
*lastport = 0xffffffff;
return (NULL);
}
gar = (struct qla_sns_ga_nxt_resp *)(ga + 1);
if (gar->port_type_id == 0 && gar->port_name == 0 &&
gar->node_name == 0) {
DPRINTF(QLA_D_PORT, "%s: GA_NXT returned junk\n", DEVNAME(sc));
return (NULL);
}
*lastport = betoh32(gar->port_type_id) & 0xffffff;
if (*lastport == *firstport) {
*lastport = 0xffffffff;
return (NULL);
}
if (*firstport == 0xffffffff)
*firstport = *lastport;
DPRINTF(QLA_D_PORT, "%s: GA_NXT: port id: %06x, wwpn %llx, wwnn %llx\n",
DEVNAME(sc), *lastport, betoh64(gar->port_name),
betoh64(gar->node_name));
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 = QLA_LOCATION_PORT_ID(*lastport);
fport->portid = *lastport;
return (fport);
}
int
qla_fabric_plogi(struct qla_softc *sc, struct qla_fc_port *port)
{
int loopid, mboxin, err;
u_int32_t id;
loopid = 0;
retry:
if (port->loopid == 0) {
mtx_enter(&sc->sc_port_mtx);
loopid = qla_get_loop_id(sc, loopid);
mtx_leave(&sc->sc_port_mtx);
if (loopid == -1) {
DPRINTF(QLA_D_PORT, "%s: ran out of loop ids\n",
DEVNAME(sc));
return (1);
}
}
mboxin = 0x000f;
sc->sc_mbox[0] = QLA_MBOX_FABRIC_PLOGI;
sc->sc_mbox[2] = (port->portid >> 16) & 0xff;
sc->sc_mbox[3] = port->portid & 0xffff;
if (sc->sc_2k_logins) {
sc->sc_mbox[1] = loopid;
sc->sc_mbox[10] = 0;
mboxin |= (1 << 10);
} else {
sc->sc_mbox[1] = loopid << 8;
}
err = qla_mbox(sc, mboxin);
switch (err) {
case 0:
DPRINTF(QLA_D_PORT, "%s: logged in to %06x as %d\n",
DEVNAME(sc), port->portid, loopid);
port->flags &= ~QLA_PORT_FLAG_NEEDS_LOGIN;
port->loopid = loopid;
return (0);
case QLA_MBOX_PORT_USED:
DPRINTF(QLA_D_PORT, "%s: already logged in to %06x as %d\n",
DEVNAME(sc), port->portid, sc->sc_mbox[1]);
port->flags &= ~QLA_PORT_FLAG_NEEDS_LOGIN;
port->loopid = sc->sc_mbox[1];
return (0);
case QLA_MBOX_LOOP_USED:
id = (sc->sc_mbox[1] << 16) | sc->sc_mbox[2];
if (qla_add_logged_in_port(sc, loopid, id)) {
return (1);
}
port->loopid = 0;
loopid++;
goto retry;
default:
DPRINTF(QLA_D_PORT, "%s: error %x logging in to port %06x\n",
DEVNAME(sc), err, port->portid);
port->loopid = 0;
return (1);
}
}
void
qla_fabric_plogo(struct qla_softc *sc, struct qla_fc_port *port)
{
int mboxin = 0x0003;
sc->sc_mbox[0] = QLA_MBOX_FABRIC_PLOGO;
if (sc->sc_2k_logins) {
sc->sc_mbox[1] = port->loopid;
sc->sc_mbox[10] = 0;
mboxin |= (1 << 10);
} else {
sc->sc_mbox[1] = port->loopid << 8;
}
if (qla_mbox(sc, mboxin))
DPRINTF(QLA_D_PORT, "%s: loop id %d logout failed\n",
DEVNAME(sc), port->loopid);
}
void
qla_update_done(struct qla_softc *sc, int task)
{
atomic_clearbits_int(&sc->sc_update_tasks, task);
}
void
qla_update_start(struct qla_softc *sc, int task)
{
atomic_setbits_int(&sc->sc_update_tasks, task);
task_add(sc->sc_update_taskq, &sc->sc_update_task);
}
void
qla_clear_port_lists(struct qla_softc *sc)
{
struct qla_fc_port *p;
while (!TAILQ_EMPTY(&sc->sc_ports_found)) {
p = TAILQ_FIRST(&sc->sc_ports_found);
TAILQ_REMOVE(&sc->sc_ports_found, p, update);
free(p, M_DEVBUF, sizeof *p);
}
while (!TAILQ_EMPTY(&sc->sc_ports_new)) {
p = TAILQ_FIRST(&sc->sc_ports_new);
TAILQ_REMOVE(&sc->sc_ports_new, p, update);
free(p, M_DEVBUF, sizeof *p);
}
while (!TAILQ_EMPTY(&sc->sc_ports_gone)) {
p = TAILQ_FIRST(&sc->sc_ports_gone);
TAILQ_REMOVE(&sc->sc_ports_gone, p, update);
}
}
void
qla_do_update(void *xsc)
{
struct qla_softc *sc = xsc;
int firstport, lastport;
struct qla_fc_port *port, *fport;
DPRINTF(QLA_D_PORT, "%s: updating\n", DEVNAME(sc));
while (sc->sc_update_tasks != 0) {
if (sc->sc_update_tasks & QLA_UPDATE_TASK_CLEAR_ALL) {
TAILQ_HEAD(, qla_fc_port) detach;
DPRINTF(QLA_D_PORT, "%s: detaching everything\n",
DEVNAME(sc));
mtx_enter(&sc->sc_port_mtx);
qla_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 & QLA_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 & QLA_LOCATION_FABRIC)
qla_fabric_plogo(sc, port);
free(port, M_DEVBUF, sizeof *port);
}
qla_update_done(sc, QLA_UPDATE_TASK_CLEAR_ALL);
continue;
}
if (sc->sc_update_tasks & QLA_UPDATE_TASK_SOFTRESET) {
qla_update_done(sc, QLA_UPDATE_TASK_SOFTRESET);
continue;
}
if (sc->sc_update_tasks & QLA_UPDATE_TASK_UPDATE_TOPO) {
DPRINTF(QLA_D_PORT, "%s: updating topology\n",
DEVNAME(sc));
qla_update_topology(sc);
qla_update_done(sc, QLA_UPDATE_TASK_UPDATE_TOPO);
continue;
}
if (sc->sc_update_tasks & QLA_UPDATE_TASK_GET_PORT_LIST) {
DPRINTF(QLA_D_PORT, "%s: getting port name list\n",
DEVNAME(sc));
mtx_enter(&sc->sc_port_mtx);
qla_clear_port_lists(sc);
mtx_leave(&sc->sc_port_mtx);
qla_get_port_name_list(sc, QLA_LOCATION_LOOP |
QLA_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 & QLA_LOCATION_FABRIC) {
port->flags |=
QLA_PORT_FLAG_NEEDS_LOGIN;
}
}
TAILQ_FOREACH(fport, &sc->sc_ports_found, update) {
port = sc->sc_targets[fport->loopid];
if (port == NULL || fport->port_name !=
port->port_name) {
continue;
}
port->flags &= ~QLA_PORT_FLAG_NEEDS_LOGIN;
if (port->location & QLA_LOCATION_LOOP)
TAILQ_REMOVE(&sc->sc_ports_gone,
port, update);
fport->location = 0;
}
mtx_leave(&sc->sc_port_mtx);
qla_update_start(sc, QLA_UPDATE_TASK_PORT_LIST);
qla_update_done(sc, QLA_UPDATE_TASK_GET_PORT_LIST);
continue;
}
if (sc->sc_update_tasks & QLA_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(QLA_D_PORT, "%s: done with ports\n",
DEVNAME(sc));
qla_update_done(sc,
QLA_UPDATE_TASK_PORT_LIST);
qla_update_start(sc,
QLA_UPDATE_TASK_SCAN_FABRIC);
} else if (fport->location & QLA_LOCATION_LOOP) {
DPRINTF(QLA_D_PORT, "%s: loop port %d\n",
DEVNAME(sc), fport->loopid);
if (qla_add_loop_port(sc, fport) != 0)
free(fport, M_DEVBUF, sizeof *fport);
} else if (fport->location & QLA_LOCATION_FABRIC) {
qla_add_fabric_port(sc, fport);
} else {
free(fport, M_DEVBUF, sizeof *fport);
}
continue;
}
if (sc->sc_update_tasks & QLA_UPDATE_TASK_SCAN_FABRIC) {
DPRINTF(QLA_D_PORT, "%s: starting fabric scan\n",
DEVNAME(sc));
lastport = sc->sc_port_id;
firstport = 0xffffffff;
if (qla_update_fabric(sc))
qla_update_start(sc,
QLA_UPDATE_TASK_SCANNING_FABRIC);
qla_update_done(sc, QLA_UPDATE_TASK_SCAN_FABRIC);
continue;
}
if (sc->sc_update_tasks & QLA_UPDATE_TASK_SCANNING_FABRIC) {
fport = qla_next_fabric_port(sc, &firstport, &lastport);
if (fport != NULL) {
int disp;
mtx_enter(&sc->sc_port_mtx);
disp = qla_classify_port(sc, fport->location,
fport->port_name, fport->node_name, &port);
switch (disp) {
case QLA_PORT_DISP_CHANGED:
case QLA_PORT_DISP_MOVED:
case QLA_PORT_DISP_NEW:
DPRINTF(QLA_D_PORT, "%s: new port "
"%06x\n", DEVNAME(sc),
fport->portid);
TAILQ_INSERT_TAIL(&sc->sc_ports_found,
fport, update);
break;
case QLA_PORT_DISP_DUP:
free(fport, M_DEVBUF, sizeof *fport);
break;
case QLA_PORT_DISP_SAME:
DPRINTF(QLA_D_PORT, "%s: existing port"
" %06x\n", DEVNAME(sc),
fport->portid);
TAILQ_REMOVE(&sc->sc_ports_gone, port,
update);
free(fport, M_DEVBUF, sizeof *fport);
break;
}
mtx_leave(&sc->sc_port_mtx);
}
if (lastport == 0xffffffff) {
DPRINTF(QLA_D_PORT, "%s: finished\n",
DEVNAME(sc));
qla_update_done(sc,
QLA_UPDATE_TASK_SCANNING_FABRIC);
qla_update_start(sc,
QLA_UPDATE_TASK_FABRIC_LOGIN);
}
continue;
}
if (sc->sc_update_tasks & QLA_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(QLA_D_PORT, "%s: found port %06x\n",
DEVNAME(sc), port->portid);
if (qla_fabric_plogi(sc, port) == 0) {
qla_add_fabric_port(sc, port);
} else {
free(port, M_DEVBUF, sizeof *port);
}
} else {
DPRINTF(QLA_D_PORT, "%s: done with logins\n",
DEVNAME(sc));
qla_update_done(sc,
QLA_UPDATE_TASK_FABRIC_LOGIN);
qla_update_start(sc,
QLA_UPDATE_TASK_ATTACH_TARGET |
QLA_UPDATE_TASK_DETACH_TARGET);
}
continue;
}
if (sc->sc_update_tasks & QLA_UPDATE_TASK_FABRIC_RELOGIN) {
TAILQ_FOREACH(port, &sc->sc_ports, ports) {
if (port->flags & QLA_PORT_FLAG_NEEDS_LOGIN) {
qla_fabric_plogi(sc, port);
break;
}
}
if (port == NULL)
qla_update_done(sc,
QLA_UPDATE_TASK_FABRIC_RELOGIN);
continue;
}
if (sc->sc_update_tasks & QLA_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(QLA_D_PORT, "%s: detaching target %d\n",
DEVNAME(sc), port->loopid);
if (sc->sc_scsibus != NULL)
scsi_detach_target(sc->sc_scsibus,
port->loopid, DETACH_FORCE |
DETACH_QUIET);
if (port->location & QLA_LOCATION_FABRIC)
qla_fabric_plogo(sc, port);
free(port, M_DEVBUF, sizeof *port);
} else {
qla_update_done(sc,
QLA_UPDATE_TASK_DETACH_TARGET);
}
continue;
}
if (sc->sc_update_tasks & QLA_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 {
qla_update_done(sc,
QLA_UPDATE_TASK_ATTACH_TARGET);
}
continue;
}
}
DPRINTF(QLA_D_PORT, "%s: done updating\n", DEVNAME(sc));
}
int
qla_async(struct qla_softc *sc, u_int16_t info)
{
u_int16_t id, exp;
switch (info) {
case QLA_ASYNC_SYSTEM_ERROR:
qla_update_start(sc, QLA_UPDATE_TASK_SOFTRESET);
break;
case QLA_ASYNC_REQ_XFER_ERROR:
qla_update_start(sc, QLA_UPDATE_TASK_SOFTRESET);
break;
case QLA_ASYNC_RSP_XFER_ERROR:
qla_update_start(sc, QLA_UPDATE_TASK_SOFTRESET);
break;
case QLA_ASYNC_LIP_OCCURRED:
DPRINTF(QLA_D_PORT, "%s: lip occurred\n", DEVNAME(sc));
break;
case QLA_ASYNC_LOOP_UP:
DPRINTF(QLA_D_PORT, "%s: loop up\n", DEVNAME(sc));
sc->sc_loop_up = 1;
sc->sc_marker_required = 1;
qla_update_start(sc, QLA_UPDATE_TASK_UPDATE_TOPO |
QLA_UPDATE_TASK_GET_PORT_LIST);
break;
case QLA_ASYNC_LOOP_DOWN:
DPRINTF(QLA_D_PORT, "%s: loop down\n", DEVNAME(sc));
sc->sc_loop_up = 0;
qla_update_start(sc, QLA_UPDATE_TASK_CLEAR_ALL);
break;
case QLA_ASYNC_LIP_RESET:
DPRINTF(QLA_D_PORT, "%s: lip reset\n", DEVNAME(sc));
sc->sc_marker_required = 1;
qla_update_start(sc, QLA_UPDATE_TASK_FABRIC_RELOGIN);
break;
case QLA_ASYNC_PORT_DB_CHANGE:
DPRINTF(QLA_D_PORT, "%s: port db changed %x\n", DEVNAME(sc),
qla_read_mbox(sc, 1));
qla_update_start(sc, QLA_UPDATE_TASK_GET_PORT_LIST);
break;
case QLA_ASYNC_CHANGE_NOTIFY:
DPRINTF(QLA_D_PORT, "%s: name server change (%02x:%02x)\n",
DEVNAME(sc), qla_read_mbox(sc, 1), qla_read_mbox(sc, 2));
qla_update_start(sc, QLA_UPDATE_TASK_GET_PORT_LIST);
break;
case QLA_ASYNC_LIP_F8:
DPRINTF(QLA_D_PORT, "%s: lip f8\n", DEVNAME(sc));
break;
case QLA_ASYNC_LOOP_INIT_ERROR:
DPRINTF(QLA_D_PORT, "%s: loop initialization error: %x\n",
DEVNAME(sc), qla_read_mbox(sc, 1));
break;
case QLA_ASYNC_LOGIN_REJECT:
id = qla_read_mbox(sc, 1);
exp = qla_read_mbox(sc, 2);
DPRINTF(QLA_D_PORT, "%s: login reject from %x (reason %d,"
" explanation %x)\n", DEVNAME(sc), id >> 8, id & 0xff, exp);
break;
case QLA_ASYNC_SCSI_CMD_COMPLETE:
break;
case QLA_ASYNC_CTIO_COMPLETE:
break;
case QLA_ASYNC_POINT_TO_POINT:
DPRINTF(QLA_D_PORT, "%s: connected in point-to-point mode\n",
DEVNAME(sc));
break;
case QLA_ASYNC_ZIO_RESP_UPDATE:
break;
case QLA_ASYNC_RND_ERROR:
break;
case QLA_ASYNC_QUEUE_FULL:
break;
default:
DPRINTF(QLA_D_INTR, "%s: unknown async %x\n", DEVNAME(sc),
info);
break;
}
return (1);
}
void
qla_dump_iocb(struct qla_softc *sc, void *buf)
{
#ifdef QLA_DEBUG
u_int8_t *iocb = buf;
int l;
int b;
if ((qladebug & QLA_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
qla_dump_iocb_segs(struct qla_softc *sc, void *segs, int n)
{
#ifdef QLA_DEBUG
u_int8_t *buf = segs;
int s, b;
if ((qladebug & QLA_D_IOCB) == 0)
return;
printf("%s: iocb segs:\n", DEVNAME(sc));
for (s = 0; s < n; s++) {
for (b = 0; b < sizeof(struct qla_iocb_seg); b++) {
printf(" %2.2x", buf[(s*(sizeof(struct qla_iocb_seg)))
+ b]);
}
printf("\n");
}
#endif
}
void
qla_put_marker(struct qla_softc *sc, void *buf)
{
struct qla_iocb_marker *marker = buf;
marker->entry_type = QLA_IOCB_MARKER;
marker->entry_count = 1;
marker->seqno = 0;
marker->flags = 0;
marker->target = 0;
marker->modifier = QLA_IOCB_MARKER_SYNC_ALL;
qla_dump_iocb(sc, buf);
}
static inline void
qla_put_data_seg(struct qla_iocb_seg *seg, bus_dmamap_t dmap, int num)
{
uint64_t addr = dmap->dm_segs[num].ds_addr;
htolem32(&seg->seg_addr_lo, addr);
htolem32(&seg->seg_addr_hi, addr >> 32);
htolem32(&seg->seg_len, dmap->dm_segs[num].ds_len);
}
void
qla_put_cmd(struct qla_softc *sc, void *buf, struct scsi_xfer *xs,
struct qla_ccb *ccb)
{
struct qla_iocb_req34 *req = buf;
u_int16_t dir;
int seg;
int target = xs->sc_link->target;
req->seqno = 0;
req->flags = 0;
req->entry_count = 1;
if (xs->datalen == 0) {
dir = QLA_IOCB_CMD_NO_DATA;
req->req_seg_count = 0;
req->entry_type = QLA_IOCB_CMD_TYPE_3;
} else {
dir = xs->flags & SCSI_DATA_IN ? QLA_IOCB_CMD_READ_DATA :
QLA_IOCB_CMD_WRITE_DATA;
htolem16(&req->req_seg_count, ccb->ccb_dmamap->dm_nsegs);
if (ccb->ccb_dmamap->dm_nsegs > QLA_IOCB_SEGS_PER_CMD) {
req->entry_type = QLA_IOCB_CMD_TYPE_4;
for (seg = 0; seg < ccb->ccb_dmamap->dm_nsegs; seg++) {
qla_put_data_seg(&ccb->ccb_t4segs[seg],
ccb->ccb_dmamap, seg);
}
req->req_type.req4.req4_seg_type = htole16(1);
req->req_type.req4.req4_seg_base = 0;
req->req_type.req4.req4_seg_addr = ccb->ccb_seg_dva;
memset(req->req_type.req4.req4_reserved, 0,
sizeof(req->req_type.req4.req4_reserved));
bus_dmamap_sync(sc->sc_dmat,
QLA_DMA_MAP(sc->sc_segments), ccb->ccb_seg_offset,
sizeof(*ccb->ccb_t4segs) * ccb->ccb_dmamap->dm_nsegs,
BUS_DMASYNC_PREWRITE);
} else {
req->entry_type = QLA_IOCB_CMD_TYPE_3;
for (seg = 0; seg < ccb->ccb_dmamap->dm_nsegs; seg++) {
qla_put_data_seg(&req->req_type.req3_segs[seg],
ccb->ccb_dmamap, seg);
}
}
}
htolem16(&req->req_flags, QLA_IOCB_CMD_SIMPLE_QUEUE | dir);
if (xs->timeout != 0)
htolem16(&req->req_time, MAX(1, xs->timeout/1000));
if (sc->sc_expanded_lun) {
if (sc->sc_2k_logins) {
req->req_target = htole16(target);
} else {
req->req_target = htole16(target << 8);
}
req->req_scclun = htole16(xs->sc_link->lun);
} else {
req->req_target = htole16(target << 8 | xs->sc_link->lun);
}
memcpy(req->req_cdb, &xs->cmd, xs->cmdlen);
req->req_totalcnt = htole32(xs->datalen);
req->req_handle = ccb->ccb_id;
qla_dump_iocb(sc, buf);
}
int
qla_verify_firmware(struct qla_softc *sc, u_int16_t addr)
{
sc->sc_mbox[0] = QLA_MBOX_VERIFY_CSUM;
sc->sc_mbox[1] = addr;
return (qla_mbox(sc, 0x0003));
}
#ifndef ISP_NOFIRMWARE
int
qla_load_firmware_words(struct qla_softc *sc, const u_int16_t *src,
u_int16_t dest)
{
u_int16_t i;
for (i = 0; i < src[3]; i++) {
sc->sc_mbox[0] = QLA_MBOX_WRITE_RAM_WORD;
sc->sc_mbox[1] = i + dest;
sc->sc_mbox[2] = src[i];
if (qla_mbox(sc, 0x07)) {
printf("firmware load failed\n");
return (1);
}
}
return (qla_verify_firmware(sc, dest));
}
int
qla_load_firmware_2100(struct qla_softc *sc)
{
return qla_load_firmware_words(sc, isp_2100_risc_code,
QLA_2100_CODE_ORG);
}
int
qla_load_firmware_2200(struct qla_softc *sc)
{
return qla_load_firmware_words(sc, isp_2200_risc_code,
QLA_2200_CODE_ORG);
}
int
qla_load_fwchunk_2300(struct qla_softc *sc, struct qla_dmamem *mem,
const u_int16_t *src, u_int32_t dest)
{
u_int16_t origin, done, total;
int i;
origin = dest;
done = 0;
total = src[3];
while (done < total) {
u_int16_t *copy;
u_int32_t words;
words = MIN(total - done, 1 << 10);
copy = QLA_DMA_KVA(mem);
for (i = 0; i < words; i++) {
copy[i] = htole16(src[done++]);
}
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, words * 2,
BUS_DMASYNC_PREWRITE);
sc->sc_mbox[0] = QLA_MBOX_LOAD_RAM_EXT;
sc->sc_mbox[1] = dest;
sc->sc_mbox[4] = words;
sc->sc_mbox[8] = dest >> 16;
qla_mbox_putaddr(sc->sc_mbox, mem);
if (qla_mbox(sc, 0x01ff)) {
printf("firmware load failed\n");
return (1);
}
bus_dmamap_sync(sc->sc_dmat, QLA_DMA_MAP(mem), 0, words * 2,
BUS_DMASYNC_POSTWRITE);
dest += words;
}
return (qla_verify_firmware(sc, origin));
}
int
qla_load_firmware_2300(struct qla_softc *sc)
{
struct qla_dmamem *mem;
const u_int16_t *fw = isp_2300_risc_code;
int rv;
mem = qla_dmamem_alloc(sc, 65536);
if (mem == NULL)
return ENOMEM;
rv = qla_load_fwchunk_2300(sc, mem, fw, QLA_2300_CODE_ORG);
qla_dmamem_free(sc, mem);
return (rv);
}
int
qla_load_firmware_2322(struct qla_softc *sc)
{
#if 0
struct qla_dmamem *mem;
const u_int16_t *fw = isp_2322_risc_code;
u_int32_t addr;
int i;
mem = qla_dmamem_alloc(sc, 65536);
if (qla_load_fwchunk_2300(sc, mem, fw, QLA_2300_CODE_ORG)) {
qla_dmamem_free(sc, mem);
return (1);
}
for (i = 0; i < 2; i++) {
fw += fw[3];
addr = fw[5] | ((fw[4] & 0x3f) << 16);
if (qla_load_fwchunk_2300(sc, mem, fw, addr)) {
qla_dmamem_free(sc, mem);
return (1);
}
}
qla_dmamem_free(sc, mem);
#endif
return (0);
}
#endif
int
qla_read_nvram(struct qla_softc *sc)
{
u_int16_t data[sizeof(sc->sc_nvram) >> 1];
u_int16_t req, cmd, val;
u_int8_t csum;
int i, base, bit;
base = sc->sc_port * 0x80;
qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL);
delay(10);
qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL | QLA_NVRAM_CLOCK);
delay(10);
for (i = 0; i < nitems(data); i++) {
req = (i + base) | (QLA_NVRAM_CMD_READ << 8);
for (bit = 10; bit >= 0; bit--) {
cmd = QLA_NVRAM_CHIP_SEL;
if ((req >> bit) & 1) {
cmd |= QLA_NVRAM_DATA_OUT;
}
qla_write(sc, QLA_NVRAM, cmd);
delay(10);
qla_read(sc, QLA_NVRAM);
qla_write(sc, QLA_NVRAM, cmd | QLA_NVRAM_CLOCK);
delay(10);
qla_read(sc, QLA_NVRAM);
qla_write(sc, QLA_NVRAM, cmd);
delay(10);
qla_read(sc, QLA_NVRAM);
}
val = 0;
for (bit = 0; bit < 16; bit++) {
val <<= 1;
qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL |
QLA_NVRAM_CLOCK);
delay(10);
if (qla_read(sc, QLA_NVRAM) & QLA_NVRAM_DATA_IN)
val |= 1;
delay(10);
qla_write(sc, QLA_NVRAM, QLA_NVRAM_CHIP_SEL);
delay(10);
qla_read(sc, QLA_NVRAM);
}
qla_write(sc, QLA_NVRAM, 0);
delay(10);
qla_read(sc, QLA_NVRAM);
data[i] = letoh16(val);
}
csum = 0;
for (i = 0; i < nitems(data); i++) {
csum += data[i] & 0xff;
csum += data[i] >> 8;
}
memcpy(&sc->sc_nvram, data, sizeof(sc->sc_nvram));
if (sc->sc_nvram.id[0] != 'I' || sc->sc_nvram.id[1] != 'S' ||
sc->sc_nvram.id[2] != 'P' || sc->sc_nvram.id[3] != ' ' ||
sc->sc_nvram.nvram_version < 1 || (csum != 0)) {
if (sc->sc_node_name == 0 || sc->sc_port_name == 0)
printf("%s: nvram corrupt\n", DEVNAME(sc));
return (1);
}
return (0);
}
struct qla_dmamem *
qla_dmamem_alloc(struct qla_softc *sc, size_t size)
{
struct qla_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
qla_dmamem_free(struct qla_softc *sc, struct qla_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
qla_alloc_ccbs(struct qla_softc *sc)
{
struct qla_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 qla_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 = qla_dmamem_alloc(sc, sc->sc_maxcmds *
QLA_QUEUE_ENTRY_SIZE);
if (sc->sc_requests == NULL) {
printf("%s: unable to allocate ccb dmamem\n", DEVNAME(sc));
goto free_ccbs;
}
sc->sc_responses = qla_dmamem_alloc(sc, sc->sc_maxcmds *
QLA_QUEUE_ENTRY_SIZE);
if (sc->sc_responses == NULL) {
printf("%s: unable to allocate rcb dmamem\n", DEVNAME(sc));
goto free_req;
}
sc->sc_segments = qla_dmamem_alloc(sc, sc->sc_maxcmds * QLA_MAX_SEGS *
sizeof(struct qla_iocb_seg));
if (sc->sc_segments == NULL) {
printf("%s: unable to allocate iocb segments\n", DEVNAME(sc));
goto free_res;
}
cmd = QLA_DMA_KVA(sc->sc_requests);
memset(cmd, 0, QLA_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,
QLA_MAX_SEGS, 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 * QLA_MAX_SEGS *
sizeof(struct qla_iocb_seg);
htolem64(&ccb->ccb_seg_dva,
QLA_DMA_DVA(sc->sc_segments) + ccb->ccb_seg_offset);
ccb->ccb_t4segs = QLA_DMA_KVA(sc->sc_segments) +
ccb->ccb_seg_offset;
qla_put_ccb(sc, ccb);
}
scsi_iopool_init(&sc->sc_iopool, sc, qla_get_ccb, qla_put_ccb);
return (0);
free_maps:
while ((ccb = qla_get_ccb(sc)) != NULL)
bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap);
qla_dmamem_free(sc, sc->sc_segments);
free_res:
qla_dmamem_free(sc, sc->sc_responses);
free_req:
qla_dmamem_free(sc, sc->sc_requests);
free_ccbs:
free(sc->sc_ccbs, M_DEVBUF, 0);
return (1);
}
void
qla_free_ccbs(struct qla_softc *sc)
{
struct qla_ccb *ccb;
scsi_iopool_destroy(&sc->sc_iopool);
while ((ccb = qla_get_ccb(sc)) != NULL)
bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap);
qla_dmamem_free(sc, sc->sc_segments);
qla_dmamem_free(sc, sc->sc_responses);
qla_dmamem_free(sc, sc->sc_requests);
free(sc->sc_ccbs, M_DEVBUF, 0);
}
void *
qla_get_ccb(void *xsc)
{
struct qla_softc *sc = xsc;
struct qla_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
qla_put_ccb(void *xsc, void *io)
{
struct qla_softc *sc = xsc;
struct qla_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);
}