Skip to content

Sections 59–62 of the ISLE Architecture. For the full table of contents, see README.md.


Part XV: User I/O and Human Interfaces

Lock-free terminal multiplexing, input event routing, zero-copy audio, and secure display compositing.


59. The TTY and PTY Subsystem

59.1 The Problem

Linux's TTY layer is a historical artifact designed for 300-baud hardware teletypes. It features monolithic locks (tty_mutex, termios_rwsem), synchronous line discipline processing (handling backspace and signals in the critical path), and a complex buffer management system that scales poorly to thousands of concurrent terminal sessions.

In modern systems, the TTY layer is primarily used for Pseudo-Terminals (PTYs) — the backends for SSH sessions, terminal emulators (GNOME Terminal, Alacritty), and container multiplexers (Docker, Kubernetes). The Linux PTY implementation requires every byte of terminal output to traverse the kernel data path, acquiring locks and waking sleeping processes, making it a significant bottleneck for high-density container logging and high-throughput terminal applications.

59.2 ISLE's Lock-Free Ring Architecture

ISLE completely rearchitects the TTY/PTY subsystem around lock-free, single-producer/single-consumer (SPSC) ring buffers, identical to the KABI ring buffers used for storage and networking (Section 8).

The PTY Data Path: A PTY consists of a master side (/dev/ptmx, held by SSHd or Docker) and a slave side (/dev/pts/N, held by the shell or containerized application).

In ISLE, a PTY pair shares a pair of mapped memory pages (8 KB total) containing two SPSC ring buffers (master-to-slave and slave-to-master). Each ring buffer occupies one 4 KB page, providing adequate buffer space for interactive terminal sessions and container logging.

/// PTY ring buffer header. 16 bytes, designed for minimal overhead.
///
/// This is a simplified SPSC ring buffer format (not the full DomainRingBuffer
/// from Section 8a.2, which has 128 bytes of header for MPSC/broadcast support).
/// PTYs are always single-producer/single-consumer, so the compact header suffices.
///
/// Layout:
///   - bytes [0..8]:   head index (write position, AtomicU64)
///   - bytes [8..16]:  tail index (read position, AtomicU64)
///   - bytes [16..4096]: data buffer (4080 bytes usable)
#[repr(C)]
pub struct PtyRingHeader {
    /// Write position (producer advances). Counts bytes written.
    /// Masked with (capacity - 1) to get buffer offset.
    pub head: AtomicU64,
    /// Read position (consumer advances). Counts bytes read.
    /// Masked with (capacity - 1) to get buffer offset.
    pub tail: AtomicU64,
}

/// PTY ring buffer page. 4 KB total, 4079 bytes usable data.
/// Aligned to page boundary for direct mmap() into userspace.
///
/// The producer writes at (head % 4080 + 16), advancing head.
/// The consumer reads at (tail % 4080 + 16), advancing tail.
///
/// **Full/empty detection**: To distinguish full from empty when head and tail
/// indices wrap around, we use the classic "waste one slot" technique:
/// - Empty when head == tail
/// - Full when ((head + 1) % 4080) == tail
/// - Usable capacity: 4079 bytes (one slot sacrificed for disambiguation)
///
/// This avoids the wraparound ambiguity where head=4080, tail=0 would appear
/// both "full" (head-tail=4080) and "empty" (head%4080 == tail%4080 == 0).
#[repr(C, align(4096))]
pub struct PtyRingPage {
    /// Ring buffer header (16 bytes).
    pub header: PtyRingHeader,
    /// Data buffer (4080 bytes).
    pub data: [u8; 4080],
}

/// The reverse-direction ring (slave→master) is a separate page allocation.
/// Same layout as PtyRingPage. This design allows each direction to be
/// mapped independently if needed, and avoids the 8 KB allocation exceeding
/// the page granularity.
#[repr(C, align(4096))]
pub struct PtyRingPageReverse {
    /// Ring buffer header (16 bytes).
    pub header: PtyRingHeader,
    /// Data buffer (4080 bytes).
    pub data: [u8; 4080],
}

/// Terminal state shared between master and slave.
/// Stored in a separate small allocation (not a full page) within a per-master
/// state arena. Multiple PTYs from the same master share a single arena,
/// amortizing the page allocation overhead.
///
/// Total size: 32 bytes (cache-line friendly when padded).
/// Layout: termios_flags(4) + winsize_seq(4) + winsize_data(8) + flow_control(1)
///         + zero_copy_enabled(1) + _pad(14) = 32.
#[repr(C, align(8))]
pub struct AtomicTtyState {
    /// Terminal flags (ICANON, ECHO, ISIG, etc.) as bit positions.
    /// Modified atomically via compare-and-swap.
    pub termios_flags: AtomicU32,
    /// Window size (rows, columns). Modified via seqlock protocol.
    /// Layout: [seq_counter: AtomicU32 (4 bytes), winsize: Winsize (8 bytes)]
    pub winsize_seq: AtomicU32,
    pub winsize_data: UnsafeCell<Winsize>,
    /// Flow control state (stopped/running).
    pub flow_control: AtomicBool,
    /// Zero-copy mode enabled flag. Set by mutual consent handshake.
    pub zero_copy_enabled: AtomicBool,
    /// Padding to 32 bytes for cache alignment.
    _pad: [u8; 14],
}

/// Window size structure (matches POSIX struct winsize from <sys/ioctl.h>).
/// Used by TIOCGWINSZ/TIOCSWINSZ ioctls.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Winsize {
    pub ws_row: u16,
    pub ws_col: u16,
    pub ws_xpixel: u16,
    pub ws_ypixel: u16,
}

