refactor mega-commit.
- reorganised the entire project so that the entire kernel is a single codebase rather than a kernel and a libk.
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
pub mod model_specific_registers {
|
||||
use core::arch::x86_64::__cpuid;
|
||||
use spin::Lazy;
|
||||
use x86_64::registers::model_specific::Msr;
|
||||
|
||||
const CPUID_FLAG_MSR: u32 = 1 << 5;
|
||||
static EDX: Lazy<u32> = Lazy::new(|| unsafe { __cpuid(1).edx });
|
||||
|
||||
pub fn cpu_has_msr() -> bool {
|
||||
*EDX & CPUID_FLAG_MSR != 0
|
||||
}
|
||||
|
||||
pub fn cpu_get_msr(msr: u32, value: &mut u64) {
|
||||
let msr = Msr::new(msr);
|
||||
unsafe {
|
||||
*value = msr.read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpu_set_msr(msr: u32, value: u64) {
|
||||
let mut msr = Msr::new(msr);
|
||||
unsafe {
|
||||
msr.write(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
use core::arch::x86_64::__cpuid;
|
||||
|
||||
use libk::drivers::memory::{FoundryOSFrameAllocator, FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
|
||||
use spin::Lazy;
|
||||
use crate::arch::x86_64::memory::memory_map::PHYSICAL_MEMORY_OFFSET;
|
||||
use crate::serial_print;
|
||||
use x86_64::{
|
||||
PhysAddr, VirtAddr,
|
||||
instructions::port::Port,
|
||||
registers::model_specific::Msr,
|
||||
structures::paging::{
|
||||
FrameAllocator, Mapper, Page, PageTableFlags, PhysFrame, Size4KiB, Translate,
|
||||
},
|
||||
structures::paging::{Mapper, Page, PageTableFlags, PhysFrame, Size4KiB},
|
||||
};
|
||||
|
||||
use crate::serial_print;
|
||||
|
||||
use super::{cpu::model_specific_registers::*, memmap::PHYSICAL_MEMORY_OFFSET};
|
||||
use crate::arch::x86_64::cpu::msr::*;
|
||||
use crate::arch::x86_64::memory::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
|
||||
|
||||
const IA32_APIC_BASE_MSR: u32 = 0x1b;
|
||||
const IA32_APIC_BASE_MSR_BSP: u64 = 0x100;
|
||||
@@ -68,7 +63,8 @@ pub fn check_apic() -> bool {
|
||||
#[inline(always)]
|
||||
unsafe fn phys_to_virt(phys: PhysAddr) -> VirtAddr {
|
||||
let phys = phys.as_u64();
|
||||
phys.checked_add(*PHYSICAL_MEMORY_OFFSET).map_or_else(|| panic!(" overflow"), VirtAddr::new)
|
||||
phys.checked_add(*PHYSICAL_MEMORY_OFFSET)
|
||||
.map_or_else(|| panic!(" overflow"), VirtAddr::new)
|
||||
}
|
||||
|
||||
pub fn enable_apic() {
|
||||
@@ -92,7 +88,7 @@ pub fn enable_apic() {
|
||||
}
|
||||
|
||||
// // FIXME: this causes a page fault
|
||||
// // TODO: map to virtual memor
|
||||
// // TODO: map to virtual memory
|
||||
|
||||
let reg = read_apic_register(&apic_virt, 0xF0);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod apic;
|
||||
mod msr;
|
||||
pub mod pic;
|
||||
@@ -0,0 +1,24 @@
|
||||
use core::arch::x86_64::__cpuid;
|
||||
use spin::Lazy;
|
||||
use x86_64::registers::model_specific::Msr;
|
||||
|
||||
const CPUID_FLAG_MSR: u32 = 1 << 5;
|
||||
static EDX: Lazy<u32> = Lazy::new(|| unsafe { __cpuid(1).edx });
|
||||
|
||||
pub fn cpu_has_msr() -> bool {
|
||||
*EDX & CPUID_FLAG_MSR != 0
|
||||
}
|
||||
|
||||
pub fn cpu_get_msr(msr: u32, value: &mut u64) {
|
||||
let msr = Msr::new(msr);
|
||||
unsafe {
|
||||
*value = msr.read();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpu_set_msr(msr: u32, value: u64) {
|
||||
let mut msr = Msr::new(msr);
|
||||
unsafe {
|
||||
msr.write(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
use x86_64::instructions::port::Port;
|
||||
|
||||
const CMD_INIT: u8 = 0x11;
|
||||
const CMD_END_OF_INT: u8 = 0x20;
|
||||
const MODE_8086: u8 = 0x01;
|
||||
|
||||
struct Pic {
|
||||
offset: u8,
|
||||
data: Port<u8>,
|
||||
command: Port<u8>,
|
||||
}
|
||||
|
||||
impl Pic {
|
||||
/// Are we in charge of handling the specified interrupt?
|
||||
/// (Each PIC handles 8 interrupts.)
|
||||
const fn handles_interrupt(&self, interrupt_id: u8) -> bool {
|
||||
self.offset <= interrupt_id && interrupt_id < self.offset + 8
|
||||
}
|
||||
|
||||
/// Notify us that an interrupt has been handled and that we're ready
|
||||
/// for more.
|
||||
unsafe fn end_of_interrupt(&mut self) {
|
||||
unsafe { self.command.write(CMD_END_OF_INT) };
|
||||
}
|
||||
|
||||
/// Reads the interrupt mask of this PIC.
|
||||
unsafe fn read_mask(&mut self) -> u8 {
|
||||
unsafe { self.data.read() }
|
||||
}
|
||||
|
||||
/// Writes the interrupt mask of this PIC.
|
||||
unsafe fn write_mask(&mut self, mask: u8) {
|
||||
unsafe { self.data.write(mask) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of chained PICs. This is the standard setup on x86.
|
||||
pub struct ChainedPics {
|
||||
pics: [Pic; 2],
|
||||
}
|
||||
|
||||
impl ChainedPics {
|
||||
/// Construct a new `ChainedPics` given the base offsets of the two PICs.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it requires you to pass valid offsets.
|
||||
/// If you pass offsets that are out of range, or if you pass offsets that
|
||||
/// conflict with one another, the resulting object will be useless and
|
||||
/// will likely cause problems for your system.
|
||||
pub const unsafe fn new(offset1: u8, offset2: u8) -> Self {
|
||||
ChainedPics {
|
||||
pics: [
|
||||
Pic {
|
||||
offset: offset1,
|
||||
command: Port::new(0x20),
|
||||
data: Port::new(0x21),
|
||||
},
|
||||
Pic {
|
||||
offset: offset2,
|
||||
command: Port::new(0xA0),
|
||||
data: Port::new(0xA1),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// .
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// .
|
||||
pub const unsafe fn new_contiguous(primary_offset: u8) -> Self {
|
||||
unsafe { Self::new(primary_offset, primary_offset + 8) }
|
||||
}
|
||||
|
||||
/// Returns a new instance of [`ChainedPics`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// this is unsafe because it required writing to CPU I/O ports.
|
||||
pub unsafe fn initialize(&mut self) {
|
||||
unsafe {
|
||||
let mut wait_port: Port<u8> = Port::new(0x80);
|
||||
let mut wait = || wait_port.write(0);
|
||||
|
||||
// Save our original interrupt masks, because I'm too lazy to
|
||||
// figure out reasonable values. We'll restore these when we're
|
||||
// done.
|
||||
let saved_masks = self.read_masks();
|
||||
|
||||
// Tell each PIC that we're going to send it a three-byte
|
||||
// initialization sequence on its data port.
|
||||
self.pics[0].command.write(CMD_INIT);
|
||||
wait();
|
||||
self.pics[1].command.write(CMD_INIT);
|
||||
wait();
|
||||
|
||||
// Byte 1: Set up our base offsets.
|
||||
self.pics[0].data.write(self.pics[0].offset);
|
||||
wait();
|
||||
self.pics[1].data.write(self.pics[1].offset);
|
||||
wait();
|
||||
|
||||
// Byte 2: Configure chaining between PIC1 and PIC2.
|
||||
self.pics[0].data.write(4);
|
||||
wait();
|
||||
self.pics[1].data.write(2);
|
||||
wait();
|
||||
|
||||
// Byte 3: Set our mode.
|
||||
self.pics[0].data.write(MODE_8086);
|
||||
wait();
|
||||
self.pics[1].data.write(MODE_8086);
|
||||
wait();
|
||||
|
||||
// Restore our saved masks.
|
||||
self.write_masks(saved_masks[0], saved_masks[1])
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the interrupt masks of both PICs.
|
||||
pub unsafe fn read_masks(&mut self) -> [u8; 2] {
|
||||
unsafe { [self.pics[0].read_mask(), self.pics[1].read_mask()] }
|
||||
}
|
||||
|
||||
/// Writes the interrupt masks of both PICs.
|
||||
pub unsafe fn write_masks(&mut self, mask1: u8, mask2: u8) {
|
||||
unsafe {
|
||||
self.pics[0].write_mask(mask1);
|
||||
self.pics[1].write_mask(mask2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables both PICs by masking all interrupts.
|
||||
pub unsafe fn disable(&mut self) {
|
||||
unsafe { self.write_masks(u8::MAX, u8::MAX) }
|
||||
}
|
||||
|
||||
/// Do we handle this interrupt?
|
||||
pub fn handles_interrupt(&self, interrupt_id: u8) -> bool {
|
||||
self.pics.iter().any(|p| p.handles_interrupt(interrupt_id))
|
||||
}
|
||||
|
||||
/// Figure out which (if any) PICs in our chain need to know about this
|
||||
/// interrupt. This is tricky, because all interrupts from `pics[1]`
|
||||
/// get chained through `pics[0]`.
|
||||
pub unsafe fn notify_end_of_interrupt(&mut self, interrupt_id: u8) {
|
||||
if self.handles_interrupt(interrupt_id) {
|
||||
if self.pics[1].handles_interrupt(interrupt_id) {
|
||||
unsafe {
|
||||
self.pics[1].end_of_interrupt();
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
self.pics[0].end_of_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
use core::fmt;
|
||||
use spin::{Lazy, Mutex};
|
||||
use x86_64::instructions::interrupts;
|
||||
|
||||
use crate::arch::x86_64::drivers::framebuffer::{colour::Colour, display::FRAMEBUFFER_WRITER};
|
||||
|
||||
use crate::resources::font::{FONT_SPLEEN_8X16, Font};
|
||||
|
||||
static FONT_WIDTH: u32 = 8;
|
||||
static FONT_HEIGHT: u32 = 16;
|
||||
|
||||
pub static WRITER: Lazy<Mutex<Writer>> = Lazy::new(|| Mutex::new(Writer::new()));
|
||||
|
||||
pub fn screensize_chars() -> (u32, u32) {
|
||||
let writer = WRITER.lock();
|
||||
(writer.screen_width, writer.screen_height)
|
||||
}
|
||||
|
||||
pub struct Writer {
|
||||
font: &'static Font,
|
||||
/// Measured in chars not pixels.
|
||||
screen_width: u32,
|
||||
/// Measured in chars not pixels.
|
||||
screen_height: u32,
|
||||
/// 16 pixels tall.
|
||||
text_line: u32,
|
||||
/// 8 pixels wide.
|
||||
text_col: u32,
|
||||
|
||||
fg_color: Colour,
|
||||
bg_color: Colour,
|
||||
}
|
||||
|
||||
impl Default for Writer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
pub fn new() -> Self {
|
||||
FRAMEBUFFER_WRITER.lock().as_mut().map_or_else(
|
||||
|| {
|
||||
panic!("Framebuffer writer not initialized.");
|
||||
},
|
||||
|writer| Self {
|
||||
font: &FONT_SPLEEN_8X16,
|
||||
screen_width: writer.width() / 8,
|
||||
screen_height: writer.height() / 16,
|
||||
text_line: 0,
|
||||
text_col: 0,
|
||||
fg_color: Colour::White,
|
||||
bg_color: Colour::Black,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn set_font(&mut self, font: &'static Font) {
|
||||
self.font = font;
|
||||
}
|
||||
|
||||
/// This is sent when the user types a backspace.
|
||||
const BACKSPACE: u8 = 8;
|
||||
|
||||
pub fn write_glyph(&mut self, c: u8) {
|
||||
if c == b'\n' {
|
||||
self.newline();
|
||||
return;
|
||||
} else if c == Self::BACKSPACE {
|
||||
self.backspace();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the character data from the font array. -- each byte is a row of pixels
|
||||
let data: &[u8] = self.font.glyph_for(c as u16);
|
||||
|
||||
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
|
||||
for (row, line) in data.iter().enumerate().take(16) {
|
||||
for col in 0..8 {
|
||||
let pixel_x: u32 = self.text_col * FONT_WIDTH + col;
|
||||
let pixel_y: u32 = self.text_line * FONT_HEIGHT + row as u32;
|
||||
|
||||
if line & (0x80 >> col) != 0 {
|
||||
// Write the foreground color
|
||||
writer.write_pixel(pixel_x as usize, pixel_y as usize, self.fg_color);
|
||||
} else {
|
||||
// Write the background color
|
||||
writer.write_pixel(pixel_x as usize, pixel_y as usize, self.bg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go to next position
|
||||
if self.text_col + 1 >= self.screen_width {
|
||||
self.newline();
|
||||
} else {
|
||||
self.text_col += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn dimensions(&self) -> (u32, u32) {
|
||||
(self.screen_width, self.screen_height)
|
||||
}
|
||||
|
||||
pub const fn next_char(&mut self) {
|
||||
self.text_col += 1;
|
||||
}
|
||||
|
||||
pub const fn newline(&mut self) {
|
||||
self.text_col = 0;
|
||||
|
||||
if self.text_line + 1 >= self.screen_height {
|
||||
self.text_line = 0;
|
||||
} else {
|
||||
self.text_line += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the backspace character. TODO: Implement VT-100 style terminal control
|
||||
/// codes alongside a shell. Not simple.
|
||||
pub fn backspace(&mut self) {
|
||||
if self.text_col > 0 {
|
||||
self.text_col -= 1;
|
||||
// Blank out the previous char.
|
||||
self.write_glyph(b' ');
|
||||
self.text_col -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_string(&mut self, s: &str) {
|
||||
for c in s.chars() {
|
||||
self.write_glyph(c as u8);
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn set_colour(&mut self, fg: Colour, bg: Colour) {
|
||||
self.fg_color = fg;
|
||||
self.bg_color = bg;
|
||||
}
|
||||
|
||||
pub const fn reset_colour(&mut self) {
|
||||
self.fg_color = Colour::White;
|
||||
self.bg_color = Colour::Black;
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
self.write_string(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(args: fmt::Arguments, fg: Colour, bg: Colour) {
|
||||
use core::fmt::Write;
|
||||
|
||||
interrupts::without_interrupts(|| {
|
||||
let mut writer = WRITER.lock();
|
||||
writer.set_colour(fg, bg);
|
||||
writer.write_fmt(args).unwrap();
|
||||
writer.reset_colour();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn _print(args: fmt::Arguments) {
|
||||
interrupts::without_interrupts(|| {
|
||||
write(args, Colour::White, Colour::Black);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn _print_err(args: fmt::Arguments) {
|
||||
interrupts::without_interrupts(|| {
|
||||
write(args, Colour::Red, Colour::Black);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn _print_log(args: fmt::Arguments) {
|
||||
interrupts::without_interrupts(|| {
|
||||
write(args, Colour::Yellow, Colour::Black);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear_screen() {
|
||||
interrupts::without_interrupts(|| {
|
||||
let mut writer = WRITER.lock();
|
||||
writer.text_line = 0;
|
||||
writer.text_col = 0;
|
||||
|
||||
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
|
||||
writer.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn reset_cursor() {
|
||||
interrupts::without_interrupts(|| {
|
||||
let mut writer = WRITER.lock();
|
||||
writer.text_line = 0;
|
||||
writer.text_col = 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println_log {
|
||||
() => ($crate::print_log!("\n"));
|
||||
($($arg:tt)*) => ($crate::print_log!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print_log {
|
||||
($($arg:tt)*) => ($crate::prelude::_print_log(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => ($crate::print!("\n"));
|
||||
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ($crate::prelude::_print(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! printlnerr {
|
||||
() => ($crate::printerr!("\n"));
|
||||
($($arg:tt)*) => ($crate::printerr!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! printerr {
|
||||
($($arg:tt)*) => ($crate::prelude::_print_err(format_args!($($arg)*)));
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Colour {
|
||||
ARGB(u8, u8, u8, u8),
|
||||
RGB(u8, u8, u8),
|
||||
HexARGB(u32),
|
||||
Black = 0x000000FF,
|
||||
Blue = 0x0000FFFF,
|
||||
Green = 0x00FF00FF,
|
||||
Cyan = 0x00FFFFFF,
|
||||
Red = 0xFF0000FF,
|
||||
Magenta = 0xFF00FFFF,
|
||||
Yellow = 0xFFFF00FF,
|
||||
White = 0xFFFFFFFF,
|
||||
}
|
||||
|
||||
#[allow(clippy::use_self)]
|
||||
impl From<Colour> for u32 {
|
||||
fn from(val: Colour) -> Self {
|
||||
match val {
|
||||
Colour::ARGB(a, r, g, b) => {
|
||||
(a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32)
|
||||
}
|
||||
Colour::RGB(r, g, b) => ((r as u32) << 16) | (g as u32) << 8 | (b as u32),
|
||||
Colour::HexARGB(hex) => hex,
|
||||
Colour::Black => 0xFF000000,
|
||||
Colour::Blue => 0xFF0000FF,
|
||||
Colour::Green => 0xFF00FF00,
|
||||
Colour::Cyan => 0xFF00FFFF,
|
||||
Colour::Red => 0xFFFF0000,
|
||||
Colour::Magenta => 0xFFFF00FF,
|
||||
Colour::Yellow => 0xFFFFFF00,
|
||||
Colour::White => 0xFFFFFFFF,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Colour {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::ARGB(r, g, b, a) => write!(f, "RGBA(#{:x}{:x}{:x}{:x})", r, g, b, a),
|
||||
Self::RGB(r, g, b) => write!(f, "RGB(#{:x}{:x}{:x})", r, g, b),
|
||||
Self::HexARGB(hex) => write!(f, "Hex(#{:x})", hex),
|
||||
Self::Black => write!(f, "Black"),
|
||||
Self::Blue => write!(f, "Blue"),
|
||||
Self::Green => write!(f, "Green"),
|
||||
Self::Cyan => write!(f, "Cyan"),
|
||||
Self::Red => write!(f, "Red"),
|
||||
Self::Magenta => write!(f, "Magenta"),
|
||||
Self::Yellow => write!(f, "Yellow"),
|
||||
Self::White => write!(f, "White"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use limine::request::FramebufferRequest;
|
||||
|
||||
#[used]
|
||||
#[unsafe(link_section = ".requests")]
|
||||
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
|
||||
|
||||
use super::colour::Colour;
|
||||
use core::panic;
|
||||
|
||||
use limine::framebuffer::Framebuffer;
|
||||
use spin::{Lazy, Mutex};
|
||||
|
||||
pub static FRAMEBUFFER_WRITER: Lazy<Mutex<Option<FramebufferWriter>>> = Lazy::new(|| {
|
||||
Mutex::new(FRAMEBUFFER_REQUEST.get_response().map_or_else(
|
||||
|| {
|
||||
panic!("Framebuffer request failed");
|
||||
},
|
||||
|framebuffer_response| {
|
||||
let framebuffer = framebuffer_response.framebuffers().next().unwrap();
|
||||
Some(FramebufferWriter::new(framebuffer))
|
||||
},
|
||||
))
|
||||
});
|
||||
|
||||
/// The updated writer stores necessary fields from the [Framebuffer].
|
||||
/// This ensures that the contained types are Send, as Framebuffer was
|
||||
/// not marked as Send.
|
||||
///
|
||||
/// It also avoids the requirement for lifetimes.
|
||||
///
|
||||
/// Note this does not implement Writer as these functions only handle drawing pixels.
|
||||
pub struct FramebufferWriter {
|
||||
pitch: u64,
|
||||
bpp: u16,
|
||||
addr: *mut u8,
|
||||
width: u64,
|
||||
height: u64,
|
||||
}
|
||||
|
||||
unsafe impl Send for FramebufferWriter {}
|
||||
unsafe impl Sync for FramebufferWriter {}
|
||||
|
||||
impl FramebufferWriter {
|
||||
pub fn new(framebuffer: Framebuffer) -> Self {
|
||||
Self {
|
||||
pitch: framebuffer.pitch(),
|
||||
bpp: framebuffer.bpp(),
|
||||
addr: framebuffer.addr(),
|
||||
width: framebuffer.width(),
|
||||
height: framebuffer.height(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_pixel(&self, x: usize, y: usize, color: Colour) {
|
||||
let pitch = self.pitch as usize;
|
||||
let bpp = (self.bpp / 8) as usize;
|
||||
let pixel_offset = y * pitch + x * bpp;
|
||||
|
||||
unsafe {
|
||||
*(self.addr.add(pixel_offset) as *mut u32) = color.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_frame(&self, buffer: &[&[Colour]]) {
|
||||
// TODO: this should return errors
|
||||
for (y, &row) in buffer.iter().enumerate() {
|
||||
if y >= self.height() as usize {
|
||||
break;
|
||||
}
|
||||
for (x, pixel) in row.iter().enumerate() {
|
||||
if x >= self.width() as usize {
|
||||
break;
|
||||
}
|
||||
self.write_pixel(x, y, *pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn width(&self) -> u32 {
|
||||
self.width as u32
|
||||
}
|
||||
|
||||
pub const fn height(&self) -> u32 {
|
||||
self.height as u32
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let width = self.width as usize;
|
||||
let height = self.height as usize;
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
self.write_pixel(x, y, Colour::Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screensize_px() -> (u32, u32) {
|
||||
FRAMEBUFFER_WRITER
|
||||
.lock()
|
||||
.as_mut()
|
||||
.map_or_else(|| (0, 0), |writer| (writer.width(), writer.height()))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod colour;
|
||||
pub mod display;
|
||||
@@ -0,0 +1,202 @@
|
||||
use core::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::println;
|
||||
use crossbeam::queue::ArrayQueue;
|
||||
use futures_util::{Stream, StreamExt, task::AtomicWaker};
|
||||
use pc_keyboard::{DecodedKey, HandleControl, KeyCode, Keyboard, ScancodeSet1, layouts::Uk105Key};
|
||||
use spin::{Lazy, Mutex, Once};
|
||||
|
||||
static KBD_QUEUE: Once<ArrayQueue<u8>> = Once::new();
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
pub static KEYBOARD: Lazy<Mutex<Keyboard<Uk105Key, ScancodeSet1>>> = Lazy::new(|| {
|
||||
Mutex::new(Keyboard::new(
|
||||
ScancodeSet1::new(),
|
||||
// TODO: Expose an API to change the default KB layout.
|
||||
Uk105Key,
|
||||
HandleControl::Ignore,
|
||||
))
|
||||
});
|
||||
pub static SCANCODE_STREAM: Lazy<Mutex<ScancodeStream>> =
|
||||
Lazy::new(|| Mutex::new(ScancodeStream::new()));
|
||||
|
||||
pub fn add_scancode(scancode: u8) {
|
||||
if let Some(queue) = KBD_QUEUE.get() {
|
||||
if queue.push(scancode).is_err() {
|
||||
println!("WARNING: scancode queue full; dropping keyboard input");
|
||||
} else {
|
||||
WAKER.wake();
|
||||
}
|
||||
} else {
|
||||
println!("WARNING: scancode queue not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScancodeStream {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl ScancodeStream {
|
||||
pub fn new() -> Self {
|
||||
KBD_QUEUE.call_once(|| ArrayQueue::new(5));
|
||||
Self { _private: () }
|
||||
}
|
||||
|
||||
pub fn try_next(&mut self) -> Option<u8> {
|
||||
KBD_QUEUE.get().and_then(|queue| queue.pop())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ScancodeStream {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ScancodeStream {
|
||||
type Item = u8;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let queue = KBD_QUEUE.get().unwrap();
|
||||
|
||||
if let Some(scancode) = queue.pop() {
|
||||
return Poll::Ready(Some(scancode));
|
||||
}
|
||||
|
||||
WAKER.register(cx.waker());
|
||||
WAKER.register(cx.waker());
|
||||
|
||||
queue.pop().map_or(Poll::Pending, |scancode| {
|
||||
WAKER.take();
|
||||
Poll::Ready(Some(scancode))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_keystroke_async() -> KeyStroke {
|
||||
loop {
|
||||
if let Some(scancode) = SCANCODE_STREAM.lock().next().await {
|
||||
if let Ok(keystroke) = KeyStroke::try_from(scancode) {
|
||||
return keystroke;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_keystroke_optional() -> Option<KeyStroke> {
|
||||
if let Some(scancode) = SCANCODE_STREAM.lock().try_next() {
|
||||
if let Ok(keystroke) = KeyStroke::try_from(scancode) {
|
||||
return Some(keystroke);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum KeyStroke {
|
||||
Char(char),
|
||||
Ctrl,
|
||||
RCtrl,
|
||||
Alt,
|
||||
RAlt,
|
||||
Shift,
|
||||
RShift,
|
||||
Meta,
|
||||
RMeta,
|
||||
Backspace,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
None,
|
||||
Enter,
|
||||
Escape,
|
||||
Del,
|
||||
}
|
||||
|
||||
impl KeyStroke {
|
||||
pub const fn from_keycode(key: KeyCode) -> Self {
|
||||
match key {
|
||||
KeyCode::LControl => Self::Ctrl,
|
||||
KeyCode::RControl => Self::RCtrl,
|
||||
KeyCode::LAlt => Self::Alt,
|
||||
KeyCode::RAlt2 => Self::RAlt,
|
||||
KeyCode::LShift => Self::Shift,
|
||||
KeyCode::RShift => Self::RShift,
|
||||
KeyCode::LWin => Self::Meta,
|
||||
KeyCode::RWin => Self::RMeta,
|
||||
KeyCode::Backspace => Self::Backspace,
|
||||
KeyCode::ArrowLeft => Self::Left,
|
||||
KeyCode::ArrowRight => Self::Right,
|
||||
KeyCode::ArrowUp => Self::Up,
|
||||
KeyCode::ArrowDown => Self::Down,
|
||||
KeyCode::Return => Self::Enter,
|
||||
KeyCode::Escape => Self::Escape,
|
||||
KeyCode::Delete => Self::Del,
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for KeyStroke {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(code: u8) -> Result<Self, Self::Error> {
|
||||
let mut keyboard = KEYBOARD.lock();
|
||||
|
||||
let key = match keyboard.add_byte(code) {
|
||||
Ok(Some(event)) => match keyboard.process_keyevent(event) {
|
||||
Some(key) => key,
|
||||
_ => return Err(()),
|
||||
},
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
match key {
|
||||
DecodedKey::Unicode(ch) => Ok(Self::Char(ch)),
|
||||
DecodedKey::RawKey(key) => match Self::from_keycode(key) {
|
||||
Self::None => Err(()),
|
||||
key => Ok(key),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<char> for KeyStroke {
|
||||
type Error = ();
|
||||
|
||||
fn try_into(self) -> Result<char, Self::Error> {
|
||||
match self {
|
||||
Self::Char(c) => Ok(c),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for KeyStroke {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Char(c) => write!(f, "{}", c),
|
||||
Self::Ctrl => write!(f, "CTRL"),
|
||||
Self::RCtrl => write!(f, "RCtrl"),
|
||||
Self::Alt => write!(f, "ALT"),
|
||||
Self::RAlt => write!(f, "RAlt"),
|
||||
Self::Shift => write!(f, "SHIFT"),
|
||||
Self::RShift => write!(f, "RShift"),
|
||||
Self::Meta => write!(f, "META"),
|
||||
Self::RMeta => write!(f, "RMeta"),
|
||||
Self::Backspace => write!(f, "BACKSPACE"),
|
||||
Self::Left => write!(f, "LEFT"),
|
||||
Self::Right => write!(f, "RIGHT"),
|
||||
Self::Up => write!(f, "UP"),
|
||||
Self::Down => write!(f, "DOWN"),
|
||||
Self::Enter => write!(f, "ENTER"),
|
||||
Self::Escape => write!(f, "ESCAPE"),
|
||||
Self::None => write!(f, "NONE"),
|
||||
Self::Del => write!(f, "DEL"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod ascii;
|
||||
pub mod framebuffer;
|
||||
pub mod keyboard;
|
||||
pub mod port;
|
||||
pub mod serial;
|
||||
@@ -0,0 +1,29 @@
|
||||
//! Functions for IO using ports.
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
#[inline]
|
||||
pub fn inb(port: u16) -> u8 {
|
||||
let value: u8;
|
||||
unsafe {
|
||||
asm!(
|
||||
"in al, dx",
|
||||
out("al") value,
|
||||
in("dx") port,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
);
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outb(port: u16, value: u8) {
|
||||
unsafe {
|
||||
asm!(
|
||||
"out dx, al",
|
||||
in("dx") port,
|
||||
in("al") value,
|
||||
options(nomem, nostack, preserves_flags)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
use core::{
|
||||
fmt,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use spin::{Lazy, Mutex};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serial_print {
|
||||
($($arg:tt)*) => ($crate::_serial_write(format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serial_println {
|
||||
() => ($crate::serial_print!("\n"));
|
||||
($($arg:tt)*) => (serial_print!("{}\n", format_args!($($arg)*)));
|
||||
}
|
||||
|
||||
use crate::arch::x86_64::drivers::port::{inb, outb};
|
||||
|
||||
use x86_64::instructions::interrupts;
|
||||
|
||||
pub fn _serial_write(args: fmt::Arguments) {
|
||||
use core::fmt::Write;
|
||||
|
||||
interrupts::without_interrupts(|| {
|
||||
if let Some(writer) = WRITER.lock().as_mut() {
|
||||
writer.write_fmt(args).unwrap();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serial_read() -> &'static str {
|
||||
serial_println!("getting value!");
|
||||
|
||||
interrupts::without_interrupts(|| {
|
||||
if let Some(reader) = READER.lock().as_mut() {
|
||||
serial_println!("stuff happnin.");
|
||||
reader.read_str_to_buffer();
|
||||
} else {
|
||||
serial_println!("failed to get writer");
|
||||
}
|
||||
});
|
||||
|
||||
serial_println!("eee");
|
||||
|
||||
let i = BUFFER_LEN.load(Ordering::SeqCst);
|
||||
|
||||
unsafe {
|
||||
if i != 0 {
|
||||
core::str::from_utf8(&BUFFER[..i - 1]).unwrap()
|
||||
} else {
|
||||
serial_println!("empty string");
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PORT: u16 = 0x3f8;
|
||||
static mut BUFFER: [u8; 256] = [0; 256];
|
||||
static BUFFER_LEN: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
static READER: Lazy<Mutex<Option<Reader>>> = Lazy::new(|| Mutex::new(None));
|
||||
static WRITER: Lazy<Mutex<Option<Writer>>> = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
struct Reader;
|
||||
|
||||
struct Writer;
|
||||
|
||||
impl fmt::Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for c in s.chars() {
|
||||
self.write_byte(c as u8);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
unsafe fn write_success(&self) -> bool {
|
||||
inb(PORT + 5) & 0x20 != 0
|
||||
}
|
||||
|
||||
pub fn write_byte(&self, data: u8) {
|
||||
unsafe {
|
||||
while !self.write_success() {}
|
||||
outb(PORT, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> Result<(), &'static str> {
|
||||
test()?;
|
||||
|
||||
if READER.lock().is_none() {
|
||||
*READER.lock() = Some(Reader);
|
||||
}
|
||||
|
||||
if WRITER.lock().is_none() {
|
||||
*WRITER.lock() = Some(Writer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test() -> Result<(), &'static str> {
|
||||
outb(PORT + 1, 0x00); // Disable all interrupts
|
||||
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
|
||||
outb(PORT, 0x03); // Set divisor to 3 (lo byte) 38400 baud
|
||||
outb(PORT + 1, 0x00); // (hi byte)
|
||||
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
|
||||
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-bytethreshold
|
||||
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
|
||||
outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
|
||||
outb(PORT, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)
|
||||
|
||||
if inb(PORT) != 0xAE {
|
||||
return Err("serial test failed");
|
||||
}
|
||||
|
||||
outb(PORT + 4, 0x0F);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Reader {
|
||||
pub fn read_str_to_buffer(&mut self) {
|
||||
unsafe {
|
||||
while !self.read_ready() {}
|
||||
|
||||
BUFFER_LEN.store(0, Ordering::SeqCst);
|
||||
|
||||
while BUFFER_LEN.load(Ordering::SeqCst) < 256 {
|
||||
let c = self.read();
|
||||
BUFFER[BUFFER_LEN.load(Ordering::SeqCst)] = c;
|
||||
if c as char == '\r' {
|
||||
break;
|
||||
}
|
||||
BUFFER_LEN.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
serial_println!("returning")
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_ready(&self) -> bool {
|
||||
inb(PORT + 5) & 1 != 0
|
||||
}
|
||||
|
||||
pub fn read(&self) -> u8 {
|
||||
unsafe {
|
||||
while !self.read_ready() {}
|
||||
inb(PORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
use x86_64::{
|
||||
VirtAddr,
|
||||
instructions::tables::load_tss,
|
||||
registers::segmentation::{Segment, CS, DS, ES, SS},
|
||||
registers::segmentation::{CS, DS, ES, SS, Segment},
|
||||
structures::{
|
||||
gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector},
|
||||
tss::TaskStateSegment,
|
||||
},
|
||||
VirtAddr,
|
||||
};
|
||||
|
||||
use spin::Lazy;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use libk::drivers::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
|
||||
use libk::prelude::*;
|
||||
use crate::serial_print;
|
||||
use pic8259::ChainedPics;
|
||||
use x86_64::registers::control::Cr2;
|
||||
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};
|
||||
use x86_64::structures::paging::mapper::MapperFlushAll;
|
||||
use x86_64::structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB};
|
||||
|
||||
use spin::{Lazy, Mutex};
|
||||
|
||||
use super::gdt;
|
||||
use crate::arch::x86_64::memory::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
|
||||
use crate::{println_log, serial_println};
|
||||
use spin::{Lazy, Mutex};
|
||||
|
||||
static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
|
||||
let mut idt = InterruptDescriptorTable::new();
|
||||
@@ -91,7 +91,7 @@ extern "x86-interrupt" fn double_fault_handler(
|
||||
}
|
||||
|
||||
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
||||
use pc_keyboard::{layouts, HandleControl, Keyboard, ScancodeSet1};
|
||||
use pc_keyboard::{HandleControl, Keyboard, ScancodeSet1, layouts};
|
||||
// use pc_keyboard::DecodedKey;
|
||||
use spin::Mutex;
|
||||
use x86_64::instructions::port::Port;
|
||||
@@ -108,7 +108,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac
|
||||
let mut port = Port::new(0x60);
|
||||
|
||||
let scancode: u8 = unsafe { port.read() };
|
||||
libk::drivers::io::keyboard::add_scancode(scancode);
|
||||
crate::arch::x86_64::drivers::keyboard::add_scancode(scancode);
|
||||
|
||||
unsafe {
|
||||
PICS.lock()
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
use core::ptr;
|
||||
use spin::{Mutex, MutexGuard};
|
||||
use x86_64::structures::paging::{Size4KiB, mapper::MapToError};
|
||||
|
||||
/// We are currently using a linked list heap allocator which uses our underlying page allocator.
|
||||
#[global_allocator]
|
||||
/// This is now Rust's global allocator, so we can use stuff requiring heap allocations.
|
||||
static ALLOCATOR: Locked<FoundryAllocator> = Locked::new(FoundryAllocator::new());
|
||||
|
||||
pub const HEAP_START: usize = 0x4444_4444_0000;
|
||||
pub const HEAP_SIZE: usize = 1024 * 1024 * 1024;
|
||||
|
||||
/// Sets up the heap using the backing page frame allocator.
|
||||
pub unsafe fn init_heap() -> Result<(), MapToError<Size4KiB>> {
|
||||
unsafe {
|
||||
// code to allocate frames is now done in the page fault interrupt handler!
|
||||
ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Locked<T> {
|
||||
inner: Mutex<T>,
|
||||
}
|
||||
|
||||
impl<T> Locked<T> {
|
||||
pub const fn new(inner: T) -> Self {
|
||||
Locked {
|
||||
inner: Mutex::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lock(&self) -> MutexGuard<T> {
|
||||
self.inner.lock()
|
||||
}
|
||||
}
|
||||
|
||||
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||
|
||||
struct ListNode {
|
||||
next: Option<&'static mut ListNode>,
|
||||
}
|
||||
|
||||
pub struct FoundryAllocator {
|
||||
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
|
||||
fallback: Locked<FoundryFallbackAllocator>,
|
||||
}
|
||||
|
||||
impl Default for FoundryAllocator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FoundryAllocator {
|
||||
pub const fn new() -> Self {
|
||||
const EMPTY: Option<&'static mut ListNode> = None;
|
||||
Self {
|
||||
list_heads: [EMPTY; BLOCK_SIZES.len()],
|
||||
fallback: Locked::new(FoundryFallbackAllocator::new()),
|
||||
}
|
||||
}
|
||||
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
|
||||
unsafe {
|
||||
self.fallback.lock().init(heap_start, heap_size);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
||||
unsafe { self.fallback.alloc(layout) }
|
||||
}
|
||||
|
||||
fn block_size(&self, layout: &Layout) -> Option<usize> {
|
||||
let required_block_size = layout.size().max(layout.align());
|
||||
BLOCK_SIZES.iter().position(|&s| s >= required_block_size)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl GlobalAlloc for Locked<FoundryAllocator> {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let mut allocator = self.lock();
|
||||
|
||||
match allocator.block_size(&layout) {
|
||||
Some(index) => {
|
||||
match allocator.list_heads[index].take() {
|
||||
Some(node) => {
|
||||
allocator.list_heads[index] = node.next.take();
|
||||
node as *mut ListNode as *mut u8
|
||||
}
|
||||
None => {
|
||||
// no block exists in list => allocate new block
|
||||
let block_size = BLOCK_SIZES[index];
|
||||
// only works if all block sizes are a power of 2
|
||||
let block_align = block_size;
|
||||
let layout = Layout::from_size_align(block_size, block_align).unwrap();
|
||||
unsafe { allocator.fallback_alloc(layout) }
|
||||
}
|
||||
}
|
||||
}
|
||||
None => unsafe { allocator.fallback_alloc(layout) },
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
let mut allocator = self.lock();
|
||||
match allocator.block_size(&layout) {
|
||||
Some(idx) => {
|
||||
let new_node = ListNode {
|
||||
next: allocator.list_heads[idx].take(),
|
||||
};
|
||||
|
||||
let new_ptr = ptr as *mut ListNode;
|
||||
unsafe { new_ptr.write(new_node) };
|
||||
allocator.list_heads[idx] = Some(unsafe { &mut *new_ptr });
|
||||
}
|
||||
None => {
|
||||
unsafe { allocator.fallback.dealloc(ptr, layout) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FallbackListNode {
|
||||
size: usize,
|
||||
next: Option<&'static mut FallbackListNode>,
|
||||
}
|
||||
|
||||
impl FallbackListNode {
|
||||
const fn new(size: usize) -> Self {
|
||||
Self { size, next: None }
|
||||
}
|
||||
|
||||
fn start_addr(&self) -> usize {
|
||||
self as *const Self as usize
|
||||
}
|
||||
|
||||
fn end_addr(&self) -> usize {
|
||||
self.start_addr() + self.size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FoundryFallbackAllocator {
|
||||
head: FallbackListNode,
|
||||
}
|
||||
|
||||
impl FoundryFallbackAllocator {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
head: FallbackListNode::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
|
||||
unsafe { self.add_region(heap_start, heap_size) };
|
||||
}
|
||||
|
||||
unsafe fn add_region(&mut self, addr: usize, size: usize) {
|
||||
unsafe {
|
||||
let mut node = FallbackListNode::new(size);
|
||||
node.next = self.head.next.take();
|
||||
let node_ptr = addr as *mut FallbackListNode;
|
||||
node_ptr.write(node);
|
||||
self.head.next = Some(&mut *node_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
fn find_region(
|
||||
&mut self,
|
||||
size: usize,
|
||||
align: usize,
|
||||
) -> Option<(&'static mut FallbackListNode, usize)> {
|
||||
let mut current = &mut self.head;
|
||||
// look for a large enough memory region in linked list
|
||||
while let Some(ref mut region) = current.next {
|
||||
if let Ok(alloc_start) = Self::alloc_from_region(®ion, size, align) {
|
||||
// region suitable for allocation -> remove node from list
|
||||
let next = region.next.take();
|
||||
let ret = Some((current.next.take().unwrap(), alloc_start));
|
||||
current.next = next;
|
||||
return ret;
|
||||
} else {
|
||||
// region not suitable -> continue with next region
|
||||
current = current.next.as_mut().unwrap();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
const fn align_up(addr: usize, align: usize) -> usize {
|
||||
(addr + align - 1) & !(align - 1)
|
||||
}
|
||||
|
||||
fn size_align(layout: Layout) -> (usize, usize) {
|
||||
let layout = layout
|
||||
.align_to(align_of::<ListNode>())
|
||||
.expect("adjusting alignment failed")
|
||||
.pad_to_align();
|
||||
let size = layout.size().max(size_of::<ListNode>());
|
||||
(size, layout.align())
|
||||
}
|
||||
|
||||
fn alloc_from_region(
|
||||
region: &FallbackListNode,
|
||||
size: usize,
|
||||
align: usize,
|
||||
) -> Result<usize, ()> {
|
||||
let alloc_start = Self::align_up(region.start_addr(), align);
|
||||
let alloc_end = alloc_start.checked_add(size).ok_or(())?;
|
||||
|
||||
if alloc_end > region.end_addr() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let excess_size = region.end_addr() - alloc_end;
|
||||
if excess_size > 0 && excess_size < size_of::<ListNode>() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(alloc_start)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl GlobalAlloc for Locked<FoundryFallbackAllocator> {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let mut allocator = self.lock();
|
||||
// perform layout adjustments
|
||||
let (size, align) = FoundryFallbackAllocator::size_align(layout);
|
||||
|
||||
if let Some((region, alloc_start)) = allocator.find_region(size, align) {
|
||||
let alloc_end = alloc_start.checked_add(size).expect("overflow");
|
||||
let excess_size = region.end_addr() - alloc_end;
|
||||
if excess_size > 0 {
|
||||
unsafe { allocator.add_region(alloc_end, excess_size) };
|
||||
}
|
||||
alloc_start as *mut u8
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
let mut allocator = self.lock();
|
||||
|
||||
// perform layout adjustments
|
||||
let (size, _) = FoundryFallbackAllocator::size_align(layout);
|
||||
|
||||
unsafe { allocator.add_region(ptr as usize, size) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod allocator;
|
||||
mod foundry_kalloc;
|
||||
@@ -0,0 +1,146 @@
|
||||
// use lib_alloc::allocator::FoundryAllocator;
|
||||
use limine::{memory_map::EntryType, response::MemoryMapResponse};
|
||||
use spin::{Mutex, Once};
|
||||
use x86_64::{
|
||||
PhysAddr, VirtAddr,
|
||||
registers::control::Cr3,
|
||||
structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB},
|
||||
};
|
||||
|
||||
pub static FRAME_ALLOCATOR: Once<Mutex<FoundryOSFrameAllocator>> = Once::new();
|
||||
pub static OFFSET_PAGE_TABLE: Once<Mutex<OffsetPageTable>> = Once::new();
|
||||
/// Returns a mutable reference to the current level 4 page table.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that the level 4 page table is not modified
|
||||
/// simultaneously. The caller must also ensure that the physical memory offset
|
||||
/// is correct, to ensure that the correct virtual address is constructed.
|
||||
unsafe fn active_l4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable {
|
||||
let (level_4_frame, _) = Cr3::read();
|
||||
|
||||
let phys_addr = level_4_frame.start_address();
|
||||
let virt = phys_addr.as_u64() + physical_memory_offset.as_u64();
|
||||
unsafe { &mut *(virt as *mut PageTable) }
|
||||
}
|
||||
|
||||
/// Initializes the `OffsetPageTable` for the current CPU architecture.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must be called only once and should be called before any
|
||||
/// memory operations are performed that rely on virtual memory management.
|
||||
/// The provided `physical_memory_offset` must be accurate to ensure correct
|
||||
/// translation of physical addresses.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `physical_memory_offset`: The offset to convert physical addresses to
|
||||
/// virtual addresses in the higher-half direct map.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns an `OffsetPageTable` that allows for manipulation of the page
|
||||
/// tables for the current CPU architecture.
|
||||
pub fn init_page_table(physical_memory_offset: VirtAddr) {
|
||||
unsafe {
|
||||
let l4_table = active_l4_table(physical_memory_offset);
|
||||
let offset_table = OffsetPageTable::new(l4_table, physical_memory_offset);
|
||||
OFFSET_PAGE_TABLE.call_once(|| Mutex::new(offset_table));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FoundryOSFrameAllocator {
|
||||
memory_map: &'static MemoryMapResponse,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
pub fn init_frame_allocator(memory_map: &'static MemoryMapResponse) {
|
||||
unsafe {
|
||||
FRAME_ALLOCATOR.call_once(|| Mutex::new(FoundryOSFrameAllocator::init(memory_map)));
|
||||
}
|
||||
}
|
||||
|
||||
impl FoundryOSFrameAllocator {
|
||||
/// Creates a new `FoundryOSFrameAllocator` from a memory map.
|
||||
///
|
||||
/// This function takes a reference to a `MemoryMapResponse` and initializes a
|
||||
/// `FoundryOSFrameAllocator` with it. The `next` field is set to 0, indicating that
|
||||
/// the first frame to be allocated is the first frame in the memory map.
|
||||
pub const unsafe fn init(memory_map: &'static MemoryMapResponse) -> Self {
|
||||
Self {
|
||||
memory_map,
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count_usable_frames(&self) -> u32 {
|
||||
self.usable_frames().count() as u32
|
||||
}
|
||||
|
||||
/// An iterator over all usable frames in the memory map.
|
||||
///
|
||||
/// Yields one `PhysFrame` for each available 4KiB frame in the memory map.
|
||||
///
|
||||
/// This function is used to allocate frames for the pagemap.
|
||||
fn usable_frames(&self) -> impl Iterator<Item = PhysFrame> + use<> {
|
||||
let regions = self.memory_map.entries().iter();
|
||||
let usable_regions = regions.filter(|region| region.entry_type == EntryType::USABLE);
|
||||
|
||||
let addr_ranges = usable_regions.map(|region| region.base..region.base + region.length);
|
||||
|
||||
let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096));
|
||||
|
||||
frame_addresses.map(|addr| PhysFrame::from_start_address(PhysAddr::new(addr)).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl FrameAllocator<Size4KiB> for FoundryOSFrameAllocator {
|
||||
/// Allocates a frame from the list of usable frames.
|
||||
///
|
||||
/// This function returns the next available `PhysFrame` from the memory map,
|
||||
/// if one exists. Once a frame is allocated, the internal counter is incremented
|
||||
/// to point to the next frame for future allocations.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Some(PhysFrame)`: If a usable frame is available.
|
||||
/// - `None`: If there are no more usable frames to allocate.
|
||||
fn allocate_frame(&mut self) -> Option<PhysFrame> {
|
||||
let frame = self.usable_frames().nth(self.next);
|
||||
self.next += 1;
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
// pub unsafe fn translate_addr(addr: VirtAddr, physical_memory_offset: VirtAddr) -> Option<PhysAddr> {
|
||||
// translate_addr_inner(addr, physical_memory_offset)
|
||||
// }
|
||||
|
||||
// fn translate_addr_inner(addr: VirtAddr, physical_memory_offset: VirtAddr) -> Option<PhysAddr> {
|
||||
// let (l4_table_frame, _) = Cr3::read();
|
||||
|
||||
// let table_indexes = [
|
||||
// addr.p4_index(),
|
||||
// addr.p3_index(),
|
||||
// addr.p2_index(),
|
||||
// addr.p1_index(),
|
||||
// ];
|
||||
// let mut frame = l4_table_frame;
|
||||
|
||||
// for &i in &table_indexes {
|
||||
// let virt = physical_memory_offset + frame.start_address().as_u64();
|
||||
// let table_ptr: *const PageTable = virt.as_ptr();
|
||||
// let table = unsafe { &*table_ptr };
|
||||
|
||||
// let entry = &table[i];
|
||||
|
||||
// frame = match entry.frame() {
|
||||
// Ok(frame) => frame,
|
||||
// Err(FrameError::FrameNotPresent) => return None,
|
||||
// Err(FrameError::HugeFrame) => panic!("huge frames are not supported!"),
|
||||
// };
|
||||
// }
|
||||
|
||||
// Some(frame.start_address() + u64::from(addr.page_offset()))
|
||||
// }
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod allocation;
|
||||
pub mod memory;
|
||||
pub mod memory_map;
|
||||
@@ -1,9 +1,6 @@
|
||||
pub mod gdt;
|
||||
|
||||
pub mod interrupts;
|
||||
|
||||
pub mod memmap;
|
||||
|
||||
pub mod apic;
|
||||
|
||||
pub mod cpu;
|
||||
pub mod drivers;
|
||||
pub mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod memory;
|
||||
pub mod processing;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod task;
|
||||
@@ -0,0 +1,148 @@
|
||||
//! Allows creation of asynchronous IO bound tasks.
|
||||
//!
|
||||
//! Written by @zxq5 for the most part with code from
|
||||
//! [here](https://github.com/phil-opp/blog_os/).
|
||||
//!
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::task::Wake;
|
||||
use core::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::atomic::AtomicU64,
|
||||
task::{Context, Poll, Waker},
|
||||
};
|
||||
use crossbeam::queue::ArrayQueue;
|
||||
use x86_64::instructions::interrupts::{self, enable_and_hlt};
|
||||
|
||||
pub struct Task {
|
||||
id: TaskId,
|
||||
future: Pin<Box<dyn Future<Output = ()>>>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(future: impl Future<Output = ()> + 'static) -> Self {
|
||||
Self {
|
||||
id: TaskId::new(),
|
||||
future: Box::pin(future),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, context: &mut Context) -> Poll<()> {
|
||||
self.future.as_mut().poll(context)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct TaskId(u64);
|
||||
|
||||
impl TaskId {
|
||||
fn new() -> Self {
|
||||
static NEXT: AtomicU64 = AtomicU64::new(0);
|
||||
Self(NEXT.fetch_add(1, core::sync::atomic::Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Executor {
|
||||
tasks: BTreeMap<TaskId, Task>,
|
||||
task_queue: Arc<ArrayQueue<TaskId>>,
|
||||
waker_cache: BTreeMap<TaskId, Waker>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tasks: BTreeMap::new(),
|
||||
task_queue: Arc::new(ArrayQueue::new(100)),
|
||||
waker_cache: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, task: Task) {
|
||||
let task_id = task.id;
|
||||
if self.tasks.insert(task.id, task).is_some() {
|
||||
panic!("task with same id already in tasks");
|
||||
}
|
||||
self.task_queue.push(task_id).expect("queue full");
|
||||
}
|
||||
|
||||
fn run_ready_tasks(&mut self) {
|
||||
// destructure `self` to avoid borrow checker errors
|
||||
let Self {
|
||||
tasks,
|
||||
task_queue,
|
||||
waker_cache,
|
||||
} = self;
|
||||
|
||||
while let Some(task_id) = task_queue.pop() {
|
||||
let task = match tasks.get_mut(&task_id) {
|
||||
Some(task) => task,
|
||||
None => continue, // task no longer exists
|
||||
};
|
||||
let waker = waker_cache
|
||||
.entry(task_id)
|
||||
.or_insert_with(|| TaskWaker::new_waker(task_id, task_queue.clone()));
|
||||
let mut context = Context::from_waker(waker);
|
||||
match task.poll(&mut context) {
|
||||
Poll::Ready(()) => {
|
||||
// task done -> remove it and its cached waker
|
||||
tasks.remove(&task_id);
|
||||
waker_cache.remove(&task_id);
|
||||
}
|
||||
Poll::Pending => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> ! {
|
||||
loop {
|
||||
self.run_ready_tasks();
|
||||
self.sleep_if_idle();
|
||||
}
|
||||
}
|
||||
|
||||
fn sleep_if_idle(&self) {
|
||||
interrupts::disable();
|
||||
if self.task_queue.is_empty() {
|
||||
enable_and_hlt();
|
||||
} else {
|
||||
interrupts::enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Executor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskWaker {
|
||||
task_id: TaskId,
|
||||
task_queue: Arc<ArrayQueue<TaskId>>,
|
||||
}
|
||||
|
||||
impl TaskWaker {
|
||||
fn wake_task(&self) {
|
||||
self.task_queue.push(self.task_id).expect("task_queue full");
|
||||
}
|
||||
|
||||
fn new_waker(task_id: TaskId, task_queue: Arc<ArrayQueue<TaskId>>) -> Waker {
|
||||
Waker::from(Arc::new(Self {
|
||||
task_id,
|
||||
task_queue,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Wake for TaskWaker {
|
||||
fn wake(self: Arc<Self>) {
|
||||
self.wake_task();
|
||||
}
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) {
|
||||
self.wake_task();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod async_io;
|
||||
pub mod threading;
|
||||
@@ -0,0 +1,116 @@
|
||||
mod switch;
|
||||
mod deprecated;
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Thread {
|
||||
id: usize,
|
||||
/// This shall be default before the program is interrupted, otherwise it will store
|
||||
/// CPU registers etc to be restored on context switch.
|
||||
ctx: ThreadContext,
|
||||
}
|
||||
|
||||
/// CPU state to be saved on context switches.
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct ThreadContext {
|
||||
/// Accumulator register.
|
||||
rax: u64,
|
||||
/// Base register.
|
||||
rbx: u64,
|
||||
/// Counter register.
|
||||
rcx: u64,
|
||||
/// Data register.
|
||||
rdx: u64,
|
||||
/// Source index register.
|
||||
rsi: u64,
|
||||
/// Destination index register.
|
||||
rdi: u64,
|
||||
/// Base pointer register.
|
||||
rbp: u64,
|
||||
/// Stack pointer register.
|
||||
rsp: u64,
|
||||
/// An extended register.
|
||||
r8: u64,
|
||||
/// An extended register.
|
||||
r9: u64,
|
||||
/// An extended register.
|
||||
r10: u64,
|
||||
/// An extended register.
|
||||
r11: u64,
|
||||
/// An extended register.
|
||||
r12: u64,
|
||||
/// An extended register.
|
||||
r13: u64,
|
||||
/// An extended register.
|
||||
r14: u64,
|
||||
/// An extended register.
|
||||
r15: u64,
|
||||
/// The instruction pointer.
|
||||
rip: u64,
|
||||
/// RFLAGS register.
|
||||
rflags: u64,
|
||||
}
|
||||
|
||||
impl ThreadContext {
|
||||
/// Saves the current registers of the CPU before a context switch
|
||||
/// to be restored later.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function should ONLY be called in interrupt handlers such
|
||||
/// as that of the timer. This will then save registers as required
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because of the usage of inline ASM.
|
||||
#[inline(always)]
|
||||
pub unsafe fn save_registers() -> Self {
|
||||
let mut context = Self::default();
|
||||
unsafe {
|
||||
asm!(
|
||||
"mov {0}, rax",
|
||||
"mov {1}, rbx",
|
||||
"mov {2}, rcx",
|
||||
"mov {3}, rdx",
|
||||
"mov {4}, rsi",
|
||||
"mov {5}, rdi",
|
||||
"mov {6}, rbp",
|
||||
"mov {7}, rsp",
|
||||
"mov {8}, r8",
|
||||
"mov {9}, r9",
|
||||
"mov {10}, r10",
|
||||
"mov {11}, r11",
|
||||
"mov {12}, r12",
|
||||
"mov {13}, r13",
|
||||
"mov {14}, r14",
|
||||
"mov {15}, r15",
|
||||
"lea {16}, [rip]",
|
||||
"pushf",
|
||||
"pop {17}",
|
||||
out(reg) context.rax,
|
||||
out(reg) context.rbx,
|
||||
out(reg) context.rcx,
|
||||
out(reg) context.rdx,
|
||||
out(reg) context.rsi,
|
||||
out(reg) context.rdi,
|
||||
out(reg) context.rbp,
|
||||
out(reg) context.rsp,
|
||||
out(reg) context.r8,
|
||||
out(reg) context.r9,
|
||||
out(reg) context.r10,
|
||||
out(reg) context.r11,
|
||||
out(reg) context.r12,
|
||||
out(reg) context.r13,
|
||||
out(reg) context.r14,
|
||||
out(reg) context.r15,
|
||||
out(reg) context.rip,
|
||||
out(reg) context.rflags,
|
||||
);
|
||||
}
|
||||
|
||||
context
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use x86_64::VirtAddr;
|
||||
|
||||
mod switch;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Thread {
|
||||
id: ThreadId,
|
||||
stack_ptr: Option<VirtAddr>,
|
||||
stack_bounds: Option<StackBounds>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct StackBounds {
|
||||
start: VirtAddr,
|
||||
end: VirtAddr,
|
||||
}
|
||||
|
||||
impl StackBounds {
|
||||
pub fn new(start: VirtAddr, end: VirtAddr) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
pub fn start(&self) -> VirtAddr {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> VirtAddr {
|
||||
self.end
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ThreadId(u64);
|
||||
|
||||
impl ThreadId {
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1);
|
||||
ThreadId(NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user