前言
前面所讲的字符设备驱动基本是从概念与流程上进行分析的,是用来学习与理解的,在实际应用中我们可不会这么一步一步去实现,在实际中我们往往会使用那些开发快、代码又简洁的途径,linux下都已经为我们准备好了;这就比如你家离镇上有10公里,你要去镇上赶集,可以走路去,可以骑车去,也可以坐车去,完全取决于你自己,不管用什么方式你都能到达,但是在实际中我想应该更多的是坐车去,因为速度快、轻松、少吃马路上的很多灰,但是有些杠精说:我就是要走路去,走路不仅可以锻炼身体又低碳环保,当然,这也是可以的,但是如果你有急事赶时间,尤其在我们现在这浮躁的社会,一个项目下来往往会催着你尽快搞出来,这样你不得不走速度快效率高的途径,linux下驱动开发就是这样的,一个驱动它有很多方式可以实现,但是你应该选择速度最快、效率最高的那一条,所以首先你就需要搞明白,哪一种才是最快最好的,好了,废话不多说,直接开干。
一、misc简介
Linux的驱动设计是趋向于分层的,大多数设备都有自己归属的类型,例如按键、触摸屏属于输入设备,Linux有一个input子系统框架。但是对于adc、蜂鸣器等设备,无法明确其属于什么类型,对于这种设备一般推荐使用misc驱动框架编写驱动程序,misc设备也是一个字符设备,在misc的初始化函数中注册了一个字符设备,主设备号为MISC_MAJOR (10),这个字符设备并不是具体的misc设备实例的实现,只是misc核心层用来将应用层的操作转发到具体的misc实例中!
二、驱动开发准备
实际开发中的驱动是怎样的,实际开发中编写驱动,我们需要编写或修改的就如下三个东西:
设备树
设备树的由来说明一下:大佬Linus在2011年3月17日的ARM Linux邮件列表中宣称“this wholeARM thing is a facking pain in the ass”,这引发了ARM Linux社区的地震,随后ARM社区进行了一系列重大修正。于是PowerPC等其他体系结构下已经使用的FDT(Flattened Device Tree)进入到了ARM社区的视野,自linux3.0之后就引入了设备树。
驱动编写
这个就是我们所讲的一个核心与重中之重,所谓驱动就是操作硬件寄存器与外设的一系列方法,我们需要实现这些方法,同时将这些方法提供给操作系统调用,其实我们只要按照操作系统的要求来进行编写填充,大部分工作linux已经替我们完成了
APP测试
我们写的驱动行不行、能不能工作,肯定要验证才行,将驱动安装好后要进行测试,于是测试app是需要的,我们需要通过应用层测试我们的驱动能否正常工作,功能是否符合要求等等
三、设备树节点
在设备树的根节点下增加如下节点,实际就是描述一个蜂鸣器设备,它的名称、IO口、驱动匹配名等,make dtbs后,开发板用新的设备树文件启动,在/proc/device-tree/下会生成beep节点,这个节点就是蜂鸣器的一些描述信息,在驱动中就可以对这个节点进行读取操作。
beep{
label = "beep";
gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
default-state = "off";
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-beep";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
三、misc驱动模板
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#define MISCBEEP_NAME "misc_beep" /* 名字 */
#define MISCBEEP_MINOR 121 /* 子设备号 */
#define BEEPOFF 0 /* 关蜂鸣器 */
#define BEEPON 1 /* 开蜂鸣器 */
#define CLOSE_CMD (_IO(0XEF, 0x1))/* 关闭命令 */
#define OPEN_CMD (_IO(0XEF, 0x2))/* 打开命令 */
/* misc_beep设备结构体 */
struct misc_beep_dev{
struct device_node *nd; /* 设备节点 */
int beep_gpio; /* beep所使用的GPIO编号 */
};
struct misc_beep_dev misc_beep; /* 定义一个beep设备 */
/* 打开设备 */
static int misc_beep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &misc_beep; /* 设置私有数据 */
return 0;
}
/* 从设备读取数据 */
static ssize_t misc_beep_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/* 向设备写数据 */
static ssize_t misc_beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char beepstat;
struct misc_beep_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0]; /* 获取状态值 */
if(beepstat == BEEPON)
gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
else if(beepstat == BEEPOFF)
gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
return 0;
}
/* 关闭/释放设备 */
static int misc_beep_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl函数 */
static long misc_beep_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct timer_dev *dev = (struct timer_dev *)filp->private_data;
int timerperiod;
unsigned long flags;
switch (cmd) {
case CLOSE_CMD:
gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
break;
case OPEN_CMD:
gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
break;
default:
break;
}
return 0;
}
/* 设备操作函数 */
static struct file_operations misc_beep_fops = {
.owner = THIS_MODULE,
.open = misc_beep_open,
.read = misc_beep_read,
.write = misc_beep_write,
.release = misc_beep_release,
.unlocked_ioctl = timer_unlocked_ioctl,
};
/* MISC设备结构体 */
static struct miscdevice beep_miscdev = {
.minor = MISCBEEP_MINOR,
.name = MISCBEEP_NAME,
.fops = &misc_beep_fops,
};
/* platform驱动的probe函数,驱动与设备匹配后此函数就会执行 */
static int misc_beep_probe(struct platform_device *dev)
{
int ret = 0;
/* 1、获取设备节点:beep */
misc_beep.nd = of_find_node_by_path("/beep");
if(misc_beep.nd == NULL) {
printk("beep node not find!\r\n");
return -EINVAL;
}
/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
misc_beep.beep_gpio = of_get_named_gpio(misc_beep.nd, "beep-gpio", 0);
if(misc_beep.beep_gpio < 0) {
printk("can't get beep-gpio");
return -EINVAL;
}
/* 3、设置GPIO5_IO01为输出,并且输出高电平,默认关闭BEEP */
ret = gpio_direction_output(misc_beep.beep_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/* 一般情况下会注册对应的字符设备,但是这里我们使用MISC设备
* 所以我们不需要自己注册字符设备驱动,只需要注册misc设备驱动即可 */
ret = misc_register(&beep_miscdev);//在sys/class/misc会自动生成.name命名的文件夹
if(ret < 0){
printk("misc device register failed!\r\n");
return -EFAULT;
}
return 0;
}
/* platform驱动的remove函数,移除platform驱动的时候此函数会执行 */
static int misc_beep_remove(struct platform_device *dev)
{
/* 注销设备的时候关闭LED灯 */
gpio_set_value(misc_beep.beep_gpio, 1);
/* 注销misc设备 */
misc_deregister(&beep_miscdev);
return 0;
}
/* 设备树匹配列表 */
static const struct of_device_id beep_of_match[] = {
{ .compatible = "atkalpha-beep" },
{}//这个空项是必须要的
};
/* platform驱动结构体 */
static struct platform_driver beep_driver = {
.driver = {
.name = "imx6ul-beep", /* 驱动名和设备名匹配 */
.of_match_table = beep_of_match, /* 与设备树的匹配表 */
},
.probe = misc_beep_probe,
.remove = misc_beep_remove,
};
/* 向paltform总线注册与注销 */
module_platform_driver(beep_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("mstar");
四、总结
这个模板的精髓在misc_register与module_platform_driver接口,misc_register接口实现了字符设备的设备号申请、初始化cdev、创建类与创建设备,大大节省了代码量,module_platform_driver接口实现了platform总线的注册与注销,以及模块的入口与出口,也减少了代码量,其过程都是一样的,只是linux将其通过这个接口封装起来,所以说代码量虽然少了,但是路程是一样的,我们对字符设备驱动编写的整个过程同样是必须要熟悉的!