blob: 2bdced7189f1fd3f8c05ac2f3ba2ea0ca9a53b63 [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use crate::counters::define_kcounter;
use crate::kernel::deadline::{Deadline, InstantUnknown};
use crate::object::{
ChannelDispatcher, HandleValue, MessagePacket, MessagePacketPtr, ProcessDispatcher,
ReadHandles, ThreadDispatcher, WriteHandles,
};
use crate::user_copy::{UserInOutPtr, UserInPtr, UserOutPtr};
use core::mem::{align_of, size_of};
use core::ptr;
use debug::ltracef;
use syscalls_macro::syscall;
use zerocopy::{FromBytes, Immutable, IntoBytes};
use zr::static_assert_size_and_align;
use zx_status::Status;
use zx_types::{
ZX_CHANNEL_READ_MAY_DISCARD, ZX_CHANNEL_WRITE_USE_IOVEC, ZX_POL_NEW_CHANNEL,
zx_channel_call_args_t, zx_channel_call_etc_args_t, zx_channel_iovec_t,
zx_handle_disposition_t, zx_handle_info_t, zx_handle_t, zx_instant_mono_t, zx_txid_t,
};
const LOCAL_TRACE: u32 = 0;
define_kcounter!(CHANNEL_MSG_0_BYTES, "channel.bytes.0", Sum);
define_kcounter!(CHANNEL_MSG_64_BYTES, "channel.bytes.64", Sum);
define_kcounter!(CHANNEL_MSG_256_BYTES, "channel.bytes.256", Sum);
define_kcounter!(CHANNEL_MSG_1K_BYTES, "channel.bytes.1k", Sum);
define_kcounter!(CHANNEL_MSG_4K_BYTES, "channel.bytes.4k", Sum);
define_kcounter!(CHANNEL_MSG_16K_BYTES, "channel.bytes.16k", Sum);
define_kcounter!(CHANNEL_MSG_64K_BYTES, "channel.bytes.64k", Sum);
define_kcounter!(CHANNEL_MSG_0_HANDLES, "channel.handles.00", Sum);
define_kcounter!(CHANNEL_MSG_1_HANDLES, "channel.handles.01", Sum);
define_kcounter!(CHANNEL_MSG_2_HANDLES, "channel.handles.02", Sum);
define_kcounter!(CHANNEL_MSG_4_HANDLES, "channel.handles.04", Sum);
define_kcounter!(CHANNEL_MSG_8_HANDLES, "channel.handles.08", Sum);
define_kcounter!(CHANNEL_MSG_16_HANDLES, "channel.handles.16", Sum);
define_kcounter!(CHANNEL_MSG_32_HANDLES, "channel.handles.32", Sum);
define_kcounter!(CHANNEL_MSG_64_HANDLES, "channel.handles.64", Sum);
define_kcounter!(CHANNEL_MSG_RECEIVED, "channel.messages", Sum);
#[inline]
fn record_recv_msg_sz(num_bytes: u32, num_handles: u32) {
CHANNEL_MSG_RECEIVED.add(1);
match num_bytes {
0 => CHANNEL_MSG_0_BYTES.add(1),
1..=64 => CHANNEL_MSG_64_BYTES.add(1),
65..=256 => CHANNEL_MSG_256_BYTES.add(1),
257..=1024 => CHANNEL_MSG_1K_BYTES.add(1),
1025..=4096 => CHANNEL_MSG_4K_BYTES.add(1),
4097..=16384 => CHANNEL_MSG_16K_BYTES.add(1),
16385..=65536 => CHANNEL_MSG_64K_BYTES.add(1),
_ => {}
}
match num_handles {
0 => CHANNEL_MSG_0_HANDLES.add(1),
1 => CHANNEL_MSG_1_HANDLES.add(1),
2 => CHANNEL_MSG_2_HANDLES.add(1),
3..=4 => CHANNEL_MSG_4_HANDLES.add(1),
5..=8 => CHANNEL_MSG_8_HANDLES.add(1),
9..=16 => CHANNEL_MSG_16_HANDLES.add(1),
17..=32 => CHANNEL_MSG_32_HANDLES.add(1),
33..=64 => CHANNEL_MSG_64_HANDLES.add(1),
_ => {}
}
}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, IntoBytes, Immutable)]
struct RawChannelCallArgs {
wr_bytes: usize,
wr_handles: usize,
rd_bytes: usize,
rd_handles: usize,
wr_num_bytes: u32,
wr_num_handles: u32,
rd_num_bytes: u32,
rd_num_handles: u32,
}
impl RawChannelCallArgs {
#[inline]
fn wr_bytes(&self) -> UserInPtr<u8> {
UserInPtr::new(ptr::with_exposed_provenance(self.wr_bytes))
}
#[inline]
fn rd_bytes(&self) -> UserOutPtr<u8> {
UserOutPtr::new(ptr::with_exposed_provenance_mut(self.rd_bytes))
}
#[inline]
fn wr_handles(&self) -> UserInPtr<zx_handle_t> {
UserInPtr::new(ptr::with_exposed_provenance(self.wr_handles))
}
#[inline]
fn rd_handles(&self) -> UserOutPtr<zx_handle_t> {
UserOutPtr::new(ptr::with_exposed_provenance_mut(self.rd_handles))
}
#[inline]
fn wr_handle_dispositions(&self) -> UserInOutPtr<zx_handle_disposition_t> {
UserInOutPtr::new(ptr::with_exposed_provenance_mut(self.wr_handles))
}
#[inline]
fn rd_handle_infos(&self) -> UserOutPtr<zx_handle_info_t> {
UserOutPtr::new(ptr::with_exposed_provenance_mut(self.rd_handles))
}
}
static_assert_size_and_align!(
RawChannelCallArgs,
size_of::<zx_channel_call_args_t>(),
align_of::<zx_channel_call_args_t>()
);
static_assert_size_and_align!(
RawChannelCallArgs,
size_of::<zx_channel_call_etc_args_t>(),
align_of::<zx_channel_call_etc_args_t>()
);
#[syscall]
pub fn sys_channel_create(
options: u32,
out0: &mut HandleValue,
out1: &mut HandleValue,
) -> Result<(), Status> {
if options != 0 {
return Err(Status::INVALID_ARGS);
}
let proc = ProcessDispatcher::get_current();
proc.enforce_basic_policy(ZX_POL_NEW_CHANNEL)?;
let (handle0, handle1, rights) = ChannelDispatcher::create()?;
let user_handle0 = proc.make_and_add_handle(handle0, rights)?;
let user_handle1 = proc.make_and_add_handle(handle1, rights)?;
*out0 = user_handle0;
*out1 = user_handle1;
Ok(())
}
#[inline]
fn copy_out_message<R: ReadHandles>(
proc: &ProcessDispatcher,
mut msg: MessagePacketPtr,
bytes: UserOutPtr<u8>,
handles: R,
) -> Result<(), Status> {
let num_bytes = msg.data_size() as u32;
let num_handles = msg.num_handles() as u32;
if num_bytes > 0 {
msg.copy_data_to(bytes)?;
}
// The documented public API states that writing to the handles buffer must happen after writing
// to the data buffer.
if num_handles > 0 {
handles.get_handles(proc, &mut msg)?;
}
record_recv_msg_sz(num_bytes, num_handles);
Ok(())
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn channel_read<R: ReadHandles>(
handle_value: HandleValue,
options: u32,
bytes: UserOutPtr<u8>,
handles: R,
mut num_bytes: u32,
mut num_handles: u32,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
ltracef!(
"handle {:#x} bytes {:?} num_bytes {} num_handles {} actual_bytes {:?} actual_handles {:?}\n",
handle_value.raw_value(),
bytes.as_ptr(),
num_bytes,
num_handles,
actual_bytes.as_ptr(),
actual_handles.as_ptr()
);
let proc = ProcessDispatcher::get_current();
let handle_table = proc.handle_table();
let channel = handle_table.get_dispatcher_with_rights::<ChannelDispatcher>(
&proc,
handle_value,
zx_types::ZX_RIGHT_READ,
)?;
// Currently MAY_DISCARD is the only allowable option.
if (options & !ZX_CHANNEL_READ_MAY_DISCARD) != 0 {
return Err(Status::NOT_SUPPORTED);
}
let read_result = channel.read(
handle_table.get_koid(),
&mut num_bytes,
&mut num_handles,
(options & ZX_CHANNEL_READ_MAY_DISCARD) != 0,
);
if let Err(err) = read_result
&& err != Status::BUFFER_TOO_SMALL
{
return Err(err);
}
// On ZX_ERR_BUFFER_TOO_SMALL, read() gives us the size of the next message (which remains
// unconsumed, unless `options` has ZX_CHANNEL_READ_MAY_DISCARD set).
if !actual_bytes.is_null() {
actual_bytes.write(num_bytes)?;
}
if !actual_handles.is_null() {
actual_handles.write(num_handles)?;
}
copy_out_message(&proc, read_result?, bytes, handles)
}
#[syscall]
pub fn sys_channel_read(
handle: HandleValue,
options: u32,
bytes: UserOutPtr<u8>,
handles: UserOutPtr<zx_handle_t>,
num_bytes: u32,
num_handles: u32,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
channel_read(
handle,
options,
bytes,
handles,
num_bytes,
num_handles,
actual_bytes,
actual_handles,
)
}
#[syscall]
pub fn sys_channel_read_etc(
handle: HandleValue,
options: u32,
bytes: UserOutPtr<u8>,
handles: UserOutPtr<zx_handle_info_t>,
num_bytes: u32,
num_handles: u32,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
channel_read(
handle,
options,
bytes,
handles,
num_bytes,
num_handles,
actual_bytes,
actual_handles,
)
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn prepare_write<W: WriteHandles>(
proc: &ProcessDispatcher,
handle: HandleValue,
rights: zx_types::zx_rights_t,
options: u32,
bytes: UserInPtr<u8>,
num_bytes: u32,
handles: W,
num_handles: u32,
min_data_size: usize,
) -> Result<(fbl::RefPtr<ChannelDispatcher>, zx_types::zx_koid_t, MessagePacketPtr), Status> {
let mut cleanup = zr::defer(|| {
if num_handles > 0 {
handles.remove_user_handles(proc, num_handles);
}
});
if (options & !ZX_CHANNEL_WRITE_USE_IOVEC) != 0 {
return Err(Status::INVALID_ARGS);
}
let handle_table = proc.handle_table();
let channel =
handle_table.get_dispatcher_with_rights::<ChannelDispatcher>(proc, handle, rights)?;
// Prepare a MessagePacket for writing.
let mut msg = if (options & ZX_CHANNEL_WRITE_USE_IOVEC) != 0 {
MessagePacket::create_from_iovecs(
bytes.reinterpret::<zx_channel_iovec_t>(),
num_bytes as usize,
num_handles as usize,
)?
} else {
MessagePacket::create_from_user(bytes, num_bytes as usize, num_handles as usize)?
};
if msg.data_size() < min_data_size {
return Err(Status::INVALID_ARGS);
}
// put_handles() always consumes all handles that should be consumed (or there are zero handles,
// and so there's nothing to be done).
cleanup.cancel();
if num_handles > 0 {
handles.put_handles(proc, &channel, &mut msg)?;
}
Ok((channel, handle_table.get_koid(), msg))
}
#[inline]
fn channel_write<W: WriteHandles>(
handle: HandleValue,
options: u32,
bytes: UserInPtr<u8>,
num_bytes: u32,
handles: W,
num_handles: u32,
) -> Result<(), Status> {
ltracef!(
"handle {:#x} bytes {:?} num_bytes {} num_handles {} options {:#x}\n",
handle.raw_value(),
bytes.as_ptr(),
num_bytes,
num_handles,
options
);
let proc = ProcessDispatcher::get_current();
let (channel, owner, msg) = prepare_write(
&proc,
handle,
zx_types::ZX_RIGHT_WRITE,
options,
bytes,
num_bytes,
handles,
num_handles,
0,
)?;
channel.write(owner, msg)
}
#[syscall]
pub fn sys_channel_write(
handle: HandleValue,
options: u32,
bytes: UserInPtr<u8>,
num_bytes: u32,
handles: UserInPtr<zx_handle_t>,
num_handles: u32,
) -> Result<(), Status> {
channel_write(handle, options, bytes, num_bytes, handles, num_handles)
}
#[syscall]
pub fn sys_channel_write_etc(
handle: HandleValue,
options: u32,
bytes: UserInPtr<u8>,
num_bytes: u32,
handles: UserInOutPtr<zx_handle_disposition_t>,
num_handles: u32,
) -> Result<(), Status> {
channel_write(handle, options, bytes, num_bytes, handles, num_handles)
}
#[inline]
fn channel_call_epilogue<R: ReadHandles>(
proc: &ProcessDispatcher,
reply: MessagePacketPtr,
args: &RawChannelCallArgs,
rd_handles: R,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let num_bytes = reply.data_size() as u32;
let num_handles = reply.num_handles() as u32;
if args.rd_num_bytes < num_bytes || args.rd_num_handles < num_handles {
return Err(Status::BUFFER_TOO_SMALL);
}
actual_bytes.write(num_bytes)?;
actual_handles.write(num_handles)?;
copy_out_message(proc, reply, args.rd_bytes(), rd_handles)
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn channel_call_noretry<W: WriteHandles, R: ReadHandles>(
handle: HandleValue,
options: u32,
deadline: zx_instant_mono_t,
args: RawChannelCallArgs,
wr_handles: W,
rd_handles: R,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let proc = ProcessDispatcher::get_current();
let (channel, owner, msg) = prepare_write(
&proc,
handle,
zx_types::ZX_RIGHT_WRITE | zx_types::ZX_RIGHT_READ,
options,
args.wr_bytes(),
args.wr_num_bytes,
wr_handles,
args.wr_num_handles,
size_of::<zx_txid_t>(),
)?;
let deadline = Deadline::new(InstantUnknown(deadline), proc.get_timer_slack_policy());
// Write message and wait for reply, deadline, or cancellation.
let reply = ChannelDispatcher::call(channel, owner, msg, deadline)?;
channel_call_epilogue(&proc, reply, &args, rd_handles, actual_bytes, actual_handles)
}
#[inline]
fn channel_call_finish<R: ReadHandles>(
deadline: zx_instant_mono_t,
args: RawChannelCallArgs,
rd_handles: R,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let waiter = ThreadDispatcher::get_current_message_waiter();
let proc = ProcessDispatcher::get_current();
let deadline = Deadline::new(InstantUnknown(deadline), proc.get_timer_slack_policy());
let reply = ChannelDispatcher::resume_interrupted_call(&waiter, deadline)?;
channel_call_epilogue(&proc, reply, &args, rd_handles, actual_bytes, actual_handles)
}
#[syscall]
pub fn sys_channel_call_noretry(
handle: HandleValue,
options: u32,
deadline: zx_instant_mono_t,
args: UserInPtr<zx_channel_call_args_t>,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let call_args = args.reinterpret::<RawChannelCallArgs>().read()?;
channel_call_noretry(
handle,
options,
deadline,
call_args,
call_args.wr_handles(),
call_args.rd_handles(),
actual_bytes,
actual_handles,
)
}
#[syscall]
pub fn sys_channel_call_finish(
deadline: zx_instant_mono_t,
args: UserInPtr<zx_channel_call_args_t>,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let call_args = args.reinterpret::<RawChannelCallArgs>().read()?;
channel_call_finish(deadline, call_args, call_args.rd_handles(), actual_bytes, actual_handles)
}
#[syscall]
pub fn sys_channel_call_etc_noretry(
handle: HandleValue,
options: u32,
deadline: zx_instant_mono_t,
args: UserInOutPtr<zx_channel_call_etc_args_t>,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let call_args = args.reinterpret::<RawChannelCallArgs>().read()?;
channel_call_noretry(
handle,
options,
deadline,
call_args,
call_args.wr_handle_dispositions(),
call_args.rd_handle_infos(),
actual_bytes,
actual_handles,
)
}
#[syscall]
pub fn sys_channel_call_etc_finish(
deadline: zx_instant_mono_t,
args: UserInOutPtr<zx_channel_call_etc_args_t>,
actual_bytes: UserOutPtr<u32>,
actual_handles: UserOutPtr<u32>,
) -> Result<(), Status> {
let call_args = args.reinterpret::<RawChannelCallArgs>().read()?;
channel_call_finish(
deadline,
call_args,
call_args.rd_handle_infos(),
actual_bytes,
actual_handles,
)
}