fl2440————LED驱动代码分析

LED烧录完了,接下来分析一下它的驱动代码。
首先从Linux的驱动入口函数module_init(s3c2440_led_init);开始分析。下面函数代码:
static int __init s3c_led_init(void)     //__init定义在include/linux/init.h中。__init宏告知编译器,将变量或函数放在一个特殊的区域,这个区域定义在vmlinux.lds中。__init将函数放在“.init.text”这个代码区中,__initdata将数据放在“.init.data”这个数据区中。标记初始化的函数,表明该函数在初始化期间使用。在模块加载后,模块装载就会扔掉初始化函数,从而释放函数占用的内存
{
int result;
dev_t devno;
// 字符设备驱动注册流程第一步: 相应的设备硬件初始化,若初始化失败,返回-ENODEV(ENODEV是默认尚未分配到具体设备的意思)
if( 0 != s3c_hw_init() )
{
printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
return -ENODEV;
}
// 字符设备驱动注册流程第二步: 分配主次设备号,这里既支持静态指定,也支持动态申请,
/* Alloc the device for driver */
if (0 != dev_major) /* Static */
{
devno = MKDEV(dev_major, 0);    /*将主设备号和次设备号转换成dev_t类型*/ /*在linux内核中,用cdev结构体描述字符设备,它是所有字符设备的抽象,包含了大量字符设备所共有的特性(下文有详细介绍)*/
result = register_chrdev_region (devno, dev_count, DEV_NAME);  /*内核中所有已分配的字符设备编号都记录在一个名为chrdevs散列表中。其表中的每一个元素是一个char_device_struct结构(详解见下文)*/ 
}
else     /*动态申请未被占用的主次设备号*/
{
result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
dev_major = MAJOR(devno);   //获取主设备号
}
/* Alloc for device major failure */
if (result < 0)              /*若设备号申请失败,在内核缓冲区打印错误信息,并返回-ENODEV*/
{
printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
return -ENODEV;
}
printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);
// 字符设备驱动注册流程第三步:分配cdev结构体,我们这里使用动态申请的方式,若分配失败,打印错误信息,返回-ENOMEM*/
if(NULL == (led_cdev=cdev_alloc()) )
{
printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
unregister_chrdev_region(devno, dev_count);
return -ENOMEM;
}
// 字符设备驱动注册流程第四步: 绑定主次设备号、fops到cdev结构体中,并注册给Linux内核
led_cdev->owner = THIS_MODULE;     //把自己编写的模块插入内核,使其成为内核的一部分。结构体struct moudle在内核中代表一个内核模块,通过insmod(实际执行init_moudle系统调用)把自己编写的内核模块
cdev_init(led_cdev, &led_fops);    //插入内核时,模块便与一个struct_moudlecdev_init(led_cdev, &led_fops); 结构体相关联,并成为内核的一部分(详介在后文)
result = cdev_add(led_cdev, devno, dev_count);  //向系统添加一个字符设备。初始化struct_cdev后,将设备添加到系统中。(详介后文)
if (0 != result)      //若注册失败,打印错误信息,并跳转到ERROR
{
printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME,
result);
goto ERROR;
}
printk(KERN_ERR "S3C %s driver[major=%d] version 1.0.0 installed successfully!\n",
DEV_NAME, dev_major);
return 0;
ERROR:
printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
cdev_del(led_cdev); //从系统中删除一个cdev (void cdev_del (struct cdev *  p))
unregister_chrdev_region(devno, dev_count);
return result;
}
cdev结构
在Linux2.6内核中一个字符设备用cdev结构来描述,其定义如下:
struct cdev{
struct kobject kobj;
struct module *owner; //所属模块
const struct file_operations *ops;//文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
struct list_head list;
dev_t dev; //设备号,int类型,高12位为主设备号,低20位为次设备号
unsigned int count;
};
可以使用如下宏调用来获得主、次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major,int minor) //通过主、次设备号来生成dev_t
以上宏仔内核源码中如此定义:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS)- 1)
//(1<<20 -1) 此操作后,MINORMASK宏的低20位为1,高12位为0
#define MAJOR(dev) ((unsigned int) ((dev)>> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) &MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
参数介绍:
     ma为主设备号
     mi为次设备号
