feat(kernel): implement syscall-based ioctl support and improve ATA caching

- Replace int 0x80 with native x86-64 SYSCALL instruction for faster system calls
- Add syscall number 16 for ioctl support with Linux-compatible ABI
- Implement TIOCGWINSZ and TIOCNOTTY terminal ioctls for console size detection
- Add ENOTTY error code definition for inappropriate ioctl operations
- Increase ATA cache capacity from 128 to 16384 entries for better performance
- Change ATA cache from LRU eviction to direct mapping using bitwise AND
- Remove least recently used tracking from ATA cache entries
-
master
韩天峰 4 weeks ago
parent 235d07f7c3
commit 6340433fbe
  1. 2
      examples/typephp-os/Makefile
  2. 6
      examples/typephp-os/README.md
  3. 13
      examples/typephp-os/ROADMAP.md
  4. 33
      examples/typephp-os/THIRDPARTY.md
  5. 1
      examples/typephp-os/kernel/core/abi/error.c
  6. 1
      examples/typephp-os/kernel/core/abi/typephp_os_syscall.h
  7. 30
      examples/typephp-os/kernel/core/ata.c
  8. 16
      examples/typephp-os/kernel/core/process-entry.S
  9. 87
      examples/typephp-os/kernel/core/process.c
  10. 33
      examples/typephp-os/tools/fetch-mlibc.sh
  11. 1
      examples/typephp-os/tools/fetch-thirdparty.sh
  12. 14
      examples/typephp-os/tools/mlibc-demo-cross.ini
  13. 26
      examples/typephp-os/tools/mlibc-headers-smoke.c
  14. 30
      examples/typephp-os/tools/test-thirdparty.sh
  15. 9
      examples/typephp-os/user/cmd/systest.c
  16. 1
      examples/typephp-os/user/include/errno.h
  17. 18
      examples/typephp-os/user/include/sys/ioctl.h
  18. 1
      examples/typephp-os/user/include/sys/syscall.h
  19. 5
      examples/typephp-os/user/runtime/README.md
  20. 18
      examples/typephp-os/user/runtime/ioctl.c
  21. 4
      examples/typephp-os/user/runtime/syscall.S

