Login
Documentation
Login

Zephyr survey: Glint's platform interface and Zephyr's drivers

This survey records what was found before Track Z was planned. Where it numbers Phase 2's slices or says hosted mode stays the default, the plan has since moved on: the exit is slice 2.6, native mode is the default from 2.10, and the seam is Track Z (docs/phases/Z-zephyr-seam.md). The findings about Zephyr itself stand.

Glint reaches hardware only through an EFI-shaped platform interface (PLAN.md, Concept). Today every protocol behind it comes from firmware. The plan's second kind of provider is upstream Zephyr C drivers compiled into Glint through compat/zephyr (principle 5), with the generators that replace Zephyr's Python build tools (slice 6.8), the shim (6.9) and the first provider (6.10). This survey checks, before that work starts, how well Glint's protocols map onto Zephyr's driver APIs. It also checks what Zephyr drivers expect from the kernel underneath them, and what the smallest useful Zephyr seam needs: Zephyr's PL011 and NS16550 UART drivers in poll mode, carrying Glint's console across ExitBootServices in Phase 2.

Paths in include/, drivers/, kernel/, scripts/, dts/ and subsys/ are in the Zephyr tree at the pinned release. Line numbers refer to that release. Glint paths are given from the checkout root.

For readers new to Glint or Zephyr

Glint's side. A protocol is a C struct of function pointers found by GUID through LocateProtocol (boot/protocol.cr, boot/efi.cr). Calls are synchronous: the caller passes its own buffer, the call returns when the work is done or reports EFI_NOT_READY, and every result is an EFI_STATUS (boot/console.cr polls ReadKeyStroke, and fs/efi_file.cr opens and reads files through SIMPLE_FILE_SYSTEM). Nothing calls back into Glint. Under U-Boot nothing uses interrupts either.

Zephyr's side. A driver instance is a struct device (include/zephyr/device.h:513): a name, a config pointer (usually read-only), a data pointer, a state record and a pointer to a class API struct such as struct uart_driver_api (include/zephyr/drivers/uart/uart_internal.h:39). Instances are created at compile time by DEVICE_DT_DEFINE (device.h:259), one per devicetree node with status = "okay" and a matching compatible. Every property a driver reads comes from macros in a generated header, devicetree_generated.h, and every option from a generated autoconf.h. Zephyr's kernel then runs each device's init function in an order set by linker sections, and drivers call kernel services (k_spin_lock, k_sem, k_work, IRQ_CONNECT, logging) as they need them.

The seam. compat/zephyr is where Glint supplies those generated headers and kernel services, so that unmodified Zephyr driver sources compile and run, and where each driver is wrapped as an EFI-shaped protocol. The plan intends Glint to implement Zephyr's API, not to copy Zephyr's kernel implementation.

Summary

1. The pin

Item Value
Release v4.4.2, the latest release on GitHub's releases page (VERSION: 4.4.2)
Commit dccb09599635bdff17633fa7e9dab014b91dce90 ("release: Zephyr 4.4.2"); the annotated tag object is 671f64aa79924606253238f801c494c84b02c2a0
Clone git clone --depth 1 --branch v4.4.2 https://github.com/zephyrproject-rtos/zephyr.git /workspace/zephyr-src
PR #117311 ("drivers: i2c: i2c_mspm0: add MSPM0 I2C driver") Not in v4.4.2. The pull request is still open and unmerged, and drivers/i2c/ has no i2c_mspm0.c. It is not in v4.5.0-rc3 either

Three facts about this pin bear on later work:

2. Interface map

"Fit" means: direct (a one-to-one call with only status translation), adapter (Glint code reshapes calls or data but the meaning carries over) or mismatch (the two sides disagree about who drives the work, who owns memory or what exists at all).

