<commit_before>/*
 * floppy.c
 * 
 * Test the standard Amiga floppy-drive interface: DF0-DF3.
 * 
 * Written & released by <NAME> <<EMAIL>>
 * 
 * This is free and unencumbered software released into the public domain.
 * See the file COPYING for more details, or visit <http://unlicense.org>.
 */

#include "testkit.h"

unsigned int mfm_decode_track(void *mfmbuf, void *headers, void *data,
                              unsigned int mfm_bytes);
void mfm_encode_track(void *mfmbuf, unsigned int tracknr,
                      unsigned int mfm_bytes,
                      unsigned int nr_secs);

/* CIAB IRQ: FLAG (disk index) pulse counter. */
static volatile unsigned int disk_index_count;
static volatile uint32_t disk_index_time, disk_index_period;

void disk_index_IRQ(void)
{
    uint32_t time = get_time();
    disk_index_count++;
    disk_index_period = time - disk_index_time;
    disk_index_time = time;
}

static void drive_deselect(void)
{
    ciab->prb |= 0xf9; /* motor-off, deselect all */
}

/* Select @drv and set motor on or off. */
static void drive_select(unsigned int drv, int on)
{
    drive_deselect(); /* motor-off, deselect all */
    if (on)
        ciab->prb &= ~CIABPRB_MTR; /* motor-on */
    ciab->prb &= ~(CIABPRB_SEL0 << drv); /* select drv */
}

/* Basic wait-for-RDY. */
static void drive_wait_ready(void)
{
    uint32_t s = get_time(), half_sec = ms_to_ticks(500);
    int ready;

    do {
        ready = !!(~ciaa->pra & CIAAPRA_RDY);
    } while (!ready && ((get_time() - s) < half_sec));
}

/* Sophisticated wait-for-RDY with diagnostic report. */
static void drive_check_ready(struct char_row *r, bool_t is_hd)
{
    uint32_t s = get_time(), e;
    unsigned int timeout = ms_to_ticks(is_hd ? 2000 : 1250);
    int ready;

    do {
        ready = !!(~ciaa->pra & CIAAPRA_RDY);
        e = get_time();
    } while (!ready && ((e - s) < timeout));

    if (ready) {
        char delaystr[10];
        e = get_time();
        ticktostr(e - s, delaystr);
        sprintf((char *)r->s,
                (e - s) < ms_to_ticks(1)
                ? "Drive READY too fast (%s): Gotek or modified PC drive?"
                : "Drive READY asserted %s after Motor On",
                delaystr);
    } else {
        sprintf((char *)r->s,
                "No READY signal: PC or Escom drive?");
    }

    print_line(r);
    r->y++;

    if (ready) {
        s = get_time();
        timeout = ms_to_ticks(10);
        do {
            ready = !!(~ciaa->pra & CIAAPRA_RDY);
            e = get_time();
        } while (ready && ((e - s) < timeout));
        if (!ready) {
            sprintf((char *)r->s,
                    "READY signal is oscillating: modified PC drive?");
            print_line(r);
            r->y++;
        }
    }
}

/* Returns number of head steps to find cylinder 0. */
static int cur_cyl;
static unsigned int seek_cyl0(void)
{
    unsigned int steps = 0;

    ciab->prb |= CIABPRB_DIR | CIABPRB_SIDE; /* outward, side 0 */
    delay_ms(18);

    cur_cyl = 0;

    while (!(~ciaa->pra & CIAAPRA_TK0)) {
        ciab->prb &= ~CIABPRB_STEP;
        ciab->prb |= CIABPRB_STEP;
        delay_ms(3);
        if (steps++ == 100) {
            cur_cyl = -1;
            break;
        }
    }

    delay_ms(15);

    return steps;
}

