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();
10static PS2: OnceLock<Mutex<Ps2State>> = OnceLock::new();
11static PIT: OnceLock<Mutex<PitState>> = OnceLock::new();
12static RTC: OnceLock<Mutex<RtcState>> = OnceLock::new();
13static VGA_TEXT: OnceLock<Mutex<Vec<u8>>> = OnceLock::new();
14
15fn vga_text_memory() -> &'static Mutex<Vec<u8>> {
16    VGA_TEXT.get_or_init(|| Mutex::new(vec![0; 0x40000]))
17}
18
19fn legacy_vga_offset(addr: u32) -> Option<usize> {
20    // Preserve the conventional A0000 graphics window. The saved v86 VGA
21    // state stores the text plane at logical offset zero, so B8000 is also
22    // exposed as an alias to offset zero for the terminal snapshot.
23    if (0xA0000..0xB8000).contains(&addr) {
24        Some((addr - 0xA0000) as usize)
25    } else if (0xB8000..0xC0000).contains(&addr) {
26        Some((addr - 0xB8000) as usize)
27    } else {
28        None
29    }
30}
31
32#[derive(Clone)]
33struct RtcState {
34    index: u8,
35    data: [u8; 128],
36    status_a: u8,
37    status_b: u8,
38    status_c: u8,
39    status_d: u8,
40    nmi_disabled: bool,
41}
42
43impl Default for RtcState {
44    fn default() -> Self {
45        let mut data = [0u8; 128];
46        data[0x0A] = 0x26;
47        data[0x0B] = 0x02;
48        data[0x0D] = 0x80;
49        Self {
50            index: 0,
51            data,
52            status_a: 0x26,
53            status_b: 0x02,
54            status_c: 0,
55            status_d: 0x80,
56            nmi_disabled: false,
57        }
58    }
59}
60
61fn rtc() -> &'static Mutex<RtcState> {
62    RTC.get_or_init(|| Mutex::new(RtcState::default()))
63}
64
65fn rtc_read(port: i32) -> Option<i32> {
66    let state = rtc().lock().ok()?;
67    match port {
68        0x71 => Some(match state.index & 0x7F {
69            0x0A => state.status_a,
70            0x0B => state.status_b,
71            0x0C => state.status_c,
72            0x0D => state.status_d,
73            index => state.data[index as usize],
74        } as i32),
75        0x70 => Some(state.index as i32 | if state.nmi_disabled { 0x80 } else { 0 }),
76        _ => None,
77    }
78}
79
80fn rtc_write(port: i32, value: i32) -> bool {
81    let Ok(mut state) = rtc().lock() else {
82        return false;
83    };
84    match port {
85        0x70 => {
86            let byte = value as u8;
87            state.index = byte & 0x7F;
88            state.nmi_disabled = byte & 0x80 != 0;
89            true
90        }
91        0x71 => {
92            let index = state.index & 0x7F;
93            let byte = value as u8;
94            match index {
95                0x0A => state.status_a = byte,
96                0x0B => state.status_b = byte,
97                0x0C | 0x0D => {}
98                _ => state.data[index as usize] = byte,
99            }
100            true
101        }
102        _ => false,
103    }
104}
105
106#[derive(Clone)]
107struct PitState {
108    next_low: [u8; 3],
109    enabled: [bool; 3],
110    mode: [u8; 3],
111    read_mode: [u8; 3],
112    latch: [u8; 3],
113    latch_value: [u16; 3],
114    reload: [u16; 3],
115    start_value: [u16; 3],
116    start: [Instant; 3],
117}
118
119impl Default for PitState {
120    fn default() -> Self {
121        Self {
122            next_low: [1; 3],
123            enabled: [false; 3],
124            mode: [3; 3],
125            read_mode: [3; 3],
126            latch: [0; 3],
127            latch_value: [0; 3],
128            reload: [0; 3],
129            start_value: [0; 3],
130            start: [Instant::now(), Instant::now(), Instant::now()],
131        }
132    }
133}
134
135fn pit() -> &'static Mutex<PitState> {
136    PIT.get_or_init(|| Mutex::new(PitState::default()))
137}
138
139const PIT_HZ: f64 = 1_193_181.6666;
140
141fn pit_counter_value(state: &PitState, channel: usize) -> u16 {
142    if !state.enabled[channel] || state.reload[channel] == 0 {
143        return 0;
144    }
145    let elapsed = state.start[channel].elapsed().as_secs_f64();
146    let ticks = (elapsed * PIT_HZ) as u64;
147    let reload = state.reload[channel] as u64;
148    (state.start_value[channel] as u64).wrapping_sub(ticks % reload.max(1)) as u16
149}
150
151fn pit_read(port: i32) -> Option<i32> {
152    if !(0x40..=0x42).contains(&port) {
153        return None;
154    }
155    let channel = (port - 0x40) as usize;
156    let mut state = pit().lock().ok()?;
157    if state.latch[channel] != 0 {
158        state.latch[channel] -= 1;
159        return Some(if state.latch[channel] == 1 {
160            (state.latch_value[channel] & 0xFF) as i32
161        } else {
162            (state.latch_value[channel] >> 8) as i32
163        });
164    }
165    let value = pit_counter_value(&state, channel);
166    let low = state.next_low[channel] != 0;
167    if state.mode[channel] == 3 {
168        state.next_low[channel] ^= 1;
169    }
170    Some(if low {
171        (value & 0xFF) as i32
172    } else {
173        (value >> 8) as i32
174    })
175}
176
177fn pit_poll() -> bool {
178    let Ok(mut state) = pit().lock() else {
179        return false;
180    };
181    if !state.enabled[0] || state.reload[0] == 0 {
182        return false;
183    }
184    let elapsed_ticks = (state.start[0].elapsed().as_secs_f64() * PIT_HZ) as u64;
185    if elapsed_ticks >= state.start_value[0] as u64 {
186        state.start[0] = Instant::now();
187        state.start_value[0] = state.reload[0];
188        drop(state);
189        unsafe {
190            crate::cpu::cpu::device_lower_irq(0);
191            crate::cpu::cpu::device_raise_irq(0);
192        }
193    }
194    true
195}
196
197fn pit_write(port: i32, value: i32) -> bool {
198    let Ok(mut state) = pit().lock() else {
199        return false;
200    };
201    if (0x40..=0x42).contains(&port) {
202        let channel = (port - 0x40) as usize;
203        let byte = value as u8;
204        if state.next_low[channel] != 0 {
205            state.reload[channel] = (state.reload[channel] & 0xFF00) | byte as u16;
206        } else {
207            state.reload[channel] = (state.reload[channel] & 0x00FF) | ((byte as u16) << 8);
208            if state.reload[channel] == 0 {
209                state.reload[channel] = 0xFFFF;
210            }
211            state.start_value[channel] = state.reload[channel];
212            state.start[channel] = Instant::now();
213            state.enabled[channel] = true;
214        }
215        state.next_low[channel] ^= 1;
216        return true;
217    }
218    if port == 0x43 {
219        let command = value as u8;
220        let channel = ((command >> 6) & 3) as usize;
221        if channel >= 3 {
222            return true;
223        }
224        let read_mode = (command >> 4) & 3;
225        if read_mode == 0 {
226            state.latch_value[channel] = pit_counter_value(&state, channel);
227            state.latch[channel] = 2;
228        } else {
229            state.read_mode[channel] = read_mode;
230            state.mode[channel] = (command >> 1) & 7;
231            state.next_low[channel] = if read_mode == 3 { 1 } else { 0 };
232        }
233        return true;
234    }
235    port == 0x61
236}
237
238#[derive(Default)]
239struct Ps2State {
240    output: VecDeque<u8>,
241    command_byte: u8,
242    pending_command: u8,
243}
244
245fn ps2() -> &'static Mutex<Ps2State> {
246    PS2.get_or_init(|| {
247        Mutex::new(Ps2State {
248            command_byte: 0x01,
249            ..Ps2State::default()
250        })
251    })
252}
253
254#[derive(Default)]
255struct UartState {
256    ints: u8,
257    baud_rate: u16,
258    line_control: u8,
259    lsr: u8,
260    fifo_control: u8,
261    ier: u8,
262    iir: u8,
263    modem_control: u8,
264    modem_status: u8,
265    scratch: u8,
266    irq: u8,
267    input: VecDeque<u8>,
268}
269
270fn uart0() -> &'static Mutex<UartState> {
271    UART0.get_or_init(|| Mutex::new(UartState::default()))
272}
273
274fn ps2_read(port: i32) -> Option<i32> {
275    let mut controller = ps2().lock().ok()?;
276    match port {
277        0x60 => {
278            let value = controller.output.pop_front().unwrap_or(0);
279            let more = !controller.output.is_empty();
280            drop(controller);
281            unsafe {
282                crate::cpu::cpu::device_lower_irq(1);
283                if more {
284                    crate::cpu::cpu::device_raise_irq(1);
285                }
286            }
287            Some(value as i32)
288        }
289        0x64 => Some(if controller.output.is_empty() { 0 } else { 1 }),
290        _ => None,
291    }
292}
293
294fn ps2_write(port: i32, value: i32) -> bool {
295    let Ok(mut controller) = ps2().lock() else {
296        return false;
297    };
298    match port {
299        0x64 => {
300            controller.pending_command = value as u8;
301            true
302        }
303        0x60 => {
304            if controller.pending_command == 0x60 {
305                controller.command_byte = value as u8;
306            }
307            controller.pending_command = 0;
308            true
309        }
310        _ => false,
311    }
312}
313
314fn keycode_for_ascii(byte: u8) -> Option<(u8, bool)> {
315    let upper = byte.to_ascii_uppercase();
316    let shifted = byte.is_ascii_uppercase();
317    let code = match upper {
318        b'A' => 0x1E,
319        b'B' => 0x30,
320        b'C' => 0x2E,
321        b'D' => 0x20,
322        b'E' => 0x12,
323        b'F' => 0x21,
324        b'G' => 0x22,
325        b'H' => 0x23,
326        b'I' => 0x17,
327        b'J' => 0x24,
328        b'K' => 0x25,
329        b'L' => 0x26,
330        b'M' => 0x32,
331        b'N' => 0x31,
332        b'O' => 0x18,
333        b'P' => 0x19,
334        b'Q' => 0x10,
335        b'R' => 0x13,
336        b'S' => 0x1F,
337        b'T' => 0x14,
338        b'U' => 0x16,
339        b'V' => 0x2F,
340        b'W' => 0x11,
341        b'X' => 0x2D,
342        b'Y' => 0x15,
343        b'Z' => 0x2C,
344        b'1' | b'!' => 0x02,
345        b'2' | b'@' => 0x03,
346        b'3' | b'#' => 0x04,
347        b'4' | b'$' => 0x05,
348        b'5' | b'%' => 0x06,
349        b'6' | b'^' => 0x07,
350        b'7' | b'&' => 0x08,
351        b'8' | b'*' => 0x09,
352        b'9' | b'(' => 0x0A,
353        b'0' | b')' => 0x0B,
354        b'-' | b'_' => 0x0C,
355        b'=' | b'+' => 0x0D,
356        b'[' | b'{' => 0x1A,
357        b']' | b'}' => 0x1B,
358        b';' | b':' => 0x27,
359        b'\'' | b'"' => 0x28,
360        b'`' | b'~' => 0x29,
361        b'\\' | b'|' => 0x2B,
362        b',' | b'<' => 0x33,
363        b'.' | b'>' => 0x34,
364        b'/' | b'?' => 0x35,
365        b' ' => 0x39,
366        b'\n' | b'\r' => 0x1C,
367        b'\t' => 0x0F,
368        8 => 0x0E,
369        _ => return None,
370    };
371    let shifted = shifted
372        || matches!(byte, b'!'..=b'&' | b'('..=b'+' | b':' | b'<'..=b'>' | b'?' | b'@' | b'^' | b'_' | b'{' | b'|' | b'}' | b'~' | b'"');
373    Some((code, shifted))
374}
375
376pub fn inject_keyboard_text(text: &str) -> usize {
377    let Ok(mut controller) = ps2().lock() else {
378        return 0;
379    };
380    let mut count = 0;
381    for byte in text.bytes() {
382        let Some((code, shifted)) = keycode_for_ascii(byte) else {
383            continue;
384        };
385        if shifted {
386            controller.output.push_back(0x2A);
387        }
388        controller.output.push_back(code);
389        controller.output.push_back(code | 0x80);
390        if shifted {
391            controller.output.push_back(0xAA);
392        }
393        count += 1;
394    }
395    drop(controller);
396    if count > 0 {
397        unsafe { crate::cpu::cpu::device_raise_irq(1) };
398    }
399    count
400}
401
402fn uart_read(port: i32) -> i32 {
403    let offset = (port - 0x3F8) as u8;
404    let mut uart = uart0().lock().expect("UART0 mutex poisoned");
405    match offset {
406        0 if uart.line_control & 0x80 != 0 => (uart.baud_rate & 0xFF) as i32,
407        0 => uart.input.pop_front().unwrap_or(0) as i32,
408        1 if uart.line_control & 0x80 != 0 => (uart.baud_rate >> 8) as i32,
409        1 => (uart.ier & 0x0F) as i32,
410        2 => {
411            let fifo = if uart.fifo_control & 1 != 0 { 0xC0 } else { 0 };
412            (uart.iir | fifo) as i32
413        }
414        3 => uart.line_control as i32,
415        4 => uart.modem_control as i32,
416        5 => (uart.lsr | if uart.input.is_empty() { 0 } else { 0x01 }) as i32,
417        6 => uart.modem_status as i32,
418        7 => uart.scratch as i32,
419        _ => 0xFF,
420    }
421}
422
423fn restore_uart_state(state: &[serde_json::Value]) -> Result<(), String> {
424    if state.len() < 11 {
425        return Err(format!(
426            "UART state has {} fields; expected 11",
427            state.len()
428        ));
429    }
430    let mut uart = uart0()
431        .lock()
432        .map_err(|_| "UART0 mutex poisoned".to_owned())?;
433    uart.ints = state[0]
434        .as_i64()
435        .ok_or_else(|| "UART ints is not an integer".to_owned())? as u8;
436    uart.baud_rate = state[1]
437        .as_i64()
438        .ok_or_else(|| "UART baud rate is not an integer".to_owned())? as u16;
439    uart.line_control = state[2]
440        .as_i64()
441        .ok_or_else(|| "UART line control is not an integer".to_owned())?
442        as u8;
443    uart.lsr = state[3]
444        .as_i64()
445        .ok_or_else(|| "UART LSR is not an integer".to_owned())? as u8;
446    uart.fifo_control = state[4]
447        .as_i64()
448        .ok_or_else(|| "UART FIFO control is not an integer".to_owned())?
449        as u8;
450    uart.ier = state[5]
451        .as_i64()
452        .ok_or_else(|| "UART IER is not an integer".to_owned())? as u8;
453    uart.iir = state[6]
454        .as_i64()
455        .ok_or_else(|| "UART IIR is not an integer".to_owned())? as u8;
456    uart.modem_control = state[7]
457        .as_i64()
458        .ok_or_else(|| "UART modem control is not an integer".to_owned())?
459        as u8;
460    uart.modem_status = state[8]
461        .as_i64()
462        .ok_or_else(|| "UART modem status is not an integer".to_owned())?
463        as u8;
464    uart.scratch = state[9]
465        .as_i64()
466        .ok_or_else(|| "UART scratch is not an integer".to_owned())? as u8;
467    uart.irq = state[10]
468        .as_i64()
469        .ok_or_else(|| "UART IRQ is not an integer".to_owned())? as u8;
470    Ok(())
471}
472
473fn nested_buffer<'a>(
474    state: &[serde_json::Value],
475    index: usize,
476    buffers: &'a [Vec<u8>],
477) -> Result<&'a [u8], String> {
478    let buffer_id = state
479        .get(index)
480        .and_then(serde_json::Value::as_object)
481        .and_then(|object| object.get("buffer_id"))
482        .and_then(serde_json::Value::as_u64)
483        .ok_or_else(|| format!("nested state[{index}] is not a typed buffer"))?
484        as usize;
485    buffers
486        .get(buffer_id)
487        .map(Vec::as_slice)
488        .ok_or_else(|| format!("nested buffer id {buffer_id} is out of range"))
489}
490
491fn restore_rtc_state(state: &[serde_json::Value], buffers: &[Vec<u8>]) -> Result<(), String> {
492    if state.len() < 14 {
493        return Err(format!("RTC state has {} fields; expected 14", state.len()));
494    }
495    let data = nested_buffer(state, 1, buffers)?;
496    let mut rtc = rtc().lock().map_err(|_| "RTC mutex poisoned".to_owned())?;
497    rtc.index = state[0].as_i64().unwrap_or(0) as u8;
498    rtc.data.fill(0);
499    let copy_len = data.len().min(rtc.data.len());
500    rtc.data[..copy_len].copy_from_slice(&data[..copy_len]);
501    rtc.status_a = state[8].as_i64().unwrap_or(rtc.data[0x0A] as i64) as u8;
502    rtc.status_b = state[9].as_i64().unwrap_or(rtc.data[0x0B] as i64) as u8;
503    rtc.status_c = state[10].as_i64().unwrap_or(0) as u8;
504    rtc.nmi_disabled = state[11].as_i64().unwrap_or(0) != 0;
505    rtc.status_d = rtc.data[0x0D].max(0x80);
506    Ok(())
507}
508
509fn restore_pit_state(state: &[serde_json::Value], buffers: &[Vec<u8>]) -> Result<(), String> {
510    if state.len() < 9 {
511        return Err(format!("PIT state has {} fields; expected 9", state.len()));
512    }
513    let next_low = nested_buffer(state, 0, buffers)?;
514    let enabled = nested_buffer(state, 1, buffers)?;
515    let mode = nested_buffer(state, 2, buffers)?;
516    let read_mode = nested_buffer(state, 3, buffers)?;
517    let latch = nested_buffer(state, 4, buffers)?;
518    let reload = nested_buffer(state, 6, buffers)?;
519    let start_value = nested_buffer(state, 8, buffers)?;
520    let mut pit = pit().lock().map_err(|_| "PIT mutex poisoned".to_owned())?;
521    for channel in 0..3 {
522        pit.next_low[channel] = *next_low.get(channel).unwrap_or(&1);
523        pit.enabled[channel] = *enabled.get(channel).unwrap_or(&0) != 0;
524        pit.mode[channel] = *mode.get(channel).unwrap_or(&3);
525        pit.read_mode[channel] = *read_mode.get(channel).unwrap_or(&3);
526        pit.latch[channel] = *latch.get(channel).unwrap_or(&0);
527        let offset = channel * 2;
528        pit.reload[channel] = u16::from_le_bytes([
529            *reload.get(offset).unwrap_or(&0),
530            *reload.get(offset + 1).unwrap_or(&0),
531        ]);
532        pit.start_value[channel] = u16::from_le_bytes([
533            *start_value.get(offset).unwrap_or(&0),
534            *start_value.get(offset + 1).unwrap_or(&0),
535        ]);
536        pit.start[channel] = Instant::now();
537    }
538    Ok(())
539}
540
541fn uart_write(port: i32, value: i32) {
542    let offset = (port - 0x3F8) as u8;
543    let byte = value as u8;
544    let mut output = None;
545    {
546        let mut uart = uart0().lock().expect("UART0 mutex poisoned");
547        match offset {
548            0 if uart.line_control & 0x80 != 0 => {
549                uart.baud_rate = (uart.baud_rate & 0xFF00) | byte as u16;
550            }
551            0 => output = Some(byte),
552            1 if uart.line_control & 0x80 != 0 => {
553                uart.baud_rate = (uart.baud_rate & 0x00FF) | ((byte as u16) << 8);
554            }
555            1 => uart.ier = byte & 0x0F,
556            2 => uart.fifo_control = byte,
557            3 => uart.line_control = byte,
558            4 => uart.modem_control = byte,
559            7 => uart.scratch = byte,
560            _ => {}
561        }
562    }
563    if let Some(byte) = output {
564        let mut stdout = std::io::stdout().lock();
565        let _ = stdout.write_all(&[byte]);
566        let _ = stdout.flush();
567    }
568}
569
570/// Minimal native host callbacks used by the v86 CPU core.
571/// Device-specific MMIO/port routing is intentionally represented as a small
572/// host surface first; concrete PC devices are added by the outer runtime.
573#[no_mangle]
574pub extern "C" fn cpu_exception_hook(_interrupt: i32) -> bool {
575    false
576}
577
578#[no_mangle]
579pub extern "C" fn microtick() -> f64 {
580    START.get_or_init(Instant::now).elapsed().as_secs_f64() * 1000.0
581}
582
583#[no_mangle]
584pub extern "C" fn run_hardware_timers(_acpi_enabled: bool, _now: f64) -> f64 {
585    let _ = pit_poll();
586    0.0
587}
588
589#[no_mangle]
590pub extern "C" fn cpu_event_halt() {}
591
592#[no_mangle]
593pub extern "C" fn stop_idling() {}
594
595#[no_mangle]
596pub extern "C" fn get_rand_int() -> i32 {
597    0x1357_9BDF
598}
599
600#[no_mangle]
601pub extern "C" fn io_port_read8(port: i32) -> i32 {
602    if let Some(value) = rtc_read(port) {
603        value
604    } else if let Some(value) = pit_read(port) {
605        value
606    } else if let Some(value) = ps2_read(port) {
607        value
608    } else if let Some(value) = native_devices::io_read8(port) {
609        value
610    } else if (0x3F8..=0x3FF).contains(&port) {
611        uart_read(port)
612    } else {
613        0xFF
614    }
615}
616
617#[no_mangle]
618pub extern "C" fn io_port_read16(port: i32) -> i32 {
619    native_devices::io_read16(port).unwrap_or(0xFFFF)
620}
621
622#[no_mangle]
623pub extern "C" fn io_port_read32(port: i32) -> i32 {
624    native_devices::io_read32(port).unwrap_or(-1)
625}
626
627#[no_mangle]
628pub extern "C" fn io_port_write8(port: i32, value: i32) {
629    if !rtc_write(port, value)
630        && !pit_write(port, value)
631        && !ps2_write(port, value)
632        && !native_devices::io_write8(port, value)
633        && (0x3F8..=0x3FF).contains(&port)
634    {
635        uart_write(port, value);
636    }
637}
638
639#[no_mangle]
640pub extern "C" fn io_port_write16(port: i32, value: i32) {
641    if !native_devices::io_write16(port, value) {}
642}
643
644#[no_mangle]
645pub extern "C" fn io_port_write32(port: i32, value: i32) {
646    if !native_devices::io_write32(port, value) {}
647}
648
649#[no_mangle]
650pub extern "C" fn mmap_read8(addr: u32) -> i32 {
651    if let Some(offset) = legacy_vga_offset(addr) {
652        return vga_text_memory()
653            .lock()
654            .ok()
655            .and_then(|m| m.get(offset).copied())
656            .unwrap_or(0xFF) as i32;
657    }
658    native_devices::mmio_read8(addr).unwrap_or(0xFF)
659}
660
661#[no_mangle]
662pub extern "C" fn mmap_read32(addr: u32) -> i32 {
663    if legacy_vga_offset(addr).is_some() && addr <= 0xBFFFC {
664        return i32::from_le_bytes([
665            mmap_read8(addr) as u8,
666            mmap_read8(addr + 1) as u8,
667            mmap_read8(addr + 2) as u8,
668            mmap_read8(addr + 3) as u8,
669        ]);
670    }
671    native_devices::mmio_read32(addr).unwrap_or(-1)
672}
673
674#[no_mangle]
675pub extern "C" fn mmap_write8(addr: u32, value: i32) {
676    if let Some(offset) = legacy_vga_offset(addr) {
677        if let Ok(mut memory) = vga_text_memory().lock() {
678            if let Some(byte) = memory.get_mut(offset) {
679                *byte = value as u8;
680            }
681        }
682        return;
683    }
684    let _ = native_devices::mmio_write8(addr, value);
685}
686
687#[no_mangle]
688pub extern "C" fn mmap_write16(addr: u32, value: i32) {
689    if legacy_vga_offset(addr).is_some() {
690        mmap_write8(addr, value);
691        mmap_write8(addr + 1, value >> 8);
692        return;
693    }
694    let _ = native_devices::mmio_write16(addr, value);
695}
696
697#[no_mangle]
698pub extern "C" fn mmap_write32(addr: u32, value: i32) {
699    if legacy_vga_offset(addr).is_some() {
700        for offset in 0..4 {
701            mmap_write8(addr + offset, value >> (offset * 8));
702        }
703        return;
704    }
705    let _ = native_devices::mmio_write32(addr, value);
706}
707
708#[no_mangle]
709pub extern "C" fn mmap_write64(addr: u32, v0: i32, v1: i32) {
710    mmap_write32(addr, v0);
711    mmap_write32(addr + 4, v1);
712}
713
714#[no_mangle]
715pub extern "C" fn mmap_write128(addr: u32, v0: i32, v1: i32, v2: i32, v3: i32) {
716    mmap_write32(addr, v0);
717    mmap_write32(addr + 4, v1);
718    mmap_write32(addr + 8, v2);
719    mmap_write32(addr + 12, v3);
720}
721
722/// Native CPU state arena and guest memory owner.
723///
724/// v86's scalar CPU state uses the first 4 KiB of the arena. The guest RAM is
725/// allocated by the core memory module and addressed with 32-bit guest physical
726/// addresses, matching the original emulator model.
727pub struct NativeCpu {
728    state_arena: Box<[u8; 4096]>,
729    ram_bytes: u32,
730    vga_bytes: u32,
731    last_timer_tick: Instant,
732    screen_width: u32,
733    screen_height: u32,
734    screen_bpp: u32,
735    graphical_mode: bool,
736}
737
738impl NativeCpu {
739    pub fn new(ram_bytes: u32, vga_bytes: u32) -> Self {
740        assert!(ram_bytes > 0, "RAM size must be non-zero");
741        assert!(vga_bytes > 0, "VGA memory size must be non-zero");
742
743        let mut state_arena = Box::new([0u8; 4096]);
744        if let Ok(mut text) = vga_text_memory().lock() {
745            text.fill(0);
746        }
747        unsafe {
748            global_pointers::init(state_arena.as_mut_ptr());
749            let _ = memory::allocate_memory(ram_bytes);
750            let _ = memory::svga_allocate_memory(vga_bytes);
751            *global_pointers::memory_size = ram_bytes;
752            memory::vga_memory_size = vga_bytes;
753            cpu::reset_cpu();
754        }
755
756        Self {
757            state_arena,
758            ram_bytes,
759            vga_bytes,
760            last_timer_tick: Instant::now(),
761            screen_width: 80,
762            screen_height: 25,
763            screen_bpp: 0,
764            graphical_mode: false,
765        }
766    }
767
768    pub fn ram_bytes(&self) -> u32 {
769        self.ram_bytes
770    }
771
772    pub fn vga_bytes(&self) -> u32 {
773        self.vga_bytes
774    }
775
776    pub fn step(&mut self, max_instructions: u32) -> u32 {
777        unsafe {
778            let halted = *global_pointers::in_hlt;
779            let timer_due = self.last_timer_tick.elapsed() >= std::time::Duration::from_millis(1);
780            if halted || timer_due {
781                let now = microtick();
782                let pit_active = pit_poll();
783                if *global_pointers::acpi_enabled {
784                    let _ = apic::apic_timer(now);
785                    cpu::handle_irqs();
786                } else if !pit_active {
787                    pic::set_irq(0);
788                    cpu::handle_irqs();
789                    pic::clear_irq(0);
790                    cpu::handle_irqs();
791                }
792                self.last_timer_tick = Instant::now();
793            }
794            cpu::main_loop_native_interpreter(max_instructions)
795        }
796    }
797
798    pub fn read_memory(&self, address: u32, output: &mut [u8]) -> bool {
799        if address.checked_add(output.len() as u32).is_none()
800            || address + output.len() as u32 > self.ram_bytes
801        {
802            return false;
803        }
804        unsafe {
805            output.copy_from_slice(std::slice::from_raw_parts(
806                memory::mem8.add(address as usize),
807                output.len(),
808            ));
809        }
810        true
811    }
812
813    pub fn write_memory(&mut self, address: u32, input: &[u8]) -> bool {
814        if address.checked_add(input.len() as u32).is_none()
815            || address + input.len() as u32 > self.ram_bytes
816        {
817            return false;
818        }
819        unsafe {
820            std::slice::from_raw_parts_mut(memory::mem8.add(address as usize), input.len())
821                .copy_from_slice(input);
822        }
823        true
824    }
825
826    pub fn instruction_pointer(&self) -> u32 {
827        unsafe { *global_pointers::instruction_pointer as u32 }
828    }
829
830    pub fn halted(&self) -> bool {
831        unsafe { *global_pointers::in_hlt }
832    }
833
834    pub fn state_arena(&self) -> &[u8; 4096] {
835        &self.state_arena
836    }
837
838    /// Return the restored SVGA framebuffer as packed RGB bytes.
839    /// The native runtime keeps the framebuffer in the same guest-visible
840    /// backing store used by v86's LFB mapping.
841    pub fn vga_text_snapshot(&self) -> Option<(u32, u32, Vec<u8>)> {
842        if self.graphical_mode {
843            return None;
844        }
845        let memory = vga_text_memory().lock().ok()?;
846        if memory.len() < 80 * 25 * 2 {
847            return None;
848        }
849        Some((80, 25, memory[..80 * 25 * 2].to_vec()))
850    }
851
852    pub fn vga_framebuffer_rgb(&self) -> Option<(u32, u32, Vec<u8>)> {
853        if !self.graphical_mode || self.screen_width == 0 || self.screen_height == 0 {
854            return None;
855        }
856        let pixels = (self.screen_width as usize).checked_mul(self.screen_height as usize)?;
857        let mut output = vec![0u8; pixels.checked_mul(3)?];
858        unsafe {
859            if memory::vga_mem8.is_null() || self.screen_bpp != 32 {
860                return None;
861            }
862            let source_len = pixels.checked_mul(4)?;
863            if source_len > self.vga_bytes as usize {
864                return None;
865            }
866            let source = std::slice::from_raw_parts(memory::vga_mem8, source_len);
867            for (index, rgb) in output.chunks_exact_mut(3).enumerate() {
868                let pixel = &source[index * 4..index * 4 + 4];
869                rgb.copy_from_slice(&[pixel[2], pixel[1], pixel[0]]);
870            }
871        }
872        Some((self.screen_width, self.screen_height, output))
873    }
874
875    pub fn set_9p_root(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
876        native_devices::set_9p_root(path)
877    }
878}
879
880#[cfg(test)]
881mod tests {
882    use super::NativeCpu;
883
884    #[test]
885    fn native_interpreter_executes_reset_vector_hlt() {
886        let mut cpu = NativeCpu::new(128 * 1024 * 1024, 8 * 1024 * 1024);
887        assert!(cpu.write_memory(0xFFFF0, &[0xF4]));
888        assert_eq!(cpu.instruction_pointer(), 0xFFFF0);
889        assert_eq!(cpu.step(1), 1);
890        assert!(cpu.halted());
891    }
892}
893
894impl NativeCpu {
895    /// Restore the CPU scalar state and packed RAM representation from the
896    /// decoded v86 state object. Device arrays are intentionally left to the
897    /// outer native device graph, but CPU execution can continue after this
898    /// method completes.
899    pub fn restore_v86_state(
900        &mut self,
901        state: &serde_json::Value,
902        buffers: &[Vec<u8>],
903    ) -> Result<(), String> {
904        let slots = state
905            .as_array()
906            .ok_or_else(|| "v86 state is not an array".to_owned())?;
907
908        let memory_size = scalar(slots, 0)? as u32;
909        if memory_size != self.ram_bytes {
910            return Err(format!(
911                "state RAM is {memory_size} bytes, NativeCpu has {} bytes",
912                self.ram_bytes
913            ));
914        }
915
916        let segment_state = buffer_for(slots, buffers, 1)?;
917        if segment_state.len() != 16 {
918            return Err(format!(
919                "state[1] length {} != expected 16",
920                segment_state.len()
921            ));
922        }
923        unsafe {
924            std::slice::from_raw_parts_mut(global_pointers::segment_is_null as *mut u8, 8)
925                .copy_from_slice(&segment_state[..8]);
926            std::slice::from_raw_parts_mut(global_pointers::segment_access_bytes, 8)
927                .copy_from_slice(&segment_state[8..]);
928        }
929        copy_i32_buffer(slots, buffers, 2, unsafe {
930            std::slice::from_raw_parts_mut(global_pointers::segment_offsets as *mut u8, 32)
931        })?;
932        copy_u32_buffer(slots, buffers, 3, unsafe {
933            std::slice::from_raw_parts_mut(global_pointers::segment_limits as *mut u8, 32)
934        })?;
935
936        unsafe {
937            *global_pointers::memory_size = memory_size;
938            *global_pointers::protected_mode = scalar(slots, 4)? != 0;
939            *global_pointers::idtr_offset = scalar(slots, 5)? as i32;
940            *global_pointers::idtr_size = scalar(slots, 6)? as i32;
941            *global_pointers::gdtr_offset = scalar(slots, 7)? as i32;
942            *global_pointers::gdtr_size = scalar(slots, 8)? as i32;
943        }
944        copy_i32_buffer(slots, buffers, 10, unsafe {
945            std::slice::from_raw_parts_mut(global_pointers::cr as *mut u8, 32)
946        })?;
947        unsafe {
948            *global_pointers::cpl = scalar(slots, 11)? as u8;
949            *global_pointers::is_32 = scalar(slots, 13)? != 0;
950            *global_pointers::stack_size_32 = scalar(slots, 16)? != 0;
951            *global_pointers::in_hlt = scalar(slots, 17)? != 0;
952            *global_pointers::last_virt_eip = scalar(slots, 18)? as i32;
953            *global_pointers::eip_phys = scalar(slots, 19)? as i32;
954            *global_pointers::sysenter_cs = scalar(slots, 22)? as i32;
955            *global_pointers::sysenter_eip = scalar(slots, 23)? as i32;
956            *global_pointers::sysenter_esp = scalar(slots, 24)? as i32;
957            *global_pointers::prefixes = scalar(slots, 25)? as u8;
958            *global_pointers::flags = scalar(slots, 26)? as i32;
959            *global_pointers::flags_changed = scalar(slots, 27)? as i32;
960            *global_pointers::last_op1 = scalar(slots, 28)? as i32;
961            *global_pointers::last_op_size = scalar(slots, 30)? as i32;
962            *global_pointers::instruction_pointer = scalar(slots, 37)? as i32;
963            *global_pointers::previous_ip = scalar(slots, 38)? as i32;
964        }
965        copy_i32_buffer(slots, buffers, 39, unsafe {
966            std::slice::from_raw_parts_mut(global_pointers::reg32 as *mut u8, 32)
967        })?;
968        copy_u16_buffer(slots, buffers, 40, unsafe {
969            std::slice::from_raw_parts_mut(global_pointers::sreg as *mut u8, 16)
970        })?;
971        copy_i32_buffer(slots, buffers, 41, unsafe {
972            std::slice::from_raw_parts_mut(global_pointers::dreg as *mut u8, 32)
973        })?;
974        copy_u64_buffer(slots, buffers, 42, unsafe {
975            std::slice::from_raw_parts_mut(global_pointers::reg_pdpte as *mut u8, 32)
976        })?;
977
978        let tsc = buffer_for(slots, buffers, 43)?;
979        if tsc.len() >= 8 {
980            let low = u32::from_le_bytes(tsc[0..4].try_into().unwrap());
981            let high = u32::from_le_bytes(tsc[4..8].try_into().unwrap());
982            unsafe {
983                cpu::set_tsc(low, high);
984            }
985        }
986
987        if let Some(uart_state) = slots.get(54).and_then(serde_json::Value::as_array) {
988            restore_uart_state(uart_state)?;
989        }
990        if let Some(rtc_state) = slots.get(47).and_then(serde_json::Value::as_array) {
991            restore_rtc_state(rtc_state, buffers)?;
992        }
993        if let Some(pit_state) = slots.get(58).and_then(serde_json::Value::as_array) {
994            restore_pit_state(pit_state, buffers)?;
995        }
996        if let Some(pic_state) = slots.get(60).and_then(serde_json::Value::as_array) {
997            let master = byte_array_from_state(pic_state, 13, "PIC master")?;
998            let slave_value = pic_state
999                .get(5)
1000                .ok_or_else(|| "PIC state has no slave controller".to_owned())?;
1001            let slave_array = slave_value
1002                .as_array()
1003                .ok_or_else(|| "PIC slave state is not an array".to_owned())?;
1004            let slave = byte_array_from_values(slave_array, 13, "PIC slave")?;
1005            pic::restore_state(&master, &slave);
1006        }
1007
1008        if slots.get(46).is_some_and(|value| !value.is_null()) {
1009            let apic_state = buffer_for(slots, buffers, 46)?;
1010            apic::restore_state_bytes(apic_state)?;
1011            unsafe {
1012                *global_pointers::apic_enabled = true;
1013                *global_pointers::acpi_enabled = true;
1014            }
1015        }
1016        if slots.get(63).is_some_and(|value| !value.is_null()) {
1017            let ioapic_state = buffer_for(slots, buffers, 63)?;
1018            ioapic::restore_state_bytes(ioapic_state)?;
1019        }
1020
1021        if let Some(vga_state) = slots.get(52).and_then(serde_json::Value::as_array) {
1022            self.screen_width = vga_state
1023                .get(15)
1024                .and_then(serde_json::Value::as_i64)
1025                .unwrap_or(0) as u32;
1026            self.screen_height = vga_state
1027                .get(16)
1028                .and_then(serde_json::Value::as_i64)
1029                .unwrap_or(0) as u32;
1030            self.screen_bpp = vga_state
1031                .get(19)
1032                .and_then(serde_json::Value::as_i64)
1033                .unwrap_or(0) as u32;
1034            self.graphical_mode = vga_state
1035                .get(9)
1036                .and_then(serde_json::Value::as_bool)
1037                .unwrap_or(false);
1038            if let Some(value) = vga_state.get(39) {
1039                let buffer_id = value
1040                    .get("buffer_id")
1041                    .and_then(serde_json::Value::as_u64)
1042                    .ok_or_else(|| "VGA state[39] is not a typed buffer".to_owned())?
1043                    as usize;
1044                let svga = buffers
1045                    .get(buffer_id)
1046                    .ok_or_else(|| format!("VGA buffer id {buffer_id} is out of range"))?;
1047                let vga_len = self.vga_bytes as usize;
1048                if svga.len() > vga_len {
1049                    return Err(format!(
1050                        "VGA framebuffer {} exceeds allocated {} bytes",
1051                        svga.len(),
1052                        vga_len
1053                    ));
1054                }
1055                unsafe {
1056                    std::ptr::copy_nonoverlapping(svga.as_ptr(), memory::vga_mem8, svga.len());
1057                }
1058            }
1059            if vga_state.get(6).is_some() {
1060                let text = nested_buffer(vga_state, 6, buffers)?;
1061                let mut target = vga_text_memory()
1062                    .lock()
1063                    .map_err(|_| "VGA text mutex poisoned".to_owned())?;
1064                let copy_len = text.len().min(target.len());
1065                target[..copy_len].copy_from_slice(&text[..copy_len]);
1066                if copy_len < target.len() {
1067                    target[copy_len..].fill(0);
1068                }
1069            }
1070        }
1071
1072        unsafe {
1073            *global_pointers::tss_size_32 = scalar(slots, 64)? != 0;
1074        }
1075        copy_buffer(slots, buffers, 66, unsafe {
1076            std::slice::from_raw_parts_mut(global_pointers::reg_xmm as *mut u8, 128)
1077        })?;
1078        copy_buffer(slots, buffers, 67, unsafe {
1079            std::slice::from_raw_parts_mut(global_pointers::fpu_st as *mut u8, 128)
1080        })?;
1081        unsafe {
1082            *global_pointers::fpu_stack_empty = scalar(slots, 68)? as u8;
1083            *global_pointers::fpu_stack_ptr = scalar(slots, 69)? as u8;
1084            *global_pointers::fpu_control_word = scalar(slots, 70)? as u16;
1085            *global_pointers::fpu_ip = scalar(slots, 71)? as i32;
1086            *global_pointers::fpu_ip_selector = scalar(slots, 72)? as i32;
1087            *global_pointers::fpu_dp = scalar(slots, 73)? as i32;
1088            *global_pointers::fpu_dp_selector = scalar(slots, 74)? as i32;
1089            *global_pointers::fpu_opcode = scalar(slots, 75)? as i32;
1090            *global_pointers::last_result = slots
1091                .get(86)
1092                .and_then(serde_json::Value::as_i64)
1093                .unwrap_or(0) as i32;
1094            *global_pointers::fpu_status_word = slots
1095                .get(87)
1096                .and_then(serde_json::Value::as_i64)
1097                .unwrap_or(0) as u16;
1098            *global_pointers::mxcsr = slots
1099                .get(88)
1100                .and_then(serde_json::Value::as_i64)
1101                .unwrap_or(0x1F80) as i32;
1102        }
1103
1104        let packed_memory = buffer_for(slots, buffers, 77)?;
1105        let bitmap = buffer_for(slots, buffers, 78)?;
1106        unsafe {
1107            std::ptr::write_bytes(memory::mem8, 0, self.ram_bytes as usize);
1108        }
1109        let page_count = self.ram_bytes as usize / 0x1000;
1110        let mut packed_page = 0usize;
1111        for page in 0..page_count {
1112            if bitmap
1113                .get(page >> 3)
1114                .map_or(false, |byte| byte & (1 << (page & 7)) != 0)
1115            {
1116                let src_start = packed_page * 0x1000;
1117                let src_end = src_start + 0x1000;
1118                if src_end > packed_memory.len() {
1119                    return Err("packed memory buffer is shorter than bitmap population".to_owned());
1120                }
1121                unsafe {
1122                    std::ptr::copy_nonoverlapping(
1123                        packed_memory.as_ptr().add(src_start),
1124                        memory::mem8.add(page * 0x1000),
1125                        0x1000,
1126                    );
1127                }
1128                packed_page += 1;
1129            }
1130        }
1131        if packed_page * 0x1000 != packed_memory.len() {
1132            return Err(format!(
1133                "packed memory has {} pages but bitmap references {}",
1134                packed_memory.len() / 0x1000,
1135                packed_page
1136            ));
1137        }
1138
1139        native_devices::restore_state(state, buffers)?;
1140        cpu::update_state_flags();
1141        unsafe {
1142            cpu::full_clear_tlb();
1143        }
1144        Ok(())
1145    }
1146}
1147
1148fn buffer_for<'a>(
1149    state: &[serde_json::Value],
1150    buffers: &'a [Vec<u8>],
1151    index: usize,
1152) -> Result<&'a [u8], String> {
1153    let buffer_id = state
1154        .get(index)
1155        .and_then(serde_json::Value::as_object)
1156        .and_then(|object| object.get("buffer_id"))
1157        .and_then(serde_json::Value::as_u64)
1158        .ok_or_else(|| format!("state[{index}] is not a typed buffer"))?
1159        as usize;
1160    buffers
1161        .get(buffer_id)
1162        .map(Vec::as_slice)
1163        .ok_or_else(|| format!("buffer id {buffer_id} is out of range"))
1164}
1165
1166fn byte_array_from_state(
1167    state: &[serde_json::Value],
1168    len: usize,
1169    name: &str,
1170) -> Result<[u8; 13], String> {
1171    byte_array_from_values(state, len, name)
1172}
1173
1174fn byte_array_from_values(
1175    state: &[serde_json::Value],
1176    len: usize,
1177    name: &str,
1178) -> Result<[u8; 13], String> {
1179    if len != 13 || state.len() < len {
1180        return Err(format!("{name} has {} fields; expected {len}", state.len()));
1181    }
1182    let mut result = [0u8; 13];
1183    for (index, value) in state.iter().take(len).enumerate() {
1184        if index == 5 {
1185            // v86 stores the slave PIC array at master[5]; Pic0 byte five is
1186            // only a legacy dummy slot and is not part of the nested state.
1187            continue;
1188        }
1189        result[index] = value
1190            .as_i64()
1191            .ok_or_else(|| format!("{name}[{index}] is not an integer"))?
1192            as u8;
1193    }
1194    Ok(result)
1195}
1196
1197fn scalar(state: &[serde_json::Value], index: usize) -> Result<i64, String> {
1198    state
1199        .get(index)
1200        .and_then(serde_json::Value::as_i64)
1201        .ok_or_else(|| format!("state[{index}] is not an integer scalar"))
1202}
1203
1204fn copy_buffer(
1205    state: &[serde_json::Value],
1206    buffers: &[Vec<u8>],
1207    index: usize,
1208    target: &mut [u8],
1209) -> Result<(), String> {
1210    let source = buffer_for(state, buffers, index)?;
1211    if source.len() != target.len() {
1212        return Err(format!(
1213            "state[{index}] length {} != expected {}",
1214            source.len(),
1215            target.len()
1216        ));
1217    }
1218    target.copy_from_slice(source);
1219    Ok(())
1220}
1221
1222fn copy_i32_buffer(
1223    state: &[serde_json::Value],
1224    buffers: &[Vec<u8>],
1225    index: usize,
1226    target: &mut [u8],
1227) -> Result<(), String> {
1228    copy_buffer(state, buffers, index, target)
1229}
1230
1231fn copy_u16_buffer(
1232    state: &[serde_json::Value],
1233    buffers: &[Vec<u8>],
1234    index: usize,
1235    target: &mut [u8],
1236) -> Result<(), String> {
1237    copy_buffer(state, buffers, index, target)
1238}
1239
1240fn copy_u32_buffer(
1241    state: &[serde_json::Value],
1242    buffers: &[Vec<u8>],
1243    index: usize,
1244    target: &mut [u8],
1245) -> Result<(), String> {
1246    copy_buffer(state, buffers, index, target)
1247}
1248
1249fn copy_u64_buffer(
1250    state: &[serde_json::Value],
1251    buffers: &[Vec<u8>],
1252    index: usize,
1253    target: &mut [u8],
1254) -> Result<(), String> {
1255    copy_buffer(state, buffers, index, target)
1256}
1257
1258#[cfg(test)]
1259mod keyboard_tests {
1260    use super::keycode_for_ascii;
1261
1262    #[test]
1263    fn maps_lowercase_without_shift() {
1264        assert_eq!(keycode_for_ascii(b'a'), Some((0x1E, false)));
1265        assert_eq!(keycode_for_ascii(b'z'), Some((0x2C, false)));
1266    }
1267
1268    #[test]
1269    fn maps_uppercase_and_punctuation_with_shift() {
1270        assert_eq!(keycode_for_ascii(b'A'), Some((0x1E, true)));
1271        assert_eq!(keycode_for_ascii(b'!'), Some((0x02, true)));
1272        assert_eq!(keycode_for_ascii(b'_'), Some((0x0C, true)));
1273    }
1274
1275    #[test]
1276    fn maps_shell_control_characters() {
1277        assert_eq!(keycode_for_ascii(b' '), Some((0x39, false)));
1278        assert_eq!(keycode_for_ascii(b'\n'), Some((0x1C, false)));
1279        assert_eq!(keycode_for_ascii(b'\t'), Some((0x0F, false)));
1280    }
1281}