xref: /aosp_15_r20/external/crosvm/devices/src/pflash.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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