Linux 시스템의 드라이버 장치 트리에 버튼 드라이버를 추가하는 방법

안녕하세요 여러분, 매일 간단한 드라이버입니다. 시간이 지남에 따라 Linux 드라이버에 점점 더 익숙해지고 드라이버 프로그램 작성을 배우는 것이 점점 더 쉬워질 것입니다. 오늘은 간단한 버튼드라이브를 해보겠습니다.

1. Linux에서의 버튼 구동 원리

버튼 드라이버와 LED 드라이버의 원리는 기본적으로 동일하며, 둘 다 GPIO를 구동하지만, 하나는 GPIO의 High와 Low 레벨을 읽고, 다른 하나는 GPIO에서 High와 Low 레벨을 출력하는 것입니다. 이번에는 키 입력을 구현하기 위해 드라이버에서 정수 변수를 사용하여 키 값을 표현하는데, 애플리케이션은 읽기 기능을 통해 키 값을 읽어 키가 눌렸는지 확인합니다.

여기서 키 값을 저장하는 변수는 공유 리소스이므로 드라이버는 여기에 키 값을 써야 하고, 애플리케이션은 키 값을 읽어야 합니다. 따라서 이를 보호해야 합니다. 정수 변수의 경우 첫 번째 선택은 원자 연산입니다. 원자 연산을 사용하여 변수를 할당하고 읽습니다. Linux에서 키 드라이버의 원리는 매우 간단합니다.다음으로 드라이버 작성을 시작합니다.

이 장의 루틴은 Linux에서 GPIO 입력 드라이버 작성을 보여주기 위한 것입니다. 실제 버튼 드라이버는 이 장에서 설명하는 방법을 사용하지 않습니다. Linux의 입력 하위 시스템은 입력 장치에 특별히 사용됩니다.

2. 하드웨어 도식 분석

그림

위 그림에서 볼 수 있듯이 KEY0 버튼은 I.MX6U의 UART1_CTS IO에 연결되어 있고 KEY0은 10K 풀업 저항에 연결되어 있으므로 KEY0을 누르지 않았을 때 UART1_CTS는 하이 레벨이 되어야 한다. 누르면 UART1_CTS가 로우 레벨이 됩니다.

3. 개발 환경

  • CPU : IMX6ULL

  • 커널 버전: Linux-5.19

4. 장치 트리 파일 수정

1. pinctrl 노드 추가

I.MX6U-ALPHA 개발 보드의 KEY는 PIN UART1_CTS_B를 사용합니다. imx6ul-14x14-evk.dtsi를 열고 iomuxc 노드의 imx6ul-evk 하위 노드 아래에 "pinctrl_key"라는 하위 노드를 만듭니다. 노드 내용 다음과 같습니다.

pinctrl_key: keygrp {
    fsl,pins = <
        MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* key0 */
    >;
};

3행에서는 PIN GPIO_IO18을 GPIO1_IO18로 재사용합니다.

2. KEY 장치 노드 추가

루트 노드 "/" 아래에 KEY 노드를 생성합니다. 노드 이름은 "key"이고 노드 내용은 다음과 같습니다.

key {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "imx6ull-key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
    status = "okay";
};
  • 6행에서 pinctrl-0 속성은 KEY에서 사용하는 PIN에 해당하는 pinctrl 노드를 설정합니다.

  • 7행에서 key-gpio 속성은 KEY에서 사용하는 GPIO를 지정합니다.

3. 해당 PIN을 다른 주변기기에서 사용하고 있는지 확인하세요.

본 실험에서 버튼이 사용하는 PIN은 UART1_CTS_B이므로 먼저 PIN UART1_CTS_B가 다른 pinctrl 노드에서 사용되는지 확인하고, 사용한다면 차단해야 하며, GPIO GPIO1_IO18이 다른 주변기기에서 사용되는지 확인한다. 있는 경우에도 차단되어야 합니다.

장치 트리가 작성된 후 "make dtbs" 명령을 사용하여 장치 트리를 다시 컴파일한 다음 새로 컴파일된 imx6ull-toto.dtb 파일을 사용하여 Linux 시스템을 시작합니다. 시동 성공 후 "/proc/device-tree" 디렉토리에 들어가 "key" 노드가 존재하는지 확인합니다. 존재한다면 디바이스 트리가 기본적으로 성공적으로 수정되었음을 의미합니다. (구체적으로 드라이버 검증이 필요합니다.) .결과는 다음과 같습니다.

