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;
}
在注销之后,应调用: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:要映射的空间的大小;
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个字节),以此类推,我们就能够在内存中访问到所有的寄存器进而操控他们了。
内核中所有已分配的字符设备编号都记录在一个名为 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 */
#ifndef __KERNEL__
#include <sys/ioctl.h> /* User space head file for macro _IO() to generate ioctl command */
#endif
#define DRV_AUTHOR "TangBin<tangbinmvp@gmail.com>"
#define DRV_DESC "S3C24XX LED driver"
#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 DRV_MAJOR_VER 1
#define DRV_MINOR_VER 0
#define DRV_REVER_VER 0
#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 GPBCON_OFFSET 0
#define GPBDAT_OFFSET 4
#define GPBUP_OFFSET 8
#define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */
int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
static void __iomem *s3c_gpb_membase;
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)
#define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase)
int dev_count = ARRAY_SIZE(led);
int dev_major = LED_MAJOR;
int dev_minor = 0;
int debug = DISABLE;
static struct cdev *led_cdev;
总结一下编写驱动加载过程: