Coverage Report

Created: 2026-04-09 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/crosvm/devices/src/virtio/rng.rs
Line
Count
Source
1
// Copyright 2017 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
use std::collections::BTreeMap;
6
use std::io::Write;
7
8
use anyhow::anyhow;
9
use anyhow::Context;
10
use base::error;
11
use base::warn;
12
use base::Event;
13
use base::EventToken;
14
use base::RawDescriptor;
15
use base::WaitContext;
16
use base::WorkerThread;
17
use snapshot::AnySnapshot;
18
use vm_memory::GuestMemory;
19
20
use super::DeviceType;
21
use super::Interrupt;
22
use super::Queue;
23
use super::VirtioDevice;
24
25
const QUEUE_SIZE: u16 = 256;
26
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
27
28
// Chosen to match the Linux guest driver RNG buffer refill size.
29
const CHUNK_SIZE: usize = 64;
30
31
struct Worker {
32
    queue: Queue,
33
}
34
35
impl Worker {
36
0
    fn process_queue(&mut self) {
37
0
        let mut needs_interrupt = false;
38
39
0
        while let Some(mut avail_desc) = self.queue.pop() {
40
0
            let writer = &mut avail_desc.writer;
41
0
            while writer.available_bytes() > 0 {
42
0
                let chunk_size = writer.available_bytes().min(CHUNK_SIZE);
43
0
                let rand_bytes: [u8; CHUNK_SIZE] = rand::random();
44
0
                let chunk = &rand_bytes[..chunk_size];
45
0
                if let Err(e) = writer.write_all(chunk) {
46
0
                    warn!("Failed to write random data to the guest: {}", e);
47
0
                    break;
48
0
                }
49
            }
50
51
0
            self.queue.add_used(avail_desc);
52
0
            needs_interrupt = true;
53
        }
54
55
0
        if needs_interrupt {
56
0
            self.queue.trigger_interrupt();
57
0
        }
58
0
    }
59
60
0
    fn run(&mut self, kill_evt: Event) -> anyhow::Result<()> {
61
        #[derive(EventToken)]
62
        enum Token {
63
            QueueAvailable,
64
            Kill,
65
        }
66
67
0
        let wait_ctx = WaitContext::build_with(&[
68
0
            (self.queue.event(), Token::QueueAvailable),
69
0
            (&kill_evt, Token::Kill),
70
0
        ])
71
0
        .context("failed creating WaitContext")?;
72
73
0
        let mut exiting = false;
74
0
        while !exiting {
75
0
            let events = wait_ctx.wait().context("failed polling for events")?;
76
0
            for event in events.iter().filter(|e| e.is_readable) {
77
0
                match event.token {
78
                    Token::QueueAvailable => {
79
0
                        self.queue
80
0
                            .event()
81
0
                            .wait()
82
0
                            .context("failed reading queue Event")?;
83
0
                        self.process_queue();
84
                    }
85
0
                    Token::Kill => exiting = true,
86
                }
87
            }
88
        }
89
90
0
        Ok(())
91
0
    }
92
}
93
94
/// Virtio device for exposing entropy to the guest OS through virtio.
95
pub struct Rng {
96
    worker_thread: Option<WorkerThread<Worker>>,
97
    virtio_features: u64,
98
}
99
100
impl Rng {
101
    /// Create a new virtio rng device that gets random data from /dev/urandom.
102
0
    pub fn new(virtio_features: u64) -> anyhow::Result<Rng> {
103
0
        Ok(Rng {
104
0
            worker_thread: None,
105
0
            virtio_features,
106
0
        })
107
0
    }
108
}
109
110
impl VirtioDevice for Rng {
111
0
    fn keep_rds(&self) -> Vec<RawDescriptor> {
112
0
        Vec::new()
113
0
    }
114
115
0
    fn device_type(&self) -> DeviceType {
116
0
        DeviceType::Rng
117
0
    }
118
119
0
    fn queue_max_sizes(&self) -> &[u16] {
120
0
        QUEUE_SIZES
121
0
    }
122
123
0
    fn features(&self) -> u64 {
124
0
        self.virtio_features
125
0
    }
126
127
0
    fn activate(
128
0
        &mut self,
129
0
        _mem: GuestMemory,
130
0
        _interrupt: Interrupt,
131
0
        mut queues: BTreeMap<usize, Queue>,
132
0
    ) -> anyhow::Result<()> {
133
0
        if queues.len() != 1 {
134
0
            return Err(anyhow!("expected 1 queue, got {}", queues.len()));
135
0
        }
136
137
0
        let queue = queues.remove(&0).unwrap();
138
139
0
        self.worker_thread = Some(WorkerThread::start("v_rng", move |kill_evt| {
140
0
            let mut worker = Worker { queue };
141
0
            if let Err(e) = worker.run(kill_evt) {
142
0
                error!("rng worker thread failed: {:#}", e);
143
0
            }
144
0
            worker
145
0
        }));
146
147
0
        Ok(())
148
0
    }
149
150
0
    fn reset(&mut self) -> anyhow::Result<()> {
151
0
        if let Some(worker_thread) = self.worker_thread.take() {
152
0
            let _worker = worker_thread.stop();
153
0
        }
154
0
        Ok(())
155
0
    }
156
157
0
    fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
158
0
        if let Some(worker_thread) = self.worker_thread.take() {
159
0
            let worker = worker_thread.stop();
160
0
            return Ok(Some(BTreeMap::from([(0, worker.queue)])));
161
0
        }
162
0
        Ok(None)
163
0
    }
164
165
0
    fn virtio_wake(
166
0
        &mut self,
167
0
        queues_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
168
0
    ) -> anyhow::Result<()> {
169
0
        if let Some((mem, interrupt, queues)) = queues_state {
170
0
            self.activate(mem, interrupt, queues)?;
171
0
        }
172
0
        Ok(())
173
0
    }
174
175
0
    fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
176
        // `virtio_sleep` ensures there is no pending state, except for the `Queue`s, which are
177
        // handled at a higher layer.
178
0
        AnySnapshot::to_any(())
179
0
    }
180
181
0
    fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
182
0
        let () = AnySnapshot::from_any(data)?;
183
0
        Ok(())
184
0
    }
185
}