Skip to main content

native_v86_core/
native_runtime.rs

1use crate::cpu::{apic, cpu, global_pointers, ioapic, memory, pic};
2use crate::native_devices;
3use std::collections::VecDeque;
4use std::io::Write;
5use std::sync::{Mutex, OnceLock};
6use std::time::Instant;
7
8static START: OnceLock<Instant> = OnceLock::new();
9static UART0: OnceLock<Mutex<UartState>> = OnceLock::new();
10
11#[derive(Default)]
12struct UartState {
13    ints: u8,
14    baud_rate: u16,
15    line_control: u8,
16    lsr: u8,
17    fifo_control: u8,
18    ier: u8,
19    iir: u8,
20    modem_control: u8,
21    modem_status: u8,
22    scratch: u8,
23    irq: u8,
24    input: VecDeque<u8>,
25}
26
27fn uart0() -> &'static Mutex<UartState> {
28    UART0.get_or_init(|| Mutex::new(UartState::default()))
29}
30
31fn uart_read(port: i32) -> i32 {
32    let offset = (port - 0x3F8) as u8;
33    let mut uart = uart0().lock().expect("UART0 mutex poisoned");
34    match offset {
35        0 if uart.line_control & 0x80 != 0 => (uart.baud_rate & 0xFF) as i32,
36        0 => uart.input.pop_front().unwrap_or(0) as i32,
37        1 if uart.line_control & 0x80 != 0 => (uart.baud_rate >> 8) as i32,
38        1 => (uart.ier & 0x0F) as i32,
39        2 => {
40            let fifo = if uart.fifo_control & 1 != 0 { 0xC0 } else { 0 };
41            (uart.iir | fifo) as i32
42        }
43        3 => uart.line_control as i32,
44        4 => uart.modem_control as i32,
45        5 => (uart.lsr | if uart.input.is_empty() { 0 } else { 0x01 }) as i32,
46        6 => uart.modem_status as i32,
47        7 => uart.scratch as i32,
48        _ => 0xFF,
49    }
50}
51
52fn restore_uart_state(state: &[serde_json::Value]) -> Result<(), String> {
53    if state.len() < 11 {
54        return Err(format!(
55            "UART state has {} fields; expected 11",
56            state.len()
57        ));
58    }
59    let mut uart = uart0()
60        .lock()
61        .map_err(|_| "UART0 mutex poisoned".to_owned())?;
62    uart.ints = state[0]
63        .as_i64()
64        .ok_or_else(|| "UART ints is not an integer".to_owned())? as u8;
65    uart.baud_rate = state[1]
66        .as_i64()
67        .ok_or_else(|| "UART baud rate is not an integer".to_owned())? as u16;
68    uart.line_control = state[2]
69        .as_i64()
70        .ok_or_else(|| "UART line control is not an integer".to_owned())?
71        as u8;
72    uart.lsr = state[3]
73        .as_i64()
74        .ok_or_else(|| "UART LSR is not an integer".to_owned())? as u8;
75    uart.fifo_control = state[4]
76        .as_i64()
77        .ok_or_else(|| "UART FIFO control is not an integer".to_owned())?
78        as u8;
79    uart.ier = state[5]
80        .as_i64()
81        .ok_or_else(|| "UART IER is not an integer".to_owned())? as u8;
82    uart.iir = state[6]
83        .as_i64()
84        .ok_or_else(|| "UART IIR is not an integer".to_owned())? as u8;
85    uart.modem_control = state[7]
86        .as_i64()
87        .ok_or_else(|| "UART modem control is not an integer".to_owned())?
88        as u8;
89    uart.modem_status = state[8]
90        .as_i64()
91        .ok_or_else(|| "UART modem status is not an integer".to_owned())?
92        as u8;
93    uart.scratch = state[9]
94        .as_i64()
95        .ok_or_else(|| "UART scratch is not an integer".to_owned())? as u8;
96    uart.irq = state[10]
97        .as_i64()
98        .ok_or_else(|| "UART IRQ is not an integer".to_owned())? as u8;
99    Ok(())
100}
101
102fn uart_write(port: i32, value: i32) {
103    let offset = (port - 0x3F8) as u8;
104    let byte = value as u8;
105    let mut output = None;
106    {
107        let mut uart = uart0().lock().expect("UART0 mutex poisoned");
108        match offset {
109            0 if uart.line_control & 0x80 != 0 => {
110                uart.baud_rate = (uart.baud_rate & 0xFF00) | byte as u16;
111            }
112            0 => output = Some(byte),
113            1 if uart.line_control & 0x80 != 0 => {
114                uart.baud_rate = (uart.baud_rate & 0x00FF) | ((byte as u16) << 8);
115            }
116            1 => uart.ier = byte & 0x0F,
117            2 => uart.fifo_control = byte,
118            3 => uart.line_control = byte,
119            4 => uart.modem_control = byte,
120            7 => uart.scratch = byte,
121            _ => {}
122        }
123    }
124    if let Some(byte) = output {
125        let mut stdout = std::io::stdout().lock();
126        let _ = stdout.write_all(&[byte]);
127        let _ = stdout.flush();
128    }
129}
130
131/// Minimal native host callbacks used by the v86 CPU core.
132/// Device-specific MMIO/port routing is intentionally represented as a small
133/// host surface first; concrete PC devices are added by the outer runtime.
134#[no_mangle]
135pub extern "C" fn cpu_exception_hook(_interrupt: i32) -> bool {
136    false
137}
138
139#[no_mangle]
140pub extern "C" fn microtick() -> f64 {
141    START.get_or_init(Instant::now).elapsed().as_secs_f64() * 1000.0
142}
143
144#[no_mangle]
145pub extern "C" fn run_hardware_timers(_acpi_enabled: bool, _now: f64) -> f64 {
146    0.0
147}
148
149#[no_mangle]
150pub extern "C" fn cpu_event_halt() {}
151
152#[no_mangle]
153pub extern "C" fn stop_idling() {}
154
155#[no_mangle]
156pub extern "C" fn get_rand_int() -> i32 {
157    0x1357_9BDF
158}
159
160#[no_mangle]
161pub extern "C" fn io_port_read8(port: i32) -> i32 {
162    if let Some(value) = native_devices::io_read8(port) {
163        value
164    } else if (0x3F8..=0x3FF).contains(&port) {
165        uart_read(port)
166    } else {
167        0xFF
168    }
169}
170
171#[no_mangle]
172pub extern "C" fn io_port_read16(port: i32) -> i32 {
173    native_devices::io_read16(port).unwrap_or(0xFFFF)
174}
175
176#[no_mangle]
177pub extern "C" fn io_port_read32(port: i32) -> i32 {
178    native_devices::io_read32(port).unwrap_or(-1)
179}
180
181#[no_mangle]
182pub extern "C" fn io_port_write8(port: i32, value: i32) {
183    if !native_devices::io_write8(port, value) && (0x3F8..=0x3FF).contains(&port) {
184        uart_write(port, value);
185    }
186}
187
188#[no_mangle]
189pub extern "C" fn io_port_write16(port: i32, value: i32) {
190    if !native_devices::io_write16(port, value) {}
191}
192
193#[no_mangle]
194pub extern "C" fn io_port_write32(port: i32, value: i32) {
195    if !native_devices::io_write32(port, value) {}
196}
197
198#[no_mangle]
199pub extern "C" fn mmap_read8(addr: u32) -> i32 {
200    native_devices::mmio_read8(addr).unwrap_or(0xFF)
201}
202
203#[no_mangle]
204pub extern "C" fn mmap_read32(addr: u32) -> i32 {
205    native_devices::mmio_read32(addr).unwrap_or(-1)
206}
207
208#[no_mangle]
209pub extern "C" fn mmap_write8(addr: u32, value: i32) {
210    let _ = native_devices::mmio_write8(addr, value);
211}
212
213#[no_mangle]
214pub extern "C" fn mmap_write16(addr: u32, value: i32) {
215    let _ = native_devices::mmio_write16(addr, value);
216}
217
218#[no_mangle]
219pub extern "C" fn mmap_write32(addr: u32, value: i32) {
220    let _ = native_devices::mmio_write32(addr, value);
221}
222
223#[no_mangle]
224pub extern "C" fn mmap_write64(_addr: u32, _v0: i32, _v1: i32) {}
225
226#[no_mangle]
227pub extern "C" fn mmap_write128(_addr: u32, _v0: i32, _v1: i32, _v2: i32, _v3: i32) {}
228
229/// Native CPU state arena and guest memory owner.
230///
231/// v86's scalar CPU state uses the first 4 KiB of the arena. The guest RAM is
232/// allocated by the core memory module and addressed with 32-bit guest physical
233/// addresses, matching the original emulator model.
234pub struct NativeCpu {
235    state_arena: Box<[u8; 4096]>,
236    ram_bytes: u32,
237    vga_bytes: u32,
238    last_timer_tick: Instant,
239}
240
241impl NativeCpu {
242    pub fn new(ram_bytes: u32, vga_bytes: u32) -> Self {
243        assert!(ram_bytes > 0, "RAM size must be non-zero");
244        assert!(vga_bytes > 0, "VGA memory size must be non-zero");
245
246        let mut state_arena = Box::new([0u8; 4096]);
247        unsafe {
248            global_pointers::init(state_arena.as_mut_ptr());
249            let _ = memory::allocate_memory(ram_bytes);
250            let _ = memory::svga_allocate_memory(vga_bytes);
251            *global_pointers::memory_size = ram_bytes;
252            memory::vga_memory_size = vga_bytes;
253            cpu::reset_cpu();
254        }
255
256        Self {
257            state_arena,
258            ram_bytes,
259            vga_bytes,
260            last_timer_tick: Instant::now(),
261        }
262    }
263
264    pub fn ram_bytes(&self) -> u32 {
265        self.ram_bytes
266    }
267
268    pub fn vga_bytes(&self) -> u32 {
269        self.vga_bytes
270    }
271
272    pub fn step(&mut self, max_instructions: u32) -> u32 {
273        unsafe {
274            let halted = *global_pointers::in_hlt;
275            let timer_due = self.last_timer_tick.elapsed() >= std::time::Duration::from_millis(1);
276            if halted || timer_due {
277                let now = microtick();
278                if *global_pointers::acpi_enabled {
279                    let _ = apic::apic_timer(now);
280                    cpu::handle_irqs();
281                } else {
282                    pic::set_irq(0);
283                    cpu::handle_irqs();
284                    pic::clear_irq(0);
285                    cpu::handle_irqs();
286                }
287                self.last_timer_tick = Instant::now();
288            }
289            cpu::main_loop_native_interpreter(max_instructions)
290        }
291    }
292
293    pub fn read_memory(&self, address: u32, output: &mut [u8]) -> bool {
294        if address.checked_add(output.len() as u32).is_none()
295            || address + output.len() as u32 > self.ram_bytes
296        {
297            return false;
298        }
299        unsafe {
300            output.copy_from_slice(std::slice::from_raw_parts(
301                memory::mem8.add(address as usize),
302                output.len(),
303            ));
304        }
305        true
306    }
307
308    pub fn write_memory(&mut self, address: u32, input: &[u8]) -> bool {
309        if address.checked_add(input.len() as u32).is_none()
310            || address + input.len() as u32 > self.ram_bytes
311        {
312            return false;
313        }
314        unsafe {
315            std::slice::from_raw_parts_mut(memory::mem8.add(address as usize), input.len())
316                .copy_from_slice(input);
317        }
318        true
319    }
320
321    pub fn instruction_pointer(&self) -> u32 {
322        unsafe { *global_pointers::instruction_pointer as u32 }
323    }
324
325    pub fn halted(&self) -> bool {
326        unsafe { *global_pointers::in_hlt }
327    }
328
329    pub fn state_arena(&self) -> &[u8; 4096] {
330        &self.state_arena
331    }
332
333    pub fn set_9p_root(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
334        native_devices::set_9p_root(path)
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::NativeCpu;
341
342    #[test]
343    fn native_interpreter_executes_reset_vector_hlt() {
344        let mut cpu = NativeCpu::new(128 * 1024 * 1024, 8 * 1024 * 1024);
345        assert!(cpu.write_memory(0xFFFF0, &[0xF4]));
346        assert_eq!(cpu.instruction_pointer(), 0xFFFF0);
347        assert_eq!(cpu.step(1), 1);
348        assert!(cpu.halted());
349    }
350}
351
352impl NativeCpu {
353    /// Restore the CPU scalar state and packed RAM representation from the
354    /// decoded v86 state object. Device arrays are intentionally left to the
355    /// outer native device graph, but CPU execution can continue after this
356    /// method completes.
357    pub fn restore_v86_state(
358        &mut self,
359        state: &serde_json::Value,
360        buffers: &[Vec<u8>],
361    ) -> Result<(), String> {
362        let slots = state
363            .as_array()
364            .ok_or_else(|| "v86 state is not an array".to_owned())?;
365
366        let memory_size = scalar(slots, 0)? as u32;
367        if memory_size != self.ram_bytes {
368            return Err(format!(
369                "state RAM is {memory_size} bytes, NativeCpu has {} bytes",
370                self.ram_bytes
371            ));
372        }
373
374        let segment_state = buffer_for(slots, buffers, 1)?;
375        if segment_state.len() != 16 {
376            return Err(format!(
377                "state[1] length {} != expected 16",
378                segment_state.len()
379            ));
380        }
381        unsafe {
382            std::slice::from_raw_parts_mut(global_pointers::segment_is_null as *mut u8, 8)
383                .copy_from_slice(&segment_state[..8]);
384            std::slice::from_raw_parts_mut(global_pointers::segment_access_bytes, 8)
385                .copy_from_slice(&segment_state[8..]);
386        }
387        copy_i32_buffer(slots, buffers, 2, unsafe {
388            std::slice::from_raw_parts_mut(global_pointers::segment_offsets as *mut u8, 32)
389        })?;
390        copy_u32_buffer(slots, buffers, 3, unsafe {
391            std::slice::from_raw_parts_mut(global_pointers::segment_limits as *mut u8, 32)
392        })?;
393
394        unsafe {
395            *global_pointers::memory_size = memory_size;
396            *global_pointers::protected_mode = scalar(slots, 4)? != 0;
397            *global_pointers::idtr_offset = scalar(slots, 5)? as i32;
398            *global_pointers::idtr_size = scalar(slots, 6)? as i32;
399            *global_pointers::gdtr_offset = scalar(slots, 7)? as i32;
400            *global_pointers::gdtr_size = scalar(slots, 8)? as i32;
401        }
402        copy_i32_buffer(slots, buffers, 10, unsafe {
403            std::slice::from_raw_parts_mut(global_pointers::cr as *mut u8, 32)
404        })?;
405        unsafe {
406            *global_pointers::cpl = scalar(slots, 11)? as u8;
407            *global_pointers::is_32 = scalar(slots, 13)? != 0;
408            *global_pointers::stack_size_32 = scalar(slots, 16)? != 0;
409            *global_pointers::in_hlt = scalar(slots, 17)? != 0;
410            *global_pointers::last_virt_eip = scalar(slots, 18)? as i32;
411            *global_pointers::eip_phys = scalar(slots, 19)? as i32;
412            *global_pointers::sysenter_cs = scalar(slots, 22)? as i32;
413            *global_pointers::sysenter_eip = scalar(slots, 23)? as i32;
414            *global_pointers::sysenter_esp = scalar(slots, 24)? as i32;
415            *global_pointers::prefixes = scalar(slots, 25)? as u8;
416            *global_pointers::flags = scalar(slots, 26)? as i32;
417            *global_pointers::flags_changed = scalar(slots, 27)? as i32;
418            *global_pointers::last_op1 = scalar(slots, 28)? as i32;
419            *global_pointers::last_op_size = scalar(slots, 30)? as i32;
420            *global_pointers::instruction_pointer = scalar(slots, 37)? as i32;
421            *global_pointers::previous_ip = scalar(slots, 38)? as i32;
422        }
423        copy_i32_buffer(slots, buffers, 39, unsafe {
424            std::slice::from_raw_parts_mut(global_pointers::reg32 as *mut u8, 32)
425        })?;
426        copy_u16_buffer(slots, buffers, 40, unsafe {
427            std::slice::from_raw_parts_mut(global_pointers::sreg as *mut u8, 16)
428        })?;
429        copy_i32_buffer(slots, buffers, 41, unsafe {
430            std::slice::from_raw_parts_mut(global_pointers::dreg as *mut u8, 32)
431        })?;
432        copy_u64_buffer(slots, buffers, 42, unsafe {
433            std::slice::from_raw_parts_mut(global_pointers::reg_pdpte as *mut u8, 32)
434        })?;
435
436        let tsc = buffer_for(slots, buffers, 43)?;
437        if tsc.len() >= 8 {
438            let low = u32::from_le_bytes(tsc[0..4].try_into().unwrap());
439            let high = u32::from_le_bytes(tsc[4..8].try_into().unwrap());
440            unsafe {
441                cpu::set_tsc(low, high);
442            }
443        }
444
445        if let Some(uart_state) = slots.get(54).and_then(serde_json::Value::as_array) {
446            restore_uart_state(uart_state)?;
447        }
448        if let Some(pic_state) = slots.get(60).and_then(serde_json::Value::as_array) {
449            let master = byte_array_from_state(pic_state, 13, "PIC master")?;
450            let slave_value = pic_state
451                .get(5)
452                .ok_or_else(|| "PIC state has no slave controller".to_owned())?;
453            let slave_array = slave_value
454                .as_array()
455                .ok_or_else(|| "PIC slave state is not an array".to_owned())?;
456            let slave = byte_array_from_values(slave_array, 13, "PIC slave")?;
457            pic::restore_state(&master, &slave);
458        }
459
460        if slots.get(46).is_some_and(|value| !value.is_null()) {
461            let apic_state = buffer_for(slots, buffers, 46)?;
462            apic::restore_state_bytes(apic_state)?;
463            unsafe {
464                *global_pointers::apic_enabled = true;
465                *global_pointers::acpi_enabled = true;
466            }
467        }
468        if slots.get(63).is_some_and(|value| !value.is_null()) {
469            let ioapic_state = buffer_for(slots, buffers, 63)?;
470            ioapic::restore_state_bytes(ioapic_state)?;
471        }
472
473        unsafe {
474            *global_pointers::tss_size_32 = scalar(slots, 64)? != 0;
475        }
476        copy_buffer(slots, buffers, 66, unsafe {
477            std::slice::from_raw_parts_mut(global_pointers::reg_xmm as *mut u8, 128)
478        })?;
479        copy_buffer(slots, buffers, 67, unsafe {
480            std::slice::from_raw_parts_mut(global_pointers::fpu_st as *mut u8, 128)
481        })?;
482        unsafe {
483            *global_pointers::fpu_stack_empty = scalar(slots, 68)? as u8;
484            *global_pointers::fpu_stack_ptr = scalar(slots, 69)? as u8;
485            *global_pointers::fpu_control_word = scalar(slots, 70)? as u16;
486            *global_pointers::fpu_ip = scalar(slots, 71)? as i32;
487            *global_pointers::fpu_ip_selector = scalar(slots, 72)? as i32;
488            *global_pointers::fpu_dp = scalar(slots, 73)? as i32;
489            *global_pointers::fpu_dp_selector = scalar(slots, 74)? as i32;
490            *global_pointers::fpu_opcode = scalar(slots, 75)? as i32;
491            *global_pointers::last_result = slots
492                .get(86)
493                .and_then(serde_json::Value::as_i64)
494                .unwrap_or(0) as i32;
495            *global_pointers::fpu_status_word = slots
496                .get(87)
497                .and_then(serde_json::Value::as_i64)
498                .unwrap_or(0) as u16;
499            *global_pointers::mxcsr = slots
500                .get(88)
501                .and_then(serde_json::Value::as_i64)
502                .unwrap_or(0x1F80) as i32;
503        }
504
505        let packed_memory = buffer_for(slots, buffers, 77)?;
506        let bitmap = buffer_for(slots, buffers, 78)?;
507        unsafe {
508            std::ptr::write_bytes(memory::mem8, 0, self.ram_bytes as usize);
509        }
510        let page_count = self.ram_bytes as usize / 0x1000;
511        let mut packed_page = 0usize;
512        for page in 0..page_count {
513            if bitmap
514                .get(page >> 3)
515                .map_or(false, |byte| byte & (1 << (page & 7)) != 0)
516            {
517                let src_start = packed_page * 0x1000;
518                let src_end = src_start + 0x1000;
519                if src_end > packed_memory.len() {
520                    return Err("packed memory buffer is shorter than bitmap population".to_owned());
521                }
522                unsafe {
523                    std::ptr::copy_nonoverlapping(
524                        packed_memory.as_ptr().add(src_start),
525                        memory::mem8.add(page * 0x1000),
526                        0x1000,
527                    );
528                }
529                packed_page += 1;
530            }
531        }
532        if packed_page * 0x1000 != packed_memory.len() {
533            return Err(format!(
534                "packed memory has {} pages but bitmap references {}",
535                packed_memory.len() / 0x1000,
536                packed_page
537            ));
538        }
539
540        native_devices::restore_state(state, buffers)?;
541        cpu::update_state_flags();
542        unsafe {
543            cpu::full_clear_tlb();
544        }
545        Ok(())
546    }
547}
548
549fn buffer_for<'a>(
550    state: &[serde_json::Value],
551    buffers: &'a [Vec<u8>],
552    index: usize,
553) -> Result<&'a [u8], String> {
554    let buffer_id = state
555        .get(index)
556        .and_then(serde_json::Value::as_object)
557        .and_then(|object| object.get("buffer_id"))
558        .and_then(serde_json::Value::as_u64)
559        .ok_or_else(|| format!("state[{index}] is not a typed buffer"))?
560        as usize;
561    buffers
562        .get(buffer_id)
563        .map(Vec::as_slice)
564        .ok_or_else(|| format!("buffer id {buffer_id} is out of range"))
565}
566
567fn byte_array_from_state(
568    state: &[serde_json::Value],
569    len: usize,
570    name: &str,
571) -> Result<[u8; 13], String> {
572    byte_array_from_values(state, len, name)
573}
574
575fn byte_array_from_values(
576    state: &[serde_json::Value],
577    len: usize,
578    name: &str,
579) -> Result<[u8; 13], String> {
580    if len != 13 || state.len() < len {
581        return Err(format!("{name} has {} fields; expected {len}", state.len()));
582    }
583    let mut result = [0u8; 13];
584    for (index, value) in state.iter().take(len).enumerate() {
585        if index == 5 {
586            // v86 stores the slave PIC array at master[5]; Pic0 byte five is
587            // only a legacy dummy slot and is not part of the nested state.
588            continue;
589        }
590        result[index] = value
591            .as_i64()
592            .ok_or_else(|| format!("{name}[{index}] is not an integer"))?
593            as u8;
594    }
595    Ok(result)
596}
597
598fn scalar(state: &[serde_json::Value], index: usize) -> Result<i64, String> {
599    state
600        .get(index)
601        .and_then(serde_json::Value::as_i64)
602        .ok_or_else(|| format!("state[{index}] is not an integer scalar"))
603}
604
605fn copy_buffer(
606    state: &[serde_json::Value],
607    buffers: &[Vec<u8>],
608    index: usize,
609    target: &mut [u8],
610) -> Result<(), String> {
611    let source = buffer_for(state, buffers, index)?;
612    if source.len() != target.len() {
613        return Err(format!(
614            "state[{index}] length {} != expected {}",
615            source.len(),
616            target.len()
617        ));
618    }
619    target.copy_from_slice(source);
620    Ok(())
621}
622
623fn copy_i32_buffer(
624    state: &[serde_json::Value],
625    buffers: &[Vec<u8>],
626    index: usize,
627    target: &mut [u8],
628) -> Result<(), String> {
629    copy_buffer(state, buffers, index, target)
630}
631
632fn copy_u16_buffer(
633    state: &[serde_json::Value],
634    buffers: &[Vec<u8>],
635    index: usize,
636    target: &mut [u8],
637) -> Result<(), String> {
638    copy_buffer(state, buffers, index, target)
639}
640
641fn copy_u32_buffer(
642    state: &[serde_json::Value],
643    buffers: &[Vec<u8>],
644    index: usize,
645    target: &mut [u8],
646) -> Result<(), String> {
647    copy_buffer(state, buffers, index, target)
648}
649
650fn copy_u64_buffer(
651    state: &[serde_json::Value],
652    buffers: &[Vec<u8>],
653    index: usize,
654    target: &mut [u8],
655) -> Result<(), String> {
656    copy_buffer(state, buffers, index, target)
657}