diff --git a/Cargo.lock b/Cargo.lock
index c09cb73527e60c0c2e4653805b2e466ffc61899a..ce392faf753f4fdfeb8e8283ea11e07edab7fd09 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7,12 +7,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "bitflags"
-version = "1.2.1"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cc"
-version = "1.0.50"
+version = "1.0.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -28,9 +28,9 @@ dependencies = [
 name = "kernel"
 version = "0.1.54"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "linked_list_allocator 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "raw-cpuid 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_syscall 0.1.56",
  "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -41,7 +41,7 @@ dependencies = [
 
 [[package]]
 name = "linked_list_allocator"
-version = "0.6.6"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -54,7 +54,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.9"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -62,10 +62,10 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.3"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -73,11 +73,11 @@ name = "raw-cpuid"
 version = "4.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -85,8 +85,8 @@ name = "raw-cpuid"
 version = "7.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -94,7 +94,7 @@ dependencies = [
 name = "redox_syscall"
 version = "0.1.56"
 dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -133,24 +133,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde"
-version = "1.0.104"
+version = "1.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde_derive"
-version = "1.0.104"
+version = "1.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "slab_allocator"
 version = "0.3.1"
 dependencies = [
- "linked_list_allocator 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -166,11 +166,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "syn"
-version = "1.0.16"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -185,19 +185,19 @@ version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [metadata]
 "checksum bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0"
-"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
+"checksum bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2"
+"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
 "checksum goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6a4013e9182f2345c6b7829b9ef6e670bce0dfca12c6f974457ed2160c2c7fe9"
-"checksum linked_list_allocator 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "47de1a43fad0250ee197e9e124e5b5deab3d7b39d4428ae8a6d741ceb340c362"
+"checksum linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea"
 "checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
-"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
-"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
+"checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0"
+"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
 "checksum raw-cpuid 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "90e0d3209fac374e168cef2d8806dde7b31ef0ee82a965bcc0bec562c078a6f5"
 "checksum raw-cpuid 7.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf"
 "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
@@ -205,10 +205,10 @@ dependencies = [
 "checksum scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383"
 "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
 "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
-"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
+"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
+"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
 "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f"
 "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
+"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
 "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
 "checksum x86 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6874753c331118b83f24d69325958b61f8ef47dbbc7aff2257a0bbe33fb24e3b"
diff --git a/src/arch/x86_64/device/local_apic.rs b/src/arch/x86_64/device/local_apic.rs
index 4cf332fa36bfe0410de788a0d75c4faed42684c1..03565cf246f27d6b51c07e4032e1d6d236254bfa 100644
--- a/src/arch/x86_64/device/local_apic.rs
+++ b/src/arch/x86_64/device/local_apic.rs
@@ -1,3 +1,4 @@
+use core::sync::atomic::{self, AtomicU64};
 use core::intrinsics::{volatile_load, volatile_store};
 use x86::cpuid::CpuId;
 use x86::msr::*;
@@ -5,6 +6,7 @@ use x86::msr::*;
 use crate::memory::Frame;
 use crate::paging::{ActivePageTable, PhysicalAddress, Page, VirtualAddress};
 use crate::paging::entry::EntryFlags;
+use crate::{interrupt, time};
 
 pub static mut LOCAL_APIC: LocalApic = LocalApic {
     address: 0,
@@ -25,6 +27,21 @@ pub struct LocalApic {
     pub x2: bool
 }
 
+#[derive(Debug)]
+struct NoFreqInfo;
+
+static BSP_APIC_ID: AtomicU64 = AtomicU64::new(0xFFFF_FFFF_FFFF_FFFF);
+
+#[no_mangle]
+pub fn bsp_apic_id() -> Option<u32> {
+    let value = BSP_APIC_ID.load(atomic::Ordering::SeqCst);
+    if value <= u64::from(u32::max_value()) {
+        Some(value as u32)
+    } else {
+        None
+    }
+}
+
 impl LocalApic {
     unsafe fn init(&mut self, active_table: &mut ActivePageTable) {
         self.address = (rdmsr(IA32_APIC_BASE) as usize & 0xFFFF_0000) + crate::KERNEL_OFFSET;
@@ -38,6 +55,7 @@ impl LocalApic {
         }
 
         self.init_ap();
+        BSP_APIC_ID.store(u64::from(self.id()), atomic::Ordering::SeqCst);
     }
 
     unsafe fn init_ap(&mut self) {
@@ -48,7 +66,7 @@ impl LocalApic {
             self.write(0xF0, 0x100);
         }
         self.setup_error_int();
-        self.setup_timer();
+        //self.setup_timer();
     }
 
     unsafe fn read(&self, reg: u32) -> u32 {
@@ -194,9 +212,36 @@ impl LocalApic {
         let vector = 49u32;
         self.set_lvt_error(vector);
     }
-    unsafe fn setup_timer(&mut self) {
-        let div_conf_value = 0b1010; // divide by 128
-        self.set_div_conf(div_conf_value);
+    unsafe fn setup_timer(&mut self) -> Result<(), NoFreqInfo> {
+        // TODO: Get the correct frequency, use the local apic timer instead of the PIT.
+        let cpuid = CpuId::new();
+        let hardcoded_frequency_in_hz = cpuid.get_tsc_info().map(|tsc| {
+            if tsc.numerator() != 0 {
+                // The core crystal clock frequency, in hertz.
+                Some(tsc.tsc_frequency())
+            } else { None }
+        }).or_else(|| {
+            cpuid.get_processor_frequency_info().map(|freq| {
+                let bus_freq = freq.bus_frequency();
+                if bus_freq != 0 {
+                    Some(u64::from(bus_freq) * 1_000_000)
+                } else { None }
+            })
+        }).flatten();
+
+        let frequency_in_hz = hardcoded_frequency_in_hz.unwrap_or_else(|| {
+            let (numer, denom) = self.determine_freq();
+            let quotient = numer / denom;
+            quotient as u64
+        });
+
+        let most_suitable_divider = most_suitable_divider(frequency_in_hz);
+
+        println!("FREQUENCY: {}", frequency_in_hz);
+        println!("MOST_SUIT_DIV: {}", most_suitable_divider);
+
+        let div_conf_value = most_suitable_divider; // divide by 128
+        self.set_div_conf(div_conf_value.into());
 
         let init_count_value = 1_000_000;
         self.set_init_count(init_count_value);
@@ -204,12 +249,83 @@ impl LocalApic {
         let lvt_timer_value = ((LvtTimerMode::Periodic as u32) << 17) | 48u32;
         self.set_lvt_timer(lvt_timer_value);
 
-        // TODO: Get the correct frequency, use the local apic timer instead of the PIT.
+        Ok(())
+    }
+    /// Determine the APIC timer frequency, if the info wasn't already retrieved directly from the
+    /// CPU.
+    unsafe fn determine_freq(&mut self) -> (u128, u128) {
+        let old_time = time::monotonic();
+        let (old_time_s, old_time_ns) = old_time;
+
+        super::super::idt::IDT[32].set_func(super::super::interrupt::irq::calib_pit);
+
+        self.set_div_conf(0b1011); // divide by 1
+        self.set_lvt_timer((LvtTimerMode::OneShot as u32) << 17 | 48);
+
+        // enable both the apic timer and the pit timer simultaneously
+        interrupt::enable_and_nop();
+
+        self.set_init_count(0xFFFF_FFFF);
+
+        let mut time;
+
+        'halt: loop {
+            time = time::monotonic();
+            if time.0 > old_time_s || time.1 - old_time_ns > 10_000_000 {
+                break 'halt;
+            }
+            x86::halt();
+        }
+
+        let (time_s, time_ns) = time;
+
+        let lvt_timer = self.lvt_timer();
+        self.set_lvt_timer(lvt_timer | 1 << 16);
+
+        let current_count = self.cur_count();
+
+        let lvt_timer_difference = 0xFFFF_FFFF - current_count;
+        let (s_difference, ns_difference) = (time_s - old_time_s, time_ns - old_time_ns);
+
+        let freq_numer = u128::from(lvt_timer_difference) * 1_000_000_000; // multiply with a billion since we're dividing by nanoseconds.
+        let freq_denom_in_s = u128::from(s_difference) * 1_000_000_000 + u128::from(ns_difference);
+
+        super::super::idt::IDT[32].set_func(super::super::interrupt::irq::pit);
+
+        (freq_numer, freq_denom_in_s)
     }
 }
+
 #[repr(u8)]
 pub enum LvtTimerMode {
     OneShot = 0b00,
     Periodic = 0b01,
     TscDeadline = 0b10,
 }
+
+/// Find the most suitable divider configuration value, which is useful if the reported frequency
+/// is way too high to actually be useful.
+fn most_suitable_divider(freq: u64) -> u8 {
+    // the current scheduler switches process about every 40 µs, with 4 µs per tick.
+    let quotient = (freq * 1000) / 2_000_000_000;
+    if quotient == 0 {
+        // the frequency is way to low, so the pit should be used
+        println!("Suboptimal APIC timer frequency");
+        0b1011 // divide by 1
+    } else if quotient == 1 {
+        // the frequency closely matches the requested frequency, so use divider 1
+        0b1011
+    } else if quotient < 4 {
+        0b0000 // divider 2
+    } else if quotient < 8 {
+        0b0001 // divider 4
+    } else if quotient < 16 {
+        0b0010 // divider 8
+    } else if quotient < 32 {
+        0b0011 // divider 16
+    } else if quotient < 64 {
+        0b1001 // divider 64
+    } else {
+        0b1010 // divider 128
+    }
+}
diff --git a/src/arch/x86_64/idt.rs b/src/arch/x86_64/idt.rs
index 7ca296d1213145b2a33f4b8af38685b23bc334a0..662cf2fcf2c9556d970fb7f328544afecbe5c9ba 100644
--- a/src/arch/x86_64/idt.rs
+++ b/src/arch/x86_64/idt.rs
@@ -1,10 +1,14 @@
 use core::mem;
+use core::num::NonZeroU8;
+
 use x86::segmentation::Descriptor as X86IdtEntry;
 use x86::dtables::{self, DescriptorTablePointer};
 
 use crate::interrupt::*;
 use crate::ipi::IpiKind;
 
+use spin::Mutex;
+
 pub static mut INIT_IDTR: DescriptorTablePointer<X86IdtEntry> = DescriptorTablePointer {
     limit: 0,
     base: 0 as *const X86IdtEntry
@@ -15,7 +19,68 @@ pub static mut IDTR: DescriptorTablePointer<X86IdtEntry> = DescriptorTablePointe
     base: 0 as *const X86IdtEntry
 };
 
+// TODO: It's probably a good idea to use a separate IDT (and IDT_RESERVATIONS) for each CPU.
+// Currently 202 interrupts are freely allocatable, for the IDT, but if different CPUs received
+// different IDTs, the number would go up to e.g. 1616 IRQs on a processor with 8 logical CPUs.
+
 pub static mut IDT: [IdtEntry; 256] = [IdtEntry::new(); 256];
+pub static IDT_RESERVATIONS: Mutex<[u64; 256 / 64]> = Mutex::new([0u64; 256 / 64]);
+
+#[inline]
+pub fn is_reserved(index: u8) -> bool {
+    let byte_index = index / 64;
+    let bit = index % 64;
+
+    IDT_RESERVATIONS.lock()[usize::from(byte_index)] & (1 << bit) != 0
+}
+
+#[inline]
+pub fn set_reserved(index: u8, reserved: bool) {
+    let byte_index = index / 64;
+    let bit = index % 64;
+
+    IDT_RESERVATIONS.lock()[usize::from(byte_index)] |= u64::from(reserved) << bit;
+}
+
+pub fn allocate_interrupt() -> Option<NonZeroU8> {
+    for number in 50..=255 {
+        if ! is_reserved(number) {
+            set_reserved(number, true);
+            return Some(unsafe { NonZeroU8::new_unchecked(number) });
+        }
+    }
+    None
+}
+
+pub fn available_irqs_iter() -> impl Iterator<Item = u8> + 'static {
+    AvailableIrqsIter { index: Some(50) }.filter(|&index| !is_reserved(index))
+}
+
+struct AvailableIrqsIter {
+    index: Option<u8>,
+}
+impl Iterator for AvailableIrqsIter {
+    type Item = u8;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let index = self.index?;
+        self.index = index.checked_add(1);
+        Some(index)
+    }
+}
+
+macro_rules! use_irq(
+    ( $number:literal, $func:ident ) => {
+        IDT[$number].set_func($func);
+    }
+);
+
+macro_rules! use_default_irqs(
+    () => {{
+        use crate::interrupt::irq::*;
+        default_irqs!(use_irq);
+    }}
+);
 
 pub unsafe fn init() {
     dtables::lidt(&INIT_IDTR);
@@ -52,6 +117,9 @@ pub unsafe fn init_paging() {
     IDT[30].set_func(exception::security);
     // 31 reserved
 
+    // reserve bits 31:0, i.e. the first 32 interrupts, which are reserved for exceptions
+    IDT_RESERVATIONS.lock()[0] |= 0x0000_0000_FFFF_FFFF;
+
     // Set up IRQs
     IDT[32].set_func(irq::pit);
     IDT[33].set_func(irq::keyboard);
@@ -72,15 +140,25 @@ pub unsafe fn init_paging() {
     IDT[48].set_func(irq::lapic_timer);
     IDT[49].set_func(irq::lapic_error);
 
+    use_default_irqs!();
+
+    // reserve bits 49:32, which are for the standard IRQs, and for the local apic timer and error.
+    IDT_RESERVATIONS.lock()[0] |= 0x0003_FFFF_0000_0000;
+
     // Set IPI handlers
     IDT[IpiKind::Wakeup as usize].set_func(ipi::wakeup);
     IDT[IpiKind::Switch as usize].set_func(ipi::switch);
     IDT[IpiKind::Tlb as usize].set_func(ipi::tlb);
     IDT[IpiKind::Pit as usize].set_func(ipi::pit);
+    set_reserved(IpiKind::Wakeup as u8, true);
+    set_reserved(IpiKind::Switch as u8, true);
+    set_reserved(IpiKind::Tlb as u8, true);
+    set_reserved(IpiKind::Pit as u8, true);
 
     // Set syscall function
     IDT[0x80].set_func(syscall::syscall);
     IDT[0x80].set_flags(IdtFlags::PRESENT | IdtFlags::RING_3 | IdtFlags::INTERRUPT);
+    set_reserved(0x80, true);
 
     dtables::lidt(&IDTR);
 }
diff --git a/src/arch/x86_64/interrupt/irq.rs b/src/arch/x86_64/interrupt/irq.rs
index 632cd4ffb60b1f3d448168b05db98314ddf8540f..637f55a076c67a5bf8372edde6731a4a548f1c34 100644
--- a/src/arch/x86_64/interrupt/irq.rs
+++ b/src/arch/x86_64/interrupt/irq.rs
@@ -143,3 +143,36 @@ interrupt!(lapic_error, {
     println!("Local apic internal error: ESR={:#0x}", local_apic::LOCAL_APIC.esr());
     lapic_eoi();
 });
+interrupt!(msi_vector, {
+    println!("MSI interrupt");
+    lapic_eoi();
+});
+interrupt!(calib_pit, {
+    const PIT_RATE: u64 = 2_250_286;
+
+    {
+        let mut offset = time::OFFSET.lock();
+        let sum = offset.1 + PIT_RATE;
+        offset.1 = sum % 1_000_000_000;
+        offset.0 += sum / 1_000_000_000;
+    }
+
+    pic::MASTER.ack();
+});
+// XXX: This would look way prettier using const generics.
+
+macro_rules! allocatable_irq(
+    ( $number:literal, $name:ident ) => {
+        interrupt!($name, {
+            allocatable_irq_generic($number);
+        });
+    }
+);
+
+pub unsafe fn allocatable_irq_generic(number: u8) {
+    println!("generic irq: {}", number);
+    trigger(number - 32);
+    lapic_eoi(); // not sure if needed
+}
+
+define_default_irqs!();
diff --git a/src/arch/x86_64/interrupt/mod.rs b/src/arch/x86_64/interrupt/mod.rs
index 85686d965f00340b317a6ba6d4e802475c79d3dc..fcf75d12033ff2227f492206e60a9a1c150c836a 100644
--- a/src/arch/x86_64/interrupt/mod.rs
+++ b/src/arch/x86_64/interrupt/mod.rs
@@ -8,6 +8,9 @@ pub mod trace;
 
 pub use self::trace::stack_trace;
 
+pub use super::idt::{available_irqs_iter, is_reserved, set_reserved};
+pub use super::device::local_apic::bsp_apic_id;
+
 /// Clear interrupts
 #[inline(always)]
 pub unsafe fn disable() {
diff --git a/src/arch/x86_64/macros.rs b/src/arch/x86_64/macros.rs
index dbbd424cbf89f61d3fda6ef87e951253bdfb68e1..d3d50b6424fd2d95fe04a3df14f16b444252a289 100644
--- a/src/arch/x86_64/macros.rs
+++ b/src/arch/x86_64/macros.rs
@@ -392,3 +392,55 @@ macro_rules! interrupt_error {
         }
     };
 }
+#[macro_export]
+macro_rules! irqs(
+    ( [ $( ($number:literal, $name:ident) ,)* ], $submac:ident ) => {
+        $(
+            $submac!($number, $name);
+        )*
+    }
+);
+
+// define the irq numbers specified in the list above, as functions of the names
+// allocatable_irq_NUM.
+#[macro_export]
+macro_rules! default_irqs(
+    ($submac:ident) => {
+        irqs!([
+            // interrupt vectors below 32 are exceptions
+            // vectors 32..=47 are used for standard 8259 pic irqs.
+            // 48 and 49 are used for the local APIC timer and error register, respectively.
+            (50, irq_50), (51, irq_51), (52, irq_52), (53, irq_53), (54, irq_54), (55, irq_55), (56, irq_56), (57, irq_57), (58, irq_58), (59, irq_59),
+            (60, irq_60), (61, irq_61), (62, irq_62), (63, irq_63),
+            // 64..=67 used for IPI
+            (68, irq_68), (69, irq_69),
+            (70, irq_70), (71, irq_71), (72, irq_72), (73, irq_73), (74, irq_74), (75, irq_75), (76, irq_76), (77, irq_77), (78, irq_78), (79, irq_79),
+            (80, irq_80), (81, irq_81), (82, irq_82), (83, irq_83), (84, irq_84), (85, irq_85), (86, irq_86), (87, irq_87), (88, irq_88), (89, irq_89),
+            (90, irq_90), (91, irq_91), (92, irq_92), (93, irq_93), (94, irq_94), (95, irq_95), (96, irq_96), (97, irq_97), (98, irq_98), (99, irq_99),
+            (100, irq_100), (101, irq_101), (102, irq_102), (103, irq_103), (104, irq_104), (105, irq_105), (106, irq_106), (107, irq_107), (108, irq_108), (109, irq_109),
+            (110, irq_110), (111, irq_111), (112, irq_112), (113, irq_113), (114, irq_114), (115, irq_115), (116, irq_116), (117, irq_117), (118, irq_118), (119, irq_119),
+            (120, irq_120), (121, irq_121), (122, irq_122), (123, irq_123), (124, irq_124), (125, irq_125), (126, irq_126), (127, irq_127),
+            // 128 is used for software interrupts
+            (129, irq_129),
+            (130, irq_130), (131, irq_131), (132, irq_132), (133, irq_133), (134, irq_134), (135, irq_135), (136, irq_136), (137, irq_137), (138, irq_138), (139, irq_139),
+            (140, irq_140), (141, irq_141), (142, irq_142), (143, irq_143), (144, irq_144), (145, irq_145), (146, irq_146), (147, irq_147), (148, irq_148), (149, irq_149),
+            (150, irq_150), (151, irq_151), (152, irq_152), (153, irq_153), (154, irq_154), (155, irq_155), (156, irq_156), (157, irq_157), (158, irq_158), (159, irq_159),
+            (160, irq_160), (161, irq_161), (162, irq_162), (163, irq_163), (164, irq_164), (165, irq_165), (166, irq_166), (167, irq_167), (168, irq_168), (169, irq_169),
+            (170, irq_170), (171, irq_171), (172, irq_172), (173, irq_173), (174, irq_174), (175, irq_175), (176, irq_176), (177, irq_177), (178, irq_178), (179, irq_179),
+            (180, irq_180), (181, irq_181), (182, irq_182), (183, irq_183), (184, irq_184), (185, irq_185), (186, irq_186), (187, irq_187), (188, irq_188), (189, irq_189),
+            (190, irq_190), (191, irq_191), (192, irq_192), (193, irq_193), (194, irq_194), (195, irq_195), (196, irq_196), (197, irq_197), (198, irq_198), (199, irq_199),
+            (200, irq_200), (201, irq_201), (202, irq_202), (203, irq_203), (204, irq_204), (205, irq_205), (206, irq_206), (207, irq_207), (208, irq_208), (209, irq_209),
+            (210, irq_210), (211, irq_211), (212, irq_212), (213, irq_213), (214, irq_214), (215, irq_215), (216, irq_216), (217, irq_217), (218, irq_218), (219, irq_219),
+            (220, irq_220), (221, irq_221), (222, irq_222), (223, irq_223), (224, irq_224), (225, irq_225), (226, irq_226), (227, irq_227), (228, irq_228), (229, irq_229),
+            (230, irq_230), (231, irq_231), (232, irq_232), (233, irq_233), (234, irq_234), (235, irq_235), (236, irq_236), (237, irq_237), (238, irq_238), (239, irq_239),
+            (240, irq_240), (241, irq_241), (242, irq_242), (243, irq_243), (244, irq_244), (245, irq_245), (246, irq_246), (247, irq_247), (248, irq_248), (249, irq_249),
+            (250, irq_250), (251, irq_251), (252, irq_252), (253, irq_253), (254, irq_254), (255, irq_255),
+        ], $submac);
+    }
+);
+
+macro_rules! define_default_irqs(
+    () => {
+        default_irqs!(allocatable_irq);
+    }
+);
diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs
index 715bcad6b78102e51fc6e8be84c8dbe23043c621..646889317a52d217c6f2d1eff1706e4a80e08d94 100644
--- a/src/arch/x86_64/mod.rs
+++ b/src/arch/x86_64/mod.rs
@@ -14,12 +14,13 @@ pub mod gdt;
 #[cfg(feature = "graphical_debug")]
 mod graphical_debug;
 
-/// Interrupt descriptor table
-pub mod idt;
-
 /// Interrupt instructions
+#[macro_use]
 pub mod interrupt;
 
+/// Interrupt descriptor table
+pub mod idt;
+
 /// Inter-processor interrupts
 pub mod ipi;
 
diff --git a/src/scheme/irq.rs b/src/scheme/irq.rs
index a238698cba243d7ffbccd18ebf1a6141cf77c6c0..18ee836fce0904a096460c77b935f354ff2350fa 100644
--- a/src/scheme/irq.rs
+++ b/src/scheme/irq.rs
@@ -3,20 +3,39 @@ use core::sync::atomic::{AtomicUsize, Ordering};
 use spin::{Mutex, RwLock};
 
 use alloc::collections::BTreeMap;
+use alloc::vec::Vec;
+use alloc::string::String;
+
+use crate::arch::interrupt::{available_irqs_iter, bsp_apic_id, is_reserved, set_reserved};
 
 use crate::event;
 use crate::interrupt::irq::acknowledge;
 use crate::scheme::{AtomicSchemeId, SchemeId};
 use crate::syscall::error::*;
-use crate::syscall::flag::{EventFlags, EVENT_READ};
+use crate::syscall::flag::{EventFlags, EVENT_READ, O_DIRECTORY, O_CREAT, O_STAT, MODE_CHR, MODE_DIR, SEEK_CUR, SEEK_END, SEEK_SET};
 use crate::syscall::scheme::Scheme;
 
 pub static IRQ_SCHEME_ID: AtomicSchemeId = AtomicSchemeId::default();
 
 /// IRQ queues
-static COUNTS: Mutex<[usize; 16]> = Mutex::new([0; 16]);
+static COUNTS: Mutex<[usize; 224]> = Mutex::new([0; 224]);
 static HANDLES: RwLock<Option<BTreeMap<usize, Handle>>> = RwLock::new(None);
 
+/// These are IRQs 0..=15 (corresponding to interrupt vectors 32..=47). They are opened without the
+/// O_CREAT flag.
+const BASE_IRQ_COUNT: u8 = 16;
+
+/// These are the extended IRQs, 16..=223 (interrupt vectors 48..=255). Some of them are reserved
+/// for other devices, and some other interrupt vectors like 0x80 (software interrupts) and
+/// 0x40..=0x43 (IPI).
+///
+/// Since these are non-sharable, they must be opened with O_CREAT, which then reserves them. They
+/// are only freed when the file descriptor is closed.
+const TOTAL_IRQ_COUNT: u8 = 224;
+
+const INO_AVAIL: u64 = 0x8000_0000_0000_0000;
+const INO_BSP: u64 = 0x8000_0000_0000_0001;
+
 /// Add to the input queue
 #[no_mangle]
 pub extern fn irq_trigger(irq: u8) {
@@ -24,7 +43,7 @@ pub extern fn irq_trigger(irq: u8) {
 
     let guard = HANDLES.read();
     if let Some(handles) = guard.as_ref() {
-        for (fd, _) in handles.iter().filter(|(_, handle)| handle.irq == irq) {
+        for (fd, _) in handles.iter().filter_map(|(fd, handle)| Some((fd, handle.as_irq_handle()?))).filter(|&(_, (_, handle_irq))| handle_irq == irq) {
             event::trigger(IRQ_SCHEME_ID.load(Ordering::SeqCst), *fd, EVENT_READ);
         }
     } else {
@@ -32,9 +51,21 @@ pub extern fn irq_trigger(irq: u8) {
     }
 }
 
-struct Handle {
-    ack: AtomicUsize,
-    irq: u8,
+enum Handle {
+    Irq {
+        ack: AtomicUsize,
+        irq: u8,
+    },
+    Avail(Vec<u8>, AtomicUsize),
+    Bsp,
+}
+impl Handle {
+    fn as_irq_handle<'a>(&'a self) -> Option<(&'a AtomicUsize, u8)> {
+        match self {
+            &Self::Irq { ref ack, irq } => Some((ack, irq)),
+            _ => None,
+        }
+    }
 }
 
 pub struct IrqScheme {
@@ -53,65 +84,183 @@ impl IrqScheme {
     }
 }
 
+const fn irq_to_vector(irq: u8) -> u8 {
+    irq + 32
+}
+const fn vector_to_irq(vector: u8) -> u8 {
+    vector - 32
+}
+
 impl Scheme for IrqScheme {
-    fn open(&self, path: &[u8], _flags: usize, uid: u32, _gid: u32) -> Result<usize> {
-        if uid == 0 {
-            let path_str = str::from_utf8(path).or(Err(Error::new(ENOENT)))?;
+    fn open(&self, path: &[u8], flags: usize, uid: u32, _gid: u32) -> Result<usize> {
+        if uid != 0 { return Err(Error::new(EACCES)) }
 
-            let id = path_str.parse::<usize>().or(Err(Error::new(ENOENT)))?;
+        let path_str = str::from_utf8(path).or(Err(Error::new(ENOENT)))?;
+        let path_str = path_str.trim_start_matches('/');
 
-            if id < COUNTS.lock().len() {
-                let fd = self.next_fd.fetch_add(1, Ordering::Relaxed);
-                HANDLES.write().as_mut().unwrap().insert(fd, Handle { ack: AtomicUsize::new(0), irq: id as u8 });
-                Ok(fd)
-            } else {
-                Err(Error::new(ENOENT))
+        let handle = if (flags & O_DIRECTORY != 0 || flags & O_STAT != 0) && path_str.is_empty() {
+            // list all of the allocatable IRQs
+
+            let mut bytes = String::new();
+
+            use core::fmt::Write;
+
+            for avail in available_irqs_iter() {
+                write!(bytes, "{}\n", vector_to_irq(avail)).unwrap();
             }
+            if bsp_apic_id().is_some() {
+                write!(bytes, "bsp\n").unwrap();
+            }
+
+            Handle::Avail(bytes.into_bytes(), AtomicUsize::new(0))
         } else {
-            Err(Error::new(EACCES))
-        }
+            if path_str == "bsp" {
+                if bsp_apic_id().is_none() {
+                    return Err(Error::new(ENOENT));
+                }
+                Handle::Bsp
+            } else if let Ok(id) = path_str.parse::<u8>() {
+                if id < BASE_IRQ_COUNT {
+                    Handle::Irq { ack: AtomicUsize::new(0), irq: id }
+                } else if id < TOTAL_IRQ_COUNT {
+                    if flags & O_CREAT == 0 && flags & O_STAT == 0 {
+                        return Err(Error::new(EINVAL));
+                    }
+                    if flags & O_STAT == 0 {
+
+                        if is_reserved(irq_to_vector(id)) {
+                            return Err(Error::new(EEXIST));
+                        }
+                        set_reserved(irq_to_vector(id), true);
+                    }
+                    Handle::Irq { ack: AtomicUsize::new(0), irq: id }
+                } else {
+                    return Err(Error::new(ENOENT));
+                }
+            } else {
+                return Err(Error::new(ENOENT));
+            }
+        };
+        let fd = self.next_fd.fetch_add(1, Ordering::SeqCst);
+        HANDLES.write().as_mut().unwrap().insert(fd, handle);
+        Ok(fd)
     }
 
     fn read(&self, file: usize, buffer: &mut [u8]) -> Result<usize> {
-        // Ensures that the length of the buffer is larger than the size of a usize
-        if buffer.len() >= mem::size_of::<usize>() {
-            let handles_guard = HANDLES.read();
-            let handle = &handles_guard.as_ref().unwrap().get(&file).ok_or(Error::new(EBADF))?;
-
-            let current = COUNTS.lock()[handle.irq as usize];
-            if handle.ack.load(Ordering::SeqCst) != current {
-                // Safe if the length of the buffer is larger than the size of a usize
-                assert!(buffer.len() >= mem::size_of::<usize>());
-                unsafe { *(buffer.as_mut_ptr() as *mut usize) = current; }
-                Ok(mem::size_of::<usize>())
+        let handles_guard = HANDLES.read();
+        let handle = handles_guard.as_ref().unwrap().get(&file).ok_or(Error::new(EBADF))?;
+
+        match handle {
+            // Ensures that the length of the buffer is larger than the size of a usize
+            &Handle::Irq { irq: handle_irq, ack: ref handle_ack } => if buffer.len() >= mem::size_of::<usize>() {
+                let current = COUNTS.lock()[handle_irq as usize];
+                if handle_ack.load(Ordering::SeqCst) != current {
+                    // Safe if the length of the buffer is larger than the size of a usize
+                    assert!(buffer.len() >= mem::size_of::<usize>());
+                    unsafe { *(buffer.as_mut_ptr() as *mut usize) = current; }
+                    Ok(mem::size_of::<usize>())
+                } else {
+                    Ok(0)
+                }
             } else {
-                Ok(0)
+                return Err(Error::new(EINVAL));
+            }
+            &Handle::Bsp => {
+                if buffer.len() < mem::size_of::<usize>() {
+                    return Err(Error::new(EINVAL));
+                }
+                if let Some(bsp_apic_id) = bsp_apic_id() {
+                    unsafe { *(buffer.as_mut_ptr() as *mut usize) = bsp_apic_id as usize; }
+                    Ok(mem::size_of::<usize>())
+                } else {
+                    return Err(Error::new(EBADFD));
+                }
+            }
+            &Handle::Avail(ref buf, ref offset) => {
+                let cur_offset = offset.load(Ordering::SeqCst);
+                let max_bytes_to_read = core::cmp::min(buf.len(), buffer.len());
+                let bytes_to_read = core::cmp::max(max_bytes_to_read, cur_offset) - cur_offset;
+                buffer[..bytes_to_read].copy_from_slice(&buf[cur_offset..cur_offset + bytes_to_read]);
+                offset.fetch_add(bytes_to_read, Ordering::SeqCst);
+                Ok(bytes_to_read)
             }
-        } else {
-            Err(Error::new(EINVAL))
+        }
+    }
+
+    fn seek(&self, id: usize, pos: usize, whence: usize) -> Result<usize> {
+        let handles_guard = HANDLES.read();
+        let handle = handles_guard.as_ref().unwrap().get(&id).ok_or(Error::new(EBADF))?;
+
+        match handle {
+            &Handle::Avail(ref buf, ref offset) => {
+                let cur_offset = offset.load(Ordering::SeqCst);
+                let new_offset = match whence {
+                    SEEK_CUR => core::cmp::min(cur_offset + pos, buf.len()),
+                    SEEK_END => core::cmp::min(buf.len() + pos, buf.len()),
+                    SEEK_SET => core::cmp::min(buf.len(), pos),
+                    _ => return Err(Error::new(EINVAL)),
+                };
+                offset.store(new_offset, Ordering::SeqCst);
+                Ok(new_offset)
+            }
+            _ => return Err(Error::new(ESPIPE)),
         }
     }
 
     fn write(&self, file: usize, buffer: &[u8]) -> Result<usize> {
-        if buffer.len() >= mem::size_of::<usize>() {
-            assert!(buffer.len() >= mem::size_of::<usize>());
+        let handles_guard = HANDLES.read();
+        let handle = handles_guard.as_ref().unwrap().get(&file).ok_or(Error::new(EBADF))?;
 
-            let handles_guard = HANDLES.read();
-            let handle = &handles_guard.as_ref().unwrap().get(&file).ok_or(Error::new(EBADF))?;
+        match handle {
+            &Handle::Irq { irq: handle_irq, ack: ref handle_ack } => if buffer.len() >= mem::size_of::<usize>() {
+                assert!(buffer.len() >= mem::size_of::<usize>());
 
-            let ack = unsafe { *(buffer.as_ptr() as *const usize) };
-            let current = COUNTS.lock()[handle.irq as usize];
+                let ack = unsafe { *(buffer.as_ptr() as *const usize) };
+                let current = COUNTS.lock()[handle_irq as usize];
 
-            if ack == current {
-                handle.ack.store(ack, Ordering::SeqCst);
-                unsafe { acknowledge(handle.irq as usize); }
-                Ok(mem::size_of::<usize>())
+                if ack == current {
+                    handle_ack.store(ack, Ordering::SeqCst);
+                    unsafe { acknowledge(handle_irq as usize); }
+                    Ok(mem::size_of::<usize>())
+                } else {
+                    Ok(0)
+                }
             } else {
-                Ok(0)
+                return Err(Error::new(EINVAL));
+            }
+            _ => return Err(Error::new(EBADF)),
+        }
+    }
+
+    fn fstat(&self, id: usize, stat: &mut syscall::data::Stat) -> Result<usize> {
+        let handles_guard = HANDLES.read();
+        let handle = handles_guard.as_ref().unwrap().get(&id).ok_or(Error::new(EBADF))?;
+
+        match handle {
+            &Handle::Irq { irq: handle_irq, .. } => {
+                stat.st_mode = MODE_CHR | 0o600;
+                stat.st_size = mem::size_of::<usize>() as u64;
+                stat.st_blocks = 1;
+                stat.st_blksize = mem::size_of::<usize>() as u32;
+                stat.st_ino = handle_irq.into();
+                stat.st_nlink = 1;
+            }
+            Handle::Bsp => {
+                stat.st_mode = MODE_CHR | 0o400;
+                stat.st_size = mem::size_of::<usize>() as u64;
+                stat.st_blocks = 1;
+                stat.st_blksize = mem::size_of::<usize>() as u32;
+                stat.st_ino = INO_BSP;
+                stat.st_nlink = 1;
+            }
+            Handle::Avail(ref buf, _) => {
+                stat.st_mode = MODE_DIR | 0o500;
+                stat.st_size = buf.len() as u64;
+                stat.st_ino = INO_AVAIL;
+                stat.st_nlink = 2;
             }
-        } else {
-            Err(Error::new(EINVAL))
         }
+        Ok(0)
     }
 
     fn fcntl(&self, _id: usize, _cmd: usize, _arg: usize) -> Result<usize> {
@@ -123,8 +272,15 @@ impl Scheme for IrqScheme {
     }
 
     fn fpath(&self, id: usize, buf: &mut [u8]) -> Result<usize> {
+        let handles_guard = HANDLES.read();
+        let handle = handles_guard.as_ref().unwrap().get(&id).ok_or(Error::new(EBADF))?;
+
+        let scheme_path = match handle {
+            &Handle::Irq { irq, .. } => format!("irq:{}", irq),
+            &Handle::Bsp => format!("irq:bsp"),
+            &Handle::Avail(_, _) => format!("irq:"),
+        }.into_bytes();
         let mut i = 0;
-        let scheme_path = format!("irq:{}", id).into_bytes();
         while i < buf.len() && i < scheme_path.len() {
             buf[i] = scheme_path[i];
             i += 1;
@@ -136,7 +292,15 @@ impl Scheme for IrqScheme {
         Ok(0)
     }
 
-    fn close(&self, _file: usize) -> Result<usize> {
+    fn close(&self, id: usize) -> Result<usize> {
+        let handles_guard = HANDLES.read();
+        let handle = handles_guard.as_ref().unwrap().get(&id).ok_or(Error::new(EBADF))?;
+
+        if let &Handle::Irq { irq: handle_irq, .. } = handle {
+            if handle_irq > BASE_IRQ_COUNT {
+                set_reserved(irq_to_vector(handle_irq), false);
+            }
+        }
         Ok(0)
     }
 }