Skip to content

Instantly share code, notes, and snippets.

@ghazanhaider
Last active September 5, 2025 14:57
Show Gist options
  • Save ghazanhaider/2e42cf3bccca96c2d99bdfc9b69243bd to your computer and use it in GitHub Desktop.
Save ghazanhaider/2e42cf3bccca96c2d99bdfc9b69243bd to your computer and use it in GitHub Desktop.
Kernel driver model

Bus

  • Bus probes all devices
  • Bus registers all drivers usable for the bus
  • Bus then 'binds' driver to each device discovered:
    • When device_register is called for a device, it is inserted into the end of this list.
    • The bus object also contains a list of all drivers of that bus type.
    • These are the two events which trigger driver binding.
    • If a match is found, the device's driver field is set to the driver and the driver's probe callback is called.
    • This gives the driver a chance to verify that it really does support the hardware, and that it's in a working state.
    • When a driver is attached to a device, the device is inserted into the driver's list of devices.
    • Upon the successful completion of probe, the device is registered with the class to which it belongs
    • Device drivers belong to one and only one class, and that is set in the driver's devclass field.
    • devclass_add_device() is called to enumerate the device within the class and actually register it with the class

Driver

Adding driver (driver_register)

  • The bus's list of devices is iterated over to find a match
  • Devices that already have a driver are skipped.

Remove

  • When a device is removed, the reference count for it will eventually go to 0.
  • When it does, the remove callback of the driver is called.

Device

When a bus driver probes and finds devices, it registers the device: int device_register(struct device * dev);

struct device {
        struct list_head g_list;              // Node in the global device list.
        struct list_head node;                // Node in device's parent's children list.
        struct list_head bus_list;            // Node in device's bus's devices list.
        struct list_head driver_list;         // Node in device's driver's devices list.
        struct list_head intf_list;           // List of intf_data. Each interface supported
        struct list_head children;            // List of child devices.
        struct device   * parent;

        char    name[DEVICE_NAME_SIZE];       // ASCII description of device. 
        char    bus_id[BUS_ID_SIZE];          // ASCII representation of device's bus position <bus>:<slot>.<id>

        spinlock_t      lock;                 // Spinlock for the device.
        atomic_t        refcount;             // Reference count on the device.

        struct bus_type * bus;                // Pointer to struct bus_type that device belongs to.
        struct driver_dir_entry dir;          // Device's sysfs directory.

	u32		class_num;                            // Class-enumerated value of the device.

        struct device_driver *driver;         // Pointer to struct device_driver that controls the device.
        void            *driver_data;         // Driver-specific data.
        void            *platform_data;       // Bus, GPIO etc.. pre-DTS?

        u32             current_state;        // Current power state of the device.
        unsigned char *saved_state;           // Pointer to saved state of the device.

        void    (*release)(struct device * dev);
};

Class

A device class describes a type of device, like an audio or network device. Device classes are agnostic with respect to what bus a device resides on. There is no list of devices in the device class. Only list of drivers, which then have a list of devices

Adding a device to it

  • Each time a device is added to the class, the class's devnum field is incremented and assigned to the device.
  • The field is never decremented, so if the device is removed from the class and re-added, it will receive a different enumerated value.

class struct:

struct device_class {
	char			* name;
	rwlock_t		lock;
	u32			devnum;
	struct list_head	node;

	struct list_head	drivers;
	struct list_head	intf_list;

	struct driver_dir_entry	dir;
	struct driver_dir_entry	device_dir;
	struct driver_dir_entry	driver_dir;

	devclass_add		add_device;
	devclass_remove		remove_device;
};

Example class:

struct device_class input_devclass = {
        .name		= "input",
        .add_device	= input_add_device,
	.remove_device	= input_remove_device,
};

Adding/removing device classes:

int devclass_register(struct device_class * cls);
void devclass_unregister(struct device_class * cls);

Kobjects

A kobject is an object of type struct kobject Kobjects have a name and a reference count. A kobject also has a parent pointer (allowing objects to be arranged into hierarchies), a specific type, and, usually, a representation in the sysfs virtual filesystem. No structure should EVER have more than one kobject embedded within it. A ktype is the type of object that embeds a kobject. The ktype controls what happens to the kobject when it is created and destroyed.

A kset is a group of kobjects. When you see a sysfs directory full of other directories, generally each of those directories corresponds to a kobject in the same kset.

Devres

devres is basically linked list of arbitrarily sized memory areas associated with a struct device. Each devres entry is associated with a release function. Managed interface is created for resources commonly used by device drivers using devres. For example, coherent DMA memory is acquired using dma_alloc_coherent(). The managed version is called dmam_alloc_coherent(). It is identical to dma_alloc_coherent() except for the DMA memory allocated using it is managed and will be automatically released on driver detach.

  • Devres is memory types usable for drivers
  • Alloc is all managed so it is automatically released

Example:

  struct dma_devres {
	size_t		size;
	void		*vaddr;
	dma_addr_t	dma_handle;
  };

  static void dmam_coherent_release(struct device *dev, void *res)
  {
	struct dma_devres *this = res;

	dma_free_coherent(dev, this->size, this->vaddr, this->dma_handle);
  }

  dmam_alloc_coherent(dev, size, dma_handle, gfp)
  {
	struct dma_devres *dr;
	void *vaddr;

	dr = devres_alloc(dmam_coherent_release, sizeof(*dr), gfp);
	...

	/* alloc DMA memory as usual */
	vaddr = dma_alloc_coherent(...);
	...

	/* record size, vaddr, dma_handle in dr */
	dr->vaddr = vaddr;
	...

	devres_add(dev, dr);

	return vaddr;
  }

Porting

Step 0: Read include/linux/device.h for object and function definitions

Step 1: Registering the bus driver.

struct bus_type pci_bus_type = {
  .name           = "pci",
};
  • Register the bus type.
static int __init pci_driver_init(void)
{
        return bus_register(&pci_bus_type);
}

subsys_initcall(pci_driver_init);
  • Export the bus type for others to use.
extern struct bus_type pci_bus_type;
EXPORT_SYMBOL(pci_bus_type);
  • This will cause the bus to show up in /sys/bus/pci/ with two subdirectories: 'devices' and 'drivers'.

Step 2: Registering Devices.

struct pci_dev {
       ...
       struct  device  dev;            /* Generic device interface */
       ...
};
  • Initialize the device on registration.

  • Register the device.

    Once the generic device has been initialized, it can be registered with the driver model core by doing:

device_register(&dev->dev);

Step 3: Registering Drivers.

struct pci_driver {
       ...
       struct device_driver    driver;
};
  • After initializing thedriver, register it:
driver_register(&drv->driver);

Step 4: Define Generic Methods for Drivers.

/* initialize common driver fields */
        drv->driver.name = drv->name;
        drv->driver.bus = &pci_bus_type;
        drv->driver.probe = pci_device_probe;
        drv->driver.resume = pci_device_resume;
        drv->driver.suspend = pci_device_suspend;
        drv->driver.remove = pci_device_remove;

        /* register with core */
        driver_register(&drv->driver);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment