1、驱动入口函数
接下来从Linux驱动的入口函数module_init(s3c_led_init);
开始学习。可以看到这是由s3c_led_init
开始。函数如下:
static int __init s3c_led_init(void) /*一般用__init修饰的变量或者函数会编译到专门的一个段里面去,这个段的数据和函数只有在kernel初始化的时候会被调用,以后一定不会被使用,kernel可能会在以后的某个时候释放掉这个段所占用的内存,给别的地方使用*/
{
int result;
dev_t devno;
/*进行硬件初始化。若初始化失败,在内核缓冲区打印错误信息,并返回-ENODEV */
if( 0 != s3c_hw_init() )
{
printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
return -ENODEV;
}
/*若硬件初始化成功,分配主、次设备号 */
if (0 != dev_major) /*静态申请主、次设备号 */
{
devno = MKDEV(dev_major, 0);
result = register_chrdev_region (devno, dev_count, DEV_NAME);
}
else/*动态申请未被占用的主、次设备号 */
{
result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
dev_major = MAJOR(devno);
}
/*若设备号申请失败,在内核缓冲区打印错误信息,并返回-ENODEV */
if (result < 0)
{
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);
/*动态为struct 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;
}
led_cdev->owner = THIS_MODULE;/*把自己编写的模块插入内核,使其成为内核的一部分*/
cdev_init(led_cdev, &led_fops);/* 初始化struct cdev内核字符设备*/
result = cdev_add(led_cdev, devno, dev_count);/*初始化struct cdev后,把它添加到系统中。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。这时/dev下才会长出设备节点。*/
/*若设备注册失败,打印错误信息,并跳转到ERROR */
if (0 != result)
{
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 %d.%d.%d installed successfully!\n",DEV_NAME,dev_major,DRV_MAJOR_VER,DRV_MINOR_VER,DRV_REVER_VER);
return 0;
ERROR:
printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
cdev_del(led_cdev);
unregister_chrdev_region(devno, dev_count);
return result;
}
1.1file_operations led_fops结构体
在系统内部,I/O设备的存取操作通过特定的的入口来进行,而这组特定的入口由驱动程序来提供的。通常这组设备驱动的接口是由结构体file_operations向系统说明的。这个结构体就像一个桥梁一样。
static struct file_operations led_fops = /*led_fops告诉系统在进行LED驱动操作时该进行哪些系统调用*/
{
.owner = THIS_MODULE, /*owner设置为THIS_MODULE 。这个是一个在 <linux/module.h> 中定义的宏*/
.open = led_open, /*打开设备*/
.release = led_release, /*卸载设备*/
.unlocked_ioctl = led_ioctl,
};
小结:可以看出大致注册设备的流程:硬件初始化=>申请设备的主、次设备号=>定义led_fops=>将设备插入内核,初始化(由struct cdev结构体完成)=>设备注册成功,出现设备节点。
其中,主、次设备号的申请有静态和动态两种方式。我们在申请的时候尽量选择动态申请,避免静态申请而导致的与原有设备号发生冲突。
struct cdev结构体。它是在<linux/cdev.h>
里的,定义如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
1.cdev 结构体中的 dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低 20 位为次设备号。
其中,struct kobject 是内嵌的 kobject 对象;
struct module 是所属模块;
struct file_operations 为文件操作结构体。
2.使用以下宏可以从 dev_t 获得主设备号和次设备号:
引用
MAJOR (dev_t dev);
MINOR (dev_t dev);
3 . 而使用下面宏可以通过主设备号和次设备号生成 dev_t :
MKDEV (int major, int minor);
4.在运行时动态的获得一个独立的 cdev 结构,可以如下这么做:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
5.在分配好 cdev 结构后,就用 cdev_init() 函数对其初始化:
void cdev_init (struct cdev *cdev, struct file_operations *fops)
6.cdev 中的 owner 要设置为 THIS_MOULE。
7.cdev 结构体设置完毕,最后一步就是要把这事告诉给内核,使用下面的函数:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
2、驱动退出函数
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 %d.%d.%d removed!\n",DEV_NAME,DRV_MAJOR_VER,DRV_MINOR_VER,DRV_REVER_VER);
return ;
}
3、硬件初始化函数
在驱动入口函数中,有对硬件进行初始化的函数s3c_hw_init()
,下面学一学它。函数如下:
static int s3c_hw_init(void) /*硬件初始化函数*/
{
int i;
volatile unsigned long gpb_con, gpb_dat, gpb_up;
/*申请内存。这里的内存为开发板的物理内存,对应着与LED的相关的寄存器。这里标识了起始地址,内存长度(大小),名字。若出错则返回。*/
if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))
{
return -EBUSY;
}
/*开始建立物理内存到虚拟内存的映射。内核启动后,操作的都是虚拟内存,如果要操作物理内存,就使用ioremap建立映射关系*/
if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); /*申请的内存有可能有别的程序在占用,此时停止申请。*/
return -ENOMEM;
}
for(i=0; i<dev_count; i++)
{
/* Set GPBCON register, set correspond GPIO port as input or output mode */
gpb_con = s3c_gpio_read(GPBCON_OFFSET); /*读GPBCON的值*/
gpb_con &= ~(0x3<<(2*led[i])); /* 清零相关位 */
gpb_con |= GPIO_OUTPUT<<(2*led[i]); /*设置LED GPIO为OUTPUT模式*/
s3c_gpio_write(gpb_con, GPBCON_OFFSET);/*写寄存器GPBCON的值*/
/* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable */
gpb_up = s3c_gpio_read(GPBUP_OFFSET);
//gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */
gpb_up |= (0x1<<led[i]); /* Disable pull up resister */
s3c_gpio_write(gpb_up, GPBUP_OFFSET);
/* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
//gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */
gpb_dat |= (0x1<<led[i]); /* This port set to high level, then turn LED off */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
return 0;
}
3、LED清除函数
使用完毕后,要进行相应的清理工作。由s3c_hw_term(void)
完成。函数如下:
static void s3c_hw_term(void)
{
int i;
volatile unsigned long gpb_dat;
for(i=0; i<dev_count; i++)
{
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat |= (0x1<<led[i]); /* Turn LED off */
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);/*释放申请的内存*/
iounmap(s3c_gpb_membase);/*解除映射关系*/
}
4、LED的一些操作
4.1设备控制函数ioctl()
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。这里的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;
}
4.2 led_open函数
static int led_open(struct inode *inode, struct file *file) /*在用户空间调用open函数,就会调用led_open,打开相应的设备节点*/
{
int minor = iminor(inode); /* iminor函数可以获取设备节点的次设备号,由次设备号即可对该设备进行操作*/
file->private_data = (void *)minor;
printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
return 0;
}
4.3 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;
}
4.3LED的开、关函数
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;
到此,代码分析完毕,接下来就可以编写应用程序放到开发板来测试一下了。