Linux第95步_Linux内核中的INPUT子系统

Linux内核对"按键、鼠标、键盘、触摸屏"等这一类输入设备而创建的"框架"被称为"input子系统",它们在本质上还是字符设备,只是采用input子系统处理输入事件,并上报给用户。前面讲的"pinctrl子系统和gpio子系统"主要用于GPIO驱动开发,它们都是Linux中的一种"框架"。

1、了解input子系统

1)、input子系统分为"驱动层,核心层和事件处理层"。

2)、了解input_class类

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/drivers/input/input.c"。

struct class input_class = {

.name = "input",

.devnode = input_devnode,

};

EXPORT_SYMBOL_GPL(input_class);

/*

EXPORT_SYMBOL_GPL是Linux内核中的一个宏,用于将一个符号(函数、变量或其他)导出为符号表的全局符号。它的作用是允许其他模块或驱动程序使用该符号,即可以在其他模块中调用该导出的符号。

*/

3)、了解input_init()

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/drivers/input/input.c"。

#define INPUT_MAX_CHAR_DEVICES 1024

static int __init input_init(void)

{

int err;

err = class_register(&input_class);

/*注册一个imput类,系统启动以后就会有"/sys/class/input"目录*/

if (err) {

pr_err("unable to register input_dev class\n");

return err;

}

err = input_proc_init();

/*input子系统用来创建"bus/input"目录,并在当前目录下创建"devices"和"handlers"*/

if (err) goto fail1;

err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),

INPUT_MAX_CHAR_DEVICES, "input");

//注册设备号

//from=MKDEV(INPUT_MAJOR, 0)表示起始设备号

//count=INPUT_MAX_CHAR_DEVICES=1024表示次设备号的数量

//name="input"表示设备名

if (err) {

pr_err("unable to register char major %d", INPUT_MAJOR);

goto fail2;

}

return 0;

fail2: input_proc_exit();

/*input子系统删除"bus/input"目录,以及这个目录下的"devices"和"handlers"*/

fail1: class_unregister(&input_class);//注销一个imput类

return err;

}

2、了解Linux系统中预定义的主设备号

input子系统的所有设备主设备号都为13。

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include/uapi/linux/major.h"

#define UNNAMED_MAJOR 0

#define MEM_MAJOR 1

#define RAMDISK_MAJOR 1

#define FLOPPY_MAJOR 2

#define PTY_MASTER_MAJOR 2

#define IDE0_MAJOR 3

#define HD_MAJOR IDE0_MAJOR

#define PTY_SLAVE_MAJOR 3

#define TTY_MAJOR 4

#define TTYAUX_MAJOR 5

#define LP_MAJOR 6

#define VCS_MAJOR 7

#define LOOP_MAJOR 7

#define SCSI_DISK0_MAJOR 8

#define SCSI_TAPE_MAJOR 9

#define MD_MAJOR 9

#define MISC_MAJOR 10 /*MISC设备的主设备号为10*/

#define SCSI_CDROM_MAJOR 11

#define MUX_MAJOR 11 /* PA-RISC only */

#define XT_DISK_MAJOR 13

#define INPUT_MAJOR 13 /*INPUT设备的主设备号为13*/

#define SOUND_MAJOR 14

#define CDU31A_CDROM_MAJOR 15

#define JOYSTICK_MAJOR 15

#define GOLDSTAR_CDROM_MAJOR 16

#define OPTICS_CDROM_MAJOR 17

#define SANYO_CDROM_MAJOR 18

#define CYCLADES_MAJOR 19

#define CYCLADESAUX_MAJOR 20

#define MITSUMI_X_CDROM_MAJOR 20

#define MFM_ACORN_MAJOR 21 /* ARM Linux /dev/mfm */

#define SCSI_GENERIC_MAJOR 21

#define IDE1_MAJOR 22

#define DIGICU_MAJOR 22

#define DIGI_MAJOR 23

#define MITSUMI_CDROM_MAJOR 23

#define CDU535_CDROM_MAJOR 24

#define STL_SERIALMAJOR 24

#define MATSUSHITA_CDROM_MAJOR 25

#define STL_CALLOUTMAJOR 25

#define MATSUSHITA_CDROM2_MAJOR 26

#define QIC117_TAPE_MAJOR 27

#define MATSUSHITA_CDROM3_MAJOR 27

#define MATSUSHITA_CDROM4_MAJOR 28

#define STL_SIOMEMMAJOR 28

#define ACSI_MAJOR 28

#define AZTECH_CDROM_MAJOR 29

#define FB_MAJOR 29 /* /dev/fb* framebuffers */

#define MTD_BLOCK_MAJOR 31

#define CM206_CDROM_MAJOR 32

#define IDE2_MAJOR 33

#define IDE3_MAJOR 34

#define Z8530_MAJOR 34

#define XPRAM_MAJOR 35 /* Expanded storage on S/390: "slow ram"*/

#define NETLINK_MAJOR 36

#define PS2ESDI_MAJOR 36

#define IDETAPE_MAJOR 37

#define Z2RAM_MAJOR 37

#define APBLOCK_MAJOR 38 /* AP1000 Block device */

#define DDV_MAJOR 39 /* AP1000 DDV block device */

#define NBD_MAJOR 43 /* Network block device */

