/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 | | } |