blob: 9778aa5f857cd2dc223fb708d86c63d9d56c35c6 [file] [log] [blame]
// Copyright 2023 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 crate::arch::vdso::VDSO_SIGRETURN_NAME;
use crate::mm::PAGE_SIZE;
use crate::time::utc::update_utc_clock;
use fidl::AsHandleRef;
use fuchsia_zircon::{
ClockTransformation, HandleBased, {self as zx},
};
use once_cell::sync::Lazy;
use process_builder::elf_parse;
use starnix_uapi::errors::Errno;
use starnix_uapi::{errno, from_status_like_fdio, uapi};
use std::mem::size_of;
use std::sync::atomic::Ordering;
use std::sync::Arc;
static VVAR_SIZE: Lazy<usize> = Lazy::new(|| *PAGE_SIZE as usize);
pub static ZX_TIME_VALUES_VMO: Lazy<Arc<zx::Vmo>> =
Lazy::new(|| load_time_values_vmo().expect("Could not find time values VMO"));
#[derive(Default)]
pub struct MemoryMappedVvar {
map_addr: usize,
}
impl MemoryMappedVvar {
/// Maps the vvar vmo to a region of the Starnix kernel root VMAR and stores the address of
/// the mapping in this object.
/// Initialises the mapped region with data by writing an initial set of vvar data
pub fn new(vmo: &zx::Vmo) -> Result<MemoryMappedVvar, zx::Status> {
let vvar_data_size = size_of::<uapi::vvar_data>();
// Check that the vvar_data struct isn't larger than the size of the memory mapped vvar
debug_assert!(vvar_data_size <= *VVAR_SIZE);
let flags = zx::VmarFlags::PERM_READ
| zx::VmarFlags::ALLOW_FAULTS
| zx::VmarFlags::REQUIRE_NON_RESIZABLE
| zx::VmarFlags::PERM_WRITE;
let map_addr = fuchsia_runtime::vmar_root_self().map(0, &vmo, 0, *VVAR_SIZE, flags)?;
let memory_mapped_vvar = MemoryMappedVvar { map_addr };
let vvar_data = memory_mapped_vvar.get_pointer_to_memory_mapped_vvar();
vvar_data.seq_num.store(0, Ordering::Release);
Ok(memory_mapped_vvar)
}
fn get_pointer_to_memory_mapped_vvar(&self) -> &uapi::vvar_data {
let vvar_data = unsafe {
// SAFETY: It is checked in the assertion in MemporyMappedVvar's constructor that the
// size of the memory region map_addr points to is larger than the size of
// uapi::vvar_data.
&*(self.map_addr as *const uapi::vvar_data)
};
vvar_data
}
pub fn update_utc_data_transform(&self, new_transform: &ClockTransformation) {
let vvar_data = self.get_pointer_to_memory_mapped_vvar();
let old_transform = ClockTransformation {
reference_offset: vvar_data.mono_to_utc_reference_offset.load(Ordering::Acquire),
synthetic_offset: vvar_data.mono_to_utc_synthetic_offset.load(Ordering::Acquire),
rate: zx::sys::zx_clock_rate_t {
synthetic_ticks: vvar_data.mono_to_utc_synthetic_ticks.load(Ordering::Acquire),
reference_ticks: vvar_data.mono_to_utc_reference_ticks.load(Ordering::Acquire),
},
};
if old_transform != *new_transform {
let seq_num = vvar_data.seq_num.fetch_add(1, Ordering::Acquire);
// Verify that no other thread is currently trying to update vvar_data
debug_assert!(seq_num & 1 == 0);
vvar_data
.mono_to_utc_reference_offset
.store(new_transform.reference_offset, Ordering::Release);
vvar_data
.mono_to_utc_synthetic_offset
.store(new_transform.synthetic_offset, Ordering::Release);
vvar_data
.mono_to_utc_reference_ticks
.store(new_transform.rate.reference_ticks, Ordering::Release);
vvar_data
.mono_to_utc_synthetic_ticks
.store(new_transform.rate.synthetic_ticks, Ordering::Release);
let seq_num_after = vvar_data.seq_num.swap(seq_num + 2, Ordering::Release);
// Verify that no other thread also tried to update vvar_data during this update
debug_assert!(seq_num_after == seq_num + 1)
}
}
}
impl Drop for MemoryMappedVvar {
fn drop(&mut self) {
// SAFETY: We owned the mapping.
unsafe {
fuchsia_runtime::vmar_root_self()
.unmap(self.map_addr, *VVAR_SIZE)
.expect("failed to unmap MemoryMappedVvar");
}
}
}
pub struct Vdso {
pub vmo: Arc<zx::Vmo>,
pub sigreturn_offset: u64,
pub vvar_writeable: Arc<MemoryMappedVvar>,
pub vvar_readonly: Arc<zx::Vmo>,
}
impl Vdso {
pub fn new() -> Self {
let vmo = load_vdso_from_file().expect("Couldn't read vDSO from disk");
let sigreturn_offset = match VDSO_SIGRETURN_NAME {
Some(name) => get_sigreturn_offset(&vmo, name)
.expect("Couldn't find sigreturn trampoline code in vDSO"),
None => 0,
};
let (vvar_writeable, vvar_readonly) = create_vvar_and_handles();
Self { vmo, sigreturn_offset, vvar_writeable, vvar_readonly }
}
}
fn create_vvar_and_handles() -> (Arc<MemoryMappedVvar>, Arc<zx::Vmo>) {
// Creating a vvar vmo which has a handle which is writeable.
let vvar_vmo_writeable =
Arc::new(zx::Vmo::create(*VVAR_SIZE as u64).expect("Couldn't create vvar vvmo"));
// Map the writeable vvar_vmo to a region of Starnix kernel VMAR and write initial vvar_data
let vvar_memory_mapped =
Arc::new(MemoryMappedVvar::new(&vvar_vmo_writeable).expect("couldn't map vvar vmo"));
// Write initial mono to utc transform to the vvar.
update_utc_clock(&vvar_memory_mapped);
let vvar_writeable_rights = vvar_vmo_writeable
.basic_info()
.expect("Couldn't get rights of writeable vvar handle")
.rights;
// Create a duplicate handle to this vvar vmo which doesn't have write permission
// This handle is used to map vvar into linux userspace
let vvar_readable_rights = vvar_writeable_rights.difference(zx::Rights::WRITE);
let vvar_vmo_readonly = Arc::new(
vvar_vmo_writeable
.as_ref()
.duplicate_handle(vvar_readable_rights)
.expect("couldn't duplicate vvar handle"),
);
(vvar_memory_mapped, vvar_vmo_readonly)
}
fn sync_open_in_namespace(
path: &str,
flags: fidl_fuchsia_io::OpenFlags,
) -> Result<fidl_fuchsia_io::DirectorySynchronousProxy, Errno> {
let (client, server) = fidl::Channel::create();
let dir_proxy = fidl_fuchsia_io::DirectorySynchronousProxy::new(client);
let namespace = fdio::Namespace::installed().map_err(|_| errno!(EINVAL))?;
namespace.open(path, flags, server).map_err(|_| errno!(ENOENT))?;
Ok(dir_proxy)
}
/// Reads the vDSO file and returns the backing VMO.
fn load_vdso_from_file() -> Result<Arc<zx::Vmo>, Errno> {
const VDSO_FILENAME: &str = "libvdso.so";
const VDSO_LOCATION: &str = "/pkg/data";
let dir_proxy = sync_open_in_namespace(VDSO_LOCATION, fuchsia_fs::OpenFlags::RIGHT_READABLE)?;
let vdso_vmo = syncio::directory_open_vmo(
&dir_proxy,
VDSO_FILENAME,
fidl_fuchsia_io::VmoFlags::READ,
zx::Time::INFINITE,
)
.map_err(|status| from_status_like_fdio!(status))?;
Ok(Arc::new(vdso_vmo))
}
fn load_time_values_vmo() -> Result<Arc<zx::Vmo>, Errno> {
const FILENAME: &str = "time_values";
const DIR: &str = "/boot/kernel";
let (client, server) = fidl::Channel::create();
let dir_proxy = fidl_fuchsia_io::DirectorySynchronousProxy::new(client);
let namespace = fdio::Namespace::installed().map_err(|_| errno!(EINVAL))?;
namespace
.open(DIR, fuchsia_fs::OpenFlags::RIGHT_READABLE, server)
.map_err(|_| errno!(ENOENT))?;
let vmo = syncio::directory_open_vmo(
&dir_proxy,
FILENAME,
fidl_fuchsia_io::VmoFlags::READ,
zx::Time::INFINITE,
)
.map_err(|status| from_status_like_fdio!(status))?;
// Check that the time values VMO is the expected size of 1 page. If it is not,
// panic the kernel, as it means that the size of the time values VMO has changed
// and the starnix vDSO linker script at //src/starnix/kernel/vdso/vdso.ld should
// be updated.
let vmo_size = vmo.get_size().expect("failed to get time values VMO size");
let expected_size = 0x1000u64;
if vmo_size != expected_size {
panic!(
"time values VMO has unexpected size; got {:?}, expected {:?}",
vmo_size, expected_size
);
}
Ok(Arc::new(vmo))
}
fn get_sigreturn_offset(vdso_vmo: &zx::Vmo, sigreturn_name: &str) -> Result<u64, Errno> {
let dyn_section = elf_parse::Elf64DynSection::from_vmo(vdso_vmo).map_err(|_| errno!(EINVAL))?;
let symtab =
dyn_section.dynamic_entry_with_tag(elf_parse::Elf64DynTag::Symtab).ok_or(errno!(EINVAL))?;
let strtab =
dyn_section.dynamic_entry_with_tag(elf_parse::Elf64DynTag::Strtab).ok_or(errno!(EINVAL))?;
let strsz =
dyn_section.dynamic_entry_with_tag(elf_parse::Elf64DynTag::Strsz).ok_or(errno!(EINVAL))?;
// Find the name of the signal trampoline in the string table and store the index.
let strtab_bytes = vdso_vmo
.read_to_vec(strtab.value, strsz.value)
.map_err(|status| from_status_like_fdio!(status))?;
let mut strtab_items = strtab_bytes.split(|c: &u8| *c == 0u8);
let strtab_idx = strtab_items
.position(|entry: &[u8]| std::str::from_utf8(entry) == Ok(sigreturn_name))
.ok_or(errno!(ENOENT))?;
const SYM_ENTRY_SIZE: usize = std::mem::size_of::<elf_parse::Elf64Sym>();
// In the symbolic table, find a symbol with a name index pointing to the name we're looking for.
let mut symtab_offset = symtab.value;
loop {
let sym_entry = vdso_vmo
.read_to_object::<elf_parse::Elf64Sym>(symtab_offset)
.map_err(|status| from_status_like_fdio!(status))?;
if sym_entry.st_name as usize == strtab_idx {
return Ok(sym_entry.st_value);
}
symtab_offset += SYM_ENTRY_SIZE as u64;
}
}