@ -22,7 +22,7 @@ OPENLIBM_SOURCES := $(shell sed -n 's|^\(.*\.c\)$$|thirdparty/openlibm/\1|p' too
COMPILER_RT_SOURCES := $(shell awk '$$2 ~ /\.c$$/ { print "thirdparty/compiler-rt/" $$2 }' tools/compiler-rt-builtins-files.sha256) COMPILER_RT_SOURCES := $(shell awk '$$2 ~ /\.c$$/ { print "thirdparty/compiler-rt/" $$2 }' tools/compiler-rt-builtins-files.sha256)
COMPILER_RT_OBJECTS := $(patsubst thirdparty/compiler-rt/lib/builtins/%.c,$(COMPILER_RT_OBJECT_DIR)/%.o,$(COMPILER_RT_SOURCES)) COMPILER_RT_OBJECTS := $(patsubst thirdparty/compiler-rt/lib/builtins/%.c,$(COMPILER_RT_OBJECT_DIR)/%.o,$(COMPILER_RT_SOURCES))
TYPEPHP_OS_LIBRARY_C_SOURCES := user/runtime/posix.c user/runtime/dirent.c \ TYPEPHP_OS_LIBRARY_C_SOURCES := user/runtime/posix.c user/runtime/dirent.c \
user/runtime/fcntl.c kernel/core/abi/error.c kernel/core/abi/string.c \ user/runtime/fcntl.c user/runtime/ioctl.c kernel/core/abi/error.c kernel/core/abi/string.c \
kernel/core/abi/libc.c $(OPENLIBM_SOURCES) kernel/core/abi/libc.c $(OPENLIBM_SOURCES)
TYPEPHP_OS_LIBRARY_CXX_SOURCES := user/runtime/host.cc kernel/core/abi/cxx.cpp TYPEPHP_OS_LIBRARY_CXX_SOURCES := user/runtime/host.cc kernel/core/abi/cxx.cpp
TYPEPHP_OS_LIBRARY_ASM_SOURCES := user/runtime/crt0.S user/runtime/syscall.S \ TYPEPHP_OS_LIBRARY_ASM_SOURCES := user/runtime/crt0.S user/runtime/syscall.S \

@ -99,9 +99,9 @@ Run the automated serial-output smoke test with:
make test make test
``` ```
To configure the mlibc header porting probe and build the restricted hosted To build and audit the restricted hosted Toybox applet set, run
Toybox applet set, install Meson 1.3 or newer and run `make thirdparty-smoke`. `make thirdparty-smoke`. This is an integration audit, not part of the boot
This is an integration audit, not part of the boot image. image.
The Makefile compiles the startup sources under `boot/` directly. The 32-bit The Makefile compiles the startup sources under `boot/` directly. The 32-bit
Multiboot bootstrap cannot participate in the 64-bit payload link; the 64-bit Multiboot bootstrap cannot participate in the 64-bit payload link; the 64-bit

@ -28,10 +28,9 @@ behavior.
`wait`, pipes, threads, scheduling signals, and job control will not be `wait`, pipes, threads, scheduling signals, and job control will not be
implemented. The shell may only launch one synchronous foreground ELF implemented. The shell may only launch one synchronous foreground ELF
through the private TypePHP-OS service. through the private TypePHP-OS service.
- `ioctl` and `termios` are outside the current scope. Third-party programs - Keep `ioctl` request support deliberately narrow. The fixed serial/VGA
must not drive implementation of these low-priority terminal interfaces; console provides `TIOCGWINSZ`; a complete termios subsystem remains outside
they can be adopted only after their selected build no longer requires the the current single-task shell requirements.
interfaces or another generally useful subsystem justifies them.
## Milestones ## Milestones
@ -90,10 +89,8 @@ behavior.
C commands and multiple tpc projects link the same platform archive. C commands and multiple tpc projects link the same platform archive.
Unavailable ABI functions remain explicit panic stubs. Unavailable ABI functions remain explicit panic stubs.
Selected upstream LLVM compiler-rt builtins now provide 128-bit integer Selected upstream LLVM compiler-rt builtins now provide 128-bit integer
helper symbols. mlibc 7.0 and a five-applet Toybox build are pinned as helper symbols. A five-applet Toybox build is pinned as a porting probe but
porting probes; neither replaces the working userspace runtime yet because does not yet replace the working userspace commands.
mlibc exceeds the C++17 baseline and Toybox's common runtime still imports
`ioctl`.
Single-task identity calls report PID/TID 1 for the resident shell, 2 for Single-task identity calls report PID/TID 1 for the resident shell, 2 for
its synchronous foreground command, and root UID/GID. Linux-compatible its synchronous foreground command, and root UID/GID. Linux-compatible
wall/monotonic clock structures are exposed at the current one-second RTC wall/monotonic clock structures are exposed at the current one-second RTC

@ -28,9 +28,8 @@ Run the upstream integration probes separately:
make thirdparty-smoke make thirdparty-smoke
``` ```
This target requires Meson 1.3 or newer for mlibc. It configures a headers-only This target builds a deliberately small hosted Toybox and audits its undefined
mlibc sysroot and builds a deliberately small hosted Toybox. Neither candidate ABI. It is not installed into the TypePHP-OS disk image by this probe.
is installed into the TypePHP-OS disk image by this probe.
## OpenLibm ## OpenLibm
@ -58,27 +57,6 @@ and TypePHP Nano userspace object set. The `builtins.elf` smoke command forces
signed and unsigned 128-bit division so the archive is tested as a real linker signed and unsigned 128-bit division so the archive is tested as a real linker
dependency instead of merely being compiled. dependency instead of merely being compiled.
## mlibc
- Upstream: <https://github.com/managarm/mlibc>
- Version: `v7.0.0`
- Commit: `7c2a178142625cc9852e59a1a090468c61a62d3b`
- Archive SHA-256: `56d62366a6a15bc3fd8741bebcb311ca543553f7b734b1e780aeae33691bd621`
The complete release tree is retained as a libc porting reference. The smoke
target configures mlibc's official Meson build in headers-only/demo-sysdeps
mode and compiles a TypePHP-OS API header probe. mlibc 7.0's implementation
build requires C++23 and GCC 13 or newer, while TypePHP-OS keeps C++17 as its
runtime baseline. Its implementation therefore does not replace the current
small libc yet; doing so also requires a real TypePHP-OS sysdeps port.
The mlibc 6 series does not have one uniform compiler baseline. Releases 6.0
and 6.1 request C++20 and do not contain the GCC 13 gate added later, so they
are more practical with an older compiler. Releases 6.2 and 6.3 already
request C++23. None of them satisfies TypePHP-OS's C++17 baseline unchanged;
using 6.0/6.1 would therefore be a separate port/toolchain choice rather than
a transparent downgrade of the pinned reference.
## Toybox ## Toybox
- Upstream: <https://codeberg.org/landley/toybox> - Upstream: <https://codeberg.org/landley/toybox>
@ -93,7 +71,6 @@ host ABI is written to `build/thirdparty-smoke/toybox-undefined-symbols.txt`.
`ioctl` is a system-call API used by Toybox's shared C support library, not a `ioctl` is a system-call API used by Toybox's shared C support library, not a
Toybox command. None of those five applet source files calls it directly, but Toybox command. None of those five applet source files calls it directly, but
the common terminal/daemon helpers still leave an `ioctl` reference in the the common terminal/daemon helpers still leave an `ioctl` reference in the
hosted binary. Toybox is therefore intentionally not linked into TypePHP-OS. hosted binary. TypePHP-OS now exposes the Linux-numbered syscall and implements
The project will not implement `ioctl` or `termios` merely to satisfy it; `TIOCGWINSZ` for its fixed console. Toybox still needs a freestanding build and
adoption requires pruning the unused common helpers or completing a different ABI audit before it can replace any existing command.
compatible libc/runtime path.

@ -26,6 +26,7 @@ char *strerror(int error)
case ENOTDIR: return "Not a directory"; case ENOTDIR: return "Not a directory";
case EISDIR: return "Is a directory"; case EISDIR: return "Is a directory";
case EINVAL: return "Invalid argument"; case EINVAL: return "Invalid argument";
case ENOTTY: return "Inappropriate ioctl for device";
case EMFILE: return "Too many open files"; case EMFILE: return "Too many open files";
case EROFS: return "Read-only file system"; case EROFS: return "Read-only file system";
case ERANGE: return "Numerical result out of range"; case ERANGE: return "Numerical result out of range";

@ -16,6 +16,7 @@ enum typephp_os_syscall_number {
TYPEPHP_SYS_MPROTECT = 10, TYPEPHP_SYS_MPROTECT = 10,
TYPEPHP_SYS_MUNMAP = 11, TYPEPHP_SYS_MUNMAP = 11,
TYPEPHP_SYS_BRK = 12, TYPEPHP_SYS_BRK = 12,
TYPEPHP_SYS_IOCTL = 16,
TYPEPHP_SYS_ACCESS = 21, TYPEPHP_SYS_ACCESS = 21,
TYPEPHP_SYS_GETPID = 39, TYPEPHP_SYS_GETPID = 39,
TYPEPHP_SYS_EXIT = 60, TYPEPHP_SYS_EXIT = 60,

@ -33,19 +33,20 @@ enum {
ATA_COMMAND_CACHE_FLUSH = 0xe7, ATA_COMMAND_CACHE_FLUSH = 0xe7,
ATA_SECTOR_SIZE = 512, ATA_SECTOR_SIZE = 512,
ATA_TIMEOUT = 10000000, ATA_TIMEOUT = 10000000,
ATA_CACHE_CAPACITY = 128, /* TNHELLO.ELF is currently about 5 MiB. A power-of-two 8 MiB cache keeps
* one complete image resident so a second launch avoids ATA PIO, while a
* direct mapping keeps every sector lookup O(1). */
ATA_CACHE_CAPACITY = 16384,
PCI_CONFIG_ADDRESS = 0xcf8, PCI_CONFIG_ADDRESS = 0xcf8,
PCI_CONFIG_DATA = 0xcfc, PCI_CONFIG_DATA = 0xcfc,
}; };
static int ata_initialized; static int ata_initialized;
static uint64_t ata_cache_clock;
static uint64_t ata_cache_hit_count; static uint64_t ata_cache_hit_count;
static uint64_t ata_cache_miss_count; static uint64_t ata_cache_miss_count;
typedef struct { typedef struct {
uint32_t lba; uint32_t lba;
uint64_t last_used;
int valid; int valid;
unsigned char data[ATA_SECTOR_SIZE]; unsigned char data[ATA_SECTOR_SIZE];
} ata_cache_entry; } ata_cache_entry;
@ -206,23 +207,9 @@ static int ata_write_sector_raw(uint32_t lba, const unsigned char *data)
static ata_cache_entry *ata_cache_slot(uint32_t lba, int *found) static ata_cache_entry *ata_cache_slot(uint32_t lba, int *found)
{ {
ata_cache_entry *oldest = &ata_cache[0]; ata_cache_entry *entry = &ata_cache[lba & (ATA_CACHE_CAPACITY - 1u)];
for (size_t index = 0; index < ATA_CACHE_CAPACITY; ++index) { *found = entry->valid && entry->lba == lba;
ata_cache_entry *entry = &ata_cache[index]; return entry;
if (entry->valid && entry->lba == lba) {
*found = 1;
return entry;
}
if (!entry->valid) {
*found = 0;
return entry;
}
if (entry->last_used < oldest->last_used) {
oldest = entry;
}
}
*found = 0;
return oldest;
} }
int typephp_os_disk_read_sector(uint32_t lba, unsigned char *data) int typephp_os_disk_read_sector(uint32_t lba, unsigned char *data)
@ -235,7 +222,6 @@ int typephp_os_disk_read_sector(uint32_t lba, unsigned char *data)
entry = ata_cache_slot(lba, &found); entry = ata_cache_slot(lba, &found);
if (found) { if (found) {
++ata_cache_hit_count; ++ata_cache_hit_count;
entry->last_used = ++ata_cache_clock;
memcpy(data, entry->data, ATA_SECTOR_SIZE); memcpy(data, entry->data, ATA_SECTOR_SIZE);
return 1; return 1;
} }
@ -245,7 +231,6 @@ int typephp_os_disk_read_sector(uint32_t lba, unsigned char *data)
} }
entry->valid = 1; entry->valid = 1;
entry->lba = lba; entry->lba = lba;
entry->last_used = ++ata_cache_clock;
memcpy(entry->data, data, ATA_SECTOR_SIZE); memcpy(entry->data, data, ATA_SECTOR_SIZE);
return 1; return 1;
} }
@ -261,7 +246,6 @@ int typephp_os_disk_write_sector(uint32_t lba, const unsigned char *data)
(void) found; (void) found;
entry->valid = 1; entry->valid = 1;
entry->lba = lba; entry->lba = lba;
entry->last_used = ++ata_cache_clock;
memcpy(entry->data, data, ATA_SECTOR_SIZE); memcpy(entry->data, data, ATA_SECTOR_SIZE);
return 1; return 1;
} }

