La Gaceta de Linux ...¡ haciendo a Linux un poco más divertido !

Diseccionando el Driver del Botón ACPI en el Kernel 2.6

Por Pramode C.E.

Traducción al español por Pablo Wolter
el día 29 de Abril de 2005, para La Gaceta de Linux

Un estudiante vino y me dijo que su sistema Linux no estaba apagándose automáticamente cuando el botón de encendido se presionaba. Era un problema con ACPI (Interface Avanzada de Configuración y Energía) que no estaba configurado adecuadamente en su maquina. Esta fue una oportunidad para aprender un poco acerca del manejo de energía y su implementación en Linux.  Yo no estaba particularmente interesado en el tema, pero leyendo unas pocas lineas bajo drivers/acpi me convencieron que podía transformar esto en una demostración interesante de como ir leyendo y descifrando el código del kernel

Empezando

El manejo de la energía es un asunto serio en los sistemas modernos -el Sistema Operativo debe ser capaz de determinar cosas como el nivel de carga de la batería, la temperatura de la CPU, etc. y tomar acciones inteligentes. ACPI esta diseñado con el objetivo de darle a los SO (N. del T. Sistemas Operativos) la mayor flexibilidad posible para lidiar con asuntos de administración de energía. Esto fue lo que fui capaz de entender haciendo una búsqueda en Google. Cuando un artículo mencionó que la especificación completa de ACPI sobrepasaba las 500 páginas, concluí que ésta no era mi taza de té. Sólo quería saber qué hacía el SO para bajar y apagar mi sistema en forma limpia cuando apretaba el botón de apagado; de ninguna manera iba a leer 500 páginas del manual para entender eso.

Algunos Experimentos

La primera cosa era averiguar si mi kernel tenia código ACPI compilado en él. Un rápido

dmesg | grep ACPI

resolvió el problema - este. Como con todas las cosas complejas (digamos USB), es una suposición importante que el kernel pueda contener algún codigo 'núcleo' para manejar las cosas realmente duras como comunicarse con el ACPI BIOS y encuentre que todos los 'eventos' han tomado parte (presionar el botón de apagado es un evento). El codigo que 'reacciona' a estos eventos debería ser más simple y debería estar mayormente escrito como un módulo. Teniendo en cuenta la filosofía básica de Unix de empujar la 'poliza' lo mus hacia el usuario posible, uno podría asumir que ésta 'reacción' podría principalmente hacer saber a un proceso a nivel usuario que algo ha pasado. Ahora, como hace un driver de dispositivo saber al proceso de espacio de usuario que algo paso? Dos de las maneras mus simples son usando un archivo proc y usando un archivo de dispositivo - el archivo proc es la mejor apuesta. Bastante seguro, mirando bajo /proc, veo un directorio llamado 'acpi'.

El último eslabón en la cadena podría ser un programa a nivel de usuario el cual lee algún archivo bajo /proc/acpi y realiza ciertas acciones basado en la información del evento que toma del kernel - digamos, un programa llamado 'acpid'. Un 'ps ax' me muestra que un programa como este esta corriendo. Corriendo un
strace -p <pid>

mostró al programa haciendo 'select' en dos descriptores de archivo 3 y 4. Matando acpid y corriendolo una vez mus de esta forma:

strace acpid

desplegó la siguiente línea como parte de la salida:
open("/proc/acpi/event", O_RDONLY);

Así, eso es! Sólo lee en /proc/acpi/event y sabras que 'eventos' de administración de energía han ocurrido. Mate acpid e hice un
cat /proc/acpi/event

El proceso se bloqueo. Presionando el botón de apagado resulto en la siguiente línea desplegada:
button/power PWRF 00000080 00000001

La cadena esta casi completa. Como sabe el código de núcleo ACPI que el botón ha sido presionado? El kernel no puede estar haciendo una especie de loop - interrupciones son la anica manera. Bastante seguro, un 'cat /proc/interrupts' mostró:
           CPU0       CPU1       
0: 11676819 11663518 IO-APIC-edge timer
1: 8998 7247 IO-APIC-edge i8042
2: 0 0 XT-PIC cascade
8: 1 0 IO-APIC-edge rtc
9: 3 1 IO-APIC-level acpi
12: 84 0 IO-APIC-edge i8042
(otras cosas borradas)

miremos la IRQ 9 - eso es en lo que estamos interesados. Presionando el botón resulta en que el contador de interrupciones se incrementa.

Aquí esta la cadena completa:
              Power Button
|
|
IRQ 9
|
|
Core ACPI Code
|
|
Button Driver
|
|
User Program Reading /proc/acpi/event

Leyendo el código

Un rápido vistazo bajo /usr/src/linux-2.6.5/drivers muestra un directorio llamado 'acpi' el cual tiene un archivo llamado 'button.c' - seguramente este debe ser el código del driver 'plug-in' el cual responde a los eventos del botón. Otro archivo llamado 'event.c' tiene rutinas que responden a las peticiones de abrir/leer desde el espacio de usuario.

