Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 28 additions & 64 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ members = [
"tests/runner",
]
exclude = ["examples/basic", "examples/test_framework", "tests/test_kernels/*"]
resolver = "3"

[workspace.package]
# don't forget to update `workspace.dependencies` below
Expand Down
2 changes: 1 addition & 1 deletion common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ xmas-elf = "0.8.0"
raw-cpuid = "10.2.0"
rand = { version = "0.8.6", default-features = false }
rand_hc = "0.3.1"
uart_16550 = "0.3.2"
uart_16550 = "0.6.0"
log = "0.4.17"

[dependencies.noto-sans-mono-bitmap]
Expand Down
15 changes: 10 additions & 5 deletions common/src/serial.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
use core::fmt;
use uart_16550::backend::PioBackend;

// TODO this type can be replaced with Uart16550Tty but using it currently panics
// in the new constructor.
pub struct SerialPort {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we replace SerialPort with Uart16550Tty? Both implement core::fmt::Write, so I don't see much of a reason to keep SerialPort around. Uart16550Tty also does the \n to \r\n conversion SerialPort does.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried but for some reason on of these checks fails:

image

This surprises me becaue in the integration test of uart_16550 it works in QEMU. I don't have time to investigate for now. Can we move forward?

I've added a TODO comment in the code

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks to me like it's spinning somewhere. I think it's spinning in receive_bytes_exact.

@phip1611 phip1611 Jun 17, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you help me investigating please? I'd love to understand why it fails

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you help me investigating please? I'd love to understand why it fails

I tried looking into this yesterday. I think it's some kind of race condition in the self-test code. I can't trigger the issue reliably though. Adding artificial system load seems to make it easier to reproduce, but it's still not completely reliable.
For some reason, I had a harder time reproducing the error with the latest HEAD for uart_16550. I'm not sure why.
I might be able to take another look at this later today.
I think my next step is going to be looking at the serial port implementation in QEMU to see if there's anything that could explain the race condition (maybe asynchronous processing?).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about you, but I've only seen this happen when booting with UEFI. Maybe UEFI's initialization and use of the serial port before the bootloader somehow causes issues?

@phip1611 phip1611 Jun 17, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting - the UEFI console takes ownership of the serial device by default. One must therefore disconnect the UEFI console from using the serial device (as long as boot services are active but one wants to use the serial device). I think this is the problem.

The forced disconnct is also fairly easy

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The forced disconnct is also fairly easy

How would one do that?


Here's something interesting: Wrapping the call to Uart16550Tty::new_port in x86_64::instructions::interrupts::without_interrupts fixes the issue. Disabling interrupts in the tty config (config.interrupts = IER::empty();) does not work however.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The forced disconnct is also fairly easy

How would one do that?

https://github.com/phip1611/uefi-serial-chat/blob/47cc3961ab047d698790dc48131799aa10c18c65/src/main.rs#L104

Regarding interrupts:thanks! That's something I can use for my testing and debugging

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW I'm preparing an update of the uefi dependency

port: uart_16550::SerialPort,
port: uart_16550::Uart16550<PioBackend>,
}

impl SerialPort {
/// # Safety
///
/// unsafe because this function must only be called once
pub unsafe fn init() -> Self {
let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
port.init();
let mut port =
unsafe { uart_16550::Uart16550::new_port(0x3F8) }.expect("should be valid port");
port.init(uart_16550::Config::default())
.expect("should init successfully");
Self { port }
}
}
Expand All @@ -19,8 +24,8 @@ impl fmt::Write for SerialPort {
fn write_str(&mut self, s: &str) -> fmt::Result {
for char in s.bytes() {
match char {
b'\n' => self.port.write_str("\r\n").unwrap(),
byte => self.port.send(byte),
b'\n' => self.port.send_bytes_exact(b"\r\n"),
byte => self.port.send_bytes_exact(&[byte]),
}
}
Ok(())
Expand Down
26 changes: 2 additions & 24 deletions examples/basic/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/basic/kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ edition = "2024"

[dependencies]
bootloader_api = "0.11.12"
uart_16550 = "0.4.0"
uart_16550 = "0.6.0"
x86_64 = "0.15.2"
9 changes: 5 additions & 4 deletions examples/basic/kernel/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

use bootloader_api::{BootInfo, entry_point};
use core::fmt::Write;
use uart_16550::backend::PioBackend;
use uart_16550::{Config, Uart16550Tty};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
Expand All @@ -24,10 +26,9 @@ pub fn exit_qemu(exit_code: QemuExitCode) -> ! {
}
}

pub fn serial() -> uart_16550::SerialPort {
let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) };
port.init();
port
pub fn serial() -> Uart16550Tty<PioBackend> {
unsafe { Uart16550Tty::new_port(0x3F8, Config::default()) }
.expect("should initialize serial device from valid config and valid port")
}

entry_point!(kernel_main);
Expand Down
6 changes: 3 additions & 3 deletions examples/basic/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ fn main() {
let mut child = cmd.spawn().expect("failed to start qemu-system-x86_64");
let status = child.wait().expect("failed to wait on qemu");
match status.code().unwrap_or(1) {
0x10 => 0, // success
0x11 => 1, // failure
_ => 2, // unknown fault
0x10 => 0, // success
0x11 => 1, // failure
_ => 2, // unknown fault
};
}
Loading
Loading