// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/* Copyright 2018-2019 IBM Corp. */

#include <assert.h>
#include <ccan/container_of/container_of.h>
#include <libflash/blocklevel.h>
#include <lock.h>
#include <lpc.h>
#include <hiomap.h>
#include <ipmi.h>
#include <opal-api.h>
#include <platform.h>
#include <stdio.h>
#include <stdlib.h>

#include "../ipmi-hiomap.h"
#include "../errors.h"

/* Stub for blocklevel debug macros */
bool libflash_debug;

const struct bmc_sw_config bmc_sw_hiomap = {
	.ipmi_oem_hiomap_cmd         = IPMI_CODE(0x3a, 0x5a),
};

const struct bmc_platform _bmc_platform = {
	.name = "generic:hiomap",
	.sw = &bmc_sw_hiomap,
};

enum scenario_event_type {
	scenario_sentinel = 0,
	scenario_event_p,
	scenario_cmd,
	scenario_sel,
	scenario_delay,
};

struct scenario_cmd_data {
	uint8_t cmd;
	uint8_t seq;
	uint8_t args[13];
} __attribute__((packed));

struct scenario_cmd {
	struct scenario_cmd_data req;
	struct scenario_cmd_data resp;
	uint8_t cc;
	size_t resp_size;
};

struct scenario_sel {
	uint8_t bmc_state;
};

struct scenario_event {
	enum scenario_event_type type;
	union {
		const struct scenario_event *p;
		struct scenario_cmd c;
		struct scenario_sel s;
	};
};

#define SCENARIO_SENTINEL { .type = scenario_sentinel }

struct ipmi_sel {
	void (*fn)(uint8_t data, void *context);
	void *context;
};

struct ipmi_msg_ctx {
	const struct scenario_event *scenario;
	const struct scenario_event *cursor;

	struct ipmi_sel sel;

	struct ipmi_msg msg;
};

struct ipmi_msg_ctx ipmi_msg_ctx;

const struct bmc_platform *bmc_platform = &_bmc_platform;

static void scenario_enter(const struct scenario_event *scenario)
{
	ipmi_msg_ctx.scenario = scenario;
	ipmi_msg_ctx.cursor = scenario;
}

static void scenario_advance(void)
{
	struct ipmi_msg_ctx *ctx = &ipmi_msg_ctx;

	assert(ctx->cursor->type == scenario_delay);
	ctx->cursor++;

	/* Deliver all the undelayed, scheduled SELs */
	while (ctx->cursor->type == scenario_sel) {
		ctx->sel.fn(ctx->cursor->s.bmc_state, ctx->sel.context);
		ctx->cursor++;
	}
}

static void scenario_exit(void)
{
	if (ipmi_msg_ctx.cursor->type != scenario_sentinel) {
		ptrdiff_t d = ipmi_msg_ctx.cursor - ipmi_msg_ctx.scenario;
		printf("%s: Exiting on event %tu with event type %d \n",
		       __func__, d, ipmi_msg_ctx.cursor->type);
		assert(false);
	}
}

void ipmi_init_msg(struct ipmi_msg *msg, int interface __attribute__((unused)),
		   uint32_t code, void (*complete)(struct ipmi_msg *),
		   void *user_data, size_t req_size, size_t resp_size)
{
	msg->backend = NULL;
	msg->cmd = IPMI_CMD(code);
	msg->netfn = IPMI_NETFN(code) << 2;
	msg->req_size = req_size;
	msg->resp_size = resp_size;
	msg->complete = complete;
	msg->user_data = user_data;
}

struct ipmi_msg *ipmi_mkmsg(int interface __attribute__((unused)),
			    uint32_t code, void (*complete)(struct ipmi_msg *),
			    void *user_data, void *req_data, size_t req_size,
			    size_t resp_size)
{
	struct ipmi_msg *msg = &ipmi_msg_ctx.msg;

	ipmi_init_msg(msg, 0 /* some bogus value */, code, complete, user_data,
		      req_size, resp_size);

	msg->data = malloc(req_size > resp_size ? req_size : resp_size);
	if (req_data)
		memcpy(msg->data, req_data, req_size);

	return msg;
}

void ipmi_free_msg(struct ipmi_msg *msg __attribute__((unused)))
{
	if (msg)
		free(msg->data);
}

void ipmi_queue_msg_sync(struct ipmi_msg *msg)
{
	struct ipmi_msg_ctx *ctx = container_of(msg, struct ipmi_msg_ctx, msg);
	const struct scenario_cmd *cmd;

	if (ctx->cursor->type == scenario_cmd) {
		cmd = &ctx->cursor->c;
	} else if (ctx->cursor->type == scenario_event_p) {
		assert(ctx->cursor->p->type == scenario_cmd);
		cmd = &ctx->cursor->p->c;
	} else {
		printf("Got unexpected request:\n");
		for (ssize_t i = 0; i < msg->req_size; i++)
			printf("msg->data[%zd]: 0x%02x\n", i, msg->data[i]);
		assert(false);
	}

	assert((msg->netfn >> 2) == 0x3a);
	assert(msg->cmd == 0x5a);
	assert(msg->req_size >= 2);

	if (memcmp(msg->data, &cmd->req, msg->req_size)) {
		printf("Comparing received vs expected message\n");
		for (ssize_t i = 0; i < msg->req_size; i++) {
			printf("msg->data[%zd]: 0x%02x, cmd->req[%zd]: 0x%02x\n",
			       i, msg->data[i], i, ((uint8_t *)(&cmd->req))[i]);
		}
		assert(false);
	}

	msg->cc = cmd->cc;
	memcpy(msg->data, &cmd->resp, msg->resp_size);

	if (cmd->resp_size)
		msg->resp_size = cmd->resp_size;

	msg->complete(msg);

	ctx->cursor++;

	/* Deliver all the scheduled SELs */
	while (ctx->cursor->type == scenario_sel) {
		ctx->sel.fn(ctx->cursor->s.bmc_state, ctx->sel.context);
		ctx->cursor++;
	}
}

int ipmi_sel_register(uint8_t oem_cmd __attribute__((unused)),
		      void (*fn)(uint8_t data, void *context),
		      void *context)
{
	ipmi_msg_ctx.sel.fn = fn;
	ipmi_msg_ctx.sel.context = context;

	return 0;
}

int64_t lpc_write(enum OpalLPCAddressType addr_type __attribute__((unused)),
		  uint32_t addr __attribute__((unused)),
		  uint32_t data __attribute__((unused)),
		  uint32_t sz)
{
	assert(sz != 0);
	return 0;
}

int64_t lpc_read(enum OpalLPCAddressType addr_type __attribute__((unused)),
		 uint32_t addr __attribute__((unused)), uint32_t *data,
		 uint32_t sz)
{
	memset(data, 0xaa, sz);

	return 0;
}

int64_t lpc_fw_read(uint32_t off, void *buf, uint32_t len)
{
	int rc;

	while (len) {
		uint32_t chunk;
		uint32_t dat;

		/* XXX: make this read until it's aligned */
		if (len > 3 && !(off & 3)) {
			rc = lpc_read(OPAL_LPC_FW, off, &dat, 4);
			if (!rc) {
				/*
				 * lpc_read swaps to CPU endian but it's not
				 * really a 32-bit value, so convert back.
				 */
				*(__be32 *)buf = cpu_to_be32(dat);
			}
			chunk = 4;
		} else {
			rc = lpc_read(OPAL_LPC_FW, off, &dat, 1);
			if (!rc)
				*(uint8_t *)buf = dat;
			chunk = 1;
		}
		if (rc)
			return rc;

		len -= chunk;
		off += chunk;
		buf += chunk;
	}

	return 0;
}

int64_t lpc_fw_write(uint32_t off, const void *buf, uint32_t len)
{
	int rc;

	while (len) {
		uint32_t chunk;

		if (len > 3 && !(off & 3)) {
			/* endian swap: see lpc_window_write */
			uint32_t dat = be32_to_cpu(*(__be32 *)buf);

			rc = lpc_write(OPAL_LPC_FW, off, dat, 4);
			chunk = 4;
		} else {
			uint8_t dat = *(uint8_t *)buf;

			rc = lpc_write(OPAL_LPC_FW, off, dat, 1);
			chunk = 1;
		}
		if (rc)
			return rc;

		len -= chunk;
		off += chunk;
		buf += chunk;
	}

	return 0;
}

static bool lpc_read_success(const uint8_t *buf, size_t len)
{
	if (len < 64) {
		while (len--)
			if (*buf++ != 0xaa)
				return false;
		return true;
	}

	for (int i = 0; i < 64; i++)
		if (buf[i] != 0xaa)
			return false;

	return !memcmp(buf, buf + 64, len - 64);
}

/* Commonly used messages */

static const struct scenario_event hiomap_ack_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_ACK,
			.seq = 1,
			.args = {
				[0] = HIOMAP_E_ACK_MASK,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_ACK,
			.seq = 1,
		},
	},
};

static const struct scenario_event hiomap_get_info_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_GET_INFO,
			.seq = 2,
			.args = {
				[0] = HIOMAP_V2,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_GET_INFO,
			.seq = 2,
			.args = {
				[0] = HIOMAP_V2,
				[1] = 12,
				[2] = 8, [3] = 0,
			},
		},
	},
};

static const struct scenario_event hiomap_get_flash_info_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_GET_FLASH_INFO,
			.seq = 3,
			.args = {
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_GET_FLASH_INFO,
			.seq = 3,
			.args = {
				[0] = 0x00, [1] = 0x20,
				[2] = 0x01, [3] = 0x00,
			},
		},
	},
};

static const struct scenario_event
hiomap_create_read_window_qs0l1_rs0l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_CREATE_READ_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0x00, [1] = 0x00,
				[2] = 0x01, [3] = 0x00,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_CREATE_READ_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0xff, [1] = 0x0f,
				[2] = 0x01, [3] = 0x00,
				[4] = 0x00, [5] = 0x00,
			},
		},
	},
};

static const struct scenario_event
hiomap_create_read_window_qs0l2_rs0l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_CREATE_READ_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0x00, [1] = 0x00,
				[2] = 0x02, [3] = 0x00,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_CREATE_READ_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0xff, [1] = 0x0f,
				[2] = 0x01, [3] = 0x00,
				[4] = 0x00, [5] = 0x00,
			},
		},
	},
};

static const struct scenario_event
hiomap_create_write_window_qs0l1_rs0l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0x00, [1] = 0x00,
				[2] = 0x01, [3] = 0x00,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0xff, [1] = 0x0f,
				[2] = 0x01, [3] = 0x00,
				[4] = 0x00, [5] = 0x00,
			},
		},
	},
};

static const struct scenario_event hiomap_mark_dirty_qs0l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_MARK_DIRTY,
			.seq = 5,
			.args = {
				[0] = 0x00, [1] = 0x00,
				[2] = 0x01, [3] = 0x00,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_MARK_DIRTY,
			.seq = 5,
		},
	},
};

static const struct scenario_event
hiomap_create_write_window_qs0l2_rs0l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0x00, [1] = 0x00,
				[2] = 0x02, [3] = 0x00,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
			.seq = 4,
			.args = {
				[0] = 0xff, [1] = 0x0f,
				[2] = 0x01, [3] = 0x00,
				[4] = 0x00, [5] = 0x00,
			},
		},
	},
};

static const struct scenario_event hiomap_flush_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_FLUSH,
			.seq = 6,
		},
		.resp = {
			.cmd = HIOMAP_C_FLUSH,
			.seq = 6,
		},
	},
};

static const struct scenario_event
hiomap_create_write_window_qs1l1_rs1l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
			.seq = 7,
			.args = {
				[0] = 0x01, [1] = 0x00,
				[2] = 0x01, [3] = 0x00,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
			.seq = 7,
			.args = {
				[0] = 0xfe, [1] = 0x0f,
				[2] = 0x01, [3] = 0x00,
				[4] = 0x01, [5] = 0x00,
			},
		},
	},
};

static const struct scenario_event hiomap_erase_qs0l1_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_ERASE,
			.seq = 5,
			.args = {
				[0] = 0x00, [1] = 0x00,
				[2] = 0x01, [3] = 0x00,
			},
		},
		.resp = {
			.cmd = HIOMAP_C_ERASE,
			.seq = 5,
		},
	},
};

static const struct scenario_event hiomap_reset_call_seq_4 = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_RESET,
			.seq = 4,
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_RESET,
			.seq = 4,
		},
	},
};

static const struct scenario_event hiomap_reset_call_seq_5 = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_RESET,
			.seq = 5,
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_RESET,
			.seq = 5,
		},
	},
};

static const struct scenario_event hiomap_reset_call_seq_6 = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_RESET,
			.seq = 6,
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_RESET,
			.seq = 6,
		},
	},
};

static const struct scenario_event hiomap_reset_call_seq_7 = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_RESET,
			.seq = 7,
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_RESET,
			.seq = 7,
		},
	},
};

static const struct scenario_event hiomap_reset_call_seq_9 = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_RESET,
			.seq = 9,
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_RESET,
			.seq = 9,
		},
	},
};

static const struct scenario_event hiomap_reset_call_seq_a = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_RESET,
			.seq = 0xa,
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_RESET,
			.seq = 0xa,
		},
	},
};

static const struct scenario_event scenario_hiomap_init[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_init(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_init);
	assert(!ipmi_hiomap_init(&bl));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_event_daemon_ready[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_event_daemon_ready(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;

	scenario_enter(scenario_hiomap_event_daemon_ready);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	assert(ctx->bmc_state == HIOMAP_E_DAEMON_READY);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_event_daemon_stopped[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET } },
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_event_daemon_stopped(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;

	scenario_enter(scenario_hiomap_event_daemon_stopped);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	assert(ctx->bmc_state == HIOMAP_E_PROTOCOL_RESET);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_event_daemon_restarted[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET } },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_event_daemon_restarted(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;

	scenario_enter(scenario_hiomap_event_daemon_restarted);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	assert(ctx->bmc_state == (HIOMAP_E_DAEMON_READY | HIOMAP_E_PROTOCOL_RESET));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_event_daemon_lost_flash_control[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = (HIOMAP_E_DAEMON_READY
					| HIOMAP_E_FLASH_LOST),
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_event_daemon_lost_flash_control(void)
{
	struct blocklevel_device *bl;
	size_t len = 2 * (1 << 12);
	void *buf;

	buf = malloc(len);
	assert(buf);

	scenario_enter(scenario_hiomap_event_daemon_lost_flash_control);
	assert(!ipmi_hiomap_init(&bl));
	assert(bl->read(bl, 0, buf, len) == FLASH_ERR_AGAIN);
	ipmi_hiomap_exit(bl);
	scenario_exit();

	free(buf);
}

static const struct scenario_event
scenario_hiomap_event_daemon_regained_flash_control_dirty[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x02, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0xfe, [1] = 0x0f,
					[2] = 0x02, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_delay
	},
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = (HIOMAP_E_DAEMON_READY
					| HIOMAP_E_FLASH_LOST),
		}
	},
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = (HIOMAP_E_DAEMON_READY
					| HIOMAP_E_WINDOW_RESET),
		}
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 5,
				.args = { [0] = HIOMAP_E_WINDOW_RESET },
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 5,
			}
		}
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 6,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x02, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 6,
				.args = {
					[0] = 0xfe, [1] = 0x0f,
					[2] = 0x02, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_event_daemon_regained_flash_control_dirty(void)
{
	struct blocklevel_device *bl;
	size_t len = 2 * (1 << 12);
	void *buf;

	buf = malloc(len);
	assert(buf);

	scenario_enter(scenario_hiomap_event_daemon_regained_flash_control_dirty);
	assert(!ipmi_hiomap_init(&bl));
	assert(!bl->read(bl, 0, buf, len));
	scenario_advance();
	assert(!bl->read(bl, 0, buf, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();

	free(buf);
}

static const struct scenario_event scenario_hiomap_protocol_reset_recovery[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x02, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0xfe, [1] = 0x0f,
					[2] = 0x02, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_delay
	},
	{
		.type = scenario_sel,
		.s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET, }
	},
	{
		.type = scenario_sel,
		.s = { .bmc_state = HIOMAP_E_DAEMON_READY, }
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 5,
				.args = { [0] = HIOMAP_E_PROTOCOL_RESET },
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 5,
			}
		}
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 6,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 6,
				.args = {
					[0] = HIOMAP_V2,
					[1] = 12,
					[2] = 8, [3] = 0,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 7,
				.args = {
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 7,
				.args = {
					[0] = 0x00, [1] = 0x20,
					[2] = 0x01, [3] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 8,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x02, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 8,
				.args = {
					[0] = 0xfe, [1] = 0x0f,
					[2] = 0x02, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_9, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_reset_recovery(void)
{
	struct blocklevel_device *bl;
	size_t len = 2 * (1 << 12);
	void *buf;

	buf = malloc(len);
	assert(buf);

	scenario_enter(scenario_hiomap_protocol_reset_recovery);
	assert(!ipmi_hiomap_init(&bl));
	assert(!bl->read(bl, 0, buf, len));
	scenario_advance();
	assert(!bl->read(bl, 0, buf, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();

	free(buf);
}

static const struct scenario_event
scenario_hiomap_protocol_read_one_block[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_read_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_read_one_block(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_read_one_block);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->read(bl, 0, buf, len));
	assert(lpc_read_success(buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_read_one_byte(void)
{
	struct blocklevel_device *bl;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_read_one_block);
	assert(!ipmi_hiomap_init(&bl));
	len = 1;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->read(bl, 0, buf, len));
	assert(lpc_read_success(buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_read_two_blocks[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_read_window_qs0l2_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 5,
				.args = {
					[0] = 0x01, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 5,
				.args = {
					[0] = 0xfe, [1] = 0x0f,
					[2] = 0x01, [3] = 0x00,
					[4] = 0x01, [5] = 0x00,
				},
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_read_two_blocks(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_read_two_blocks);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 2 * (1 << ctx->block_size_shift);
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->read(bl, 0, buf, len));
	assert(lpc_read_success(buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_read_1block_1byte(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_read_two_blocks);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = (1 << ctx->block_size_shift) + 1;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->read(bl, 0, buf, len));
	assert(lpc_read_success(buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_read_one_block_twice[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_read_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_read_one_block_twice(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_read_one_block_twice);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->read(bl, 0, buf, len));
	assert(!bl->read(bl, 0, buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_event_before_action[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_FLASH_LOST,
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_event_before_read(void)
{
	struct blocklevel_device *bl;
	char buf;
	int rc;

	scenario_enter(scenario_hiomap_protocol_event_before_action);
	assert(!ipmi_hiomap_init(&bl));
	rc = bl->read(bl, 0, &buf, sizeof(buf));
	assert(rc == FLASH_ERR_AGAIN);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_event_during_read[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_read_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_FLASH_LOST,
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_event_during_read(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;
	int rc;

	scenario_enter(scenario_hiomap_protocol_event_during_read);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	rc = bl->read(bl, 0, buf, len);
	assert(rc == FLASH_ERR_AGAIN);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_write_one_block[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_write_one_block(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_write_one_block);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->write(bl, 0, buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_write_one_byte(void)
{
	struct blocklevel_device *bl;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_write_one_block);
	assert(!ipmi_hiomap_init(&bl));
	len = 1;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->write(bl, 0, buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_write_two_blocks[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l2_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs1l1_rs1l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 8,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 8,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 9,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 9,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_a, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_write_two_blocks(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_write_two_blocks);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 2 * (1 << ctx->block_size_shift);
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->write(bl, 0, buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_write_1block_1byte(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_write_two_blocks);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len =  (1 << ctx->block_size_shift) + 1;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->write(bl, 0, buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_write_one_block_twice[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 7,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 7,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 8,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 8,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_9, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_write_one_block_twice(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	uint8_t *buf;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_write_one_block_twice);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(!bl->write(bl, 0, buf, len));
	assert(!bl->write(bl, 0, buf, len));
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_event_before_write(void)
{
	struct blocklevel_device *bl;
	char buf;
	int rc;

	scenario_enter(scenario_hiomap_protocol_event_before_action);
	assert(!ipmi_hiomap_init(&bl));
	rc = bl->write(bl, 0, &buf, sizeof(buf));
	assert(rc == FLASH_ERR_AGAIN);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_event_during_write[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_FLASH_LOST,
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_event_during_write(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	char *buf;
	int rc;

	scenario_enter(scenario_hiomap_protocol_event_during_write);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	rc = bl->write(bl, 0, buf, len);
	free(buf);
	assert(rc == FLASH_ERR_AGAIN);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_erase_one_block[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_event_p,
		.p = &hiomap_erase_qs0l1_call,
	},
	{
		.type = scenario_event_p,
		.p = &hiomap_flush_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, },
	SCENARIO_SENTINEL,
};

static const struct scenario_event
scenario_hiomap_protocol_erase_two_blocks[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l2_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs1l1_rs1l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 8,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 8,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 9,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 9,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_a, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_erase_two_blocks(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_erase_two_blocks);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 2 * (1 << ctx->block_size_shift);
	assert(!bl->erase(bl, 0, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_erase_one_block_twice[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 7,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 7,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 8,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 8,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_9, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_erase_one_block_twice(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_erase_one_block_twice);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	assert(!bl->erase(bl, 0, len));
	assert(!bl->erase(bl, 0, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_erase_one_block(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_erase_one_block);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	assert(!bl->erase(bl, 0, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_protocol_event_before_erase(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	int rc;

	scenario_enter(scenario_hiomap_protocol_event_before_action);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	rc = bl->erase(bl, 0, len);
	assert(rc == FLASH_ERR_AGAIN);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_event_during_erase[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_FLASH_LOST,
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_event_during_erase(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	int rc;

	scenario_enter(scenario_hiomap_protocol_event_during_erase);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	rc = bl->erase(bl, 0, len);
	assert(rc == FLASH_ERR_AGAIN);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_protocol_bad_sequence[] = {
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 1,
				.args = {
					[0] = HIOMAP_E_ACK_MASK,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 0,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_bad_sequence(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_protocol_bad_sequence);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_protocol_action_error[] = {
	{
		.type = scenario_cmd,
		.c = {
			/* Ack is legitimate, but we'll pretend it's invalid */
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 1,
				.args = { [0] = 0x3 },
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 1,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_action_error(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_protocol_action_error);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_get_flash_info[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 4,
				.args = {
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x20,
					[2] = 0x01, [3] = 0x00,
				},
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_get_flash_info(void)
{
	struct blocklevel_device *bl;
	const char *name;
	uint32_t granule;
	uint64_t size;

	scenario_enter(scenario_hiomap_protocol_get_flash_info);
	assert(!ipmi_hiomap_init(&bl));
	assert(!bl->get_info(bl, &name, &size, &granule));
	assert(!name);
	assert(size == (32 * 1024 * 1024));
	assert(granule == (4 * 1024));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_persistent_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{ .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET } },
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_persistent_error(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	char buf;
	int rc;

	scenario_enter(scenario_hiomap_protocol_persistent_error);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	assert(ctx->bmc_state == HIOMAP_E_PROTOCOL_RESET);
	rc = bl->read(bl, 0, &buf, sizeof(buf));
	assert(rc == FLASH_ERR_DEVICE_GONE);
	rc = bl->read(bl, 0, &buf, sizeof(buf));
	assert(rc == FLASH_ERR_DEVICE_GONE);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_get_info_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 2,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_get_info_error(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_get_info_error);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_get_flash_info_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 3,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_get_flash_info_error(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_get_flash_info_error);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_create_read_window_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_create_read_window_error(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_create_read_window_error);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->read(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_create_write_window_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_create_write_window_error(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_create_write_window_error);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_mark_dirty_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 5,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_mark_dirty_error(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_mark_dirty_error);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_flush_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 6,
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_flush_error(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_flush_error);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static void test_hiomap_ack_error(void)
{
	/* Same thing at the moment */
	test_hiomap_protocol_action_error();
}

static const struct scenario_event scenario_hiomap_erase_error[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 5,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_INVALID_COMMAND_ERR,
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_erase_error(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_erase_error);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	assert(bl->erase(bl, 0, len) > 0);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_ack_malformed_small[] = {
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 1,
				.args = { [0] = 0x3 },
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 1
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_ack_malformed_small(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_ack_malformed_small);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event scenario_hiomap_ack_malformed_large[] = {
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 1,
				.args = { [0] = 0x3 },
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 3,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 1,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_ack_malformed_large(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_ack_malformed_large);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_get_info_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 2,
				.args = { [0] = 0x2 },
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 7,
			.resp = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 2,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_get_info_malformed_small(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_get_info_malformed_small);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_get_info_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 2,
				.args = { [0] = 0x2 },
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 9,
			.resp = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 2,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_get_info_malformed_large(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_get_info_malformed_large);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_get_flash_info_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 3,
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 5,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 3,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_get_flash_info_malformed_small(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_get_flash_info_malformed_small);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_get_flash_info_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 3,
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 7,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 3,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_get_flash_info_malformed_large(void)
{
	struct blocklevel_device *bl;

	scenario_enter(scenario_hiomap_get_flash_info_malformed_large);
	assert(ipmi_hiomap_init(&bl) > 0);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_create_read_window_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 7,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_create_read_window_malformed_small(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_create_read_window_malformed_small);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->read(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();

}

static const struct scenario_event
scenario_hiomap_create_read_window_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 9,
			.resp = {
				.cmd = HIOMAP_C_CREATE_READ_WINDOW,
				.seq = 4,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_create_read_window_malformed_large(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_create_read_window_malformed_large);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->read(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_create_write_window_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 7,
			.resp = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 4,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_create_write_window_malformed_small(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_create_write_window_malformed_small);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();

}

static const struct scenario_event
scenario_hiomap_create_write_window_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 4,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp_size = 9,
			.resp = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 4,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_create_write_window_malformed_large(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_create_write_window_malformed_large);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_mark_dirty_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 5,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp_size = 1,
			.resp = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 5,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_mark_dirty_malformed_small(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_mark_dirty_malformed_small);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();

}

static const struct scenario_event
scenario_hiomap_mark_dirty_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 5,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp_size = 3,
			.resp = {
				.cmd = HIOMAP_C_MARK_DIRTY,
				.seq = 5,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_mark_dirty_malformed_large(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_mark_dirty_malformed_large);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_flush_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 6,
			},
			.resp_size = 1,
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 6,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_flush_malformed_small(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_flush_malformed_small);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();

}

static const struct scenario_event
scenario_hiomap_flush_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 6,
			},
			.resp_size = 3,
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 6,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_flush_malformed_large(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;
	void *buf;

	scenario_enter(scenario_hiomap_flush_malformed_large);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	buf = calloc(1, len);
	assert(buf);
	assert(bl->write(bl, 0, buf, len) > 0);
	free(buf);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_erase_malformed_small[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 5,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp_size = 1,
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 5,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_erase_malformed_small(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_erase_malformed_small);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	assert(bl->erase(bl, 0, len) > 0);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_erase_malformed_large[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 5,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp_size = 3,
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 5,
			},
		},
	},
	{ .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, },
	SCENARIO_SENTINEL,
};

static void test_hiomap_erase_malformed_large(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_erase_malformed_large);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	assert(bl->erase(bl, 0, len) > 0);
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

/* Common recovery calls */

static const struct scenario_event hiomap_recovery_ack_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_ACK,
			.seq = 7,
			.args = {
				[0] = HIOMAP_E_PROTOCOL_RESET,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_ACK,
			.seq = 7,
		},
	},
};

static const struct scenario_event hiomap_recovery_get_info_call = {
	.type = scenario_cmd,
	.c = {
		.req = {
			.cmd = HIOMAP_C_GET_INFO,
			.seq = 8,
			.args = {
				[0] = HIOMAP_V2,
			},
		},
		.cc = IPMI_CC_NO_ERROR,
		.resp = {
			.cmd = HIOMAP_C_GET_INFO,
			.seq = 8,
			.args = {
				[0] = HIOMAP_V2,
				[1] = 12,
				[2] = 8, [3] = 0,
			},
		},
	},
};

static const struct scenario_event
scenario_hiomap_protocol_recovery_failure_ack[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{ .type = scenario_delay },
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_PROTOCOL_RESET
		}
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 7,
				.args = {
					[0] = HIOMAP_E_PROTOCOL_RESET,
				},
			},
			.cc = IPMI_ERR_UNSPECIFIED,
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 8,
				.args = {
					[0] = HIOMAP_E_PROTOCOL_RESET,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 8,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 9,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 9,
				.args = {
					[0] = HIOMAP_V2,
					[1] = 12,
					[2] = 8, [3] = 0,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 10,
				.args = {
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 10,
				.args = {
					[0] = 0x00, [1] = 0x20,
					[2] = 0x01, [3] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 11,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 11,
				.args = {
					[0] = 0xff, [1] = 0x0f,
					[2] = 0x01, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 12,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 12,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 13,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 13,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_RESET,
				.seq = 14,
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_RESET,
				.seq = 14,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_recovery_failure_ack(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_recovery_failure_ack);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	/*
	 * We're erasing the same block 3 times - it's irrelevant, we're just
	 * trying to manipulate window state
	 */
	assert(!bl->erase(bl, 0, len));
	scenario_advance();
	assert(bl->erase(bl, 0, len) > 0);
	assert(!bl->erase(bl, 0, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_recovery_failure_get_info[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{ .type = scenario_delay },
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_PROTOCOL_RESET
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_recovery_ack_call, },
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 8,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_ERR_UNSPECIFIED,
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 9,
				.args = {
					[0] = HIOMAP_E_PROTOCOL_RESET,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 9,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 10,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 10,
				.args = {
					[0] = HIOMAP_V2,
					[1] = 12,
					[2] = 8, [3] = 0,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 11,
				.args = {
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 11,
				.args = {
					[0] = 0x00, [1] = 0x20,
					[2] = 0x01, [3] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 12,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 12,
				.args = {
					[0] = 0xff, [1] = 0x0f,
					[2] = 0x01, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 13,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 13,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 14,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 14,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_RESET,
				.seq = 15,
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_RESET,
				.seq = 15,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_recovery_failure_get_info(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_recovery_failure_get_info);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	/*
	 * We're erasing the same block 3 times - it's irrelevant, we're just
	 * trying to manipulate window state
	 */
	assert(!bl->erase(bl, 0, len));
	scenario_advance();
	assert(bl->erase(bl, 0, len) > 0);
	assert(!bl->erase(bl, 0, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

static const struct scenario_event
scenario_hiomap_protocol_recovery_failure_get_flash_info[] = {
	{ .type = scenario_event_p, .p = &hiomap_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_info_call, },
	{ .type = scenario_event_p, .p = &hiomap_get_flash_info_call, },
	{
		.type = scenario_event_p,
		.p = &hiomap_create_write_window_qs0l1_rs0l1_call,
	},
	{ .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, },
	{ .type = scenario_event_p, .p = &hiomap_flush_call, },
	{ .type = scenario_delay },
	{
		.type = scenario_sel,
		.s = {
			.bmc_state = HIOMAP_E_DAEMON_READY |
					HIOMAP_E_PROTOCOL_RESET
		}
	},
	{ .type = scenario_event_p, .p = &hiomap_recovery_ack_call, },
	{ .type = scenario_event_p, .p = &hiomap_recovery_get_info_call},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 9,
			},
			.cc = IPMI_ERR_UNSPECIFIED,
		},

	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ACK,
				.seq = 10,
				.args = {
					[0] = HIOMAP_E_PROTOCOL_RESET,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_ACK,
				.seq = 10,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 11,
				.args = {
					[0] = HIOMAP_V2,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_INFO,
				.seq = 11,
				.args = {
					[0] = HIOMAP_V2,
					[1] = 12,
					[2] = 8, [3] = 0,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 12,
				.args = {
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_GET_FLASH_INFO,
				.seq = 12,
				.args = {
					[0] = 0x00, [1] = 0x20,
					[2] = 0x01, [3] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 13,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_CREATE_WRITE_WINDOW,
				.seq = 13,
				.args = {
					[0] = 0xff, [1] = 0x0f,
					[2] = 0x01, [3] = 0x00,
					[4] = 0x00, [5] = 0x00,
				},
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 14,
				.args = {
					[0] = 0x00, [1] = 0x00,
					[2] = 0x01, [3] = 0x00,
				},
			},
			.resp = {
				.cmd = HIOMAP_C_ERASE,
				.seq = 14,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 15,
			},
			.resp = {
				.cmd = HIOMAP_C_FLUSH,
				.seq = 15,
			},
		},
	},
	{
		.type = scenario_cmd,
		.c = {
			.req = {
				.cmd = HIOMAP_C_RESET,
				.seq = 16,
			},
			.cc = IPMI_CC_NO_ERROR,
			.resp = {
				.cmd = HIOMAP_C_RESET,
				.seq = 16,
			},
		},
	},
	SCENARIO_SENTINEL,
};

static void test_hiomap_protocol_recovery_failure_get_flash_info(void)
{
	struct blocklevel_device *bl;
	struct ipmi_hiomap *ctx;
	size_t len;

	scenario_enter(scenario_hiomap_protocol_recovery_failure_get_flash_info);
	assert(!ipmi_hiomap_init(&bl));
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	/*
	 * We're erasing the same block 3 times - it's irrelevant, we're just
	 * trying to manipulate window state
	 */
	assert(!bl->erase(bl, 0, len));
	scenario_advance();
	ctx = container_of(bl, struct ipmi_hiomap, bl);
	len = 1 << ctx->block_size_shift;
	assert(bl->erase(bl, 0, len) > 0);
	assert(!bl->erase(bl, 0, len));
	ipmi_hiomap_exit(bl);
	scenario_exit();
}

struct test_case {
	const char *name;
	void (*fn)(void);
};

#define TEST_CASE(x) { #x, x }

struct test_case test_cases[] = {
	TEST_CASE(test_hiomap_init),
	TEST_CASE(test_hiomap_event_daemon_ready),
	TEST_CASE(test_hiomap_event_daemon_stopped),
	TEST_CASE(test_hiomap_event_daemon_restarted),
	TEST_CASE(test_hiomap_event_daemon_lost_flash_control),
	TEST_CASE(test_hiomap_event_daemon_regained_flash_control_dirty),
	TEST_CASE(test_hiomap_protocol_reset_recovery),
	TEST_CASE(test_hiomap_protocol_read_one_block),
	TEST_CASE(test_hiomap_protocol_read_one_byte),
	TEST_CASE(test_hiomap_protocol_read_two_blocks),
	TEST_CASE(test_hiomap_protocol_read_1block_1byte),
	TEST_CASE(test_hiomap_protocol_read_one_block_twice),
	TEST_CASE(test_hiomap_protocol_event_before_read),
	TEST_CASE(test_hiomap_protocol_event_during_read),
	TEST_CASE(test_hiomap_protocol_write_one_block),
	TEST_CASE(test_hiomap_protocol_write_one_byte),
	TEST_CASE(test_hiomap_protocol_write_two_blocks),
	TEST_CASE(test_hiomap_protocol_write_1block_1byte),
	TEST_CASE(test_hiomap_protocol_write_one_block_twice),
	TEST_CASE(test_hiomap_protocol_event_before_write),
	TEST_CASE(test_hiomap_protocol_event_during_write),
	TEST_CASE(test_hiomap_protocol_erase_one_block),
	TEST_CASE(test_hiomap_protocol_erase_two_blocks),
	TEST_CASE(test_hiomap_protocol_erase_one_block_twice),
	TEST_CASE(test_hiomap_protocol_event_before_erase),
	TEST_CASE(test_hiomap_protocol_event_during_erase),
	TEST_CASE(test_hiomap_protocol_bad_sequence),
	TEST_CASE(test_hiomap_protocol_action_error),
	TEST_CASE(test_hiomap_protocol_persistent_error),
	TEST_CASE(test_hiomap_protocol_get_flash_info),
	TEST_CASE(test_hiomap_get_info_error),
	TEST_CASE(test_hiomap_get_flash_info_error),
	TEST_CASE(test_hiomap_create_read_window_error),
	TEST_CASE(test_hiomap_create_write_window_error),
	TEST_CASE(test_hiomap_mark_dirty_error),
	TEST_CASE(test_hiomap_flush_error),
	TEST_CASE(test_hiomap_ack_error),
	TEST_CASE(test_hiomap_erase_error),
	TEST_CASE(test_hiomap_ack_malformed_small),
	TEST_CASE(test_hiomap_ack_malformed_large),
	TEST_CASE(test_hiomap_get_info_malformed_small),
	TEST_CASE(test_hiomap_get_info_malformed_large),
	TEST_CASE(test_hiomap_get_flash_info_malformed_small),
	TEST_CASE(test_hiomap_get_flash_info_malformed_large),
	TEST_CASE(test_hiomap_create_read_window_malformed_small),
	TEST_CASE(test_hiomap_create_read_window_malformed_large),
	TEST_CASE(test_hiomap_create_write_window_malformed_small),
	TEST_CASE(test_hiomap_create_write_window_malformed_large),
	TEST_CASE(test_hiomap_mark_dirty_malformed_small),
	TEST_CASE(test_hiomap_mark_dirty_malformed_large),
	TEST_CASE(test_hiomap_flush_malformed_small),
	TEST_CASE(test_hiomap_flush_malformed_large),
	TEST_CASE(test_hiomap_erase_malformed_small),
	TEST_CASE(test_hiomap_erase_malformed_large),
	TEST_CASE(test_hiomap_protocol_recovery_failure_ack),
	TEST_CASE(test_hiomap_protocol_recovery_failure_get_info),
	TEST_CASE(test_hiomap_protocol_recovery_failure_get_flash_info),
	{ NULL, NULL },
};

int main(void)
{
	struct test_case *tc = &test_cases[0];

	do {
		printf("%s\n", tc->name);
		tc->fn();
		printf("\n");
	} while ((++tc)->fn);

	return 0;
}
