Linux驱动开发基础

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

1 适用场景

2 使用流程

3 驱动编程

4 应用编程

5 代码

5.1 gpio_key_drv.c

5.2 button_test.c

5.3 Makefile

6 异步通知机制内核代码详解


1 适用场景

在前面引入中断时我们曾经举过一个例子

 妈妈怎么知道卧室里小孩醒了 异步通知妈妈在客厅干活小孩醒了他会自己走出房门告诉妈妈妈妈、小孩互不耽误。 使用休眠-唤醒、POLL 机制时都需要休眠等待某个事件发生时它们的差别在于后者可以指定休眠的时长。

在现实生活中妈妈可以不陪小孩睡觉小孩醒了之后可以主动通知妈妈。 如果 APP 不想休眠怎么办也有类似的方法驱动程序有数据时主动通知APPAPP 收到信号后执行信息处理函数。

2 使用流程

驱动程序怎么通知 APP发信号这只有 3 个字却可以引发很多问题 

  • 谁发驱动程序发 
  • 发什么信号 
  • 发什么信号SIGIO 
  • 怎么发内核里提供有函数 
  • 发给谁APPAPP 要把自己告诉驱动 
  • APP 收到后做什么执行信号处理函数 
  • 信号处理函数和信号之间怎么挂钩APP 注册信号处理函数 

小孩通知妈妈的事情有很多饿了、渴了、想找人玩。 
Linux 系统中也有很多信号在 Linux 内核源文件 include\uapi\asm-generic\signal.h 中有很多信号的宏定义

 就 APP 而言你想处理 SIGIO 信息那么需要提供信号处理函数并且要跟SIGIO 挂钩。这可以通过一个 signal 函数来“给某个信号注册处理函数”用法如下 

 APP 还要做什么事想想这几个问题 
a)  内核里有那么多驱动你想让哪一个驱动给你发 SIGIO 信号 APP 要打开驱动程序的设备节点。 
b)  驱动程序怎么知道要发信号给你而不是别人 APP 要把自己的进程 ID 告诉驱动程序。 
c)  APP 有时候想收到信号有时候又不想收到信号 应该可以把 APP 的意愿告诉驱动。 
驱动程序要做什么发信号。 
a)  APP 设置进程 ID 时驱动程序要记录下进程 ID 
b)  APP 还要使能驱动程序的异步通知功能驱动中有对应的函数 APP 打开驱动程序时内核会创建对应的 file 结构体file 中有 f_flags f_flags 中有一个 FASYNC 位它被设置为 1 时表示使能异步通知功能。 当 f_flags 中的 FASYNC 位发生变化时驱动程序的 fasync 函数被调用。d) c)发生中断时有数据时驱动程序调用内核辅助函数发信号。 这个辅助函数名为 kill_fasync。完美  
综上所述使用异步通知也就是使用信号的流程如下图所示 

重点从②开始 
②  APP 给 SIGIO 这个信号注册信号处理函数 func以后 APP 收到 SIGIO信号时这个函数会被自动调用 
③ 把 APP 的 PID(进程 ID)告诉驱动程序这个调用不涉及驱动程序在内核的文件系统层次记录 PID 
④ 读取驱动程序文件 Flag 
⑤  设置 Flag 里面的 FASYNC 位为 1当 FASYNC 位发生变化时会导致驱动程序的 fasync 被调用 
⑥⑦  调 用 faync_helper 它 会 根 据 FAYSNC 的 值 决 定 是 否 设 置button_async->fa_file=驱动文件 filp 驱动文件 filp 结构体里面含有之前设置的 PID。 
⑧ APP 可以做其他事 
⑨⑩ 按下按键发生中断驱动程序的中断服务程序被调用里面调用kill_fasync 发信号  ⑪⑫⑬  APP 收到信号后它的信号处理函数被自动调用可以在里面调用read 函数读取按键。 

3 驱动编程

使用异步通知时驱动程序的核心有 2

  • 提供对应的 drv_fasync 函数 
  • 并在合适的时机发信号。 

drv_fasync 函数很简单调用 fasync_helper 函数就可以如下 

static struct fasync_struct *button_async; 
static int drv_fasync (int fd, struct file *filp, int on) 
{ 
  return fasync_helper (fd, filp, on, &button_async); 
} 

fasync_helper 函 数 会 分 配 、 构 造 一 个 fasync_struct 结 构 体button_async

驱动文件的 flag 被设置为 FAYNC 时 

button_async->fa_file = filp;  // filp 表示驱动程序文件里面含有之前设置的 PID 

驱动文件被设置为非 FASYNC 时

 button_async->fa_file = NULL; 

以后想发送信号时使用 button_async 作为参数就可以它里面“可能”含有 PID。 
什么时候发信号呢在本例中在 GPIO 中断服务程序中发信号。 怎么发信号呢代码如下 
kill_fasync (&button_async, SIGIO, POLL_IN); 
  第 1 个参数button_async->fa_file 非空时可以从中得到 PID表示发给
哪一个 APP 
  第 2 个参数表示发什么信号SIGIO 
  第 3 个参数表示为什么发信号POLL_IN有数据可以读了。(APP 用不到
这个参数) 

4 应用编程