Memory layout: A PTY pair consists of three shared memory regions: 1. Master→slave ring (1 page, 4 KB): Written by master, read by slave 2. Slave→master ring (1 page, 4 KB): Written by slave, read by master 3. State arena (shared across PTYs from same master): Contains multiple AtomicTtyState structs (32 bytes each). A 4 KB arena supports up to 128 PTYs.

Seqlock protocol for window size: Reads use the standard seqlock pattern: 1. Read winsize_seq. If odd, retry (writer in progress). 2. Read winsize_data. 3. Read winsize_seq again. If changed, retry. Writes increment winsize_seq to odd, update winsize_data, then increment to even.

When the slave application calls write() to stdout, the ISLE syscall interface (isle-compat) writes the data directly into the slave_tx ring buffer. If the master application is polling via epoll() or io_uring, the kernel signals the eventfd associated with the ring.

Zero-Copy PTYs for Containers: For high-density container environments, ISLE supports a zero-copy PTY mode. If both the master and slave processes explicitly request it via an ISLE-specific ioctl(PTY_REQ_DIRECT), the kernel maps the PtyRingPage directly into the address spaces of both processes. The master and slave can then exchange terminal data entirely in userspace, bypassing the kernel data path completely. The kernel is only invoked to handle buffer full/empty wakeups (via futex). This allows a single node to stream gigabytes of container logs per second with near-zero CPU overhead.

Zero-copy mode restrictions: - Raw mode only: Zero-copy mode requires the PTY to be in raw mode (ICANON flag clear in termios). The kernel's asynchronous TTY worker thread (Section 59.3) is bypassed, so no line discipline processing occurs. Applications receive raw bytes without backspace handling, line buffering, or signal generation. - No signal generation: Because the kernel data path is bypassed, Ctrl+C, Ctrl+\, and Ctrl+Z do not generate SIGINT, SIGQUIT, or SIGTSTP. The master side (SSHd, container runtime) is responsible for monitoring the ring buffer and interpreting control sequences if signal semantics are needed. For most container logging use cases, this is acceptable — the slave process is typically a daemon writing structured logs, not an interactive shell. - No echo processing: Local echo (ECHO flag) is disabled automatically when zero-copy mode is activated. The master must implement echo if required. - Termios changes require renegotiation: If either side calls tcsetattr() to change terminal settings, the kernel automatically disables zero-copy mode and falls back to kernel-mediated mode. To re-enable, both sides must repeat the consent handshake.

Security Model for Zero-Copy PTYs:

Zero-copy PTY mode requires explicit security checks before enabling direct memory sharing:

  1. Capability requirement: The master side (the process requesting zero-copy mode) must hold CAP_TTY_DIRECT (defined in Section 11.2.1). This capability grants permission to bypass the kernel's TTY data path security checks. Container runtimes (Docker, containerd) typically hold this capability; unprivileged processes do not.

  2. Mutual consent: Both master and slave must explicitly agree to zero-copy mode:

  3. Master requests via ioctl(fd, PTY_REQ_DIRECT, &params) where params includes a nonce
  4. Slave acknowledges via ioctl(slave_fd, PTY_ACK_DIRECT, nonce) within a timeout window
  5. If the slave never acknowledges, the request fails with -ETIMEDOUT
  6. This prevents a malicious master from forcing zero-copy mode on an unsuspecting slave

  7. Same-cgroup constraint: Both processes must be in the same cgroup namespace (or the master must have CAP_SYS_ADMIN to bypass). This prevents a compromised container from mapping PTY buffers into a process outside its containment boundary.

  8. Memory isolation guarantee: The PtyRingPage is mapped with PROT_READ | PROT_WRITE into both processes, but the kernel retains a back-reference to the physical pages. If either process exits or execs a binary with elevated capability grants (Section 11.5), the kernel immediately revokes the direct mapping and falls back to standard ring-buffer mode. This prevents privilege escalation via persistent shared memory.

  9. Audit logging: Successful zero-copy mode activation generates an audit event (Section 40.9) with both PIDs and the PTY device identifier, enabling post-incident forensics.

The fallback path (when zero-copy is not requested or denied) uses the standard kernel-mediated ring buffer with full security checks on every data transfer.

59.3 Asynchronous Line Disciplines (N_TTY)

The line discipline (N_TTY) translates raw characters into canonical input (handling backspace, line buffering) and generates signals (translating Ctrl+C into SIGINT).

In Linux, this processing happens synchronously during the write() or read() syscall, while holding the tty_mutex.

In ISLE, line discipline processing is asynchronous and decoupled from the data path. 1. When the user types Ctrl+C, the raw byte (0x03) is placed into the master_tx ring buffer. 2. The kernel's asynchronous TTY worker thread (running in ISLE Core) consumes the raw ring, processes the line discipline rules based on the termios state, and pushes the processed output to the canonical ring (or generates the SIGINT signal to the foreground process group). 3. The foreground application reads from the canonical ring.

Because the TTY worker thread is the sole consumer of the raw ring and the sole producer of the canonical ring, it operates entirely lock-free.


60. The Input Subsystem (evdev)

Linux's evdev interface (/dev/input/eventX) is the standard for delivering keyboard, mouse, touch, and joystick events to userspace (Wayland compositors, X11).

60.1 Tier 2 Input Drivers

In ISLE, modern input drivers (USB HID, Bluetooth HID, I2C touchscreens) run in Tier 2 (Ring 3, process-isolated) (Section 6). An input driver's only responsibility is to parse hardware-specific reports and translate them into standardized input_event structs.

The driver communicates with isle-core via a shared memory ring established during driver registration (isle_driver_register, Section 5.3).

/// Internal kernel input event representation.
/// Uses 64-bit time fields for y2038 safety across all architectures.
///
/// **32-bit compatibility**: The userspace-visible `struct input_event` exposed
/// via `/dev/input/eventX` uses Linux-compatible layout that varies by architecture:
/// - 64-bit platforms: time_sec (u64), time_usec (u64), type (u16), code (u16), value (i32) = 24 bytes
/// - 32-bit platforms: time_sec (u32), time_usec (u32), type (u16), code (u16), value (i32) = 16 bytes
///
/// The `isle-compat` layer translates from this internal format to the
/// architecture-specific Linux input_event layout when copying to userspace.
/// This translation is zero-cost on 64-bit platforms (direct copy) and
/// requires field truncation/conversion on 32-bit platforms.
#[repr(C)]
pub struct InputEvent {
    /// Event timestamp in seconds since boot (CLOCK_MONOTONIC).
    /// 64-bit for y2038 safety; truncated to u32 on 32-bit compat path.
    pub time_sec: u64,
    /// Event timestamp microseconds component.
    /// 64-bit for consistency with time_sec; truncated to u32 on 32-bit compat.
    pub time_usec: u64,
    /// Event type (EV_KEY, EV_REL, EV_ABS, etc.).
    pub type_: u16,
    /// Event code (key code, relative axis, absolute axis, etc.).
    pub code: u16,
    /// Event value (key state, relative delta, absolute position, etc.).
    pub value: i32,
}

When a user presses a key, the Tier 2 USB HID driver pushes an InputEvent into the shared ring and calls isle_driver_complete (Section 5.4). The ISLE Core's input multiplexer (isle-input) wakes up, reads the event, and copies it to all open file descriptors for the corresponding /dev/input/eventX node.

Because the input driver is a standard Tier 2 process, a crash in the complex USB HID parsing logic simply restarts the driver process (~10ms recovery) without dropping subsequent keystrokes.

60.2 Secure VT Switching and Panic Console

The Virtual Terminal (VT) subsystem provides the emergency text console and the mechanism for switching between graphical sessions (Ctrl+Alt+F1-F6).

In Linux, the VT subsystem is deeply entangled with the console driver, input layer, and DRM.

In ISLE, the VT subsystem is a minimal state machine inside isle-input: 1. Normal Operation: isle-input routes all input_event structs to the active Wayland compositor (the process holding the DRM master node). 2. VT Switch Detected: When isle-input detects a VT switch chord (e.g., Ctrl+Alt+F1), it immediately revokes the DRM master capability from the current compositor and pauses input event delivery to that process. 3. Panic Console Handoff: If the system panics, ISLE Core forcefully reclaims the display hardware from the Tier 1 DRM driver. It resets the display controller to a known-safe text mode (or simple framebuffer mode) using a minimal, statically linked Tier 0 VGA/EFI driver, and dumps the panic log. The complex Tier 1 DRM driver is completely bypassed during a panic to ensure the log is always visible, even if the GPU state machine is deadlocked.


61. Audio Architecture (ALSA Compatibility)

Linux's Advanced Linux Sound Architecture (ALSA) provides the /dev/snd/pcmC0D0p interfaces for audio playback and capture.

61.1 ALSA PCM as DMA Rings

Audio devices are uniquely suited for ISLE's architecture because audio playback is fundamentally a ring buffer problem. Modern audio interfaces (Intel HDA, USB Audio Class 2.0) operate by reading PCM audio samples from a host memory ring buffer via DMA.

In ISLE, audio drivers (Tier 2) do not implement complex ALSA state machines. Instead, an ISLE audio driver simply allocates an IOMMU-fenced DMA buffer (Section 5.5, isle_driver_dma_alloc) and programs the hardware to consume it.

When a userspace audio server (PipeWire or PulseAudio) opens the ALSA PCM node, isle-compat directly maps the hardware's DMA ring buffer into the PipeWire process's address space.

The Audio Data Path: 1. PipeWire writes PCM audio samples directly into the mapped DMA buffer in userspace. 2. PipeWire updates the ring buffer's "appl_ptr" (application pointer) in the shared memory control page. 3. The audio hardware consumes the samples via DMA and generates a period interrupt. 4. The kernel handles the interrupt, updates the "hw_ptr" (hardware pointer) in the shared control page, and wakes PipeWire via a futex.

Zero-Copy Routing: This architecture is purely zero-copy. The audio samples never pass through kernel memory, and the kernel never executes a copy_from_user(). The kernel's only role in the audio data path is routing the hardware interrupt to the PipeWire futex.

61.2 Tier 2 Audio Driver Resilience

Audio drivers (especially USB Audio and complex DSPs) are prone to state machine bugs. Because they run in Tier 2, an audio driver crash is seamlessly contained.

When the Tier 2 audio process crashes, the kernel's device registry (Section 7) revokes its MMIO mappings, leaving the DMA ring buffer intact. The registry restarts the driver process. The new driver instance re-initializes the hardware and binds back to the existing DMA ring buffer. PipeWire experiences a brief ~10-20ms audio glitch (a single dropped period) but does not need to close and reopen the ALSA device, as the memory mapping remains valid throughout the recovery process.

61.3 Audio Device Trait

Interface contract: §12.3 (AudioDriver trait, audio_device_v1 KABI). This section specifies the Intel HDA, USB Audio Class, and HDMI/DP audio endpoint implementations of that contract. Tier decision and ALSA compat approach are authoritative in §12.3.

Architecture: Native ISLE audio driver framework with ALSA compatibility in isle-compat. The kernel provides a clean, low-latency PCM interface via the AudioDriver trait (§12.3). isle-compat translates snd_pcm_*/snd_ctl_* ioctls to native calls, enabling existing applications (PipeWire, PulseAudio, JACK) to work unmodified.

// isle-core/src/audio/mod.rs

/// Audio device handle.
#[repr(C)]
pub struct AudioDeviceId(u64);

/// PCM stream direction.
#[repr(u32)]
pub enum PcmDirection {
    /// Playback (host → device).
    Playback = 0,
    /// Capture (device → host).
    Capture = 1,
}

/// PCM sample format.
#[repr(u32)]
pub enum PcmFormat {
    /// Signed 16-bit little-endian (most common).
    S16Le = 0,
    /// Signed 24-bit little-endian (3 bytes per sample).
    S24Le = 1,
    /// Signed 32-bit little-endian.
    S32Le = 2,
    /// IEEE 754 32-bit float (pro audio, VST plugins).
    F32Le = 3,
}

/// PCM stream parameters.
#[repr(C)]
pub struct PcmParams {
    /// Direction (playback or capture).
    pub direction: PcmDirection,
    /// Sample format.
    pub format: PcmFormat,
    /// Sample rate in Hz (44100, 48000, 96000, 192000).
    pub rate: u32,
    /// Number of channels (2 = stereo, 6 = 5.1 surround, 8 = 7.1).
    pub channels: u8,
    /// Period size in frames (power of 2, typically 64-2048).
    /// A "period" is the granularity of interrupts: hardware fires an interrupt
    /// every `period_frames` samples. Smaller = lower latency, more CPU overhead.
    pub period_frames: u32,
    /// Buffer size in frames (multiple of period_frames, typically 4-8 periods).
    /// Total buffer duration = buffer_frames / rate. Example: 2048 frames @ 48kHz = 42.7ms.
    pub buffer_frames: u32,
}

/// PCM stream handle (opaque to userspace).
#[repr(C)]
pub struct PcmStreamHandle(u64);

/// Audio device trait. Implemented by audio drivers (Intel HDA, USB Audio, etc.).
pub trait AudioDevice {
    /// Open a PCM stream.
    fn open_pcm_stream(&self, params: PcmParams) -> Result<PcmStream, AudioError>;

    /// Get mixer controls (volume, mute, input source).
    fn get_mixer_controls(&self) -> Vec<MixerControl>;

    /// Set mixer control value.
    fn set_mixer_control(&self, control_id: u32, value: i32) -> Result<(), AudioError>;
}

/// PCM stream (active playback or capture).
pub struct PcmStream {
    /// Stream handle.
    pub handle: PcmStreamHandle,
    /// Parameters.
    pub params: PcmParams,
    /// DMA buffer (ring buffer, mapped into userspace via isle-compat).
    pub dma_buffer: DmaBufferHandle,
    /// Hardware pointer (read position for playback, write position for capture).
    /// Updated by hardware via DMA or interrupt. Atomic for lock-free read from userspace.
    pub hw_ptr: Arc<AtomicU64>,
    /// Application pointer (write position for playback, read position for capture).
    /// Updated by userspace (PipeWire, ALSA lib).
    pub appl_ptr: Arc<AtomicU64>,
}

impl PcmStream {
    /// Start the stream (begin DMA).
    pub fn start(&self) -> Result<(), AudioError> {
        // Program hardware to start DMA from/to the ring buffer.
        // For playback: hardware reads from dma_buffer[hw_ptr..appl_ptr].
        // For capture: hardware writes to dma_buffer[hw_ptr..].
        // Returns Err if not enough data buffered (playback) or buffer full (capture).
        todo!("driver-specific implementation")
    }

    /// Stop the stream (pause DMA).
    pub fn stop(&self) -> Result<(), AudioError> {
        todo!("driver-specific implementation")
    }
}

/// Mixer control (volume slider, mute toggle, input source selector).
#[repr(C)]
pub struct MixerControl {
    /// Control ID (for set_mixer_control).
    pub id: u32,
    /// Control type.
    pub control_type: MixerControlType,
    /// Name (e.g., "Master Playback Volume").
    pub name: [u8; 64],
    /// Min value (for volume controls).
    pub min: i32,
    /// Max value (for volume controls).
    pub max: i32,
    /// Current value.
    pub value: AtomicI32,
}

/// Mixer control type.
#[repr(u32)]
pub enum MixerControlType {
    /// Volume (integer range, min..max).
    Volume = 0,
    /// Mute (boolean, 0=unmuted, 1=muted).
    Mute = 1,
    /// Enumeration (e.g., input source: "Mic", "Line In", "CD").
    Enum = 2,
}

61.4 Intel HDA Driver Model

Intel High Definition Audio (HDA) is the dominant audio controller on Intel and AMD x86 platforms. The HDA spec defines: - HDA controller: PCI device (class 0x0403), exposes MMIO registers for command/response, DMA buffer descriptors, interrupt status. - Codecs: Audio chips connected via the HDA link (typically 1-2 codecs: one for analog audio, one for HDMI/DP audio). Each codec has a tree of widgets (nodes: DAC, ADC, mixer, pin, amplifier).

// isle-hda-driver/src/lib.rs (Tier 1 driver)

/// HDA controller state.
pub struct HdaController {
    /// PCI device.
    pub pci_dev: PciDevice,
    /// MMIO base address (from BAR0).
    pub mmio: *mut HdaRegisters,
    /// Codecs discovered on the HDA link.
    pub codecs: Vec<HdaCodec>,
    /// Active PCM streams.
    pub streams: Vec<Arc<HdaPcmStream>>,
}

/// HDA codec (represents one audio chip on the HDA link).
pub struct HdaCodec {
    /// Codec address (0-14).
    pub addr: u8,
    /// Vendor ID (from root node).
    pub vendor_id: u32,
    /// Subsystem ID.
    pub subsystem_id: u32,
    /// Widget tree (parsed from codec capabilities).
    pub widgets: Vec<HdaWidget>,
}

/// HDA widget (node in codec's audio routing graph).
pub struct HdaWidget {
    /// Node ID (NID).
    pub nid: u8,
    /// Widget type.
    pub widget_type: HdaWidgetType,
    /// Capabilities (from GET_PARAMETER verb).
    pub caps: u32,
    /// Connections (for mixer/mux widgets: list of input NIDs).
    pub connections: Vec<u8>,
}

/// HDA widget type.
#[repr(u8)]
pub enum HdaWidgetType {
    /// Audio output (DAC - Digital-to-Analog Converter).
    AudioOut = 0,
    /// Audio input (ADC - Analog-to-Digital Converter).
    AudioIn = 1,
    /// Mixer (combines multiple inputs).
    Mixer = 2,
    /// Selector (mux: selects one of multiple inputs).
    Selector = 3,
    /// Pin (physical connector: headphone jack, speaker, mic).
    Pin = 4,
    /// Power widget.
    Power = 5,
    /// Volume knob.
    VolumeKnob = 6,
    /// Vendor-specific.
    VendorDefined = 15,
}

impl HdaController {
    /// Send a verb (command) to a codec. Returns the response.
    /// HDA verbs use CORB (Command Outbound Ring Buffer) and RIRB (Response Inbound Ring Buffer).
    pub fn send_verb(&self, codec_addr: u8, nid: u8, verb: u32) -> Result<u32, HdaError> {
        // Write to CORB: codec_addr | nid | verb.
        // Wait for RIRB: response appears in ring buffer, signaled by interrupt or polling.
        todo!("CORB/RIRB protocol implementation")
    }

    /// Probe codecs on the HDA link.
    pub fn probe_codecs(&mut self) -> Result<(), HdaError> {
        // Read STATESTS register to discover codec addresses (bit set = codec present).
        let statests = unsafe { (*self.mmio).statests };
        for addr in 0..15 {
            if (statests & (1 << addr)) != 0 {
                // Codec present: read vendor ID, build widget tree.
                let vendor_id = self.send_verb(addr, 0, VERB_GET_VENDOR_ID)?;
                let codec = self.build_codec(addr, vendor_id)?;
                self.codecs.push(codec);
            }
        }
        Ok(())
    }

    /// Build widget tree for a codec (enumerate all nodes, parse capabilities).
    fn build_codec(&self, addr: u8, vendor_id: u32) -> Result<HdaCodec, HdaError> {
        // Send GET_SUBORDINATE_NODE_COUNT to root (NID 0) to discover function groups.
        // Send GET_SUBORDINATE_NODE_COUNT to each function group to discover widgets.
        // For each widget, send GET_PARAMETER to read capabilities.
        todo!("widget enumeration")
    }
}

DMA buffer descriptor list (BDLIST): HDA uses a scatter-gather DMA model. Each PCM stream has a BDLIST (Buffer Descriptor List) in host memory, containing entries like:

/// HDA Buffer Descriptor List Entry (BDL entry).
#[repr(C)]
pub struct HdaBdlEntry {
    /// Physical address of buffer segment.
    pub addr: u64,
    /// Length of buffer segment in bytes.
    pub length: u32,
    /// IOC (Interrupt On Completion) flag: interrupt when this segment is consumed.
    pub ioc: u32,
}

The HDA controller DMA engine walks the BDLIST, fetching audio data from the buffers, and generates an interrupt when ioc=1 entries complete (every period).

61.5 PipeWire Integration

§15 defines PipeWire ring buffers for audio routing in userspace. The integration: 1. Kernel provides raw PCM streams (§61.3 PcmStream): a DMA ring buffer that hardware directly reads/writes. 2. PipeWire runs in userspace (Tier 2): implements the audio graph (mixing, routing, resampling, effects). 3. Zero-copy path: PipeWire's "audio device" node directly mmaps the kernel PCM DMA buffer. PipeWire writes mixed samples to appl_ptr, advances the pointer, the kernel driver sees the update and programs the hardware to consume up to appl_ptr.

Low-latency timer: PipeWire needs a periodic callback to refill the buffer every period. The kernel provides a timer (HPET or TSC-deadline APIC timer, configured to fire every period_frames / rate seconds, e.g., 1ms for 48-frame periods at 48kHz). Timer interrupt wakes PipeWire, which renders the next period's samples.

61.6 Jack Detection

HDA codecs support unsolicited responses (jack detection events): when a headphone is plugged/unplugged, the codec sends an event to the controller.

impl HdaController {
    /// Enable unsolicited response for a pin widget (jack detection).
    pub fn enable_jack_detect(&self, codec_addr: u8, pin_nid: u8) -> Result<(), HdaError> {
        // Send SET_UNSOLICITED_ENABLE verb to pin widget.
        let verb = VERB_SET_UNSOLICITED_ENABLE | (1 << 7) | (pin_nid as u32);
        self.send_verb(codec_addr, pin_nid, verb)?;
        Ok(())
    }

    /// Handle unsolicited response interrupt (jack detection event).
    pub fn handle_unsolicited_response(&self, codec_addr: u8, response: u32) {
        // Parse response: extract pin NID, jack state (connected/disconnected).
        let pin_nid = (response >> 4) & 0xF;
        let connected = (response & 0x1) != 0;

        // Post event to userspace via event ring buffer.
        isle_event::post_event(Event::AudioJackChanged {
            device_id: self.device_id(),
            codec_addr,
            pin_nid: pin_nid as u8,
            connected,
        });
    }
}

Audio routing policy: Audio routing policy (default device selection, per-app routing, volume control) is handled by PipeWire in userspace. Kernel provides DMA ring buffers and jack detection events.

