#include "sili.h"
void sili_port_interrupt_enable(struct sili_port *ap);
void sili_port_interrupt_redisable(struct sili_port *ap);
void sili_port_interrupt_reenable(struct sili_port *ap);
int sili_load_prb(struct sili_ccb *);
void sili_unload_prb(struct sili_ccb *);
static void sili_load_prb_callback(void *info, bus_dma_segment_t *segs,
int nsegs, int error);
void sili_start(struct sili_ccb *);
static void sili_port_reinit(struct sili_port *ap);
int sili_port_softreset(struct sili_port *ap);
int sili_port_hardreset(struct sili_port *ap);
void sili_port_hardstop(struct sili_port *ap);
void sili_port_listen(struct sili_port *ap);
static void sili_ata_cmd_timeout_unserialized(void *);
static int sili_core_timeout(struct sili_ccb *ccb, int really_error);
void sili_check_active_timeouts(struct sili_port *ap);
void sili_issue_pending_commands(struct sili_port *ap, struct sili_ccb *ccb);
void sili_port_read_ncq_error(struct sili_port *, int);
struct sili_dmamem *sili_dmamem_alloc(struct sili_softc *, bus_dma_tag_t tag);
void sili_dmamem_free(struct sili_softc *, struct sili_dmamem *);
static void sili_dmamem_saveseg(void *info, bus_dma_segment_t *segs, int nsegs, int error);
static void sili_dummy_done(struct ata_xfer *xa);
static void sili_empty_done(struct sili_ccb *ccb);
static void sili_ata_cmd_done(struct sili_ccb *ccb);
int
sili_init(struct sili_softc *sc)
{
DPRINTF(SILI_D_VERBOSE, " GHC 0x%pb%i",
SILI_FMT_GHC, sili_read(sc, SILI_REG_GHC));
sili_write(sc, SILI_REG_GCTL, SILI_REG_GCTL_GRESET);
sili_os_sleep(10);
sili_write(sc, SILI_REG_GCTL, 0);
sili_os_sleep(10);
return (0);
}
int
sili_port_alloc(struct sili_softc *sc, u_int port)
{
struct sili_port *ap;
struct ata_port *at;
struct sili_prb *prb;
struct sili_ccb *ccb;
int rc = ENOMEM;
int error;
int i;
ap = kmalloc(sizeof(*ap), M_DEVBUF, M_WAITOK | M_ZERO);
ap->ap_err_scratch = kmalloc(512, M_DEVBUF, M_WAITOK | M_ZERO);
ksnprintf(ap->ap_name, sizeof(ap->ap_name), "%s%d.%d",
device_get_name(sc->sc_dev),
device_get_unit(sc->sc_dev),
port);
sc->sc_ports[port] = ap;
if (ap->ap_ata == NULL) {
ap->ap_ata = kmalloc(sizeof(*ap->ap_ata) * SILI_MAX_PMPORTS,
M_DEVBUF, M_INTWAIT | M_ZERO);
for (i = 0; i < SILI_MAX_PMPORTS; ++i) {
at = &ap->ap_ata[i];
at->at_sili_port = ap;
at->at_target = i;
at->at_probe = ATA_PROBE_NEED_INIT;
at->at_features |= ATA_PORT_F_RESCAN;
ksnprintf(at->at_name, sizeof(at->at_name),
"%s.%d", ap->ap_name, i);
}
}
if (bus_space_subregion(sc->sc_piot, sc->sc_pioh,
SILI_PORT_REGION(port), SILI_PORT_SIZE,
&ap->ap_ioh) != 0) {
device_printf(sc->sc_dev,
"unable to create register window for port %d\n",
port);
goto freeport;
}
ap->ap_sc = sc;
ap->ap_num = port;
ap->ap_probe = ATA_PROBE_NEED_INIT;
TAILQ_INIT(&ap->ap_ccb_free);
TAILQ_INIT(&ap->ap_ccb_pending);
lockinit(&ap->ap_ccb_lock, "silipo", 0, 0);
sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_MASK);
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
if (sili_pread(ap, SILI_PREG_STATUS) & SILI_PREG_STATUS_READY) {
device_printf(sc->sc_dev,
"Port %d will not go into reset\n", port);
goto freeport;
}
sili_os_sleep(10);
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESET);
ap->ap_dmamem_prbs = sili_dmamem_alloc(sc, sc->sc_tag_prbs);
if (ap->ap_dmamem_prbs == NULL) {
kprintf("%s: NOSGET\n", PORTNAME(ap));
goto freeport;
}
ap->ap_prbs = (struct sili_prb *)SILI_DMA_KVA(ap->ap_dmamem_prbs);
ap->ap_ccbs = kmalloc(sizeof(struct sili_ccb) * sc->sc_ncmds, M_DEVBUF,
M_WAITOK | M_ZERO);
for (i = 0; i < sc->sc_ncmds; i++) {
ccb = &ap->ap_ccbs[i];
error = bus_dmamap_create(sc->sc_tag_data, BUS_DMA_ALLOCNOW,
&ccb->ccb_dmamap);
if (error) {
device_printf(sc->sc_dev,
"unable to create dmamap for port %d "
"ccb %d\n", port, i);
goto freeport;
}
callout_init(&ccb->ccb_timeout);
ccb->ccb_slot = i;
ccb->ccb_port = ap;
ccb->ccb_prb = &ap->ap_prbs[i];
ccb->ccb_prb_paddr = SILI_DMA_DVA(ap->ap_dmamem_prbs) +
sizeof(*ccb->ccb_prb) * i;
ccb->ccb_xa.fis = &ccb->ccb_prb->prb_h2d;
prb = bus_space_kva(ap->ap_sc->sc_iot, ap->ap_ioh,
SILI_PREG_LRAM_SLOT(i));
ccb->ccb_prb_lram = prb;
ccb->ccb_xa.rfis = (void *)ccb->ccb_xa.fis;
ccb->ccb_xa.packetcmd = prb_packet(ccb->ccb_prb);
ccb->ccb_xa.tag = i;
ccb->ccb_xa.state = ATA_S_COMPLETE;
if (i == 1)
ap->ap_err_ccb = ccb;
else
sili_put_ccb(ccb);
}
sili_os_start_port(ap);
return(0);
freeport:
sili_port_free(sc, port);
return (rc);
}
int
sili_port_init(struct sili_port *ap)
{
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
sili_os_sleep(10);
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESET);
sili_pwrite(ap, SILI_PREG_FIFO_CTL,
SILI_PREG_FIFO_CTL_ENCODE(1024, 1024));
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_32BITDMA |
SILI_PREG_CTL_PMA);
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_NOAUTOCC);
if (ap->ap_sc->sc_flags & SILI_F_SSNTF)
sili_pwrite(ap, SILI_PREG_SNTF, -1);
ap->ap_probe = ATA_PROBE_NEED_HARD_RESET;
ap->ap_pmcount = 0;
sili_port_interrupt_enable(ap);
return (0);
}
static
void
sili_port_reinit(struct sili_port *ap)
{
struct sili_ccb *ccb;
struct ata_port *at;
int slot;
int target;
u_int32_t data;
if (bootverbose || 1) {
kprintf("%s: reiniting port after error reent=%d "
"expired=%08x\n",
PORTNAME(ap),
(ap->ap_flags & AP_F_REINIT_ACTIVE),
ap->ap_expired);
}
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESUME);
sili_os_sleep(10);
for (target = 0; target < SILI_MAX_PMPORTS; ++target) {
data = sili_pread(ap, SILI_PREG_PM_STATUS(target));
data &= ~(SILI_PREG_PM_STATUS_SERVICE |
SILI_PREG_PM_STATUS_LEGACY |
SILI_PREG_PM_STATUS_NATIVE |
SILI_PREG_PM_STATUS_VBSY);
sili_pwrite(ap, SILI_PREG_PM_STATUS(target), data);
sili_pwrite(ap, SILI_PREG_PM_QACTIVE(target), 0);
}
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_INIT);
if (sili_pwait_clr_to(ap, 5000, SILI_PREG_STATUS, SILI_PREG_CTL_INIT)) {
kprintf("%s: Unable to reinit, port failed\n",
PORTNAME(ap));
}
if (sili_pwait_set(ap, SILI_PREG_STATUS, SILI_PREG_STATUS_READY)) {
kprintf("%s: Unable to reinit, port will not come ready\n",
PORTNAME(ap));
}
if (ap->ap_flags & AP_F_REINIT_ACTIVE)
return;
ap->ap_flags |= AP_F_REINIT_ACTIVE;
if ((ap->ap_flags & AP_F_ERR_CCB_RESERVED) == 0) {
for (target = 0; target < SILI_MAX_PMPORTS; ++target) {
at = &ap->ap_ata[target];
if (at->at_features & ATA_PORT_F_READLOG) {
at->at_features &= ~ATA_PORT_F_READLOG;
sili_port_read_ncq_error(ap, target);
}
}
}
while (ap->ap_expired) {
slot = ffs(ap->ap_expired) - 1;
ap->ap_expired &= ~(1 << slot);
KKASSERT(ap->ap_active & (1 << slot));
ap->ap_active &= ~(1 << slot);
--ap->ap_active_cnt;
ccb = &ap->ap_ccbs[slot];
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ccb->ccb_done(ccb);
ccb->ccb_xa.complete(&ccb->ccb_xa);
}
ap->ap_flags &= ~AP_F_REINIT_ACTIVE;
if (ap->ap_probe == ATA_PROBE_FAILED) {
kprintf("%s: reinit failed, port is dead\n", PORTNAME(ap));
while ((ccb = TAILQ_FIRST(&ap->ap_ccb_pending)) != NULL) {
TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_DESIRED;
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ccb->ccb_done(ccb);
ccb->ccb_xa.complete(&ccb->ccb_xa);
}
} else {
sili_issue_pending_commands(ap, NULL);
}
}
void
sili_port_interrupt_enable(struct sili_port *ap)
{
u_int32_t data;
data = SILI_PREG_INT_CCOMPLETE | SILI_PREG_INT_CERROR |
SILI_PREG_INT_PHYRDYCHG | SILI_PREG_INT_DEVEXCHG |
SILI_PREG_INT_DECODE | SILI_PREG_INT_CRC |
SILI_PREG_INT_HANDSHK | SILI_PREG_INT_PMCHANGE;
if (ap->ap_sc->sc_flags & SILI_F_SSNTF)
data |= SILI_PREG_INT_SDB;
sili_pwrite(ap, SILI_PREG_INT_ENABLE, data);
}
void
sili_port_interrupt_redisable(struct sili_port *ap)
{
u_int32_t data;
data = sili_read(ap->ap_sc, SILI_REG_GCTL);
data &= SILI_REG_GINT_PORTMASK;
data &= ~(1 << ap->ap_num);
sili_write(ap->ap_sc, SILI_REG_GCTL, data);
}
void
sili_port_interrupt_reenable(struct sili_port *ap)
{
u_int32_t data;
data = sili_read(ap->ap_sc, SILI_REG_GCTL);
data &= SILI_REG_GINT_PORTMASK;
data |= (1 << ap->ap_num);
sili_write(ap->ap_sc, SILI_REG_GCTL, data);
}
void
sili_port_state_machine(struct sili_port *ap, int initial)
{
struct ata_port *at;
u_int32_t data;
int target;
int didsleep;
int loop;
{
if (initial == 0 && ap->ap_probe <= ATA_PROBE_NEED_HARD_RESET) {
kprintf("%s: Waiting 7 seconds on insertion\n",
PORTNAME(ap));
sili_os_sleep(7000);
initial = 1;
}
if (ap->ap_probe == ATA_PROBE_NEED_INIT)
sili_port_init(ap);
if (ap->ap_probe == ATA_PROBE_NEED_HARD_RESET)
sili_port_reset(ap, NULL, 1);
if (ap->ap_probe == ATA_PROBE_NEED_SOFT_RESET)
sili_port_reset(ap, NULL, 0);
if (ap->ap_probe == ATA_PROBE_NEED_IDENT)
sili_cam_probe(ap, NULL);
}
if (ap->ap_type != ATA_PORT_T_PM) {
if (ap->ap_probe == ATA_PROBE_FAILED) {
sili_cam_changed(ap, NULL, 0);
} else if (ap->ap_probe >= ATA_PROBE_NEED_IDENT) {
sili_cam_changed(ap, NULL, 1);
}
return;
}
for (loop = 0; ;++loop) {
if (sili_pm_read(ap, 15, SATA_PMREG_EINFO, &data)) {
kprintf("%s: PM unable to read hot-plug bitmap\n",
PORTNAME(ap));
break;
}
if (loop && data == 0)
break;
didsleep = 0;
for (target = 0; target < ap->ap_pmcount; ++target) {
at = &ap->ap_ata[target];
if (data & (1 << target)) {
if (initial == 0 && didsleep == 0)
sili_os_sleep(1000);
sili_pm_check_good(ap, target);
if (initial == 0 && didsleep == 0 &&
at->at_probe <= ATA_PROBE_NEED_HARD_RESET
) {
didsleep = 1;
kprintf("%s: Waiting 5 seconds on insertion\n", PORTNAME(ap));
sili_os_sleep(5000);
}
}
if (data & (1 << target)) {
kprintf("%s: HOTPLUG (PM) - ", ATANAME(ap, at));
switch(at->at_probe) {
case ATA_PROBE_NEED_INIT:
case ATA_PROBE_NEED_HARD_RESET:
kprintf("Device inserted\n");
break;
case ATA_PROBE_FAILED:
kprintf("Device removed\n");
break;
default:
kprintf("Device probe in progress\n");
break;
}
}
if (at->at_probe != ATA_PROBE_FAILED &&
at->at_probe != ATA_PROBE_GOOD) {
if (at->at_probe == ATA_PROBE_NEED_INIT)
sili_pm_port_init(ap, at);
if (at->at_probe == ATA_PROBE_NEED_HARD_RESET)
sili_port_reset(ap, at, 1);
if (at->at_probe == ATA_PROBE_NEED_SOFT_RESET)
sili_port_reset(ap, at, 0);
if (at->at_probe == ATA_PROBE_NEED_IDENT)
sili_cam_probe(ap, at);
}
if (at->at_features & ATA_PORT_F_RESCAN) {
at->at_features &= ~ATA_PORT_F_RESCAN;
if (at->at_probe == ATA_PROBE_FAILED) {
sili_cam_changed(ap, at, 0);
} else if (at->at_probe >= ATA_PROBE_NEED_IDENT) {
sili_cam_changed(ap, at, 1);
}
}
data &= ~(1 << target);
}
if (data) {
kprintf("%s: WARNING (PM): extra bits set in "
"EINFO: %08x\n", PORTNAME(ap), data);
while (target < SILI_MAX_PMPORTS) {
sili_pm_check_good(ap, target);
++target;
}
}
}
}
void
sili_port_free(struct sili_softc *sc, u_int port)
{
struct sili_port *ap = sc->sc_ports[port];
struct sili_ccb *ccb;
if (ap->ap_sc) {
sili_os_stop_port(ap);
sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_MASK);
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
sili_write(ap->ap_sc, SILI_REG_GCTL,
sili_read(ap->ap_sc, SILI_REG_GCTL) &
~SILI_REG_GINT_PORTST(ap->ap_num));
}
if (ap->ap_ccbs) {
while ((ccb = sili_get_ccb(ap)) != NULL) {
if (ccb->ccb_dmamap) {
bus_dmamap_destroy(sc->sc_tag_data,
ccb->ccb_dmamap);
ccb->ccb_dmamap = NULL;
}
}
if ((ccb = ap->ap_err_ccb) != NULL) {
if (ccb->ccb_dmamap) {
bus_dmamap_destroy(sc->sc_tag_data,
ccb->ccb_dmamap);
ccb->ccb_dmamap = NULL;
}
ap->ap_err_ccb = NULL;
}
kfree(ap->ap_ccbs, M_DEVBUF);
ap->ap_ccbs = NULL;
}
if (ap->ap_dmamem_prbs) {
sili_dmamem_free(sc, ap->ap_dmamem_prbs);
ap->ap_dmamem_prbs = NULL;
}
if (ap->ap_ata) {
kfree(ap->ap_ata, M_DEVBUF);
ap->ap_ata = NULL;
}
if (ap->ap_err_scratch) {
kfree(ap->ap_err_scratch, M_DEVBUF);
ap->ap_err_scratch = NULL;
}
kfree(ap, M_DEVBUF);
sc->sc_ports[port] = NULL;
}
int
sili_port_reset(struct sili_port *ap, struct ata_port *at, int hard)
{
int rc;
if (hard) {
if (at)
rc = sili_pm_hardreset(ap, at->at_target, hard);
else
rc = sili_port_hardreset(ap);
} else {
if (at)
rc = sili_pm_softreset(ap, at->at_target);
else
rc = sili_port_softreset(ap);
}
return(rc);
}
int
sili_port_softreset(struct sili_port *ap)
{
struct sili_ccb *ccb = NULL;
struct sili_prb *prb;
int error;
u_int32_t sig;
error = EIO;
if (bootverbose)
kprintf("%s: START SOFTRESET\n", PORTNAME(ap));
crit_enter();
ap->ap_state = AP_S_NORMAL;
ccb = sili_get_err_ccb(ap);
ccb->ccb_done = sili_empty_done;
ccb->ccb_xa.flags = ATA_F_POLL | ATA_F_AUTOSENSE | ATA_F_EXCLUSIVE;
ccb->ccb_xa.complete = sili_dummy_done;
ccb->ccb_xa.at = NULL;
prb = ccb->ccb_prb;
bzero(&prb->prb_h2d, sizeof(prb->prb_h2d));
prb->prb_h2d.flags = 0;
prb->prb_control = SILI_PRB_CTRL_SOFTRESET;
prb->prb_override = 0;
prb->prb_xfer_count = 0;
ccb->ccb_xa.state = ATA_S_PENDING;
if (sili_poll(ccb, 8000, sili_quick_timeout) != ATA_S_COMPLETE) {
kprintf("%s: First FIS failed\n", PORTNAME(ap));
goto err;
}
sig = (prb->prb_d2h.lba_high << 24) |
(prb->prb_d2h.lba_mid << 16) |
(prb->prb_d2h.lba_low << 8) |
(prb->prb_d2h.sector_count);
if (bootverbose)
kprintf("%s: SOFTRESET SIGNATURE %08x\n", PORTNAME(ap), sig);
if (ap->ap_type == ATA_PORT_T_NONE) {
ap->ap_type = sili_port_signature(ap, NULL, sig);
} else {
if (sili_port_signature(ap, NULL, sig) != ap->ap_type) {
kprintf("%s: device signature unexpectedly "
"changed\n", PORTNAME(ap));
error = EBUSY;
}
}
error = 0;
err:
if (ccb != NULL) {
sili_put_err_ccb(ccb);
}
if (bootverbose) {
kprintf("%s: END SOFTRESET %d prob=%d state=%d\n",
PORTNAME(ap), error, ap->ap_probe, ap->ap_state);
}
if (error) {
sili_port_hardstop(ap);
} else {
ap->ap_probe = ATA_PROBE_NEED_IDENT;
ap->ap_pmcount = 1;
}
crit_exit();
sili_pwrite(ap, SILI_PREG_SERR, -1);
if (bootverbose)
kprintf("%s: END SOFTRESET\n", PORTNAME(ap));
return (error);
}
int
sili_port_hardreset(struct sili_port *ap)
{
u_int32_t data;
int error;
int loop;
if (bootverbose)
kprintf("%s: START HARDRESET\n", PORTNAME(ap));
ap->ap_state = AP_S_NORMAL;
data = SILI_PREG_SCTL_SPM_NONE |
SILI_PREG_SCTL_IPM_NONE |
SILI_PREG_SCTL_SPD_NONE |
SILI_PREG_SCTL_DET_NONE;
if (SiliForceGen1 & (1 << ap->ap_num)) {
data &= ~SILI_PREG_SCTL_SPD_NONE;
data |= SILI_PREG_SCTL_SPD_GEN1;
}
sili_pwrite(ap, SILI_PREG_SCTL, data);
loop = 4000;
while (loop > 0) {
data = sili_pread(ap, SILI_PREG_SSTS);
if (data & SILI_PREG_SSTS_DET)
break;
loop -= sili_os_softsleep();
}
sili_os_sleep(100);
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_PMA);
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESUME);
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_DEVRESET);
if (sili_pwait_clr(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_DEVRESET)) {
kprintf("%s: hardreset failed to clear\n", PORTNAME(ap));
}
sili_os_sleep(20);
loop = 300;
while (loop > 0) {
data = sili_pread(ap, SILI_PREG_SSTS);
if (data & SILI_PREG_SSTS_DET)
break;
loop -= sili_os_softsleep();
}
if (loop <= 0) {
if (bootverbose) {
kprintf("%s: Port appears to be unplugged\n",
PORTNAME(ap));
}
error = ENODEV;
goto done;
}
if (sili_pwait_eq(ap, 3000, SILI_PREG_SSTS,
SILI_PREG_SSTS_DET, SILI_PREG_SSTS_DET_DEV)) {
if (bootverbose) {
kprintf("%s: Device may be powered down\n",
PORTNAME(ap));
}
error = ENODEV;
goto pmdetect;
}
if (sili_pwait_set_to(ap, 3000, SILI_PREG_STATUS,
SILI_PREG_STATUS_READY)) {
error = EBUSY;
} else {
error = 0;
}
pmdetect:
if (ap->ap_sc->sc_flags & SILI_F_SPM) {
error = sili_pm_port_probe(ap, error);
}
done:
switch(error) {
case 0:
if (ap->ap_type == ATA_PORT_T_PM)
ap->ap_probe = ATA_PROBE_GOOD;
else
ap->ap_probe = ATA_PROBE_NEED_SOFT_RESET;
break;
case ENODEV:
data = sili_pread(ap, SILI_PREG_SSTS);
switch(data & SATA_PM_SSTS_DET) {
case SILI_PREG_SSTS_DET_DEV_NE:
kprintf("%s: Device not communicating\n",
PORTNAME(ap));
break;
case SILI_PREG_SSTS_DET_OFFLINE:
kprintf("%s: PHY offline\n",
PORTNAME(ap));
break;
default:
kprintf("%s: No device detected\n",
PORTNAME(ap));
break;
}
sili_port_hardstop(ap);
break;
default:
kprintf("%s: Device on port is bricked\n",
PORTNAME(ap));
sili_port_hardstop(ap);
break;
}
sili_pwrite(ap, SILI_PREG_SERR, -1);
if (bootverbose)
kprintf("%s: END HARDRESET %d\n", PORTNAME(ap), error);
return (error);
}
void
sili_port_hardstop(struct sili_port *ap)
{
struct sili_ccb *ccb;
struct ata_port *at;
int i;
int slot;
int serial;
ap->ap_state = AP_S_FATAL_ERROR;
ap->ap_probe = ATA_PROBE_FAILED;
ap->ap_type = ATA_PORT_T_NONE;
for (i = 0; ap->ap_ata && i < SILI_MAX_PMPORTS; ++i) {
at = &ap->ap_ata[i];
at->at_type = ATA_PORT_T_NONE;
at->at_probe = ATA_PROBE_FAILED;
at->at_features &= ~ATA_PORT_F_READLOG;
}
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESET);
if (sili_pread(ap, SILI_PREG_STATUS) & SILI_PREG_STATUS_READY) {
kprintf("%s: Port will not go into reset\n",
PORTNAME(ap));
}
sili_os_sleep(10);
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_RESET);
sili_pwrite(ap, SILI_PREG_CTL_CLR, SILI_PREG_CTL_PMA);
restart:
while (ap->ap_active) {
slot = ffs(ap->ap_active) - 1;
ap->ap_active &= ~(1 << slot);
ap->ap_expired &= ~(1 << slot);
--ap->ap_active_cnt;
ccb = &ap->ap_ccbs[slot];
if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_RUNNING) {
serial = ccb->ccb_xa.serial;
callout_cancel(&ccb->ccb_timeout);
if (serial != ccb->ccb_xa.serial) {
kprintf("%s: Warning: timeout race ccb %p\n",
PORTNAME(ap), ccb);
goto restart;
}
ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_RUNNING;
}
ccb->ccb_xa.flags &= ~(ATA_F_TIMEOUT_DESIRED |
ATA_F_TIMEOUT_EXPIRED);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ccb->ccb_done(ccb);
ccb->ccb_xa.complete(&ccb->ccb_xa);
}
while ((ccb = TAILQ_FIRST(&ap->ap_ccb_pending)) != NULL) {
TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_DESIRED;
ccb->ccb_done(ccb);
ccb->ccb_xa.complete(&ccb->ccb_xa);
}
KKASSERT(ap->ap_active_cnt == 0);
sili_port_listen(ap);
}
void
sili_port_listen(struct sili_port *ap)
{
u_int32_t data;
#if 1
data = SILI_PREG_SCTL_SPM_NONE |
SILI_PREG_SCTL_IPM_NONE |
SILI_PREG_SCTL_SPD_NONE |
SILI_PREG_SCTL_DET_INIT;
if (SiliForceGen1 & (1 << ap->ap_num)) {
data &= ~SILI_PREG_SCTL_SPD_NONE;
data |= SILI_PREG_SCTL_SPD_GEN1;
}
#endif
sili_os_sleep(20);
sili_pwrite(ap, SILI_PREG_SERR, -1);
sili_pwrite(ap, SILI_PREG_INT_ENABLE, SILI_PREG_INT_PHYRDYCHG |
SILI_PREG_INT_DEVEXCHG);
}
int
sili_port_signature(struct sili_port *ap, struct ata_port *at, u_int32_t sig)
{
if (bootverbose)
kprintf("%s: sig %08x\n", ATANAME(ap, at), sig);
if ((sig & 0xffff0000) == (SATA_SIGNATURE_ATAPI & 0xffff0000)) {
return(ATA_PORT_T_ATAPI);
} else if ((sig & 0xffff0000) ==
(SATA_SIGNATURE_PORT_MULTIPLIER & 0xffff0000)) {
return(ATA_PORT_T_PM);
} else {
return(ATA_PORT_T_DISK);
}
}
int
sili_load_prb(struct sili_ccb *ccb)
{
struct sili_port *ap = ccb->ccb_port;
struct sili_softc *sc = ap->ap_sc;
struct ata_xfer *xa = &ccb->ccb_xa;
struct sili_prb *prb = ccb->ccb_prb;
struct sili_sge *sge;
bus_dmamap_t dmap = ccb->ccb_dmamap;
int error;
prb->prb_xfer_count = 0;
prb->prb_control = 0;
prb->prb_override = 0;
sge = (ccb->ccb_xa.flags & ATA_F_PACKET) ?
&prb->prb_sge_packet : &prb->prb_sge_normal;
if (xa->datalen == 0) {
sge->sge_flags = SILI_SGE_FLAGS_TRM | SILI_SGE_FLAGS_DRD;
sge->sge_count = 0;
return (0);
}
if (ccb->ccb_xa.flags & ATA_F_READ)
prb->prb_control |= SILI_PRB_CTRL_READ;
if (ccb->ccb_xa.flags & ATA_F_WRITE)
prb->prb_control |= SILI_PRB_CTRL_WRITE;
sge->sge_flags = SILI_SGE_FLAGS_LNK;
sge->sge_count = 0;
sge->sge_paddr = ccb->ccb_prb_paddr +
offsetof(struct sili_prb, prb_sge[0]);
error = bus_dmamap_load(sc->sc_tag_data, dmap,
xa->data, xa->datalen,
sili_load_prb_callback,
ccb,
((xa->flags & ATA_F_NOWAIT) ?
BUS_DMA_NOWAIT : BUS_DMA_WAITOK));
if (error != 0) {
kprintf("%s: error %d loading dmamap\n", PORTNAME(ap), error);
return (1);
}
bus_dmamap_sync(sc->sc_tag_data, dmap,
(xa->flags & ATA_F_READ) ?
BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
return (0);
}
static
void
sili_load_prb_callback(void *info, bus_dma_segment_t *segs, int nsegs,
int error)
{
struct sili_ccb *ccb = info;
struct sili_sge *sge;
int sgi;
KKASSERT(nsegs <= SILI_MAX_SGET);
sgi = 0;
sge = &ccb->ccb_prb->prb_sge[0];
while (nsegs) {
if ((sgi & 3) == 3) {
sge->sge_paddr = htole64(ccb->ccb_prb_paddr +
offsetof(struct sili_prb,
prb_sge[sgi + 1]));
sge->sge_count = 0;
sge->sge_flags = SILI_SGE_FLAGS_LNK;
} else {
sge->sge_paddr = htole64(segs->ds_addr);
sge->sge_count = htole32(segs->ds_len);
sge->sge_flags = 0;
--nsegs;
++segs;
}
++sge;
++sgi;
}
--sge;
sge->sge_flags |= SILI_SGE_FLAGS_TRM;
}
void
sili_unload_prb(struct sili_ccb *ccb)
{
struct sili_port *ap = ccb->ccb_port;
struct sili_softc *sc = ap->ap_sc;
struct ata_xfer *xa = &ccb->ccb_xa;
bus_dmamap_t dmap = ccb->ccb_dmamap;
if (xa->datalen != 0) {
bus_dmamap_sync(sc->sc_tag_data, dmap,
(xa->flags & ATA_F_READ) ?
BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_tag_data, dmap);
if (ccb->ccb_xa.flags & ATA_F_NCQ)
xa->resid = 0;
else
xa->resid = xa->datalen -
le32toh(ccb->ccb_prb->prb_xfer_count);
}
}
int
sili_poll(struct sili_ccb *ccb, int timeout,
void (*timeout_fn)(struct sili_ccb *))
{
struct sili_port *ap = ccb->ccb_port;
if (ccb->ccb_port->ap_state == AP_S_FATAL_ERROR) {
ccb->ccb_xa.state = ATA_S_ERROR;
return(ccb->ccb_xa.state);
}
KKASSERT((ap->ap_expired & (1 << ccb->ccb_slot)) == 0);
sili_start(ccb);
do {
sili_port_intr(ap, 1);
switch(ccb->ccb_xa.state) {
case ATA_S_ONCHIP:
timeout -= sili_os_softsleep();
break;
case ATA_S_PENDING:
#if 0
if (xxx AP_F_EXCLUSIVE_ACCESS)
timeout -= sili_os_softsleep();
else
#endif
sili_os_softsleep();
sili_check_active_timeouts(ap);
break;
default:
return (ccb->ccb_xa.state);
}
} while (timeout > 0);
if (ap->ap_probe != ATA_PROBE_NEED_HARD_RESET) {
kprintf("%s: Poll timeout slot %d\n",
ATANAME(ap, ccb->ccb_xa.at),
ccb->ccb_slot);
}
timeout_fn(ccb);
return(ccb->ccb_xa.state);
}
void
sili_check_active_timeouts(struct sili_port *ap)
{
struct sili_ccb *ccb;
u_int32_t mask;
int tag;
mask = ap->ap_active;
while (mask) {
tag = ffs(mask) - 1;
mask &= ~(1 << tag);
ccb = &ap->ap_ccbs[tag];
if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_EXPIRED) {
sili_core_timeout(ccb, 0);
}
}
}
static
__inline
void
sili_start_timeout(struct sili_ccb *ccb)
{
if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_DESIRED) {
ccb->ccb_xa.flags |= ATA_F_TIMEOUT_RUNNING;
callout_reset(&ccb->ccb_timeout,
(ccb->ccb_xa.timeout * hz + 999) / 1000,
sili_ata_cmd_timeout_unserialized, ccb);
}
}
void
sili_start(struct sili_ccb *ccb)
{
struct sili_port *ap = ccb->ccb_port;
#if 0
struct sili_softc *sc = ap->ap_sc;
#endif
KKASSERT(ccb->ccb_xa.state == ATA_S_PENDING);
bus_dmamap_sync(ap->ap_dmamem_prbs->adm_tag,
ap->ap_dmamem_prbs->adm_map,
BUS_DMASYNC_PREWRITE);
sili_issue_pending_commands(ap, ccb);
}
void
sili_exclusive_access(struct sili_port *ap)
{
while (ap->ap_active) {
sili_port_intr(ap, 1);
sili_os_softsleep();
}
}
void
sili_issue_pending_commands(struct sili_port *ap, struct sili_ccb *ccb)
{
if (ccb) {
TAILQ_INSERT_TAIL(&ap->ap_ccb_pending, ccb, ccb_entry);
} else if (ap->ap_expired) {
return;
}
while ((ccb = TAILQ_FIRST(&ap->ap_ccb_pending)) != NULL) {
if ((sili_pread(ap, SILI_PREG_STATUS) &
SILI_PREG_STATUS_READY) == 0) {
kprintf("%s: slot %d NOT READY\n",
ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_slot);
sili_pwrite(ap, SILI_PREG_INT_ENABLE,
SILI_PREG_INT_READY);
break;
}
if (ap->ap_active & ~ap->ap_expired) {
if (ap->ap_run_flags &
(ATA_F_EXCLUSIVE | ATA_F_AUTOSENSE)) {
break;
}
if (ccb->ccb_xa.flags &
(ATA_F_EXCLUSIVE | ATA_F_AUTOSENSE)) {
break;
}
}
TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_ONCHIP;
ap->ap_active |= 1 << ccb->ccb_slot;
ap->ap_active_cnt++;
ap->ap_run_flags = ccb->ccb_xa.flags;
sili_pwrite(ap, SILI_PREG_CMDACT(ccb->ccb_slot),
(u_int32_t)ccb->ccb_prb_paddr);
sili_pwrite(ap, SILI_PREG_CMDACT(ccb->ccb_slot) + 4,
(u_int32_t)(ccb->ccb_prb_paddr >> 32));
sili_start_timeout(ccb);
}
}
void
sili_intr(void *arg)
{
struct sili_softc *sc = arg;
struct sili_port *ap;
u_int32_t gint;
int port;
if ((sc->sc_flags & SILI_F_INT_GOOD) == 0)
return;
gint = sili_read(sc, SILI_REG_GINT);
if (gint == 0 || gint == 0xffffffff)
return;
sili_write(sc, SILI_REG_GINT, gint);
while (gint & SILI_REG_GINT_PORTMASK) {
port = ffs(gint) - 1;
ap = sc->sc_ports[port];
if (ap) {
if (sili_os_lock_port_nb(ap) == 0) {
sili_port_intr(ap, 0);
sili_os_unlock_port(ap);
} else {
sili_port_interrupt_redisable(ap);
sili_os_signal_port_thread(ap, AP_SIGF_PORTINT);
}
}
gint &= ~(1 << port);
}
}
void
sili_port_thread_core(struct sili_port *ap, int mask)
{
sili_os_lock_port(ap);
if (mask & AP_SIGF_TIMEOUT) {
sili_check_active_timeouts(ap);
}
if (mask & AP_SIGF_PORTINT) {
sili_port_intr(ap, 1);
sili_port_interrupt_reenable(ap);
}
sili_os_unlock_port(ap);
}
void
sili_port_intr(struct sili_port *ap, int blockable)
{
struct sili_softc *sc = ap->ap_sc;
u_int32_t is;
int slot;
struct sili_ccb *ccb = NULL;
struct ata_port *ccb_at = NULL;
u_int32_t active;
u_int32_t finished;
const u_int32_t blockable_mask = SILI_PREG_IST_PHYRDYCHG |
SILI_PREG_IST_DEVEXCHG |
SILI_PREG_IST_CERROR |
SILI_PREG_IST_DECODE |
SILI_PREG_IST_CRC |
SILI_PREG_IST_HANDSHK;
const u_int32_t fatal_mask = SILI_PREG_IST_PHYRDYCHG |
SILI_PREG_IST_DEVEXCHG |
SILI_PREG_IST_DECODE |
SILI_PREG_IST_CRC |
SILI_PREG_IST_HANDSHK;
enum { NEED_NOTHING, NEED_HOTPLUG_INSERT,
NEED_HOTPLUG_REMOVE } need = NEED_NOTHING;
is = sili_pread(ap, SILI_PREG_INT_STATUS);
is &= SILI_PREG_IST_MASK;
if (is & SILI_PREG_IST_CCOMPLETE)
sili_pwrite(ap, SILI_PREG_INT_STATUS, SILI_PREG_IST_CCOMPLETE);
if (blockable == 0) {
if (ap->ap_state == AP_S_FATAL_ERROR) {
sili_port_interrupt_redisable(ap);
sili_os_signal_port_thread(ap, AP_SIGF_PORTINT);
return;
}
if (is & blockable_mask) {
sili_port_interrupt_redisable(ap);
sili_os_signal_port_thread(ap, AP_SIGF_PORTINT);
return;
}
}
if (is & SILI_PREG_IST_CERROR) {
u_int32_t error;
int target;
int resume = 1;
target = (sili_pread(ap, SILI_PREG_CONTEXT) >>
SILI_PREG_CONTEXT_PMPORT_SHIFT) &
SILI_PREG_CONTEXT_PMPORT_MASK;
sili_pwrite(ap, SILI_PREG_INT_STATUS, SILI_PREG_IST_CERROR);
active = ap->ap_active & ~ap->ap_expired;
error = sili_pread(ap, SILI_PREG_CERROR);
kprintf("%s.%d target error %d active=%08x hactive=%08x "
"SERR=%pb%i\n",
PORTNAME(ap), target, error,
active, sili_pread(ap, SILI_PREG_SLOTST),
SILI_PFMT_SERR, sili_pread(ap, SILI_PREG_SERR));
while (active) {
slot = ffs(active) - 1;
ccb = &ap->ap_ccbs[slot];
if ((ccb_at = ccb->ccb_xa.at) == NULL)
ccb_at = &ap->ap_ata[0];
if (target == ccb_at->at_target) {
if ((ccb->ccb_xa.flags & ATA_F_NCQ) &&
(error == SILI_PREG_CERROR_DEVICE ||
error == SILI_PREG_CERROR_SDBERROR)) {
ccb_at->at_features |= ATA_PORT_F_READLOG;
}
if (sili_core_timeout(ccb, 1) == 0)
resume = 0;
}
active &= ~(1 << slot);
}
if (resume)
sili_pwrite(ap, SILI_PREG_CTL_SET, SILI_PREG_CTL_RESUME);
}
if (sc->sc_flags & SILI_F_SSNTF) {
u_int32_t data;
const char *xstr;
data = sili_pread(ap, SILI_PREG_SNTF);
if (is & SILI_PREG_IST_SDB) {
sili_pwrite(ap, SILI_PREG_INT_STATUS,
SILI_PREG_IST_SDB);
is &= ~SILI_PREG_IST_SDB;
xstr = " (no SDBS!)";
} else {
xstr = "";
}
if (data) {
kprintf("%s: NOTIFY %08x%s\n",
PORTNAME(ap), data, xstr);
sili_pwrite(ap, SILI_PREG_SNTF, data);
sili_cam_changed(ap, NULL, -1);
}
}
if (is & (SILI_PREG_IST_PHYRDYCHG | SILI_PREG_IST_DEVEXCHG)) {
sili_pwrite(ap, SILI_PREG_SERR,
(SILI_PREG_SERR_DIAG_N | SILI_PREG_SERR_DIAG_X));
sili_pwrite(ap, SILI_PREG_INT_STATUS,
is & (SILI_PREG_IST_PHYRDYCHG | SILI_PREG_IST_DEVEXCHG));
is &= ~(SILI_PREG_IST_PHYRDYCHG | SILI_PREG_IST_DEVEXCHG);
kprintf("%s: Port change\n", PORTNAME(ap));
switch (sili_pread(ap, SILI_PREG_SSTS) & SILI_PREG_SSTS_DET) {
case SILI_PREG_SSTS_DET_DEV:
if (ap->ap_type == ATA_PORT_T_NONE &&
ap->ap_probe == ATA_PROBE_FAILED) {
need = NEED_HOTPLUG_INSERT;
goto fatal;
}
break;
default:
kprintf("%s: Device lost\n", PORTNAME(ap));
if (ap->ap_type != ATA_PORT_T_NONE) {
need = NEED_HOTPLUG_REMOVE;
goto fatal;
}
break;
}
}
if (is & fatal_mask) {
u_int32_t serr;
sili_pwrite(ap, SILI_PREG_INT_STATUS, is & fatal_mask);
serr = sili_pread(ap, SILI_PREG_SERR);
kprintf("%s: Unrecoverable errors (IS: %pb%i, SERR: %pb%i), "
"disabling port.\n",
PORTNAME(ap),
SILI_PFMT_INT_STATUS, is,
SILI_PFMT_SERR, serr
);
is &= ~fatal_mask;
goto fatal;
}
if (ap->ap_state == AP_S_FATAL_ERROR &&
(ap->ap_active & ~ap->ap_expired)) {
kprintf("%s: Fatal port error, expiring %08x\n",
PORTNAME(ap), ap->ap_active & ~ap->ap_expired);
fatal:
ap->ap_state = AP_S_FATAL_ERROR;
active = ap->ap_active & ~ap->ap_expired;
while (active) {
slot = ffs(active) - 1;
active &= ~(1 << slot);
ccb = &ap->ap_ccbs[slot];
sili_core_timeout(ccb, 1);
}
}
active = ap->ap_active & ~sili_pread(ap, SILI_PREG_SLOTST);
active &= ~ap->ap_expired;
finished = active;
while (active) {
slot = ffs(active) - 1;
ccb = &ap->ap_ccbs[slot];
DPRINTF(SILI_D_INTR, "%s: slot %d is complete%s\n",
PORTNAME(ap), slot, ccb->ccb_xa.state == ATA_S_ERROR ?
" (error)" : "");
active &= ~(1 << slot);
ap->ap_active &= ~(1 << ccb->ccb_slot);
--ap->ap_active_cnt;
if (ap->ap_expired & (1 << ccb->ccb_slot)) {
ap->ap_expired &= ~(1 << ccb->ccb_slot);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ccb->ccb_done(ccb);
ccb->ccb_xa.complete(&ccb->ccb_xa);
} else {
if (ccb->ccb_xa.state == ATA_S_ONCHIP) {
ccb->ccb_xa.state = ATA_S_COMPLETE;
if (ccb->ccb_xa.flags & ATA_F_AUTOSENSE) {
memcpy(ccb->ccb_xa.rfis,
&ccb->ccb_prb_lram->prb_d2h,
sizeof(ccb->ccb_prb_lram->prb_d2h));
if (ccb->ccb_xa.state == ATA_S_TIMEOUT)
ccb->ccb_xa.state = ATA_S_ERROR;
}
}
ccb->ccb_done(ccb);
}
}
if (is & SILI_PREG_IST_READY) {
is &= ~SILI_PREG_IST_READY;
sili_pwrite(ap, SILI_PREG_INT_DISABLE, SILI_PREG_INT_READY);
sili_pwrite(ap, SILI_PREG_INT_STATUS, SILI_PREG_IST_READY);
}
if (ap->ap_expired && ap->ap_active == ap->ap_expired) {
if (finished)
sili_port_reinit(ap);
} else {
sili_issue_pending_commands(ap, NULL);
}
switch(need) {
case NEED_HOTPLUG_INSERT:
kprintf("%s: HOTPLUG - Device inserted\n",
PORTNAME(ap));
ap->ap_probe = ATA_PROBE_NEED_INIT;
sili_cam_changed(ap, NULL, -1);
break;
case NEED_HOTPLUG_REMOVE:
kprintf("%s: HOTPLUG - Device removed\n",
PORTNAME(ap));
sili_port_hardstop(ap);
sili_cam_changed(ap, NULL, -1);
break;
default:
break;
}
}
struct sili_ccb *
sili_get_ccb(struct sili_port *ap)
{
struct sili_ccb *ccb;
lockmgr(&ap->ap_ccb_lock, LK_EXCLUSIVE);
ccb = TAILQ_FIRST(&ap->ap_ccb_free);
if (ccb != NULL) {
KKASSERT(ccb->ccb_xa.state == ATA_S_PUT);
TAILQ_REMOVE(&ap->ap_ccb_free, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_SETUP;
ccb->ccb_xa.at = NULL;
}
lockmgr(&ap->ap_ccb_lock, LK_RELEASE);
return (ccb);
}
void
sili_put_ccb(struct sili_ccb *ccb)
{
struct sili_port *ap = ccb->ccb_port;
lockmgr(&ap->ap_ccb_lock, LK_EXCLUSIVE);
ccb->ccb_xa.state = ATA_S_PUT;
++ccb->ccb_xa.serial;
TAILQ_INSERT_TAIL(&ap->ap_ccb_free, ccb, ccb_entry);
lockmgr(&ap->ap_ccb_lock, LK_RELEASE);
}
struct sili_ccb *
sili_get_err_ccb(struct sili_port *ap)
{
struct sili_ccb *err_ccb;
KKASSERT((ap->ap_flags & AP_F_ERR_CCB_RESERVED) == 0);
ap->ap_flags |= AP_F_ERR_CCB_RESERVED;
err_ccb = ap->ap_err_ccb;
KKASSERT(err_ccb != NULL);
err_ccb->ccb_xa.flags = 0;
err_ccb->ccb_done = sili_empty_done;
return err_ccb;
}
void
sili_put_err_ccb(struct sili_ccb *ccb)
{
struct sili_port *ap = ccb->ccb_port;
KKASSERT((ap->ap_flags & AP_F_ERR_CCB_RESERVED) != 0);
KKASSERT(ccb == ap->ap_err_ccb);
ap->ap_flags &= ~AP_F_ERR_CCB_RESERVED;
}
void
sili_port_read_ncq_error(struct sili_port *ap, int target)
{
struct sili_ccb *ccb;
struct ata_fis_h2d *fis;
int status;
DPRINTF(SILI_D_VERBOSE, "%s: read log page\n", PORTNAME(ap));
ccb = sili_get_err_ccb(ap);
ccb->ccb_done = sili_empty_done;
ccb->ccb_xa.flags = ATA_F_NOWAIT | ATA_F_READ | ATA_F_POLL;
ccb->ccb_xa.data = ap->ap_err_scratch;
ccb->ccb_xa.datalen = 512;
ccb->ccb_xa.complete = sili_dummy_done;
ccb->ccb_xa.at = &ap->ap_ata[target];
fis = &ccb->ccb_prb->prb_h2d;
bzero(fis, sizeof(*fis));
fis->type = ATA_FIS_TYPE_H2D;
fis->flags = ATA_H2D_FLAGS_CMD | target;
fis->command = ATA_C_READ_LOG_EXT;
fis->lba_low = 0x10;
fis->sector_count = 1;
fis->sector_count_exp = 0;
fis->lba_mid = 0;
fis->lba_mid_exp = 0;
fis->device = 0;
if (sili_load_prb(ccb) != 0) {
status = ATA_S_ERROR;
} else {
ccb->ccb_xa.state = ATA_S_PENDING;
status = sili_poll(ccb, 1000, sili_quick_timeout);
}
if (status != ATA_S_COMPLETE) {
kprintf("%s: log page read failed, slot %d was still active.\n",
ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_slot);
}
sili_unload_prb(ccb);
sili_put_err_ccb(ccb);
if (status == ATA_S_COMPLETE) {
struct ata_log_page_10h *log;
int err_slot;
log = (struct ata_log_page_10h *)ap->ap_err_scratch;
if (log->err_regs.type & ATA_LOG_10H_TYPE_NOTQUEUED) {
} else {
err_slot = log->err_regs.type &
ATA_LOG_10H_TYPE_TAG_MASK;
ccb = &ap->ap_ccbs[err_slot];
if (ap->ap_expired & (1 << ccb->ccb_slot)) {
kprintf("%s: read NCQ error page slot=%d\n",
ATANAME(ap, ccb->ccb_xa.at), err_slot
);
memcpy(&ccb->ccb_prb->prb_d2h, &log->err_regs,
sizeof(struct ata_fis_d2h));
ccb->ccb_prb->prb_d2h.type = ATA_FIS_TYPE_D2H;
ccb->ccb_prb->prb_d2h.flags = 0;
if (ccb->ccb_xa.state == ATA_S_TIMEOUT)
ccb->ccb_xa.state = ATA_S_ERROR;
} else {
kprintf("%s: read NCQ error page slot=%d, "
"slot does not match any cmds\n",
ATANAME(ccb->ccb_port, ccb->ccb_xa.at),
err_slot
);
}
}
}
}
struct sili_dmamem *
sili_dmamem_alloc(struct sili_softc *sc, bus_dma_tag_t tag)
{
struct sili_dmamem *adm;
int error;
adm = kmalloc(sizeof(*adm), M_DEVBUF, M_INTWAIT | M_ZERO);
error = bus_dmamem_alloc(tag, (void **)&adm->adm_kva,
BUS_DMA_ZERO, &adm->adm_map);
if (error == 0) {
adm->adm_tag = tag;
error = bus_dmamap_load(tag, adm->adm_map,
adm->adm_kva,
bus_dma_tag_getmaxsize(tag),
sili_dmamem_saveseg, &adm->adm_busaddr,
0);
}
if (error) {
if (adm->adm_map) {
bus_dmamap_destroy(tag, adm->adm_map);
adm->adm_map = NULL;
adm->adm_tag = NULL;
adm->adm_kva = NULL;
}
kfree(adm, M_DEVBUF);
adm = NULL;
}
return (adm);
}
static
void
sili_dmamem_saveseg(void *info, bus_dma_segment_t *segs, int nsegs, int error)
{
KKASSERT(error == 0);
KKASSERT(nsegs == 1);
*(bus_addr_t *)info = segs->ds_addr;
}
void
sili_dmamem_free(struct sili_softc *sc, struct sili_dmamem *adm)
{
if (adm->adm_map) {
bus_dmamap_unload(adm->adm_tag, adm->adm_map);
bus_dmamap_destroy(adm->adm_tag, adm->adm_map);
adm->adm_map = NULL;
adm->adm_tag = NULL;
adm->adm_kva = NULL;
}
kfree(adm, M_DEVBUF);
}
u_int32_t
sili_read(struct sili_softc *sc, bus_size_t r)
{
bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4,
BUS_SPACE_BARRIER_READ);
return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, r));
}
void
sili_write(struct sili_softc *sc, bus_size_t r, u_int32_t v)
{
bus_space_write_4(sc->sc_iot, sc->sc_ioh, r, v);
bus_space_barrier(sc->sc_iot, sc->sc_ioh, r, 4,
BUS_SPACE_BARRIER_WRITE);
}
u_int32_t
sili_pread(struct sili_port *ap, bus_size_t r)
{
bus_space_barrier(ap->ap_sc->sc_iot, ap->ap_ioh, r, 4,
BUS_SPACE_BARRIER_READ);
return (bus_space_read_4(ap->ap_sc->sc_iot, ap->ap_ioh, r));
}
void
sili_pwrite(struct sili_port *ap, bus_size_t r, u_int32_t v)
{
bus_space_write_4(ap->ap_sc->sc_iot, ap->ap_ioh, r, v);
bus_space_barrier(ap->ap_sc->sc_iot, ap->ap_ioh, r, 4,
BUS_SPACE_BARRIER_WRITE);
}
int
sili_pwait_eq(struct sili_port *ap, int timeout,
bus_size_t r, u_int32_t mask, u_int32_t target)
{
int t;
for (t = 0; t < 100; ++t) {
if ((sili_pread(ap, r) & mask) == target)
return (0);
sili_os_hardsleep(1);
}
do {
timeout -= sili_os_softsleep();
if ((sili_pread(ap, r) & mask) == target)
return (0);
} while (timeout > 0);
return (1);
}
int
sili_wait_ne(struct sili_softc *sc, bus_size_t r, u_int32_t mask,
u_int32_t target)
{
int t;
for (t = 0; t < 100; ++t) {
if ((sili_read(sc, r) & mask) != target)
return (0);
sili_os_hardsleep(1);
}
t = 1000;
do {
t -= sili_os_softsleep();
if ((sili_read(sc, r) & mask) != target)
return (0);
} while (t > 0);
return (1);
}
struct ata_xfer *
sili_ata_get_xfer(struct sili_port *ap, struct ata_port *at)
{
struct sili_ccb *ccb;
ccb = sili_get_ccb(ap);
if (ccb == NULL) {
DPRINTF(SILI_D_XFER, "%s: sili_ata_get_xfer: NULL ccb\n",
PORTNAME(ap));
return (NULL);
}
DPRINTF(SILI_D_XFER, "%s: sili_ata_get_xfer got slot %d\n",
PORTNAME(ap), ccb->ccb_slot);
bzero(ccb->ccb_xa.fis, sizeof(*ccb->ccb_xa.fis));
ccb->ccb_xa.at = at;
ccb->ccb_xa.fis->type = ATA_FIS_TYPE_H2D;
return (&ccb->ccb_xa);
}
void
sili_ata_put_xfer(struct ata_xfer *xa)
{
struct sili_ccb *ccb = (struct sili_ccb *)xa;
DPRINTF(SILI_D_XFER, "sili_ata_put_xfer slot %d\n", ccb->ccb_slot);
sili_put_ccb(ccb);
}
int
sili_ata_cmd(struct ata_xfer *xa)
{
struct sili_ccb *ccb = (struct sili_ccb *)xa;
KKASSERT(xa->state == ATA_S_SETUP);
if (ccb->ccb_port->ap_state == AP_S_FATAL_ERROR)
goto failcmd;
#if 0
kprintf("%s: started std command %pb%i ccb %d ccb_at %p %d\n",
ATANAME(ccb->ccb_port, ccb->ccb_xa.at),
SILI_PFMT_CMD, sili_pread(ccb->ccb_port, SILI_PREG_CMD),
ccb->ccb_slot,
ccb->ccb_xa.at,
ccb->ccb_xa.at ? ccb->ccb_xa.at->at_target : -1);
#endif
ccb->ccb_done = sili_ata_cmd_done;
if (sili_load_prb(ccb) != 0)
goto failcmd;
xa->state = ATA_S_PENDING;
if (xa->flags & ATA_F_POLL)
return (sili_poll(ccb, xa->timeout, sili_ata_cmd_timeout));
crit_enter();
KKASSERT((xa->flags & ATA_F_TIMEOUT_EXPIRED) == 0);
xa->flags |= ATA_F_TIMEOUT_DESIRED;
sili_start(ccb);
crit_exit();
return (xa->state);
failcmd:
crit_enter();
xa->state = ATA_S_ERROR;
xa->complete(xa);
crit_exit();
return (ATA_S_ERROR);
}
static void
sili_ata_cmd_done(struct sili_ccb *ccb)
{
struct ata_xfer *xa = &ccb->ccb_xa;
int serial;
if (xa->flags & ATA_F_TIMEOUT_RUNNING) {
serial = ccb->ccb_xa.serial;
callout_cancel(&ccb->ccb_timeout);
if (serial != ccb->ccb_xa.serial) {
kprintf("%s: Warning: timeout race ccb %p\n",
PORTNAME(ccb->ccb_port), ccb);
return;
}
xa->flags &= ~ATA_F_TIMEOUT_RUNNING;
}
xa->flags &= ~(ATA_F_TIMEOUT_DESIRED | ATA_F_TIMEOUT_EXPIRED);
KKASSERT(xa->state != ATA_S_ONCHIP);
sili_unload_prb(ccb);
if (xa->state != ATA_S_TIMEOUT)
xa->complete(xa);
}
static void
sili_ata_cmd_timeout_unserialized(void *arg)
{
struct sili_ccb *ccb = arg;
struct sili_port *ap = ccb->ccb_port;
ccb->ccb_xa.flags &= ~ATA_F_TIMEOUT_RUNNING;
ccb->ccb_xa.flags |= ATA_F_TIMEOUT_EXPIRED;
sili_os_signal_port_thread(ap, AP_SIGF_TIMEOUT);
}
void
sili_ata_cmd_timeout(struct sili_ccb *ccb)
{
sili_core_timeout(ccb, 0);
}
static
int
sili_core_timeout(struct sili_ccb *ccb, int really_error)
{
struct ata_xfer *xa = &ccb->ccb_xa;
struct sili_port *ap = ccb->ccb_port;
struct ata_port *at;
at = ccb->ccb_xa.at;
kprintf("%s: CMD %s state=%d slot=%d\n"
"\t active=%08x\n"
"\texpired=%08x\n"
"\thactive=%08x\n",
ATANAME(ap, at),
(really_error ? "ERROR" : "TIMEOUT"),
ccb->ccb_xa.state, ccb->ccb_slot,
ap->ap_active,
ap->ap_expired,
sili_pread(ap, SILI_PREG_SLOTST)
);
KKASSERT(xa->flags & (ATA_F_POLL | ATA_F_TIMEOUT_DESIRED |
ATA_F_TIMEOUT_RUNNING));
if (xa->flags & ATA_F_TIMEOUT_RUNNING) {
callout_stop(&ccb->ccb_timeout);
xa->flags &= ~ATA_F_TIMEOUT_RUNNING;
}
xa->flags &= ~ATA_F_TIMEOUT_EXPIRED;
if (ccb->ccb_xa.state == ATA_S_PENDING) {
TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ccb->ccb_done(ccb);
xa->complete(xa);
sili_issue_pending_commands(ap, NULL);
return(1);
}
if (ccb->ccb_xa.state != ATA_S_ONCHIP) {
kprintf("%s: Unexpected state during timeout: %d\n",
ATANAME(ap, at), ccb->ccb_xa.state);
return(1);
}
ap->ap_expired |= 1 << ccb->ccb_slot;
if (ap->ap_active != ap->ap_expired) {
kprintf("%s: Deferred timeout until its safe, slot %d\n",
ATANAME(ap, at), ccb->ccb_slot);
return(1);
}
sili_port_reinit(ap);
return(0);
}
void
sili_quick_timeout(struct sili_ccb *ccb)
{
struct sili_port *ap = ccb->ccb_port;
switch (ccb->ccb_xa.state) {
case ATA_S_PENDING:
TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
break;
case ATA_S_ONCHIP:
KKASSERT((ap->ap_active & ~ap->ap_expired) ==
(1 << ccb->ccb_slot));
ccb->ccb_xa.state = ATA_S_TIMEOUT;
ap->ap_active &= ~(1 << ccb->ccb_slot);
KKASSERT(ap->ap_active_cnt > 0);
--ap->ap_active_cnt;
sili_port_reinit(ap);
break;
default:
panic("%s: sili_quick_timeout: ccb in bad state %d",
ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_xa.state);
}
}
static void
sili_dummy_done(struct ata_xfer *xa)
{
}
static void
sili_empty_done(struct sili_ccb *ccb)
{
}