Wei Dongshan Phase 2 Driver Encyclopedia-002_Input Subsystem_Lesson 13 Sección 2 Escritura del controlador del subsistema de entrada

1. ¿Cómo escribir el controlador?

Primero explique el marco del controlador y luego escriba el controlador first_drv para imprimir cierta información

Escribir el controlador first_drv requiere los siguientes pasos:

(1) Escriba el controlador first_drv_open first_drv_write

(2) Es necesario definir la estructura file_operations para encapsular la función del controlador first_drv_open first_drv_write

Para los dispositivos de caracteres, se utilizan comúnmente los siguientes miembros de file_operations:
Inserte la descripción de la imagen aquí
(3) Función de carga del módulo, a través de la función register_chrdev (major, "first_drv" y first_drv_fops) para registrar el dispositivo de caracteres

(4) Escriba la función de entrada first_drv_init del controlador para llamar a la función de registro register_chrdev (),

(5) Modifique la función de entrada a través de module_init () para que el kernel sepa que existe esta función

(6) Escriba la función de salida first_drv_exit del controlador, llame a esta función unregister_chrdev () para desinstalar,

(7) Modifique la función de salida a través de module_exit ()

(8) Declaración de licencia del módulo, la declaración más común es MODULE_LICENSE ("GPL v2")

Dos códigos de controlador de escritura

  1. archivo de cabeza
/* 参考drivers\input\keyboard\gpio_keys.c */

#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>

#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
  1. Definir la estructura input_dev
struct input_dev {
    
          

       void *private;
       const char *name;  //设备名字
       const char *phys;  //文件路径,比如 input/buttons
       const char *uniq;   
       struct input_id id;

 
       unsigned long evbit[NBITS(EV_MAX)];  //表示支持哪类事件,常用有以下几种事件(可以多选)
       //EV_SYN      同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
       //EV_KEY       键盘事件
       //EV_REL       (relative)相对坐标事件,比如鼠标
       //EV_ABS       (absolute)绝对坐标事件,比如摇杆、触摸屏感应

       unsigned long keybit[NBITS(KEY_MAX)];   //存放支持的键盘按键值
                                    //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)

       unsigned long relbit[NBITS(REL_MAX)];    //存放支持的相对坐标值
       unsigned long absbit[NBITS(ABS_MAX)];   //存放支持的绝对坐标值
       unsigned long mscbit[NBITS(MSC_MAX)];   //存放支持的其它事件,也就是功能
       unsigned long ledbit[NBITS(LED_MAX)];    //存放支持的各种状态LED
       unsigned long sndbit[NBITS(SND_MAX)];    //存放支持的各种声音
       unsigned long ffbit[NBITS(FF_MAX)];       //存放支持的受力设备
       unsigned long swbit[NBITS(SW_MAX)];     //存放支持的开关功能

 ... ...
  1. implementación de la función buttons_init
static struct input_dev *buttons_dev;

static int buttons_init(void)
{
    
    
	int i;
	
	/* 1. 分配一个input_dev结构体 */
	buttons_dev = input_allocate_device();

	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);//按键类事件
	set_bit(EV_REP, buttons_dev->evbit);//重复类事件
	
	/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
	set_bit(KEY_L, buttons_dev->keybit);
	set_bit(KEY_S, buttons_dev->keybit);
	set_bit(KEY_ENTER, buttons_dev->keybit);
	set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

	/* 3. 注册 */
	input_register_device(buttons_dev);
	
	/* 4. 硬件相关的操作 */
	
	/*4.2 定时器初始化*/
	init_timer(&buttons_timer);//防抖动的
	buttons_timer.function = buttons_timer_function;//定义定时器服务函数
	add_timer(&buttons_timer);
	
	/*4.2 注册4个中断*/
	for (i = 0; i < 4; i++)
	{
    
    
		request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name,&pins_desc[i]);
	}
	
	return 0;
}