static void seek_track(unsigned int track)
{
    unsigned int cyl = track >> 1;
    int diff;

    if (cur_cyl == -1)
        return;

    if (cyl == 0)
        seek_cyl0();

    diff = cyl - cur_cyl;
    if (diff < 0) {
        diff = -diff;
        ciab->prb |= CIABPRB_DIR; /* outward */
    } else {
        ciab->prb &= ~CIABPRB_DIR; /* inward */
    }
    delay_ms(18);

    while (diff--) {
        ciab->prb &= ~CIABPRB_STEP;
        ciab->prb |= CIABPRB_STEP;
        delay_ms(3);
    }

    delay_ms(15);

    cur_cyl = cyl;

    if (!(track & 1)) {
        ciab->prb |= CIABPRB_SIDE; /* side 0 */
    } else {
        ciab->prb &= ~CIABPRB_SIDE; /* side 1 */
    }
}

static void disk_read_track(void *mfm, uint16_t mfm_bytes)
{
    cust->dskpt.p = mfm;
    cust->adkcon = 0x7f00; /* clear disk flags */
    cust->intreq = INT_DSKBLK; /* clear dsk-blk-done */
    cust->adkcon = 0x9500; /* MFM, wordsync */
    cust->dsksync = 0x4489; /* sync 4489 */
    cust->dsklen = 0x8000 + mfm_bytes / 2;
    cust->dsklen = 0x8000 + mfm_bytes / 2;
}

static void disk_write_track(void *mfm, uint16_t mfm_bytes)
{
    cust->dskpt.p = mfm;
    cust->adkcon = 0x7f00; /* clear disk flags */
    cust->intreq = INT_DSKBLK; /* clear dsk-blk-done */
    /* 140ns precomp for cylinders 40-79 (exactly the same as
     * trackdisk.device, tested on Kickstart 3.1). */
    if (cur_cyl >= 40)
        cust->adkcon = 0xa000;
    cust->adkcon = 0x9100; /* MFM, no wordsync */
    cust->dsklen = 0xc000 + mfm_bytes / 2;
    cust->dsklen = 0xc000 + mfm_bytes / 2;
}

static void disk_wait_dma(bool_t is_hd)
{
    unsigned int i, timeout = is_hd ? 1000 : 500;
    for (i = 0; i < timeout; i++) {
        if (cust->intreqr & INT_DSKBLK) /* dsk-blk-done? */
            break;
        delay_ms(1);
    }
    cust->dsklen = 0x4000; /* no more dma */
}

/* Amiga Drive IDs, from resources/disk.i */
#define DRT_AMIGA      0x00000000
#define DRT_37422D2S   0x55555555
#define DRT_EMPTY      0xFFFFFFFF
#define DRT_150RPM     0xAAAAAAAA

static uint32_t _drive_id(unsigned int drv)
{
    uint32_t id = 0;
    uint8_t mask = CIABPRB_SEL0 << drv;
    unsigned int i;

    ciab->prb |= 0xf8;  /* motor-off, deselect all */
    ciab->prb &= 0x7f;  /* 1. MTRXD low */
    ciab->prb &= ~mask; /* 2. SELxB low */
    ciab->prb |= mask;  /* 3. SELxB high */
    ciab->prb |= 0x80;  /* 4. MTRXD high */
    ciab->prb &= ~mask; /* 5. SELxB low */
    ciab->prb |= mask;  /* 6. SELxB high */
    for (i = 0; i < 32; i++) {
        ciab->prb &= ~mask; /* 7. SELxB low */
        id = (id<<1) | ((ciaa->pra>>5)&1); /* 8. Read and save state of RDY */
        ciab->prb |= mask;  /* 9. SELxB high */
    }
    return id;
}

/* Get drive id but handle Gotek's unsynchronised HD ID bitstream. 
 * We can see either 5555.... or AAAA.... */
static uint32_t drive_id(unsigned int drv)
{
    uint32_t id[3];
    id[0] = _drive_id(drv);
    id[1] = _drive_id(drv);
    ciab->prb &= ~(CIABPRB_SEL0 << drv);
    ciab->prb |= CIABPRB_SEL0 << drv;
    id[2] = _drive_id(drv);
    if (((id[0] != id[1]) || (id[1] != id[2]))
        && ((id[0] == 0x55555555) || (id[0] == 0xaaaaaaaa))
        && ((id[1] == id[0]) || (id[1] == ~id[0]))
        && ((id[2] == id[0]) || (id[2] == ~id[0]))) {
        id[0] = DRT_150RPM;
    }
    if ((id[0] == DRT_EMPTY) && (drv == 0))
        id[0] = DRT_AMIGA;
    return id[0];
}

