macsehw/firmware/rtc/test/test-rtc.c

3042 lines
85 KiB
C

/* A very simple RTC test interface program. */
/* First of all, we expose a similar software interface that the
original Macintosh had through the VIA hardware registers.
* VIA base address = vBase = VIA
* Data register B, offset from VIA base = vBufB:
Bit 2: rtcEnb
Bit 1: rtcClk
Bit 0: rtcData
* Direction register B, offset = vDirB: Same layout as data
register B.
Finally, the one-second interrupt enable and one-second interrupt
signal. Let's just go really basic on that, yeah there's registers
for that, they are separate and they have the name peripheral in
them.
Then, for the sake of software test, we plug in our own software
program that exposes a text user interface through a serial
terminal. Yes, keep it simple but reasonably user-friendly. We
provide a set of command-line commands to encode/decode data,
send/receive serial communications, and to top it off, a high-level
Apple II monitor interface. And, by virtue of being a
command-line, it allows the easy scripting of test suites.
By default, the command-line command runs the non-interactive test
script. Use the `-i` command-line option to run in interactive
mode. The Apple II monitor interface is disabled by default, it
must be enabled and the "address space" set to either traditional
PRAM or XPRAM.
We also expose a Macintosh-style PRAM interface where the PRAM
registers arae pre-copied into host memory.
Finally, not only is this good for testing a design on a test
bench, but through the use of different driver back-ends, you can
make an integrated Raspberry Pi system that can be used to dump out
the contents of an existing PRAM chip using an IC clip, remove
power, then restore the contents, like you might do during a
battery replacement. Okay... so now that I mentioned it, there's
an easier way. Of course. Put an IC clip on the RTC while you are
changing the battery. You provide diode power through the IC clip,
so you can change the battery without loosing a beat.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
// Define for strptime():
#define __USE_XOPEN
#include <time.h>
#include <signal.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/epoll.h>
#include "sim_avr.h"
#include "avr_ioport.h"
#include "avr_timer.h"
#include "sim_elf.h"
#include "sim_gdb.h"
#include "sim_vcd_file.h"
/********************************************************************/
/* Simplified Arduino definitions support module header */
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned uint32_t;
typedef enum { false, true } bool;
typedef uint8_t byte;
typedef bool boolean;
typedef uint8_t bool8_t;
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))
/********************************************************************/
/* Miniature Apple II monitor module header */
/* Apple II monitor mode: 0 = disable, 1 = traditional PRAM, 2 =
XPRAM. XPRAM monitor mode is only valid when the host PRAM is
configured likewise, of course. */
uint8_t monMode = 0;
/********************************************************************/
/* `simavr` support module header */
/* Note that the test bench's input is the RTC's output. Input and
output here are specified from the perspective of the RTC. */
enum BenchIrqs { IRQ_SEC1, IRQ_CE, IRQ_CLK, IRQ_DATA_IN, IRQ_DATA_OUT };
extern avr_t *avr;
extern avr_irq_t *bench_irqs;
/********************************************************************/
/* Automated test suite module header */
extern bool8_t g_suiteActive;
void prTsStat(const char *status);
void recTsResult(bool8_t result, const char *desc);
void recTsSkip(const char *desc);
#define PR_TS_INFO() { if (g_suiteActive) prTsStat("INFO:"); }
bool8_t autoTestSuite(bool8_t verbose, bool8_t simRealTime,
bool8_t testXPram);
void suiteStart(void);
uint8_t suiteEnd(void);
uint8_t getSuiteMode(void);
/********************************************************************/
/* Raspberry Pi GPIO module */
/*
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
*/
unsigned int *gpio_mem;
/* From BCM2835 data-sheet, p.91 */
const unsigned GPREGS_BASE = 0x7e200000;
/* N.B. To avoid memory alignment issues, we change these to 32-bit
integer offsets. */
const unsigned GPFSEL_OFFSET = 0x00 >> 2;
const unsigned GPSET_OFFSET = 0x1c >> 2;
const unsigned GPCLR_OFFSET = 0x28 >> 2;
const unsigned GPLEV_OFFSET = 0x34 >> 2;
const unsigned GPEDS_OFFSET = 0x40 >> 2;
const unsigned GPREN_OFFSET = 0x4c >> 2;
const unsigned GPFEN_OFFSET = 0x58 >> 2;
const unsigned GPHEN_OFFSET = 0x64 >> 2;
const unsigned GPLEN_OFFSET = 0x70 >> 2;
const unsigned GPAREN_OFFSET = 0x7c >> 2;
const unsigned GPAFEN_OFFSET = 0x88 >> 2;
const unsigned GPPUD_OFFSET = 0x94 >> 2;
const unsigned GPPUDCLK_OFFSET = 0x98 >> 2;
const unsigned char N = 4;
enum {
GPFN_INPUT,
GPFN_OUTPUT,
GPFN_ALT5,
GPFN_ALT4,
GPFN_ALT0,
GPFN_ALT1,
GPFN_ALT2,
GPFN_ALT3,
};
enum {
GPUL_OFF,
GPUL_DOWN,
GPUL_UP,
};
int
rpi_gpio_init (void)
{
int result;
int fd = open ("/dev/gpiomem", O_RDWR | O_SYNC);
if (fd == -1)
return 0;
gpio_mem = mmap (NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (gpio_mem == (unsigned int*)-1)
return 0;
return 1;
}
void
rpi_gpio_set_fn (unsigned char idx, unsigned char fn)
{
unsigned word_idx = idx / 10;
unsigned int wordbuf = gpio_mem[GPFSEL_OFFSET+word_idx];
wordbuf &= ~(0x07 << ((idx % 10) * 3));
wordbuf |= (fn & 0x07) << ((idx % 10) * 3);
gpio_mem[GPFSEL_OFFSET+word_idx] = wordbuf;
}
void
rpi_gpio_set_pull (unsigned char idx, unsigned char pull)
{
unsigned int wordbuf;
unsigned i;
gpio_mem[GPPUD_OFFSET] = (unsigned int)pull & 0x03;
/* Wait at least 150 cycles. */
for (i = 150; i > 0; i--);
wordbuf = 1 << idx;
gpio_mem[GPPUDCLK_OFFSET] = wordbuf;
/* Wait at least 150 cycles. */
for (i = 150; i > 0; i--);
gpio_mem[GPPUD_OFFSET] = (unsigned int)GPUL_OFF;
gpio_mem[GPPUDCLK_OFFSET] = 0;
}
void
rpi_gpio_set_pin (unsigned char idx, unsigned char val)
{
/* N.B. Do not read the current value and use that to set the new
value else you get problems with random junk. Only set/clear the
value you want to change. */
if (val) { /* set the pin to 1 */
unsigned int wordbuf = gpio_mem[GPSET_OFFSET];
/* wordbuf |= 1 << idx; */
wordbuf = 1 << idx;
gpio_mem[GPSET_OFFSET] = wordbuf;
} else { /* clear the pin to zero */
unsigned int wordbuf = gpio_mem[GPCLR_OFFSET];
/* wordbuf |= 1 << idx; */
wordbuf = 1 << idx;
gpio_mem[GPCLR_OFFSET] = wordbuf;
}
}
unsigned char
rpi_gpio_get_pin (unsigned char idx)
{
unsigned int wordbuf = gpio_mem[GPLEV_OFFSET];
/* N.B. Interpret the values as follows. The value of the pin is
the current flowing through the pull-up/down termination. For
example:
* If you have pull-up termination, the value is one when the
switch is open, zero when the switch is closed.
* If you have pull-down termination, the value is zero when the
switch is open, one when the switch is closed. */
return (wordbuf >> idx) & 1;
}
unsigned char
rpi_gpio_get_pin_event (unsigned char idx)
{
unsigned int wordbuf = gpio_mem[GPEDS_OFFSET];
return (wordbuf >> idx) & 1;
}
void
rpi_gpio_clear_pin_event (unsigned char idx)
{
gpio_mem[GPEDS_OFFSET] = 1 << idx;
}
/* Watch for rising edge. */
void
rpi_gpio_watch_re (unsigned char idx)
{
gpio_mem[GPREN_OFFSET] |= 1 << idx;
}
void
rpi_gpio_unwatch_re (unsigned char idx)
{
gpio_mem[GPREN_OFFSET] &= ~(1 << idx);
}
/* Watch for falling edge. */
void
rpi_gpio_watch_fe (unsigned char idx)
{
gpio_mem[GPFEN_OFFSET] |= 1 << idx;
}
void
rpi_gpio_unwatch_fe (unsigned char idx)
{
gpio_mem[GPFEN_OFFSET] &= ~(1 << idx);
}
/* Watch for asynchronous rising edge. */
void
rpi_gpio_watch_async_re (unsigned char idx)
{
gpio_mem[GPAREN_OFFSET] |= 1 << idx;
}
void
rpi_gpio_unwatch_async_re (unsigned char idx)
{
gpio_mem[GPAREN_OFFSET] &= ~(1 << idx);
}
/* Watch for asynchronous falling edge. */
void
rpi_gpio_watch_async_fe (unsigned char idx)
{
gpio_mem[GPAFEN_OFFSET] |= 1 << idx;
}
void
rpi_gpio_unwatch_async_fe (unsigned char idx)
{
gpio_mem[GPAFEN_OFFSET] &= ~(1 << idx);
}
/********************************************************************/
/* Linux GPIO interrupts support module */
/* Linux `epoll` is an ugly way to get GPIO interrupts into
user-space, but it works and it is relativelly old/stable. Matter
of fact, Raspbian was first released without any Linux kernel
support for GPIO interrupts in user-space, despite the hardware
having the capability. As soon as the software capability was
added in `epoll` and `gpio-keys`, those were the first of their
kind on that platform. So, that's the word.
Only bothers with the Linux `sysfs` filesystem manipulation as much
as it is required to get interrupts into user-space. Use
memory-mapped BCM2835 registers in your own code as much as
possible for the rest on Raspberry Pi.
Only a single GPIO pin is supported for interrupt wait-and-notify.
To support more than one thread, though, we simply use a single
epfd_thread descriptor (likewise only a single wait-notify thread)
and then open and add one file descriptor per GPIO pin. Adding
watches on all read pins can be particularly useful for producing
VCD files for poor man's oscilloscope analysis of Apple's custom
silicon RTC.
*/
/*
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/epoll.h>
#include "arduino_sdef.h"
*/
int g_gpio_num;
pthread_t g_epoll_thread;
bool8_t g_thread_running = false;
bool8_t g_thread_initial = true;
int g_gpio_fd = -1;
int epfd_thread = -1;
void sec1Isr(void);
void *lingpirq_poll_thread(void *thread_arg)
{
int old_type;
struct epoll_event events;
char buf;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
g_thread_running = true;
while (g_thread_running) {
int result = epoll_wait(epfd_thread, &events, 1, -1);
if (result > 0) {
lseek(events.data.fd, 0, SEEK_SET);
if (read(events.data.fd, &buf, 1) != 1) {
g_thread_running = false;
pthread_exit((void*)0);
}
if (g_thread_initial) // ignore first epoll trigger
g_thread_initial = false;
else
sec1Isr();
} else if (result == -1) {
if (errno == EINTR)
continue;
g_thread_running = false;
pthread_exit((void*)0);
}
}
pthread_exit((void*)0);
}
// If `falling_edge` is `false`, then use the rising edge instead.
bool8_t lingpirq_setup(int gpio_num, bool8_t falling_edge)
{
struct epoll_event ev;
char cmd[64];
char filename[64];
g_gpio_num = gpio_num;
g_gpio_fd = -1;
epfd_thread = -1;
snprintf(cmd, sizeof(cmd), "echo %d >/sys/class/gpio/export", g_gpio_num);
if (system(cmd) != 0)
return false; /* error */
// "direction" is one of the following: "out", "in"
// "edge" is one of the following: "none", "rising", "falling", "both"
snprintf(cmd, sizeof(cmd), "echo %s >/sys/class/gpio/gpio%d/direction",
"in", g_gpio_num);
if (system(cmd) != 0)
goto cleanup_fail; /* error */
snprintf(cmd, sizeof(cmd), "echo %s >/sys/class/gpio/gpio%d/edge",
(falling_edge) ? "falling" : "rising", g_gpio_num);
if (system(cmd) != 0)
goto cleanup_fail; /* error */
snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/value",
g_gpio_num);
g_gpio_fd = open(filename, O_RDONLY | O_NONBLOCK);
if (g_gpio_fd < 0)
goto cleanup_fail; /* error */
// Create and configure an `epoll` for the GPIO file descriptor.
epfd_thread = epoll_create(1);
if (epfd_thread == -1)
goto cleanup_fail; /* error */
ev.events = EPOLLIN | EPOLLET | EPOLLPRI;
ev.data.fd = g_gpio_fd;
if (epoll_ctl(epfd_thread, EPOLL_CTL_ADD, g_gpio_fd, &ev) == -1)
goto cleanup_fail; /* error */
// Create the wait-and-notify thread.
if (pthread_create(&g_epoll_thread, NULL,
lingpirq_poll_thread, (void*)0) != 0)
goto cleanup_fail; /* error */
return true; /* success */
cleanup_fail:
close(epfd_thread);
close(g_gpio_fd);
snprintf(cmd, sizeof(cmd), "echo %d >/sys/class/gpio/unexport", g_gpio_num);
system(cmd);
return false;
}
void lingpirq_cleanup(void)
{
void *thread_retval;
struct epoll_event ev;
char cmd[64];
pthread_cancel(g_epoll_thread);
pthread_join(g_epoll_thread, &thread_retval);
g_thread_running = false;
ev.events = EPOLLIN | EPOLLET | EPOLLPRI;
ev.data.fd = g_gpio_fd;
close(g_gpio_fd);
epoll_ctl(epfd_thread, EPOLL_CTL_DEL, g_gpio_fd, &ev);
close(epfd_thread);
close(g_gpio_fd);
snprintf(cmd, sizeof(cmd), "echo %d >/sys/class/gpio/unexport", g_gpio_num);
system(cmd);
}
/********************************************************************/
/* VIA emulation module */
/*
#include <stdlib.h>
#include <time.h>
#include "arduino_sdef.h"
#include "rpi-gpio.h"
#include "lin-gpirq.h"
#include "simavr-support.h"
*/
bool8_t simAvrStep(void);
avr_cycle_count_t notify_timeup(avr_t *avr, avr_cycle_count_t when,
void *param);
#define rtcEnb 2
#define rtcClk 1
#define rtcData 0
// VIA direction: 0 for input, 1 for output.
const uint8_t DIR_IN = 0;
const uint8_t DIR_OUT = 1;
const uint8_t vBufB = 0;
const uint8_t vDirB = 1;
const uint8_t irqEnb = 2; // Enable a particular interrupt
const uint8_t irqFlags = 3; // Indicates which interrupt triggered
// VIA registers in memory
uint8_t vBase[4];
uint8_t const *VIA = vBase;
bool g_waitTimeUp = true;
uint8_t g_timePoll = 0;
enum PhyPins { PHY_SEC1, PHY_CE, PHY_CLK, PHY_DATA };
// Physical hardware test mode (via Raspberry Pi).
bool8_t g_phyMode = false;
// Emulated RTC VIA connections to GPIO pin mappings.
uint8_t g_phyToGpio[4] = { 0, 0, 0, 0 };
uint8_t viaBitRead(uint8_t *ptr, uint8_t bit)
{
if (g_phyMode) {
switch (bit) {
case rtcData:
return rpi_gpio_get_pin(g_phyToGpio[PHY_DATA]);
default:
return 0; // unrecognized signals read as zero
}
} // else
return bitRead(*(ptr), (bit));
}
void viaBitWrite(uint8_t *ptr, uint8_t bit, uint8_t bitvalue)
{
// Only handle the vBufB and vDirB registers for now.
if (ptr == vBase + vBufB) {
// Ensure the direction is correctly configured before sending an
// output, otherwise do nothing.
if (bitRead(vBase[vDirB], bit) != DIR_OUT)
return;
// Send the signal to the actual hardware.
switch (bit) {
case rtcEnb:
if (g_phyMode)
rpi_gpio_set_pin(g_phyToGpio[PHY_CE], bitvalue & 1);
else
avr_raise_irq(bench_irqs + IRQ_CE, bitvalue & 1);
break;
case rtcClk:
if (g_phyMode)
rpi_gpio_set_pin(g_phyToGpio[PHY_CLK], bitvalue & 1);
else
avr_raise_irq(bench_irqs + IRQ_CLK, bitvalue & 1);
break;
case rtcData:
if (g_phyMode)
rpi_gpio_set_pin(g_phyToGpio[PHY_DATA], bitvalue & 1);
else
avr_raise_irq(bench_irqs + IRQ_DATA_IN, bitvalue & 1);
break;
default:
break; // unrecognized signals do nothing
}
} else if (ptr == vBase + vDirB) {
if (g_phyMode) {
// We only support changing the direction of the data pin.
if (bit == rtcData) {
uint8_t fn = (bitvalue == DIR_IN) ? GPFN_INPUT : GPFN_OUTPUT;
rpi_gpio_set_fn(g_phyToGpio[PHY_DATA], fn);
}
} else {
// The main special handling we do here is to set to the
// corresponding buffer bit to the input value as soon as we
// change to an input type. With the `simavr` setup, we simply
// set to default logic value 1 and we will get an IRQ if we
// should do otherwise.
if (bitvalue == DIR_IN)
bitWrite(vBase[vBufB], bit, 1);
}
} else
return;
// Update our register value.
bitWrite(*ptr, bit, bitvalue);
}
/* Time our wait periods based off of a maximum 500 Hz (minimum 2 ms
period) clock signal. That means we need to wait at least 0.5 ms
(500 us = 500000 ns) for a quarter-cycle wait time.
PLEASE NOTE: This cautious maximum serial clock speed results in
considerably slow memory access compared to modern standards. From
testing at 32.768 kHz core clock, the speed limit is a 50 Hz serial
clock, so it takes 128 seconds to write all 256 bytes of XPRAM.
This should be compared with the speed limits of Apple custom
silicon RTC. */
void waitQuarterCycle(void)
{
if (g_phyMode) {
struct timespec tv = { 0, 500000 };
struct timespec tvNext;
do {
if (clock_nanosleep(CLOCK_MONOTONIC, 0, &tv, &tvNext) == 0)
break;
tv.tv_nsec = tvNext.tv_nsec;
} while (tv.tv_nsec > 0);
} else {
// Unfortunately, if the AVR runs at 32.768 kHz, I've found from
// simulation that serial communications are only reliable at an
// abysmal 50 Hz serial clock speed. Therefore, running at a
// higher core speed and using a phase-locked loop on the crystal
// clock frequency a must.
struct timespec tv, tvTarget;
// N.B. Over here we are using cycle timers mainly to prevent
// simulation waits stretching unbearably long.
g_timePoll = 16;
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
clock_gettime(CLOCK_MONOTONIC, &tv);
tvTarget.tv_nsec = tv.tv_nsec + 500000;
tvTarget.tv_sec = tv.tv_sec;
if (tvTarget.tv_nsec >= 1000000000) {
tvTarget.tv_nsec -= 1000000000;
tvTarget.tv_sec++;
}
while (tv.tv_sec < tvTarget.tv_sec ||
(tv.tv_sec == tvTarget.tv_sec &&
tv.tv_nsec < tvTarget.tv_nsec)) {
if (!simAvrStep())
break;
clock_gettime(CLOCK_MONOTONIC, &tv);
}
g_timePoll = 0;
}
}
void waitHalfCycle(void)
{
waitQuarterCycle();
waitQuarterCycle();
}
void waitCycle(void)
{
waitHalfCycle();
waitHalfCycle();
}
void waitOneSec(void)
{
if (g_phyMode) {
sleep(1);
} else {
struct timespec tv, tvTarget;
// N.B. Over here we are using cycle timers mainly to prevent
// simulation waits stretching unbearably long.
g_timePoll = 16;
avr_cycle_timer_register(avr, g_timePoll, notify_timeup, NULL);
clock_gettime(CLOCK_MONOTONIC, &tv);
tvTarget.tv_nsec = tv.tv_nsec;
tvTarget.tv_sec = tv.tv_sec + 1;
while (tv.tv_sec < tvTarget.tv_sec ||
(tv.tv_sec == tvTarget.tv_sec &&
tv.tv_nsec < tvTarget.tv_nsec)) {
if (!simAvrStep())
break;
clock_gettime(CLOCK_MONOTONIC, &tv);
}
g_timePoll = 0;
}
}
bool8_t viaInit(void)
{
if (g_phyMode) {
// Setup GPIO pins.
if (!rpi_gpio_init())
return false;
rpi_gpio_set_fn(g_phyToGpio[PHY_CE], GPFN_OUTPUT);
rpi_gpio_set_fn(g_phyToGpio[PHY_CLK], GPFN_OUTPUT);
rpi_gpio_set_fn(g_phyToGpio[PHY_DATA], GPFN_OUTPUT);
// Setup GPIO IRQ for 1-second pin.
if (!lingpirq_setup(g_phyToGpio[PHY_SEC1], false))
return false;
// Configure pull up/down here since `sysfs` can't do it. (?)
rpi_gpio_set_pull(g_phyToGpio[PHY_SEC1], GPUL_UP);
}
return true;
}
void viaDestroy(void)
{
if (g_phyMode) {
// Cleanup GPIO pins, change to all pull-up inputs.
rpi_gpio_set_fn(g_phyToGpio[PHY_CE], GPFN_INPUT);
rpi_gpio_set_pull(g_phyToGpio[PHY_CE], GPUL_UP);
rpi_gpio_set_fn(g_phyToGpio[PHY_CLK], GPFN_INPUT);
rpi_gpio_set_pull(g_phyToGpio[PHY_CLK], GPUL_UP);
rpi_gpio_set_fn(g_phyToGpio[PHY_DATA], GPFN_INPUT);
rpi_gpio_set_pull(g_phyToGpio[PHY_DATA], GPUL_UP);
// Cleanup GPIO IRQ for 1-second pin.
lingpirq_cleanup();
}
}
/********************************************************************/
/* PRAM C library module */
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Define for strptime():
#define __USE_XOPEN
#include <time.h>
#include <pthread.h>
#include "arduino_sdef.h"
#include "via-emu.h"
#include "simavr-support.h"
*/
// PRAM configuration, set to XPRAM by default
int pramSize = 256;
int group1Base = 0x10;
int group2Base = 0x08;
// Host copy of RTC chip memory. Note that the write-protect register
// cannot be read.
volatile uint32_t timeSecs = 0;
pthread_mutex_t timeSecsMutex;
byte writeProtect = 0;
byte pram[256];
// Delta between Macintosh time epoch and Unix time epoch. Number of
// seconds between 1904 and 1970 = 16 4-year cycles plus 1 regular
// year plus one leap year. Does not cross 100-year or 400-year
// boundaries.
const uint32_t macUnixDelta = 60UL * 60 * 24 *
((365 * 4 + 1) * 16 + (365 * 2 + 1));
// Initialize the `timeSecs` mutex.
void pramInit(void)
{
pthread_mutex_init(&timeSecsMutex, NULL);
}
// Destroy the `timeSecs` mutex.
void pramDestroy(void)
{
pthread_mutex_destroy(&timeSecsMutex);
}
// Configure whether the PRAM should be traditional 20-byte PRAM
// (false) or XPRAM (true).
void setPramType(bool8_t isXPram)
{
if (isXPram) {
pramSize = 256;
group1Base = 0x10;
group2Base = 0x08;
} else {
pramSize = 20;
group1Base = 0x00;
group2Base = 0x10;
}
}
// Return true if the PRAM type is set to XPRAM, false otherwise.
bool8_t getPramType(void)
{
if (pramSize == 256)
return true;
return false;
}
void serialBegin(void)
{
viaBitWrite(vBase + vDirB, rtcEnb, DIR_OUT);
viaBitWrite(vBase + vDirB, rtcData, DIR_OUT);
viaBitWrite(vBase + vDirB, rtcClk, DIR_OUT);
viaBitWrite(vBase + vBufB, rtcClk, 0);
viaBitWrite(vBase + vBufB, rtcEnb, 0);
waitQuarterCycle();
}
void serialEnd(void)
{
viaBitWrite(vBase + vBufB, rtcEnb, 1);
waitQuarterCycle();
}
void sendByte(byte data)
{
uint8_t bitNum = 0;
viaBitWrite(vBase + vDirB, rtcData, DIR_OUT);
while (bitNum <= 7) {
uint8_t bit = (data >> (7 - bitNum)) & 1;
bitNum++;
viaBitWrite(vBase + vBufB, rtcData, bit);
waitQuarterCycle();
viaBitWrite(vBase + vBufB, rtcClk, 1);
waitHalfCycle();
viaBitWrite(vBase + vBufB, rtcClk, 0);
waitQuarterCycle();
}
}
byte recvByte(void)
{
byte serialData = 0;
uint8_t bitNum = 0;
viaBitWrite(vBase + vDirB, rtcData, DIR_IN);
while (bitNum <= 7) {
uint8_t bit;
waitQuarterCycle();
viaBitWrite(vBase + vBufB, rtcClk, 1);
waitHalfCycle();
viaBitWrite(vBase + vBufB, rtcClk, 0);
waitQuarterCycle();
bit = viaBitRead(vBase + vBufB, rtcData);
serialData |= bit << (7 - bitNum);
bitNum++;
}
return serialData;
}
byte sendReadCmd(byte cmd)
{
byte serialData;
serialBegin();
sendByte(cmd);
serialData = recvByte();
serialEnd();
return serialData;
}
void sendWriteCmd(byte cmd, byte data)
{
serialBegin();
sendByte(cmd);
sendByte(data);
serialEnd();
}
byte sendReadXCmd(byte cmd1, byte cmd2)
{
byte serialData;
serialBegin();
sendByte(cmd1);
sendByte(cmd2);
serialData = recvByte();
serialEnd();
return serialData;
}
void sendWriteXCmd(byte cmd1, byte cmd2, byte data)
{
serialBegin();
sendByte(cmd1);
sendByte(cmd2);
sendByte(data);
serialEnd();
}
// Perform a test write, does nothing since there is no indication if
// it succeeds.
void testWrite(void)
{
sendWriteCmd(0x30, 0x80);
}
// Set the write-protect register on the RTC.
void setWriteProtect(void)
{
sendWriteCmd(0x34, 0x80);
writeProtect = 1;
}
// Clear the write-protect register on the RTC.
void clearWriteProtect(void)
{
sendWriteCmd(0x34, 0x00);
writeProtect = 0;
}
/* Copy the time from RTC to host. The time is read twice and
compared for equality to verify a consistent read. If the read is
inconsistent, this function will retry up to a maximum of 4 times
before returning failure. */
bool8_t dumpTime(void)
{
uint8_t retry = 0;
uint32_t newTime1, newTime2;
while (retry < 4) {
newTime1 = 0; newTime2 = 0;
newTime1 |= sendReadCmd(0x80);
newTime1 |= sendReadCmd(0x84) << 8;
newTime1 |= sendReadCmd(0x88) << 16;
newTime1 |= sendReadCmd(0x8c) << 24;
newTime2 |= sendReadCmd(0x90);
newTime2 |= sendReadCmd(0x94) << 8;
newTime2 |= sendReadCmd(0x98) << 16;
newTime2 |= sendReadCmd(0x9c) << 24;
if (newTime1 == newTime2) {
pthread_mutex_lock(&timeSecsMutex);
timeSecs = newTime1;
pthread_mutex_unlock(&timeSecsMutex);
return true;
}
retry++;
}
return false;
}
// Accessor function to return the current host time copy.
uint32_t getTime(void)
{
uint32_t result;
pthread_mutex_lock(&timeSecsMutex);
result = timeSecs;
pthread_mutex_unlock(&timeSecsMutex);
return result;
}
// Clear write-protect and copy the time from host to RTC.
void loadTime(void)
{
uint32_t ourTimeSecs;
byte serialData = 0;
ourTimeSecs = getTime();
clearWriteProtect();
serialData = ourTimeSecs & 0xff;
sendWriteCmd(0x00, serialData);
serialData = (ourTimeSecs >> 8) & 0xff;
sendWriteCmd(0x04, serialData);
serialData = (ourTimeSecs >> 16) & 0xff;
sendWriteCmd(0x08, serialData);
serialData = (ourTimeSecs >> 24) & 0xff;
sendWriteCmd(0x0c, serialData);
}
// Set the host time to the given new time and propagate it to the
// RTC. Also clears write-protect.
void setTime(uint32_t newTimeSecs)
{
pthread_mutex_lock(&timeSecsMutex);
timeSecs = newTimeSecs;
pthread_mutex_unlock(&timeSecsMutex);
loadTime();
}
// 1-second interrupt service routine, increment the current time.
void sec1Isr(void)
{
pthread_mutex_lock(&timeSecsMutex);
timeSecs++;
pthread_mutex_unlock(&timeSecsMutex);
}
// Convert Macintosh numeric time into ISO 8601 format (YYYY-MM-DD
// HH:MM:SS) string time.
void macToStrTime(char *outBuf, size_t outBufLen, uint32_t macTime)
{
time_t unixTime = macTime - macUnixDelta;
struct tm calTime;
gmtime_r(&unixTime, &calTime);
strftime(outBuf, outBufLen, "%Y-%m-%d %H:%M:%S", &calTime);
}
// Convert ISO 8601 format (YYYY-MM-DD HH:MM:SS) string time into
// Macintosh numeric time. On error, returns zero.
uint32_t strToMacTime(const char *strBuf)
{
struct tm calTime;
char *envOldTz;
char oldTz[16];
time_t unixTime;
if (strptime(strBuf, "%Y-%m-%d %H:%M:%S", &calTime) == NULL)
return 0;
// Ensure there is no timezone correction.
envOldTz = getenv("TZ");
if (envOldTz != NULL)
strncpy(oldTz, envOldTz, 16);
oldTz[15] = '\0';
setenv("TZ", "UTC", 1);
unixTime = mktime(&calTime);
if (envOldTz == NULL)
unsetenv("TZ");
else
setenv("TZ", oldTz, 1);
return unixTime + macUnixDelta;
}
// Set the host time to the new time given as a string and propagate
// it to the RTC. Also clears write-protect. If the time string is
// invalid, no changes are made.
void setStrTime(const char *strBuf)
{
uint32_t newTimeSecs = strToMacTime(strBuf);
if (newTimeSecs == 0)
return; // error
setTime(newTimeSecs);
}
// Accessor function to return the current host time copy, formatted
// as a string.
void getStrTime(char *outBuf, size_t outBufLen)
{
macToStrTime(outBuf, outBufLen, getTime());
}
// Set the RTC to the current Unix time. Also clears write-protect.
void setCurTime(void)
{
time_t unixTime = time(NULL);
struct tm calTime;
char *envOldTz;
char oldTz[16];
// Ensure we apply the proper timezone offset to get local epoch
// time.
localtime_r(&unixTime, &calTime);
envOldTz = getenv("TZ");
if (envOldTz != NULL)
strncpy(oldTz, envOldTz, 16);
oldTz[15] = '\0';
setenv("TZ", "UTC", 1);
unixTime = mktime(&calTime);
if (envOldTz == NULL)
unsetenv("TZ");
else
setenv("TZ", oldTz, 1);
setTime(unixTime + macUnixDelta);
}
// Convenience function to generate a traditional PRAM command from
// logical command address and write-request flag. `addr` must not
// exceed 0x1f.
byte genCmd(byte addr, bool8_t writeRequest)
{
return ((!writeRequest) << 7) | (addr << 2);
}
byte genSendReadCmd(byte addr)
{
return sendReadCmd(genCmd(addr, false));
}
void genSendWriteCmd(byte addr, byte data)
{
sendWriteCmd(genCmd(addr, true), data);
}
// Copy all traditional 20-byte PRAM memory from RTC to host.
void dumpAllTradMem(void)
{
uint8_t i;
// Copy group 2 registers.
for (i = 0; i < 4; i++) {
pram[group2Base+i] = genSendReadCmd(8 + i);
}
// Copy group 1 registers.
for (i = 0; i < 16; i++) {
pram[group1Base+i] = genSendReadCmd(16 + i);
}
}
// Clear write-protect and copy all traditional 20-byte PRAM memory
// from host to RTC.
void loadAllTradMem(void)
{
uint8_t i;
clearWriteProtect();
// Copy group 2 registers.
for (i = 0; i < 4; i++) {
genSendWriteCmd(8 + i, pram[group2Base+i]);
}
// Copy group 1 registers.
for (i = 0; i < 16; i++) {
genSendWriteCmd(16 + i, pram[group1Base+i]);
}
}
// Generate an extended command from a byte address. The first byte
// to send is the most significant byte in the returned 16-bit
// integer.
uint16_t genXCmd(byte addr, bool8_t writeRequest)
{
uint16_t xcmd = 0x3800 | ((addr & 0xe0) << 3) | ((addr & 0x1f) << 2);
if (!writeRequest)
xcmd |= 0x8000;
return xcmd;
}
// Generate and send and extended read command from a byte address.
byte genSendReadXCmd(byte addr)
{
uint16_t xcmd = genXCmd(addr, false);
return sendReadXCmd((xcmd >> 8) & 0xff, xcmd & 0xff);
}
// Generate and send and extended write command from a byte address.
void genSendWriteXCmd(byte addr, byte data)
{
uint16_t xcmd = genXCmd(addr, true);
return sendWriteXCmd((xcmd >> 8) & 0xff, xcmd & 0xff, data);
}
// Copy all XPRAM memory from RTC to host.
void dumpAllXMem(void)
{
uint8_t i = 0;
do {
pram[i] = genSendReadXCmd(i);
i++;
} while (i != 0);
// N.B. We rely on overflow here to copy all 256 bytes.
}
// Clear write-protect and copy all XPRAM memory from host to RTC.
void loadAllXMem(void)
{
uint8_t i = 0;
clearWriteProtect();
do {
genSendWriteXCmd(i, pram[i]);
i++;
} while (i != 0);
// N.B. We rely on overflow here to copy all 256 bytes.
}
/* For 20-byte equivalent PRAM commands, read or write the
corresponding host memory. Writes are also propagated to the RTC.
For reads, `data` is ignored. Invalid reads return zero.
Successful writes return 1, unsuccessful writes return zero.
Copied almost exactly from the corresponding subroutine in the
firmware. */
byte hostTradPramCmd(byte cmd, byte data)
{
bool8_t writeRequest = !(cmd&(1<<7));
// Discard the first bit and the last two bits, it's not pertinent
// to address interpretation.
byte address = (cmd&~(1<<7))>>2;
if (writeRequest && writeProtect &&
address != 13) // 13 == update write-protect register
return 0; // invalid command
if (address < 8) {
// Little endian clock data byte
if (writeRequest) {
address = (address&0x03)<<3;
timeSecs &= ~(0xff<<address);
timeSecs |= data<<address;
// Fall through to send command to RTC.
} else {
address = (address&0x03)<<3;
return (timeSecs>>address)&0xff;
}
} else if (address < 12) {
// Group 2 register
address = (address&0x03) + group2Base;
if (writeRequest) {
pram[address] = data;
// Fall through to send command to RTC.
} else
return pram[address];
} else if (address < 16) {
if (writeRequest) {
if (address == 12) // test write, do nothing
; // Fall through to send command to RTC.
else if (address == 13) {
// Update the write-protect register.
writeProtect = ((data & 0x80)) ? 1 : 0;
// Fall through to send command to RTC.
}
else {
// Addresses 14 and 15 are used for the encoding of the first
// byte of an extended command. Therefore, interpretation as
// a traditional PRAM command is invalid.
return 0;
}
} else
return 0; // invalid command
} else {
// Group 1 register
address = (address&0x0f) + group1Base;
if (writeRequest) {
pram[address] = data;
// Fall through to send command to RTC.
} else
return pram[address];
}
// We only reach this point for valid write commands.
sendWriteCmd(cmd, data);
return 1;
}
// Write to host XPRAM memory and also propagate the changes to the
// RTC. Always succeeds and returns 1.
byte hostWriteXMem(byte address, byte data)
{
genSendWriteXCmd(address, data);
pram[address] = data;
return 1;
}
// Accessor function to read host XPRAM memory.
byte hostReadXMem(byte address)
{
return pram[address];
}
// Load the host copy of the traditional PRAM from a file and update
// the RTC device memory. Also clears write-protect. Returns true on
// success, false on failure.
bool8_t fileLoadAllTradMem(const char *filename)
{
FILE *fp = fopen(filename, "rb");
int ch;
uint8_t i;
if (fp == NULL)
return false;
clearWriteProtect();
// Copy group 1 registers.
for (i = 0; i < 16; i++) {
byte data;
ch = getc(fp);
if (ch == EOF)
goto cleanup_fail;
data = ch;
pram[group1Base+i] = data;
genSendWriteCmd(16 + i, data);
}
// Copy group 2 registers.
for (i = 0; i < 4; i++) {
byte data;
ch = getc(fp);
if (ch == EOF)
goto cleanup_fail;
data = ch;
pram[group2Base+i] = data;
genSendWriteCmd(8 + i, data);
}
if (fclose(fp) == EOF)
return false;
return true;
cleanup_fail:
fclose(fp);
return false;
}
// Save the host copy of the traditional PRAM to a file. Returns true
// on success, false on failure.
bool8_t fileDumpAllTradMem(const char *filename)
{
FILE *fp = fopen(filename, "wb");
uint8_t i;
if (fp == NULL)
return false;
// Copy group 1 registers.
for (i = 0; i < 16; i++) {
byte data = pram[group1Base+i];
if (putc(data, fp) == EOF)
goto cleanup_fail;
}
// Copy group 2 registers.
for (i = 0; i < 4; i++) {
byte data = pram[group2Base+i];
if (putc(data, fp) == EOF)
goto cleanup_fail;
}
if (fclose(fp) == EOF)
return false;
return true;
cleanup_fail:
fclose(fp);
return false;
}
// Load the host copy of the XPRAM from a file and update the RTC
// device memory. Also clears write-protect. Returns true on
// success, false on failure.
bool8_t fileLoadAllXMem(const char *filename)
{
FILE *fp = fopen(filename, "rb");
if (fp == NULL)
return false;
if (fread(pram, 1, 256, fp) != 256) {
fclose(fp);
return false;
}
if (fclose(fp) == EOF)
return false;
loadAllXMem();
return true;
}
// Save the host copy of the XPRAM to a file. Returns true on
// success, false on failure.
bool8_t fileDumpAllXMem(const char *filename)
{
FILE *fp = fopen(filename, "wb");
if (fp == NULL)
return false;
if (fwrite(pram, 1, 256, fp) != 256) {
fclose(fp);
return false;
}
if (fclose(fp) == EOF)
return false;
return true;
}
/********************************************************************/
/* PRAM interactive command line module */
/*
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "arduino_sdef.h"
#include "pram-lib.h"
#include "a2mon-pram.h"
#include "simavr-support.h"
#include "auto-test-suite.h"
*/
void simRec(void);
void simNoRec(void);
void setMonMode(uint8_t newMonMode);
uint8_t getMonMode(void);
byte monMemAccess(uint16_t address, bool8_t writeRequest, byte data);
bool8_t execMonLine(char *lineBuf);
uint8_t g_lastRetVal;
/* Since every subroutine for command-line commands only has zero to
three arguments, all being numeric except for the file commands
that take a single string argument, I can use a very simple
command-line parser, space separation for arguments only, no
quoting semantics. */
// Parse the desired number of 8-bit numbers expressed in hexidecimal
// on a command line and store them in the designated output array.
// Returns the actual number of 8-bit numbers parsed. On format
// error or extra arguments, 0xff is returned.
uint8_t parse8Bits(byte *output, uint8_t limit, char *parsePtr)
{
uint8_t numParsed = 0;
char *token;
char *firstPtr = parsePtr;
char *savePtr;
while (numParsed < limit &&
(token = strtok_r(firstPtr, " \t", &savePtr)) != NULL) {
long num = strtol(token, NULL, 16);
firstPtr = NULL;
if (num < 0 || num > 255)
return 0xff;
output[numParsed++] = (byte)num;
}
// Ensure that we do not have extra arguments.
if ((token = strtok_r(firstPtr, " \t", &savePtr)) != NULL)
return 0xff;
return numParsed;
}
#define SKIP_WHITESPACE(str) \
while (*(str) != '\0' && (*(str) == ' ' || *(str) == '\t')) \
(str)++;
#define PARSE_8BIT_HEAD(numParams) \
uint8_t params[(numParams)+1]; \
if (parse8Bits(params, (numParams), parsePtr) != (numParams)) { \
fputs("Error: Argument syntax error\n", stderr); \
return 0; \
}
// Parse and execute a command line. Return value contains bit flags:
// Bit flag 1|0: Command succeeded/failed
// Bit flag 2|0: Quit command encountered vs. continue
uint8_t execCmdLine(char *lineBuf)
{
bool8_t splitCmd = false;
char *cmdName;
char *parsePtr = lineBuf;
SKIP_WHITESPACE(parsePtr);
cmdName = parsePtr;
while (*parsePtr != '\0' && *parsePtr != ' ' && *parsePtr != '\t')
parsePtr++;
if (*parsePtr != '\0') {
splitCmd = true;
*parsePtr++ = '\0';
}
SKIP_WHITESPACE(parsePtr);
if (strcmp(cmdName, "?") == 0 ||
strcmp(cmdName, "help") == 0) {
fputs(
"Summary of command-line commands:\n"
" ?, help -- show this help page\n"
" # comment\n"
" echo str\n"
" set-pram-type isXPram -- 0 for 20-byte PRAM, 1 for XPRAM (default)\n"
" get-pram-type\n"
" send-read-cmd cmd\n"
" send-write-cmd cmd data\n"
" send-read-xcmd cmd1 cmd2\n"
" send-write-xcmd cmd1 cmd2 data\n"
" test-write\n"
" set-write-protect\n"
" clear-write-protect\n"
" dump-time -- copy time from RTC to host\n"
" load-time -- clear write-protect, copy time from host to RTC\n"
" set-time b1 b2 b3 b4 -- also clears write-protect\n"
" get-time\n"
" mac-to-str-time b1 b2 b3 b4\n"
" str-to-mac-time timeStr\n"
" set-str-time timeStr -- also clears write-protect\n"
" get-str-time\n"
" set-cur-time -- also clears write-protect\n"
" gen-cmd address writeRequest\n"
" gen-send-read-cmd address\n"
" gen-send-write-cmd address data\n"
" dump-all-trad-mem -- copy all traditional 20-byte PRAM memory from\n"
" RTC to host\n"
" load-all-trad-mem -- clear write-protect, copy from host to RTC\n"
" gen-xcmd address writeRequest\n"
" gen-send-read-xcmd address\n"
" gen-send-write-xcmd address data\n"
" dump-all-xmem\n"
" load-all-xmem -- also clears write-protect\n"
" host-trad-pram-cmd cmd data\n"
" host-write-xmem address data\n"
" host-read-xmem address\n"
" set-mon-mode newMode -- 0 = disable, 1 = traditional PRAM,\n"
" 2 = XPRAM\n"
" get-mon-mode\n"
" mon-mem-access address writeRequest data\n"
" file-load-all-trad-mem filename -- also clears write-protect\n"
" file-dump-all-trad-mem filename\n"
" file-load-all-xmem filename -- also clears write-protect\n"
" file-dump-all-xmem filename\n"
" sim-rec -- start recording RTC pin signal waveforms\n"
" sim-no-rec -- stop recording RTC pin signal waveforms\n"
" auto-test-suite verbose simRealTime testXPram\n"
" suite-start\n"
" suite-end\n"
" rec-ts-result desc\n"
" rec-ts-skip desc\n"
" get-suite-mode\n"
" q, quit -- exit the program\n"
"\n"
"Most commands are named after the corresponding library subroutines,\n"
"see the source code comments for more information. All arguments\n"
"are 8-bit hexidecimal integers, except for file names and string\n"
"time.\n"
"\n"
"If one of the \"monitor modes\" are enabled, a subset of the most\n"
"basic Apple II monitor commands can be used and it will operate in the\n"
"configured address space. Namely, dumping memory and writing memory\n"
"contents.\n"
"\n"
"For example, to write memory:\n"
"\n"
"You type> 0000: 01 02 1a 2c\n"
"\n"
"To dump memory:\n"
"\n"
"You type> 00C0\n"
"You get> 00C0- 53 52 68 2E 0A 00 00 68\n"
"\n"
"Other noteworthy tricks:\n"
"\n"
"* Type a memory address and ENTER to dump one line of memory.\n"
"\n"
"* Press ENTER repeatedly to dump the next line of memory.\n"
"\n"
"* Type \".\" (dot) ADDR and ENTER to dump memory from the last address\n"
" up to the given address.\n"
"\n"
"* Type \"G\" to execute at the last address. NOT RECOMMENDED.\n"
"\n"
"* You can omit the address and type \":\" when writing memory to\n"
" continue from the last address.\n"
"\n"
"* \"-\" (hyphen) is also supported on entry for convenience.\n"
"\n",
stdout);
// If XOR checksum mode is enabled, add it to the example:
"* The XOR checksum at the end (example X3114) is optional when writing\n"
" memory.\n"
"\n";
return 1;
} else if (strcmp(cmdName, "#") == 0) {
// Comment, ignore to end of line.
return 1;
} else if (strcmp(cmdName, "echo") == 0) {
PR_TS_INFO();
fputs(parsePtr, stdout);
putchar('\n');
return 1;
} else if (strcmp(cmdName, "set-pram-type") == 0) {
PARSE_8BIT_HEAD(1);
setPramType(params[0]);
return 1;
} else if (strcmp(cmdName, "get-pram-type") == 0) {
byte result;
PARSE_8BIT_HEAD(0);
result = getPramType();
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "send-read-cmd") == 0) {
byte result;
PARSE_8BIT_HEAD(1);
result = sendReadCmd(params[0]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "send-write-cmd") == 0) {
PARSE_8BIT_HEAD(2);
sendWriteCmd(params[0], params[1]);
return 1;
} else if (strcmp(cmdName, "send-read-xcmd") == 0) {
byte result;
PARSE_8BIT_HEAD(2);
result = sendReadXCmd(params[0], params[1]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "send-write-xcmd") == 0) {
PARSE_8BIT_HEAD(3);
sendWriteXCmd(params[0], params[1], params[2]);
return 1;
} else if (strcmp(cmdName, "test-write") == 0) {
PARSE_8BIT_HEAD(0);
testWrite();
return 1;
} else if (strcmp(cmdName, "set-write-protect") == 0) {
PARSE_8BIT_HEAD(0);
setWriteProtect();
return 1;
} else if (strcmp(cmdName, "clear-write-protect") == 0) {
PARSE_8BIT_HEAD(0);
clearWriteProtect();
return 1;
} else if (strcmp(cmdName, "dump-time") == 0) {
byte result;
PARSE_8BIT_HEAD(0);
result = dumpTime();
printf("0x%02x\n", result);
return result;
} else if (strcmp(cmdName, "load-time") == 0) {
PARSE_8BIT_HEAD(0);
loadTime();
return 1;
} else if (strcmp(cmdName, "set-time") == 0) {
uint32_t newTimeSecs;
PARSE_8BIT_HEAD(4);
newTimeSecs = params[0] | (params[1] << 8) |
(params[2] << 16) | (params[3] << 24);
setTime(newTimeSecs);
return 1;
} else if (strcmp(cmdName, "get-time") == 0) {
uint32_t result;
PARSE_8BIT_HEAD(0);
result = getTime();
printf("%02x %02x %02x %02x\n",
result & 0xff, (result >> 8) & 0xff,
(result >> 16) & 0xff, (result >> 24) & 0xff);
return 1;
} else if (strcmp(cmdName, "mac-to-str-time") == 0) {
uint32_t readTimeSecs;
char outBuf[64];
PARSE_8BIT_HEAD(4);
readTimeSecs = params[0] | (params[1] << 8) |
(params[2] << 16) | (params[3] << 24);
macToStrTime(outBuf, 64, readTimeSecs);
printf("%s\n", outBuf);
return 1;
} else if (strcmp(cmdName, "str-to-mac-time") == 0) {
uint32_t result = strToMacTime(parsePtr);
printf("%02x %02x %02x %02x\n",
result & 0xff, (result >> 8) & 0xff,
(result >> 16) & 0xff, (result >> 24) & 0xff);
return 1;
} else if (strcmp(cmdName, "set-str-time") == 0) {
setStrTime(parsePtr);
return 1;
} else if (strcmp(cmdName, "get-str-time") == 0) {
char outBuf[64];
PARSE_8BIT_HEAD(0);
getStrTime(outBuf, 64);
printf("%s\n", outBuf);
return 1;
} else if (strcmp(cmdName, "set-cur-time") == 0) {
PARSE_8BIT_HEAD(0);
setCurTime();
return 1;
} else if (strcmp(cmdName, "gen-cmd") == 0) {
byte result;
PARSE_8BIT_HEAD(2);
result = genCmd(params[0], params[1]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "gen-send-read-cmd") == 0) {
byte result;
PARSE_8BIT_HEAD(1);
result = genSendReadCmd(params[0]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "gen-send-write-cmd") == 0) {
PARSE_8BIT_HEAD(2);
genSendWriteCmd(params[0], params[1]);
return 1;
} else if (strcmp(cmdName, "dump-all-trad-mem") == 0) {
PARSE_8BIT_HEAD(0);
dumpAllTradMem();
return 1;
} else if (strcmp(cmdName, "load-all-trad-mem") == 0) {
PARSE_8BIT_HEAD(0);
loadAllTradMem();
return 1;
} else if (strcmp(cmdName, "gen-xcmd") == 0) {
uint16_t result;
PARSE_8BIT_HEAD(2);
result = genXCmd(params[0], params[1]);
printf("%02x %02x\n", (result >> 8) & 0xff, result & 0xff);
return 1;
} else if (strcmp(cmdName, "gen-send-read-xcmd") == 0) {
byte result;
PARSE_8BIT_HEAD(1);
result = genSendReadXCmd(params[0]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "gen-send-write-xcmd") == 0) {
PARSE_8BIT_HEAD(2);
genSendWriteXCmd(params[0], params[1]);
return 1;
} else if (strcmp(cmdName, "dump-all-xmem") == 0) {
PARSE_8BIT_HEAD(0);
dumpAllXMem();
return 1;
} else if (strcmp(cmdName, "load-all-xmem") == 0) {
PARSE_8BIT_HEAD(0);
loadAllXMem();
return 1;
} else if (strcmp(cmdName, "host-trad-pram-cmd") == 0) {
byte result;
PARSE_8BIT_HEAD(2);
result = hostTradPramCmd(params[0], params[1]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "host-write-xmem") == 0) {
PARSE_8BIT_HEAD(2);
hostWriteXMem(params[0], params[1]);
return 1;
} else if (strcmp(cmdName, "host-read-xmem") == 0) {
byte result;
PARSE_8BIT_HEAD(1);
result = hostReadXMem(params[0]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "set-mon-mode") == 0) {
PARSE_8BIT_HEAD(1);
setMonMode(params[0]);
return 1;
} else if (strcmp(cmdName, "get-mon-mode") == 0) {
byte result;
PARSE_8BIT_HEAD(0);
result = getMonMode();
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "mon-mem-access") == 0) {
byte result;
PARSE_8BIT_HEAD(3);
result = monMemAccess(params[0], params[1], params[2]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "file-load-all-trad-mem") == 0) {
byte result = fileLoadAllTradMem(parsePtr);
printf("0x%02x\n", result);
return result;
} else if (strcmp(cmdName, "file-dump-all-trad-mem") == 0) {
byte result = fileDumpAllTradMem(parsePtr);
printf("0x%02x\n", result);
return result;
} else if (strcmp(cmdName, "file-load-all-xmem") == 0) {
byte result = fileLoadAllXMem(parsePtr);
printf("0x%02x\n", result);
return result;
} else if (strcmp(cmdName, "file-dump-all-xmem") == 0) {
byte result = fileDumpAllXMem(parsePtr);
printf("0x%02x\n", result);
return result;
} else if (strcmp(cmdName, "sim-rec") == 0) {
PARSE_8BIT_HEAD(0);
if (!g_phyMode)
simRec();
return 1;
} else if (strcmp(cmdName, "sim-no-rec") == 0) {
PARSE_8BIT_HEAD(0);
if (!g_phyMode)
simNoRec();
return 1;
} else if (strcmp(cmdName, "auto-test-suite") == 0) {
byte result;
PARSE_8BIT_HEAD(3);
result = autoTestSuite(params[0], params[1], params[2]);
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "suite-start") == 0) {
PARSE_8BIT_HEAD(0);
suiteStart();
return 1;
} else if (strcmp(cmdName, "suite-end") == 0) {
byte result;
PARSE_8BIT_HEAD(0);
result = suiteEnd();
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "rec-ts-result") == 0) {
recTsResult(g_lastRetVal, parsePtr);
return 1;
} else if (strcmp(cmdName, "rec-ts-skip") == 0) {
recTsSkip(parsePtr);
return 1;
} else if (strcmp(cmdName, "get-suite-mode") == 0) {
byte result;
PARSE_8BIT_HEAD(0);
result = getSuiteMode();
printf("0x%02x\n", result);
return 1;
} else if (strcmp(cmdName, "q") == 0 ||
strcmp(cmdName, "quit") == 0) {
return 3; // Time to quit.
} else if (*cmdName == '\0') {
// Empty command line, handle specially if in Apple II monitor
// mode.
if (monMode != 0) {
if (splitCmd)
*(--parsePtr) = ' '; // unsplit
// Unchomp the newline character.
lineBuf[strlen(lineBuf)] = '\n';
return execMonLine(lineBuf);
}
return 1;
} else {
/* Default action. If monitor mode is enabled, jump to the
monitor command parser. Otherwise indicate a syntax error
right away. */
if (monMode == 0) {
fputs("Error: Unknown command\n", stderr);
} else {
if (splitCmd)
*(--parsePtr) = ' '; // unsplit
// Unchomp the newline character.
lineBuf[strlen(lineBuf)] = '\n';
return execMonLine(lineBuf);
}
}
// NOT REACHED
return 0;
}
// Execute a command line with additional handling for test suite
// result recording.
uint8_t execTsCmdLine(char *lineBuf)
{
uint8_t result = execCmdLine(lineBuf);
g_lastRetVal = result;
return result;
}
// Split up semicolon-delimited command lines and execute each one.
// The return value is the result of the last command.
uint8_t execMultiCmdLine(char *lineBuf)
{
uint8_t retVal = 1;
char *nextCmd = lineBuf;
char *delimPos;
while ((delimPos = strchr(nextCmd, ';')) != NULL) {
char saveChar = delimPos[1];
*delimPos = '\0';
// Reserve space for null character normally following chomped-off
// newline character.
delimPos[1] = '\0';
retVal = execTsCmdLine(nextCmd);
delimPos[1] = saveChar;
if ((retVal & 2) == 2)
return retVal; // Time to quit.
nextCmd = delimPos + 1;
}
/* Execute the last command. For Apple II monitor mode
compatibility, semicolons are strictly intra-line separators, so
an empty command will still get passed to `execTsCmdLine()` for
interpretation. */
retVal = execTsCmdLine(nextCmd);
return retVal;
}
// Return false on exit with error, true on graceful exit.
bool8_t cmdLoop(bool8_t notScripted)
{
uint8_t retVal = true;
char lineBuf[512];
char *parsePtr;
// Print the prompt character.
if (notScripted) putchar('*');
while (1) {
if (fgets(lineBuf, 512, stdin) == NULL) {
if (feof(stdin)) {
if (notScripted) putchar('\n');
break; // End of file
}
else if (errno == EWOULDBLOCK) {
// Run one simulation step.
if (!simAvrStep()) {
fputs("Simulation terminated.\n", stdout);
return true;
}
continue;
} else
break; // Other I/O error.
}
parsePtr = lineBuf + strlen(lineBuf) - 1;
if (*parsePtr != '\n') {
fputs("Error: Command line too long.\n", stderr);
return false;
}
*parsePtr = '\0'; // Chomp off the newline character.
// Dispatch on the command name.
retVal = execMultiCmdLine(lineBuf);
if ((retVal & 2) == 2)
return retVal & 1;
// Print the prompt character.
if (notScripted) putchar('*');
}
return retVal;
}
/********************************************************************/
/* Miniature Apple II monitor module */
/* Tailored for PRAM interface */
/*
#include <stdio.h>
#include "arduino_sdef.h"
#include "pram-lib.h"
*/
// Set the Apple II monitor mode.
void setMonMode(uint8_t newMonMode)
{
monMode = newMonMode;
}
// Get the Apple II monitor mode.
uint8_t getMonMode(void)
{
return monMode;
}
/* Read/write to either traditional PRAM or XPRAM depending on the
Apple II monitor mode. Addresses out of range return zero on read
and do nothing on write. For reads, `data` is ignored. Returns
data on successful reads, zero on unsuccessful reads, one on
successful writes, zero on unsuccessful writes. */
byte monMemAccess(uint16_t address, bool8_t writeRequest, byte data)
{
if (monMode == 1) {
// Traditional PRAM
if (address > 0x1f)
return 0; // invalid address
return hostTradPramCmd(genCmd(address, writeRequest), data);
} else if (monMode == 2) {
// XPRAM
if (address > 0xff)
return 0; // invalid address
if (writeRequest)
return hostWriteXMem(address, data);
else
return hostReadXMem(address);
}
// else Monitor mode disabled, always return zero.
return 0;
}
/* NOTE: We're copying in some hexidecimal helper subroutines in here
just because they are a little more convenient to use than the
standard C library routines, even though it's a duplication of
effort. Plus I already wrote up and tested an Apple II style
monitor, so I was copying and pasting the code into here with minor
modifications. */
#define ishex(ch) (((ch >= '0') && (ch <= '9')) || \
((ch >= 'A') && (ch <= 'F')) || \
((ch >= 'a') && (ch <= 'f')))
/* #define USE_XOR_CK */
/* #define XOR_CK_LEN 2 */
unsigned short last_addr;
#ifdef USE_XOR_CK
unsigned short error_count = 0;
#endif
char *g_monLineBuf;
void puthex(unsigned char len, unsigned short data);
unsigned short parsehex(unsigned char len, char *data);
unsigned short gethex(unsigned char maxlen, char *rch);
void dumphex(unsigned short addr, unsigned short end_addr,
unsigned char one_line);
void writehex(char *rch);
bool8_t execMonLine(char *lineBuf)
{
char ch;
g_monLineBuf = lineBuf;
ch = *g_monLineBuf++;
while (ch != '\0') {
if (ch == '\n') {
/* Display one line of hex dump. */
unsigned short addr = last_addr;
unsigned short end_addr = addr + 8;
dumphex(addr, end_addr, 1);
} else if (ishex(ch)) {
/* Read an address. */
last_addr = gethex(4, &ch);
/* Skip standard actions at end of loop, we may have additional
commands to read. */
continue;
} else if (ch == '.') {
/* Read memory range. */
unsigned short end_addr;
ch = *g_monLineBuf++;
if (ch == '\0')
break;
end_addr = gethex(4, &ch);
dumphex(last_addr, end_addr, 0);
} else if (ch == '-' || ch == ':') {
/* Write bytes to address. */
writehex(&ch);
if (ch == '\0')
break;
} else if (ch == 'G' || ch == 'g') {
/* Ignore characters until end of newline. */
while ((ch = *g_monLineBuf++) != '\0' && ch != '\n');
/* Execute! */
/* PLEASE NOTE: This will always crash unless you've changed the
section headers to make the XPRAM section executable, which
it isn't by default. Also,this only works in a linear
address space, of course, i.e. XPRAM. */
if (monMode != 2 || last_addr > 0xff) {
fputs("\aINVALID EXECUTE MODE\n", stderr);
return false;
}
((void (*)(void))&pram[last_addr])();
} else if (ch == ' ') {
/* Horizontal whitespace, ignore. */
ch = *g_monLineBuf++;
continue;
} else {
fputs("\a?SYNTAX ERROR\n", stderr);
return false;
}
// return true; // putprompt();
ch = *g_monLineBuf++;
}
return true;
}
/* `len' is length in hex chars, should be either 2 (byte) or 4
(word).
N.B. Shifting is expensive on early 8-bit processors because you
can only shift one bit at a time, so we try to minimize that
here. */
void puthex(unsigned char len, unsigned short data)
{
char buf[4];
unsigned char i = len;
while (i > 0) {
unsigned char val;
i--;
val = data & 0x0f;
data >>= 4;
if (val < 0xa)
val += '0';
else
val += 'A' - 0xa;
buf[i] = val;
}
while (i < len) {
putchar(buf[i++]);
}
}
unsigned short parsehex(unsigned char len, char *data)
{
unsigned short result = 0;
unsigned char i = 0;
while (i < len) {
unsigned char val;
val = data[i];
if (val >= 'a')
val -= 'a' - 0xa;
else if (val >= 'A')
val -= 'A' - 0xa;
else
val -= '0';
val &= 0x0f;
result <<= 4;
result |= val;
i++;
}
return result;
}
/* TODO: Every time after calling gethex(), check if we are stuck on
an invalid non-hex char. */
unsigned short gethex(unsigned char maxlen, char *rch)
{
unsigned short result;
char ch;
char rdbuf[4];
unsigned rdbuf_len = 0;
if (maxlen > 4) /* programmer error, i.e. assert() failure */
return 0;
ch = *rch;
while (ch != '\0' && rdbuf_len < maxlen && ishex(ch)) {
rdbuf[rdbuf_len++] = (char)ch;
ch = *g_monLineBuf++;
}
*rch = ch;
result = parsehex(rdbuf_len, rdbuf);
return result;
}
void dumphex(unsigned short addr, unsigned short end_addr,
unsigned char one_line)
{
#ifdef USE_XOR_CK
unsigned char xor_cksum[XOR_CK_LEN] = { 0, 0/*, 0, 0*/ };
unsigned char xor_pos = 0;
#endif
PR_TS_INFO();
puthex(4, addr); putchar('-');
/* TODO FIXME: I trid to fold the last iteration into here to reduce
code, but that introduces a bug that does not properly handle
"0000.ffff". Fix this. */
/* N.B. If end_addr < addr, we print one byte at addr,
similar to the Apple II monitor. */
do {
unsigned char val = monMemAccess(addr++, false, 0);
#ifdef USE_XOR_CK
xor_cksum[xor_pos++] ^= val;
xor_pos &= XOR_CK_LEN - 1;
#endif
putchar(' ');
puthex(2, val);
if ((addr & 0x07) == 0x00) {
#ifdef USE_XOR_CK
/* Print XOR checksum. */
putchar(' ');
putchar('X');
puthex(4, ((unsigned short)xor_cksum[0] << 8) | xor_cksum[1]);
/* puthex(4, ((unsigned short)xor_cksum[2] << 8) | xor_cksum[3]); */
xor_cksum[0] = 0; xor_cksum[1] = 0;
/* xor_cksum[2] = 0; xor_cksum[3] = 0; */
#endif
if (one_line)
break;
if (addr <= end_addr) {
putchar('\n');
PR_TS_INFO();
puthex(4, addr); putchar('-');
}
}
} while (addr <= end_addr);
putchar('\n');
last_addr = addr;
}
void writehex(char *rch)
{
char ch;
unsigned short addr = last_addr;
#ifdef USE_XOR_CK
unsigned char xor_cksum[XOR_CK_LEN] = { 0, 0/*, 0, 0*/ };
unsigned char xor_pos = 0;
#endif
ch = *g_monLineBuf++;
if (ch == '\0')
goto cleanup;
do {
unsigned char val;
while (ch == ' ')
ch = *g_monLineBuf++;
if (ch == '\0' || ch == '\n' || ch == 'X' || ch == 'x')
break;
val = (unsigned char)gethex(2, &ch);
#ifdef USE_XOR_CK
xor_cksum[xor_pos++] ^= val;
xor_pos &= XOR_CK_LEN - 1;
#endif
monMemAccess(addr++, true, val);
} while (ch != '\n' && ch != 'X' && ch != 'x');
#ifdef USE_XOR_CK
if (ch == 'X' || ch == 'x') {
/* Read and validate XOR checksum. */
unsigned char rd_cksum[XOR_CK_LEN] = { 0, 0/*, 0, 0*/ };
ch = *g_monLineBuf++;
if (ch == '\0')
goto cleanup;
rd_cksum[0] = (unsigned char)gethex(2, &ch);
rd_cksum[1] = (unsigned char)gethex(2, &ch);
/* rd_cksum[2] = (unsigned char)gethex(2, &ch);
rd_cksum[3] = (unsigned char)gethex(2, &ch); */
if (xor_cksum[0] != rd_cksum[0] ||
xor_cksum[1] != rd_cksum[1] /* ||
xor_cksum[2] != rd_cksum[2] ||
xor_cksum[3] != rd_cksum[3] */) {
putchar('\a'); putchar('E');
/* With our current checksumming algorithm, after 128 detected
errors, it is pretty much guaranteed that there may be one
undetected error. */
if (error_count >= 128)
{ putchar('\a'); putchar('!'); }
else
error_count++;
putchar('\n');
/* Rewind to `last_addr' on error. */
addr = last_addr;
}
}
#endif
last_addr = addr;
cleanup:
*rch = ch;
}
/********************************************************************/
/* `simavr` support module */
/*
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "sim_avr.h"
#include "avr_ioport.h"
#include "avr_timer.h"
#include "sim_elf.h"
#include "sim_gdb.h"
#include "sim_vcd_file.h"
#include "arduino_sdef.h"
#include "pram-lib.h"
*/
static const char * bench_irq_names[5] =
{ "BENCH.SEC1", "BENCH.CE*", "BENCH.CLK",
"BENCH.DATA.IN", "BENCH.DATA.OUT*" };
// simavr variables
avr_t *avr = NULL;
avr_vcd_t vcd_file;
avr_irq_t *bench_irqs = NULL;
avr_cycle_count_t notify_timeup(avr_t *avr, avr_cycle_count_t when,
void *param)
{
g_waitTimeUp = true;
if (g_timePoll)
return avr->cycle + g_timePoll;
return 0;
}
// Start recording VCD signal waveforms for RTC pins. Only applicable
// when running under simulation.
void simRec(void)
{
printf("Starting VCD trace\n");
avr_vcd_start(&vcd_file);
}
// Stop recording VCD signal waveforms for RTC pins. Only applicable
// when running under simulation.
void simNoRec(void)
{
printf("Stopping VCD trace\n");
avr_vcd_stop(&vcd_file);
}
void pin_change_notify(avr_irq_t *irq, uint32_t value, void *param)
{
if (irq == bench_irqs + IRQ_SEC1 && value)
sec1Isr();
else if (irq == bench_irqs + IRQ_DATA_OUT) {
// Only write the updated value to the buffer register if the VIA
// is in the input mode. Also, note that the value we receive is
// inverted.
if (bitRead(vBase[vDirB], rtcData) == DIR_IN)
bitWrite(vBase[vBufB], rtcData, !value);
}
}
int setupSimAvr(char *progName, const char *fname, bool8_t interactMode)
{
elf_firmware_t f;
if (elf_read_firmware(fname, &f) != 0) {
fprintf(stderr, "%s: firmware '%s' invalid\n", progName, fname);
return 1;
}
strcpy(f.mmcu, "attiny85");
//f.frequency = 8000000;
// DEBUG NOTE: I'm only able to do real-time simulation at 400 kHz.
f.frequency = 400000;
printf("firmware %s f=%d mmcu=%s\n", fname, (int)f.frequency, f.mmcu);
avr = avr_make_mcu_by_name(f.mmcu);
if (!avr) {
fprintf(stderr, "%s: AVR '%s' not known\n", progName, f.mmcu);
return 1;
}
avr_init(avr);
avr_load_firmware(avr, &f);
// Initialize our host circuit "peripheral."
// Setup IRQ connections and connect our test bench and AVR
// together.
bench_irqs = avr_alloc_irq(&avr->irq_pool, 0, 5, bench_irq_names);
avr_connect_irq(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 5),
bench_irqs + IRQ_SEC1);
avr_connect_irq(bench_irqs + IRQ_CE,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 0));
avr_connect_irq(bench_irqs + IRQ_CLK,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 2));
// Since we use open-drain signaling on data, this a bit trickier to
// connect to, but this is how to do it.
avr_connect_irq(bench_irqs + IRQ_DATA_IN,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 1));
avr_connect_irq(avr_iomem_getirq(avr, AVR_IO_TO_DATA(0x17), "RTC.DATA.OUT*", 1),
bench_irqs + IRQ_DATA_OUT);
// Register notify functions for inputs to test bench (outputs):
avr_irq_register_notify(bench_irqs + IRQ_SEC1,
pin_change_notify, NULL);
avr_irq_register_notify(bench_irqs + IRQ_DATA_OUT,
pin_change_notify, NULL);
// Give the RTC input pins sane initial values.
avr_raise_irq(bench_irqs + IRQ_CE, 1);
avr_raise_irq(bench_irqs + IRQ_CLK, 0);
avr_raise_irq(bench_irqs + IRQ_DATA_IN, 0);
// NOTE: Propagation of connected IRQs is unidirectional, so we need
// special handling for the bi-directional communication pin.
// Actually, we always need special handling due to our quirk used
// for open collector communication. We always end up using two
// different IRQs, and we decide whether to listen or ignore outputs
// based off of the VIA direction register.
// even if not setup at startup, activate gdb if crashing
avr->gdb_port = 1234;
if (0) {
//avr->state = cpu_Stopped;
avr_gdb_init(avr);
}
/*
* VCD file initialization
*
* This will allow you to create a "wave" file and display it in
* gtkwave. Use the `sim-rec`/`sim-no-rec` commands to
* start/stop recording pin changes.
*/
avr_vcd_init(avr, "gtkwave_trace.vcd", &vcd_file, 10000 /* usec */);
// ATTiny85 PINB == 0x16
// ATTiny85 DDRB == 0x17
// ATTiny85 PORTB == 0x18
avr_vcd_add_signal(&vcd_file,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 5),
1 /* bits */,
"RTC.SEC1" );
avr_vcd_add_signal(&vcd_file,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 0),
1 /* bits */,
"RTC.CE*" );
avr_vcd_add_signal(&vcd_file,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 2),
1 /* bits */,
"RTC.CLK" );
// Since we use open-drain signaling on data, this a bit trickier to
// monitor, but this is how to do it.
avr_vcd_add_signal(&vcd_file,
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ('B'), 1),
1 /* bits */,
"RTC.DATA.IN" );
avr_irq_t *rtc_data_out_irq =
avr_iomem_getirq(avr, AVR_IO_TO_DATA(0x17), "RTC.DATA.OUT*", 1);
// Let's just process inverted data for now.
/* uint8_t flags = avr_irq_get_flags(rtc_data_out_irq);
flags |= IRQ_FLAG_NOT;
avr_irq_set_flags(rtc_data_out_irq, flags); */
avr_vcd_add_signal(&vcd_file, rtc_data_out_irq, 1 /* bits */,
"RTC.DATA.OUT*" );
// TIMER0_OVF == 5
avr_vcd_add_signal(&vcd_file,
avr_get_interrupt_irq(avr, 5),
1 /* bits */ ,
"TIMER0_OVF" );
// printf("Starting VCD trace\n");
// avr_vcd_start(&vcd_file);
if (interactMode) {
// Configure non-blocking mode on standard input so that the
// simulator can still run when we're waiting for user input.
int fflags = fcntl(STDIN_FILENO, F_GETFL);
int result;
if (fflags == -1) {
perror("error getting stdin flags");
return 1;
}
fflags |= O_NONBLOCK;
result = fcntl(STDIN_FILENO, F_SETFL, fflags);
if (result == -1) {
perror("error setting stdin flags");
return 1;
}
}
fputs( "\nSimulation launching:\n", stdout);
return 0;
}
// Run a single step of the AVR simulation, return true if the
// simulation should continue, false if it should stop.
bool8_t simAvrStep(void)
{
// Simulation main loop.
int state = avr_run(avr);
if ((state == cpu_Done) || (state == cpu_Crashed))
return false;
return true;
// NOTE: In the main loop, if we're using multiple threads and
// message passing, we can check if we should send an I/O
// peripheral IRQ message.
}
/********************************************************************/
/* Automated test suite module */
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "arduino_sdef.h"
#include "via-emu.h"
#include "pram-lib.h"
#include "a2mon-pram.h"
*/
bool8_t g_suiteActive = false;
struct timespec g_tsStartTm;
uint8_t g_passCount;
uint8_t g_failCount;
uint8_t g_skipCount;
// Print the elapsed time in the test suite.
void prTestTime(void)
{
struct timespec tv, tvDiff;
clock_gettime(CLOCK_MONOTONIC, &tv);
tvDiff.tv_sec = tv.tv_sec - g_tsStartTm.tv_sec;
tvDiff.tv_nsec = tv.tv_nsec - g_tsStartTm.tv_nsec;
if (tvDiff.tv_nsec < 0) {
tvDiff.tv_sec--;
tvDiff.tv_nsec += 1000000000;
}
printf("[ %3d.%09d ] ", tvDiff.tv_sec, tvDiff.tv_nsec);
}
void prTsStat(const char *status)
{
prTestTime();
fputs(status, stdout);
}
void recTsResult(bool8_t result, const char *desc)
{
prTsStat((result) ? "PASS:" : "FAIL:");
fputs(desc, stdout);
putchar('\n');
if (result)
g_passCount++;
else
g_failCount++;
}
void recTsSkip(const char *desc)
{
prTsStat("SKIP:");
fputs(desc, stdout);
putchar('\n');
g_skipCount++;
}
void suiteStart(void)
{
g_suiteActive = true;
clock_gettime(CLOCK_MONOTONIC, &g_tsStartTm);
g_passCount = 0;
g_failCount = 0;
g_skipCount = 0;
}
bool8_t suiteEnd(void)
{
putchar('\n'); prTsStat("INFO:");
printf("%d passed, %d failed, %d skipped\n",
g_passCount, g_failCount, g_skipCount);
g_suiteActive = false;
return (g_failCount == 0);
}
uint8_t getSuiteMode(void)
{
return g_suiteActive;
}
bool8_t autoTestSuite(bool8_t verbose, bool8_t simRealTime,
bool8_t testXPram)
{
suiteStart();
// Use a non-deterministic seed for randomized tests... but print
// out the value just in case we want to go deterministic.
time_t seed = time(NULL);
prTsStat("INFO:");
printf("random seed = 0x%08x\n", seed);
srand(seed);
if (!simRealTime)
recTsSkip("1-second interrupt line");
else {
/* Listen for 1-second ping, compare with host clock to verify
second counting is working correctly. */
bool8_t result = false;
uint8_t tries = 0;
// Retry up to one time simply because we might cross a one-second
// time boundary intermittently.
do {
// Check that the 1-second line interrupt is working as expected
// for three seconds.
uint32_t expectTimeSecs, actualTimeSecs;
expectTimeSecs = getTime();
waitOneSec(); expectTimeSecs++;
actualTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
}
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec(); expectTimeSecs++;
actualTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
}
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec(); expectTimeSecs++;
actualTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
}
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
} while (!result && ++tries < 2);
recTsResult(result, "1-second interrupt line");
}
{ /* Do a test write, just because we can. Yes, even though it does
absolutely nothing. */
bool8_t result = false;
testWrite();
result = true;
recTsResult(result, "Test write");
}
if (!simRealTime)
recTsSkip("Read clock registers");
else {
/* Read the clock registers into host memory to sync our time. */
bool8_t result = dumpTime();
recTsResult(result, "Read clock registers");
}
if (!simRealTime)
recTsSkip("Write and read clock time registers");
else {
/* Test writing and reading all bytes of the clock time in seconds
register. Code that doesn't properly cast to long can result
in inability to write the high-order bytes. */
bool8_t result = false;
uint8_t tries = 0;
// Retry up to one time simply because we might cross a one-second
// time boundary intermittently.
do {
uint32_t testTimeSecs = 0x983b80d5;
uint32_t readTimeSecs;
setTime(testTimeSecs);
dumpTime();
readTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", readTimeSecs, testTimeSecs);
}
result = (readTimeSecs == testTimeSecs);
} while (!result && ++tries < 2);
recTsResult(result, "Write and read clock time registers");
}
{ /* Set/clear write-protect, test seconds registers, traditional
PRAM, and XPRAM writes and reads with write-protect set and
clear. */
bool8_t result = false;
byte oldVal, newVal, actualVal;
setWriteProtect();
oldVal = genSendReadCmd(0x07);
newVal = ~oldVal;
genSendWriteCmd(0x07, newVal);
actualVal = genSendReadCmd(0x07);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?!= 0x%02x\n", actualVal, newVal);
}
result = (actualVal != newVal);
recTsResult(result,
"Clock register write nulled with write-protect enabled");
clearWriteProtect();
oldVal = genSendReadCmd(0x07);
newVal = ~oldVal;
genSendWriteCmd(0x07, newVal);
actualVal = genSendReadCmd(0x07);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?= 0x%02x\n", actualVal, newVal);
}
result = (actualVal == newVal);
recTsResult(result,
"Clock register write with write-protect disabled");
setWriteProtect();
oldVal = genSendReadCmd(0x08);
newVal = ~oldVal;
genSendWriteCmd(0x08, newVal);
actualVal = genSendReadCmd(0x08);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?!= 0x%02x\n", actualVal, newVal);
}
result = (actualVal != newVal);
recTsResult(result,
"Traditional PRAM write nulled with write-protect enabled");
clearWriteProtect();
oldVal = genSendReadCmd(0x08);
newVal = ~oldVal;
genSendWriteCmd(0x08, newVal);
actualVal = genSendReadCmd(0x08);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?= 0x%02x\n", actualVal, newVal);
}
result = (actualVal == newVal);
recTsResult(result,
"Traditional PRAM write with write-protect disabled");
if (!testXPram)
recTsSkip("XPRAM write nulled with write-protect enabled");
else {
setWriteProtect();
oldVal = genSendReadXCmd(0x30);
newVal = ~oldVal;
genSendWriteXCmd(0x30, newVal);
actualVal = genSendReadXCmd(0x30);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?!= 0x%02x\n", actualVal, newVal);
}
result = (actualVal != newVal);
recTsResult(result,
"XPRAM write nulled with write-protect enabled");
}
if (!testXPram)
recTsSkip("XPRAM write with write-protect disabled");
else {
clearWriteProtect();
oldVal = genSendReadXCmd(0x30);
newVal = ~oldVal;
genSendWriteXCmd(0x30, newVal);
actualVal = genSendReadXCmd(0x30);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?= 0x%02x\n", actualVal, newVal);
}
result = (actualVal == newVal);
recTsResult(result,
"XPRAM write with write-protect disabled");
}
}
{ /* Test for expected memory overlap behavior for memory regions
sharaed in common in both traditional PRAM and XPRAM. Only
applicable to XPRAM. */
bool8_t result = true;
byte groupVal, xpramVal;
if (!testXPram)
recTsSkip("Group 1 and XPRAM memory overlap");
else {
groupVal = genSendReadCmd(0x10);
xpramVal = genSendReadXCmd(0x10);
if (verbose) {
prTsStat("INFO:");
printf(" 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
}
result &= (groupVal == xpramVal);
genSendWriteCmd(0x10, ~groupVal);
groupVal = genSendReadCmd(0x10);
xpramVal = genSendReadXCmd(0x10);
if (verbose) {
prTsStat("INFO:");
printf(" 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
}
result &= (groupVal == xpramVal);
recTsResult(result,
"Group 1 and XPRAM memory overlap");
}
if (!testXPram)
recTsSkip("Group 2 and XPRAM memory overlap");
else {
result = true;
groupVal = genSendReadCmd(0x08);
xpramVal = genSendReadXCmd(0x08);
if (verbose) {
prTsStat("INFO:");
printf(" 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
}
result &= (groupVal == xpramVal);
genSendWriteCmd(0x08, ~groupVal);
groupVal = genSendReadCmd(0x08);
xpramVal = genSendReadXCmd(0x08);
if (verbose) {
prTsStat("INFO:");
printf(" 0x%02x ?= 0x%02x\n", groupVal, xpramVal);
}
result &= (groupVal == xpramVal);
recTsResult(result, "Group 2 and XPRAM memory overlap");
}
}
if (!simRealTime)
recTsSkip("Consistent 1-second interrupt and clock register"
"increment");
else {
/* Test that we can read the contents of the clock, wait a few
seconds, incrementing on the one-second interrupt, then read
the clock register again. The values should match
equivalently. */
bool8_t result = false;
uint8_t tries = 0;
// Retry up to one time simply because we might cross a one-second
// time boundary intermittently.
do {
uint32_t expectTimeSecs, actualTimeSecs;
// Two one-second waits in succession, followed by a
// three-second wait.
dumpTime();
waitOneSec();
expectTimeSecs = getTime();
dumpTime(); actualTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
}
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec();
expectTimeSecs = getTime();
dumpTime(); actualTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
}
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
waitOneSec();
waitOneSec();
waitOneSec();
expectTimeSecs = getTime();
dumpTime(); actualTimeSecs = getTime();
if (verbose) {
prTsStat("INFO:");
printf("0x%08x ?= 0x%08x\n", expectTimeSecs, actualTimeSecs);
}
result = (expectTimeSecs == actualTimeSecs);
if (!result)
continue;
} while (!result && ++tries < 2);
recTsResult(result,
"Consistent interrupt and clock register increment");
}
{ /* Write/read memory regions randomly and verify expected memory
behavior. */
bool8_t result = true;
/* Suitable traditional PRAM address range for testing, keep out
of the clock, write-protect, test write, and extended command
registers:
0x08 - 0x0b
0x10 - 0x1f
Total 20 bytes
Select 8 bytes at random for testing.
*/
byte src_addrs[256];
uint16_t src_addrs_len = 0;
byte rnd_addrs[64], rnd_data[64];
byte rnd_len = 0;
byte i;
// Draw and remove from a source address pool, this guarantees we
// don't pick the same address twice.
while (src_addrs_len < 20) {
byte pick = 8 + src_addrs_len;
if (pick >= 0x0c)
pick += 4;
src_addrs[src_addrs_len++] = pick;
}
while (rnd_len < 8) {
byte pick = rand() % src_addrs_len;
rnd_addrs[rnd_len] = src_addrs[pick];
src_addrs[pick] = src_addrs[--src_addrs_len];
rnd_data[rnd_len] = rand() & 0xff;
genSendWriteCmd(rnd_addrs[rnd_len], rnd_data[rnd_len]);
rnd_len++;
}
while (rnd_len > 0) {
// Pick an element randomly, read-verify it, then delete it from
// the list by overwriting it with the last element.
byte pick = rand() % rnd_len;
byte actualVal = genSendReadCmd(rnd_addrs[pick]);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x: 0x%02x ?= 0x%02x\n", rnd_addrs[pick],
actualVal, rnd_data[pick]);
}
result &= (actualVal == rnd_data[pick]);
rnd_len--;
rnd_addrs[pick] = rnd_addrs[rnd_len];
rnd_data[pick] = rnd_data[rnd_len];
}
recTsResult(result,
"Random traditional PRAM register write/read");
if (!testXPram)
recTsSkip("Random XPRAM register write/read");
else {
result = true;
src_addrs_len = 0;
// Draw and remove from a source address pool, this guarantees we
// don't pick the same address twice.
while (src_addrs_len < 256) {
src_addrs[src_addrs_len] = src_addrs_len;
src_addrs_len++;
}
while (rnd_len < 64) {
byte pick = rand() % src_addrs_len;
rnd_addrs[rnd_len] = src_addrs[pick];
src_addrs[pick] = src_addrs[--src_addrs_len];
rnd_data[rnd_len] = rand() & 0xff;
genSendWriteXCmd(rnd_addrs[rnd_len], rnd_data[rnd_len]);
rnd_len++;
}
while (rnd_len > 0) {
// Pick an element randomly, read-verify it, then delete it from
// the list by overwriting it with the last element.
byte pick = rand() % rnd_len;
byte actualVal = genSendReadXCmd(rnd_addrs[pick]);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x: 0x%02x ?= 0x%02x\n", rnd_addrs[pick],
actualVal, rnd_data[pick]);
}
result &= (actualVal == rnd_data[pick]);
rnd_len--;
rnd_addrs[pick] = rnd_addrs[rnd_len];
rnd_data[pick] = rnd_data[rnd_len];
}
recTsResult(result, "Random XPRAM register write/read");
}
}
{ /* Load and dump and memory linearly, compare for expected memory
behavior. */
bool8_t result = false;
uint8_t oldMonMode = getMonMode();
byte expectedXPram[256];
uint16_t i;
result = true;
setMonMode(2);
// Randomly initialize group 1 registers.
for (i = 0; i < 16; i++)
expectedXPram[group1Base+i] = rand() & 0xff;
// Randomly initialize group 2 registers.
for (i = 0; i < 4; i++)
expectedXPram[group2Base+i] = rand() & 0xff;
// Copy both groups to RTC.
memcpy(pram + group1Base, expectedXPram + group1Base, 16);
memcpy(pram + group2Base, expectedXPram + group2Base, 4);
if (verbose) {
prTsStat("INFO:Expected data:\n");
execMonLine("0008.001f\n");
}
loadAllTradMem();
// Zero our host copy to be sure we don't compare stale data.
memset(pram + group1Base, 0, 16);
memset(pram + group2Base, 0, 4);
dumpAllTradMem();
if (verbose) {
prTsStat("INFO:Actual data:\n");
execMonLine("0008.001f\n");
}
result &= (memcmp(pram + group1Base,
expectedXPram + group1Base, 16) == 0);
result &= (memcmp(pram + group2Base,
expectedXPram + group2Base, 4) == 0);
recTsResult(result, "Load and dump traditional PRAM");
if (!testXPram)
recTsSkip("Load and dump XPRAM");
else {
setMonMode(2);
for (i = 0; i < 256; i++)
expectedXPram[i] = rand() & 0xff;
memcpy(pram, expectedXPram, 256);
if (verbose) {
prTsStat("INFO:Expected data:\n");
execMonLine("0000.00ff\n");
}
loadAllXMem();
// Zero our host copy to be sure we don't compare stale data.
memset(pram, 0, 256);
dumpAllXMem();
if (verbose) {
prTsStat("INFO:Actual data:\n");
execMonLine("0000.00ff\n");
}
result = (memcmp(pram, expectedXPram, 256) == 0);
recTsResult(result, "Load and dump XPRAM");
}
setMonMode(oldMonMode);
}
{ /* Send invalid communication bit sequence, de-select, re-select
chip, then send a valid communication sequence. Verify that
chip can robustly recover from invalid communication
sequences.
It turns out that the protocol is actually quite robust, the
only way to potentially cause an invalid communication state
would be to disable the chip-enable line before a communication
sequence is complete. */
bool8_t result = false;
byte testVal;
genSendWriteCmd(0x10, 0xcd);
serialBegin();
{ /* Fragmented sendByte() that would otherwise clobber the byte
we just wrote. Send only 6 out of 8 bits. */
uint8_t data = genCmd(0x10, true);
uint8_t bitNum = 0;
viaBitWrite(vBase + vDirB, rtcData, DIR_OUT);
while (bitNum <= 5) {
uint8_t bit = (data >> (7 - bitNum)) & 1;
bitNum++;
viaBitWrite(vBase + vBufB, rtcData, bit);
waitQuarterCycle();
viaBitWrite(vBase + vBufB, rtcClk, 1);
waitHalfCycle();
viaBitWrite(vBase + vBufB, rtcClk, 0);
waitQuarterCycle();
}
}
serialEnd();
testVal = genSendReadCmd(0x10);
if (verbose) {
prTsStat("INFO:");
printf("0x%02x ?= 0x%02x\n", testVal, 0xcd);
}
result = (testVal == 0xcd);
recTsResult(result, "Recovery from invalid communication");
}
return suiteEnd();
}
/********************************************************************/
/* `test-rtc` main function module */
/*
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include "arduino_sdef.h"
#include "via-emu.h"
#include "simavr-support.h"
#include "cmdline.h"
#include "auto-test-suite.h"
*/
void mainCleanup(void)
{
viaDestroy();
if (!g_phyMode) {
if (avr)
{ avr_terminate(avr); avr = NULL; }
}
pramDestroy();
}
static void
sig_int(int sign)
{
printf("signal caught, terminating\n");
mainCleanup();
exit(0);
}
int main(int argc, char *argv[])
{
char *firmwareName = "";
bool8_t interactMode = false;
int retVal;
{ // Parse command-line arguments.
unsigned i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 ||
strcmp(argv[i], "--help") == 0) {
printf(
"Usage: %s [-i] [-r a,b,c,d] [FIRMWARE_FILE]\n"
"\n"
" -i Run interactive mode\n"
" -r Physical hardware test mode (via Raspberry Pi).\n"
" Configure SEC1,CE*,CLK,DATA to the given BCM GPIO pin numbers.\n"
"\n", argv[0]);
return 0;
} else if (strcmp(argv[i], "-i") == 0)
interactMode = true;
else if (strcmp(argv[i], "-r") == 0) {
i++;
if (i >= argc) {
fprintf(stderr, "%s: Missing command line argument.\n", argv[0]);
return 1;
}
g_phyMode = true;
sscanf(argv[i], "%d,%d,%d,%d",
g_phyToGpio + PHY_SEC1, g_phyToGpio + PHY_CE,
g_phyToGpio + PHY_CLK, g_phyToGpio + PHY_DATA);
if (g_phyToGpio[PHY_SEC1] == 0 ||
g_phyToGpio[PHY_CE] == 0 ||
g_phyToGpio[PHY_CLK] == 0 ||
g_phyToGpio[PHY_DATA] == 0) {
fprintf(stderr, "%s: Invalid pin configuration.\n", argv[0]);
return 1;
}
} else
firmwareName = argv[i];
}
}
signal(SIGINT, sig_int);
signal(SIGTERM, sig_int);
pramInit();
if (!viaInit()) {
fprintf(stderr, "%s: Failed to initialize VIA emulation.\n", argv[0]);
fprintf(stderr, "%s: Check for driver GPIO reservations.\n", argv[0]);
return 1;
}
if (!g_phyMode)
retVal = setupSimAvr(argv[0], firmwareName, interactMode);
if (retVal != 0)
return retVal;
if (interactMode) {
bool8_t notScripted = isatty(STDIN_FILENO);
fputs("Launching interactive console.\n", stdout);
if (notScripted)
fputs("Type help for summary of commands.\n", stdout);
if (!cmdLoop(notScripted)) {
mainCleanup();
return 1;
}
mainCleanup();
return 0;
}
// Run automated test suite.
fputs("Running automated test suite.\n", stdout);
retVal = !autoTestSuite(false, true, true);
mainCleanup();
return retVal;
}