Monday, November 25, 2013

switching to the RTC

Still on a quest to minimize CPU energy wastage by halting the CPU and disabling the millisecond interrupts on IRQ0 when no processes were ready to be scheduled, but failing in my attempt to use the second and third timers on the Intel 8253 as sources of interrupts at lower frequencies, I continued to search for options.

Before proceeding, however, I experimented with changing the behavior of the clkint assembler routine in clkint.S to determine whether it was the interrupt itself that was causing the CPU usage, or the interrupt handler. I soon learned that my own concept of a basic interrupt handler was fatally flawed. Not understanding the handler code, I replaced it with the following version:

clkint:
pushal
cli
sti
popal
iret

Surprisingly, an interrupt handler this simple actually caused major problems. Apparently the PIC requires that you notify it of your reception of an interrupt, and the removed instructions

.set    EOI,0x20
pushal
cli
movb $EOI,%al
outb %al,$OCW1_2
sti
popal
iret

were necessary for doing exactly that. After reinserting these instructions, though of course system timekeeping and preemption no longer happened, the system continued to function. However, CPU usage did not decrease. Apparently, the simple act of waking up to receive an interrupt was requiring significant CPU time.

Obviously, any attempt to conserve energy and CPU cycles would need to come by selectively disabling IRQ0 altogether, while somehow maintaining proper system time and providing preemption and timed millisecond sleeps to processes that needed them.

Research unveiled a number of options. This set of slides on timing measurement in Linux was initially helpful. My options included the Advanced Programmable Interrupt Controller (APIC) timer, the Real Time Clock (RTC), the High Performance Event Timer (HPET), and the Time Stamp Counter (TSC).

According to the OSDev Wiki (a very useful source of information on many operating systems development issues), the APIC timer runs at the speed of the CPU clock, and therefore it is necessary to first discover your CPU clock speed and then adjust accordingly. The HPET is part of a newer specification designed to replace the PIT and the RTC, and therefore must be discovered and configured using the Advanced Configuration and Power Interface (ACPI).

In contrast to these more complicated solutions, I decided that using the RTC would be perfectly suited to my goals, as its own clock speed is an exact divisor of a second, allowing basic timekeeping without any arithmetic drift. I proposed to use the RTC to keep a twice-per-second tick (its slowest available interrupt frequency), while using the PIT only in cases where millisecond accuracy was needed.

The RTC initialization proved to be only slightly more involved than the PIT initialization, mainly because the RTC does not automatically send interrupts to IRQ8 even though IRQ8 is reserved for its use. Instead, it requires that you turn those interrupts on via configuration. While configuring the chip, it is also necessary to disable the chip from receiving Non-Maskable Interrupts (NMIs) in order not to leave the RTC chip in an undefined state by only halfway configuring it. The following code performed the necessary initialization:


#define RTC_CMD     0x70  /*** Control port for RTC */
#define RTC_CMOS    0x71  /*** mem I/O port for RTC */
#define RTC_REG_A   0x0A
#define RTC_REG_B   0x0B
#define RTC_REG_C   0x0C
#define RTC_NMI     0x80 

.....

{
   /*** write the desired rate into the bottom 4 bits of register A */
    outb(RTC_CMD, RTC_REG_A | RTC_NMI);
    outb(RTC_CMOS, (char)intv);

    /*** then, enable IRQ8 to start the clock */
    /*** by writing 0x40 to enable bit 6 of register B */
    outb(RTC_CMD, RTC_REG_B | RTC_NMI);
    char reg_B_val = inb(RTC_CMOS);
    outb(RTC_CMD, RTC_REG_B | RTC_NMI); 

    /*** this enables the firing of interrupts */
    outb(RTC_CMOS, reg_B_val | 0x40); 


    rtc_nmi_enable();
}

My initial attempts were failures because I did not re-disable the NMIs with every write to the RTC_CMD register, causing the RTC to end up in a failed state and occasionally causing Global Protection Faults. Eventually, however, I was able to verify that I was receiving IRQ8 interrupts twice per second, fulfilling the first half of my goal!

No comments:

Post a Comment