返回值:成功执行返回dev_t类型的设备编号
下面一组函数用来对cdev结构体进行操作:
void cdev_init(struct cdev *, const struct file_operatios *);  //初始化,建立cdev和file_operations之间的连接
struct cdev *cdev_alloc(void);    //动态申请一个cdev内存
void cdev_put(struct cdev *p);  //释放
int cdev_add(struct cdev *, dev_t, unsigned);  //注册设备,通常发生在驱动模块的加载函数中
void cdev_del(struct cdev *); //注销设备,通常发生驱动模块的卸载函数中,在注册时应先调用:int register_chrdev_region(dev_t from, unsigned count, const char *name)函数为其分配设备号,此函数可用:int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,constchar *name)函数代替,他们之间的区别在于:register_chrdev_region()用于已知设备号时,另一个用于动态申请,其优点在于不会造成设备号重复的冲突。

在注销之后,应调用:void unregister_chrdev_region(dev_t from, unsigned count)函数释放原先申请的设备号。

from:取消注册的数字范围中的第一个;count:要注销的设备号的数量

他们之间的顺序关系如下:
register_chrdev_region()-->cdev_add()    //此过程在加载模块中
cdev_del()-->unregister_chrdev_region()     //此过程在卸载模块中
驱动退出函数

module_exit(s3c_led_exit); module_exit:驱动程序退出入口点;函数在驱动程序被删除时运行

static void __exit s3c_led_exit(void)
{
dev_t devno = MKDEV(dev_major, dev_minor);
s3c_hw_term();
cdev_del(led_cdev);    //删除cdev结构,释放结构本身
unregister_chrdev_region(devno, dev_count); //释放原先申请的设备号 
printk(KERN_ERR "S3C %s driver version 1.0.0 removed!\n", DEV_NAME);
return ;
}

硬件初始化函数

_hw_init()执行电路板通用硬件初始化的功能。在驱动入口函数中,有对硬件进行初始化的函数s3c_hw_init(),代码如下:

static int s3c_hw_init(void)
{
int i;
unsigned int regval;
if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))  /*申请内存。这里的内存为开发板的物理内存,对应着与LED的相关的寄存器。这里标识了起始地址,内存长度(大小),名字。若出错则返回。*/
{
printk(KERN_ERR "request_mem_region failure!\n");  //打印错误信息
return -EBUSY;
}
if( !(gpbbase=(unsigned int *)ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )  //在虚拟机中,用户使用的是虚拟内存。所以这里开始建立物理内存到虚拟内存的映射。内核启动后,操作的都是虚拟内存,如果要操作物理内存,就使用ioremap建立映射关系,取消映射iounmap (void* 地址(虚拟起始地址))
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);  //释放I/O存储区域
printk(KERN_ERR "release_mem_region failure!\n");
return -ENOMEM;
}
for(i=0; i<dev_count; i++)
{
/* Set GPBCON register, set correspond GPIO port as input or output mode */
regval = read_reg32(gpbbase+GPBCON_OFFSET);  //读GPBCON的值
regval &= ~(0x3<<(2*led[i])); /* Clear the correspond LED GPIO configure register*/清零相关为
regval |= GPIO_OUTPUT<<(2*led[i]);
/* Set the currespond LED GPIO as output mode*/
write_reg32(gpbbase+GPBCON_OFFSET, regval);/* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable *//*写寄存器相关的值*/
regval = read_reg32(gpbbase+GPBUP_OFFSET);
regval |= (0x1<<led[i]); /* Disable pull up resister */
write_reg32(gpbbase+GPBUP_OFFSET, regval);
/* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
regval = read_reg32(gpbbase+GPBDAT_OFFSET);
regval |= (0x1<<led[i]); /* This port set to high level, then turn LED off */
write_reg32(gpbbase+GPBDAT_OFFSET, regval);
}
return 0;
}

下文摘自:http://blog.chinaunix.net/uid-21289517-id-1828602.html

ioremap 与__ioremap的区别 

                                     

void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)

void *ioremap(unsigned long phys_addr, unsigned long size)

