Skip to main content

native_v86_core/
native_devices.rs

1use crate::cpu::memory;
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4use std::sync::{Mutex, OnceLock};
5
6const DESC_NEXT: u16 = 1;
7const DESC_WRITE: u16 = 2;
8const VIRTIO_9P_COMMON: i32 = 0xA800;
9const VIRTIO_9P_NOTIFY: i32 = 0xA900;
10const VIRTIO_9P_ISR: i32 = 0xA700;
11const VIRTIO_9P_CONFIG: i32 = 0xA600;
12const PCI_CONFIG_ADDRESS: i32 = 0xCF8;
13const PCI_CONFIG_DATA: i32 = 0xCFC;
14
15#[derive(Clone, Default)]
16struct Queue {
17    size: u16,
18    enabled: bool,
19    desc: u32,
20    avail: u32,
21    avail_last: u16,
22    used: u32,
23    staged: u16,
24}
25
26#[derive(Clone, Default)]
27struct Fid {
28    path: PathBuf,
29    opened: bool,
30}
31
32struct Virtio9p {
33    queue: Queue,
34    status: u8,
35    isr: u8,
36    device_feature_select: u32,
37    driver_feature_select: u32,
38    driver_feature: u32,
39    queue_select: u16,
40    tag: Vec<u8>,
41    root: Option<PathBuf>,
42    fids: HashMap<u32, Fid>,
43}
44
45impl Default for Virtio9p {
46    fn default() -> Self {
47        Self {
48            queue: Queue {
49                size: 32,
50                ..Queue::default()
51            },
52            status: 0,
53            isr: 0,
54            device_feature_select: 0,
55            driver_feature_select: 0,
56            driver_feature: 0,
57            queue_select: 0,
58            tag: b"host9p".to_vec(),
59            root: None,
60            fids: HashMap::new(),
61        }
62    }
63}
64
65#[derive(Default)]
66struct DeviceBus {
67    ninep: Virtio9p,
68    pci_address: u32,
69}
70static BUS: OnceLock<Mutex<DeviceBus>> = OnceLock::new();
71
72fn bus() -> &'static Mutex<DeviceBus> {
73    BUS.get_or_init(|| {
74        Mutex::new(DeviceBus {
75            ninep: Virtio9p::default(),
76            pci_address: 0,
77        })
78    })
79}
80
81pub fn set_9p_root(path: impl AsRef<Path>) -> Result<(), String> {
82    let path = path
83        .as_ref()
84        .canonicalize()
85        .map_err(|e| format!("9p root {}: {e}", path.as_ref().display()))?;
86    let mut b = bus().lock().map_err(|_| "device bus poisoned".to_owned())?;
87    b.ninep.root = Some(path.clone());
88    b.ninep.fids.insert(
89        0,
90        Fid {
91            path,
92            opened: false,
93        },
94    );
95    Ok(())
96}
97
98pub fn restore_state(state: &serde_json::Value, buffers: &[Vec<u8>]) -> Result<(), String> {
99    let slots = state.as_array().ok_or("v86 state is not an array")?;
100    let mut b = bus().lock().map_err(|_| "device bus poisoned".to_owned())?;
101    let Some(s) = slots.get(45).and_then(|v| v.as_array()) else {
102        return Ok(());
103    };
104    if let Some(tag) = s.get(0).and_then(|v| v.as_array()) {
105        b.ninep.tag = tag
106            .iter()
107            .filter_map(|v| v.as_u64().map(|x| x as u8))
108            .collect();
109    }
110    if let Some(v) = s.get(2).and_then(|v| v.as_array()) {
111        if let Some(q) = v.get(10).and_then(|v| v.as_array()) {
112            b.ninep.queue.size = q.first().and_then(|v| v.as_u64()).unwrap_or(32) as u16;
113            b.ninep.queue.enabled = q.get(2).and_then(|v| v.as_bool()).unwrap_or(false);
114            b.ninep.queue.desc = q.get(4).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
115            b.ninep.queue.avail = q.get(5).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
116            b.ninep.queue.avail_last = q.get(6).and_then(|v| v.as_u64()).unwrap_or(0) as u16;
117            b.ninep.queue.used = q.get(7).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
118        }
119    }
120    b.ninep.fids.clear();
121    // A restored v86 fid stores an inode number, not a host pathname.  The
122    // native backend cannot safely reconstruct arbitrary inode paths, so use
123    // the configured host root for the attach fid and let subsequent Twalk
124    // requests resolve real path components below it.
125    if let Some(root) = b.ninep.root.clone() {
126        b.ninep.fids.insert(
127            0,
128            Fid {
129                path: root,
130                opened: false,
131            },
132        );
133    } else if let Some(fids) = s.get(8).and_then(|v| v.as_array()) {
134        // Without a host root, retain a diagnostic placeholder rather than
135        // silently claiming that a synthetic inode path is readable.
136        for (id, fid) in fids.iter().enumerate() {
137            let inode = fid.get(0).and_then(|v| v.as_i64()).unwrap_or(0);
138            b.ninep.fids.insert(
139                id as u32,
140                Fid {
141                    path: PathBuf::from(format!("inode-{inode}")),
142                    opened: false,
143                },
144            );
145        }
146    }
147    let _ = buffers;
148    Ok(())
149}
150
151pub fn io_read8(port: i32) -> Option<i32> {
152    if (VIRTIO_9P_ISR..VIRTIO_9P_ISR + 4).contains(&port) {
153        let value = {
154            let mut b = bus().lock().ok()?;
155            let value = b.ninep.isr;
156            b.ninep.isr = 0;
157            value
158        };
159        unsafe { crate::cpu::cpu::device_lower_irq(9) };
160        return Some(value as i32);
161    }
162    let b = bus().lock().ok()?;
163    if (PCI_CONFIG_ADDRESS..PCI_CONFIG_ADDRESS + 4).contains(&port) {
164        return Some(((b.pci_address >> (8 * (port - PCI_CONFIG_ADDRESS))) & 0xFF) as i32);
165    }
166    if (PCI_CONFIG_DATA..PCI_CONFIG_DATA + 4).contains(&port) {
167        let value = pci_config_read(b.pci_address);
168        return Some(((value >> (8 * (port - PCI_CONFIG_DATA))) & 0xFF) as i32);
169    }
170    if (VIRTIO_9P_CONFIG..VIRTIO_9P_CONFIG + 8).contains(&port) {
171        let off = port - VIRTIO_9P_CONFIG;
172        return Some(if off < b.ninep.tag.len() as i32 {
173            b.ninep.tag[off as usize] as i32
174        } else {
175            0
176        });
177    }
178    None
179}
180
181pub fn mmio_read8(addr: u32) -> Option<i32> {
182    io_read8(addr as i32)
183}
184
185pub fn mmio_read32(addr: u32) -> Option<i32> {
186    io_read32(addr as i32)
187}
188
189pub fn mmio_write8(addr: u32, value: i32) -> bool {
190    io_write8(addr as i32, value)
191}
192
193pub fn mmio_write16(addr: u32, value: i32) -> bool {
194    io_write16(addr as i32, value)
195}
196
197pub fn mmio_write32(addr: u32, value: i32) -> bool {
198    io_write32(addr as i32, value)
199}
200
201pub fn io_read16(port: i32) -> Option<i32> {
202    let b = bus().lock().ok()?;
203    if (VIRTIO_9P_COMMON..VIRTIO_9P_COMMON + 0x40).contains(&port) {
204        let off = port - VIRTIO_9P_COMMON;
205        return Some(match off {
206            20 => b.ninep.status as i32,
207            22 => b.ninep.queue_select as i32,
208            24 => b.ninep.queue.size as i32,
209            26 => 0xFFFF,
210            28 => b.ninep.queue.enabled as i32,
211            30 => 0,
212            _ => 0,
213        });
214    }
215    if (PCI_CONFIG_ADDRESS..PCI_CONFIG_ADDRESS + 3).contains(&port) {
216        return Some(((b.pci_address >> (8 * (port - PCI_CONFIG_ADDRESS))) & 0xFFFF) as i32);
217    }
218    if (PCI_CONFIG_DATA..PCI_CONFIG_DATA + 3).contains(&port) {
219        let value = pci_config_read(b.pci_address);
220        return Some(((value >> (8 * (port - PCI_CONFIG_DATA))) & 0xFFFF) as i32);
221    }
222    drop(b);
223    io_read32(port)
224}
225
226pub fn io_read32(port: i32) -> Option<i32> {
227    let b = bus().lock().ok()?;
228    if (VIRTIO_9P_COMMON..VIRTIO_9P_COMMON + 0x40).contains(&port) {
229        let off = port - VIRTIO_9P_COMMON;
230        return Some(match off {
231            0 => 0,
232            4 => 0,
233            8 => b.ninep.driver_feature_select as i32,
234            12 => b.ninep.driver_feature as i32,
235            20 => b.ninep.status as i32,
236            24 => b.ninep.queue.size as i32,
237            32 => b.ninep.queue.desc as i32,
238            40 => b.ninep.queue.avail as i32,
239            48 => b.ninep.queue.used as i32,
240            _ => 0,
241        });
242    }
243    if port == PCI_CONFIG_DATA {
244        return Some(pci_config_read(b.pci_address) as i32);
245    }
246    if (VIRTIO_9P_COMMON..VIRTIO_9P_COMMON + 0x100).contains(&port) {
247        let off = port - VIRTIO_9P_COMMON;
248        return Some(match off {
249            0 => 0,
250            4 => 0,
251            8 => 0,
252            12 => 0,
253            20 => b.ninep.status as i32,
254            24 => 1,
255            _ => 0,
256        });
257    }
258    None
259}
260
261pub fn io_write8(port: i32, value: i32) -> bool {
262    if port == VIRTIO_9P_ISR {
263        if let Ok(mut b) = bus().lock() {
264            b.ninep.isr = 0;
265        }
266        return true;
267    }
268    if (VIRTIO_9P_COMMON..VIRTIO_9P_COMMON + 0x100).contains(&port) {
269        if let Ok(mut b) = bus().lock() {
270            if port - VIRTIO_9P_COMMON == 20 {
271                b.ninep.status = value as u8;
272            }
273        }
274        return true;
275    }
276    false
277}
278
279pub fn io_write16(port: i32, value: i32) -> bool {
280    if port == VIRTIO_9P_NOTIFY {
281        process_queue();
282        return true;
283    }
284    if let Ok(mut b) = bus().lock() {
285        if (VIRTIO_9P_COMMON..VIRTIO_9P_COMMON + 0x40).contains(&port) {
286            match port - VIRTIO_9P_COMMON {
287                22 => b.ninep.queue_select = value as u16,
288                24 => b.ninep.queue.size = (value as u16).min(32),
289                28 => b.ninep.queue.enabled = value as u16 == 1,
290                _ => {}
291            }
292            return true;
293        }
294    }
295    false
296}
297
298pub fn io_write32(port: i32, value: i32) -> bool {
299    if let Ok(mut b) = bus().lock() {
300        if port == PCI_CONFIG_ADDRESS {
301            b.pci_address = value as u32;
302            return true;
303        }
304        if (VIRTIO_9P_COMMON..VIRTIO_9P_COMMON + 0x40).contains(&port) {
305            match port - VIRTIO_9P_COMMON {
306                0 => b.ninep.device_feature_select = value as u32,
307                8 => b.ninep.driver_feature_select = value as u32,
308                12 => b.ninep.driver_feature = value as u32,
309                32 => b.ninep.queue.desc = value as u32,
310                40 => b.ninep.queue.avail = value as u32,
311                48 => b.ninep.queue.used = value as u32,
312                _ => {}
313            }
314            return true;
315        }
316    }
317    io_write16(port, 0)
318}
319
320fn pci_config_read(address: u32) -> u32 {
321    if address & 0x8000_0000 == 0 || (address >> 11) & 0x1F != 0 {
322        return 0xFFFF_FFFF;
323    }
324    let offset = ((address >> 2) & 0x3F) * 4;
325    match offset {
326        0x00 => 0x1009_1AF4,
327        0x08 => 0x0180_0000,
328        0x10 => 0x0000_A001,
329        0x14 => 0x0000_A101,
330        0x18 => 0x0000_A201,
331        0x1C => 0x0000_A301,
332        0x34 => 0x0000_0040,
333        // VirtIO common configuration capability: BAR0, offset 0, size 0x38.
334        0x40 => 0x0110_5009,
335        0x44 => 0,
336        0x48 => 0,
337        0x4C => 0x0000_0038,
338        // VirtIO notification capability: BAR1, offset 0, size 0x20,
339        // notify_off_multiplier = 4.
340        0x50 => 0x0214_6009,
341        0x54 => 1,
342        0x58 => 0,
343        0x5C => 0x0000_0020,
344        0x60 => 4,
345        // VirtIO ISR capability: BAR2, offset 0, one-byte region.
346        0x64 => 0x0310_7409,
347        0x68 => 2,
348        0x6C => 0,
349        0x70 => 1,
350        // VirtIO device-specific capability: BAR3, offset 0.
351        0x74 => 0x0410_8409,
352        0x78 => 3,
353        0x7C => 0,
354        0x80 => 0x0000_0100,
355        // Terminating PCI configuration capability.
356        0x84 => 0x0510_0009,
357        0x88 => 0,
358        0x8C => 0,
359        0x90 => 0,
360        0x94 => 0,
361        0x98 => 0,
362        0x9C => 0,
363        0xA0 => 0,
364        0xA4 => 0,
365        0xA8 => 0,
366        0xAC => 0,
367        0xB0 => 0,
368        0xB4 => 0,
369        0xB8 => 0,
370        0xBC => 0,
371        0xC0 => 0,
372        0xC4 => 0,
373        0xC8 => 0,
374        0xCC => 0,
375        0xD0 => 0,
376        0xD4 => 0,
377        0xD8 => 0,
378        0xDC => 0,
379        0xE0 => 0,
380        0xE4 => 0,
381        0xE8 => 0,
382        0xEC => 0,
383        0xF0 => 0,
384        0xF4 => 0,
385        0xF8 => 0,
386        0xFC => 0,
387        _ => 0,
388    }
389}
390
391fn read8(addr: u32) -> u8 {
392    unsafe { memory::read8_no_mmap_check(addr) as u8 }
393}
394fn read16(addr: u32) -> u16 {
395    unsafe { memory::read16_no_mmap_check(addr) as u16 }
396}
397fn read32(addr: u32) -> u32 {
398    unsafe { memory::read32_no_mmap_check(addr) as u32 }
399}
400fn write16(addr: u32, value: u16) {
401    unsafe { memory::write16_no_mmap_or_dirty_check(addr, value as i32) }
402}
403fn write32(addr: u32, value: u32) {
404    unsafe { memory::write32_no_mmap_or_dirty_check(addr, value as i32) }
405}
406
407fn process_queue() {
408    let mut b = match bus().lock() {
409        Ok(x) => x,
410        Err(_) => return,
411    };
412    let mut q = b.ninep.queue.clone();
413    if !q.enabled || q.desc == 0 || q.avail == 0 || q.used == 0 {
414        return;
415    }
416    let avail_idx = read16(q.avail + 2);
417    while q.avail_last != avail_idx {
418        let head = read16(q.avail + 4 + 2u32 * (q.avail_last & q.size.saturating_sub(1)) as u32);
419        let mut request = Vec::new();
420        let mut writable = Vec::new();
421        let mut idx = head;
422        for _ in 0..q.size {
423            let p = q.desc + idx as u32 * 16;
424            let addr = read32(p);
425            let len = read32(p + 8);
426            let flags = read16(p + 12);
427            let next = read16(p + 14);
428            let mut buf = vec![0u8; len as usize];
429            for (i, x) in buf.iter_mut().enumerate() {
430                *x = read8(addr + i as u32);
431            }
432            if flags & DESC_WRITE != 0 {
433                writable.push((addr, len));
434            } else {
435                request.extend_from_slice(&buf);
436            }
437            if flags & DESC_NEXT == 0 {
438                break;
439            }
440            idx = next;
441        }
442        let reply = handle_9p(&mut b.ninep, &request);
443        let mut pos = 0usize;
444        let mut written = 0u32;
445        for (addr, len) in writable {
446            let n = (len as usize).min(reply.len().saturating_sub(pos));
447            for i in 0..n {
448                unsafe {
449                    memory::write8_no_mmap_or_dirty_check(addr + i as u32, reply[pos + i] as i32);
450                }
451            }
452            pos += n;
453            written += n as u32;
454        }
455        let used_idx = read16(q.used + 2);
456        let ring_off = 8u32 * (used_idx & (q.size - 1)) as u32;
457        write32(q.used + 4 + ring_off, head as u32);
458        write32(q.used + 8 + ring_off, written);
459        write16(q.used + 2, used_idx.wrapping_add(1));
460        q.avail_last = q.avail_last.wrapping_add(1);
461        b.ninep.isr |= 1;
462        // v86 routes the legacy VirtIO 9P interrupt through IRQ9.  Use the
463        // CPU's combined PIC/IOAPIC path so an HLT guest is woken correctly.
464        unsafe { crate::cpu::cpu::device_raise_irq(9) };
465    }
466    b.ninep.queue.avail_last = q.avail_last;
467}
468
469fn u16_at(x: &[u8], p: &mut usize) -> u16 {
470    let v = u16::from_le_bytes([x[*p], x[*p + 1]]);
471    *p += 2;
472    v
473}
474fn u32_at(x: &[u8], p: &mut usize) -> u32 {
475    let v = u32::from_le_bytes(x[*p..*p + 4].try_into().unwrap());
476    *p += 4;
477    v
478}
479fn u64_at(x: &[u8], p: &mut usize) -> u64 {
480    let v = u64::from_le_bytes(x[*p..*p + 8].try_into().unwrap());
481    *p += 8;
482    v
483}
484fn string_at(x: &[u8], p: &mut usize) -> String {
485    let n = u16_at(x, p) as usize;
486    let e = (*p + n).min(x.len());
487    let s = String::from_utf8_lossy(&x[*p..e]).into_owned();
488    *p = e;
489    s
490}
491fn string_put(out: &mut Vec<u8>, value: &str) {
492    let bytes = value.as_bytes();
493    let len = bytes.len().min(u16::MAX as usize) as u16;
494    out.extend_from_slice(&len.to_le_bytes());
495    out.extend_from_slice(&bytes[..len as usize]);
496}
497fn reply(id: u8, tag: u16, payload: &[u8]) -> Vec<u8> {
498    let mut r = Vec::with_capacity(payload.len() + 7);
499    r.extend_from_slice(&((payload.len() + 7) as u32).to_le_bytes());
500    r.push(id + 1);
501    r.extend_from_slice(&tag.to_le_bytes());
502    r.extend_from_slice(payload);
503    r
504}
505
506fn append_qid(out: &mut Vec<u8>, path: &Path, metadata: &std::fs::Metadata) {
507    use std::hash::{Hash, Hasher};
508    let mut hasher = std::collections::hash_map::DefaultHasher::new();
509    path.to_string_lossy().hash(&mut hasher);
510    let qid_path = hasher.finish();
511    let qid_type = if metadata.is_dir() { 0x80 } else { 0 };
512    out.push(qid_type);
513    out.extend_from_slice(&0u32.to_le_bytes());
514    out.extend_from_slice(&qid_path.to_le_bytes());
515}
516
517fn append_getattr(out: &mut Vec<u8>, path: &Path, metadata: &std::fs::Metadata) {
518    // Rgetattr payload after the request mask: valid, qid, mode, uid, gid,
519    // nlink, rdev, size, blksize, blocks, four timestamp pairs, generation,
520    // and data_version.  The native backend exposes conservative portable
521    // values for uid/gid/timestamps while preserving mode and file size.
522    out.extend_from_slice(&0x1FFFu64.to_le_bytes());
523    append_qid(out, path, metadata);
524    let mut mode = if metadata.permissions().readonly() {
525        0o444u32
526    } else {
527        0o666u32
528    };
529    if metadata.is_dir() {
530        mode = 0o755;
531        mode |= 0x8000_0000;
532    }
533    out.extend_from_slice(&mode.to_le_bytes());
534    out.extend_from_slice(&0u32.to_le_bytes());
535    out.extend_from_slice(&0u32.to_le_bytes());
536    out.extend_from_slice(&1u64.to_le_bytes());
537    out.extend_from_slice(&0u64.to_le_bytes());
538    out.extend_from_slice(&metadata.len().to_le_bytes());
539    out.extend_from_slice(&8192u64.to_le_bytes());
540    out.extend_from_slice(&metadata.len().div_ceil(512).to_le_bytes());
541    for _ in 0..8 {
542        out.extend_from_slice(&0u64.to_le_bytes());
543    }
544}
545
546fn handle_9p(dev: &mut Virtio9p, req: &[u8]) -> Vec<u8> {
547    if req.len() < 7 {
548        return reply(6, 0, &2u32.to_le_bytes());
549    }
550    let mut p = 4;
551    let id = req[p];
552    p += 1;
553    let tag = u16_at(req, &mut p);
554    match id {
555        100 => {
556            let m = u32_at(req, &mut p);
557            let _version = string_at(req, &mut p);
558            let mut out = Vec::new();
559            out.extend_from_slice(&m.min(8192).to_le_bytes());
560            out.extend_from_slice(&6u16.to_le_bytes());
561            out.extend_from_slice(b"9P2000.L");
562            reply(id, tag, &out)
563        }
564        104 => {
565            let fid = u32_at(req, &mut p);
566            let _afid = u32_at(req, &mut p);
567            let _uname = string_at(req, &mut p);
568            let _aname = string_at(req, &mut p);
569            let root = dev.root.clone().unwrap_or_else(|| PathBuf::from("."));
570            dev.fids.insert(
571                fid,
572                Fid {
573                    path: root,
574                    opened: false,
575                },
576            );
577            let mut out = vec![0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
578            reply(id, tag, &out)
579        }
580        110 => {
581            let fid = u32_at(req, &mut p);
582            let newfid = u32_at(req, &mut p);
583            let nwname = u16_at(req, &mut p);
584            let base = dev
585                .fids
586                .get(&fid)
587                .map(|f| f.path.clone())
588                .unwrap_or_default();
589            let mut path = base;
590            let mut qids = Vec::new();
591            for _ in 0..nwname {
592                let name = string_at(req, &mut p);
593                path.push(&name);
594                if !path.exists() {
595                    return reply(6, tag, &2u32.to_le_bytes());
596                }
597                qids.extend_from_slice(&[0u8; 13]);
598            }
599            dev.fids.insert(
600                newfid,
601                Fid {
602                    path,
603                    opened: false,
604                },
605            );
606            let mut out = (nwname as u16).to_le_bytes().to_vec();
607            out.extend_from_slice(&qids);
608            reply(id, tag, &out)
609        }
610        12 | 112 => {
611            let fid = u32_at(req, &mut p);
612            dev.fids.entry(fid).or_default().opened = true;
613            let mut out = vec![0u8; 13];
614            out.extend_from_slice(&8192u32.to_le_bytes());
615            reply(id, tag, &out)
616        }
617        24 => {
618            let fid = u32_at(req, &mut p);
619            let _request_mask = u64_at(req, &mut p);
620            let Some(path) = dev.fids.get(&fid).map(|f| f.path.clone()) else {
621                return reply(6, tag, &2u32.to_le_bytes());
622            };
623            let Ok(metadata) = std::fs::symlink_metadata(&path) else {
624                return reply(6, tag, &2u32.to_le_bytes());
625            };
626            let mut out = Vec::with_capacity(200);
627            append_getattr(&mut out, &path, &metadata);
628            reply(id, tag, &out)
629        }
630        40 => {
631            let fid = u32_at(req, &mut p);
632            let offset = u64_at(req, &mut p) as usize;
633            let count = u32_at(req, &mut p) as usize;
634            let Some(path) = dev.fids.get(&fid).map(|f| f.path.clone()) else {
635                return reply(6, tag, &2u32.to_le_bytes());
636            };
637            let Ok(entries) = std::fs::read_dir(&path) else {
638                return reply(6, tag, &2u32.to_le_bytes());
639            };
640            let mut payload = Vec::new();
641            for (index, entry) in entries.flatten().enumerate().skip(offset) {
642                let entry_path = entry.path();
643                let Ok(metadata) = entry.metadata() else {
644                    continue;
645                };
646                let mut item = Vec::new();
647                append_qid(&mut item, &entry_path, &metadata);
648                item.extend_from_slice(&((index + 1) as u64).to_le_bytes());
649                item.push(if metadata.is_dir() { 4 } else { 8 });
650                let name = entry.file_name().to_string_lossy().into_owned();
651                string_put(&mut item, &name);
652                if payload.len() + item.len() > count {
653                    break;
654                }
655                payload.extend_from_slice(&item);
656            }
657            let mut out = (payload.len() as u32).to_le_bytes().to_vec();
658            out.extend_from_slice(&payload);
659            reply(id, tag, &out)
660        }
661        120 => {
662            let fid = u32_at(req, &mut p);
663            dev.fids.remove(&fid);
664            reply(id, tag, &[])
665        }
666        8 => {
667            let mut out = Vec::new();
668            out.extend_from_slice(&0x01021997u32.to_le_bytes());
669            out.extend_from_slice(&8192u32.to_le_bytes());
670            out.extend_from_slice(&1_000_000u64.to_le_bytes());
671            out.extend_from_slice(&900_000u64.to_le_bytes());
672            out.extend_from_slice(&900_000u64.to_le_bytes());
673            out.extend_from_slice(&1_000_000u64.to_le_bytes());
674            out.extend_from_slice(&900_000u64.to_le_bytes());
675            out.extend_from_slice(&0u64.to_le_bytes());
676            out.extend_from_slice(&256u16.to_le_bytes());
677            reply(id, tag, &out)
678        }
679        116 => {
680            let fid = u32_at(req, &mut p);
681            let off = u64::from_le_bytes(req[p..p + 8].try_into().unwrap());
682            p += 8;
683            let count = u32_at(req, &mut p) as usize;
684            let path = dev.fids.get(&fid).map(|f| f.path.clone());
685            let mut data = Vec::new();
686            if let Some(path) = path {
687                if let Ok(mut f) = std::fs::File::open(path) {
688                    use std::io::{Read, Seek};
689                    let _ = f.seek(std::io::SeekFrom::Start(off));
690                    let _ = f.take(count as u64).read_to_end(&mut data);
691                }
692            }
693            let mut out = (data.len() as u32).to_le_bytes().to_vec();
694            out.extend_from_slice(&data);
695            reply(id, tag, &out)
696        }
697        _ => reply(6, tag, &2u32.to_le_bytes()),
698    }
699}
700
701#[cfg(test)]
702mod tests {
703    use super::*;
704
705    fn request(id: u8, tag: u16, payload: &[u8]) -> Vec<u8> {
706        let mut r = Vec::new();
707        r.extend_from_slice(&((payload.len() + 7) as u32).to_le_bytes());
708        r.push(id);
709        r.extend_from_slice(&tag.to_le_bytes());
710        r.extend_from_slice(payload);
711        r
712    }
713    fn string(value: &str) -> Vec<u8> {
714        let mut r = (value.len() as u16).to_le_bytes().to_vec();
715        r.extend_from_slice(value.as_bytes());
716        r
717    }
718
719    #[test]
720    fn ninep_host_directory_round_trip() {
721        let root = std::env::temp_dir().join(format!("x86-native-9p-{}", std::process::id()));
722        let _ = std::fs::remove_dir_all(&root);
723        std::fs::create_dir_all(&root).unwrap();
724        std::fs::write(root.join("hello.txt"), b"hello").unwrap();
725        let mut dev = Virtio9p {
726            root: Some(root.clone()),
727            ..Virtio9p::default()
728        };
729
730        let mut version = 8192u32.to_le_bytes().to_vec();
731        version.extend_from_slice(&string("9P2000.L"));
732        assert_eq!(handle_9p(&mut dev, &request(100, 1, &version))[4], 101);
733
734        let mut attach = 0u32.to_le_bytes().to_vec();
735        attach.extend_from_slice(&u32::MAX.to_le_bytes());
736        attach.extend_from_slice(&string("root"));
737        attach.extend_from_slice(&string(""));
738        assert_eq!(handle_9p(&mut dev, &request(104, 2, &attach))[4], 105);
739
740        let mut walk = 0u32.to_le_bytes().to_vec();
741        walk.extend_from_slice(&1u32.to_le_bytes());
742        walk.extend_from_slice(&1u16.to_le_bytes());
743        walk.extend_from_slice(&string("hello.txt"));
744        assert_eq!(handle_9p(&mut dev, &request(110, 3, &walk))[4], 111);
745
746        let mut open = 1u32.to_le_bytes().to_vec();
747        open.extend_from_slice(&0u32.to_le_bytes());
748        assert_eq!(handle_9p(&mut dev, &request(12, 4, &open))[4], 13);
749
750        let mut read = 1u32.to_le_bytes().to_vec();
751        read.extend_from_slice(&0u64.to_le_bytes());
752        read.extend_from_slice(&5u32.to_le_bytes());
753        let response = handle_9p(&mut dev, &request(116, 5, &read));
754        assert_eq!(response[4], 117);
755        assert_eq!(&response[7 + 4..7 + 9], b"hello");
756        let mut getattr = 0u32.to_le_bytes().to_vec();
757        getattr.extend_from_slice(&0x1FFFu64.to_le_bytes());
758        let getattr_response = handle_9p(&mut dev, &request(24, 6, &getattr));
759        assert_eq!(getattr_response[4], 25);
760        assert!(getattr_response.len() >= 7 + 8 + 13 + 4);
761
762        let mut readdir = 0u32.to_le_bytes().to_vec();
763        readdir.extend_from_slice(&0u64.to_le_bytes());
764        readdir.extend_from_slice(&4096u32.to_le_bytes());
765        let readdir_response = handle_9p(&mut dev, &request(40, 7, &readdir));
766        assert_eq!(readdir_response[4], 41);
767        let readdir_count = u32::from_le_bytes(readdir_response[7..11].try_into().unwrap());
768        assert!(readdir_count > 0);
769
770        let clunk = 1u32.to_le_bytes().to_vec();
771        assert_eq!(handle_9p(&mut dev, &request(120, 8, &clunk))[4], 121);
772        let _ = std::fs::remove_dir_all(root);
773    }
774}
775
776#[cfg(test)]
777mod pci_tests {
778    use super::*;
779
780    #[test]
781    fn pci_config_exposes_virtio_9p_identity() {
782        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0000u32 as i32).to_owned());
783        let id = io_read32(PCI_CONFIG_DATA).expect("PCI data port");
784        assert_eq!(id as u32, 0x1009_1AF4);
785        assert_eq!(io_read16(PCI_CONFIG_DATA).unwrap() as u16, 0x1AF4);
786        assert_eq!(io_read8(PCI_CONFIG_DATA).unwrap() as u8, 0xF4);
787
788        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0010u32 as i32).to_owned());
789        let bar = io_read32(PCI_CONFIG_DATA).expect("PCI BAR");
790        assert_eq!(bar as u32, 0x0000_A001);
791
792        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0034u32 as i32));
793        assert_eq!(io_read32(PCI_CONFIG_DATA).unwrap() as u32, 0x40);
794        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0040u32 as i32));
795        assert_eq!(io_read32(PCI_CONFIG_DATA).unwrap() as u32, 0x0110_5009);
796        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0050u32 as i32));
797        assert_eq!(io_read32(PCI_CONFIG_DATA).unwrap() as u32, 0x0214_6009);
798        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0054u32 as i32));
799        assert_eq!(io_read32(PCI_CONFIG_DATA).unwrap() as u32, 1);
800        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0064u32 as i32));
801        assert_eq!(io_read32(PCI_CONFIG_DATA).unwrap() as u32, 0x0310_7409);
802        assert!(io_write32(PCI_CONFIG_ADDRESS, 0x8000_0074u32 as i32));
803        assert_eq!(io_read32(PCI_CONFIG_DATA).unwrap() as u32, 0x0410_8409);
804    }
805
806    #[test]
807    fn virtio_common_queue_registers_round_trip() {
808        assert!(io_write16(VIRTIO_9P_COMMON + 22, 0));
809        assert!(io_write16(VIRTIO_9P_COMMON + 24, 16));
810        assert!(io_write32(VIRTIO_9P_COMMON + 32, 0x0010_0000));
811        assert!(io_write32(VIRTIO_9P_COMMON + 40, 0x0010_0200));
812        assert!(io_write32(VIRTIO_9P_COMMON + 48, 0x0010_0280));
813        assert_eq!(io_read16(VIRTIO_9P_COMMON + 24).unwrap(), 16);
814        assert_eq!(
815            io_read32(VIRTIO_9P_COMMON + 32).unwrap() as u32,
816            0x0010_0000
817        );
818        assert_eq!(
819            io_read32(VIRTIO_9P_COMMON + 40).unwrap() as u32,
820            0x0010_0200
821        );
822        assert_eq!(
823            io_read32(VIRTIO_9P_COMMON + 48).unwrap() as u32,
824            0x0010_0280
825        );
826        assert!(io_write16(VIRTIO_9P_COMMON + 28, 1));
827        assert_eq!(io_read16(VIRTIO_9P_COMMON + 28).unwrap(), 1);
828    }
829}