字符设备驱动程序分析

字符设备驱动程序分析

下面是针对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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值