# 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`](https://openvmm.dev/rustdoc/linux/disk_backend/trait.DiskIo.html)
calls, see the [storage pipeline](../devices/storage.md) page.
## Overview
The OpenHCL storage settings surface is easiest to read as a small tree:
1. a guest-visible **controller**
2. one or more guest-visible **child devices** under that controller
3. 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](../../../user_guide/openhcl/run/hyperv.md)
- 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.
## Fixed settings
The `fixed` section of VTL2 settings contains values that apply
globally, not per-controller. The key storage-related field is
`scsi_sub_channels`:
| Field | Type | Default | Meaning |
|-------|------|---------|---------|
| `scsi_sub_channels` | `u16` | `0` | Maximum sub-channel count for all SCSI controllers. Clamped to 256 at runtime. |
| `io_ring_size` | `u32` | `256` | Size of each per-CPU io_uring submission queue in the OpenHCL threadpool. Not StorVSP-specific — affects all async I/O. |
See [StorVSP Channels & Subchannels](../../devices/vmbus/storvsp_channels.md)
for the full subchannel model, performance characteristics, and
configuration guidance.
## One controller tree
```text
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_number`
- `physical_devices`
- `is_dvd`
- `chunk_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 `PhysicalDevice`s 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](../../../user_guide/openhcl/run/hyperv.md) page includes a concrete example of this shape:
```json
{
"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 SCSI
- `location: 15` says the child appears as guest LUN 15
- `device_type: "vscsi"` says the backing side is vSCSI
- `device_path` identifies the backing SCSI controller offered into VTL2
- `sub_device_path: 5` selects 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:
```json
{
"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 |
| SCSI subchannel count is clamped to 256 | `underhill_core::dispatch::vtl2_settings_worker`, `scsi_sub_channels.min(256)` |
| 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](../../../user_guide/openhcl/run/openvmm.md) page shows where OpenVMM consumes this model. In code, `openvmm_entry::storage_builder` makes the two-axis split visible:
1. `add_underhill()` chooses a **source** backing device family and a **target** guest-visible controller family separately.
2. It derives `sub_device_path` from the source child it created in VTL2.
3. It emits `Lun` objects whose `physical_devices` point back to that source.
4. `build_underhill()` groups those `Lun`s under guest-visible `StorageController`s 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`](https://github.com/microsoft/openvmm/tree/main/vm/devices/get/underhill_config/src/lib.rs) | [`underhill_config`](https://openvmm.dev/rustdoc/linux/underhill_config/index.html) |
| 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`](https://github.com/microsoft/openvmm/tree/main/vm/devices/get/underhill_config/src/schema/v1.rs) | [`underhill_config`](https://openvmm.dev/rustdoc/linux/underhill_config/index.html) |
| Test-side builders | Documents `device_path`, `sub_device_path`, single backing, and striped backing in a compact API | [`petri/src/vm/vtl2_settings.rs`](https://github.com/microsoft/openvmm/tree/main/petri/src/vm/vtl2_settings.rs) | [`petri`](https://openvmm.dev/rustdoc/linux/petri/index.html) |
| OpenVMM builder path | Shows how CLI shorthand is expanded into guest-visible `StorageController` and `Lun` objects | [`openvmm/openvmm_entry/src/storage_builder.rs`](https://github.com/microsoft/openvmm/tree/main/openvmm/openvmm_entry/src/storage_builder.rs) | [`openvmm_entry`](https://openvmm.dev/rustdoc/linux/openvmm_entry/index.html) |microsoft/openvmm
Publicmirrored fromhttps://github.com/microsoft/openvmmAvailable
Guide/src/reference/architecture/openhcl/storage_configuration.md
216lines · modepreview