1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// SPDX-License-Identifier: MPL-2.0

//! The console I/O.

#![allow(dead_code)]
#![allow(unused_variables)]

use alloc::{fmt, sync::Arc, vec::Vec};
use core::fmt::Write;

use log::debug;
use spin::Once;
use trapframe::TrapFrame;

use super::{device::serial::SerialPort, kernel::IO_APIC};
use crate::{sync::SpinLock, trap::IrqLine};

/// Prints the formatted arguments to the standard output.
#[inline]
pub fn print(args: fmt::Arguments) {
    Stdout.write_fmt(args).unwrap();
}

/// The callback function for console input.
pub type InputCallback = dyn Fn(u8) + Send + Sync + 'static;

/// Registers a callback function to be called when there is console input.
pub fn register_console_input_callback(f: &'static InputCallback) {
    SERIAL_INPUT_CALLBACKS.lock_irq_disabled().push(Arc::new(f));
}

struct Stdout;

impl Write for Stdout {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for &c in s.as_bytes() {
            send(c);
        }
        Ok(())
    }
}

bitflags::bitflags! {
  struct LineSts: u8 {
    const INPUT_FULL = 1;
    const OUTPUT_EMPTY = 1 << 5;
  }
}

static CONSOLE_COM1_PORT: SerialPort = unsafe { SerialPort::new(0x3F8) };

static CONSOLE_IRQ_CALLBACK: Once<SpinLock<IrqLine>> = Once::new();
static SERIAL_INPUT_CALLBACKS: SpinLock<Vec<Arc<InputCallback>>> = SpinLock::new(Vec::new());

/// Initializes the serial port.
pub(crate) fn init() {
    CONSOLE_COM1_PORT.init();
}

pub(crate) fn callback_init() {
    let mut irq = if !IO_APIC.is_completed() {
        crate::arch::x86::kernel::pic::allocate_irq(4).unwrap()
    } else {
        let irq = IrqLine::alloc().unwrap();
        let mut io_apic = IO_APIC.get().unwrap().first().unwrap().lock();
        io_apic.enable(4, irq.clone()).unwrap();
        irq
    };
    irq.on_active(handle_serial_input);
    CONSOLE_IRQ_CALLBACK.call_once(|| SpinLock::new(irq));
}

pub(crate) fn register_console_callback<F>(callback: F)
where
    F: Fn(&TrapFrame) + Sync + Send + 'static,
{
    CONSOLE_IRQ_CALLBACK
        .get()
        .unwrap()
        .lock_irq_disabled()
        .on_active(callback);
}

fn handle_serial_input(trap_frame: &TrapFrame) {
    // debug!("keyboard interrupt was met");
    let lock = if let Some(lock) = SERIAL_INPUT_CALLBACKS.try_lock() {
        lock
    } else {
        return;
    };
    let received_char = receive_char().unwrap();
    debug!("receive char = {:?}", received_char);
    for callback in lock.iter() {
        callback(received_char);
    }
}

fn line_sts() -> LineSts {
    LineSts::from_bits_truncate(CONSOLE_COM1_PORT.line_status.read())
}

/// Sends a byte on the serial port.
pub fn send(data: u8) {
    match data {
        8 | 0x7F => {
            while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
            CONSOLE_COM1_PORT.data.write(8);
            while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
            CONSOLE_COM1_PORT.data.write(b' ');
            while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
            CONSOLE_COM1_PORT.data.write(8);
        }
        _ => {
            while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
            CONSOLE_COM1_PORT.data.write(data);
        }
    }
}

/// Receives a byte on the serial port. non-blocking
pub fn receive_char() -> Option<u8> {
    if line_sts().contains(LineSts::INPUT_FULL) {
        Some(CONSOLE_COM1_PORT.data.read())
    } else {
        None
    }
}