OpenHCL storage configuration model
The VTL2 settings model describes guest-visible storage controllers,
child devices, and their backing devices. For the internal
architecture of the disk backend abstraction, the layered disk model,
and how frontends translate guest I/O into
DiskIo
calls, see the storage pipeline page.
Overview
The OpenHCL storage settings surface is easiest to read as a small tree:
- a guest-visible controller
- one or more guest-visible child devices under that controller
- one or more backing devices behind each child
The same tree appears in three places:
- the VTL2 settings JSON shown in Running OpenHCL with Hyper-V
- helper builders in
petri::vtl2_settings - runtime structs in
underhill_config
This page lines those views up so you do not have to infer the schema from the JSON alone.
One controller tree
StorageController (protocol + instance_id)
├── Lun / child 0
│ └── PhysicalDevices::Single
│ └── PhysicalDevice
└── Lun / child 1
└── PhysicalDevices::Striped
├── PhysicalDevice A
└── PhysicalDevice B
The protobuf and JSON surface uses StorageController and Lun. The runtime view in underhill_config splits that same tree into concrete controller and child types such as ScsiController, ScsiDisk, NvmeController, and NvmeNamespace.
Two naming layers
| Concept | JSON / protobuf surface | Runtime surface in underhill_config |
|---|---|---|
| Guest-visible controller | StorageController | IdeController, ScsiController, NvmeController |
| Guest-visible child device | Lun | IdeDisk, ScsiDisk, NvmeNamespace |
| Backing wrapper | PhysicalDevices | PhysicalDevices |
| Backing device | PhysicalDevice | PhysicalDevice |
One non-obvious point is that the protobuf type keeps using the name Lun even when the guest-visible protocol is NVMe. At runtime, that same object becomes an NvmeNamespace, because the meaning of the child address depends on the controller protocol.
StorageController
StorageController is the guest-visible controller object. It answers the question "what controller family will VTL0 enumerate?"
| Field | Meaning |
|---|---|
instance_id | Guest-visible controller instance GUID |
protocol | Guest-visible controller family: IDE, SCSI, or NVMe |
luns | Child devices exposed under that controller |
io_queue_depth | Optional queue-depth tuning for supported controllers |
The protocol field is the most important one architecturally. It does not describe the backing side. It describes what kind of controller the guest will see.
Lun
Lun is the schema object for a guest-visible child device under a controller. The guest meaning of location depends on the controller protocol:
| Controller protocol | Guest meaning of the child | Relevant child fields |
|---|---|---|
IDE | IDE drive slot | channel + location |
SCSI | SCSI LUN | location |
NVMe | NVMe namespace | location |
Besides the child address, a Lun also carries the device identity strings and the backing-device description:
device_id,vendor_id,product_id,product_revision_level,serial_number,model_numberphysical_devicesis_dvdchunk_size_in_kb
For NVMe, location becomes the namespace ID at runtime. For IDE, channel is required because the guest-visible slot is a (channel, location) pair instead of a single number.
PhysicalDevices
PhysicalDevices wraps the backing side of a child device. It tells OpenHCL whether the guest-visible child is backed by zero, one, or multiple physical devices.
| Shape | Meaning |
|---|---|
EmptyDrive | No backing media. This is the empty-DVD case in the runtime model. |
Single { device } | Exactly one backing PhysicalDevice |
Striped { devices, chunk_size_in_kb } | Two or more backing PhysicalDevices combined into one guest-visible child |
In the protobuf and JSON shape, Single uses the singular device field. Striped uses the plural devices field and takes the stripe size from chunk_size_in_kb.
PhysicalDevice
PhysicalDevice describes one backing device offered into VTL2.
| Field | Meaning |
|---|---|
device_type | Backing device family: nvme or vscsi |
device_path | VMBus instance GUID of the backing controller or device |
sub_device_path | Child inside that backing device |
The petri::vtl2_settings helpers describe this directly: both SCSI and NVMe backing devices are treated as VMBus devices, so device_path is the VMBus instance ID. The sub_device_path field selects the child inside that device:
- for
vscsi, it is the host LUN - for
nvme, it is the namespace ID
IDE is a guest-visible target family, but it is not supported as a VTL2 backing device.
Example: one SCSI target backed by one vSCSI device
The Running OpenHCL with Hyper-V page includes a concrete example of this shape:
{
"instance_id": "<GUEST_SCSI_CONTROLLER_GUID>",
"protocol": "SCSI",
"luns": [
{
"location": 15,
"physical_devices": {
"type": "single",
"device": {
"device_type": "vscsi",
"device_path": "<HOST_SCSI_CONTROLLER_GUID>",
"sub_device_path": 5
}
}
}
]
}
Read this from top to bottom:
protocol: "SCSI"says the guest-visible controller is SCSIlocation: 15says the child appears as guest LUN 15device_type: "vscsi"says the backing side is vSCSIdevice_pathidentifies the backing SCSI controller offered into VTL2sub_device_path: 5selects host LUN 5 on that controller
That is the clearest example of why the guest-visible target and the backing side are different layers.
Example: striped backing
When a guest-visible child is striped, the guest-visible side can stay the same while the backing side fans out:
{
"physical_devices": {
"type": "striped",
"devices": [
{ "device_type": "nvme", "device_path": "<GUID_A>", "sub_device_path": 1 },
{ "device_type": "nvme", "device_path": "<GUID_B>", "sub_device_path": 1 }
]
},
"chunk_size_in_kb": 128
}
The schema treats stripe geometry as a property of the guest-visible child, not of the controller as a whole.
Validation rules and limits
The runtime model and schema enforce several important constraints:
| Rule | Where it shows up |
|---|---|
| IDE has 2 channels and 2 drive slots per channel | underhill_config::IDE_NUM_CHANNELS and IDE_MAX_DRIVES_PER_CHANNEL |
| SCSI supports up to 4 controllers and 64 LUN slots per controller | underhill_config::SCSI_CONTROLLER_NUM and SCSI_LUN_NUM |
Single must carry exactly one backing device | schema::v1 parses device and rejects extra entries |
Striped must carry at least two backing devices | schema::v1 rejects too few devices |
NVMe namespace IDs cannot be 0 or !0 | schema::v1 validates location for NVMe |
| NVMe children cannot be DVDs | schema::v1 rejects is_dvd on NVMe |
| IDE can be a guest-visible target but not a VTL2 backing source | petri::vtl2_settings and openvmm_entry::storage_builder reject IDE backing |
These rules are useful when debugging configuration failures because they tell you whether the problem is in controller selection, child addressing, or backing-device declaration.
How OpenVMM fills this model
The Running OpenHCL with OpenVMM page shows where OpenVMM consumes this model. In code, openvmm_entry::storage_builder makes the two-axis split visible:
add_underhill()chooses a source backing device family and a target guest-visible controller family separately.- It derives
sub_device_pathfrom the source child it created in VTL2. - It emits
Lunobjects whosephysical_devicespoint back to that source. build_underhill()groups thoseLuns under guest-visibleStorageControllers for SCSI or NVMe.
Read the configuration model as a description of the guest-visible tree plus a separate description of the backing tree under each child.
Implementation map
| Component | Why read it | Source | Rustdoc |
|---|---|---|---|
| Underhill configuration model | Defines the runtime controller, child, and backing types | vm/devices/get/underhill_config/src/lib.rs | underhill_config |
| Schema parsing and validation | Shows how JSON and protobuf fields map into runtime structs and what errors are enforced | vm/devices/get/underhill_config/src/schema/v1.rs | underhill_config |
| Test-side builders | Documents device_path, sub_device_path, single backing, and striped backing in a compact API | petri/src/vm/vtl2_settings.rs | petri |
| OpenVMM builder path | Shows how CLI shorthand is expanded into guest-visible StorageController and Lun objects | openvmm/openvmm_entry/src/storage_builder.rs | openvmm_entry |