1883 lines
48 KiB
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);
|
|
}
|