Linux驱动开发笔记

疑问
file_operation中每个操作函数的形参中inode的作用

file_operation定义了Linux内核驱动的所有的操作函数,每个操作函数与一个系统调用对应,对于字符设备来说,常用的函数有:llseekreadwritepool等等,这些操作函数都要需要完成实现。

file结构体代表一个打开的文件,其指针类型位filp。系统中每个打开的文件在内核空间中都关联一个file结构体,在打开文件时创建,在关闭后释放,其中定义了读写权限标志、读写模式标志、当前读写位置等信息,但其中在驱动中使用较多的是名为private_data的void类型的指针,大多被指向设备驱动中的设备结构体
inode结构体描述文件的访问权限、拥有者、所属组、文件大小、生成时间、访问时间、最后修改时间、设备号等信息,是Linux管理文件系统的最基本单位。其中在驱动中较为常用的信息是设备号,设备号被分为主设备号和次设备号,同一设备使用相同的主设备号,次设备号描述使用该驱动的设备序号

为实现“高内聚,低耦合”的软件设计理念,Linux驱动采用了内核加载的方式,将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块,用“rmmod”命令卸载具体驱动模块,相应地需要注册模块加载函数和模块卸载函数。

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。输入命令“cat /proc/devices”可以查看当前已经被使用掉的设备号

wangchenxiao@sdc-uvdise057:~$ cat /proc/devices
Character devices:1 mem····
Block devices:2 fd····

Linux 中每个设备都有一个设备号,设备号由主设备号次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,设备号由dev_t数据类型描述,是一个32位的数据类型,中高 12 位为主设备号,其范围为 0~4095,所低 20 位为次设备号
Linux中每个设备号是独一无二的,申请新的设备号不能与已有的设备号重复,为避免冲突,使用alloc_chrdev_regiond unregister_chrdev_region动态申请和注销设备号

1.使用insmodmodprobe加载驱动模块。可以通过lsmod查看当前系统中存在的模块,模块加载后会注册设备,然后可以通过cat /proc/devices查看当前系统中存在的设备。
2.使用mknod创建设备对应的设备节点文件。通过ls /dev查看当前系统中的设备节点文件。设备节点文件是设备在用户空间中的实现,在应用程序中可以通过设备节点文件操作该设备。
3.不再使用该设备后,通过rmmod卸载设备。

cdev结构体描述了一个字符设备,其中定义了设备号dev_t和字符设驱动提供给虚拟文件系统的接口函数file_operations,驱动开发者需要编写其中接口函数。
用户空间调用Linux的API函数open打开设备时,设备驱动的open函数最终被调用。在驱动中大多在open函数中将file中的private_data指针指向设备结构体。

static int open(struct inode* inode, struct file* filp)

用户空间调用Linux的API函数close关闭设备时,release函数最终被调用。

static int close(struct inode* inode, struct file* filp)

用户空间调用Linux的API函数read读取数据时,read函数被调用

 

MMU(内存管理单元)完成虚拟内存到物理内存的映射和内存保护的功能。可以通过ioremap完成物理内存地址到虚拟内存地址的映射,相应在卸载内存时需要通过unremap释放虚拟内存的映射,在获得指向虚拟内存地址的指针后,可以通过readbreadwreadlwritebwritewwritel对内存进行读写操作

设备树

设备树通过树形数据结构描述板级设备信息,将板级信息从内核中分离出来,提升Linux驱动的灵活性。
通常SOC可以制作多个板子,SOC的信息对于这些板子来说是相同的,因此可以类似于C语言中的头文件,将这些共同的信息提取出来作为一个通用的dtsi文件,其他的dts文件引用该文件,并针对不同的设备进行相应的修改即可。
设备树compatible属性用于设备和驱动的绑定,指定该设备对应的驱动,而驱动模块中的of_device_id列表定义了该驱动对应的设备,设备首先按照先后顺序在内核里查找驱动模块,如果完成匹配则调用驱动的probe函数,完成初始化工作。在根节点中,compatible属性用于指定设备和SOC名称,Linux内核中由DT_MACHINE_START和MACHINE_END所定义的machine_desc 结构体中有个dt_compat 成员变量,其中保存了内核支持的设备,如果设备树中根节点下的compatible属性与dt_compat匹配,则表示Linux内核支持该设备,该开发板可以正常启动Linux内核。
编写驱动程序时可以通过Linux内核提供的API获取设备树上的设备信息,device_node描述了设备树上的一个节点,通常将device_node包含在设备结构体中,在初始化函数中使用of_find_node_by_path通过路径获取指定的节点,节点中的property保存了该节点的属性,可以通过of_find_property指定设备树节点和属性名称,获得相应的property结构体。
通过make dtbs命令将.dts编译为.dtb文件,启动内核后/proc/device-tree/目录下产生设备树上的所以节点
疑问:
1、根据Makefile了解设备树文件编译的具体规
2、根据Uboot移植和Linux内核移植了解dtb文件的具体使用过程。

