Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/pflash.rs
5394 views
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
use vm_control::DeviceId;
34
use vm_control::PlatformDeviceId;
35
36
use crate::BusAccessInfo;
37
use crate::BusDevice;
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
49
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 {
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 {
103
fn device_id(&self) -> DeviceId {
104
PlatformDeviceId::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
235
impl 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)]
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
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
}
419
420