freebsd-dev/sys/dev/mvs/mvs.c
Alexander Motin dd48af360f Import mvs(4) - Marvell 88SX50XX/88SX60XX/88SX70XX/SoC SATA controllers
driver for CAM ATA subsystem. This driver supports same hardware as
atamarvell, ataadaptec and atamvsata drivers from ata(4), but provides
many additional features, such as NCQ, PMP, etc.
2010-05-02 19:28:30 +00:00

2174 lines
64 KiB
C

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