#include "payload_common.h"
#include "payload_utils.h"
#include "test_defs.h"
#define RTC_SEC 0x00
#define RTC_MIN 0x02
#define RTC_HOUR 0x04
#define RTC_DAY 0x07
#define RTC_MONTH 0x08
#define RTC_YEAR 0x09
#define RTC_CENTURY 0x32
#define RTC_REGA 0x0a
#define RTC_REGB 0x0b
#define RTC_REGC 0x0c
#define RTC_REGD 0x0d
#define REGA_DIVIDER_32K 0x20
#define REGA_DIVIDER_DIS 0x70
#define REGA_PERIOD_512HZ 0x07
#define REGA_PERIOD_128HZ 0x09
#define REGB_HALT 0x80
#define REGB_IE_PERIODIC 0x40
#define REGB_IE_ALARM 0x20
#define REGB_IE_UPDATE 0x10
#define REGB_DATA_BIN 0x04
#define REGB_24HR 0x02
#define REGB_DST 0x01
#define REGC_IRQ 0x80
#define REGC_PERIODIC 0x40
#define REGC_ALARM 0x20
#define REGC_UPDATE 0x10
#define PPM_THRESHOLD 500
#define ABS(x) ((x) < 0 ? -(x) : (x))
static uint8_t rtc_last_off = 0xff;
static uint8_t
rtc_read(uint8_t off)
{
if (off != rtc_last_off) {
outb(IOP_RTC_ADDR, off);
rtc_last_off = off;
}
return (inb(IOP_RTC_DATA));
}
static void
rtc_write(uint8_t off, uint8_t data)
{
if (off != rtc_last_off) {
outb(IOP_RTC_ADDR, off);
rtc_last_off = off;
}
return (outb(IOP_RTC_DATA, data));
}
static uint8_t
wait_for_flag(uint8_t mask)
{
uint8_t regc;
do {
regc = rtc_read(RTC_REGC);
} while ((regc & mask) == 0);
return (regc);
}
static void
atpic_init(void)
{
outb(IOP_ATPIC_SCMD, 0x11);
outb(IOP_ATPIC_SDATA, 0x20);
outb(IOP_ATPIC_SDATA, 0x00);
outb(IOP_ATPIC_SDATA, 0x03);
outb(IOP_ATPIC_SDATA, 0x00);
}
static uint8_t
atpit_poll_for_intr(void)
{
uint8_t val = 0;
do {
outb(IOP_ATPIC_SCMD, 0x0c);
val = inb(IOP_ATPIC_SDATA);
} while ((val & 0x80) == 0);
return (val);
}
static void
test_periodic_polling(void)
{
rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
rtc_write(RTC_REGB, REGB_HALT);
(void) rtc_read(RTC_REGC);
test_msg("testing periodic (polling)");
rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ);
rtc_write(RTC_REGB, 0);
uint_t periodic_fire = 0;
uint8_t events = 0;
do {
events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC);
if ((events & REGC_PERIODIC) != 0) {
periodic_fire++;
}
} while ((events & REGC_UPDATE) == 0);
if (periodic_fire != 256) {
TEST_ABORT("unexpected periodic firing count at 512Hz");
}
rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_128HZ);
periodic_fire = 0;
do {
events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC);
if ((events & REGC_PERIODIC) != 0) {
periodic_fire++;
}
} while ((events & REGC_UPDATE) == 0);
if (periodic_fire != 128) {
TEST_ABORT("unexpected periodic firing count at 128Hz");
}
}
static void
test_periodic_interrupts(void)
{
rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
rtc_write(RTC_REGB, REGB_HALT);
(void) rtc_read(RTC_REGC);
test_msg("testing periodic (interrupts)");
atpic_init();
rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ);
rtc_write(RTC_REGB, REGB_IE_PERIODIC | REGB_IE_UPDATE);
uint_t periodic_fire = 0;
uint8_t events = 0;
do {
const uint8_t irq = atpit_poll_for_intr();
if (irq != 0x80) {
TEST_ABORT("spurious interrupt on PIC");
}
events = rtc_read(RTC_REGC);
if ((events & REGC_IRQ) == 0) {
TEST_ABORT("missing IRQ flag in regc");
}
if ((events & REGC_PERIODIC) != 0) {
periodic_fire++;
}
} while ((events & REGC_UPDATE) == 0);
if (periodic_fire != 256) {
TEST_ABORT("unexpected periodic firing count at 512Hz");
}
rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
rtc_write(RTC_REGB, REGB_HALT);
}
void
start(void)
{
rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
rtc_write(RTC_REGB, REGB_HALT | REGB_DATA_BIN | REGB_24HR);
(void) rtc_read(RTC_REGC);
rtc_write(RTC_DAY, 1);
rtc_write(RTC_MONTH, 1);
rtc_write(RTC_YEAR, 70);
rtc_write(RTC_CENTURY, 19);
rtc_write(RTC_HOUR, 0);
rtc_write(RTC_MIN, 0);
rtc_write(RTC_SEC, 0);
uint64_t start, end;
rtc_write(RTC_REGA, REGA_DIVIDER_32K);
start = rdtsc();
rtc_write(RTC_REGB, REGB_DATA_BIN | REGB_24HR);
if (rtc_read(RTC_REGC) != 0) {
TEST_ABORT("unexpected flags set in regC");
}
test_msg("waiting for first update");
(void) wait_for_flag(REGC_UPDATE);
end = rdtsc();
const uint64_t tsc_500ms = end - start;
start = end;
if (rtc_read(RTC_SEC) != 1) {
TEST_ABORT("did not find 01 in seconds field");
}
test_msg("waiting for second update");
(void) wait_for_flag(REGC_UPDATE);
end = rdtsc();
const uint64_t tsc_1s = end - start;
if (rtc_read(RTC_SEC) != 2) {
TEST_ABORT("did not find 02 in seconds field");
}
int64_t ppm_delta = (int64_t)(tsc_500ms * 2 * 1000000) / tsc_1s;
ppm_delta = ABS(ppm_delta - 1000000);
if (ppm_delta > PPM_THRESHOLD) {
TEST_ABORT("clock update timing outside threshold");
}
rtc_write(RTC_REGA, REGA_DIVIDER_DIS);
rtc_write(RTC_REGB, REGB_HALT);
rtc_write(RTC_HOUR, 0x11);
rtc_write(RTC_MIN, 0x59);
rtc_write(RTC_SEC, 0x59);
rtc_write(RTC_REGA, REGA_DIVIDER_32K);
rtc_write(RTC_REGB, 0);
test_msg("waiting for noon tick-over");
(void) wait_for_flag(REGC_UPDATE);
if (rtc_read(RTC_SEC) != 0) {
TEST_ABORT("invalid RTC_SEC value");
}
if (rtc_read(RTC_MIN) != 0) {
TEST_ABORT("invalid RTC_MIN value");
}
if (rtc_read(RTC_HOUR) != 0x92) {
TEST_ABORT("invalid RTC_HOUR value");
}
test_periodic_polling();
test_periodic_interrupts();
test_result_pass();
}