linux驱动开发5之字符设备驱动新接口

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_alloccdev_initcdev_addcdev_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);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值