ARM Linux驱动开发学习

一、Linux三大驱动类型:

  1. 字符设备驱动:字符设备驱动最多,从最简单的点灯到 I2C、 SPI、音频等都属于字符设备驱动的类型。
  2. 块设备驱动:块设备驱动就是存储器设备的驱动,比如 EMMC、 NAND、 SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。
  3. 网络设备驱动:不管是有线还是无线,都属于网络设备驱动。

一个设备可以属于多种设备驱动类型,比如 USB WIFI,其使用 USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。

二、字符设备驱动开发

1.字符驱动设备介绍

字符设备是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IC、SPI,LCD等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
Linux应用程序对驱动程序的调用流程
在Linux中一切皆为文件,驱动加载成功以后会在"/dev"目录下生成一个相应的文件,应用程序通过对这个名为"/dev/xxx"(xxx是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

比如现在有个叫做/dev/led的驱动文件,此文件是led灯的驱动文件。应用程序使用open函数来打开文件/devled,使用完成以后使用close函数关闭/dev/led这个文件。open和close就是打开和关闭led驱动的函数,如果要点亮或关闭led,那么就使用write函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开led的控制参数。如果要获取led灯的状态,就用read函数从驱动中读取相应的状态。

应用程序运行在用户空间,驱动运行于内核空间。
当我们在用户空间想要实现对内核的操作,比如使用open函数打开/dev/led这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open.close,write和read等这些函数是由C库提供的,在Linux系统中,系统调用作为C库的一部分。
在这里插入图片描述

2.字符驱动开发步骤
2.1 Linux驱动有两种运行方式:

第一种就是将驱动编译进Linux内核中,这样当Linux内核启动的时候就会自动运行驱动程序。
第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用"insmod"命令加载驱动模块。
在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个Linux代码。
而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。

2.2 模块有加载和卸载两种操作
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

“insmod”命令加载驱动的时候, xxx_init 这个函数就会被调用。
“rmmod”命令卸载具体驱动的时候, xxx_exit 函数就会被调用。

2.3 字符设备驱动模块加载和卸载模板
/* 驱动入口函数 */
static int __init xxx_init(void)
{
	/* 入口函数具体内容 */
	return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	/* 出口函数具体内容 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

驱动编译完成以后扩展名为.ko,
驱动模块的加载使用命令“modprobe ”,比如要加载 drv.ko,命令如下:
modprobe drv.ko”
驱动模块的卸载使用命令“rmmod”即可,比如要卸载 drv.ko,命令如下:
rmmod drv.ko
也可以使用“modprobe -r”命令卸载驱动,比如要卸载 drv.ko,命令如下:
modprobe -r drv.ko

3.字符驱动注册于注销
/*
功能:注册字符设备
参数:
major:主设备号
name:设备名字,指向字符串
fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。
*/
static inline int register_chrdev(unsigned int major, const char *name,
									const struct file_operations *fops)
/*
功能:注销字符设备
参数:
major:主设备号
name:设备名字,指向字符串
*/
static inline void unregister_chrdev(unsigned int major, const char *name)

在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合。

struct file_operations {
......
}
  1. owner拥有该结构体的模块的指针,一般设置为THISMODULE.
  2. Ilsek函数用于修改文件当前的读写位置。
  3. read函数用于读取设备文件。
  4. write函数用于向设备文件写入(发送)数据。
  5. poll是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
  6. unlocked ioctl函数提供对于设备的控制功能,与应用程序中的ioctl函数对应。
  7. compat ioctl函数与unlocked ioctl函数功能一样,区别在于在64位系统上,32位的应用程序调用将会使用此函数。在32位的系统上运行32位的应用程序调用的是unlocked ioctl
  8. mmap函数用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如LCD驱动的显存,将帧缓冲(LCD显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
  9. open函数用于打开设备文件
  10. release函数用于释放(关闭)设备文件,与应用程序中的close函数对应。
  11. fasync函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
  12. aio_fsync函数与fasync函数的功能类似,只是aio-fsync是异步刷新待处理的数据。
4.字符驱动框架实现模板
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>


#define CHRDEVBASE_MAJOR	200				/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */

static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	
	/* 向用户空间发送数据 */
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	retvalue = copy_to_user(buf, readbuf, cnt);
	if(retvalue == 0){
		printk("kernel senddata ok!\r\n");
	}else{
		printk("kernel senddata failed!\r\n");
	}
	
	//printk("chrdevbase read!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	/* 接收用户空间传递给内核的数据并且打印出来 */
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0){
		printk("kernel recevdata:%s\r\n", writebuf);
	}else{
		printk("kernel recevdata failed!\r\n");
	}
	
	//printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("muchunpeng");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值