嵌入式字符设备驱动程序
字符设备驱动程序是将硬件设备封装的方法,对操作系统和应用程序隐藏硬件细节,仅向上提供API,即应用程序接口,read/oped/write/close等API函数接口。
嵌入式分为应用层,操作系统Linux层,驱动层,硬件层。以应用程序打开一个文件为例:
应用层:fd = open("/dev/myled", O_RDWR);
linux操作系统层会有一个sys_open()与之对应
驱动层struct file_operations结构体中定义.open = drv_myled_open()与sys_open()对应
驱动层执行对硬件的打开,读写等。
驱动程序编写的流程:
1.确定主设备号,一般设置major = 0;让内核进行分配。
2.定义自己的file_operations结构体
3.实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体。
4.把file_operations结构体告诉内核:register_chrdev()
5.注册驱动程序,在入口函数中注册:安装驱动程序时,会调用这个函数
6.出口函数:卸载驱动函数时调用这个函数,unregister_chrdev();
7.创建设备信息,比如设备节点:class_create(),device_create(),便于对设备进行操作
1. Hello驱动(不涉及硬件操作)
本节会写一个不涉及硬件操作的驱动程序,也是一般驱动的模板,后面会涉及对具体引脚的操作,及驱动程序的优化:分离,设备树等
//一些头文件,可以参考内核中其他的字符设备
#include <linux/module.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>
//1.确定设备号
static int major = 0;//static表示major变量仅本程序可见
static char kernel_buf[1024] = {0};//创建一个内核字符变量,用来存取从用户发送来的字符数据
static struct class *hello_class;//创建一个class结构体,用于创建设备节点的
//对要实现的函数进行声明
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset);
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset);
static int hello_drv_open (struct inode *node, struct file *file);
static int hello_drv_close (struct inode *node, struct file *file);
//2.定义自己的file_operations结构体
static struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.read = hello_drv_read,
.write = hello_drv_write,
.release = hello_drv_close,
};
//3.实现定义的hello_drv_open...函数,参考其他驱动程序
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset){
//读函数,从内核中读取数据
int ret;
printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
ret = copy_to_user(buf, kernel_buf, size);//内核与用户进行交互的函数
return 0;
}
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){
//写函数,从用户写入到内核
int ret;
printk("%s %s line %d\n", _FILE__, __FUNCTION__, __LINE__);
ret = copy_from_user(kernel_buf, buf, size);//从用户拷贝到内核,上面从内核拷贝到用户
return 0;
}
static int hello_drv_open(struct inode *node, struct file *file){
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int hello_drv_close(struct inode* node, struct file* file){
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
//4.把file_operations结构体告诉内核:注册驱动程序
//5.在入口函数中注册驱动程序
static int __init hello_init(void){
int ret;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "hello", &hello_drv); //注册file_operations,设备名
hello_class = class_create(THIS_MODULE, "hello_class");
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello_drv");/*/dev/hello设备节点*/
return 0;
}
//6.出口函数,卸载驱动程序
static void __exit hello_exit(void){
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destory(hello_class, MKDEV(major, 0);
class_destroy(hello_class);
unregister_chrdev(major, "hello");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
上面就是标准的字符设备驱动程序的代码。
在Ubuntu中用交叉编译工具编译成hello_drv.ko文件,就可以在开发板上运行.
insmod hello_drv.ko:加载安装驱动程序
ls /dev/:查看设备节点,看是否有我们安装的hello设备节点
cat /proc/devices:查看所有的设备文件,包括主设备号和设备名
lsmod:查看已经加载的设备驱动
rmmod hello_drv:卸载驱动
2.hello驱动的测试程序
3.涉及硬件操作的设备驱动程序
先把坑挖好,后面有时间过来填坑。。。。
了解嵌入式开发的更多内容,关注微信公众号:河边小乌龟爬