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

news/2024/11/24 4:50:56/

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,然后再松开,观看串口数据输出。


http://www.ppmy.cn/news/1549449.html

相关文章

《Python制作动态爱心粒子特效》

一、实现思路 粒子效果&#xff1a; – 使用Pygame模拟粒子运动&#xff0c;粒子会以爱心的轨迹分布并运动。爱心公式&#xff1a; 爱心的数学公式&#xff1a; x16sin 3 (t),y13cos(t)−5cos(2t)−2cos(3t)−cos(4t) 参数 t t 的范围决定爱心形状。 动态效果&#xff1a; 粒子…

2024/11/18学习日志

为了更好地记录并反思自己的学习状况&#xff0c;将每日学习的内容、时长、心得等记录于此日志。 于9月3日开始记录&#xff0c;计划每日记录&#xff0c;希望至少能够坚持一个学期。 学习内容&#xff1a; 大物&#xff1a; 尺缩效应m的变化相对论框架下的能量 计数&#…

C#调用JAVA

参考教程&#xff1a;使用IKVMC转换Jar为dll动态库(含idea打包jar方法)-CSDN博客 已经实践过&#xff0c;好使。

【Android、IOS、Flutter、鸿蒙、ReactNative 】实现 MVP 架构

Android Studio 版本 Android Java MVP 模式 参考 模型层 model public class User {private String email;private String password;public User(String email, String password) {this.email = email;this.password = password;}public String getEmail() {return email;}…

Zabbix:使用CentOS 9,基于LNMP平台,源码部署Zabbix 7。

ZBX&#xff1a;源码部署Zabbix 7 一、Zabbix概述1. 什么是zabbix2. 为什么学习zabbix3. 逻辑架构3. 实验环境4. 软件下载&#xff1a; 二、安装前的系统准备工作1. 配置主机名2. 关闭防火墙3. 关闭selinux4. 配置yum源5. 配置时钟同步6. 优化系统限制7. 安装JDK 三、部署LNMP环…

el-table-column自动生成序号在序号前插入图标

实现效果&#xff1a; 代码如下&#xff1a; 在el-table里加入这个就可以了&#xff0c;需要拿到值可以用scope.$index ​​​​​​​<el-table-column type"index" label"序号" show-overflow-tooltip"true" min-width"40">…

在SpringBoot项目中集成MongoDB

文章目录 1. 准备工作2. 在SpringBoot项目中集成MongoDB2.1 引入依赖2.2 编写配置文件2.3 实体类 3. 测试4. 文档操作4.1 插入操作4.1.1 单次插入4.1.2 批量插入 4.2 查询操作4.2.1 根据id查询4.2.2 根据特定条件查询4.2.3 正则查询4.2.4 查询所有文档4.2.5 排序后返回 4.3 删除…

【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL

技术栈 【后端】Java&#xff0c;基于Spring Boot 3 MySQL Redis RabbitMQ Nacos Docker部署 【前端】基于vue3 typescript5.x element-plus2.8.x unocss0.5.x 访问管理后台&#xff08;演示环境&#xff09; 管理后台的地址是&#xff1a; http://java.test.yesapi.c…