Glint protocol Zephyr API that would provide it Fit Specific mismatches
Console output (SIMPLE_TEXT_OUTPUT.OutputString) uart_poll_out (include/zephyr/drivers/uart.h:397) adapter, thin One byte per call against a NUL-terminated CHAR16 string, so the adapter converts UCS-2 and \n. SetAttribute, SetCursorPosition, QueryMode and ClearScreen have no UART equivalent; the adapter can emit ANSI sequences or return EFI_UNSUPPORTED. poll_out spins until the FIFO has room, with no timeout (uart_pl011.c:224, uart_ns16550.c:986)
Console input (SIMPLE_TEXT_INPUT.ReadKeyStroke, WaitForKey) uart_poll_in (uart.h:359); later the interrupt API, uart_irq_callback_user_data_set (uart.h:710) and uart_fifo_read (uart.h:524) direct for ReadKeyStroke; adapter for keys and events poll_in returns 0 or −1 for "nothing yet", which maps to EFI_NOT_READY. Terminal escape sequences (arrows, Delete) must become EFI scan codes. WaitForKey is an EFI event, which Glint must supply itself. In interrupt mode the callback runs in interrupt context and the driver keeps no receive buffer, so the adapter owns a ring buffer
SERIAL_IO (SetAttributes, SetControl, GetControl, Read, Write, Mode) uart_configure / uart_config_get (uart.h:434, 451; need CONFIG_UART_USE_RUNTIME_CONFIGURE), uart_line_ctrl_set / _get (uart.h:942, 957; CONFIG_UART_LINE_CTRL), poll, interrupt or async transfer adapter with mismatches The enumerations differ. EFI parity counts Default=0, None=1, Even=2, Odd=3; Zephyr counts NONE=0, ODD=1, EVEN=2 (uart.h:80). EFI stop bits are Default=0, One=1, OneFive=2, Two=3; Zephyr's are 0.5=0, 1=1, 1.5=2, 2=3 (uart.h:89). Data bits are a count in EFI and an enum in Zephyr (uart.h:97). Zephyr has no "Default". Baud is UINT64 in EFI and uint32_t in Zephyr (uart.h:122). EFI's Timeout (µs) and ReceiveFifoDepth have no Zephyr field. Read/Write take a buffer and a timeout; Zephyr offers bytes (poll), FIFO callbacks (interrupt) or callback-completed transfers (async). In SBSA mode the PL011 refuses configure with -ENOTSUP (uart_pl011.c:274–278)
Async UART (for SERIAL_IO under load) uart_callback_set, uart_tx, uart_rx_enable, uart_rx_buf_rsp (uart.h:754, 775, 834, 882); events in struct uart_event (uart.h:293) mismatch Buffers stay owned by the driver until UART_TX_DONE or UART_RX_BUF_RELEASED. The driver asks for the next receive buffer from interrupt context. Timeouts need k_work_delayable (the NS16550 async path, uart_ns16550.c:306, 317). An EFI-shaped synchronous Read must block on a semaphore, which needs a scheduler (Phase 8), or poll for completion
BLOCK_IO (ReadBlocks, WriteBlocks, FlushBlocks, Media) disk_access_read / _write / _ioctl (include/zephyr/storage/disk_access.h:82, 100, 134), backed by struct disk_operations (include/zephyr/drivers/disk.h:116); NVMe in drivers/disk/nvme/; sdhc_request (include/zephyr/drivers/sdhc.h:322) is one layer lower adapter with mismatches Sector numbers and counts are uint32_t, so a 512-byte-sector disk tops out at 2 TiB, against EFI's 64-bit EFI_LBA. Disks are found by name string and registered at run time (disk_access_register, disk.h:134), not by handle. EFI passes a byte count, Zephyr a sector count. EFI's IoAlign meets driver DMA rules and cache handling (NVMe calls k_mem_phys_addr ten times). MediaId, MediaChanged and removable media have only DISK_STATUS_*. Calls block the caller while the driver waits on a k_sem. An sdhc driver is not a disk: it needs subsys/sd and drivers/disk/sdmmc_subsys.c above it
SIMPLE_FILE_SYSTEM fs_open, fs_opendir (include/zephyr/fs/fs.h:315, 518) over FatFs (the separate fatfs west module, west.yml:144–146) or LittleFS mismatch This is a subsystem with a mount table and path strings, not a driver, and its FAT code lives outside the Zephyr tree. Glint should keep its own FAT over BLOCK_IO (Phase 10) and not map this protocol to Zephyr
SIMPLE_NETWORK (Transmit, Receive, GetStatus) struct ethernet_api (include/zephyr/net/ethernet.h:538), whose send takes a struct net_pkt (line 599); received frames are pushed with net_recv_data (include/zephyr/net/net_core.h:105). Example: drivers/ethernet/eth_virtio_net.c (ETH_NET_DEVICE_DT_INST_DEFINE at line 245, net_recv_data at 172) mismatch, the worst SNP is pull: the caller polls Receive with its own buffer. A Zephyr Ethernet driver pushes each frame into Zephyr's network stack from interrupt or thread context, in net_pkt/net_buf buffers from Zephyr's pools. The device must also be a net_if, which is registered in an iterable linker section (include/zephyr/net/net_if.h:3512). The adapter must implement net_pkt allocation, net_recv_data and a stub net_if, plus a frame queue
GRAPHICS_OUTPUT (QueryMode, SetMode, Blt, FrameBufferBase) display_write (include/zephyr/drivers/display.h:543) with struct display_buffer_descriptor (line 302), display_get_framebuffer (line 416); QEMU-relevant drivers are drivers/display/display_qemu_ramfb.c and display_intel_multibootfb.c adapter GOP's four Blt operations become rectangle writes and reads. Pixel formats differ: GOP's 32-bit BGRX against Zephyr's bitmask enum (PIXEL_FORMAT_ARGB_8888, RGB_888, RGB_565, …, display.h:49–107). There is no mode setting, since the resolution comes from the devicetree. A linear framebuffer exists only where the driver exposes one
Timers (CreateEvent, SetTimer, WaitForEvent, Stall) the system-timer driver interface sys_clock_set_timeout, sys_clock_elapsed, sys_clock_announce (include/zephyr/drivers/timer/system_timer.h:157, 221, 205) with drivers/timer/arm_arch_timer.c, apic_tsc.c, hpet.c; counter_set_channel_alarm (include/zephyr/drivers/counter.h:816) for hardware counters; k_timer_start, k_busy_wait (include/zephyr/kernel.h:1969, 741) for drivers mismatch of direction Zephyr's timer drivers exist to drive Zephyr's own tick: they call sys_clock_announce into kernel/timeout.c. Glint owns the Arm generic timer and the LAPIC itself (slice 6.2, principle 6). So compat/zephyr consumes Glint's time: it implements k_busy_wait, k_uptime_get, k_timer and k_work_delayable on Glint's timers and links no Zephyr timer driver. Units differ: EFI uses 100 ns, Zephyr uses ticks (CONFIG_SYS_CLOCK_TICKS_PER_SEC) or counter ticks that wrap at a top value, and counter alarms call back in interrupt context, where EFI uses events and task priority levels
Page allocation (AllocatePages, FreePages, AllocatePool) no driver API. Drivers consume k_malloc (kernel.h:6409), k_heap_alloc (kernel.h:6178), k_mem_map (include/zephyr/kernel/mm.h:192), k_mem_phys_addr (include/zephyr/kernel/internal/mm.h:116) and k_mem_map_phys_bare (kernel/internal/mm.h:225) inverse direction Nothing in Zephyr provides pages to Glint. Glint provides memory to Zephyr drivers: compat/zephyr implements these over Glint's page allocator (Phase 3), including DMA-safe allocation, virtual-to-physical translation and uncached device mappings (K_MEM_CACHE_NONE)
I2C master (Track S; the plan shapes it on the UEFI PI I2C master protocol) i2c_transfer with struct i2c_msg (include/zephyr/drivers/i2c.h:871, 185), i2c_transfer_cb (line 925) adapter, close A PI StartRequest with a request packet of operations maps onto an array of i2c_msg. PI completes through an optional event; Zephyr is synchronous or callback-based

