add missing files
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,3 +7,6 @@ bx_enh_dbg.ini
|
|||||||
|
|
||||||
# limine
|
# limine
|
||||||
limine/
|
limine/
|
||||||
|
|
||||||
|
# rewrite stuff
|
||||||
|
src.bak
|
||||||
375
src/arch/x86_64/interrupts/apic.rs
Normal file
375
src/arch/x86_64/interrupts/apic.rs
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
use crate::{drivers::acpi::SMP_REQUEST, hcf, libs::cell::OnceCell};
|
||||||
|
|
||||||
|
use alloc::{sync::Arc, vec::Vec};
|
||||||
|
|
||||||
|
use super::super::{
|
||||||
|
cpu_get_msr, cpu_set_msr,
|
||||||
|
io::{inb, outb},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct MADT {
|
||||||
|
pub local_apic_address: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const IA32_APIC_BASE_MSR: u32 = 0x1B;
|
||||||
|
const IA32_APIC_BASE_MSR_ENABLE: usize = 0x800;
|
||||||
|
|
||||||
|
pub fn has_apic() -> bool {
|
||||||
|
return unsafe { core::arch::x86_64::__cpuid_count(1, 0).edx } & 1 << 9 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_apic_base(apic: usize) {
|
||||||
|
let edx: u32 = 0;
|
||||||
|
let eax = (apic & 0xfffff0000) | IA32_APIC_BASE_MSR_ENABLE;
|
||||||
|
|
||||||
|
unsafe { cpu_set_msr(IA32_APIC_BASE_MSR, &(eax as u32), &edx) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_apic_base() -> u32 {
|
||||||
|
let mut eax: u32 = 0;
|
||||||
|
let mut edx: u32 = 0;
|
||||||
|
unsafe { cpu_get_msr(IA32_APIC_BASE_MSR, &mut eax, &mut edx) };
|
||||||
|
|
||||||
|
return eax & 0xfffff000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct LAPIC {
|
||||||
|
pub acpi_processor_id: u8,
|
||||||
|
pub apic_id: u8,
|
||||||
|
pub flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct IOAPIC {
|
||||||
|
pub ioapic_id: u8,
|
||||||
|
_reserved: u8,
|
||||||
|
pub ptr: *mut u8,
|
||||||
|
pub global_interrupt_base: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct IOAPICSourceOverride {
|
||||||
|
bus_source: u8,
|
||||||
|
irq_source: u8,
|
||||||
|
global_system_interrupt: u32,
|
||||||
|
flags: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct APIC {
|
||||||
|
pub io_apic: IOAPIC,
|
||||||
|
local_apic: *mut u8,
|
||||||
|
pub cpus: Arc<[LAPIC]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn test(info: *const limine::SmpInfo) -> ! {
|
||||||
|
crate::log_ok!("hey from CPU {:<02}", unsafe { (*info).processor_id });
|
||||||
|
|
||||||
|
hcf();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl APIC {
|
||||||
|
pub fn new() -> Result<Self, ()> {
|
||||||
|
disable_pic();
|
||||||
|
|
||||||
|
if !has_apic() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let apic_base = get_apic_base() as usize;
|
||||||
|
|
||||||
|
set_apic_base(apic_base);
|
||||||
|
|
||||||
|
let madt = crate::drivers::acpi::find_table::<MADT>("APIC");
|
||||||
|
|
||||||
|
if madt.is_none() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cpus: Vec<LAPIC> = Vec::new();
|
||||||
|
|
||||||
|
let madt = madt.unwrap();
|
||||||
|
|
||||||
|
crate::log_info!("MADT located at: {:p}", core::ptr::addr_of!(madt));
|
||||||
|
|
||||||
|
let mut lapic_ptr = madt.inner.local_apic_address as *mut u8;
|
||||||
|
let mut io_apic = None;
|
||||||
|
let mut io_apic_source_override = None;
|
||||||
|
|
||||||
|
let mut ptr = madt.extra.unwrap().as_ptr();
|
||||||
|
let ptr_end = unsafe { ptr.add(madt.header.length as usize - 44) };
|
||||||
|
|
||||||
|
while (ptr as usize) < (ptr_end as usize) {
|
||||||
|
match unsafe { *ptr } {
|
||||||
|
0 => {
|
||||||
|
if unsafe { *(ptr.add(4)) } & 1 != 0 {
|
||||||
|
cpus.push(unsafe { *ptr.add(2).cast::<LAPIC>() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => unsafe {
|
||||||
|
io_apic = Some(IOAPIC {
|
||||||
|
ioapic_id: *ptr.add(2),
|
||||||
|
_reserved: *ptr.add(3),
|
||||||
|
ptr: (*ptr.add(4).cast::<u32>()) as *mut u8,
|
||||||
|
global_interrupt_base: *ptr.add(8).cast::<u32>(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
2 => unsafe {
|
||||||
|
io_apic_source_override = Some(IOAPICSourceOverride {
|
||||||
|
bus_source: *ptr.add(2),
|
||||||
|
irq_source: *ptr.add(3),
|
||||||
|
global_system_interrupt: *ptr.add(4).cast::<u32>(),
|
||||||
|
flags: *ptr.add(8).cast::<u16>(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
5 => lapic_ptr = unsafe { *(ptr.add(4).cast::<u64>()) } as *mut u8,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = unsafe { ptr.add((*ptr.add(1)) as usize) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if io_apic.is_none() || io_apic_source_override.is_none() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let io_apic_ptr = io_apic.unwrap().ptr;
|
||||||
|
|
||||||
|
crate::println!(
|
||||||
|
"Found {} core{}, IOAPIC {:p}, LAPIC {lapic_ptr:p}, Processor IDs:",
|
||||||
|
cpus.len(),
|
||||||
|
if cpus.len() > 1 { "s" } else { "" },
|
||||||
|
io_apic_ptr,
|
||||||
|
);
|
||||||
|
|
||||||
|
for apic in &cpus {
|
||||||
|
crate::println!(" {}", apic.acpi_processor_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let apic = Self {
|
||||||
|
io_apic: io_apic.unwrap(),
|
||||||
|
local_apic: lapic_ptr,
|
||||||
|
cpus: cpus.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enable APIC by setting bit 8 to 1
|
||||||
|
apic.write_lapic(0xF0, apic.read_lapic(0xF0) | 0x100);
|
||||||
|
|
||||||
|
let io_apic_ver = apic.read_ioapic(0x01);
|
||||||
|
|
||||||
|
let number_of_inputs = ((io_apic_ver >> 16) & 0xFF) + 1;
|
||||||
|
|
||||||
|
crate::println!("{number_of_inputs}");
|
||||||
|
|
||||||
|
// // hopefully nothing important is on that page :shrug:
|
||||||
|
// // TODO: use the page allocator we wrote maybe
|
||||||
|
// unsafe { core::ptr::copy(test as *mut u8, 0x8000 as *mut u8, 4096) }
|
||||||
|
|
||||||
|
let smp_request = SMP_REQUEST.get_response().get_mut();
|
||||||
|
|
||||||
|
if smp_request.is_none() {
|
||||||
|
panic!("Failed to get smp from limine!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let smp_request = smp_request.unwrap();
|
||||||
|
let bsp_lapic_id = smp_request.bsp_lapic_id;
|
||||||
|
|
||||||
|
for cpu in smp_request.cpus() {
|
||||||
|
if cpu.processor_id == bsp_lapic_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu.goto_address = test;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for cpu_apic in apic.cpus.iter() {
|
||||||
|
// let lapic_id = cpu_apic.apic_id;
|
||||||
|
|
||||||
|
// // TODO: If CPU is the BSP, do not intialize it
|
||||||
|
|
||||||
|
// crate::log_info!("Initializing CPU {processor_id:<02}, please wait",);
|
||||||
|
|
||||||
|
// match apic.bootstrap_processor(processor_id, 0x8000) {
|
||||||
|
// Err(_) => crate::log_error!("Failed to initialize CPU {processor_id:<02}!"),
|
||||||
|
// Ok(_) => crate::log_ok!("Successfully initialized CPU {processor_id:<02}!"),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Set and enable keyboard interrupt
|
||||||
|
apic.set_interrupt(0x01, 0x01);
|
||||||
|
|
||||||
|
return Ok(apic);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn bootstrap_processor(&self, processor_id: u8, startup_page: usize) -> Result<(), ()> {
|
||||||
|
// // Clear LAPIC errors
|
||||||
|
// self.write_lapic(0x280, 0);
|
||||||
|
// // Select Auxiliary Processor
|
||||||
|
// self.write_lapic(
|
||||||
|
// 0x310,
|
||||||
|
// (self.read_lapic(0x310) & 0x00FFFFFF) | (processor_id as u32) << 24,
|
||||||
|
// );
|
||||||
|
// // send INIT Inter-Processor Interrupt
|
||||||
|
// self.write_lapic(0x300, (self.read_lapic(0x300) & 0x00FFFFFF) | 0x00C500);
|
||||||
|
|
||||||
|
// // Wait for IPI delivery
|
||||||
|
// while self.read_lapic(0x300) & (1 << 12) != 0 {
|
||||||
|
// unsafe {
|
||||||
|
// core::arch::asm!("pause");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Select Auxiliary Processor
|
||||||
|
// self.write_lapic(
|
||||||
|
// 0x310,
|
||||||
|
// (self.read_lapic(0x310) & 0x00FFFFFF) | (processor_id as u32) << 24,
|
||||||
|
// );
|
||||||
|
// // deassert
|
||||||
|
// self.write_lapic(0x300, (self.read_lapic(0x300) & 0x00FFFFFF) | 0x00C500);
|
||||||
|
|
||||||
|
// // Wait for IPI delivery
|
||||||
|
// while self.read_lapic(0x300) & (1 << 12) != 0 {
|
||||||
|
// unsafe {
|
||||||
|
// core::arch::asm!("pause");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// msdelay(10);
|
||||||
|
|
||||||
|
// for i in 0..2 {
|
||||||
|
// self.write_lapic(0x280, 0);
|
||||||
|
// self.write_lapic(
|
||||||
|
// 0x310,
|
||||||
|
// (self.read_lapic(0x310) & 0x00FFFFFF) | (processor_id as u32) << 24,
|
||||||
|
// );
|
||||||
|
// self.write_lapic(0x300, (self.read_lapic(0x300) & 0xfff0f800) | 0x000608);
|
||||||
|
// if i == 0 {
|
||||||
|
// usdelay(200);
|
||||||
|
// } else {
|
||||||
|
// msdelay(1000);
|
||||||
|
// }
|
||||||
|
// while self.read_lapic(0x300) & (1 << 12) != 0 {
|
||||||
|
// unsafe {
|
||||||
|
// core::arch::asm!("pause");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn read_ioapic(&self, reg: u32) -> u32 {
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_volatile(self.io_apic.ptr.cast::<u32>(), reg & 0xff);
|
||||||
|
return core::ptr::read_volatile(self.io_apic.ptr.cast::<u32>().add(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_ioapic(&self, reg: u32, value: u32) {
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_volatile(self.io_apic.ptr.cast::<u32>(), reg & 0xff);
|
||||||
|
core::ptr::write_volatile(self.io_apic.ptr.cast::<u32>().add(4), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_lapic(&self, reg: u32) -> u32 {
|
||||||
|
unsafe {
|
||||||
|
return *self.local_apic.add(reg as usize).cast::<u32>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_lapic(&self, reg: u32, value: u32) {
|
||||||
|
unsafe {
|
||||||
|
*self.local_apic.add(reg as usize).cast::<u32>() = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lapic_send_ipi(&self, dest_id: u32, vec: u32) {
|
||||||
|
self.write_lapic(0x310, dest_id << 24);
|
||||||
|
self.write_lapic(0x300, vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_interrupt(&self, int_num: u8, redirtion_num: u8) {
|
||||||
|
let retbl_offset: u32 = 0x10 + (int_num as u32 * 2);
|
||||||
|
let interrupts_vt =
|
||||||
|
((self.read_ioapic(retbl_offset) & 0x7FFF) & 0xFF00) | (0x20 + redirtion_num as u32);
|
||||||
|
|
||||||
|
self.write_ioapic(retbl_offset, interrupts_vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_of_interrupt(&self) {
|
||||||
|
self.write_lapic(0xB0, 0x00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PIC_CMD_MASTER: u16 = 0x20;
|
||||||
|
const PIC_CMD_SLAVE: u16 = 0xA0;
|
||||||
|
const PIC_DATA_MASTER: u16 = 0x21;
|
||||||
|
const PIC_DATA_SLAVE: u16 = 0xA1;
|
||||||
|
|
||||||
|
fn disable_pic() {
|
||||||
|
// Tell each PIC we're going to initialize it.
|
||||||
|
outb(PIC_CMD_MASTER, 0x11);
|
||||||
|
outb(PIC_CMD_SLAVE, 0x11);
|
||||||
|
|
||||||
|
// Byte 1: Set up our base offsets.
|
||||||
|
outb(PIC_DATA_MASTER, 0x20);
|
||||||
|
outb(PIC_DATA_SLAVE, 0x28);
|
||||||
|
|
||||||
|
// Byte 2: Configure chaining
|
||||||
|
outb(PIC_DATA_MASTER, 0x04); // Tell Master Pic that there is a slave Pic at IRQ2
|
||||||
|
outb(PIC_DATA_SLAVE, 0x02); // Tell Slave PIC it's cascade identity
|
||||||
|
|
||||||
|
// Byte 3: Set out mode to 8086.
|
||||||
|
outb(PIC_DATA_MASTER, 0x01);
|
||||||
|
outb(PIC_DATA_SLAVE, 0x01);
|
||||||
|
|
||||||
|
// Set each PIC's mask to 0xFF, disabling PIC interrupts
|
||||||
|
outb(PIC_DATA_MASTER, 0xFF);
|
||||||
|
outb(PIC_DATA_SLAVE, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usdelay(useconds: u16) {
|
||||||
|
let pit_count = ((useconds as u32 * 1193) / 1000) as u16;
|
||||||
|
|
||||||
|
pit_delay(pit_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn msdelay(ms: u32) {
|
||||||
|
let mut total_count = ms * 1193;
|
||||||
|
|
||||||
|
while total_count > 0 {
|
||||||
|
let chunk_count = if total_count > u16::MAX as u32 {
|
||||||
|
u16::MAX
|
||||||
|
} else {
|
||||||
|
total_count as u16
|
||||||
|
};
|
||||||
|
|
||||||
|
pit_delay(chunk_count);
|
||||||
|
|
||||||
|
total_count -= chunk_count as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pit_delay(count: u16) {
|
||||||
|
// Set PIT to mode 0
|
||||||
|
outb(0x43, 0x30);
|
||||||
|
outb(0x40, (count & 0xFF) as u8);
|
||||||
|
outb(0x40, ((count & 0xFF00) >> 8) as u8);
|
||||||
|
loop {
|
||||||
|
// Tell PIT to give us a timer status
|
||||||
|
outb(0x43, 0xE2);
|
||||||
|
if ((inb(0x40) >> 7) & 0x01) != 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static APIC: OnceCell<APIC> = OnceCell::new();
|
||||||
132
src/arch/x86_64/io.rs
Normal file
132
src/arch/x86_64/io.rs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
use core::arch::asm;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn outb(port: u16, value: u8) {
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"out dx, al",
|
||||||
|
in("dx") port,
|
||||||
|
in("al") value,
|
||||||
|
options(preserves_flags, nomem, nostack)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn inb(port: u16) -> u8 {
|
||||||
|
let mut value: u8;
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"in al, dx",
|
||||||
|
out("al") value,
|
||||||
|
in("dx") port,
|
||||||
|
options(preserves_flags, nomem, nostack)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn outw(port: u16, value: u16) {
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"out dx, ax",
|
||||||
|
in("dx") port,
|
||||||
|
in("ax") value,
|
||||||
|
options(preserves_flags, nomem, nostack)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn inw(port: u16) -> u16 {
|
||||||
|
let mut value: u16;
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"in ax, dx",
|
||||||
|
out("ax") value,
|
||||||
|
in("dx") port,
|
||||||
|
options(preserves_flags, nomem, nostack)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads `count` 16-bit values from the specified `port` into the `buffer`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function panics if the supplied buffer's size is smaller than `count`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn insw(port: u16, buffer: *mut u16, count: usize) {
|
||||||
|
asm!("cld",
|
||||||
|
"rep insw",
|
||||||
|
in("dx") port,
|
||||||
|
inout("rdi") buffer => _,
|
||||||
|
inout("rcx") count => _
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outputs `count` 8-bit values from the specified `port` into the `buffer`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function panics if the supplied buffer's size is smaller than `count`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn outsb(port: u16, buffer: *const u8, count: usize) {
|
||||||
|
asm!("cld",
|
||||||
|
"rep outsb",
|
||||||
|
in("dx") port,
|
||||||
|
inout("rsi") buffer => _,
|
||||||
|
inout("rcx") count => _
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outputs `count` 16-bit values from the specified `port` into the `buffer`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function panics if the supplied buffer's size is smaller than `count`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn outsw(port: u16, buffer: *const u16, count: usize) {
|
||||||
|
asm!("cld",
|
||||||
|
"rep outsw",
|
||||||
|
in("dx") port,
|
||||||
|
inout("rsi") buffer => _,
|
||||||
|
inout("rcx") count => _
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn outl(port: u16, value: u32) {
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"out dx, eax",
|
||||||
|
in("dx") port,
|
||||||
|
in("eax") value,
|
||||||
|
options(preserves_flags, nomem, nostack)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn inl(port: u16) -> u32 {
|
||||||
|
let mut value: u32;
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"in eax, dx",
|
||||||
|
out("eax") value,
|
||||||
|
in("dx") port,
|
||||||
|
options(preserves_flags, nomem, nostack)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn io_wait() {
|
||||||
|
outb(0x80, 0);
|
||||||
|
}
|
||||||
99
src/arch/x86_64/stack_trace.rs
Normal file
99
src/arch/x86_64/stack_trace.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use alloc::{borrow::ToOwned, string::String, vec::Vec};
|
||||||
|
|
||||||
|
// use crate::drivers::fs::vfs::VfsFileSystem;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct StackFrame {
|
||||||
|
back: *const StackFrame,
|
||||||
|
rip: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_stack_trace(max_frames: usize, rbp: u64) {
|
||||||
|
let mut stackframe = rbp as *const StackFrame;
|
||||||
|
|
||||||
|
crate::println!("Stack Trace:");
|
||||||
|
for _frame in 0..max_frames {
|
||||||
|
if stackframe.is_null() || unsafe { (*stackframe).back.is_null() } {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instruction_pointer = unsafe { (*stackframe).rip };
|
||||||
|
|
||||||
|
if instruction_pointer == 0x0 {
|
||||||
|
unsafe {
|
||||||
|
stackframe = (*stackframe).back;
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::print!(" {:#X} ", instruction_pointer);
|
||||||
|
|
||||||
|
let instrcution_info = get_function_name(instruction_pointer);
|
||||||
|
|
||||||
|
if let Ok((function_name, function_offset)) = instrcution_info {
|
||||||
|
crate::println!("<{}+{:#X}>", function_name, function_offset);
|
||||||
|
} else {
|
||||||
|
crate::println!();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
stackframe = (*stackframe).back;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_function_name(function_address: u64) -> Result<(String, u64), ()> {
|
||||||
|
return Err(());
|
||||||
|
|
||||||
|
// let symbols_fd = (*crate::drivers::fs::initramfs::INITRAMFS).open("/symbols.table")?;
|
||||||
|
|
||||||
|
// let symbols_table_bytes = symbols_fd.read()?;
|
||||||
|
// let symbols_table = core::str::from_utf8(&symbols_table_bytes).ok().ok_or(())?;
|
||||||
|
|
||||||
|
// let mut previous_symbol: Option<(&str, u64)> = None;
|
||||||
|
|
||||||
|
// let symbols_table_lines: Vec<&str> = symbols_table.lines().collect();
|
||||||
|
|
||||||
|
// for (i, line) in symbols_table_lines.iter().enumerate() {
|
||||||
|
// let line_parts: Vec<&str> = line.splitn(2, ' ').collect();
|
||||||
|
|
||||||
|
// if line_parts.len() < 2 {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let (address, function_name) = (
|
||||||
|
// u64::from_str_radix(line_parts[0], 16).ok().ok_or(())?,
|
||||||
|
// line_parts[1],
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if address == function_address {
|
||||||
|
// return Ok((function_name.to_owned(), 0));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if i == symbols_table_lines.len() - 1 {
|
||||||
|
// return Ok((function_name.to_owned(), function_address - address));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if i == 0 {
|
||||||
|
// if function_address < address {
|
||||||
|
// return Err(());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// previous_symbol = Some((function_name, address));
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if function_address > previous_symbol.unwrap().1 && function_address < address {
|
||||||
|
// // function is previous symbol
|
||||||
|
// return Ok((
|
||||||
|
// previous_symbol.unwrap().0.to_owned(),
|
||||||
|
// address - previous_symbol.unwrap().1,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// previous_symbol = Some((function_name, address));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// unreachable!();
|
||||||
|
}
|
||||||
19
src/drivers/fs/devfs.rs
Normal file
19
src/drivers/fs/devfs.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use alloc::{boxed::Box, string::String, sync::Arc};
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum DeviceType {
|
||||||
|
CharacterDevice = 0,
|
||||||
|
BlockDevice = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Device {
|
||||||
|
typ: DeviceType,
|
||||||
|
block_size: usize,
|
||||||
|
name: String,
|
||||||
|
ops: Box<dyn DeviceOperations>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DeviceOperations {
|
||||||
|
fn read(&self, sector: u64, sector_count: usize) -> Result<Arc<[u8]>, ()>;
|
||||||
|
fn write(&self, sector: u64, data: &[u8]) -> Result<(), ()>;
|
||||||
|
}
|
||||||
@@ -62,16 +62,24 @@
|
|||||||
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
|
||||||
use alloc::{alloc::handle_alloc_error, boxed::Box, sync::Arc, vec::Vec};
|
use alloc::{
|
||||||
|
alloc::{alloc, handle_alloc_error},
|
||||||
|
boxed::Box,
|
||||||
|
sync::Arc,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{log_info, mem::PHYSICAL_MEMORY_MANAGER};
|
use crate::{
|
||||||
|
log_info,
|
||||||
|
mem::{ALLOCATOR, PHYSICAL_MEMORY_MANAGER},
|
||||||
|
};
|
||||||
|
|
||||||
static mut ROOT_VFS: Vfs = Vfs::null();
|
static mut ROOT_VFS: Vfs = Vfs::null();
|
||||||
|
|
||||||
pub struct Vfs {
|
pub struct Vfs {
|
||||||
next: Option<*mut Vfs>,
|
next: Option<*mut Vfs>,
|
||||||
ops: Option<Box<dyn FsOps>>,
|
ops: Option<Box<dyn FsOps>>,
|
||||||
vnode_covered: Option<Box<VNode>>,
|
vnode_covered: Option<*const VNode>,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
block_size: u32,
|
block_size: u32,
|
||||||
pub data: *mut u8,
|
pub data: *mut u8,
|
||||||
@@ -259,7 +267,7 @@ pub struct VAttr {
|
|||||||
|
|
||||||
pub fn add_vfs(mount_point: &str, fs_ops: Box<dyn FsOps>) -> Result<(), ()> {
|
pub fn add_vfs(mount_point: &str, fs_ops: Box<dyn FsOps>) -> Result<(), ()> {
|
||||||
let layout = alloc::alloc::Layout::new::<Vfs>();
|
let layout = alloc::alloc::Layout::new::<Vfs>();
|
||||||
let vfs = PHYSICAL_MEMORY_MANAGER.alloc(1).unwrap().cast::<Vfs>();
|
let vfs = unsafe { alloc(layout).cast::<Vfs>() };
|
||||||
|
|
||||||
let vfs = unsafe { &mut *vfs };
|
let vfs = unsafe { &mut *vfs };
|
||||||
|
|
||||||
|
|||||||
@@ -690,8 +690,6 @@ fn ide_initialize(bar0: u32, bar1: u32, _bar2: u32, _bar3: u32, _bar4: u32) {
|
|||||||
|
|
||||||
let fat_fs = fat_fs.unwrap();
|
let fat_fs = fat_fs.unwrap();
|
||||||
|
|
||||||
crate::println!("adding fat fs to / :scared:");
|
|
||||||
|
|
||||||
add_vfs("/", Box::new(fat_fs));
|
add_vfs("/", Box::new(fat_fs));
|
||||||
|
|
||||||
// let vfs = crate::drivers::fs::vfs::Vfs::new(
|
// let vfs = crate::drivers::fs::vfs::Vfs::new(
|
||||||
|
|||||||
93
src/drivers/video.rs
Normal file
93
src/drivers/video.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use limine::FramebufferRequest;
|
||||||
|
|
||||||
|
use crate::libs::cell::OnceCell;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Framebuffer {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub bpp: usize,
|
||||||
|
pub pitch: usize,
|
||||||
|
pub pointer: *mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Framebuffer {
|
||||||
|
#[inline]
|
||||||
|
const fn new(bpp: usize, pitch: usize, ptr: *mut u8, width: usize, height: usize) -> Self {
|
||||||
|
return Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bpp,
|
||||||
|
pitch,
|
||||||
|
pointer: ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the size of the framebuffer in bytes
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
return self.pitch * self.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_pixel(&self, x: u32, y: u32, color: u32) {
|
||||||
|
let pixel_offset = (y * self.pitch as u32 + (x * (self.bpp / 8) as u32)) as isize;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*(self.pointer.offset(pixel_offset) as *mut u32) = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is slow, but significantly faster than filling the framebuffer pixel-by-pixel with for loops.
|
||||||
|
// idk, fix it later ig.
|
||||||
|
pub fn fill_screen(&self, color: u32, mirror_buffer: Option<Self>) {
|
||||||
|
let buffer_size = (self.pitch / (self.bpp / 8)) * self.height;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if let Some(mirror_buffer) = mirror_buffer {
|
||||||
|
crate::mem::memset32(mirror_buffer.pointer as *mut u32, color, buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::mem::memset32(self.pointer as *mut u32, color, buffer_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blit_screen(&self, buffer: &mut [u32], mirror_buffer: Option<Self>) {
|
||||||
|
unsafe {
|
||||||
|
core::ptr::copy_nonoverlapping(buffer.as_ptr(), self.pointer as *mut u32, buffer.len());
|
||||||
|
|
||||||
|
if let Some(mirror_buffer) = mirror_buffer {
|
||||||
|
core::ptr::copy_nonoverlapping(
|
||||||
|
buffer.as_ptr(),
|
||||||
|
mirror_buffer.pointer as *mut u32,
|
||||||
|
buffer.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new(0);
|
||||||
|
pub static FRAMEBUFFER: OnceCell<Option<Framebuffer>> = OnceCell::new();
|
||||||
|
|
||||||
|
pub fn get_framebuffer() -> Option<Framebuffer> {
|
||||||
|
*FRAMEBUFFER.get_or_set(|| {
|
||||||
|
let framebuffer_response = crate::drivers::video::FRAMEBUFFER_REQUEST
|
||||||
|
.get_response()
|
||||||
|
.get()?;
|
||||||
|
|
||||||
|
if framebuffer_response.framebuffer_count < 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let framebuffer_response = &framebuffer_response.framebuffers()[0];
|
||||||
|
|
||||||
|
let framebuffer = Framebuffer::new(
|
||||||
|
framebuffer_response.bpp as usize,
|
||||||
|
framebuffer_response.pitch as usize,
|
||||||
|
framebuffer_response.address.as_ptr().unwrap(),
|
||||||
|
framebuffer_response.width as usize,
|
||||||
|
framebuffer_response.height as usize,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Some(framebuffer);
|
||||||
|
})
|
||||||
|
}
|
||||||
54
src/libs/cell/lazy.rs
Executable file
54
src/libs/cell/lazy.rs
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
use core::ops::Deref;
|
||||||
|
|
||||||
|
use super::Cell;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum LazyState<T, F = fn() -> T> {
|
||||||
|
Uninitialized(F),
|
||||||
|
Initializing,
|
||||||
|
Initialized(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LazyCell<T, F = fn() -> T> {
|
||||||
|
state: Cell<LazyState<T, F>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F: Fn() -> T> LazyCell<T, F> {
|
||||||
|
pub const fn new(init_func: F) -> Self {
|
||||||
|
Self {
|
||||||
|
state: Cell::new(LazyState::Uninitialized(init_func)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Option<&T> {
|
||||||
|
match self.state.get() {
|
||||||
|
LazyState::Initialized(data) => Some(data),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F: Fn() -> T> Deref for LazyCell<T, F> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self.state.get() {
|
||||||
|
LazyState::Uninitialized(func) => {
|
||||||
|
self.state.set(LazyState::Initializing);
|
||||||
|
|
||||||
|
// initialize and return value
|
||||||
|
let new_value = func();
|
||||||
|
|
||||||
|
self.state.set(LazyState::Initialized(new_value));
|
||||||
|
|
||||||
|
self.get().unwrap()
|
||||||
|
}
|
||||||
|
LazyState::Initialized(data) => data,
|
||||||
|
LazyState::Initializing => {
|
||||||
|
panic!("Attempted to initialize Lazy while initializing Lazy!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T, F: Fn() -> T + Send> Sync for LazyCell<T, F> {}
|
||||||
27
src/libs/cell/mod.rs
Normal file
27
src/libs/cell/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use core::cell::UnsafeCell;
|
||||||
|
|
||||||
|
mod lazy;
|
||||||
|
mod once;
|
||||||
|
|
||||||
|
pub use lazy::LazyCell;
|
||||||
|
pub use once::OnceCell;
|
||||||
|
|
||||||
|
pub struct Cell<T: ?Sized> {
|
||||||
|
value: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Cell<T> {
|
||||||
|
pub const fn new(value: T) -> Cell<T> {
|
||||||
|
return Self {
|
||||||
|
value: UnsafeCell::new(value),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &T {
|
||||||
|
return unsafe { &*self.value.get() };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, new_value: T) {
|
||||||
|
unsafe { *self.value.get() = new_value };
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/libs/cell/once.rs
Normal file
63
src/libs/cell/once.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use core::ops::Deref;
|
||||||
|
|
||||||
|
use super::Cell;
|
||||||
|
|
||||||
|
pub struct OnceCell<T> {
|
||||||
|
state: Cell<OnceCellState<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T> Sync for OnceCell<T> {}
|
||||||
|
|
||||||
|
enum OnceCellState<T> {
|
||||||
|
Uninitialized,
|
||||||
|
Initializing,
|
||||||
|
Initialized(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OnceCell<T> {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
return OnceCell {
|
||||||
|
state: Cell::new(OnceCellState::Uninitialized),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, new_data: T) {
|
||||||
|
match self.state.get() {
|
||||||
|
OnceCellState::Uninitialized => {
|
||||||
|
self.state.set(OnceCellState::Initializing);
|
||||||
|
|
||||||
|
self.state.set(OnceCellState::Initialized(new_data));
|
||||||
|
}
|
||||||
|
_ => panic!("Tried to initialize data that is alread initialized"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_or_set<F>(&self, func: F) -> &T
|
||||||
|
where
|
||||||
|
F: FnOnce() -> T,
|
||||||
|
{
|
||||||
|
match self.state.get() {
|
||||||
|
OnceCellState::Uninitialized => {
|
||||||
|
self.set(func());
|
||||||
|
self.get()
|
||||||
|
}
|
||||||
|
OnceCellState::Initializing => panic!("Tried to get or set data that is initializing"),
|
||||||
|
OnceCellState::Initialized(data) => data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self) -> &T {
|
||||||
|
match self.state.get() {
|
||||||
|
OnceCellState::Initialized(data) => data,
|
||||||
|
_ => panic!("Attempted to access uninitialized data!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for OnceCell<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/libs/sync/mod.rs
Normal file
3
src/libs/sync/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod mutex;
|
||||||
|
|
||||||
|
pub use mutex::Mutex;
|
||||||
72
src/libs/sync/mutex.rs
Executable file
72
src/libs/sync/mutex.rs
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
use core::{
|
||||||
|
cell::UnsafeCell,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Mutex<T: ?Sized> {
|
||||||
|
locked: AtomicBool,
|
||||||
|
data: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: ?Sized> Sync for Mutex<T> {}
|
||||||
|
|
||||||
|
impl<T> Mutex<T> {
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(data: T) -> Self {
|
||||||
|
return Self {
|
||||||
|
locked: AtomicBool::new(false),
|
||||||
|
data: UnsafeCell::new(data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||||
|
// if self.locked.load(Ordering::Acquire) == true {
|
||||||
|
// unsafe { core::arch::asm!("out dx, al", in("dx") 0x3f8, in("al") 'S' as u8) };
|
||||||
|
// }
|
||||||
|
while self.locked.swap(true, Ordering::Acquire) {
|
||||||
|
// spin lock
|
||||||
|
}
|
||||||
|
return MutexGuard { mutex: self };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::fmt::Debug for Mutex<T> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
let locked = self.locked.load(Ordering::SeqCst);
|
||||||
|
write!(f, "Mutex: {{ data: ")?;
|
||||||
|
|
||||||
|
if locked {
|
||||||
|
write!(f, "<locked> }}")
|
||||||
|
} else {
|
||||||
|
write!(f, "{:?} }}", self.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MutexGuard<'a, T: ?Sized> {
|
||||||
|
mutex: &'a Mutex<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
// unsafe { core::arch::asm!("out dx, al", in("dx") 0x3f8, in("al") 'D' as u8) };
|
||||||
|
|
||||||
|
unsafe { &*self.mutex.data.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
// unsafe { core::arch::asm!("out dx, al", in("dx") 0x3f8, in("al") 'M' as u8) };
|
||||||
|
unsafe { &mut *self.mutex.data.get() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.mutex.locked.store(false, Ordering::Release);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/main.rs
11
src/main.rs
@@ -35,7 +35,14 @@ pub extern "C" fn _start() -> ! {
|
|||||||
|
|
||||||
drivers::storage::ide::init();
|
drivers::storage::ide::init();
|
||||||
|
|
||||||
crate::println!("{:?}", vfs_open("/example.txt"));
|
let nested_file = vfs_open("/boot/limine/limine.cfg").unwrap();
|
||||||
|
|
||||||
|
crate::println!(
|
||||||
|
"{:X?}",
|
||||||
|
nested_file
|
||||||
|
.ops
|
||||||
|
.open(0, UserCred { uid: 0, gid: 0 }, nested_file.as_ptr())
|
||||||
|
);
|
||||||
|
|
||||||
let file = vfs_open("/example.txt").unwrap();
|
let file = vfs_open("/example.txt").unwrap();
|
||||||
crate::println!(
|
crate::println!(
|
||||||
@@ -102,7 +109,7 @@ macro_rules! print {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! log_info {
|
macro_rules! log_info {
|
||||||
($($arg:tt)*) => ($crate::println!("\x1B[97m[ \x1B[90m? \x1B[97m]\x1B[0m () {}", &alloc::format!($($arg)*)));
|
($($arg:tt)*) => ($crate::println!("\x1B[97m[ \x1B[90m? \x1B[97m]\x1B[0m {}", &alloc::format!($($arg)*)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
Reference in New Issue
Block a user