Module Name: src Committed By: jdolecek Date: Thu Oct 11 20:57:51 UTC 2018
Modified Files: src/sys/dev/ata [jdolecek-ncqfixes]: TODO.ncq ata.c ata_subr.c atavar.h files.ata src/sys/dev/ic [jdolecek-ncqfixes]: ahcisata_core.c ahcisatavar.h mvsata.c mvsatavar.h siisata.c siisatavar.h Added Files: src/sys/dev/ata [jdolecek-ncqfixes]: ata_recovery.c Log Message: refactor shared parts of the SATA error recovery into new function ata_recovery_resume() and use for ahcisata/siisata/mvsata, also replace per-controller hold/unhold with generic version move the shared recovery code into separate file ata_recovery.c To generate a diff of this commit: cvs rdiff -u -r1.4.2.12 -r1.4.2.13 src/sys/dev/ata/TODO.ncq cvs rdiff -u -r1.141.6.15 -r1.141.6.16 src/sys/dev/ata/ata.c cvs rdiff -u -r0 -r1.1.2.1 src/sys/dev/ata/ata_recovery.c cvs rdiff -u -r1.6.2.7 -r1.6.2.8 src/sys/dev/ata/ata_subr.c cvs rdiff -u -r1.99.2.10 -r1.99.2.11 src/sys/dev/ata/atavar.h cvs rdiff -u -r1.27 -r1.27.6.1 src/sys/dev/ata/files.ata cvs rdiff -u -r1.62.2.8 -r1.62.2.9 src/sys/dev/ic/ahcisata_core.c cvs rdiff -u -r1.18 -r1.18.6.1 src/sys/dev/ic/ahcisatavar.h cvs rdiff -u -r1.41.2.6 -r1.41.2.7 src/sys/dev/ic/mvsata.c cvs rdiff -u -r1.4 -r1.4.2.1 src/sys/dev/ic/mvsatavar.h cvs rdiff -u -r1.35.6.8 -r1.35.6.9 src/sys/dev/ic/siisata.c cvs rdiff -u -r1.7 -r1.7.6.1 src/sys/dev/ic/siisatavar.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/ata/TODO.ncq diff -u src/sys/dev/ata/TODO.ncq:1.4.2.12 src/sys/dev/ata/TODO.ncq:1.4.2.13 --- src/sys/dev/ata/TODO.ncq:1.4.2.12 Sat Oct 6 20:27:28 2018 +++ src/sys/dev/ata/TODO.ncq Thu Oct 11 20:57:51 2018 @@ -4,7 +4,8 @@ jdolecek-ncqfixes goals: - run recovery via atathread, move to new function and share ahci/siisata/mvsata - maybe do device error handling in not-interrupt-context (maybe this should be done on a mpata branch?) -- remove controller-specific slot bitmaps (ic/siisata.c, ic/ahcisata_core.c) +- adjust mvsata() intr code to accept tfd (instead of irq 0/1) so that + ata_recovery_resume() works properly for it Bugs ---- Index: src/sys/dev/ata/ata.c diff -u src/sys/dev/ata/ata.c:1.141.6.15 src/sys/dev/ata/ata.c:1.141.6.16 --- src/sys/dev/ata/ata.c:1.141.6.15 Sat Oct 6 21:19:55 2018 +++ src/sys/dev/ata/ata.c Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: ata.c,v 1.141.6.15 2018/10/06 21:19:55 jdolecek Exp $ */ +/* $NetBSD: ata.c,v 1.141.6.16 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 1998, 2001 Manuel Bouyer. All rights reserved. @@ -25,7 +25,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ata.c,v 1.141.6.15 2018/10/06 21:19:55 jdolecek Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ata.c,v 1.141.6.16 2018/10/11 20:57:51 jdolecek Exp $"); #include "opt_ata.h" @@ -933,98 +933,6 @@ out: return rv; } -int -ata_read_log_ext_ncq(struct ata_drive_datas *drvp, uint8_t flags, - uint8_t *slot, uint8_t *status, uint8_t *err) -{ - struct ata_xfer *xfer = &drvp->recovery_xfer; - int rv; - struct ata_channel *chp = drvp->chnl_softc; - struct atac_softc *atac = chp->ch_atac; - uint8_t *tb, cksum, page; - - ATADEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS); - - /* Only NCQ ATA drives support/need this */ - if (drvp->drive_type != ATA_DRIVET_ATA || - (drvp->drive_flags & ATA_DRIVE_NCQ) == 0) - return EOPNOTSUPP; - - memset(xfer, 0, sizeof(*xfer)); - - tb = drvp->recovery_blk; - memset(tb, 0, sizeof(drvp->recovery_blk)); - - /* - * We could use READ LOG DMA EXT if drive supports it (i.e. - * when it supports Streaming feature) to avoid PIO command, - * and to make this a little faster. Realistically, it - * should not matter. - */ - xfer->c_flags |= C_SKIP_QUEUE; - xfer->c_ata_c.r_command = WDCC_READ_LOG_EXT; - xfer->c_ata_c.r_lba = page = WDCC_LOG_PAGE_NCQ; - xfer->c_ata_c.r_st_bmask = WDCS_DRDY; - xfer->c_ata_c.r_st_pmask = WDCS_DRDY; - xfer->c_ata_c.r_count = 1; - xfer->c_ata_c.r_device = WDSD_LBA; - xfer->c_ata_c.flags = AT_READ | AT_LBA | AT_LBA48 | flags; - xfer->c_ata_c.timeout = 1000; /* 1s */ - xfer->c_ata_c.data = tb; - xfer->c_ata_c.bcount = sizeof(drvp->recovery_blk); - - if ((*atac->atac_bustype_ata->ata_exec_command)(drvp, - xfer) != ATACMD_COMPLETE) { - rv = EAGAIN; - goto out; - } - if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) { - rv = EINVAL; - goto out; - } - - cksum = 0; - for (int i = 0; i < sizeof(drvp->recovery_blk); i++) - cksum += tb[i]; - if (cksum != 0) { - device_printf(drvp->drv_softc, - "invalid checksum %x for READ LOG EXT page %x\n", - cksum, page); - rv = EINVAL; - goto out; - } - - if (tb[0] & WDCC_LOG_NQ) { - /* not queued command */ - rv = EOPNOTSUPP; - goto out; - } - - *slot = tb[0] & 0x1f; - *status = tb[2]; - *err = tb[3]; - - if ((*status & WDCS_ERR) == 0) { - /* - * We expect error here. Normal physical drives always - * do, it's part of ATA standard. However, QEMU AHCI emulation - * misehandles READ LOG EXT in a way that the command itself - * returns without error, but no data is transferred. - */ - device_printf(drvp->drv_softc, - "READ LOG EXT page %x failed to report error: " - "slot %d err %x status %x\n", - page, *slot, *err, *status); - rv = EOPNOTSUPP; - goto out; - } - - rv = 0; - -out: - return rv; -} - #if NATA_DMA void ata_dmaerr(struct ata_drive_datas *drvp, int flags) Index: src/sys/dev/ata/ata_subr.c diff -u src/sys/dev/ata/ata_subr.c:1.6.2.7 src/sys/dev/ata/ata_subr.c:1.6.2.8 --- src/sys/dev/ata/ata_subr.c:1.6.2.7 Sat Oct 6 21:19:55 2018 +++ src/sys/dev/ata/ata_subr.c Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: ata_subr.c,v 1.6.2.7 2018/10/06 21:19:55 jdolecek Exp $ */ +/* $NetBSD: ata_subr.c,v 1.6.2.8 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 1998, 2001 Manuel Bouyer. All rights reserved. @@ -25,7 +25,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ata_subr.c,v 1.6.2.7 2018/10/06 21:19:55 jdolecek Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ata_subr.c,v 1.6.2.8 2018/10/11 20:57:51 jdolecek Exp $"); #include "opt_ata.h" @@ -333,3 +333,42 @@ ata_queue_free_slot(struct ata_channel * chq->queue_xfers_avail |= __BIT(c_slot); } + +void +ata_queue_hold(struct ata_channel *chp) +{ + struct ata_queue *chq = chp->ch_queue; + + KASSERT(mutex_owned(&chp->ch_lock)); + + chq->queue_hold |= chq->active_xfers_used; + chq->active_xfers_used = 0; +} + +void +ata_queue_unhold(struct ata_channel *chp) +{ + struct ata_queue *chq = chp->ch_queue; + + KASSERT(mutex_owned(&chp->ch_lock)); + + chq->active_xfers_used |= chq->queue_hold; + chq->queue_hold = 0; +} + +/* + * Must be called with interrupts blocked. + */ +uint32_t +ata_queue_active(struct ata_channel *chp) +{ + struct ata_queue *chq = chp->ch_queue; + + return chq->active_xfers_used; +} + +uint8_t +ata_queue_openings(struct ata_channel *chp) +{ + return chp->ch_queue->queue_openings; +} Index: src/sys/dev/ata/atavar.h diff -u src/sys/dev/ata/atavar.h:1.99.2.10 src/sys/dev/ata/atavar.h:1.99.2.11 --- src/sys/dev/ata/atavar.h:1.99.2.10 Sat Oct 6 21:19:55 2018 +++ src/sys/dev/ata/atavar.h Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: atavar.h,v 1.99.2.10 2018/10/06 21:19:55 jdolecek Exp $ */ +/* $NetBSD: atavar.h,v 1.99.2.11 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 1998, 2001 Manuel Bouyer. @@ -233,6 +233,7 @@ struct ata_queue { TAILQ_HEAD(, ata_xfer) active_xfers; /* active commands */ uint32_t active_xfers_used; /* mask of active commands */ uint32_t queue_xfers_avail; /* available xfers mask */ + uint32_t queue_hold; /* slots held during recovery */ kcondvar_t c_active; /* somebody actively waiting for xfer */ kcondvar_t c_cmd_finish; /* somebody waiting for cmd finish */ }; @@ -414,6 +415,7 @@ struct ata_channel { #define ATACH_NCQ 0x800 /* channel executing NCQ commands */ #define ATACH_DMA_BEFORE_CMD 0x1000 /* start DMA first */ #define ATACH_TH_DRIVE_RESET 0x2000 /* thread asked for drive(s) reset */ +#define ATACH_RECOVERING 0x4000 /* channel is recovering */ #define ATACH_NODRIVE 0xff /* no drive selected for reset */ @@ -524,6 +526,7 @@ int ata_get_params(struct ata_drive_data int ata_set_mode(struct ata_drive_datas *, uint8_t, uint8_t); int ata_read_log_ext_ncq(struct ata_drive_datas *, uint8_t, uint8_t *, uint8_t *, uint8_t *); +void ata_recovery_resume(struct ata_channel *, int, int, int); /* return code for these cmds */ #define CMD_OK 0 @@ -573,6 +576,10 @@ struct ata_xfer * ata_queue_drive_active_xfer(struct ata_channel *, int); bool ata_queue_alloc_slot(struct ata_channel *, uint8_t *, uint8_t); void ata_queue_free_slot(struct ata_channel *, uint8_t); +uint32_t ata_queue_active(struct ata_channel *); +uint8_t ata_queue_openings(struct ata_channel *); +void ata_queue_hold(struct ata_channel *); +void ata_queue_unhold(struct ata_channel *); void ata_delay(struct ata_channel *, int, const char *, int); Index: src/sys/dev/ata/files.ata diff -u src/sys/dev/ata/files.ata:1.27 src/sys/dev/ata/files.ata:1.27.6.1 --- src/sys/dev/ata/files.ata:1.27 Tue Oct 10 17:19:38 2017 +++ src/sys/dev/ata/files.ata Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -# $NetBSD: files.ata,v 1.27 2017/10/10 17:19:38 jdolecek Exp $ +# $NetBSD: files.ata,v 1.27.6.1 2018/10/11 20:57:51 jdolecek Exp $ # # Config file and device description for machine-independent devices # which attach to ATA busses. Included by ports that need it. Ports @@ -16,6 +16,7 @@ defflag opt_wd.h WD_CHAOS_MONKEY file dev/ata/ata.c (ata_hl | atapi) & atabus file dev/ata/ata_subr.c (ata_hl | atapi) +file dev/ata/ata_recovery.c (ata_hl | atapi) # ATA RAID configuration support defpseudodev ataraid {[vendtype = -1], [unit = -1]} Index: src/sys/dev/ic/ahcisata_core.c diff -u src/sys/dev/ic/ahcisata_core.c:1.62.2.8 src/sys/dev/ic/ahcisata_core.c:1.62.2.9 --- src/sys/dev/ic/ahcisata_core.c:1.62.2.8 Sun Oct 7 15:44:47 2018 +++ src/sys/dev/ic/ahcisata_core.c Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: ahcisata_core.c,v 1.62.2.8 2018/10/07 15:44:47 jdolecek Exp $ */ +/* $NetBSD: ahcisata_core.c,v 1.62.2.9 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 2006 Manuel Bouyer. @@ -26,7 +26,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ahcisata_core.c,v 1.62.2.8 2018/10/07 15:44:47 jdolecek Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ahcisata_core.c,v 1.62.2.9 2018/10/11 20:57:51 jdolecek Exp $"); #include <sys/types.h> #include <sys/malloc.h> @@ -569,6 +569,7 @@ ahci_intr_port(struct ahci_softc *sc, st struct ata_xfer *xfer; int slot = -1; bool recover = false; + uint32_t aslots; is = AHCI_READ(sc, AHCI_P_IS(chp->ch_channel)); AHCI_WRITE(sc, AHCI_P_IS(chp->ch_channel), is); @@ -622,14 +623,14 @@ ahci_intr_port(struct ahci_softc *sc, st DEBUG_INTR); } - if (!achp->ahcic_recovering) + if (!ISSET(chp->ch_flags, ATACH_RECOVERING)) recover = true; } else if (is & (AHCI_P_IX_DHRS|AHCI_P_IX_SDBS)) { tfd = AHCI_READ(sc, AHCI_P_TFD(chp->ch_channel)); /* D2H Register FIS or Set Device Bits */ if ((tfd & WDCS_ERR) != 0) { - if (!achp->ahcic_recovering) + if (!ISSET(chp->ch_flags, ATACH_RECOVERING)) recover = true; AHCIDEBUG_PRINT(("%s port %d: transfer aborted 0x%x\n", @@ -643,8 +644,10 @@ ahci_intr_port(struct ahci_softc *sc, st if (__predict_false(recover)) ata_channel_freeze(chp); + aslots = ata_queue_active(chp); + if (slot >= 0) { - if ((achp->ahcic_cmds_active & __BIT(slot)) != 0 && + if ((aslots & __BIT(slot)) != 0 && (sact & __BIT(slot)) == 0) { xfer = ata_queue_hwslot_to_xfer(chp, slot); xfer->ops->c_intr(chp, xfer, tfd); @@ -659,7 +662,6 @@ ahci_intr_port(struct ahci_softc *sc, st * can activate another command(s), so must only process * commands active before we start processing. */ - uint32_t aslots = achp->ahcic_cmds_active; for (slot=0; slot < sc->sc_ncmds; slot++) { if ((aslots & __BIT(slot)) != 0 && @@ -1071,7 +1073,6 @@ ahci_cmd_start(struct ata_channel *chp, DEBUG_XFERS); ata_channel_lock_owned(chp); - KASSERT((achp->ahcic_cmds_active & (1U << slot)) == 0); cmd_tbl = achp->ahcic_cmd_tbl[slot]; AHCIDEBUG_PRINT(("%s port %d tbl %p\n", AHCINAME(sc), chp->ch_channel, @@ -1105,8 +1106,6 @@ ahci_cmd_start(struct ata_channel *chp, } /* start command */ AHCI_WRITE(sc, AHCI_P_CI(chp->ch_channel), 1U << slot); - /* and says we started this command */ - achp->ahcic_cmds_active |= 1U << slot; if ((ata_c->flags & AT_POLL) == 0) { callout_reset(&chp->c_timo_callout, mstohz(ata_c->timeout), @@ -1164,7 +1163,6 @@ ahci_cmd_abort(struct ata_channel *chp, static void ahci_cmd_kill_xfer(struct ata_channel *chp, struct ata_xfer *xfer, int reason) { - struct ahci_channel *achp = (struct ahci_channel *)chp; struct ata_command *ata_c = &xfer->c_ata_c; bool deactivate = true; @@ -1191,11 +1189,8 @@ ahci_cmd_kill_xfer(struct ata_channel *c ahci_cmd_done_end(chp, xfer); - if (deactivate) { - KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0); - achp->ahcic_cmds_active &= ~(1U << xfer->c_slot); + if (deactivate) ata_deactivate_xfer(chp, xfer); - } } static int @@ -1229,8 +1224,6 @@ ahci_cmd_complete(struct ata_channel *ch ahci_cmd_done(chp, xfer); - KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0); - achp->ahcic_cmds_active &= ~(1U << xfer->c_slot); ata_deactivate_xfer(chp, xfer); if ((ata_c->flags & (AT_TIMEOU|AT_ERROR)) == 0) @@ -1359,8 +1352,6 @@ ahci_bio_start(struct ata_channel *chp, AHCI_WRITE(sc, AHCI_P_SACT(chp->ch_channel), 1U << xfer->c_slot); /* start command */ AHCI_WRITE(sc, AHCI_P_CI(chp->ch_channel), 1U << xfer->c_slot); - /* and says we started this command */ - achp->ahcic_cmds_active |= 1U << xfer->c_slot; if ((xfer->c_flags & C_POLL) == 0) { callout_reset(&chp->c_timo_callout, mstohz(ATA_DELAY), @@ -1413,7 +1404,6 @@ ahci_bio_kill_xfer(struct ata_channel *c { int drive = xfer->c_drive; struct ata_bio *ata_bio = &xfer->c_bio; - struct ahci_channel *achp = (struct ahci_channel *)chp; bool deactivate = true; AHCIDEBUG_PRINT(("ahci_bio_kill_xfer channel %d\n", chp->ch_channel), @@ -1439,11 +1429,8 @@ ahci_bio_kill_xfer(struct ata_channel *c } ata_bio->r_error = WDCE_ABRT; - if (deactivate) { - KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0); - achp->ahcic_cmds_active &= ~(1U << xfer->c_slot); + if (deactivate) ata_deactivate_xfer(chp, xfer); - } (*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc, xfer); } @@ -1503,8 +1490,6 @@ ahci_bio_complete(struct ata_channel *ch } AHCIDEBUG_PRINT((" now %ld\n", ata_bio->bcount), DEBUG_XFERS); - KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0); - achp->ahcic_cmds_active &= ~(1U << xfer->c_slot); ata_deactivate_xfer(chp, xfer); (*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc, xfer); @@ -1579,34 +1564,17 @@ ahci_channel_start(struct ahci_softc *sc AHCI_WRITE(sc, AHCI_P_CMD(chp->ch_channel), p_cmd); } -static void -ahci_hold(struct ahci_channel *achp) -{ - achp->ahcic_cmds_hold |= achp->ahcic_cmds_active; - achp->ahcic_cmds_active = 0; -} - -static void -ahci_unhold(struct ahci_channel *achp) -{ - achp->ahcic_cmds_active = achp->ahcic_cmds_hold; - achp->ahcic_cmds_hold = 0; -} - /* Recover channel after command failure */ void ahci_channel_recover(struct ahci_softc *sc, struct ata_channel *chp, int tfd) { - struct ahci_channel *achp = (struct ahci_channel *)chp; - struct ata_drive_datas *drvp; - uint8_t slot, eslot, st, err; - int drive = -1, error; - struct ata_xfer *xfer; + int drive = ATACH_NODRIVE; bool reset = false; - KASSERT(!achp->ahcic_recovering); - - achp->ahcic_recovering = true; + ata_channel_lock(chp); + KASSERT(!ISSET(chp->ch_flags, ATACH_RECOVERING)); + SET(chp->ch_flags, ATACH_RECOVERING); + ata_channel_unlock(chp); /* * Read FBS to get the drive which caused the error, if PM is in use. @@ -1637,19 +1605,17 @@ ahci_channel_recover(struct ahci_softc * } if ((fbs & AHCI_P_FBS_DEC) != 0) { /* follow non-device specific recovery */ - drive = -1; + drive = ATACH_NODRIVE; reset = true; } } else { /* not device specific, reset channel */ - drive = -1; + drive = ATACH_NODRIVE; reset = true; } } else drive = 0; - drvp = &chp->ch_drive[drive]; - /* * If BSY or DRQ bits are set, must execute COMRESET to return * device to idle state. If drive is idle, it's enough to just @@ -1657,86 +1623,26 @@ ahci_channel_recover(struct ahci_softc * * After resetting CMD.ST, need to execute READ LOG EXT for NCQ * to unblock device processing if COMRESET was not done. */ - if (reset || (AHCI_TFD_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0) - goto reset; - - KASSERT(drive >= 0); - ahci_channel_stop(sc, chp, AT_POLL); - ahci_channel_start(sc, chp, AT_POLL, - (sc->sc_ahci_cap & AHCI_CAP_CLO) ? 1 : 0); - - ahci_hold(achp); - - /* - * When running NCQ commands, READ LOG EXT is necessary to clear the - * error condition and unblock the device. - */ - error = ata_read_log_ext_ncq(drvp, AT_POLL, &eslot, &st, &err); - - ahci_unhold(achp); - - switch (error) { - case 0: - /* Error out the particular NCQ xfer, then requeue the others */ - if ((achp->ahcic_cmds_active & (1U << eslot)) != 0) { - xfer = ata_queue_hwslot_to_xfer(chp, eslot); - xfer->c_flags |= C_RECOVERED; - xfer->ops->c_intr(chp, xfer, - (err << AHCI_P_TFD_ERR_SHIFT) | st); - } - break; - - case EOPNOTSUPP: - /* - * Non-NCQ command error, just find the slot and end with - * the error. - */ - for (slot = 0; slot < sc->sc_ncmds; slot++) { - if ((achp->ahcic_cmds_active & (1U << slot)) != 0) { - xfer = ata_queue_hwslot_to_xfer(chp, slot); - xfer->ops->c_intr(chp, xfer, tfd); - } - } - break; - - case EAGAIN: - /* - * Failed to get resources to run the recovery command, must - * reset the drive. This will also kill all still outstanding - * transfers. - */ -reset: + if (reset || (AHCI_TFD_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0) { ata_channel_lock(chp); ahci_reset_channel(chp, AT_POLL); ata_channel_unlock(chp); goto out; - /* NOTREACHED */ - - default: - /* - * The command to get the slot failed. Kill outstanding - * commands for the same drive only. No need to reset - * the drive, it's unblocked nevertheless. - */ - break; } - /* Requeue all unfinished commands for same drive as failed command */ - for (slot = 0; slot < sc->sc_ncmds; slot++) { - if ((achp->ahcic_cmds_active & (1U << slot)) == 0) - continue; - - xfer = ata_queue_hwslot_to_xfer(chp, slot); - if (drive != xfer->c_drive) - continue; + KASSERT(drive != ATACH_NODRIVE && drive >= 0); + ahci_channel_stop(sc, chp, AT_POLL); + ahci_channel_start(sc, chp, AT_POLL, + (sc->sc_ahci_cap & AHCI_CAP_CLO) ? 1 : 0); - xfer->ops->c_kill_xfer(chp, xfer, - (error == 0) ? KILL_REQUEUE : KILL_RESET); - } + ata_recovery_resume(chp, drive, tfd, AT_POLL); out: /* Drive unblocked, back to normal operation */ - achp->ahcic_recovering = false; + ata_channel_lock(chp); + CLR(chp->ch_flags, ATACH_RECOVERING); + ata_channel_unlock(chp); + atastart(chp); } @@ -1951,8 +1857,6 @@ ahci_atapi_start(struct ata_channel *chp } /* start command */ AHCI_WRITE(sc, AHCI_P_CI(chp->ch_channel), 1U << xfer->c_slot); - /* and says we started this command */ - achp->ahcic_cmds_active |= 1U << xfer->c_slot; if ((xfer->c_flags & C_POLL) == 0) { callout_reset(&chp->c_timo_callout, mstohz(sc_xfer->timeout), @@ -2044,8 +1948,6 @@ ahci_atapi_complete(struct ata_channel * } } - KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0); - achp->ahcic_cmds_active &= ~(1U << xfer->c_slot); ata_deactivate_xfer(chp, xfer); ata_free_xfer(chp, xfer); @@ -2059,7 +1961,6 @@ static void ahci_atapi_kill_xfer(struct ata_channel *chp, struct ata_xfer *xfer, int reason) { struct scsipi_xfer *sc_xfer = xfer->c_scsipi; - struct ahci_channel *achp = (struct ahci_channel *)chp; bool deactivate = true; /* remove this command from xfer queue */ @@ -2081,11 +1982,8 @@ ahci_atapi_kill_xfer(struct ata_channel panic("ahci_ata_atapi_kill_xfer"); } - if (deactivate) { - KASSERT((achp->ahcic_cmds_active & (1U << xfer->c_slot)) != 0); - achp->ahcic_cmds_active &= ~(1U << xfer->c_slot); + if (deactivate) ata_deactivate_xfer(chp, xfer); - } ata_free_xfer(chp, xfer); scsipi_done(sc_xfer); Index: src/sys/dev/ic/ahcisatavar.h diff -u src/sys/dev/ic/ahcisatavar.h:1.18 src/sys/dev/ic/ahcisatavar.h:1.18.6.1 --- src/sys/dev/ic/ahcisatavar.h:1.18 Sat Oct 7 16:05:32 2017 +++ src/sys/dev/ic/ahcisatavar.h Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: ahcisatavar.h,v 1.18 2017/10/07 16:05:32 jdolecek Exp $ */ +/* $NetBSD: ahcisatavar.h,v 1.18.6.1 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 2006 Manuel Bouyer. @@ -82,9 +82,6 @@ struct ahci_softc { struct ahci_cmd_tbl *ahcic_cmd_tbl[AHCI_MAX_CMDS]; bus_addr_t ahcic_bus_cmd_tbl[AHCI_MAX_CMDS]; bus_dmamap_t ahcic_datad[AHCI_MAX_CMDS]; - volatile uint32_t ahcic_cmds_active; /* active commands */ - uint32_t ahcic_cmds_hold; /* held commands */ - bool ahcic_recovering; } sc_channels[AHCI_MAX_PORTS]; void (*sc_channel_start)(struct ahci_softc *, struct ata_channel *); Index: src/sys/dev/ic/mvsata.c diff -u src/sys/dev/ic/mvsata.c:1.41.2.6 src/sys/dev/ic/mvsata.c:1.41.2.7 --- src/sys/dev/ic/mvsata.c:1.41.2.6 Thu Oct 4 17:59:35 2018 +++ src/sys/dev/ic/mvsata.c Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: mvsata.c,v 1.41.2.6 2018/10/04 17:59:35 jdolecek Exp $ */ +/* $NetBSD: mvsata.c,v 1.41.2.7 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 2008 KIYOHARA Takashi * All rights reserved. @@ -26,7 +26,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: mvsata.c,v 1.41.2.6 2018/10/04 17:59:35 jdolecek Exp $"); +__KERNEL_RCSID(0, "$NetBSD: mvsata.c,v 1.41.2.7 2018/10/11 20:57:51 jdolecek Exp $"); #include "opt_mvsata.h" @@ -172,8 +172,6 @@ static void mvsata_channel_recover(struc static int mvsata_port_init(struct mvsata_hc *, int); static int mvsata_wdc_reg_init(struct mvsata_port *, struct wdc_regs *); #ifndef MVSATA_WITHOUTDMA -static inline void mvsata_quetag_get(struct mvsata_port *, uint8_t); -static inline void mvsata_quetag_put(struct mvsata_port *, uint8_t); static void *mvsata_edma_resource_prepare(struct mvsata_port *, bus_dma_tag_t, bus_dmamap_t *, size_t, int); static void mvsata_edma_resource_purge(struct mvsata_port *, bus_dma_tag_t, @@ -539,7 +537,8 @@ mvsata_error(struct mvsata_port *mvport) device_xname(MVSATA_DEV2(mvport)), mvport->port_hc->hc, mvport->port); - if (!mvport->port_recovering) + if (!ISSET(mvport->port_ata_channel.ch_flags, + ATACH_RECOVERING)) mvsata_channel_recover(mvport); } @@ -547,31 +546,15 @@ mvsata_error(struct mvsata_port *mvport) } static void -mvsata_hold(struct mvsata_port *mvport) -{ - mvport->port_hold_slots |= mvport->port_quetagidx; - mvport->port_quetagidx = 0; -} - -static void -mvsata_unhold(struct mvsata_port *mvport) -{ - mvport->port_quetagidx = mvport->port_hold_slots; - mvport->port_hold_slots = 0; -} - -static void mvsata_channel_recover(struct mvsata_port *mvport) { struct ata_channel *chp = &mvport->port_ata_channel; - struct ata_drive_datas *drvp; - int drive, error; - uint8_t eslot, slot, st, err; - struct ata_xfer *xfer; + int drive, tfd = 1; - KASSERT(!mvport->port_recovering); - - mvport->port_recovering = true; + ata_channel_lock(chp); + KASSERT(!ISSET(chp->ch_flags, ATACH_RECOVERING)); + SET(chp->ch_flags, ATACH_RECOVERING); + ata_channel_unlock(chp); if (chp->ch_ndrives > PMP_PORT_CTL) { /* Get PM port number for the device in error. This device @@ -582,90 +565,29 @@ mvsata_channel_recover(struct mvsata_por } else drive = 0; - drvp = &chp->ch_drive[drive]; - /* * Controller doesn't need any special action. Simply execute * READ LOG EXT for NCQ to unblock device processing, then continue * as if nothing happened. */ - KASSERT(drive >= 0); - - mvsata_hold(mvport); - /* - * When running NCQ commands, READ LOG EXT is necessary to clear the - * error condition and unblock the device. - */ - error = ata_read_log_ext_ncq(drvp, AT_POLL, &eslot, &st, &err); - - mvsata_unhold(mvport); +#if 0 + XXX recheck - mvsata used to have this to handle the successful + recovery via read_log_ext: - switch (error) { - case 0: - /* Error out the particular NCQ xfer, then requeue the others */ - if ((mvport->port_quetagidx & (1 << eslot)) != 0) { xfer = ata_queue_hwslot_to_xfer(chp, eslot); xfer->c_flags |= C_RECOVERED; xfer->c_bio.error = ERROR; xfer->c_bio.r_error = err; xfer->ops->c_intr(chp, xfer, 1); - } - break; - - case EOPNOTSUPP: - /* - * Non-NCQ command error, just find the slot and end it with - * an error. Handler figures the error itself. - */ - for (slot = 0; slot < MVSATA_EDMAQ_LEN; slot++) { - if ((mvport->port_quetagidx & (1 << slot)) != 0) { - xfer = ata_queue_hwslot_to_xfer(chp, slot); - if (xfer->c_drive != drive) - continue; - - xfer->ops->c_intr(chp, xfer, 1); - } - } - break; - - case EAGAIN: - /* - * Failed to get resources to run the recovery command, must - * reset the drive. This will also kill all still outstanding - * transfers. - */ - ata_channel_lock(chp); - mvsata_reset_channel(chp, AT_POLL); - ata_channel_unlock(chp); - goto out; - /* NOTREACHED */ - - default: - /* - * The command to get the slot failed. Kill outstanding - * commands for the same drive only. No need to reset - * the drive, it's unblocked nevertheless. - */ - break; - } - - /* Requeue the non-errorred commands */ - for (slot = 0; slot < MVSATA_EDMAQ_LEN; slot++) { - if (((mvport->port_quetagidx >> slot) & 1) == 0) - continue; - - xfer = ata_queue_hwslot_to_xfer(chp, slot); - if (xfer->c_drive != drive) - continue; - - xfer->ops->c_kill_xfer(chp, xfer, - (error == 0) ? KILL_REQUEUE : KILL_RESET); - } +#endif + ata_recovery_resume(chp, drive, tfd, AT_POLL); -out: /* Drive unblocked, back to normal operation */ - mvport->port_recovering = false; + ata_channel_lock(chp); + CLR(chp->ch_flags, ATACH_RECOVERING); + ata_channel_unlock(chp); + atastart(chp); } @@ -1129,8 +1051,6 @@ mvsata_bio_start(struct ata_channel *chp ata_channel_lock_owned(chp); - mvsata_quetag_get(mvport, xfer->c_slot); - if (xfer->c_flags & C_DMA) if (drvp->n_xfers <= NXFER) drvp->n_xfers++; @@ -1521,10 +1441,8 @@ mvsata_bio_kill_xfer(struct ata_channel } ata_bio->r_error = WDCE_ABRT; - if (deactivate) { - mvsata_quetag_put(mvport, xfer->c_slot); + if (deactivate) ata_deactivate_xfer(chp, xfer); - } (*chp->ch_drive[drive].drv_done)(chp->ch_drive[drive].drv_softc, xfer); } @@ -1555,7 +1473,6 @@ mvsata_bio_done(struct ata_channel *chp, ata_bio->bcount = xfer->c_bcount; /* mark controller inactive and free xfer */ - mvsata_quetag_put(mvport, xfer->c_slot); ata_deactivate_xfer(chp, xfer); ata_bio->flags |= ATA_ITSDONE; @@ -1741,8 +1658,6 @@ mvsata_wdc_cmd_start(struct ata_channel ata_channel_lock_owned(chp); - mvsata_quetag_get(mvport, xfer->c_slot); - /* First, EDMA disable, if enabled this channel. */ KASSERT((chp->ch_flags & ATACH_NCQ) == 0); if (mvport->port_edmamode_curr != nodma) @@ -1951,10 +1866,8 @@ mvsata_wdc_cmd_kill_xfer(struct ata_chan mvsata_wdc_cmd_done_end(chp, xfer); - if (deactivate) { - mvsata_quetag_put(mvport, xfer->c_slot); + if (deactivate) ata_deactivate_xfer(chp, xfer); - } } static void @@ -2014,8 +1927,6 @@ mvsata_wdc_cmd_done(struct ata_channel * } } - mvsata_quetag_put(mvport, xfer->c_slot); - if (ata_c->flags & AT_POLL) { /* enable interrupts */ MVSATA_WDC_WRITE_1(mvport, SRB_CAS, WDCTL_4BIT); @@ -2127,8 +2038,6 @@ mvsata_atapi_start(struct ata_channel *c ata_channel_lock_owned(chp); - mvsata_quetag_get(mvport, xfer->c_slot); - KASSERT((chp->ch_flags & ATACH_NCQ) == 0); if (mvport->port_edmamode_curr != nodma) mvsata_edma_disable(mvport, 10 /* ms */, wait_flags); @@ -2566,10 +2475,8 @@ mvsata_atapi_kill_xfer(struct ata_channe panic("mvsata_atapi_kill_xfer"); } - if (deactivate) { - mvsata_quetag_put(mvport, xfer->c_slot); + if (deactivate) ata_deactivate_xfer(chp, xfer); - } ata_free_xfer(chp, xfer); scsipi_done(sc_xfer); @@ -2696,7 +2603,6 @@ mvsata_atapi_phase_complete(struct ata_x static void mvsata_atapi_done(struct ata_channel *chp, struct ata_xfer *xfer) { - struct mvsata_port *mvport = (struct mvsata_port *)chp; struct scsipi_xfer *sc_xfer = xfer->c_scsipi; bool iserror = (sc_xfer->error != XS_NOERROR); @@ -2709,7 +2615,6 @@ mvsata_atapi_done(struct ata_channel *ch return; /* mark controller inactive and free the command */ - mvsata_quetag_put(mvport, xfer->c_slot); ata_deactivate_xfer(chp, xfer); ata_free_xfer(chp, xfer); @@ -2887,6 +2792,8 @@ mvsata_edma_handle(struct mvsata_port *m erpqop * sizeof(struct crpb), n * sizeof(struct crpb), BUS_DMASYNC_POSTREAD); + uint32_t aslots = ata_queue_active(chp); + prev_erpqop = erpqop; while (erpqop != erpqip) { #ifdef MVSATA_DEBUG @@ -2898,7 +2805,7 @@ mvsata_edma_handle(struct mvsata_port *m quetag = CRPB_CHOSTQUETAG(le16toh(crpb->id)); - if ((mvport->port_quetagidx & __BIT(quetag)) == 0) { + if ((aslots & __BIT(quetag)) == 0) { /* not actually executing */ continue; } @@ -2987,10 +2894,12 @@ mvsata_edma_rqq_remove(struct mvsata_por bus_dmamap_sync(mvport->port_dmat, mvport->port_crqb_dmamap, 0, sizeof(union mvsata_crqb) * MVSATA_EDMAQ_LEN, BUS_DMASYNC_PREWRITE); + uint32_t aslots = ata_queue_active(chp); + for (i = 0, erqqip = 0; i < MVSATA_EDMAQ_LEN; i++) { struct ata_xfer *rqxfer; - if ((mvport->port_quetagidx & __BIT(i)) == 0) + if ((aslots & __BIT(i)) == 0) continue; if (i == xfer->c_slot) { @@ -3294,30 +3203,6 @@ mvsata_wdc_reg_init(struct mvsata_port * #ifndef MVSATA_WITHOUTDMA -/* - * There are functions to remember Host Queue Tag. - */ - -static inline void -mvsata_quetag_get(struct mvsata_port *mvport, uint8_t quetag) -{ - KASSERT(quetag <= 32); - - /* - * Do not check whether it's already set, can happen when - * postponing bio or atapi xfer to thread. - */ - mvport->port_quetagidx |= __BIT(quetag); -} - -static inline void -mvsata_quetag_put(struct mvsata_port *mvport, uint8_t quetag) -{ - KASSERT(quetag <= 32); - KASSERT((mvport->port_quetagidx & __BIT(quetag)) != 0); - mvport->port_quetagidx &= ~__BIT(quetag); -} - static void * mvsata_edma_resource_prepare(struct mvsata_port *mvport, bus_dma_tag_t dmat, bus_dmamap_t *dmamap, size_t size, int write) Index: src/sys/dev/ic/mvsatavar.h diff -u src/sys/dev/ic/mvsatavar.h:1.4 src/sys/dev/ic/mvsatavar.h:1.4.2.1 --- src/sys/dev/ic/mvsatavar.h:1.4 Fri Aug 31 18:43:29 2018 +++ src/sys/dev/ic/mvsatavar.h Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: mvsatavar.h,v 1.4 2018/08/31 18:43:29 jdolecek Exp $ */ +/* $NetBSD: mvsatavar.h,v 1.4.2.1 2018/10/11 20:57:51 jdolecek Exp $ */ /* * Copyright (c) 2008 KIYOHARA Takashi * All rights reserved. @@ -73,8 +73,6 @@ struct mvsata_port { enum mvsata_edmamode port_edmamode_negotiated; enum mvsata_edmamode port_edmamode_curr; - volatile uint32_t port_quetagidx; /* Host Queue Tag valid */ - int port_prev_erqqop; /* previous Req Queue Out-Pointer */ bus_dma_tag_t port_dmat; union mvsata_crqb *port_crqb; /* EDMA Command Request Block */ @@ -96,10 +94,6 @@ struct mvsata_port { bus_space_handle_t port_sata_sstatus; /* SATA Interface status reg */ struct _fix_phy_param _fix_phy_param; - - /* Recovery context */ - uint32_t port_hold_slots; - bool port_recovering; }; struct mvsata_hc { Index: src/sys/dev/ic/siisata.c diff -u src/sys/dev/ic/siisata.c:1.35.6.8 src/sys/dev/ic/siisata.c:1.35.6.9 --- src/sys/dev/ic/siisata.c:1.35.6.8 Sun Oct 7 15:42:47 2018 +++ src/sys/dev/ic/siisata.c Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: siisata.c,v 1.35.6.8 2018/10/07 15:42:47 jdolecek Exp $ */ +/* $NetBSD: siisata.c,v 1.35.6.9 2018/10/11 20:57:51 jdolecek Exp $ */ /* from ahcisata_core.c */ @@ -79,7 +79,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: siisata.c,v 1.35.6.8 2018/10/07 15:42:47 jdolecek Exp $"); +__KERNEL_RCSID(0, "$NetBSD: siisata.c,v 1.35.6.9 2018/10/11 20:57:51 jdolecek Exp $"); #include <sys/types.h> #include <sys/param.h> @@ -535,7 +535,7 @@ siisata_intr_port(struct siisata_channel * We don't expect the recovery to trigger error, * but handle this just in case. */ - if (!schp->sch_recovering) + if (!ISSET(chp->ch_flags, ATACH_RECOVERING)) recover = true; else { aprint_error_dev(sc->sc_atac.atac_dev, @@ -574,7 +574,7 @@ process: * can activate another command(s), so must only process * commands active before we start processing. */ - uint32_t aslots = schp->sch_active_slots; + uint32_t aslots = ata_queue_active(chp); for (int slot=0; slot < SIISATA_MAX_SLOTS; slot++) { if ((aslots & __BIT(slot)) != 0 && @@ -591,20 +591,6 @@ process: } } -static void -siisata_hold(struct siisata_channel *schp) -{ - schp->sch_hold_slots |= schp->sch_active_slots; - schp->sch_active_slots = 0; -} - -static void -siisata_unhold(struct siisata_channel *schp) -{ - schp->sch_active_slots = schp->sch_hold_slots; - schp->sch_hold_slots = 0; -} - /* Recover channel after transfer aborted */ void siisata_channel_recover(struct ata_channel *chp, uint32_t tfd) @@ -612,14 +598,12 @@ siisata_channel_recover(struct ata_chann struct siisata_channel *schp = (struct siisata_channel *)chp; struct siisata_softc *sc = (struct siisata_softc *)schp->ata_channel.ch_atac; - struct ata_drive_datas *drvp; - int drive, error; - uint8_t eslot, slot, st, err; - struct ata_xfer *xfer; - - KASSERT(!schp->sch_recovering); + int drive; - schp->sch_recovering = true; + ata_channel_lock(chp); + KASSERT(!ISSET(chp->ch_flags, ATACH_RECOVERING)); + SET(chp->ch_flags, ATACH_RECOVERING); + ata_channel_unlock(chp); if (chp->ch_ndrives > PMP_PORT_CTL) { /* Get PM port number for the device in error */ @@ -628,8 +612,6 @@ siisata_channel_recover(struct ata_chann } else drive = 0; - drvp = &chp->ch_drive[drive]; - /* * If BSY or DRQ bits are set, must execute COMRESET to return * device to idle state. Otherwise, commands can be reissued @@ -637,86 +619,24 @@ siisata_channel_recover(struct ata_chann * READ LOG EXT for NCQ to unblock device processing if COMRESET * was not done. */ - if ((ATACH_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0) - goto reset; - - KASSERT(drive >= 0); - siisata_reinit_port(chp, drive); - - siisata_hold(schp); - - /* - * When running NCQ commands, READ LOG EXT is necessary to clear the - * error condition and unblock the device. - */ - error = ata_read_log_ext_ncq(drvp, AT_POLL, &eslot, &st, &err); - - siisata_unhold(schp); - - switch (error) { - case 0: - /* Error out the particular NCQ xfer, then requeue the others */ - if ((schp->sch_active_slots & (1 << eslot)) != 0) { - xfer = ata_queue_hwslot_to_xfer(chp, eslot); - xfer->c_flags |= C_RECOVERED; - xfer->ops->c_intr(chp, xfer, ATACH_ERR_ST(err, st)); - } - break; - - case EOPNOTSUPP: - /* - * Non-NCQ command error, just find the slot and end it with - * the error. - */ - for (slot = 0; slot < SIISATA_MAX_SLOTS; slot++) { - if ((schp->sch_active_slots & (1 << slot)) != 0) { - xfer = ata_queue_hwslot_to_xfer(chp, slot); - if (xfer->c_drive != drive) - continue; - - xfer->ops->c_intr(chp, xfer, tfd); - } - } - break; - - case EAGAIN: - /* - * Failed to get resources to run the recovery command, must - * reset the drive. This will also kill all still outstanding - * transfers. - */ -reset: + if ((ATACH_ST(tfd) & (WDCS_BSY|WDCS_DRQ)) != 0) { ata_channel_lock(chp); siisata_device_reset(chp); ata_channel_unlock(chp); goto out; - /* NOTREACHED */ - - default: - /* - * The command to get the slot failed. Kill outstanding - * commands for the same drive only. No need to reset - * the drive, it's unblocked nevertheless. - */ - break; } - /* Requeue the non-errorred commands */ - for (slot = 0; slot < SIISATA_MAX_SLOTS; slot++) { - if (((schp->sch_active_slots >> slot) & 1) == 0) - continue; + KASSERT(drive >= 0); + siisata_reinit_port(chp, drive); - xfer = ata_queue_hwslot_to_xfer(chp, slot); - if (xfer->c_drive != drive) - continue; - - xfer->ops->c_kill_xfer(chp, xfer, - (error == 0) ? KILL_REQUEUE : KILL_RESET); - } + ata_recovery_resume(chp, drive, tfd, AT_POLL); out: /* Drive unblocked, back to normal operation */ - schp->sch_recovering = false; + ata_channel_lock(chp); + CLR(chp->ch_flags, ATACH_RECOVERING); + ata_channel_unlock(chp); + atastart(chp); } @@ -1493,12 +1413,7 @@ siisata_activate_prb(struct siisata_chan sc = (struct siisata_softc *)schp->ata_channel.ch_atac; - KASSERTMSG((schp->sch_active_slots & __BIT(slot)) == 0, - "%s: trying to activate active slot %d", SIISATANAME(sc), slot); - SIISATA_PRB_SYNC(sc, schp, slot, BUS_DMASYNC_PREWRITE); - /* keep track of what's going on */ - schp->sch_active_slots |= __BIT(slot); offset = PRO_CARX(schp->ata_channel.ch_channel, slot); @@ -1515,11 +1430,6 @@ siisata_deactivate_prb(struct siisata_ch sc = (struct siisata_softc *)schp->ata_channel.ch_atac; - KASSERTMSG((schp->sch_active_slots & __BIT(slot)) != 0, - "%s: trying to deactivate inactive slot %d", SIISATANAME(sc), - slot); - - schp->sch_active_slots &= ~__BIT(slot); /* mark free */ SIISATA_PRB_SYNC(sc, schp, slot, BUS_DMASYNC_POSTWRITE); } Index: src/sys/dev/ic/siisatavar.h diff -u src/sys/dev/ic/siisatavar.h:1.7 src/sys/dev/ic/siisatavar.h:1.7.6.1 --- src/sys/dev/ic/siisatavar.h:1.7 Sat Oct 7 16:05:32 2017 +++ src/sys/dev/ic/siisatavar.h Thu Oct 11 20:57:51 2018 @@ -1,4 +1,4 @@ -/* $NetBSD: siisatavar.h,v 1.7 2017/10/07 16:05:32 jdolecek Exp $ */ +/* $NetBSD: siisatavar.h,v 1.7.6.1 2018/10/11 20:57:51 jdolecek Exp $ */ /* from ahcisatavar.h */ @@ -99,10 +99,6 @@ struct siisata_softc { bus_addr_t sch_bus_prb[SIISATA_MAX_SLOTS]; bus_dmamap_t sch_datad[SIISATA_MAX_SLOTS]; - - volatile uint32_t sch_active_slots; - uint32_t sch_hold_slots; - bool sch_recovering; } sc_channels[SIISATA_MAX_PORTS]; }; Added files: Index: src/sys/dev/ata/ata_recovery.c diff -u /dev/null src/sys/dev/ata/ata_recovery.c:1.1.2.1 --- /dev/null Thu Oct 11 20:57:51 2018 +++ src/sys/dev/ata/ata_recovery.c Thu Oct 11 20:57:51 2018 @@ -0,0 +1,247 @@ +/* $NetBSD: ata_recovery.c,v 1.1.2.1 2018/10/11 20:57:51 jdolecek Exp $ */ + +/*- + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: ata_recovery.c,v 1.1.2.1 2018/10/11 20:57:51 jdolecek Exp $"); + +#include "opt_ata.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/proc.h> +#include <sys/kthread.h> +#include <sys/errno.h> +#include <sys/ataio.h> +#include <sys/kmem.h> +#include <sys/intr.h> +#include <sys/bus.h> +#include <sys/bitops.h> + +#include <dev/ata/ataconf.h> +#include <dev/ata/atareg.h> +#include <dev/ata/atavar.h> + +#define DEBUG_FUNCS 0x08 +#define DEBUG_PROBE 0x10 +#define DEBUG_DETACH 0x20 +#define DEBUG_XFERS 0x40 +#ifdef ATADEBUG +extern int atadebug_mask; +#define ATADEBUG_PRINT(args, level) \ + if (atadebug_mask & (level)) \ + printf args +#else +#define ATADEBUG_PRINT(args, level) +#endif + +int +ata_read_log_ext_ncq(struct ata_drive_datas *drvp, uint8_t flags, + uint8_t *slot, uint8_t *status, uint8_t *err) +{ + struct ata_xfer *xfer = &drvp->recovery_xfer; + int rv; + struct ata_channel *chp = drvp->chnl_softc; + struct atac_softc *atac = chp->ch_atac; + uint8_t *tb, cksum, page; + + ATADEBUG_PRINT(("%s\n", __func__), DEBUG_FUNCS); + + /* Only NCQ ATA drives support/need this */ + if (drvp->drive_type != ATA_DRIVET_ATA || + (drvp->drive_flags & ATA_DRIVE_NCQ) == 0) + return EOPNOTSUPP; + + memset(xfer, 0, sizeof(*xfer)); + + tb = drvp->recovery_blk; + memset(tb, 0, sizeof(drvp->recovery_blk)); + + /* + * We could use READ LOG DMA EXT if drive supports it (i.e. + * when it supports Streaming feature) to avoid PIO command, + * and to make this a little faster. Realistically, it + * should not matter. + */ + xfer->c_flags |= C_SKIP_QUEUE; + xfer->c_ata_c.r_command = WDCC_READ_LOG_EXT; + xfer->c_ata_c.r_lba = page = WDCC_LOG_PAGE_NCQ; + xfer->c_ata_c.r_st_bmask = WDCS_DRDY; + xfer->c_ata_c.r_st_pmask = WDCS_DRDY; + xfer->c_ata_c.r_count = 1; + xfer->c_ata_c.r_device = WDSD_LBA; + xfer->c_ata_c.flags = AT_READ | AT_LBA | AT_LBA48 | flags; + xfer->c_ata_c.timeout = 1000; /* 1s */ + xfer->c_ata_c.data = tb; + xfer->c_ata_c.bcount = sizeof(drvp->recovery_blk); + + if ((*atac->atac_bustype_ata->ata_exec_command)(drvp, + xfer) != ATACMD_COMPLETE) { + rv = EAGAIN; + goto out; + } + if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) { + rv = EINVAL; + goto out; + } + + cksum = 0; + for (int i = 0; i < sizeof(drvp->recovery_blk); i++) + cksum += tb[i]; + if (cksum != 0) { + device_printf(drvp->drv_softc, + "invalid checksum %x for READ LOG EXT page %x\n", + cksum, page); + rv = EINVAL; + goto out; + } + + if (tb[0] & WDCC_LOG_NQ) { + /* not queued command */ + rv = EOPNOTSUPP; + goto out; + } + + *slot = tb[0] & 0x1f; + *status = tb[2]; + *err = tb[3]; + + if ((*status & WDCS_ERR) == 0) { + /* + * We expect error here. Normal physical drives always + * do, it's part of ATA standard. However, QEMU AHCI emulation + * mishandles READ LOG EXT in a way that the command itself + * returns without error, but no data is transferred. + */ + device_printf(drvp->drv_softc, + "READ LOG EXT page %x failed to report error: " + "slot %d err %x status %x\n", + page, *slot, *err, *status); + rv = EOPNOTSUPP; + goto out; + } + + rv = 0; + +out: + return rv; +} + +/* + * Must be called without channel lock, and with interrupts blocked. + */ +void +ata_recovery_resume(struct ata_channel *chp, int drive, int tfd, int flags) +{ + struct ata_drive_datas *drvp; + uint8_t slot, eslot, st, err; + int error; + struct ata_xfer *xfer; + const uint8_t ch_openings = ata_queue_openings(chp); + + ata_channel_lock(chp); + ata_queue_hold(chp); + ata_channel_unlock(chp); + + KASSERT(drive < chp->ch_ndrives); + drvp = &chp->ch_drive[drive]; + + /* + * When running NCQ commands, READ LOG EXT is necessary to clear the + * error condition and unblock the device. + */ + error = ata_read_log_ext_ncq(drvp, flags, &eslot, &st, &err); + + ata_channel_lock(chp); + ata_queue_unhold(chp); + ata_channel_unlock(chp); + + switch (error) { + case 0: + /* Error out the particular NCQ xfer, then requeue the others */ + if ((ata_queue_active(chp) & (1U << eslot)) != 0) { + xfer = ata_queue_hwslot_to_xfer(chp, eslot); + xfer->c_flags |= C_RECOVERED; + xfer->ops->c_intr(chp, xfer, ATACH_ERR_ST(err, st)); + } + break; + + case EOPNOTSUPP: + /* + * Non-NCQ command error, just find the slot and end with + * the error. + */ + for (slot = 0; slot < ch_openings; slot++) { + if ((ata_queue_active(chp) & (1U << slot)) != 0) { + xfer = ata_queue_hwslot_to_xfer(chp, slot); + xfer->ops->c_intr(chp, xfer, tfd); + } + } + break; + + case EAGAIN: + /* + * Failed to get resources to run the recovery command, must + * reset the drive. This will also kill all still outstanding + * transfers. + */ + ata_channel_lock(chp); + ata_thread_run(chp, ATACH_TH_RESET, ATACH_NODRIVE, flags); + ata_channel_unlock(chp); + goto out; + /* NOTREACHED */ + + default: + /* + * The command to get the slot failed. Kill outstanding + * commands for the same drive only. No need to reset + * the drive, it's unblocked nevertheless. + */ + break; + } + + /* Requeue all unfinished commands for same drive as failed command */ + for (slot = 0; slot < ch_openings; slot++) { + if ((ata_queue_active(chp) & (1U << slot)) == 0) + continue; + + xfer = ata_queue_hwslot_to_xfer(chp, slot); + if (drive != xfer->c_drive) + continue; + + xfer->ops->c_kill_xfer(chp, xfer, + (error == 0) ? KILL_REQUEUE : KILL_RESET); + } + +out: + /* Nothing more to do */ + return; +}