一、linux设备驱动程序概述
1、驱动模型
2、驱动分类
3、驱动程序学习方法
1、驱动模型1
驱动模型2
模型1:没有实现驱动程序的复用,对每一个使用此串口的应用程序都要重新编写驱动程序。这样造成了很大浪费。
模型2:很好的解决了模型1中,驱动程序没有复用的问题,实现了驱动的复用,可是驱动的编写没有统一的规范。
2、设备驱动的分类:字符设备、块设备、网络接口、其他分类法、设备访问。
2.1、字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open,close,read和write系统调用。例如:串口,LED,按键。
2.2、块设备
在大部分的Unix系统中,块设备定义为:以块(通常为512字节)为最小传输单位的设备,块设备不能按字节处理数据。
而linux则允许块设备传送任意数目的字节(最少访问字节没有限制)。因此,块和字符设备的区别仅仅是驱动与内核的接口不同。常用的块设备包括硬盘,flash,SD卡......
2.3、网络接口
网络接口可以是一个硬件设备,如网卡;但也可以是一个纯粹的软件设备,比如回环接口(lo)。一个网络接口负责发送和接收数据报文。
2.4、其他分类
总线方式划分:
USB设备,PCI设备,平台总线设备(平台总线是linux系统的一重要总线,platform_bus为虚拟的总线,目的是为了提高驱动程序的可移植性)。
2.5、设备使用
3、驱动到底怎么去学:1、硬件工作原理 2、linux驱动模型的学习
驱动的学习方法:相关理论->分析范例程序->制作思维导图->自己编写代码
驱动的学习初期,最好不要深入的读内核代码。
二、使用字符设备驱动
1、编译/安装驱动
在linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。
2、创建设备文件
通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备。创建字符设备文件的方法一般有两种
1、mknod /dev/文件名 c(代表一个字符设备) 主设备号 次设备号
可以通过cat /proc/devices中看到安装的驱动程序的主设备号,创建的设备文件要和这个号码一致,因为设备文件是通过主设备号和设备驱动程序建立一一对应关系。而次设备号可以随意取一非负整数。次设备号是代表同一种设备区分多种接口。
2、使用函数在驱动程序中实现。
3、访问设备
整个程序代码如下:
注:memdev是使用数组模拟了驱动程序
memdev.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
int dev1_registers[5];
int dev2_registers[5];
struct cdev cdev;
dev_t devno;
/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
/*获取次设备号*/
int num = MINOR(inode->i_rdev);
if (num==0)
filp->private_data = dev1_registers;
else if(num == 1)
filp->private_data = dev2_registers;
else
return -ENODEV; //无效的次设备号
return 0;
}
/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/
/*判断读位置是否有效*/
if (p >= 5*sizeof(int))
return 0;
if (count > 5*sizeof(int) - p)
count = 5*sizeof(int) - p;
/*读数据到用户空间*/
if (copy_to_user(buf, register_addr+p, count))
{
ret = -EFAULT;
}
else
{
*ppos += count;
ret = count;
}
return ret;
}
/*写函数*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
/*分析和获取有效的写长度*/
if (p >= 5*sizeof(int))
return 0;
if (count > 5*sizeof(int) - p)
count = 5*sizeof(int) - p;
/*从用户空间写入数据*/
if (copy_from_user(register_addr + p, buf, count))
ret = -EFAULT;
else
{
*ppos += count;
ret = count;
}
return ret;
}
/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch(whence) {
case SEEK_SET:
newpos = offset;
break;
case SEEK_CUR:
newpos = filp->f_pos + offset;
break;
case SEEK_END:
newpos = 5*sizeof(int)-1 + offset;
break;
default:
return -EINVAL;
}
if ((newpos<0) || (newpos>5*sizeof(int)))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*文件操作结构体*/
static const struct file_operations mem_fops =
{
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
/*设备驱动模块加载函数*/
static int memdev_init(void)
{
/*初始化cdev结构*/
cdev_init(&cdev, &mem_fops);
/* 注册字符设备 */
alloc_chrdev_region(&devno, 0, 2, "memdev");
cdev_add(&cdev, devno, 2);
}
/*模块卸载函数*/
static void memdev_exit(void)
{
cdev_del(&cdev); /*注销设备*/
unregister_chrdev_region(devno, 2); /*释放设备号*/
}
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
Makefile
obj-m :=memdev.o
KDIR :=/home/kernal_driver/linux-tiny6410/
all:
make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
write_mem.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
int src = 2013;
fd = open("/dev/memdev0", O_RDWR);
write(fd, &src, sizeof(int));
return 0;
}
read_mem.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
int dst = 0;
fd = open("/dev/memdev0", O_RDWR);
read(fd, &dst, sizeof(int));
printf("dst is %d\n", dst);
return 0;
}
注解:read_mem和write_mem使用静态交叉编译的方式,因为开发板中没有动态链接库,除非你将动态链接库复制到开发板中。
memdev.c的编译使用的是Makefile文件组织编译、链接成内核模块,然后就是使用insmod安装模块了。