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。
//返回值: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/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>
/*
参数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,然后再松开,观看串口数据输出。