Skip to content

Commit 88f3566

Browse files
Alexandra Ghecencoalxiord
authored andcommitted
Block device live resize
Added support for resizing of block devices (as in: detecting the new size and updating the block device) after the VM has booted. See also: https://sim.amazon.com/issues/P12874968 Fixes #211 Signed-off-by: Alexandra Ghecenco <[email protected]>
1 parent ce94a3a commit 88f3566

File tree

10 files changed

+218
-38
lines changed

10 files changed

+218
-38
lines changed

api_server/src/request/sync/drive.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub enum DriveError {
3636
RootBlockDeviceAlreadyAdded,
3737
InvalidBlockDevicePath,
3838
BlockDevicePathAlreadyExists,
39+
BlockDeviceUpdateFailed,
40+
BlockDeviceUpdateNotAllowed,
3941
NotImplemented,
4042
}
4143

@@ -55,9 +57,17 @@ impl GenerateResponse for DriveError {
5557
StatusCode::BadRequest,
5658
json_fault_message("The block device path was already added to a different drive!"),
5759
),
60+
BlockDeviceUpdateFailed => json_response(
61+
StatusCode::InternalServerError,
62+
json_fault_message("The update operation failed!"),
63+
),
64+
BlockDeviceUpdateNotAllowed => json_response(
65+
StatusCode::Forbidden,
66+
json_fault_message("The update operation is not allowed!"),
67+
),
5868
NotImplemented => json_response(
5969
StatusCode::InternalServerError,
60-
json_fault_message("The update operation is not implemented!"),
70+
json_fault_message("The operation is not implemented!"),
6171
),
6272
}
6373
}
@@ -133,6 +143,18 @@ mod tests {
133143
.status(),
134144
StatusCode::BadRequest
135145
);
146+
assert_eq!(
147+
DriveError::BlockDeviceUpdateFailed
148+
.generate_response()
149+
.status(),
150+
StatusCode::InternalServerError
151+
);
152+
assert_eq!(
153+
DriveError::BlockDeviceUpdateNotAllowed
154+
.generate_response()
155+
.status(),
156+
StatusCode::Forbidden
157+
);
136158
assert_eq!(
137159
DriveError::NotImplemented.generate_response().status(),
138160
StatusCode::InternalServerError

devices/src/bus.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub trait BusDevice: Send {
1919
fn read(&mut self, offset: u64, data: &mut [u8]) {}
2020
/// Writes at `offset` into this device
2121
fn write(&mut self, offset: u64, data: &[u8]) {}
22+
/// Triggers the `irq_mask` interrupt on this device
23+
fn interrupt(&self, irq_mask: u32) {}
2224
}
2325

2426
#[derive(Debug)]
@@ -79,7 +81,7 @@ impl Bus {
7981
None
8082
}
8183

82-
fn get_device(&self, addr: u64) -> Option<(u64, &Mutex<BusDevice>)> {
84+
pub fn get_device(&self, addr: u64) -> Option<(u64, &Mutex<BusDevice>)> {
8385
if let Some((BusRange(start, len), dev)) = self.first_before(addr) {
8486
let offset = addr - start;
8587
if offset < len {

devices/src/virtio/block.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ use std::sync::mpsc;
1414
use {DeviceEventT, EpollHandler};
1515
use super::{ActivateError, ActivateResult};
1616
use epoll;
17-
use super::{DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_BLOCK};
17+
use super::{DescriptorChain, Queue, VirtioDevice, TYPE_BLOCK, VIRTIO_MMIO_INT_VRING};
1818
use sys_util::Result as SysResult;
1919
use sys_util::{EventFd, GuestAddress, GuestMemory, GuestMemoryError};
2020
use virtio_sys::virtio_blk::*;
2121
use virtio_sys::virtio_config::*;
2222

2323
const SECTOR_SHIFT: u8 = 9;
24-
const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
24+
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
2525
const QUEUE_SIZE: u16 = 256;
2626
const NUM_QUEUES: usize = 1;
2727
const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE];
@@ -256,7 +256,7 @@ impl BlockEpollHandler {
256256

257257
fn signal_used_queue(&self) {
258258
self.interrupt_status
259-
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
259+
.fetch_or(VIRTIO_MMIO_INT_VRING as usize, Ordering::SeqCst);
260260
self.interrupt_evt.write(1).unwrap();
261261
}
262262

@@ -320,7 +320,7 @@ pub struct Block {
320320
epoll_config: EpollConfig,
321321
}
322322

323-
fn build_config_space(disk_size: u64) -> Vec<u8> {
323+
pub fn build_config_space(disk_size: u64) -> Vec<u8> {
324324
// We only support disk size, which uses the first two words of the configuration space.
325325
// If the image is not a multiple of the sector size, the tail bits are not exposed.
326326
// The config space is little endian.
@@ -437,6 +437,17 @@ impl VirtioDevice for Block {
437437
}
438438
}
439439

440+
fn write_config(&mut self, offset: u64, data: &[u8]) {
441+
let data_len = data.len() as u64;
442+
let config_len = self.config_space.len() as u64;
443+
if offset + data_len > config_len {
444+
warn!("block: failed to write config space");
445+
return;
446+
}
447+
let (_, right) = self.config_space.split_at_mut(offset as usize);
448+
right.copy_from_slice(&data[..]);
449+
}
450+
440451
fn activate(
441452
&mut self,
442453
mem: GuestMemory,
@@ -773,6 +784,18 @@ mod tests {
773784
let result = b.activate(m.clone(), ievt, stat, queues, queue_evts);
774785

775786
assert!(result.is_ok());
787+
788+
// test write config
789+
let new_config: [u8; 8] = [0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
790+
b.write_config(0, &new_config);
791+
let mut new_config_read = [0u8; 8];
792+
b.read_config(0, &mut new_config_read);
793+
assert_eq!(new_config, new_config_read);
794+
// invalid write
795+
b.write_config(5, &new_config);
796+
new_config_read = [0u8; 8];
797+
b.read_config(0, &mut new_config_read);
798+
assert_eq!(new_config, new_config_read);
776799
}
777800

778801
fn invoke_handler(h: &mut BlockEpollHandler, e: DeviceEventT) {

devices/src/virtio/mmio.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,12 @@ impl BusDevice for MmioDevice {
268268
}
269269

270270
if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
271-
if let Some(interrupt_evt) = self.interrupt_evt.take() {
271+
if let Some(ref interrupt_evt) = self.interrupt_evt {
272272
if let Some(mem) = self.mem.take() {
273273
self.device
274274
.activate(
275275
mem,
276-
interrupt_evt,
276+
interrupt_evt.try_clone().unwrap(),
277277
self.interrupt_status.clone(),
278278
self.queues.clone(),
279279
self.queue_evts.split_off(0),
@@ -284,6 +284,15 @@ impl BusDevice for MmioDevice {
284284
}
285285
}
286286
}
287+
288+
fn interrupt(&self, irq_mask: u32) {
289+
self.interrupt_status
290+
.fetch_or(irq_mask as usize, Ordering::SeqCst);
291+
// interrupt_evt() is safe to unwrap because the inner interrupt_evt is initialized in the
292+
// constructor.
293+
// write() is safe to unwrap because the inner syscall is tailored to be safe as well.
294+
self.interrupt_evt().unwrap().write(1).unwrap();
295+
}
287296
}
288297

289298
#[cfg(test)]

devices/src/virtio/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ const DEVICE_DRIVER_OK: u32 = 0x04;
2323
const DEVICE_FEATURES_OK: u32 = 0x08;
2424
const DEVICE_FAILED: u32 = 0x80;
2525

26-
// Types taken from linux/virtio_ids.h
26+
/// Types taken from linux/virtio_ids.h.
2727
const TYPE_NET: u32 = 1;
2828
const TYPE_BLOCK: u32 = 2;
2929

30-
const INTERRUPT_STATUS_USED_RING: u32 = 0x1;
30+
/// Interrupt flags (re: interrupt status & acknowledge registers).
31+
/// See linux/virtio_mmio.h.
32+
pub const VIRTIO_MMIO_INT_VRING: u32 = 0x01;
33+
pub const VIRTIO_MMIO_INT_CONFIG: u32 = 0x02;
3134

3235
/// Offset from the base MMIO address of a virtio device used by the guest to notify the device of
3336
/// queue events.

devices/src/virtio/net.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use super::{ActivateError, ActivateResult};
2020
use epoll;
2121
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
2222
use net_sys;
23-
use super::{Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_NET};
23+
use super::{Queue, VirtioDevice, TYPE_NET, VIRTIO_MMIO_INT_VRING};
2424
use sys_util::{Error as SysError, EventFd, GuestMemory};
2525
use virtio_sys::virtio_net::*;
2626
use virtio_sys::virtio_config::*;
@@ -85,7 +85,7 @@ struct NetEpollHandler {
8585
impl NetEpollHandler {
8686
fn signal_used_queue(&self) {
8787
self.interrupt_status
88-
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
88+
.fetch_or(VIRTIO_MMIO_INT_VRING as usize, Ordering::SeqCst);
8989
self.interrupt_evt.write(1).unwrap();
9090
}
9191

sys_util/src/tempdir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct TempDir {
2121
}
2222

2323
impl TempDir {
24-
/// Creates a new tempory directory.
24+
/// Creates a new temporary directory.
2525
/// The directory will be removed when the object goes out of scope.
2626
///
2727
/// # Examples

vmm/src/device_config/drive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl BlockDeviceConfigs {
8383
}
8484

8585
// check whether the Device Config belongs to a root device
86-
// we need to satify the condition by which a VMM can only have on root device
86+
// we need to satisfy the condition by which a VMM can only have on root device
8787
if block_device_config.is_root_device {
8888
if self.has_root_block {
8989
return Err(DriveError::RootBlockDeviceAlreadyAdded);

vmm/src/device_manager/mmio.rs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
use std::collections::HashMap;
56
use std::fmt;
67
use std::sync::{Arc, Mutex};
78

@@ -25,6 +26,8 @@ pub enum Error {
2526
Cmdline(kernel_cmdline::Error),
2627
/// No more IRQs are available.
2728
IrqsExhausted,
29+
/// Failed to update the mmio device.
30+
UpdateFailed,
2831
}
2932

3033
impl fmt::Display for Error {
@@ -37,6 +40,7 @@ impl fmt::Display for Error {
3740
write!(f, "unable to add device to kernel command line: {}", e)
3841
}
3942
&Error::IrqsExhausted => write!(f, "no more IRQs are available"),
43+
&Error::UpdateFailed => write!(f, "failed to update the mmio device"),
4044
}
4145
}
4246
}
@@ -54,13 +58,18 @@ const IRQ_BASE: u32 = 5;
5458
/// Currently hardcoded to 4K
5559
const MMIO_LEN: u64 = 0x1000;
5660

61+
/// This represents the offset at which the device should call BusDevice::write in order to write
62+
/// to its configuration space.
63+
const MMIO_CFG_SPACE_OFF: u64 = 0x100;
64+
5765
/// Manages the complexities of adding a device.
5866
pub struct MMIODeviceManager {
5967
pub bus: devices::Bus,
6068
pub vm_requests: Vec<VmRequest>,
6169
guest_mem: GuestMemory,
6270
mmio_base: u64,
6371
irq: u32,
72+
id_to_addr_map: HashMap<String, u64>,
6473
}
6574

6675
impl MMIODeviceManager {
@@ -72,6 +81,7 @@ impl MMIODeviceManager {
7281
mmio_base: mmio_base,
7382
irq: IRQ_BASE,
7483
bus: devices::Bus::new(),
84+
id_to_addr_map: HashMap::new(),
7585
}
7686
}
7787

@@ -80,7 +90,8 @@ impl MMIODeviceManager {
8090
&mut self,
8191
device: Box<devices::virtio::VirtioDevice>,
8292
cmdline: &mut kernel_cmdline::Cmdline,
83-
) -> Result<()> {
93+
id: Option<String>,
94+
) -> Result<u64> {
8495
if self.irq > MAX_IRQ {
8596
return Err(Error::IrqsExhausted);
8697
}
@@ -111,17 +122,43 @@ impl MMIODeviceManager {
111122
// as per doc, [virtio_mmio.]device=<size>@<baseaddr>:<irq> needs to be appended
112123
// to kernel commandline for virtio mmio devices to get recognized
113124
// the size parameter has to be transformed to KiB, so dividing hexadecimal value in
114-
// bytes to 1024; further, the '{}' formatting rust construct will automatically transform it to decimal
125+
// bytes to 1024; further, the '{}' formatting rust construct will automatically
126+
// transform it to decimal
115127
cmdline
116128
.insert(
117129
"virtio_mmio.device",
118130
&format!("{}K@0x{:08x}:{}", MMIO_LEN / 1024, self.mmio_base, self.irq),
119131
)
120132
.map_err(Error::Cmdline)?;
133+
let ret = self.mmio_base;
121134
self.mmio_base += MMIO_LEN;
122135
self.irq += 1;
123136

124-
Ok(())
137+
if let Some(device_id) = id {
138+
self.id_to_addr_map.insert(device_id.clone(), ret);
139+
}
140+
141+
Ok(ret)
142+
}
143+
144+
/// Update a drive by rebuilding its config space and rewriting it on the bus.
145+
pub fn update_drive(&self, addr: u64, new_size: u64) -> Result<()> {
146+
if let Some((_, device)) = self.bus.get_device(addr) {
147+
let data = devices::virtio::build_config_space(new_size);
148+
let mut busdev = device.lock().unwrap();
149+
150+
busdev.write(MMIO_CFG_SPACE_OFF, &data[..]);
151+
busdev.interrupt(devices::virtio::VIRTIO_MMIO_INT_CONFIG);
152+
153+
Ok(())
154+
} else {
155+
Err(Error::UpdateFailed)
156+
}
157+
}
158+
159+
/// Gets the address of the specified device on the bus.
160+
pub fn get_address(&self, id: &String) -> Option<&u64> {
161+
return self.id_to_addr_map.get(id.as_str());
125162
}
126163
}
127164

@@ -176,7 +213,7 @@ mod tests {
176213

177214
assert!(
178215
device_manager
179-
.register_device(dummy_box, &mut cmdline)
216+
.register_device(dummy_box, &mut cmdline, None)
180217
.is_ok()
181218
);
182219
}
@@ -193,14 +230,14 @@ mod tests {
193230
let dummy_box = Box::new(DummyDevice { dummy: 0 });
194231
for _i in IRQ_BASE..(MAX_IRQ + 1) {
195232
device_manager
196-
.register_device(dummy_box.clone(), &mut cmdline)
233+
.register_device(dummy_box.clone(), &mut cmdline, None)
197234
.unwrap();
198235
}
199236
assert_eq!(
200237
format!(
201238
"{}",
202239
device_manager
203-
.register_device(dummy_box.clone(), &mut cmdline)
240+
.register_device(dummy_box.clone(), &mut cmdline, None)
204241
.unwrap_err()
205242
),
206243
"no more IRQs are available".to_string()
@@ -251,5 +288,43 @@ mod tests {
251288
assert_eq!(format!("{}", e), "failed to clone ioeventfd: Error(0)");
252289
let e = Error::CloneIrqFd(sys_util::Error::new(0));
253290
assert_eq!(format!("{}", e), "failed to clone irqfd: Error(0)");
291+
let e = Error::UpdateFailed;
292+
assert_eq!(format!("{}", e), "failed to update the mmio device");
293+
}
294+
295+
#[test]
296+
fn test_update_drive() {
297+
let start_addr1 = GuestAddress(0x0);
298+
let start_addr2 = GuestAddress(0x1000);
299+
let guest_mem =
300+
GuestMemory::new(&vec![(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
301+
let mut device_manager = MMIODeviceManager::new(guest_mem, 0xd0000000);
302+
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
303+
let dummy_box = Box::new(DummyDevice { dummy: 0 });
304+
305+
if let Ok(addr) =
306+
device_manager.register_device(dummy_box, &mut cmdline, Some(String::from("foo")))
307+
{
308+
assert!(device_manager.update_drive(addr, 1048576).is_ok());
309+
}
310+
assert!(device_manager.update_drive(0xbeef, 1048576).is_err());
311+
}
312+
313+
#[test]
314+
fn test_get_address() {
315+
let start_addr1 = GuestAddress(0x0);
316+
let start_addr2 = GuestAddress(0x1000);
317+
let guest_mem =
318+
GuestMemory::new(&vec![(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
319+
let mut device_manager = MMIODeviceManager::new(guest_mem, 0xd0000000);
320+
let mut cmdline = kernel_cmdline::Cmdline::new(4096);
321+
let dummy_box = Box::new(DummyDevice { dummy: 0 });
322+
323+
let id = String::from("foo");
324+
if let Ok(addr) = device_manager.register_device(dummy_box, &mut cmdline, Some(id.clone()))
325+
{
326+
assert_eq!(Some(&addr), device_manager.get_address(&id));
327+
}
328+
assert_eq!(None, device_manager.get_address(&String::from("bar")));
254329
}
255330
}

0 commit comments

Comments
 (0)