1.新接口与老接口
1)老接口:register_chrdev
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
2)新接口:register_chrdev_region(注册设备号)/alloc_chrdev_region(让内核分配设备号) + cdev(注册驱动)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
}
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
3)为什么需要新接口
2.cdev介绍
结构体
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
3相关函数:cdev_alloc、cdev_init、cdev_add、cdev_del
4.设备号
1)主设备号和次设备号
2)dev_t类型
3)MKDEV、MAJOR、MINOR三个宏
5.编程实践
使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册
实践编程
测试
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>
#include <linux/cdev.h>
#define MYMAJOR 200
#define MYCOUNT 1
#define MYNAME "led_driver"
#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
sttic struct cdev led_driver_cdev;
char kbuf[100];
int mymajor;
static dev_t mydev_id;
void led_on(void)
{
// led初始化,也就是把GPJ0CON中设置为输出模式
rGPJ0CON = 0x11111111;
// led亮
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}
void led_off(void)
{
// led初始化,也就是把GPJ0CON中设置为输出模式
rGPJ0CON = 0x11111111;
// led亮
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
static int led_open(struct inode *ip, struct file *fp)
{
printk(KERN_INFO "led_device_open\n");
printk(KERN_INFO "operaton:led_on/led_off\n");
return 0;
}
ssize_t led_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
int ret = -1;
printk(KERN_INFO "led_device_led_read success!\n");
//读取内核中kbuf的数据
ret = copy_to_user(buf, kbuf, size);
if(ret != 0)
{
printk(KERN_ERR "copy_to_user fail!\n");
}
printk(KERN_INFO "copy_to_user success!\n");
return 0;
}
static ssize_t led_write( struct file * file, const char __user * buf,
size_t count, loff_t *ppos )
{
int ret = -1;
printk(KERN_INFO "led_device_led_write success!\n");
//使用copy_from_user函数将应用层传过来的buf内容拷贝到驱动空间的一个buf中
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, buf, count);
if(ret != 0)
{
printk(KERN_ERR "copy_from_user fail!\n");
}
printk(KERN_INFO "copy_from_user success!\n");
//下面就是对数据的操作,根据数据去操作硬件,下面就是操作硬件代码
if(kbuf[0] == '1')
{
led_on();
}
else if(kbuf[0] == '0')
{
led_off();
}
else
{
return 0;
}
return 0;
}
static int led_release(struct inode *ip, struct file *fp)
{
printk(KERN_INFO "led_device_release\n");
return 0;
}
//定义一个file_operations结构体变量
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
.open = led_open, //将来应用open打开这个设备时实际调用的函数
.release = led_release,
};
// 模块安装函数
static int __init chrdev_init(void)
{
int ret = -1;
printk(KERN_INFO "chrdev_init led_driver init\n");
//使用新的cdev接口来注册字符设备驱动
//第一步:注册主次设备号
mydev_id = MKDEV(MYMAJOR, 0);
ret = register_chrdev_region(mydev_id, MYCOUNT, MYNAME);
if (ret) {
printk(KERN_ERR "register_chrdev_region led_driver fail!\n");
return -1;
}
printk(KERN_INFO "register_chrdev_region led_driver success!mydev_id:%d\n", MYMAJOR);
//第二步:注册设备驱动
cdev_init(&led_driver_cdev, &led_fops);
ret = cdev_add(&led_driver_cdev, mydev_id, MYCOUNT);
if (ret) {
printk(KERN_ERR "cdev_add led_driver fail!\n");
return -1;
}
printk(KERN_INFO "cdev_add led_driver success!\n");
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "led_driver unregister ready!\n");
//使用新的cdev接口来注销字符设备驱动
cdev_del(&led_driver_cdev);
unregister_chrdev_region(mydev_id, MYCOUNT);
printk(KERN_INFO "led_driver unregister success!\n");
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
6.使用alloc_chrdev_region自动分配设备号
1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
//自动分配设备号,并注册
ret= alloc_chrdev_region(&mydev_id, 0, MYCOUNT, MYNAME);
if(ret)
{
printk(KERN_ERR "register_chrdev_region led_driver fail!\n");
return -1;
}
printk(KERN_INFO "alloc_chrdev_region success!");
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev_id),MINOR(mydev_id));
7.得到分配的主设备号和次设备号
1)使用MAJOR宏和MINOR宏从dev_t得到major和minor
printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(mydev_id),MINOR(mydev_id));
2)反过来使用MKDEV宏从major和minor得到dev_t。
3)使用这些宏的代码具有可移植性
8.中途出错的倒影式错误处理方法
内核中很多函数中包含了很多个操作,这些操作每一步都有可能出错,而且出错后后面的步骤就没有进行下去的必要性了。
9.使用cdev_alloc
1)cdev_alloc的编程实践
//第二步:注册设备驱动
//cdev_init(&led_driver_cdev, &led_fops);
pcdev = cdev_alloc();//给pcdev分配内存,指针实例化
cdev_init(pcdev, &led_fops);
ret = cdev_add(pcdev, mydev_id, MYCOUNT);
if (ret) {
printk(KERN_ERR "cdev_add led_driver fail!\n");
return -1;
}
printk(KERN_INFO "cdev_add led_driver success!\n");
return 0;
}
2)从内存角度体会cdev_alloc用与不用的差别,将变量分配空间至堆内存中,按需分配。
不使用的话,分配在数据段,不方便。
3)这就是非面向对象的语言和面向对象的代码
4)再次感叹C语言的博大精深,好好去看《4.C语言高级专题》
10.cdev_init的替代
1)cdev_init源码分析
2)不使用cdev_init时的编程
3)为什么讲这个
//第二步:注册设备驱动
//cdev_init(&led_driver_cdev, &led_fops);
pcdev = cdev_alloc();//给pcdev分配内存,指针实例化
pcdev->owner = THIS_MODULE;
pcdev->ops = &led_fops
//cdev_init(pcdev, &led_fops);
ret = cdev_add(pcdev, mydev_id, MYCOUNT);
if (ret) {
printk(KERN_ERR "cdev_add led_driver fail!\n");
return -1;
}
printk(KERN_INFO "cdev_add led_driver success!\n");
return 0;
}
11.老接口分析
register_chrdev
__register_chrdev
__register_chrdev_region
cdev_alloc
cdev_add
12.新接口分析
register_chrdev_region
__register_chrdev_region
alloc_chrdev_region
__register_chrdev_region
13.自动创建字符设备驱动的设备文件
13.1问题描述:
1)整体流程回顾
2)使用mknod创建设备文件的缺点
3)能否自动生成和删除设备文件
13.2解决方案:udev(嵌入式中用的是mdev)
1)什么是udev?应用层的一个应用程序
2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
3)应用层启用udev,内核驱动中使用相应接口
4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除
13.3内核驱动设备类相关函数
1)class_create
2)device_create
3)device_destroy
4) class _destroy
//第二步:注册设备驱动
//cdev_init(&led_driver_cdev, &led_fops);
pcdev = cdev_alloc();//给pcdev分配内存,指针实例化
pcdev->owner = THIS_MODULE;
pcdev->ops = &led_fops;
//cdev_init(pcdev, &led_fops);
ret = cdev_add(pcdev, mydev_id, MYCOUNT);
if (ret) {
printk(KERN_ERR "cdev_add led_driver fail!\n");
return -1;
}
printk(KERN_INFO "cdev_add led_driver success!\n");
//注册设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
//给udev,让udev自动创建和删除设备文件
led_class = class_create(THIS_MODULE, "tom_class");
if (IS_ERR(led_class)) {
printk(KERN_ERR "class_create() failed for led_class\n");
return -1;;
}
device_create(led_class, NULL, mydev_id, NULL, "test");
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "led_driver unregister ready!\n");
//使用新的cdev接口来注销字符设备驱动
cdev_del(pcdev);
unregister_chrdev_region(mydev_id, MYCOUNT);
device_destroy(led_class, mydev_id);
class_destroy(led_class);
printk(KERN_INFO "led_driver unregister success!\n");
}
14.sys文件系统简介
1)sys文件系统的设计思想
2)设备类的概念ls
3)/sys/class/xxx/中的文件的作用
15.class_create
__class_create
__class_register
kset_register
kobject_uevent
16.device_create
device_create_vargs
kobject_set_name_vargs
device_register
device_add
kobject_add
device_create_file
device_create_sys_dev_entry
devtmpfs_create_node
device_add_class_symlinks
device_add_attrs
device_pm_add
kobject_uevent
17.静态映射表建立过程分析
17.1建立映射表的三个关键部分
1)映射表描述:映射表具体物理地址和虚拟地址的值相关的宏定义
2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io
s5p_init_io
iotable_init
结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
3)开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
start_kernel
setup_arch
paging_init
devicemaps_init
if (mdesc->map_io)
mdesc->map_io();
18动态映射结构体方式操作寄存器
18.1问题描述
仿效真实驱动中,用结构体封装的方式来进行单次多寄存器的地址映射。来代替我们5.2.17节中讲的多次映射。
18.2实践编码
typedef struct GPJ0REG
{
volatile unsigned int gpj0con;
volatile unsigned int gpj0dat;
}jpj0_reg_t;
#define GPJ0_REGBASE 0xe0200240
jpj0_reg_t *pGPJ0REG;
//动态虚拟地址映射
// 2步完成了映射
if (!request_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t), "GPJ0REG"))
return -EINVAL;
pGPJ0REG = ioremap(GPJ0_REGBASE, sizeof(gpj0_reg_t));
// 映射之后用指向结构体的指针来进行操作
// 指针使用->结构体内元素的方式来操作各个寄存器
pGPJ0REG->gpj0con = 0x11111111;
pGPJ0REG->gpj0dat = ((0<<3) | (0<<4) | (0<<5)); // 亮
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "led_driver unregister ready!\n");
//使用新的cdev接口来注销字符设备驱动
cdev_del(pcdev);
unregister_chrdev_region(mydev_id, MYCOUNT);
iounmap(pGPJ0REG);
release_mem_region(GPJ0_REGBASE, sizeof(gpj0_reg_t));
device_destroy(led_class, mydev_id);
class_destroy(led_class);
printk(KERN_INFO "led_driver unregister success!\n");
}
18.3 分析和总结
19内核提供的读写寄存器接口
19.1前面访问寄存器的方式
1)行不行
2)好不好
19.2内核提供的寄存器读写接口
1)writel和readl
2)iowrite32和ioread32
19.3代码实践
*/
// 测试1:用2次ioremap得到的动态映射虚拟地址来操作,测试成功
// writel(0x11111111, pGPJ0CON);
// writel(((0<<3) | (0<<4) | (0<<5)), pGPJ0DAT);
// 测试2:用静态映射的虚拟地址来操作,测试成功
// writel(0x11111111, GPJ0CON);
// writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);
// 测试3:用1次ioremap映射多个寄存器得到虚拟地址,测试成功
if (!request_mem_region(GPJ0CON_PA, 8, "GPJ0BASE"))
return -EINVAL;
baseaddr = ioremap(GPJ0CON_PA, 8);
writel(0x11111111, baseaddr + S5P_GPJ0CON);
writel(((0<<3) | (0<<4) | (0<<5)), baseaddr + S5P_GPJ0DAT);
//注册设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
//给udev,让udev自动创建和删除设备文件
led_class = class_create(THIS_MODULE, "tom_class");
if (IS_ERR(led_class)) {
printk(KERN_ERR "class_create() failed for led_class\n");
return -1;;
}
device_create(led_class, NULL, mydev_id, NULL, "test");
return 0;
}
writel(((1<<3) | (1<<4) | (1<<5)), baseaddr + S5P_GPJ0DAT);
iounmap(baseaddr);
release_mem_region(baseaddr, 8);