forked from rcore-os/zCore
Merge remote-tracking branch 'origin/master' into merging
This commit is contained in:
commit
e15ce35e61
16
README.md
16
README.md
|
@ -1,6 +1,6 @@
|
||||||
# zCore
|
# zCore
|
||||||
|
|
||||||
[](https://github.com/rcore-os/zCore/actions)
|
[](https://github.com/rcore-os/zCore/actions)
|
||||||
[](https://rcore-os.github.io/zCore/zircon_object/)
|
[](https://rcore-os.github.io/zCore/zircon_object/)
|
||||||
[](https://coveralls.io/github/rcore-os/zCore?branch=master)
|
[](https://coveralls.io/github/rcore-os/zCore?branch=master)
|
||||||
|
|
||||||
|
@ -14,10 +14,18 @@ Reimplement [Zircon][zircon] microkernel in safe Rust as a userspace program!
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
|
Environments:
|
||||||
|
|
||||||
|
* [Rust toolchain](http://rustup.rs)
|
||||||
|
* [QEMU](https://www.qemu.org)
|
||||||
|
* [Git LFS](https://git-lfs.github.com)
|
||||||
|
|
||||||
|
Clone repo and pull prebuilt fuchsia images:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/rcore-os/zCore
|
git clone https://github.com/rcore-os/zCore --recursive
|
||||||
git lfs pull
|
|
||||||
cd zCore
|
cd zCore
|
||||||
|
git lfs pull
|
||||||
```
|
```
|
||||||
|
|
||||||
Prepare Alpine Linux rootfs:
|
Prepare Alpine Linux rootfs:
|
||||||
|
@ -32,7 +40,7 @@ Run native Linux program (Busybox):
|
||||||
cargo run --release -p linux-loader /bin/busybox [args]
|
cargo run --release -p linux-loader /bin/busybox [args]
|
||||||
```
|
```
|
||||||
|
|
||||||
Run native Zircon program (userboot):
|
Run native Zircon program (shell):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run --release -p zircon-loader prebuilt/zircon
|
cargo run --release -p zircon-loader prebuilt/zircon
|
||||||
|
|
|
@ -340,17 +340,36 @@ fn vdso_constants() -> VdsoConstants {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the HAL.
|
/// Initialize the HAL.
|
||||||
pub fn init() {
|
pub fn init(config: Config) {
|
||||||
timer_init();
|
timer_init();
|
||||||
interrupt::init();
|
interrupt::init();
|
||||||
COM1.lock().init();
|
COM1.lock().init();
|
||||||
unsafe {
|
unsafe {
|
||||||
// enable global page
|
// enable global page
|
||||||
Cr4::update(|f| f.insert(Cr4Flags::PAGE_GLOBAL));
|
Cr4::update(|f| f.insert(Cr4Flags::PAGE_GLOBAL));
|
||||||
|
// store config
|
||||||
|
CONFIG = config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration of HAL.
|
||||||
|
pub struct Config {
|
||||||
|
pub acpi_rsdp: u64,
|
||||||
|
pub smbios: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[export_name = "fetch_fault_vaddr"]
|
#[export_name = "fetch_fault_vaddr"]
|
||||||
pub fn fetch_fault_vaddr() -> VirtAddr {
|
pub fn fetch_fault_vaddr() -> VirtAddr {
|
||||||
Cr2::read().as_u64() as _
|
Cr2::read().as_u64() as _
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get physical address of `acpi_rsdp` and `smbios` on x86_64.
|
||||||
|
#[export_name = "hal_pc_firmware_tables"]
|
||||||
|
pub fn pc_firmware_tables() -> (u64, u64) {
|
||||||
|
unsafe { (CONFIG.acpi_rsdp, CONFIG.smbios) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut CONFIG: Config = Config {
|
||||||
|
acpi_rsdp: 0,
|
||||||
|
smbios: 0,
|
||||||
|
};
|
||||||
|
|
|
@ -171,10 +171,11 @@ pub fn frame_copy(src: PhysAddr, target: PhysAddr) {
|
||||||
|
|
||||||
/// Zero `target` frame.
|
/// Zero `target` frame.
|
||||||
#[export_name = "hal_frame_zero"]
|
#[export_name = "hal_frame_zero"]
|
||||||
pub fn frame_zero(target: PhysAddr) {
|
pub fn frame_zero_in_range(target: PhysAddr, start: usize, end: usize) {
|
||||||
|
assert!(start < PAGE_SIZE && end <= PAGE_SIZE);
|
||||||
trace!("frame_zero: {:#x}", target);
|
trace!("frame_zero: {:#x}", target);
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(phys_to_virt(target) as *mut u8, 0, PAGE_SIZE);
|
core::ptr::write_bytes(phys_to_virt(target + start) as *mut u8, 0, end - start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,11 +212,11 @@ pub fn timer_tick() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the HAL.
|
/// Initialize the HAL.
|
||||||
pub fn init() {
|
pub fn init(config: Config) {
|
||||||
unsafe {
|
unsafe {
|
||||||
trapframe::init();
|
trapframe::init();
|
||||||
}
|
}
|
||||||
arch::init();
|
arch::init(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -209,7 +209,7 @@ pub fn frame_copy(_src: PhysAddr, _target: PhysAddr) {
|
||||||
/// Zero `target` frame.
|
/// Zero `target` frame.
|
||||||
#[linkage = "weak"]
|
#[linkage = "weak"]
|
||||||
#[export_name = "hal_frame_zero"]
|
#[export_name = "hal_frame_zero"]
|
||||||
pub fn frame_zero(_target: PhysAddr) {
|
pub fn frame_zero_in_range(_target: PhysAddr, _start: usize, _end: usize) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,3 +282,10 @@ pub fn vdso_constants() -> VdsoConstants {
|
||||||
pub fn fetch_fault_vaddr() -> VirtAddr {
|
pub fn fetch_fault_vaddr() -> VirtAddr {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get physical address of `acpi_rsdp` and `smbios` on x86_64.
|
||||||
|
#[linkage = "weak"]
|
||||||
|
#[export_name = "hal_pc_firmware_tables"]
|
||||||
|
pub fn pc_firmware_tables() -> (u64, u64) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
2
rboot
2
rboot
|
@ -1 +1 @@
|
||||||
Subproject commit 5ab7648d1401f56255d3d90fb160b6b7c13c879a
|
Subproject commit 228ef7902486b8c5e5a69272b1f0f1dcf962c098
|
|
@ -29,7 +29,10 @@ pub extern "C" fn _start(boot_info: &BootInfo) -> ! {
|
||||||
#[cfg(feature = "graphic")]
|
#[cfg(feature = "graphic")]
|
||||||
init_framebuffer(boot_info);
|
init_framebuffer(boot_info);
|
||||||
info!("{:#x?}", boot_info);
|
info!("{:#x?}", boot_info);
|
||||||
kernel_hal_bare::init();
|
kernel_hal_bare::init(kernel_hal_bare::Config {
|
||||||
|
acpi_rsdp: boot_info.acpi2_rsdp_addr,
|
||||||
|
smbios: boot_info.smbios_addr,
|
||||||
|
});
|
||||||
|
|
||||||
let zbi_data = unsafe {
|
let zbi_data = unsafe {
|
||||||
core::slice::from_raw_parts(
|
core::slice::from_raw_parts(
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub fn create_kcounter_vmo() -> (Arc<VmObject>, Arc<VmObject>) {
|
||||||
kcounters_arena_end as usize / PAGE_SIZE,
|
kcounters_arena_end as usize / PAGE_SIZE,
|
||||||
"all kcounters must in the same page"
|
"all kcounters must in the same page"
|
||||||
);
|
);
|
||||||
unsafe { VmObject::new_physical(paddr, 1) }
|
VmObject::new_physical(paddr, 1)
|
||||||
};
|
};
|
||||||
kcounters_vmo.set_name("counters/arena");
|
kcounters_vmo.set_name("counters/arena");
|
||||||
(counter_name_vmo, kcounters_vmo)
|
(counter_name_vmo, kcounters_vmo)
|
||||||
|
|
|
@ -12,14 +12,7 @@ use {
|
||||||
alloc::{boxed::Box, sync::Arc, vec::Vec},
|
alloc::{boxed::Box, sync::Arc, vec::Vec},
|
||||||
kernel_hal::GeneralRegs,
|
kernel_hal::GeneralRegs,
|
||||||
xmas_elf::ElfFile,
|
xmas_elf::ElfFile,
|
||||||
zircon_object::{
|
zircon_object::{ipc::*, object::*, task::*, util::elf_loader::*, vm::*, resource::*},
|
||||||
ipc::*,
|
|
||||||
object::*,
|
|
||||||
resource::{Resource, ResourceFlags, ResourceKind},
|
|
||||||
task::*,
|
|
||||||
util::elf_loader::*,
|
|
||||||
vm::*,
|
|
||||||
},
|
|
||||||
zircon_syscall::Syscall,
|
zircon_syscall::Syscall,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,7 +49,13 @@ pub fn run_userboot(images: &Images<impl AsRef<[u8]>>, cmdline: &str) -> Arc<Pro
|
||||||
let job = Job::root();
|
let job = Job::root();
|
||||||
let proc = Process::create(&job, "proc", 0).unwrap();
|
let proc = Process::create(&job, "proc", 0).unwrap();
|
||||||
let thread = Thread::create(&proc, "thread", 0).unwrap();
|
let thread = Thread::create(&proc, "thread", 0).unwrap();
|
||||||
let resource = Resource::create("root", ResourceKind::ROOT, 0, 0, ResourceFlags::empty());
|
let resource = Resource::create(
|
||||||
|
"root",
|
||||||
|
ResourceKind::ROOT,
|
||||||
|
0,
|
||||||
|
0x1_0000_0000,
|
||||||
|
ResourceFlags::empty(),
|
||||||
|
);
|
||||||
let vmar = proc.vmar();
|
let vmar = proc.vmar();
|
||||||
|
|
||||||
// userboot
|
// userboot
|
||||||
|
|
|
@ -104,15 +104,20 @@ impl Fifo {
|
||||||
let count_size = count * elem_size;
|
let count_size = count * elem_size;
|
||||||
assert_eq!(data.len(), count_size);
|
assert_eq!(data.len(), count_size);
|
||||||
|
|
||||||
let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?;
|
let peer = self.peer.upgrade();
|
||||||
let mut recv_queue = self.recv_queue.lock();
|
let mut recv_queue = self.recv_queue.lock();
|
||||||
if recv_queue.is_empty() {
|
if recv_queue.is_empty() {
|
||||||
|
if peer.is_none() {
|
||||||
|
return Err(ZxError::PEER_CLOSED);
|
||||||
|
}
|
||||||
return Err(ZxError::SHOULD_WAIT);
|
return Err(ZxError::SHOULD_WAIT);
|
||||||
}
|
}
|
||||||
let read_size = count_size.min(recv_queue.len());
|
let read_size = count_size.min(recv_queue.len());
|
||||||
if recv_queue.len() == self.capacity() {
|
if recv_queue.len() == self.capacity() {
|
||||||
|
if let Some(peer) = peer {
|
||||||
peer.base.signal_set(Signal::WRITABLE);
|
peer.base.signal_set(Signal::WRITABLE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (i, x) in recv_queue.drain(..read_size).enumerate() {
|
for (i, x) in recv_queue.drain(..read_size).enumerate() {
|
||||||
data[i] = x;
|
data[i] = x;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ bitflags! {
|
||||||
const SIGNALED = 1 << 3;
|
const SIGNALED = 1 << 3;
|
||||||
const HANDLE_CLOSED = 1 << 23;
|
const HANDLE_CLOSED = 1 << 23;
|
||||||
|
|
||||||
|
const KERNEL_ALL = 0xff_ffff;
|
||||||
const USER_ALL = 0xff << 24;
|
const USER_ALL = 0xff << 24;
|
||||||
|
|
||||||
const CLOCK_STARTED = 1 << 4;
|
const CLOCK_STARTED = 1 << 4;
|
||||||
|
|
|
@ -45,11 +45,7 @@ impl Resource {
|
||||||
flags: ResourceFlags,
|
flags: ResourceFlags,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
Arc::new(Resource {
|
Arc::new(Resource {
|
||||||
base: {
|
base: KObjectBase::with_name(name),
|
||||||
let base = KObjectBase::new();
|
|
||||||
base.set_name(name);
|
|
||||||
base
|
|
||||||
},
|
|
||||||
kind,
|
kind,
|
||||||
addr,
|
addr,
|
||||||
len,
|
len,
|
||||||
|
@ -72,9 +68,6 @@ impl Resource {
|
||||||
len: usize,
|
len: usize,
|
||||||
) -> ZxResult {
|
) -> ZxResult {
|
||||||
self.validate(kind)?;
|
self.validate(kind)?;
|
||||||
if self.kind == ResourceKind::MMIO {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
if addr >= self.addr && (addr + len) <= (self.addr + self.len) {
|
if addr >= self.addr && (addr + len) <= (self.addr + self.len) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -185,20 +185,14 @@ impl Job {
|
||||||
self.exceptionate.clone()
|
self.exceptionate.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumerate_process(&self, mut f: impl FnMut(KoID) -> bool) {
|
/// Get KoIDs of Processes.
|
||||||
self.inner
|
pub fn process_ids(&self) -> Vec<KoID> {
|
||||||
.lock()
|
self.inner.lock().processes.iter().map(|p| p.id()).collect()
|
||||||
.processes
|
|
||||||
.iter()
|
|
||||||
.find(|child| !f(child.id()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumerate_children(&self, mut f: impl FnMut(KoID) -> bool) {
|
/// Get KoIDs of children Jobs.
|
||||||
self.inner
|
pub fn children_ids(&self) -> Vec<KoID> {
|
||||||
.lock()
|
self.inner.lock().children.iter().map(|j| j.id()).collect()
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.find(|child| !f(child.id()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use {
|
use {
|
||||||
super::{exception::*, job::Job, job_policy::*, resource::*, thread::Thread, *},
|
super::{exception::*, job::Job, job_policy::*, thread::Thread, *},
|
||||||
crate::{object::*, signal::Futex, vm::*},
|
crate::{object::*, signal::Futex, vm::*},
|
||||||
alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec},
|
alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec},
|
||||||
core::{any::Any, sync::atomic::AtomicI32},
|
core::{any::Any, sync::atomic::AtomicI32},
|
||||||
|
@ -341,16 +341,6 @@ impl Process {
|
||||||
Ok(object)
|
Ok(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to get Resource and validate it
|
|
||||||
pub fn validate_resource(&self, handle_value: HandleValue, kind: ResourceKind) -> ZxResult {
|
|
||||||
let handle = self.get_handle(handle_value)?;
|
|
||||||
let object = handle
|
|
||||||
.object
|
|
||||||
.downcast_arc::<Resource>()
|
|
||||||
.map_err(|_| ZxError::WRONG_TYPE)?;
|
|
||||||
object.validate(kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_handle_info(&self, handle_value: HandleValue) -> ZxResult<HandleBasicInfo> {
|
pub fn get_handle_info(&self, handle_value: HandleValue) -> ZxResult<HandleBasicInfo> {
|
||||||
let handle = self.get_handle(handle_value)?;
|
let handle = self.get_handle(handle_value)?;
|
||||||
Ok(handle.get_info())
|
Ok(handle.get_info())
|
||||||
|
@ -424,12 +414,9 @@ impl Process {
|
||||||
self.exceptionate.clone()
|
self.exceptionate.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumerate_thread(&self, mut f: impl FnMut(KoID) -> bool) {
|
/// Get KoIDs of Threads.
|
||||||
self.inner
|
pub fn thread_ids(&self) -> Vec<KoID> {
|
||||||
.lock()
|
self.inner.lock().threads.iter().map(|t| t.id()).collect()
|
||||||
.threads
|
|
||||||
.iter()
|
|
||||||
.find(|child| !f(child.id()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ impl Thread {
|
||||||
context.general.rsp = stack;
|
context.general.rsp = stack;
|
||||||
context.general.rdi = arg1;
|
context.general.rdi = arg1;
|
||||||
context.general.rsi = arg2;
|
context.general.rsi = arg2;
|
||||||
context.general.rflags |= 0x202;
|
context.general.rflags |= 0x3202;
|
||||||
context.vector.fcw = 0x37f;
|
context.vector.fcw = 0x37f;
|
||||||
inner.state = ThreadState::Running;
|
inner.state = ThreadState::Running;
|
||||||
self.base.signal_set(Signal::THREAD_RUNNING);
|
self.base.signal_set(Signal::THREAD_RUNNING);
|
||||||
|
@ -191,7 +191,7 @@ impl Thread {
|
||||||
let mut inner = self.inner.lock();
|
let mut inner = self.inner.lock();
|
||||||
let context = inner.context.as_mut().ok_or(ZxError::BAD_STATE)?;
|
let context = inner.context.as_mut().ok_or(ZxError::BAD_STATE)?;
|
||||||
context.general = regs;
|
context.general = regs;
|
||||||
context.general.rflags |= 0x202;
|
context.general.rflags |= 0x3202;
|
||||||
context.vector.fcw = 0x37f;
|
context.vector.fcw = 0x37f;
|
||||||
inner.state = ThreadState::Running;
|
inner.state = ThreadState::Running;
|
||||||
self.base.signal_set(Signal::THREAD_RUNNING);
|
self.base.signal_set(Signal::THREAD_RUNNING);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::sync::atomic::*;
|
use core::sync::atomic::*;
|
||||||
use {
|
use {
|
||||||
super::*, crate::object::*, alloc::collections::VecDeque, alloc::sync::Arc, alloc::vec::Vec,
|
super::*, crate::object::*, alloc::sync::Arc, alloc::vec::Vec, bitflags::bitflags,
|
||||||
bitflags::bitflags, kernel_hal::PageTable, spin::Mutex,
|
kernel_hal::PageTable, spin::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
@ -449,28 +449,20 @@ impl VmAddressRegion {
|
||||||
Err(ZxError::NOT_FOUND)
|
Err(ZxError::NOT_FOUND)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_task_stats(&self) -> ZxInfoTaskStats {
|
fn for_each_mapping(&self, f: &mut impl FnMut(&Arc<VmMapping>)) {
|
||||||
let mut task_stats = ZxInfoTaskStats::default();
|
let guard = self.inner.lock();
|
||||||
let mut list = VecDeque::new();
|
let inner = guard.as_ref().unwrap();
|
||||||
self.inner
|
for map in inner.mappings.iter() {
|
||||||
.lock()
|
f(map);
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.for_each(|child| {
|
|
||||||
list.push_back(child.clone());
|
|
||||||
});
|
|
||||||
while let Some(vmar) = list.pop_front() {
|
|
||||||
let vmar_inner = vmar.inner.lock();
|
|
||||||
let inner = vmar_inner.as_ref().unwrap();
|
|
||||||
inner.children.iter().for_each(|child| {
|
|
||||||
list.push_back(child.clone());
|
|
||||||
});
|
|
||||||
inner.mappings.iter().for_each(|map| {
|
|
||||||
map.fill_in_task_status(&mut task_stats);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
for child in inner.children.iter() {
|
||||||
|
child.for_each_mapping(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_task_stats(&self) -> TaskStatsInfo {
|
||||||
|
let mut task_stats = TaskStatsInfo::default();
|
||||||
|
self.for_each_mapping(&mut |map| map.fill_in_task_status(&mut task_stats));
|
||||||
task_stats
|
task_stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +506,7 @@ struct VmMappingInner {
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ZxInfoTaskStats {
|
pub struct TaskStatsInfo {
|
||||||
mapped_bytes: u64,
|
mapped_bytes: u64,
|
||||||
private_bytes: u64,
|
private_bytes: u64,
|
||||||
shared_bytes: u64,
|
shared_bytes: u64,
|
||||||
|
@ -582,7 +574,7 @@ impl VmMapping {
|
||||||
.unmap_from(&mut page_table, inner.addr, inner.vmo_offset, inner.size);
|
.unmap_from(&mut page_table, inner.addr, inner.vmo_offset, inner.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_in_task_status(&self, task_stats: &mut ZxInfoTaskStats) {
|
fn fill_in_task_status(&self, task_stats: &mut TaskStatsInfo) {
|
||||||
let inner = self.inner.lock();
|
let inner = self.inner.lock();
|
||||||
let start_idx = inner.vmo_offset / PAGE_SIZE;
|
let start_idx = inner.vmo_offset / PAGE_SIZE;
|
||||||
let end_idx = start_idx + inner.size / PAGE_SIZE;
|
let end_idx = start_idx + inner.size / PAGE_SIZE;
|
||||||
|
|
|
@ -74,7 +74,7 @@ pub trait VMObjectTrait: Sync + Send {
|
||||||
|
|
||||||
fn remove_mapping(&self, mapping: Weak<VmMapping>);
|
fn remove_mapping(&self, mapping: Weak<VmMapping>);
|
||||||
|
|
||||||
fn complete_info(&self, info: &mut ZxInfoVmo);
|
fn complete_info(&self, info: &mut VmoInfo);
|
||||||
|
|
||||||
fn get_cache_policy(&self) -> CachePolicy;
|
fn get_cache_policy(&self) -> CachePolicy;
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ pub trait VMObjectTrait: Sync + Send {
|
||||||
fn is_paged(&self) -> bool {
|
fn is_paged(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
fn zero(&self, offset: usize, len: usize) -> ZxResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VmObject {
|
pub struct VmObject {
|
||||||
|
@ -132,12 +133,8 @@ impl VmObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new VMO representing a piece of contiguous physical memory.
|
/// Create a new VMO representing a piece of contiguous physical memory.
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure nobody has the ownership of this piece of memory yet.
|
/// You must ensure nobody has the ownership of this piece of memory yet.
|
||||||
#[allow(unsafe_code)]
|
pub fn new_physical(paddr: PhysAddr, pages: usize) -> Arc<Self> {
|
||||||
pub unsafe fn new_physical(paddr: PhysAddr, pages: usize) -> Arc<Self> {
|
|
||||||
Arc::new(VmObject {
|
Arc::new(VmObject {
|
||||||
base: KObjectBase::with_signal(Signal::VMO_ZERO_CHILDREN),
|
base: KObjectBase::with_signal(Signal::VMO_ZERO_CHILDREN),
|
||||||
parent: Mutex::new(Default::default()),
|
parent: Mutex::new(Default::default()),
|
||||||
|
@ -247,8 +244,8 @@ impl VmObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get information of this VMO.
|
/// Get information of this VMO.
|
||||||
pub fn get_info(&self) -> ZxInfoVmo {
|
pub fn get_info(&self) -> VmoInfo {
|
||||||
let mut ret = ZxInfoVmo {
|
let mut ret = VmoInfo {
|
||||||
koid: self.base.id,
|
koid: self.base.id,
|
||||||
name: {
|
name: {
|
||||||
let mut arr = [0u8; 32];
|
let mut arr = [0u8; 32];
|
||||||
|
@ -313,6 +310,14 @@ impl Drop for VmObject {
|
||||||
let mut children = parent.children.lock();
|
let mut children = parent.children.lock();
|
||||||
children.append(&mut my_children);
|
children.append(&mut my_children);
|
||||||
children.retain(|c| c.strong_count() != 0);
|
children.retain(|c| c.strong_count() != 0);
|
||||||
|
children.iter().for_each(|child| {
|
||||||
|
let arc_child = child.upgrade().unwrap();
|
||||||
|
let mut locked_children = arc_child.children.lock();
|
||||||
|
locked_children.retain(|c| c.strong_count() != 0);
|
||||||
|
if locked_children.is_empty() {
|
||||||
|
arc_child.base.signal_set(Signal::VMO_ZERO_CHILDREN);
|
||||||
|
}
|
||||||
|
});
|
||||||
// Non-zero to zero?
|
// Non-zero to zero?
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
parent.base.signal_set(Signal::VMO_ZERO_CHILDREN);
|
parent.base.signal_set(Signal::VMO_ZERO_CHILDREN);
|
||||||
|
@ -324,7 +329,7 @@ impl Drop for VmObject {
|
||||||
/// Describes a VMO.
|
/// Describes a VMO.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ZxInfoVmo {
|
pub struct VmoInfo {
|
||||||
/// The koid of this VMO.
|
/// The koid of this VMO.
|
||||||
koid: KoID,
|
koid: KoID,
|
||||||
/// The name of this VMO.
|
/// The name of this VMO.
|
||||||
|
|
|
@ -276,6 +276,32 @@ impl VMObjectTrait for VMObjectPaged {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn zero(&self, offset: usize, len: usize) -> ZxResult {
|
||||||
|
if offset + len > self.inner.lock().size {
|
||||||
|
return Err(ZxError::OUT_OF_RANGE);
|
||||||
|
}
|
||||||
|
let iter = BlockIter {
|
||||||
|
begin: offset,
|
||||||
|
end: offset + len,
|
||||||
|
block_size_log2: 12,
|
||||||
|
};
|
||||||
|
let mut unwanted = VecDeque::new();
|
||||||
|
for block in iter {
|
||||||
|
//let paddr = self.commit_page(block.block, MMUFlags::READ)?;
|
||||||
|
if block.len() == PAGE_SIZE {
|
||||||
|
let _ = self.commit_page(block.block, MMUFlags::WRITE)?;
|
||||||
|
unwanted.push_back(block.block);
|
||||||
|
self.inner.lock().frames.remove(&block.block);
|
||||||
|
} else if self.committed_pages_in_range(block.block, block.block + 1) != 0 {
|
||||||
|
// check whether this page is initialized, otherwise nothing should be done
|
||||||
|
let paddr = self.commit_page(block.block, MMUFlags::WRITE)?;
|
||||||
|
kernel_hal::frame_zero_in_range(paddr, block.begin, block.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.inner.lock().release_unwanted_pages(unwanted);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.inner.lock().size
|
self.inner.lock().size
|
||||||
}
|
}
|
||||||
|
@ -317,8 +343,12 @@ impl VMObjectTrait for VMObjectPaged {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
let mut inner = self.inner.lock();
|
let mut inner = self.inner.lock();
|
||||||
// non-slice child VMOs do not support decommit.
|
if inner.type_.is_slice() {
|
||||||
if inner.parent.is_some() {
|
let parent_offset = offset + inner.parent_offset;
|
||||||
|
return inner.parent.as_ref().unwrap().decommit(parent_offset, len);
|
||||||
|
}
|
||||||
|
let check = inner.parent.is_none();
|
||||||
|
if !check {
|
||||||
return Err(ZxError::NOT_SUPPORTED);
|
return Err(ZxError::NOT_SUPPORTED);
|
||||||
}
|
}
|
||||||
let start_page = offset / PAGE_SIZE;
|
let start_page = offset / PAGE_SIZE;
|
||||||
|
@ -398,7 +428,7 @@ impl VMObjectTrait for VMObjectPaged {
|
||||||
.drain_filter(|x| x.strong_count() == 0 || Weak::ptr_eq(x, &mapping));
|
.drain_filter(|x| x.strong_count() == 0 || Weak::ptr_eq(x, &mapping));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_info(&self, info: &mut ZxInfoVmo) {
|
fn complete_info(&self, info: &mut VmoInfo) {
|
||||||
info.flags |= VmoInfoFlags::TYPE_PAGED;
|
info.flags |= VmoInfoFlags::TYPE_PAGED;
|
||||||
self.inner.lock().complete_info(info);
|
self.inner.lock().complete_info(info);
|
||||||
}
|
}
|
||||||
|
@ -563,6 +593,15 @@ impl VMObjectPaged {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
let mut inner = self.inner.lock();
|
let mut inner = self.inner.lock();
|
||||||
|
if inner.type_.is_slice() {
|
||||||
|
assert!((inner.parent_limit - inner.parent_offset) / PAGE_SIZE > page_idx);
|
||||||
|
let parent_idx = page_idx + inner.parent_offset / PAGE_SIZE;
|
||||||
|
return inner.parent.as_ref().unwrap().commit_page_internal(
|
||||||
|
parent_idx,
|
||||||
|
flags,
|
||||||
|
&inner.self_ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
// special case
|
// special case
|
||||||
let no_parent = inner.parent.is_none();
|
let no_parent = inner.parent.is_none();
|
||||||
let no_frame = !inner.frames.contains_key(&page_idx);
|
let no_frame = !inner.frames.contains_key(&page_idx);
|
||||||
|
@ -580,7 +619,7 @@ impl VMObjectPaged {
|
||||||
}
|
}
|
||||||
// lazy allocate zero frame
|
// lazy allocate zero frame
|
||||||
let target_frame = PhysFrame::alloc().ok_or(ZxError::NO_MEMORY)?;
|
let target_frame = PhysFrame::alloc().ok_or(ZxError::NO_MEMORY)?;
|
||||||
kernel_hal::frame_zero(target_frame.addr());
|
kernel_hal::frame_zero_in_range(target_frame.addr(), 0, PAGE_SIZE);
|
||||||
if out_of_range {
|
if out_of_range {
|
||||||
// can never be a hidden vmo
|
// can never be a hidden vmo
|
||||||
assert!(!inner.type_.is_hidden());
|
assert!(!inner.type_.is_hidden());
|
||||||
|
@ -735,7 +774,7 @@ impl VMObjectPaged {
|
||||||
let mut inner = self.inner.lock();
|
let mut inner = self.inner.lock();
|
||||||
inner.contiguous = true;
|
inner.contiguous = true;
|
||||||
for (i, f) in frames.drain(0..).enumerate() {
|
for (i, f) in frames.drain(0..).enumerate() {
|
||||||
kernel_hal::frame_zero(f.addr());
|
kernel_hal::frame_zero_in_range(f.addr(), 0, PAGE_SIZE);
|
||||||
let mut state = PageState::new(f);
|
let mut state = PageState::new(f);
|
||||||
state.pin_count += 1;
|
state.pin_count += 1;
|
||||||
inner.frames.insert(i, state);
|
inner.frames.insert(i, state);
|
||||||
|
@ -774,7 +813,7 @@ impl VMObjectPagedInner {
|
||||||
/// Count committed pages of the VMO.
|
/// Count committed pages of the VMO.
|
||||||
fn committed_pages_in_range(&self, start_idx: usize, end_idx: usize) -> usize {
|
fn committed_pages_in_range(&self, start_idx: usize, end_idx: usize) -> usize {
|
||||||
assert!(
|
assert!(
|
||||||
start_idx < self.size / PAGE_SIZE,
|
start_idx < self.size / PAGE_SIZE || start_idx == 0,
|
||||||
"start_idx {:#x}, self.size {:#x}",
|
"start_idx {:#x}, self.size {:#x}",
|
||||||
start_idx,
|
start_idx,
|
||||||
self.size
|
self.size
|
||||||
|
@ -804,6 +843,9 @@ impl VMObjectPagedInner {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if inner.user_id != self.user_id {
|
||||||
|
break;
|
||||||
|
}
|
||||||
current_idx += inner.parent_offset / PAGE_SIZE;
|
current_idx += inner.parent_offset / PAGE_SIZE;
|
||||||
if current_idx >= inner.parent_limit / PAGE_SIZE {
|
if current_idx >= inner.parent_limit / PAGE_SIZE {
|
||||||
break;
|
break;
|
||||||
|
@ -926,7 +968,7 @@ impl VMObjectPagedInner {
|
||||||
Ok(child)
|
Ok(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_info(&self, info: &mut ZxInfoVmo) {
|
fn complete_info(&self, info: &mut VmoInfo) {
|
||||||
if let VMOType::Snapshot = self.type_ {
|
if let VMOType::Snapshot = self.type_ {
|
||||||
info.flags |= VmoInfoFlags::IS_COW_CLONE;
|
info.flags |= VmoInfoFlags::IS_COW_CLONE;
|
||||||
}
|
}
|
||||||
|
@ -946,7 +988,6 @@ impl VMObjectPagedInner {
|
||||||
let mut child = self.self_ref.clone();
|
let mut child = self.self_ref.clone();
|
||||||
while let Some(parent) = option_parent {
|
while let Some(parent) = option_parent {
|
||||||
let mut locked_parent = parent.inner.lock();
|
let mut locked_parent = parent.inner.lock();
|
||||||
if locked_parent.user_id == self.user_id {
|
|
||||||
let (tag, other) = locked_parent.type_.get_tag_and_other(&child);
|
let (tag, other) = locked_parent.type_.get_tag_and_other(&child);
|
||||||
let arc_other = other.upgrade().unwrap();
|
let arc_other = other.upgrade().unwrap();
|
||||||
let mut locked_other = arc_other.inner.lock();
|
let mut locked_other = arc_other.inner.lock();
|
||||||
|
@ -962,6 +1003,7 @@ impl VMObjectPagedInner {
|
||||||
to_insert.tag = PageStateTag::Owned;
|
to_insert.tag = PageStateTag::Owned;
|
||||||
locked_other.frames.insert(idx - start, to_insert);
|
locked_other.frames.insert(idx - start, to_insert);
|
||||||
}
|
}
|
||||||
|
unwanted.push_back(idx + locked_parent.parent_offset / PAGE_SIZE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise, if it exists in our frames, remove it; if not, push_back it again
|
// otherwise, if it exists in our frames, remove it; if not, push_back it again
|
||||||
|
@ -975,18 +1017,21 @@ impl VMObjectPagedInner {
|
||||||
child = locked_parent.self_ref.clone();
|
child = locked_parent.self_ref.clone();
|
||||||
option_parent = locked_parent.parent.clone();
|
option_parent = locked_parent.parent.clone();
|
||||||
drop(locked_parent);
|
drop(locked_parent);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&mut self, new_size: usize) {
|
fn resize(&mut self, new_size: usize) {
|
||||||
if new_size < self.size {
|
if new_size == 0 && new_size < self.size {
|
||||||
|
self.frames.clear();
|
||||||
|
if let Some(parent) = self.parent.as_ref() {
|
||||||
|
parent.inner.lock().remove_child(&self.self_ref);
|
||||||
|
self.parent = None;
|
||||||
|
}
|
||||||
|
} else if new_size < self.size {
|
||||||
let mut unwanted = VecDeque::<usize>::new();
|
let mut unwanted = VecDeque::<usize>::new();
|
||||||
let parent_end = (self.parent_limit - self.parent_offset) / PAGE_SIZE;
|
let parent_end = (self.parent_limit - self.parent_offset) / PAGE_SIZE;
|
||||||
for i in new_size / PAGE_SIZE..self.size / PAGE_SIZE {
|
for i in new_size / PAGE_SIZE..self.size / PAGE_SIZE {
|
||||||
if self.frames.remove(&i).is_none() && parent_end > i {
|
if parent_end > i {
|
||||||
unwanted.push_back(i);
|
unwanted.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,8 @@ impl VMObjectPhysicalInner {
|
||||||
|
|
||||||
impl VMObjectPhysical {
|
impl VMObjectPhysical {
|
||||||
/// Create a new VMO representing a piece of contiguous physical memory.
|
/// Create a new VMO representing a piece of contiguous physical memory.
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// You must ensure nobody has the ownership of this piece of memory yet.
|
/// You must ensure nobody has the ownership of this piece of memory yet.
|
||||||
#[allow(unsafe_code)]
|
pub fn new(paddr: PhysAddr, pages: usize) -> Arc<Self> {
|
||||||
pub unsafe fn new(paddr: PhysAddr, pages: usize) -> Arc<Self> {
|
|
||||||
assert!(page_aligned(paddr));
|
assert!(page_aligned(paddr));
|
||||||
Arc::new(VMObjectPhysical {
|
Arc::new(VMObjectPhysical {
|
||||||
paddr,
|
paddr,
|
||||||
|
@ -91,7 +87,6 @@ impl VMObjectTrait for VMObjectPhysical {
|
||||||
Err(ZxError::NOT_SUPPORTED)
|
Err(ZxError::NOT_SUPPORTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
fn create_slice(
|
fn create_slice(
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
_id: KoID,
|
_id: KoID,
|
||||||
|
@ -99,7 +94,7 @@ impl VMObjectTrait for VMObjectPhysical {
|
||||||
len: usize,
|
len: usize,
|
||||||
) -> ZxResult<Arc<dyn VMObjectTrait>> {
|
) -> ZxResult<Arc<dyn VMObjectTrait>> {
|
||||||
assert!(page_aligned(offset) && page_aligned(len));
|
assert!(page_aligned(offset) && page_aligned(len));
|
||||||
let obj = unsafe { VMObjectPhysical::new(self.paddr + offset, len / PAGE_SIZE) };
|
let obj = VMObjectPhysical::new(self.paddr + offset, len / PAGE_SIZE);
|
||||||
obj.inner.lock().cache_policy = self.inner.lock().cache_policy;
|
obj.inner.lock().cache_policy = self.inner.lock().cache_policy;
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
@ -116,8 +111,8 @@ impl VMObjectTrait for VMObjectPhysical {
|
||||||
inner.mapping_count -= 1;
|
inner.mapping_count -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_info(&self, _info: &mut ZxInfoVmo) {
|
fn complete_info(&self, _info: &mut VmoInfo) {
|
||||||
unimplemented!()
|
warn!("VmoInfo for physical is unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cache_policy(&self) -> CachePolicy {
|
fn get_cache_policy(&self) -> CachePolicy {
|
||||||
|
@ -140,10 +135,14 @@ impl VMObjectTrait for VMObjectPhysical {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn share_count(&self) -> usize {
|
fn share_count(&self) -> usize {
|
||||||
unimplemented!()
|
self.inner.lock().mapping_count as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn committed_pages_in_range(&self, _start_idx: usize, _end_idx: usize) -> usize {
|
fn committed_pages_in_range(&self, _start_idx: usize, _end_idx: usize) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zero(&self, _offset: usize, _len: usize) -> ZxResult {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +159,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_write() {
|
fn read_write() {
|
||||||
let vmo = unsafe { VmObject::new_physical(0x1000, 2) };
|
let vmo = VmObject::new_physical(0x1000, 2);
|
||||||
let vmphy = vmo.inner.clone();
|
let vmphy = vmo.inner.clone();
|
||||||
assert_eq!(vmphy.get_cache_policy(), CachePolicy::Uncached);
|
assert_eq!(vmphy.get_cache_policy(), CachePolicy::Uncached);
|
||||||
super::super::tests::read_write(&vmo);
|
super::super::tests::read_write(&vmo);
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
use {
|
use {
|
||||||
super::*,
|
super::*,
|
||||||
zircon_object::{
|
|
||||||
dev::*,
|
|
||||||
resource::*,
|
|
||||||
},
|
|
||||||
bitflags::bitflags,
|
bitflags::bitflags,
|
||||||
kernel_hal::DevVAddr,
|
kernel_hal::DevVAddr,
|
||||||
zircon_object::vm::{page_aligned, VmObject},
|
zircon_object::vm::{page_aligned, VmObject},
|
||||||
|
zircon_object::{dev::*, resource::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Syscall<'_> {
|
impl Syscall<'_> {
|
||||||
|
@ -23,7 +20,8 @@ impl Syscall<'_> {
|
||||||
resource, type_, desc, desc_size, out
|
resource, type_, desc, desc_size, out
|
||||||
);
|
);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
proc.validate_resource(resource, ResourceKind::ROOT)?;
|
proc.get_object::<Resource>(resource)?
|
||||||
|
.validate(ResourceKind::ROOT)?;
|
||||||
if desc_size > IOMMU_MAX_DESC_LEN {
|
if desc_size > IOMMU_MAX_DESC_LEN {
|
||||||
return Err(ZxError::INVALID_ARGS);
|
return Err(ZxError::INVALID_ARGS);
|
||||||
}
|
}
|
||||||
|
@ -91,7 +89,6 @@ impl Syscall<'_> {
|
||||||
|
|
||||||
let mut iommu_perms = IommuPerms::empty();
|
let mut iommu_perms = IommuPerms::empty();
|
||||||
let options = BtiOptions::from_bits_truncate(options);
|
let options = BtiOptions::from_bits_truncate(options);
|
||||||
|
|
||||||
if options.contains(BtiOptions::PERM_READ) {
|
if options.contains(BtiOptions::PERM_READ) {
|
||||||
if !rights.contains(Rights::READ) {
|
if !rights.contains(Rights::READ) {
|
||||||
return Err(ZxError::ACCESS_DENIED);
|
return Err(ZxError::ACCESS_DENIED);
|
||||||
|
@ -115,7 +112,6 @@ impl Syscall<'_> {
|
||||||
}
|
}
|
||||||
iommu_perms.insert(IommuPerms::PERM_EXECUTE);
|
iommu_perms.insert(IommuPerms::PERM_EXECUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.contains(BtiOptions::CONTIGUOUS) && options.contains(BtiOptions::COMPRESS) {
|
if options.contains(BtiOptions::CONTIGUOUS) && options.contains(BtiOptions::COMPRESS) {
|
||||||
return Err(ZxError::INVALID_ARGS);
|
return Err(ZxError::INVALID_ARGS);
|
||||||
}
|
}
|
||||||
|
@ -127,8 +123,11 @@ impl Syscall<'_> {
|
||||||
let pmt = bti.pin(vmo, offset, size, iommu_perms)?;
|
let pmt = bti.pin(vmo, offset, size, iommu_perms)?;
|
||||||
let encoded_addrs = pmt.as_ref().encode_addrs(compress_results, contiguous)?;
|
let encoded_addrs = pmt.as_ref().encode_addrs(compress_results, contiguous)?;
|
||||||
if encoded_addrs.len() != addrs_count {
|
if encoded_addrs.len() != addrs_count {
|
||||||
warn!("bti.pin addrs_count = {}, but encoded_addrs.len = {}",
|
warn!(
|
||||||
addrs_count, encoded_addrs.len());
|
"bti.pin addrs_count = {}, but encoded_addrs.len = {}",
|
||||||
|
addrs_count,
|
||||||
|
encoded_addrs.len()
|
||||||
|
);
|
||||||
return Err(ZxError::INVALID_ARGS);
|
return Err(ZxError::INVALID_ARGS);
|
||||||
}
|
}
|
||||||
addrs.write_array(&encoded_addrs)?;
|
addrs.write_array(&encoded_addrs)?;
|
||||||
|
@ -137,26 +136,35 @@ impl Syscall<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sys_pmt_unpin(
|
pub fn sys_pmt_unpin(&self, pmt: HandleValue) -> ZxResult {
|
||||||
&self,
|
|
||||||
pmt: HandleValue,
|
|
||||||
) -> ZxResult {
|
|
||||||
info!("pmt.unpin: pmt={:#x}", pmt);
|
info!("pmt.unpin: pmt={:#x}", pmt);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
let pmt = proc.remove_object::<Pmt>(pmt)?;
|
let pmt = proc.remove_object::<Pmt>(pmt)?;
|
||||||
pmt.as_ref().unpin_and_remove()
|
pmt.as_ref().unpin_and_remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sys_bti_release_quarantine(
|
pub fn sys_bti_release_quarantine(&self, bti: HandleValue) -> ZxResult {
|
||||||
&self,
|
|
||||||
bti: HandleValue,
|
|
||||||
) -> ZxResult {
|
|
||||||
info!("bti.release_quarantine: bti = {:#x}", bti);
|
info!("bti.release_quarantine: bti = {:#x}", bti);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
let bti = proc.get_object_with_rights::<Bti>(bti, Rights::WRITE)?;
|
let bti = proc.get_object_with_rights::<Bti>(bti, Rights::WRITE)?;
|
||||||
bti.release_quarantine();
|
bti.release_quarantine();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn sys_pc_firmware_tables(
|
||||||
|
&self,
|
||||||
|
resource: HandleValue,
|
||||||
|
mut acpi_rsdp_ptr: UserOutPtr<u64>,
|
||||||
|
mut smbios_ptr: UserOutPtr<u64>,
|
||||||
|
) -> ZxResult {
|
||||||
|
info!("pc_firmware_tables: handle={:?}", resource);
|
||||||
|
let proc = self.thread.proc();
|
||||||
|
proc.get_object::<Resource>(resource)?
|
||||||
|
.validate(ResourceKind::ROOT)?;
|
||||||
|
let (acpi_rsdp, smbios) = kernel_hal::pc_firmware_tables();
|
||||||
|
acpi_rsdp_ptr.write(acpi_rsdp)?;
|
||||||
|
smbios_ptr.write(smbios)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IOMMU_MAX_DESC_LEN: usize = 4096;
|
const IOMMU_MAX_DESC_LEN: usize = 4096;
|
||||||
|
@ -172,4 +180,3 @@ bitflags! {
|
||||||
const CONTIGUOUS = 1 << 4;
|
const CONTIGUOUS = 1 << 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use zircon_object::resource::ResourceKind;
|
use zircon_object::resource::*;
|
||||||
|
|
||||||
impl Syscall<'_> {
|
impl Syscall<'_> {
|
||||||
pub fn sys_debug_write(&self, buf: UserInPtr<u8>, len: usize) -> ZxResult {
|
pub fn sys_debug_write(&self, buf: UserInPtr<u8>, len: usize) -> ZxResult {
|
||||||
|
@ -21,7 +21,8 @@ impl Syscall<'_> {
|
||||||
handle, buf, buf_size
|
handle, buf, buf_size
|
||||||
);
|
);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
proc.validate_resource(handle, ResourceKind::ROOT)?;
|
proc.get_object::<Resource>(handle)?
|
||||||
|
.validate(ResourceKind::ROOT)?;
|
||||||
// FIXME: To make 'console' work, now debug_read is a blocking call.
|
// FIXME: To make 'console' work, now debug_read is a blocking call.
|
||||||
// But it should be non-blocking.
|
// But it should be non-blocking.
|
||||||
// let mut vec = vec![0u8; buf_size as usize];
|
// let mut vec = vec![0u8; buf_size as usize];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use {
|
use {
|
||||||
super::*,
|
super::*,
|
||||||
zircon_object::{debuglog::DebugLog, resource::ResourceKind},
|
zircon_object::{debuglog::DebugLog, resource::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
const FLAG_READABLE: u32 = 0x4000_0000u32;
|
const FLAG_READABLE: u32 = 0x4000_0000u32;
|
||||||
|
@ -18,7 +18,8 @@ impl Syscall<'_> {
|
||||||
);
|
);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
if rsrc != 0 {
|
if rsrc != 0 {
|
||||||
proc.validate_resource(rsrc, ResourceKind::ROOT)?;
|
proc.get_object::<Resource>(rsrc)?
|
||||||
|
.validate(ResourceKind::ROOT)?;
|
||||||
}
|
}
|
||||||
let dlog = DebugLog::create(options);
|
let dlog = DebugLog::create(options);
|
||||||
let dlog_right = if options & FLAG_READABLE == 0 {
|
let dlog_right = if options & FLAG_READABLE == 0 {
|
||||||
|
|
|
@ -248,6 +248,7 @@ impl Syscall<'_> {
|
||||||
Sys::OBJECT_GET_CHILD => {
|
Sys::OBJECT_GET_CHILD => {
|
||||||
self.sys_object_get_child(a0 as _, a1 as _, a2 as _, a3.into())
|
self.sys_object_get_child(a0 as _, a1 as _, a2 as _, a3.into())
|
||||||
}
|
}
|
||||||
|
Sys::PC_FIRMWARE_TABLES => self.sys_pc_firmware_tables(a0 as _, a1.into(), a2.into()),
|
||||||
_ => {
|
_ => {
|
||||||
error!("syscall unimplemented: {:?}", sys_type);
|
error!("syscall unimplemented: {:?}", sys_type);
|
||||||
Err(ZxError::NOT_SUPPORTED)
|
Err(ZxError::NOT_SUPPORTED)
|
||||||
|
|
|
@ -134,17 +134,11 @@ impl Syscall<'_> {
|
||||||
deadline: Deadline,
|
deadline: Deadline,
|
||||||
mut observed: UserOutPtr<Signal>,
|
mut observed: UserOutPtr<Signal>,
|
||||||
) -> ZxResult {
|
) -> ZxResult {
|
||||||
|
let signals = Signal::from_bits_truncate(signals);
|
||||||
info!(
|
info!(
|
||||||
"object.wait_one: handle={:#x?}, signals={:#x?}, deadline={:#x?}, observed={:#x?}",
|
"object.wait_one: handle={:#x?}, signals={:#x?}, deadline={:#x?}, observed={:#x?}",
|
||||||
handle, signals, deadline, observed
|
handle, signals, deadline, observed
|
||||||
);
|
);
|
||||||
let signals = Signal::from_bits(signals).ok_or_else(|| {
|
|
||||||
if !deadline.is_positive() {
|
|
||||||
ZxError::TIMED_OUT
|
|
||||||
} else {
|
|
||||||
ZxError::INVALID_ARGS
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
let object = proc.get_dyn_object_with_rights(handle, Rights::WAIT)?;
|
let object = proc.get_dyn_object_with_rights(handle, Rights::WAIT)?;
|
||||||
let cancel_token = proc.get_cancel_token(handle)?;
|
let cancel_token = proc.get_cancel_token(handle)?;
|
||||||
|
@ -168,7 +162,6 @@ impl Syscall<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
pub fn sys_object_get_info(
|
pub fn sys_object_get_info(
|
||||||
&self,
|
&self,
|
||||||
handle: HandleValue,
|
handle: HandleValue,
|
||||||
|
@ -224,75 +217,39 @@ impl Syscall<'_> {
|
||||||
let mut info = vmo.get_info();
|
let mut info = vmo.get_info();
|
||||||
info.flags |= VmoInfoFlags::VIA_HANDLE;
|
info.flags |= VmoInfoFlags::VIA_HANDLE;
|
||||||
info.rights |= rights;
|
info.rights |= rights;
|
||||||
UserOutPtr::<ZxInfoVmo>::from(buffer).write(info)?;
|
UserOutPtr::<VmoInfo>::from(buffer).write(info)?;
|
||||||
}
|
}
|
||||||
Topic::KmemStats => {
|
Topic::KmemStats => {
|
||||||
let mut kmem = ZxInfoKmem::default();
|
let mut kmem = KmemInfo::default();
|
||||||
kmem.vmo_bytes = vmo_page_bytes() as u64;
|
kmem.vmo_bytes = vmo_page_bytes() as u64;
|
||||||
UserOutPtr::<ZxInfoKmem>::from(buffer).write(kmem)?;
|
UserOutPtr::<KmemInfo>::from(buffer).write(kmem)?;
|
||||||
}
|
|
||||||
Topic::JobProcess => {
|
|
||||||
let job = proc.get_object_with_rights::<Job>(handle, Rights::ENUMERATE)?;
|
|
||||||
let (mut count, mut avail_count) = (0usize, 0usize);
|
|
||||||
let ptr = UserOutPtr::<KoID>::from(buffer).as_ptr();
|
|
||||||
let item_size = core::mem::size_of::<KoID>();
|
|
||||||
job.enumerate_process(|id| {
|
|
||||||
if count < buffer_size / item_size {
|
|
||||||
unsafe {
|
|
||||||
ptr.add(count).write(id);
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
avail_count += 1;
|
|
||||||
true
|
|
||||||
});
|
|
||||||
actual.write(count)?;
|
|
||||||
avail.write(avail_count)?;
|
|
||||||
}
|
}
|
||||||
Topic::TaskStats => {
|
Topic::TaskStats => {
|
||||||
assert_eq!(core::mem::size_of::<ZxInfoTaskStats>(), buffer_size);
|
assert_eq!(core::mem::size_of::<TaskStatsInfo>(), buffer_size);
|
||||||
let vmar = proc
|
let vmar = proc
|
||||||
.get_object_with_rights::<Process>(handle, Rights::INSPECT)?
|
.get_object_with_rights::<Process>(handle, Rights::INSPECT)?
|
||||||
.vmar();
|
.vmar();
|
||||||
//let mut task_stats = ZxInfoTaskStats::default();
|
//let mut task_stats = ZxInfoTaskStats::default();
|
||||||
let task_stats = vmar.get_task_stats();
|
let task_stats = vmar.get_task_stats();
|
||||||
UserOutPtr::<ZxInfoTaskStats>::from(buffer).write(task_stats)?;
|
UserOutPtr::<TaskStatsInfo>::from(buffer).write(task_stats)?;
|
||||||
}
|
}
|
||||||
Topic::ProcessThreads => {
|
Topic::JobChildren | Topic::JobProcess | Topic::ProcessThreads => {
|
||||||
let (mut count, mut avail_count) = (0usize, 0usize);
|
let ids = match topic {
|
||||||
let ptr = UserOutPtr::<KoID>::from(buffer).as_ptr();
|
Topic::JobChildren => proc
|
||||||
let item_size = core::mem::size_of::<KoID>();
|
.get_object_with_rights::<Job>(handle, Rights::ENUMERATE)?
|
||||||
proc.get_object_with_rights::<Process>(handle, Rights::ENUMERATE)?
|
.children_ids(),
|
||||||
.enumerate_thread(|id| {
|
Topic::JobProcess => proc
|
||||||
if count < buffer_size / item_size {
|
.get_object_with_rights::<Job>(handle, Rights::ENUMERATE)?
|
||||||
unsafe {
|
.process_ids(),
|
||||||
ptr.add(count).write(id);
|
Topic::ProcessThreads => proc
|
||||||
}
|
.get_object_with_rights::<Process>(handle, Rights::ENUMERATE)?
|
||||||
count += 1;
|
.thread_ids(),
|
||||||
}
|
_ => unreachable!(),
|
||||||
avail_count += 1;
|
};
|
||||||
true
|
let count = (buffer_size / core::mem::size_of::<KoID>()).min(ids.len());
|
||||||
});
|
UserOutPtr::<KoID>::from(buffer).write_array(&ids[..count])?;
|
||||||
actual.write(count)?;
|
actual.write(count)?;
|
||||||
avail.write(avail_count)?;
|
avail.write(ids.len())?;
|
||||||
}
|
|
||||||
Topic::JobChildren => {
|
|
||||||
let (mut count, mut avail_count) = (0usize, 0usize);
|
|
||||||
let ptr = UserOutPtr::<KoID>::from(buffer).as_ptr();
|
|
||||||
let item_size = core::mem::size_of::<KoID>();
|
|
||||||
proc.get_object_with_rights::<Job>(handle, Rights::ENUMERATE)?
|
|
||||||
.enumerate_children(|id| {
|
|
||||||
if count < buffer_size / item_size {
|
|
||||||
unsafe {
|
|
||||||
ptr.add(count).write(id);
|
|
||||||
}
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
avail_count += 1;
|
|
||||||
true
|
|
||||||
});
|
|
||||||
actual.write(count)?;
|
|
||||||
avail.write(avail_count)?;
|
|
||||||
}
|
}
|
||||||
Topic::Bti => {
|
Topic::Bti => {
|
||||||
let bti = proc.get_object_with_rights::<Bti>(handle, Rights::INSPECT)?;
|
let bti = proc.get_object_with_rights::<Bti>(handle, Rights::INSPECT)?;
|
||||||
|
@ -333,7 +290,7 @@ impl Syscall<'_> {
|
||||||
signals: u32,
|
signals: u32,
|
||||||
options: u32,
|
options: u32,
|
||||||
) -> ZxResult {
|
) -> ZxResult {
|
||||||
let signals = Signal::from_bits(signals).ok_or(ZxError::INVALID_ARGS)?;
|
let signals = Signal::from_bits_truncate(signals);
|
||||||
info!(
|
info!(
|
||||||
"object.wait_async: handle={:#x}, port={:#x}, key={:#x}, signal={:?}, options={:#X}",
|
"object.wait_async: handle={:#x}, port={:#x}, key={:#x}, signal={:?}, options={:#X}",
|
||||||
handle_value, port_handle_value, key, signals, options
|
handle_value, port_handle_value, key, signals, options
|
||||||
|
@ -484,7 +441,7 @@ pub struct UserWaitItem {
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ZxInfoKmem {
|
struct KmemInfo {
|
||||||
total_bytes: u64,
|
total_bytes: u64,
|
||||||
free_bytes: u64,
|
free_bytes: u64,
|
||||||
wired_bytes: u64,
|
wired_bytes: u64,
|
||||||
|
|
|
@ -32,14 +32,14 @@ impl Syscall<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sys_clock_adjust(&self, hrsrc: HandleValue, clock_id: u32, offset: u64) -> ZxResult {
|
pub fn sys_clock_adjust(&self, resource: HandleValue, clock_id: u32, offset: u64) -> ZxResult {
|
||||||
info!(
|
info!(
|
||||||
"clock.adjust: hrsrc={:#x?}, id={:#x}, offset={:#x}",
|
"clock.adjust: resource={:#x?}, id={:#x}, offset={:#x}",
|
||||||
hrsrc, clock_id, offset
|
resource, clock_id, offset
|
||||||
);
|
);
|
||||||
self.thread
|
let proc = self.thread.proc();
|
||||||
.proc()
|
proc.get_object::<Resource>(resource)?
|
||||||
.validate_resource(hrsrc, ResourceKind::ROOT)?;
|
.validate(ResourceKind::ROOT)?;
|
||||||
match clock_id {
|
match clock_id {
|
||||||
ZX_CLOCK_MONOTONIC => Err(ZxError::ACCESS_DENIED),
|
ZX_CLOCK_MONOTONIC => Err(ZxError::ACCESS_DENIED),
|
||||||
ZX_CLOCK_UTC => {
|
ZX_CLOCK_UTC => {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use {
|
||||||
super::*,
|
super::*,
|
||||||
bitflags::bitflags,
|
bitflags::bitflags,
|
||||||
kernel_hal::CachePolicy,
|
kernel_hal::CachePolicy,
|
||||||
|
numeric_enum_macro::numeric_enum,
|
||||||
zircon_object::{dev::*, resource::*, task::PolicyCondition, vm::*},
|
zircon_object::{dev::*, resource::*, task::PolicyCondition, vm::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,7 +84,8 @@ impl Syscall<'_> {
|
||||||
);
|
);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
if vmex != INVALID_HANDLE {
|
if vmex != INVALID_HANDLE {
|
||||||
proc.validate_resource(vmex, ResourceKind::VMEX)?;
|
proc.get_object::<Resource>(vmex)?
|
||||||
|
.validate(ResourceKind::VMEX)?;
|
||||||
} else {
|
} else {
|
||||||
proc.check_policy(PolicyCondition::AmbientMarkVMOExec)?;
|
proc.check_policy(PolicyCondition::AmbientMarkVMOExec)?;
|
||||||
}
|
}
|
||||||
|
@ -166,21 +168,21 @@ impl Syscall<'_> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
pub fn sys_vmo_create_physical(
|
pub fn sys_vmo_create_physical(
|
||||||
&self,
|
&self,
|
||||||
rsrc: HandleValue,
|
resource: HandleValue,
|
||||||
paddr: PhysAddr,
|
paddr: PhysAddr,
|
||||||
size: usize,
|
size: usize,
|
||||||
mut out: UserOutPtr<HandleValue>,
|
mut out: UserOutPtr<HandleValue>,
|
||||||
) -> ZxResult {
|
) -> ZxResult {
|
||||||
info!(
|
info!(
|
||||||
"vmo.create_physical: handle={:#x?}, paddr={:#x?}, size={:#x}, out={:#x?}",
|
"vmo.create_physical: handle={:#x?}, paddr={:#x?}, size={:#x}, out={:#x?}",
|
||||||
size, paddr, size, out
|
resource, paddr, size, out
|
||||||
);
|
);
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
proc.check_policy(PolicyCondition::NewVMO)?;
|
proc.check_policy(PolicyCondition::NewVMO)?;
|
||||||
proc.validate_resource(rsrc, ResourceKind::MMIO)?;
|
proc.get_object::<Resource>(resource)?
|
||||||
|
.validate_ranged_resource(ResourceKind::MMIO, paddr, size)?;
|
||||||
let size = roundup_pages(size);
|
let size = roundup_pages(size);
|
||||||
if size == 0 || !page_aligned(paddr) {
|
if size == 0 || !page_aligned(paddr) {
|
||||||
return Err(ZxError::INVALID_ARGS);
|
return Err(ZxError::INVALID_ARGS);
|
||||||
|
@ -188,7 +190,7 @@ impl Syscall<'_> {
|
||||||
if paddr.overflowing_add(size).1 {
|
if paddr.overflowing_add(size).1 {
|
||||||
return Err(ZxError::INVALID_ARGS);
|
return Err(ZxError::INVALID_ARGS);
|
||||||
}
|
}
|
||||||
let vmo = unsafe { VmObject::new_physical(paddr, size / PAGE_SIZE) };
|
let vmo = VmObject::new_physical(paddr, size / PAGE_SIZE);
|
||||||
let handle_value = proc.add_handle(Handle::new(vmo, Rights::DEFAULT_VMO | Rights::EXECUTE));
|
let handle_value = proc.add_handle(Handle::new(vmo, Rights::DEFAULT_VMO | Rights::EXECUTE));
|
||||||
out.write(handle_value)?;
|
out.write(handle_value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -250,25 +252,35 @@ impl Syscall<'_> {
|
||||||
"vmo.op_range: handle={:#x}, op={:#X}, offset={:#x}, len={:#x}, buffer_size={:#x}",
|
"vmo.op_range: handle={:#x}, op={:#X}, offset={:#x}, len={:#x}, buffer_size={:#x}",
|
||||||
handle_value, op, offset, len, _buffer_size,
|
handle_value, op, offset, len, _buffer_size,
|
||||||
);
|
);
|
||||||
|
let op = VmoOpType::try_from(op).or(Err(ZxError::INVALID_ARGS))?;
|
||||||
let proc = self.thread.proc();
|
let proc = self.thread.proc();
|
||||||
let (vmo, rights) = proc.get_object_and_rights::<VmObject>(handle_value)?;
|
let (vmo, rights) = proc.get_object_and_rights::<VmObject>(handle_value)?;
|
||||||
if !page_aligned(offset) || !page_aligned(len) {
|
|
||||||
return Err(ZxError::INVALID_ARGS);
|
|
||||||
}
|
|
||||||
match op {
|
match op {
|
||||||
VMO_OP_COMMIT => {
|
VmoOpType::Commit => {
|
||||||
if !rights.contains(Rights::WRITE) {
|
if !rights.contains(Rights::WRITE) {
|
||||||
return Err(ZxError::ACCESS_DENIED);
|
return Err(ZxError::ACCESS_DENIED);
|
||||||
}
|
}
|
||||||
|
if !page_aligned(offset) || !page_aligned(len) {
|
||||||
|
return Err(ZxError::INVALID_ARGS);
|
||||||
|
}
|
||||||
vmo.commit(offset, len)?;
|
vmo.commit(offset, len)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
VMO_OP_DECOMMIT => {
|
VmoOpType::Decommit => {
|
||||||
if !rights.contains(Rights::WRITE) {
|
if !rights.contains(Rights::WRITE) {
|
||||||
return Err(ZxError::ACCESS_DENIED);
|
return Err(ZxError::ACCESS_DENIED);
|
||||||
}
|
}
|
||||||
|
if !page_aligned(offset) || !page_aligned(len) {
|
||||||
|
return Err(ZxError::INVALID_ARGS);
|
||||||
|
}
|
||||||
vmo.decommit(offset, len)
|
vmo.decommit(offset, len)
|
||||||
}
|
}
|
||||||
|
VmoOpType::Zero => {
|
||||||
|
if !rights.contains(Rights::WRITE) {
|
||||||
|
return Err(ZxError::ACCESS_DENIED);
|
||||||
|
}
|
||||||
|
vmo.zero(offset, len)
|
||||||
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,6 +304,18 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
numeric_enum! {
|
||||||
|
#[repr(u32)]
|
||||||
/// VMO Opcodes (for vmo_op_range)
|
/// VMO Opcodes (for vmo_op_range)
|
||||||
const VMO_OP_COMMIT: u32 = 1;
|
pub enum VmoOpType {
|
||||||
const VMO_OP_DECOMMIT: u32 = 2;
|
Commit = 1,
|
||||||
|
Decommit = 2,
|
||||||
|
Lock = 3,
|
||||||
|
Unlock = 4,
|
||||||
|
CacheSync = 6,
|
||||||
|
CacheInvalidate = 7,
|
||||||
|
CacheClean = 8,
|
||||||
|
CacheCleanInvalidate = 9,
|
||||||
|
Zero = 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue