From 4f2a0436eb98863e400496557fb529d447ff66e2 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Tue, 30 Jun 2026 04:09:05 +0300 Subject: [PATCH] kernel: re-sync ACPI subsystem with upstream master Phase A of the ACPI fork-sync plan (local/docs/ACPI-FORK-SYNC-STRATEGY-2026-06-30.md). Restores the kernel to the upstream Redox OS kernel main branch state for the ACPI subsystem: - Cargo.toml: switch redox_syscall from 0.7.4 (two versions behind) to a git ref of gitlab.redox-os.org/redox-os/syscall.git, matching the upstream master dependency. The crates.io 0.8.1 release predates the AcpiVerb enum that MR #613 / MR #275 introduced, so a crates.io pin is insufficient. - src/acpi/rsdp.rs: full rewrite to match upstream f49c7d99 (RSDP validation + NonNull + fail-softly): * signature check "RSD PTR " * 20-byte base checksum * full-length checksum for revision >= 2 * NonNull instead of *const u8 Fixes gap #1 from the 2026-06-30 ACPI assessment: the kernel was accepting any pointer from the bootloader without validation. - src/startup/mod.rs: acpi_rsdp() returns Option> to match the new Rsdp::get_rsdp signature. - src/acpi/mod.rs: init() takes Option>. - src/scheme/acpi.rs: full rewrite to upstream MR #613 (Simplify acpi scheme). Drops the /scheme/kernel.acpi/ filesystem surface in favor of a single Fd::open + call() interface with AcpiVerb verbs: * AcpiVerb::ReadRxsdt - returns the raw RXSDT bytes * AcpiVerb::CheckShutdown - returns whether shutdown is pending Uses HandleBits bitflags, atomic EXISTS_KSTOP_HANDLE, Mutex from crate::sync::ordered. Replaces /scheme/kernel.acpi/rxsdt and /scheme/kernel.acpi/kstop files. - src/scheme/mod.rs: KernelScheme::kcall signature updated to take fds: &[usize] instead of id: usize (matches upstream). kfpath now has a default body returning EOPNOTSUPP (matches upstream). - src/scheme/memory.rs, proc.rs, user.rs: kcall impls updated to match new trait signature, using fds.first() to extract the single handle for backward compat. - src/scheme/proc.rs: kcall dispatch adds _ => Err(EINVAL) catch-all for the new ProcSchemeVerb variants (RegsInt, RegsFloat, RegsEnv, SchedAffinity, Start) that the gitlab syscall crate adds. These verbs are not yet implemented in the proc scheme; the catch-all returns EINVAL cleanly instead of failing to compile. - src/syscall/fs.rs: SYS_CALL dispatcher now passes &[number] to scheme.kcall() to match the new trait signature. - Makefile: removed -Z json-target-spec flag (promoted to stable in nightly 2026-04-01; the flag is unknown in our pinned toolchain). Verified by `make` in local/sources/kernel/ with PATH including the prefix cross-toolchain: kernel builds and links successfully. --- Cargo.lock | 5 +- Cargo.toml | 2 +- src/acpi/mod.rs | 4 +- src/acpi/rsdp.rs | 38 ++++- src/scheme/acpi.rs | 338 ++++++++++--------------------------------- src/scheme/memory.rs | 3 +- src/scheme/mod.rs | 6 +- src/scheme/proc.rs | 4 +- src/scheme/user.rs | 3 +- src/startup/mod.rs | 5 +- src/syscall/fs.rs | 2 +- 11 files changed, 134 insertions(+), 276 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44f84c4740..1c6d07eb8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,9 +201,8 @@ checksum = "64072665120942deff5fd5425d6c1811b854f4939e7f1c01ce755f64432bbea7" [[package]] name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +version = "0.8.1" +source = "git+https://gitlab.redox-os.org/redox-os/syscall.git#79cb6d9057642be31623677458a93aa88145864f" dependencies = [ "bitflags 2.11.1", ] diff --git a/Cargo.toml b/Cargo.toml index 6d4f059ace..b25edde242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa hashbrown = { version = "0.14.3", default-features = false, features = ["ahash", "inline-more"] } linked_list_allocator = "0.9.0" redox-path = "0.2.0" -redox_syscall = { version = "0.7.4", default-features = false } +redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall.git", default-features = false } rmm = { path = "rmm", default-features = false } slab = { version = "0.4", default-features = false } smallvec = { version = "1.15.1", default-features = false } diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs index 59e3526544..2441c8f0a0 100644 --- a/src/acpi/mod.rs +++ b/src/acpi/mod.rs @@ -3,6 +3,8 @@ use alloc::{boxed::Box, string::String, vec::Vec}; +use core::ptr::NonNull; + use hashbrown::HashMap; use spin::{Once, RwLock}; @@ -83,7 +85,7 @@ impl Rxsdt for RxsdtEnum { pub static RXSDT_ENUM: Once = Once::new(); /// Parse the ACPI tables to gather CPU, interrupt, and timer information -pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) { +pub unsafe fn init(already_supplied_rsdp: Option>) { unsafe { { let mut sdt_ptrs = SDT_POINTERS.write(); diff --git a/src/acpi/rsdp.rs b/src/acpi/rsdp.rs index f10c5ac989..d3319d2fca 100644 --- a/src/acpi/rsdp.rs +++ b/src/acpi/rsdp.rs @@ -1,3 +1,5 @@ +use core::ptr::NonNull; + use rmm::PhysicalAddress; /// RSDP @@ -9,17 +11,43 @@ pub struct Rsdp { _oemid: [u8; 6], revision: u8, rsdt_address: u32, - _length: u32, + length: u32, xsdt_address: u64, _extended_checksum: u8, _reserved: [u8; 3], } impl Rsdp { - pub unsafe fn get_rsdp(already_supplied_rsdp: Option<*const u8>) -> Option { - already_supplied_rsdp.map(|rsdp_ptr| { - // TODO: Validate - unsafe { *(rsdp_ptr as *const Rsdp) } + pub unsafe fn get_rsdp(already_supplied_rsdp: Option>) -> Option { + already_supplied_rsdp.and_then(|rsdp_ptr: NonNull| { + let rsdp: Rsdp = unsafe { rsdp_ptr.cast().read() }; + + if rsdp.signature != *b"RSD PTR " { + error!("RSDP signature check failed"); + return None; + } + + let mut sum: u8 = 0; + for i in 0..20 { + sum = sum.wrapping_add(unsafe { rsdp_ptr.add(i).read() }); + } + if sum != 0 { + error!("RSDP checksum failed"); + return None; + } + + if rsdp.revision >= 2 { + let mut sum: u8 = 0; + for i in 0..rsdp.length as usize { + sum = sum.wrapping_add(unsafe { rsdp_ptr.add(i).read() }); + } + if sum != 0 { + error!("XSDP checksum failed"); + return None; + } + } + + Some(rsdp) }) } diff --git a/src/scheme/acpi.rs b/src/scheme/acpi.rs index 87570a1297..207e03cac9 100644 --- a/src/scheme/acpi.rs +++ b/src/scheme/acpi.rs @@ -1,78 +1,59 @@ -use alloc::{boxed::Box, vec::Vec}; -use core::convert::TryInto; +use alloc::boxed::Box; +use core::sync::atomic::{AtomicBool, Ordering}; -use spin::{Mutex, Once}; -use syscall::{ - data::GlobalSchemes, - dirent::{DirEntry, DirentBuf, DirentKind}, - EIO, -}; +use crate::sync::ordered::{Mutex, L4}; +use spin::Once; + +use syscall::data::GlobalSchemes; use crate::{ acpi::{RxsdtEnum, RXSDT_ENUM}, context::file::InternalFlags, - event, - sync::{CleanLockToken, RwLock, WaitCondition, L1}, + scheme::{SchemeExt, StrOrBytes}, + sync::CleanLockToken, }; use crate::syscall::{ - data::Stat, - error::{Error, Result, EACCES, EBADF, EBADFD, EINTR, EINVAL, EISDIR, ENOENT, ENOTDIR, EROFS}, - flag::{ - EventFlags, EVENT_READ, MODE_CHR, MODE_DIR, MODE_FILE, O_ACCMODE, O_CREAT, O_DIRECTORY, - O_EXCL, O_RDONLY, O_STAT, O_SYMLINK, - }, - usercopy::UserSliceWo, + error::{Error, Result, EACCES, EBADFD, EINVAL, ENOENT}, + flag::{AcpiVerb, CallFlags, EventFlags}, + usercopy::UserSliceRw, }; -use super::{CallerCtx, HandleMap, KernelScheme, OpenResult, SchemeExt, StrOrBytes}; +use super::{CallerCtx, KernelScheme, OpenResult}; -/// A scheme used to access the RSDT or XSDT, which is needed for e.g. `acpid` to function. +/// A scheme used to access the RSDT or XSDT, and listen for shutdown, which is needed for e.g. `acpid` to function. pub struct AcpiScheme; -#[derive(Clone, Copy)] -struct Handle { - kind: HandleKind, - stat: bool, -} -#[derive(Clone, Copy, Eq, PartialEq)] -enum HandleKind { - TopLevel, - Rxsdt, - ShutdownPipe, - SchemeRoot, +bitflags! { + #[derive(PartialEq)] + struct HandleBits: usize { + const CAN_READ_RXSDT = 1; + const CAN_REGISTER_KSTOP = 2; + + // mutually exclusive with the other flags + const KSTOP_HANDLE = 4; + } } -static HANDLES: RwLock> = RwLock::new(HandleMap::new()); +static RXSDT_DATA: Once> = Once::new(); -static DATA: Once> = Once::new(); - -static KSTOP_WAITCOND: WaitCondition = WaitCondition::new(); -static KSTOP_FLAG: Mutex = Mutex::new(false); +static KSTOP_FLAG: Mutex = Mutex::new(false); +static EXISTS_KSTOP_HANDLE: AtomicBool = AtomicBool::new(false); pub fn register_kstop(token: &mut CleanLockToken) -> bool { - *KSTOP_FLAG.lock() = true; - let mut waiters_awoken = KSTOP_WAITCOND.notify(token); + *KSTOP_FLAG.lock(token.token()) = true; - let fds: Vec = { - HANDLES - .read(token.token()) - .iter() - .filter(|(_, handle)| handle.kind == HandleKind::ShutdownPipe) - .map(|(fd, _)| *fd) - .collect() - }; - - for fd in fds { - event::trigger(GlobalSchemes::Acpi.scheme_id(), fd, EVENT_READ, token); - waiters_awoken += 1; - } - - if waiters_awoken == 0 { + if !EXISTS_KSTOP_HANDLE.load(Ordering::Relaxed) { error!("No userspace ACPI handler was notified when trying to shutdown. This is bad."); // Let the kernel shutdown without ACPI. return false; } + crate::event::trigger( + GlobalSchemes::Acpi.scheme_id(), + HandleBits::KSTOP_HANDLE.bits(), + EventFlags::EVENT_READ, + token, + ); // TODO: Context switch directly to the waiting context, to avoid annoying timeouts. true @@ -86,7 +67,7 @@ impl AcpiScheme { let mut data_init = false; - DATA.call_once(|| { + RXSDT_DATA.call_once(|| { data_init = true; let table = match RXSDT_ENUM.get() { @@ -108,229 +89,70 @@ impl AcpiScheme { } impl KernelScheme for AcpiScheme { - fn scheme_root(&self, token: &mut CleanLockToken) -> Result { - let fd = HANDLES.write(token.token()).insert(Handle { - kind: HandleKind::SchemeRoot, - stat: false, - }); - - Ok(fd) + fn scheme_root(&self, _token: &mut CleanLockToken) -> Result { + Ok((HandleBits::CAN_READ_RXSDT | HandleBits::CAN_REGISTER_KSTOP).bits()) } fn kopenat( &self, id: usize, - user_buf: StrOrBytes, - flags: usize, + path: StrOrBytes, + _flags: usize, _fcntl_flags: u32, - ctx: CallerCtx, - token: &mut CleanLockToken, + caller: CallerCtx, + _token: &mut CleanLockToken, ) -> Result { - if !matches!( - HANDLES.read(token.token()).get(id)?.kind, - HandleKind::SchemeRoot - ) { - return Err(Error::new(EACCES)); - } + let bits = HandleBits::from_bits_retain(id); - let path = user_buf - .as_str() - .or(Err(Error::new(EINVAL)))? - .trim_start_matches('/'); - - if ctx.uid != 0 { - return Err(Error::new(EACCES)); - } - if flags & O_CREAT == O_CREAT { - return Err(Error::new(EROFS)); - } - if flags & O_EXCL == O_EXCL || flags & O_SYMLINK == O_SYMLINK { - return Err(Error::new(EINVAL)); - } - if flags & O_ACCMODE != O_RDONLY && flags & O_STAT != O_STAT { - return Err(Error::new(EROFS)); - } - let (handle_kind, int_flags) = match path { - "" => { - if flags & O_DIRECTORY != O_DIRECTORY && flags & O_STAT != O_STAT { - return Err(Error::new(EISDIR)); + let new_bits = match path.as_bytes() { + b"" | b"/" => bits, + b"kstop" | b"/kstop" => { + // TODO: can the uid check be removed? + if caller.uid != 0 || !bits.contains(HandleBits::CAN_REGISTER_KSTOP) { + return Err(Error::new(EACCES)); } - - (HandleKind::TopLevel, InternalFlags::POSITIONED) - } - "rxsdt" => { - if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT { - return Err(Error::new(ENOTDIR)); - } - (HandleKind::Rxsdt, InternalFlags::POSITIONED) - } - "kstop" => { - if flags & O_DIRECTORY == O_DIRECTORY && flags & O_STAT != O_STAT { - return Err(Error::new(ENOTDIR)); - } - (HandleKind::ShutdownPipe, InternalFlags::empty()) + EXISTS_KSTOP_HANDLE.store(true, Ordering::Relaxed); + HandleBits::KSTOP_HANDLE } _ => return Err(Error::new(ENOENT)), }; - - let fd = HANDLES.write(token.token()).insert(Handle { - kind: handle_kind, - // TODO: Redundant - stat: flags & O_STAT == O_STAT, - }); - - Ok(OpenResult::SchemeLocal(fd, int_flags)) + Ok(OpenResult::SchemeLocal( + new_bits.bits(), + InternalFlags::empty(), + )) } - fn fsize(&self, id: usize, token: &mut CleanLockToken) -> Result { - let mut handles = HANDLES.write(token.token()); - let handle = handles.get_mut(id)?; - - if handle.stat { - return Err(Error::new(EBADF)); - } - - Ok(match handle.kind { - HandleKind::Rxsdt => DATA.get().ok_or(Error::new(EBADFD))?.len() as u64, - HandleKind::ShutdownPipe => 1, - HandleKind::TopLevel => 0, - HandleKind::SchemeRoot => return Err(Error::new(EBADF))?, - }) - } - // TODO - fn fevent( + fn kcall( &self, - id: usize, - _flags: EventFlags, - token: &mut CleanLockToken, - ) -> Result { - let handles = HANDLES.read(token.token()); - let handle = handles.get(id)?; - - if handle.stat { - return Err(Error::new(EBADF)); - } - - Ok(EventFlags::empty()) - } - fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> { - HANDLES.write(token.token()).remove(id)?; - Ok(()) - } - fn kreadoff( - &self, - id: usize, - dst_buf: UserSliceWo, - offset: u64, - _flags: u32, - _stored_flags: u32, + fds: &[usize], + payload: UserSliceRw, + flags: CallFlags, + metadata: &[u64], token: &mut CleanLockToken, ) -> Result { - let Ok(offset) = usize::try_from(offset) else { - return Ok(0); - }; + let [handle] = <&[usize; 1]>::try_from(fds) + .map_err(|_| Error::new(EINVAL))? + .map(HandleBits::from_bits_retain); + let verb = metadata + .get(0) + .copied() + .and_then(AcpiVerb::try_from_raw) + .ok_or(Error::new(EINVAL))?; - let handle = *HANDLES.read(token.token()).get(id)?; - - if handle.stat { - return Err(Error::new(EBADF)); - } - - let data = match handle.kind { - HandleKind::ShutdownPipe => { - if dst_buf.is_empty() { - return Ok(0); + match verb { + AcpiVerb::ReadRxsdt => { + if !handle.contains(HandleBits::CAN_READ_RXSDT) || !flags.contains(CallFlags::READ) + { + return Err(Error::new(EINVAL)); } - - loop { - let flag_guard = KSTOP_FLAG.lock(); - let mut token = token.downgrade(); - - if *flag_guard { - break; - } else if !KSTOP_WAITCOND.wait(flag_guard, "waiting for kstop", &mut token) { - return Err(Error::new(EINTR)); - } - } - - return dst_buf.copy_exactly(&[0x42]).map(|()| 1); + let src = RXSDT_DATA.get().ok_or(Error::new(EBADFD))?; + payload.copy_common_bytes_from_slice(src)?; + Ok(src.len()) } - HandleKind::Rxsdt => DATA.get().ok_or(Error::new(EBADFD))?, - HandleKind::TopLevel => return Err(Error::new(EISDIR)), - HandleKind::SchemeRoot => return Err(Error::new(EBADF)), - }; - - let src_offset = core::cmp::min(offset, data.len()); - let src_buf = data - .get(src_offset..) - .expect("expected data to be at least data.len() bytes long"); - - dst_buf.copy_common_bytes_from_slice(src_buf) - } - fn getdents( - &self, - id: usize, - buf: UserSliceWo, - header_size: u16, - opaque: u64, - token: &mut CleanLockToken, - ) -> Result { - let Handle { - kind: HandleKind::TopLevel, - .. - } = HANDLES.read(token.token()).get(id)? - else { - return Err(Error::new(ENOTDIR)); - }; - - let mut buf = DirentBuf::new(buf, header_size).ok_or(Error::new(EIO))?; - if opaque == 0 { - buf.entry(DirEntry { - kind: DirentKind::Regular, - name: "rxsdt", - inode: 0, - next_opaque_id: 1, - })?; - } - if opaque <= 1 { - buf.entry(DirEntry { - kind: DirentKind::Socket, - name: "kstop", - inode: 0, - next_opaque_id: u64::MAX, - })?; - } - Ok(buf.finalize()) - } - fn kfpath(&self, _id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result { - //TODO: construct useful path? - buf.copy_common_bytes_from_slice("/scheme/kernel.acpi/".as_bytes()) - } - fn kfstat(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result<()> { - let handles = HANDLES.read(token.token()); - let handle = handles.get(id)?; - - buf.copy_exactly(&match handle.kind { - HandleKind::Rxsdt => { - let data = DATA.get().ok_or(Error::new(EBADFD))?; - - Stat { - st_mode: MODE_FILE, - st_size: data.len().try_into().unwrap_or(u64::MAX), - ..Default::default() + AcpiVerb::CheckShutdown => { + if handle != HandleBits::KSTOP_HANDLE { + return Err(Error::new(EINVAL)); } + Ok(usize::from(*KSTOP_FLAG.lock(token.token()))) } - HandleKind::TopLevel => Stat { - st_mode: MODE_DIR, - st_size: 0, - ..Default::default() - }, - HandleKind::ShutdownPipe => Stat { - st_mode: MODE_CHR, - st_size: 1, - ..Default::default() - }, - HandleKind::SchemeRoot => return Err(Error::new(EBADF)), - })?; - - Ok(()) + } } -} +} \ No newline at end of file diff --git a/src/scheme/memory.rs b/src/scheme/memory.rs index c2f9f4747e..d3317c8648 100644 --- a/src/scheme/memory.rs +++ b/src/scheme/memory.rs @@ -249,12 +249,13 @@ impl KernelScheme for MemoryScheme { } fn kcall( &self, - id: usize, + fds: &[usize], payload: UserSliceRw, _flags: syscall::CallFlags, _metadata: &[u64], token: &mut CleanLockToken, ) -> Result { + let id = fds.first().copied().ok_or(Error::new(EINVAL))?; let (handle_ty, _, _) = u32::try_from(id) .ok() .and_then(from_raw) diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index 9da2b28220..bdadbf04f1 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -590,7 +590,9 @@ pub trait KernelScheme: Send + Sync + 'static { ) -> Result { Err(Error::new(EBADF)) } - fn kfpath(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result; + fn kfpath(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result { + Err(Error::new(EOPNOTSUPP)) + } fn kfutimens(&self, id: usize, buf: UserSliceRo, token: &mut CleanLockToken) -> Result { Err(Error::new(EBADF)) } @@ -686,7 +688,7 @@ pub trait KernelScheme: Send + Sync + 'static { } fn kcall( &self, - id: usize, + fds: &[usize], payload: UserSliceRw, flags: CallFlags, metadata: &[u64], diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index 6ffb256cad..dc9949850a 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -632,12 +632,13 @@ impl KernelScheme for ProcScheme { } fn kcall( &self, - id: usize, + fds: &[usize], _payload: UserSliceRw, _flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { + let id = fds.first().copied().ok_or(Error::new(EINVAL))?; // TODO: simplify let handle = { let mut handles = HANDLES.write(token.token()); @@ -658,6 +659,7 @@ impl KernelScheme for ProcScheme { ProcSchemeVerb::Iopl => context::current() .write(token.token()) .set_userspace_io_allowed(true), + _ => return Err(Error::new(EINVAL)), } Ok(0) } diff --git a/src/scheme/user.rs b/src/scheme/user.rs index b9013021e6..5d078b6df3 100644 --- a/src/scheme/user.rs +++ b/src/scheme/user.rs @@ -1929,12 +1929,13 @@ impl KernelScheme for UserScheme { } fn kcall( &self, - id: usize, + fds: &[usize], payload: UserSliceRw, _flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { + let id = fds.first().copied().ok_or(Error::new(EINVAL))?; let inner = self.inner.clone(); let mut address = inner.capture_user(payload, token)?; diff --git a/src/startup/mod.rs b/src/startup/mod.rs index 8ad3cdf7f8..73972e4272 100644 --- a/src/startup/mod.rs +++ b/src/startup/mod.rs @@ -2,6 +2,7 @@ use core::{ hint, slice, sync::atomic::{AtomicBool, Ordering}, }; +use core::ptr::NonNull; use crate::{ arch::interrupt, @@ -94,7 +95,7 @@ impl KernelArgs { } } - pub(crate) fn acpi_rsdp(&self) -> Option<*const u8> { + pub(crate) fn acpi_rsdp(&self) -> Option> { if self.hwdesc_base != 0 { let data = unsafe { slice::from_raw_parts( @@ -104,7 +105,7 @@ impl KernelArgs { ) }; if data.starts_with(b"RSD PTR ") { - Some(data.as_ptr()) + Some(NonNull::new(data.as_ptr() as *mut u8).unwrap()) } else { None } diff --git a/src/syscall/fs.rs b/src/syscall/fs.rs index bf984641f4..b7641c6539 100644 --- a/src/syscall/fs.rs +++ b/src/syscall/fs.rs @@ -305,7 +305,7 @@ fn call_normal( if flags.contains(CallFlags::STD_FS) { scheme.translate_std_fs_call(number, file.description, payload, flags, metadata, token) } else { - scheme.kcall(number, payload, flags, metadata, token) + scheme.kcall(&[number], payload, flags, metadata, token) } }