[Aprendizaje del controlador de Linux] controlador de caracteres GPIO

Este artículo tiene como objetivo resumir la experiencia de aprender GPIO basado en caracteres basado en la placa de desarrollo IMX6ULL PRO de Wei Dongshan.
[1] En el caso de que no haya operación de hardware, ¿qué partes debe incluir un controlador de caracteres simple?
El siguiente nombre de dispositivo predeterminado: dispositivo
Dos variables básicas:
principal → número de dispositivo principal
struct class *device_class → clasificación de dispositivo
Estas dos variables deben usarse al registrar y generar el dispositivo.
(1) Escribir funciones de operación alrededor de la estructura de archivos del dispositivo file_operations.
Su estructura incluye cinco atributos: entre ellos, abrir, leer, escribir y liberar son en realidad punteros de función.
propietario【ESTE_MÓDULO】→Esclavo
abierto→Función de lectura cuando se abre el archivo
→Función de escritura llamada cuando se lee el archivo
→Función de liberación llamada cuando se escribe el archivo
→Función llamada cuando se cierra el archivo
(2) Declarar la estructura del dispositivo file_operations
es en realidad es permitir que el puntero de función en file_operations apunte a la función de operación escrita por el usuario.

static struct file_operations device_drv = {
    
    
   .owner = THIS_MODULE,
   .open  = device_drv_open,
   .read  = device_drv_read,
   .write = device_drv_write,
   .release = device_drv_close,
}

(3) Escriba la función __init device_init(void).Esta
función es la función que el dispositivo de caracteres ingresa primero cuando se carga, pero por qué debe ser así, presiónela primero.
Por ejemplo:
insmod device.ko
al cargar el módulo device.ko, primero ingresará a la función device_init para registrar y generar el dispositivo.
El primer paso es registrarse:
El llamado registro es el proceso de solicitar un número de dispositivo del kernel.

major = register_chrdev(0,"device",&device_drv);
第二步生成类:
device_class = class_create(THIS_MODULE,"device_class");

Hasta ahora, se han utilizado dos variables básicas.
El tercer paso es generar el archivo del dispositivo:

device_create(device_class,NULL,MKDEV(major,0),NULL,"device");

Este paso es crucial porque genera un archivo de dispositivo operable en el directorio /dev/.
Si necesita generar una serie de dispositivos del mismo tipo, debe cambiar el número de dispositivo menor y el nombre de archivo del dispositivo correspondiente. Por ejemplo:

for(i = 0;i<num;i++)
device_create(device_class,NULL,MKDEV(major,i),NULL,"device%d",i);

De esta forma, se generarán una serie de archivos de dispositivos según el número de num, y los nombres de los archivos son: dispositivo0, dispositivo1····dispositivoN. Vale la pena señalar que sus números de dispositivos principales se determinan durante el registro, y los números de dispositivos secundarios se completan de acuerdo con el orden del ciclo y se determinan en MKDEV (primario, menor).
(4) Escriba la función __exit device_exit(void).Esta
función es la función a la que ingresa el dispositivo de caracteres cuando se descarga, pero por qué debe ser así, presiónela primero.
Por ejemplo:

rmmod device.ko

Al desinstalar el módulo device.ko, primero ingresará a la función device_exit para cerrar la sesión del dispositivo, borrar la clase y destruir el archivo del dispositivo.
Paso 1: Destrucción de archivos del dispositivo

 device_destroy(device_class,MKDEV(major,0));

Paso dos: limpieza de la clase

 class_destroy(device_class);

Paso tres: cierre de sesión del dispositivo

 unregister_chrdev(major,"device");

(5) Mejorar la información del dispositivo
Este paso es principalmente para especificar los punteros de función que deben cargarse cuando se carga y descarga el módulo, y para complementar la información de pasaporte del módulo.

module_init(device_init);//指定加载时使用device_init
module_exit(device_exit);//指定卸载时使用device_exit
MODULE_LICENSE("GPL");

Resumen: un controlador de caracteres básico incluye principalmente estos cinco pasos, que giran en torno a tres elementos básicos: principal, struct class *device_class y file_operations.
Cuando se carga el módulo del dispositivo (.ko), primero ingresa la función de inicio especificada para registrar el dispositivo, generar clases y archivos de dispositivos. Luego, de acuerdo con los atributos en file_operations, se llamará a la función de operación correspondiente al operar el archivo del dispositivo. Finalmente, cuando se desinstala el módulo del dispositivo (.ko), ingresará a la función de salida especificada para destruir el archivo del dispositivo, borrar la clase y cancelar el número del dispositivo.
Las dos variables major y struct class *device_class se ejecutan a través de la carga y descarga de módulos de dispositivos. Al cargar, major obtiene el número de dispositivo principal proporcionado por el kernel al dispositivo y device_class obtiene la clase de dispositivo. A continuación, genere los archivos de dispositivos operables correspondientes en el directorio /dev/ de acuerdo con el número de dispositivo principal, el número de dispositivo secundario y la clase de dispositivo.
file_operations se ejecuta a través de la apertura, lectura/escritura y cierre de archivos de dispositivos operables, y los miembros en sus atributos apuntan a las funciones de operación bajo diferentes operaciones.
[2] Cómo manejar GPIO
En el microcontrolador, hay tres pasos principales para hacer que GPIO encienda el LED:
(1) Habilite el reloj GPIOx
(2) Declare la estructura de configuración de GPIO y configure el GPIO en las propiedades de la estructura, y finalmente configure la función GPIO_Init para la inicialización.
(3) Setbit/Resetbit el GPIO.
Pero, de hecho, ya sea habilitando el reloj o configurando el GPIO, todo esto se implementa en el registro. Es similar en IMX6ULL, su GPIO se divide principalmente en tres partes para la gestión:
(1) CCM_CCGRx → registro de reloj
(2) IOMUXC → controlador de multiplexor de entrada y salida (multiplexación de pines de tubo, pull high pull low, loopback, etc. conjunto)
(3) GPIO → Registro GPIO (DR\GDIR\PSR\ICRx\IMR\ISR\EDGE_SEL)
La primera parte es similar al control del reloj del microcontrolador.
La segunda parte es similar a la configuración de la microcomputadora de un solo chip para la estructura GPIO, pero esta vez los registros ya no se colocan junto con el GPIO, sino que son administrados exclusivamente por IOMUXC.
La tercera parte es similar a la operación GPIO de la microcomputadora de un solo chip, lectura/escritura de niveles altos y bajos o terminales de lectura, etc.
Diagrama del módulo interno GPIO
Entonces, ¿cómo operar estos registros? Utilice ioremap para asignar el espacio de direcciones de E/S al espacio de direcciones virtuales del kernel para facilitar el acceso. Donde hay mapeo, hay desmapeo: iounmap. Entonces, ¿en qué orden debemos organizar estos tres pasos en el controlador?
En la microcomputadora de un solo chip, estos tres pasos a menudo se integran en la etapa de inicialización del sistema de los dos primeros pasos, y la escritura de niveles altos y bajos se coloca en la etapa del programa de usuario.
El programa del controlador se divide en: cargar módulo→init→usar APP para operar el archivo de hardware generado→descargar módulo→salir En términos
generales, el método de escritura más simple es colocar el mapa de memoria en init y configurar CCM e IOMUXC en el archivo abierto escenario En la fase de lectura y escritura, lee y escribe registros GPIO.
Pero esto tiene una desventaja, es decir, no es del todo portátil, porque el mapeo de memoria ya está involucrado en la etapa de inicio y la dirección de mapeo es inseparable del hardware.
Por lo tanto, la forma más segura es abstraer el dispositivo en una estructura nuevamente y luego escribir el programa del controlador correspondiente a la placa de acuerdo con las diferentes condiciones de placa/IO, y el controlador de caracteres es más similar a la interfaz para llamar a este controlador. La inicialización de la asignación de memoria y CCM e IOMUXC se colocan en la función de apertura del subcontrolador.
Por ejemplo: para abstraer un dispositivo como este:

struct Device_operations{
    
    
   int(*init)(int which);//init函数指针
   int(*write)(int which,int value);//写函数指针
   int(*read)(int which);//读函数指针
   int(*exit)(int which);//exit函数指针
};

En el programa principal se puede utilizar así:

struct Device_operations *p_dev_opr;
p_dev_opr->init(iminor);
p_dev_opr->write(iminor,value);
value = p_dev_opr->read(iminor);
p_dev_opr->exit(iminor);

¿Cómo resolver el problema de apuntamiento del puntero de función? La función de inicialización de la estructura de la subrutina se puede llamar en la función init del programa principal.

p_dev_opr = get_board_device_opr();
static struct Device_operations board_demo_device_opr={
    
    
   .init = board_demo_device_init;
   .exit = board_demo_device_exit;
   .write = board_demo_device_write;
   .read = board_demo_device_read;
};
struct Device_operations *get_board_device_opr(void)
{
    
    
  return &board_demo_device_opr;
}

De esta forma podemos mapear memoria, configurar, etc. en la subrutina board_demo_device_init. Si se cambia la placa y la función es la misma, solo necesitamos reemplazar la subrutina al compilar, y el programa principal no se puede cambiar.

Supongo que te gusta

Origin blog.csdn.net/qq_32006213/article/details/128964268
Recomendado
Clasificación