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; }
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))
在注销之后,应调用: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 */ #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;