4412开发板android入门篇_字符设备驱动框架
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
字符设备驱动程序框架介绍
本章部分内容参看了《linux设备驱动详解》
第一部分为以前学习linux设备驱动详解一书时记录的一些字符设备常用函数及结构体,第二部分为以字符设备为模板写的LED驱动程序。
linux驱动程序一般分为三类:字符设备,块设备,网络设备
字符设备:
字符设备以字节流的方式被访问,也即对字符设备的读写操作是以字节为单位的,字符设备的操作函数一般用到open,close,read,write等系统调用的函数。
常用的串口等设备的数据传输也是以字节为单位进行数据的交互。
块设备:
在块设备上数据以块的方式被存放,比如flash,SD卡等上的数据以页为单位进行读写。
对SD卡,硬盘等块设备的读写,应用程序可以使用open,open,
close,read,write等系统调用函数对块设备以任意字节进行读写。
网络设备:
网络设备同时具有字符设备,块设备的特点,数据的读写有一定的格式,以socket包的方式进行数据的传输。
编写设备驱动程序的一般步骤:
1.查看原理图硬件连接,查看控制设备数据手册,了解kernel中设备的操作函数集
2.在kernel中找到相似的设备驱动程序仿写。
一般情况芯片商会提供相应芯片的驱动程序模板
3.实现驱动程序的初始化,及设备注册到kernel,创建设备节点
4.实现设备控制的操作函数集,如,常用的系统调用函数open,close,read,write等。
5.将驱动程序编译进kernel
6.编写应用测试驱动程序。
一.字符设备的常用函数
1.驱动程序中设备的加载和卸载函数
module_init和module_exit,在写模块的时候这两个函数分别在insmod的时候和rmmod的时候调用。
调用module_init函数用来向kernel中注册驱动程序,调用module_exit下载驱动程序。
二..字符设备常用函数和结构体
1.描述字符设备的结构体cdev
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
cdev结构体的dev_t 成员定义了设备号,为32 位,其中高12 位为主设备号,低 20 位为次设备号。
使用下列宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_t dev)
MINOR(dev_t dev)
而使用下列宏则可以通过主设备号和设备号生成dev_t。
MKDEV(int major, int minor)
2.操作cdev结构体用到的函数
linux/fs/char_dev.c
此函数用于初始化cdev结构体的成员
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
此函数为cdev结构体申请一块内存
struct cdev *cdev_alloc(void)
此函数向内核注册cdev
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
此函数从内核删除cdev
void cdev_del(struct cdev *p)
3.设备号的申请与释放函数
为一个字符驱动一个或多个设备编号来使用
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数:from:是你要分配的起始设备编号,常常取值为0
count:是你请求的连续设备编号的总数
是应当连接到这个编号范围的设备的名字; 它会出现在/proc/devices 和sysfs 中.
返回值:成功返回0,出错返回负数
用于设备号未知,向系统动态申请未被占用的设备号的情况
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
作用:该函数需要传递给它指定的第一个次设备号baseminor(一般为0)和要分配的设备数count,以及设备名,调用该函数后自动分配得到的设备号保存在dev中。
baseminor: 通常为0;
*dev:存放返回的设备号;
count:连续编号范围.
这个意思说假如major是248,count是2的话,249也就是相当于被使用的了
成功返回0,失败返回-1;
释放申请的设备号
void unregister_chrdev_region(dev_t from, unsigned count);
4.file_operations结构体
struct file_operations {
struct module *owner; // 拥有该结构的模块的指针,一般为THIS_MODULES
loff_t (*llseek) (struct file *, loff_t, int); // 用来修改文件当前的读写位置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); // 从设备中同步读取数据
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); // 向设备发送数据
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); // 初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); // 初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t); // 仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); // 轮询函数,判断目前是否可以进行非阻塞的读取或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); // 执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // 不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); // 在64位系统上,32位的ioctl 调用将使用此函数指针代替
int (*mmap) (struct file *, struct vm_area_struct *); // 用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync); // 刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int); // 异步fsync
int (*lock) (struct file *, int, struct file_lock *); // 通知设备FASYNC标志发生变化
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
write()函数向设备发送数据,成功时该函数返回写入的字节数。
如果此函数未被实现,
当用户进行write()系统调用时,将得到-EINVAL返回值。
readdir()函数仅用于目录,设备节点不需要实现它。
ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,
返回给调用程序一个非负值。
内核本身识别部分控制命令,而不必调用设备驱动中的
ioctl()。
如果设备不提供ioctl()函数,对于内核不能识别的命令,用户进行ioctl()系统调
用时将获得-EINVAL返回值。
mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进
行mmap()系统调用时将获得-ENODEV返回值。
这个函数对于帧缓冲等设备特别有意
义。
poll()函数一般用于询问设备是否可被非阻塞地立即读写。
当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞
aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。
设备实现这两个函数后,
用户空间可以对该设备文件描述符调用aio_read()、aio_write() 等系统调用进行读写
5.copy_from_user()和copy_to_user()的原型如下所示
由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_from_user() 完成用户空间到内核空间的复制,
函数copy_to_user()完成内核空间到用户空间的复制。
unsigned long copy_from_user(void *to, const void __user *from, unsigned long count); unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);
上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。
如果要复制的内存是简单类型,如char、int、long等,则可以使用简单的put_user()
和get_user(),如下所示:
int val; //内核空间整型变量
get_user(val, (int *) arg); //用户空间到内核空间,arg是用户空间的地址
put_user(val, (int *) arg); //内核空间到用户空间,arg是用户空间的地址
}
第二部分:
第一部分笔记讲解了字符设备驱动的一些常用函数,下面将基于UT4412BV03开发板编写一个控制LED的驱动程序。
对于字符设备来说,驱动程序的编写本质上就是实现file_operations结构体的常用函数。
下面将说明以字符设备为模板LED驱动程序设计。
/*************************************
** ut_4412 :led驱动程序设计
** LED:接口引脚
** EINT5--------GPX0_5-----D3
** EINT7--------GPX0_7------D4
** EINT20-------GPX2_4-----D6
** EINT21-------GPX2_5-----D7
*************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/smsc911x.h>
#include <asm/mach/arch.h>
#include <asm/mach-types.h>
#include <plat/exynos4.h>
#include <plat/cpu.h>
#include <plat/clock.h>
#include <plat/devs.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
#include <linux/timer.h>
#include <mach/hardware.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#define LED_MAJOR 97 //设备号
static struct class *led_class;
#define IOCTL_GPIO_ON 1
#define IOCTL_GPIO_OFF 0
/* 用来指定LED所用的GPIO引脚*/
static unsigned long led_table [] ={
EXYNOS4_GPX0(5), //D3
EXYNOS4_GPX0(7), //D4
EXYNOS4_GPX2(4), //D6
EXYNOS4_GPX2(5), //D7
};
static int ut_4412_leds_open(struct inode *inode,struct file *file)
{
printk("led device open success!\n");
return 0;
}
ssize_t ut_4412_leds_write(struct file *filep, const void __user *buf, size_t n,
loff_t *ppos)
{
unsigned int cmd[1],ret;
ret=copy_from_user(&cmd, buf, 1); //用户空间向内核空间传值。
用于控制LED的亮灭printk("cmd=%d\n",cmd[0]);//控制带答应用户空间传到内核空间的值
printk("write cmd to kernel success\n");
switch(cmd[0])
{
case 48: //控制LED灭
{
gpio_set_value(EXYNOS4_GPX0(5),0);
gpio_set_value(EXYNOS4_GPX0(7),0);
gpio_set_value(EXYNOS4_GPX2(4),0);
gpio_set_value(EXYNOS4_GPX2(5),0);
}
break;
case 49: //控制LED亮
{
gpio_set_value(EXYNOS4_GPX0(5),1);
gpio_set_value(EXYNOS4_GPX0(7),1);
gpio_set_value(EXYNOS4_GPX2(4),1);
gpio_set_value(EXYNOS4_GPX2(5),1);
}
break;
default:
return -EINV AL;
}
return ret;
}
long ut_4412_leds_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
if(arg>4)
return -EINV AL;
switch(cmd)
{
case IOCTL_GPIO_OFF:
gpio_set_value(led_table[arg],0);break;
case IOCTL_GPIO_ON:
gpio_set_value(led_table[arg],1);break;
default:
return -EINV AL;
}
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = ut_4412_leds_open,
.unlocked_ioctl = ut_4412_leds_ioctl,
// .read = ut_4412_leds_read,
.write = ut_4412_leds_write,
};
static int __init ut_4412_leds_init(void)
{
int ret;
printk("===led driver init===\n");
s3c_gpio_cfgpin(EXYNOS4_GPX0(5), S3C_GPIO_OUTPUT);
s3c_gpio_cfgpin(EXYNOS4_GPX0(7), S3C_GPIO_OUTPUT);
s3c_gpio_cfgpin(EXYNOS4_GPX2(4), S3C_GPIO_OUTPUT);
s3c_gpio_cfgpin(EXYNOS4_GPX2(5), S3C_GPIO_OUTPUT);
ret=register_chrdev(LED_MAJOR, "myled", &dev_fops);
if (ret < 0)
{
printk(KERN_ERR "VIB: unable to get major %d\n", ret);
return ret;
}
led_class = class_create(THIS_MODULE, "myled");
if (IS_ERR(led_class))
{
unregister_chrdev(LED_MAJOR, "myled");
return PTR_ERR(led_class);
}
device_create(led_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "ledhal");
// create a point under /dev/class/vib
gpio_set_value(EXYNOS4_GPD0(0),1);
return ret;
}
static void __exit ut_4412_leds_exit(void)
{
printk("===led driver exi===t\n");
device_destroy(led_class, MKDEV(LED_MAJOR, 0));
class_destroy(led_class);
unregister_chrdev(LED_MAJOR, "myled");
}
module_init(ut_4412_leds_init);
module_exit(ut_4412_leds_exit);
// MODULE_ALLAS_CHARDEV(LED_MAJOR,0);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("urbetter");
MODULE_DESCRIPTION("ut_4412");
将以上LED驱动程序加入内核:
打开kernel/driver/char目录下的Makefile,在其中加入LED驱动文件,
打开kernel/driver/char目录下的Kconfig,在其中加入如下内容
UT4412BV03开发板入门篇
之后编译出kernel镜像文件,运用fastboot或者SD卡升级方式升级镜像。
下面将一命令的方式来测试LED驱动程序
在控制台下利用su root转到超级用户
之后转到dev目录,找到LED设备的设备节点
运用ls看到LED设备的设备节点为ledhal,
则说明LED驱动程序创建成功
可以用如下命令控制LED的亮灭:
在控制台输入如下命令控制LED打开
在控制台输入如下命令控制LED关闭。