Skip to main content

gpt_component/
partition.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use crate::gpt::GptPartition;
5use anyhow::{Context as _, Error};
6use block_client::{ReadOptions, VmoId, WriteOptions};
7use block_server::async_interface::{Interface, PassthroughSession, SessionManager};
8use block_server::{DeviceInfo, OffsetMap};
9use fidl::endpoints::{RequestStream, ServerEnd};
10use fidl_fuchsia_storage_block as fblock;
11use fuchsia_async as fasync;
12
13use fuchsia_sync::Mutex;
14use std::borrow::Cow;
15use std::collections::BTreeMap;
16use std::future::Future;
17use std::num::NonZero;
18use std::sync::{Arc, OnceLock, Weak};
19
20/// A wrapper around a VmoId which keeps it active until all requests which use the Vmoid are
21/// complete.  Strong references are held by ongoing requests.
22pub struct VmoIdWrapper {
23    partition: Weak<GptPartition>,
24    vmo_id: VmoId,
25}
26
27impl std::ops::Deref for VmoIdWrapper {
28    type Target = VmoId;
29    fn deref(&self) -> &Self::Target {
30        &self.vmo_id
31    }
32}
33
34impl Drop for VmoIdWrapper {
35    fn drop(&mut self) {
36        // Turn it into an ID so that if the spawned task is dropped, the assertion in VmoId::drop
37        // doesn't fire.  It will mean the ID is leaked, but it's most likely that the server is
38        // being shut down anyway so it shouldn't matter.
39        let vmo_id = self.vmo_id.take().into_id();
40        if let Some(partition) = self.partition.upgrade() {
41            fasync::Task::spawn(async move {
42                if let Err(e) = partition.detach_vmo(VmoId::new(vmo_id)).await {
43                    // When a partition connection is cancelled (e.g. during a table reset),
44                    // the block client terminates its FIFO to avoid DMA corruption, so any
45                    // subsequent `detach_vmo` will fail with CANCELED or PEER_CLOSED.
46                    // This is normal and expected, downgrade this to debug logging to avoid
47                    // failing tests on log severity.
48                    if e == zx::Status::CANCELED || e == zx::Status::PEER_CLOSED {
49                        log::debug!("detach_vmo failed during shutdown: {:?}", e);
50                    } else {
51                        log::error!("detach_vmo failed: {:?}", e);
52                    }
53                }
54            })
55            .detach();
56        }
57    }
58}
59
60/// PartitionBackend is an implementation of block_server's Interface which is backed by a windowed
61/// view of the underlying GPT device.
62pub struct PartitionBackend {
63    partition: Arc<GptPartition>,
64    vmo_keys_to_vmoids_map: Mutex<BTreeMap<usize, Arc<VmoIdWrapper>>>,
65    offset_map: OffsetMap,
66    mapper_key: OnceLock<u64>,
67}
68
69impl Interface for PartitionBackend {
70    async fn open_session(
71        &self,
72        session_manager: Arc<SessionManager<Self>>,
73        stream: fblock::SessionRequestStream,
74        offset_map: OffsetMap,
75        block_size: u32,
76    ) -> Result<(), Error> {
77        if !offset_map.is_empty() {
78            // For now, we don't support double-passthrough.  We could as needed for nested GPT.
79            let _ = stream.control_handle().shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
80            anyhow::bail!("Client-provided offset maps are not supported");
81        }
82        if self.offset_map.is_empty() {
83            return session_manager
84                .serve_session(
85                    stream,
86                    OffsetMap::empty(),
87                    self.get_info().max_transfer_blocks(),
88                    block_size,
89                )
90                .await;
91        }
92        let (proxy, server_end) = fidl::endpoints::create_proxy::<fblock::SessionMarker>();
93        self.partition.open_passthrough_session(server_end, &self.offset_map);
94        let passthrough = PassthroughSession::new(proxy);
95        passthrough.serve(stream).await
96    }
97
98    fn open_mapper_session(
99        session_manager: Arc<SessionManager<Self>>,
100        session: ServerEnd<fblock::MapperSessionMarker>,
101        mapping_vmo: zx::Vmo,
102        _block_size: u32,
103        port: Option<zx::Port>,
104        delivery_queue: Option<zx::Vmo>,
105    ) -> Result<impl Future<Output = Result<(), Error>> + Send + 'static, zx::Status> {
106        let this = session_manager.interface();
107        let Some(gpt) = this.partition.gpt() else {
108            return Err(zx::Status::BAD_STATE);
109        };
110        if this.offset_map.is_empty() || !gpt.has_mapper() {
111            return Err(zx::Status::NOT_SUPPORTED);
112        }
113        let this = this.clone();
114        Ok(async move {
115            let mut init = false;
116            let key = *this.mapper_key.get_or_init(|| {
117                init = true;
118                gpt.next_partition_key()
119            });
120            // This is thread-safe because `open_child_session` in the server waits for mappings if
121            // they arrive late.
122            if init {
123                gpt.register_mappings(key, &this.offset_map).await?;
124            }
125            let session_proxy = gpt.mapper_session_proxy().await;
126            session_proxy
127                .open_child_session(session, mapping_vmo, key, port, delivery_queue)
128                .await
129                .context("FIDL error calling OpenChildSession on mapper session")?
130                .map_err(|status| {
131                    anyhow::anyhow!(
132                        "OpenChildSession failed: {:?}",
133                        zx::Status::err_from_raw(status)
134                    )
135                })?;
136            Ok(())
137        })
138    }
139
140    async fn on_attach_vmo(&self, vmo: &zx::Vmo) -> Result<(), zx::Status> {
141        let key = std::ptr::from_ref(vmo) as usize;
142        // SAFETY: GPT does not map VMOs in its own process, so it cannot violate Rust's aliasing
143        // guarantees.  Safety is delegated to the client process that mapped the VMO.
144        let vmo_id = unsafe { self.partition.attach_vmo(vmo) }.await?;
145        self.vmo_keys_to_vmoids_map.lock().insert(
146            key,
147            Arc::new(VmoIdWrapper { partition: Arc::downgrade(&self.partition), vmo_id }),
148        );
149        Ok(())
150    }
151
152    fn on_detach_vmo(&self, vmo: &zx::Vmo) {
153        // Note that we will not immediately detach the VMO.  This happens when the last reference
154        // to it is dropped (in [`VmoIdWrapper::drop`]).
155        let key = std::ptr::from_ref(vmo) as usize;
156        self.vmo_keys_to_vmoids_map.lock().remove(&key);
157    }
158
159    fn get_info(&self) -> Cow<'_, DeviceInfo> {
160        Cow::Owned(self.partition.get_info())
161    }
162
163    async fn read(
164        &self,
165        device_block_offset: u64,
166        block_count: u32,
167        vmo: &Arc<zx::Vmo>,
168        vmo_offset: u64, // *bytes* not blocks
169        opts: ReadOptions,
170        trace_flow_id: Option<NonZero<u64>>,
171    ) -> Result<(), zx::Status> {
172        let vmo_id = self.get_vmoid(vmo)?;
173        self.partition
174            .read(device_block_offset, block_count, &vmo_id, vmo_offset, opts, trace_flow_id)
175            .await
176    }
177
178    async fn write(
179        &self,
180        device_block_offset: u64,
181        length: u32,
182        vmo: &Arc<zx::Vmo>,
183        vmo_offset: u64, // *bytes* not blocks
184        opts: WriteOptions,
185        trace_flow_id: Option<NonZero<u64>>,
186    ) -> Result<(), zx::Status> {
187        let vmo_id = self.get_vmoid(vmo)?;
188        self.partition
189            .write(device_block_offset, length, &vmo_id, vmo_offset, opts, trace_flow_id)
190            .await
191    }
192
193    async fn flush(&self, trace_flow_id: Option<NonZero<u64>>) -> Result<(), zx::Status> {
194        self.partition.flush(trace_flow_id).await
195    }
196
197    async fn trim(
198        &self,
199        device_block_offset: u64,
200        block_count: u32,
201        trace_flow_id: Option<NonZero<u64>>,
202    ) -> Result<(), zx::Status> {
203        self.partition.trim(device_block_offset, block_count, trace_flow_id).await
204    }
205}
206
207impl PartitionBackend {
208    #[cfg(test)]
209    pub fn passthrough(&self) -> bool {
210        !self.offset_map.is_empty()
211    }
212
213    #[cfg(test)]
214    pub fn vmo_count(&self) -> usize {
215        self.vmo_keys_to_vmoids_map.lock().len()
216    }
217
218    /// If `offset_map` is non-empty, the partition will pass through requests using the provided
219    /// offset map.  Otherwise, the partition will proxy I/O requests (and `read`, `write`, etc will
220    /// be called on this instance).
221    pub fn new(partition: Arc<GptPartition>, offset_map: block_server::OffsetMap) -> Arc<Self> {
222        Arc::new(Self {
223            partition,
224            offset_map,
225            vmo_keys_to_vmoids_map: Mutex::new(BTreeMap::new()),
226            mapper_key: OnceLock::new(),
227        })
228    }
229
230    /// Updates the info.
231    pub fn update_info(&self, info: gpt::PartitionInfo) {
232        self.partition.update_info(info)
233    }
234
235    fn get_vmoid(&self, vmo: &zx::Vmo) -> Result<Arc<VmoIdWrapper>, zx::Status> {
236        let key = std::ptr::from_ref(vmo) as usize;
237        self.vmo_keys_to_vmoids_map.lock().get(&key).map(Arc::clone).ok_or(zx::Status::NOT_FOUND)
238    }
239}