XXNU Source Handbook Download offline version from here
Part 12 · Chapter 12.01

IOKit, driver execution, and libkern

Guide index · Architecture

IOKit connects discoverable devices and software services to drivers, gives those drivers execution and memory-transfer facilities, and exposes selected operations to user processes. libkern supplies the object runtime beneath this machinery. The central relationship is a provider and its clients: a provider describes a resource; clients attach to it, negotiate access, publish further services, and eventually detach. A storage controller can therefore participate in several layers without the generic IOKit code knowing the disk format or hardware command set. See IOService and IORegistryEntry.

This checkout contains the generic runtime, selected platform classes, DriverKit interfaces, and kernel-side DriverKit transport. It does not contain every driver family or device implementation shipped with an operating system. Follow an implemented virtual call to its concrete subclass before attributing device behavior to this repository.

The components and their connections

IOKit, driver execution, and libkern diagram
IOKit, driver execution, and libkern diagram · Open SVG
Mermaid source
flowchart TD
    R[IORegistryEntry: properties and plane links] --> S[IOService: provider/client lifecycle]
    C[IOCatalogue: driver personalities] --> S
    K[OSKext and OSMetaClass: loaded classes] --> S
    S --> W[IOWorkLoop and event sources]
    S --> P[IOServicePM and root power domain]
    S --> U[IOUserClient: process connection]
    U <--> IPC[Mach ports and messages]
    IPC <--> D[IOUserServer: DriverKit RPC]
    S --> M[IOMemoryDescriptor and IODMACommand]
    M <--> VM[VM maps, UPLs, device pager]
    M --> I[IOMapper and device-specific DMA]
    O[OSObject and libkern collections] --> R
    O --> K

The arrows show ownership or call relationships, not a boot sequence. Three graphs coexist: registry planes describe relationships, the C++ superclass chain describes implementation inheritance, and OSKext dependency lists describe executable dependencies. A service's provider is not necessarily its C++ superclass. The power plane can also differ from the service plane; joinPMtree delegates placement to the platform expert.

Component Main state Questions it answers
Registry Property dictionaries, plane-specific parent/child arrays, entry ID What objects exist, and how are they connected?
Service/catalogue Published and inactive flags, provider, matching category, personality dictionaries Which driver should attach, and is it ready or terminating?
Workloop Recursive gate, worker thread, event-source chains Which callbacks may change shared driver state together?
User client/server Connection ports, task ownership, method arguments, RPC objects Which process can invoke which exported operations?
Memory/DMA Source ranges, prepared pages, CPU maps, device maps, segment constraints What memory may the CPU or device access during a transfer?
Power management Requested/current states, parent capabilities, queued requests and acknowledgments When can devices and the system change power state?
libkern/runtime Object references, class registration, executable dependencies How are objects allocated and classes made available?

Reading routes

  1. Start with bootstrap and BSD integration for framework initialization, device-tree import, platform matching and root-device discovery.
  2. Read libkern and kext runtime, then object runtime and collections, for allocation, reference counts, metaclasses, casts and container ownership. Follow symbol interning for shared key identity, no-copy backing and delayed destruction.
  3. Read registry and service lifecycle, then registry planes and properties, matching and termination, and notifications and arbitration. These connect object discovery to concurrent driver attachment, callbacks and removal.
  4. Read workloops and power, callback protocols, and interrupt routing and command queues to follow execution context, wakeups, command ownership and handler teardown.
  5. Read user clients and DriverKit, connection lifetime, DriverKit dispatch and actions, and shared queues and notifications for process-facing calls and asynchronous results.
  6. Read memory and DMA, descriptors and CPU mappings, and DMA preparation and mapping. CPU virtual, physical and device-visible addresses have different owners and lifetimes.
  7. Read power assertions and idle policy for the decisions that permit or defer sleep. Follow device power transitions into system sleep and wake, then hibernation creation and restore.
  8. Read reporting and performance control for report channels, state residency, sample consistency and device-work attribution.
  9. Follow kernel collections and OSKext loading, serialization and property transport, and NVRAM variables and persistence for executable dependencies, module start/unload, parser ownership, receiver policy, and V3/CHRP bank updates.

The focused implementation chapters extend those introductions:

External-method call lifecycle follows a concrete local user client from opening through selector admission, current-caller checks, scalar/structure buffers, dispatch, output and close. It distinguishes the opener, connection object, current caller and memory owner, then connects the common bridge to DriverKit asynchronous completion.

The queue chapter now compares classic, circular and DriverKit cursor/publication protocols, including reset, overwrite and callback limits. The matching and notification chapters follow catalogue-triggered rescans, deferred work, category replacement and callback selection/removal through concrete event orders. For operations that cross beyond IOKit, follow the user-buffer DMA walkthrough and the Skywalk task-mapping/channel walkthrough; both distinguish memory visibility, consumer progress and final resource release.

Question Detailed chapter
How do matching retries and start failures interact with termination? Matching and coordinated termination follows publication, candidate ownership, start deferral, finalization, stop, and detach.
What survives close, no-senders, or provider loss? Connection and DriverKit lifetime follows typed exports, active-call finalization, mapping exports, transport cleanup, and remote stop.
Where does a callback run, and what does cancellation wait for? Workloop and power protocols follows wakeup locking, source removal, timer delivery/generations, PM callouts, and acknowledgments.
Which preparation owns pages, IOVAs, and bounce buffers? DMA preparation and mapping follows UPL rollback, command activity/error state, mapper accounting, segment generation, and completion.
What exactly does genIOVMSegments() return, and how are its addresses computed? DMA segment walkthrough traces descriptor dispatch, UPL/physical/IOVA coordinates, padded mappings, splitting/coalescing, bounce copies, bounded iteration and partial errors.
How do assertion IDs, sleep-preventer services and aggressiveness settings affect sleep permission? Power assertions and idle policy follows asynchronous publication, DriverKit exclusion counts, aggregate masks, idle timers and driver callbacks.
How does a sleep request reach CPU shutdown and return to working services? System sleep and wake follows capability policy, client replies/cancellation, filesystem and task preparation, CPU handoff and wake completion.
Which references belong to an object, its class, a collection, or an iterator? Object runtime and collections follows class registration, instance accounting, collection mutation and object release.
How does a shared string become a stable dictionary key, and when can its storage be freed? Symbol interning follows try-retain, duplicate insertion, ordinary/huge lookup, permanent backing, resize and SMR-zone destruction.
What does a registry traversal or property lookup protect? Registry planes and properties distinguishes graph synchronization, retained results, iterator validity and serialization.
What happens if a notifier removes itself or a service changes during registration? Notifications and arbitration follows registration, replay, dispatch ownership, removal and lock ordering.
How does an interrupt source reach a handler, and who owns a queued command? Interrupt routing and command queues connects controller lookup, shared vectors, filtering, legacy command queues and command pools.
How is a device's desired power state constrained by its parents and children? Device power state machine follows state ordering, tree admission, requests, callouts, acknowledgments and stopping.
How is a remote method assigned to a queue or completed through an action? DriverKit dispatch and actions follows local class/method metadata, queue routing, actions, dispatch sources and external implementation boundaries.
How do subdescriptors, mappings and redirected backing interact? Memory descriptors and CPU mappings follows concrete backing, composite ranges, map compatibility, pager/cache policy and cleanup.
Are shared queues lossless, and what does a Mach notification acknowledge? Shared queues and notifications contrasts full-queue failure, overwrite detection, memory publication and completion messages.
How does a published device become BSD's root device? Bootstrap and BSD integration follows readiness resources, matching completion, media identity and VFS handoff.
What does a report sample capture, and how does device work retain its submitter? Reporting and performance control follows reporter buffer swaps, sample copying, state/histogram updates, work tokens and thread-group/coalition references.
How does a boot collection become started driver code, and when can it unload? Kernel collections and module lifetime follows metadata ingestion, dependencies, C++ initialization, start, matching and teardown.
How do serialized bytes become authorized property operations? Serialization and property transport follows all three parser families, graph ownership, binary formats and registry dispatch.
When does an NVRAM property update reach persistent storage? NVRAM variables and persistence follows namespace/permission checks, staged updates, raw/block sync, bank reclaim and controller boundaries.

