devices/
pflash.rs

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
24use std::path::PathBuf;
25
26use anyhow::bail;
27use base::error;
28use base::VolatileSlice;
29use disk::DiskFile;
30use serde::Deserialize;
31use serde::Serialize;
32use snapshot::AnySnapshot;
33
34use crate::pci::CrosvmDeviceId;
35use crate::BusAccessInfo;
36use crate::BusDevice;
37use crate::DeviceId;
38use crate::Suspendable;
39
40const COMMAND_WRITE_BYTE: u8 = 0x10;
41const COMMAND_BLOCK_ERASE: u8 = 0x20;
42const COMMAND_CLEAR_STATUS: u8 = 0x50;
43const COMMAND_READ_STATUS: u8 = 0x70;
44const COMMAND_BLOCK_ERASE_CONFIRM: u8 = 0xd0;
45const COMMAND_READ_ARRAY: u8 = 0xff;
46
47const STATUS_READY: u8 = 0x80;
48
49fn pflash_parameters_default_block_size() -> u32 {
50    // 4K
51    4 * (1 << 10)
52}
53
54#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
55pub 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)]
62enum State {
63    ReadArray,
64    ReadStatus,
65    BlockErase(u64),
66    Write(u64),
67}
68
69pub struct Pflash {
70    image: Box<dyn DiskFile>,
71    image_size: u64,
72    block_size: u32,
73
74    state: State,
75    status: u8,
76}
77
78impl 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
102impl BusDevice for Pflash {
103    fn device_id(&self) -> DeviceId {
104        CrosvmDeviceId::Pflash.into()
105    }
106
107    fn debug_label(&self) -> String {
108        "pflash".to_owned()
109    }
110
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
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
235impl Suspendable for Pflash {
236    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
237        AnySnapshot::to_any((self.status, self.state))
238    }
239
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
247    fn sleep(&mut self) -> anyhow::Result<()> {
248        // TODO(schuffelen): Flush the disk after lifting flush() from AsyncDisk to DiskFile
249        Ok(())
250    }
251
252    fn wake(&mut self) -> anyhow::Result<()> {
253        Ok(())
254    }
255}
256
257#[cfg(test)]
258mod 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
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
274    fn new(f: Box<dyn DiskFile>) -> Pflash {
275        Pflash::new(f, BLOCK_SIZE).unwrap()
276    }
277
278    fn off(offset: u64) -> BusAccessInfo {
279        BusAccessInfo {
280            offset,
281            address: 0,
282            id: 0,
283        }
284    }
285
286    #[test]
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]
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]
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]
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]
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}