blob: d8beef6357e82e82349d557863495a3300c61438 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::Guid,
anyhow::{Context, Result},
device_watcher::recursive_wait_and_open,
fidl::endpoints::Proxy as _,
fidl_fuchsia_device::ControllerProxy,
fidl_fuchsia_hardware_block::BlockMarker,
fidl_fuchsia_hardware_block_partition::Guid as FidlGuid,
fidl_fuchsia_hardware_block_volume::{VolumeManagerMarker, VolumeManagerProxy},
fidl_fuchsia_io as fio,
fuchsia_component::client::connect_to_named_protocol_at_dir_root,
fuchsia_zircon::{self as zx, sys::zx_handle_t, zx_status_t, AsHandleRef},
};
const FVM_DRIVER_PATH: &str = "fvm.cm";
#[link(name = "fvm")]
extern "C" {
// This function initializes FVM on a fuchsia.hardware.block.Block device
// with a given slice size.
fn fvm_init(device: zx_handle_t, slice_size: usize) -> zx_status_t;
}
/// Formats the block device at `block_device` to be an empty FVM instance.
pub fn format_for_fvm(block_device: &fio::DirectoryProxy, fvm_slice_size: usize) -> Result<()> {
// TODO(https://fxbug.dev/42072917): In order to remove multiplexing, callers of this function
// should directly pass in a BlockProxy. Callers holding onto a ramdisk should replace as_dir()
// with a connect_to_device_fidl() call. This requires work downstream.
let device = connect_to_named_protocol_at_dir_root::<BlockMarker>(block_device, ".")?;
let device_raw = device.as_channel().raw_handle();
let status = unsafe { fvm_init(device_raw, fvm_slice_size) };
zx::ok(status).context("fvm_init failed")
}
/// Binds the FVM driver to the device at `controller`. Does not wait for the driver to be ready.
pub async fn bind_fvm_driver(controller: &ControllerProxy) -> Result<()> {
controller.bind(FVM_DRIVER_PATH).await.context("fvm driver bind call failed").unwrap().unwrap();
Ok(())
}
/// Binds the fvm driver and returns a connection to the newly created FVM instance.
pub async fn start_fvm_driver(
controller: &ControllerProxy,
block_device: &fio::DirectoryProxy,
) -> Result<VolumeManagerProxy> {
bind_fvm_driver(controller).await?;
const FVM_DEVICE_NAME: &str = "fvm";
recursive_wait_and_open::<VolumeManagerMarker>(block_device, FVM_DEVICE_NAME)
.await
.context("wait_for_fvm_driver wait failed")
}
/// Sets up an FVM instance on `block_device`. Returns a connection to the newly created FVM
/// instance.
pub async fn set_up_fvm(
controller: &ControllerProxy,
block_device: &fio::DirectoryProxy,
fvm_slice_size: usize,
) -> Result<VolumeManagerProxy> {
format_for_fvm(block_device, fvm_slice_size)?;
start_fvm_driver(controller, block_device).await
}
/// Creates an FVM volume in `volume_manager`.
///
/// If `volume_size` is not provided then the volume will start with 1 slice. If `volume_size` is
/// provided then the volume will start with the minimum number of slices required to have
/// `volume_size` bytes.
///
/// `wait_for_block_device` can be used to find the volume after its created.
pub async fn create_fvm_volume(
volume_manager: &VolumeManagerProxy,
name: &str,
type_guid: &Guid,
instance_guid: &Guid,
volume_size: Option<u64>,
flags: u32,
) -> Result<()> {
let slice_count = match volume_size {
Some(volume_size) => {
let (status, info) =
volume_manager.get_info().await.context("Failed to get FVM info")?;
zx::ok(status).context("Get Info Error")?;
let slice_size = info.unwrap().slice_size;
assert!(slice_size > 0);
// Number of slices needed to satisfy volume_size.
(volume_size + slice_size - 1) / slice_size
}
None => 1,
};
let type_guid = FidlGuid { value: type_guid.clone() };
let instance_guid = FidlGuid { value: instance_guid.clone() };
let status = volume_manager
.allocate_partition(slice_count, &type_guid, &instance_guid, name, flags)
.await?;
zx::ok(status).context("error allocating partition")
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{wait_for_block_device, BlockDeviceMatcher},
fidl_fuchsia_hardware_block_volume::VolumeMarker,
fidl_fuchsia_hardware_block_volume::ALLOCATE_PARTITION_FLAG_INACTIVE,
fuchsia_component::client::connect_to_protocol_at_path,
ramdevice_client::RamdiskClient,
};
const BLOCK_SIZE: u64 = 512;
const BLOCK_COUNT: u64 = 64 * 1024 * 1024 / BLOCK_SIZE;
const FVM_SLICE_SIZE: usize = 1024 * 1024;
const INSTANCE_GUID: Guid = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f,
];
const TYPE_GUID: Guid = [
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0,
0xf0,
];
const VOLUME_NAME: &str = "volume-name";
#[fuchsia::test]
async fn set_up_fvm_test() {
let ramdisk = RamdiskClient::create(BLOCK_SIZE, BLOCK_COUNT).await.unwrap();
let fvm = set_up_fvm(
ramdisk.as_controller().expect("invalid controller"),
ramdisk.as_dir().expect("invalid directory proxy"),
FVM_SLICE_SIZE,
)
.await
.expect("Failed to set up FVM");
let fvm_info = fvm.get_info().await.unwrap();
zx::ok(fvm_info.0).unwrap();
let fvm_info = fvm_info.1.unwrap();
assert_eq!(fvm_info.slice_size, FVM_SLICE_SIZE as u64);
assert_eq!(fvm_info.assigned_slice_count, 0);
}
#[fuchsia::test]
async fn create_fvm_volume_without_volume_size_has_one_slice() {
let ramdisk = RamdiskClient::create(BLOCK_SIZE, BLOCK_COUNT).await.unwrap();
let fvm = set_up_fvm(
ramdisk.as_controller().expect("invalid controller"),
ramdisk.as_dir().expect("invalid directory proxy"),
FVM_SLICE_SIZE,
)
.await
.expect("Failed to set up FVM");
create_fvm_volume(
&fvm,
VOLUME_NAME,
&TYPE_GUID,
&INSTANCE_GUID,
None,
ALLOCATE_PARTITION_FLAG_INACTIVE,
)
.await
.expect("Failed to create fvm volume");
let block_device_path = wait_for_block_device(&[
BlockDeviceMatcher::TypeGuid(&TYPE_GUID),
BlockDeviceMatcher::InstanceGuid(&INSTANCE_GUID),
BlockDeviceMatcher::Name(VOLUME_NAME),
])
.await
.expect("Failed to find block device");
let volume =
connect_to_protocol_at_path::<VolumeMarker>(block_device_path.to_str().unwrap())
.unwrap();
let volume_info = volume.get_volume_info().await.unwrap();
zx::ok(volume_info.0).unwrap();
let volume_info = volume_info.2.unwrap();
assert_eq!(volume_info.partition_slice_count, 1);
}
#[fuchsia::test]
async fn create_fvm_volume_with_unaligned_volume_size_rounds_up_to_slice_multiple() {
let ramdisk = RamdiskClient::create(BLOCK_SIZE, BLOCK_COUNT).await.unwrap();
let fvm = set_up_fvm(
ramdisk.as_controller().expect("invalid controller"),
ramdisk.as_dir().expect("invalid directory proxy"),
FVM_SLICE_SIZE,
)
.await
.expect("Failed to set up FVM");
create_fvm_volume(
&fvm,
VOLUME_NAME,
&TYPE_GUID,
&INSTANCE_GUID,
Some((FVM_SLICE_SIZE * 5 + 4) as u64),
ALLOCATE_PARTITION_FLAG_INACTIVE,
)
.await
.expect("Failed to create fvm volume");
let block_device_path = wait_for_block_device(&[
BlockDeviceMatcher::TypeGuid(&TYPE_GUID),
BlockDeviceMatcher::InstanceGuid(&INSTANCE_GUID),
BlockDeviceMatcher::Name(VOLUME_NAME),
])
.await
.expect("Failed to find block device");
let volume =
connect_to_protocol_at_path::<VolumeMarker>(block_device_path.to_str().unwrap())
.unwrap();
let volume_info = volume.get_volume_info().await.unwrap();
zx::ok(volume_info.0).unwrap();
let volume_info = volume_info.2.unwrap();
assert_eq!(volume_info.partition_slice_count, 6);
}
}