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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Creation and management of virtual consoles or terminals atop Theseus.

#![no_std]

extern crate alloc;

use alloc::{format, sync::Arc};
use sync_channel::Receiver;
use core::sync::atomic::{AtomicU16, Ordering};
use core2::io::Write;
use sync_irq::IrqSafeMutex;
use log::{error, info, warn};
use serial_port::{get_serial_port, DataChunk, SerialPort, SerialPortAddress};
use task::{JoinableTaskRef, KillReason};

/// The serial port being used for the default system logger can optionally
/// ignore inputs.
static IGNORED_SERIAL_PORT_INPUT: AtomicU16 = AtomicU16::new(u16::MAX);

/// Configures the console connection listener to ignore inputs from the given
/// serial port.
///
/// Only one serial port can be ignored, typically the one used for system
/// logging.
pub fn ignore_serial_port_input(serial_port_address: u16) {
    IGNORED_SERIAL_PORT_INPUT.store(serial_port_address, Ordering::Relaxed)
}

/// Starts a new task that detects new console connections
/// by waiting for new data to be received on serial ports.
///
/// Returns the newly-spawned detection task.
pub fn start_connection_detection() -> Result<JoinableTaskRef, &'static str> {
    let (sender, receiver) = sync_channel::new_channel(4);
    serial_port::set_connection_listener(sender);

    spawn::new_task_builder(console_connection_detector, receiver)
        .name("console_connection_detector".into())
        .spawn()
}

/// The entry point for the console connection detector task.
fn console_connection_detector(
    connection_listener: Receiver<SerialPortAddress>,
) -> Result<(), &'static str> {
    loop {
        let serial_port_address = connection_listener.receive().map_err(|e| {
            error!("Error receiving console connection request: {:?}", e);
            "error receiving console connection request"
        })?;

        if IGNORED_SERIAL_PORT_INPUT.load(Ordering::Relaxed) == serial_port_address as u16 {
            warn!(
				"Currently ignoring inputs on serial port {:?}. \
				 \n --> Note: QEMU is forwarding control sequences (like Ctrl+C) to Theseus. To exit QEMU, press Ctrl+A then X.",
				serial_port_address,
			);
            continue;
        }

        let serial_port = match get_serial_port(serial_port_address) {
            Some(sp) => sp.clone(),
            _ => {
                error!(
                    "Serial port {:?} was not initialized, skipping console connection request",
                    serial_port_address
                );
                continue;
            }
        };

        let (sender, receiver) = sync_channel::new_channel(16);
        if serial_port.lock().set_data_sender(sender).is_err() {
            warn!(
                "Serial port {:?} already had a data sender, skipping console connection request",
                serial_port_address
            );
            continue;
        }

        if spawn::new_task_builder(shell_loop, (serial_port, serial_port_address, receiver))
            .name(format!("{serial_port_address:?}_manager"))
            .spawn()
            .is_err()
        {
            warn!(
                "failed to spawn manager for serial port {:?}",
                serial_port_address
            );
        }
    }
}

fn shell_loop(
    (port, address, receiver): (
        Arc<IrqSafeMutex<SerialPort>>,
        SerialPortAddress,
        Receiver<DataChunk>,
    ),
) -> Result<(), &'static str> {
    info!("creating new tty for serial port {:?}", address);

    let tty = tty::Tty::new();

    let reader_task = spawn::new_task_builder(tty_to_port_loop, (port.clone(), tty.master()))
        .name(format!("tty_to_{address:?}"))
        .spawn()?;
    let writer_task = spawn::new_task_builder(port_to_tty_loop, (receiver, tty.master()))
        .name(format!("{address:?}_to_tty"))
        .spawn()?;


    let new_app_ns = mod_mgmt::create_application_namespace(None)?;

    let (app_file, _ns) =
        mod_mgmt::CrateNamespace::get_crate_object_file_starting_with(&new_app_ns, "hull-")
            .expect("Couldn't find hull in default app namespace");

    let path = app_file.lock().get_absolute_path();
    let task = spawn::new_application_task_builder(path.as_ref(), Some(new_app_ns))?
        .name(format!("{address:?}_hull"))
        .block()
        .spawn()?;

    let id = task.id;
    let stream = Arc::new(tty.slave());
    app_io::insert_child_streams(
        id,
        app_io::IoStreams {
            discipline: Some(stream.discipline()),
            stdin: stream.clone(),
            stdout: stream.clone(),
            stderr: stream,
        },
    );

    task.unblock().map_err(|_| "couldn't unblock hull task")?;
    task.join()?;

    reader_task.kill(KillReason::Requested).unwrap();
    writer_task.kill(KillReason::Requested).unwrap();

    // Flush the tty in case the reader task didn't run between the last time the
    // shell wrote something to the slave end and us killing the task.
    let mut data = [0; 256];
    if let Ok(len) = tty.master().try_read(&mut data) {
        port.lock()
            .write(&data[..len])
            .map_err(|_| "couldn't write to serial port")?;
    };

    // TODO: Close port?

    Ok(())
}

fn tty_to_port_loop((port, master): (Arc<IrqSafeMutex<SerialPort>>, tty::Master)) {
    let mut data = [0; 256];
    loop {
        let len = match master.read(&mut data) {
            Ok(l) => l,
            Err(e) => {
                error!("couldn't read from master: {e}");
                continue;
            }
        };

        if let Err(e) = port.lock().write(&data[..len]) {
            error!("couldn't write to port: {e}");
        }
    }
}

fn port_to_tty_loop((receiver, master): (Receiver<DataChunk>, tty::Master)) {
    loop {
        let DataChunk { data, len } = match receiver.receive() {
            Ok(d) => d,
            Err(e) => {
                error!("couldn't read from port: {e:?}");
                continue;
            },
        };

        if let Err(e) = master.write(&data[..len as usize]) {
            error!("couldn't write to master: {e}");
        }
    }
}