From eccb202d0462ba471b88348d3c6c302ed55ee351 Mon Sep 17 00:00:00 2001 From: janis Date: Sun, 19 Jul 2026 22:51:35 +0200 Subject: [PATCH] interrupts --- kernel/Cargo.lock | 14 + kernel/Cargo.toml | 2 + kernel/run.sh | 2 +- kernel/src/lib.rs | 2 +- kernel/src/x86_64.rs | 32 +- kernel/src/x86_64/idt.rs | 648 ++++++++++++++++++++++++++++++ kernel/src/x86_64/instructions.rs | 45 +++ 7 files changed, 737 insertions(+), 8 deletions(-) create mode 100644 kernel/src/x86_64/idt.rs create mode 100644 kernel/src/x86_64/instructions.rs diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index f742c00..ec6524c 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "2.13.1" @@ -12,5 +18,13 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" name = "kernel" version = "0.1.0" dependencies = [ + "bit_field", "bitflags", + "seq-macro", ] + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index ad92720..7870692 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -18,4 +18,6 @@ name = "simple" harness = false [dependencies] +bit_field = "0.10.3" bitflags = "2.13.1" +seq-macro = "0.3.6" diff --git a/kernel/run.sh b/kernel/run.sh index dab2f71..91795d7 100755 --- a/kernel/run.sh +++ b/kernel/run.sh @@ -72,7 +72,7 @@ qemu-system-x86_64 \ -drive file="$image",format=raw \ -machine q35,accel=kvm -enable-kvm \ -drive if=pflash,format=raw,readonly=on,file="$OVMF_PATH/FV/OVMF_CODE.fd" \ - -chardev stdio,id=serial0,logfile=qemu.log,signal=off \ + -chardev stdio,id=serial0,logfile=qemu.log,signal=on \ -serial chardev:serial0 \ -vga std \ ${QEMU_TEST_ARGS[@]} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 6145d17..ad53e9c 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -#![feature(const_trait_impl, const_default)] +#![feature(const_trait_impl, const_default, abi_x86_interrupt, never_type)] #![cfg_attr(test, feature(custom_test_frameworks))] #![cfg_attr(test, test_runner(crate::tests::test_runner))] #![cfg_attr(test, no_main)] diff --git a/kernel/src/x86_64.rs b/kernel/src/x86_64.rs index 67595b6..be526e4 100644 --- a/kernel/src/x86_64.rs +++ b/kernel/src/x86_64.rs @@ -1,11 +1,9 @@ #![cfg(target_arch = "x86_64")] -#[inline] -pub fn hlt() { - unsafe { - core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); - } -} +pub mod idt; +pub mod instructions; + +pub use instructions::hlt; pub fn halt_loop() -> ! { loop { @@ -47,3 +45,25 @@ impl PortU16 { } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +// registers in the order they are pushed onto the stack: +pub struct Registers { + pub r15: u64, + pub r14: u64, + pub r13: u64, + pub r12: u64, + pub r11: u64, + pub r10: u64, + pub r9: u64, + pub r8: u64, + pub rdi: u64, + pub rsi: u64, + pub rbp: u64, + // rsp is not included here + pub rbx: u64, + pub rdx: u64, + pub rcx: u64, + pub rax: u64, +} diff --git a/kernel/src/x86_64/idt.rs b/kernel/src/x86_64/idt.rs new file mode 100644 index 0000000..5731675 --- /dev/null +++ b/kernel/src/x86_64/idt.rs @@ -0,0 +1,648 @@ +use core::{arch::naked_asm, fmt::Debug, marker::PhantomData, ops::Deref, ptr::NonNull}; + +use bit_field::BitField; + +use crate::{serial_println, x86_64::Registers}; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct Entry { + pub offset_low: u16, + pub selector: u16, + pub options: EntryOptions, + pub offset_middle: u16, + pub offset_high: u32, + pub reserved: u32, +} + +impl core::fmt::Debug for Entry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Entry") + .field("handler", &self.handler_address()) + .field("selector", &self.selector) + .field("options", &self.options) + .finish() + } +} + +impl Entry { + pub unsafe fn new(handler: T, selector: u16, options: EntryOptions) -> Self { + let handler_addr = handler.to_address(); + Self { + offset_low: handler_addr as u16, + selector, + options, + offset_middle: (handler_addr >> 16) as u16, + offset_high: (handler_addr >> 32) as u32, + reserved: 0, + } + } + + pub unsafe fn new_default_interrupt() -> Self { + let cs = unsafe { super::instructions::get_cs() }; + let mut options = EntryOptions::empty_interrupt_gate(); + options.set_present(true); + let handler = match IDX { + 8 | 10 | 11 | 12 | 13 | 14 | 17 | 21 | 29 | 30 => { + interrupt_trampoline_with_err:: as *const () as u64 + } + _ => interrupt_trampoline_no_err:: as *const () as u64, + }; + + unsafe { Self::new(handler, cs, options) } + } + + pub const fn missing_interrupt() -> Self { + Self { + offset_low: 0, + selector: 0, + options: EntryOptions::empty_interrupt_gate(), + offset_middle: 0, + offset_high: 0, + reserved: 0, + } + } + + pub fn handler_address(&self) -> u64 { + let low = self.offset_low as u64; + let middle = self.offset_middle as u64; + let high = self.offset_high as u64; + (high << 32) | (middle << 16) | low + } + + pub fn set_handler(&mut self, handler: T) + where + T: ToAddress, + { + let handler_addr = handler.to_address(); + self.offset_low = handler_addr as u16; + self.offset_middle = (handler_addr >> 16) as u16; + self.offset_high = (handler_addr >> 32) as u32; + + self.options = EntryOptions::empty_interrupt_gate(); + unsafe { + self.selector = super::instructions::get_cs(); + } + self.options.set_present(true); + } +} + +pub trait ToAddress { + fn to_address(&self) -> u64; +} + +impl ToAddress for u64 { + fn to_address(&self) -> u64 { + *self + } +} + +impl ToAddress for *const T { + fn to_address(&self) -> u64 { + *self as u64 + } +} + +impl ToAddress for *mut T { + fn to_address(&self) -> u64 { + *self as u64 + } +} + +impl ToAddress for NonNull { + fn to_address(&self) -> u64 { + self.as_ptr() as u64 + } +} + +impl ToAddress for &T { + fn to_address(&self) -> u64 { + *self as *const T as u64 + } +} + +impl ToAddress for &mut T { + fn to_address(&self) -> u64 { + *self as *const T as u64 + } +} + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct EntryOptions(u16); + +impl Debug for EntryOptions { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EntryOptions") + .field("present", &self.present()) + .field("privilege_level", &self.privilege_level()) + .field( + "interrupt_stack_table_index", + &self.interrupt_stack_table_index(), + ) + .field("kind", &self.kind()) + .finish() + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum EntryType { + #[default] + InterruptGate = 0b1110, + TrapGate = 0b1111, +} + +impl EntryType { + pub fn into_u8(self) -> u8 { + self as u8 + } + + /// # Safety + /// The caller must ensure that `value` is either `0b1110` or `0b1111`, or + /// the result of calling `into_u8` on an `EntryType`. + pub unsafe fn from_u8_unchecked(value: u8) -> Self { + unsafe { core::mem::transmute(value) } + } +} + +impl EntryOptions { + pub const fn empty_interrupt_gate() -> Self { + Self(0b1110_0000_0000) + } + pub const fn empty_trap_gate() -> Self { + Self(0b1111_0000_0000) + } + pub fn present(&self) -> bool { + self.0.get_bit(15) + } + pub fn set_present(&mut self, present: bool) { + self.0.set_bit(15, present); + } + + pub fn privilege_level(&self) -> u8 { + self.0.get_bits(13..15) as u8 + } + pub fn set_privilege_level(&mut self, level: u8) { + self.0.set_bits(13..15, level as u16); + } + pub fn interrupt_stack_table_index(&self) -> Option { + let index = self.0.get_bits(0..3) as u8; + if index == 0 { None } else { Some(index - 1) } + } + pub fn set_interrupt_stack_table_index(&mut self, index: u8) { + self.0.set_bits(0..3, index as u16 + 1); + } + pub fn kind(&self) -> EntryType { + unsafe { EntryType::from_u8_unchecked(self.0.get_bits(8..12) as u8) } + } + pub fn set_kind(&mut self, kind: EntryType) { + self.0.set_bits(8..12, kind.into_u8() as u16); + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct InterruptStackFrameInner { + pub instruction_pointer: u64, + pub code_segment: u16, + _reserved: [u16; 3], + pub stack_pointer: u64, + pub stack_segment: u16, + _reserved2: [u16; 3], +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct InterruptStackFrame(InterruptStackFrameInner); + +impl Deref for InterruptStackFrame { + type Target = InterruptStackFrameInner; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(transparent)] + pub struct PageFaultErrorCode: u64 { + const PRESENT = 1 << 0; + const WRITE = 1 << 1; + const USER = 1 << 2; + const RESERVED = 1 << 3; + const INSTRUCTION_FETCH = 1 << 4; + const PROTECTION_KEY = 1 << 5; + const SGX = 1 << 15; + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed(2))] +pub struct InterruptDescriptorTablePointer<'idt> { + pub limit: u16, + pub base: u64, + _pd: PhantomData<&'idt ()>, +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct InterruptDescriptorTable { + pub divide_by_zero: Entry, + pub debug: Entry, + pub non_maskable_interrupt: Entry, + pub breakpoint: Entry, + pub overflow: Entry, + pub bound_range_exceeded: Entry, + pub invalid_opcode: Entry, + pub device_not_available: Entry, + pub double_fault: Entry, + pub coprocessor_segment_overrun: Entry, + pub invalid_tss: Entry, + pub segment_not_present: Entry, + pub stack_segment_fault: Entry, + pub general_protection_fault: Entry, + pub page_fault: Entry, + _reserved1: Entry, + pub x87_floating_point: Entry, + pub alignment_check: Entry, + pub machine_check: Entry, + pub simd_floating_point: Entry, + pub virtualization: Entry, + pub cp_protection: Entry, + _reserved2: [Entry; 6], + pub hypervisor: Entry, + pub vmm_communication_exception: Entry, + pub security_exception: Entry, + _reserved3: Entry, + pub interrupts: [Entry; 256 - 32], +} + +const impl Default for InterruptDescriptorTable { + fn default() -> Self { + Self::new_empty() + } +} + +impl InterruptDescriptorTable { + pub const fn new_empty() -> Self { + Self { + divide_by_zero: Entry::missing_interrupt(), + debug: Entry::missing_interrupt(), + non_maskable_interrupt: Entry::missing_interrupt(), + breakpoint: Entry::missing_interrupt(), + overflow: Entry::missing_interrupt(), + bound_range_exceeded: Entry::missing_interrupt(), + invalid_opcode: Entry::missing_interrupt(), + device_not_available: Entry::missing_interrupt(), + double_fault: Entry::missing_interrupt(), + coprocessor_segment_overrun: Entry::missing_interrupt(), + invalid_tss: Entry::missing_interrupt(), + segment_not_present: Entry::missing_interrupt(), + stack_segment_fault: Entry::missing_interrupt(), + general_protection_fault: Entry::missing_interrupt(), + page_fault: Entry::missing_interrupt(), + _reserved1: Entry::missing_interrupt(), + x87_floating_point: Entry::missing_interrupt(), + alignment_check: Entry::missing_interrupt(), + machine_check: Entry::missing_interrupt(), + simd_floating_point: Entry::missing_interrupt(), + virtualization: Entry::missing_interrupt(), + cp_protection: Entry::missing_interrupt(), + _reserved2: [Entry::missing_interrupt(); 6], + hypervisor: Entry::missing_interrupt(), + vmm_communication_exception: Entry::missing_interrupt(), + security_exception: Entry::missing_interrupt(), + _reserved3: Entry::missing_interrupt(), + interrupts: [Entry::missing_interrupt(); 256 - 32], + } + } + + pub fn new_default() -> Self { + let mut idt = Self::new_empty(); + let slice = unsafe { idt.as_mut_type_erased() }; + seq_macro::seq!( + N in 0..=255 { + slice[N] = unsafe { Entry::new_default_interrupt::() }; + } + ); + idt + } + + pub unsafe fn pointer(&self) -> InterruptDescriptorTablePointer<'_> { + InterruptDescriptorTablePointer { + limit: (core::mem::size_of::() - 1) as u16, + base: self as *const _ as u64, + _pd: PhantomData, + } + } + + pub unsafe fn load(&self) { + unsafe { + super::instructions::lidt(&self.pointer()); + } + } + + pub fn set_interrupt)>( + &mut self, + _handler: T, + ) { + let wrapper = InterruptHandlerWrapper::(PhantomData); + let handler_addr = handler_entry_by_index::(wrapper); + + unsafe { + let entry = self.as_mut_type_erased().get_unchecked_mut(N as usize); + entry.set_handler(handler_addr); + } + } + + pub unsafe fn as_type_erased(&self) -> &[Entry; 256] { + unsafe { core::mem::transmute::<&Self, &[Entry; 256]>(self) } + } + pub unsafe fn as_mut_type_erased(&mut self) -> &mut [Entry; 256] { + unsafe { core::mem::transmute::<&mut Self, &mut [Entry; 256]>(self) } + } +} + +#[unsafe(naked)] +extern "C" fn interrupt_trampoline_with_err() { + naked_asm!( + "push {idx}", + "jmp {dispatcher}", + idx = const { IDX }, + dispatcher = sym interrupt_dispatcher, + ) +} + +#[unsafe(naked)] +extern "C" fn interrupt_trampoline_no_err() { + naked_asm!( + "push 0", // push a dummy error code of 0 + "push {idx}", + "jmp {dispatcher}", + idx = const { IDX }, + dispatcher = sym interrupt_dispatcher, + ) +} + +#[unsafe(naked)] +extern "C" fn interrupt_dispatcher() { + naked_asm!( + "push rax", + "push rcx", + "push rdx", + "push rbx", + "push rbp", + "push rsi", + "push rdi", + "push r8", + "push r9", + "push r10", + "push r11", + "push r12", + "push r13", + "push r14", + "push r15", + "lea rdi, [rsp + {registers_size} + 16]", // interrupt frame + "mov rdx, [rsp + {registers_size} + 8]", // error code + "mov rsi, [rsp + {registers_size}]", // interrupt index + "mov rcx, rsp", // pass the current stack pointer as the registers pointer + "call {global_handler}", + "pop r15", + "pop r14", + "pop r13", + "pop r12", + "pop r11", + "pop r10", + "pop r9", + "pop r8", + "pop rdi", + "pop rsi", + "pop rbp", + "pop rbx", + "pop rdx", + "pop rcx", + "pop rax", + "iretq", + registers_size = const { core::mem::size_of::() }, + global_handler = sym global_interrupt_handler, + ) +} + +extern "C" fn global_interrupt_handler( + frame: &InterruptStackFrame, + index: u8, + error_code: u64, + registers: &Registers, +) { + crate::serial_println!( + "Interrupt {} occurred! Error code: {:#x}, Frame: {:?}, Registers: {:?}", + index, + error_code, + frame, + registers + ); +} + +pub type InterruptHandler = extern "x86-interrupt" fn(InterruptStackFrame); +pub type InterruptWithErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64); +pub type PageFaultHandler = extern "x86-interrupt" fn(InterruptStackFrame, PageFaultErrorCode); +pub type DoubleFaultHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64) -> !; +pub type DivergingHandler = extern "x86-interrupt" fn(InterruptStackFrame) -> !; +pub type GeneralHandler = fn(InterruptStackFrame, index: u8, error_code: Option); + +macro_rules! impl_handler_type { + ($($handler_type:ty),*) => { + $( + impl ToAddress for $handler_type { + fn to_address(&self) -> u64 { + *self as *const () as u64 + } + } + )* + }; +} + +impl_handler_type!( + InterruptHandler, + InterruptWithErrorCodeHandler, + PageFaultHandler, + DoubleFaultHandler, + DivergingHandler +); + +pub trait HandleGeneratorTrait { + fn get_handler_addr(&self) -> u64; +} + +fn handler_entry_by_index(handler: InterruptHandlerWrapper) -> u64 +where + T: Fn(InterruptStackFrame, u8, Option) + Sized, +{ + if IDX == 8 { + // Double fault + DoubleFaultHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 10 { + // Invalid TSS + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 11 { + // Segment not present + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 12 { + // Stack segment fault + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 13 { + // General protection fault + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 14 { + // Page fault + PageFaultHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 17 { + // Alignment check + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 18 { + // Machine check + DivergingHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 29 { + // VMM communication exception + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else if IDX == 30 { + // Security exception + InterruptWithErrorCodeHandlerTrait::::get_handler_addr(&handler) + } else { + // All other interrupts + InterruptHandlerTrait::::get_handler_addr(&handler) + } +} + +pub struct InterruptHandlerWrapper(PhantomData T>); + +pub trait InterruptErrorCode: Sized { + fn as_u64(&self) -> u64; +} + +impl InterruptErrorCode for u64 { + fn as_u64(&self) -> u64 { + *self + } +} + +impl InterruptErrorCode for PageFaultErrorCode { + fn as_u64(&self) -> u64 { + self.bits() + } +} + +macro_rules! interrupt_error_param { + ( $err:ty) => { + $err + }; + () => { + () + }; + ($val:ident: $err:ty) => { + Some(InterruptErrorCode::as_u64(&$val)) + }; + (err:) => { + None + }; +} + +macro_rules! interrupt_return_expr { + ( $expr:ty) => { + $crate::serial_println!("halt looping after unrecoverable interrupt"); + $crate::x86_64::halt_loop() + }; + () => { + () + }; +} + +macro_rules! impl_interrupt_handler_trait { + (trait $trait_name:ident (InterruptStackFrame $(, $err:ty)?) $(-> $ret:ty)?) => { + pub trait $trait_name + where + T: Fn(InterruptStackFrame, u8, Option), + { + extern "x86-interrupt" fn handler( + frame: InterruptStackFrame, + $( err: interrupt_error_param!{$err})*) + $(-> $ret)* { + assert_eq!( + core::mem::size_of::(), + 0, + "Handler type must be zero-sized" + ); + let f: T = unsafe { core::mem::transmute_copy::<(), T>(&()) }; + $crate::serial_println!("calling interrupt handler {} with {:?}", IDX, core::any::type_name::()); + f(frame, IDX, interrupt_error_param!{err: $($err)*}); + $crate::serial_println!("returning from interrupt handler {} with {:?}", IDX, core::any::type_name::()); + interrupt_return_expr!{$($ret)*} + } + + fn get_handler_addr(&self) -> u64 { + Self::handler as *const () as u64 + } + } + + impl $trait_name for InterruptHandlerWrapper + where + T: Fn(InterruptStackFrame, u8, Option), + { + } + }; +} + +impl_interrupt_handler_trait!(trait InterruptHandlerTrait(InterruptStackFrame)); +impl_interrupt_handler_trait!(trait DivergingHandlerTrait(InterruptStackFrame) -> !); +impl_interrupt_handler_trait!(trait InterruptWithErrorCodeHandlerTrait(InterruptStackFrame, u64)); +impl_interrupt_handler_trait!(trait PageFaultHandlerTrait(InterruptStackFrame, PageFaultErrorCode)); +impl_interrupt_handler_trait!(trait DoubleFaultHandlerTrait(InterruptStackFrame, u64) -> !); + +fn my_handler(frame: InterruptStackFrame, index: u8) { + // Handle the interrupt here + crate::serial_println!("Interrupt {} occurred!", index); +} + +// fn asdf() { +// let wrapper = InterruptHandlerWrapper::<>(PhantomData); +// let handler = InterruptHandlerTrait::<32, _>::get_handler(&wrapper); +// } + +#[cfg_attr(test, test_case)] +fn test_interrupt_handler_trait() { + let mut idt = InterruptDescriptorTable::new_empty(); + idt.set_interrupt::<3, _>(my_handler_a); + let addr_a = unsafe { idt.as_type_erased() }[3].handler_address(); + + serial_println!("idt: {:#?}", idt); + + unsafe { + idt.load(); + } + + super::instructions::int3(); + + serial_println!("back from int3"); + + idt.set_interrupt::<3, _>(my_handler_b); + let addr_b = unsafe { idt.as_type_erased() }[3].handler_address(); + assert_ne!(addr_a, addr_b, "Handler addresses should be different"); + + unsafe { + idt.load(); + } + + super::instructions::int3(); + + idt.set_interrupt::<3, _>(|frame, index, error_code| { + crate::serial_println!("handler c!"); + }); + + unsafe { + idt.load(); + } + + super::instructions::int3(); +} diff --git a/kernel/src/x86_64/instructions.rs b/kernel/src/x86_64/instructions.rs new file mode 100644 index 0000000..b4d8b13 --- /dev/null +++ b/kernel/src/x86_64/instructions.rs @@ -0,0 +1,45 @@ +#[inline] +pub fn hlt() { + unsafe { + core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); + } +} + +#[inline] +pub unsafe fn lidt(idt: &super::idt::InterruptDescriptorTablePointer) { + unsafe { + core::arch::asm!( + "lidt [{}]", + in(reg) idt, + options(readonly, nostack, preserves_flags) + ); + } +} + +#[inline] +pub fn int3() { + unsafe { + core::arch::asm!("int3", options(nomem, nostack, preserves_flags)); + } +} + +macro_rules! read_segment { + ($segment:literal) => { + { + let value: u16; + unsafe { + core::arch::asm!( + concat!("mov {0:x}, ", $segment), + out(reg) value, + options(nomem, nostack, preserves_flags) + ); + } + value + } + } +} + +#[inline] +pub unsafe fn get_cs() -> u16 { + read_segment!("cs") +}