@ -4,7 +4,21 @@
.global typephp_os_syscall_entry .global typephp_os_syscall_entry
.type typephp_os_syscall_entry, @function .type typephp_os_syscall_entry, @function
.extern typephp_os_syscall_dispatch .extern typephp_os_syscall_dispatch
.extern typephp_os_syscall_stack
.extern typephp_os_syscall_user_rsp
typephp_os_syscall_entry: typephp_os_syscall_entry:
# SYSCALL leaves RSP unchanged, stores the user RIP in RCX and stores the
# user RFLAGS in R11. Save the untrusted user stack pointer before moving
# to the kernel-owned syscall stack, then construct the same IRET frame
# used by exceptions and the former interrupt-gate entry.
mov %rsp, typephp_os_syscall_user_rsp(%rip)
lea typephp_os_syscall_stack+65536(%rip), %rsp
pushq $0x1b
pushq typephp_os_syscall_user_rsp(%rip)
push %r11
pushq $0x23
push %rcx
# Save every general-purpose register. The saved RAX slot is overwritten # Save every general-purpose register. The saved RAX slot is overwritten
# with the syscall result before the frame is restored. # with the syscall result before the frame is restored.
push %rbx push %rbx
@ -120,7 +134,7 @@ typephp_os_exception_common:
.type typephp_os_enter_user, @function .type typephp_os_enter_user, @function
typephp_os_enter_user: typephp_os_enter_user:
# RDI = ELF entry, RSI = top of the user stack. Interrupts remain masked; # RDI = ELF entry, RSI = top of the user stack. Interrupts remain masked;
# int 0x80 is synchronous and the first process manager has no scheduler. # SYSCALL is synchronous and the first process manager has no scheduler.
mov $0x1b, %ax mov $0x1b, %ax
mov %ax, %ds mov %ax, %ds
mov %ax, %es mov %ax, %es

@ -2,7 +2,7 @@
+----------------------------------------------------------------------+ +----------------------------------------------------------------------+
| TypePHP OS | | TypePHP OS |
+----------------------------------------------------------------------+ +----------------------------------------------------------------------+
| Single foreground process, ELF64 loader and int 0x80 syscall core. | | Single foreground process, ELF64 loader and x86-64 syscall core. |
| SPDX-License-Identifier: BSD-3-Clause | | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+ +----------------------------------------------------------------------+
*/ */
@ -34,7 +34,6 @@ enum {
USER_DATA_SELECTOR = 0x1b, USER_DATA_SELECTOR = 0x1b,
KERNEL_CODE_SELECTOR = 0x08, KERNEL_CODE_SELECTOR = 0x08,
TSS_SELECTOR = 0x28, TSS_SELECTOR = 0x28,
IDT_SYSCALL = 0x80,
MAX_USER_ARGUMENTS = 8, MAX_USER_ARGUMENTS = 8,
USER_PATH_MAX = 128, USER_PATH_MAX = 128,
UTSNAME_LENGTH = 65, UTSNAME_LENGTH = 65,
@ -59,6 +58,13 @@ typedef struct {
long microseconds; long microseconds;
} user_timeval; } user_timeval;
typedef struct {
uint16_t rows;
uint16_t columns;
uint16_t pixel_width;
uint16_t pixel_height;
} user_winsize;
enum { enum {
ELF_PT_LOAD = 1, ELF_PT_LOAD = 1,
ELF_PF_X = 1, ELF_PF_X = 1,
@ -215,9 +221,31 @@ extern uint64_t physical_page_available(void);
static uint64_t gdt[7] __attribute__((aligned(16))); static uint64_t gdt[7] __attribute__((aligned(16)));
static idt_gate idt[256] __attribute__((aligned(16))); static idt_gate idt[256] __attribute__((aligned(16)));
static task_state_segment tss; static task_state_segment tss;
static unsigned char syscall_stack[64u * 1024u] __attribute__((aligned(16))); unsigned char typephp_os_syscall_stack[64u * 1024u] __attribute__((aligned(16)));
uint64_t typephp_os_syscall_user_rsp;
static process_state foreground_process = {.pid = 1, .cwd = "/"}; static process_state foreground_process = {.pid = 1, .cwd = "/"};
enum {
IA32_EFER = 0xc0000080u,
IA32_STAR = 0xc0000081u,
IA32_LSTAR = 0xc0000082u,
IA32_FMASK = 0xc0000084u,
};
static uint64_t read_msr(uint32_t index)
{
uint32_t low;
uint32_t high;
__asm__ volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(index));
return ((uint64_t) high << 32u) | low;
}
static void write_msr(uint32_t index, uint64_t value)
{
__asm__ volatile("wrmsr" : : "c"(index), "a"((uint32_t) value),
"d"((uint32_t) (value >> 32u)) : "memory");
}
static void panic(const char *message) static void panic(const char *message)
{ {
typephp_os_write("Process panic: ", sizeof("Process panic: ") - 1); typephp_os_write("Process panic: ", sizeof("Process panic: ") - 1);
@ -287,7 +315,8 @@ static void install_descriptor_tables(void)
gdt[3] = UINT64_C(0x00cff2000000ffff); gdt[3] = UINT64_C(0x00cff2000000ffff);
gdt[4] = UINT64_C(0x00affa000000ffff); gdt[4] = UINT64_C(0x00affa000000ffff);
memset(&tss, 0, sizeof(tss)); memset(&tss, 0, sizeof(tss));
tss.rsp0 = (uint64_t) (uintptr_t) (syscall_stack + sizeof(syscall_stack)); tss.rsp0 = (uint64_t) (uintptr_t)
(typephp_os_syscall_stack + sizeof(typephp_os_syscall_stack));
tss.iomap_base = sizeof(tss); tss.iomap_base = sizeof(tss);
install_tss_descriptor((uint64_t) (uintptr_t) &tss, sizeof(tss) - 1u); install_tss_descriptor((uint64_t) (uintptr_t) &tss, sizeof(tss) - 1u);
@ -314,10 +343,20 @@ static void install_descriptor_tables(void)
install_idt_gate(12, typephp_os_exception_12, 0x8e); install_idt_gate(12, typephp_os_exception_12, 0x8e);
install_idt_gate(13, typephp_os_exception_13, 0x8e); install_idt_gate(13, typephp_os_exception_13, 0x8e);
install_idt_gate(14, typephp_os_exception_14, 0x8e); install_idt_gate(14, typephp_os_exception_14, 0x8e);
install_idt_gate(IDT_SYSCALL, typephp_os_syscall_entry, 0xee);
pointer.limit = sizeof(idt) - 1u; pointer.limit = sizeof(idt) - 1u;
pointer.base = (uint64_t) (uintptr_t) idt; pointer.base = (uint64_t) (uintptr_t) idt;
__asm__ volatile("lidt %0" : : "m"(pointer) : "memory"); __asm__ volatile("lidt %0" : : "m"(pointer) : "memory");
/* STAR[47:32] selects kernel CS 0x08 and its following SS 0x10.
* SYSRET's selector arithmetic starts at 0x10, yielding user SS 0x1b
* and user CS 0x23. This kernel returns with IRETQ so that spawn/exit can
* replace the complete user context, but the entry ABI remains the native
* Linux x86-64 SYSCALL register convention. */
write_msr(IA32_STAR,
(UINT64_C(0x10) << 48u) | ((uint64_t) KERNEL_CODE_SELECTOR << 32u));
write_msr(IA32_LSTAR, (uint64_t) (uintptr_t) typephp_os_syscall_entry);
write_msr(IA32_FMASK, UINT64_C(0x700)); /* TF, IF and DF */
write_msr(IA32_EFER, read_msr(IA32_EFER) | UINT64_C(1));
} }
static int read_exact(int fd, void *buffer, size_t size) static int read_exact(int fd, void *buffer, size_t size)
@ -402,6 +441,13 @@ static int load_user_elf_file(
if (segment.type != ELF_PT_LOAD) { if (segment.type != ELF_PT_LOAD) {
continue; continue;
} }
/* GNU ld can retain an empty PT_LOAD from the linker script when a
* tiny static executable has no data or bss sections. It maps no
* bytes and must not be rejected merely because its placeholder
* virtual address is zero. */
if (segment.filesz == 0 && segment.memsz == 0) {
continue;
}
if (segment.filesz > segment.memsz if (segment.filesz > segment.memsz
|| !range_inside(segment.offset, segment.filesz, 0, image_size) || !range_inside(segment.offset, segment.filesz, 0, image_size)
|| !range_inside(segment.vaddr, segment.memsz, region_begin, region_end)) { || !range_inside(segment.vaddr, segment.memsz, region_begin, region_end)) {
@ -770,6 +816,34 @@ static long syscall_uname(user_utsname *result)
return 0; return 0;
} }
static long syscall_ioctl(int fd, unsigned long request, void *argument)
{
enum {
LINUX_TIOCGWINSZ = 0x5413,
LINUX_TIOCNOTTY = 0x5422,
};
if (fd < STDIN_FILENO || fd > STDERR_FILENO) {
return -ENOTTY;
}
switch (request) {
case LINUX_TIOCGWINSZ: {
user_winsize *window = (user_winsize *) argument;
if (!user_buffer(window, sizeof(*window))) {
return -EFAULT;
}
window->rows = 25;
window->columns = 80;
window->pixel_width = 0;
window->pixel_height = 0;
return 0;
}
case LINUX_TIOCNOTTY:
default:
return -ENOTTY;
}
}
static unsigned int vm_protection(int protection) static unsigned int vm_protection(int protection)
{ {
unsigned int flags = 0; unsigned int flags = 0;
@ -1153,6 +1227,9 @@ long typephp_os_syscall_dispatch(syscall_frame *frame)
return syscall_munmap(frame->rdi, frame->rsi); return syscall_munmap(frame->rdi, frame->rsi);
case TYPEPHP_SYS_BRK: case TYPEPHP_SYS_BRK:
return syscall_brk(frame->rdi); return syscall_brk(frame->rdi);
case TYPEPHP_SYS_IOCTL:
return syscall_ioctl((int) frame->rdi, frame->rsi,
(void *) frame->rdx);
case TYPEPHP_SYS_ACCESS: case TYPEPHP_SYS_ACCESS:
return syscall_access_path(AT_FDCWD, (const char *) frame->rdi, return syscall_access_path(AT_FDCWD, (const char *) frame->rdi,
(int) frame->rsi, 0); (int) frame->rsi, 0);

@ -1,33 +0,0 @@
#!/usr/bin/env sh
set -eu
project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
version=7.0.0
commit=7c2a178142625cc9852e59a1a090468c61a62d3b
archive_sha256=56d62366a6a15bc3fd8741bebcb311ca543553f7b734b1e780aeae33691bd621
target="${project_dir}/thirdparty/mlibc"
marker="${target}/.typephp-source-${version}"
if [ -f "${marker}" ] && [ -f "${target}/meson.build" ]; then
exit 0
fi
temporary=$(mktemp -d "${TMPDIR:-/tmp}/typephp-os-mlibc.XXXXXX")
trap 'rm -rf "${temporary}"' EXIT HUP INT TERM
archive="${temporary}/mlibc.tar.gz"
staging="${temporary}/installed"
echo "Downloading mlibc v${version} (${commit})"
curl -L --fail --retry 3 --silent --show-error -o "${archive}" \
"https://github.com/managarm/mlibc/archive/refs/tags/v${version}.tar.gz"
printf '%s %s\n' "${archive_sha256}" "${archive}" | sha256sum -c -
mkdir -p "${staging}"
tar -xzf "${archive}" -C "${staging}" --strip-components=1
test -f "${staging}/LICENSE"
mkdir -p "${project_dir}/thirdparty"
rm -rf "${target}"
mv "${staging}" "${target}"
printf '%s\n' "${commit}" > "${marker}"
echo "Installed mlibc v${version} in ${target}"

@ -6,5 +6,4 @@ tools_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
"${tools_dir}/fetch-openlibm.sh" "${tools_dir}/fetch-openlibm.sh"
"${tools_dir}/fetch-compiler-rt-builtins.sh" "${tools_dir}/fetch-compiler-rt-builtins.sh"
"${tools_dir}/fetch-mlibc.sh"
"${tools_dir}/fetch-toybox.sh" "${tools_dir}/fetch-toybox.sh"

@ -1,14 +0,0 @@
[binaries]
c = 'gcc'
cpp = 'g++'
ar = 'ar'
strip = 'strip'
[host_machine]
system = 'demo'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
[properties]
needs_exe_wrapper = true

@ -1,26 +0,0 @@
#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <time.h>
#include <unistd.h>
_Static_assert(sizeof(uintptr_t) == 8, "TypePHP-OS requires a 64-bit mlibc ABI");
static void typecheck(void)
{
struct dirent entry;
struct stat status;
struct timespec timestamp;
struct utsname identity;
(void) entry;
(void) status;
(void) timestamp;
(void) identity;
}

@ -4,7 +4,6 @@ set -euo pipefail
project_dir=$(cd -- "$(dirname -- "$0")/.." && pwd) project_dir=$(cd -- "$(dirname -- "$0")/.." && pwd)
build_dir="${project_dir}/build/thirdparty-smoke" build_dir="${project_dir}/build/thirdparty-smoke"
meson_command=${MESON:-meson}
if [[ ! -f "${project_dir}/build/libcompiler-rt-builtins.a" ]]; then if [[ ! -f "${project_dir}/build/libcompiler-rt-builtins.a" ]]; then
echo "compiler-rt archive is missing; run make compiler-rt-builtins first" >&2 echo "compiler-rt archive is missing; run make compiler-rt-builtins first" >&2
@ -19,31 +18,6 @@ for symbol in __udivti3 __udivmodti4 __divti3 __modti3 __multi3; do
done done
echo "compiler-rt builtins archive: OK" echo "compiler-rt builtins archive: OK"
if ! command -v "${meson_command}" >/dev/null 2>&1; then
echo "Meson 1.3 or newer is required for the mlibc headers probe" >&2
exit 1
fi
mlibc_build="${build_dir}/mlibc-build"
mlibc_sysroot="${build_dir}/mlibc-sysroot"
rm -rf "${mlibc_build}" "${mlibc_sysroot}"
"${meson_command}" setup \
"${mlibc_build}" "${project_dir}/thirdparty/mlibc" \
--cross-file "${project_dir}/tools/mlibc-demo-cross.ini" \
--prefix=/usr \
-Dheaders_only=true \
-Dposix_option=enabled \
-Dlinux_option=disabled \
-Dglibc_option=disabled \
-Dbsd_option=disabled
DESTDIR="${mlibc_sysroot}" "${meson_command}" install --quiet -C "${mlibc_build}"
compiler_headers=$(gcc -print-file-name=include)
gcc -std=c11 -ffreestanding -fsyntax-only -nostdinc \
-isystem "${mlibc_sysroot}/usr/include" \
-isystem "${compiler_headers}" \
"${project_dir}/tools/mlibc-headers-smoke.c"
echo "mlibc public-header ABI probe: OK"
toybox_source="${project_dir}/thirdparty/toybox" toybox_source="${project_dir}/thirdparty/toybox"
toybox_config="${build_dir}/toybox.config" toybox_config="${build_dir}/toybox.config"
mkdir -p "${build_dir}" mkdir -p "${build_dir}"
@ -67,8 +41,8 @@ if grep -Eq '^(fork|vfork|execv|execve|execvp|posix_spawn)$' "${toybox_symbols}"
fi fi
echo "Toybox selected applets build: OK" echo "Toybox selected applets build: OK"
if grep -qx ioctl "${toybox_symbols}"; then if grep -qx ioctl "${toybox_symbols}"; then
echo "Toybox install: deferred (the common runtime still references unsupported ioctl)" echo "Toybox ABI audit: common runtime imports the supported ioctl entry point"
else else
echo "Toybox install: ABI audit no longer reports ioctl; reassess target linking" echo "Toybox ABI audit: common runtime does not import ioctl"
fi fi
echo "Toybox ABI report: ${toybox_symbols}" echo "Toybox ABI report: ${toybox_symbols}"

@ -1,6 +1,8 @@
#include <dirent.h> #include <dirent.h>
#include <fcntl.h> #include <fcntl.h>
#include <stddef.h> #include <stddef.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>
#include <time.h> #include <time.h>
@ -38,6 +40,7 @@ int main(int argc, char **argv)
struct timespec realtime; struct timespec realtime;
struct timespec monotonic; struct timespec monotonic;
struct timespec resolution; struct timespec resolution;
struct winsize window;
DIR *directory; DIR *directory;
struct dirent *entry; struct dirent *entry;
int found_hello = 0; int found_hello = 0;
@ -100,6 +103,12 @@ int main(int argc, char **argv)
|| getuid() != 0 || geteuid() != 0 || getgid() != 0 || getegid() != 0) { || getuid() != 0 || geteuid() != 0 || getgid() != 0 || getegid() != 0) {
return fail("single-task identity"); return fail("single-task identity");
} }
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window) != 0
|| window.ws_row != 25 || window.ws_col != 80
|| window.ws_xpixel != 0 || window.ws_ypixel != 0
|| ioctl(STDOUT_FILENO, TIOCNOTTY) != -1 || errno != ENOTTY) {
return fail("ioctl console ABI");
}
if (gettimeofday(&wall, NULL) != 0 || wall.tv_sec <= 0 if (gettimeofday(&wall, NULL) != 0 || wall.tv_sec <= 0
|| clock_gettime(CLOCK_REALTIME, &realtime) != 0 || clock_gettime(CLOCK_REALTIME, &realtime) != 0
|| clock_gettime(CLOCK_MONOTONIC, &monotonic) != 0 || clock_gettime(CLOCK_MONOTONIC, &monotonic) != 0

@ -16,6 +16,7 @@ int *__errno_location(void);
#define EISDIR 21 #define EISDIR 21
#define EINVAL 22 #define EINVAL 22
#define EMFILE 24 #define EMFILE 24
#define ENOTTY 25
#define EROFS 30 #define EROFS 30
#define ERANGE 34 #define ERANGE 34
#define ENAMETOOLONG 36 #define ENAMETOOLONG 36

@ -0,0 +1,18 @@
#ifndef TYPEPHP_OS_USER_SYS_IOCTL_H
#define TYPEPHP_OS_USER_SYS_IOCTL_H
/* Linux-compatible terminal request values and winsize layout. */
struct winsize {
unsigned short ws_row;
unsigned short ws_col;
unsigned short ws_xpixel;
unsigned short ws_ypixel;
};
#define TIOCGWINSZ 0x5413UL
#define TIOCSWINSZ 0x5414UL
#define TIOCNOTTY 0x5422UL
int ioctl(int fd, unsigned long request, ...);
#endif

@ -14,6 +14,7 @@
#define SYS_mprotect TYPEPHP_SYS_MPROTECT #define SYS_mprotect TYPEPHP_SYS_MPROTECT
#define SYS_munmap TYPEPHP_SYS_MUNMAP #define SYS_munmap TYPEPHP_SYS_MUNMAP
#define SYS_brk TYPEPHP_SYS_BRK #define SYS_brk TYPEPHP_SYS_BRK
#define SYS_ioctl TYPEPHP_SYS_IOCTL
#define SYS_access TYPEPHP_SYS_ACCESS #define SYS_access TYPEPHP_SYS_ACCESS
#define SYS_getpid TYPEPHP_SYS_GETPID #define SYS_getpid TYPEPHP_SYS_GETPID
#define SYS_exit TYPEPHP_SYS_EXIT #define SYS_exit TYPEPHP_SYS_EXIT

@ -39,9 +39,8 @@ Selected LLVM compiler-rt 128-bit integer helpers are built separately as
Ring-3 `builtins` command deliberately generates signed and unsigned Ring-3 `builtins` command deliberately generates signed and unsigned
division/remainder helper calls and verifies the results at runtime. division/remainder helper calls and verifies the results at runtime.
mlibc and Toybox are currently integration probes rather than runtime Toybox is currently an integration probe rather than a runtime dependency.
dependencies. See `THIRDPARTY.md` for the tested boundary and the generated See `THIRDPARTY.md` for the tested boundary and generated ABI report.
Toybox ABI report.
The archive and each consumer must use the same freestanding ABI flags and The archive and each consumer must use the same freestanding ABI flags and
PHP Nano/PHPX feature definitions. Those shared settings are maintained in the PHP Nano/PHPX feature definitions. Those shared settings are maintained in the

@ -0,0 +1,18 @@
#include <stdarg.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
int ioctl(int fd, unsigned long request, ...)
{
void *argument = 0;
/* TIOCNOTTY has no third argument. The currently supported window-size
* requests use a pointer, as required by the Linux userspace ABI. */
if (request != TIOCNOTTY) {
va_list arguments;
va_start(arguments, request);
argument = va_arg(arguments, void *);
va_end(arguments);
}
return (int) syscall(SYS_ioctl, fd, request, argument);
}

@ -6,7 +6,7 @@
.extern __errno_location .extern __errno_location
syscall: syscall:
# Translate the System V variadic-call ABI to Linux x86-64 syscall # Translate the System V variadic-call ABI to Linux x86-64 syscall
# registers. TypePHP-OS currently enters the kernel through int 0x80. # registers, then use the native 64-bit fast-system-call instruction.
mov %rdi, %rax mov %rdi, %rax
mov %rsi, %rdi mov %rsi, %rdi
mov %rdx, %rsi mov %rdx, %rsi
@ -14,7 +14,7 @@ syscall:
mov %r8, %r10 mov %r8, %r10
mov %r9, %r8 mov %r9, %r8
mov 8(%rsp), %r9 mov 8(%rsp), %r9
int $0x80 syscall
cmp $-4095, %rax cmp $-4095, %rax
jb 1f jb 1f
neg %eax neg %eax

Loading…
Cancel
Save