STM32MP157驱动开发——按键驱动(POLL 机制)

文章目录

  • “POLL ”机制:
  • APP执行过程
  • 驱动使用的函数
  • 应用使用的函数
    • pollfd结构体
    • poll函数
    • 事件类型
    • 实现原理
  • poll方式的按键驱动程序(stm32mp157)
    • gpio_key_drv.c
    • button_test.c
    • Makefile
    • 修改设备树文件
    • 编译测试

“POLL ”机制:

使用休眠-唤醒的方式等待某个事件发生时,有一个缺点:等待的时间可能很久。我们可以加上一个超时时间,这时就可以使用 poll 机制。

  • ① APP 不知道驱动程序中是否有数据,可以先调用 poll 函数查询一下,poll 函数可以传入超时时间;
  • ② APP 进入内核态,调用到驱动程序的 poll 函数,如果有数据的话立刻返回
  • ③ 如果发现没有数据时就休眠一段时间;
  • ④ 当有数据时,比如当按下按键时,驱动程序的中断服务程序被调用,它会记录数据、唤醒 APP;
  • ⑤ 当超时时间到了之后,内核也会唤醒 APP;
  • ⑥ APP 根据 poll 函数的返回值就可以知道是否有数据,如果有数据就调用read 得到数据。

会调用两次poll函数

APP执行过程

STM32MP157驱动开发——按键驱动(POLL 机制)_第1张图片

从③开始看。假设一开始无按键数据但后面有按键中断:

  • ③PP 调用 poll 之后,进入内核态;
  • ④致驱动程序的 drv_poll 被调用;【把线程放入wq,但未想休眠,返回event状态】
  • ⑤当前没有数据,则休眠一会;【在内核中休眠,而不是在驱动中休眠】
  • ⑥过程中,按下了按键,发生了中断;【在中断服务程序里记录了按键值,并且从 wq 中把线程唤醒了】
  • ⑦从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll:【drv_poll 返回数据状态】
  • ⑧如果有数据,则从内核态返回到应用态
  • ⑨APP 调用 read 函数读数据

如果一直没有数据,流程如下:

  • ③ APP 调用 poll 之后,进入内核态;
  • ④ 导致驱动程序的 drv_poll 被调用:
  • ⑤ 假设当前没有数据,则休眠一会;
  • ⑥ 在休眠过程中,一直没有按下了按键,超时时间到:内核把这个线程唤醒;
  • ⑦ 线程从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll:drv_poll 返回数据状态
  • ⑧ 虽然没有数据,但是超时时间到了,则从内核态返回到应用态
  • ⑨ APP 不能调用 read 函数读数据

注意几点:

  • drv_poll 要把线程挂入队列 wq,但是并不是在 drv_poll 中进入休眠,而是在调用 drv_poll 之后休眠
  • drv_poll 要返回数据状态
  • APP 调用一次 poll,有可能会导致 drv_poll 被调用 2 次
  • 线程被唤醒的原因有 2:中断发生了去队列 wq 中把它唤醒,超时时间到了内核把它唤醒
    -APP 要判断 poll 返回的原因:判断是有数据,还是超时。有数据时再去调用 read函数。

驱动使用的函数

使用 poll 机制时,驱动程序的核心就是提供对应的 drv_poll 函数。在drv_poll 函数中要做 2 件事:

① 把当前线程挂入队列 wq:poll_wait

  • a) APP 调用一次 poll,可能导致 drv_poll 被调用 2 次,但是我们并不需要把当前线程挂入队列 2 次。
  • b) 可以使用内核的函数 poll_wait 把线程挂入队列,如果线程已经在队列里了,它就不会再次挂入。

② 返回设备状态:
APP 调用 poll 函数时,有可能是查询“有没有数据可以读”:POLLIN,也有可能是查询“你有没有空间给我写数据”:POLLOUT

所以 drv_poll 要返回自己的当前状态:(POLLIN | POLLRDNORM) 或 (POLLOUT | POLLWRNORM)

  • a) POLLRDNORM 等同于 POLLIN,为了兼容某些 APP 把它们一起返回。
  • b) POLLWRNORM 等同于 POLLOUT ,为了兼容某些 APP 把它们一起返回。