#define RISCOM8_NORMAL_MAJOR 48

#define DAC960_MAJOR 48 /* 48..55 */

#define RISCOM8_CALLOUT_MAJOR 49

#define MKISS_MAJOR 55

#define DSP56K_MAJOR 55 /* DSP56001 processor device */

#define IDE4_MAJOR 56

#define IDE5_MAJOR 57

#define SCSI_DISK1_MAJOR 65

#define SCSI_DISK2_MAJOR 66

#define SCSI_DISK3_MAJOR 67

#define SCSI_DISK4_MAJOR 68

#define SCSI_DISK5_MAJOR 69

#define SCSI_DISK6_MAJOR 70

#define SCSI_DISK7_MAJOR 71

#define COMPAQ_SMART2_MAJOR 72

#define COMPAQ_SMART2_MAJOR1 73

#define COMPAQ_SMART2_MAJOR2 74

#define COMPAQ_SMART2_MAJOR3 75

#define COMPAQ_SMART2_MAJOR4 76

#define COMPAQ_SMART2_MAJOR5 77

#define COMPAQ_SMART2_MAJOR6 78

#define COMPAQ_SMART2_MAJOR7 79

#define SPECIALIX_NORMAL_MAJOR 75

#define SPECIALIX_CALLOUT_MAJOR 76

#define AURORA_MAJOR 79

#define I2O_MAJOR 80 /* 80->87 */

#define SHMIQ_MAJOR 85 /* Linux/mips, SGI /dev/shmiq */

#define SCSI_CHANGER_MAJOR 86

#define IDE6_MAJOR 88

#define IDE7_MAJOR 89

#define IDE8_MAJOR 90

#define MTD_CHAR_MAJOR 90

#define IDE9_MAJOR 91

#define DASD_MAJOR 94

#define MDISK_MAJOR 95

#define UBD_MAJOR 98

#define PP_MAJOR 99

#define JSFD_MAJOR 99

#define PHONE_MAJOR 100

#define COMPAQ_CISS_MAJOR 104

#define COMPAQ_CISS_MAJOR1 105

#define COMPAQ_CISS_MAJOR2 106

#define COMPAQ_CISS_MAJOR3 107

#define COMPAQ_CISS_MAJOR4 108

#define COMPAQ_CISS_MAJOR5 109

#define COMPAQ_CISS_MAJOR6 110

#define COMPAQ_CISS_MAJOR7 111

#define VIODASD_MAJOR 112

#define VIOCD_MAJOR 113

#define ATARAID_MAJOR 114

#define SCSI_DISK8_MAJOR 128

#define SCSI_DISK9_MAJOR 129

#define SCSI_DISK10_MAJOR 130

#define SCSI_DISK11_MAJOR 131

#define SCSI_DISK12_MAJOR 132

#define SCSI_DISK13_MAJOR 133

#define SCSI_DISK14_MAJOR 134

#define SCSI_DISK15_MAJOR 135

#define UNIX98_PTY_MASTER_MAJOR 128

#define UNIX98_PTY_MAJOR_COUNT 8

#define UNIX98_PTY_SLAVE_MAJOR (UNIX98_PTY_MASTER_MAJOR+UNIX98_PTY_MAJOR_COUNT)

#define DRBD_MAJOR 147

#define RTF_MAJOR 150

#define RAW_MAJOR 162

#define USB_ACM_MAJOR 166

#define USB_ACM_AUX_MAJOR 167

#define USB_CHAR_MAJOR 180

#define MMC_BLOCK_MAJOR 179

#define VXVM_MAJOR 199 /* VERITAS volume i/o driver */

#define VXSPEC_MAJOR 200 /* VERITAS volume config driver */

#define VXDMP_MAJOR 201 /* VERITAS volume multipath driver */

#define XENVBD_MAJOR 202 /* Xen virtual block device */

#define MSR_MAJOR 202

#define CPUID_MAJOR 203

#define OSST_MAJOR 206 /* OnStream-SCx0 SCSI tape */

#define IBM_TTY3270_MAJOR 227

#define IBM_FS3270_MAJOR 228

#define VIOTAPE_MAJOR 230

#define BLOCK_EXT_MAJOR 259

#define SCSI_OSD_MAJOR 260 /* open-osd's OSD scsi device */

3、了解input_dev结构

struct input_dev {

const char *name;

const char *phys;

const char *uniq;

struct input_id id;

unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

unsigned long evbit[BITS_TO_LONGS(EV_CNT)];/*表示输入事件类型*/

unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];

unsigned long relbit[BITS_TO_LONGS(REL_CNT)];

unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];

unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];

unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];

unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];

unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];

unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

unsigned int hint_events_per_packet;

unsigned int keycodemax;

unsigned int keycodesize;

void *keycode;

int (*setkeycode)(struct input_dev *dev,

const struct input_keymap_entry *ke,

unsigned int *old_keycode);

int (*getkeycode)(struct input_dev *dev,

struct input_keymap_entry *ke);

struct ff_device *ff;

struct input_dev_poller *poller;

unsigned int repeat_key;

struct timer_list timer;

int rep[REP_CNT];

struct input_mt *mt;

struct input_absinfo *absinfo;

unsigned long key[BITS_TO_LONGS(KEY_CNT)];

unsigned long led[BITS_TO_LONGS(LED_CNT)];