For surrounding machinery, use Mach IPC, kernel execution and locking, VM, and VFS/storage. Platform code owns processor startup and the low-level sleep boundary; security explains the authorization mechanisms called from IOKit.

Sources inspected and limits

“Deep” means selected function bodies, state definitions, and adjacent cleanup/error paths were read together. It does not mean every line of a large source file was reviewed. “Survey” means declarations, selected boundaries, or build/source inventories were checked.

Area Sources and depth Limits
Bootstrap Deep selected paths: IOStartIOKit.cpp, IODeviceTreeSupport.cpp, platform init, IOKitBSDInit.cpp, BSD root mounting Full device-tree address/interrupt translation, boot recovery, firmware and concrete storage/network families remain separate.
Registry and matching Deep: IORegistryEntry.cpp, IOCatalogue.cpp, IOService.cpp, corresponding headers; selected notifier and arbitration bodies Every rematching policy, graph mutation race and user-visible notification variant is not exhaustively traced. Iterator and partial-attachment limitations are stated where visible.
Execution Deep: IOWorkLoop.cpp, IOCommandGate.cpp, IOEventSource.cpp, timer, interrupt/filter/controller and command-pool sources Concrete interrupt-controller hardware, every callback subclass and external event-link consumers remain separate. IOCommandQueue is identified as a legacy non-LP64 implementation.
User connections Deep: open, method dispatch, mapping, ownership and no-senders paths in IOUserClient.cpp, typed lookup in iokit_rpc.c Legacy method shims and all notification variants were surveyed rather than individually verified.
DriverKit Deep selected paths: server/RPC, launch/check-in, class/method metadata, queue routing, OSAction, dispatch sources and entitlement boundaries in IOUserServer.cpp. Interface inspection: DriverKit External executables, user dispatch scheduling, missing timer bodies, launch policy and every generated RPC method remain boundaries.
Memory transfer Deep selected paths: concrete buffer/composite descriptors, named entries, CPU map compatibility/redirect/pager lifetime, prepare/complete, UPL creation and DMA operations in IOMemoryDescriptor.cpp, descriptor subclasses and IODMACommand.cpp. Survey: IOMapper.cpp Concrete IOMMU drivers, cache instructions, every page-size/redirect combination and all VM faults remain outside this driver-layer trace.
Shared queues Deep selected paths: IODataQueue.cpp, IOSharedDataQueue.cpp, IOCircularDataQueueImplementation.h, IODataQueueDispatchSourceShared.h, asynchronous-result construction and selected queue test bodies No formal memory-order proof, exhaustive corruption/interleaving audit, kernel test execution or classic userspace framework reconstruction.
Reporting and performance control Deep selected paths: IOReporter.cpp, simple/state/histogram update bodies, legend publication and IOPerfControl.cpp tokens/work contexts External report aggregation, performance-controller algorithms, every telemetry format and concrete driver work cancellation remain separate.
Power Deep selected paths: state choice, power-tree admission, request/reply scheduling, driver-call/acknowledgment races and stop in IOServicePM.cpp; capability policy, client replies/cancellation, filesystem/tasks, CPU handoff and wake completion in IOPMrootDomain.cpp Every thermal, assertion, battery, dark-wake/AOT policy combination and concrete hardware sequence remain separate. Hibernation image creation/restoration now have platform chapters.
Object/runtime libraries Deep selected paths: OSObject.cpp, OSMetaClass.cpp, OSRuntime.cpp, collection and iterator ownership/mutation; OSSymbol.cpp pool insertion, ordinary/huge lookup, borrowed/permanent backing and delayed destruction; selected OSKext start/unload This is not a complete KXLD relocation review, every-container/concurrency audit, crypto review or account of every kernel-collection format.

The upstream startup guide, hibernation guide, and allocator guide provide useful context. Configuration guards such as CONFIG_KXLD, CONFIG_MACF, NO_KEXTD, HIBERNATION, XNU_TARGET_OS_*, and development/debug options select materially different paths. These pages describe source behavior; they do not infer which configuration a particular machine runs.

Search the handbook

Search every chapter, interface and code excerpt.

    Diagram

    Source preview

    Open full file