/ # ls /proc/device-tree/
#address-cells      clock-osc           pmu
#size-cells         compatible          regulator-can-3v3
aliases             cpus                regulator-peri-3v3
backlight-display   dts_led             regulator-sd1-vmmc
beep                key                 soc
chosen              memory@80000000     sound-wm8960
clock-cli           model               spi4
clock-di0           name                timer
clock-di1           panel

5. 버튼 드라이버 작성

장치 트리가 준비되면 드라이버를 작성할 수 있습니다. key.c에 다음 내용을 입력하십시오.

/************************************************************
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * Description: 
 * Version: 1.0
 * Autor: toto
 * Date: Do not edit
 * LastEditors: Seven
 * LastEditTime: Do not edit
************************************************************/

#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define KEY_CNT         1       /* 设备号数量 */
#define KEY_NAME        "key"   /* 设备名字 */

/*定义按键值*/
#define KEY0_VALUE      0xF0    /* 按键值 */
#define INVAKEY         0x00    /* 无效按键值 */

/* key 设备结构体 */
struct key_dev
{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int key_gpio;           /* key 所使用的GPIO编号 */
    atomic_t keyvalue;      /* 按键值 */
};

struct key_dev keydev;      /* key 设备 */

/*
 * @Brief   初始化按键使用的GPIO管脚
 * @Call    Internal or External
 * @Param   
 * @Note    NOne
 * @RetVal  无
 */
int keyio_init(void)
{
    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL)
    {
        return -EINVAL;
    }

    keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);
    if(keydev.key_gpio < 0)
    {
        printk("can't get key-gpio\n");
        return -EINVAL;
    }
    printk("key_gpio:%d\n", keydev.key_gpio);

    /* c初始化 key 使用的IO */
    gpio_request(keydev.key_gpio, "key0");  /* 请求IO */
    gpio_direction_input(keydev.key_gpio);  /* 设置为输入 */

    return 0;
}

/*
 * @Brief   打开设备
 * @Call    Internal or External
 * @Param   inode:
 * @Param   filp:设备文件
 * @Note    NOne
 * @RetVal  0:成功 其他值:失败
 */
static int beep_open(struct inode *inode, struct file *filp)
{
    int ret = 0;

    /* 设置私有数据 */
    filp->private_data = &keydev;

    /* 初始化按键IO */
    ret = keyio_init();
    if(ret < 0)
    {
        return ret;
    }

    return 0;
}

/*
 * @Brief   从设备读数据
 * @Call    Internal or External
 * @Param   filp:要打开的设备文件描述符
 * @Param   buf:返回给用户空间的数据地址
 * @Param   cnt:要读取的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  读取的字节数,若为负值,表示读失败
 */
static ssize_t beep_read(struct file *filp, char __user *buf,
                        size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char value;
    struct key_dev *dev = filp->private_data;

    /* key0 按下 */
    if(gpio_get_value(dev->key_gpio) == 0)
    {
        /* 等待按键释放 */
        while(!gpio_get_value(dev->key_gpio));
        atomic_set(&dev->keyvalue, KEY0_VALUE);
    }
    else
    {
        /* 无效的按键值 */
        atomic_set(&dev->keyvalue, INVAKEY);
    }

    /* 保存按键值 */
    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf, &value, sizeof(value));
    
    return ret;
}

/*
 * @Brief   写数据到设备
 * @Call    Internal or External
 * @Param   filp:要打开的设备文件描述符
 * @Param   buf:要写入设备的数据地址
 * @Param   cnt:要写入的数据长度
 * @Param   offt:相对于文件首地址的偏移
 * @Note    NOne
 * @RetVal  写入的字节数,若为负值,表示写失败
 */
static ssize_t beep_write(struct file *filp, const char __user *buf,
                        size_t cnt, loff_t *offt)
{
    return 0;
}


/*
 * @Brief   关闭/释放设备
 * @Call    Internal or External
 * @Param   inode:
 * @Param   filp:要关闭的设备文件描述符
 * @Note    NOne
 * @RetVal  NOne
 */
static int beep_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open  = beep_open,
    .read  = beep_read,
    .write = beep_write,
    .release = beep_release,
};

