wandres.dev
EL DEVICE MODEL · kobject, bus/class/driver

La tríada del modelo: bus_type, device_driver y device

El device model se sostiene sobre tres tipos que se relacionan entre sí: struct bus_type es el árbitro que conoce sus dos listas de dispositivos y drivers; struct device_driver describe lo que un driver sabe gobernar; struct device representa lo que hay físicamente conectado. La función match del bus es la bisagra que une a los otros dos.

⏱ 16 min

El device model tiene tres protagonistas y una regla de oro: un dispositivo pertenece a un bus, un driver pertenece a un bus, y es el bus quien decide si un dispositivo y un driver casan. struct bus_type, struct device_driver y struct device no son tres piezas sueltas sino un triángulo con el bus en el vértice: mantiene dos listas —una de dispositivos, otra de drivers— y una función, match, que actúa de casamentera entre ambas.

🎯 Al terminar esta lección sabrás
  • Conocer struct bus_type y sus dos listas: dispositivos y drivers.
  • Conocer struct device_driver y cómo apunta a su bus y a su tabla de compatibilidad.
  • Conocer struct device y cómo referencia su bus y, una vez enlazado, su driver.
  • Entender el papel central de bus->match como árbitro entre device y driver.

El bus: el árbitro y sus dos listas

El bus_type representa una clase de conexión —PCI, USB, I2C, platform— y es el nodo central del modelo. Mantiene internamente dos listas: la de todos los dispositivos de ese bus y la de todos los drivers registrados para él. Y expone la operación decisiva del modelo entero: match.

/* include/linux/device/bus.h (resumido) */
struct bus_type {
	const char *name;                                 /* aparece en /sys/bus/<name> */
	const struct attribute_group **dev_groups;        /* atributos de sus devices */
	const struct attribute_group **drv_groups;        /* atributos de sus drivers */

	/* EL ARBITRO: decide si este device casa con este driver */
	int  (*match)(struct device *dev, const struct device_driver *drv);
	int  (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
	int  (*probe)(struct device *dev);
	void (*remove)(struct device *dev);
	const struct dev_pm_ops *pm;
};

/* al iniciar el modulo del bus */
static struct bus_type mi_bus = {
	.name  = "mi_bus",
	.match = mi_match,
};
bus_register(&mi_bus);   /* crea /sys/bus/mi_bus con devices/ y drivers/ */

Registrar el bus con bus_register hace aparecer /sys/bus/mi_bus/ con dos subdirectorios, devices/ y drivers/, que son la cara visible de sus dos listas. Todo el emparejamiento del nivel siguiente gira alrededor de match.

El driver: qué sabe gobernar

Un device_driver describe la lógica capaz de manejar cierta clase de hardware. Apunta a su bus y lleva una tabla de compatibilidad que declara con qué dispositivos sabe entenderse.

/* include/linux/device/driver.h (resumido) */
struct device_driver {
	const char		*name;
	const struct bus_type	*bus;              /* a que bus pertenece */
	struct module		*owner;

	const struct of_device_id *of_match_table; /* con que dispositivos casa */
	const struct acpi_device_id *acpi_match_table;

	int  (*probe)(struct device *dev);         /* enlazar: arranca el hardware */
	void (*remove)(struct device *dev);        /* desenlazar: lo apaga */
	const struct dev_pm_ops *pm;
	struct driver_private	*p;                /* lista de los devices que gobierna */
};

driver_register(&mi_driver.driver);   /* lo inscribe en la lista del bus */

La tabla of_match_table es el criterio que la mayoría de buses consultan en match: contiene cadenas compatible o identificadores que se comparan contra los del dispositivo. El driver no nombra un dispositivo concreto; describe una familia, y el bus se encarga de casarlo con las instancias que aparezcan.

El dispositivo: qué hay conectado

Un struct device es una instancia física concreta enchufada al bus. Incrusta un kobject (de ahí su nombre en sysfs, su conteo de referencias y su lugar en el árbol), apunta a su bus y, una vez emparejado, a su driver.

/* include/linux/device.h (resumido) */
struct device {
	struct kobject		kobj;      /* ES un kobject: nombre, refcount, sysfs */
	struct device		*parent;   /* posicion en el arbol fisico */
	const struct bus_type	*bus;      /* como esta conectado */
	struct device_driver	*driver;   /* quien lo gobierna, o NULL si sin enlazar */
	struct device_private	*p;        /* enganche a las listas del bus */
	dev_t			devt;      /* major:minor para el nodo en /dev */
	struct class		*class;    /* que hace, funcionalmente */
	void	(*release)(struct device *dev);
};

device_register(&mi_dev);   /* lo publica: sysfs, uevent, e intenta emparejar */

Mientras driver sea NULL, el dispositivo existe pero nadie lo gobierna. El acto de rellenar ese puntero —el emparejamiento— es el tema de la lección 28.4.

Cómo se relacionan

El triángulo se cierra así: el bus guarda dos listas; cada device y cada device_driver apuntan al bus; y bus->match decide qué elemento de una lista se enlaza con cuál de la otra. Cuando el enlace se consuma, device->driver deja de ser NULL y aparecen enlaces simbólicos cruzados en sysfs.

flowchart TD
BUS[bus_type mi_bus] --> DL[lista de devices]
BUS --> RL[lista de drivers]
DL --> D1[device widget0]
DL --> D2[device widget1]
RL --> R1[driver mi_widget]
BUS -. match arbitra el enlace .-> D1
BUS -. match arbitra el enlace .-> R1
R1 -. gobierna tras enlazar .-> D1
🚌

bus_type

La clase de conexión y el árbitro. Guarda las dos listas y aporta match, probe y uevent. Vive en /sys/bus/<name>.

🧠

device_driver

La lógica. Apunta a su bus, declara con qué casa mediante su tabla, y aporta probe y remove. Vive en /sys/bus/<name>/drivers.

🔧

device

La instancia física. Incrusta un kobject, apunta a su bus y a su driver una vez enlazado. Vive en /sys/devices con enlaces desde el bus.

ℹ️
Bus no es lo mismo que clase

No confundas struct bus_type con struct class. El bus describe cómo está conectado un dispositivo —cómo se enumera y se le habla— y define el emparejamiento con drivers; se ve en /sys/bus. La clase describe qué hace funcionalmente —un disco, una red, un led— con independencia de por qué bus llegó; se ve en /sys/class. Un mismo dispositivo aparece bajo su bus y bajo su clase a la vez: son dos ejes ortogonales de la misma realidad.

La tríada separa tres preguntas que antes estaban enredadas

Observa qué es lo que realmente logra este triángulo, porque su elegancia es de diseño, no de código. Todo dispositivo del mundo plantea tres preguntas independientes: cómo está conectado (el bus), qué lógica lo maneja (el driver) y qué instancia concreta es esta (el device). Antes del device model esas tres preguntas vivían enmarañadas en cada subsistema, y por eso añadir un driver o soportar un dispositivo nuevo obligaba a tocar código entrelazado. La tríada las desacopla en tres tipos ortogonales, y de ese desacoplamiento brota su poder combinatorio: un solo driver de mi_bus gobierna sin cambios a las mil instancias de su familia que aparezcan; un solo bus arbitra sin conocerlos a todos los drivers y dispositivos que se registren en él; una sola instancia se deja gobernar por cualquier driver que el bus considere compatible. La relación no se cablea en tiempo de compilación sino que se descubre en ejecución a través de match, y ahí está la clave: el bus es el único que conoce el criterio de compatibilidad de su tecnología —comparar IDs PCI, cadenas compatible de un árbol de dispositivos, identificadores USB—, así que centralizarlo en bus->match permite que drivers y dispositivos se ignoren mutuamente hasta el instante del emparejamiento. Cuando internalices que la tríada no describe hardware sino que factoriza las tres dimensiones en las que el hardware varía —conexión, lógica e instancia—, verás por qué el kernel puede absorber decenas de miles de dispositivos distintos sin colapsar bajo su propio peso: cada dimensión crece por separado y el producto cartesiano lo resuelve una sola función.

⚔️ Cartografía la tríada de tu sistema
  1. Lista /sys/bus y elige un bus real; explora sus subdirectorios devices/ y drivers/ e identifica las dos listas de la teoría.
  2. Toma un dispositivo bajo /sys/bus/<bus>/devices/ y sigue el enlace driver hacia el driver que lo gobierna; luego el enlace inverso desde el driver.
  3. Localiza el mismo dispositivo bajo /sys/class y argumenta qué eje —conexión o función— representa cada ubicación.
  4. Enumera los campos de struct device que apuntan a los otros dos vértices de la tríada y explica cuál de ellos vale NULL antes del emparejamiento.
  5. Razona por qué match vive en el bus y no en el driver ni en el dispositivo, y qué se rompería si cada driver implementara su propia comparación.