fl2440————LED driver code analysis

After the LED is burned, let's analyze its driver code.
First start the analysis from the Linux driver entry function module_init(s3c2440_led_init);. The following function code:
static int __init s3c_led_init(void) //__init is defined in include/linux/init.h. The __init macro tells the compiler to place the variable or function in a special area defined in vmlinux.lds. __init puts the function in the ".init.text" code area, and __initdata puts the data in the ".init.data" data area. Marks an initialized function to indicate that the function is used during initialization. After the module is loaded, the module loading will throw away the initialization function, thereby freeing the memory occupied by the function
{
int result;
dev_t devno;
// The first step of the character device driver registration process: The corresponding device hardware is initialized. If the initialization fails, return -ENODEV (ENODEV means that it has not been allocated to a specific device by default)
if( 0 != s3c_hw_init() )
{
printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
return -ENODEV;
}
// The second step of the character device driver registration process: Allocate the major and minor device numbers, which supports both static designation and dynamic application.
/* Alloc the device for driver */
if (0 != dev_major) /* Static */
{
devno = MKDEV(dev_major, 0); /*Convert the major device number and minor device number into dev_t type*/ /*In the linux kernel, the character device is described by the cdev structure, which is an abstraction of all character devices, including Features common to a large number of character devices (detailed below) */
result = register_chrdev_region (devno, dev_count, DEV_NAME); /* All allocated character device numbers in the kernel are recorded in a hash table called chrdevs. Each element in the table is a char_device_struct structure (see below for details) */
}
else /* Dynamically apply for unoccupied primary and secondary device numbers*/
{
result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
dev_major = MAJOR(devno); //Get the major device number
}
/* Alloc for device major failure */
if (result < 0) /*If the device number application fails, print an error message in the kernel buffer and return -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);
// The third step of the character device driver registration process: allocate the cdev structure, we use the dynamic application method here, if the allocation fails, print an error message and return -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;
}
// The fourth step of the character device driver registration process: Bind the major and minor device numbers, fops to the cdev structure, and register with the Linux kernel
led_cdev->owner = THIS_MODULE; //Insert the module written by yourself into the kernel to make it part of the kernel. The structure struct moudle represents a kernel module in the kernel, through insmod (actually executes the init_moudle system call) the kernel module written by oneself
cdev_init(led_cdev, &led_fops); //When the kernel is inserted, the module is associated with a struct_moudlecdev_init(led_cdev, &led_fops); structure and becomes a part of the kernel (detailed later)
result = cdev_add(led_cdev, devno, dev_count); //Add a character device to the system. After initializing struct_cdev, add the device to the system. (detailed later)
if (0 != result) //If the registration fails, print the error message and jump to 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); // delete a cdev from the system (void cdev_del (struct cdev * p))
unregister_chrdev_region(devno, dev_count);
return result;
}
cdev structure
In the Linux 2.6 kernel, a character device is described by the cdev structure, which is defined as follows:
struct cdev{
struct kobject kobj;
struct module *owner; //The owning module
const struct file_operations *ops;//File operation structure, when writing the driver, most of the functions in the structure should be implemented
struct list_head list;
dev_t dev; //device number, int type, the upper 12 bits are the major device number, and the lower 20 bits are the minor device number
unsigned int count;
};
The major and minor device numbers can be obtained using the following macro calls:
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major,int minor) //Generate dev_t through major and minor device numbers
The above Hongzi kernel source code is defined as follows:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS)- 1)
//(1<<20 -1) After this operation, the lower 20 bits of the MINORMASK macro are 1, and the upper 12 bits are 0
#define MAJOR(dev) ((unsigned int) ((dev)>> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) &MINORMASK))
#define MKDEV (ma, mi) (((ma) << MINORBITS) | (mi))
Parameter introduction:
     ma is the main device number
     mi is the minor device number
Return value: Successful execution returns the device number of type dev_t
The following set of functions are used to operate on the cdev structure:
void cdev_init(struct cdev *, const struct file_operatios *); //Initialize, establish the connection between cdev and file_operations
struct cdev *cdev_alloc(void); // dynamically apply for a cdev memory
void cdev_put(struct cdev *p); //release
int cdev_add(struct cdev *, dev_t, unsigned); //Register the device, which usually happens in the loading function of the driver module
void cdev_del(struct cdev *); //To cancel the device, usually happens in the unload function of the driver module, it should be called first when registering: int register_chrdev_region(dev_t from, unsigned count, const char *name) The function assigns the device number to it, This function can be replaced by: int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,constchar *name) function, the difference between them is: when register_chrdev_region() is used for a known device number, the other is used for dynamic application. The advantage is that there will be no conflict of duplicate device numbers.

After cancellation, call: void unregister_chrdev_region(dev_t from, unsigned count) function to release the originally applied device number.

from: the first in the range of numbers to be deregistered; count: the number of device numbers to be deregistered

他们之间的顺序关系如下:
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:要映射的空间的大小;

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;







Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325810850&siteId=291194637