Skip to content

Commit

Permalink
Handle RTC interrupts (#71)
Browse files Browse the repository at this point in the history
* Fix setting and clearing of IRQ masks

* Add kernel::clock::time_between_ticks

* Add methods to enable RTC interrupts

* Keep track of last RTC update time

* Add fractional seconds to timestamps

* Fix spacing

* Use more accurate PIT frequency

* Change PIT frequency divider

* Replace mutexes by atomics
  • Loading branch information
vinc authored Jul 3, 2020
1 parent e61552f commit c8b1a51
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 26 deletions.
19 changes: 11 additions & 8 deletions src/kernel/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@ const DAYS_BEFORE_MONTH: [u64; 13] = [

// NOTE: This clock is monotonic
pub fn uptime() -> f64 {
1.0 / (1.193182 * 1000000.0 / 65536.0) * kernel::time::ticks() as f64
kernel::time::time_between_ticks() * kernel::time::ticks() as f64
}

// NOTE: This clock is not monotonic
pub fn realtime() -> f64 {
let rtc = CMOS::new().rtc(); // Assuming GMT

let t = 86400 * days_before_year(rtc.year as u64)
+ 86400 * days_before_month(rtc.year as u64, rtc.month as u64)
+ 86400 * (rtc.day - 1) as u64
+ 3600 * rtc.hour as u64
+ 60 * rtc.minute as u64
+ rtc.second as u64;
let timestamp = 86400 * days_before_year(rtc.year as u64)
+ 86400 * days_before_month(rtc.year as u64, rtc.month as u64)
+ 86400 * (rtc.day - 1) as u64
+ 3600 * rtc.hour as u64
+ 60 * rtc.minute as u64
+ rtc.second as u64;

t as f64
let fract = kernel::time::time_between_ticks()
* (kernel::time::ticks() - kernel::time::last_rtc_update()) as f64;

(timestamp as f64) + fract
}

fn days_before_year(year: u64) -> u64 {
Expand Down
73 changes: 73 additions & 0 deletions src/kernel/cmos.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::print;
use x86_64::instructions::port::Port;
use x86_64::instructions::interrupts;

#[repr(u8)]
enum Register {
Expand All @@ -9,7 +10,16 @@ enum Register {
Day = 0x07,
Month = 0x08,
Year = 0x09,
A = 0x0A,
B = 0x0B,
C = 0x0C,
}

#[repr(u8)]
enum Interrupt {
Periodic = 1 << 6,
Alarm = 1 << 5,
Update = 1 << 4,
}

#[derive(Debug)]
Expand Down Expand Up @@ -61,6 +71,55 @@ impl CMOS {
RTC { year, month, day, hour, minute, second }
}

pub fn enable_periodic_interrupt(&mut self) {
self.enable_interrupt(Interrupt::Periodic);
}

pub fn enable_alarm_interrupt(&mut self) {
self.enable_interrupt(Interrupt::Alarm);
}

pub fn enable_update_interrupt(&mut self) {
self.enable_interrupt(Interrupt::Update);
}

/// Rate must be between 3 and 15
/// Resulting in the following frequency: 32768 >> (rate - 1)
pub fn set_periodic_interrupt_rate(&mut self, rate: u8) {
interrupts::without_interrupts(|| {
self.disable_nmi();
unsafe {
self.addr.write(Register::A as u8);
let prev = self.data.read();
self.addr.write(Register::A as u8);
self.data.write((prev & 0xF0) | rate);
}
self.enable_nmi();
self.notify_end_of_interrupt();
});
}

fn enable_interrupt(&mut self, interrupt: Interrupt) {
interrupts::without_interrupts(|| {
self.disable_nmi();
unsafe {
self.addr.write(Register::B as u8);
let prev = self.data.read();
self.addr.write(Register::B as u8);
self.data.write(prev | interrupt as u8);
}
self.enable_nmi();
self.notify_end_of_interrupt();
});
}

pub fn notify_end_of_interrupt(&mut self) {
unsafe {
self.addr.write(Register::C as u8);
self.data.read();
}
}

fn is_updating(&mut self) -> bool {
unsafe {
self.addr.write(0x0A as u8);
Expand All @@ -74,4 +133,18 @@ impl CMOS {
self.data.read()
}
}

fn enable_nmi(&mut self) {
unsafe {
let prev = self.addr.read();
self.addr.write(prev & 0x7F);
}
}

fn disable_nmi(&mut self) {
unsafe {
let prev = self.addr.read();
self.addr.write(prev | 0x80);
}
}
}
14 changes: 8 additions & 6 deletions src/kernel/idt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use lazy_static::lazy_static;
use spin::Mutex;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
use x86_64::instructions::interrupts;
use x86_64::instructions::port::Port;

const PIC1: u16 = 0x21;
const PIC2: u16 = 0xA1;

// Translate IRQ into system interrupt
fn interrupt_index(irq: u8) -> u8 {
Expand Down Expand Up @@ -73,8 +77,6 @@ pub fn init() {
IDT.load();
}

use x86_64::instructions::port::Port;

pub fn set_irq_handler(irq: u8, handler: fn()) {
interrupts::without_interrupts(|| {
let mut handlers = IRQ_HANDLERS.lock();
Expand All @@ -85,17 +87,17 @@ pub fn set_irq_handler(irq: u8, handler: fn()) {
}

pub fn set_irq_mask(irq: u8) {
let mut port: Port<u8> = Port::new(if irq < 8 { 0x21 } else { 0xA1 } );
let mut port: Port<u8> = Port::new(if irq < 8 { PIC1 } else { PIC2 } );
unsafe {
let value = port.read() & (1 << irq);
let value = port.read() | (1 << (if irq < 8 { irq } else { irq - 8 }));
port.write(value);
}
}

pub fn clear_irq_mask(irq: u8) {
let mut port: Port<u8> = Port::new(if irq < 8 { 0x21 } else { 0xA1 } );
let mut port: Port<u8> = Port::new(if irq < 8 { PIC1 } else { PIC2 } );
unsafe {
let value = port.read() & !(1 << irq);
let value = port.read() & !(1 << if irq < 8 { irq } else { irq - 8 });
port.write(value);
}
}
Expand Down
65 changes: 53 additions & 12 deletions src/kernel/time.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::kernel::cmos::CMOS;
use crate::kernel;
use lazy_static::lazy_static;
use spin::Mutex;
use x86_64::instructions::interrupts;
use x86_64::instructions::port::Port;

lazy_static! {
pub static ref TICKS: Mutex<usize> = Mutex::new(0);
}
// At boot the PIT starts with a frequency divider of 0 (equivalent to 65536)
// which will result in about 54.926 ms between ticks.
// During init we will change the divider to 1193 to have about 1.000 ms
// between ticks to improve time measurements accuracy.
const PIT_FREQUENCY: f64 = 3_579_545.0 / 3.0; // 1_193_181.666 Hz
const PIT_DIVIDER: usize = 1193;
const PIT_INTERVAL: f64 = (PIT_DIVIDER as f64) / PIT_FREQUENCY;

static PIT_TICKS: AtomicUsize = AtomicUsize::new(0);
static LAST_RTC_UPDATE: AtomicUsize = AtomicUsize::new(0);

pub fn ticks() -> usize {
*TICKS.lock()
PIT_TICKS.load(Ordering::Relaxed)
}

pub fn time_between_ticks() -> f64 {
PIT_INTERVAL
}

pub fn last_rtc_update() -> usize {
LAST_RTC_UPDATE.load(Ordering::Relaxed)
}

pub fn sleep(duration: f64) {
let interval = 1.0 / (1.193182 * 1000000.0 / 65536.0);
let start = kernel::clock::uptime();
while kernel::clock::uptime() - start < duration - interval {
while kernel::clock::uptime() - start < duration - time_between_ticks() {
halt();
}
}
Expand All @@ -23,10 +39,35 @@ pub fn halt() {
}

pub fn init() {
kernel::idt::set_irq_handler(0, interrupt_handler);
// PIT timmer
let divider = if PIT_DIVIDER < 65536 { PIT_DIVIDER } else { 0 };
set_pit_frequency_divider(divider as u16);
kernel::idt::set_irq_handler(0, pit_interrupt_handler);

// RTC timmer
kernel::idt::set_irq_handler(8, rtc_interrupt_handler);
CMOS::new().enable_update_interrupt();
}

/// The frequency divider must be between 0 and 65535, with 0 acting as 65536
fn set_pit_frequency_divider(divider: u16) {
interrupts::without_interrupts(|| {
let bytes = divider.to_le_bytes();
let mut cmd: Port<u8> = Port::new(0x43);
let mut data: Port<u8> = Port::new(0x40);
unsafe {
cmd.write(0x36);
data.write(bytes[0]);
data.write(bytes[1]);
}
});
}

pub fn pit_interrupt_handler() {
PIT_TICKS.fetch_add(1, Ordering::Relaxed);
}

pub fn interrupt_handler() {
let mut ticks = TICKS.lock();
*ticks += 1;
pub fn rtc_interrupt_handler() {
LAST_RTC_UPDATE.store(ticks(), Ordering::Relaxed);
CMOS::new().notify_end_of_interrupt();
}

0 comments on commit c8b1a51

Please sign in to comment.