pinctrl和gpio子系统

疑问:

用途:大多数的SOC的引脚是支持复用功能的,在裸机驱动开发中,设备的引脚和gpio通过设置相应的寄存器进行配置,在Linux驱动开发中引入了pinctrl和gpio子系统,通过在设备树中添加节点并完成引脚复用,电气等相应属性的配置,即可在驱动程序中通过提供的API函数完成引脚和gpio驱动的开发工作。
pinctrl子系统可以获取引脚信息,设置引脚的复用功能和电气特性

在设备树的外设节点下创建以pinctrl开头的节点,例如

pinctrl_led: ledgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */>;
};

fsl,pins属性定义了引脚的复用属性和电气属性,宏定义由C语言的.h头文件定义,对应五个值,分别代表mux_reg的偏移地址、conf_reg的偏移地址、input_reg的偏移地址、mux_reg寄存器的值、input_reg寄存器的值,conf_reg寄存器的值设置电气属性,在该宏定义之外设置。疑问:如何通过寄存器的值设置引脚的复用和电气属性,具体都有哪些属性可以设置。
fsl,pins属性通过宏定义MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 将GPIO1_IO03复用为GPIO1_IO03,并设置电气属性为0X10B0。
1、同一个外设的 PIN 都放到一个节点里,因此需要在相应的外设节点中添加以pinctrl开头的节点,在其中添加fsl,pins属性,在其中设置引脚的复用和电气属性。
2、设置GPIO控制器节点,其中规定了GPIO控制器的寄存器节点,#gpio-cells节点规定了GPIO控制器cell的个数,第一个 cell 为 GPIO 编号,比如gpio1 3就表示 GPIO1_IO03。第二个 cell 表示GPIO 极 性, 如 果 为0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平有效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。gpio-controller将节点声明为GPIO控制器节点。
3、在设备节点中添加属性,pinctrl-n代表使用的pinctrl节点,说明该设备的复用和电气属性,在gpio属性中定义GPIO控制器节点,
4、在驱动程序中,首先在设备结构体中定义GPIO编号,在初始化函数里通过of_get_named_gpio读取设备节点的gpio属性,获得GPIO编号,然后通过gpio_direction_outputgpio_direction_input将GPIO编号对应的GPIO设置为默认输出或输入,然后在读取函数中通过gpio_get_value获取GPIO的值,在写入函数中通过gpio_set_value在相应的GPIO中写入值

再设备树的根节点下创建gpio对应的外设节点

gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
}

pinctrl-0属性设置了该节点对应的pintrcl节点,led-gpio属性设置了该设备对应的gpio节点信息,通过该属性可以获得GPIO编号

在驱动文件中
在设备结构体中定义GPIO编号
在初始化函数中根据of_find_node_by_path获得设备节点,通过of_get_named_gpio获得设备gpio编号,设置gpio输出输入属性
在写入函数中,通过gpio_set_value设置gpio的输出值。

Linux并发与竞争

原子变量:该变量的赋值和读取为原子操作
自旋锁:加锁后其他线程无法访问临界区,会处于忙等状态,因此适用于临界区耗时不长的场景。
信号量/互斥体:其他线程无法访问临界区时会进入休眠状态,进行上下文切换,适用于临界区较为耗时的场景。
同时只允许一个进程打开设备
通过在设备结构体中设置原子变量,初始化时设置该设备的原子变量为1,打开设备时原子变量减一,并判断是否小于零,若小于零则打开失败,关闭设备时原子变量加一。
在设备结构体中定义整形变量表示该设备是否使用,并定义自旋锁保护该变量,在初始化函数中初始化自旋锁,在打开设备时变量加一,如果变量大于零打开失败,关闭设备时变量减一,注意在对变量进行读写时需要通过自旋锁进行保护。
在设备结构体中定义信号量,在初始化函数中初始化信号量为1,在打开函数中获取信号量,若此时信号量为0则打开失败,在关闭函数中释放信号量。
在设备结构体中定义互斥体,在设备初始化函数中初始化互斥体,在打开设备函数中获取互斥体,如果获取互斥体失败则打开文件失败,在关闭函数中释放互斥体。