unsigned long snd[BITS_TO_LONGS(SND_CNT)];

unsigned long sw[BITS_TO_LONGS(SW_CNT)];

int (*open)(struct input_dev *dev);

void (*close)(struct input_dev *dev);

int (*flush)(struct input_dev *dev, struct file *file);

int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

struct input_handle __rcu *grab;

spinlock_t event_lock;

struct mutex mutex;

unsigned int users;

bool going_away;

struct device dev;

struct list_head h_list;

struct list_head node;

unsigned int num_vals;

unsigned int max_vals;

struct input_value *vals;

bool devres_managed;

ktime_t timestamp[INPUT_CLK_MAX];

};

4.1、了解input_event结构体

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include/uapi/linux/input.h"

struct input_event {

#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(KERNEL)

struct timeval time; /*事件发生的时间*/

#define input_event_sec time.tv_sec

#define input_event_usec time.tv_usec

#else

__kernel_ulong_t __sec;

#if defined(sparc) && defined(arch64)

unsigned int __usec;

unsigned int __pad;

#else

__kernel_ulong_t __usec;

#endif

#define input_event_sec __sec

#define input_event_usec __usec

#endif

__u16 type;/*事件类型*/

__u16 code;/*事件码*/

__s32 value;/*事件值*/

};

4.2、了解timeval结构体

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include/uapi/linux/time.h"

typedef long __kernel_long_t;

typedef __kernel_long_t __kernel_time_t**;**

typedef __kernel_long_t __kernel_suseconds_t**;**

struct timeval {

__kernel_time_t tv_sec; /*秒为long型数据*/

__kernel_suseconds_t tv_usec; /*毫秒为long型数据*/

};

5、了解事件类型

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include/uapi/linux/input-event-codes.h"

/*Event types*/

#define EV_SYN 0x00 /*十进制数据0,表示同步事件*/

#define EV_KEY 0x01 /*十进制数据1,表示按键事件*/

#define EV_REL 0x02 /*十进制数据2,表示相对坐标事件*/

#define EV_ABS 0x03 /*十进制数据3,表示绝对坐标事件*/

#define EV_MSC 0x04 /*十进制数据4,表示杂项(其他)事件*/

#define EV_SW 0x05 /*十进制数据5,表示开关事件*/

#define EV_LED 0x11 /*十进制数据17,表示LED事件*/

#define EV_SND 0x12 /*十进制数据18,表示sound(声音)事件*/

#define EV_REP 0x14 /*十进制数据20,表示重复事件*/

#define EV_FF 0x15 /*十进制数据21,表示压力事件*/

#define EV_PWR 0x16 /*十进制数据22,表示电源事件*/

#define EV_FF_STATUS 0x17 /*十进制数据23,表示压力状态事件*/

#define EV_MAX 0x1f /*只有最5位有效,因此最大为31*/

#define EV_CNT (EV_MAX+1)

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/alpha/include/asm/bitops.h"

/*WARNING: non atomic version.*/

//根据位置nr,修改int型位图

static inline void __set_bit(unsigned long nr, volatile void * addr)

{

int *m = ( (int *) addr ) + (nr >> 5);

//当nr<=31时,m=addr;

//当32<=nr<=63时,m=addr+1;

*m |= 1 << (nr & 31);

//当nr<=31时,修改addr所指向的存储区

//当32<=nr<=63时m=addr+1, 修改(addr+1)所指向的存储区

}

__set_bit(EV_KEY, key.idev->evbit);

#define __WORDSIZE (SIZEOF_LONG * 8)

//__sizeof_long__‌:表示long类型的字节大小,这里是8个字节;

//* 8‌:由于1字节等于8位,乘以8是将字节大小转换为位数,这里是64位

#define BITS_PER_LONG __WORDSIZE

#define BIT_MASK(nr) (UL(1) << ((nr) % BITS_PER_LONG))

//将第nr位置1

#define BIT_WORD(nr) ( (nr) / BITS_PER_LONG )

//将"位值"转换为字的偏移量

6、了解按键值

打开

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include/uapi/linux/input-event-codes.h"

#define KEY_RESERVED 0

#define KEY_ESC 1

#define KEY_1 2

#define KEY_2 3

#define KEY_3 4

#define KEY_4 5

#define KEY_5 6

#define KEY_6 7

#define KEY_7 8

#define KEY_8 9

#define KEY_9 10

#define KEY_0 11

... ... ...

#define BTN_TRIGGER_HAPPY34 0x2e1

#define BTN_TRIGGER_HAPPY35 0x2e2

#define BTN_TRIGGER_HAPPY36 0x2e3

#define BTN_TRIGGER_HAPPY37 0x2e4

#define BTN_TRIGGER_HAPPY38 0x2e5

#define BTN_TRIGGER_HAPPY39 0x2e6

#define BTN_TRIGGER_HAPPY40 0x2e

//当type=EV_KEY, code=KEY_0将dev->keybit中的第code位置1

void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)

{

switch (type) {

case EV_KEY: __set_bit(code, dev->keybit); break;

case EV_REL: __set_bit(code, dev->relbit); break;

case EV_ABS: input_alloc_absinfo(dev);

if (!dev->absinfo) return;

__set_bit(code, dev->absbit); break;

case EV_MSC: __set_bit(code, dev->mscbit); break;

case EV_SW: __set_bit(code, dev->swbit); break;

case EV_LED: __set_bit(code, dev->ledbit); break;

case EV_SND: __set_bit(code, dev->sndbit); break;

case EV_FF: __set_bit(code, dev->ffbit); break;

case EV_PWR:/* do nothing */ break;

default:

pr_err("%s: unknown type %u (code %u)\n", func, type, code);

dump_stack();

return;

}

__set_bit(type, dev->evbit);

}

7、了解input子系统下的函数

1)、struct input_dev *input_allocate_device(void)

用来申请input_dev结构变量,返回值就是申请到的input_dev。

2)、void input_free_device(struct input_dev *dev)

注销input设备,释放input_dev结构变量。dev就是要释放的input_dev结构变量。

3)、int input_register_device(struct input_dev *dev)

向Linux内核注册input_dev。

dev就是要注册的input_dev结构变量

返回值:0,input_dev注册成功;负值,input_dev注册失败。

4)、void input_unregister_device(struct input_dev *dev)

注销input_dev。dev就是要注销的input_dev结构变量

②、input_dev注册过程举例

使用 input_allocate_device()申请一个input_dev。

初始化input_dev事件类型以及事件值。

使用input_register_device()函数向Linux系统注册前面初始化好的input_dev。

struct input_dev * inputdev**;** /* input结构体变量 */

/*驱动入口函数*/

static int __init xxx_init(void)

{

inputdev = input_allocate_device(); /*申请input_dev*/

inputdev->name = "test_inputdev"; /*设置input_dev名字*/

/*********第一种设置事件和事件值的方法***********/

__set_bit**(** EV_KEY**,** inputdev**->** evbit**);** /*设置产生按键事件*/

__set_bit(EV_REP, inputdev->evbit); /*重复事件*/

__set_bit**(** KEY_0**,** inputdev**->** keybit**);** /*设置按键值为KEY_0*/

/*********第二种设置事件和事件值的方法***********/

keyinputdev**.** inputdev**->** evbit**[** 0**] =** BIT_MASK**(** EV_KEY**) |** BIT_MASK**(** EV_REP**);**

//BIT_MASK**(** EV_KEY**)**表示 将第EV_KEY位置1

//BIT_MASK**(** EV_REP**)**表示 将第EV_REP位置1

keyinputdev**.** inputdev**->** keybit**[** BIT_WORD**(** KEY_0**)] |=** BIT_MASK**(** KEY_0**);**

//由于KEY_0=11,因此BIT_WORD**(** KEY_0**)=0**

//BIT_MASK**(** KEY_0**)表示 将第KEY_0)**位置1

/*********第三种设置事件和事件值的方法***********/

keyinputdev**.** inputdev**->** evbit**[** 0**] =** BIT_MASK**(** EV_KEY**) |** BIT_MASK**(** EV_REP**);**

//BIT_MASK**(** EV_KEY**)**表示 将第EV_KEY位置1

//BIT_MASK**(** EV_REP**)**表示 将第EV_REP位置1

input_set_capability**(** keyinputdev**.** inputdev**,** EV_KEY**,** KEY_0**);**

//当type=EV_KEY, code=KEY_0将dev->keybit中的第code位置1

/*注册input_dev*/

input_register_device(inputdev);

//向Linux内核注册inputdev。

//inputdev就是要注册的input_dev结构变量

//返回值:0,input_dev注册成功;负值,input_dev注册失败。

return 0;

}

①、input_dev注销过程举例

卸载input驱动的时候,先使用input_unregister_device()注销掉注册的input_dev,然后使用input_free_device()释放掉前面申请的input_dev。

/*驱动出口函数*/

static void __exit xxx_exit(void)

{

input_unregister_device(inputdev); /*注销input_dev*/

input_free_device(inputdev); /*删除input_dev*/

}

5)、void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

用于上报所有的事件类型和事件值。

dev:需要上报的input_dev;

type: 上报的事件类型,比如 EV_KEY;

code:事件码,也就是我们注册的按键值,比如KEY_0、KEY_1等。

value:事件值,比如 1表示按键下,0表示按键松开;

6)、static inline void input_report_key**(** struct input_dev * dev**,**

unsigned int code, int value)

只用于上报按键指定的事件和事件值。

dev:需要上报的input_dev;

type: 上报的事件类型,比如 EV_KEY;

code:事件码,也就是我们注册的按键值,比如KEY_0、KEY_1等。

value:事件值,比如 1表示按键下,0表示按键松开;

7)、void input_report_rel(struct input_dev *dev, unsigned int code, int value)

只用于上报相对坐标事件和事件值;

8)、void input_report_abs(struct input_dev *dev, unsigned int code, int value)

只用于上报绝对坐标事件和事件值;

9)、void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)

只用于上报压力事件和事件值;

10)、void input_report_switch(struct input_dev *dev, unsigned int code, int value)

只用于上报开关事件和事件值;

11)、void input_mt_sync(struct input_dev *dev)

只用于上报同步事件和事件值;

12)、当我们上报事件以后,还需要使用input_sync()来告诉Linux内核input子系统上报结束,input_sync()本质是上报一个同步事件。

void input_sync(struct input_dev *dev)

告诉Linux内核input子系统上报结束。

事件上报参考代码举例:

/* 用于按键消抖的定时器服务函数 */

void timer_function(unsigned long arg)

{

unsigned char value;

value = gpio_get_value(keydesc->gpio); /* 读取IO 值 */

if(value == 0){ /* 按下按键 */

{

/* 上报按键值 */

input_report_key(inputdev, KEY_0, 1);

/* 向Linux系统通知KEY_0这个按键被按下,1 表示按下*/

input_sync(inputdev); /* 同步事件 */

}

else /* 按键松开 */

{

input_report_key(inputdev, KEY_0, 0);

/* 向Linux系统通知KEY_0这个按键被按下,0 表示松开*/

input_sync(inputdev); /* 同步事件 */

}

}

8、修改设备树

1)、打开虚拟机上"VSCode",点击"文件",点击"打开文件夹",点击"zgq",点击"linux",点击"atk-mp1",点击"linux",点击"my_linux",点击"linux-5.4.31",点击"确定",见下图:

2)、点击"转到",点击"转到文件",输入"stm32mp15-pinctrl.dtsi回车",打开设备树文件stm32mp15-pinctrl.dtsi。找到"pinctrl"节点,然后添加内容如下:

key_pins_a: key_pins-0 {/*"led_pins_a既是标号也是节点*/

pins1 {

pinmux = <STM32_PINMUX('G', 3, GPIO)>,/*设置PG3复用为GPIO功能*/

<STM32_PINMUX('H', 7, GPIO)>;

/*设置PH7复用为GPIO功能*/

/*KEY1连接到PH7*/

bias-pull-up; /*设置引脚内部上拉*/

slew-rate = <0>;/*设置引脚的速度为0档,0最慢,3 最高*/

};

pins2 {

pinmux = <STM32_PINMUX('A', 0, GPIO)>;

/*设置PA0复用为GPIO功能*/

/*WK_UP连接到PA0*/

bias-pull-down; /*内部下拉*/

slew-rate = <0>;/*设置引脚的速度为0档,0最慢,3 最高*/

};

};

3)、查看PG3,PH7和PA0是否被使用了。

①、点击"编辑",点击"查找",输入"STM32_PINMUX('G', 3",然后"回车",没有发现PG3被复用;

②、点击"编辑",点击"查找",输入"STM32_PINMUX('H', 7",然后"回车",发现PH7被复用,屏蔽该语句,见下图:

③、点击"编辑",点击"查找",输入"STM32_PINMUX('H', 7",然后"回车",发现PH7被复用,屏蔽该语句,见下图:

④、点击"编辑",点击"查找",输入"STM32_PINMUX('A', 0",然后"回车",没有发现PA0被复用;

4)、点击"转到",点击"转到文件",输入"stm32mp157d-atk.dts回车",打开设备树文件stm32mp157d-atk.dts。

5)、在根节点"/"下创建一个名为"key"的子节点,添加内容如下:

key {

compatible = "zgq,key";/*设置属性compatible的值为"zgq,led"*/

status = "okay";/*设置属性status的值为"okay"*/

pinctrl-names = "default";

pinctrl-0 = <&key_pins_a>;

key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;

/*"&gpiog"表示key-gpio引脚所使用的IO属于GPIOG组*/

/*"3'表示GPIOG组的第3号IO,即PG3引脚*/

/*"GPIO_ACTIVE_LOW"表示低电平有效,"GPIO_PULL_UP"表示上拉*/

interrupt-parent = <&gpiog>;/*指定父中断器为&gpiog*/

/*通过interrupt-parent属性指定pinctrl所有子节点的中断父节点为 gpiog*/

interrupts = <3 IRQ_TYPE_EDGE_BOTH>;

/*"3'表示GPIOG组的第3号IO,即PG3引脚*/

/*IRQ_TYPE_EDGE_BOTH为边沿触发*/

};

6)、编译设备树

①、在VSCode终端,输入"make dtbs回车",执行编译设备树

②、输入"ls arch/arm/boot/uImage -l"

查看是否生成了新的"uImage"文件

③、输入"ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l"

查看是否生成了新的"stm32mp157d-atk.dtb"文件

7)、拷贝输出的文件:

①、输入"cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车",执行文件拷贝,准备烧录到EMMC;

②、输入"cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车",执行文件拷贝,准备烧录到EMMC

③、输入"cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车",执行文件拷贝,准备从tftp下载;

④、输入"cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车",执行文件拷贝,准备从tftp下载;

⑤、输入"ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车",查看"/home/zgq/linux/atk-mp1/linux/bootfs/"目录下的所有文件和文件夹

⑥、输入"ls -l /home/zgq/linux/tftpboot/回车",查看"/home/zgq/linux/tftpboot/"目录下的所有文件和文件夹

⑦、输入"chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车"

给"stm32mp157d-atk.dtb"文件赋予可执行权限

⑧、输入"chmod 777 /home/zgq/linux/tftpboot/uImage回车" ,给"uImage"文件赋予可执行权限

⑨、输入"ls /home/zgq/linux/tftpboot/-l回车",查看"/home/zgq/linux/tftpboot/"目录下的所有文件和文件夹

8)、查看"/sys/bus/platform/devices/key"这个目录是否存在

我们在设备树文件stm32mp157d-atk.dts的根节点"/"下创建过"key"子节点,因此,需要给开发板上电,查看是否有"key"这个目录。

