/*
 * gpio-common.c: functions common to both gpio.c and gpio-nls.c
 *
 * Copyright © 2015 Oscar Vermeulen, © 2016-2018 by Warren Young
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors above shall
 * not be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization from those
 * authors.
 *
 * www.obsolescenceguaranteed.blogspot.com
 * 
 * This is part of the GPIO thread, which communicates with the
 * simulator's main thread via *pdis_update and switchstatus[].
 * All of this module's other external interfaces are only called
 * by the other gpio-* modules, from the GPIO thread.
*/

#include "pidp8i.h"

#include <config.h>

#include <pthread.h>
#include <sys/file.h>
#include <sys/time.h>

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_TIME_H
#   include <time.h>
#endif

#define BLOCK_SIZE (4*1024)


//// GLOBALS ///////////////////////////////////////////////////////////

// Flag set after we successfully init the GPIO mechanism.  While this
// is false, the rest of the code knows not to expect useful values for
// LED and switch states.  It is also useful as a cross-thread signal,
// since merely starting the blink() thread doesn't tell you whether it
// managed to lock the GPIO device.
uint8_t pidp8i_gpio_present;

// Flag external programs can set to make us use the old-style (and
// simpler, hence still supported) ledstatus[] interface for updating
// the LED values when swapping displays internally.  The external
// program doesn't need to know anything about struct display.
int pidp8i_simple_gpio_mode = 0;

// GPIO peripheral info, initted in start_pidp8i_gpio_thread()
struct bcm2835_peripheral gpio;
#define pgpio (&gpio)
#ifdef PCB_SERIAL_MOD_JLW
struct bcm2835_peripheral gpio2;
#endif


// A constant meaning "indeterminate milliseconds", used for error
// returns from ms_time() and for the case where the switch is in the
// stable state in the switch_state array.
static const ms_time_t na_ms = (ms_time_t)-1;


// Adjust columns to scan based on whether the Oscar Vermeulen or James L-W
// serial mods were done, as that affects the free GPIOs for our use, and how
// the PCB connects them to the LED matrix.
#if defined(PCB_SERIAL_MOD_OV) || defined(PCB_SERIAL_MOD_JLW)
uint8_t cols[NCOLS] = {13, 12, 11,    10, 9, 8,    7, 6, 5,    4, 3, 2};
#else
uint8_t cols[NCOLS] = {13, 12, 11,    10, 9, 8,    7, 6, 5,    4, 15, 14};
#endif

uint8_t ledrows[NLEDROWS] = {20, 21, 22, 23, 24, 25, 26, 27};

uint8_t rows[NROWS] = {16, 17, 18};


// Current switch states, as reported by the debouncing algorithm.  Set
// from the GPIO thread to control the SIMH CPU thread.
uint16_t switchstatus[NROWS];

// Double-buffered LED display brightness values.  The update-to copy is
// modified by the SIMH CPU thread as it executes instructions, and the
// paint-from copy is read by the gpio-*.c module in its "set LEDs"
// loop.  When the GPIO thread is finished with the paint-from copy, it
// zeroes it and swaps it for the current "update-to" copy, giving the
// CPU thread a blank slate, and giving the GPIO thread a stable set of
// LED "on" time values to work with.
display display_bufs[2];
display* pdis_update = display_bufs + 0;    // exported to SIMH CPU thread
display* pdis_paint  = display_bufs + 1;    // exported to gpio-*.c

// Pi Zero ILS counter
static int gpio_pwm_cnt = 0;

// GPIO thread control variables manipulated by start/stop_*() below
static pthread_t gpio_thread_info;
static int terminate_gpio_thread = 0;
static pthread_mutex_t gpio_start_mutex;


// Time-delayed reaction to switch changes to debounce the contacts.
// This is especially important with the incandescent lamp simulation
// feature enabled since that speeds up the GPIO scanning loop, making
// it more susceptible to contact bounce.
struct switch_state {
    // switch state currently reported via switchstatus[]
    int stable_state;

    // ms the switch state has been != stable_state, or na_ms
    // if it is currently in that same state
    ms_time_t last_change;      
};
static struct switch_state gss[NROWS][NCOLS];
static int gss_initted = 0;
static const ms_time_t debounce_ms = 50;    // time switch state must remain stable


