Skip to content

Device Manager

OpenCode Bot edited this page May 9, 2026 · 1 revision

Device Manager

Central registry for hardware devices, managing the device tree, driver binding, and lifecycle in JNode.

Overview

The DeviceManager (core/src/driver/org/jnode/driver/DeviceManager.java) is the heart of JNode's hardware discovery and management system. It maintains the complete device tree, binds devices to their drivers, handles device startup/shutdown ordering, and provides a query API for locating devices by ID or by the DeviceAPI they implement.

The device tree is hierarchical, rooted at the system bus. Each bus can have child buses or leaf devices attached. The Bus abstraction (Device.java, Bus.java) mirrors the physical topology: the PCI bus sits under the system bus, USB controllers sit under PCI, and individual devices (network cards, disk controllers, keyboards) sit under their respective bus controllers.

Key Components

Class Location Purpose
DeviceManager org/jnode/driver/DeviceManager.java Interface defining the public API
AbstractDeviceManager org/jnode/driver/AbstractDeviceManager.java Core implementation with device map, bus tree, listener dispatch
DefaultDeviceManager org/jnode/driver/DefaultDeviceManager.java Concrete implementation; loads finders/mappers from plugin extension points
Device org/jnode/driver/Device.java Software representation of a hardware device
Bus org/jnode/driver/Bus.java A (potentially hierarchical) collection of devices
Driver org/jnode/driver/Driver.java Abstract base class for device controllers
DeviceToDriverMapper org/jnode/driver/DeviceToDriverMapper.java Interface for matching devices to drivers
DeviceFinder org/jnode/driver/DeviceFinder.java Interface for hardware discovery
DeviceAPI org/jnode/driver/DeviceAPI.java Marker interface for device capability interfaces

How It Works

Device Tree Structure

The device tree is a directed acyclic graph rooted at the system bus (AbstractDeviceManager.getSystemBus()). Every Device and Bus has a reference to its parent bus:

// System bus is the root
Bus systemBus = deviceManager.getSystemBus();

// Each device knows its parent bus
Device dev = deviceManager.getDevice("eth0");
Bus parentBus = dev.getBus(); // e.g., the PCI bus

DeviceManager does not hold the tree structure directly in a single object — instead, each node (device or bus) holds a reference to its parent. The system bus is created in the AbstractDeviceManager constructor as a SystemBus (a package-private inner class extending Bus).

Registration and Driver Binding

When a new Device is registered via register(Device):

flowchart TD
    A[register device] --> B{ID already in map?}
    B -->|yes| E[throw DeviceAlreadyRegisteredException]
    B -->|no| C[set device manager reference]
    C --> D[findDriver device]
    D --> F{Driver found?}
    F -->|no| G[device registered but not started]
    F -->|yes| H[connect device to driver]
    H --> I[start device]
    I --> J[fire deviceRegistered event]
    J --> K[notify DeviceManagerListener]
Loading

The findDriver method iterates over all registered DeviceToDriverMapper instances (sorted by match level, lowest first). Each mapper returns a Driver or null:

// AbstractDeviceManager.findDriver()
protected final Driver findDriver(Device device) {
    synchronized (mappers) {
        for (DeviceToDriverMapper mapper : mappers) {
            final Driver drv = mapper.findDriver(device);
            if (drv != null) {
                return drv;
            }
        }
    }
    return null;
}

Match levels (DeviceToDriverMapper):

  • MATCH_DEVICE_PREDEFINED = -1 — Custom logic
  • MATCH_DEVICE_REVISION = 0 — Exact device + revision (best)
  • MATCH_DEVICE = 1 — Exact device
  • MATCH_DEVCLASS = 2 — Device class

After a driver is found, Device.setDriver(Driver) calls driver.connect(this), giving the driver a chance to initialize. Device startup follows (onStartDevice hook, then Driver.startDevice()).

Device API Pattern

Every device exposes one or more Device APIs — typed interfaces that applications use to interact with the hardware. All device APIs extend the marker interface DeviceAPI:

// The marker interface
public interface DeviceAPI { }

// Example: Serial port API
public interface SerialPortAPI extends DeviceAPI {
    void send(byte b);
    int receive();
    void setSpeed(int baud);
}

// In the driver's afterConnect() or startDevice():
device.registerAPI(SerialPortAPI.class, new MySerialPortDriver(this));

Clients look up APIs by class:

Device dev = deviceManager.getDevice("COM1");
SerialPortAPI serial = dev.getAPI(SerialPortAPI.class);
serial.setSpeed(9600);

The Device.implementsAPI() and DeviceManager.getDevicesByAPI() methods support service discovery without coupling to specific device implementations:

// Find all devices that expose a network API
Collection<Device> netDevices = deviceManager.getDevicesByAPI(NetDeviceAPI.class);

The getDevicesByAPI scan iterates all devices and checks each one:

public final Collection<Device> getDevicesByAPI(Class<? extends DeviceAPI> apiClass) {
    final ArrayList<Device> result = new ArrayList<Device>();
    for (Device dev : devices.values()) {
        if (dev.implementsAPI(apiClass)) {
            result.add(dev);
        }
    }
    return result;
}

API registration also propagates parent interfaces: if SerialPortAPI extends CharacterDeviceAPI which extends DeviceAPI, registering under SerialPortAPI automatically registers under both parent interfaces.

Loading Finders and Mappers via Plugins

DefaultDeviceManager listens to two plugin extension points:

  • org.jnode.driver.finders — contributions are DeviceFinder implementations that probe hardware
  • org.jnode.driver.mappers — contributions are DeviceToDriverMapper implementations that match devices to drivers

When a plugin extension is added, extensionAdded() calls findDevices() and findDeviceDrivers() to re-evaluate the device tree.

// DefaultDeviceManager.extensionAdded()
public final void extensionAdded(ExtensionPoint point, Extension extension) {
    loadExtensions();
    findDeviceDrivers();
}

Mappers are sorted by match level so more specific mappers (e.g., exact PCI vendor/device ID) take precedence over generic ones.

Device Lifecycle

State Transition
Registered DeviceManager.register()
Driver connected findDriver() succeeds → device.setDriver()
Started device.start()Driver.startDevice()
Stopped device.stop()Driver.stopDevice()
Unregistered DeviceManager.unregister() → device removed from map

The DeviceManager also supports renaming devices with optional auto-numbering (rename(device, "eth", true) produces eth0, eth1, etc.).

Command-line Device Control

Devices can be disabled at boot via the jnode.cmdline property:

// If cmdline contains "no<deviceId>", device start is blocked
if (cmdLine.indexOf("no" + device.getId()) >= 0) {
    shouldStart = false;
}

Event System

Two listener interfaces:

  • DeviceManagerListener — receives deviceRegistered, deviceUnregister events for all devices
  • DeviceListener — receives deviceStarted, deviceStop events

Both are fire-and-forget: listeners that take longer than 100ms to respond trigger an error log.

System Bus

The system bus is a special Bus created without a parent:

this.systemBus = new SystemBus(); // in AbstractDeviceManager constructor

Child buses (PCI, USB, ISA) attach to it. The system bus is the root of all hardware topology.

Gotchas

  • Multi-isolate API lookupsimplementsAPI() and getAPI() use class-name matching (clazz.getName().equals(...)) rather than == comparison, to support multi-isolate scenarios where the same API interface may be loaded in different isolates.

  • Device IDs can be renamed — A device's ID is mutable (Device.setId()), controlled by DeviceManager.rename(). Do not rely on device ID strings being stable across hotplug events.

  • Mapper match level ordering — The MapperComparator sorts mappers ascending by match level. A MATCH_DEVICE_REVISION mapper (level 0) will be queried before a MATCH_DEVCLASS mapper (level 2). Drivers that claim broad compatibility must have higher (worse) match levels.

  • Delayed driver binding — If findDriver() returns null, the device is registered but not started. findDeviceDrivers() is called whenever a new mapper extension is registered, allowing late-bound drivers to connect and start previously registered devices.

  • Plugin stop cascades to devices — When a plugin is stopped, its PluginListener automatically calls dev.stop(true) on any device driven by that plugin's classloader. This couples plugin lifecycle to device lifecycle.

  • Startup timing warnings — Device startups taking longer than 1 second log an info message; longer than 10 seconds (configurable defaultStartTimeout) log an error. Slow startDevice() implementations can cause boot hangs.

Related Pages

  • Driver-Framework — Broader driver subsystem including bus drivers, resource management, and API hierarchies.
  • Resource-Management — How drivers claim IRQ, I/O port, DMA, and memory resources.
  • Interrupt-Handling — How hardware IRQs are routed to device driver handlers.
  • Plugin-System — How DeviceFinder and DeviceToDriverMapper are registered via plugin descriptors.

Clone this wiki locally