nvme/
resolver.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Resource resolver for the nvme controller.
5
6use crate::NsidConflict;
7use crate::NvmeController;
8use crate::NvmeControllerCaps;
9use crate::NvmeControllerClient;
10use anyhow::Context;
11use async_trait::async_trait;
12use disk_backend::resolve::ResolveDiskParameters;
13use futures::StreamExt;
14use nvme_resources::NamespaceDefinition;
15use nvme_resources::NvmeControllerHandle;
16use nvme_resources::NvmeControllerRequest;
17use pal_async::task::Spawn;
18use pci_resources::ResolvePciDeviceHandleParams;
19use pci_resources::ResolvedPciDevice;
20use thiserror::Error;
21use vm_resource::AsyncResolveResource;
22use vm_resource::ResolveError;
23use vm_resource::ResourceResolver;
24use vm_resource::declare_static_async_resolver;
25use vm_resource::kind::PciDeviceHandleKind;
26use vmcore::vm_task::VmTaskDriverSource;
27
28/// Resource resolver for [`NvmeControllerHandle`].
29pub struct NvmeControllerResolver;
30
31declare_static_async_resolver! {
32    NvmeControllerResolver,
33    (PciDeviceHandleKind, NvmeControllerHandle),
34}
35
36/// Error returned by [`NvmeControllerResolver`].
37#[derive(Debug, Error)]
38#[expect(missing_docs)]
39pub enum Error {
40    #[error("failed to resolve namespace {nsid}")]
41    NamespaceResolve {
42        nsid: u32,
43        #[source]
44        source: ResolveError,
45    },
46    #[error(transparent)]
47    NsidConflict(NsidConflict),
48}
49
50#[async_trait]
51impl AsyncResolveResource<PciDeviceHandleKind, NvmeControllerHandle> for NvmeControllerResolver {
52    type Output = ResolvedPciDevice;
53    type Error = Error;
54
55    async fn resolve(
56        &self,
57        resolver: &ResourceResolver,
58        resource: NvmeControllerHandle,
59        input: ResolvePciDeviceHandleParams<'_>,
60    ) -> Result<Self::Output, Self::Error> {
61        let controller = NvmeController::new(
62            input.driver_source,
63            input.guest_memory.clone(),
64            input.msi_target,
65            input.register_mmio,
66            NvmeControllerCaps {
67                msix_count: resource.msix_count,
68                max_io_queues: resource.max_io_queues,
69                subsystem_id: resource.subsystem_id,
70            },
71        );
72        for NamespaceDefinition {
73            nsid,
74            read_only,
75            disk,
76        } in resource.namespaces
77        {
78            let disk = resolver
79                .resolve(
80                    disk,
81                    ResolveDiskParameters {
82                        read_only,
83                        driver_source: input.driver_source,
84                    },
85                )
86                .await
87                .map_err(|source| Error::NamespaceResolve { nsid, source })?;
88            controller
89                .client()
90                .add_namespace(nsid, disk.0)
91                .await
92                .map_err(Error::NsidConflict)?;
93        }
94
95        if let Some(requests) = resource.requests {
96            let driver = input.driver_source.simple();
97            driver
98                .spawn(
99                    "nvme-requests",
100                    handle_requests(
101                        input.driver_source.clone(),
102                        controller.client(),
103                        resolver.clone(),
104                        requests,
105                    ),
106                )
107                .detach();
108        }
109
110        Ok(controller.into())
111    }
112}
113
114async fn handle_requests(
115    driver_source: VmTaskDriverSource,
116    client: NvmeControllerClient,
117    resolver: ResourceResolver,
118    mut requests: mesh::Receiver<NvmeControllerRequest>,
119) {
120    while let Some(req) = requests.next().await {
121        match req {
122            NvmeControllerRequest::AddNamespace(rpc) => {
123                rpc.handle_failable(
124                    async |NamespaceDefinition {
125                               nsid,
126                               read_only,
127                               disk,
128                           }| {
129                        let disk = resolver
130                            .resolve(
131                                disk,
132                                ResolveDiskParameters {
133                                    read_only,
134                                    driver_source: &driver_source,
135                                },
136                            )
137                            .await
138                            .context("failed to resolve disk")?;
139
140                        client
141                            .add_namespace(nsid, disk.0)
142                            .await
143                            .context("failed to add namespace")?;
144
145                        anyhow::Ok(())
146                    },
147                )
148                .await
149            }
150            NvmeControllerRequest::RemoveNamespace(rpc) => {
151                rpc.handle_failable(async |nsid| {
152                    let removed = client.remove_namespace(nsid).await;
153                    if !removed {
154                        anyhow::bail!("namespace {nsid} not found");
155                    }
156                    anyhow::Ok(())
157                })
158                .await
159            }
160        }
161    }
162}