1use crate::{
11 arch::pci::{self, PciDevice},
12 hardware::{
13 nic as net,
14 virtio::{
15 common::{VirtioDevice, Virtqueue},
16 status,
17 },
18 },
19 memory::{self, PhysFrame},
20 sync::{FixedQueue, SpinLock},
21};
22use alloc::sync::Arc;
23use core::{mem, ptr, sync::atomic::Ordering};
24use endian_num::Le;
25use net_core::{NetError, NetworkDevice};
26use spin::RwLock as SpinRwLock;
27
28static NET_HDR_SIZE: core::sync::atomic::AtomicUsize =
31 core::sync::atomic::AtomicUsize::new(mem::size_of::<VirtioNetHeader>());
32const RX_FRAME_TRACK_CAPACITY: usize = 128;
33const TX_FRAME_TRACK_CAPACITY: usize = 128;
34
35pub mod features {
37 pub const VIRTIO_NET_F_CSUM: u32 = 1 << 0;
38 pub const VIRTIO_NET_F_GUEST_CSUM: u32 = 1 << 1;
39 pub const VIRTIO_NET_F_MAC: u32 = 1 << 5;
40 pub const VIRTIO_NET_F_GSO: u32 = 1 << 6;
41 pub const VIRTIO_NET_F_GUEST_TSO4: u32 = 1 << 7;
42 pub const VIRTIO_NET_F_GUEST_TSO6: u32 = 1 << 8;
43 pub const VIRTIO_NET_F_GUEST_ECN: u32 = 1 << 9;
44 pub const VIRTIO_NET_F_GUEST_UFO: u32 = 1 << 10;
45 pub const VIRTIO_NET_F_HOST_TSO4: u32 = 1 << 11;
46 pub const VIRTIO_NET_F_HOST_TSO6: u32 = 1 << 12;
47 pub const VIRTIO_NET_F_HOST_ECN: u32 = 1 << 13;
48 pub const VIRTIO_NET_F_HOST_UFO: u32 = 1 << 14;
49 pub const VIRTIO_NET_F_MRG_RXBUF: u32 = 1 << 15;
50 pub const VIRTIO_NET_F_STATUS: u32 = 1 << 16;
51 pub const VIRTIO_NET_F_CTRL_VQ: u32 = 1 << 17;
52 pub const VIRTIO_NET_F_CTRL_RX: u32 = 1 << 18;
53 pub const VIRTIO_NET_F_CTRL_VLAN: u32 = 1 << 19;
54 pub const VIRTIO_NET_F_GUEST_ANNOUNCE: u32 = 1 << 21;
55 pub const VIRTIO_NET_F_MQ: u32 = 1 << 22;
56}
57
58pub mod net_status {
60 pub const VIRTIO_NET_S_LINK_UP: u16 = 1;
61 pub const VIRTIO_NET_S_ANNOUNCE: u16 = 2;
62}
63
64#[repr(C)]
69#[derive(Debug, Clone, Copy, Default)]
70pub struct VirtioNetHeader {
71 pub flags: u8,
72 pub gso_type: u8,
73 pub hdr_len: Le<u16>,
74 pub gso_size: Le<u16>,
75 pub csum_start: Le<u16>,
76 pub csum_offset: Le<u16>,
77 pub num_buffers: Le<u16>,
78}
79
80pub struct VirtioNetDevice {
82 device: VirtioDevice,
83 rx_queue: SpinLock<Virtqueue>,
84 tx_queue: SpinLock<Virtqueue>,
85 mac_address: [u8; 6],
86 pub rx_frames: SpinLock<FixedQueue<(PhysFrame, u8), RX_FRAME_TRACK_CAPACITY>>,
87 tx_frames: SpinLock<FixedQueue<(PhysFrame, u8), TX_FRAME_TRACK_CAPACITY>>,
88}
89
90unsafe impl Send for VirtioNetDevice {}
92unsafe impl Sync for VirtioNetDevice {}
93
94fn read_plausible_mac(device: &VirtioDevice, offset: u16, out: &mut [u8; 6]) -> bool {
97 for i in 0..6 {
98 out[i] = device.read_reg_u8(offset + i as u16);
99 }
100 *out != [0xFF; 6] && *out != [0x00; 6] && (out[0] & 0x01) == 0
101}
102
103impl VirtioNetDevice {
104 pub unsafe fn new(pci_dev: PciDevice) -> Result<Self, &'static str> {
106 log::info!("VirtIO-net: Initializing device at {:?}", pci_dev.address);
107
108 let mut device = VirtioDevice::new(pci_dev)?;
110
111 device.reset();
113
114 device.add_status(status::ACKNOWLEDGE as u8);
116
117 device.add_status(status::DRIVER as u8);
119
120 let device_features = device.read_device_features();
122 let needed = features::VIRTIO_NET_F_MAC | features::VIRTIO_NET_F_STATUS;
123 let desired = needed | features::VIRTIO_NET_F_MRG_RXBUF;
128 if device_features & needed != needed {
129 return Err("Device lacks mandatory MAC/STATUS features");
130 }
131 let guest_features = device_features & desired;
132 device.write_guest_features(guest_features);
133
134 device.add_status(status::FEATURES_OK as u8);
136
137 if device.get_status() & (status::FEATURES_OK as u8) == 0 {
139 return Err("Device rejected our feature set");
140 }
141
142 let negotiated = device.read_device_features();
146 if negotiated & features::VIRTIO_NET_F_MRG_RXBUF != 0 {
147 NET_HDR_SIZE.store(mem::size_of::<VirtioNetHeader>(), Ordering::Release);
148 } else {
149 NET_HDR_SIZE.store(10, Ordering::Release);
151 }
152
153 let rx_queue = Virtqueue::new(128)?;
157 let tx_queue = Virtqueue::new(128)?;
158
159 device.setup_queue(0, &rx_queue);
161 device.setup_queue(1, &tx_queue);
162
163 log::info!(
167 "VirtIO-net: io_base={:#06x} raw[0x14..0x1c]={:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} link={:#06x}",
168 device.io_base,
169 device.read_reg_u8(0x14),
170 device.read_reg_u8(0x15),
171 device.read_reg_u8(0x16),
172 device.read_reg_u8(0x17),
173 device.read_reg_u8(0x18),
174 device.read_reg_u8(0x19),
175 device.read_reg_u8(0x1a),
176 device.read_reg_u8(0x1b),
177 device.read_reg_u16(0x1a),
178 );
179
180 let mut mac_address = [0u8; 6];
186 let mut mac_offset = 0x14u16;
187 if !read_plausible_mac(&device, 0x14, &mut mac_address) {
188 if read_plausible_mac(&device, 0x18, &mut mac_address) {
189 mac_offset = 0x18;
190 log::warn!(
191 "VirtIO-net: MAC at io_base+0x18 (window shifted by 4) - transitional layout"
192 );
193 device.io_base = device.io_base.wrapping_add(4);
194 } else {
195 log::error!(
196 "VirtIO-net: MAC unreadable at io_base+0x14 and +0x18 (all 0xFF/0x00)"
197 );
198 return Err("device config window unreadable (MAC)");
199 }
200 }
201
202 log::info!(
203 "VirtIO-net: MAC address: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} (offset {:#x})",
204 mac_address[0],
205 mac_address[1],
206 mac_address[2],
207 mac_address[3],
208 mac_address[4],
209 mac_address[5],
210 mac_offset
211 );
212
213 device.add_status(status::DRIVER_OK as u8);
215
216 log::info!(
217 "VirtIO-net: post-DRIVER_OK link status={:#06x} up={}",
218 device.read_reg_u16(0x1a),
219 device.read_reg_u16(0x1a) & net_status::VIRTIO_NET_S_LINK_UP as u16 != 0
220 );
221
222 let net_device = Self {
223 device,
224 rx_queue: SpinLock::new(rx_queue),
225 tx_queue: SpinLock::new(tx_queue),
226 mac_address,
227 rx_frames: SpinLock::new(FixedQueue::new()),
228 tx_frames: SpinLock::new(FixedQueue::new()),
229 };
230
231 net_device.refill_rx_queue()?;
233
234 Ok(net_device)
235 }
236
237 fn refill_rx_queue(&self) -> Result<(), &'static str> {
239 let mut rx_queue = self.rx_queue.lock();
240 let mut rx_frames = self.rx_frames.lock();
241
242 let current_filled = rx_frames.len();
244 let target_filled = 64;
245 let mut added = 0usize;
246
247 if current_filled >= target_filled {
248 return Ok(());
249 }
250
251 for _ in 0..(target_filled - current_filled) {
252 let buf_size = NET_HDR_SIZE.load(Ordering::Relaxed) + net::MTU;
254 let buf_pages = (buf_size + 4095) / 4096;
255 let buf_order = buf_pages.next_power_of_two().trailing_zeros() as u8;
256
257 let buf_frame = match crate::sync::with_irqs_disabled(|token| {
258 memory::allocate_phys_contiguous(token, buf_order)
259 }) {
260 Ok(frame) => frame,
261 Err(_) => break, };
263
264 let buf_addr = buf_frame.start_address.as_u64();
265 let virt_addr = crate::memory::phys_to_virt(buf_addr);
266
267 unsafe {
269 ptr::write_bytes(virt_addr as *mut u8, 0, buf_size);
270 }
271
272 match rx_queue.add_buffer(&[(buf_addr, buf_size as u32, true)]) {
274 Ok(_) => {
275 if rx_frames.push_back((buf_frame, buf_order)).is_err() {
276 crate::sync::with_irqs_disabled(|token| {
277 memory::free_phys_contiguous(token, buf_frame, buf_order);
278 });
279 break;
280 }
281 added += 1;
282 }
283 Err(_) => {
284 crate::sync::with_irqs_disabled(|token| {
286 memory::free_phys_contiguous(token, buf_frame, buf_order);
287 });
288 break;
289 }
290 }
291 }
292
293 if rx_queue.should_notify() {
295 self.device.notify_queue(0);
296 }
297
298 if rx_frames.is_empty() && current_filled == 0 && added == 0 {
299 return Err("Failed to allocate RX buffers");
300 }
301
302 Ok(())
303 }
304
305 fn read_link_status(&self) -> u16 {
307 self.device.read_reg_u16(26)
309 }
310}
311
312impl NetworkDevice for VirtioNetDevice {
313 fn name(&self) -> &str {
315 "virtio-net"
316 }
317
318 fn receive(&self, buf: &mut [u8]) -> Result<usize, NetError> {
320 let mut rx_queue = self.rx_queue.lock();
321
322 if !rx_queue.has_used() {
324 return Err(NetError::NoPacket);
325 }
326
327 let (frame, order) = match self.rx_frames.lock().pop_front() {
332 Some(f) => f,
333 None => {
334 log::warn!("[vtnet] rx: used entry without tracking frame");
337 return Err(NetError::NotReady);
338 }
339 };
340
341 let hdr_size = NET_HDR_SIZE.load(Ordering::Relaxed);
342 let (token, len) = match rx_queue.get_used() {
343 Some(v) => v,
344 None => {
345 crate::sync::with_irqs_disabled(|t| {
346 memory::free_phys_contiguous(t, frame, order);
347 });
348 return Err(NetError::NoPacket);
349 }
350 };
351
352 let _desc_index = token as usize;
353 let _desc_table = rx_queue.desc_area(); let buf_addr = frame.start_address.as_u64();
356 let virt_addr = crate::memory::phys_to_virt(buf_addr);
357
358 let header_ptr = virt_addr as *const VirtioNetHeader;
359 let data_ptr = (virt_addr + hdr_size as u64) as *const u8;
360
361 let header = unsafe { ptr::read(header_ptr) };
362 let packet_len = (len as usize).saturating_sub(hdr_size);
363
364 log::trace!(
365 "[vtnet] rx: token={} len={} pkt={} flags={}",
366 token,
367 len,
368 packet_len,
369 header.flags,
370 );
371
372 if buf.len() < packet_len {
373 crate::sync::with_irqs_disabled(|token| {
375 memory::free_phys_contiguous(token, frame, order);
376 });
377 drop(rx_queue);
378 let _ = self.refill_rx_queue();
380 return Err(NetError::BufferTooSmall);
381 }
382
383 if packet_len > 0 {
385 unsafe {
386 ptr::copy_nonoverlapping(data_ptr, buf.as_mut_ptr(), packet_len);
387 }
388 }
389
390 crate::sync::with_irqs_disabled(|token| {
392 memory::free_phys_contiguous(token, frame, order);
393 });
394 drop(rx_queue);
395
396 let _ = self.refill_rx_queue();
398
399 Ok(packet_len)
400 }
401
402 fn transmit(&self, buf: &[u8]) -> Result<(), NetError> {
404 if buf.len() > net::MTU {
405 return Err(NetError::BufferTooSmall);
406 }
407
408 let buf_size = NET_HDR_SIZE.load(Ordering::Relaxed) + buf.len();
410 let buf_pages = (buf_size + 4095) / 4096;
411 let buf_order = buf_pages.next_power_of_two().trailing_zeros() as u8;
412
413 let buf_frame = crate::sync::with_irqs_disabled(|token| {
414 memory::allocate_phys_contiguous(token, buf_order)
415 })
416 .map_err(|_| NetError::NotReady)?;
417
418 let buf_addr = buf_frame.start_address.as_u64();
419 let virt_addr = crate::memory::phys_to_virt(buf_addr);
420
421 let header_ptr = virt_addr as *mut VirtioNetHeader;
422 let data_ptr = (virt_addr + NET_HDR_SIZE.load(Ordering::Relaxed) as u64) as *mut u8;
423
424 unsafe {
426 ptr::write(header_ptr, VirtioNetHeader::default());
427 ptr::copy_nonoverlapping(buf.as_ptr(), data_ptr, buf.len());
428 }
429
430 let mut tx_queue = self.tx_queue.lock();
432
433 while let Some((_token, _len)) = tx_queue.get_used() {
437 if let Some((_frame, order)) = self.tx_frames.lock().pop_front() {
438 crate::sync::with_irqs_disabled(|token| {
439 memory::free_phys_contiguous(token, _frame, order);
440 });
441 }
442 }
443
444 let head = tx_queue
445 .add_buffer(&[(buf_addr, buf_size as u32, false)]) .map_err(|_| {
447 crate::sync::with_irqs_disabled(|token| {
449 memory::free_phys_contiguous(token, buf_frame, buf_order);
450 });
451 NetError::TxQueueFull
452 })?;
453
454 if let Err(_) = self.tx_frames.lock().push_back((buf_frame, buf_order)) {
455 crate::sync::with_irqs_disabled(|token| {
460 memory::free_phys_contiguous(token, buf_frame, buf_order);
461 });
462 }
463
464 log::trace!("[vtnet] tx: submit {} bytes @ {:#x}", buf_size, buf_addr);
465
466 if tx_queue.should_notify() {
467 self.device.notify_queue(1);
468 }
469 drop(tx_queue);
470
471 Ok(())
472 }
473
474 fn mac_address(&self) -> [u8; 6] {
476 self.mac_address
477 }
478
479 fn link_up(&self) -> bool {
481 let status = self.read_link_status();
482 status & net_status::VIRTIO_NET_S_LINK_UP != 0
483 }
484
485 fn handle_interrupt(&self) {
488 if self.device.read_isr_status() == 0 {
489 return;
490 }
491 self.device.ack_interrupt();
492 }
493
494 fn poll(&self) {
496 loop {
497 let used = {
498 let mut tx_queue = self.tx_queue.lock();
499 tx_queue.get_used()
500 };
501 let Some((_token, _len)) = used else {
502 break;
503 };
504 if let Some((_frame, order)) = self.tx_frames.lock().pop_front() {
505 crate::sync::with_irqs_disabled(|token| {
506 memory::free_phys_contiguous(token, _frame, order);
507 });
508 }
509 }
510 let _ = self.refill_rx_queue();
511 }
512}
513
514static VIRTIO_NET: SpinRwLock<Option<Arc<VirtioNetDevice>>> = SpinRwLock::new(None);
516
517pub fn init() {
522 if VIRTIO_NET.read().is_some() {
523 return;
524 }
525
526 log::info!("VirtIO-net: Scanning for devices...");
527
528 let pci_dev = match pci::probe_first(pci::ProbeCriteria {
531 vendor_id: Some(pci::vendor::VIRTIO),
532 device_id: Some(pci::device::VIRTIO_NET),
533 class_code: Some(pci::class::NETWORK),
534 subclass: Some(pci::net_subclass::ETHERNET),
535 prog_if: None,
536 })
537 .or_else(|| pci::find_virtio_device(pci::device::VIRTIO_NET))
538 {
539 Some(dev) => dev,
540 None => {
541 log::warn!("VirtIO-net: No network device found");
542 return;
543 }
544 };
545
546 let client_dev = crate::hardware::pci_client::PciDevice {
551 address: crate::hardware::pci_client::PciAddress::new(
552 pci_dev.address.bus,
553 pci_dev.address.device,
554 pci_dev.address.function,
555 ),
556 vendor_id: pci_dev.vendor_id,
557 device_id: pci_dev.device_id,
558 class_code: pci_dev.class_code,
559 subclass: pci_dev.subclass,
560 prog_if: pci_dev.prog_if,
561 revision: pci_dev.revision,
562 header_type: pci_dev.header_type,
563 interrupt_line: pci_dev.interrupt_line,
564 interrupt_pin: pci_dev.interrupt_pin,
565 };
566 let (irq, vector) = crate::arch::msi::probe_and_enable(&client_dev, true);
567 let msi_active =
568 (client_dev.read_config_u16(pci::config::COMMAND) & pci::command::INTERRUPT_DISABLE) != 0;
569
570 let mut device = None;
574 for attempt in 1..=2u32 {
575 match unsafe { VirtioNetDevice::new(pci_dev) } {
576 Ok(dev) => {
577 log::info!("VirtIO-net: init ok on attempt {}", attempt);
578 device = Some(dev);
579 break;
580 }
581 Err(e) => {
582 log::warn!("VirtIO-net: init attempt {} failed: {}", attempt, e);
583 if attempt == 1 {
584 let mut cmd = client_dev.read_config_u16(pci::config::COMMAND);
585 cmd |= pci::command::BUS_MASTER | pci::command::IO_SPACE;
586 cmd &= !pci::command::INTERRUPT_DISABLE;
587 client_dev.write_config_u16(pci::config::COMMAND, cmd);
588 log::info!(
589 "VirtIO-net: reprogrammed PCI COMMAND={:#06x} (bus_master|io_space, intx enabled)",
590 cmd
591 );
592 }
593 }
594 }
595 }
596
597 match device {
598 Some(device) => {
599 let arc = Arc::new(device);
600 *VIRTIO_NET.write() = Some(arc.clone());
601 let iface = net::register_device(arc.clone());
602
603 log::info!(
606 "[VirtIO-net] {}: link_up={} status_raw={:#06x}",
607 iface,
608 arc.link_up(),
609 arc.read_link_status()
610 );
611
612 if msi_active {
613 crate::arch::idt::register_nic_irq(vector);
616 let irq_for_eoi = if irq != 0 && irq != 0xFF { irq } else { vector };
617 net::set_nic_device(arc, irq_for_eoi);
618 log::info!(
619 "[VirtIO-net] {}: MSI/MSI-X active on vector {:#x}",
620 iface,
621 vector
622 );
623 } else if irq == 0 || irq == 0xFF {
624 log::warn!(
625 "[VirtIO-net] {}: no valid IRQ line, running in polling mode",
626 iface
627 );
628 } else {
629 crate::arch::ioapic::route_nic_irq(irq, vector);
630 log::info!(
631 "[VirtIO-net] {}: INTx IRQ {} => vector {:#x}",
632 iface,
633 irq,
634 vector
635 );
636 crate::arch::idt::register_nic_irq(irq);
637 net::set_nic_device(arc, irq);
638 }
639 }
640 None => {
641 log::error!("VirtIO-net: failed to initialize device after retries");
642 }
643 }
644}
645
646pub fn get_device() -> Option<Arc<VirtioNetDevice>> {
648 VIRTIO_NET.read().clone()
649}