应用程序要做的事情有这几件

编写信号处理函数

static void sig_func(int sig) 
{ 
  int val; 
  read(fd, &val, 4); 
  printf("get button : 0x%x\n", val); 
} 

注册信号处理函数

 signal(SIGIO, sig_func); 

打开驱动 

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

把进程 ID 告诉驱动 

fcntl(fd, F_SETOWN, getpid()); 

使能驱动的 FASYNC 功能 

flags = fcntl(fd, F_GETFL); 
fcntl(fd, F_SETFL, flags | FASYNC); 

5 代码

5.1 gpio_key_drv.c

#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>

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

static struct gpio_key *gpio_keys_100ask;

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

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

struct fasync_struct *button_fasync;

#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;
}

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


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


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);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
	
	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_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);
	}

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

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

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

	device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio_key"); /* /dev/100ask_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, "100ask_gpio_key");

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


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_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");


5.2 button_test.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>

static int fd;
static void sig_func(int sig)
{
	int val;
	read(fd, &val, 4);
	printf("get button : 0x%x\n", val);
}

/*
 * ./button_test /dev/100ask_button0
 *
 */
int main(int argc, char **argv)
{
	int val;
	struct pollfd fds[1];
	int timeout_ms = 5000;
	int ret;
	int	flags;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	signal(SIGIO, sig_func);

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

	fcntl(fd, F_SETOWN, getpid());
	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC);

	while (1)
	{
		printf("www.100ask.net \n");
		sleep(2);
	}
	
	close(fd);
	
	return 0;
}


5.3 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 =  # 板子所用内核源码的目录

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

6 异步通知机制内核代码详解

异步通知的本质是“发信号”涉及 2 个对象发送者、接收者。发送者可以是驱动程序可以是进程接收者必定是进程。驱动程序要想给进程发送信号有 2 个问题需要解决 
① 使能驱动程序的“异步”功能即允许它发出信号 
② 告诉驱动程序发信号时发给“谁” 
应用编程时需要执行如下操作 

⚫  打开驱动 
fd = open(“/dev/xxx”, O_RDWR); 
⚫  把进程 ID 告诉驱动 
fcntl(fd, F_SETOWN, getpid()); 
⚫  使能驱动的 FASYNC 功能 
flags = fcntl(fd, F_GETFL); 
fcntl(fd, F_SETFL, flags | FASYNC); 
对于 F_SETOWN、F_GETFL、F_SETFL内核或驱动程序如何处理 
APP 执行 fcntl 系统调用时会导致内核“fs/fcntl.c”的如下函数被调用 

在“do_fcntl”函数中对于“F_SETOWN”一路查看代码发现最终如下设置

在“do_fcntl”函数中对于“F_GETFL”仅仅是返回“filp->f_flags”对于“F_SETFL”会调用“setfl”函数进一步处理。代码如下

 “setfl”函数会比较“filp->f_flags”中的“FASYNC”位发现它发生了变化时就会调用驱动程序的 faysnc 函数

 驱动程序的 faysnc 函数代码如下

 它使用 fasync_helper 函数来设置指针 button_fasync简化后的示例代码如下

if (on) 
{ 
    struct fasync_struct *new; 
    new = fasync_alloc(); 
    new->magic = FASYNC_MAGIC; 
    new->fa_file = filp; 
    new->fa_fd = fd; 
    button_fasync = new; 
} 
else 
{ 
    kfree(button_fasync); 
    button_fasync = NULL; 
} 

所以启动了 FASYNC 功能的话驱动程序的 button_fasync 就被设置了它指向的 fasync_struct 结构体里含有 filpfilp 里含有 PID(接收方的 PID)。 
在驱动程序的中断函数里使用如下代码发出信号 kill_fasync(&button_fasync, SIGIO, POLL_IN); 
它的核心就是从 button_fasync 指针中取出 fasync_struct 结构体从这个结构体的 fa_file 中得到接收方的 PID然后使用“send_sigio”函数发送信号。 

“send_sigio”函数的实质是根据 PID 找到进程在内核的 task_struct结构体修改里面的某些成员表示收到了信号。 
 APP 收到信号后它的信号处理函数时如何被调用的呢信号相当于 APP 的中断处理过程也跟中断的处理过程类似保存现场、处理信号恢复现场。 
 APP 进入内核态时内核在 APP 的栈里保存“APP 的运行环境”APP 在用户态进入内核态瞬间各个寄存器的值包括“运行地址”(即恢复运行时从哪里继续运行)。 
APP 退出内核态时内核会从 APP 的栈里恢复“APP 的运行环境”比如 APP将从之前保存的“运行地址”继续运行。  
APP 收到信号瞬间APP 必定处于内核态因为信号的发送函数“send_sigio”要么由驱动程序调用要么由 APP 通过系统调用来间接调用函数“send_sigio”处于内核态。APP 从内核态返回到用户态前内核发现 APP 有信号在等待处理时会修改 APP 的栈增加一个新的“运行环境”新环境里“运行地址”是信号处 理函数的地址。这样APP 从内核态返回用户态时运行的是信号处理函数。信号处理函数执行完后会再次返回到内核态在内核态里再使用旧的“运行环境”
恢复 APP 的运行。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux