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
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Virtio console device control queue handling.

use std::collections::VecDeque;
use std::io::Write;

use anyhow::anyhow;
use anyhow::Context;
use base::debug;
use base::error;
use zerocopy::AsBytes;

use crate::virtio::console::worker::WorkerPort;
use crate::virtio::device_constants::console::virtio_console_control;
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_CONSOLE_PORT;
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_DEVICE_ADD;
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_DEVICE_READY;
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_PORT_NAME;
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_PORT_OPEN;
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_PORT_READY;
use crate::virtio::Queue;
use crate::virtio::Reader;

pub type ControlMsgBytes = Box<[u8]>;

fn control_msg(id: u32, event: u16, value: u16, extra_bytes: &[u8]) -> ControlMsgBytes {
    virtio_console_control {
        id: id.into(),
        event: event.into(),
        value: value.into(),
    }
    .as_bytes()
    .iter()
    .chain(extra_bytes.iter())
    .copied()
    .collect()
}

fn process_control_msg(
    reader: &mut Reader,
    ports: &[WorkerPort],
    pending_receive_control_msgs: &mut VecDeque<ControlMsgBytes>,
) -> anyhow::Result<()> {
    let ctrl_msg: virtio_console_control =
        reader.read_obj().context("failed to read from reader")?;
    let id = ctrl_msg.id.to_native();
    let event = ctrl_msg.event.to_native();
    let value = ctrl_msg.value.to_native();

    match event {
        VIRTIO_CONSOLE_DEVICE_READY => {
            // value of 1 indicates success, and 0 indicates failure
            if value != 1 {
                return Err(anyhow!("console device ready failure ({value})"));
            }

            for (index, port) in ports.iter().enumerate() {
                let port_id = index as u32;
                // TODO(dverkamp): cap the size of `pending_receive_control_msgs` somehow
                pending_receive_control_msgs.push_back(control_msg(
                    port_id,
                    VIRTIO_CONSOLE_DEVICE_ADD,
                    0,
                    &[],
                ));

                if let Some(name) = port.name() {
                    pending_receive_control_msgs.push_back(control_msg(
                        port_id,
                        VIRTIO_CONSOLE_PORT_NAME,
                        0,
                        name.as_bytes(),
                    ));
                }
            }
            Ok(())
        }
        VIRTIO_CONSOLE_PORT_READY => {
            // value of 1 indicates success, and 0 indicates failure
            if value != 1 {
                return Err(anyhow!("console port{id} ready failure ({value})"));
            }

            let port = ports
                .get(id as usize)
                .with_context(|| format!("invalid port id {id}"))?;

            pending_receive_control_msgs.push_back(control_msg(
                id,
                VIRTIO_CONSOLE_PORT_OPEN,
                1,
                &[],
            ));

            if port.is_console() {
                pending_receive_control_msgs.push_back(control_msg(
                    id,
                    VIRTIO_CONSOLE_CONSOLE_PORT,
                    1,
                    &[],
                ));
            }
            Ok(())
        }
        VIRTIO_CONSOLE_PORT_OPEN => {
            match value {
                // Currently, port state change is not supported, default is open.
                // And only print debug info here.
                0 => debug!("console port{id} close"),
                1 => debug!("console port{id} open"),
                _ => error!("console port{id} unknown value {value}"),
            }
            Ok(())
        }
        _ => Err(anyhow!("unexpected control event {}", event)),
    }
}

pub fn process_control_transmit_queue(
    queue: &mut Queue,
    ports: &[WorkerPort],
    pending_receive_control_msgs: &mut VecDeque<ControlMsgBytes>,
) {
    let mut needs_interrupt = false;

    while let Some(mut avail_desc) = queue.pop() {
        if let Err(e) =
            process_control_msg(&mut avail_desc.reader, ports, pending_receive_control_msgs)
        {
            error!("failed to handle control msg: {:#}", e);
        }

        queue.add_used(avail_desc, 0);
        needs_interrupt = true;
    }

    if needs_interrupt {
        queue.trigger_interrupt();
    }
}

pub fn process_control_receive_queue(
    queue: &mut Queue,
    pending_receive_control_msgs: &mut VecDeque<ControlMsgBytes>,
) {
    let mut needs_interrupt = false;

    while !pending_receive_control_msgs.is_empty() {
        let Some(mut avail_desc) = queue.pop() else {
            break;
        };

        // Get a reply to copy into `avail_desc`. This should never fail since we check that
        // `pending_receive_control_msgs` is not empty in the loop condition.
        let reply = pending_receive_control_msgs
            .pop_front()
            .expect("missing reply");

        let len = match avail_desc.writer.write_all(&reply) {
            Ok(()) => avail_desc.writer.bytes_written() as u32,
            Err(e) => {
                error!("failed to write control receiveq reply: {}", e);
                0
            }
        };

        queue.add_used(avail_desc, len);
        needs_interrupt = true;
    }

    if needs_interrupt {
        queue.trigger_interrupt();
    }
}