static const char *drive_id_type(unsigned int drv, uint32_t id)
{
    return (id == 0) || ((id == ~0) && (drv == 0)) ? "DS-DD 80"
        : (id == 0x55555555) ? "DS-DD 40"
        : (id == 0xaaaaaaaa) ? "DS-HD 80"
        : (id == 0xffffffff) ? "No Drive" : "Unknown";
}

static unsigned int drive_signal_test(unsigned int drv, struct char_row *r)
{
    char *s = (char *)r->s;
    uint8_t motors = 0, pra, old_pra, prb, old_prb, key = 0;
    unsigned int i, old_disk_index_count;
    uint32_t rdy_delay, mtr_time, key_time, mtr_timeout;
    int rdy_changed;

    /* Motor on for 30 seconds at a time when there is no user input. */
    mtr_timeout = ms_to_ticks(30*1000);

    while (!do_exit && (key != K_ESC)) {

        /* Oddities of external drives when motor is off:
         *  1. TRK0 signal may be switched off;
         *  2. Drive may not physically step heads, in one or both directions.
         * However:
         *  1. CHNG signal correctly asserts on disk removal and deasserts on
         *     disk insertion + step signal (even if the drive does not
         *     physically step);
         *  2. WPRO signal appears to be correctly produced at all times 
         *     when a disk is in the drive. 
         * In summary: 
         *  Do not step heads or synchronise to track 0 except when the motor 
         *  is switched on, and preferably after waiting for RDY or 500ms. 
         *  CHNG and WPRO handling can occur with motor switched off. */
        drive_select(drv, 1);

        /* We shouldn't strictly need to wait for RDY but it's sensible to
         * allow the turn-on current surge to subside before energising the
         * stepper motor. */
        drive_wait_ready();

        seek_cyl0();
        if (cur_cyl == 0) {
            unsigned int nr_cyls;
            seek_track(159);
            nr_cyls = seek_cyl0() + 1;
            if (cur_cyl == 0)
                sprintf(s, "-- DF%u: %u cylinders --", drv, nr_cyls);
        }
        if (cur_cyl < 0)
            sprintf(s, "-- DF%u: No Track 0 (Drive not present?) --", drv);
        print_line(r);
        r->y += 3;

        /* Switch off the drive motor if it was only turned on for
         * external-drive seek test. */
        if (!(motors & (1u << drv)))
            drive_select(drv, 0);

        sprintf(s, "$1 DF0$  $2 DF1$  $3 DF2$  $4 DF3$");
        print_line(r);
        r->y++;
        sprintf(s, "$5 Motor On/Off$  $6 Step$");
        print_line(r);
        r->y -= 3;

        old_pra = ciaa->pra;
        old_prb = ciab->prb;
        mtr_time = get_time();
        rdy_delay = rdy_changed = 0;
        old_disk_index_count = disk_index_count = 0;
        key_time = get_time();
        key = 1; /* force print */

        while (!do_exit) {
            pra = ciaa->pra;
            prb = ciab->prb;
            if ((pra != old_pra) || key) {
                rdy_changed = !!((old_pra ^ pra) & CIAAPRA_RDY);
                if (rdy_changed)
                    rdy_delay = get_time() - mtr_time;
                sprintf(s, "Motors=(%c%c%c%c) CIAAPRA=0x%02x (%s %s %s %s)",
                        motors&(1u<<0) ? '0' : ' ',
                        motors&(1u<<1) ? '1' : ' ',
                        motors&(1u<<2) ? '2' : ' ',
                        motors&(1u<<3) ? '3' : ' ',
                        pra,
                        ~pra & CIAAPRA_CHNG ? "CHG" : "",
                        ~pra & CIAAPRA_WPRO ? "WPR" : "",
                        ~pra & CIAAPRA_TK0  ? "TK0" : "",
                        ~pra & CIAAPRA_RDY  ? "RDY" : "");
                print_line(r);
                old_pra = pra;
            }
            if ((prb != old_prb) || key) {
                r->y += 4;
                sprintf(s, "$7 STEP=%u$  $8 DIR=%u$",
                        !!(prb & CIABPRB_STEP), !!(prb & CIABPRB_DIR));
                print_line(r);
                r->y -= 4;
                old_prb = prb;
            }
            if ((old_disk_index_count != disk_index_count)
                || rdy_changed || key) {
                char rdystr[10], idxstr[10];
                ticktostr(disk_index_period, idxstr);
                ticktostr(rdy_delay, rdystr);
                sprintf(s, "%u Index Pulses (period %s); MTR%s->RDY%u %s",
                        disk_index_count, idxstr,
                        !!(motors&(1u<<drv)) ? "On" : "Off",
                        !!(old_pra & CIAAPRA_RDY), rdystr);
                r->y++;
                print_line(r);
                r->y--;
                old_disk_index_count = disk_index_count;
                rdy_changed = 0;
            }
            if (((get_time() - key_time) >= mtr_timeout) && motors) {
                int was_on = !!(motors & (1u<<drv));
                motors = 0;
                for (i = 0; i < 4; i++)
                    drive_select(i, 0);
                drive_select(drv, 0);
                if (was_on)
                    mtr_time = get_time();
                key = 1; /* force print */
                continue;
            }
            if (!(key = keycode_buffer))
                continue;
            keycode_buffer = 0;
            key_time = get_time();
            if ((key >= K_F1) && (key <= 0x53)) { /* F1-F4 */
                drv = key - K_F1;
                r->y--;
                break;
            } else if (key == 0x54) { /* F5 */
                motors ^= 1u << drv;
                drive_select(drv, !!(motors & (1u << drv)));
                old_pra = ciaa->pra;
                mtr_time = get_time();
                rdy_delay = 0;
            } else if (key == 0x55) { /* F6 */<commit_msg>testkit: Remove 30-second motor timeout in floppy signal test.<commit_after>/*
 * floppy.c
 * 
 * Test the standard Amiga floppy-drive interface: DF0-DF3.
 * 
 * Written & released by <NAME> <<EMAIL>>
 * 
 * This is free and unencumbered software released into the public domain.
 * See the file COPYING for more details, or visit <http://unlicense.org>.
 */

#include "testkit.h"

unsigned int mfm_decode_track(void *mfmbuf, void *headers, void *data,
                              unsigned int mfm_bytes);
void mfm_encode_track(void *mfmbuf, unsigned int tracknr,
                      unsigned int mfm_bytes,
                      unsigned int nr_secs);

/* CIAB IRQ: FLAG (disk index) pulse counter. */
static volatile unsigned int disk_index_count;
static volatile uint32_t disk_index_time, disk_index_period;

void disk_index_IRQ(void)
{
    uint32_t time = get_time();
    disk_index_count++;
    disk_index_period = time - disk_index_time;
    disk_index_time = time;
}

static void drive_deselect(void)
{
    ciab->prb |= 0xf9; /* motor-off, deselect all */
}

/* Select @drv and set motor on or off. */
static void drive_select(unsigned int drv, int on)
{
    drive_deselect(); /* motor-off, deselect all */
    if (on)
        ciab->prb &= ~CIABPRB_MTR; /* motor-on */
    ciab->prb &= ~(CIABPRB_SEL0 << drv); /* select drv */
}

/* Basic wait-for-RDY. */
static void drive_wait_ready(void)
{
    uint32_t s = get_time(), half_sec = ms_to_ticks(500);
    int ready;

    do {
        ready = !!(~ciaa->pra & CIAAPRA_RDY);
    } while (!ready && ((get_time() - s) < half_sec));
}

/* Sophisticated wait-for-RDY with diagnostic report. */
static void drive_check_ready(struct char_row *r, bool_t is_hd)
{
    uint32_t s = get_time(), e;
    unsigned int timeout = ms_to_ticks(is_hd ? 2000 : 1250);
    int ready;

    do {
        ready = !!(~ciaa->pra & CIAAPRA_RDY);
        e = get_time();
    } while (!ready && ((e - s) < timeout));

    if (ready) {
        char delaystr[10];
        e = get_time();
        ticktostr(e - s, delaystr);
        sprintf((char *)r->s,
                (e - s) < ms_to_ticks(1)
                ? "Drive READY too fast (%s): Gotek or modified PC drive?"
                : "Drive READY asserted %s after Motor On",
                delaystr);
    } else {
        sprintf((char *)r->s,
                "No READY signal: PC or Escom drive?");
    }

    print_line(r);
    r->y++;

    if (ready) {
        s = get_time();
        timeout = ms_to_ticks(10);
        do {
            ready = !!(~ciaa->pra & CIAAPRA_RDY);
            e = get_time();
        } while (ready && ((e - s) < timeout));
        if (!ready) {
            sprintf((char *)r->s,
                    "READY signal is oscillating: modified PC drive?");
            print_line(r);
            r->y++;
        }
    }
}

/* Returns number of head steps to find cylinder 0. */
static int cur_cyl;
static unsigned int seek_cyl0(void)
{
    unsigned int steps = 0;

    ciab->prb |= CIABPRB_DIR | CIABPRB_SIDE; /* outward, side 0 */
    delay_ms(18);

    cur_cyl = 0;

    while (!(~ciaa->pra & CIAAPRA_TK0)) {
        ciab->prb &= ~CIABPRB_STEP;
        ciab->prb |= CIABPRB_STEP;
        delay_ms(3);
        if (steps++ == 100) {
            cur_cyl = -1;
            break;
        }
    }

    delay_ms(15);

    return steps;
}

static void seek_track(unsigned int track)
{
    unsigned int cyl = track >> 1;
    int diff;

    if (cur_cyl == -1)
        return;

    if (cyl == 0)
        seek_cyl0();

    diff = cyl - cur_cyl;
    if (diff < 0) {
        diff = -diff;
        ciab->prb |= CIABPRB_DIR; /* outward */
    } else {
        ciab->prb &= ~CIABPRB_DIR; /* inward */
    }
    delay_ms(18);

    while (diff--) {
        ciab->prb &= ~CIABPRB_STEP;
        ciab->prb |= CIABPRB_STEP;
        delay_ms(3);
    }

    delay_ms(15);

    cur_cyl = cyl;

    if (!(track & 1)) {
        ciab->prb |= CIABPRB_SIDE; /* side 0 */
    } else {
        ciab->prb &= ~CIABPRB_SIDE; /* side 1 */
    }
}

static void disk_read_track(void *mfm, uint16_t mfm_bytes)
{
    cust->dskpt.p = mfm;
    cust->adkcon = 0x7f00; /* clear disk flags */
    cust->intreq = INT_DSKBLK; /* clear dsk-blk-done */
    cust->adkcon = 0x9500; /* MFM, wordsync */
    cust->dsksync = 0x4489; /* sync 4489 */
    cust->dsklen = 0x8000 + mfm_bytes / 2;
    cust->dsklen = 0x8000 + mfm_bytes / 2;
}

static void disk_write_track(void *mfm, uint16_t mfm_bytes)
{
    cust->dskpt.p = mfm;
    cust->adkcon = 0x7f00; /* clear disk flags */
    cust->intreq = INT_DSKBLK; /* clear dsk-blk-done */
    /* 140ns precomp for cylinders 40-79 (exactly the same as
     * trackdisk.device, tested on Kickstart 3.1). */
    if (cur_cyl >= 40)
        cust->adkcon = 0xa000;
    cust->adkcon = 0x9100; /* MFM, no wordsync */
    cust->dsklen = 0xc000 + mfm_bytes / 2;
    cust->dsklen = 0xc000 + mfm_bytes / 2;
}

static void disk_wait_dma(bool_t is_hd)
{
    unsigned int i, timeout = is_hd ? 1000 : 500;
    for (i = 0; i < timeout; i++) {
        if (cust->intreqr & INT_DSKBLK) /* dsk-blk-done? */
            break;
        delay_ms(1);
    }
    cust->dsklen = 0x4000; /* no more dma */
}

/* Amiga Drive IDs, from resources/disk.i */
#define DRT_AMIGA      0x00000000
#define DRT_37422D2S   0x55555555
#define DRT_EMPTY      0xFFFFFFFF
#define DRT_150RPM     0xAAAAAAAA

static uint32_t _drive_id(unsigned int drv)
{
    uint32_t id = 0;
    uint8_t mask = CIABPRB_SEL0 << drv;
    unsigned int i;

    ciab->prb |= 0xf8;  /* motor-off, deselect all */
    ciab->prb &= 0x7f;  /* 1. MTRXD low */
    ciab->prb &= ~mask; /* 2. SELxB low */
    ciab->prb |= mask;  /* 3. SELxB high */
    ciab->prb |= 0x80;  /* 4. MTRXD high */
    ciab->prb &= ~mask; /* 5. SELxB low */
    ciab->prb |= mask;  /* 6. SELxB high */
    for (i = 0; i < 32; i++) {
        ciab->prb &= ~mask; /* 7. SELxB low */
        id = (id<<1) | ((ciaa->pra>>5)&1); /* 8. Read and save state of RDY */
        ciab->prb |= mask;  /* 9. SELxB high */
    }
    return id;
}

/* Get drive id but handle Gotek's unsynchronised HD ID bitstream. 
 * We can see either 5555.... or AAAA.... */
static uint32_t drive_id(unsigned int drv)
{
    uint32_t id[3];
    id[0] = _drive_id(drv);
    id[1] = _drive_id(drv);
    ciab->prb &= ~(CIABPRB_SEL0 << drv);
    ciab->prb |= CIABPRB_SEL0 << drv;
    id[2] = _drive_id(drv);
    if (((id[0] != id[1]) || (id[1] != id[2]))
        && ((id[0] == 0x55555555) || (id[0] == 0xaaaaaaaa))
        && ((id[1] == id[0]) || (id[1] == ~id[0]))
        && ((id[2] == id[0]) || (id[2] == ~id[0]))) {
        id[0] = DRT_150RPM;
    }
    if ((id[0] == DRT_EMPTY) && (drv == 0))
        id[0] = DRT_AMIGA;
    return id[0];
}

static const char *drive_id_type(unsigned int drv, uint32_t id)
{
    return (id == 0) || ((id == ~0) && (drv == 0)) ? "DS-DD 80"
        : (id == 0x55555555) ? "DS-DD 40"
        : (id == 0xaaaaaaaa) ? "DS-HD 80"
        : (id == 0xffffffff) ? "No Drive" : "Unknown";
}

static unsigned int drive_signal_test(unsigned int drv, struct char_row *r)
{
    char *s = (char *)r->s;
    uint8_t motors = 0, pra, old_pra, prb, old_prb, key = 0;
    unsigned int i, old_disk_index_count;
    uint32_t rdy_delay, mtr_time;
    int rdy_changed;

    while (!do_exit && (key != K_ESC)) {

        /* Oddities of external drives when motor is off:
         *  1. TRK0 signal may be switched off;
         *  2. Drive may not physically step heads, in one or both directions.
         * However:
         *  1. CHNG signal correctly asserts on disk removal and deasserts on
         *     disk insertion + step signal (even if the drive does not
         *     physically step);
         *  2. WPRO signal appears to be correctly produced at all times 
         *     when a disk is in the drive. 
         * In summary: 
         *  Do not step heads or synchronise to track 0 except when the motor 
         *  is switched on, and preferably after waiting for RDY or 500ms. 
         *  CHNG and WPRO handling can occur with motor switched off. */
        drive_select(drv, 1);

        /* We shouldn't strictly need to wait for RDY but it's sensible to
         * allow the turn-on current surge to subside before energising the
         * stepper motor. */
        drive_wait_ready();

        seek_cyl0();
        if (cur_cyl == 0) {
            unsigned int nr_cyls;
            seek_track(159);
            nr_cyls = seek_cyl0() + 1;
            if (cur_cyl == 0)
                sprintf(s, "-- DF%u: %u cylinders --", drv, nr_cyls);
        }
        if (cur_cyl < 0)
            sprintf(s, "-- DF%u: No Track 0 (Drive not present?) --", drv);
        print_line(r);
        r->y += 3;

        /* Switch off the drive motor if it was only turned on for
         * external-drive seek test. */
        if (!(motors & (1u << drv)))
            drive_select(drv, 0);

        sprintf(s, "$1 DF0$  $2 DF1$  $3 DF2$  $4 DF3$");
        print_line(r);
        r->y++;
        sprintf(s, "$5 Motor On/Off$  $6 Step$");
        print_line(r);
        r->y -= 3;

        old_pra = ciaa->pra;
        old_prb = ciab->prb;
        mtr_time = get_time();
        rdy_delay = rdy_changed = 0;
        old_disk_index_count = disk_index_count = 0;
        key = 1; /* force print */

        while (!do_exit) {
            pra = ciaa->pra;
            prb = ciab->prb;
            if ((pra != old_pra) || key) {
                rdy_changed = !!((old_pra ^ pra) & CIAAPRA_RDY);
                if (rdy_changed)
                    rdy_delay = get_time() - mtr_time;
                sprintf(s, "Motors=(%c%c%c%c) CIAAPRA=0x%02x (%s %s %s %s)",
                        motors&(1u<<0) ? '0' : ' ',
                        motors&(1u<<1) ? '1' : ' ',
                        motors&(1u<<2) ? '2' : ' ',
                        motors&(1u<<3) ? '3' : ' ',
                        pra,
                        ~pra & CIAAPRA_CHNG ? "CHG" : "",
                        ~pra & CIAAPRA_WPRO ? "WPR" : "",
                        ~pra & CIAAPRA_TK0  ? "TK0" : "",
                        ~pra & CIAAPRA_RDY  ? "RDY" : "");
                print_line(r);
                old_pra = pra;
            }
            if ((prb != old_prb) || key) {
                r->y += 4;
                sprintf(s, "$7 STEP=%u$  $8 DIR=%u$",
                        !!(prb & CIABPRB_STEP), !!(prb & CIABPRB_DIR));
                print_line(r);
                r->y -= 4;
                old_prb = prb;
            }
            if ((old_disk_index_count != disk_index_count)
                || rdy_changed || key) {
                char rdystr[10], idxstr[10];
                ticktostr(disk_index_period, idxstr);
                ticktostr(rdy_delay, rdystr);
                sprintf(s, "%u Index Pulses (period %s); MTR%s->RDY%u %s",
                        disk_index_count, idxstr,
                        !!(motors&(1u<<drv)) ? "On" : "Off",
                        !!(old_pra & CIAAPRA_RDY), rdystr);
                r->y++;
                print_line(r);
                r->y--;
                old_disk_index_count = disk_index_count;
                rdy_changed = 0;
            }
            if (!(key = keycode_buffer))
                continue;
            keycode_buffer = 0;
            if ((key >= K_F1) && (key <= 0x53)) { /* F1-F4 */
                drv = key - K_F1;
                r->y--;
                break;
            } else if (key == 0x54) { /* F5 */
                motors ^= 1u << drv;
                drive_select(drv, !!(motors & (1u << drv)));
                old_pra = ciaa->pra;
                mtr_time = get_time();
                rdy_delay = 0;
            } else if (key == 0x55) { /* F6 */
{'fraction_non_alphanumeric': 0.14602194501230756, 'fraction_numerical': 0.030163961783970964, 'mean_word_length': 1.7198456825144672, 'pattern_counts': {'":': 0, '<': 84, '<?xml version=': 0, '>': 152, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 18, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4898345', 'n_tokens_mistral': 9198, 'n_tokens_neox': 8509, 'n_words': 2184}