Is this RTC code incorrect?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Ethin
Member
Member
Posts: 625
Joined: Sun Jun 23, 2019 5:36 pm
Location: North Dakota, United States

Is this RTC code incorrect?

Post by Ethin »

So, I ported the code on the wiki about reading the time from the RTC. It looks something like this:

Code: Select all

fn read(index: u8) -> u8 {
    let idx = index | NO_NMI;
    unsafe {
        outb(idx, IDX);
        inb(DATA)
    }
}

fn write(index: u8, val: u8) {
    let idx = index | NO_NMI;
    unsafe {
        outb(idx, IDX);
        outb(val, DATA);
    }
}

fn mask(index: u8, off: u8, on: u8) {
    let index = index | NO_NMI;
    unsafe {
        outb(index, IDX);
        let val = inb(DATA);
        outb((val & !off) | on, DATA);
    }
}

// ...
// Initialize RTC
    without_interrupts(|| {
        write(STTSA, 0x26);
        mask(STTSB, !(1 << 0), 1 << 1);
        let _ = read(STTSC);
        let _ = read(STTSD);
        let prev = read(STTSB);
        write(STTSB, prev | 0x40);
    });
// Read the time from the RTC
    loop {
        lsec = second;
        lmin = minute;
        lhr = hour;
        lday = day;
        lmo = month;
        lyr = year;
        lcent = century;
        loop {
            if !StatusA::from_bits_truncate(read(STTSA)).contains(StatusA::UIP) {
                break;
            }
        }
        second = read(SECS) as u128;
        minute = read(MINS) as u128;
        hour = read(HRS) as u128;
        day = read(DAYMO) as u128;
        month = read(MON) as u128;
        year = read(YR) as u128;
        century = read(CENT) as u128;
        if (lsec != second)
            || (lmin != minute)
            || (lhr != hour)
            || (lday != day)
            || (lmo != month)
            || (lyr != year)
            || (lcent != century)
        {
            break;
        }
    }
    let sttsb = StatusB::from_bits(read(STTSB)).unwrap();
    if sttsb.contains(StatusB::DATMD) {
        second = (second & 0x0F) + ((second / 16) * 10);
        minute = (minute & 0x0F) + ((minute / 16) * 10);
        hour = ((hour & 0x0F) + (((hour & 0x70) / 16) * 10)) | (hour & 0x80);
        day = (day & 0x0F) + ((day / 16) * 10);
        month = (month & 0x0F) + ((month / 16) * 10);
        year = (year & 0x0F) + ((year / 16) * 10);
        century = (century & 0x0F) + ((century / 16) * 10);
        if sttsb.contains(StatusB::HR24) && hour.get_bit(7) {
            hour = ((hour & 0x7F) + 12) % 24;
        }
    }
    if century * 100 == CURYR {
        year += century * 100;
    } else {
        year += (CURYR / 100) * 100;
        if year < CURYR {
            year += 100;
        }
    }
My code defines the following constants:

Code: Select all

const IDX: u16 = 0x0070; // Index port
const DATA: u16 = 0x0071; // Data port
const NO_NMI: u8 = 0x80;
const SECS: u8 = 0x00; // Seconds
const SECSALRM: u8 = 0x01;// Seconds alarm
const MINS: u8 = 0x02; // Minutes
const MINSALRM: u8 = 0x03; // Minutes alarm
const HRS: u8 = 0x04; // Hours
const HRSALRM: u8 = 0x05; // Hours alarm
const DAYWK: u8 = 0x06; // Day of week
const DAYMO: u8 = 0x07; // Day of month
const MON: u8 = 0x08; // Month
const YR: u8 = 0x09; // Year
const STTSA: u8 = 0x0A; // Status A
const STTSB: u8 = 0x0B; // Status B
const STTSC: u8 = 0x0C; // Status C
const STTSD: u8 = 0x0D; // Status D
const RST: u8 = 0x0F; // Reset
const FLP_DRV_TYP: u8 = 0x10; // Floppy disk drive type
// Other RTC registers that are probably not relevant
//...
const CURYR: u128 = 2021; // Current year
And here are how I define each bit:

Code: Select all

bitflags! {
struct StatusA: u8 {
/// Update in progress
const UIP = 0x80;
}
}

bitflags! {
struct StatusB: u8 {
/// enable clock setting by freezing updates
const CLKSET = 1 << 7;
/// enable periodic interrupt
const PIE = 1 << 6;
/// enable alarm interrupt
const AIE = 1 << 5;
/// enable update-ended interrupt
const UEIE = 1 << 4;
/// enable square wave output
const SQWOE = 1 << 3;
/// Data Mode - 0: BCD, 1: Binary
const DATMD = 1 << 2;
/// 24/12 hour selection - 1 enables 24 hour mode
const HR24 = 1 << 1;
/// Daylight Savings Enable
const DSTE = 1 << 0;
}
}

bitflags! {
struct StatusC: u8 {
/// Interrupt request flag =1 when any or all of bits 6-4 are 1 and appropriate enables
/// (Register B) are set to 1. Generates IRQ 8 when triggered.
const IRQ = 1 << 7;
/// Periodic Interrupt flag
const PIRQ = 1 << 6;
/// Alarm Interrupt flag
const AI = 1 << 5;
/// Update-Ended Interrupt Flag
const UEI = 1 << 4;
}
}

bitflags! {
struct StatusD: u8 {
/// Valid RAM - 1 indicates battery power good, 0 if dead or disconnected.
const VRAM = 1 << 7;
}
}
Part of this code was taken from the wiki and part of it was taken from Ralf Brown's Interrupt List. When this code runs, I get weird dates that aren't valid, like 2021-33-62. So is this a bug in QEMU (I'm having QEMU set the RTC time to the host time) or a bug with my code? (I'm using "-rtc base=utc,clock=host" with QEMU.)
Edit: So I think I just realized the problem -- I forgot that BCD was only being used if the DATMD bit was clear. Whoops.
Post Reply