Analizar: cuando llamas a input_register_devicetiempodev和handler两者是否匹配?

buttons_exitImplementación de la función correspondiente

static void buttons_exit(void)
{
    
    
	int i;
	for (i = 0; i < 4; i++)
	{
    
    
		free_irq(pins_desc[i].irq, &pins_desc[i]);//对应request_irq
	}
	del_timer(&buttons_timer);//对应add_timer
	input_unregister_device(buttons_dev);//对应input_register_device
	
	input_free_device(buttons_dev);	//对应input_allocate_device
}
  1. Construye una estructura con cuatro botones
struct pin_desc{
    
    
	int irq;//中断号
	char *name;//名字
	unsigned int pin;//引脚号
	unsigned int key_val;//键值
};

struct pin_desc pins_desc[4] = {
    
    
	{
    
    IRQ_EINT0,  "S2", S3C2410_GPF0,   KEY_L},//S2按键对应L..
	{
    
    IRQ_EINT2,  "S3", S3C2410_GPF2,   KEY_S},
	{
    
    IRQ_EINT11, "S4", S3C2410_GPG3,   KEY_ENTER},
	{
    
    IRQ_EINT19, "S5",  S3C2410_GPG11, KEY_LEFTSHIFT},
};
  1. Interrumpir la rutina de servicio
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;//定时器相关结构体

/*按键中断服务函数*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    
    
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies+HZ/100);
	return IRQ_RETVAL(IRQ_HANDLED);
}

/*定时器中断服务函数*/
static void buttons_timer_function(unsigned long data)
{
    
    
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
    
    
		/*
			struct input_dev *dev, 设备
			unsigned int type, 表示支持哪类事件,键盘事件
			unsigned int code, 表示事件编码,KEY_L,KEY_S,KEY_ENTER,KEY_LEFTSHIFT
			int value,松开 : 最后一个参数: 0-松开, 1-按下 自己定义即可
		*/
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		/*
		*/
		input_sync(buttons_dev);
	}
	else
	{
    
    
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}
}

Flujo de trabajo:
inicialización completa ->
pulsación de tecla ->
activar botones de programa de interrupción_irq->
modificar temporizador, activar temporizador de interrupción después de 10ms ->
activar temporizador interrumpir programa botones_función_timer->
leer valor pin: pulsar / soltar- >
Si se pulsa: informar evento input_event

4. Analizar el proceso detrás del incidente informado.

  1. función input_event: (analizada en el artículo anterior)
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    
    
	struct input_handle *handle;
	...
	/* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
	list_for_each_entry(handle, &dev->h_list, d_node);
	if (handle->open)
		handle->handler->event(handle, type, code, value);
}
  1. En comparación con lo anterior, necesitamos construir la estructura completa del controlador por nosotros mismos, ahora el subsistema de entrada ha sido construido para completar file_operations y funciones relacionadas. ¡Solo necesitamos completar la parte del hardware!
static const struct file_operations evdev_fops = {
    
    
	.owner =	THIS_MODULE,
	.read =		evdev_read,
	.write =	evdev_write,
	.poll =		evdev_poll,
	.open =		evdev_open,
	.release =	evdev_release,
	.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl =	evdev_ioctl_compat,
#endif
	.fasync =	evdev_fasync,
	.flush =	evdev_flush
};

5. Prueba

  1. Modifique el archivo MAKE:
  2. Cargue el controlador:insmod buttons.ko
  3. ls -l / dev / event *
    Inserte la descripción de la imagen aquí
    Número de dispositivo principal: 13; número de dispositivo menor: 65; nombre: event1
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
    
    
	evdev->exist = 1;
	evdev->minor = minor;
	evdev->handle.dev = dev;
	evdev->handle.name = evdev->name;
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;
	sprintf(evdev->name, "event%d", minor);//设备名 event1

	evdev_table[minor] = evdev;

	//EVDEV_MINOR_BASE=64 次设备号 64+1=65
	//INPUT_MAJOR=13 主设备号13
	devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
	cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);
}	
  1. Método de comando de prueba 1 hexdump /dev/event1:;

Función: 1.open (/ dev / event1); 2.read (); 3. Mostrar los datos leídos dentro en hexadecimal

Número de línea ------------ segundos ----------- microsegundos -------
código de clase -valor 0000000-0bb2 0000-0e48 000c-0001- 0026 -0001 0000
0000010-0bb2 0000-0e54 000c-0000-0000-0000 0000
0000020-0bb2 0000-5815 000e-0001-0026-0000 0000
0000030-0bb2 0000-581f 000e-0000-0000-0000 0000

static void buttons_timer_function(unsigned long data)
{
    
    
	struct pin_desc * pindesc = irq_pd;
	unsigned int pinval;

	if (!pindesc)
		return;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
    
    
		/* 松开 : 最后一个参数: 0-松开, 1-按下 */
		/*
			type=EV_KEY=0x01;
			code=pindesc->key_val=38/31/28/42=0x26/0x...;
			value=0;
		*/
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
		input_sync(buttons_dev);
	}
	else
	{
    
    
		/* 按下 */
		input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
		input_sync(buttons_dev);
	}
}
  1. Método de comando de prueba 2

