Device Drivers and Hardware Support in Haiku
How Haiku's device manager matches modular drivers, builds a node tree, publishes /dev interfaces, handles removal, and limits support claims.
Hardware support is not a single yes/no property. A bus manager may enumerate a device, a driver may bind to it, a /dev node may appear, and an application may still lack a working higher-level function. Haiku’s device architecture makes those stages visible through kernel modules, a device-node tree, published device interfaces, and class-specific subsystems.
Haiku supports both the legacy BeOS driver architecture and its newer device-manager model. The official device-manager documentation calls the newer architecture a moving target in some areas, so driver authors must use current headers and in-tree examples rather than assuming that every historical design note is a frozen ABI.
The device manager builds a tree
The newer device manager represents hardware and implementation layers as device_node objects. A node has a module and can carry attributes, resources, parent/child relationships, and driver-specific interfaces. Starting from a root node, primary buses register children; an intelligent bus such as PCI can attach vendor, device, class, subclass, and interface information discovered from hardware.
Tree structure lets one physical function pass through several drivers. A PCI controller can own a child bus, which owns storage devices, which expose block devices consumed by partition and file-system layers. No single module must perform discovery, transport, block semantics, and file access.
The device manager can delay deeper exploration until a subsystem asks for a relevant path. Its documentation describes translating /dev path categories and device type information into a narrower search for matching modules. On-demand discovery reduces boot work and prevents every driver from probing every possible device blindly.
Nodes also reserve I/O ports, memory ranges, and other resources. Registration should fail cleanly when another node already owns a conflicting resource. A driver must not touch hardware before it has validated the parent interface and acquired what it needs.
Matching separates driver and device modules
A driver module implements discovery and binding operations. The documented API includes supports_device(), register_device(), initialization and teardown, child registration or rescanning, removal notification, and suspend/resume hooks. supports_device() evaluates a parent node and its attributes; it should return an accurate support score rather than claiming broad hardware it has never initialized.
register_device() creates the driver’s node and associates resources and attributes. init_driver() allocates state needed for the bound device, while uninit_driver() reverses it. Bus drivers can register fixed or dynamic children and respond to rescans or removal.
A separate device module publishes operations offered to clients. Its documented hooks include initialization, open, close, free, read, write, control, select/deselect, and removal handling. The close()/free() distinction matters: closing a file descriptor and releasing the per-open cookie can occur at different lifecycle points.
Module names carry API-version suffixes such as driver_v1 and device_v1 in the documented architecture. Use the constants and templates in current source. Inventing a similar name or exporting a partially compatible function table risks a load failure or worse, a call through the wrong layout.
Publishing under /dev defines the user boundary
A driver can call the device manager’s publish_device() to create a path beneath /dev, naming the device module that implements operations. User-space code opens that node through normal file-descriptor APIs and uses read, write, control, or notification behavior defined by the device class.
The path is a stable interface only when its semantics are documented. A control operation needs a versioned opcode, exact input/output sizes, privilege checks, and error statuses. Never copy a user pointer into kernel state or trust a claimed length. Validate ranges before mapping, allocating, or programming DMA.
Not every hardware function should expose a bespoke user interface. Storage, networking, audio, input, and graphics have subsystem contracts that applications already understand. A driver should connect to the appropriate Haiku framework so one supported device works across native applications instead of requiring a vendor-specific tool.
Device names and “pretty name” attributes improve diagnostics but are not identity by themselves. The device-manager documentation provides a unique-ID attribute for hardware that supplies one. Persistent user preferences should combine stable identity with class and topology information and tolerate a device moving to another port.
Hot-plug requires a lifecycle, not just detection
Bus support can report newly connected or removed hardware. The device tree can add or remove nodes, and the driver/device callbacks receive removal notification. That mechanism makes hot-plug possible, but safe removal depends on every open request, transfer, and child node obeying a teardown protocol.
On removal, stop scheduling new hardware work, mark open handles unusable, cancel or complete pending I/O with errors, detach interrupts, release DMA mappings, unregister children, and free state only after no callback can access it. A physical disconnect can occur during a read or control call; code that assumes removal happens only while idle will eventually use freed memory.
User-space applications also need removal semantics. A blocked operation must wake, and the returned error should distinguish device loss from end-of-file. Reconnecting similar hardware may create a new kernel object; stale file descriptors and node IDs must not silently attach to it.
Hot-plug support varies by bus and class. The existence of device_removed() in an API is not proof that every driver implements flawless surprise removal. Test the exact controller, device, transfer load, and reconnect sequence before advertising hot-plug as supported.
Porting code is integration work
Haiku sometimes adapts drivers or hardware knowledge from compatible open-source systems. Source availability can provide register definitions and tested algorithms, but it does not supply Haiku integration automatically. Interrupt handling, memory allocation, DMA, locking, timers, firmware loading, network or media APIs, power transitions, and error conventions must match the host kernel.
Imported code also carries license and update responsibilities. Preserve required notices, document the upstream revision, isolate compatibility layers, and track security fixes. A one-time copy that cannot be compared with upstream becomes increasingly expensive and may leave known parser or firmware defects unfixed.
Avoid claiming support based on a matching PCI or USB ID alone. A family can contain revisions with different firmware, queues, radio components, or power behavior. The correct statement names detection, tested operations, performance constraints, suspend/removal status, and known missing features.
Driver safety and concurrency
Kernel code runs with privileges that make bounds and lifetime bugs system-wide. Validate every descriptor supplied by hardware and every request supplied by userland. Check integer overflow in buffer sizes, enforce DMA address limits, and never assume the device will return a length within the buffer it was given.
Interrupt handlers should perform bounded work, acknowledge the device correctly, and defer expensive processing. Locks shared between interrupt and thread context need rules appropriate to both contexts. Do not sleep while holding a spinlock or call into unknown subsystems with internal locks held.
Memory ordering matters with DMA and multiprocessor callbacks. Ownership of each descriptor must be explicit: driver, device, or completed queue. Teardown must wait for the hardware and every CPU-visible callback before freeing a ring. Timeouts should trigger bounded recovery, not infinite reset loops inside the kernel.
Fault injection improves confidence: allocation failure, truncated firmware, stuck completion, repeated interrupt, disconnect under load, and invalid control requests should return errors without leaking resources or panicking. A driver that works only on the successful probe path is not ready hardware support.
Diagnose support by layer
Start with exact hardware identifiers and bus enumeration. If the parent bus does not see the function, a class driver cannot bind. If a node exists but no driver claims it, inspect supported IDs and module loading. If the driver binds but publishes no expected device, capture its initialization error and resources.
When /dev contains the device, test the class subsystem before an end application. For networking, separate interface creation, link, address, and traffic. For audio, separate enumeration, format negotiation, playback, capture, and hot removal. For storage, separate controller, media, partition scan, file-system mount, and sustained I/O.
Use current Haiku hardware compatibility information and release notes for purchase decisions, then verify the exact model and revision. A virtual machine reduces physical variation by presenting a known virtual controller set, which is useful for evaluating Haiku but does not validate a laptop’s Wi-Fi, audio codec, touchpad, or power management.
Capture kernel logs and safe-mode comparisons before replacing modules. If disabling one driver makes the system boot, that is evidence about the binding or initialization path, not proof the hardware is defective. Hardware support advances one subsystem and device family at a time; precise reports make that progress possible.
Related:
- Fixing Display and Graphics Driver Issues in Haiku
- The Haiku Kernel: A Modular, Pervasively Multithreaded Design
Sources: