โฌข DragonFlyBSD Kernel Audit
DF-5026 / df5026_fixed.c
โ† back to finding โ†“ download raw
/*
 * DF-5026 sink demo โ€” ips: bios queued to an offline adapter are never
 * biodone'd; ips_start_io_request() swallows ips_get_free_cmd()'s EIO
 *
 * Root cause chain replicated verbatim:
 *
 *   sys/dev/raid/ips/ips.c:200-236 ips_get_free_cmd():
 *         if (sc->state & IPS_OFFLINE) { error = EIO; ... }
 *   sys/dev/raid/ips/ips_commands.c:238-253 ips_start_io_request():
 *         bio = bioq_first(&sc->bio_queue);
 *         if (bio == NULL) return;
 *         if (ips_get_free_cmd(sc, &command, 0) != 0)
 *             return;                        <-- EIO/EBUSY SWALLOWED:
 *                                                 bio stays queued forever
 *   sys/dev/raid/ips/ips_disk.c:128-143 ipsd_strategy():
 *         lockmgr(&dsc->sc->queue_lock, LK_EXCLUSIVE|LK_RETRY);
 *         bioqdisksort(&dsc->sc->bio_queue, bio);
 *         ips_start_io_request(dsc->sc);    <-- no fail-fast on
 *         lockmgr(...LK_RELEASE);               IPS_OFFLINE/IPS_TIMEOUT
 *
 * sc->state IPS_OFFLINE is set by ips_timeout() (ips.c:358) after a failed
 * forced adapter reinit ("AIEE! adapter reset failed ... giving up") and by
 * ips_cmdqueue_free() (ips.c:137). From then on every bio enqueued by
 * ipsd_strategy() waits forever: no biodone(), no B_ERROR, no b_resid โ€”
 * unkillable D-state for every issuer, and bio_queue grows without bound.
 *
 * Following the DF-4986/DF-5012 precedent (guest has no IBM ServeRAID
 * adapter), this module wires the exact enqueue/start/swallow pattern to a
 * raw D_DISK device:
 *
 *   read @ 3M   -> toggle the simulated IPS_OFFLINE state
 *   read @ 2M   -> bio parked in bio_queue forever while offline
 *   read @ 1M   -> drain-and-complete (what a correct error path must do)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/buf2.h>
#include <sys/lock.h>
#include <sys/malloc.h>

#define	PARK_OFF	(2UL << 20)	/* offset that parks the bio */
#define	POKE_OFF	(1UL << 20)	/* offset that drains the queue */
#define	TOGGLE_OFF	(3UL << 20)	/* offset that toggles offline */

static struct lock		queue_lock;	/* sc->queue_lock analog */
static struct bio_queue_head	bio_queue;	/* sc->bio_queue analog */
static int			offline;	/* sc->state & IPS_OFFLINE */
static int			parked;		/* leaked-bio counter */

static d_open_t	df5026_open;
static d_close_t df5026_close;
static d_strategy_t df5026_strategy;

static struct dev_ops df5026_ops = {
	{ "df5026", 0, D_DISK | D_MPSAFE },
	.d_open = df5026_open,
	.d_close = df5026_close,
	.d_strategy = df5026_strategy,
};

static struct disk	df5026_disk;

static int
df5026_open(struct dev_open_args *ap)
{
	return (0);
}

static int
df5026_close(struct dev_close_args *ap)
{
	return (0);
}

/* ips.c:207-209 analog โ€” OFFLINE yields EIO from get_free_cmd */
static int
sim_get_free_cmd(void)
{
	if (offline)
		return (EIO);		/* also EBUSY for IPS_TIMEOUT, ips.c:220 */
	return (0);
}

static void
sim_complete_bio(struct bio *bio)
{
	struct buf *bp = bio->bio_buf;

	bp->b_resid = 0;		/* ipsd_finish() healthy path, ips_disk.c:119 */
	biodone(bio);
}

static void sim_drain_queue(int error);

/* ips_commands.c:238-253 ips_start_io_request() โ€” VERBATIM structure */
static void
sim_start_io_request(void)
{
	struct bio *bio;

	bio = bioq_first(&bio_queue);
	if (bio == NULL)
		return;
	if (sim_get_free_cmd() != 0) {
		/* DF-5026 fix, ips_commands.c ips_start_io_request: terminal
		 * errors fail every queued bio instead of parking them */
		sim_drain_queue(EIO);
		return;
	}
	bioq_remove(&bio_queue, bio);
	sim_complete_bio(bio);		/* healthy adapter: command + completion */
}