Una petición 'read' del espacio de usuario en el archivo /proc/acpi/events resulta en una  invocación 'acpi_system_read_event'. Esta función a su vez invoca a 'acpi_bus_receive_event' el que se bloquea si ningún evento ha sido publicado por el código del driver del botón. Los eventos son publicados por una función llamada 'acpi_bus_generate_event' definida en el archivo bus.c.

Entendiendo button.c

Tener una idea de 'alto-nivel' del funcionamiento de código del driver del botón es bastante simple. Cuando tratas de obtener una comprensión del código de un driver como éste, es una buena idea sacar algunas líneas para asu concentrarte en lo esencial. Las funciones acpi_button_info_seq_show, acpi_button_info_open_fs, acpi_button_state_seq_show, acpi_button_state_open_fs, acpi_button_add_fs and acpi_button_remove_fs se pueden sacar sin problema. Unas pocas y simples líneas printk al principio de las funciones que encontremos interesantes nos darán una idea del flujo del control. Aquí esta la primera versión cortada de button.c:

Listado 1

Aquí están las cosas que he encontrado cuando el módulo se compila en forma separada y se carga en el kernel (2.6.5).

Es tiempo de nuevo de hacer el trabajo de adivinanza. La función acpi_button_init esta llamando a acpi_bus_register_driver con la dirección de un objeto de tipo 'struct acpi_driver':
static struct acpi_driver acpi_button_driver = {
.name = ACPI_BUTTON_DRIVER_NAME,
.class = ACPI_BUTTON_CLASS,
.ids = "ACPI_FPB,ACPI_FSB,PNP0C0D,PNP0C0C,PNP0C0E",
.ops = {
.add = acpi_button_add,
.remove = acpi_button_remove,
},
};

La sospecha es que acpi_button_add esta hablando con el código 'core' de bajo nivel y haciendo arreglos para cuando algunos manejadores de funciones sean llamados cuando eventos como power o sleep sean detectados (bueno - un montón de ambigüedades aquí - el código fuente habla de POWER y POWERF button y SLEEP y SLEEPF button. Puede alguien que conozca el código mejor alumbrarme para qué POWERF y SLEEPF son?) Como manejadores van a  ser invocados por muchos tipos de botones, la función acpi_bus_register_driver examinará los campos .ids de la estructura y analizará los nombres en ella uno por uno, invocando la función acpi_button_add cada vez que encuentre un nombre nuevo (esta puede ser una suposición estúpida - de verdad nunca he visto dentro de scan.c el que implementa la función acpi_bus_register_driver. El problema es, no entiendo por qué 'add' es llamado solo dos veces).

La siguiente revisión de button.c, mantuve sólo un nombre, ACPI_FPB en el campo .ids y corte acpi_button_add/acpi_button_remove para manejar sólo el caso del botón de encendido. La línea más importante para ver en ésta función es la invocación deacpi_install_fixed_event_handler la que registra la función acpi_button_notify_fixed como un manejador a ser invocado cuando el sistema ACPI detecta actividad en el botón.

Listado 2

El manejador (acpi_button_notify_fixed) llama a acpi_button_notify el cual a su vez llama a acpi_bus_generate_event. Recuerda, la lectura en /proc/acpi/events fue bloqueada por acpi_bus_receive_event. La lectura (y el proceso a nivel de usuario) viene fuera del bloque cuando acpi_bus_generate_event es invocado!

Agregando una interfaz de driver char

Modifiquemos el driver para que use una interfaz distinta para comunicarse con la tierra del usuario - un simple driver char (bueno, esta es más bien una idea estúpida - pero tenemos que hacer algún cambio!). El método 'read' del driver se va a dormir llamando a 'interruptible_sleep_on' mientras la función acpi_button_notify_fixed lo despierta.

Listado 3

Digamos que el driver esta allotted un número mayor que 254. Hacemos un archivo de device:

mknod button c 254 0

y tratando 'cat button'. El proceso de lectura se va a dormir. Ahora, presionamos el botón de apagado e inmediatamente se va fuera del bloque!

Amarrando

Un scrip shell simple (llamemoslo 'silly_acpid')

#!/bin/sh
cat /home/pce/button
poweroff

Córrelo en el background y presiona el botón de apagado - voala, el sistema se baja y apaga automágicamente!

Lectura adicional y Referencias

La edición de Octubre 2003 de ACM Queue tiene un artículo introductorio acerca de ACPI para newbies; es una buena lectura. El proyecto ACPI de Linux tiene su página web aquí. El código fuente del simple programa 'acpi daemon' se puede descargar aquí. El proceso de compilación del modulo para la serie de kernel 2.6 es diferente del de la serie 2.4. Mas información está disponible bajo /usr/src/linux/Documentation/kbuild/.

 


[BIO] Soy un instructor trabajando para IC Software en Kerala, India. Me hubiese encantado ser un químico orgánico, pero hice la segunda mejor cosa posible, que es jugar con Linux y ensenar programación!

Copyright © 2004, Pramode C.E.. Publicado bajo los términos de la Open Publication license

Publicado en el número 106 de la Gaceta de Linux, Septiembre 2004