Si no se inicia QT:

cat / dev / tty1 Presione: s2, s3, s4 para obtener ls
o:
exec 0 </ dev / tty1

Acerca de exec 0</dev/tty1explicado de la siguiente manera:
en la entrada y salida de error estándar:

# ls -l /proc/770/fd #770表示shell
lrwx------    1 0        0              64 Jan  1 00:03 0 -> /dev/s3c2410_serial0 #标准输入是串口
lrwx------    1 0        0              64 Jan  1 00:03 1 -> /dev/s3c2410_serial0 #标准输出是串口
lrwx------    1 0        0              64 Jan  1 00:03 10 -> /dev/tty
lrwx------    1 0        0              64 Jan  1 00:03 2 -> /dev/s3c2410_serial0 #标准错误是串口

exec 0</dev/tty1: Representa la entrada de / dev / tty1, es decir, la entrada en el teclado como entrada estándar. Antes se ingresaba desde el puerto serie, ahora se ingresa desde el teclado.

Si se ha iniciado QT:

Puede hacer clic en el bloc de notas y presionar: s2, s3, s4

¿Por qué cat /dev/tty1no se puede lograr el efecto esperado usando el comando después de iniciar QT ?
Porque en este momento, las instrucciones anteriores se ejecutan a través del programa tty_io.c-> keyboard.c. keyboard.c también pertenece al marco del subsistema de entrada, no a buttons.c.
Consulte "Wei Dongshan Phase 2 Driver Encyclopedia-002_Input Subsystem_Lesson 13, Sección 2 keyboard.xmind"
Inserte la descripción de la imagen aquí

  1. Comando de prueba-Método 3-Significa exec 0</dev/tty1que la entrada de / dev / tty1, es decir, la entrada en el teclado, se utiliza como entrada estándar, la entrada que
    antes se obtenía del puerto serie, ahora es la entrada del teclado.

  2. ¿Cómo lograr una entrada repetida al presionar el botón sin soltarlo?

static int buttons_init(void)
{
    
    
	...
	/* 2. 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, buttons_dev->evbit);//按键类事件
	set_bit(EV_REP, buttons_dev->evbit);//重复类事件
	...
}

En este momento, cuando el teclado se presiona continuamente, se llama input_event ().
Consulte "Wei Dongshan Phase 2 Driver Encyclopedia-002_Input Subsystem_Lesson 13, Section 2 Keyboard Repeat Input"

Supongo que te gusta

Origin blog.csdn.net/xiaoaojianghu09/article/details/104756084
Recomendado
Clasificación