devices/virtio/console/
control.rs

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