/* what a correct driver must do when the adapter is dead: fail everything */
static void
sim_drain_queue(int error)
{
	struct bio *bio;

	while ((bio = bioq_first(&bio_queue)) != NULL) {
		struct buf *bp = bio->bio_buf;
		bioq_remove(&bio_queue, bio);
		bp->b_flags |= B_ERROR;
		bp->b_error = error;
		bp->b_resid = bp->b_bcount;
		biodone(bio);
		parked--;
	}
}

/*
 * ips_disk.c:128-143 ipsd_strategy() โ€” VERBATIM structure (control offsets
 * muxed in front, DF-5012 style, so the demo is self-driving and the guest
 * can always be returned to a clean state).
 */
static int
df5026_strategy(struct dev_strategy_args *ap)
{
	struct bio *bio = ap->a_bio;
	struct buf *bp = bio->bio_buf;

	/* ---- demo control plane (NOT part of the replicated code) ---- */
	if (bio->bio_offset == TOGGLE_OFF) {
		lockmgr(&queue_lock, LK_EXCLUSIVE | LK_RETRY);
		if (!offline) {
			offline = 1;
			kprintf("df5026: OFFLINE (ips_timeout reinit failed) "
			    "-- ips_get_free_cmd now returns EIO, "
			    "ips_start_io_request will swallow it\n");
		} else {
			offline = 0;
			kprintf("df5026: back ONLINE; restarting queue\n");
			sim_start_io_request();
		}
		lockmgr(&queue_lock, LK_RELEASE);
		bp->b_resid = 0;
		biodone(bio);
		return (0);
	}
	if (bio->bio_offset == POKE_OFF) {
		lockmgr(&queue_lock, LK_EXCLUSIVE | LK_RETRY);
		kprintf("df5026: poke -- draining %d parked bio(s) with EIO "
		    "(what the driver should have done)\n", parked);
		sim_drain_queue(EIO);
		lockmgr(&queue_lock, LK_RELEASE);
		bp->b_resid = 0;
		biodone(bio);
		return (0);
	}

	/* ---- ipsd_strategy (ips_disk.c:134-141) with the DF-5026 fix:
	 * fail fast when the adapter is dead instead of enqueueing into a
	 * queue that nobody will ever drain ---- */
	if (offline) {
		bp->b_flags |= B_ERROR;
		bp->b_error = EIO;
		bp->b_resid = bp->b_bcount;
		biodone(bio);
		return (0);
	}
	bio->bio_driver_info = NULL;	/* ips_disk.c:136 (dsc elided) */
	lockmgr(&queue_lock, LK_EXCLUSIVE | LK_RETRY);
	bioqdisksort(&bio_queue, bio);	/* ips_disk.c:139 */
	parked++;			/* observability only */
	sim_start_io_request();		/* ips_disk.c:140 โ€” parks if offline */
	lockmgr(&queue_lock, LK_RELEASE);/* ips_disk.c:141 */
	return (0);
}

static int
df5026_modevent(module_t mod, int type, void *data)
{
	struct disk_info info;
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		lockinit(&queue_lock, "df5026q", 0, 0);
		bioq_init(&bio_queue);
		bzero(&df5026_disk, sizeof(df5026_disk));
		bzero(&info, sizeof(info));
		info.d_media_blksize = 512;
		info.d_media_blocks = 8192;	/* 4 MiB test media */
		info.d_nheads = 64;
		info.d_secpertrack = 32;
		disk_create(0, &df5026_disk, &df5026_ops);
		disk_setdiskinfo(&df5026_disk, &info);
		offline = 0;
		parked = 0;
		kprintf("df5026: raw disk \"%s\" up (ONLINE) โ€” "
		    "read@3M toggle-offline, read@2M park, read@1M drain\n",
		    df5026_disk.d_cdev->si_name);
		break;
	case MOD_UNLOAD:
		/* hygiene: never unload with bios still parked */
		lockmgr(&queue_lock, LK_EXCLUSIVE | LK_RETRY);
		if (parked)
			kprintf("df5026: unload -- draining %d parked bio(s)\n",
			    parked);
		sim_drain_queue(ENXIO);
		lockmgr(&queue_lock, LK_RELEASE);
		disk_destroy(&df5026_disk);
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

DEV_MODULE(df5026, df5026_modevent, NULL);