1use crate::device::DeviceMode;
6use crate::device::block::canonicalize_ioctl_request;
7use crate::device::kobject::DeviceMetadata;
8use crate::fs::sysfs::{BlockDeviceInfo, build_block_device_directory};
9use crate::mm::MemoryAccessorExt;
10use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
11use crate::task::{CurrentTask, Kernel, KernelThreads};
12use crate::vfs::buffers::{InputBuffer, OutputBuffer};
13use crate::vfs::{FileObject, FileOps, FsString, NamespaceNode, SeekTarget, default_seek};
14use anyhow::{Context as _, Error};
15use block_client::{BlockClient, BufferSlice, MutableBufferSlice, RemoteBlockClient};
16use fidl::endpoints::ClientEnd;
17use fidl_fuchsia_storage_block::BlockMarker;
18use futures::channel::oneshot;
19use futures::executor::block_on;
20use starnix_sync::{LockDepMutex, RemoteBlockDeviceRegistryDevicesLock};
21use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
22use starnix_uapi::device_id::{BLOCK_EXTENDED_MAJOR, DeviceId};
23use starnix_uapi::errors::Errno;
24use starnix_uapi::open_flags::OpenFlags;
25use starnix_uapi::user_address::{MultiArchUserRef, UserRef};
26use starnix_uapi::{BLKGETSIZE, BLKGETSIZE64, errno, error, from_status_like_fdio, off_t};
27use std::collections::btree_map::BTreeMap;
28use std::sync::Arc;
29use std::sync::atomic::{AtomicU32, Ordering};
30
31pub struct RemoteBlockDevice {
33 block_client: Arc<SyncBlockClient>,
34}
35
36impl RemoteBlockDevice {
37 pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), Error> {
38 self.block_client
39 .read_at(MutableBufferSlice::Memory(buf), offset as u64)
40 .context("read_at failed")
41 }
42
43 fn new(
44 kernel: &Kernel,
45 minor: u32,
46 name: &str,
47 block: ClientEnd<BlockMarker>,
48 ) -> Result<Arc<Self>, Errno> {
49 let registry = &kernel.device_registry;
50 let device_name = FsString::from(name);
51 let block_class = registry.objects.block_class();
52 let block_client = SyncBlockClient::new(&kernel.kthreads, block)?;
53 let device = Arc::new(Self { block_client });
54 let device_weak = Arc::<RemoteBlockDevice>::downgrade(&device);
55 registry.add_major_device(
56 kernel,
57 device_name.as_ref(),
58 DeviceMetadata::new(
59 device_name.clone(),
60 DeviceId::new(BLOCK_EXTENDED_MAJOR, minor),
61 DeviceMode::Block,
62 )
63 .with_devtype("disk"),
66 None,
67 block_class,
68 |device, dir| build_block_device_directory(device, device_weak, dir),
69 );
70 Ok(device)
71 }
72
73 pub fn create_file_ops(&self) -> Box<dyn FileOps> {
74 Box::new(RemoteBlockDeviceFile { block_client: self.block_client.clone() })
75 }
76}
77
78impl BlockDeviceInfo for RemoteBlockDevice {
79 fn size(&self) -> Result<usize, Errno> {
80 (self.block_client.block_count() as usize)
81 .checked_mul(self.block_client.block_size() as usize)
82 .ok_or_else(|| errno!(EINVAL))
83 }
84}
85
86pub struct SyncBlockClient {
87 client: Arc<RemoteBlockClient>,
88 terminate_tx: Option<oneshot::Sender<()>>,
89}
90
91impl SyncBlockClient {
92 fn new(kthreads: &KernelThreads, block: ClientEnd<BlockMarker>) -> Result<Arc<Self>, Errno> {
93 let (init_tx, init_rx) = std::sync::mpsc::channel();
94 let (terminate_tx, terminate_rx) = oneshot::channel();
95
96 let closure = move |_: &CurrentTask| async move {
98 let proxy = block.into_proxy();
99 match RemoteBlockClient::new(proxy).await {
100 Ok(client) => {
101 let _ = init_tx.send(Ok(Arc::new(Self {
102 client: Arc::new(client),
103 terminate_tx: Some(terminate_tx),
104 })));
105 }
106 Err(e) => {
107 let _ = init_tx.send(Err(e));
108 return;
109 }
110 }
111 let _ = terminate_rx.await;
115 };
116
117 let req = SpawnRequestBuilder::new()
118 .with_debug_name("remote-block-client")
119 .with_async_closure(closure)
120 .build();
121 kthreads.spawner().spawn_from_request(req);
122
123 match init_rx.recv() {
124 Ok(Ok(client)) => Ok(client),
125 Ok(Err(status)) => Err(from_status_like_fdio!(status)),
126 Err(_) => Err(errno!(EINVAL)),
127 }
128 }
129
130 fn block_size(&self) -> u32 {
131 self.client.block_size()
132 }
133
134 fn block_count(&self) -> u64 {
135 self.client.block_count()
136 }
137
138 fn read_at(
139 &self,
140 buffer_slice: MutableBufferSlice<'_>,
141 device_offset: u64,
142 ) -> Result<(), zx::Status> {
143 block_on(self.client.read_at(buffer_slice, device_offset))
147 }
148
149 fn write_at(
150 &self,
151 buffer_slice: BufferSlice<'_>,
152 device_offset: u64,
153 ) -> Result<(), zx::Status> {
154 block_on(self.client.write_at(buffer_slice, device_offset))
158 }
159
160 fn flush(&self) -> Result<(), zx::Status> {
161 block_on(self.client.flush())
165 }
166}
167
168impl Drop for SyncBlockClient {
169 fn drop(&mut self) {
170 if let Some(tx) = self.terminate_tx.take() {
171 let _ = tx.send(());
172 }
173 }
174}
175
176struct RemoteBlockDeviceFile {
177 block_client: Arc<SyncBlockClient>,
178}
179
180impl RemoteBlockDeviceFile {
181 fn size(&self) -> Result<usize, Errno> {
182 (self.block_client.block_count() as usize)
183 .checked_mul(self.block_client.block_size() as usize)
184 .ok_or_else(|| errno!(EINVAL))
185 }
186}
187
188impl FileOps for RemoteBlockDeviceFile {
189 fn has_persistent_offsets(&self) -> bool {
190 true
191 }
192
193 fn is_seekable(&self) -> bool {
194 true
195 }
196
197 fn seek(
200 &self,
201 _file: &FileObject,
202 _current_task: &CurrentTask,
203 current_offset: off_t,
204 target: SeekTarget,
205 ) -> Result<off_t, Errno> {
206 default_seek(current_offset, target, || self.size()?.try_into().map_err(|_| errno!(EINVAL)))
207 }
208
209 fn read(
210 &self,
211 _file: &FileObject,
212 _current_task: &CurrentTask,
213 mut offset: usize,
214 data: &mut dyn OutputBuffer,
215 ) -> Result<usize, Errno> {
216 let size = self.size()?;
217 let block_size = self.block_client.block_size() as usize;
218 const MAX_CHUNK_SIZE: usize = 32 * 1024; let mut total_read = 0;
221 while data.available() > 0 && offset < size {
222 let chunk_len = std::cmp::min(data.available(), MAX_CHUNK_SIZE);
223 let chunk_len = std::cmp::min(chunk_len, size - offset);
224 if chunk_len == 0 {
225 break;
226 }
227
228 let aligned_offset = offset - offset % block_size;
229 let end_offset = offset + chunk_len;
230 let aligned_end_offset = std::cmp::min(
231 end_offset.checked_next_multiple_of(block_size).ok_or_else(|| errno!(EINVAL))?,
232 size,
233 );
234 let aligned_data_length = aligned_end_offset - aligned_offset;
235
236 let mut read_data = vec![0u8; aligned_data_length];
237 self.block_client
238 .read_at(MutableBufferSlice::Memory(&mut read_data), aligned_offset as u64)
239 .map_err(|status| from_status_like_fdio!(status))?;
240
241 let read_offset = offset - aligned_offset;
242 let read_end = read_offset + chunk_len;
243 let bytes_written = data.write(&read_data[read_offset..read_end])?;
244
245 offset += bytes_written;
246 total_read += bytes_written;
247
248 if bytes_written < chunk_len {
249 break;
250 }
251 }
252 Ok(total_read)
253 }
254
255 fn write(
256 &self,
257 _file: &FileObject,
258 _current_task: &CurrentTask,
259 mut offset: usize,
260 data: &mut dyn InputBuffer,
261 ) -> Result<usize, Errno> {
262 let size = self.size()?;
263 let block_size = self.block_client.block_size() as usize;
264 const MAX_CHUNK_SIZE: usize = 32 * 1024; let mut total_written = 0;
267 while data.available() > 0 && offset < size {
268 let chunk_len = std::cmp::min(data.available(), MAX_CHUNK_SIZE);
269 let chunk_len = std::cmp::min(chunk_len, size - offset);
270 if chunk_len == 0 {
271 break;
272 }
273
274 let aligned_offset = offset - offset % block_size;
275 let end_offset = offset + chunk_len;
276 let aligned_end_offset = std::cmp::min(
277 end_offset.checked_next_multiple_of(block_size).ok_or_else(|| errno!(EINVAL))?,
278 size,
279 );
280 let aligned_data_length = aligned_end_offset - aligned_offset;
281
282 let mut write_buf = vec![0u8; aligned_data_length];
283
284 let head_unaligned = offset > aligned_offset;
287 let tail_unaligned = end_offset < aligned_end_offset;
288
289 if head_unaligned {
290 self.block_client
291 .read_at(
292 MutableBufferSlice::Memory(&mut write_buf[..block_size]),
293 aligned_offset as u64,
294 )
295 .map_err(|status| from_status_like_fdio!(status))?;
296 }
297
298 if tail_unaligned {
299 let last_block_start = aligned_data_length - block_size;
300 if !head_unaligned || last_block_start > 0 {
303 self.block_client
304 .read_at(
305 MutableBufferSlice::Memory(&mut write_buf[last_block_start..]),
306 (aligned_offset + last_block_start) as u64,
307 )
308 .map_err(|status| from_status_like_fdio!(status))?;
309 }
310 }
311
312 let write_offset = offset - aligned_offset;
313 let write_slice = &mut write_buf[write_offset..write_offset + chunk_len];
314 let write_slice_uninit = unsafe {
317 std::slice::from_raw_parts_mut(
318 write_slice.as_mut_ptr() as *mut std::mem::MaybeUninit<u8>,
319 write_slice.len(),
320 )
321 };
322 let bytes_read = data.read(write_slice_uninit)?;
323
324 self.block_client
325 .write_at(BufferSlice::Memory(&write_buf), aligned_offset as u64)
326 .map_err(|status| from_status_like_fdio!(status))?;
327
328 offset += bytes_read;
329 total_written += bytes_read;
330
331 if bytes_read < chunk_len {
332 break;
333 }
334 }
335 Ok(total_written)
336 }
337
338 fn sync(&self, _file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
339 self.block_client.flush().map_err(|status| from_status_like_fdio!(status))
340 }
341
342 fn ioctl(
343 &self,
344 _file: &FileObject,
345 current_task: &CurrentTask,
346 request: u32,
347 arg: SyscallArg,
348 ) -> Result<SyscallResult, Errno> {
349 match canonicalize_ioctl_request(current_task, request) {
350 BLKGETSIZE => {
351 let user_size = MultiArchUserRef::<u64, u32>::new(current_task, arg);
352 let size = self.block_client.block_count();
353 current_task.write_multi_arch_object(user_size, size)?;
354 Ok(SUCCESS)
355 }
356 BLKGETSIZE64 => {
357 let user_size = UserRef::<u64>::from(arg);
358 let size = self.size()? as u64;
359 current_task.write_object(user_size, &size)?;
360 Ok(SUCCESS)
361 }
362 _ => error!(ENOTTY),
363 }
364 }
365}
366
367fn open_remote_block_device(
368 current_task: &CurrentTask,
369 id: DeviceId,
370 _node: &NamespaceNode,
371 _flags: OpenFlags,
372) -> Result<Box<dyn FileOps>, Errno> {
373 Ok(current_task.kernel().remote_block_device_registry.open(id.minor())?.create_file_ops())
374}
375
376pub fn remote_block_device_init(kernel: &Kernel) {
377 kernel
378 .device_registry
379 .register_major(
380 "remote-block".into(),
381 DeviceMode::Block,
382 BLOCK_EXTENDED_MAJOR,
383 open_remote_block_device,
384 )
385 .expect("remote block device register failed.");
386}
387
388#[derive(Default)]
389pub struct RemoteBlockDeviceRegistry {
390 devices:
391 LockDepMutex<BTreeMap<u32, Arc<RemoteBlockDevice>>, RemoteBlockDeviceRegistryDevicesLock>,
392 next_minor: AtomicU32,
393}
394
395impl RemoteBlockDeviceRegistry {
396 pub fn create_remote_block_device(
397 &self,
398 kernel: &Kernel,
399 name: &str,
400 block: ClientEnd<BlockMarker>,
401 ) -> Result<(), Error> {
402 let mut devices = self.devices.lock();
403 let minor = self.next_minor.fetch_add(1, Ordering::Relaxed);
404 let device = RemoteBlockDevice::new(kernel, minor, name, block)?;
405 devices.insert(minor, device);
406 Ok(())
407 }
408
409 pub fn open(&self, minor: u32) -> Result<Arc<RemoteBlockDevice>, Errno> {
410 self.devices.lock().get(&minor).ok_or_else(|| errno!(ENODEV)).cloned()
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::testing::{anon_test_file, map_object_anywhere, spawn_kernel_and_run};
418 use crate::vfs::{SeekTarget, VecInputBuffer, VecOutputBuffer};
419 use starnix_uapi::open_flags::OpenFlags;
420 use starnix_uapi::{BLKGETSIZE, BLKGETSIZE64};
421 use vmo_backed_block_server::VmoBackedServer;
422 use zerocopy::FromBytes as _;
423
424 #[::fuchsia::test]
425 async fn test_remote_block_device_registry() {
426 spawn_kernel_and_run(async |current_task| {
427 let kernel = current_task.kernel();
428 remote_block_device_init(kernel);
429 let registry = kernel.remote_block_device_registry.clone();
430 let server =
431 VmoBackedServer::new(2, 512, &[]).expect("Failed to create VmoBackedServer");
432 let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
433 std::thread::spawn(move || {
434 let mut executor = fuchsia_async::LocalExecutor::default();
435 executor.run_singlethreaded(async move {
436 use fidl::endpoints::RequestStream;
437 server.serve(server_end.into_stream().cast_stream()).await.unwrap();
438 });
439 });
440
441 registry
442 .create_remote_block_device(kernel, "test", client)
443 .expect("create_remote_block_device failed.");
444
445 let device = registry.open(0).expect("open failed.");
446 let file = anon_test_file(¤t_task, device.create_file_ops(), OpenFlags::RDWR);
447
448 let arg_addr = map_object_anywhere(¤t_task, &0u64);
449 let mut arg = [0u8; 8];
450
451 file.ioctl(¤t_task, BLKGETSIZE64, arg_addr.into()).expect("ioctl failed");
452 current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
453 assert_eq!(u64::read_from_bytes(&arg).unwrap(), 1024);
454
455 file.ioctl(¤t_task, BLKGETSIZE, arg_addr.into()).expect("ioctl failed");
456 current_task.read_memory_to_slice(arg_addr, &mut arg).unwrap();
457 assert_eq!(u64::read_from_bytes(&arg).unwrap(), 2);
458
459 let mut buf = VecOutputBuffer::new(256);
462 file.read(¤t_task, &mut buf).expect("read failed.");
463 assert_eq!(buf.data(), &[0u8; 256]);
464
465 let mut buf = VecInputBuffer::from(vec![1u8; 256]);
466 file.seek(¤t_task, SeekTarget::Set(0)).expect("seek failed");
467 file.write(¤t_task, &mut buf).expect("write failed.");
468
469 let mut buf = VecOutputBuffer::new(256);
470 file.seek(¤t_task, SeekTarget::Set(0)).expect("seek failed");
471 file.read(¤t_task, &mut buf).expect("read failed.");
472 assert_eq!(buf.data(), &[1u8; 256]);
473 })
474 .await;
475 }
476
477 #[::fuchsia::test]
478 async fn test_read_write_past_eof() {
479 spawn_kernel_and_run(async |current_task| {
480 let kernel = current_task.kernel();
481 remote_block_device_init(kernel);
482 let registry = kernel.remote_block_device_registry.clone();
483 let server =
484 VmoBackedServer::new(2, 512, &[]).expect("Failed to create VmoBackedServer");
485 let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
486 std::thread::spawn(move || {
487 let mut executor = fuchsia_async::LocalExecutor::default();
488 executor.run_singlethreaded(async move {
489 use fidl::endpoints::RequestStream;
490 server.serve(server_end.into_stream().cast_stream()).await.unwrap();
491 });
492 });
493
494 registry
495 .create_remote_block_device(kernel, "test", client)
496 .expect("create_remote_block_device failed.");
497
498 let device = registry.open(0).expect("open failed.");
499 let file = anon_test_file(¤t_task, device.create_file_ops(), OpenFlags::RDWR);
500
501 file.seek(¤t_task, SeekTarget::End(0)).expect("seek failed");
502 let mut buf = VecOutputBuffer::new(512);
503 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 0);
504
505 let mut buf = VecInputBuffer::from(vec![1u8; 512]);
506 assert_eq!(file.write(¤t_task, &mut buf).expect("write failed."), 0);
507 })
508 .await;
509 }
510
511 #[::fuchsia::test]
512 async fn test_unaligned_read_write_spanning_blocks() {
513 spawn_kernel_and_run(async |current_task| {
514 let kernel = current_task.kernel();
515 remote_block_device_init(kernel);
516 let registry = kernel.remote_block_device_registry.clone();
517 let server =
519 VmoBackedServer::new(3, 512, &[]).expect("Failed to create VmoBackedServer");
520 let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
521 std::thread::spawn(move || {
522 let mut executor = fuchsia_async::LocalExecutor::default();
523 executor.run_singlethreaded(async move {
524 use fidl::endpoints::RequestStream;
525 server.serve(server_end.into_stream().cast_stream()).await.unwrap();
526 });
527 });
528
529 registry
530 .create_remote_block_device(kernel, "test", client)
531 .expect("create_remote_block_device failed.");
532
533 let device = registry.open(0).expect("open failed.");
534 let file = anon_test_file(¤t_task, device.create_file_ops(), OpenFlags::RDWR);
535
536 let mut buf = VecInputBuffer::from(vec![0xAAu8; 516]);
540 file.seek(¤t_task, SeekTarget::Set(510)).expect("seek failed");
541 assert_eq!(file.write(¤t_task, &mut buf).expect("write failed."), 516);
542
543 let mut buf = VecOutputBuffer::new(516);
545 file.seek(¤t_task, SeekTarget::Set(510)).expect("seek failed");
546 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 516);
547 assert_eq!(buf.data(), &[0xAAu8; 516]);
548
549 let mut buf = VecOutputBuffer::new(1);
551 file.seek(¤t_task, SeekTarget::Set(509)).expect("seek failed");
552 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 1);
553 assert_eq!(buf.data(), &[0u8]);
554
555 let mut buf = VecOutputBuffer::new(1);
556 file.seek(¤t_task, SeekTarget::Set(1026)).expect("seek failed");
557 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 1);
558 assert_eq!(buf.data(), &[0u8]);
559 })
560 .await;
561 }
562
563 #[::fuchsia::test]
564 async fn test_exact_eof_boundary() {
565 spawn_kernel_and_run(async |current_task| {
566 let kernel = current_task.kernel();
567 remote_block_device_init(kernel);
568 let registry = kernel.remote_block_device_registry.clone();
569 let server =
571 VmoBackedServer::new(2, 512, &[]).expect("Failed to create VmoBackedServer");
572 let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
573 std::thread::spawn(move || {
574 let mut executor = fuchsia_async::LocalExecutor::default();
575 executor.run_singlethreaded(async move {
576 use fidl::endpoints::RequestStream;
577 server.serve(server_end.into_stream().cast_stream()).await.unwrap();
578 });
579 });
580
581 registry
582 .create_remote_block_device(kernel, "test", client)
583 .expect("create_remote_block_device failed.");
584
585 let device = registry.open(0).expect("open failed.");
586 let file = anon_test_file(¤t_task, device.create_file_ops(), OpenFlags::RDWR);
587
588 let mut buf = VecInputBuffer::from(vec![0xBBu8; 4]);
591 file.seek(¤t_task, SeekTarget::Set(1020)).expect("seek failed");
592 assert_eq!(file.write(¤t_task, &mut buf).expect("write failed."), 4);
593
594 let mut buf = VecOutputBuffer::new(4);
596 file.seek(¤t_task, SeekTarget::Set(1020)).expect("seek failed");
597 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 4);
598 assert_eq!(buf.data(), &[0xBBu8; 4]);
599
600 let mut buf = VecOutputBuffer::new(5);
602 file.seek(¤t_task, SeekTarget::Set(1020)).expect("seek failed");
603 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 4);
604 assert_eq!(buf.data()[..4], [0xBBu8; 4]);
605 })
606 .await;
607 }
608
609 #[::fuchsia::test]
610 async fn test_rmw_preserves_data() {
611 spawn_kernel_and_run(async |current_task| {
612 let kernel = current_task.kernel();
613 remote_block_device_init(kernel);
614 let registry = kernel.remote_block_device_registry.clone();
615 let server = VmoBackedServer::new(3, 512, &[0xFFu8; 1536])
618 .expect("Failed to create VmoBackedServer");
619 let (client, server_end) = fidl::endpoints::create_endpoints::<BlockMarker>();
620 std::thread::spawn(move || {
621 let mut executor = fuchsia_async::LocalExecutor::default();
622 executor.run_singlethreaded(async move {
623 use fidl::endpoints::RequestStream;
624 server.serve(server_end.into_stream().cast_stream()).await.unwrap();
625 });
626 });
627
628 registry
629 .create_remote_block_device(kernel, "test", client)
630 .expect("create_remote_block_device failed.");
631
632 let device = registry.open(0).expect("open failed.");
633 let file = anon_test_file(¤t_task, device.create_file_ops(), OpenFlags::RDWR);
634
635 let mut buf = VecInputBuffer::from(vec![0xAAu8; 100]);
639 file.seek(¤t_task, SeekTarget::Set(600)).expect("seek failed");
640 assert_eq!(file.write(¤t_task, &mut buf).expect("write failed."), 100);
641
642 let mut buf = VecOutputBuffer::new(512);
644 file.seek(¤t_task, SeekTarget::Set(512)).expect("seek failed");
645 assert_eq!(file.read(¤t_task, &mut buf).expect("read failed."), 512);
646 let data = buf.data();
647
648 assert_eq!(&data[0..88], &[0xFFu8; 88]);
650 assert_eq!(&data[88..188], &[0xAAu8; 100]);
652 assert_eq!(&data[188..512], &[0xFFu8; 324]);
654 })
655 .await;
656 }
657}