字符设备驱动程序分析

字符设备驱动程序分析

下面是针对jz2440开发板写的一个led驱动程序,重点不在于该程序,而是以此为例,对字符设备驱动程序框架的分析总结;

/*
 * jz2440 leds driver
**/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static int major;
static struct class *leds_drv_class;
static struct class_device *leds_class_device[4];

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

#define DEVICE_NAME  "leds"  /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
/*
 * 在应用程序中使用open(),打开/dev/led*某个led设备文件时会调用该函数,
 * 具体的处理就是根据子设备号的不同用来区分不同的led设备,进而分别处理;
**/
static int jz2440_led_drv_open (struct inode *inode, struct file *file)
{
    int minor;

    /* 得到打开设备的子设备号,也可以使用minor = MINOR(inode->i_cdev) */
    minor= MINOR(inode->i_rdev); 

    switch(minor){
    case 0: /* 控制所有led */
        /* 设置GPF4(D10)、GPF5(D11)、GPF6(D12) 为output mode*/
        *gpfcon &= ~((3<<8) | (3<<10) | (3<<12));
        *gpfcon |=  ((1<<8) | (1<<10) | (1<<12));
        break;

    case 1: /* 控制/dev/led1(D10) */
        /* 设置GPF4为output mode*/
        *gpfcon &= ~(3<<8);
        *gpfcon |=  (1<<8);
        break;

    case 2: /* 控制/dev/led2(D11) */
        /* 设置GPF5为output mode*/
        *gpfcon &= ~(3<<10);
        *gpfcon |=  (1<<10);
        break;

    case 3: /* 控制/dev/led3(D12) */
        /* 设置GPF6为output mode*/
        *gpfcon &= ~(3<<12);
        *gpfcon |=  (1<<12);
        break;
    }

    return 0;
}

/*
 * 在应用程序中使用write(),对不同的led设备进行写操作时会调用该函数,
 * 具体的处理就是根据子设备号的不同用来区分不同的led设备,进而分别处理;
**/
static ssize_t jz2440_led_drv_write (struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    int minor;
    char val;

    minor = MINOR(file->f_dentry->d_inode->i_rdev); /* 得到子设备号 */
    copy_from_user(&val, buf, count); /* 从用户空间获取数据 */

    switch(minor){
    case 0:
        if(val == 1){
            *gpfdat &= ~((1<<6) | (1<<5) | (1<<4));
        }else{
            *gpfdat |=  ((1<<6) | (1<<5) | (1<<4));
        }
        break;

    case 1:
        if(val == 1){
            *gpfdat &= ~(1<<6);
        }else{
            *gpfdat |=  (1<<6);
        }
        break;

    case 2:
        if(val == 1){
            *gpfdat &= ~(1<<5);
        }else{
            *gpfdat |=  (1<<5);
        }
        break;

    case 3:
        if(val == 1){
            *gpfdat &= ~(1<<4);
        }else{
            *gpfdat |=  (1<<4);
        }
        break;
    }

    return 0;
}

static struct file_operations jz2440_leds_drv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   jz2440_led_drv_open,
    .write  =   jz2440_led_drv_write,
};

/*
 * 扩展说明:
 * 当应用程序使用open("/dev/leds")打开一个设备文件时,其中/dev/leds设备文件如何而来?
 * 有两种方式:
 *     1. 手动创建
 *        mknod /dev/leds c 主设备号 次设备号
 *     2. 自动创建
 *        mdev机制,会根据/sys目录下的设备信息,创建对应的设备节点
 * 所以重点就是驱动程序中如何来提供这些设备信息,具体的操作如下:
 * 1. 使用class_create创建一个类;
 * 2. 使用class_device_create来创建对应类的设备,里面包含了设备名、主次设备号等信息;
 * 总结:(结合以下程序)当insmod模块后,就会在/sys/class目录下新建一个名为ledsdrv的类,在类的下面会创
 *      建出led1、led2、led3、leds等四个设备信息,而mdev则会根据这些信息自动创建/dev/led1
 *      、/dev/led2、/dev/led3、/dev/leds等四个设备节点,而在rmmod模块后会自动这些设备信息和节点;
 * 问题来了,mdev为什么能够动态识别这些设备信息并创建设备节点?
 * 因为在构建根文件系统时,/etc/init.d/rcS这个脚本中,设置了如下:
 * echo /sbin/mdev > /proc/sys/kernel/hotplug
 * 意思就是当内核检测到有设备热插拔时,会调用/sbin/mdev程序;
 */
static int __init s3c2440_leds_init(void)
{
    int ret;
    int minor;

    /* 向内核注册file_operations结构变量 */
    ret = register_chrdev(0, DEVICE_NAME, &jz2440_leds_drv_fops);
    if(ret < 0){
        printk(DEVICE_NAME "can't register\n");
        return ret;
    }
    major = ret;

    leds_drv_class = class_create(THIS_MODULE, "ledsdrv");
    if (IS_ERR(leds_drv_class))
        return PTR_ERR(leds_drv_class);

    leds_class_device[0] = class_device_create(leds_drv_class, NULL, MKDEV(major, 0), NULL, "leds");

    for(minor=1; minor<4; minor++){
        leds_class_device[minor] = class_device_create(leds_drv_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);
        if (unlikely(IS_ERR(leds_class_device[minor])))
            return PTR_ERR(leds_class_device[minor]);
    }

    /* 地址映射 */
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
    gpfdat = (volatile unsigned long *)ioremap(0x56000054, 8);

    printk(DEVICE_NAME " initialization\n");

    return 0;
}

