strat9_kernel/hardware/nic/
virtio_net.rs1use crate::{
11 arch::x86_64::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;
33
34pub mod features {
36 pub const VIRTIO_NET_F_CSUM: u32 = 1 << 0;
37 pub const VIRTIO_NET_F_GUEST_CSUM: u32 = 1 << 1;
38 pub const VIRTIO_NET_F_MAC: u32 = 1 << 5;
39 pub const VIRTIO_NET_F_GSO: u32 = 1 << 6;
40 pub const VIRTIO_NET_F_GUEST_TSO4: u32 = 1 << 7;
41 pub const VIRTIO_NET_F_GUEST_TSO6: u32 = 1 << 8;
42 pub const VIRTIO_NET_F_GUEST_ECN: u32 = 1 << 9;
43 pub const VIRTIO_NET_F_GUEST_UFO: u32 = 1 << 10;
44 pub const VIRTIO_NET_F_HOST_TSO4: u32 = 1 << 11;
45 pub const VIRTIO_NET_F_HOST_TSO6: u32 = 1 << 12;
46 pub const VIRTIO_NET_F_HOST_ECN: u32 = 1 << 13;
47 pub const VIRTIO_NET_F_HOST_UFO: u32 = 1 << 14;
48 pub const VIRTIO_NET_F_MRG_RXBUF: u32 = 1 << 15;
49 pub const VIRTIO_NET_F_STATUS: u32 = 1 << 16;
50 pub const VIRTIO_NET_F_CTRL_VQ: u32 = 1 << 17;
51 pub const VIRTIO_NET_F_CTRL_RX: u32 = 1 << 18;
52 pub const VIRTIO_NET_F_CTRL_VLAN: u32 = 1 << 19;
53 pub const VIRTIO_NET_F_GUEST_ANNOUNCE: u32 = 1 << 21;
54 pub const VIRTIO_NET_F_MQ: u32 = 1 << 22;
55}
56
57pub mod net_status {
59 pub const VIRTIO_NET_S_LINK_UP: u16 = 1;
60 pub const VIRTIO_NET_S_ANNOUNCE: u16 = 2;
61}
62
63#[repr(C)]
68#[derive(Debug, Clone, Copy, Default)]
69pub struct VirtioNetHeader {
70 pub flags: u8,
71 pub gso_type: u8,
72 pub hdr_len: Le<u16>,
73 pub gso_size: Le<u16>,
74 pub csum_start: Le<u16>,
75 pub csum_offset: Le<u16>,
76 pub num_buffers: Le<u16>,
77}
78
79pub struct VirtioNetDevice {
81 device: VirtioDevice,
82 rx_queue: SpinLock<Virtqueue>,
83 tx_queue: SpinLock<Virtqueue>,
84 mac_address: [u8; 6],
85 pub rx_frames: SpinLock<FixedQueue<(PhysFrame, u8), RX_FRAME_TRACK_CAPACITY>>, }
87
88unsafe impl Send for VirtioNetDevice {}
90unsafe impl Sync for VirtioNetDevice {}
91
92impl VirtioNetDevice {
93 pub unsafe fn new(pci_dev: PciDevice) -> Result<Self, &'static str> {
95 log::info!("VirtIO-net: Initializing device at {:?}", pci_dev.address);
96
97 let device = VirtioDevice::new(pci_dev)?;
99
100 device.reset();
102
103 device.add_status(status::ACKNOWLEDGE as u8);
105
106 device.add_status(status::DRIVER as u8);
108
109 let device_features = device.read_device_features();
111 let needed = features::VIRTIO_NET_F_MAC | features::VIRTIO_NET_F_STATUS;
112 let desired = needed | features::VIRTIO_NET_F_MRG_RXBUF;
117 if device_features & needed != needed {
118 return Err("Device lacks mandatory MAC/STATUS features");
119 }
120 let guest_features = device_features & desired;
121 device.write_guest_features(guest_features);
122
123 device.add_status(status::FEATURES_OK as u8);
125
126 if device.get_status() & (status::FEATURES_OK as u8) == 0 {
128 return Err("Device rejected our feature set");
129 }
130
131 let negotiated = device.read_device_features();
135 if negotiated & features::VIRTIO_NET_F_MRG_RXBUF != 0 {
136 NET_HDR_SIZE.store(mem::size_of::<VirtioNetHeader>(), Ordering::Release);
137 } else {
138 NET_HDR_SIZE.store(10, Ordering::Release);
140 }
141
142 let rx_queue = Virtqueue::new(128)?;
146 let tx_queue = Virtqueue::new(128)?;
147
148 device.setup_queue(0, &rx_queue);
150 device.setup_queue(1, &tx_queue);
151
152 let mut mac_address = [0u8; 6];
155 for i in 0..6 {
156 mac_address[i] = device.read_reg_u8(20 + i as u16);
157 }
158
159 log::info!(
160 "VirtIO-net: MAC address: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
161 mac_address[0],
162 mac_address[1],
163 mac_address[2],
164 mac_address[3],
165 mac_address[4],
166 mac_address[5]
167 );
168
169 device.add_status(status::DRIVER_OK as u8);
171
172 let net_device = Self {
173 device,
174 rx_queue: SpinLock::new(rx_queue),
175 tx_queue: SpinLock::new(tx_queue),
176 mac_address,
177 rx_frames: SpinLock::new(FixedQueue::new()),
178 };
179
180 net_device.refill_rx_queue()?;
182
183 Ok(net_device)
184 }
185
186 fn refill_rx_queue(&self) -> Result<(), &'static str> {
188 let mut rx_queue = self.rx_queue.lock();
189 let mut rx_frames = self.rx_frames.lock();
190
191 let current_filled = rx_frames.len();
193 let target_filled = 64;
194 let mut added = 0usize;
195
196 if current_filled >= target_filled {
197 return Ok(());
198 }
199
200 for _ in 0..(target_filled - current_filled) {
201 let buf_size = NET_HDR_SIZE.load(Ordering::Relaxed) + net::MTU;
203 let buf_pages = (buf_size + 4095) / 4096;
204 let buf_order = buf_pages.next_power_of_two().trailing_zeros() as u8;
205
206 let buf_frame = match crate::sync::with_irqs_disabled(|token| {
207 memory::allocate_phys_contiguous(token, buf_order)
208 }) {
209 Ok(frame) => frame,
210 Err(_) => break, };
212
213 let buf_addr = buf_frame.start_address.as_u64();
214 let virt_addr = crate::memory::phys_to_virt(buf_addr);
215
216 unsafe {
218 ptr::write_bytes(virt_addr as *mut u8, 0, buf_size);
219 }
220
221 match rx_queue.add_buffer(&[(buf_addr, buf_size as u32, true)]) {
223 Ok(_) => {
224 if rx_frames.push_back((buf_frame, buf_order)).is_err() {
225 crate::sync::with_irqs_disabled(|token| {
226 memory::free_phys_contiguous(token, buf_frame, buf_order);
227 });
228 break;
229 }
230 added += 1;
231 }
232 Err(_) => {
233 crate::sync::with_irqs_disabled(|token| {
235 memory::free_phys_contiguous(token, buf_frame, buf_order);
236 });
237 break;
238 }
239 }
240 }
241
242 if rx_queue.should_notify() {
244 self.device.notify_queue(0);
245 }
246
247 if rx_frames.is_empty() && current_filled == 0 && added == 0 {
248 return Err("Failed to allocate RX buffers");
249 }
250
251 Ok(())
252 }
253
254 fn read_link_status(&self) -> u16 {
256 self.device.read_reg_u16(26)
258 }
259}
260
261impl NetworkDevice for VirtioNetDevice {
262 fn name(&self) -> &str {
264 "virtio-net"
265 }
266
267 fn receive(&self, buf: &mut [u8]) -> Result<usize, NetError> {
269 let mut rx_queue = self.rx_queue.lock();
270
271 if !rx_queue.has_used() {
273 let (dev_idx, drv_idx) = rx_queue.used_indices();
274 log::info!(
275 "[vtnet] rx: no used buf (used.idx={}, last_used={})",
276 dev_idx,
277 drv_idx,
278 );
279 return Err(NetError::NoPacket);
280 }
281
282 let hdr_size = NET_HDR_SIZE.load(Ordering::Relaxed);
283 let (token, len) = rx_queue.get_used().ok_or(NetError::NoPacket)?;
284
285 let _desc_index = token as usize;
286 let _desc_table = rx_queue.desc_area(); let (frame, order) = self
289 .rx_frames
290 .lock()
291 .pop_front()
292 .ok_or(NetError::NotReady)?;
293
294 let buf_addr = frame.start_address.as_u64();
295 let virt_addr = crate::memory::phys_to_virt(buf_addr);
296
297 let header_ptr = virt_addr as *const VirtioNetHeader;
298 let data_ptr = (virt_addr + hdr_size as u64) as *const u8;
299
300 let header = unsafe { ptr::read(header_ptr) };
301 let packet_len = (len as usize).saturating_sub(hdr_size);
302
303 let mut dump = [0u8; 16];
305 if packet_len >= 16 {
306 unsafe {
307 ptr::copy_nonoverlapping(data_ptr, dump.as_mut_ptr(), 16);
308 }
309 }
310 log::info!(
311 "[vtnet] rx: token={} len={} hdr={} pkt={} flags={} num_buf={}",
312 token,
313 len,
314 hdr_size,
315 packet_len,
316 header.flags,
317 header.num_buffers,
318 );
319 log::info!(
320 "[vtnet] rx: pkt[0..16] = {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ...",
321 dump[0],
322 dump[1],
323 dump[2],
324 dump[3],
325 dump[4],
326 dump[5],
327 dump[6],
328 dump[7],
329 );
330
331 if buf.len() < packet_len {
332 crate::sync::with_irqs_disabled(|token| {
334 memory::free_phys_contiguous(token, frame, order);
335 });
336 drop(rx_queue);
337 let _ = self.refill_rx_queue();
339 return Err(NetError::BufferTooSmall);
340 }
341
342 if packet_len > 0 {
344 unsafe {
345 ptr::copy_nonoverlapping(data_ptr, buf.as_mut_ptr(), packet_len);
346 }
347 }
348
349 crate::sync::with_irqs_disabled(|token| {
351 memory::free_phys_contiguous(token, frame, order);
352 });
353 drop(rx_queue);
354
355 let _ = self.refill_rx_queue();
357
358 Ok(packet_len)
359 }
360
361 fn transmit(&self, buf: &[u8]) -> Result<(), NetError> {
363 if buf.len() > net::MTU {
364 return Err(NetError::BufferTooSmall);
365 }
366
367 let buf_size = NET_HDR_SIZE.load(Ordering::Relaxed) + buf.len();
369 let buf_pages = (buf_size + 4095) / 4096;
370 let buf_order = buf_pages.next_power_of_two().trailing_zeros() as u8;
371
372 let buf_frame = crate::sync::with_irqs_disabled(|token| {
373 memory::allocate_phys_contiguous(token, buf_order)
374 })
375 .map_err(|_| NetError::NotReady)?;
376
377 let buf_addr = buf_frame.start_address.as_u64();
378 let virt_addr = crate::memory::phys_to_virt(buf_addr);
379
380 let header_ptr = virt_addr as *mut VirtioNetHeader;
381 let data_ptr = (virt_addr + NET_HDR_SIZE.load(Ordering::Relaxed) as u64) as *mut u8;
382
383 unsafe {
385 ptr::write(header_ptr, VirtioNetHeader::default());
386 ptr::copy_nonoverlapping(buf.as_ptr(), data_ptr, buf.len());
387 }
388
389 let mut tx_queue = self.tx_queue.lock();
391 let _ = tx_queue
392 .add_buffer(&[(buf_addr, buf_size as u32, false)]) .map_err(|_| {
394 crate::sync::with_irqs_disabled(|token| {
396 memory::free_phys_contiguous(token, buf_frame, buf_order);
397 });
398 NetError::TxQueueFull
399 })?;
400
401 log::info!("[vtnet] tx: submit {} bytes @ {:#x}", buf_size, buf_addr,);
402
403 if tx_queue.should_notify() {
404 self.device.notify_queue(1);
405 }
406 drop(tx_queue);
407
408 Ok(())
415 }
416
417 fn mac_address(&self) -> [u8; 6] {
419 self.mac_address
420 }
421
422 fn link_up(&self) -> bool {
424 let status = self.read_link_status();
425 status & net_status::VIRTIO_NET_S_LINK_UP != 0
426 }
427}
428
429static VIRTIO_NET: SpinRwLock<Option<Arc<VirtioNetDevice>>> = SpinRwLock::new(None);
431
432pub fn init() {
434 log::info!("VirtIO-net: Scanning for devices...");
435
436 let pci_dev = match pci::probe_first(pci::ProbeCriteria {
439 vendor_id: Some(pci::vendor::VIRTIO),
440 device_id: Some(pci::device::VIRTIO_NET),
441 class_code: Some(pci::class::NETWORK),
442 subclass: Some(pci::net_subclass::ETHERNET),
443 prog_if: None,
444 })
445 .or_else(|| pci::find_virtio_device(pci::device::VIRTIO_NET))
446 {
447 Some(dev) => dev,
448 None => {
449 log::warn!("VirtIO-net: No network device found");
450 return;
451 }
452 };
453
454 match unsafe { VirtioNetDevice::new(pci_dev) } {
455 Ok(device) => {
456 let arc = Arc::new(device);
457 *VIRTIO_NET.write() = Some(arc.clone());
458 net::register_device(arc);
459 }
460 Err(e) => {
461 log::error!("VirtIO-net: Failed to initialize device: {}", e);
462 }
463 }
464}
465
466pub fn get_device() -> Option<Arc<VirtioNetDevice>> {
468 VIRTIO_NET.read().clone()
469}