// Flow-control switch states which are owned by -- that is, primarily
// modified by -- the PDP8/pidp8i module, but we can't define these
// there because we refer to them below, and not all programs that link
// to us link to that module as well.  For such programs, it's fine if
// these two flags stay 0.
int swStop = 0, swSingInst = 0;

// Flag to override ILS mode, forcing fallback to NLS mode.  Set when
// the PDP-8 instruction decoding loop detects that we're using the
// ratio form of SET THROTTLE, which prevents the use of ILS due to the
// way instructions are executed in that mode.  Defined here rather than
// in gpio-ils.c because we don't want to make code that sets this
// conditional based on whether ILS is in fact actually enabled.
int suppressILS = 0;

// Flag set when sim_instr() exits due to some SIMH event like Ctrl-E,
// which lets us resume from our imposed "pause" display state.
int resumeFromInstructionLoopExit = 0;


// SCP's signal handlers, which we extend.
#if !defined(HAVE_SIGHANDLER_T) && defined(HAVE_SIG_T)
typedef sig_t sighandler_t;
#endif
static sighandler_t scp_term_handler = 0;


//// MEMORY MAPPED GPIO FUNCTIONS //////////////////////////////////////

//// map_peripheral ////////////////////////////////////////////////////
// Exposes the physical address defined in the passed structure

static int map_peripheral(struct bcm2835_peripheral *p, int exclusive)
{
    // Name of GPIO memory-mapped device
    static const char* gpio_mem_dev = "/dev/gpiomem";

    if (access(gpio_mem_dev, F_OK) < 0) {
        // That dev node isn't even present, so it's probably not a Pi
        return -1;
    }

    // Open the GPIO device
    if ((p->mem_fd = open(gpio_mem_dev, O_RDWR|O_SYNC)) < 0) {
#ifdef DEBUG
        printf("Failed to open %s: %s\n", gpio_mem_dev, strerror(errno));
        puts("Disabling PiDP-8/I front panel functionality.");
#endif
        return -1;
    }

    // Attempt to lock it.  If we can't, another program has it locked,
    // so we shouldn't keep running; it'll just end in tears.
    if (exclusive && (flock(p->mem_fd, LOCK_EX | LOCK_NB) < 0)) {
        if (errno == EWOULDBLOCK) {
            printf("Failed to lock %s.  Only one PiDP-8/I\n", gpio_mem_dev);
            puts("program can be running at a given time.");
        }
        else {
            printf("Failed to lock %s: %s\n", gpio_mem_dev, strerror(errno));
            puts("Only one PiDP-8/I program can be running at a given time.");
        }
        return -1;
    }

    // Map the GPIO peripheral into our address space
    if ((p->map = mmap(
            NULL, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED,
            p->mem_fd,
            p->addr_p)) == MAP_FAILED) {
        perror("mmap");
        return -1;
    }

    // Success!
    p->addr = (volatile unsigned int *)p->map;
    pidp8i_gpio_present = 1;
    return 0;
}


//// unmap_peripheral //////////////////////////////////////////////////
// Unwind the map_peripheral() steps in reverse order

void unmap_peripheral(struct bcm2835_peripheral *p)
{
    if (pidp8i_gpio_present) {
        if (p->mem_fd > 0) {
            if (p->map) munmap(p->map, BLOCK_SIZE);
            flock(p->mem_fd, LOCK_UN);
            close(p->mem_fd);
        }
        pidp8i_gpio_present = 0;
    }
}


//// bcm_host_get_peripheral_address ///////////////////////////////////
// Find Pi's GPIO base address

static unsigned bcm_host_get_peripheral_address(void)
{
    unsigned address = ~0;
    FILE *fp = fopen("/proc/device-tree/soc/ranges", "rb");
    if (fp)
    {   unsigned char buf[4];
        fseek(fp, 4, SEEK_SET);
        if (fread(buf, 1, sizeof buf, fp) == sizeof buf)
            address = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0;
        fclose(fp);
    }

    return address == ~0 ? 0x20000000 : address;
}


//// DOUBLE BUFFERED DISPLAY MANIPULATION FUNCTIONS ////////////////////

