Linux设备驱动浅谈 利用Linux内核接口可以实现自定义模块机制

驱动硬件

接触编程,大家应该都有这样一个疑惑,我们写的应用程序一执行,便在操作系统上工作,其原理是(当程序一执行,它便成为一个操作系统上的进程,然后由操作系统进行管理),那如果我们写的程序要让某些硬件设备工作呢,那又是如何工作的,这对于应用开发的人员而言,一般不作太深入的了解,但是在计算机体系中,它是不可或缺的一部分。

在实际的项目开发中以Linux环境为多数,此图诠释了计算机软件层和硬件层的组成,本文将对下面这个结构不断拆解,最后回答这个问题——如何用程序让硬件设备工作

应用软件的工程师,一般于用户空间中对于应用程序的设计与开发,以及内核空间中的进程与内存管理的协调工作。而底层的开发人员,即设计驱动程序的工程师,不得不在内核空间和用户空间以及硬件资源做协调。

其实,总结成一句话,让硬件设备工作,就是让驱动程序所编译的模块(目标代码文件)被计算机的操作系统所识别到,做法就是作为其内核组件模块装载在操作系统中,当应用程序需要使用到时,通过系统调用,让处理器切换到特权模式(内核模式)读懂模块(被装载并解析的二进制文件),然后执行,这样便达到了控制硬件工作的目标。( 当然,对于一些传感器硬件,其实在用户模式下通过一些标准接口也可以驱动硬件工作,但是本质也是调用了别人设计好的驱动进行的间接调用,由于涉及内容太多,本文主要以驱动为主脉络进行阐述 )

更加直接了当的过程,如下(其中电平逻辑到晶体管开关中,也有由外设寄存器操作控制,CPU在处理二进制程序时会有指令构造,是这样一个过程,取指令、译码、执行、访存、写回;这一顿操作后就会影响到硬件的具体工作情况,从而驱动硬件工作)

大白话(高级语言->汇编语言->二进制程序->数字逻辑->电平逻辑->晶体管开关)

前面提到了很多名词,诸如系统调用、内核空间、用户空间等这些主要是操作系统及计算机组成的基本知识,想全面了解的可以自行搜索哈。

设备驱动程序的作用

同上面所赘述的,总结而言,大多数编程问题实际上都是可以分成两部分 ① 需要提供什么功能(机制) ② 如何使用这些功能 (策略) 如果这两个问题由程序的不同部分来处理,那管理起来也就非常方便了。因此,驱动程序和应用程序的划分,应运而生。前者给用户提供统一接口,后者实现特定的应用需求。

驱动程序可以看作是应用程序和实际设备之间的一个软件层。说到驱动、不得不简要地提一下内核、因为驱动是基于内核的函数接口所做的模块拓展

内核功能划分

某种角度上,内核即操作系统;其负责有进程管理、内存管理、文件系统、设备控制、网络功能。

而设备控制则主要是驱动程序的大头,尽管驱动程序也会涉及到内核子系统的各个组件。

内核模式下的系统调用

在Linux系统编程中,我们常用的文件IO操作,open,read,write等即为系统调用的接口,而程序程序则是把机制设计成模块接口,供系统调用来控制。

在高级的系统中(非嵌入式的裸机系统),会把内存划分为两个或者多个空间

这样做的目的就是让程序分层,让系统更加健硕,让用户空间中的程序想访问内核空间,必须通过系统调用(此时CPU的操作模式会切换为特权模式(在Unix中称为内核模式)),然后去调用为底层硬件所设计的模块接口(机制)。

模块化编程

Linux有一个特性,即内核提供的特性可在运行时进行扩展(向内核添加功能,也可以移除功能)。可在运行时添加到内核中的代码被称为“模块”,包括但不限于设备驱动程序,每个模块由目标代码组成(没有链接成一个完整的可执行程序)

驱动程序的模块化编程,是一个很好的特性,使得硬件机制的移植变得更加灵活,功能的模块化也易于管理。内核开发者实现整个系统设备类型的共有特性,然后提供给驱动程序实现者,进而避免了重复的工作,也是模块化的一个优势,降低了出现缺陷的可能。

可装载模块

在驱动程序中,可以使用insmod工具和rmmod工具进行模块的装载和卸载。

设备和模块的分类,主要划分为三种类型,字符设备(字节流(类似文件))、块设备、网络设备。字符设备的一个特性是它为只能顺序访问的数据通道。块设备,每个块包含512字节(或者2的更高次幂字节的数据)

在内核中,和字符驱动程序相比,块驱动程序具有完全不同的接口。而网络接口,内核调用一套和数据包传输相关的函数而不是read、write等。

举个栗子

在设计驱动程序,并编译成模块,需要涉及到指定Linux版本内核的内核源代码树。

如下为在某款国产芯片(也是ARM架构)上设计的字符设备驱动程序,其中使用5.10版本内核的GPIO子系统来实现对dht11字符设备驱动的模块设计。

其中所包含的头文件,即为linux内核的接口函数声明,要编译这一段代码,还需要做的工作有,配置好交叉编译的工具链、设计引导内核源代码树的Makefile文件。

#include "linux/jiffies.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/delay.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>
#include <linux/timer.h>

#include <linux/module.h>

struct gpio_desc{
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

// gpio group   PHYTIUMPI   GPIO 4_11    432 + 11
static struct gpio_desc gpios[] = {
    {443, 0, "dht11", },
};


static int major = 0;
static struct class *gpio_class;

static u64 g_dht11_irq_time[84];
static int g_dht11_irq_cnt = 0;

/* recur buffer */
#define BUF_LEN 128
static char g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

static irqreturn_t dht11_isr(int irq, void *dev_id);
static void parse_dht11_datas(void);

#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(char key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

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


static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

static void key_timer_expire(struct timer_list *t){
	
	parse_dht11_datas();
}


static ssize_t dht11_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kern_buf[2];