用新的umage和stm32mpl57d-atk.dtb启动开发板。

①、输入"root回车"。

②、输入"cd /sys/bus/platform/devices/key回车"

切换到"/sys/bus/platform/devices/key"目录。若可以切换,说明有这个"key"这个目录。

③、输入"ls -l回车"

9、编写input驱动和APP

9.1、创建"/home/zgq/linux/Linux_Drivers/input_key/"目录

1)、打开终端,输入"cd /home/zgq/linux/Linux_Drivers/回车",切换到"/home/zgq/linux/Linux_Drivers/"目录;

2)、输入"ls回车",列举"/home/zgq/linux/Linux_Drivers/"目录的所有文件和文件夹;

3)、输入"mkdir input_key回车",创建"/home/zgq/linux/Linux_Drivers/input_key/"目录;

4)、输入"ls回车",列举"/home/zgq/linux/Linux_Drivers/"目录的所有文件和文件夹;

9.2、input驱动之按键驱动程序

1)、打开虚拟机中的VSCode,点击"文件",点击"打开文件夹",然后点击"zgg,linux,Linux_Drivers,input_key",如下图:

2)、点击上图中的确定,然后点击"文件",点击"新建文件",点击"文件",点击"另存为",输入"input_key.c"。见下图:

3)、点击"保存"。输入下面的内容:

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/of.h>

#include <linux/platform_device.h>

#include <linux/of_gpio.h>

#include <linux/input.h>

#include <linux/timer.h>

#include <linux/of_irq.h>

#include <linux/interrupt.h>

#define KEYINPUT_NAME "keyinput" /* 设备名字*/

/* key设备结构体 */

struct key_dev{

struct input_dev *idev; /* 按键对应的input_dev指针 */

struct timer_list timer; /* 定时器结构参数 */

int gpio_key; /* 按键对应的GPIO编号 */

int irq_key; /* 按键对应的中断号 */

};

static struct key_dev key; /* 按键设备 */

//函数功能:按键中断服务函数

//irq: 触发该中断事件对应的中断号

//arg : arg参数可以在申请中断的时候进行配置

static irqreturn_t key_interrupt_fun(int irq,void *dev_id)

{

if(key.irq_key != irq) return IRQ_NONE;

disable_irq_nosync(irq); /*禁止按键中断*/

mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));

//设置定时器在15ms后产生中断,用于按键消抖

//如果定时器还没有激活,则该函数会激活定时器;

//返回值为0表示调用mod_timer()函数前,该定时器未被激活;

//返回值为1表示调用mod_timer()函数前,该定时器已被激活

//timer=&key.timer:要修改超时时间(定时值)的定时器;

//expires=jiffies + msecs_to_jiffies(timerperiod):修改后的超时时间;

return IRQ_HANDLED;

}

//函数功能:定时器中断服务函数

//用于按键消抖,读取按键值,并上报相应的事件。

static void key_timer_function(struct timer_list *arg)

{

int val;

val = gpio_get_value(key.gpio_key);

//根据key.gpio_key读取按键值

input_report_key(key.idev, KEY_0, !val);

/ 向Linux系统通知KEY_0这个按键被按下,1 表示按下

//由于key0被配置为边沿触发中断,且低电平表示按下,因此val取反

input_sync(key.idev);//同步事件

enable_irq(key.irq_key);

//irq=key.irq_key表示要使能的中断号,这里是使能按键中断;

}

//函数功能:按键初始化函数

//nd为device_node型结构指针,指向设备

//返回值为0表示成功,负数表示失败

static int key_gpio_init(struct device_node *nd)

{

int ret;

unsigned long irq_flags;

key.gpio_key = of_get_named_gpio(nd, "key-gpio", 0);

//在key节点中,key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;

//np指定的"设备节点"

//propname="key-gpio",给定要读取的属性名字

//Index=0,给定的GPIO索引为0

//返回值:正值,获取到的GPIO编号;负值,失败。

if(!gpio_is_valid(key.gpio_key)) {

printk("key:Failed to get key-gpio\n");

return -EINVAL;

}

/* 申请使用GPIO */

ret = gpio_request(key.gpio_key, "KEY0");

//gpio=key.gpio_key,指定要申请的"gpio编号"

//Label="KEY0",给这个gpio引脚设置个名字为"KEY0"

//返回值:0,申请"gpio编号"成功;其他值,申请"gpio编号"失败;

if (ret) {

printk(KERN_ERR "key: Failed to request key-gpio\n");

return ret;

}

gpio_direction_input(key.gpio_key);
//设置"某个GPIO为输入口"

//gpio= key.gpio_key,指定的"gpio编号"

//返回值:0,表示设置成功;负值,表示设置失败。

key.irq_key = irq_of_parse_and_map(nd, 0);

//获取GPIO对应的中断号

//dev= nd:为设备节点;

//Index=0:索引号,intemrupts属性可能包含多条中断信息,通过index指定要获取的信息;

//返回值:中断号;

if(!key.irq_key){ return -EINVAL; }

irq_flags = irq_get_trigger_type(key.irq_key);

//根据中断号key.irq_key获取"设备树中指定的中断触发类型"

if (IRQF_TRIGGER_NONE == irq_flags)

irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;

ret = request_irq(key.irq_key, key_interrupt_fun, irq_flags, "Key0_IRQ", NULL);

//用来申请"按键中断";

//irq=key.irq_key为中断号;

//handler=key_interrupt_fun:中断处理函数,当中断发生以后就会执行此中断处理函数;

//fags=irq_flags:中断标志;可以在文件"include/linux/interrupt.h"里面查看所有的中断标志;

//name="Key0_IRQ":中断名字,设置以后可以在"/proc/interrupts"文件中看到对应的中断名字;

//dev=NULL:如果将flags设置为IRQF_SHARED的话,dev用来区分不同的中断。

//一般情况下将dev设置为"设备结构体",dev会传递给中断处理函数irg_handler_t的第二个参数。

//返回值:0表示申请"按键中断"成功,如果返回"-EBUSY"的话表示该中断已经被申请过了, 其他负值,表示申请"按键中断"失败。

if (ret)

{

gpio_free(key.gpio_key);//释放"gpio编号"

return ret;

}

return 0;

}