//// swap_displays ////////////////////////////////////////////////////
// Clear the current "paint-from" display, then exchange the double-
// buffered display pointers atomically, saving the current update-to
// display pointer as our paint-from display pointer and re-pointing
// the update-to pointer at the now-zeroed paint-from values.  This
// gives the CPU thread a blank slate to begin modifying while the GPIO
// thread consumes the values provided by the CPU thread.

#define SWAP(dir) \
    __atomic_exchange_n (&pdis_update, display_bufs + dir, __ATOMIC_SEQ_CST)

void swap_displays ()
{
    if (!swStop && !swSingInst) {
        if (pidp8i_simple_gpio_mode) {
            // We're linked to a program that wants to use the old
            // ledstatus[] interface for updating the display, so copy
            // its values into the paint-from display structure and
            // return.  We don't need to touch the update-to display or
            // swap anything, because set_pidp8i_leds won't be called.
            static const int levels = 32;
            memcpy (pdis_paint->curr, ledstatus, 
                    sizeof (pdis_paint->curr));
            pdis_paint->inst_count = levels;

            for (size_t row = 0; row < NLEDROWS; ++row) {
                size_t *prow = pdis_paint->on[row];
                for (size_t col = 0, mask = 1; col < NCOLS;
                        ++col, mask <<= 1) {
                    prow[col] = !!(ledstatus[row] & mask) * levels;
                }
            }
        }
        else {
            // Clear old paint-from display
            memset (pdis_paint, 0, sizeof(display));

            // Send old paint-from display to CPU as new update-to
            // display, and overwrite paint-from pointer with prior
            // update-to pointer.
            pdis_paint = pdis_update == display_bufs + 0 ?
                    SWAP(1) :
                    SWAP(0);
        }
    }
    // else, leave current LED values as-is so we don't go to a black
    // screen while in STOP mode, either from front panel or HLT
}


//// FINE GRAINED SLEEP FUNCTIONS //////////////////////////////////////

//// sleep_ns //////////////////////////////////////////////////////////
// Like sleep(2) except that it takes nanoseconds instead of seconds

void sleep_ns(ns_time_t ns)
{
    struct timespec ts = { 0, ns };
#if defined(HAVE_CLOCK_NANOSLEEP)
    clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL);
#elif defined(HAVE_NANOSLEEP)
    nanosleep(&ts, NULL);
#elif defined(HAVE_USLEEP)
    usleep(ns / 1000);
#else
#   error Cannot build GPIO controller without high-res "sleep" function!
#endif
}


//// ms_time ///////////////////////////////////////////////////////////
// Like time(2) except that it returns milliseconds since the Unix epoch

ms_time_t ms_time(ms_time_t* pt)
{
    struct timeval tv;
    if (gettimeofday(&tv, 0) == 0) {
        ms_time_t t =
                tv.tv_sec  * 1000.0 +
                tv.tv_usec / 1000.0;
        if (pt) *pt = t;
        return t;
    }
    else {
        return na_ms;
    }
}


//// SWITCH DEBOUNCING and READING FUNCTIONS ///////////////////////////

//// report_ss /////////////////////////////////////////////////////////
// Save given switch state ss into the exported switchstatus bitfield
// so the simulator core will see it.  (Constrast the gss matrix,
// which holds our internal view of the unstable truth.)

static void report_ss(int row, int col, int ss,
        struct switch_state* pss)
{
    pss->stable_state = ss;
    pss->last_change = na_ms;

    int mask = 1 << col;
    if (ss) switchstatus[row] |=  mask;
    else    switchstatus[row] &= ~mask;

    #ifdef DEBUG
        printf("%cSS[%d][%02d] = %d  ", gss_initted ? 'N' : 'I', row, col, ss);
    #endif
}


//// debounce_switch ///////////////////////////////////////////////////
// Given the state of the switch at (row,col), work out if this requires
// a change in our exported switch state.

static void debounce_switch(int row, int col, int ss, ms_time_t now_ms)
{
    struct switch_state* pss = &gss[row][col];

    if (!gss_initted) {
        // First time thru, so set this switch's module-global and
        // exported state to its defaults now that we know the switch's
        // initial state.
        report_ss(row, col, ss, pss);
    }
    else if (ss == pss->stable_state) {
        // This switch is still/again in the state we consider "stable",
        // which we are reporting in our switchstatus bitfield.  Reset
        // the debounce timer in case it is returning to its stable
        // state from a brief blip into the other state.
        pss->last_change = na_ms;
    }
    else if (pss->last_change == na_ms) {
        // This switch just left what we consider the "stable" state, so
        // start the debounce timer.
        pss->last_change = now_ms;
    }
    else if ((now_ms - pss->last_change) > debounce_ms) {
        // Switch has been in the new state long enough for the contacts
        // to have stopped bouncing: report its state change to outsiders.
        report_ss(row, col, ss, pss);
    }
    // else, switch was in the new state both this time and the one prior,
    // but it hasn't been there long enough to report it
}


//// read_switches /////////////////////////////////////////////////////
// Iterate through the switch GPIO pins, passing them to the debouncing
// mechanism above for eventual reporting to the PDP-8 CPU thread.

void read_switches (ns_time_t delay)
{
    // Save current ms-since-epoch for debouncer.  No point making it
    // retrieve this value for each switch.
    ms_time_t now_ms;
    ms_time(&now_ms);

    // Flip columns to input.  Since the internal pull-ups are enabled,
    // this pulls all switch GPIO pins high that aren't shorted to the
    // row line by the switch.
    for (size_t i = 0; i < NCOLS; ++i) {
        INP_GPIO(cols[i]);
    }

    // Read the switch rows
    for (size_t i = 0; i < NROWS; ++i) {
        // Put 0V out on the switch row so that closed switches will
        // drag its column line down; give it time to settle.
        OUT_GPIO(rows[i]);
        GPIO_CLR = 1 << rows[i];
        sleep_ns (delay);

        // Read all the switches in this row
        for (size_t j = 0; j < NCOLS; ++j) {
            int ss = GPIO_READ(cols[j]);
            debounce_switch(i, j, !!ss, now_ms);
        }

        // Stop sinking current from this row of switches
        INP_GPIO(rows[i]);
    }

    fflush(stdout);
    gss_initted = 1;
}


//// UNGROUPED FUNCTIONS ///////////////////////////////////////////////

//// pi_type ///////////////////////////////////////////////////////////
// Return a short string succinctly describing the type of Raspberry Pi
// we're running on, or "cake" if it's not a pi.

static const char* pi_type()
{
    static char ac[60] = { '\0' };
    static const char* prefix = "Raspberry Pi ";

    FILE* fp = fopen("/proc/device-tree/model", "r");
    if (fp &&
            fgets(ac, sizeof(ac), fp) &&
            (strlen(ac) > 20) &&
            (strstr(ac, prefix) == ac)) {
        const char* kind = ac + strlen(prefix);
        int series = 1;
        if (kind[0] == 'M') {
            // It's one of the "plus" models.
            const char* pm = kind + strlen("Model ");
            char model = *pm;
            model = (isalpha(model) && isupper(model)) ?
                    tolower(model) : 'x';
            snprintf(ac, sizeof(ac), "pi%d%c", series, model);
        }
        else if (kind[0] == 'C') {
            // It's one of the compute modules.  We don't actually
            // support these, but we need to report them in case
            // someone tries it.
            const char* ps = kind + strlen("Compute Module");
            char series = *ps;
            if (series) {
                // It's should be one of the later Compute Module series.
                series = isdigit(ps[1]) ? ps[1] : 'x';
                snprintf(ac, sizeof(ac), "picm%c", series);
            }
            else {
                // We're at the end of the string, so it's the original
                // Compute Module.
                return "picm1";
            }
        }
        else if (kind[0] == 'Z') {
            // It's a Pi Zero
            return "pi0";
        }
        else if ((series = atoi(kind)) > 1) {
            // Pi 2 and newer have a number after the "Pi"
            char* pm = strstr(kind, " Model ");
            snprintf(ac, sizeof(ac), "pi%d%c", series,
                    pm ? tolower(pm[7]) : 'x');
        }
        else {
            // Not a model string we can parse, but it's some kind of
            // RPi.  Two 'x's stand for unknown series and model.
            return "pixx";
        }
    }
    else {
        return "cake";      // not pi
    }

    return ac;
}


