aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/mvs/mvs.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/mvs/mvs.c')
-rw-r--r--sys/dev/mvs/mvs.c2173
1 files changed, 2173 insertions, 0 deletions
diff --git a/sys/dev/mvs/mvs.c b/sys/dev/mvs/mvs.c
new file mode 100644
index 000000000000..038e9f210722
--- /dev/null
+++ b/sys/dev/mvs/mvs.c
@@ -0,0 +1,2173 @@
+/*-
+ * Copyright (c) 2010 Alexander Motin <mav@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * without modification, immediately at the beginning of the file.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/ata.h>
+#include <sys/bus.h>
+#include <sys/endian.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <vm/uma.h>
+#include <machine/stdarg.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include "mvs.h"
+
+#include <cam/cam.h>
+#include <cam/cam_ccb.h>
+#include <cam/cam_sim.h>
+#include <cam/cam_xpt_sim.h>
+#include <cam/cam_debug.h>
+
+/* local prototypes */
+static int mvs_ch_suspend(device_t dev);
+static int mvs_ch_resume(device_t dev);
+static void mvs_dmainit(device_t dev);
+static void mvs_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error);
+static void mvs_dmafini(device_t dev);
+static void mvs_slotsalloc(device_t dev);
+static void mvs_slotsfree(device_t dev);
+static void mvs_setup_edma_queues(device_t dev);
+static void mvs_set_edma_mode(device_t dev, enum mvs_edma_mode mode);
+static void mvs_ch_pm(void *arg);
+static void mvs_ch_intr_locked(void *data);
+static void mvs_ch_intr(void *data);
+static void mvs_reset(device_t dev);
+static void mvs_softreset(device_t dev, union ccb *ccb);
+
+static int mvs_sata_connect(struct mvs_channel *ch);
+static int mvs_sata_phy_reset(device_t dev);
+static int mvs_wait(device_t dev, u_int s, u_int c, int t);
+static void mvs_tfd_read(device_t dev, union ccb *ccb);
+static void mvs_tfd_write(device_t dev, union ccb *ccb);
+static void mvs_legacy_intr(device_t dev);
+static void mvs_crbq_intr(device_t dev);
+static void mvs_begin_transaction(device_t dev, union ccb *ccb);
+static void mvs_legacy_execute_transaction(struct mvs_slot *slot);
+static void mvs_timeout(struct mvs_slot *slot);
+static void mvs_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error);
+static void mvs_requeue_frozen(device_t dev);
+static void mvs_execute_transaction(struct mvs_slot *slot);
+static void mvs_end_transaction(struct mvs_slot *slot, enum mvs_err_type et);
+
+static void mvs_issue_read_log(device_t dev);
+static void mvs_process_read_log(device_t dev, union ccb *ccb);
+
+static void mvsaction(struct cam_sim *sim, union ccb *ccb);
+static void mvspoll(struct cam_sim *sim);
+
+MALLOC_DEFINE(M_MVS, "MVS driver", "MVS driver data buffers");
+
+static int
+mvs_ch_probe(device_t dev)
+{
+
+ device_set_desc_copy(dev, "Marvell SATA channel");
+ return (0);
+}
+
+static int
+mvs_ch_attach(device_t dev)
+{
+ struct mvs_controller *ctlr = device_get_softc(device_get_parent(dev));
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct cam_devq *devq;
+ int rid, error, i, sata_rev = 0;
+
+ ch->dev = dev;
+ ch->unit = (intptr_t)device_get_ivars(dev);
+ ch->quirks = ctlr->quirks;
+ mtx_init(&ch->mtx, "MVS channel lock", NULL, MTX_DEF);
+ resource_int_value(device_get_name(dev),
+ device_get_unit(dev), "pm_level", &ch->pm_level);
+ if (ch->pm_level > 3)
+ callout_init_mtx(&ch->pm_timer, &ch->mtx, 0);
+ resource_int_value(device_get_name(dev),
+ device_get_unit(dev), "sata_rev", &sata_rev);
+ for (i = 0; i < 16; i++) {
+ ch->user[i].revision = sata_rev;
+ ch->user[i].mode = 0;
+ ch->user[i].bytecount = (ch->quirks & MVS_Q_GENIIE) ? 8192 : 2048;
+ ch->user[i].tags = MVS_MAX_SLOTS;
+ ch->curr[i] = ch->user[i];
+ if (ch->pm_level) {
+ ch->user[i].caps = CTS_SATA_CAPS_H_PMREQ |
+ CTS_SATA_CAPS_H_APST |
+ CTS_SATA_CAPS_D_PMREQ | CTS_SATA_CAPS_D_APST;
+ }
+ }
+ rid = ch->unit;
+ if (!(ch->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &rid, RF_ACTIVE)))
+ return (ENXIO);
+ mvs_dmainit(dev);
+ mvs_slotsalloc(dev);
+ mvs_ch_resume(dev);
+ mtx_lock(&ch->mtx);
+ rid = ATA_IRQ_RID;
+ if (!(ch->r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ,
+ &rid, RF_SHAREABLE | RF_ACTIVE))) {
+ device_printf(dev, "Unable to map interrupt\n");
+ error = ENXIO;
+ goto err0;
+ }
+ if ((bus_setup_intr(dev, ch->r_irq, ATA_INTR_FLAGS, NULL,
+ mvs_ch_intr_locked, dev, &ch->ih))) {
+ device_printf(dev, "Unable to setup interrupt\n");
+ error = ENXIO;
+ goto err1;
+ }
+ /* Create the device queue for our SIM. */
+ devq = cam_simq_alloc(MVS_MAX_SLOTS - 1);
+ if (devq == NULL) {
+ device_printf(dev, "Unable to allocate simq\n");
+ error = ENOMEM;
+ goto err1;
+ }
+ /* Construct SIM entry */
+ ch->sim = cam_sim_alloc(mvsaction, mvspoll, "mvsch", ch,
+ device_get_unit(dev), &ch->mtx,
+ 2, (ch->quirks & MVS_Q_GENI) ? 0 : MVS_MAX_SLOTS - 1,
+ devq);
+ if (ch->sim == NULL) {
+ cam_simq_free(devq);
+ device_printf(dev, "unable to allocate sim\n");
+ error = ENOMEM;
+ goto err1;
+ }
+ if (xpt_bus_register(ch->sim, dev, 0) != CAM_SUCCESS) {
+ device_printf(dev, "unable to register xpt bus\n");
+ error = ENXIO;
+ goto err2;
+ }
+ if (xpt_create_path(&ch->path, /*periph*/NULL, cam_sim_path(ch->sim),
+ CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
+ device_printf(dev, "unable to create path\n");
+ error = ENXIO;
+ goto err3;
+ }
+ if (ch->pm_level > 3) {
+ callout_reset(&ch->pm_timer,
+ (ch->pm_level == 4) ? hz / 1000 : hz / 8,
+ mvs_ch_pm, dev);
+ }
+ mtx_unlock(&ch->mtx);
+ return (0);
+
+err3:
+ xpt_bus_deregister(cam_sim_path(ch->sim));
+err2:
+ cam_sim_free(ch->sim, /*free_devq*/TRUE);
+err1:
+ bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq);
+err0:
+ bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem);
+ mtx_unlock(&ch->mtx);
+ mtx_destroy(&ch->mtx);
+ return (error);
+}
+
+static int
+mvs_ch_detach(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ mtx_lock(&ch->mtx);
+ xpt_async(AC_LOST_DEVICE, ch->path, NULL);
+ xpt_free_path(ch->path);
+ xpt_bus_deregister(cam_sim_path(ch->sim));
+ cam_sim_free(ch->sim, /*free_devq*/TRUE);
+ mtx_unlock(&ch->mtx);
+
+ if (ch->pm_level > 3)
+ callout_drain(&ch->pm_timer);
+ bus_teardown_intr(dev, ch->r_irq, ch->ih);
+ bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq);
+
+ mvs_ch_suspend(dev);
+ mvs_slotsfree(dev);
+ mvs_dmafini(dev);
+
+ bus_release_resource(dev, SYS_RES_MEMORY, ch->unit, ch->r_mem);
+ mtx_destroy(&ch->mtx);
+ return (0);
+}
+
+static int
+mvs_ch_suspend(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ /* Stop EDMA */
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ /* Disable port interrupts. */
+ ATA_OUTL(ch->r_mem, EDMA_IEM, 0);
+ return (0);
+}
+
+static int
+mvs_ch_resume(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint32_t reg;
+
+ /* Disable port interrupts */
+ ATA_OUTL(ch->r_mem, EDMA_IEM, 0);
+ /* Stop EDMA */
+ ch->curr_mode = MVS_EDMA_UNKNOWN;
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ /* Clear and configure FIS interrupts. */
+ ATA_OUTL(ch->r_mem, SATA_FISIC, 0);
+ reg = ATA_INL(ch->r_mem, SATA_FISC);
+ reg |= SATA_FISC_FISWAIT4HOSTRDYEN_B1;
+ ATA_OUTL(ch->r_mem, SATA_FISC, reg);
+ reg = ATA_INL(ch->r_mem, SATA_FISIM);
+ reg |= SATA_FISC_FISWAIT4HOSTRDYEN_B1;
+ ATA_OUTL(ch->r_mem, SATA_FISC, reg);
+ /* Clear SATA error register. */
+ ATA_OUTL(ch->r_mem, SATA_SE, 0xffffffff);
+ /* Clear any outstanding error interrupts. */
+ ATA_OUTL(ch->r_mem, EDMA_IEC, 0);
+ /* Unmask all error interrupts */
+ ATA_OUTL(ch->r_mem, EDMA_IEM, ~EDMA_IE_TRANSIENT);
+ return (0);
+}
+
+struct mvs_dc_cb_args {
+ bus_addr_t maddr;
+ int error;
+};
+
+static void
+mvs_dmainit(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct mvs_dc_cb_args dcba;
+
+ /* EDMA command request area. */
+ if (bus_dma_tag_create(bus_get_dma_tag(dev), 1024, 0,
+ BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
+ NULL, NULL, MVS_WORKRQ_SIZE, 1, MVS_WORKRQ_SIZE,
+ 0, NULL, NULL, &ch->dma.workrq_tag))
+ goto error;
+ if (bus_dmamem_alloc(ch->dma.workrq_tag, (void **)&ch->dma.workrq, 0,
+ &ch->dma.workrq_map))
+ goto error;
+ if (bus_dmamap_load(ch->dma.workrq_tag, ch->dma.workrq_map, ch->dma.workrq,
+ MVS_WORKRQ_SIZE, mvs_dmasetupc_cb, &dcba, 0) || dcba.error) {
+ bus_dmamem_free(ch->dma.workrq_tag, ch->dma.workrq, ch->dma.workrq_map);
+ goto error;
+ }
+ ch->dma.workrq_bus = dcba.maddr;
+ /* EDMA command response area. */
+ if (bus_dma_tag_create(bus_get_dma_tag(dev), 256, 0,
+ BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
+ NULL, NULL, MVS_WORKRP_SIZE, 1, MVS_WORKRP_SIZE,
+ 0, NULL, NULL, &ch->dma.workrp_tag))
+ goto error;
+ if (bus_dmamem_alloc(ch->dma.workrp_tag, (void **)&ch->dma.workrp, 0,
+ &ch->dma.workrp_map))
+ goto error;
+ if (bus_dmamap_load(ch->dma.workrp_tag, ch->dma.workrp_map, ch->dma.workrp,
+ MVS_WORKRP_SIZE, mvs_dmasetupc_cb, &dcba, 0) || dcba.error) {
+ bus_dmamem_free(ch->dma.workrp_tag, ch->dma.workrp, ch->dma.workrp_map);
+ goto error;
+ }
+ ch->dma.workrp_bus = dcba.maddr;
+ /* Data area. */
+ if (bus_dma_tag_create(bus_get_dma_tag(dev), 2, MVS_EPRD_MAX,
+ BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
+ NULL, NULL,
+ MVS_SG_ENTRIES * PAGE_SIZE * MVS_MAX_SLOTS,
+ MVS_SG_ENTRIES, MVS_EPRD_MAX,
+ 0, busdma_lock_mutex, &ch->mtx, &ch->dma.data_tag)) {
+ goto error;
+ }
+ return;
+
+error:
+ device_printf(dev, "WARNING - DMA initialization failed\n");
+ mvs_dmafini(dev);
+}
+
+static void
+mvs_dmasetupc_cb(void *xsc, bus_dma_segment_t *segs, int nsegs, int error)
+{
+ struct mvs_dc_cb_args *dcba = (struct mvs_dc_cb_args *)xsc;
+
+ if (!(dcba->error = error))
+ dcba->maddr = segs[0].ds_addr;
+}
+
+static void
+mvs_dmafini(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ if (ch->dma.data_tag) {
+ bus_dma_tag_destroy(ch->dma.data_tag);
+ ch->dma.data_tag = NULL;
+ }
+ if (ch->dma.workrp_bus) {
+ bus_dmamap_unload(ch->dma.workrp_tag, ch->dma.workrp_map);
+ bus_dmamem_free(ch->dma.workrp_tag, ch->dma.workrp, ch->dma.workrp_map);
+ ch->dma.workrp_bus = 0;
+ ch->dma.workrp_map = NULL;
+ ch->dma.workrp = NULL;
+ }
+ if (ch->dma.workrp_tag) {
+ bus_dma_tag_destroy(ch->dma.workrp_tag);
+ ch->dma.workrp_tag = NULL;
+ }
+ if (ch->dma.workrq_bus) {
+ bus_dmamap_unload(ch->dma.workrq_tag, ch->dma.workrq_map);
+ bus_dmamem_free(ch->dma.workrq_tag, ch->dma.workrq, ch->dma.workrq_map);
+ ch->dma.workrq_bus = 0;
+ ch->dma.workrq_map = NULL;
+ ch->dma.workrq = NULL;
+ }
+ if (ch->dma.workrq_tag) {
+ bus_dma_tag_destroy(ch->dma.workrq_tag);
+ ch->dma.workrq_tag = NULL;
+ }
+}
+
+static void
+mvs_slotsalloc(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int i;
+
+ /* Alloc and setup command/dma slots */
+ bzero(ch->slot, sizeof(ch->slot));
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ struct mvs_slot *slot = &ch->slot[i];
+
+ slot->dev = dev;
+ slot->slot = i;
+ slot->state = MVS_SLOT_EMPTY;
+ slot->ccb = NULL;
+ callout_init_mtx(&slot->timeout, &ch->mtx, 0);
+
+ if (bus_dmamap_create(ch->dma.data_tag, 0, &slot->dma.data_map))
+ device_printf(ch->dev, "FAILURE - create data_map\n");
+ }
+}
+
+static void
+mvs_slotsfree(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int i;
+
+ /* Free all dma slots */
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ struct mvs_slot *slot = &ch->slot[i];
+
+ callout_drain(&slot->timeout);
+ if (slot->dma.data_map) {
+ bus_dmamap_destroy(ch->dma.data_tag, slot->dma.data_map);
+ slot->dma.data_map = NULL;
+ }
+ }
+}
+
+static void
+mvs_setup_edma_queues(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint64_t work;
+
+ /* Requests queue. */
+ work = ch->dma.workrq_bus;
+ ATA_OUTL(ch->r_mem, EDMA_REQQBAH, work >> 32);
+ ATA_OUTL(ch->r_mem, EDMA_REQQIP, work & 0xffffffff);
+ ATA_OUTL(ch->r_mem, EDMA_REQQOP, work & 0xffffffff);
+ bus_dmamap_sync(ch->dma.workrq_tag, ch->dma.workrq_map, BUS_DMASYNC_PREWRITE);
+ /* Reponses queue. */
+ bzero(ch->dma.workrp, 256);
+ work = ch->dma.workrp_bus;
+ ATA_OUTL(ch->r_mem, EDMA_RESQBAH, work >> 32);
+ ATA_OUTL(ch->r_mem, EDMA_RESQIP, work & 0xffffffff);
+ ATA_OUTL(ch->r_mem, EDMA_RESQOP, work & 0xffffffff);
+ bus_dmamap_sync(ch->dma.workrp_tag, ch->dma.workrp_map, BUS_DMASYNC_PREREAD);
+ ch->out_idx = 0;
+ ch->in_idx = 0;
+}
+
+static void
+mvs_set_edma_mode(device_t dev, enum mvs_edma_mode mode)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int timeout;
+ uint32_t ecfg, fcfg, hc, ltm, unkn;
+
+ if (mode == ch->curr_mode)
+ return;
+ /* If we are running, we should stop first. */
+ if (ch->curr_mode != MVS_EDMA_OFF) {
+ ATA_OUTL(ch->r_mem, EDMA_CMD, EDMA_CMD_EDSEDMA);
+ timeout = 0;
+ while (ATA_INL(ch->r_mem, EDMA_CMD) & EDMA_CMD_EENEDMA) {
+ DELAY(1000);
+ if (timeout++ > 1000) {
+ device_printf(dev, "stopping EDMA engine failed\n");
+ break;
+ }
+ };
+ }
+ ch->curr_mode = mode;
+ ch->fbs_enabled = 0;
+ ch->fake_busy = 0;
+ /* Report mode to controller. Needed for correct CCC operation. */
+ MVS_EDMA(device_get_parent(dev), dev, mode);
+ /* Configure new mode. */
+ ecfg = EDMA_CFG_RESERVED | EDMA_CFG_RESERVED2 | EDMA_CFG_EHOSTQUEUECACHEEN;
+ if (ch->pm_present) {
+ ecfg |= EDMA_CFG_EMASKRXPM;
+ if (ch->quirks & MVS_Q_GENIIE) {
+ ecfg |= EDMA_CFG_EEDMAFBS;
+ ch->fbs_enabled = 1;
+ }
+ }
+ if (ch->quirks & MVS_Q_GENI)
+ ecfg |= EDMA_CFG_ERDBSZ;
+ else if (ch->quirks & MVS_Q_GENII)
+ ecfg |= EDMA_CFG_ERDBSZEXT | EDMA_CFG_EWRBUFFERLEN;
+ if (ch->quirks & MVS_Q_CT)
+ ecfg |= EDMA_CFG_ECUTTHROUGHEN;
+ if (mode != MVS_EDMA_OFF)
+ ecfg |= EDMA_CFG_EEARLYCOMPLETIONEN;
+ if (mode == MVS_EDMA_QUEUED)
+ ecfg |= EDMA_CFG_EQUE;
+ else if (mode == MVS_EDMA_NCQ)
+ ecfg |= EDMA_CFG_ESATANATVCMDQUE;
+ ATA_OUTL(ch->r_mem, EDMA_CFG, ecfg);
+ mvs_setup_edma_queues(dev);
+ if (ch->quirks & MVS_Q_GENIIE) {
+ /* Configure FBS-related registers */
+ fcfg = ATA_INL(ch->r_mem, SATA_FISC);
+ ltm = ATA_INL(ch->r_mem, SATA_LTM);
+ hc = ATA_INL(ch->r_mem, EDMA_HC);
+ if (ch->fbs_enabled) {
+ fcfg |= SATA_FISC_FISDMAACTIVATESYNCRESP;
+ if (mode == MVS_EDMA_NCQ) {
+ fcfg &= ~SATA_FISC_FISWAIT4HOSTRDYEN_B0;
+ hc &= ~EDMA_IE_EDEVERR;
+ } else {
+ fcfg |= SATA_FISC_FISWAIT4HOSTRDYEN_B0;
+ hc |= EDMA_IE_EDEVERR;
+ }
+ ltm |= (1 << 8);
+ } else {
+ fcfg &= ~SATA_FISC_FISDMAACTIVATESYNCRESP;
+ fcfg &= ~SATA_FISC_FISWAIT4HOSTRDYEN_B0;
+ hc |= EDMA_IE_EDEVERR;
+ ltm &= ~(1 << 8);
+ }
+ ATA_OUTL(ch->r_mem, SATA_FISC, fcfg);
+ ATA_OUTL(ch->r_mem, SATA_LTM, ltm);
+ ATA_OUTL(ch->r_mem, EDMA_HC, hc);
+ /* This is some magic, required to handle several DRQs
+ * with basic DMA. */
+ unkn = ATA_INL(ch->r_mem, EDMA_UNKN_RESD);
+ if (mode == MVS_EDMA_OFF)
+ unkn |= 1;
+ else
+ unkn &= ~1;
+ ATA_OUTL(ch->r_mem, EDMA_UNKN_RESD, unkn);
+ }
+ /* Run EDMA. */
+ if (mode != MVS_EDMA_OFF)
+ ATA_OUTL(ch->r_mem, EDMA_CMD, EDMA_CMD_EENEDMA);
+}
+
+devclass_t mvs_devclass;
+devclass_t mvsch_devclass;
+static device_method_t mvsch_methods[] = {
+ DEVMETHOD(device_probe, mvs_ch_probe),
+ DEVMETHOD(device_attach, mvs_ch_attach),
+ DEVMETHOD(device_detach, mvs_ch_detach),
+ DEVMETHOD(device_suspend, mvs_ch_suspend),
+ DEVMETHOD(device_resume, mvs_ch_resume),
+ { 0, 0 }
+};
+static driver_t mvsch_driver = {
+ "mvsch",
+ mvsch_methods,
+ sizeof(struct mvs_channel)
+};
+DRIVER_MODULE(mvsch, mvs, mvsch_driver, mvsch_devclass, 0, 0);
+DRIVER_MODULE(mvsch, sata, mvsch_driver, mvsch_devclass, 0, 0);
+
+static void
+mvs_phy_check_events(device_t dev, u_int32_t serr)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ if (ch->pm_level == 0) {
+ u_int32_t status = ATA_INL(ch->r_mem, SATA_SS);
+ union ccb *ccb;
+
+ if (bootverbose) {
+ if (((status & SATA_SS_DET_MASK) == SATA_SS_DET_PHY_ONLINE) &&
+ ((status & SATA_SS_SPD_MASK) != SATA_SS_SPD_NO_SPEED) &&
+ ((status & SATA_SS_IPM_MASK) == SATA_SS_IPM_ACTIVE)) {
+ device_printf(dev, "CONNECT requested\n");
+ } else
+ device_printf(dev, "DISCONNECT requested\n");
+ }
+ mvs_reset(dev);
+ if ((ccb = xpt_alloc_ccb_nowait()) == NULL)
+ return;
+ if (xpt_create_path(&ccb->ccb_h.path, NULL,
+ cam_sim_path(ch->sim),
+ CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
+ xpt_free_ccb(ccb);
+ return;
+ }
+ xpt_rescan(ccb);
+ }
+}
+
+static void
+mvs_notify_events(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct cam_path *dpath;
+ uint32_t fis;
+ int d;
+
+ /* Try to read PMP field from SDB FIS. Present only for Gen-IIe. */
+ fis = ATA_INL(ch->r_mem, SATA_FISDW0);
+ if ((fis & 0x80ff) == 0x80a1)
+ d = (fis & 0x0f00) >> 8;
+ else
+ d = ch->pm_present ? 15 : 0;
+ if (bootverbose)
+ device_printf(dev, "SNTF %d\n", d);
+ if (xpt_create_path(&dpath, NULL,
+ xpt_path_path_id(ch->path), d, 0) == CAM_REQ_CMP) {
+ xpt_async(AC_SCSI_AEN, dpath, NULL);
+ xpt_free_path(dpath);
+ }
+}
+
+static void
+mvs_ch_intr_locked(void *data)
+{
+ struct mvs_intr_arg *arg = (struct mvs_intr_arg *)data;
+ device_t dev = (device_t)arg->arg;
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ mtx_lock(&ch->mtx);
+ mvs_ch_intr(data);
+ mtx_unlock(&ch->mtx);
+}
+
+static void
+mvs_ch_pm(void *arg)
+{
+ device_t dev = (device_t)arg;
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint32_t work;
+
+ if (ch->numrslots != 0)
+ return;
+ /* If we are idle - request power state transition. */
+ work = ATA_INL(ch->r_mem, SATA_SC);
+ work &= ~SATA_SC_SPM_MASK;
+ if (ch->pm_level == 4)
+ work |= SATA_SC_SPM_PARTIAL;
+ else
+ work |= SATA_SC_SPM_SLUMBER;
+ ATA_OUTL(ch->r_mem, SATA_SC, work);
+}
+
+static void
+mvs_ch_pm_wake(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint32_t work;
+ int timeout = 0;
+
+ work = ATA_INL(ch->r_mem, SATA_SS);
+ if (work & SATA_SS_IPM_ACTIVE)
+ return;
+ /* If we are not in active state - request power state transition. */
+ work = ATA_INL(ch->r_mem, SATA_SC);
+ work &= ~SATA_SC_SPM_MASK;
+ work |= SATA_SC_SPM_ACTIVE;
+ ATA_OUTL(ch->r_mem, SATA_SC, work);
+ /* Wait for transition to happen. */
+ while ((ATA_INL(ch->r_mem, SATA_SS) & SATA_SS_IPM_ACTIVE) == 0 &&
+ timeout++ < 100) {
+ DELAY(100);
+ }
+}
+
+static void
+mvs_ch_intr(void *data)
+{
+ struct mvs_intr_arg *arg = (struct mvs_intr_arg *)data;
+ device_t dev = (device_t)arg->arg;
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint32_t iec, serr = 0, fisic = 0;
+ enum mvs_err_type et;
+ int i, ccs, port = -1, selfdis = 0;
+ int edma = (ch->numtslots != 0 || ch->numdslots != 0);
+
+//device_printf(dev, "irq cause %02x EDMA %d IEC %08x\n",
+// arg->cause, edma, ATA_INL(ch->r_mem, EDMA_IEC));
+ /* New item in response queue. */
+ if ((arg->cause & 2) && edma)
+ mvs_crbq_intr(dev);
+ /* Some error or special event. */
+ if (arg->cause & 1) {
+ iec = ATA_INL(ch->r_mem, EDMA_IEC);
+//device_printf(dev, "irq cause %02x EDMA %d IEC %08x\n",
+// arg->cause, edma, iec);
+ if (iec & EDMA_IE_SERRINT) {
+ serr = ATA_INL(ch->r_mem, SATA_SE);
+ ATA_OUTL(ch->r_mem, SATA_SE, serr);
+//device_printf(dev, "SERR %08x\n", serr);
+ }
+ /* EDMA self-disabled due to error. */
+ if (iec & EDMA_IE_ESELFDIS)
+ selfdis = 1;
+ /* Transport interrupt. */
+ if (iec & EDMA_IE_ETRANSINT) {
+ /* For Gen-I this bit means self-disable. */
+ if (ch->quirks & MVS_Q_GENI)
+ selfdis = 1;
+ /* For Gen-II this bit means SDB-N. */
+ else if (ch->quirks & MVS_Q_GENII)
+ fisic = SATA_FISC_FISWAIT4HOSTRDYEN_B1;
+ else /* For Gen-IIe - read FIS interrupt cause. */
+ fisic = ATA_INL(ch->r_mem, SATA_FISIC);
+//device_printf(dev, "FISIC %08x\n", fisic);
+ }
+ if (selfdis)
+ ch->curr_mode = MVS_EDMA_UNKNOWN;
+ ATA_OUTL(ch->r_mem, EDMA_IEC, ~iec);
+ /* Interface errors or Device error. */
+ if (iec & (0xfc1e9000 | EDMA_IE_EDEVERR)) {
+ port = -1;
+ if (ch->numpslots != 0) {
+ ccs = 0;
+ } else {
+ if (ch->quirks & MVS_Q_GENIIE)
+ ccs = EDMA_S_EIOID(ATA_INL(ch->r_mem, EDMA_S));
+ else
+ ccs = EDMA_S_EDEVQUETAG(ATA_INL(ch->r_mem, EDMA_S));
+ /* Check if error is one-PMP-port-specific, */
+ if (ch->fbs_enabled) {
+ /* Which ports were active. */
+ for (i = 0; i < 16; i++) {
+ if (ch->numrslotspd[i] == 0)
+ continue;
+ if (port == -1)
+ port = i;
+ else if (port != i) {
+ port = -2;
+ break;
+ }
+ }
+ /* If several ports were active and EDMA still enabled -
+ * other ports are probably unaffected and may continue.
+ */
+ if (port == -2 && !selfdis) {
+ uint16_t p = ATA_INL(ch->r_mem, SATA_SATAITC) >> 16;
+ port = ffs(p) - 1;
+ if (port != (fls(p) - 1))
+ port = -2;
+ }
+ }
+ }
+//device_printf(dev, "err slot %d port %d\n", ccs, port);
+ mvs_requeue_frozen(dev);
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ /* XXX: reqests in loading state. */
+ if (((ch->rslots >> i) & 1) == 0)
+ continue;
+ if (port >= 0 &&
+ ch->slot[i].ccb->ccb_h.target_id != port)
+ continue;
+ if (iec & EDMA_IE_EDEVERR) { /* Device error. */
+ if (port != -2) {
+ if (ch->numtslots == 0) {
+ /* Untagged operation. */
+ if (i == ccs)
+ et = MVS_ERR_TFE;
+ else
+ et = MVS_ERR_INNOCENT;
+ } else {
+ /* Tagged operation. */
+ et = MVS_ERR_NCQ;
+ }
+ } else {
+ et = MVS_ERR_TFE;
+ ch->fatalerr = 1;
+ }
+ } else if (iec & 0xfc1e9000) {
+ if (ch->numtslots == 0 && i != ccs && port != -2)
+ et = MVS_ERR_INNOCENT;
+ else
+ et = MVS_ERR_SATA;
+ } else
+ et = MVS_ERR_INVALID;
+ mvs_end_transaction(&ch->slot[i], et);
+ }
+ }
+ /* Process SDB-N. */
+ if (fisic & SATA_FISC_FISWAIT4HOSTRDYEN_B1)
+ mvs_notify_events(dev);
+ if (fisic)
+ ATA_OUTL(ch->r_mem, SATA_FISIC, ~fisic);
+ /* Process hot-plug. */
+ if ((iec & (EDMA_IE_EDEVDIS | EDMA_IE_EDEVCON)) ||
+ (serr & SATA_SE_PHY_CHANGED))
+ mvs_phy_check_events(dev, serr);
+ }
+ /* Legacy mode device interrupt. */
+ if ((arg->cause & 2) && !edma)
+ mvs_legacy_intr(dev);
+}
+
+static uint8_t
+mvs_getstatus(device_t dev, int clear)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint8_t status = ATA_INB(ch->r_mem, clear ? ATA_STATUS : ATA_ALTSTAT);
+
+ if (ch->fake_busy) {
+ if (status & (ATA_S_BUSY | ATA_S_DRQ | ATA_S_ERROR))
+ ch->fake_busy = 0;
+ else
+ status |= ATA_S_BUSY;
+ }
+ return (status);
+}
+
+static void
+mvs_legacy_intr(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct mvs_slot *slot = &ch->slot[0]; /* PIO is always in slot 0. */
+ union ccb *ccb = slot->ccb;
+ enum mvs_err_type et = MVS_ERR_NONE;
+ int port;
+ u_int length;
+ uint8_t status, ireason;
+
+ /* Clear interrupt and get status. */
+ status = mvs_getstatus(dev, 1);
+// device_printf(dev, "Legacy intr status %02x\n",
+// status);
+ if (slot->state < MVS_SLOT_RUNNING)
+ return;
+ port = ccb->ccb_h.target_id & 0x0f;
+ /* Wait a bit for late !BUSY status update. */
+ if (status & ATA_S_BUSY) {
+ DELAY(100);
+ if ((status = mvs_getstatus(dev, 1)) & ATA_S_BUSY) {
+ DELAY(1000);
+ if ((status = mvs_getstatus(dev, 1)) & ATA_S_BUSY)
+ return;
+ }
+ }
+ /* If we got an error, we are done. */
+ if (status & ATA_S_ERROR) {
+ et = MVS_ERR_TFE;
+ goto end_finished;
+ }
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) { /* ATA PIO */
+ ccb->ataio.res.status = status;
+ /* Are we moving data? */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
+ /* If data read command - get them. */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
+ if (mvs_wait(dev, ATA_S_DRQ, ATA_S_BUSY, 1000) < 0) {
+ device_printf(dev, "timeout waiting for read DRQ\n");
+ et = MVS_ERR_TIMEOUT;
+ goto end_finished;
+ }
+ ATA_INSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->ataio.data_ptr + ch->donecount),
+ ch->transfersize / 2);
+ }
+ /* Update how far we've gotten. */
+ ch->donecount += ch->transfersize;
+ /* Do we need more? */
+ if (ccb->ataio.dxfer_len > ch->donecount) {
+ /* Set this transfer size according to HW capabilities */
+ ch->transfersize = min(ccb->ataio.dxfer_len - ch->donecount,
+ ch->curr[ccb->ccb_h.target_id].bytecount);
+ /* If data write command - put them */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
+ if (mvs_wait(dev, ATA_S_DRQ, ATA_S_BUSY, 1000) < 0) {
+ device_printf(dev, "timeout waiting for write DRQ\n");
+ et = MVS_ERR_TIMEOUT;
+ goto end_finished;
+ }
+ ATA_OUTSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->ataio.data_ptr + ch->donecount),
+ ch->transfersize / 2);
+ return;
+ }
+ /* If data read command, return & wait for interrupt */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
+ return;
+ }
+ }
+ } else if (ch->basic_dma) { /* ATAPI DMA */
+ if (status & ATA_S_DWF)
+ et = MVS_ERR_TFE;
+ else if (ATA_INL(ch->r_mem, DMA_S) & DMA_S_ERR)
+ et = MVS_ERR_TFE;
+ /* Stop basic DMA. */
+ ATA_OUTL(ch->r_mem, DMA_C, 0);
+ goto end_finished;
+ } else { /* ATAPI PIO */
+ length = ATA_INB(ch->r_mem,ATA_CYL_LSB) | (ATA_INB(ch->r_mem,ATA_CYL_MSB) << 8);
+ ireason = ATA_INB(ch->r_mem,ATA_IREASON);
+//device_printf(dev, "status %02x, ireason %02x, length %d\n", status, ireason, length);
+ switch ((ireason & (ATA_I_CMD | ATA_I_IN)) |
+ (status & ATA_S_DRQ)) {
+
+ case ATAPI_P_CMDOUT:
+device_printf(dev, "ATAPI CMDOUT\n");
+ /* Return wait for interrupt */
+ return;
+
+ case ATAPI_P_WRITE:
+//device_printf(dev, "ATAPI WRITE\n");
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
+ device_printf(dev, "trying to write on read buffer\n");
+ et = MVS_ERR_TFE;
+ goto end_finished;
+ break;
+ }
+ ATA_OUTSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->csio.data_ptr + ch->donecount),
+ length / 2);
+ ch->donecount += length;
+ /* Set next transfer size according to HW capabilities */
+ ch->transfersize = min(ccb->csio.dxfer_len - ch->donecount,
+ ch->curr[ccb->ccb_h.target_id].bytecount);
+ /* Return wait for interrupt */
+ return;
+
+ case ATAPI_P_READ:
+//device_printf(dev, "ATAPI READ\n");
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
+ device_printf(dev, "trying to read on write buffer\n");
+ et = MVS_ERR_TFE;
+ goto end_finished;
+ }
+ ATA_INSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->csio.data_ptr + ch->donecount),
+ length / 2);
+ ch->donecount += length;
+ /* Set next transfer size according to HW capabilities */
+ ch->transfersize = min(ccb->csio.dxfer_len - ch->donecount,
+ ch->curr[ccb->ccb_h.target_id].bytecount);
+ /* Return wait for interrupt */
+ return;
+
+ case ATAPI_P_DONEDRQ:
+device_printf(dev, "ATAPI DONEDRQ\n");
+ device_printf(dev,
+ "WARNING - DONEDRQ non conformant device\n");
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
+ ATA_INSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->csio.data_ptr + ch->donecount),
+ length / 2);
+ ch->donecount += length;
+ }
+ else if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
+ ATA_OUTSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->csio.data_ptr + ch->donecount),
+ length / 2);
+ ch->donecount += length;
+ }
+ else
+ et = MVS_ERR_TFE;
+ /* FALLTHROUGH */
+
+ case ATAPI_P_ABORT:
+ case ATAPI_P_DONE:
+//device_printf(dev, "ATAPI ABORT/DONE\n");
+ if (status & (ATA_S_ERROR | ATA_S_DWF))
+ et = MVS_ERR_TFE;
+ goto end_finished;
+
+ default:
+ device_printf(dev, "unknown transfer phase (status %02x, ireason %02x)\n",
+ status, ireason);
+ et = MVS_ERR_TFE;
+ }
+ }
+
+end_finished:
+ mvs_end_transaction(slot, et);
+}
+
+static void
+mvs_crbq_intr(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct mvs_crpb *crpb;
+ union ccb *ccb;
+ int in_idx, cin_idx, slot;
+ uint16_t flags;
+
+ in_idx = (ATA_INL(ch->r_mem, EDMA_RESQIP) & EDMA_RESQP_ERPQP_MASK) >>
+ EDMA_RESQP_ERPQP_SHIFT;
+ bus_dmamap_sync(ch->dma.workrp_tag, ch->dma.workrp_map,
+ BUS_DMASYNC_POSTREAD);
+ cin_idx = ch->in_idx;
+ ch->in_idx = in_idx;
+ while (in_idx != cin_idx) {
+ crpb = (struct mvs_crpb *)
+ (ch->dma.workrp + MVS_CRPB_OFFSET + (MVS_CRPB_SIZE * cin_idx));
+ slot = le16toh(crpb->id) & MVS_CRPB_TAG_MASK;
+ flags = le16toh(crpb->rspflg);
+//device_printf(dev, "CRPB %d %d %04x\n", cin_idx, slot, flags);
+ /*
+ * Handle only successfull completions here.
+ * Errors will be handled by main intr handler.
+ */
+ if (ch->numtslots != 0 || (flags & EDMA_IE_EDEVERR) == 0) {
+if ((flags >> 8) & ATA_S_ERROR)
+device_printf(dev, "ERROR STATUS CRPB %d %d %04x\n", cin_idx, slot, flags);
+ if (ch->slot[slot].state >= MVS_SLOT_RUNNING) {
+ ccb = ch->slot[slot].ccb;
+ ccb->ataio.res.status = (flags & MVS_CRPB_ATASTS_MASK) >>
+ MVS_CRPB_ATASTS_SHIFT;
+ mvs_end_transaction(&ch->slot[slot], MVS_ERR_NONE);
+ } else
+device_printf(dev, "EMPTY CRPB %d (->%d) %d %04x\n", cin_idx, in_idx, slot, flags);
+ } else
+device_printf(dev, "ERROR FLAGS CRPB %d %d %04x\n", cin_idx, slot, flags);
+
+ cin_idx = (cin_idx + 1) & (MVS_MAX_SLOTS - 1);
+ }
+ bus_dmamap_sync(ch->dma.workrp_tag, ch->dma.workrp_map,
+ BUS_DMASYNC_PREREAD);
+ if (cin_idx == ch->in_idx) {
+ ATA_OUTL(ch->r_mem, EDMA_RESQOP,
+ ch->dma.workrp_bus | (cin_idx << EDMA_RESQP_ERPQP_SHIFT));
+ }
+}
+
+/* Must be called with channel locked. */
+static int
+mvs_check_collision(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) {
+ /* NCQ DMA */
+ if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
+ /* Can't mix NCQ and non-NCQ DMA commands. */
+ if (ch->numdslots != 0)
+ return (1);
+ /* Can't mix NCQ and PIO commands. */
+ if (ch->numpslots != 0)
+ return (1);
+ /* If we have no FBS */
+ if (!ch->fbs_enabled) {
+ /* Tagged command while tagged to other target is active. */
+ if (ch->numtslots != 0 &&
+ ch->taggedtarget != ccb->ccb_h.target_id)
+ return (1);
+ }
+ /* Non-NCQ DMA */
+ } else if (ccb->ataio.cmd.flags & CAM_ATAIO_DMA) {
+ /* Can't mix non-NCQ DMA and NCQ commands. */
+ if (ch->numtslots != 0)
+ return (1);
+ /* Can't mix non-NCQ DMA and PIO commands. */
+ if (ch->numpslots != 0)
+ return (1);
+ /* PIO */
+ } else {
+ /* Can't mix PIO with anything. */
+ if (ch->numrslots != 0)
+ return (1);
+ }
+ if (ccb->ataio.cmd.flags & (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT)) {
+ /* Atomic command while anything active. */
+ if (ch->numrslots != 0)
+ return (1);
+ }
+ } else { /* ATAPI */
+ /* ATAPI goes without EDMA, so can't mix it with anything. */
+ if (ch->numrslots != 0)
+ return (1);
+ }
+ /* We have some atomic command running. */
+ if (ch->aslots != 0)
+ return (1);
+ return (0);
+}
+
+static void
+mvs_tfd_read(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct ata_res *res = &ccb->ataio.res;
+
+ res->status = ATA_INB(ch->r_mem, ATA_ALTSTAT);
+ res->error = ATA_INB(ch->r_mem, ATA_ERROR);
+ res->device = ATA_INB(ch->r_mem, ATA_DRIVE);
+ ATA_OUTB(ch->r_mem, ATA_CONTROL, ATA_A_HOB);
+ res->sector_count_exp = ATA_INB(ch->r_mem, ATA_COUNT);
+ res->lba_low_exp = ATA_INB(ch->r_mem, ATA_SECTOR);
+ res->lba_mid_exp = ATA_INB(ch->r_mem, ATA_CYL_LSB);
+ res->lba_high_exp = ATA_INB(ch->r_mem, ATA_CYL_MSB);
+ ATA_OUTB(ch->r_mem, ATA_CONTROL, 0);
+ res->sector_count = ATA_INB(ch->r_mem, ATA_COUNT);
+ res->lba_low = ATA_INB(ch->r_mem, ATA_SECTOR);
+ res->lba_mid = ATA_INB(ch->r_mem, ATA_CYL_LSB);
+ res->lba_high = ATA_INB(ch->r_mem, ATA_CYL_MSB);
+}
+
+static void
+mvs_tfd_write(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct ata_cmd *cmd = &ccb->ataio.cmd;
+
+ ATA_OUTB(ch->r_mem, ATA_DRIVE, cmd->device);
+ ATA_OUTB(ch->r_mem, ATA_CONTROL, cmd->control);
+ ATA_OUTB(ch->r_mem, ATA_FEATURE, cmd->features_exp);
+ ATA_OUTB(ch->r_mem, ATA_FEATURE, cmd->features);
+ ATA_OUTB(ch->r_mem, ATA_COUNT, cmd->sector_count_exp);
+ ATA_OUTB(ch->r_mem, ATA_COUNT, cmd->sector_count);
+ ATA_OUTB(ch->r_mem, ATA_SECTOR, cmd->lba_low_exp);
+ ATA_OUTB(ch->r_mem, ATA_SECTOR, cmd->lba_low);
+ ATA_OUTB(ch->r_mem, ATA_CYL_LSB, cmd->lba_mid_exp);
+ ATA_OUTB(ch->r_mem, ATA_CYL_LSB, cmd->lba_mid);
+ ATA_OUTB(ch->r_mem, ATA_CYL_MSB, cmd->lba_high_exp);
+ ATA_OUTB(ch->r_mem, ATA_CYL_MSB, cmd->lba_high);
+ ATA_OUTB(ch->r_mem, ATA_COMMAND, cmd->command);
+}
+
+
+/* Must be called with channel locked. */
+static void
+mvs_begin_transaction(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ struct mvs_slot *slot;
+ int slotn, tag;
+
+ if (ch->pm_level > 0)
+ mvs_ch_pm_wake(dev);
+ /* Softreset is a special case. */
+ if (ccb->ccb_h.func_code == XPT_ATA_IO &&
+ (ccb->ataio.cmd.flags & CAM_ATAIO_CONTROL)) {
+ mvs_softreset(dev, ccb);
+ return;
+ }
+ /* Choose empty slot. */
+ slotn = ffs(~ch->oslots) - 1;
+ if ((ccb->ccb_h.func_code == XPT_ATA_IO) &&
+ (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA)) {
+ if (ch->quirks & MVS_Q_GENIIE)
+ tag = ffs(~ch->otagspd[ccb->ccb_h.target_id]) - 1;
+ else
+ tag = slotn;
+ } else
+ tag = 0;
+ /* Occupy chosen slot. */
+ slot = &ch->slot[slotn];
+ slot->ccb = ccb;
+ slot->tag = tag;
+ /* Stop PM timer. */
+ if (ch->numrslots == 0 && ch->pm_level > 3)
+ callout_stop(&ch->pm_timer);
+ /* Update channel stats. */
+ ch->oslots |= (1 << slot->slot);
+ ch->numrslots++;
+ ch->numrslotspd[ccb->ccb_h.target_id]++;
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) {
+ if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
+ ch->otagspd[ccb->ccb_h.target_id] |= (1 << slot->tag);
+ ch->numtslots++;
+ ch->numtslotspd[ccb->ccb_h.target_id]++;
+ ch->taggedtarget = ccb->ccb_h.target_id;
+ mvs_set_edma_mode(dev, MVS_EDMA_NCQ);
+ } else if (ccb->ataio.cmd.flags & CAM_ATAIO_DMA) {
+ ch->numdslots++;
+ mvs_set_edma_mode(dev, MVS_EDMA_ON);
+ } else {
+ ch->numpslots++;
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ }
+ if (ccb->ataio.cmd.flags &
+ (CAM_ATAIO_CONTROL | CAM_ATAIO_NEEDRESULT)) {
+ ch->aslots |= (1 << slot->slot);
+ }
+ } else {
+ uint8_t *cdb = (ccb->ccb_h.flags & CAM_CDB_POINTER) ?
+ ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes;
+ ch->numpslots++;
+ /* Use ATAPI DMA only for commands without under-/overruns. */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE &&
+ ch->curr[ccb->ccb_h.target_id].mode >= ATA_DMA &&
+ (ch->quirks & MVS_Q_SOC) == 0 &&
+ (cdb[0] == 0x08 ||
+ cdb[0] == 0x0a ||
+ cdb[0] == 0x28 ||
+ cdb[0] == 0x2a ||
+ cdb[0] == 0x88 ||
+ cdb[0] == 0x8a ||
+ cdb[0] == 0xa8 ||
+ cdb[0] == 0xaa ||
+ cdb[0] == 0xbe)) {
+ ch->basic_dma = 1;
+ }
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ }
+ if (ch->numpslots == 0 || ch->basic_dma) {
+ void *buf;
+ bus_size_t size;
+
+ slot->state = MVS_SLOT_LOADING;
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) {
+ buf = ccb->ataio.data_ptr;
+ size = ccb->ataio.dxfer_len;
+ } else {
+ buf = ccb->csio.data_ptr;
+ size = ccb->csio.dxfer_len;
+ }
+ bus_dmamap_load(ch->dma.data_tag, slot->dma.data_map,
+ buf, size, mvs_dmasetprd, slot, 0);
+ } else
+ mvs_legacy_execute_transaction(slot);
+}
+
+/* Locked by busdma engine. */
+static void
+mvs_dmasetprd(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
+{
+ struct mvs_slot *slot = arg;
+ struct mvs_channel *ch = device_get_softc(slot->dev);
+ struct mvs_eprd *eprd;
+ int i;
+
+ if (error) {
+ device_printf(slot->dev, "DMA load error\n");
+ mvs_end_transaction(slot, MVS_ERR_INVALID);
+ return;
+ }
+ KASSERT(nsegs <= MVS_SG_ENTRIES, ("too many DMA segment entries\n"));
+ /* If there is only one segment - no need to use S/G table on Gen-IIe. */
+ if (nsegs == 1 && ch->basic_dma == 0 && (ch->quirks & MVS_Q_GENIIE)) {
+ slot->dma.addr = segs[0].ds_addr;
+ slot->dma.len = segs[0].ds_len;
+ } else {
+ slot->dma.addr = 0;
+ /* Get a piece of the workspace for this EPRD */
+ eprd = (struct mvs_eprd *)
+ (ch->dma.workrq + MVS_EPRD_OFFSET + (MVS_EPRD_SIZE * slot->slot));
+ /* Fill S/G table */
+ for (i = 0; i < nsegs; i++) {
+ eprd[i].prdbal = htole32(segs[i].ds_addr);
+ eprd[i].bytecount = htole32(segs[i].ds_len & MVS_EPRD_MASK);
+ eprd[i].prdbah = htole32((segs[i].ds_addr >> 16) >> 16);
+ }
+ eprd[i - 1].bytecount |= htole32(MVS_EPRD_EOF);
+ }
+ bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map,
+ ((slot->ccb->ccb_h.flags & CAM_DIR_IN) ?
+ BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE));
+ if (ch->basic_dma)
+ mvs_legacy_execute_transaction(slot);
+ else
+ mvs_execute_transaction(slot);
+}
+
+static void
+mvs_legacy_execute_transaction(struct mvs_slot *slot)
+{
+ device_t dev = slot->dev;
+ struct mvs_channel *ch = device_get_softc(dev);
+ bus_addr_t eprd;
+ union ccb *ccb = slot->ccb;
+ int port = ccb->ccb_h.target_id & 0x0f;
+ int timeout;
+
+ slot->state = MVS_SLOT_RUNNING;
+ ch->rslots |= (1 << slot->slot);
+ ATA_OUTB(ch->r_mem, SATA_SATAICTL, port << SATA_SATAICTL_PMPTX_SHIFT);
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) {
+// device_printf(dev, "%d Legacy command %02x size %d\n",
+// port, ccb->ataio.cmd.command, ccb->ataio.dxfer_len);
+ mvs_tfd_write(dev, ccb);
+ /* Device reset doesn't interrupt. */
+ if (ccb->ataio.cmd.command == ATA_DEVICE_RESET) {
+ int timeout = 1000000;
+ do {
+ DELAY(10);
+ ccb->ataio.res.status = ATA_INB(ch->r_mem, ATA_STATUS);
+ } while (ccb->ataio.res.status & ATA_S_BUSY && timeout--);
+ mvs_legacy_intr(dev);
+ return;
+ }
+ ch->donecount = 0;
+ ch->transfersize = min(ccb->ataio.dxfer_len,
+ ch->curr[port].bytecount);
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE)
+ ch->fake_busy = 1;
+ /* If data write command - output the data */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) {
+ if (mvs_wait(dev, ATA_S_DRQ, ATA_S_BUSY, 1000) < 0) {
+ device_printf(dev, "timeout waiting for write DRQ\n");
+ mvs_end_transaction(slot, MVS_ERR_TIMEOUT);
+ return;
+ }
+ ATA_OUTSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)(ccb->ataio.data_ptr + ch->donecount),
+ ch->transfersize / 2);
+ }
+ } else {
+// device_printf(dev, "%d ATAPI command %02x size %d dma %d\n",
+// port, ccb->csio.cdb_io.cdb_bytes[0], ccb->csio.dxfer_len,
+// ch->basic_dma);
+ ch->donecount = 0;
+ ch->transfersize = min(ccb->csio.dxfer_len,
+ ch->curr[port].bytecount);
+ /* Write ATA PACKET command. */
+ if (ch->basic_dma) {
+ ATA_OUTB(ch->r_mem, ATA_FEATURE, ATA_F_DMA);
+ ATA_OUTB(ch->r_mem, ATA_CYL_LSB, 0);
+ ATA_OUTB(ch->r_mem, ATA_CYL_MSB, 0);
+ } else {
+ ATA_OUTB(ch->r_mem, ATA_FEATURE, 0);
+ ATA_OUTB(ch->r_mem, ATA_CYL_LSB, ch->transfersize);
+ ATA_OUTB(ch->r_mem, ATA_CYL_MSB, ch->transfersize >> 8);
+ }
+ ATA_OUTB(ch->r_mem, ATA_COMMAND, ATA_PACKET_CMD);
+ ch->fake_busy = 1;
+ /* Wait for ready to write ATAPI command block */
+ if (mvs_wait(dev, 0, ATA_S_BUSY, 1000) < 0) {
+ device_printf(dev, "timeout waiting for ATAPI !BUSY\n");
+ mvs_end_transaction(slot, MVS_ERR_TIMEOUT);
+ return;
+ }
+ timeout = 5000;
+ while (timeout--) {
+ int reason = ATA_INB(ch->r_mem, ATA_IREASON);
+ int status = ATA_INB(ch->r_mem, ATA_STATUS);
+
+ if (((reason & (ATA_I_CMD | ATA_I_IN)) |
+ (status & (ATA_S_DRQ | ATA_S_BUSY))) == ATAPI_P_CMDOUT)
+ break;
+ DELAY(20);
+ }
+ if (timeout <= 0) {
+ device_printf(dev, "timeout waiting for ATAPI command ready\n");
+ mvs_end_transaction(slot, MVS_ERR_TIMEOUT);
+ return;
+ }
+ /* Write ATAPI command. */
+ ATA_OUTSW_STRM(ch->r_mem, ATA_DATA,
+ (uint16_t *)((ccb->ccb_h.flags & CAM_CDB_POINTER) ?
+ ccb->csio.cdb_io.cdb_ptr : ccb->csio.cdb_io.cdb_bytes),
+ ch->curr[port].atapi / 2);
+ DELAY(10);
+ if (ch->basic_dma) {
+ /* Start basic DMA. */
+ eprd = ch->dma.workrq_bus + MVS_EPRD_OFFSET +
+ (MVS_EPRD_SIZE * slot->slot);
+ ATA_OUTL(ch->r_mem, DMA_DTLBA, eprd);
+ ATA_OUTL(ch->r_mem, DMA_DTHBA, (eprd >> 16) >> 16);
+ ATA_OUTL(ch->r_mem, DMA_C, DMA_C_START |
+ (((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) ?
+ DMA_C_READ : 0));
+ } else if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE)
+ ch->fake_busy = 1;
+ }
+ /* Start command execution timeout */
+ callout_reset(&slot->timeout, (int)ccb->ccb_h.timeout * hz / 1000,
+ (timeout_t*)mvs_timeout, slot);
+}
+
+/* Must be called with channel locked. */
+static void
+mvs_execute_transaction(struct mvs_slot *slot)
+{
+ device_t dev = slot->dev;
+ struct mvs_channel *ch = device_get_softc(dev);
+ bus_addr_t eprd;
+ struct mvs_crqb *crqb;
+ struct mvs_crqb_gen2e *crqb2e;
+ union ccb *ccb = slot->ccb;
+ int port = ccb->ccb_h.target_id & 0x0f;
+ int i;
+
+// device_printf(dev, "%d EDMA command %02x size %d slot %d tag %d\n",
+// port, ccb->ataio.cmd.command, ccb->ataio.dxfer_len, slot->slot, slot->tag);
+ /* Get address of the prepared EPRD */
+ eprd = ch->dma.workrq_bus + MVS_EPRD_OFFSET + (MVS_EPRD_SIZE * slot->slot);
+ /* Prepare CRQB. Gen IIe uses different CRQB format. */
+ if (ch->quirks & MVS_Q_GENIIE) {
+ crqb2e = (struct mvs_crqb_gen2e *)
+ (ch->dma.workrq + MVS_CRQB_OFFSET + (MVS_CRQB_SIZE * ch->out_idx));
+ crqb2e->ctrlflg = htole32(
+ ((ccb->ccb_h.flags & CAM_DIR_IN) ? MVS_CRQB2E_READ : 0) |
+ (slot->tag << MVS_CRQB2E_DTAG_SHIFT) |
+ (port << MVS_CRQB2E_PMP_SHIFT) |
+ (slot->slot << MVS_CRQB2E_HTAG_SHIFT));
+ /* If there is only one segment - no need to use S/G table. */
+ if (slot->dma.addr != 0) {
+ eprd = slot->dma.addr;
+ crqb2e->ctrlflg |= htole32(MVS_CRQB2E_CPRD);
+ crqb2e->drbc = slot->dma.len;
+ }
+ crqb2e->cprdbl = htole32(eprd);
+ crqb2e->cprdbh = htole32((eprd >> 16) >> 16);
+ crqb2e->cmd[0] = 0;
+ crqb2e->cmd[1] = 0;
+ crqb2e->cmd[2] = ccb->ataio.cmd.command;
+ crqb2e->cmd[3] = ccb->ataio.cmd.features;
+ crqb2e->cmd[4] = ccb->ataio.cmd.lba_low;
+ crqb2e->cmd[5] = ccb->ataio.cmd.lba_mid;
+ crqb2e->cmd[6] = ccb->ataio.cmd.lba_high;
+ crqb2e->cmd[7] = ccb->ataio.cmd.device;
+ crqb2e->cmd[8] = ccb->ataio.cmd.lba_low_exp;
+ crqb2e->cmd[9] = ccb->ataio.cmd.lba_mid_exp;
+ crqb2e->cmd[10] = ccb->ataio.cmd.lba_high_exp;
+ crqb2e->cmd[11] = ccb->ataio.cmd.features_exp;
+ if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
+ crqb2e->cmd[12] = slot->tag << 3;
+ crqb2e->cmd[13] = 0;
+ } else {
+ crqb2e->cmd[12] = ccb->ataio.cmd.sector_count;
+ crqb2e->cmd[13] = ccb->ataio.cmd.sector_count_exp;
+ }
+ crqb2e->cmd[14] = 0;
+ crqb2e->cmd[15] = 0;
+ } else {
+ crqb = (struct mvs_crqb *)
+ (ch->dma.workrq + MVS_CRQB_OFFSET + (MVS_CRQB_SIZE * ch->out_idx));
+ crqb->cprdbl = htole32(eprd);
+ crqb->cprdbh = htole32((eprd >> 16) >> 16);
+ crqb->ctrlflg = htole16(
+ ((ccb->ccb_h.flags & CAM_DIR_IN) ? MVS_CRQB_READ : 0) |
+ (slot->slot << MVS_CRQB_TAG_SHIFT) |
+ (port << MVS_CRQB_PMP_SHIFT));
+ i = 0;
+ /*
+ * Controller can handle only 11 of 12 ATA registers,
+ * so we have to choose which one to skip.
+ */
+ if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
+ crqb->cmd[i++] = ccb->ataio.cmd.features_exp;
+ crqb->cmd[i++] = 0x11;
+ }
+ crqb->cmd[i++] = ccb->ataio.cmd.features;
+ crqb->cmd[i++] = 0x11;
+ if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
+ crqb->cmd[i++] = slot->tag << 3;
+ crqb->cmd[i++] = 0x12;
+ } else {
+ crqb->cmd[i++] = ccb->ataio.cmd.sector_count_exp;
+ crqb->cmd[i++] = 0x12;
+ crqb->cmd[i++] = ccb->ataio.cmd.sector_count;
+ crqb->cmd[i++] = 0x12;
+ }
+ crqb->cmd[i++] = ccb->ataio.cmd.lba_low_exp;
+ crqb->cmd[i++] = 0x13;
+ crqb->cmd[i++] = ccb->ataio.cmd.lba_low;
+ crqb->cmd[i++] = 0x13;
+ crqb->cmd[i++] = ccb->ataio.cmd.lba_mid_exp;
+ crqb->cmd[i++] = 0x14;
+ crqb->cmd[i++] = ccb->ataio.cmd.lba_mid;
+ crqb->cmd[i++] = 0x14;
+ crqb->cmd[i++] = ccb->ataio.cmd.lba_high_exp;
+ crqb->cmd[i++] = 0x15;
+ crqb->cmd[i++] = ccb->ataio.cmd.lba_high;
+ crqb->cmd[i++] = 0x15;
+ crqb->cmd[i++] = ccb->ataio.cmd.device;
+ crqb->cmd[i++] = 0x16;
+ crqb->cmd[i++] = ccb->ataio.cmd.command;
+ crqb->cmd[i++] = 0x97;
+ }
+ bus_dmamap_sync(ch->dma.workrq_tag, ch->dma.workrq_map,
+ BUS_DMASYNC_PREWRITE);
+ bus_dmamap_sync(ch->dma.workrp_tag, ch->dma.workrp_map,
+ BUS_DMASYNC_PREREAD);
+ slot->state = MVS_SLOT_RUNNING;
+ ch->rslots |= (1 << slot->slot);
+ /* Issue command to the controller. */
+ ch->out_idx = (ch->out_idx + 1) & (MVS_MAX_SLOTS - 1);
+ ATA_OUTL(ch->r_mem, EDMA_REQQIP,
+ ch->dma.workrq_bus + MVS_CRQB_OFFSET + (MVS_CRQB_SIZE * ch->out_idx));
+ /* Start command execution timeout */
+ callout_reset(&slot->timeout, (int)ccb->ccb_h.timeout * hz / 1000,
+ (timeout_t*)mvs_timeout, slot);
+ return;
+}
+
+/* Must be called with channel locked. */
+static void
+mvs_process_timeout(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int i;
+
+ mtx_assert(&ch->mtx, MA_OWNED);
+ /* Handle the rest of commands. */
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ /* Do we have a running request on slot? */
+ if (ch->slot[i].state < MVS_SLOT_RUNNING)
+ continue;
+ mvs_end_transaction(&ch->slot[i], MVS_ERR_TIMEOUT);
+ }
+}
+
+/* Must be called with channel locked. */
+static void
+mvs_rearm_timeout(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int i;
+
+ mtx_assert(&ch->mtx, MA_OWNED);
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ struct mvs_slot *slot = &ch->slot[i];
+
+ /* Do we have a running request on slot? */
+ if (slot->state < MVS_SLOT_RUNNING)
+ continue;
+ if ((ch->toslots & (1 << i)) == 0)
+ continue;
+ callout_reset(&slot->timeout,
+ (int)slot->ccb->ccb_h.timeout * hz / 2000,
+ (timeout_t*)mvs_timeout, slot);
+ }
+}
+
+/* Locked by callout mechanism. */
+static void
+mvs_timeout(struct mvs_slot *slot)
+{
+ device_t dev = slot->dev;
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ /* Check for stale timeout. */
+ if (slot->state < MVS_SLOT_RUNNING)
+ return;
+ device_printf(dev, "Timeout on slot %d\n", slot->slot);
+ device_printf(dev, "iec %08x sstat %08x serr %08x edma_s %08x "
+ "dma_c %08x dma_s %08x rs %08x status %02x\n",
+ ATA_INL(ch->r_mem, EDMA_IEC),
+ ATA_INL(ch->r_mem, SATA_SS), ATA_INL(ch->r_mem, SATA_SE),
+ ATA_INL(ch->r_mem, EDMA_S), ATA_INL(ch->r_mem, DMA_C),
+ ATA_INL(ch->r_mem, DMA_S), ch->rslots,
+ ATA_INB(ch->r_mem, ATA_ALTSTAT));
+ /* Handle frozen command. */
+ mvs_requeue_frozen(dev);
+ /* We wait for other commands timeout and pray. */
+ if (ch->toslots == 0)
+ xpt_freeze_simq(ch->sim, 1);
+ ch->toslots |= (1 << slot->slot);
+ if ((ch->rslots & ~ch->toslots) == 0)
+ mvs_process_timeout(dev);
+ else
+ device_printf(dev, " ... waiting for slots %08x\n",
+ ch->rslots & ~ch->toslots);
+}
+
+/* Must be called with channel locked. */
+static void
+mvs_end_transaction(struct mvs_slot *slot, enum mvs_err_type et)
+{
+ device_t dev = slot->dev;
+ struct mvs_channel *ch = device_get_softc(dev);
+ union ccb *ccb = slot->ccb;
+
+//device_printf(dev, "cmd done status %d\n", et);
+ bus_dmamap_sync(ch->dma.workrq_tag, ch->dma.workrq_map,
+ BUS_DMASYNC_POSTWRITE);
+ /* Read result registers to the result struct
+ * May be incorrect if several commands finished same time,
+ * so read only when sure or have to.
+ */
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) {
+ struct ata_res *res = &ccb->ataio.res;
+
+ if ((et == MVS_ERR_TFE) ||
+ (ccb->ataio.cmd.flags & CAM_ATAIO_NEEDRESULT)) {
+ mvs_tfd_read(dev, ccb);
+ } else
+ bzero(res, sizeof(*res));
+ }
+ if (ch->numpslots == 0 || ch->basic_dma) {
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
+ bus_dmamap_sync(ch->dma.data_tag, slot->dma.data_map,
+ (ccb->ccb_h.flags & CAM_DIR_IN) ?
+ BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(ch->dma.data_tag, slot->dma.data_map);
+ }
+ }
+ if (et != MVS_ERR_NONE)
+ ch->eslots |= (1 << slot->slot);
+ /* In case of error, freeze device for proper recovery. */
+ if ((et != MVS_ERR_NONE) && (!ch->readlog) &&
+ !(ccb->ccb_h.status & CAM_DEV_QFRZN)) {
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ ccb->ccb_h.status |= CAM_DEV_QFRZN;
+ }
+ /* Set proper result status. */
+ ccb->ccb_h.status &= ~CAM_STATUS_MASK;
+ switch (et) {
+ case MVS_ERR_NONE:
+ ccb->ccb_h.status |= CAM_REQ_CMP;
+ if (ccb->ccb_h.func_code == XPT_SCSI_IO)
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ break;
+ case MVS_ERR_INVALID:
+ ch->fatalerr = 1;
+ ccb->ccb_h.status |= CAM_REQ_INVALID;
+ break;
+ case MVS_ERR_INNOCENT:
+ ccb->ccb_h.status |= CAM_REQUEUE_REQ;
+ break;
+ case MVS_ERR_TFE:
+ case MVS_ERR_NCQ:
+ if (ccb->ccb_h.func_code == XPT_SCSI_IO) {
+ ccb->ccb_h.status |= CAM_SCSI_STATUS_ERROR;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ } else {
+ ccb->ccb_h.status |= CAM_ATA_STATUS_ERROR;
+ }
+ break;
+ case MVS_ERR_SATA:
+ ch->fatalerr = 1;
+ if (!ch->readlog) {
+ xpt_freeze_simq(ch->sim, 1);
+ ccb->ccb_h.status &= ~CAM_STATUS_MASK;
+ ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
+ }
+ ccb->ccb_h.status |= CAM_UNCOR_PARITY;
+ break;
+ case MVS_ERR_TIMEOUT:
+ if (!ch->readlog) {
+ xpt_freeze_simq(ch->sim, 1);
+ ccb->ccb_h.status &= ~CAM_STATUS_MASK;
+ ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
+ }
+ ccb->ccb_h.status |= CAM_CMD_TIMEOUT;
+ break;
+ default:
+ ch->fatalerr = 1;
+ ccb->ccb_h.status |= CAM_REQ_CMP_ERR;
+ }
+ /* Free slot. */
+ ch->oslots &= ~(1 << slot->slot);
+ ch->rslots &= ~(1 << slot->slot);
+ ch->aslots &= ~(1 << slot->slot);
+ if (et != MVS_ERR_TIMEOUT) {
+ if (ch->toslots == (1 << slot->slot))
+ xpt_release_simq(ch->sim, TRUE);
+ ch->toslots &= ~(1 << slot->slot);
+ }
+ slot->state = MVS_SLOT_EMPTY;
+ slot->ccb = NULL;
+ /* Update channel stats. */
+ ch->numrslots--;
+ ch->numrslotspd[ccb->ccb_h.target_id]--;
+ if (ccb->ccb_h.func_code == XPT_ATA_IO) {
+ if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
+ ch->otagspd[ccb->ccb_h.target_id] &= ~(1 << slot->tag);
+ ch->numtslots--;
+ ch->numtslotspd[ccb->ccb_h.target_id]--;
+ } else if (ccb->ataio.cmd.flags & CAM_ATAIO_DMA) {
+ ch->numdslots--;
+ } else {
+ ch->numpslots--;
+ }
+ } else {
+ ch->numpslots--;
+ ch->basic_dma = 0;
+ }
+ /* If it was our READ LOG command - process it. */
+ if (ch->readlog) {
+ mvs_process_read_log(dev, ccb);
+ /* If it was NCQ command error, put result on hold. */
+ } else if (et == MVS_ERR_NCQ) {
+ ch->hold[slot->slot] = ccb;
+ ch->holdtag[slot->slot] = slot->tag;
+ ch->numhslots++;
+ } else
+ xpt_done(ccb);
+ /* Unfreeze frozen command. */
+ if (ch->frozen && !mvs_check_collision(dev, ch->frozen)) {
+ union ccb *fccb = ch->frozen;
+ ch->frozen = NULL;
+ mvs_begin_transaction(dev, fccb);
+ xpt_release_simq(ch->sim, TRUE);
+ }
+ /* If we have no other active commands, ... */
+ if (ch->rslots == 0) {
+ /* if there was fatal error - reset port. */
+ if (ch->toslots != 0 || ch->fatalerr) {
+ mvs_reset(dev);
+ } else {
+ /* if we have slots in error, we can reinit port. */
+ if (ch->eslots != 0) {
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ ch->eslots = 0;
+ }
+ /* if there commands on hold, we can do READ LOG. */
+ if (!ch->readlog && ch->numhslots)
+ mvs_issue_read_log(dev);
+ }
+ /* If all the rest of commands are in timeout - give them chance. */
+ } else if ((ch->rslots & ~ch->toslots) == 0 &&
+ et != MVS_ERR_TIMEOUT)
+ mvs_rearm_timeout(dev);
+ /* Start PM timer. */
+ if (ch->numrslots == 0 && ch->pm_level > 3 &&
+ (ch->curr[ch->pm_present ? 15 : 0].caps & CTS_SATA_CAPS_D_PMREQ)) {
+ callout_schedule(&ch->pm_timer,
+ (ch->pm_level == 4) ? hz / 1000 : hz / 8);
+ }
+}
+
+static void
+mvs_issue_read_log(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ union ccb *ccb;
+ struct ccb_ataio *ataio;
+ int i;
+
+ ch->readlog = 1;
+ /* Find some holden command. */
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ if (ch->hold[i])
+ break;
+ }
+ ccb = xpt_alloc_ccb_nowait();
+ if (ccb == NULL) {
+ device_printf(dev, "Unable allocate READ LOG command");
+ return; /* XXX */
+ }
+ ccb->ccb_h = ch->hold[i]->ccb_h; /* Reuse old header. */
+ ccb->ccb_h.func_code = XPT_ATA_IO;
+ ccb->ccb_h.flags = CAM_DIR_IN;
+ ccb->ccb_h.timeout = 1000; /* 1s should be enough. */
+ ataio = &ccb->ataio;
+ ataio->data_ptr = malloc(512, M_MVS, M_NOWAIT);
+ if (ataio->data_ptr == NULL) {
+ device_printf(dev, "Unable allocate memory for READ LOG command");
+ return; /* XXX */
+ }
+ ataio->dxfer_len = 512;
+ bzero(&ataio->cmd, sizeof(ataio->cmd));
+ ataio->cmd.flags = CAM_ATAIO_48BIT;
+ ataio->cmd.command = 0x2F; /* READ LOG EXT */
+ ataio->cmd.sector_count = 1;
+ ataio->cmd.sector_count_exp = 0;
+ ataio->cmd.lba_low = 0x10;
+ ataio->cmd.lba_mid = 0;
+ ataio->cmd.lba_mid_exp = 0;
+ /* Freeze SIM while doing READ LOG EXT. */
+ xpt_freeze_simq(ch->sim, 1);
+ mvs_begin_transaction(dev, ccb);
+}
+
+static void
+mvs_process_read_log(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ uint8_t *data;
+ struct ata_res *res;
+ int i;
+
+ ch->readlog = 0;
+
+ data = ccb->ataio.data_ptr;
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP &&
+ (data[0] & 0x80) == 0) {
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ if (!ch->hold[i])
+ continue;
+ if (ch->hold[i]->ccb_h.target_id != ccb->ccb_h.target_id)
+ continue;
+ if ((data[0] & 0x1F) == ch->holdtag[i]) {
+ res = &ch->hold[i]->ataio.res;
+ res->status = data[2];
+ res->error = data[3];
+ res->lba_low = data[4];
+ res->lba_mid = data[5];
+ res->lba_high = data[6];
+ res->device = data[7];
+ res->lba_low_exp = data[8];
+ res->lba_mid_exp = data[9];
+ res->lba_high_exp = data[10];
+ res->sector_count = data[12];
+ res->sector_count_exp = data[13];
+ } else {
+ ch->hold[i]->ccb_h.status &= ~CAM_STATUS_MASK;
+ ch->hold[i]->ccb_h.status |= CAM_REQUEUE_REQ;
+ }
+ xpt_done(ch->hold[i]);
+ ch->hold[i] = NULL;
+ ch->numhslots--;
+ }
+ } else {
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ device_printf(dev, "Error while READ LOG EXT\n");
+ else if ((data[0] & 0x80) == 0) {
+ device_printf(dev, "Non-queued command error in READ LOG EXT\n");
+ }
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ if (!ch->hold[i])
+ continue;
+ if (ch->hold[i]->ccb_h.target_id != ccb->ccb_h.target_id)
+ continue;
+ xpt_done(ch->hold[i]);
+ ch->hold[i] = NULL;
+ ch->numhslots--;
+ }
+ }
+ free(ccb->ataio.data_ptr, M_MVS);
+ xpt_free_ccb(ccb);
+ xpt_release_simq(ch->sim, TRUE);
+}
+
+static int
+mvs_wait(device_t dev, u_int s, u_int c, int t)
+{
+ int timeout = 0;
+ uint8_t st;
+
+ while (((st = mvs_getstatus(dev, 0)) & (s | c)) != s) {
+ DELAY(1000);
+ if (timeout++ > t) {
+ device_printf(dev, "Wait status %02x\n", st);
+ return (-1);
+ }
+ }
+ return (timeout);
+}
+
+static void
+mvs_requeue_frozen(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ union ccb *fccb = ch->frozen;
+
+ if (fccb) {
+ ch->frozen = NULL;
+ fccb->ccb_h.status = CAM_REQUEUE_REQ | CAM_RELEASE_SIMQ;
+ if (!(fccb->ccb_h.status & CAM_DEV_QFRZN)) {
+ xpt_freeze_devq(fccb->ccb_h.path, 1);
+ fccb->ccb_h.status |= CAM_DEV_QFRZN;
+ }
+ xpt_done(fccb);
+ }
+}
+
+static void
+mvs_reset(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int i;
+
+ xpt_freeze_simq(ch->sim, 1);
+ if (bootverbose)
+ device_printf(dev, "MVS reset...\n");
+ /* Requeue freezed command. */
+ mvs_requeue_frozen(dev);
+ /* Kill the engine and requeue all running commands. */
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ ATA_OUTL(ch->r_mem, DMA_C, 0);
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ /* Do we have a running request on slot? */
+ if (ch->slot[i].state < MVS_SLOT_RUNNING)
+ continue;
+ /* XXX; Commands in loading state. */
+ mvs_end_transaction(&ch->slot[i], MVS_ERR_INNOCENT);
+ }
+ for (i = 0; i < MVS_MAX_SLOTS; i++) {
+ if (!ch->hold[i])
+ continue;
+ xpt_done(ch->hold[i]);
+ ch->hold[i] = NULL;
+ ch->numhslots--;
+ }
+ if (ch->toslots != 0)
+ xpt_release_simq(ch->sim, TRUE);
+ ch->eslots = 0;
+ ch->toslots = 0;
+ ch->fatalerr = 0;
+ /* Tell the XPT about the event */
+ xpt_async(AC_BUS_RESET, ch->path, NULL);
+ ATA_OUTL(ch->r_mem, EDMA_IEM, 0);
+ ATA_OUTL(ch->r_mem, EDMA_CMD, EDMA_CMD_EATARST);
+ DELAY(25);
+ ATA_OUTL(ch->r_mem, EDMA_CMD, 0);
+ /* Reset and reconnect PHY, */
+ if (!mvs_sata_phy_reset(dev)) {
+ if (bootverbose)
+ device_printf(dev,
+ "MVS reset done: phy reset found no device\n");
+ ch->devices = 0;
+ ATA_OUTL(ch->r_mem, SATA_SE, 0xffffffff);
+ ATA_OUTL(ch->r_mem, EDMA_IEC, 0);
+ ATA_OUTL(ch->r_mem, EDMA_IEM, ~EDMA_IE_TRANSIENT);
+ xpt_release_simq(ch->sim, TRUE);
+ return;
+ }
+ /* Wait for clearing busy status. */
+ if ((i = mvs_wait(dev, 0, ATA_S_BUSY | ATA_S_DRQ, 15000)) < 0)
+ device_printf(dev, "device is not ready\n");
+ else if (bootverbose)
+ device_printf(dev, "ready wait time=%dms\n", i);
+ ch->devices = 1;
+ ATA_OUTL(ch->r_mem, SATA_SE, 0xffffffff);
+ ATA_OUTL(ch->r_mem, EDMA_IEC, 0);
+ ATA_OUTL(ch->r_mem, EDMA_IEM, ~EDMA_IE_TRANSIENT);
+ if (bootverbose)
+ device_printf(dev, "MVS reset done: device found\n");
+ xpt_release_simq(ch->sim, TRUE);
+}
+
+static void
+mvs_softreset(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int port = ccb->ccb_h.target_id & 0x0f;
+ int i;
+
+ mvs_set_edma_mode(dev, MVS_EDMA_OFF);
+ ATA_OUTB(ch->r_mem, SATA_SATAICTL, port << SATA_SATAICTL_PMPTX_SHIFT);
+ ATA_OUTB(ch->r_mem, ATA_CONTROL, ATA_A_RESET);
+ DELAY(10000);
+ ATA_OUTB(ch->r_mem, ATA_CONTROL, 0);
+ ccb->ccb_h.status &= ~CAM_STATUS_MASK;
+ /* Wait for clearing busy status. */
+ if ((i = mvs_wait(dev, 0, ATA_S_BUSY | ATA_S_DRQ, ccb->ccb_h.timeout)) < 0) {
+ ccb->ccb_h.status |= CAM_CMD_TIMEOUT;
+ } else {
+ ccb->ccb_h.status |= CAM_REQ_CMP;
+ }
+ mvs_tfd_read(dev, ccb);
+ xpt_done(ccb);
+}
+
+static int
+mvs_sata_connect(struct mvs_channel *ch)
+{
+ u_int32_t status;
+ int timeout;
+
+ /* Wait up to 100ms for "connect well" */
+ for (timeout = 0; timeout < 100 ; timeout++) {
+ status = ATA_INL(ch->r_mem, SATA_SS);
+ if (((status & SATA_SS_DET_MASK) == SATA_SS_DET_PHY_ONLINE) &&
+ ((status & SATA_SS_SPD_MASK) != SATA_SS_SPD_NO_SPEED) &&
+ ((status & SATA_SS_IPM_MASK) == SATA_SS_IPM_ACTIVE))
+ break;
+ if ((status & SATA_SS_DET_MASK) == SATA_SS_DET_PHY_OFFLINE) {
+ if (bootverbose) {
+ device_printf(ch->dev, "SATA offline status=%08x\n",
+ status);
+ }
+ return (0);
+ }
+ DELAY(1000);
+ }
+ if (timeout >= 100) {
+ if (bootverbose) {
+ device_printf(ch->dev, "SATA connect timeout status=%08x\n",
+ status);
+ }
+ return (0);
+ }
+ if (bootverbose) {
+ device_printf(ch->dev, "SATA connect time=%dms status=%08x\n",
+ timeout, status);
+ }
+ /* Clear SATA error register */
+ ATA_OUTL(ch->r_mem, SATA_SE, 0xffffffff);
+ return (1);
+}
+
+static int
+mvs_sata_phy_reset(device_t dev)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+ int sata_rev;
+ uint32_t val;
+
+ sata_rev = ch->user[ch->pm_present ? 15 : 0].revision;
+ if (sata_rev == 1)
+ val = SATA_SC_SPD_SPEED_GEN1;
+ else if (sata_rev == 2)
+ val = SATA_SC_SPD_SPEED_GEN2;
+ else if (sata_rev == 3)
+ val = SATA_SC_SPD_SPEED_GEN3;
+ else
+ val = 0;
+ ATA_OUTL(ch->r_mem, SATA_SC,
+ SATA_SC_DET_RESET | val |
+ SATA_SC_IPM_DIS_PARTIAL | SATA_SC_IPM_DIS_SLUMBER);
+ DELAY(5000);
+ ATA_OUTL(ch->r_mem, SATA_SC,
+ SATA_SC_DET_IDLE | val | ((ch->pm_level > 0) ? 0 :
+ (SATA_SC_IPM_DIS_PARTIAL | SATA_SC_IPM_DIS_SLUMBER)));
+ DELAY(5000);
+ if (!mvs_sata_connect(ch)) {
+ if (ch->pm_level > 0)
+ ATA_OUTL(ch->r_mem, SATA_SC, SATA_SC_DET_DISABLE);
+ return (0);
+ }
+ return (1);
+}
+
+static int
+mvs_check_ids(device_t dev, union ccb *ccb)
+{
+ struct mvs_channel *ch = device_get_softc(dev);
+
+ if (ccb->ccb_h.target_id > ((ch->quirks & MVS_Q_GENI) ? 0 : 15)) {
+ ccb->ccb_h.status = CAM_TID_INVALID;
+ xpt_done(ccb);
+ return (-1);
+ }
+ if (ccb->ccb_h.target_lun != 0) {
+ ccb->ccb_h.status = CAM_LUN_INVALID;
+ xpt_done(ccb);
+ return (-1);
+ }
+ return (0);
+}
+
+static void
+mvsaction(struct cam_sim *sim, union ccb *ccb)
+{
+ device_t dev;
+ struct mvs_channel *ch;
+
+ CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("mvsaction func_code=%x\n",
+ ccb->ccb_h.func_code));
+
+ ch = (struct mvs_channel *)cam_sim_softc(sim);
+ dev = ch->dev;
+ switch (ccb->ccb_h.func_code) {
+ /* Common cases first */
+ case XPT_ATA_IO: /* Execute the requested I/O operation */
+ case XPT_SCSI_IO:
+ if (mvs_check_ids(dev, ccb))
+ return;
+ if (ch->devices == 0 ||
+ (ch->pm_present == 0 &&
+ ccb->ccb_h.target_id > 0 && ccb->ccb_h.target_id < 15)) {
+ ccb->ccb_h.status = CAM_SEL_TIMEOUT;
+ break;
+ }
+ /* Check for command collision. */
+ if (mvs_check_collision(dev, ccb)) {
+ /* Freeze command. */
+ ch->frozen = ccb;
+ /* We have only one frozen slot, so freeze simq also. */
+ xpt_freeze_simq(ch->sim, 1);
+ return;
+ }
+ mvs_begin_transaction(dev, ccb);
+ return;
+ case XPT_EN_LUN: /* Enable LUN as a target */
+ case XPT_TARGET_IO: /* Execute target I/O request */
+ case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */
+ case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/
+ case XPT_ABORT: /* Abort the specified CCB */
+ /* XXX Implement */
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ break;
+ case XPT_SET_TRAN_SETTINGS:
+ {
+ struct ccb_trans_settings *cts = &ccb->cts;
+ struct mvs_device *d;
+
+ if (mvs_check_ids(dev, ccb))
+ return;
+ if (cts->type == CTS_TYPE_CURRENT_SETTINGS)
+ d = &ch->curr[ccb->ccb_h.target_id];
+ else
+ d = &ch->user[ccb->ccb_h.target_id];
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_REVISION)
+ d->revision = cts->xport_specific.sata.revision;
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_MODE)
+ d->mode = cts->xport_specific.sata.mode;
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_BYTECOUNT) {
+ d->bytecount = min((ch->quirks & MVS_Q_GENIIE) ? 8192 : 2048,
+ cts->xport_specific.sata.bytecount);
+ }
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_TAGS)
+ d->tags = min(MVS_MAX_SLOTS, cts->xport_specific.sata.tags);
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_PM)
+ ch->pm_present = cts->xport_specific.sata.pm_present;
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_ATAPI)
+ d->atapi = cts->xport_specific.sata.atapi;
+ if (cts->xport_specific.sata.valid & CTS_SATA_VALID_CAPS)
+ d->caps = cts->xport_specific.sata.caps;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ }
+ case XPT_GET_TRAN_SETTINGS:
+ /* Get default/user set transfer settings for the target */
+ {
+ struct ccb_trans_settings *cts = &ccb->cts;
+ struct mvs_device *d;
+ uint32_t status;
+
+ if (mvs_check_ids(dev, ccb))
+ return;
+ if (cts->type == CTS_TYPE_CURRENT_SETTINGS)
+ d = &ch->curr[ccb->ccb_h.target_id];
+ else
+ d = &ch->user[ccb->ccb_h.target_id];
+ cts->protocol = PROTO_ATA;
+ cts->protocol_version = PROTO_VERSION_UNSPECIFIED;
+ cts->transport = XPORT_SATA;
+ cts->transport_version = XPORT_VERSION_UNSPECIFIED;
+ cts->proto_specific.valid = 0;
+ cts->xport_specific.sata.valid = 0;
+ if (cts->type == CTS_TYPE_CURRENT_SETTINGS &&
+ (ccb->ccb_h.target_id == 15 ||
+ (ccb->ccb_h.target_id == 0 && !ch->pm_present))) {
+ status = ATA_INL(ch->r_mem, SATA_SS) & SATA_SS_SPD_MASK;
+ if (status & 0x0f0) {
+ cts->xport_specific.sata.revision =
+ (status & 0x0f0) >> 4;
+ cts->xport_specific.sata.valid |=
+ CTS_SATA_VALID_REVISION;
+ }
+ cts->xport_specific.sata.caps = d->caps & CTS_SATA_CAPS_D;
+// if (ch->pm_level)
+// cts->xport_specific.sata.caps |= CTS_SATA_CAPS_H_PMREQ;
+ cts->xport_specific.sata.caps &=
+ ch->user[ccb->ccb_h.target_id].caps;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_CAPS;
+ } else {
+ cts->xport_specific.sata.revision = d->revision;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_REVISION;
+ cts->xport_specific.sata.caps = d->caps;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_CAPS;
+ }
+ cts->xport_specific.sata.mode = d->mode;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_MODE;
+ cts->xport_specific.sata.bytecount = d->bytecount;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_BYTECOUNT;
+ cts->xport_specific.sata.pm_present = ch->pm_present;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_PM;
+ cts->xport_specific.sata.tags = d->tags;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_TAGS;
+ cts->xport_specific.sata.atapi = d->atapi;
+ cts->xport_specific.sata.valid |= CTS_SATA_VALID_ATAPI;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ }
+ case XPT_RESET_BUS: /* Reset the specified SCSI bus */
+ case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */
+ mvs_reset(dev);
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ case XPT_TERM_IO: /* Terminate the I/O process */
+ /* XXX Implement */
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ break;
+ case XPT_PATH_INQ: /* Path routing inquiry */
+ {
+ struct ccb_pathinq *cpi = &ccb->cpi;
+
+ cpi->version_num = 1; /* XXX??? */
+ cpi->hba_inquiry = PI_SDTR_ABLE;
+ if (!(ch->quirks & MVS_Q_GENI)) {
+ cpi->hba_inquiry |= PI_SATAPM;
+ /* Gen-II is extremely slow with NCQ on PMP. */
+ if ((ch->quirks & MVS_Q_GENIIE) || ch->pm_present == 0)
+ cpi->hba_inquiry |= PI_TAG_ABLE;
+ }
+ cpi->target_sprt = 0;
+ cpi->hba_misc = PIM_SEQSCAN;
+ cpi->hba_eng_cnt = 0;
+ if (!(ch->quirks & MVS_Q_GENI))
+ cpi->max_target = 15;
+ else
+ cpi->max_target = 0;
+ cpi->max_lun = 0;
+ cpi->initiator_id = 0;
+ cpi->bus_id = cam_sim_bus(sim);
+ cpi->base_transfer_speed = 150000;
+ strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
+ strncpy(cpi->hba_vid, "Marvell", HBA_IDLEN);
+ strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
+ cpi->unit_number = cam_sim_unit(sim);
+ cpi->transport = XPORT_SATA;
+ cpi->transport_version = XPORT_VERSION_UNSPECIFIED;
+ cpi->protocol = PROTO_ATA;
+ cpi->protocol_version = PROTO_VERSION_UNSPECIFIED;
+ cpi->maxio = MAXPHYS;
+ cpi->ccb_h.status = CAM_REQ_CMP;
+ break;
+ }
+ default:
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ break;
+ }
+ xpt_done(ccb);
+}
+
+static void
+mvspoll(struct cam_sim *sim)
+{
+ struct mvs_channel *ch = (struct mvs_channel *)cam_sim_softc(sim);
+ struct mvs_intr_arg arg;
+
+ arg.arg = ch->dev;
+ arg.cause = 2; /* XXX */
+ mvs_ch_intr(arg.arg);
+}
+