/*
 * @Brief   驱动入口函数
 * @Call    Internal or External
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init mykey_init(void)
{
    /* 初始化原子变量 */
    atomic_set(&keydev.keyvalue, INVAKEY);

    /* 注册字符设备驱动 */
    /* 创建设备号 */
    if(keydev.major) /* 定义了设备号 */
    {
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
    }
    else
    {
        /* 申请设备号 */
        alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);
        /* 获取主设备号 */
        keydev.major = MAJOR(keydev.devid);
        /* 获取次设备号 */
        keydev.minor = MINOR(keydev.devid);
    }
    printk("%s new_chrdev major:%d minor:%d\n", __func__,
            keydev.major, keydev.minor);

    /* 初始化cdev */
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);

    /* 添加一个cdev */
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);

    /* 创建类 */
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if(IS_ERR(keydev.class))
    {
        return PTR_ERR(keydev.class);
    }

    /* 创建设备 */
    keydev.device = device_create(keydev.class, NULL,
                        keydev.devid, NULL, KEY_NAME);
    if(IS_ERR(keydev.device))
    {
        return PTR_ERR(keydev.device);
    }

    return 0;
}

/*
 * @Brief   驱动出口函数
 * @Call    Internal or External
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit mykey_exit(void)
{
    /* 注销字符设备驱动 */
    gpio_free(keydev.key_gpio);
    /* 注销字符设备 */
    /* 删除cdev */
    cdev_del(&keydev.cdev);
    /* 释放分配的设备号 */
    unregister_chrdev_region(keydev.devid, KEY_CNT);

    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");

6. 테스트 앱 작성

key_app.c 테스트 프로그램의 구체적인 코드는 다음과 같습니다.

/********************************************
 *Description: 
 *Version: 1.0
 *Autor: toto
 *Date: Do not edit
 *LastEditors: Seven
 *LastEditTime: Do not edit
********************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#define KEY0VALUE   0xF0
#define INVAKEY     0x00

/*
 * @Brief   main 主程序
 * @Call    Internal or External
 * @Param   argc:
 * @Param   argv:
 * @Note    NOne
 * @RetVal  0-成功;其他-失败
 */
int main(int argc, char *argv[])
{
    int fd, retval;
    char *filename;
    unsigned char keyvalue;

    if(argc != 2)
    {
        printf("argc != 2\n");
        return -1;
    }

    filename = argv[1];

    /*打开驱动文件*/
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("open filename:%d failed\n", filename);
        return -1;
    }

    /* 要执行的操作:打开或关闭 */
    while(1)
    {
        read(fd, &keyvalue, sizeof(keyvalue));
        if(keyvalue == KEY0VALUE)
        {
            /* 按下 */
            printf("KEY0 down, value:0x%x\n", keyvalue);
        }
    }

    /*关闭文件*/
    close(fd);

    return 0;
}

7. 작동 확인

개발 보드의 전원을 켜고 key.ko 및 key_app 두 파일을 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 디렉터리에 복사한 후 다음 명령을 입력하여 key.ko 드라이버 모듈을 로드합니다.

/ # insmod /lib/modules/5.19.0-g794a2f7be62d-dirty/key.ko 
[  108.608375] key_driver: loading out-of-tree module taints kernel.
[  108.619185] mykey_init new_chrdev major:242 minor:0

드라이버가 성공적으로 로드된 후 다음 명령을 사용하여 테스트할 수 있습니다.

./key_app /dev/key

개발 보드에서 KEY0 버튼을 누르면 key_app이 아래와 같이 키 정보를 획득하고 출력합니다.

/home/app # ./key_app /dev/key 
[  155.043576] key_gpio:18
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0
KEY0 down, value:0xf0

위에서 볼 수 있듯이 KEY0을 누르면 "KEY0 down, value = 0XF0"이 인쇄되어 키가 눌렸음을 나타냅니다. 가끔 KEY0을 한 번 누르면 "KEY0 down, value = 0XF0"이라는 여러 줄이 출력되는 경우가 있는데, 이는 우리 코드가 키 디바운스 처리를 수행하지 않기 때문입니다. 드라이버를 제거하려면 다음 명령을 입력하십시오.

rmmod key.ko

추천

출처blog.csdn.net/weixin_41114301/article/details/132776511