blob: 59a1ae6f56b376aeb8b83a629911388062b69ec3 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_fuchsia_hardware_google_nanohub::DeviceMarker;
use fuchsia_component as fcomponent;
use starnix_core::mm::ProtectionFlags;
use starnix_core::task::CurrentTask;
use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
use starnix_core::vfs::{
FdNumber, FileObject, FileOps, fileops_impl_nonseekable, fileops_impl_noop_sync,
};
use starnix_logging::{impossible_error, log_error};
use starnix_sync::{FileOpsCore, Locked, Unlocked};
use starnix_uapi::errors::Errno;
use starnix_uapi::file_mode::{Access, AccessCheck, FileMode};
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::vfs::ResolveFlags;
use starnix_uapi::{errno, error};
use zx::HandleBased;
pub struct NanohubFirmwareFile {}
const FIRMWARE_DIRECTORIES: &[&str] =
&["/vendor/etc/firmware/", "/vendor/firmware/", "/data/vendor/mcu_firmware/"];
/// Split a firmware file name and offset integer.
///
/// **NOTE**: Filenames that contain spaces are not supported.
///
/// Examples:
///
/// ```
/// assert_eq!(
/// split_filename_and_offset(&String::from("some_file 1234")),
/// Ok(("some_file", 1234)),
/// );
/// assert_eq!(
/// split_filename_and_offset(&String::from("some file with spaces 5678")),
/// Err(errno!(EINVAL)),
/// );
/// ```
pub fn split_filename_and_offset(input_string: &String) -> Result<(&str, u64), Errno> {
let content = input_string.trim();
// Split on the first space.
match content.find(' ') {
Some(index) => {
let (file_name, offset_string) = content.split_at(index);
let offset: u64 = offset_string.trim().parse::<u64>().map_err(|_| errno!(EINVAL))?;
Ok((file_name, offset))
}
None => Ok((content, 0)),
}
}
impl FileOps for NanohubFirmwareFile {
fileops_impl_nonseekable!();
fileops_impl_noop_sync!();
fn read(
&self,
_locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
_current_task: &CurrentTask,
_offset: usize,
_data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
error!(EINVAL)
}
fn write(
&self,
locked: &mut Locked<FileOpsCore>,
_file: &FileObject,
current_task: &CurrentTask,
_offset: usize,
data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
debug_assert!(_offset == 0);
let input_bytes = data.read_all()?;
let input_string = String::from_utf8(input_bytes).map_err(|_| errno!(ENOENT))?;
let (file_name, offset) = split_filename_and_offset(&input_string)?;
// WARNING: Creating an Unlocked::new() here is incorrect and
// could introduce deadlocks. Please do not copy this pattern.
//
// For full context see the discussion on
// https://fuchsia-review.googlesource.com/c/fuchsia/+/1221058
#[allow(
clippy::undocumented_unsafe_blocks,
reason = "Force documented unsafe blocks in Starnix"
)]
let mut unlocked = unsafe { Unlocked::new() };
for prefix_path in FIRMWARE_DIRECTORIES {
let firmware_full_path = prefix_path.to_string() + file_name;
let firmware_open_result = current_task.open_file_at(
&mut unlocked,
FdNumber::AT_FDCWD,
firmware_full_path.as_bytes().into(),
OpenFlags::RDONLY,
FileMode::default(),
ResolveFlags::empty(),
AccessCheck::check_for(Access::READ),
);
if firmware_open_result.is_err() {
continue;
}
let memory = firmware_open_result.unwrap().get_memory(
locked,
current_task,
None,
ProtectionFlags::READ | ProtectionFlags::EXEC,
)?;
let vmo = memory
.as_vmo()
.ok_or_else(|| errno!(EINVAL))?
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.map_err(impossible_error)?;
let device_proxy = fcomponent::client::connect_to_protocol_sync::<DeviceMarker>()
.map_err(|_| errno!(ENOENT))?;
device_proxy
.download_firmware(vmo, offset, zx::MonotonicInstant::INFINITE)
.map_err(|_| errno!(ENOENT))?
.map_err(|_| errno!(ENOENT))?;
return Ok(data.bytes_read());
}
log_error!("Unable to locate a firmware file in any know directories '{:?}'", file_name);
error!(ENOENT)
}
}
impl NanohubFirmwareFile {
pub fn new() -> Box<Self> {
Box::new(NanohubFirmwareFile {})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[::fuchsia::test]
async fn test_split_filename_and_offset() {
assert_eq!(
split_filename_and_offset(&String::from("some_file 1234")),
Ok(("some_file", 1234)),
);
assert_eq!(
split_filename_and_offset(&String::from(" some_file 5678 ")),
Ok(("some_file", 5678))
);
assert_eq!(split_filename_and_offset(&String::from("some_file")), Ok(("some_file", 0)));
assert_eq!(
split_filename_and_offset(&String::from("some_file 1234")),
Ok(("some_file", 1234)),
);
assert_eq!(
split_filename_and_offset(&String::from("some file with spaces 5678")),
Err(errno!(EINVAL)),
);
}
}