Linux内核定时器

Linux通过硬件定时器湖区时钟源,该时钟源的频率被称为系统频率或节拍率,该频率可以在编译LInux内核的时候设置。Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0
在设备结构体中定义timer_list定时器结构体,在初始化函数中初始化定时器,添加超时时间和处理函数,并注册定时器,开始定时功能,在关闭函数中删除定时器。

Linux中断

裸机中断系统?
设备树中通过interrupt-parent指定相应的gpio中断控制器,通过interrupts设置引脚号和中断出发标志。gpio中断控制器节点中,通过interrupts设置该gpio中断源信息
在驱动文件的设备结构体中定义中断IO描述结构体,其中包含gpio、终端号、中断服务函数,名字等信息。通过of_get_named_gpio提取gpio号,然后根据irq_of_parse_and_map从设备树中获取中断号,完成中断处理函数后,通过request_irq完成中断的申请。在关闭函数中,释放中断号
在设备树中设置中断控制器,在节点中添加interrupt-controller属性将该节点声明为中断控制器,reg设置中断控制器的寄存器地址,#interrupt-cells设置中断的cell大小,

interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;

interrupts 描述中断源信息,如果是中断控制器节点,则#interrupt-cells设置为3,第一个代表中断类型,0 表示 SPI 中断,1 表示 PPI 中断,第二个代表:中断号,对于 SPI 中断来说中断号的范围为 0~ 987,对于 PPI 中断来说中断号的范围为 0~15,第三个代表标志位。如果是GPIO控制器节点,则#interrupt-cells设置为2,第一个代表GPIO的IO号,第二个代表标志位
GPIO控制器节点同时可以作为中断控制器节点。

在设备结构体中添加中断号和中断处理函数指针,然后在初始化函数中通过irq_of_parse_and_map获取中断号,然后将中断处理函数指针指向中断处理函数,然后通过request_irq以及中断号、中断处理函数,标志位等信息注册中断。

···
// 申请中断号
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
···
// 根据中断号、中断处理函数等信息向内核注册
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,imx6uirq.irqkeydesc[i].handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,imx6uirq.irqkeydesc[i].name, &imx6uirq);
···				  

中断处理函数原型

static irqreturn_t handler(int irq, void *dev_id)

阻塞和非阻塞IO

当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃,设备驱动文件的默认读取方式就是阻塞式的,若要通过非阻塞方式打开,则需要再open函数中传入O_NONBLOCK

通过等待队列实现阻塞IO
在设备结构体中添加等待队列头结构体

平台设备驱动

在Linux内核中包含了大量驱动代码,因此必须提高驱动的可重用性。
驱动的分离:采用驱动、总线、设备信息模型,将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息,后根据获取到的设备信息来初始化设备,即于驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。
在这里插入图片描述
驱动的分层:分层的目的也是为了在不同的层处理不同的内容。
定义platform_driver结构体,在其中设置驱动名称name,表示/sys/bus/platform/drivers/目录下的驱动名称,设备树匹配表of_match_table,用于和设备树中的compatible属性匹配,probe函数用于设备和驱动完成匹配时的执行,remove函数。
在初始化函数中通过platform_driver_register注册platform_driver结构体,在卸载函数中通过platform_driver_unregister卸载platform_driver结构体,
在probe函数中完成初始化,在remove函数中完成卸载。

 /* 设置platform_driver的设备匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "atkalpha-gpioled" },
{ /* Sentinel */ }
};
/* 定义platform_driver驱动结构体 */
static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */.of_match_table = led_of_match, /* 设备树匹配表 */},.probe = led_probe,.remove = led_remove,};