	if (size != 2)
		return -EINVAL;

	g_dht11_irq_cnt = 0;

	/* 1. 发送18ms的低脉冲 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	gpio_direction_output(gpios[0].gpio, 0);
	gpio_free(gpios[0].gpio);

	mdelay(18);
	gpio_direction_input(gpios[0].gpio);  /* 引脚变为输入方向, 由上拉电阻拉为1 */

	/* 2. 注册中断 */
	err = request_irq(gpios[0].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[0].name, &gpios[0]);
	mod_timer(&gpios[0].key_timer, jiffies + 10);	

	/* 3. 休眠等待数据 */
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());

	free_irq(gpios[0].irq, &gpios[0]);

	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 设置DHT11 GPIO引脚的初始状态: output 1 */
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err)
	{
		printk("%s %s %d, gpio_request err\n", __FILE__, __FUNCTION__, __LINE__);
	}
	gpio_direction_output(gpios[0].gpio, 1);
	gpio_free(gpios[0].gpio);


	/* 4. copy_to_user */
	kern_buf[0] = get_key();      // will move r = NEXT_POS(r);
	kern_buf[1] = get_key();

	printk("get val : 0x%x, 0x%x\n", kern_buf[0], kern_buf[1]);
	if ((kern_buf[0] == (char)-1) && (kern_buf[1] == (char)-1))
	{
		printk("get err val\n");
		return -EIO;
	}

	err = copy_to_user(buf, kern_buf, 2);
	
	return 2;
}

static int dht11_release (struct inode *inode, struct file *filp)
{
	return 0;
}


static struct file_operations dht11_drv = {
	.owner	 = THIS_MODULE,
	.read    = dht11_read,
	.release = dht11_release,
};

static void parse_dht11_datas(void)
{
	int i;
	u64 high_time;
	unsigned char data = 0;
	int bits = 0;
	unsigned char datas[5];
	int byte = 0;
	unsigned char crc;

	/* 数据个数: 可能是81、82、83、84 */
	if (g_dht11_irq_cnt < 81)
	{
		/* 出错 */
		put_key(-1);
		put_key(-1);

		// 唤醒APP
		wake_up_interruptible(&gpio_wait);
		g_dht11_irq_cnt = 0;
		return;
	}

	// 解析数据
	for (i = g_dht11_irq_cnt - 80; i < g_dht11_irq_cnt; i+=2)
	{
		high_time = g_dht11_irq_time[i] - g_dht11_irq_time[i-1];

		data <<= 1;

		if (high_time > 50000) /* data 1 */
		{
			data |= 1;
		}//有校验保护,可以不处理低电平

		bits++;

		if (bits == 8)
		{
			datas[byte] = data;
			data = 0;
			bits = 0;
			byte++;
		}
	}

	// 放入环形buffer
	crc = datas[0] + datas[1] + datas[2] + datas[3];
	if (crc == datas[4])
	{
		put_key(datas[0]);
		put_key(datas[2]);
	}
	else
	{
		put_key(-1);
		put_key(-1);
	}

	g_dht11_irq_cnt = 0;
	// 唤醒APP
	wake_up_interruptible(&gpio_wait);
}

static irqreturn_t dht11_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	u64 time;
	
	/* 1. 记录中断发生的时间 */
	time = ktime_get_ns();
	g_dht11_irq_time[g_dht11_irq_cnt] = time;

	/* 2. 累计次数 */
	g_dht11_irq_cnt++;

	/* 3. 次数足够: 解析数据, 放入环形buffer, 唤醒APP */
	if (g_dht11_irq_cnt == 84)
	{
		del_timer(&gpio_desc->key_timer);
		parse_dht11_datas();    //significal
	}

	return IRQ_HANDLED;
}


static int __init dht11_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	for (i = 0; i < count; i++)
	{		
		gpios[i].irq  = gpio_to_irq(gpios[i].gpio);

		/* 设置DHT11 GPIO引脚的初始状态: output 1 */
		err = gpio_request(gpios[i].gpio, gpios[i].name);
		gpio_direction_output(gpios[i].gpio, 1);
		gpio_free(gpios[i].gpio);

		//setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);   //5.0内核版本以下
	 	timer_setup(&gpios[i].key_timer, key_timer_expire, 0);

		//gpios[i].key_timer.expires = ~0;
		//add_timer(&gpios[i].key_timer);
		//err = request_irq(gpios[i].irq, dht11_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "name_gpio_key", &gpios[i]);
	}

	major = register_chrdev(0, "phytium_Openamp_dht11", &dht11_drv);  
	gpio_class = class_create(THIS_MODULE, "phytium_Openamp_dht11_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "phytium_Openamp_dht11");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "mydht11"); /* /dev/mydht11 */
	
	return err;
}


static void __exit dht11_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "phytium_Openamp_dht11");

	for (i = 0; i < count; i++)
	{
		//free_irq(gpios[i].irq, &gpios[i]);
		//del_timer(&gpios[i].key_timer);
	}
}


module_init(dht11_init);
module_exit(dht11_exit);

MODULE_LICENSE("GPL");

其中涉及到比较多的驱动开发知识和传感器的工作时序,包括MODULE_前缀的宏、module_前缀的函数、__init、__exit、以及file_operations结构体、Linux的内核定时器及中断内容,注册设备,系统调用接口机制的设计,内容多且杂。将在未来沉淀些自己的理解并做个文章专题来进行详细介绍,感兴趣的读者可以自行百度了解学习哈。

完结撒花!!!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值