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