The Problem & Industry Shift The OpenBSD project is renowned for its security focus, but its hardware support philosophy is equally distinctive: it actively supports a vast array of obscure and obsolete devices, some of which feel like relics from a medieval technological era. While mainstream OSes drop support for legacy hardware to reduce maintenance costs, OpenBSD's driver architecture and coding standards allow it to keep these strange devices functional. This article explores the engineering behind OpenBSD's support for such devices, the challenges involved, and the trade-offs the project makes to preserve this unique capability. In an industry that pushes rapid hardware obsolescence, OpenBSD's approach is a contrarian shift. The project's commitment to "portability" isn't just about running on many platforms; it's about ensuring that even the strangest hardware—like ancient SCSI controllers or obscure network cards—can be used reliably. This is achieved through a clean abstraction layer, rigorous code review, and a driver model that separates bus handling from device logic. Architecture & Core Mechanics OpenBSD's device driver framework is built on a layered architecture that decouples hardware discovery from device operation. The core is the bus_space and bus_dma APIs, which abstract memory-mapped I/O and DMA operations across different bus types (PCI, ISA, etc.). This allows a single driver to work on multiple platforms without modification. For example, consider a hypothetical driver for a "medieval" device—say, a 1980s-era SCSI controller. The driver registers itself with the system via a cfattach structure, which defines the match and attach functions. During autoconfiguration, the kernel walks the device tree, calling match functions to determine if a device is present. Once matched, the attach function initializes the device and registers it with the appropriate subsystem. Below is a simplified ASCII diagram of the autoconfiguration process: +----------------+ +----------------+ +----------------+ | mainbus | --> | pci* | --> | scsi* | | (root bus) | | (PCI bus) | | (SCSI bus) | +----------------+ +----------------+ +----------------+ | | | | match/attach | match/attach | match/attach v v v +---------+ +---------+ +---------+ | device | | device | | device | +---------+ +---------+ +---------+ Each bus driver iterates over its children, calling their match functions. If a match returns success, the attach function is invoked. This recursive process builds a device tree that reflects the physical topology. For strange devices, the match function often relies on device IDs or even probing heuristics. For instance, some legacy devices lack proper PCI configuration registers, so drivers must perform risky I/O probes. OpenBSD mitigates this by requiring careful probe routines that avoid system crashes. Production Code Example Let's look at a simplified driver skeleton for a fictional "medieval" device—a 16-bit ISA sound card with a proprietary interface. The code below demonstrates key engineering decisions: using bus_space for I/O, handling interrupts, and implementing a minimal match function. /* * Example driver for a fictional medieval ISA device. * This is not a real driver but illustrates OpenBSD driver patterns. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> #include <sys/bus.h> struct medieval_softc { struct device sc_dev; bus_space_tag_t sc_iot; /* I/O space tag */ bus_space_handle_t sc_ioh; /* I/O space handle */ int sc_irq; /* IRQ number */ }; int medieval_match(struct device *, struct cfdata *, void *); void medieval_attach(struct device *, struct device *, void *); struct cfattach medieval_ca = { sizeof(struct medieval_softc), medieval_match, medieval_attach }; struct cfdriver medieval_cd = { NULL, "medieval", DV_DULL }; /* * Match function: check if the device is present at the given ISA I/O port. * We use bus_space_map to probe the port and read a signature. */ int medieval_match(struct device *parent, struct cfdata *cf, void *aux) { struct isa_attach_args *ia = aux; bus_space_tag_t iot = ia->ia_iot; bus_space_handle_t ioh; int rv = 0; /* Map the I/O port range (e.g., 0x300-0x307) */ if (bus_space_map(iot, 0x300, 8, 0, &ioh) != 0) return 0; /* Read a signature register; if it matches, the device is present */ if (bus_space_read_1(iot, ioh, 0) == 0x5A) rv = 1; bus_space_unmap(iot, ioh, 8); return rv; } void medieval_attach(struct device *parent, struct device *self, void *aux) { struct medieval_softc *sc = (struct medieval_softc *)self; struct isa_attach_args *ia = aux; /* Save bus tag and map I/O space for the device */ sc->sc_iot = ia->ia_iot; if (bus_space_map(sc->sc_iot, 0x300, 8, 0, &sc->sc_ioh) != 0) { printf("medieval: can't map I/O space\n"); return; } /* Set up interrupt */ sc->sc_irq = ia->ia_irq; if (sc->sc_irq != -1) { isa_intr_establish(ia->ia_ic, sc->sc_irq, IST_EDGE, IPL_BIO, medieval_intr, sc, sc->sc_dev.dv_xname); } printf("medieval: found strange device at 0x300\n"); } int medieval_intr(void *arg) { struct medieval_softc *sc = arg; /* Handle interrupt: read status, clear, etc. */ return 1; /* handled */ } Critical engineering decisions: Using bus_space_map ensures we don't access I/O ports without proper mapping, which is crucial on platforms like sparc64 where I/O is not directly addressable. The match function performs a minimal probe and unmaps immediately to avoid leaving resources allocated. Interrupt establishment uses the ISA interrupt controller abstraction, allowing the driver to work on different platforms. Performance, Cost & Trade-offs Supporting strange devices comes at a cost. Each driver adds code to the kernel, increasing memory footprint and compile time. More importantly, maintaining drivers for obsolete hardware consumes developer time that could be spent on modern features. The OpenBSD project accepts this trade-off because it aligns with its goal of running on as many platforms as possible, which is valuable for security research and embedded systems. Performance-wise, drivers for old devices are rarely performance-critical. However, the abstraction layers (bus_space, bus_dma) introduce a slight overhead compared to direct hardware access. In practice, this overhead is negligible for the types of workloads these devices handle. Security is another consideration. Legacy devices often lack proper security features, and their drivers may have vulnerabilities. OpenBSD mitigates this by rigorous code audits and by isolating drivers in userland where possible (e.g., using uvisor for some devices). However, for kernel-resident drivers, the attack surface is increased. The project's proactive security practices help mitigate this risk. Benchmarks are rarely published for such devices, but the real cost is in maintenance. For example, the com driver (serial ports) has been maintained for decades, with periodic fixes for new platforms. The OpenBSD team's commitment to clean code and documentation reduces this burden. Actionable Checklist / Summary When adopting OpenBSD for environments with legacy or unusual hardware, consider the following: Check hardware compatibility: Consult the OpenBSD hardware compatibility list (HCL) for your specific device. If it's not listed, you may need to write a driver. Understand the driver framework: Familiarize yourself with bus_space, bus_dma, and the autoconfiguration mechanism. Use match functions conservatively: Avoid risky probes that could hang the system. Prefer device IDs when available. Leverage userland drivers: For devices that can be driven from userland (e.g., via uvisor), do so to reduce kernel risk. Contribute back: If you write a driver, submit it to the OpenBSD project. Follow the style guidelines and ensure it compiles on multiple architectures. Monitor security: Keep your system updated, as drivers for old devices may receive security fixes. References OpenBSD Device Driver Framework OpenBSD bus_space(9) man page OpenBSD Hardware Compatibility List OpenBSD Style Guide for Kernel Code OpenBSD FAQ: Writing Device Drivers