入口: phys_addr:要映射的起始的IO地址;

size:要映射的空间的大小;

扫描二维码关注公众号,回复: 168502 查看本文章

flags:要映射的IO空间的和权限有关的标志;

phys_addr:是要映射的物理地址,

size:是要映射的长度,

S3C2410的long是32位而非你说的64位。

功能: 将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问;

实现: 对要映射的IO地址空间进行判断,低PCI/ISA地址不需要重新映射,也不允许用户将IO地址空间映射到正在使用的RAM中,最后申请一个vm_area_struct结构,调用remap_area_pages填写页表,若填写过程不成功则释放申请的vm_area_struct空间;

ioremap 依靠 __ioremap实现,它只是在__ioremap中以第三个参数为0调用来实现.

ioremap是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经从pci_dev中读了出来(上一步),这样就水到渠成的成功映射了而不会和其他地址有冲突。映射完了有什么效果呢,我举个例子,比如某个网卡有100 个寄存器,他们都是连在一块的,位置是固定的,加入每个寄存器占4个字节,那么一共400个字节的空间被映射到内存成功后,ioaddr就是这段地址的开头(注意ioaddr是虚拟地址,而mmio_start是物理地址,它是BIOS得到的,肯定是物理地址,而保护模式下CPU不认物理地址,只认虚拟地址),ioaddr+0就是第一个寄存器的地址,ioaddr+4就是第二个寄存器地址(每个寄存器占4个字节),以此类推,我们就能够在内存中访问到所有的寄存器进而操控他们了。

字符设备驱动register_chrdev_region()系列

内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构,它的定义如下:

static struct char_device_struct {
struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
unsigned int major;           // 主设备号
unsigned int baseminor;       // 起始次设备号
int minorct;                 // 设备编号的范围大小
char name[64];        // 处理该设备编号范围内的设备驱动的名称
struct file_operations *fops;     
struct cdev *cdev;        // 指向字符设备驱动程序描述符的指针
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

内核并不是为每一个字符设备编号定义一个char_device_struct结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个char_device_struct结构。chrdevs散列表的大小是255。散列算法是把每组字符设备号范围的主设备号以255取模插入相应的散列桶中。同一个散列桶中的字符设备编号范围是按起始次设备号递增排序的。

注册

内核提供了三个函数来注册一组设备编号,这三个函数分别是register_chrdev_region(), alloc_chrdev_region(), register_chrdev().这三个函数都会调用一个共用的_register_chrdev_region()函数来注册一组设备范围编号(即一个char_device_struct结构)

三个函数原型

register_chrdev_region(dev_t first, unsigned  int count,  char *name)

first:要分配的设备编号范围的初始值(次设备号常为0)

first:要分配设备编号范围的起始值,first的次设备号经常被置为0,但对函数来说是并不是必须的。

count:是请求连续设备编号的个数(注意如果count非常大,则请求的范围可能会和下一个主设备号重叠,但只要请求的编号范为可用,就没有问题)。

name:设备号的名称,返回值小于0表示分配失败。

https://blog.csdn.net/tigerjibo/article/details/6412672#reply register_chrdev_region系列函数代码介绍)

file_operations led_fops结构体(假设,我们已经为自己保留了一些设备号,但尚未将任何驱动程序操作连接到这些编号。( file_operations led_fops结构就是用来建立这种连接的。这个结构定义在<linux/fs.h>中)

static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
struct module *owner   //第一个file_operations字段并不是一个操作;相反,它是指向“拥有”该结构的模块的指针。内核使用这个字段以避免在模块的操作正在被使用时卸载该模块。几乎所有的情况,该成员都会被初始化为THIS_MODULE,它是定义在<linux/module.h>中的宏
int (*open) (struct inode *, struct file *);  //尽管这始终是对设备文件执行的第一个操作,但却并不要求驱动程序一定要声明相应的方法。如果这个入口为NULL,设备操作永远成功,但系统不会通知驱动程序。
int (*release) (struct inode *, struct file *); //当file结构被释放时,将调用这个操作。与open操作相仿,也可以将release设置为NULL

注意:release并不是在进程每次调用close时都会被调用。只要file结构被共享(如fork或dup调用之后),release就会等到所有的副本都关闭之后才会得到调用。

iotcl:在kernel2.6.35及之前的版本中struct file_operations 一共有3个:ioctl、unlocked_ioctl和compat_ioctl;但现在只有unlocked_ioctl和compat_ioctl了

系统调用ioctl函数的作用:通过设备驱动程序执行各种类型的硬件控制。ioctl方法实现了同名系统的调用。

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int which = (int)file->private_data; /*获取次设备号*/

/*由cmd传来的命令码,来执行LED的开或关操作*/
    switch (cmd)
    {
        case LED_ON:

            turn_led(which, LED_ON);
            break;

        case LED_OFF:
            turn_led(which, LED_OFF);
            break;

        default:
            printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
            print_help();
            break;
    }

    return 0;
}
long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);

