src/sys/dev/ic/sili.c

1883 lines
48 KiB
C

/* $OpenBSD: sili.c,v 1.61 2022/04/09 20:10:26 naddy Exp $ */
/*
* Copyright (c) 2007 David Gwynne <dlg@openbsd.org>
* Copyright (c) 2010 Conformal Systems LLC <info@conformal.com>
* Copyright (c) 2010 Jonathan Matthew <jonathan@d14n.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/timeout.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <machine/bus.h>
#include <dev/ata/atascsi.h>
#include <dev/ata/pmreg.h>
#include <dev/ic/silireg.h>
#include <dev/ic/silivar.h>
/* use SILI_DEBUG for dmesg spam */
#define NO_SILI_DEBUG
#ifdef SILI_DEBUG
#define SILI_D_VERBOSE (1<<0)
#define SILI_D_INTR (1<<1)
int silidebug = SILI_D_VERBOSE;
#define DPRINTF(m, a...) do { if ((m) & silidebug) printf(a); } while (0)
#else
#define DPRINTF(m, a...)
#endif
/* these can be used to simulate read and write errors on specific PMP ports */
#undef SILI_ERROR_TEST
int sili_error_pmp_ports = 0; /* bitmask containing ports to fail*/
int sili_error_test_inv_p = 500; /* 1/P(error) */
int sili_error_restart_type = SILI_PREG_PCS_PORTINIT; /* or _DEVRESET */
struct cfdriver sili_cd = {
NULL, "sili", DV_DULL
};
/* wrapper around dma memory */
struct sili_dmamem {
bus_dmamap_t sdm_map;
bus_dma_segment_t sdm_seg;
size_t sdm_size;
caddr_t sdm_kva;
};
#define SILI_DMA_MAP(_sdm) ((_sdm)->sdm_map)
#define SILI_DMA_DVA(_sdm) ((_sdm)->sdm_map->dm_segs[0].ds_addr)
#define SILI_DMA_KVA(_sdm) ((_sdm)->sdm_kva)
struct sili_dmamem *sili_dmamem_alloc(struct sili_softc *, bus_size_t,
bus_size_t);
void sili_dmamem_free(struct sili_softc *,
struct sili_dmamem *);
/* per port goo */
struct sili_ccb;
/* size of scratch space for use in error recovery. */
#define SILI_SCRATCH_LEN 512 /* must be at least 1 sector */
struct sili_port {
struct sili_softc *sp_sc;
bus_space_handle_t sp_ioh;
struct sili_ccb *sp_ccbs;
struct sili_dmamem *sp_cmds;
struct sili_dmamem *sp_scratch;
TAILQ_HEAD(, sili_ccb) sp_free_ccbs;
struct mutex sp_free_ccb_mtx;
volatile u_int32_t sp_active;
TAILQ_HEAD(, sili_ccb) sp_active_ccbs;
TAILQ_HEAD(, sili_ccb) sp_deferred_ccbs;
int sp_port;
int sp_pmp_ports;
int sp_active_pmp_ports;
int sp_pmp_error_recovery; /* port bitmask */
volatile u_int32_t sp_err_active; /* cmd bitmask */
volatile u_int32_t sp_err_cmds; /* cmd bitmask */
#ifdef SILI_DEBUG
char sp_name[16];
#define PORTNAME(_sp) ((_sp)->sp_name)
#else
#define PORTNAME(_sp) DEVNAME((_sp)->sp_sc)
#endif
};
int sili_ports_alloc(struct sili_softc *);
void sili_ports_free(struct sili_softc *);
/* ccb shizz */
/*
* the dma memory for each command will be made up of a prb followed by
* 7 sgts, this is a neat 512 bytes.
*/
#define SILI_CMD_LEN 512
/*
* you can fit 22 sge's into 7 sgts and a prb:
* there's 1 sgl in an atapi prb (two in the ata one, but we cant over
* advertise), but that's needed for the chain element. you get three sges
* per sgt cos you lose the 4th sge for the chaining, but you keep it in
* the last sgt. so 3 x 6 + 4 is 22.
*/
#define SILI_DMA_SEGS 22
struct sili_ccb {
struct ata_xfer ccb_xa;
void *ccb_cmd;
u_int64_t ccb_cmd_dva;
bus_dmamap_t ccb_dmamap;
struct sili_port *ccb_port;
TAILQ_ENTRY(sili_ccb) ccb_entry;
};
int sili_ccb_alloc(struct sili_port *);
void sili_ccb_free(struct sili_port *);
struct sili_ccb *sili_get_ccb(struct sili_port *);
void sili_put_ccb(struct sili_ccb *);
/* bus space ops */
u_int32_t sili_read(struct sili_softc *, bus_size_t);
void sili_write(struct sili_softc *, bus_size_t, u_int32_t);
u_int32_t sili_pread(struct sili_port *, bus_size_t);
void sili_pwrite(struct sili_port *, bus_size_t, u_int32_t);
int sili_pwait_eq(struct sili_port *, bus_size_t,
u_int32_t, u_int32_t, int);
int sili_pwait_ne(struct sili_port *, bus_size_t,
u_int32_t, u_int32_t, int);
/* command handling */
void sili_post_direct(struct sili_port *, u_int,
void *, size_t buflen);
void sili_post_indirect(struct sili_port *,
struct sili_ccb *);
void sili_pread_fis(struct sili_port *, u_int,
struct ata_fis_d2h *);
u_int32_t sili_signature(struct sili_port *, u_int);
u_int32_t sili_port_softreset(struct sili_port *sp);
int sili_load(struct sili_ccb *, struct sili_sge *, int);
void sili_unload(struct sili_ccb *);
int sili_poll(struct sili_ccb *, int, void (*)(void *));
void sili_start(struct sili_port *, struct sili_ccb *);
int sili_read_ncq_error(struct sili_port *, int *, int);
int sili_pmp_port_start_error_recovery(struct sili_port *,
int);
void sili_pmp_port_do_error_recovery(struct sili_port *,
int, u_int32_t *);
void sili_port_clear_commands(struct sili_port *sp);
/* pmp operations */
int sili_pmp_read(struct sili_port *, int, int,
u_int32_t *);
int sili_pmp_write(struct sili_port *, int, int, u_int32_t);
int sili_pmp_phy_status(struct sili_port *, int,
u_int32_t *);
int sili_pmp_identify(struct sili_port *, int *);
/* port interrupt handler */
u_int32_t sili_port_intr(struct sili_port *, int);
/* atascsi interface */
int sili_ata_probe(void *, int, int);
void sili_ata_free(void *, int, int);
struct ata_xfer *sili_ata_get_xfer(void *, int);
void sili_ata_put_xfer(struct ata_xfer *);
void sili_ata_cmd(struct ata_xfer *);
int sili_pmp_portreset(struct sili_softc *, int, int);
int sili_pmp_softreset(struct sili_softc *, int, int);
#ifdef SILI_ERROR_TEST
void sili_simulate_error(struct sili_ccb *ccb,
int *need_restart, int *err_port);
#endif
const struct atascsi_methods sili_atascsi_methods = {
sili_ata_probe,
sili_ata_free,
sili_ata_get_xfer,
sili_ata_put_xfer,
sili_ata_cmd
};
/* completion paths */
void sili_ata_cmd_done(struct sili_ccb *, int);
void sili_ata_cmd_timeout(void *);
void sili_dummy_done(struct ata_xfer *);
void sili_pmp_op_timeout(void *);
int
sili_attach(struct sili_softc *sc)
{
struct atascsi_attach_args aaa;
printf("\n");
if (sili_ports_alloc(sc) != 0) {
/* error already printed by sili_port_alloc */
return (1);
}
/* bounce the controller */
sili_write(sc, SILI_REG_GC, SILI_REG_GC_GR);
sili_write(sc, SILI_REG_GC, 0x0);
bzero(&aaa, sizeof(aaa));
aaa.aaa_cookie = sc;
aaa.aaa_methods = &sili_atascsi_methods;
aaa.aaa_minphys = NULL;
aaa.aaa_nports = sc->sc_nports;
aaa.aaa_ncmds = SILI_MAX_CMDS;
aaa.aaa_capability = ASAA_CAP_NCQ | ASAA_CAP_PMP_NCQ;
sc->sc_atascsi = atascsi_attach(&sc->sc_dev, &aaa);
return (0);
}
int
sili_detach(struct sili_softc *sc, int flags)
{
int rv;
if (sc->sc_atascsi != NULL) {
rv = atascsi_detach(sc->sc_atascsi, flags);
if (rv != 0)
return (rv);
}
if (sc->sc_ports != NULL)
sili_ports_free(sc);
return (0);
}
void
sili_resume(struct sili_softc *sc)
{
int i, j;
/* bounce the controller */
sili_write(sc, SILI_REG_GC, SILI_REG_GC_GR);
sili_write(sc, SILI_REG_GC, 0x0);
for (i = 0; i < sc->sc_nports; i++) {
if (sili_ata_probe(sc, i, 0) == ATA_PORT_T_PM) {
struct sili_port *sp = &sc->sc_ports[i];
for (j = 0; j < sp->sp_pmp_ports; j++) {
sili_ata_probe(sc, i, j);
}
}
}
}
int
sili_pmp_port_start_error_recovery(struct sili_port *sp, int err_port)
{
struct sili_ccb *ccb;
sp->sp_pmp_error_recovery |= (1 << err_port);
/* create a bitmask of active commands on non-error ports */
sp->sp_err_active = 0;
TAILQ_FOREACH(ccb, &sp->sp_active_ccbs, ccb_entry) {
int bit = (1 << ccb->ccb_xa.pmp_port);
if ((sp->sp_pmp_error_recovery & bit) == 0) {
DPRINTF(SILI_D_VERBOSE, "%s: slot %d active on port "
"%d\n", PORTNAME(sp), ccb->ccb_xa.tag,
ccb->ccb_xa.pmp_port);
sp->sp_err_active |= (1 << ccb->ccb_xa.tag);
}
}
if (sp->sp_err_active == 0) {
DPRINTF(SILI_D_VERBOSE, "%s: no other PMP ports active\n",
PORTNAME(sp));
sp->sp_pmp_error_recovery = 0;
return (0);
}
/* set port resume */
sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_RESUME);
DPRINTF(SILI_D_VERBOSE, "%s: beginning error recovery (port %d); "
"error port mask %x, active slot mask %x\n", PORTNAME(sp), err_port,
sp->sp_pmp_error_recovery, sp->sp_err_active);
return (1);
}
void
sili_port_clear_commands(struct sili_port *sp)
{
int port;
DPRINTF(SILI_D_VERBOSE, "%s: clearing active commands\n",
PORTNAME(sp));
/* clear port resume */
sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_RESUME);
delay(10000);
/* clear port status and port active for all ports */
for (port = 0; port < 16; port++) {
sili_pwrite(sp, SILI_PREG_PMP_STATUS(port), 0);
sili_pwrite(sp, SILI_PREG_PMP_QACTIVE(port), 0);
}
}
void
sili_pmp_port_do_error_recovery(struct sili_port *sp, int slot,
u_int32_t *need_restart)
{
if (sp->sp_pmp_error_recovery == 0) {
return;
}
/* have all outstanding commands finished yet? */
if (sp->sp_err_active != 0) {
DPRINTF(SILI_D_VERBOSE, "%s: PMP error recovery still waiting "
"for %x\n", PORTNAME(sp), sp->sp_err_active);
*need_restart = 0;
return;
}
sili_port_clear_commands(sp);
/* get the main error recovery code to reset the port and
* resubmit commands. it will also reset the error recovery flags.
*/
*need_restart = SILI_PREG_PCS_PORTINIT;
DPRINTF(SILI_D_VERBOSE, "%s: PMP error recovery complete\n",
PORTNAME(sp));
}
#ifdef SILI_ERROR_TEST
void
sili_simulate_error(struct sili_ccb *ccb, int *need_restart, int *err_port)
{
struct sili_port *sp = ccb->ccb_port;
if (*need_restart == 0 &&
((1 << ccb->ccb_xa.pmp_port) & sili_error_pmp_ports)) {
switch (ccb->ccb_xa.fis->command) {
case ATA_C_WRITE_FPDMA:
case ATA_C_READ_FPDMA:
case ATA_C_WRITEDMA_EXT:
case ATA_C_READDMA_EXT:
case ATA_C_WRITEDMA:
case ATA_C_READDMA:
if (arc4random_uniform(sili_error_test_inv_p) == 0) {
printf("%s: faking error on slot %d\n",
PORTNAME(sp), ccb->ccb_xa.tag);
ccb->ccb_xa.state = ATA_S_ERROR;
*need_restart = sili_error_restart_type;
*err_port = ccb->ccb_xa.pmp_port;
ccb->ccb_port->sp_err_cmds |=
(1 << ccb->ccb_xa.tag);
}
break;
default:
/* leave other commands alone, we only want to mess
* with normal read/write ops
*/
break;
}
}
}
#endif
u_int32_t
sili_port_intr(struct sili_port *sp, int timeout_slot)
{
u_int32_t is, pss_saved, pss_masked;
u_int32_t processed = 0, need_restart = 0;
u_int32_t err_port = 0;
int slot;
struct sili_ccb *ccb;
is = sili_pread(sp, SILI_PREG_IS);
pss_saved = sili_pread(sp, SILI_PREG_PSS); /* reading acks CMDCOMP */
#ifdef SILI_DEBUG
if ((pss_saved & SILI_PREG_PSS_ALL_SLOTS) != sp->sp_active ||
((is >> 16) & ~SILI_PREG_IS_CMDCOMP)) {
DPRINTF(SILI_D_INTR, "%s: IS: 0x%08x (0x%b), PSS: %08x, "
"active: %08x\n", PORTNAME(sp), is, is >> 16, SILI_PFMT_IS,
pss_saved, sp->sp_active);
}
#endif
/* Only interested in slot status bits. */
pss_saved &= SILI_PREG_PSS_ALL_SLOTS;
if (is & SILI_PREG_IS_CMDERR) {
int err_slot, err_code;
u_int32_t sactive = 0;
sili_pwrite(sp, SILI_PREG_IS, SILI_PREG_IS_CMDERR);
err_slot = SILI_PREG_PCS_ACTIVE(sili_pread(sp, SILI_PREG_PCS));
err_code = sili_pread(sp, SILI_PREG_CE);
ccb = &sp->sp_ccbs[err_slot];
switch (err_code) {
case SILI_PREG_CE_DEVICEERROR:
case SILI_PREG_CE_DATAFISERROR:
/* Extract error from command slot in LRAM. */
sili_pread_fis(sp, err_slot, &ccb->ccb_xa.rfis);
err_port = ccb->ccb_xa.pmp_port;
break;
case SILI_PREG_CE_SDBERROR:
if (sp->sp_pmp_ports > 0) {
/* get the PMP port number for the error */
err_port = (sili_pread(sp, SILI_PREG_CONTEXT)
>> SILI_PREG_CONTEXT_PMPORT_SHIFT) &
SILI_PREG_CONTEXT_PMPORT_MASK;
DPRINTF(SILI_D_VERBOSE, "%s: error port is "
"%d\n", PORTNAME(sp), err_port);
/* were there any NCQ commands active for
* the port?
*/
sactive = sili_pread(sp,
SILI_PREG_PMP_QACTIVE(err_port));
DPRINTF(SILI_D_VERBOSE, "%s: error SActive "
"%x\n", PORTNAME(sp), sactive);
if (sactive == 0)
break;
} else {
/* No NCQ commands active? Treat as a normal
* error.
*/
sactive = sili_pread(sp, SILI_PREG_SACT);
if (sactive == 0)
break;
}
/* Extract real NCQ error slot & RFIS from
* log page.
*/
if (!sili_read_ncq_error(sp, &err_slot, err_port)) {
/* got real err_slot */
DPRINTF(SILI_D_VERBOSE, "%s.%d: error slot "
"%d\n", PORTNAME(sp), err_port, err_slot);
ccb = &sp->sp_ccbs[err_slot];
break;
}
DPRINTF(SILI_D_VERBOSE, "%s.%d: failed to get error "
"slot\n", PORTNAME(sp), err_port);
/* failed to get error or not NCQ */
/* FALLTHROUGH */
default:
/* All other error types are fatal. */
if (err_code != SILI_PREG_CE_SDBERROR) {
err_port = (sili_pread(sp, SILI_PREG_CONTEXT)
>> SILI_PREG_CONTEXT_PMPORT_SHIFT) &
SILI_PREG_CONTEXT_PMPORT_MASK;
}
printf("%s.%d: fatal error (%d), aborting active slots "
"(%08x) and resetting device.\n", PORTNAME(sp),
err_port, err_code, pss_saved);
while (pss_saved) {
slot = ffs(pss_saved) - 1;
pss_saved &= ~(1 << slot);
ccb = &sp->sp_ccbs[slot];
KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP);
ccb->ccb_xa.state = ATA_S_ERROR;
}
need_restart = SILI_PREG_PCS_DEVRESET;
goto fatal;
}
DPRINTF(SILI_D_VERBOSE, "%s.%d: %serror, code %d, slot %d, "
"active %08x\n", PORTNAME(sp), err_port,
sactive ? "NCQ " : "", err_code, err_slot, sp->sp_active);
/* Clear the failed command in saved PSS so cmd_done runs. */
pss_saved &= ~(1 << err_slot);
/* Track errored commands until we finish recovery */
sp->sp_err_cmds |= (1 << err_slot);
KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP);
ccb->ccb_xa.state = ATA_S_ERROR;
need_restart = SILI_PREG_PCS_PORTINIT;
}
fatal:
/* Process command timeout request only if command is still active. */
if (timeout_slot >= 0 && (pss_saved & (1 << timeout_slot))) {
DPRINTF(SILI_D_VERBOSE, "%s: timing out slot %d, active %08x\n",
PORTNAME(sp), timeout_slot, sp->sp_active);
/* Clear the failed command in saved PSS so cmd_done runs. */
pss_saved &= ~(1 << timeout_slot);
ccb = &sp->sp_ccbs[timeout_slot];
KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
/* Reinitialise the port and clear all active commands */
need_restart = SILI_PREG_PCS_PORTINIT;
err_port = ccb->ccb_xa.pmp_port;
sp->sp_err_cmds |= (1 << timeout_slot);
sili_port_clear_commands(sp);
}
/* Command slot is complete if its bit in PSS is 0 but 1 in active. */
pss_masked = ~pss_saved & sp->sp_active;
while (pss_masked) {
slot = ffs(pss_masked) - 1;
ccb = &sp->sp_ccbs[slot];
pss_masked &= ~(1 << slot);
/* copy the rfis into the ccb if we were asked for it */
if (ccb->ccb_xa.state == ATA_S_ONCHIP &&
ccb->ccb_xa.flags & ATA_F_GET_RFIS) {
sili_pread_fis(sp, slot, &ccb->ccb_xa.rfis);
}
#ifdef SILI_ERROR_TEST
/* introduce random errors on reads and writes for testing */
sili_simulate_error(ccb, &need_restart, &err_port);
#endif
DPRINTF(SILI_D_INTR, "%s: slot %d is complete%s%s\n",
PORTNAME(sp), slot, ccb->ccb_xa.state == ATA_S_ERROR ?
" (error)" : (ccb->ccb_xa.state == ATA_S_TIMEOUT ?
" (timeout)" : ""),
ccb->ccb_xa.flags & ATA_F_NCQ ? " (ncq)" : "");
sili_ata_cmd_done(ccb, need_restart);
processed |= 1 << slot;
sili_pmp_port_do_error_recovery(sp, slot, &need_restart);
}
if (need_restart) {
if (sp->sp_pmp_error_recovery) {
if (sp->sp_err_active != 0) {
DPRINTF(SILI_D_VERBOSE, "%s: still waiting for "
"non-error commands to finish; port mask "
"%x, slot mask %x\n", PORTNAME(sp),
sp->sp_pmp_error_recovery,
sp->sp_err_active);
return (processed);
}
} else if (timeout_slot < 0 && sp->sp_pmp_ports > 0) {
/* wait until all other commands have finished before
* attempting to reinit the port.
*/
DPRINTF(SILI_D_VERBOSE, "%s: error on port with PMP "
"attached, error port %d\n", PORTNAME(sp),
err_port);
if (sili_pmp_port_start_error_recovery(sp, err_port)) {
DPRINTF(SILI_D_VERBOSE, "%s: need to wait for "
"other commands to finish\n", PORTNAME(sp));
return (processed);
}
} else if (sp->sp_pmp_ports > 0) {
DPRINTF(SILI_D_VERBOSE, "%s: timeout on PMP port\n",
PORTNAME(sp));
} else {
DPRINTF(SILI_D_VERBOSE, "%s: error on non-PMP port\n",
PORTNAME(sp));
}
/* Re-enable transfers on port. */
sili_pwrite(sp, SILI_PREG_PCS, need_restart);
if (!sili_pwait_eq(sp, SILI_PREG_PCS, need_restart, 0, 5000)) {
printf("%s: port reset bit didn't clear after error\n",
PORTNAME(sp));
}
if (!sili_pwait_eq(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRDY,
SILI_PREG_PCS_PORTRDY, 1000)) {
printf("%s: couldn't restart port after error\n",
PORTNAME(sp));
}
sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_RESUME);
/* check that our active CCB list matches the restart mask */
pss_masked = pss_saved & ~(sp->sp_err_cmds);
DPRINTF(SILI_D_VERBOSE, "%s: restart mask %x\n",
PORTNAME(sp), pss_masked);
TAILQ_FOREACH(ccb, &sp->sp_active_ccbs, ccb_entry) {
if (!(pss_masked & (1 << ccb->ccb_xa.tag))) {
panic("sili_intr: slot %d not active in "
"pss_masked: %08x, state %02x",
ccb->ccb_xa.tag, pss_masked,
ccb->ccb_xa.state);
}
pss_masked &= ~(1 << ccb->ccb_xa.tag);
}
if (pss_masked != 0) {
printf("%s: mask excluding active slots: %x\n",
PORTNAME(sp), pss_masked);
}
KASSERT(pss_masked == 0);
/* if we had a timeout on a PMP port, do a portreset.
* exclude the control port here as there isn't a real
* device there to reset.
*/
if (timeout_slot >= 0 && sp->sp_pmp_ports > 0 &&
err_port != 15) {
DPRINTF(SILI_D_VERBOSE,
"%s.%d: doing portreset after timeout\n",
PORTNAME(sp), err_port);
sili_pmp_portreset(sp->sp_sc, sp->sp_port, err_port);
/* wait a bit to let the port settle down */
delay(2000000);
}
/* if we sent a device reset to a PMP, we need to reset the
* devices behind it too.
*/
if (need_restart == SILI_PREG_PCS_DEVRESET &&
sp->sp_pmp_ports > 0) {
int port_type;
int i;
port_type = sili_port_softreset(sp);
if (port_type != ATA_PORT_T_PM) {
/* device disappeared or changed type? */
printf("%s: expected to find a port multiplier,"
" got %d\n", PORTNAME(sp), port_type);
}
/* and now portreset all active ports */
for (i = 0; i < sp->sp_pmp_ports; i++) {
struct sili_softc *sc = sp->sp_sc;
if ((sp->sp_active_pmp_ports & (1 << i)) == 0)
continue;
if (sili_pmp_portreset(sc, sp->sp_port, i)) {
printf("%s.%d: failed to portreset "
"after error\n", PORTNAME(sp), i);
}
}
}
/* Restart CCBs in the order they were originally queued. */
TAILQ_FOREACH(ccb, &sp->sp_active_ccbs, ccb_entry) {
DPRINTF(SILI_D_VERBOSE, "%s: restarting slot %d "
"after error, state %02x\n", PORTNAME(sp),
ccb->ccb_xa.tag, ccb->ccb_xa.state);
KASSERT(ccb->ccb_xa.state == ATA_S_ONCHIP);
sili_post_indirect(sp, ccb);
}
sp->sp_err_cmds = 0;
sp->sp_pmp_error_recovery = 0;
/*
* Finally, run atascsi completion for any finished CCBs. If
* we had run these during cmd_done above, any ccbs that their
* completion generated would have been activated out of order.
*/
while ((ccb = TAILQ_FIRST(&sp->sp_deferred_ccbs)) != NULL) {
TAILQ_REMOVE(&sp->sp_deferred_ccbs, ccb, ccb_entry);
DPRINTF(SILI_D_VERBOSE, "%s: running deferred "
"completion for slot %d, state %02x\n",
PORTNAME(sp), ccb->ccb_xa.tag, ccb->ccb_xa.state);
KASSERT(ccb->ccb_xa.state == ATA_S_COMPLETE ||
ccb->ccb_xa.state == ATA_S_ERROR ||
ccb->ccb_xa.state == ATA_S_TIMEOUT);
ata_complete(&ccb->ccb_xa);
}
}
return (processed);
}
int
sili_intr(void *arg)
{
struct sili_softc *sc = arg;
u_int32_t is;
int port;
/* If the card has gone away, this will return 0xffffffff. */
is = sili_read(sc, SILI_REG_GIS);
if (is == 0 || is == 0xffffffff)
return (0);
sili_write(sc, SILI_REG_GIS, is);
DPRINTF(SILI_D_INTR, "sili_intr, GIS: %08x\n", is);
while (is & SILI_REG_GIS_PIS_MASK) {
port = ffs(is) - 1;
sili_port_intr(&sc->sc_ports[port], -1);
is &= ~(1 << port);
}
return (1);
}
int
sili_ports_alloc(struct sili_softc *sc)
{
struct sili_port *sp;
int i;
sc->sc_ports = mallocarray(sc->sc_nports, sizeof(struct sili_port),
M_DEVBUF, M_WAITOK | M_ZERO);
for (i = 0; i < sc->sc_nports; i++) {
sp = &sc->sc_ports[i];
sp->sp_sc = sc;
sp->sp_port = i;
#ifdef SILI_DEBUG
snprintf(sp->sp_name, sizeof(sp->sp_name), "%s.%d",
DEVNAME(sc), i);
#endif
if (bus_space_subregion(sc->sc_iot_port, sc->sc_ioh_port,
SILI_PORT_OFFSET(i), SILI_PORT_SIZE, &sp->sp_ioh) != 0) {
printf("%s: unable to create register window "
"for port %d\n", DEVNAME(sc), i);
goto freeports;
}
}
return (0);
freeports:
/* bus_space(9) says subregions dont have to be freed */
free(sc->sc_ports, M_DEVBUF, sc->sc_nports * sizeof(struct sili_port));
sc->sc_ports = NULL;
return (1);
}
void
sili_ports_free(struct sili_softc *sc)
{
struct sili_port *sp;
int i;
for (i = 0; i < sc->sc_nports; i++) {
sp = &sc->sc_ports[i];
if (sp->sp_ccbs != NULL)
sili_ccb_free(sp);
}
/* bus_space(9) says subregions dont have to be freed */
free(sc->sc_ports, M_DEVBUF, sc->sc_nports * sizeof(struct sili_port));
sc->sc_ports = NULL;
}
int
sili_ccb_alloc(struct sili_port *sp)
{
struct sili_softc *sc = sp->sp_sc;
struct sili_ccb *ccb;
struct sili_prb *prb;
int i;
TAILQ_INIT(&sp->sp_free_ccbs);
mtx_init(&sp->sp_free_ccb_mtx, IPL_BIO);
TAILQ_INIT(&sp->sp_active_ccbs);
TAILQ_INIT(&sp->sp_deferred_ccbs);
sp->sp_ccbs = mallocarray(SILI_MAX_CMDS, sizeof(struct sili_ccb),
M_DEVBUF, M_WAITOK);
sp->sp_cmds = sili_dmamem_alloc(sc, SILI_CMD_LEN * SILI_MAX_CMDS,
SILI_PRB_ALIGN);
if (sp->sp_cmds == NULL)
goto free_ccbs;
sp->sp_scratch = sili_dmamem_alloc(sc, SILI_SCRATCH_LEN, PAGE_SIZE);
if (sp->sp_scratch == NULL)
goto free_cmds;
bzero(sp->sp_ccbs, sizeof(struct sili_ccb) * SILI_MAX_CMDS);
for (i = 0; i < SILI_MAX_CMDS; i++) {
ccb = &sp->sp_ccbs[i];
ccb->ccb_port = sp;
ccb->ccb_cmd = SILI_DMA_KVA(sp->sp_cmds) + i * SILI_CMD_LEN;
ccb->ccb_cmd_dva = SILI_DMA_DVA(sp->sp_cmds) + i * SILI_CMD_LEN;
if (bus_dmamap_create(sc->sc_dmat, MAXPHYS, SILI_DMA_SEGS,
MAXPHYS, 0, BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW,
&ccb->ccb_dmamap) != 0)
goto free_scratch;
prb = ccb->ccb_cmd;
ccb->ccb_xa.fis = (struct ata_fis_h2d *)&prb->fis;
ccb->ccb_xa.packetcmd = ((struct sili_prb_packet *)prb)->cdb;
ccb->ccb_xa.tag = i;
ccb->ccb_xa.state = ATA_S_COMPLETE;
sili_put_ccb(ccb);
}
return (0);
free_scratch:
sili_dmamem_free(sc, sp->sp_scratch);
free_cmds:
sili_dmamem_free(sc, sp->sp_cmds);
free_ccbs:
sili_ccb_free(sp);
return (1);
}
void
sili_ccb_free(struct sili_port *sp)
{
struct sili_softc *sc = sp->sp_sc;
struct sili_ccb *ccb;
while ((ccb = sili_get_ccb(sp)) != NULL)
bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmamap);
free(sp->sp_ccbs, M_DEVBUF, 0);
sp->sp_ccbs = NULL;
}
struct sili_ccb *
sili_get_ccb(struct sili_port *sp)
{
struct sili_ccb *ccb;
/*
* Don't allow new commands to start while doing PMP error
* recovery
*/
if (sp->sp_pmp_error_recovery != 0) {
return (NULL);
}
mtx_enter(&sp->sp_free_ccb_mtx);
ccb = TAILQ_FIRST(&sp->sp_free_ccbs);
if (ccb != NULL) {
KASSERT(ccb->ccb_xa.state == ATA_S_PUT);
TAILQ_REMOVE(&sp->sp_free_ccbs, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_SETUP;
}
mtx_leave(&sp->sp_free_ccb_mtx);
return (ccb);
}
void
sili_put_ccb(struct sili_ccb *ccb)
{
struct sili_port *sp = ccb->ccb_port;
#ifdef DIAGNOSTIC
if (ccb->ccb_xa.state != ATA_S_COMPLETE &&
ccb->ccb_xa.state != ATA_S_TIMEOUT &&
ccb->ccb_xa.state != ATA_S_ERROR) {
printf("%s: invalid ata_xfer state %02x in sili_put_ccb, "
"slot %d\n", PORTNAME(sp), ccb->ccb_xa.state,
ccb->ccb_xa.tag);
}
#endif
ccb->ccb_xa.state = ATA_S_PUT;
mtx_enter(&sp->sp_free_ccb_mtx);
TAILQ_INSERT_TAIL(&sp->sp_free_ccbs, ccb, ccb_entry);
mtx_leave(&sp->sp_free_ccb_mtx);
}
struct sili_dmamem *
sili_dmamem_alloc(struct sili_softc *sc, bus_size_t size, bus_size_t align)
{
struct sili_dmamem *sdm;
int nsegs;
sdm = malloc(sizeof(*sdm), M_DEVBUF, M_WAITOK | M_ZERO);
sdm->sdm_size = size;
if (bus_dmamap_create(sc->sc_dmat, size, 1, size, 0,
BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &sdm->sdm_map) != 0)
goto sdmfree;
if (bus_dmamem_alloc(sc->sc_dmat, size, align, 0, &sdm->sdm_seg,
1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0)
goto destroy;
if (bus_dmamem_map(sc->sc_dmat, &sdm->sdm_seg, nsegs, size,
&sdm->sdm_kva, BUS_DMA_NOWAIT) != 0)
goto free;
if (bus_dmamap_load(sc->sc_dmat, sdm->sdm_map, sdm->sdm_kva, size,
NULL, BUS_DMA_NOWAIT) != 0)
goto unmap;
return (sdm);
unmap:
bus_dmamem_unmap(sc->sc_dmat, sdm->sdm_kva, size);
free:
bus_dmamem_free(sc->sc_dmat, &sdm->sdm_seg, 1);
destroy:
bus_dmamap_destroy(sc->sc_dmat, sdm->sdm_map);
sdmfree:
free(sdm, M_DEVBUF, sizeof *sdm);
return (NULL);
}
void
sili_dmamem_free(struct sili_softc *sc, struct sili_dmamem *sdm)
{
bus_dmamap_unload(sc->sc_dmat, sdm->sdm_map);
bus_dmamem_unmap(sc->sc_dmat, sdm->sdm_kva, sdm->sdm_size);
bus_dmamem_free(sc->sc_dmat, &sdm->sdm_seg, 1);
bus_dmamap_destroy(sc->sc_dmat, sdm->sdm_map);
free(sdm, M_DEVBUF, sizeof *sdm);
}
u_int32_t
sili_read(struct sili_softc *sc, bus_size_t r)
{
u_int32_t rv;
bus_space_barrier(sc->sc_iot_global, sc->sc_ioh_global, r, 4,
BUS_SPACE_BARRIER_READ);
rv = bus_space_read_4(sc->sc_iot_global, sc->sc_ioh_global, r);
return (rv);
}
void
sili_write(struct sili_softc *sc, bus_size_t r, u_int32_t v)
{
bus_space_write_4(sc->sc_iot_global, sc->sc_ioh_global, r, v);
bus_space_barrier(sc->sc_iot_global, sc->sc_ioh_global, r, 4,
BUS_SPACE_BARRIER_WRITE);
}
u_int32_t
sili_pread(struct sili_port *sp, bus_size_t r)
{
u_int32_t rv;
bus_space_barrier(sp->sp_sc->sc_iot_port, sp->sp_ioh, r, 4,
BUS_SPACE_BARRIER_READ);
rv = bus_space_read_4(sp->sp_sc->sc_iot_port, sp->sp_ioh, r);
return (rv);
}
void
sili_pwrite(struct sili_port *sp, bus_size_t r, u_int32_t v)
{
bus_space_write_4(sp->sp_sc->sc_iot_port, sp->sp_ioh, r, v);
bus_space_barrier(sp->sp_sc->sc_iot_port, sp->sp_ioh, r, 4,
BUS_SPACE_BARRIER_WRITE);
}
int
sili_pwait_eq(struct sili_port *sp, bus_size_t r, u_int32_t mask,
u_int32_t value, int timeout)
{
while ((sili_pread(sp, r) & mask) != value) {
if (timeout == 0)
return (0);
delay(1000);
timeout--;
}
return (1);
}
int
sili_pwait_ne(struct sili_port *sp, bus_size_t r, u_int32_t mask,
u_int32_t value, int timeout)
{
while ((sili_pread(sp, r) & mask) == value) {
if (timeout == 0)
return (0);
delay(1000);
timeout--;
}
return (1);
}
void
sili_post_direct(struct sili_port *sp, u_int slot, void *buf, size_t buflen)
{
bus_size_t r = SILI_PREG_SLOT(slot);
#ifdef DIAGNOSTIC
if (buflen != 64 && buflen != 128)
panic("sili_pcopy: buflen of %lu is not 64 or 128", buflen);
#endif
bus_space_write_raw_region_4(sp->sp_sc->sc_iot_port, sp->sp_ioh, r,
buf, buflen);
bus_space_barrier(sp->sp_sc->sc_iot_port, sp->sp_ioh, r, buflen,
BUS_SPACE_BARRIER_WRITE);
sili_pwrite(sp, SILI_PREG_FIFO, slot);
}
void
sili_pread_fis(struct sili_port *sp, u_int slot, struct ata_fis_d2h *fis)
{
bus_size_t r = SILI_PREG_SLOT(slot) + 8;
bus_space_barrier(sp->sp_sc->sc_iot_port, sp->sp_ioh, r,
sizeof(struct ata_fis_d2h), BUS_SPACE_BARRIER_READ);
bus_space_read_raw_region_4(sp->sp_sc->sc_iot_port, sp->sp_ioh, r,
fis, sizeof(struct ata_fis_d2h));
}
void
sili_post_indirect(struct sili_port *sp, struct sili_ccb *ccb)
{
sili_pwrite(sp, SILI_PREG_CAR_LO(ccb->ccb_xa.tag),
(u_int32_t)ccb->ccb_cmd_dva);
sili_pwrite(sp, SILI_PREG_CAR_HI(ccb->ccb_xa.tag),
(u_int32_t)(ccb->ccb_cmd_dva >> 32));
}
u_int32_t
sili_signature(struct sili_port *sp, u_int slot)
{
u_int32_t sig_hi, sig_lo;
sig_hi = sili_pread(sp, SILI_PREG_SIG_HI(slot));
sig_hi <<= SILI_PREG_SIG_HI_SHIFT;
sig_lo = sili_pread(sp, SILI_PREG_SIG_LO(slot));
sig_lo &= SILI_PREG_SIG_LO_MASK;
return (sig_hi | sig_lo);
}
void
sili_dummy_done(struct ata_xfer *xa)
{
}
int
sili_pmp_portreset(struct sili_softc *sc, int port, int pmp_port)
{
struct sili_port *sp;
u_int32_t data;
int loop;
sp = &sc->sc_ports[port];
DPRINTF(SILI_D_VERBOSE, "%s: resetting pmp port %d\n", PORTNAME(sp),
pmp_port);
if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1))
goto err;
if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SCTL,
SATA_PM_SCTL_IPM_DISABLED))
goto err;
delay(10000);
/* enable PHY by writing 1 then 0 to Scontrol DET field, using
* Write Port Multiplier commands
*/
data = SATA_PM_SCTL_IPM_DISABLED | SATA_PM_SCTL_DET_INIT |
SATA_PM_SCTL_SPD_ANY;
if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SCTL, data))
goto err;
delay(100000);
if (sili_pmp_phy_status(sp, pmp_port, &data)) {
printf("%s: cannot clear phy status for PMP probe\n",
PORTNAME(sp));
goto err;
}
sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1);
data = SATA_PM_SCTL_IPM_DISABLED | SATA_PM_SCTL_DET_NONE;
if (sili_pmp_write(sp, pmp_port, SATA_PMREG_SCTL, data))
goto err;
delay(100000);
/* wait for PHYRDY by polling SStatus */
for (loop = 3; loop; loop--) {
if (sili_pmp_read(sp, pmp_port, SATA_PMREG_SSTS, &data))
goto err;
if (data & SATA_PM_SSTS_DET)
break;
delay(100000);
}
if (loop == 0) {
DPRINTF(SILI_D_VERBOSE, "%s.%d: port appears to be unplugged\n",
PORTNAME(sp), pmp_port);
goto err;
}
/* give it a bit more time to complete negotiation */
for (loop = 30; loop; loop--) {
if (sili_pmp_read(sp, pmp_port, SATA_PMREG_SSTS, &data))
goto err;
if ((data & SATA_PM_SSTS_DET) == SATA_PM_SSTS_DET_DEV)
break;
delay(10000);
}
if (loop == 0) {
printf("%s.%d: device may be powered down\n", PORTNAME(sp),
pmp_port);
goto err;
}
DPRINTF(SILI_D_VERBOSE, "%s.%d: device detected; SStatus=%08x\n",
PORTNAME(sp), pmp_port, data);
/* clear the X-bit and all other error bits in Serror (PCSR[1]) */
sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1);
return (0);
err:
DPRINTF(SILI_D_VERBOSE, "%s.%d: port reset failed\n", PORTNAME(sp),
pmp_port);
sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1);
return (1);
}
void
sili_pmp_op_timeout(void *cookie)
{
struct sili_ccb *ccb = cookie;
struct sili_port *sp = ccb->ccb_port;
int s;
switch (ccb->ccb_xa.state) {
case ATA_S_PENDING:
TAILQ_REMOVE(&sp->sp_active_ccbs, ccb, ccb_entry);
ccb->ccb_xa.state = ATA_S_TIMEOUT;
break;
case ATA_S_ONCHIP:
KASSERT(sp->sp_active == (1 << ccb->ccb_xa.tag));
s = splbio();
sili_port_intr(sp, ccb->ccb_xa.tag);
splx(s);
break;
case ATA_S_ERROR:
/* don't do anything? */
break;
default:
panic("%s: sili_pmp_op_timeout: ccb in bad state %d",
PORTNAME(sp), ccb->ccb_xa.state);
}
}
int
sili_pmp_softreset(struct sili_softc *sc, int port, int pmp_port)
{
struct sili_ccb *ccb;
struct sili_prb *prb;
struct sili_port *sp;
struct ata_fis_h2d *fis;
u_int32_t data;
u_int32_t signature;
sp = &sc->sc_ports[port];
ccb = sili_get_ccb(sp);
if (ccb == NULL) {
printf("%s: sili_pmp_softreset NULL ccb!\n", PORTNAME(sp));
return (-1);
}
ccb->ccb_xa.flags = ATA_F_POLL | ATA_F_GET_RFIS;
ccb->ccb_xa.complete = sili_dummy_done;
ccb->ccb_xa.pmp_port = pmp_port;
prb = ccb->ccb_cmd;
bzero(prb, sizeof(*prb));
fis = (struct ata_fis_h2d *)&prb->fis;
fis->flags = pmp_port;
prb->control = SILI_PRB_SOFT_RESET;
ccb->ccb_xa.state = ATA_S_PENDING;
if (sili_poll(ccb, 8000, sili_pmp_op_timeout) != 0) {
DPRINTF(SILI_D_VERBOSE, "%s.%d: softreset FIS failed\n",
PORTNAME(sp), pmp_port);
sili_put_ccb(ccb);
/* don't return a valid device type here so the caller knows
* it can retry if it wants to
*/
return (-1);
}
signature = ccb->ccb_xa.rfis.sector_count |
(ccb->ccb_xa.rfis.lba_low << 8) |
(ccb->ccb_xa.rfis.lba_mid << 16) |
(ccb->ccb_xa.rfis.lba_high << 24);
DPRINTF(SILI_D_VERBOSE, "%s.%d: signature: %08x\n", PORTNAME(sp),
pmp_port, signature);
sili_put_ccb(ccb);
/* clear phy status and error bits */
if (sili_pmp_phy_status(sp, pmp_port, &data)) {
printf("%s.%d: cannot clear phy status after softreset\n",
PORTNAME(sp), pmp_port);
}
sili_pmp_write(sp, pmp_port, SATA_PMREG_SERR, -1);
/* classify the device based on its signature */
switch (signature) {
case SATA_SIGNATURE_DISK:
return (ATA_PORT_T_DISK);
case SATA_SIGNATURE_ATAPI:
return (ATA_PORT_T_ATAPI);
case SATA_SIGNATURE_PORT_MULTIPLIER:
return (ATA_PORT_T_NONE);
default:
return (ATA_PORT_T_NONE);
}
}
u_int32_t
sili_port_softreset(struct sili_port *sp)
{
struct sili_prb_softreset sreset;
u_int32_t signature;
bzero(&sreset, sizeof(sreset));
sreset.control = htole16(SILI_PRB_SOFT_RESET | SILI_PRB_INTERRUPT_MASK);
sreset.fis[1] = SATA_PMP_CONTROL_PORT;
/* we use slot 0 */
sili_post_direct(sp, 0, &sreset, sizeof(sreset));
if (!sili_pwait_eq(sp, SILI_PREG_PSS, (1 << 0), 0, 1000)) {
DPRINTF(SILI_D_VERBOSE, "%s: timed out while waiting for soft "
"reset\n", PORTNAME(sp));
return (ATA_PORT_T_NONE);
}
/* Read device signature from command slot. */
signature = sili_signature(sp, 0);
DPRINTF(SILI_D_VERBOSE, "%s: signature 0x%08x\n", PORTNAME(sp),
signature);
switch (signature) {
case SATA_SIGNATURE_DISK:
return (ATA_PORT_T_DISK);
case SATA_SIGNATURE_ATAPI:
return (ATA_PORT_T_ATAPI);
case SATA_SIGNATURE_PORT_MULTIPLIER:
return (ATA_PORT_T_PM);
default:
return (ATA_PORT_T_NONE);
}
}
int
sili_ata_probe(void *xsc, int port, int lun)
{
struct sili_softc *sc = xsc;
struct sili_port *sp = &sc->sc_ports[port];
int port_type;
/* handle pmp port probes */
if (lun != 0) {
int i;
int rc;
int pmp_port = lun - 1;
if (lun > sp->sp_pmp_ports)
return (ATA_PORT_T_NONE);
for (i = 0; i < 2; i++) {
if (sili_pmp_portreset(sc, port, pmp_port)) {
continue;
}
/* small delay between attempts to allow error
* conditions to settle down. this doesn't seem
* to affect portreset operations, just
* commands sent to the device.
*/
if (i != 0) {
delay(5000000);
}
rc = sili_pmp_softreset(sc, port, pmp_port);
switch (rc) {
case -1:
/* possibly try again */
break;
case ATA_PORT_T_DISK:
case ATA_PORT_T_ATAPI:
/* mark this port as active */
sp->sp_active_pmp_ports |= (1 << pmp_port);
default:
return (rc);
}
}
DPRINTF(SILI_D_VERBOSE, "%s.%d: probe failed\n", PORTNAME(sp),
pmp_port);
return (ATA_PORT_T_NONE);
}
sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRESET);
delay(10000);
sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_PORTRESET);
sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTINIT);
if (!sili_pwait_eq(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRDY,
SILI_PREG_PCS_PORTRDY, 1000)) {
printf("%s: couldn't initialize port\n", PORTNAME(sp));
return (ATA_PORT_T_NONE);
}
sili_pwrite(sp, SILI_PREG_PCC, SILI_PREG_PCC_A32B);
if (!sili_pwait_eq(sp, SILI_PREG_SSTS, SATA_SStatus_DET,
SATA_SStatus_DET_DEV, 2000)) {
DPRINTF(SILI_D_VERBOSE, "%s: unattached\n", PORTNAME(sp));
return (ATA_PORT_T_NONE);
}
DPRINTF(SILI_D_VERBOSE, "%s: SSTS 0x%08x\n", PORTNAME(sp),
sili_pread(sp, SILI_PREG_SSTS));
port_type = sili_port_softreset(sp);
if (port_type == ATA_PORT_T_NONE)
return (port_type);
/* allocate port resources */
if (sili_ccb_alloc(sp) != 0)
return (ATA_PORT_T_NONE);
/* do PMP probe now that we can talk to the device */
if (port_type == ATA_PORT_T_PM) {
int i;
sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PMEN);
if (sili_pmp_identify(sp, &sp->sp_pmp_ports)) {
return (ATA_PORT_T_NONE);
}
/* reset all the PMP ports to wake devices up */
for (i = 0; i < sp->sp_pmp_ports; i++) {
sili_pmp_portreset(sp->sp_sc, sp->sp_port, i);
}
}
/* enable port interrupts */
sili_write(sc, SILI_REG_GC, sili_read(sc, SILI_REG_GC) | 1 << port);
sili_pwrite(sp, SILI_PREG_IES, SILI_PREG_IE_CMDERR |
SILI_PREG_IE_CMDCOMP);
return (port_type);
}
void
sili_ata_free(void *xsc, int port, int lun)
{
struct sili_softc *sc = xsc;
struct sili_port *sp = &sc->sc_ports[port];
if (lun == 0) {
if (sp->sp_ccbs != NULL)
sili_ccb_free(sp);
/* XXX we should do more here */
}
}
void
sili_ata_cmd(struct ata_xfer *xa)
{
struct sili_ccb *ccb = (struct sili_ccb *)xa;
struct sili_port *sp = ccb->ccb_port;
struct sili_softc *sc = sp->sp_sc;
struct sili_prb_ata *ata;
struct sili_prb_packet *atapi;
struct sili_sge *sgl;
int sgllen;
int s;
KASSERT(xa->state == ATA_S_SETUP || xa->state == ATA_S_TIMEOUT);
if (xa->flags & ATA_F_PACKET) {
atapi = ccb->ccb_cmd;
if (xa->flags & ATA_F_WRITE)
atapi->control = htole16(SILI_PRB_PACKET_WRITE);
else
atapi->control = htole16(SILI_PRB_PACKET_READ);
sgl = atapi->sgl;
sgllen = nitems(atapi->sgl);
} else {
ata = ccb->ccb_cmd;
ata->control = 0;
sgl = ata->sgl;
sgllen = nitems(ata->sgl);
}
if (sili_load(ccb, sgl, sgllen) != 0)
goto failcmd;
bus_dmamap_sync(sc->sc_dmat, SILI_DMA_MAP(sp->sp_cmds),
xa->tag * SILI_CMD_LEN, SILI_CMD_LEN, BUS_DMASYNC_PREWRITE);
timeout_set(&xa->stimeout, sili_ata_cmd_timeout, ccb);
xa->state = ATA_S_PENDING;
if (xa->flags & ATA_F_POLL)
sili_poll(ccb, xa->timeout, sili_ata_cmd_timeout);
else {
s = splbio();
timeout_add_msec(&xa->stimeout, xa->timeout);
sili_start(sp, ccb);
splx(s);
}
return;
failcmd:
s = splbio();
xa->state = ATA_S_ERROR;
ata_complete(xa);
splx(s);
}
void
sili_ata_cmd_done(struct sili_ccb *ccb, int defer_completion)
{
struct sili_port *sp = ccb->ccb_port;
struct sili_softc *sc = sp->sp_sc;
struct ata_xfer *xa = &ccb->ccb_xa;
splassert(IPL_BIO);
timeout_del(&xa->stimeout);
bus_dmamap_sync(sc->sc_dmat, SILI_DMA_MAP(sp->sp_cmds),
xa->tag * SILI_CMD_LEN, SILI_CMD_LEN, BUS_DMASYNC_POSTWRITE);
sili_unload(ccb);
TAILQ_REMOVE(&sp->sp_active_ccbs, ccb, ccb_entry);
sp->sp_active &= ~(1 << xa->tag);
if (sp->sp_err_active & (1 << xa->tag)) {
sp->sp_err_active &= ~(1 << xa->tag);
DPRINTF(SILI_D_VERBOSE, "%s: slot %d complete, error mask now "
"%x\n", PORTNAME(sp), xa->tag, sp->sp_err_active);
}
if (xa->state == ATA_S_ONCHIP)
xa->state = ATA_S_COMPLETE;
#ifdef DIAGNOSTIC
else if (xa->state != ATA_S_ERROR && xa->state != ATA_S_TIMEOUT)
printf("%s: invalid ata_xfer state %02x in sili_ata_cmd_done, "
"slot %d\n", PORTNAME(sp), xa->state, xa->tag);
#endif
if (defer_completion)
TAILQ_INSERT_TAIL(&sp->sp_deferred_ccbs, ccb, ccb_entry);
else if (xa->state == ATA_S_COMPLETE)
ata_complete(xa);
#ifdef DIAGNOSTIC
else
printf("%s: completion not deferred, but xa->state is %02x?\n",
PORTNAME(sp), xa->state);
#endif
}
void
sili_ata_cmd_timeout(void *xccb)
{
struct sili_ccb *ccb = xccb;
struct sili_port *sp = ccb->ccb_port;
int s;
s = splbio();
sili_port_intr(sp, ccb->ccb_xa.tag);
splx(s);
}
int
sili_load(struct sili_ccb *ccb, struct sili_sge *sgl, int sgllen)
{
struct sili_port *sp = ccb->ccb_port;
struct sili_softc *sc = sp->sp_sc;
struct ata_xfer *xa = &ccb->ccb_xa;
struct sili_sge *nsge = sgl, *ce = NULL;
bus_dmamap_t dmap = ccb->ccb_dmamap;
u_int64_t addr;
int error;
int i;
if (xa->datalen == 0)
return (0);
error = bus_dmamap_load(sc->sc_dmat, dmap, xa->data, xa->datalen, NULL,
(xa->flags & ATA_F_NOWAIT) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK);
if (error != 0) {
printf("%s: error %d loading dmamap\n", PORTNAME(sp), error);
return (1);
}
if (dmap->dm_nsegs > sgllen)
ce = &sgl[sgllen - 1];
for (i = 0; i < dmap->dm_nsegs; i++) {
if (nsge == ce) {
nsge++;
addr = ccb->ccb_cmd_dva;
addr += ((u_int8_t *)nsge - (u_int8_t *)ccb->ccb_cmd);
ce->addr_lo = htole32((u_int32_t)addr);
ce->addr_hi = htole32((u_int32_t)(addr >> 32));
ce->flags = htole32(SILI_SGE_LNK);
if ((dmap->dm_nsegs - i) > SILI_SGT_SGLLEN)
ce += SILI_SGT_SGLLEN;
else
ce = NULL;
}
sgl = nsge;
addr = dmap->dm_segs[i].ds_addr;
sgl->addr_lo = htole32((u_int32_t)addr);
sgl->addr_hi = htole32((u_int32_t)(addr >> 32));
sgl->data_count = htole32(dmap->dm_segs[i].ds_len);
sgl->flags = 0;
nsge++;
}
sgl->flags |= htole32(SILI_SGE_TRM);
bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize,
(xa->flags & ATA_F_READ) ? BUS_DMASYNC_PREREAD :
BUS_DMASYNC_PREWRITE);
return (0);
}
void
sili_unload(struct sili_ccb *ccb)
{
struct sili_port *sp = ccb->ccb_port;
struct sili_softc *sc = sp->sp_sc;
struct ata_xfer *xa = &ccb->ccb_xa;
bus_dmamap_t dmap = ccb->ccb_dmamap;
if (xa->datalen == 0)
return;
bus_dmamap_sync(sc->sc_dmat, dmap, 0, dmap->dm_mapsize,
(xa->flags & ATA_F_READ) ? BUS_DMASYNC_POSTREAD :
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_dmat, dmap);
if (xa->flags & ATA_F_READ)
xa->resid = xa->datalen - sili_pread(sp,
SILI_PREG_RX_COUNT(xa->tag));
else
xa->resid = 0;
}
int
sili_poll(struct sili_ccb *ccb, int timeout, void (*timeout_fn)(void *))
{
struct sili_port *sp = ccb->ccb_port;
int s;
s = splbio();
sili_start(sp, ccb);
do {
if (sili_port_intr(sp, -1) & (1 << ccb->ccb_xa.tag)) {
splx(s);
return (ccb->ccb_xa.state != ATA_S_COMPLETE);
}
delay(1000);
} while (--timeout > 0);
/* Run timeout while at splbio, otherwise sili_intr could interfere. */
if (timeout_fn != NULL)
timeout_fn(ccb);
splx(s);
return (1);
}
void
sili_start(struct sili_port *sp, struct sili_ccb *ccb)
{
int slot = ccb->ccb_xa.tag;
splassert(IPL_BIO);
KASSERT(ccb->ccb_xa.state == ATA_S_PENDING);
KASSERT(sp->sp_pmp_error_recovery == 0);
TAILQ_INSERT_TAIL(&sp->sp_active_ccbs, ccb, ccb_entry);
sp->sp_active |= 1 << slot;
ccb->ccb_xa.state = ATA_S_ONCHIP;
sili_post_indirect(sp, ccb);
}
int
sili_read_ncq_error(struct sili_port *sp, int *err_slotp, int pmp_port)
{
struct sili_softc *sc = sp->sp_sc;
struct sili_prb_ata read_10h;
u_int64_t addr;
struct ata_fis_h2d *fis;
struct ata_log_page_10h *log;
struct sili_ccb *ccb;
int rc;
sili_pwrite(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTINIT);
if (!sili_pwait_eq(sp, SILI_PREG_PCS, SILI_PREG_PCS_PORTRDY,
SILI_PREG_PCS_PORTRDY, 1000)) {
printf("%s: couldn't ready port during log page read\n",
PORTNAME(sp));
return (1);
}
/* READ LOG EXT 10h into scratch space */
bzero(&read_10h, sizeof(read_10h));
read_10h.control = htole16(SILI_PRB_INTERRUPT_MASK);
addr = SILI_DMA_DVA(sp->sp_scratch);
read_10h.sgl[0].addr_lo = htole32((u_int32_t)addr);
read_10h.sgl[0].addr_hi = htole32((u_int32_t)(addr >> 32));
read_10h.sgl[0].data_count = htole32(512);
read_10h.sgl[0].flags = htole32(SILI_SGE_TRM);
fis = (struct ata_fis_h2d *)read_10h.fis;
fis->type = ATA_FIS_TYPE_H2D;
fis->flags = ATA_H2D_FLAGS_CMD | pmp_port;
fis->command = ATA_C_READ_LOG_EXT;
fis->lba_low = 0x10; /* queued error log page (10h) */
fis->sector_count = 1; /* number of sectors (1) */
fis->sector_count_exp = 0;
fis->lba_mid = 0; /* starting offset */
fis->lba_mid_exp = 0;
fis->device = 0;
bus_dmamap_sync(sc->sc_dmat, SILI_DMA_MAP(sp->sp_scratch), 0,
512, BUS_DMASYNC_PREREAD);
/* issue read and poll for completion */
sili_post_direct(sp, 0, &read_10h, sizeof(read_10h));
rc = sili_pwait_eq(sp, SILI_PREG_PSS, (1 << 0), 0, 1000);
bus_dmamap_sync(sc->sc_dmat, SILI_DMA_MAP(sp->sp_scratch), 0,
512, BUS_DMASYNC_POSTREAD);
if (!rc) {
DPRINTF(SILI_D_VERBOSE, "%s: timed out while waiting for log "
"page read\n", PORTNAME(sp));
return (1);
}
/* Extract failed register set and tags from the scratch space. */
log = (struct ata_log_page_10h *)SILI_DMA_KVA(sp->sp_scratch);
if (ISSET(log->err_regs.type, ATA_LOG_10H_TYPE_NOTQUEUED)) {
/* Not queued bit was set - wasn't an NCQ error? */
printf("%s: read NCQ error page, but not an NCQ error?\n",
PORTNAME(sp));
return (1);
}
/* Copy back the log record as a D2H register FIS. */
*err_slotp = log->err_regs.type & ATA_LOG_10H_TYPE_TAG_MASK;
ccb = &sp->sp_ccbs[*err_slotp];
memcpy(&ccb->ccb_xa.rfis, &log->err_regs, sizeof(struct ata_fis_d2h));
ccb->ccb_xa.rfis.type = ATA_FIS_TYPE_D2H;
ccb->ccb_xa.rfis.flags = 0;
return (0);
}
struct ata_xfer *
sili_ata_get_xfer(void *xsc, int port)
{
struct sili_softc *sc = xsc;
struct sili_port *sp = &sc->sc_ports[port];
struct sili_ccb *ccb;
ccb = sili_get_ccb(sp);
if (ccb == NULL) {
printf("%s: sili_ata_get_xfer NULL ccb!\n", PORTNAME(sp));
return (NULL);
}
bzero(ccb->ccb_cmd, SILI_CMD_LEN);
return ((struct ata_xfer *)ccb);
}
void
sili_ata_put_xfer(struct ata_xfer *xa)
{
struct sili_ccb *ccb = (struct sili_ccb *)xa;
sili_put_ccb(ccb);
}
/* PMP register ops */
int
sili_pmp_read(struct sili_port *sp, int target, int which, u_int32_t *datap)
{
struct sili_ccb *ccb;
struct sili_prb *prb;
struct ata_fis_h2d *fis;
int error;
ccb = sili_get_ccb(sp);
if (ccb == NULL) {
printf("%s: sili_pmp_read NULL ccb!\n", PORTNAME(sp));
return (1);
}
ccb->ccb_xa.flags = ATA_F_POLL | ATA_F_GET_RFIS;
ccb->ccb_xa.complete = sili_dummy_done;
ccb->ccb_xa.pmp_port = SATA_PMP_CONTROL_PORT;
ccb->ccb_xa.state = ATA_S_PENDING;
prb = ccb->ccb_cmd;
bzero(prb, sizeof(*prb));
fis = (struct ata_fis_h2d *)&prb->fis;
fis->type = ATA_FIS_TYPE_H2D;
fis->flags = ATA_H2D_FLAGS_CMD | SATA_PMP_CONTROL_PORT;
fis->command = ATA_C_READ_PM;
fis->features = which;
fis->device = target | ATA_H2D_DEVICE_LBA;
fis->control = ATA_FIS_CONTROL_4BIT;
if (sili_poll(ccb, 1000, sili_pmp_op_timeout) != 0) {
printf("sili_pmp_read(%d, %d) failed\n", target, which);
error = 1;
} else {
*datap = ccb->ccb_xa.rfis.sector_count |
(ccb->ccb_xa.rfis.lba_low << 8) |
(ccb->ccb_xa.rfis.lba_mid << 16) |
(ccb->ccb_xa.rfis.lba_high << 24);
error = 0;
}
sili_put_ccb(ccb);
return (error);
}
int
sili_pmp_write(struct sili_port *sp, int target, int which, u_int32_t data)
{
struct sili_ccb *ccb;
struct sili_prb *prb;
struct ata_fis_h2d *fis;
int error;
ccb = sili_get_ccb(sp);
if (ccb == NULL) {
printf("%s: sili_pmp_write NULL ccb!\n", PORTNAME(sp));
return (1);
}
ccb->ccb_xa.complete = sili_dummy_done;
ccb->ccb_xa.flags = ATA_F_POLL;
ccb->ccb_xa.pmp_port = SATA_PMP_CONTROL_PORT;
ccb->ccb_xa.state = ATA_S_PENDING;
prb = ccb->ccb_cmd;
bzero(prb, sizeof(*prb));
fis = (struct ata_fis_h2d *)&prb->fis;
fis->type = ATA_FIS_TYPE_H2D;
fis->flags = ATA_H2D_FLAGS_CMD | SATA_PMP_CONTROL_PORT;
fis->command = ATA_C_WRITE_PM;
fis->features = which;
fis->device = target | ATA_H2D_DEVICE_LBA;
fis->sector_count = (u_int8_t)data;
fis->lba_low = (u_int8_t)(data >> 8);
fis->lba_mid = (u_int8_t)(data >> 16);
fis->lba_high = (u_int8_t)(data >> 24);
fis->control = ATA_FIS_CONTROL_4BIT;
error = sili_poll(ccb, 1000, sili_pmp_op_timeout);
sili_put_ccb(ccb);
return (error);
}
int
sili_pmp_phy_status(struct sili_port *sp, int target, u_int32_t *datap)
{
int error;
error = sili_pmp_read(sp, target, SATA_PMREG_SSTS, datap);
if (error == 0)
error = sili_pmp_write(sp, target, SATA_PMREG_SERR, -1);
if (error)
*datap = 0;
return (error);
}
int
sili_pmp_identify(struct sili_port *sp, int *ret_nports)
{
u_int32_t chipid;
u_int32_t rev;
u_int32_t nports;
u_int32_t features;
u_int32_t enabled;
if (sili_pmp_read(sp, 15, 0, &chipid) ||
sili_pmp_read(sp, 15, 1, &rev) ||
sili_pmp_read(sp, 15, 2, &nports) ||
sili_pmp_read(sp, 15, SATA_PMREG_FEA, &features) ||
sili_pmp_read(sp, 15, SATA_PMREG_FEAEN, &enabled)) {
printf("%s: port multiplier identification failed\n",
PORTNAME(sp));
return (1);
}
nports &= 0x0F;
/* ignore SEMB port on SiI3726 port multiplier chips */
if (chipid == 0x37261095) {
nports--;
}
printf("%s: port multiplier found: chip=%08x rev=0x%b nports=%d, "
"features: 0x%b, enabled: 0x%b\n", PORTNAME(sp), chipid, rev,
SATA_PFMT_PM_REV, nports, features, SATA_PFMT_PM_FEA, enabled,
SATA_PFMT_PM_FEA);
*ret_nports = nports;
return (0);
}