APP 调用 poll 后,很有可能会休眠。对应的,在按键驱动的中断服务程序中,也要有唤醒操作。驱动程序中 poll 的代码如下:

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_key_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

应用使用的函数

APP 可以调用 poll 或 select 函数,这 2 个函数的作用是一样的。poll/select 函数可以监测多个文件,可以监测多种事件:

pollfd结构体

struct pollfd
{
	int fd; 
	short events;//等待发生的事件类型
	short revents; //检测之后返回的事件,当某个文件描述符有变化时,值就不为空
}

poll函数

#include 
int poll(struct pollfd* fds, nfds_t nfds, int timeout);

参数说明:

  • fds 是一个struct pollfd类型的指针,用于存放需要检测其状态的socket描述符
  • nfds 是nfd_t类型的参数,用于标记fds数组中结构体元素的数量
  • timeout 没有接受事件时等待的事件,单位毫秒,若值为-1,则永远不会超时

poll机制会判断fds中的文件是否满足条件,如果休眠时间内条件满足则会唤醒进程;超过休眠时间,条件一直不满足则自动唤醒。

  • 返回值>0:fds中准备好读写,或出错状态的那些socket描述符;
  • 返回值=0:fds中没有socket描述符需要读写或出错;此时poll超时,时长为timeout;
  • 返回值=-1:调用失败。

事件类型

事件类型 说明
POLLIN 有数据可读
POLLRDNORM 等同于 POLLIN
POLLRDBAND Priority band data can be read,有优先级较较高的“band data”可读Linux 系统中很少使用这个事件
POLLPRI 高优先级数据可读
POLLOUT 可以写数据
POLLWRNORM 等同于 POLLOUT
POLLWRBAND Priority data may be written
POLLERR 发生了错误
POLLHUP 挂起
POLLNVAL 无效的请求,一般是 fd 未 open

实例:

struct pollfd fds[1];
int timeout_ms = 5000;
int ret;

fds[0].fd = fd;
fds[0].events = POLLIN;

ret = poll(fds, 1, timeout_ms);//返回就绪事件的个数
if ((ret == 1) && (fds[0].revents == POLLIN))
{
	read(fd, &val, 4);
	printf("get button : 0x%x\n", val);
}

实现原理

内核将用户的fds结构体数组拷贝到内核中。当有事件发生时,再将所有事件都返回到fds结构体数组中,poll只返回已就绪事件的个数,所以用户要操作就绪事件就要用轮询的方法。

poll方式的按键驱动程序(stm32mp157)

相比于休眠唤醒的程序,只需要调用在file_operations 结构体里面添加poll函数,使用 poll 机制时,驱动程序的核心就是提供对应的 drv_poll 函数。在drv_poll 函数中要做 2 件事:一个是挂入队列,一个是返回状态

gpio_key_drv.c

#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
} ;

static struct gpio_key *gpio_keys_first;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_key_class;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;
	
	wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);//内核会打印该函数两次
	poll_wait(fp, &gpio_key_wait, wait);//挂入队列
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;//返回状态
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_key_drv_read,
	.poll    = gpio_key_drv_poll,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	int key;
	
	val = gpiod_get_value(gpio_key->gpiod);
	

	printk("key %d %d\n", gpio_key->gpio, val);
	key = (gpio_key->gpio << 8) | val;
	put_key(key);
	wake_up_interruptible(&gpio_key_wait);
	
	return IRQ_HANDLED;
}

