1 // Copyright 2022 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 //! Programmable flash device that supports the minimum interface that OVMF
6 //! requires. This is purpose-built to allow OVMF to store UEFI variables in
7 //! the same way that it stores them on QEMU.
8 //!
9 //! For that reason it's heavily based on [QEMU's pflash implementation], while
10 //! taking even more shortcuts, chief among them being the complete lack of CFI
11 //! tables, which systems would normally use to learn how to use the device.
12 //!
13 //! In addition to full-width reads, we only support single byte writes,
14 //! block erases, and status requests, which OVMF uses to probe the device to
15 //! determine if it is pflash.
16 //!
17 //! Note that without SMM support in crosvm (which it doesn't yet have) this
18 //! device is directly accessible to potentially malicious kernels. With SMM
19 //! and the appropriate changes to this device this could be made more secure
20 //! by ensuring only the BIOS is able to touch the pflash.
21 //!
22 //! [QEMU's pflash implementation]: https://github.com/qemu/qemu/blob/master/hw/block/pflash_cfi01.c
23
24 use std::path::PathBuf;
25
26 use anyhow::bail;
27 use base::error;
28 use base::VolatileSlice;
29 use disk::DiskFile;
30 use serde::Deserialize;
31 use serde::Serialize;
32 use snapshot::AnySnapshot;
33
34 use crate::pci::CrosvmDeviceId;
35 use crate::BusAccessInfo;
36 use crate::BusDevice;
37 use crate::DeviceId;
38 use crate::Suspendable;
39
40 const COMMAND_WRITE_BYTE: u8 = 0x10;
41 const COMMAND_BLOCK_ERASE: u8 = 0x20;
42 const COMMAND_CLEAR_STATUS: u8 = 0x50;
43 const COMMAND_READ_STATUS: u8 = 0x70;
44 const COMMAND_BLOCK_ERASE_CONFIRM: u8 = 0xd0;
45 const COMMAND_READ_ARRAY: u8 = 0xff;
46
47 const STATUS_READY: u8 = 0x80;
48
pflash_parameters_default_block_size() -> u3249 fn pflash_parameters_default_block_size() -> u32 {
50 // 4K
51 4 * (1 << 10)
52 }
53
54 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
55 pub struct PflashParameters {
56 pub path: PathBuf,
57 #[serde(default = "pflash_parameters_default_block_size")]
58 pub block_size: u32,
59 }
60
61 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
62 enum State {
63 ReadArray,
64 ReadStatus,
65 BlockErase(u64),
66 Write(u64),
67 }
68
69 pub struct Pflash {
70 image: Box<dyn DiskFile>,
71 image_size: u64,
72 block_size: u32,
73
74 state: State,
75 status: u8,
76 }
77
78 impl Pflash {
new(image: Box<dyn DiskFile>, block_size: u32) -> anyhow::Result<Pflash>79 pub fn new(image: Box<dyn DiskFile>, block_size: u32) -> anyhow::Result<Pflash> {
80 if !block_size.is_power_of_two() {
81 bail!("Block size {} is not a power of 2", block_size);
82 }
83 let image_size = image.get_len()?;
84 if image_size % block_size as u64 != 0 {
85 bail!(
86 "Disk size {} is not a multiple of block size {}",
87 image_size,
88 block_size
89 );
90 }
91
92 Ok(Pflash {
93 image,
94 image_size,
95 block_size,
96 state: State::ReadArray,
97 status: STATUS_READY,
98 })
99 }
100 }
101
102 impl BusDevice for Pflash {
device_id(&self) -> DeviceId103 fn device_id(&self) -> DeviceId {
104 CrosvmDeviceId::Pflash.into()
105 }
106
debug_label(&self) -> String107 fn debug_label(&self) -> String {
108 "pflash".to_owned()
109 }
110
read(&mut self, info: BusAccessInfo, data: &mut [u8])111 fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
112 let offset = info.offset;
113 match &self.state {
114 State::ReadArray => {
115 if offset + data.len() as u64 >= self.image_size {
116 error!("pflash read request beyond disk");
117 return;
118 }
119 if let Err(e) = self
120 .image
121 .read_exact_at_volatile(VolatileSlice::new(data), offset)
122 {
123 error!("pflash failed to read: {}", e);
124 }
125 }
126 State::ReadStatus => {
127 self.state = State::ReadArray;
128 for d in data {
129 *d = self.status;
130 }
131 }
132 _ => {
133 error!(
134 "pflash received unexpected read in state {:?}, recovering to ReadArray mode",
135 self.state
136 );
137 self.state = State::ReadArray;
138 }
139 }
140 }
141
write(&mut self, info: BusAccessInfo, data: &[u8])142 fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
143 if data.len() > 1 {
144 error!("pflash write request for >1 byte, ignoring");
145 return;
146 }
147 let data = data[0];
148 let offset = info.offset;
149
150 match self.state {
151 State::Write(expected_offset) => {
152 self.state = State::ReadArray;
153 self.status = STATUS_READY;
154
155 if offset != expected_offset {
156 error!("pflash received write for offset {} that doesn't match offset from WRITE_BYTE command {}", offset, expected_offset);
157 return;
158 }
159 if offset >= self.image_size {
160 error!(
161 "pflash offset {} greater than image size {}",
162 offset, self.image_size
163 );
164 return;
165 }
166
167 if let Err(e) = self
168 .image
169 .write_all_at_volatile(VolatileSlice::new(&mut [data]), offset)
170 {
171 error!("failed to write to pflash: {}", e);
172 }
173 }
174 State::BlockErase(expected_offset) => {
175 self.state = State::ReadArray;
176 self.status = STATUS_READY;
177
178 if data != COMMAND_BLOCK_ERASE_CONFIRM {
179 error!("pflash write data {} after BLOCK_ERASE command, wanted COMMAND_BLOCK_ERASE_CONFIRM", data);
180 return;
181 }
182 if offset != expected_offset {
183 error!("pflash offset {} for BLOCK_ERASE_CONFIRM command does not match the one for BLOCK_ERASE {}", offset, expected_offset);
184 return;
185 }
186 if offset >= self.image_size {
187 error!(
188 "pflash block erase attempt offset {} beyond image size {}",
189 offset, self.image_size
190 );
191 return;
192 }
193 if offset % self.block_size as u64 != 0 {
194 error!(
195 "pflash block erase offset {} not on block boundary with block size {}",
196 offset, self.block_size
197 );
198 return;
199 }
200
201 if let Err(e) = self.image.write_all_at_volatile(
202 VolatileSlice::new(&mut [0xff].repeat(self.block_size.try_into().unwrap())),
203 offset,
204 ) {
205 error!("pflash failed to erase block: {}", e);
206 }
207 }
208 _ => {
209 // If we're not expecting anything else then assume this is a
210 // command to transition states.
211 let command = data;
212
213 match command {
214 COMMAND_READ_ARRAY => {
215 self.state = State::ReadArray;
216 self.status = STATUS_READY;
217 }
218 COMMAND_READ_STATUS => self.state = State::ReadStatus,
219 COMMAND_CLEAR_STATUS => {
220 self.state = State::ReadArray;
221 self.status = 0;
222 }
223 COMMAND_WRITE_BYTE => self.state = State::Write(offset),
224 COMMAND_BLOCK_ERASE => self.state = State::BlockErase(offset),
225 _ => {
226 error!("received unexpected/unsupported pflash command {}, ignoring and returning to read mode", command);
227 self.state = State::ReadArray
228 }
229 }
230 }
231 }
232 }
233 }
234
235 impl Suspendable for Pflash {
snapshot(&mut self) -> anyhow::Result<AnySnapshot>236 fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
237 AnySnapshot::to_any((self.status, self.state))
238 }
239
restore(&mut self, data: AnySnapshot) -> anyhow::Result<()>240 fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
241 let (status, state) = AnySnapshot::from_any(data)?;
242 self.status = status;
243 self.state = state;
244 Ok(())
245 }
246
sleep(&mut self) -> anyhow::Result<()>247 fn sleep(&mut self) -> anyhow::Result<()> {
248 // TODO(schuffelen): Flush the disk after lifting flush() from AsyncDisk to DiskFile
249 Ok(())
250 }
251
wake(&mut self) -> anyhow::Result<()>252 fn wake(&mut self) -> anyhow::Result<()> {
253 Ok(())
254 }
255 }
256
257 #[cfg(test)]
258 mod tests {
259 use base::FileReadWriteAtVolatile;
260 use tempfile::tempfile;
261
262 use super::*;
263
264 const IMAGE_SIZE: usize = 4 * (1 << 20); // 4M
265 const BLOCK_SIZE: u32 = 4 * (1 << 10); // 4K
266
empty_image() -> Box<dyn DiskFile>267 fn empty_image() -> Box<dyn DiskFile> {
268 let f = Box::new(tempfile().unwrap());
269 f.write_all_at_volatile(VolatileSlice::new(&mut [0xff].repeat(IMAGE_SIZE)), 0)
270 .unwrap();
271 f
272 }
273
new(f: Box<dyn DiskFile>) -> Pflash274 fn new(f: Box<dyn DiskFile>) -> Pflash {
275 Pflash::new(f, BLOCK_SIZE).unwrap()
276 }
277
off(offset: u64) -> BusAccessInfo278 fn off(offset: u64) -> BusAccessInfo {
279 BusAccessInfo {
280 offset,
281 address: 0,
282 id: 0,
283 }
284 }
285
286 #[test]
read()287 fn read() {
288 let f = empty_image();
289 let mut want = [0xde, 0xad, 0xbe, 0xef];
290 let offset = 0x1000;
291 f.write_all_at_volatile(VolatileSlice::new(&mut want), offset)
292 .unwrap();
293
294 let mut pflash = new(f);
295 let mut got = [0u8; 4];
296 pflash.read(off(offset), &mut got[..]);
297 assert_eq!(want, got);
298 }
299
300 #[test]
write()301 fn write() {
302 let f = empty_image();
303 let want = [0xdeu8];
304 let offset = 0x1000;
305
306 let mut pflash = new(f);
307 pflash.write(off(offset), &[COMMAND_WRITE_BYTE]);
308 pflash.write(off(offset), &want);
309
310 // Make sure the data reads back correctly over the bus...
311 pflash.write(off(0), &[COMMAND_READ_ARRAY]);
312 let mut got = [0u8; 1];
313 pflash.read(off(offset), &mut got);
314 assert_eq!(want, got);
315
316 // And from the backing file itself...
317 pflash
318 .image
319 .read_exact_at_volatile(VolatileSlice::new(&mut got), offset)
320 .unwrap();
321 assert_eq!(want, got);
322
323 // And when we recreate the device.
324 let mut pflash = new(pflash.image);
325 pflash.read(off(offset), &mut got);
326 assert_eq!(want, got);
327
328 // Finally make sure our status is ready.
329 let mut got = [0u8; 4];
330 pflash.write(off(offset), &[COMMAND_READ_STATUS]);
331 pflash.read(off(offset), &mut got);
332 let want = [STATUS_READY; 4];
333 assert_eq!(want, got);
334 }
335
336 #[test]
erase()337 fn erase() {
338 let f = empty_image();
339 let mut data = [0xde, 0xad, 0xbe, 0xef];
340 let offset = 0x1000;
341 f.write_all_at_volatile(VolatileSlice::new(&mut data), offset)
342 .unwrap();
343 f.write_all_at_volatile(VolatileSlice::new(&mut data), offset * 2)
344 .unwrap();
345
346 let mut pflash = new(f);
347 pflash.write(off(offset), &[COMMAND_BLOCK_ERASE]);
348 pflash.write(off(offset), &[COMMAND_BLOCK_ERASE_CONFIRM]);
349
350 pflash.write(off(0), &[COMMAND_READ_ARRAY]);
351 let mut got = [0u8; 4];
352 pflash.read(off(offset), &mut got);
353 let want = [0xffu8; 4];
354 assert_eq!(want, got);
355
356 let want = data;
357 pflash.read(off(offset * 2), &mut got);
358 assert_eq!(want, got);
359
360 // Make sure our status is ready.
361 pflash.write(off(offset), &[COMMAND_READ_STATUS]);
362 pflash.read(off(offset), &mut got);
363 let want = [STATUS_READY; 4];
364 assert_eq!(want, got);
365 }
366
367 #[test]
status()368 fn status() {
369 let f = empty_image();
370 let mut data = [0xde, 0xad, 0xbe, 0xff];
371 let offset = 0x0;
372 f.write_all_at_volatile(VolatileSlice::new(&mut data), offset)
373 .unwrap();
374
375 let mut pflash = new(f);
376
377 // Make sure we start off in the "ready" status.
378 pflash.write(off(offset), &[COMMAND_READ_STATUS]);
379 let mut got = [0u8; 4];
380 pflash.read(off(offset), &mut got);
381 let want = [STATUS_READY; 4];
382 assert_eq!(want, got);
383
384 // Make sure we can clear the status properly.
385 pflash.write(off(offset), &[COMMAND_CLEAR_STATUS]);
386 pflash.write(off(offset), &[COMMAND_READ_STATUS]);
387 pflash.read(off(offset), &mut got);
388 let want = [0; 4];
389 assert_eq!(want, got);
390
391 // We implicitly jump back into READ_ARRAY mode after reading the,
392 // status but for OVMF's probe we require that this doesn't actually
393 // affect the cleared status.
394 pflash.read(off(offset), &mut got);
395 pflash.write(off(offset), &[COMMAND_READ_STATUS]);
396 pflash.read(off(offset), &mut got);
397 let want = [0; 4];
398 assert_eq!(want, got);
399 }
400
401 #[test]
overwrite()402 fn overwrite() {
403 let f = empty_image();
404 let data = [0];
405 let offset = off((16 * IMAGE_SIZE).try_into().unwrap());
406
407 // Ensure a write past the pflash device doesn't grow the backing file.
408 let mut pflash = new(f);
409 let old_size = pflash.image.get_len().unwrap();
410 assert_eq!(old_size, IMAGE_SIZE as u64);
411
412 pflash.write(offset, &[COMMAND_WRITE_BYTE]);
413 pflash.write(offset, &data);
414
415 let new_size = pflash.image.get_len().unwrap();
416 assert_eq!(new_size, IMAGE_SIZE as u64);
417 }
418 }
419