DF-3607 / ncr_timeout_race.c
/* DF-3607 trigger โ ncr timeout vs chip-owned nccb. * * Target: sys/dev/disk/ncr/ncr.c โ ncr_timeout() completes an nccb that is * still armed in the 53C8xx script processor's DSA (a CONNECTED, mid-phase * command). After completion, cp->ccb == NULL and cp->magic != CCB_MAGIC, * but ncr_int_ma() still finds the nccb by physical DSA match and either * (a) wedges the chip (returns without restart on opcode mismatch), or * (b) writes cp->patch[] from the stale (or already-reused) scatter table * and restarts the script processor โ resuming host DMA into a buffer the * CAM periph has already released/reused. * * Preconditions: * 1. an NCR 53C8xx HBA with an attached SCSI target * 2. the target accepts a command, then stalls in a data phase longer * than the CCB timeout (tlimit = timeout/1000 + 2 s in ncr_action) * 3. the target then resumes / changes phase -> MA interrupt -> ncr_int_ma * * This program floods pass(4) with 10-byte READ commands carrying a 1 ms * CCB timeout. Against a target that is slower than that (or deliberately * stalls mid-phase) this drives the exact daemon-completes-connected-job * window; watch the console for "timeout nccb=%p (skip)" (ncr.c:5162) * while the device is connected. * * Build (in guest, as root): cc -O2 -o ncr_timeout_race ncr_timeout_race.c -lcam * Run (in guest, as root): ./ncr_timeout_race pass0 200 */ #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <camlib.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/scsi/scsi_message.h> int main(int argc, char **argv) { struct cam_device *dev; union ccb *ccb; u_int8_t *cdb; int i, n = 200, ok = 0; if (argc < 2) { fprintf(stderr, "usage: %s <passN> [iterations]\n", argv[0]); return (2); } if (argc > 2) n = atoi(argv[2]); dev = cam_open_pass(argv[1], O_RDWR, NULL); if (dev == NULL) { fprintf(stderr, "cam_open_pass(%s): %s\n", argv[1], cam_errbuf); return (1); } for (i = 0; i < n; i++) { ccb = cam_getccb(dev); if (ccb == NULL) break; /* 10-byte READ, 1 block, no data buffer (phase choreography * is the target's job); 1 ms timeout << device service time * => ncr_timeout() daemon completes it while connected. */ memset(&ccb->csio, 0, sizeof(ccb->csio)); ccb->ccb_h.func_code = XPT_SCSI_IO; ccb->ccb_h.flags = CAM_DIR_NONE; ccb->ccb_h.retry_count = 0; ccb->ccb_h.timeout = 1; /* milliseconds */ ccb->csio.tag_action = MSG_SIMPLE_Q_TAG; ccb->csio.cdb_len = 10; ccb->csio.sense_len = 0; ccb->csio.dxfer_len = 0; ccb->csio.data_ptr = NULL; cdb = ccb->csio.cdb_io.cdb_bytes; cdb[0] = 0x28; /* READ(10) */ cdb[2] = cdb[3] = cdb[4] = cdb[5] = 0; /* LBA 0 */ cdb[7] = 0; cdb[8] = 1; /* 1 block */ cdb[9] = 0; if (cam_send_ccb(dev, ccb) < 0) fprintf(stderr, "cam_send_ccb: %s\n", cam_errbuf); else ok++; cam_freeccb(ccb); } printf("sent %d/%d ccb(s) with 1ms timeout\n", ok, n); cam_close_device(dev); return (0); } |