/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;
	enum of_gpio_flags flag;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node);
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_first = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{
		gpio_keys_first[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_first[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_first[i].gpiod = gpio_to_desc(gpio_keys_first[i].gpio);
		gpio_keys_first[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_first[i].irq  = gpio_to_irq(gpio_keys_first[i].gpio);
	}

	for (i = 0; i < count; i++)
	{
		err = request_irq(gpio_keys_first[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "my_gpio_key", &gpio_keys_first[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "my_gpio_key", &gpio_key_drv);  /* /dev/gpio_key */

	gpio_key_class = class_create(THIS_MODULE, "my_gpio_key_class");
	if (IS_ERR(gpio_key_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "my_gpio_key");
		return PTR_ERR(gpio_key_class);
	}

	device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "my_gpio_key"); /* /dev/my_gpio_key */
        
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
	//int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;

	device_destroy(gpio_key_class, MKDEV(major, 0));
	class_destroy(gpio_key_class);
	unregister_chrdev(major, "my_gpio_key");

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_keys_first[i].irq, &gpio_keys_first[i]);
	}
	kfree(gpio_keys_first);
    return 0;
}


static const struct of_device_id my_keys[] = {
    { .compatible = "first_key,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "my_gpio_key",
        .of_match_table = my_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");



button_test.c


#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * ./button_test /dev/my_gpio_key
 *
 */
int main(int argc, char **argv)
{
	int fd;
	int val;
	struct pollfd fds[1];
	int timeout_ms = 5000;//5s之后返回打印驱动函数drv_poll的信息
	int ret;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s \n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	fds[0].fd = fd;
	fds[0].events = POLLIN;
	

	while (1)
	{
		/* 3. 读文件 */
		ret = poll(fds, 1, timeout_ms);
		if ((ret == 1) && (fds[0].revents & POLLIN))
		{
			read(fd, &val, 4);
			printf("get button : 0x%x\n", val);
		}
		else
		{
			printf("timeout\n");
		}
	}
	
	close(fd);
	
	return 0;
}



Makefile

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR =   /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o button_test button_test.c
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order  button_test

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o



obj-m += gpio_key_drv.o


修改设备树文件

在这里插入图片描述
对于一个引脚要用作中断时,

  • a) 要通过 PinCtrl 把它设置为 GPIO 功能;【ST 公司对于 STM32MP157 系列芯片,GPIO 为默认模式 不需要再进行配置Pinctrl 信息】
  • b) 表明自身:是哪一个 GPIO 模块里的哪一个引脚【修改设备树】

打开内核的设备树文件:arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts

gpio_keys_first {
	compatible = "first_key,gpio_key";
	gpios = <&gpiog 3 GPIO_ACTIVE_LOW
			&gpiog 2 GPIO_ACTIVE_LOW>;
};

与此同时,需要把用到引脚的节点禁用

注意,如果其他设备树文件也用到该节点,需要设置属性为disabled状态,在arch/arm/boot/dts目录下执行如下指令查找哪些设备树用到该节点

grep "&gpiog" * -nr

如果用到该节点,需要添加属性去屏蔽:

status = "disabled"; 

在这里插入图片描述

编译测试

首先要设置 ARCH、CROSS_COMPILE、PATH 这三个环境变量后,进入 ubuntu 上板子内核源码的目录,在Linux内核源码根目录下,执行如下命令即可编译 dtb 文件:

make dtbs V=1

编译好的文件在路径由DTC指定,移植设备树到开发板的共享文件夹中,先保存源文件,然后覆盖源文件,重启后会挂载新的设备树,进入该目录查看是否有新添加的设备节点

cd /sys/firmware/devicetree/base 

编译驱动程序,在Makefile文件目录下执行make指令,此时,目录下有编译好的内核模块gpio_key_drv.ko和可执行文件button_test文件移植到开发板上

确定一下烧录系统:cat /proc/mounts,查看boot分区挂载的位置,将其重新挂载在boot分区:mount /dev/mmcblk2p2 /boot,然后将共享文件夹里面的设备树文件拷贝到boot目录下,这样的话设备树文件就在boot目录下

cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot

重启后挂载,运行

insmod -f gpio_key_drv.ko // 强制安装驱动程序
ls /dev/my_gpio_key
./button_test /dev/my_gpio_key & //后台运行,此时prink函数打印的内容看不到

然后按下按键

你可能感兴趣的:(stm32,驱动开发,嵌入式硬件)