/*
 * DF-4837 harness — ida bio path leaves qcb->flags == 0:
 *   ida.c:391   qcb->flags = 0;                        (never DMA_DATA_*)
 *   ida.c:399   op = qcb->flags & DMA_DATA_IN ? PREREAD : PREWRITE;  -> PREWRITE
 *   ida.c:506   if (qcb->flags & DMA_DATA_TRANSFER)    -> 0: SKIPPED
 *   ida.c:509   POSTREAD sync SKIPPED, bus_dmamap_unload SKIPPED
 *
 * On a >4GB machine the parent tag (ida_pci.c:260-265 lowaddr 32-bit;
 * busdma clamps child lowaddr, busdma_machdep.c:272) forces bouncing for
 * buffers above 4GB.  DFly busdma then requires:
 *   bus_dmamap_sync(PREWRITE)  orig -> bounce   (busdma_machdep.c:1044-1053)
 *   bus_dmamap_sync(POSTREAD)  bounce -> orig   (busdma_machdep.c:1054-1063)
 *   bus_dmamap_unload          return bounce pages to the zone (:1038 bpages)
 *
 * Skipping POSTREAD  => device-read data stays in the bounce page: the
 *                        caller's buffer keeps STALE contents (silent data
 *                        corruption on every bounced READ).
 * Skipping unload    => the map keeps its bounce pages forever: they are
 *                        never returned to the zone free list (leak per I/O).
 *
 * This harness models the map/zone bookkeeping and runs the exact flag logic
 * of ida_construct_qcb()/ida_done() for READ bios, baseline vs fixed.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define IDA_NSEG 32
#define DMA_DATA_IN       0x0001
#define DMA_DATA_OUT      0x0002
#define IDA_COMMAND       0x0004
#define DMA_DATA_TRANSFER (DMA_DATA_IN | DMA_DATA_OUT)

enum { PREWRITE = 1, POSTREAD = 2, PREREAD = 4 };

/* --- model of a DFly bounce-capable map/zone (busdma_machdep.c) --- */
static long zone_total_bpages, zone_free_pool;   /* allocated / returned */
static long zone_active(void) { return zone_total_bpages - zone_free_pool; }
struct map_s {
    int   loaded;
    long  held_bpages;         /* pages this map holds until unload */
    uint8_t bounce[512];
} qcb_map;

static uint8_t disk_buf[512];  /* bp->b_data page (the FS/user buffer) */
static uint8_t disk_hw[512];   /* what the DEVICE actually wrote (in bounce) */

static void map_load(void) {
    /* each load reserves a bounce page (add_bounce_page appends to
     * map->bpages; the zone grows on demand when the free list is empty) */
    qcb_map.loaded = 1;
    if (zone_free_pool > 0) zone_free_pool--; else zone_total_bpages++;
    qcb_map.held_bpages++;
}
static void dmasync(int op) {              /* _bus_dmamap_sync:1034-1067 */
    if (!qcb_map.loaded) return;
    if (op & PREWRITE) {                   /* orig -> bounce */
        memcpy(qcb_map.bounce, disk_buf, sizeof disk_buf);
        fprintf(stderr, "    sync(PREWRITE): orig->bounce copy done\n");
    }
    if (op & POSTREAD) {                   /* bounce -> orig */
        memcpy(disk_buf, qcb_map.bounce, sizeof disk_buf);
        fprintf(stderr, "    sync(POSTREAD): bounce->orig copy done (data delivered)\n");
    }
}
static void map_unload(void) {             /* bus_dmamap_unload */
    if (qcb_map.loaded) {
        qcb_map.loaded = 0;
        zone_free_pool += qcb_map.held_bpages;
        qcb_map.held_bpages = 0;
        fprintf(stderr, "    dmamap_unload: bounce page(s) returned to zone\n");
    }
}

/* --- verbatim flag logic of ida.c --- */
struct ida_qcb_h { int flags; };
static struct ida_qcb_h qcb;

static void ida_construct_qcb(int is_read, int fixed) {   /* ida.c:373-417 */
    qcb.flags = 0;                                        /* :391 */
    if (fixed)
        qcb.flags = DMA_DATA_TRANSFER | (is_read ? DMA_DATA_IN : DMA_DATA_OUT);
    map_load();
    int op = qcb.flags & DMA_DATA_IN ? PREREAD : PREWRITE; /* :399-401 */
    dmasync(op);
    /* device performs the transfer into/out of the bounce page */
}
static void ida_done(int fixed) {                          /* ida.c:499-538 */
    if (qcb.flags & DMA_DATA_TRANSFER) {                   /* :506 */
        int op = qcb.flags & DMA_DATA_IN ? POSTREAD : 0;   /* :509-510 */
        if (op) dmasync(op);
        map_unload();                                      /* :512 */
    } else if (!fixed) {
        fprintf(stderr, "    ida_done: flags&DMA_DATA_TRANSFER==0 -> "
                        "POSTREAD sync and dmamap_unload SKIPPED\n");
    }
}

static void run_reads(int fixed, int n) {
    memset(&qcb_map, 0, sizeof(qcb_map));
    zone_total_bpages = zone_free_pool = 0;
    memset(disk_buf, 0x11, sizeof disk_buf);          /* stale FS page */

    for (int i = 0; i < n; i++) {
        /* the device returns sector data 0xAA.. (only visible in bounce) */
        memset(disk_hw, 0xAA, sizeof disk_hw);
        ida_construct_qcb(1, fixed);
        memcpy(qcb_map.bounce, disk_hw, sizeof disk_hw);  /* device DMA (into bounce) */
        ida_done(fixed);
    }
    int stale = 0;
    for (size_t k = 0; k < sizeof disk_buf; k++) if (disk_buf[k] != 0xAA) stale = 1;
    printf("  %s: after %d READ bios: caller buffer %s; bounce pages allocated=%ld "
           "still held=%ld (leaked=%ld)\n", fixed ? "FIXED " : "BASELINE", n,
           stale ? "STALE (device data LOST)" : "has device data",
           zone_total_bpages, zone_active(), zone_active());
}

int main(void) {
    printf("[arm 1] single READ bio\n");
    run_reads(0, 1);
    run_reads(1, 1);
    printf("\n[arm 2] sustained I/O (bounce-page accounting per qcb map)\n");
    run_reads(0, 64);
    run_reads(1, 64);
    return 0;
}
