Coverage Report

Created: 2026-03-31 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/crosvm/devices/src/virtio/console/control.rs
Line
Count
Source
1
// Copyright 2024 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Virtio console device control queue handling.
6
7
use std::collections::VecDeque;
8
use std::io::Write;
9
10
use anyhow::anyhow;
11
use anyhow::Context;
12
use base::debug;
13
use base::error;
14
use zerocopy::IntoBytes;
15
16
use crate::virtio::console::worker::WorkerPort;
17
use crate::virtio::device_constants::console::virtio_console_control;
18
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_CONSOLE_PORT;
19
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_DEVICE_ADD;
20
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_DEVICE_READY;
21
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_PORT_NAME;
22
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_PORT_OPEN;
23
use crate::virtio::device_constants::console::VIRTIO_CONSOLE_PORT_READY;
24
use crate::virtio::Queue;
25
use crate::virtio::Reader;
26
27
pub type ControlMsgBytes = Box<[u8]>;
28
29
0
fn control_msg(id: u32, event: u16, value: u16, extra_bytes: &[u8]) -> ControlMsgBytes {
30
0
    virtio_console_control {
31
0
        id: id.into(),
32
0
        event: event.into(),
33
0
        value: value.into(),
34
0
    }
35
0
    .as_bytes()
36
0
    .iter()
37
0
    .chain(extra_bytes.iter())
38
0
    .copied()
39
0
    .collect()
40
0
}
41
42
0
fn process_control_msg(
43
0
    reader: &mut Reader,
44
0
    ports: &[WorkerPort],
45
0
    pending_receive_control_msgs: &mut VecDeque<ControlMsgBytes>,
46
0
) -> anyhow::Result<()> {
47
0
    let ctrl_msg: virtio_console_control =
48
0
        reader.read_obj().context("failed to read from reader")?;
49
0
    let id = ctrl_msg.id.to_native();
50
0
    let event = ctrl_msg.event.to_native();
51
0
    let value = ctrl_msg.value.to_native();
52
53
0
    match event {
54
        VIRTIO_CONSOLE_DEVICE_READY => {
55
            // value of 1 indicates success, and 0 indicates failure
56
0
            if value != 1 {
57
0
                return Err(anyhow!("console device ready failure ({value})"));
58
0
            }
59
60
0
            for (index, port) in ports.iter().enumerate() {
61
0
                let port_id = index as u32;
62
                // TODO(dverkamp): cap the size of `pending_receive_control_msgs` somehow
63
0
                pending_receive_control_msgs.push_back(control_msg(
64
0
                    port_id,
65
                    VIRTIO_CONSOLE_DEVICE_ADD,
66
                    0,
67
0
                    &[],
68
                ));
69
70
0
                if let Some(name) = port.name() {
71
0
                    pending_receive_control_msgs.push_back(control_msg(
72
0
                        port_id,
73
0
                        VIRTIO_CONSOLE_PORT_NAME,
74
0
                        0,
75
0
                        name.as_bytes(),
76
0
                    ));
77
0
                }
78
            }
79
0
            Ok(())
80
        }
81
        VIRTIO_CONSOLE_PORT_READY => {
82
            // value of 1 indicates success, and 0 indicates failure
83
0
            if value != 1 {
84
0
                return Err(anyhow!("console port{id} ready failure ({value})"));
85
0
            }
86
87
0
            let port = ports
88
0
                .get(id as usize)
89
0
                .with_context(|| format!("invalid port id {id}"))?;
90
91
0
            pending_receive_control_msgs.push_back(control_msg(
92
0
                id,
93
                VIRTIO_CONSOLE_PORT_OPEN,
94
                1,
95
0
                &[],
96
            ));
97
98
0
            if port.is_console() {
99
0
                pending_receive_control_msgs.push_back(control_msg(
100
0
                    id,
101
0
                    VIRTIO_CONSOLE_CONSOLE_PORT,
102
0
                    1,
103
0
                    &[],
104
0
                ));
105
0
            }
106
0
            Ok(())
107
        }
108
        VIRTIO_CONSOLE_PORT_OPEN => {
109
0
            match value {
110
                // Currently, port state change is not supported, default is open.
111
                // And only print debug info here.
112
0
                0 => debug!("console port{id} close"),
113
0
                1 => debug!("console port{id} open"),
114
0
                _ => error!("console port{id} unknown value {value}"),
115
            }
116
0
            Ok(())
117
        }
118
0
        _ => Err(anyhow!("unexpected control event {}", event)),
119
    }
120
0
}
121
122
0
pub fn process_control_transmit_queue(
123
0
    queue: &mut Queue,
124
0
    ports: &[WorkerPort],
125
0
    pending_receive_control_msgs: &mut VecDeque<ControlMsgBytes>,
126
0
) {
127
0
    let mut needs_interrupt = false;
128
129
0
    while let Some(mut avail_desc) = queue.pop() {
130
0
        if let Err(e) =
131
0
            process_control_msg(&mut avail_desc.reader, ports, pending_receive_control_msgs)
132
        {
133
0
            error!("failed to handle control msg: {:#}", e);
134
0
        }
135
136
0
        queue.add_used(avail_desc);
137
0
        needs_interrupt = true;
138
    }
139
140
0
    if needs_interrupt {
141
0
        queue.trigger_interrupt();
142
0
    }
143
0
}
144
145
0
pub fn process_control_receive_queue(
146
0
    queue: &mut Queue,
147
0
    pending_receive_control_msgs: &mut VecDeque<ControlMsgBytes>,
148
0
) {
149
0
    let mut needs_interrupt = false;
150
151
0
    while !pending_receive_control_msgs.is_empty() {
152
0
        let Some(mut avail_desc) = queue.pop() else {
153
0
            break;
154
        };
155
156
        // Get a reply to copy into `avail_desc`. This should never fail since we check that
157
        // `pending_receive_control_msgs` is not empty in the loop condition.
158
0
        let reply = pending_receive_control_msgs
159
0
            .pop_front()
160
0
            .expect("missing reply");
161
162
0
        let len = match avail_desc.writer.write_all(&reply) {
163
0
            Ok(()) => avail_desc.writer.bytes_written() as u32,
164
0
            Err(e) => {
165
0
                error!("failed to write control receiveq reply: {}", e);
166
0
                0
167
            }
168
        };
169
170
0
        queue.add_used_with_bytes_written(avail_desc, len);
171
0
        needs_interrupt = true;
172
    }
173
174
0
    if needs_interrupt {
175
0
        queue.trigger_interrupt();
176
0
    }
177
0
}