//函数功能:platform驱动的probe函数

//当驱动与设备匹配成功以后此函数会被执行

//pdev为platform设备指针

//返回值为0,表示成功;其他负值,失败

static int Zhang_key_probe(struct platform_device *pdev)

{

int ret;

ret = key_gpio_init(pdev->dev.of_node);//按键初始化函数

if(ret < 0)return ret;

timer_setup(&key.timer, key_timer_function, 0);

//初始化定时器"timer_list结构变量key.timer"

//key_timer_function为定时器的中断处理函数;

//flags=0为标志位;

key.idev = input_allocate_device();

//申请input_dev结构变量为key.idev

key.idev->name = KEYINPUT_NAME;//设置"设备名字"

#if 0

__set_bit(EV_KEY, key.idev->evbit);

//设置产生"按键事件"

__set_bit(EV_REP, key.idev->evbit);

/* 重复事件,比如按下去不放开,就会一直输出信息 */

/* 初始化input_dev,设置产生哪些按键 */

__set_bit(KEY_0, key.idev->keybit);

#endif

#if 0

key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);

//BIT_MASK(EV_KEY)表示将第EV_KEY位置1

//BIT_MASK(EV_REP)表示将第EV_REP位置1

key.idev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);

//BIT_WORD**(** KEY_0**)**表示 将"KEY_0"转换为字的偏移量

//BIT_MASK**(** KEY_0**)表示 将第KEY_0)**位置1

#endif

#if 1

key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);

//BIT_MASK(EV_KEY)表示将第EV_KEY位置1

//BIT_MASK(EV_REP)表示将第EV_REP位置1

input_set_capability(key.idev, EV_KEY, KEY_0);

//当type=EV_KEY, code=KEY_0将dev->keybit中的第code位置1

#endif

ret = input_register_device(key.idev);

//向Linux内核注册key.idev

// key.idev就是要注册的input_dev结构变量

//返回值:0,input_dev注册成功;负值,input_dev注册失败

if (ret) {

printk("register input device failed!\r\n");

goto free_gpio;

}

return 0;

free_gpio:

free_irq(key.irq_key,NULL);

//key.irq_key:要释放的中断号。

//dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。//共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

//返回值:无。

gpio_free(key.gpio_key); //释放"gpio编号"

del_timer_sync(&key.timer);

//key.timer指向要被删除的定时器

//等待其他处理器使用完定时器后再删除该定时器,del_timer_sync()不能用在

//中断服务程序中;

return -EIO;

}

//函数功能:platform驱动的remove函数,当platform驱动模块卸载时此函数会被执行

//dev为platform设备指针

//返回值为0,成功;其他负值,失败

static int Zhang_key_remove(struct platform_device *pdev)

{

free_irq(key.irq_key,NULL);

//key.irq_key:要释放的中断号。

//dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。//共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

//返回值:无。

gpio_free(key.gpio_key); //释放"gpio编号"

del_timer_sync(&key.timer);

//key.timer指向要被删除的定时器

//等待其他处理器使用完定时器后再删除该定时器,del_timer_sync()不能用在

//中断服务程序中;

input_unregister_device(key.idev);

//释放input_dev型结构key.idev

return 0;

}

static const struct of_device_id key_of_match[] = {

{.compatible = "zgq,key"},/*这是驱动中的compatible属性*/

{/*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/}

};

static struct platform_driver Zhang_key_driver = {

.driver = {

.name = "stm32mp1-key",

.of_match_table = key_of_match,

},

.probe = Zhang_key_probe,

/*platform的probe函数为Zhang_key_probe()*/

.remove = Zhang_key_remove,

/*platform的probe函数为Zhang_key_remove()*/

};

module_platform_driver(Zhang_key_driver);

//Zhang_key_driver为platform_driver结构

//用来向linux内核注册platform驱动

MODULE_AUTHOR("zgq"); //添加作者名字

MODULE_LICENSE("GPL"); //LICENSE采用"GPL协议"

MODULE_DESCRIPTION("This is Key_Driver_Test_Module!");//模块介绍

MODULE_INFO(intree, "Y");

//去除显示"loading out-of-tree module taints kernel."

MODULE_ALIAS("input:key-gpio");

4)、点击"文件",点击"新建文件",点击"文件",点击"另存为",输入"input_key_APP.c"。见下图:

