Abstract the virtual_exception_handler function.

This commit is contained in:
Hsy-Intel
2023-09-05 14:26:01 +08:00
committed by Tate, Hongliang Tian
parent 4f0df44b29
commit 82518955d7
6 changed files with 229 additions and 257 deletions

View File

@ -5,9 +5,11 @@ use core::fmt::Debug;
use trapframe::{GeneralRegs, UserContext as RawUserContext}; use trapframe::{GeneralRegs, UserContext as RawUserContext};
#[cfg(feature = "intel_tdx")]
use crate::arch::tdx_guest::{virtual_exception_handler, TdxTrapFrame};
use log::debug; use log::debug;
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
use tdx_guest::{serial_println, tdcall, tdvmcall, TdxVirtualExceptionType}; use tdx_guest::tdcall;
use x86_64::registers::rflags::RFlags; use x86_64::registers::rflags::RFlags;
use crate::trap::call_irq_callback_functions; use crate::trap::call_irq_callback_functions;
@ -42,72 +44,52 @@ pub struct TrapInformation {
} }
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
pub fn virtual_exception_handler(trapframe: &mut GeneralRegs, ve_info: &tdcall::TdgVeInfo) { struct VeGeneralRegs<'a>(&'a mut GeneralRegs);
match ve_info.exit_reason.into() {
TdxVirtualExceptionType::Hlt => {
serial_println!("Ready to halt");
tdvmcall::hlt();
}
TdxVirtualExceptionType::Io => {
if !handle_ve_io(trapframe, ve_info) {
serial_println!("Handle tdx ioexit errors, ready to halt");
tdvmcall::hlt();
}
}
TdxVirtualExceptionType::MsrRead => {
let msr = tdvmcall::rdmsr(trapframe.rcx as u32).unwrap();
trapframe.rax = (msr as u32 & u32::MAX) as usize;
trapframe.rdx = ((msr >> 32) as u32 & u32::MAX) as usize;
}
TdxVirtualExceptionType::MsrWrite => {
let data = trapframe.rax as u64 | ((trapframe.rdx as u64) << 32);
tdvmcall::wrmsr(trapframe.rcx as u32, data).unwrap();
}
TdxVirtualExceptionType::CpuId => {
let cpuid_info = tdvmcall::cpuid(trapframe.rax as u32, trapframe.rcx as u32).unwrap();
let mask = 0xFFFF_FFFF_0000_0000_usize;
trapframe.rax = (trapframe.rax & mask) | cpuid_info.eax;
trapframe.rbx = (trapframe.rbx & mask) | cpuid_info.ebx;
trapframe.rcx = (trapframe.rcx & mask) | cpuid_info.ecx;
trapframe.rdx = (trapframe.rdx & mask) | cpuid_info.edx;
}
TdxVirtualExceptionType::Other => panic!("Unknown TDX vitrual exception type"),
_ => return,
}
trapframe.rip = trapframe.rip + ve_info.exit_instruction_length as usize;
}
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
pub fn handle_ve_io(trapframe: &mut GeneralRegs, ve_info: &tdcall::TdgVeInfo) -> bool { impl TdxTrapFrame for VeGeneralRegs<'_> {
let size = match ve_info.exit_qualification & 0x3 { fn rax(&self) -> usize {
0 => 1, self.0.rax
1 => 2, }
3 => 4, fn set_rax(&mut self, rax: usize) {
_ => panic!("Invalid size value"), self.0.rax = rax;
}; }
let direction = if (ve_info.exit_qualification >> 3) & 0x1 == 0 { fn rbx(&self) -> usize {
tdvmcall::Direction::Out self.0.rbx
} else { }
tdvmcall::Direction::In fn set_rbx(&mut self, rbx: usize) {
}; self.0.rbx = rbx;
let string = (ve_info.exit_qualification >> 4) & 0x1 == 1; }
let repeat = (ve_info.exit_qualification >> 5) & 0x1 == 1; fn rcx(&self) -> usize {
let operand = if (ve_info.exit_qualification >> 6) & 0x1 == 0 { self.0.rcx
tdvmcall::Operand::Dx }
} else { fn set_rcx(&mut self, rcx: usize) {
tdvmcall::Operand::Immediate self.0.rcx = rcx;
}; }
let port = (ve_info.exit_qualification >> 16) as u16; fn rdx(&self) -> usize {
self.0.rdx
match direction { }
tdvmcall::Direction::In => { fn set_rdx(&mut self, rdx: usize) {
trapframe.rax = tdvmcall::io_read(size, port).unwrap() as usize; self.0.rdx = rdx;
} }
tdvmcall::Direction::Out => { fn rsi(&self) -> usize {
tdvmcall::io_write(size, port, trapframe.rax as u32).unwrap(); self.0.rsi
} }
}; fn set_rsi(&mut self, rsi: usize) {
true self.0.rsi = rsi;
}
fn rdi(&self) -> usize {
self.0.rdi
}
fn set_rdi(&mut self, rdi: usize) {
self.0.rdi = rdi;
}
fn rip(&self) -> usize {
self.0.rip
}
fn set_rip(&mut self, rip: usize) {
self.0.rip = rip;
}
} }
impl UserContext { impl UserContext {
@ -148,7 +130,8 @@ impl UserContextApiInternal for UserContext {
if *exception == VIRTUALIZATION_EXCEPTION { if *exception == VIRTUALIZATION_EXCEPTION {
let ve_info = let ve_info =
tdcall::get_veinfo().expect("#VE handler: fail to get VE info\n"); tdcall::get_veinfo().expect("#VE handler: fail to get VE info\n");
virtual_exception_handler(self.general_regs_mut(), &ve_info); let mut ve_f = VeGeneralRegs(self.general_regs_mut());
virtual_exception_handler(&mut ve_f, &ve_info);
continue; continue;
} }
if exception.typ == CpuExceptionType::FaultOrTrap if exception.typ == CpuExceptionType::FaultOrTrap

View File

@ -6,6 +6,8 @@ pub(crate) mod irq;
mod kernel; mod kernel;
pub(crate) mod mm; pub(crate) mod mm;
pub(crate) mod pci; pub(crate) mod pci;
#[cfg(feature = "intel_tdx")]
pub(crate) mod tdx_guest;
pub(crate) mod timer; pub(crate) mod timer;
use alloc::fmt; use alloc::fmt;

View File

@ -0,0 +1,89 @@
use tdx_guest::{
tdcall::TdgVeInfo,
tdvmcall::{cpuid, hlt, rdmsr, wrmsr},
{serial_println, tdcall, tdvmcall, TdxVirtualExceptionType},
};
pub trait TdxTrapFrame {
fn rax(&self) -> usize;
fn set_rax(&mut self, rax: usize);
fn rbx(&self) -> usize;
fn set_rbx(&mut self, rbx: usize);
fn rcx(&self) -> usize;
fn set_rcx(&mut self, rcx: usize);
fn rdx(&self) -> usize;
fn set_rdx(&mut self, rdx: usize);
fn rsi(&self) -> usize;
fn set_rsi(&mut self, rsi: usize);
fn rdi(&self) -> usize;
fn set_rdi(&mut self, rdi: usize);
fn rip(&self) -> usize;
fn set_rip(&mut self, rip: usize);
}
fn io_handler(trapframe: &mut dyn TdxTrapFrame, ve_info: &tdcall::TdgVeInfo) -> bool {
let size = match ve_info.exit_qualification & 0x3 {
0 => 1,
1 => 2,
3 => 4,
_ => panic!("Invalid size value"),
};
let direction = if (ve_info.exit_qualification >> 3) & 0x1 == 0 {
tdvmcall::Direction::Out
} else {
tdvmcall::Direction::In
};
let string = (ve_info.exit_qualification >> 4) & 0x1 == 1;
let repeat = (ve_info.exit_qualification >> 5) & 0x1 == 1;
let operand = if (ve_info.exit_qualification >> 6) & 0x1 == 0 {
tdvmcall::Operand::Dx
} else {
tdvmcall::Operand::Immediate
};
let port = (ve_info.exit_qualification >> 16) as u16;
match direction {
tdvmcall::Direction::In => {
trapframe.set_rax(tdvmcall::io_read(size, port).unwrap() as usize);
}
tdvmcall::Direction::Out => {
tdvmcall::io_write(size, port, trapframe.rax() as u32).unwrap();
}
};
true
}
pub fn virtual_exception_handler(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) {
match ve_info.exit_reason.into() {
TdxVirtualExceptionType::Hlt => {
serial_println!("Ready to halt");
hlt();
}
TdxVirtualExceptionType::Io => {
if !io_handler(trapframe, ve_info) {
serial_println!("Handle tdx ioexit errors, ready to halt");
hlt();
}
}
TdxVirtualExceptionType::MsrRead => {
let msr = rdmsr(trapframe.rcx() as u32).unwrap();
trapframe.set_rax((msr as u32 & u32::MAX) as usize);
trapframe.set_rdx(((msr >> 32) as u32 & u32::MAX) as usize);
}
TdxVirtualExceptionType::MsrWrite => {
let data = trapframe.rax() as u64 | ((trapframe.rdx() as u64) << 32);
wrmsr(trapframe.rcx() as u32, data).unwrap();
}
TdxVirtualExceptionType::CpuId => {
let cpuid_info = cpuid(trapframe.rax() as u32, trapframe.rcx() as u32).unwrap();
let mask = 0xFFFF_FFFF_0000_0000_usize;
trapframe.set_rax((trapframe.rax() & mask) | cpuid_info.eax);
trapframe.set_rbx((trapframe.rbx() & mask) | cpuid_info.ebx);
trapframe.set_rcx((trapframe.rcx() & mask) | cpuid_info.ecx);
trapframe.set_rdx((trapframe.rdx() & mask) | cpuid_info.edx);
}
TdxVirtualExceptionType::Other => panic!("Unknown TDX vitrual exception type"),
_ => return,
}
trapframe.set_rip(trapframe.rip() + ve_info.exit_instruction_length as usize);
}

View File

@ -1,76 +1,58 @@
use crate::{arch::irq::IRQ_LIST, cpu::CpuException}; use crate::{arch::irq::IRQ_LIST, cpu::CpuException};
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
use tdx_guest::{serial_println, tdcall, tdvmcall, TdxVirtualExceptionType}; use crate::arch::tdx_guest::{virtual_exception_handler, TdxTrapFrame};
#[cfg(feature = "intel_tdx")]
use tdx_guest::tdcall;
use trapframe::TrapFrame; use trapframe::TrapFrame;
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
pub fn virtual_exception_handler(trapframe: &mut TrapFrame, ve_info: &tdcall::TdgVeInfo) { struct VeTrapFrame<'a>(&'a mut TrapFrame);
match ve_info.exit_reason.into() {
TdxVirtualExceptionType::Hlt => {
serial_println!("Ready to halt");
tdvmcall::hlt();
}
TdxVirtualExceptionType::Io => {
if !handle_ve_io(trapframe, ve_info) {
serial_println!("Handle tdx ioexit errors, ready to halt");
tdvmcall::hlt();
}
}
TdxVirtualExceptionType::MsrRead => {
let msr = tdvmcall::rdmsr(trapframe.rcx as u32).unwrap();
trapframe.rax = (msr as u32 & u32::MAX) as usize;
trapframe.rdx = ((msr >> 32) as u32 & u32::MAX) as usize;
}
TdxVirtualExceptionType::MsrWrite => {
let data = trapframe.rax as u64 | ((trapframe.rdx as u64) << 32);
tdvmcall::wrmsr(trapframe.rcx as u32, data).unwrap();
}
TdxVirtualExceptionType::CpuId => {
let cpuid_info = tdvmcall::cpuid(trapframe.rax as u32, trapframe.rcx as u32).unwrap();
let mask = 0xFFFF_FFFF_0000_0000_usize;
trapframe.rax = (trapframe.rax & mask) | cpuid_info.eax;
trapframe.rbx = (trapframe.rbx & mask) | cpuid_info.ebx;
trapframe.rcx = (trapframe.rcx & mask) | cpuid_info.ecx;
trapframe.rdx = (trapframe.rdx & mask) | cpuid_info.edx;
}
TdxVirtualExceptionType::Other => panic!("Unknown TDX vitrual exception type"),
_ => return,
}
trapframe.rip = trapframe.rip + ve_info.exit_instruction_length as usize;
}
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
pub fn handle_ve_io(trapframe: &mut TrapFrame, ve_info: &tdcall::TdgVeInfo) -> bool { impl TdxTrapFrame for VeTrapFrame<'_> {
let size = match ve_info.exit_qualification & 0x3 { fn rax(&self) -> usize {
0 => 1, self.0.rax
1 => 2, }
3 => 4, fn set_rax(&mut self, rax: usize) {
_ => panic!("Invalid size value"), self.0.rax = rax;
}; }
let direction = if (ve_info.exit_qualification >> 3) & 0x1 == 0 { fn rbx(&self) -> usize {
tdvmcall::Direction::Out self.0.rbx
} else { }
tdvmcall::Direction::In fn set_rbx(&mut self, rbx: usize) {
}; self.0.rbx = rbx;
let string = (ve_info.exit_qualification >> 4) & 0x1 == 1; }
let repeat = (ve_info.exit_qualification >> 5) & 0x1 == 1; fn rcx(&self) -> usize {
let operand = if (ve_info.exit_qualification >> 6) & 0x1 == 0 { self.0.rcx
tdvmcall::Operand::Dx }
} else { fn set_rcx(&mut self, rcx: usize) {
tdvmcall::Operand::Immediate self.0.rcx = rcx;
}; }
let port = (ve_info.exit_qualification >> 16) as u16; fn rdx(&self) -> usize {
self.0.rdx
match direction { }
tdvmcall::Direction::In => { fn set_rdx(&mut self, rdx: usize) {
trapframe.rax = tdvmcall::io_read(size, port).unwrap() as usize; self.0.rdx = rdx;
} }
tdvmcall::Direction::Out => { fn rsi(&self) -> usize {
tdvmcall::io_write(size, port, trapframe.rax as u32).unwrap(); self.0.rsi
} }
}; fn set_rsi(&mut self, rsi: usize) {
true self.0.rsi = rsi;
}
fn rdi(&self) -> usize {
self.0.rdi
}
fn set_rdi(&mut self, rdi: usize) {
self.0.rdi = rdi;
}
fn rip(&self) -> usize {
self.0.rip
}
fn set_rip(&mut self, rip: usize) {
self.0.rip = rip;
}
} }
/// Only from kernel /// Only from kernel
@ -80,7 +62,8 @@ extern "sysv64" fn trap_handler(f: &mut TrapFrame) {
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
if f.trap_num as u16 == 20 { if f.trap_num as u16 == 20 {
let ve_info = tdcall::get_veinfo().expect("#VE handler: fail to get VE info\n"); let ve_info = tdcall::get_veinfo().expect("#VE handler: fail to get VE info\n");
virtual_exception_handler(f, &ve_info); let mut ve_f = VeTrapFrame(f);
virtual_exception_handler(&mut ve_f, &ve_info);
} }
#[cfg(not(feature = "intel_tdx"))] #[cfg(not(feature = "intel_tdx"))]
panic!("cannot handle kernel cpu fault now, information:{:#x?}", f); panic!("cannot handle kernel cpu fault now, information:{:#x?}", f);

View File

@ -4,7 +4,7 @@
extern crate alloc; extern crate alloc;
pub mod asm; mod asm;
pub mod tdcall; pub mod tdcall;
pub mod tdvmcall; pub mod tdvmcall;
@ -15,79 +15,23 @@ use raw_cpuid::{native_cpuid::cpuid_count, CpuIdResult};
use tdcall::{InitError, TdgVeInfo, TdgVpInfo}; use tdcall::{InitError, TdgVeInfo, TdgVpInfo};
use tdvmcall::*; use tdvmcall::*;
const TDX_CPUID_LEAF_ID: u64 = 0x21;
pub fn tdx_early_init() -> Result<TdgVpInfo, InitError> { pub fn tdx_early_init() -> Result<TdgVpInfo, InitError> {
match is_tdx_guest() { check_tdx_guest()?;
Ok(_) => Ok(tdcall::get_tdinfo()?), Ok(tdcall::get_tdinfo()?)
Err(err) => Err(err),
}
} }
fn is_tdx_guest() -> Result<(), InitError> { fn check_tdx_guest() -> Result<(), InitError> {
const TDX_CPUID_LEAF_ID: u64 = 0x21;
let cpuid_leaf = cpuid_count(0, 0).eax as u64; let cpuid_leaf = cpuid_count(0, 0).eax as u64;
if cpuid_leaf < TDX_CPUID_LEAF_ID { if cpuid_leaf < TDX_CPUID_LEAF_ID {
return Err(InitError::TdxCpuLeafIdError); return Err(InitError::TdxCpuLeafIdError);
} }
let cpuid_result: CpuIdResult = cpuid_count(TDX_CPUID_LEAF_ID as u32, 0); let cpuid_result: CpuIdResult = cpuid_count(TDX_CPUID_LEAF_ID as u32, 0);
if &cpuid_result.ebx.to_ne_bytes() == b"Inte" if &cpuid_result.ebx.to_ne_bytes() != b"Inte"
&& &cpuid_result.ebx.to_ne_bytes() == b"lTDX" || &cpuid_result.ebx.to_ne_bytes() != b"lTDX"
&& &cpuid_result.ecx.to_ne_bytes() == b" " || &cpuid_result.ecx.to_ne_bytes() != b" "
{ {
Ok(()) return Err(InitError::TdxVendorIdError);
} else {
Err(InitError::TdxVendorIdError)
} }
} Ok(())
pub trait TdxTrapFrame {
fn rax(&self) -> usize;
fn set_rax(&mut self, rax: usize);
fn rbx(&self) -> usize;
fn set_rbx(&mut self, rbx: usize);
fn rcx(&self) -> usize;
fn set_rcx(&mut self, rcx: usize);
fn rdx(&self) -> usize;
fn set_rdx(&mut self, rdx: usize);
fn rsi(&self) -> usize;
fn set_rsi(&mut self, rsi: usize);
fn rdi(&self) -> usize;
fn set_rdi(&mut self, rdi: usize);
fn rip(&self) -> usize;
fn set_rip(&mut self, rip: usize);
}
pub fn virtual_exception_handler(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) {
match ve_info.exit_reason.into() {
TdxVirtualExceptionType::Hlt => {
serial_println!("Ready to halt");
hlt();
}
TdxVirtualExceptionType::Io => {
if !handle_io(trapframe, ve_info) {
serial_println!("Handle tdx ioexit errors, ready to halt");
hlt();
}
}
TdxVirtualExceptionType::MsrRead => {
let msr = rdmsr(trapframe.rcx() as u32).unwrap();
trapframe.set_rax((msr as u32 & u32::MAX) as usize);
trapframe.set_rdx(((msr >> 32) as u32 & u32::MAX) as usize);
}
TdxVirtualExceptionType::MsrWrite => {
let data = trapframe.rax() as u64 | ((trapframe.rdx() as u64) << 32);
wrmsr(trapframe.rcx() as u32, data).unwrap();
}
TdxVirtualExceptionType::CpuId => {
let cpuid_info = cpuid(trapframe.rax() as u32, trapframe.rcx() as u32).unwrap();
let mask = 0xFFFF_FFFF_0000_0000_usize;
trapframe.set_rax((trapframe.rax() & mask) | cpuid_info.eax);
trapframe.set_rbx((trapframe.rbx() & mask) | cpuid_info.ebx);
trapframe.set_rcx((trapframe.rcx() & mask) | cpuid_info.ecx);
trapframe.set_rdx((trapframe.rdx() & mask) | cpuid_info.edx);
}
TdxVirtualExceptionType::Other => panic!("Unknown TDX vitrual exception type"),
_ => return,
}
trapframe.set_rip(trapframe.rip() + ve_info.exit_instruction_length as usize);
} }

View File

@ -5,7 +5,7 @@
//! resumes the TD via a SEAMCALL [TDH.VP.ENTER] invocation. //! resumes the TD via a SEAMCALL [TDH.VP.ENTER] invocation.
extern crate alloc; extern crate alloc;
use crate::{asm::asm_td_vmcall, tdcall::TdgVeInfo, TdxTrapFrame}; use crate::{asm::asm_td_vmcall, tdcall::TdgVeInfo};
use alloc::fmt; use alloc::fmt;
use bitflags::bitflags; use bitflags::bitflags;
use core::fmt::Write; use core::fmt::Write;
@ -193,38 +193,6 @@ pub fn write_mmio(size: u64, mmio_addr: u64, data: u64) -> Result<(), TdVmcallEr
} }
} }
pub fn handle_io(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) -> bool {
let size = match ve_info.exit_qualification & 0x3 {
0 => 1,
1 => 2,
3 => 4,
_ => panic!("Invalid size value"),
};
let direction = if (ve_info.exit_qualification >> 3) & 0x1 == 0 {
Direction::Out
} else {
Direction::In
};
let string = (ve_info.exit_qualification >> 4) & 0x1 == 1;
let repeat = (ve_info.exit_qualification >> 5) & 0x1 == 1;
let operand = if (ve_info.exit_qualification >> 6) & 0x1 == 0 {
Operand::Dx
} else {
Operand::Immediate
};
let port = (ve_info.exit_qualification >> 16) as u16;
match direction {
Direction::In => {
trapframe.set_rax(io_read(size, port).unwrap() as usize);
}
Direction::Out => {
io_write(size, port, trapframe.rax() as u32).unwrap();
}
};
true
}
macro_rules! io_read { macro_rules! io_read {
($port:expr, $ty:ty) => {{ ($port:expr, $ty:ty) => {{
let mut args = TdVmcallArgs { let mut args = TdVmcallArgs {
@ -286,22 +254,42 @@ fn td_vmcall(args: &mut TdVmcallArgs) -> Result<(), TdVmcallError> {
} }
bitflags! { bitflags! {
struct LineSts: u8 { struct LineSts: u8 {
const INPUT_FULL = 1; const INPUT_FULL = 1;
const OUTPUT_EMPTY = 1 << 5; const OUTPUT_EMPTY = 1 << 5;
} }
} }
fn line_sts() -> LineSts { fn read_line_sts() -> LineSts {
LineSts::from_bits_truncate(unsafe { PortRead::read_from_port(SERIAL_LINE_STS) }) LineSts::from_bits_truncate(unsafe { PortRead::read_from_port(SERIAL_LINE_STS) })
} }
struct Serial; struct Serial;
impl Serial {
fn serial_write_byte(byte: u8) {
match byte {
// Backspace/Delete
8 | 0x7F => {
while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, b' ', u8).unwrap();
while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
}
_ => {
while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, byte, u8).unwrap();
}
}
}
}
impl Write for Serial { impl Write for Serial {
fn write_str(&mut self, s: &str) -> fmt::Result { fn write_str(&mut self, s: &str) -> fmt::Result {
for &c in s.as_bytes() { for &c in s.as_bytes() {
serial_write_byte(c); Serial::serial_write_byte(c);
} }
Ok(()) Ok(())
} }
@ -313,23 +301,6 @@ pub fn print(args: fmt::Arguments) {
.expect("Failed to write to serial port"); .expect("Failed to write to serial port");
} }
fn serial_write_byte(byte: u8) {
match byte {
8 | 0x7F => {
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, b' ', u8).unwrap();
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
}
_ => {
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
io_write!(SERIAL_IO_PORT, byte, u8).unwrap();
}
}
}
#[macro_export] #[macro_export]
macro_rules! serial_print { macro_rules! serial_print {
($fmt: literal $(, $($arg: tt)+)?) => { ($fmt: literal $(, $($arg: tt)+)?) => {
@ -339,7 +310,7 @@ macro_rules! serial_print {
#[macro_export] #[macro_export]
macro_rules! serial_println { macro_rules! serial_println {
($fmt: literal $(, $($arg: tt)+)?) => { ($fmt: literal $(, $($arg: tt)+)?) => {
$crate::tdvmcall::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)) $crate::tdvmcall::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?))
} }
} }