//// update_led_states /////////////////////////////////////////////////
// Unlike the trunk version, this function generates a PWM sequence for
// the LEDs according to the count value in pdis_paint->on. This array
// contains 'on' counts betqeen 0 (off) and 9 (fully on).  This data is
// compared with a cycle 10 counter (gpio_pwm_cnt) and each led in a
// row/col is turned between 0 and 9 out of 80 calls to this function.
// This 10 level PWM is sufficient to give an impression of varying
// brightness although not quite as good as the trunk's ILS mode.  The
// advantage is that it uses far less host CPU power, so that it will
// run on a Pi Zero.  Another virtue of this scheme is that if this
// function is called synchronously from the CPU instruction decoding
// loop (pdp8_cpu.c), the LEDs will not flicker even if the primary CPU
// loop varies in speed.

void update_led_states (const us_time_t delay)
{
    size_t *prow = pdis_paint->on[0];

#if 0   // debugging
    static time_t last = 0, now;
    if (time(&now) != last) {
        printf("\r\nLED: [PC:%04o] [MA:%04o] [MB:%04o] [AC:%04o] [MQ:%04o]",
                pcurr[0], pcurr[1], pcurr[2], pcurr[3], pcurr[4]);
        last = now;
    }
#endif
    
    // Override Execute and Run LEDs if the CPU is currently stopped,
    // since we only get set_pidp8i_leds calls while the CPU's running.
    if (swStop || swSingInst) {
        pdis_paint->curr[5] &= ~(1 << 2);
        pdis_paint->curr[6] &= ~(1 << 7);
    }

    if (++gpio_pwm_cnt >= 10) gpio_pwm_cnt = 0;

    for (size_t row = 0; row < NLEDROWS; ++row) {
        for (size_t col = 0; col < NCOLS; ++col, ++prow) {
            if (gpio_pwm_cnt >= *prow) {
                GPIO_SET = 1 << cols[col];
            }
            else {
                GPIO_CLR = 1 << cols[col];
            }
        }

        // Toggle this LED row on
        INP_GPIO (ledrows[row]);
        GPIO_SET = 1 << ledrows[row];
        OUT_GPIO (ledrows[row]);

        sleep_us (delay);

        // Toggle this LED row off
        GPIO_CLR = 1 << ledrows[row]; // superstition
        INP_GPIO (ledrows[row]);

        // Small delay to reduce UDN2981 ghosting
        for (int i = 0; i < 2000; ++i)
            __asm__ ("nop");
    }
}


//// turn_on/off_pidp8i_leds ///////////////////////////////////////////
// Set GPIO pins into a state that [dis]connects power to/from the LEDs.
// Doesn't pay any attention to the panel values.

void turn_on_pidp8i_leds ()
{
    for (size_t row = 0; row < NLEDROWS; ++row) {
        INP_GPIO (ledrows[row]);
        GPIO_CLR = 1 << ledrows[row];
    }
    for (size_t col = 0; col < NCOLS; ++col) {
        INP_GPIO (cols[col]);
    }
}

void turn_off_pidp8i_leds ()
{
    for (size_t col = 0; col < NCOLS; ++col) {
        OUT_GPIO (cols[col]);
    }
    for (size_t row = 0; row < NLEDROWS; ++row) {
        INP_GPIO (ledrows[row]);
    }
}


//// init_pidp8i_gpio //////////////////////////////////////////////////
// Initialize the GPIO pins to the initial states required by
// gpio_thread().   It's a separate exported function so that scanswitch
// can also use it.

