// Does opening one built-in SCC port survive the OTHER one?
//
// The Zilog 8530 in a IIgs carries both built-in ports: channel A is the
// printer port (where AppleTalk lives), channel B is the modem port. WR9's
// reset command is chip-wide, so an open that issues the force-hardware-reset
// (0xC0) takes the other channel down with it -- registers, FIFOs, baud
// generator and, decisively for this probe, the transmit enable in WR5.
//
// So: bring channel A up through the real HAL, hand it back (the IIgs close
// deliberately leaves the chip configured), then open channel B -- the
// operation under test -- and finally poke a byte STRAIGHT into channel A's
// data register, bypassing the library. If channel A survived, its transmitter
// is still enabled and the byte reaches the host peer. If the open reset the
// whole chip, WR5 went to zero, the transmitter is off, and nothing arrives.
//
// Run headless by scripts/verify-iigs-serial.sh, which reads the bytes off a
// socket and the progress flags out of the SHR framebuffer.

#include <joey/serial.h>

// SHR framebuffer, used purely as a mailbox the emulator harness can read: the
// probe never calls jlInit, so nothing else is drawing here.
#define SHR_BASE          ((volatile uint8_t *)0x00E12000L)
#define MB_SIGNATURE_0    0
#define MB_SIGNATURE_1    1
#define MB_PRINTER_OPEN   2
#define MB_PRINTER_WROTE  3
#define MB_MODEM_OPEN     4
#define MB_DIRECT_WROTE   5
#define MB_SIG_VALUE_0    0xA5u
#define MB_SIG_VALUE_1    0x5Au

// Channel A (printer port) registers -- the channel this probe checks for
// survival. Deliberately spelled out here rather than shared with the HAL: the
// point is to look at the hardware independently of the code under test.
#define SCC_A_CTRL        ((volatile uint8_t *)0x00C039L)
#define SCC_A_DATA        ((volatile uint8_t *)0x00C03BL)
#define SCC_RR0_TX_EMPTY  0x04u

// Bounded so a dead transmitter reports "nothing arrived" instead of hanging
// the machine and stalling the gate.
#define TX_SPIN_LIMIT     200000ul

static void mailbox(uint16_t slot, uint8_t value);
static bool pokeChannelA(uint8_t byte);


static void mailbox(uint16_t slot, uint8_t value) {
    SHR_BASE[slot] = value;
}


// Write one byte to channel A without going through the HAL. Returns false if
// the transmitter never reports empty, which is exactly what a wiped channel
// looks like.
static bool pokeChannelA(uint8_t byte) {
    uint32_t spin;

    for (spin = 0; spin < TX_SPIN_LIMIT; spin++) {
        if ((*SCC_A_CTRL & SCC_RR0_TX_EMPTY) != 0u) {
            *SCC_A_DATA = byte;
            return true;
        }
    }
    return false;
}


int main(void) {
    jlSerialConfigT  cfg;
    uint16_t         i;

    mailbox(MB_SIGNATURE_0, MB_SIG_VALUE_0);
    mailbox(MB_SIGNATURE_1, MB_SIG_VALUE_1);

    cfg.baud     = 9600u;
    cfg.dataBits = 8u;
    cfg.stopBits = 1u;
    cfg.parity   = JL_SERIAL_PARITY_NONE;
    cfg.flow     = JL_SERIAL_FLOW_NONE;
    cfg.unit     = 0u;

    // 1. Bring channel A (printer) up through the HAL and prove it transmits.
    if (!jlSerialOpen(JL_SERIAL_PRINTER, &cfg)) {
        return 1;
    }
    mailbox(MB_PRINTER_OPEN, 1u);
    if (jlSerialWrite((const uint8_t *)"A1\r\n", 4u) == 4u) {
        mailbox(MB_PRINTER_WROTE, 1u);
    }
    jlSerialFlush();
    jlSerialClose();

    // 2. Open channel B (modem). THIS is the operation under test: with the
    //    old chip-wide 0xC0 reset it also wipes channel A.
    if (!jlSerialOpen(JL_SERIAL_MODEM, &cfg)) {
        return 1;
    }
    mailbox(MB_MODEM_OPEN, 1u);

    // 3. Channel A is untouched by the library from here on. Poke it directly:
    //    the byte only leaves the chip if channel A's transmitter is still on.
    if (pokeChannelA('A')) {
        mailbox(MB_DIRECT_WROTE, 1u);
    }
    (void)pokeChannelA('2');
    (void)pokeChannelA('\r');
    (void)pokeChannelA('\n');

    // Hold the machine still so the harness can read the mailbox, and give the
    // last byte time to clock out at 9600 baud.
    for (i = 0; i < 60000u; i++) {
        (void)*SCC_A_CTRL;
    }
    jlSerialClose();
    for (;;) {
    }
}