static void __exit s3c2440_leds_exit(void)
{
    int minor;
    unregister_chrdev(major, DEVICE_NAME);

    for(minor=0; minor<4; minor++){
        class_device_unregister(leds_class_device[minor]);
    }

    class_destroy(leds_drv_class);

    iounmap(gpfcon);
    iounmap(gpfdat);

    printk(DEVICE_NAME " exit\n");
}

module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jason.tian");
MODULE_VERSION("0.0.1");
MODULE_DESCRIPTION("S3C2440 LED Driver");

在上面的s3c2440_leds_init函数加上了__init修饰,具体作用说明如下:

在Llinux中,所有标识为**__init(两个下划线)的函数在连接的时候都放在.init.text这个区段内,此外,所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些__init函数,并在初始化完成后,释放init区段(包括.init.text、.initcall.init**等);

和**__init一样,__exit**也可以使对应函数在运行完成后自动回收内存;

原文地址:https://www.cnblogs.com/jasontian996/p/11419151.html

时间: 2024-10-13 07:33:30

字符设备驱动程序分析的相关文章

linux字符设备驱动程序源码分析

本文主要分析linux-2.6.28内核版本的字符设备抽象层源码文件char_dev.c.该文件代码量不大,但其为linux应用程序访问实际字符型硬件设备搭建了桥梁,进一步限定了linux字符设备驱动的设计框架. void __init chrdev_init(void) {  cdev_map = kobj_map_init(base_probe, &chrdevs_lock);  bdi_init(&directly_mappable_cdev_bdi); } 该函数在系统初始化启动时

浅析Linux字符设备驱动程序内核机制

前段时间在学习linux设备驱动的时候,看了陈学松著的<深入Linux设备驱动程序内核机制>一书. 说实话.这是一本非常好的书,作者不但给出了在设备驱动程序开发过程中的所须要的知识点(如对应的函数和数据结构),还深入到linux内核里去分析了这些函数或数据结构的原理.对设备驱动开发的整个过程和原理都分析的非常到位.但可能是因为知识点太多.原理也比較深的原因,这本书在知识点的排版上跨度有些大.所以读起来显得有点吃力,可是假设第一遍看的比較认真的话,再回头看第二次就真的可以非常好地理解作者的写作思

linux 驱动学习(一)简单的字符设备驱动程序

linux 系统将设备分为三种类型:字符设备.块设备和网络接口设备. 文章将先给出字符设备驱动程序,参照程序记录知识点,可能会不全,以后会慢慢加 .知识点记录完成后,会贴出字符设备驱动程序的测试程序并记录测试过程. 注释版 1 #include "linux/kernel.h" //内核头文件,含有一些内核常用函数的原形定义 2 #include "linux/module.h" //包含大量加载模块需要的函数和符号的定义 3 #include "linu

字符设备驱动程序的使用

1.编译.安装驱动linux系统中,驱动程序通常采用内核模块的程序结构来进行编码,因此,编译.安装一个驱动程序,其实质就是编译.安装一个内核模块.将文件memdev.c makefile 放入虚拟机中,make 得到.ko文件cp .ko rootfs启动开发板 insmod *.ko2.创建设备文件通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备.创建字符设备文件的方法:a.使用mkmod命令 mknod /dev/文件名 c 主设备号(将驱动和设备文件关联) 次设备号m

0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序

0.前言 研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧! 1.准备工作 a)查看内核版本 uname -r b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html) 在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils: 解压方法示例:xz -d linux-3.1-rc4.

Linux中块设备驱动程序分析

基于<Linux设备驱动程序>书中的sbull程序以对Linux块设备驱动总结分析. 开始之前先来了解这个块设备中的核心数据结构: struct sbull_dev { int size;                       /* Device size in sectors */ u8 *data;                       /* The data array */ short users;                    /* How many users

20150216简单的Linux字符设备驱动程序

20150216简单的Linux字符设备驱动程序 2015-02-16 李海沿 关于字符设备驱动程序详细的知识点,本文就不再介绍了,很多同志,看了知识点,还是一头雾水,写不出来,所以,本文从实战出发,带领各位同胞们来实现一个字符设备驱动程序,改程序可作为字符设备的通用模板. 好了废话不多说,先上驱动程序,在驱动程序中加入详细注释: 1 /****************************** 2 linux 字符设备驱动程序 3 *****************************/

LINUX设备驱动程序笔记(三)字符设备驱动程序

      <一>.主设备号和次设备号        对字符设备的访问时通过文件系统内的设备名称进行的.那些设备名称简单称之为文件系统树的节点,它们通常位于/dev目录.字符设备驱动程序的设备文件可通过ls -l命令输出的第一列中的'c'来识别.块设备同样位于/dev下,由字符'b'标识 crw-rw----  1 root root    253,   0 2013-09-11 20:33 usbmon0 crw-rw----  1 root root    253,   1 2013-09

linux字符设备驱动程序框架

#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/a