1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
| /*
* 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);
}
|