加载驱动后,/sys/bus/platform/drivers/目录下生成相应的驱动,由于在设备树中完成了设备的定义,/sys/bus/platform/devices/目录下会生成相应的设备,当驱动的platform_driverof_device_id列表中的compatible属性和设备树中的compatible属性匹配时,加载模块后会同时驱动和设备匹配成功,从而调用probe函数进行相应的初始化工作

misc驱动

misc为杂项驱动,主设备号为10。misc_register会完成申请设备号、初始化cdev、添加cdev、创建类、创建设备等操作,misc_registe会完成删除cdev、注销设备号、删除设备、删除类等操作

input子系统

Linux为输入设备创建的框架,统分为 input 驱动层、input 核心层、input 事件处理层。
在这里插入图片描述
首先在设备结构体定义一个input_dev指针,然后通过 input_allocate_device函数申请一个input_dev结构体,然后设置该结构体的名称,事件类型和事件值。input_devevbit表示输入事件的类型,需要通过以下方式设置输入事件的类型

/* 设置事件的事件类型 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |BIT_MASK(EV_REP);
/* 设置事件的事件码 */
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

然后通过input_register_device向Linux内核注册结构体,卸载驱动模块时需要通过input_unregister_deviceinput_free_device注销和删除设备。
然后向input_eventLinux上报事件。在产生输入时,通过input_event根据事件类型和事件码上报对应的事件值。上报结束后通过input_sync通知内核上报结束。

加载驱动模块后,驱动会向Linux注册输入结构体,会在/dev/input目录下生成一个名eventX(X=0….n)的文件,这个/dev/input/eventX就是对应的 input 设备文件。
编写应用软件时,首先定义input_event结构体,其中type表示事件类型,code表示事件码,value表示按键值。在应用程序中通过open打开该input设备文件,然后通过read读取该设备文件中的输入事件,根据通过switch语句对不同的type事件类型进行处理

注:事件类型的定义如下

#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按键事件 */
#define EV_REL 0x02 /* 相对坐标事件 */
#define EV_ABS 0x03 /* 绝对坐标事件 */
#define EV_MSC 0x04 /* 杂项(其他)事件 */
#define EV_SW 0x05 /* 开关事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */

LCD
驱动文件基本无需更改,可以在设备树中修改相应的参数

I2C驱动

I2C驱动分为总线驱动和设备驱动。I2C总线驱动也被称为I2C控制器驱动或I2C适配器驱动,SOC的I2C总线驱动一般由半导体厂商编写,不需用户编写。I2C设备驱动针对不同的设备进行编写,是I2C驱动的重点。
platform_driver驱动框架类似,首先需要定义i2c_driver,并定义其中的proberemove函数,以及of_match_table设备树匹配列表,并完成设备树匹配列表中的compatible属性的定义,在驱动模块初始化函数中通过i2c_add_driver注册i2c_driver,在驱动模块卸载函数中通过i2c_del_driver注销i2c_driver,在probe函数中完成初始化工作,在remove函数中完成卸载工作。

static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)

在设备结构体中定义void*类型的private_data类型的指针,在probe函数中指向i2c_client结构体,之后即可通过设备结构体中的private_data指针获取i2c_client结构体,i2c_client结构体包含了芯片地址和adapter结构体。i2c_client 就是描述设备信息,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client。Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,其中包含i2c_algorithm,定义了I2C 适配器与 IIC 设备进行通信的方法。i2c_msg定义了I2C通信的消息,其中定义了从机地址、消息数据、消息长度、标志位。通过i2c_transfer传递信息的时候需要传入i2c_client结构体的i2c_adapter

static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len){int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->private_data;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr; /* ap3216c 地址 */msg[0].flags = 0; /* 标记为发送数据 */msg[0].buf = &reg; /* 读取的首地址 */msg[0].len = 1; /* reg 长度 *//* msg[1]读取数据 */msg[1].addr = client->addr; /* ap3216c 地址 */msg[1].flags = I2C_M_RD; /* 标记为读取数据 */msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度 */ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len){u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)
dev->private_data;b[0] = reg; /* 寄存器首地址 */memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组 b 里面 */msg.addr = client->addr; /* ap3216c 地址 */msg.flags = 0; /* 标记为写数据 */msg.buf = b; /* 要写入的数据缓冲区 */msg.len = len + 1; /* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}

I2C读数据需要定义两个i2c_msg,第一个i2c_msg描述了读取寄存器的首地址,第二个i2c_msg描述了读取寄存器的数据,I2C写数据需要定义一个256长度的u8数组,首先保存寄存器的首地址,然后保存需要发送的数据,最后调用i2c_transfer完成数据的读写操作。

SPI驱动

SPI驱动和I2C驱动类似,分为主机驱动和设备驱动,其中主机驱动一般由SOC厂商编写,因此通常需要实现设备驱动。
首先需要定义SPI驱动结构体spi_driver,定义设备树匹配列表,在其中定义compatible属性,定义probe函数,在其中设置spi_device的mode属性,并通过spi_setup完成设备的初始化工作,定义remove函数,完成设备的卸载工作,在初始化函数中通过spi_register_driver注册spi_driver,在卸载函数中通过spi_unregister_driver注销spi_driver。
spi_transfer定义了SPI传输中的数据,其中tx_buf定义了发送数据,rx_buf定义了接收数据,len定义了长度,然后需要定义spi_message,并通过spi_message_init完成初始化,通过spi_message_add_tail将spi_transfer添加至spi_message中,最后通过spi_sync发送
对于SPI读取数据来说,首先要片选拉低,发送需要读取的寄存器地址,然后读取数据,最后拉高片选;对于SPI写数据来说,首先片选拉低,发送需要读取的寄存器地址,然后发送数据,最后拉高片选。

UART驱动框架

串口驱动没有什么主机端和设备端之分,就只有一个串口驱动,通常由SOC厂商完成编写,只需要在设备树中添加串口节点信息,系统启动以后串口驱动和设备匹配成功,相应的串口就会被驱动。

电容触摸屏驱动

电容屏驱动框架
1、电容触摸屏是通过I2C接口与主机通信的,因此需要I2C驱动作为整体框架
2、触摸IC提供中断信号引脚来获得中断触摸信息,因此需要申请中断号,并注册中断处理函数
3、触摸屏幕会产生坐标、按下抬起信息,需要通过input子系统向Linux内核上报信息
4、需要通过MT协议上报触摸屏幕信息,一般来说,对于支持多点电容触摸的屏幕使用Type B类型的MT协议

首先在probe函数中,通过devm_input_allocate_device申请input_dev,并进行初始化,注册需要上报的事件evbitEV_KEYEV_ABSEV_KEY上报触摸屏是否被按下,EV_ABS上报触摸点的坐标,设定上报的按键码keybitBTN_TOUCH

/* 3,input 设备申请与初始化 */input = devm_input_allocate_device(&client->dev);// TODO 一下代码的含义input->name = client->name;input->id.bustype = BUS_I2C;input->dev.parent = &client->dev;__set_bit(EV_KEY, ft5x06.input->evbit);__set_bit(EV_ABS, ft5x06.input->evbit);__set_bit(BTN_TOUCH, ft5x06.input->keybit);

调用 input_set_abs_params 函数设置 EV_ABS 事件需要上报 ABS_XABS_YABS_MT_POSITION_XABS_MT_POSITION_Y。单点触摸需要上报 ABS_XABS_Y,对于多点触摸需要上报 ABS_MT_POSITION_XABS_MT_POSITION_Yinput_set_abs_paramsh函数原型如下:

void input_set_abs_params(struct input_dev *dev, unsigned int axis, int min, int max, int fuzz, int flat)
形参: dev --input_dev结构体
   axis --上报的数值
   min --最小值
   max --最大值
    fuzz --数据偏差值
    flat --平滑位置
设置触摸屏x坐标范围:
 input_set_abs_params(touch_dev,ABS_X,0,800,0,0);//设置x坐标范围
设置触摸屏x坐标范围:
 input_set_abs_params(touch_dev,ABS_PRESSURE,0,1,0,0);//设置压力值范围

input_mt_init_slotsinput_dev进行SLOT的初始化,并指定触摸点的数量。

input_set_abs_params(input, ABS_X, 0, width, 0, 0);
input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 
input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);

申请中断号,并在Linux内核中注册中断处理函数。触摸屏的中断需要进行中断线程化。硬件中断发生的时候内核会暂停当前执行的操作,转而执行中断处理程序,由于触摸中断的产生十分频繁,从而会使得内核频繁地暂停当前的操作指向中断程序,使得任务无法得到及时的处理,而中断线程化可以将中断作为具有一定优先级的内核线程运行,从而使得优先级更高的任务得到及时处理。

ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name,&ft5x06);

在中断处理函数中通过I2C读取寄存器数据,获得每个触摸点的信息,首先通过input_mt_slot上报触摸点的ID,然后通过input_mt_report_slot_state上报触摸类型,一般是MT_TOOL_FINGER,然后通过input_report_abs上报xy坐标信息。所有触摸点的信息上班完成后,通过input_mt_report_pointer_emulation上报追踪到的触摸点数量是否多余当前上报的触摸点数量,最后由input_sync宣布事件上报结束


...// 用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据input_mt_slot(multidata->input, id);// 用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事件, ABS_MT_TRACKING_ID 事 件 给 slot 关联一个 ABS_MT_TRACKING_ID ,ABS_MT_TOOL_TYPE 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 )。input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);if (!down)continue;// 此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 实 现 X 和 Y 轴 坐 标 信 息 上 报input_report_abs(multidata->input, ABS_MT_POSITION_X, x);input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
}
// 报追踪到的触摸点数量是否多余当前上报的触摸点数量
input_mt_report_pointer_emulation(multidata->input, true);
// 宣布事件上报结束
input_sync(multidata->input);

疑问:input_dev结构体中以下成员函数的作用,以及probe函数中以下代码的作用

ft5x06.input->name = client->name;
ft5x06.input->id.bustype = BUS_I2C;
ft5x06.input->dev.parent = &client->dev;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/145351.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云七代云服务器实例、倚天云服务器及通用算力型和经济型实例规格介绍

在目前阿里云的云服务器产品中&#xff0c;既有五代六代实例规格&#xff0c;也有七代和八代倚天云服务器&#xff0c;同时还有通用算力型及经济型这些刚推出不久的新品云服务器实例&#xff0c;其中第五代实例规格目前不在是主推的实例规格了&#xff0c;现在主售的实例规格是…

【数据结构】堆,堆的实现,堆排序,TOP-K问题

大家好&#xff01;今天我们来学习数据结构中的堆及其应用 目录 1. 堆的概念及结构 2. 堆的实现 2.1 初始化堆 2.2 销毁堆 2.3 打印堆 2.4 交换函数 2.5 堆的向上调整 2.6 堆的向下调整 2.7 堆的插入 2.8 堆的删除 2.9 取堆顶的数据 2.10 堆的数据个数 2.11 堆的判…

内存函数的介绍和模拟实现

目录 1.memcpy的使用(内存拷贝) 2.memcpy的实现 3.memmove的使用&#xff08;内存拷贝&#xff09; 4.memmove的实现 5.memset 的使用&#xff08;内存设置&#xff09; 6.memcmp的使用&#xff08;内存比较&#xff09; 1.memcpy的使用(内存拷贝) void * memcpy ( void * …

整型提升——(巩固提高——字符截取oneNote笔记详解)

文章目录 前言一、整型提升是什么&#xff1f;二、详细图解1.图解展示 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 整型提升是数据存储的重要题型&#xff0c;也是计算机组成原理的核心知识点。学习c语言进阶的时候,了解内存中数据怎么存&#…

孤举者难起,众行者易趋,openGauss 5.1.0版本正式发布!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

华为云云耀云服务器L实例评测|云耀云服务器L实例搭建个人镜像站

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例搭建个人镜像站 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、Apache介绍2.1 Apache简介2.2 Apache特点 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划 四、远程登录华为云…

SpringCloud Alibaba 入门到精通 - Sentinel

SpringCloud Alibaba 入门到精通 - Sentinel 一、基础结构搭建1.父工程创建2.子工程创建 二、Sentinel的整合SpringCloud1.微服务可能存在的问题2.SpringCloud集成Sentinel搭建Dashboard3 SpringCloud 整合Sentinel 三、服务降级1 服务降级-Sentinel2 Sentinel 整合 OpenFeign3…

【深度学习实验】卷积神经网络(三):自定义二维卷积层:步长、填充、输入输出通道

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 步长、填充 a. 二维互相关运算&#xff08;corr2d&#xff09; b. 二维卷积层类&#xff08;Conv2D&#xff09; c. 模型测试 d. 代码整合 2. 输入输出通道 a…

Arcgis克里金插值报错:ERROR 999999: 执行函数时出错。 表名无效。 空间参考不存在。 ERROR 010429: GRID IO 中存在错误

ERROR 999999: 执行函数时出错。 问题描述 表名无效。 空间参考不存在。 ERROR 010429: GRID IO 中存在错误: WindowSetLyr: Window cell size does not match layer cell size. name: c:\users\lenovo\appdata\local\temp\arc2f89\t_t164, adepth: 32, type: 1, iomode: 6, …

智能合约漏洞,Dyna 事件分析

智能合约漏洞&#xff0c;Dyna 事件分析 1. 漏洞简介 https://twitter.com/BlockSecTeam/status/1628319536117153794 https://twitter.com/BeosinAlert/status/1628301635834486784 2. 相关地址或交易 攻击交易 1&#xff1a; https://bscscan.com/tx/0x7fa89d869fd1b89e…

算法通过村第十一关-位运算|青铜笔记|初始位运算

文章目录 前言1. 数字在计算中的表示拓展&#xff1a;为什么要有原码、反码和补码? 2. 位运算规则2.1 与、或、异或和取反2.2 位移运算2.3 位移运算和乘除的关系2.4 位运算的常用技巧 总结 前言 提示&#xff1a;我的父亲从我出生起便认识我&#xff0c;可他对我的了解却那么少…

西北主要河流水系(绿洲)流域(山区)及高程分类数据集(一)

最近收集整理的了西北地区主要河流水系&#xff08;绿洲&#xff09;流域&#xff08;山区&#xff09;及高程分类数据&#xff0c;&#xff0c;本次主要是新疆的河流水系&#xff08;绿洲&#xff09;流域&#xff08;山区&#xff09;及高程分类数据&#xff08;矢量&#xf…

ThemeForest – Canvas 7.2.0 – 多用途 HTML5 模板

ThemeForest 上的 HTML 网站模板受到全球数百万客户的喜爱。与包含网站所有页面并允许您在 WP 仪表板中自定义字体和样式的 WordPress 主题不同&#xff0c;这些设计模板是用 HTML 构建的。您可以在 HTML 编辑器中编辑模板&#xff0c;但不能在 WordPress 上编辑模板&#xff0…

机器人过程自动化(RPA)入门 7. 处理用户事件和助手机器人

在UiPath中,有两种类型的Robot用于自动化任何流程。一个是后台机器人,它在后台工作。它独立工作,这意味着它不需要用户的输入或任何用户交互。另一个是前台机器人,也被称为助理机器人。 本章介绍前台机器人。在这里,我们将了解自动化过程中通过简单按键、单击鼠标等触发事…

【Vue】数据监视输入绑定

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如有需要&#xff0c;可以收藏哈 本章给大家讲解的是数据监视&#xff0c;前面的章节已经更新完毕&#xff0c;后面的章节持续输出&#xff0c;有任何问题都可以…

Pikachu-xxe (xml外部实体注入漏洞)过关笔记

Pikachu-xxe过关笔记 有回显探测是否有回显file:///协议查看本地系统文件php://协议查看php源代码&#xff08;无法查看当前网页代码&#xff0c;只能看别的&#xff09;http://协议爆破开放端口&#xff08;两者的加载时间不同&#xff09; 无回显第一步第二步第三步 运行结果…

【面试题】2023前端面试真题之JS篇

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 表妹一键制作自己的五星红旗国庆头像&#xff0c;超好看 世界上只有一种真正的英雄主义&#xff0c;那就是看清生活的真相之后&#xff0c;依然热爱生活。…

1.项目创建与角色移动

目录 1.创建项目 2.导入素材 3.搭建场景 4.创建玩家 1.创建项目 2.导入素材 3D GUNS | Guns Pack | 3D 武器 | Unity Asset Storehttps://assetstore.unity.com/packages/3d/props/weapons/3d-guns-guns-pack-228975 Prototyping Pack (Free) | 3D | Unity Asset S…

外包公司干了2个月,技术倒退两年...

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入杭州某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年8月份&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了三年的功能测试…

87、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->List相关命令

本次讲解要点&#xff1a; List相关命令&#xff1a;是指value中的数据类型 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe redi…