cmd参数需要与应用程序调用ioctl时的参数约定,才可以表示一种功能

cmd的值不能为2,内核里保留此值。

cmd是32位的数,分成以下四个部分

1). 最高两位表示方向: 读/写/读写(输出/输入/输出输入)
2). 第16位至第29位表示ioctl的第三个参数的大小(unlocked_ioctl的arg).
3). 第8位至第15位表示ioctl命令的类型.
4). 最低8位表示ioctl命令类型里的第几个命令

LED清除函数

使用完毕之后,要进行相应的清理。由s3c_hw_term(void)处理。函数如下:

static void s3c_hw_term(void)
{
int i;
unsigned int regval;
for(i=0; i<dev_count; i++)
{
regval = read_reg32(gpbbase+GPBDAT_OFFSET);
regval |= (0x1<<led[i]); /* Turn LED off */
write_reg32(gpbbase+GPBDAT_OFFSET, regval);
}
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);  //释放申请的内存
iounmap(gpbbase);  //取消映射关系
}

Led的开关函数

static int led_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);              //获取次设备号
    file->private_data = (void *)minor;
    printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
    return 0;
}

Led_release函数:

static int led_release(struct inode *inode, struct file *file) /*在用户空间调用close函数,就会调用led_release,关闭节点*/
{
    printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));

    return 0;
}

Led的开、关函数

static void turn_led(int which, unsigned int cmd)
{
    volatile unsigned long  gpb_dat;

    gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);

    if(LED_ON == cmd)
    {
        gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */
    }
    else if(LED_OFF == cmd)
    {
        gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */
    }

    s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}

最后是一些头文件、宏和一些变量的定义

#include <linux/module.h> /* Every Linux kernel module must include this head */
#include <linux/init.h> /* Every Linux kernel module must include this head */
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* struct fops */
#include <linux/errno.h> /* error codes */
#include <linux/cdev.h> /* cdev_alloc() */
#include <asm/io.h> /* ioremap() */
#include <linux/ioport.h> /* request_mem_region() */
#include <asm/ioctl.h> /* Linux kernel space head file for macro _IO() to generate ioctl
command */
#include <linux/printk.h> /* Define log level KERN_DEBUG */
#define DEV_NAME "led"
#define LED_NUM 4
/* Set the LED dev major number */
//#define LED_MAJOR 79
#ifndef LED_MAJOR
#define LED_MAJOR 0
#endif
#define DISABLE 0
#define ENABLE 1
#define GPIO_INPUT 0x00
#define GPIO_OUTPUT 0x01
#define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
#define S3C_GPB_BASE 0x56000010
#define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */
#define GPBCON_OFFSET 0
#define GPBDAT_OFFSET 4
#define GPBUP_OFFSET 8
static void __iomem *gpbbase = NULL;
#define read_reg32(addr) *(volatile unsigned int *)(addr)                //操作虚拟地址,强制类型转换,将地址转换为指针,以此来达到读写的目的
#define write_reg32(addr, val) *(volatile unsigned int *)(addr) = (val)
int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
int dev_count = ARRAY_SIZE(led);
int dev_major = LED_MAJOR;
int dev_minor = 0;
int debug = DISABLE;
static struct cdev *led_cdev;







猜你喜欢

转载自blog.csdn.net/buhuiguowang/article/details/79684630