第12课第2.3节 字符设备驱动程序之LED驱动程序_操作LED.wmv

从初始化和退出开始分析:

  • 声明
module_init(second_drv_init);
module_exit(second_drv_exit);

在一个驱动设备中一般是有这两条函数的,这两条函数的目的是将括号内的初始化函数传递到某个执行函数中,在内核引导以及该驱动被装载到内核时调用,该函数的声明在include/linux/init中,声明段落如下:

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if                       
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)    __initcall(x);

/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 * 
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x)    __exitcall(x);
View Code

在该声明中我们看到使用了宏定义,对象是_initcall,相关代码在/init/main.c里面,它是内核采用的一种机制,在linux初始化的过程中,它利用gcc的扩展功能以及ld的连接控制脚本实现了在内核初始化的过程中通过简单的循环就实现了相关驱动的初始化,折合前面对module_init的描述大致一样,因此当我们在写内核函数且用到相关初始化时需要有这样一个声明。

  • 函数本体

    以一个GPIO驱动程序为例:

static int second_drv_init(void)
{
    major = register_chrdev(0, "second_drv", &sencod_drv_fops);   //字符型设备注册register_chrdev, 设备名second_drv, 设备调用函数结构sencod_drv_fops,即告诉有open和read两个函数可被调用

    seconddrv_class = class_create(THIS_MODULE, "second_drv");    //返回至是什么

    seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */  //返回什么值

    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);   //每4一个地址
    gpfdat = gpfcon + 1;                             //指向ioremap(0x56000050, 16) 对应地址的下一个地址

    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpgdat = gpgcon + 1;

    return 0;
}

static void second_drv_exit(void)
{
    unregister_chrdev(major, "second_drv");
    class_device_unregister(seconddrv_class_dev);
    class_destroy(seconddrv_class);
    iounmap(gpfcon);
    iounmap(gpgcon);
    return 0;
}

两个函数分别是对加载和卸载驱动对应执行函数做的定义,声明格式是static void 函数名(void),在加载驱动我们需要做如下工作:
  1.设备注册

  2.建立一个类

  3.在类下面建立一个设备

  4.和设备对象相关的初始化函数,比如内存映射(补充)等等

(补充:驱动程序并不能直接通过物理地址访问IO设备,只能通过访问指令对内核虚拟地址进行访问,因此需要将IO的硬件地址映射到内核虚拟地址空间内【通过页表】)

下面分别对对应步骤详细叙述:

  •     设备注册

    函数原型:int register_chrdev( unsigned int major, const char *name, const struct file_operations *fops )

    major:主设备号  name:驱动名  fops:file_operations结构体类型指针,可定义结构体中有的函数类型(里面可以放需要定义的函数,比如下面这个:)

static struct file_operations sencod_drv_fops = {
    .owner  =   THIS_MODULE, 
    .open   =   second_drv_open,     
    .read    =    second_drv_read,       
};  

    second_drv_open、second_drv_read这两个函数写我们的执行函数,这简单介绍用到的:

    open:这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功,但是你的驱动不会得到通知。

    read: 用来从设备中获取数据,在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败,一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型,常常是目标平台本地的整数类型)。

  •  建立一个类

    函数原型:extern struct class *class_create(struct module *owner, const char *name);

    struct module *owner:(暂不深究)默认为THIS_MODULE

  •  在类下面建立一个设备

    函数原型:

extern struct class_device *class_device_create(
struct class *cls, struct class_device *parent, dev_t devt, struct device *device, const char *fmt, ...);

    *cls:pointer to the struct class that this device should be registered to.

    *parent:pointer to the parent struct class_device of this new device, if any.(if any:有的话)

    devt:the dev_t for the char device to be added.

    *device: a pointer to a struct device that is assiociated with this class device.

    *fmt: string for the class device's name.

  •    和设备对象相关的初始化函数,比如内存映射(补充)
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);   
    gpfdat = gpfcon + 1;                             
    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpgdat = gpgcon + 1;

    ioremap是一个虚拟映射函数,因为驱动程序并不能直接通过物理地址访问IO设备,只能通过访问指令对内核虚拟地址进行访问,所以我们将其重映射到内存的相关位置,gpfdat是一个4字节指针,指针移动一位,地址移动4。

    附上完整程序,特别需要注意头文件:

#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 struct class *seconddrv_class;
static struct class_device    *seconddrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

static int second_drv_open(struct inode *inode, struct file *file)
{
    /* 配置GPF0,2为输入引脚 */
    *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));

    /* 配置GPG3,11为输入引脚 */
    *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));

    return 0;
}

ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    /* 返回4个引脚的电平 */
    unsigned char key_vals[4];
    int regval;

    if (size != sizeof(key_vals))
        return -EINVAL;

    /* 读GPF0,2 */
    regval = *gpfdat;                                   // gpfdat = (volatile unsigned long *)ioremap(0x56000050, 16) + 1 至于这个地址里面数据是如何被存进去的,我们未知
    key_vals[0] = (regval & (1<<0)) ? 1 : 0;
    key_vals[1] = (regval & (1<<2)) ? 1 : 0;
    

    /* 读GPG3,11 */
    regval = *gpgdat;
    key_vals[2] = (regval & (1<<3)) ? 1 : 0;
    key_vals[3] = (regval & (1<<11)) ? 1 : 0;

    copy_to_user(buf, key_vals, sizeof(key_vals)); //我们通过调用驱动调用读函数读取硬件值,然后需要将得到的硬件值反馈给用户。
    
    return sizeof(key_vals);
}

/* 暂时理解为这个结构体备用 */
static struct file_operations sencod_drv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ //?只有这一种设备定义吗澹?
    .open   =   second_drv_open,     
    .read    =    second_drv_read,       
};                                                                        //定义sencod_drv_fops ,此时其具有了file_operations 结构体一样的特征,可以把它视作file_operations
                                                                           //然后取其成员owner/open/read ,分别对其进行赋值

int major;
static int second_drv_init(void)
{
    major = register_chrdev(0, "second_drv", &sencod_drv_fops);   //字符型设备注册register_chrdev, 设备名second_drv, 设备调用函数结构sencod_drv_fops,即告诉有open和read两个函数可被调用

    seconddrv_class = class_create(THIS_MODULE, "second_drv");    //返回至是什么

    seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */  //返回什么值

    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);   //每4一个地址
    gpfdat = gpfcon + 1;                             //指向ioremap(0x56000050, 16) 对应地址的下一个地址

    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpgdat = gpgcon + 1;

    return 0;
}

static void second_drv_exit(void)
{
    unregister_chrdev(major, "second_drv");
    class_device_unregister(seconddrv_class_dev);
    class_destroy(seconddrv_class);
    iounmap(gpfcon);
    iounmap(gpgcon);
    return 0;
}


module_init(second_drv_init);

module_exit(second_drv_exit);

MODULE_LICENSE("GPL");
View Code

 

 

   

   

      

   

 

posted @ 2016-09-25 15:20  达达kiki  阅读(132)  评论(0)    收藏  举报