5)、点击"保存"。输入下面的内容:

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdlib.h>

#include <string.h>

#include <linux/input.h>

/*

参数argc: argv[]数组元素个数

参数argv[]:是一个指针数组

返回值: 0 成功;其他 失败

*/

int main(int argc, char *argv[])

{

int fd, ret;

struct input_event ev;

if(2 != argc) {

printf("Usage:\n"

"\t./keyinputApp /dev/input/eventX @ Open Key\n"

);

return -1;

}

/* 打开设备 */

fd = open(argv[1], O_RDWR);

if(0 > fd) {

printf("Error: file %s open failed!\r\n", argv[1]);

return -1;

}

/* 读取按键数据 */

for ( ; ; )

{

ret = read(fd, &ev, sizeof(struct input_event));//读文件

if (ret)

{

switch (ev.type)

{

case EV_KEY:// 按键事件

if (KEY_0 == ev.code)

{//判断是不是KEY_0按键

if(ev.value) // 按键按下

printf("Key0 Press\n");

else // 按键松开

printf("Key0 Release\n");

}

break;

/* 其他类型的事件,自行处理 */

case EV_REL:

break;

case EV_ABS:

break;

case EV_MSC:

break;

case EV_SW:

break;

};

}

else {

printf("Error: file %s read failed!\r\n", argv[1]);

goto out;

}

}

out:

/* 关闭设备 */

close(fd);

return 0;

}

9.3、新建Makefile

Makefile文件如下:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用":="将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用"shell pwd"获取当前打开的路径

#使用"$(变量名)"引用"变量的值"

MyAPP := input_key_APP

input_key_drv-objs = input_key.o

obj-m := input_key_drv.o

CC := arm-none-linux-gnueabihf-gcc

drv:

$(MAKE) -C (KERNELDIR) M=(CURRENT_PATH) modules

app:

$(CC) $(MyAPP).c -o $(MyAPP)

clean:

$(MAKE) -C (KERNELDIR) M=(CURRENT_PATH) clean

rm $(MyAPP)

install:

sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f

9.4、添加"c_cpp_properties.json"

按下"Ctrl+Shift+P",打开VSCode控制台,然后输入"C/C++:Edit Configurations(JSON)",打开以后会自动在".vscode "目录下生成一个名为"c_cpp_properties.json" 的文件。

修改c_cpp_properties.json内容如下所示:

{

"configurations": [

{

"name": "Linux",

"includePath": [

"${workspaceFolder}/**",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",

"/home/zgq/linux/Linux_Drivers/input_key",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"
],

"defines": [],

"compilerPath": "/usr/bin/gcc",

"cStandard": "gnu11",

"cppStandard": "gnu++14",

"intelliSenseMode": "gcc-x64"

}

],

"version": 4

}

10、编译

输入"make clean回车"

输入"make drv回车"

输入"make app回车"

输入"make install回车"

输入"ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车"查看是存在"MISC_beep_APP和MISC_beep_drv.ko"

11、拷贝驱动

输入"cd /home/zgq/linux/Linux_Drivers/input_key/"

输入"ls"

输入"sudo cp input_key_drv.ko /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31"

输入密码"123456回车"

输入"sudo cp input_key_APP /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31"

输入"cd /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31"

输入"ls -l"

12、测试

启动开发板,从网络下载程序

输入"root"

输入"cd /lib/modules/5.4.31/"

在nfs挂载中,切换到"/lib/modules/5.4.31/"目录,

注意:"lib/modules/5.4.31/"在虚拟机中是位于"/home/zgq/linux/nfs/rootfs/"目录下,但在开发板中,却是位于根目录中。

输入"ls -l"

输入"depmod",驱动在第一次执行时,需要运行"depmod"

输入"lsmod"查看有哪些驱动在工作;

输入"modprobe input_key_drv.ko",加载"input_key_drv.ko"模块

输入"cd /dev/input回车"切换到"cd /dev/input"目录;

输入"ls -l"

输入"cd /lib/modules/5.4.31/回车"

在nfs挂载中,切换到"/lib/modules/5.4.31/"目录

输入"./input_key_APP /dev/input/event0回车"读取按键

按下key0,然后再松开,观看串口数据输出。

相关推荐
Allen Bright9 分钟前
Jedis存储一个以byte[]的形式的对象到Redis
数据库·redis·缓存
NiNg_1_23413 分钟前
Redis中的zset用法详解
数据库·redis·缓存
尘浮生36 分钟前
Java项目实战II基于SpringBoot前后端分离的网吧管理系统(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
Who_Mr.Lin38 分钟前
【虚拟机】VMWare的CentOS虚拟机断电或强制关机出现问题
linux·运维·centos
我是唐青枫1 小时前
Linux nc 命令详解
linux·运维·服务器
南东山人1 小时前
关于内核编程的一些笔记
linux·笔记
ejinxian1 小时前
Windows 系统上构建 Linux 应用
linux·运维·服务器·red hat
Java 第一深情1 小时前
详细教程-Linux上安装单机版的Hadoop
linux·运维·hadoop
MC何失眠1 小时前
泷羽sec-----shell编程(完结)
linux·学习·网络安全
Linux运维技术栈2 小时前
ELK配置索引清理策略
linux·运维·elk