e1000d: DMA barriers on RX and TX descriptor paths

Without explicit memory ordering, the Rust compiler is free to
reorder loads of the device-written descriptor (status, length)
relative to the writeback of the descriptor ownership flag. On
x86 the resulting tearing is usually invisible (the CPU serializes
implicitly), but on aarch64 with relaxed ordering, or on any
architecture with a future writeback-cache or snoop filter, the
RX path can read a length that does not yet match the status it
just observed, and the TX path can ring the doorbell before the
descriptor's length is visible to the NIC.

Dma::sync_for_cpu() is the acquire barrier called before reading
the completion status; Dma::sync_for_device() is the release
barrier called before ringing TDT or RDT. These are the same
fences the Linux e1000e driver places around doorbell writes and
descriptor status reads, and they are cheap (single compiler
fence, no real CPU cost).
This commit is contained in:
Red Bear OS
2026-07-26 17:12:18 +09:00
parent 263a41a926
commit b3fd5cc682
+7
View File
@@ -135,6 +135,11 @@ impl NetworkAdapter for Intel8254x {
fn read_packet(&mut self, buf: &mut [u8]) -> Result<Option<usize>> {
let desc = unsafe { &mut *(self.receive_ring.as_ptr().add(self.receive_index) as *mut Rd) };
// Acquire fence pairs the device's store to desc.status with our
// load of desc.length; without it the compiler can reorder
// and we read a stale length on weakly-ordered architectures.
self.receive_buffer[self.receive_index].sync_for_cpu();
if desc.status & RD_DD == RD_DD {
desc.status = 0;
@@ -143,6 +148,7 @@ impl NetworkAdapter for Intel8254x {
let i = cmp::min(buf.len(), data.len());
buf[..i].copy_from_slice(&data[..i]);
self.receive_buffer[self.receive_index].sync_for_device();
unsafe { self.write_reg(RDT, self.receive_index as u32) };
self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len());
@@ -200,6 +206,7 @@ impl NetworkAdapter for Intel8254x {
self.transmit_index = wrap_ring(self.transmit_index, self.transmit_ring.len());
self.transmit_ring_free -= 1;
self.transmit_buffer[self.transmit_index].sync_for_device();
unsafe { self.write_reg(TDT, self.transmit_index as u32) };
Ok(i)