3. The core services drivers expect

The table separates three verdicts. Implement means compat/zephyr provides the API, with declarations in C headers and implementations as Crystal fun exports. In most rows the header is Zephyr's own and only the out-of-line functions are Glint's. Stub means a trivial body or an empty generated file is enough. Avoid means the need disappears through driver choice, devicetree content or Kconfig. "Copy" (linking Zephyr's kernel/ sources) is not proposed for any row. Where a Zephyr header holds the API as static inline code over Zephyr internals, implementing the API means replacing that header, and the row says so.

Service What drivers use Verdict for Phase 2 (UART, poll) Verdict for Phase 6 and later
Device model struct device (device.h:513), struct device_state (device.h:458), DEVICE_DT_DEFINE (device.h:259) via Z_DEVICE_DEFINE (device.h:1317), DEVICE_DT_GET (device.h:317), DEVICE_API (device.h:1365) Use Zephyr's header unchanged. Implement the init runner with the semantics of do_device_init (kernel/device.c:18–50: call ops.init, store the positive errno in state->init_res, set state->initialized) and z_impl_device_is_ready (kernel/device.c:186) Same. Avoid device_get_binding, DEVICE_API_IS (device.h:1387), device_handle_get (device.h:563) and CONFIG_DEVICE_DEPS, which all walk the linker-built device list
Init levels PRE_KERNEL_1, PRE_KERNEL_2, POST_KERNEL, APPLICATION with a priority and a devicetree-ordinal sub-priority, encoded in section names (include/zephyr/init.h:82–95, Z_INIT_ENTRY_SECTION at init.h:111) and sorted by the linker script (include/zephyr/linker/linker-defs.h:70–74, run by z_sys_init_run_level at kernel/init.c:220) Implement ordering with a generated device table in devicetree dependency order. Both UART drivers are PRE_KERNEL_1 at CONFIG_SERIAL_INIT_PRIORITY Ordering across classes needs level and priority, which live in driver macros and not in the devicetree. Read them from the section names of the compiled objects (.z_init_PRE_KERNEL_1_P_50_SUB_00002_) and generate the table. Avoid SYS_INIT users
Devicetree macros include/zephyr/devicetree.h (5,915 lines, macros only) over devicetree_generated.h, made by scripts/dts/gen_defines.py (1,245 lines) on edtlib.py (3,694 lines) and dtlib.py (2,304 lines) Implement the generator in Crystal on cleave's devicetree code, plus Zephyr's YAML bindings. Use devicetree.h unchanged The generator grows to interrupts with named cells, ranges, pinctrl, chosen, aliases and node labels
Kconfig autoconf.h; DT_HAS_<compat>_ENABLED symbols from scripts/dts/gen_driver_kconfig_dts.py:18–21; $(dt_*) functions (39 of them in scripts/kconfig/kconfigfunctions.py, e.g. dt_compat_enabled at line 817, dt_compat_any_has_prop at line 881) Implement autoconf.h from a Crystal table of about 15 to 20 symbols per architecture, checked against the Kconfig files that define them The full tree needs a board and SoC (Kconfig.zephyr), which Glint does not have by principle 6. Keep the table for native Glint. Solve the full tree only for Track S, where Zephyr's own board exists
Syscalls __syscall prototypes (637 declarations in 89 headers) and the generated zephyr/syscalls/*.h (template at scripts/build/gen_syscalls.py:229–310) Implement a stub generator for CONFIG_USERSPACE=n: each wrapper calls z_impl_<name>. Empty syscall_list.h, kobj-types-enum.h and heap_constants.h suffice Unchanged. Glint never enables Zephyr userspace
k_spinlock k_spin_lock / k_spin_unlock / K_SPINLOCK (include/zephyr/spinlock.h:181, 301, 440); static inline over arch_irq_lock Use as is: on one CPU, masking interrupts is exactly slice 6.9's planned stand-in. NS16550 takes it in poll_in and poll_out (uart_ns16550.c:958, 984); PL011 does not Phase 7 maps it to Glint's locks. Because it is inline, that means replacing spinlock.h in compat/zephyr
k_mutex, k_sem k_mutex_lock, k_sem_take (kernel.h:3525, 3743), syscalls to z_impl_* not needed Implement in Crystal. Before Phase 8 there are no threads, so a blocking take must spin with interrupts enabled. Block and network drivers depend on this
Work queues, timers k_work_init, k_work_submit, k_work_schedule (kernel.h:3942, 4007, 4341), k_timer_start (kernel.h:1969) not needed Implement on Glint's timer (6.2) and a deferred-work list. Async UART, NVMe and virtio-net use them
Threads k_thread_create (kernel.h:444) not needed Avoid drivers and subsystems that start their own threads (the USB host stack does: subsys/usb/host/usbh_core.c:195). Phase 8 can implement it if one is worth having
IRQ_CONNECT, irq_enable include/zephyr/irq.h:48, 298. On arm64, ARCH_IRQ_CONNECT (include/zephyr/arch/arm64/irq.h:85–89) places a struct _isr_list in section .intList (include/zephyr/sw_isr_table.h:282–285), which scripts/build/gen_isr_tables.py turns into the vector table. On x86-64 it calls arch_irq_connect_dynamic (include/zephyr/arch/x86/intel64/arch.h:89–93) Avoid: CONFIG_UART_INTERRUPT_DRIVEN=n removes every reference Implement arch_irq_connect_dynamic, arch_irq_enable and z_arm64_irq_priority_set on 6.1's dispatcher. On arm64 the static .intList path must become a generated table, read from the objects' .intList sections, or the dynamic path
Logging LOG_MODULE_REGISTER (include/zephyr/logging/log.h:771; NS16550 calls it unconditionally at uart_ns16550.c:45) Avoid: with CONFIG_LOG unset it registers nothing (log.h:723–728) and LOG_ERR is (void)0 (log.h:857) Route to Glint's console through CONFIG_LOG_CUSTOM_HEADER (log.h:836–839), which includes a Glint-supplied zephyr_custom_log.h
pinctrl PINCTRL_DT_INST_DEFINE, pinctrl_apply_state (include/zephyr/drivers/pinctrl.h:257, 345) Avoid: no pinctrl-0 in the build devicetree, CONFIG_PINCTRL unset Needed for SoC UARTs in native mode, e.g. UART_NS16550_TI_K3 selects PINCTRL (drivers/serial/Kconfig.ns16550:71). This is where No BSPs meets Zephyr's SoC drivers
clock_control clock_control_on, clock_control_get_rate (include/zephyr/drivers/clock_control.h:173, 274) Avoid by devicetree: the PL011's clocks phandle points to a fixed-clock (uart_pl011.c:713–717, 805–807); the NS16550 has clock-frequency (uart_ns16550.c:2061–2072) As pinctrl
reset reset_line_toggle_dt (include/zephyr/drivers/reset.h:375) Avoid: no resets property, CONFIG_RESET unset As pinctrl
Power management pm_device_driver_init Avoid: with CONFIG_PM_DEVICE unset it is inline (include/zephyr/pm/device.h:814–830) and calls the driver's own action for TURN_ON then RESUME. For the NS16550 that is uart_ns16550_configure Keep unset
Device MMIO DEVICE_MMIO_ROM, DEVICE_MMIO_MAP, DEVICE_MMIO_GET (include/zephyr/sys/device_mmio.h:265, 320–327) Avoid with CONFIG_MMU unset: the driver uses the physical address directly (device_mmio.h:37–39), which matches firmware's identity map With CONFIG_MMU=y, device_map calls k_mem_map_phys_bare (device_mmio.h:97–104), which Glint implements on its Phase 3 page tables. The probe confirmed this is then the PL011's only new external symbol
Iterable sections and other linker tricks STRUCT_SECTION_ITERABLE (include/zephyr/sys/iterable_sections.h:42–44, section names from include/zephyr/toolchain/gcc.h:197–201), .z_devstate (device.h:947–949), .z_init_*, .intList, net_if_dev Avoid iteration: generate tables instead. The sections still compile and link on COFF, and nothing reads them Grows with every subsystem. See risk 3
Atomics include/zephyr/sys/atomic.h with CONFIG_ATOMIC_OPERATIONS_BUILTIN header only, compiler builtins same
C library memcpy, memset; headers from lib/libc/minimal/include Stub: Glint supplies the two functions grows with drivers

4. The UART probe in detail

PL011 (drivers/serial/uart_pl011.c, compatibles arm,pl011 and arm,sbsa-uart)

NS16550 (drivers/serial/uart_ns16550.c, I/O-port access)

What the probe showed

Both drivers were compiled with clang for Glint's two targets (the flags are in the appendix). Each was linked with a 52-line C file standing in for compat/zephyr into a PE32+ application, using the same ld.lld -flavor link /subsystem:efi_application step as tools/glint_build.cr. The images were booted from an ESP written by Glint::Run::ESP under the project's QEMU and U-Boot builds:

zephyr PL011: poll_out in boot services          (aarch64, QEMU virt)
zephyr PL011: poll_out after ExitBootServices
zephyr NS16550: poll_out in boot services        (x86_64, QEMU q35)
zephyr NS16550: poll_out after ExitBootServices

The stand-in for the shim is small enough to quote. It shows the device table that replaces linker-section iteration:

static const struct device *const glint_devices[] = { DEVICE_DT_GET(PROBE_NODE) };

static void glint_zephyr_init(void)
{
	for (size_t i = 0; i < ARRAY_SIZE(glint_devices); i++) {
		const struct device *dev = glint_devices[i];
		int r = dev->ops.init != NULL ? dev->ops.init(dev) : 0;
		dev->state->init_res = (uint8_t)(r < 0 ? -r : r);
		dev->state->initialized = true;
	}
}

Getting there exposed four toolchain findings. The first three needed fixes, which the probe applied:

  1. include/zephyr/kernel.h:37 asserts sizeof(intptr_t) == sizeof(long), which fails on LLP64. The probe removed that line in an overlay copy of kernel.h.
  2. On *-unknown-windows, clang defines _MSC_VER and not __GNUC__. include/zephyr/toolchain.h:49–50 still picks toolchain/llvm.h from __llvm__, but 16 Zephyr headers test __GNUC__ directly (toolchain/gcc.h:22, 85, 145, 244; sys/rb.h falls back to alloca.h), so -fgnuc-version=4.2.1 is needed. With it added, the default -fms-compatibility remains, and its preprocessor makes IS_EMPTY() return 0 (include/zephyr/sys/util_macro.h). DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets) then evaluated to 1 for a node with no resets, so the NS16550 silently compiled in its reset path and referenced reset_line_toggle. Adding -fno-ms-compatibility fixed it. This failure gives no error.
  3. On x86-64, arch_irq_lock (include/zephyr/arch/x86/intel64/arch.h:57–64) does pushfq; cli; popq into an unsigned long. On LLP64 that is 4 bytes, so the 8-byte popq overwrote half of the return address. The first character printed, then the machine triple-faulted (QEMU -d int: page fault at RIP 0x2). Widening key to unsigned long long fixed it.
  4. lld-link keeps each Zephyr section as a separate 4 KiB-aligned PE section, with its name cut to eight characters (._device, ._uart_d, .z_devst, .z_init_). This is harmless but wasteful. /merge: folded .z_devstate and .z_init_… into .data and .rdata; it did not fold ._device.static.1_50_ or ._uart_driver_api.static.… in this test.

Minimal shim surface for the two drivers in poll mode

Kind Items
Generated headers zephyr/devicetree_generated.h; zephyr/autoconf.h; zephyr/syscalls/{device,uart,kernel,clock,errno_private,kobject,log_msg,reset}.h (only device.h and uart.h need real wrappers here: device_is_ready, uart_poll_in, uart_poll_out); empty zephyr/syscall_list.h, zephyr/kobj-types-enum.h, zephyr/heap_constants.h
Devicetree macro families the two drivers reach per node: _PATH, _FULL_NAME, _PARENT, _ORD, _ORD_STR_SORTABLE, _EXISTS, _STATUS_okay, _COMPAT_MATCHES_<compat>, _REG_NUM, _REG_IDX_0_EXISTS, _REG_IDX_0_VAL_ADDRESS, _REG_IDX_0_VAL_SIZE, _P_<prop> and _P_<prop>_EXISTS for every binding property (booleans always present as 0 or 1), _P_compatible_IDX_<i>[_STRING_TOKEN|_STRING_UPPER_TOKEN|_EXISTS], _P_compatible_LEN, _P_clocks_IDX_<i>_PH; identifiers DT_N_INST_<i>_<compat>; global DT_FOREACH_HELPER, DT_FOREACH_OKAY_HELPER (used by device.h:1352 in every file), DT_COMPAT_HAS_OKAY_<compat>, DT_N_INST_<compat>_NUM_OKAY, DT_FOREACH_OKAY_<compat>, DT_FOREACH_OKAY_VARGS_<compat>, DT_FOREACH_OKAY_INST_<compat>, DT_FOREACH_OKAY_INST_VARGS_<compat>; DT_DEBRACKET_INTERNAL. Names follow scripts/dts/gen_defines.py:72–104, 235–256, 338–388, 458–475, 680–760, 813–831 and 1024–1116
Functions Glint provides z_impl_device_is_ready; memcpy, memset; the device-table init runner; later k_mem_map_phys_bare (with CONFIG_MMU)
Patches to the pinned tree kernel.h:37 (the LP64 assertion); arch/x86/intel64/arch.h:59 (key width). Both are one line, kept as separate patch files as principle 5 requires
Compile flags -fno-ms-compatibility -fgnuc-version=4.2.1 -std=gnu11 -ffreestanding -fno-builtin -mno-red-zone -imacros autoconf.h -D__ZEPHYR__=1 -DKERNEL -fno-strict-aliasing -fno-common (the last four follow Zephyr's CMakeLists.txt:151–152, 376 and cmake/compiler/gcc/compiler_flags.cmake:156, 236; the probe used -include and needed none of the four to pass), plus -I for the generated directory, include/, lib/libc/minimal/include and drivers/serial

Estimated size (non-spec lines, Crystal unless stated):

Piece Early (Phase 2) Notes
devicetree_generated.h generator: DTB or DTS through cleave, YAML bindings with include: merging, property types (int, boolean, string, string-array, array, phandle, phandles, phandle-array), defaults, dependency ordinals, the macro families above 600–800 Python reference is about 5,000 lines (gen_defines.py + edtlib.py); a full Crystal port is likely 2,000–2,500
Build devicetree inputs: QEMU virt (the 2.4 fixture) plus an overlay; PC COM1 node about 30 (DTS)
autoconf.h from a symbol table, checked against the Kconfig files with Cleave::Kconfig 120–180 full tree with Kconfig.dts, rsource/osource, $(dt_*): 800–1,200
Syscall stub generator 120–150
Shim runtime: device table emitter and init runner, z_impl_device_is_ready, status and errno mapping 100–150
Console provider: SIMPLE_TEXT_OUTPUT and SIMPLE_TEXT_INPUT over uart_poll_out/uart_poll_in 120–160
Build glue in tools/glint_build.cr: Zephyr source list, flags, patch application 60–100
Total about 1,150–1,550 five to seven slices at the plan's ~200-line target; the devicetree generator alone needs two or three

For scale, the probe's hand-written inputs were a 70-line and a 45-line devicetree header, a 14-line and an 18-line autoconf.h, two 3- to 5-line syscall headers and the 52-line stand-in.

What is hard in these two drivers

5. Risks to the mapping

Risk 1: object format and C dialect (the biggest). Zephyr's C assumes LP64 and GNU C. Glint's images are PE/COFF from *-unknown-windows, which is LLP64 and MSVC-compatible by default. The probe hit an assertion, a silent macro misevaluation and a runtime stack corruption, and it was driving only two small drivers. Other latent cases are visible in the headers: ROUND_UP/ROUND_DOWN cast pointers to unsigned long (include/zephyr/sys/util.h:318–326), which truncates addresses above 4 GiB on LLP64; atomic_t is long (include/zephyr/sys/atomic_types.h:15); and the arch headers use unsigned long 49 times, some in inline assembly that no warning can check. There are two ways to resolve it: (a) keep COFF, compile Zephyr with -fno-ms-compatibility -fgnuc-version=4.2.1, carry small LP64 patches, and build all Zephyr code with -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=shorten-64-to-32, replacing arch inline assembly that holds long with shim code; or (b) build Zephyr objects, or the whole kernel, as LP64 ELF with ld.lld and a linker script, and write the PE header with a Crystal tool, as Linux's arm64 Image does. Option (b) also gives Zephyr its linker script (risk 3), but it changes Glint's build and Crystal target. x86_64-pc-cygwin is LP64 COFF, but it has no aarch64 counterpart, so it does not solve the problem.

Risk 2: compile-time devicetree against run-time discovery. Zephyr fixes addresses, instance counts and clock wiring when it compiles. Glint finds its hardware at boot (slice 2.4) and promises to run on any EBBR or x86 UEFI machine without board code. A build devicetree per machine class (QEMU virt, PC COM1) works if Glint checks at run time that the firmware's description agrees with the build node: same compatible, same reg. If it does not agree, Glint stays in hosted mode (slice 2.6 already has that decision point). Later, CONFIG_MMU=y routes every MMIO driver's base through k_mem_map_phys_bare, where Glint can substitute the address it discovered. That is late binding at one choke point, without patching const config structs (the NS16550's config is static const, uart_ns16550.c:2098).

Risk 3: linker sections and generated tables. Device lists, init levels, DEVICE_API_IS, device_get_binding, the arm64 interrupt table (.intList and gen_isr_tables.py), net_if lists and log modules all rely on ELF linker-script sorting with start and end symbols. lld-link for EFI applications has no linker script. Each new subsystem therefore needs either a generated table built from object-file section names, or a COFF-specific section scheme. In COFF, lld-link sorts .name$suffix sections alphabetically within a group, which can play the role of SORT(). The hook is __TOOLCHAIN_CUSTOM__ (include/zephyr/toolchain.h:36–40), but Z_INIT_ENTRY_SECTION (init.h:111) and .z_devstate (device.h:949) name sections directly and would still need patches.

Risk 4: who drives the work. EFI protocols are synchronous and use caller-owned buffers. Zephyr's interrupt and async APIs call back from interrupt context and own buffers until a completion event, and block and network drivers wait on k_sem, which needs threads. Before Phase 8, compat/zephyr can only spin in k_sem_take. So the 6.10 provider runs with a spinning semaphore until Phase 8, and its benchmark should say so.

Risk 5: the network stack. No Ethernet driver stands alone. Wrapping one as SIMPLE_NETWORK means imitating net_pkt, net_buf and net_if (section 2). Prefer, in order: Glint's own virtio-net (Phase 12, a standard device under principle 6); the Linux guest; and only then a Zephyr Ethernet driver behind a deliberately small net_pkt imitation.

Risk 6: Kconfig without a board. Zephyr's Kconfig tree expects a board and SoC. Native Glint has none. Keep a short, checked symbol table for native Glint. Run the full tree only in Track S, where Zephyr's own board exists.

Risk 7: error conventions. Zephyr returns negative errno values, plus −1 from poll_in and positive errno values in device_state.init_res. EFI returns EFI_STATUS with the high bit set. Write one mapping table in the shim and use it everywhere.

Keeping the seam thin. These choices avoid pulling in more of Zephyr's core:

6. Recommendation

What a minimal early 6.8 should contain. These pieces come forward into Phase 2 ahead of slice 2.5:

What a minimal early 6.9 should contain:

The Phase 2 demonstration is 2.5's own: bin/glint-run aarch64 --expect "Left boot services" (and x86_64), with the line written by Zephyr's driver. Hosted mode stays the default, so the native path is chosen by 2.6 only when the firmware's UART matches the build node, or when it is explicitly selected.

What to leave for Phase 6: IRQ_CONNECT, irq_enable and the interrupt-driven UART (6.3); every k_* object beyond the spinlock; the async API; logging through LOG_CUSTOM_HEADER; pinctrl, clocks and resets; the full devicetree generator (interrupt cells, ranges, pinctrl, chosen) and the full Kconfig tree; block and network providers. Before 6.10 is refined, also move to v4.5.0 for drivers/disk/virtio_blk.c, or choose NVMe or virtio-console as the first provider. CONFIG_MMU with k_mem_map_phys_bare belongs with Phase 3's page tables.

What to check first. The riskiest assumption is that Zephyr C built for Glint's PE/COFF LLP64 target behaves as it does on LP64. The two drivers here pass with two patches and two flags. The cheap next check, before any generator is written, is to compile the drivers Phase 6 is likely to want with the warnings in risk 1 promoted to errors: uart_pl011.c and uart_ns16550.c with interrupts, virtio_mmio.c, virtio_pci.c, virtqueue.c, drivers/disk/nvme/*.c and, at v4.5.0, virtio_blk.c. Hand-written generated headers are enough for this, as the probe shows. Also grep the arm64 and x86-64 arch headers for inline assembly that writes into a long. If the list is short, choose option (a) of risk 1. If it is not, decide on the ELF build path before slice 2.5.

7. USB host: what Zephyr offers, and the guest-facing shim

The maintainer has set the direction: Glint owns every xHCI controller and the whole USB tree behind it, including hubs and every device class. xHCI is a standard, so it falls under principle 6. Guests reach USB devices only through a shim, and the keyboard is a trusted path to Glint even while a guest owns the display. This section informs that plan and changes nothing above.

What v4.4.2 has. The host controller API is include/zephyr/drivers/usb/uhc.h: struct uhc_api at line 297 and struct uhc_transfer at line 120, which carries a net_buf. Events arrive through uhc_event_cb_t (line 219). The controller drivers in drivers/usb/uhc/ are uhc_max3421e.c (an SPI-attached controller), NXP's uhc_mcux_ehci.c, uhc_mcux_ip3516hs.c, uhc_mcux_khci.c and uhc_mcux_ohci.c (all over NXP's SDK), and uhc_virtual.c. There is no xHCI host driver. The only xHCI references in the tree are constants for Intel's audio-offload link (drivers/uaol/uaol_intel_adsp.c:40). The host stack in subsys/usb/host/ (usbh_core.c, usbh_device.c, usbh_ch9.c, usbh_desc.c, usbh_class.c, about 1,550 lines) is marked experimental (subsys/usb/host/Kconfig:5–8, drivers/usb/uhc/Kconfig:5–8). It has no hub support and one class driver, video (class/usbh_uvc.c). It has no host-side HID, mass storage or network class. It runs two threads fed by message queues (usbh_core.c:22–41, 195). subsys/usb/host/usbip.c is a USB/IP server: it exports host-attached devices over TCP and needs the whole network stack (subsys/usb/host/Kconfig.usbip). The HID headers in include/zephyr/usb/class/ are for the device side.

Is any of it worth using? No, not for this direction. Glint would still have to write the xHCI driver, hub support and every class driver, and it would take on threads, message queues and net_buf to use an experimental core. Glint's own xHCI driver is the cleaner fit, built in Crystal from the xHCI specification as Glint's other standard devices are. Each class then needs its own driver in Glint: hubs; HID in boot protocol for the keyboard; mass storage as Bulk-Only Transport with SCSI; and CDC-ECM or NCM for network adapters. The boot protocol's fixed 8-byte keyboard report needs no report-descriptor parser, which keeps the trusted path small enough to audit. Glint sees every keystroke first and reserves a key chord for itself before anything reaches a guest. In hosted mode, U-Boot's USB keyboard already arrives through SIMPLE_TEXT_INPUT.

The guest-facing shim. There are three options:

Option What the guest sees For Against
Emulated xHCI, as QEMU's hw/usb/hcd-xhci.c (about 3,700 lines) a USB controller with real device descriptors, driven by Linux's xhci-hcd and class drivers any class; no guest changes the largest emulation; Glint must filter forwarded HID traffic to keep the trusted path; isochronous timing and device ownership shared between Glint and guest
Per-class paravirtual devices: virtio-input for keyboard and mouse, virtio-blk for storage (6.6), virtio-net for USB network adapters (Phase 12) ordinary virtio devices, backed by Glint's class drivers through BLOCK_IO and the network protocol fits Glint's existing virtio model; the guest-visible surface is small; Glint stays in control, and the trusted keyboard path is natural because Glint injects events; virtio-input's device side is small (QEMU's hw/input/virtio-input*.c totals about 1,150 lines) only classes Glint has a driver for are reachable; the guest sees a virtio device, not "a USB stick"
USB/IP-style forwarding of USB requests over vsock (Phase 9) to Linux's vhci-hcd a virtual host controller in the guest, attached by the usbip user-space tool any class with one mechanism and no xHCI emulation needs guest user space and setup; not available during guest boot; adds latency; the keyboard must be kept out of it

The middle row is the natural default for keyboards, storage and network. USB/IP-style forwarding is a fallback for classes Glint has no driver for. Emulated xHCI is worth building only for a guest without virtio drivers.

Appendix: reproducing the probe

The commands assume the clone at /workspace/zephyr-src, and gen/ holding the hand-written generated headers (section 4 lists their contents). The overlay directory ovl/ holds zephyr/kernel.h without line 37 and, for x86-64, zephyr/arch/x86/intel64/arch.h with key declared unsigned long long. These were per-session files and are not in the checkout.

clang -target aarch64-unknown-windows   # or x86_64-unknown-windows
  -fno-ms-compatibility -fgnuc-version=4.2.1 -ffreestanding -fno-builtin
  -std=gnu11 -O2 -include gen/zephyr/autoconf.h -Iovl -Igen
  -I/workspace/zephyr-src/include
  -I/workspace/zephyr-src/lib/libc/minimal/include
  -I/workspace/zephyr-src/drivers/serial
  -c /workspace/zephyr-src/drivers/serial/uart_pl011.c   # or uart_ns16550.c
ld.lld -flavor link /subsystem:efi_application /entry:efi_main /nodefaultlib
  /out:BOOTAA64.EFI main.obj driver.obj

main.c defines memcpy, memset and z_impl_device_is_ready, the device table and init runner quoted in section 4, and an efi_main. That efi_main prints through uart_poll_out, calls GetMemoryMap and ExitBootServices (retrying once on a stale key), and prints again. The ESP image was written by Glint::Run::ESP (src/glint/run/esp.cr) with cleave's Fat16. QEMU was started with the arguments that src/glint/run/runner.cr uses for each architecture, with /workspace/glint-qbuild and /workspace/glint-ubuild as the QEMU and U-Boot build directories.