crosvm: align pmem region size to 2MiB

Linux commit 7ea6216049ff9cf250a6722cd766d99c8d1424e5 "mm/sparsemem: prepare
for sub-section ranges" added validation of memory region sizes for hotplugging.
This requires alignment of the region to 2MiB, which can be done with a
MemoryMappingArena that will pad the end of the region with read-only pages.

BUG=chromium:1031408
TEST=crostini.Sanity.artifact with 5.4 guest kernel

Change-Id: I526f23a5ef32edd3268cd23f010e2bc20f9c305a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1979257
Tested-by: kokoro <noreply+kokoro@google.com>
Tested-by: Stephen Barber <smbarber@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Commit-Queue: Stephen Barber <smbarber@chromium.org>
This commit is contained in:
Stephen Barber 2019-12-20 12:43:35 -08:00 committed by Commit Bot
parent 0b788defc6
commit dc7c07bd08

View file

@ -50,7 +50,7 @@ use sys_util::{
self, block_signal, clear_signal, drop_capabilities, error, flock, get_blocked_signals,
get_group_id, get_user_id, getegid, geteuid, info, register_rt_signal_handler,
set_cpu_affinity, validate_raw_fd, warn, EventFd, FlockOperation, GuestAddress, GuestMemory,
Killable, MemoryMapping, PollContext, PollToken, Protection, SignalFd, Terminal, TimerFd,
Killable, MemoryMappingArena, PollContext, PollToken, Protection, SignalFd, Terminal, TimerFd,
WatchingEvents, SIGRTMIN,
};
use vhost;
@ -841,9 +841,26 @@ fn create_pmem_device(
.open(&disk.path)
.map_err(Error::Disk)?;
let image_size = {
let (disk_size, arena_size) = {
let metadata = std::fs::metadata(&disk.path).map_err(Error::Disk)?;
metadata.len()
let disk_len = metadata.len();
// Linux requires pmem region sizes to be 2 MiB aligned. Linux will fill any partial page
// at the end of an mmap'd file and won't write back beyond the actual file length, but if
// we just align the size of the file to 2 MiB then access beyond the last page of the
// mapped file will generate SIGBUS. So use a memory mapping arena that will provide
// padding up to 2 MiB.
let alignment = 2 * 1024 * 1024;
let align_adjust = if disk_len % alignment != 0 {
alignment - (disk_len % alignment)
} else {
0
};
(
disk_len,
disk_len
.checked_add(align_adjust)
.ok_or(Error::PmemDeviceImageTooBig)?,
)
};
let protection = {
@ -854,18 +871,22 @@ fn create_pmem_device(
}
};
let memory_mapping = {
let arena = {
// Conversion from u64 to usize may fail on 32bit system.
let image_size = usize::try_from(image_size).map_err(|_| Error::PmemDeviceImageTooBig)?;
let arena_size = usize::try_from(arena_size).map_err(|_| Error::PmemDeviceImageTooBig)?;
let disk_size = usize::try_from(disk_size).map_err(|_| Error::PmemDeviceImageTooBig)?;
MemoryMapping::from_fd_offset_protection(&fd, image_size, 0, protection)
.map_err(Error::ReservePmemMemory)?
let mut arena = MemoryMappingArena::new(arena_size).map_err(Error::ReservePmemMemory)?;
arena
.add_fd_offset_protection(0, disk_size, &fd, 0, protection)
.map_err(Error::ReservePmemMemory)?;
arena
};
let mapping_address = resources
.mmio_allocator(MmioType::High)
.allocate_with_align(
image_size,
arena_size,
Alloc::PmemDevice(index),
format!("pmem_disk_image_{}", index),
// Linux kernel requires pmem namespaces to be 128 MiB aligned.
@ -873,15 +894,15 @@ fn create_pmem_device(
)
.map_err(Error::AllocatePmemDeviceAddress)?;
vm.add_mmio_memory(
vm.add_mmap_arena(
GuestAddress(mapping_address),
memory_mapping,
arena,
/* read_only = */ disk.read_only,
/* log_dirty_pages = */ false,
)
.map_err(Error::AddPmemDeviceMemory)?;
let dev = virtio::Pmem::new(fd, GuestAddress(mapping_address), image_size)
let dev = virtio::Pmem::new(fd, GuestAddress(mapping_address), arena_size)
.map_err(Error::PmemDeviceNew)?;
Ok(VirtioDeviceStub {