Syscall interface (INT 30h)

Programs loaded from the filesystem can use INT 30h for OS services. Syscall numbers are defined symbolically as SYS_* constants in kernel/include/constants.asm — programs reference the names, not the numbers.

AH Name Description
00h fs_chmod Set file flags, SI = filename, AL = flags, CF on err
01h fs_mkdir Create subdirectory, SI = name, AX = start sector, CF on err
02h fs_rename Rename or move file, SI = old name, DI = new name, CF on err
03h fs_rmdir Remove an empty directory, SI = name, CF on err
04h fs_unlink Delete a file, SI = filename, CF on err
10h io_close Close fd, BX = fd, CF on error
11h io_dup BX = old_fd; AX = new fd, CF on error
12h io_dup2 BX = old_fd, DX = target_fd; AX = target (closes target first; if old == target, no-op), CF on error
13h io_fstat Get file status, BX = fd; AL = mode, CX:DX = size
14h io_getdents Read directory entries, BX = fd (must be directory), DI = buf, CX = count; AX = bytes written (0 at EOF); CF=1 + AL=ERROR_NOT_DIRECTORY if fd is not a directory; record layout below
15h io_ioctl Device control, BX = fd, AL = cmd, args in other regs per (fd_type, cmd); CF on err
16h io_open Open file, SI = filename, AL = flags, DL = mode; AX = fd, CF on err
17h io_read Read from fd, BX = fd, DI = buf, CX = count; AX = bytes, CF on err; CF=1 + AL=ERROR_IS_DIRECTORY when fd is a directory (use io_getdents)
18h io_seek Seek file fd, BX = fd, ECX = offset, AL = whence (SEEK_SET=0, SEEK_CUR=1, SEEK_END=2); EAX = new position (clamped to [0, size]), CF on err
19h io_write Write to fd, BX = fd, SI = buf, CX = count; AX = bytes, CF on err
20h net_mac Read cached MAC, DI = 6-byte buffer, CF if no NIC
21h net_open Open socket, AL = type (SOCK_RAW=0, SOCK_DGRAM=1), DL = protocol (IPPROTO_UDP=17, IPPROTO_ICMP=1; 0 for raw); AX = fd, CF if no NIC or table full
22h net_recvfrom Recv datagram via fd (UDP or ICMP): BX=fd, DI=buf, CX=len, DX=port (UDP) or ignored (ICMP); reads per-fd SO_RCVTIMEO (0 = non-blocking, >0 = blocks up to that many ms); AX=bytes (0=none), CF err
23h net_sendto Send datagram via fd: BX=fd, SI=buf, CX=len, DI=IP; UDP also uses DX=src port, BP=dst port (ignored for ICMP); AX=bytes, CF err
24h net_setsockopt Set socket option: BX=fd, AL=option_name (SO_RCVTIMEO=1), ECX=value; returns AX=0 on success, -1 on bad fd / wrong fd type / unknown option / negative value, CF err
30h rtc_alarm Arm/disarm interval timer. EBX = ms_until_first_fire (0 = cancel), ECX = ms_interval (0 = one-shot). EAX = ms remaining on prior alarm (0 if none). CF clear, no error path. Fires SIGALRM via SIGNAL_TAIL_CHECK.
31h rtc_datetime Get wall-clock time, EAX = unsigned seconds since 1970-01-01 UTC
32h rtc_millis Get milliseconds since boot, EAX = ms (wraps at ~49.7 days)
33h rtc_sleep Sleep ECX milliseconds (hlt between PIT ticks); returns CF=1 + AL=ERROR_INTERRUPTED if a signal (SIGINT or SIGALRM) is pending
34h rtc_uptime Get uptime in seconds, EAX = elapsed seconds (wraps at ~136 yr)
40h video_map Map mode-13h framebuffer into program PD; EAX = user-virt (0xB8000000) on success, EAX = 0 + CF on PT-allocation failure
F0h sys_break Set/query program break, EBX = new break (0 to query); EAX = resulting break
F1h sys_exec Execute program, SI = filename, DX = argv (char **, 0 = none); CF on error
F2h sys_exit Reload and return to shell
F3h sys_pipeline2 Run two pipeline children: SI = left_path, DI = right_path, DX = left_argv (char **, 0 = none), CX = right_argv (char **, 0 = none); cmd1’s stdout is piped to cmd2’s stdin; AX = cmd2 wait status on success; CF on error (AL = ERROR_*). Each argv is a NULL-terminated char ** array in the shell’s user-virt; the kernel validates it under the shell’s PD up front and stays on the shell’s PD across both child builds. For each child, stage_user_argv re-walks the array under that PD and copies the strings directly into the child’s stack page through a kmap alias, building the Linux SysV i386 startup frame (argc / argv / NULL / empty envp) in place — no intermediate kernel buffer. Only callable from the shell (slot_a); nested pipelines are rejected with ERROR_INVALID.
F4h sys_reboot Reboot
F5h sys_shutdown Shutdown
F6h sys_signal Register signal handler. EBX = signum (SIGINT, SIGPIPE, or SIGALRM), ECX = handler (SIG_DFL=0, SIG_IGN=1, or user-virt addr ≥ PROGRAM_BASE); EAX = previous handler. CF set + AL=ERROR_INVALID on bad signum/addr
F7h sys_sigreturn Restore sigcontext from user stack at [user_esp + 4]; never returns through the regular path — resumes the saved EIP/EFLAGS/ESP/registers. Used only via the libbboeos trampoline at the end of a signal handler

/dev/midi ioctls (FD_TYPE_MIDI = 6)

AL Name Behavior
00h MIDI_IOCTL_DRAIN block via sti/hlt until the kernel ring drains (head == tail), AX = 0, CF clear

Wire format on /dev/midi is 6-byte commands: (delay_lo, delay_hi, bank, reg, value, reserved).

Error codes

When a syscall sets CF on return, AL holds one of these codes (symbolic names in kernel/include/constants.asm):

AL Name Meaning
01h ERROR_DIRECTORY_FULL No free directory entries (copy/create)
02h ERROR_EXISTS Destination name already exists (rename/copy)
03h ERROR_FAULT Bad user pointer: out of user range, wraps, or filename has no NUL within MAX_PATH
04h ERROR_INTERRUPTED Cooperative-interrupt return (SIGINT or SIGALRM pending during blocking syscall) — maps to EINTR in libc
05h ERROR_INVALID Invalid argument (bad signum, out-of-range handler address, etc.)
06h ERROR_NOT_EMPTY Directory is not empty (rmdir)
07h ERROR_NOT_EXECUTE File exists but is not executable (exec)
08h ERROR_NOT_FOUND File not found
09h ERROR_PROTECTED File is protected (rename/chmod)
0Ah ERROR_IS_DIRECTORY io_read called on a directory fd (use io_getdents instead)
0Bh ERROR_NOT_DIRECTORY io_getdents called on a non-directory fd

io_getdents record layout

Each record is variable-length, packed back-to-back in the user buffer:

Offset Size Field Notes
0 4 d_ino bbfs: file’s start sector; ext2: full 32-bit inode
4 2 d_reclen byte count of THIS record incl. padding (advance by)
6 1 d_type DT_REG = 8, DT_DIR = 4, DT_UNKNOWN = 0
7 N d_name null-terminated; record padded so next record’s d_ino is 4-byte aligned

d_reclen = round_up(7 + namelen + 1, 4). Walk the buffer by adding d_reclen to the cursor; stop when cursor == bytes_returned.


This site uses Just the Docs, a documentation theme for Jekyll.