void init_pidp8i_gpio (void)
{
    // Set GPIO pins to their starting state
    turn_on_pidp8i_leds ();
    for (size_t i = 0; i < NROWS; i++) {       // Define rows as input
        INP_GPIO (rows[i]);
    }

    // BCM2835 ARM Peripherals PDF p 101 & elinux.org/RPi_Low-level_peripherals#Internal_Pull-Ups_.26_Pull-Downs
    GPIO_PULL = 2;  // pull-up
    usleep(1);  // must wait 150 cycles
#if defined(PCB_SERIAL_MOD_OV) || defined(PCB_SERIAL_MOD_JLW)
    // The Oscar Vermeulen and James L-W serial mods rearrange the PiDP-8/I
    // GPIO matrix to use Pi GPIO pins 2..13, freeing up the hardware
    // serial port on GPIO pins 14 & 15.
    GPIO_PULLCLK0 = 0x03ffc;
#else
    // The standard PiDP-8/I board drive scheme uses Pi GPIO pins 4..15.
    GPIO_PULLCLK0 = 0x0fff0;
#endif
    usleep(1);
    GPIO_PULL = 0; // reset GPPUD register
    usleep(1);
    GPIO_PULLCLK0 = 0; // remove clock
    usleep(1); // probably unnecessary

    // BCM2835 ARM Peripherals PDF p 101 & elinux.org/RPi_Low-level_peripherals#Internal_Pull-Ups_.26_Pull-Downs
    GPIO_PULL = 1;  // pull-down to avoid ghosting (dec2015)
    usleep(1);  // must wait 150 cycles
    GPIO_PULLCLK0 = 0x0ff00000; // selects GPIO pins 20..27
    usleep(1);
    GPIO_PULL = 0; // reset GPPUD register
    usleep(1);
    GPIO_PULLCLK0 = 0; // remove clock
    usleep(1); // probably unnecessary

    // BCM2835 ARM Peripherals PDF p 101 & elinux.org/RPi_Low-level_peripherals#Internal_Pull-Ups_.26_Pull-Downs
    GPIO_PULL = 0;  // no pull-up no pull down just float
    usleep(1);  // must wait 150 cycles
    GPIO_PULLCLK0 = 0x070000; // selects GPIO pins 16..18
    usleep(1);
    GPIO_PULL = 0; // reset GPPUD register
    usleep(1);
    GPIO_PULLCLK0 = 0; // remove clock
    usleep(1); // probably unnecessary
}


//// gpio_thread ///////////////////////////////////////////////////////
// The GPIO thread entry point: initializes GPIO and then calls
// the gpio_core () implementation linked to this program.

static void *gpio_thread (void *terminate)
{
    // Set thread to real time priority
    struct sched_param sp;
    sp.sched_priority = 4;  // not high, just above the minimum of 1
    int rt = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) == 0;

    // Tell the user about our configuration, succinctly
    const char* pt = pi_type();
    printf(
        "PiDP-8/I @VERSION@ [%s] [%cls] [%spcb] [%sgpio]"
#ifdef DEBUG
        " [debug]"
#endif
        "%s",
        pt,
        ILS_MODE ? 'i' : 'n',
        pt[0] == 'p' ? 
#ifdef PCB_SERIAL_MOD_OV
            "sermod" : 
#elif PCB_SERIAL_MOD_JLW
            "altser" : 
#else
            "std" : 
#endif
            "no",
        (pidp8i_gpio_present ? "" : "no"),
        (rt ? " [rt]" : "")
    );

    // It's okay for our caller to resume executing, if it's locked
    // waiting for us to finish the initialization bits that can only
    // happen in this thread.
    pthread_mutex_unlock (&gpio_start_mutex);

    // If we didn't map the GPIO peripheral and get here, it was
    // optional, and we've done all we can without it.
    if (!pidp8i_gpio_present) return (void*)-1;

    // Set GPIO pins to initial states
    init_pidp8i_gpio ();

    // Hand off control to the gpio_core () variant linked to this
    // program: either the new incandescent lamp simulator or the old
    // stock version.
    extern void gpio_core (struct bcm2835_peripheral*, int* terminate);
    gpio_core (&gpio, (int*)terminate);

    // gpio_core () leaves all cols, rows, ledrows are set to input, and
    // it's safe to leave them in that state.  No need to de-init GPIO.
    gss_initted = 0;
    return 0;
}


//// map_gpio_for_pidp8i ///////////////////////////////////////////////
// Wrapper around map_peripheral() taking care of some higher level
// details.  This is the interface we expose to outsiders.

int map_gpio_for_pidp8i (int must_map)
{
    // Find GPIO address (it varies by Pi model)
    unsigned int gpio_base_addr = bcm_host_get_peripheral_address();
    gpio.addr_p = gpio_base_addr + 0x200000;

    // Attempt to map the GPIO peripheral for our exclusive use.  Some
    // callers care if this fails, and some don't.
    map_peripheral (&gpio, 1);
    if (must_map && !pidp8i_gpio_present) return EFAULT;

#ifdef PCB_SERIAL_MOD_JLW
    // James L-W's alternative serial mods were declared to be done here
    // at configure time, so disable the hysteresis on the GPIO inputs.
    gpio2.addr_p = gpio_base_addr + 0x100000;
    map_peripheral (&gpio2, 0);  // assume success since prior mapping worked
    gpio2.addr[0x0B] = (gpio2.addr[0x0B] & 0xF7) | (0x5A << 24);
#endif

    return 0;
}


//// pidp8i_term_handler ///////////////////////////////////////////////
// Handle SIGTERM by shutting down our GPIO thread, then pass it on to
// SCP's handler, if one is registered.
//
// We don't do this with SIGINT instead because that's basically an
// alias for SCP's Ctrl-E handler.  We don't want to do this every time
// someone pulls up the SCP command console.

static void
pidp8i_term_handler (int sig)
{
    if (scp_term_handler) scp_term_handler (sig);

    if (pidp8i_gpio_present) {
        turn_off_pidp8i_leds ();
        stop_pidp8i_gpio_thread ();
    }
}


//// start/stop_pidp8i_gpio_thread /////////////////////////////////////
// Start and stop gpio_thread().  We export these functions rather than
// export gpio_thread() directly so this module's users don't have to
// know anything about pthreads and such.

int start_pidp8i_gpio_thread (const char* must_map)
{
    char errs[100];
    
    if (map_gpio_for_pidp8i (must_map != 0) != 0) {
        return EFAULT;
    }

    // Until gpio_core () reads the switches for the first time, we need
    // to mark them as all-open, lest we have a race condition with the
    // simulator where it interprets the all-0 initial switchstatus[]
    // value as "all switches closed," which includes the shutdown seq!
    memset (switchstatus, 0xFF, sizeof (switchstatus));

    // Create the startup sequencing mutex and lock it once to block the
    // GPIO thread after it's sufficiently initialized.
    int pcerr;
    if (    ((pcerr = pthread_mutex_init (&gpio_start_mutex, NULL)) != 0) ||
            ((pcerr = pthread_mutex_lock (&gpio_start_mutex)) != 0)) {
        perror ("GPIO startup sequence mutex creation failed");
        return errno;
    }

    // Create the actual GPIO handler thread
    pcerr = pthread_create (&gpio_thread_info, NULL, gpio_thread,
                            &terminate_gpio_thread);
    if (pcerr != 0) {
        int errlen = snprintf (errs, sizeof(errs), "Error creating "
                "PiDP-8/I GPIO thread: %s\n", strerror (pcerr));
        if (errlen > 0 && errlen < sizeof(errs)) write (2, errs, errlen);
    }
    else if (must_map && !pidp8i_gpio_present) {
        int errlen = snprintf (errs, sizeof(errs), "Cannot run the %s "
                "while another PiDP-8/I program runs.\r\n", must_map);
        if (errlen > 0 && errlen < sizeof(errs)) write (2, errs, errlen);
        pcerr = EACCES;
    }
    else {
        // Shut the GPIO thread down gracefully on fatal signals.
        scp_term_handler = signal (SIGTERM, pidp8i_term_handler);

        // Don't return until GPIO thread is sufficiently initted.
        //
        // There are two possible sequences:
        //
        // 1. We get here before the GPIO thread gets to its "unlock"
        //    call, so our back-to-back locks in this thread stall this
        //    thread until the GPIO thread gets to its unlock condition,
        //    which removes the first lock taken above, and our second
        //    unlock call below removes the second lock.
        //
        // 2. The GPIO thread hits its "unlock" call before we get here,
        //    so it unlocks the lock we made above, so that these two
        //    calls happen back to back, with no real effect.  We got
        //    here too late to cause any problem that this interlock
        //    solves, so it just locks and immediately unlocks.
        pthread_mutex_lock   (&gpio_start_mutex);
        pthread_mutex_unlock (&gpio_start_mutex);
    }

    return pcerr;
}

void stop_pidp8i_gpio_thread ()
{
    terminate_gpio_thread = 1;
    if (pthread_join (gpio_thread_info, NULL) && pidp8i_gpio_present) {
        // Warn only if we think it should succeed.  If we die due to a
        // signal, we'll be called twice and this'll fail the second
        // time, but the flag will be cleared, so we shouldn't complain.
        printf("\r\nError joining multiplex thread\r\n");
    }
    unmap_peripheral (&gpio);
#ifdef PCB_SERIAL_MOD_JLW
    unmap_peripheral (&gpio2);
#endif
    pthread_mutex_destroy (&gpio_start_mutex);
}