61.7 Architectural Decision

Audio: Native ISLE framework + ALSA compat

Kernel provides native PCM interface with clean ABI. isle-compat translates ALSA ioctls to native calls, enabling existing applications (PipeWire, PulseAudio, JACK) to work unmodified. Best of both worlds: clean kernel API, full userspace compatibility.


62. Display and Graphics (DRM/KMS)

The Direct Rendering Manager (DRM) and Kernel Mode Setting (KMS) subsystems manage GPUs, display outputs, and hardware-accelerated rendering.

62.1 DRM as a Tier 1 Subsystem

GPUs are complex, high-bandwidth devices that require aggressive memory management (GART/TTM) and rapid command submission. Therefore, ISLE GPU drivers (e.g., isle-amdgpu, isle-i915) operate in Tier 1 (Ring 0, MPK-isolated) (Section 6). Full implementation details covering display device models, atomic modesetting, framebuffer objects, and scanout planes are specified in §62.3–62.9.

The GPU driver runs in a dedicated hardware memory domain. It receives command buffers from userspace (Mesa/Vulkan) via shared memory rings. The driver validates the command buffers (ensuring they don't contain malicious GPU memory writes) and submits them to the hardware command rings.

Because the driver is MPK-isolated, a bug in the complex command validation logic (a frequent source of Linux CVEs) cannot corrupt ISLE Core memory or the page cache. If the GPU driver faults, it is reloaded (~50-150ms). Userspace rendering contexts are lost (triggering a VK_ERROR_DEVICE_LOST in Vulkan applications), but the system remains stable.

62.2 DMA-BUF and Secure File Descriptor Passing

Modern Linux graphics rely entirely on DMA-BUF: a mechanism for sharing hardware-backed memory buffers between different devices and processes (e.g., sharing a rendered frame from the GPU to the Wayland compositor, or from a V4L2 webcam to the GPU).

In Linux, a DMA-BUF is represented as a standard file descriptor. Passing the file descriptor over a UNIX domain socket grants access to the underlying memory.

ISLE's DMA-BUF Implementation: ISLE implements DMA-BUF using the core Capability System (Section 11). 1. When the GPU driver allocates a framebuffer, it creates an ISLE Memory Object and mints a Capability Token granting MEM_READ | MEM_WRITE access. 2. isle-compat wraps this Capability Token in a synthetic file descriptor. 3. When the Wayland client passes the file descriptor to the compositor over AF_UNIX (using SCM_RIGHTS), the kernel securely delegates the Capability Token to the compositor's capability space. 4. The compositor uses the Capability Token to map the framebuffer into its own address space, or passes it back to the GPU driver to queue a page flip (KMS).

By backing DMA-BUF file descriptors with cryptographic Capability Tokens, ISLE guarantees that memory access rights cannot be forged or leaked, and seamlessly supports distributed graphics rendering (Section 47) where the compositor and the rendering client exist on different physical nodes in the cluster.

62.3 Display Device Model

Interface contract: §12.2 (DisplayDriver trait, display_device_v1 KABI). This section specifies the Intel i915, AMD DCN, and embedded display pipeline implementations of that contract. Tier decision and atomic modesetting requirement are authoritative in §12.2.

Tier: Tier 1 for integrated GPUs (Intel i915, AMD amdgpu iGPU). Tier 2 only for fully offloaded display (USB DisplayLink, network display servers).

// isle-core/src/display/mod.rs

/// Display device handle.
#[repr(C)]
pub struct DisplayDeviceId(u64);

/// Display connector type.
#[repr(u32)]
pub enum ConnectorType {
    /// Unknown or internal.
    Unknown = 0,
    /// HDMI.
    Hdmi = 1,
    /// DisplayPort.
    DisplayPort = 2,
    /// embedded DisplayPort (laptop internal screen).
    EmbeddedDp = 3,
    /// USB-C with DP Alt Mode.
    UsbTypeC = 4,
    /// DVI.
    Dvi = 5,
    /// VGA (legacy).
    Vga = 6,
    /// Virtual (for VNC, RDP).
    Virtual = 7,
}

/// Display connector state.
#[repr(u32)]
pub enum ConnectorState {
    /// No display attached.
    Disconnected = 0,
    /// Display attached, EDID read successfully.
    Connected = 1,
    /// Display may be attached, but no EDID (fallback to safe mode).
    ConnectedNoEdid = 2,
}

/// Display connector.
pub struct DisplayConnector {
    /// Connector ID (unique per display device).
    pub id: u32,
    /// Connector type.
    pub connector_type: ConnectorType,
    /// Current state (connected, disconnected).
    pub state: AtomicU32, // ConnectorState
    /// EDID (Extended Display Identification Data, 128-256 bytes).
    pub edid: RwLock<Option<Vec<u8>>>,
    /// Supported display modes (parsed from EDID or driver-provided fallbacks).
    pub modes: RwLock<Vec<DisplayMode>>,
    /// Currently active mode (if connected and enabled).
    pub active_mode: RwLock<Option<DisplayMode>>,
    /// DPMS (Display Power Management Signaling) state.
    pub dpms: AtomicU32, // DpmsState
}

/// Display mode (resolution, refresh rate).
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct DisplayMode {
    /// Horizontal resolution in pixels.
    pub hdisplay: u16,
    /// Vertical resolution in pixels.
    pub vdisplay: u16,
    /// Refresh rate in millihertz (60000 = 60.000 Hz).
    pub vrefresh_mhz: u32,
    /// Flags (interlaced, VRR capable, preferred mode).
    pub flags: u32,
    /// Pixel clock in kHz (for driver use, validates mode is achievable).
    pub clock_khz: u32,
    /// Horizontal timings (front porch, sync, back porch).
    pub hsync_start: u16,
    pub hsync_end: u16,
    pub htotal: u16,
    /// Vertical timings (front porch, sync, back porch).
    pub vsync_start: u16,
    pub vsync_end: u16,
    pub vtotal: u16,
}

/// Display mode flags.
pub mod mode_flags {
    /// Interlaced mode.
    pub const INTERLACED: u32 = 1 << 0;
    /// Variable Refresh Rate (VRR) capable (FreeSync, G-Sync, HDMI VRR).
    pub const VRR: u32 = 1 << 1;
    /// Preferred mode (from EDID).
    pub const PREFERRED: u32 = 1 << 2;
}

/// DPMS (Display Power Management Signaling) state.
#[repr(u32)]
pub enum DpmsState {
    /// Display on, normal operation.
    On = 0,
    /// Display standby (monitor sleeps, can wake instantly).
    Standby = 1,
    /// Display suspend (lower power than standby).
    Suspend = 2,
    /// Display off (lowest power, may take 1-2 seconds to wake).
    Off = 3,
}

62.4 Atomic Modesetting Protocol

ISLE uses an atomic modesetting model (same as Linux DRM atomic). Changes to the display configuration (resolution, framebuffer, connector enable/disable) are batched into a single atomic transaction. Either all changes apply or none do. This eliminates tearing and half-configured states.

// isle-core/src/display/atomic.rs

/// Atomic modesetting request.
pub struct AtomicModeset {
    /// Connector changes (enable, disable, mode change).
    pub connectors: Vec<ConnectorUpdate>,
    /// Plane changes (scanout buffer, position, scaling).
    pub planes: Vec<PlaneUpdate>,
    /// Flags (test-only, allow modeset, async).
    pub flags: AtomicFlags,
}

/// Connector update (part of atomic transaction).
pub struct ConnectorUpdate {
    /// Connector ID.
    pub connector_id: u32,
    /// New mode (None = disable connector).
    pub mode: Option<DisplayMode>,
    /// CRTC to attach this connector to (if enabling).
    pub crtc_id: Option<u32>,
}

/// Plane update (part of atomic transaction).
pub struct PlaneUpdate {
    /// Plane ID.
    pub plane_id: u32,
    /// Framebuffer handle (None = disable plane).
    pub fb: Option<FramebufferHandle>,
    /// Source rectangle in framebuffer (for scaling/cropping).
    pub src: Rectangle,
    /// Destination rectangle on screen.
    pub dst: Rectangle,
}

/// Atomic modesetting flags.
pub mod atomic_flags {
    /// Test-only (validate but don't apply; used by compositors to check if mode is possible).
    pub const TEST_ONLY: u32 = 1 << 0;
    /// Allow modeset (may cause visible glitch; only allow during VT switch or initial setup).
    pub const ALLOW_MODESET: u32 = 1 << 1;
    /// Async flip (flip on next vblank, don't wait; lower latency).
    pub const ASYNC: u32 = 1 << 2;
}

/// Rectangle (for plane src/dst).
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Rectangle {
    pub x: u32,
    pub y: u32,
    pub width: u32,
    pub height: u32,
}

Atomic commit flow: 1. Wayland compositor builds an AtomicModeset transaction: "attach framebuffer FB123 to primary plane, set mode to 1920x1080@60Hz on connector 0, disable connector 1". 2. Compositor calls ioctl(dri_fd, ISLE_DRM_ATOMIC_COMMIT, &atomic_modeset) (via isle-compat DRM emulation). 3. Kernel validates the transaction: - Mode is supported by the connector (in the modes list from EDID). - Framebuffer format is supported by the plane (RGB888, XRGB8888, NV12, etc.). - Bandwidth is achievable (pixel clock within limits, memory bandwidth sufficient). 4. If valid, kernel programs the display controller hardware (Intel i915 writes to plane registers, GGT, pipe config; AMD writes to DCN registers). 5. Hardware scans out the new framebuffer on the next vblank (tear-free).

62.5 Framebuffer Objects

A framebuffer is a region of GPU memory containing pixel data. The display controller's scanout engine reads from the framebuffer via DMA and sends pixels to the monitor.

// isle-core/src/display/framebuffer.rs

/// Framebuffer handle (opaque to userspace).
#[repr(C)]
pub struct FramebufferHandle(u64);

/// Framebuffer format (pixel layout).
#[repr(u32)]
pub enum FramebufferFormat {
    /// 32bpp XRGB (X=unused, R=red, G=green, B=blue; 8 bits each).
    Xrgb8888 = 0x34325258,
    /// 32bpp ARGB (with alpha channel).
    Argb8888 = 0x34325241,
    /// 24bpp RGB (no alpha, no padding).
    Rgb888 = 0x34324752,
    /// 16bpp RGB565.
    Rgb565 = 0x36314752,
    /// YUV 4:2:0 planar (NV12, for video).
    Nv12 = 0x3231564e,
}

/// Framebuffer descriptor.
pub struct Framebuffer {
    /// Handle.
    pub handle: FramebufferHandle,
    /// Width in pixels.
    pub width: u32,
    /// Height in pixels.
    pub height: u32,
    /// Pixel format.
    pub format: FramebufferFormat,
    /// Pitch (bytes per row; may be larger than width * bpp if aligned).
    pub pitch: u32,
    /// GPU memory object backing this framebuffer (for DMA-BUF export, §15/§62).
    pub mem_obj: MemoryObjectHandle,
}

Framebuffer allocation: Compositor allocates GPU memory (via the GPU driver, §11/§42), renders the desktop into it (via Vulkan/OpenGL), then creates a framebuffer object pointing to that memory and passes it to the display subsystem for scanout.

62.6 Scanout Planes

Modern display controllers have multiple planes (hardware overlays) that can scan out independent framebuffers simultaneously: - Primary plane: The desktop/window contents (always present). - Cursor plane: The mouse cursor (small, can be moved with no desktop re-render). - Overlay planes: Video playback windows (compositor passes video framebuffer directly to hardware, zero-copy).

// isle-core/src/display/plane.rs

/// Display plane (hardware overlay).
pub struct DisplayPlane {
    /// Plane ID (unique per display device).
    pub id: u32,
    /// Plane type.
    pub plane_type: PlaneType,
    /// Supported framebuffer formats.
    pub formats: Vec<FramebufferFormat>,
    /// Current framebuffer attached (None = disabled).
    pub fb: RwLock<Option<FramebufferHandle>>,
    /// Current position/scaling.
    pub src: RwLock<Rectangle>,
    pub dst: RwLock<Rectangle>,
}

/// Plane type.
#[repr(u32)]
pub enum PlaneType {
    /// Primary plane (desktop contents).
    Primary = 0,
    /// Cursor plane (mouse cursor, small, high-priority).
    Cursor = 1,
    /// Overlay plane (video, additional window).
    Overlay = 2,
}

Cursor plane optimization: Moving the cursor only requires updating the cursor plane's dst rectangle. The compositor does NOT need to re-render the desktop or flip the primary plane. This is why modern desktops have smooth 144Hz cursors even with a 60Hz desktop.

62.7 Hotplug Detection

When a display is connected (USB-C DP Alt Mode, HDMI, etc.), the display controller raises an interrupt. The driver handles hotplug in the interrupt handler:

// isle-core/src/display/hotplug.rs

impl DisplayDevice {
    /// Hotplug interrupt handler (runs in Tier 1 driver domain).
    pub fn handle_hotplug_interrupt(&self) {
        // Scan all connectors for state changes.
        for connector in &self.connectors {
            let new_state = self.read_connector_state(connector.id);
            let old_state = connector.state.load(Ordering::Acquire);

            if new_state != old_state {
                connector.state.store(new_state, Ordering::Release);

                if new_state == ConnectorState::Connected {
                    // Display connected: read EDID, parse modes.
                    if let Ok(edid) = self.read_edid(connector.id) {
                        let modes = parse_edid(&edid);
                        *connector.edid.write() = Some(edid);
                        *connector.modes.write() = modes;
                    }
                    // Post hotplug event to userspace.
                    self.post_hotplug_event(connector.id, HotplugEventType::Connected);
                } else {
                    // Display disconnected.
                    *connector.edid.write() = None;
                    *connector.modes.write() = Vec::new();
                    self.post_hotplug_event(connector.id, HotplugEventType::Disconnected);
                }
            }
        }
    }
}

Compositor response: When the compositor receives a hotplug event (via the event ring buffer), it: 1. Re-enumerates connectors and modes (ioctl(DRM_IOCTL_MODE_GETRESOURCES)). 2. Decides how to configure the new display (extended desktop, mirror, ignore). 3. Allocates new framebuffers (if needed) for the new resolution. 4. Submits an atomic modesetting request to enable the new connector.

62.8 Panel Self-Refresh (PSR)

When the compositor has not updated the framebuffer (static desktop), the display controller can enter Panel Self-Refresh mode: - The monitor's internal controller (eDP panel, DP monitor with PSR support) caches the last frame. - The GPU's scanout engine stops reading from VRAM (memory bandwidth saved). - The GPU's memory controller enters a low-power state (watts saved).

When the compositor updates the framebuffer (user moves the mouse, window animates), the display driver detects the change (via atomic commit) and exits PSR mode, resuming scanout.

Power savings: PSR saves 1-2W on a laptop when the screen is static (reading a document, watching a video with no UI movement). This extends battery life by ~10-15% for typical office workloads.

62.9 Variable Refresh Rate (VRR)

Modern monitors support VRR (FreeSync, G-Sync, HDMI VRR): the display refreshes at variable intervals (e.g., 40-144Hz) synchronized with the compositor's render rate. This eliminates tearing without vsync's fixed-cadence latency.

// isle-core/src/display/vrr.rs

/// VRR mode.
#[repr(u32)]
pub enum VrrMode {
    /// VRR disabled (fixed refresh rate).
    Disabled = 0,
    /// VRR enabled (variable refresh within supported range).
    Enabled = 1,
}

impl DisplayConnector {
    /// Enable VRR (if supported by mode and monitor).
    pub fn set_vrr(&self, enabled: bool) -> Result<(), DisplayError> {
        let mode = self.active_mode.read().ok_or(DisplayError::NoActiveMode)?;
        if (mode.flags & mode_flags::VRR) == 0 {
            return Err(DisplayError::VrrNotSupported);
        }
        // Program display controller to enable VRR (DP Adaptive-Sync, HDMI VRR, or FreeSync).
        self.driver.set_vrr_mode(self.id, if enabled { VrrMode::Enabled } else { VrrMode::Disabled })
    }
}

Compositor use: Wayland compositors (KWin, Mutter, wlroots) detect VRR capability from the display mode flags, enable it, and schedule presentation to match the compositor's render loop (unlocked framerate, no vsync wait).

62.10 Architectural Decision

Display: Wayland-only + Xwayland

ISLE's KMS interface (§62) is Wayland-native (DRM atomic modesetting, DMA-BUF via capabilities). X11 support via Xwayland (same as Fedora, Ubuntu 22.04+). No native X11 server support — X11 protocol is a 40-year-old security liability (MIT-MAGIC-COOKIE-1, unrestricted window snooping). Xwayland provides compatibility for legacy apps without compromising security.