DF-4503 / trigger_DF4503.c
/* * DF-4503 trigger (post-condition half). * * Bug: ahci_pm_read()/ahci_pm_write() call ahci_ata_put_xfer() on ANY * non-ATA_S_COMPLETE return. When ahci_poll()'s timeout handler defers * ("Deferred timeout until its safe", ahci.c:3881) because OTHER commands * are still active on the port, ahci_ata_cmd() returns with * xa->state == ATA_S_ONCHIP -- the command is STILL PROGRAMMED ON THE * CHIP -- and the PM register accessors then recycle the CCB into the * free pool while the chip still owns the slot. The eventual completion * (or deferred-expiry processing) operates on a CCB that is marked * ATA_S_PUT or has been re-issued for an unrelated command: * * - ahci_ata_cmd_done(): KKASSERT(xa->state != ATA_S_ONCHIP && * xa->state != ATA_S_PUT) ahci.c:3775 -> panic on INVARIANTS kernels * - or the deferred-expiry loop (ahci.c:3894-3910) overwrites the state * of a CCB that has already been re-issued -> cross-command completion * corruption. * * Preconditions (hardware): SATA port multiplier with FBS enabled * (AP_F_FBSS_ENABLED; ahci_pm_port_probe enables it when controller+PM * support it) so that more than one standard command can be co-active * (ahci.c:2426-2433 limit>1), concurrent disk I/O on another PM target, * and a PM register access (e.g. the EINFO hotplug bitmap read, * ahci.c:841, or ahci_pm_check_good, ahci.c:891) that times out (1s) * while the co-issued I/O is still outstanding. PM register commands are * issued from the port state machine OUTSIDE beg/end_exclusive_access, * which is exactly the window. * * This program is the load half: keep the disks behind the PM busy while * poking the CAM bus so hotplug/notify processing keeps issuing PM * register reads. Run as root alongside an aggressive rescan loop * (run.sh). * * Usage: ./trigger_DF4503 <disk-device> [seconds] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #include <err.h> int main(int argc, char **argv) { char buf[65536]; const char *dev = (argc > 1) ? argv[1] : "/dev/da1"; long secs = (argc > 2) ? strtol(argv[2], NULL, 0) : 60; struct timespec start, now; int fd; long n = 0; fd = open(dev, O_RDONLY); if (fd < 0) err(1, "open %s", dev); if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) err(1, "clock_gettime"); for (;;) { if (pread(fd, buf, sizeof(buf), (off_t)(n % 1024) * 65536) < 0) err(1, "pread"); n++; clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec - start.tv_sec >= secs) break; } printf("done: %ld reads on %s\n", n, dev); return (0); } |