这几天学习并且编写了在s3c2440开发板上的led驱动程序,并且测试成功。下面来记录一下心得体会。
一、 编辑s3c_led.c
首先在我创建的一个系统目录的“driver”路径下创建一个文件夹“led”
接着需要创建s3c_led.c
[GTT@Gongtaitinglocaldomain ~]$vim fl2440/driver/led/s3c_led.c
#include /* Every Linux kernel module must include this head */
#include /* Every Linux kernel module must include this head */
#include /* printk() */
#include /* struct fops */
#include /* error codes */
#include /* cdev_alloc() */
#include /* ioremap() */
#include /* request_mem_region() */
#include /* Linux kernel space head file for macro _IO() to generate ioctl command */
#ifndef __KERNEL__
#include /* User space head file for macro _IO() to generate ioctl command */
#endif
#define DRV_DESC "S3C24XX LED driver"
#define DEV_NAME "led"
#define LED_NUM 4
#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 S3C_GPB_BASE 0X56000010 //寄存器的基地址
#define GPBCON_OFFSET 0 //管脚控制器的偏移地址
#define GPBDAT_OFFSET 4 //数据寄存器的偏移地址
#define GPBUP_OFFSET 8 //管脚上下拉偏移地址
#define S3C_GPB_LEN 0X10 //申请虚拟内存时的长度
#define PLATDRV_MAGIC 0x60 //魔数
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)//通过一个简单操作将给定的魔数与一个简单的编号生成一个新的数字,而内核保证这个数字是没有使用过的
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
static void __iomem *s3c_gpb_membase ;//申请的一段IO内存,返回指向这段内存的指针
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase) /*在地址(reg)+s3c_gpb_membase处写入val*/
#define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase)/*将地址(reg)+s3c_gpb_membase的寄存器的值读出来*/
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;//将主设备号给dev_major
int dev_minor = 0;//此设备号赋值为0
int debug = DISABLE;
struct cdev *led_cdev; //定义一个cdev类型的指针
static int s3c_hw_init(void)
{
int i;
volatile unsigned long gpb_con , gpb_dat,gpb_up;//寄存器的值 需要用volatile类型
//申请内存,
if(!request_mem_region(S3C_GPB_BASE,S3C_GPB_LEN,"s3c2440 led"))//申请一段IO内存
{
return -EBUSY;
}
if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE,S3C_GPB_LEN)) )//将物理地址转化为虚拟地址,在linux操作系统中只能使用虚拟地址
{
release_mem_region(S3C_GPB_BASE,S3C_GPB_LEN);
return -ENOMEM;
}
for(i=0; i//修改寄存器的值
{
gpb_con=s3c_gpio_read(GPBCON_OFFSET);
gpb_con &=~(0x3<<2*led[i]);
gpb_con |= (0x1<<2*led[i]);
s3c_gpio_write(gpb_con, GPBCON_OFFSET);
gpb_up=s3c_gpio_read(GPBUP_OFFSET);
gpb_up |=0x1<0x1<<5);
s3c_gpio_write(gpb_dat,GPBDAT_OFFSET);
}
return 0;
}
static void turn_led (int which , unsigned int cmd)//led灯的具体操作
{
volatile unsigned long gpb_dat;
gpb_dat=s3c_gpio_read(GPBDAT_OFFSET);
if(LED_ON==cmd)
{
gpb_dat &=~(0x1<if(LED_OFF==cmd)
{
gpb_dat |= 0x1<static void s3c_hw_term(void)//硬件反初始化
{ int i;
volatile unsigned long gpb_dat;
for(i=0; i0x1</* Turn LED off */ // 结束的时候将所有led关闭
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);
iounmap(s3c_gpb_membase);
}
static void print_help(void)
{ printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME); //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
printk("Turn LED on command : %u\n", LED_ON);
printk("Turn LED off command : %u\n", LED_OFF);
return ;
}
static int led_open(struct inode *inode,struct file *file)//驱动层的驱动函数,对应应用层的open
{
int minor = iminor(inode);
file->private_data = (void *)minor; //通过file的一个私有成员传递参数,即把次设备号的值付给private_data,但是private_data是一个指针,所以需要强制类型转换。
printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
return 0;
}
/* 用户层的open函数的int open(const char *pathname, int oflag, ... );的第一个参数(如/dev/led)对应着这里的第一个参数。内核使用inode结构体在内核内部表示一个文件。*/
static int led_release(struct inode *inode,struct file *file)
{
printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
return 0;
}
static long led_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
{
int which = (int)(file->private_data);
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;
}
static struct file_operations led_fops = //file_operation 中定义了字符设备的一些函数操作
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
//驱动初始化
static int __init s3c_led_init(void)
{
dev_t devno;
int result;
if( 0 != s3c_hw_init() )//检查硬件是否正常初始化
{
printk(KERN_ERR "s3c2400 LED hardware initialize failure.\n");
}
//两种名分配设备号的方法
if(0 !=dev_major)//静态分配
{
devno = MKDEV(dev_major,0);//MKDEV这个宏是将主次设备号转化为一个dev_t类型的整数
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);//提取主设备号的方法
}
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);
if(NULL == cdev_alloc())//为申请的c_dev结构体分配一段内存空间 ,是动态内存分配
{
printk(KERN_ERR "S3C %s 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);//建立设备与 file_operatione的联系
result = cdev_add(led_cdev,devno,dev_count);//将该设备添加到内核当中
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);
ERROR:
printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
cdev_del(led_cdev);
unregister_chrdev_region(devno, dev_count);
return result;
}
static void __exit s3c_led_exit(void)//移除驱动
{
dev_t devno = MKDEV(dev_major,dev_minor);
s3c_hw_term();//硬件反初始化
cdev_del(led_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;
}
module_init(s3c_led_init);
module_exit(s3c_led_exit);
module_param(debug, int, S_IRUGO); //传参 声明模块参数debug的宏,指定模块参数,用于在加载模块时或者模块加载以后传递参数给模块
module_param(dev_major, int, S_IRUGO);
小结:
学了几天的驱动,感觉linux下的设备驱动跟单片机下的驱动还是有很大的区别的,我总结了一下流程,主要是:硬件的初始化,设备号的分配,c_dev结构体的内存申请,建立与c_dev与file_operation的联系,最后将设备添加到内核中。
我觉得在这次学习linux环境下的驱动时需要注意一些问题,首先是在操作硬件的时候注意操作系统操作的是虚拟地址,我们先要将物理地址转化为虚拟地址,其次是用户层的open()函数与驱动的open()函数的联系,一些参数传递的过程。
二、Makefile的编译
Makefile
1 LINUX_SRC ?= /home/GTT/fl2440/kernel/linux-3.0/
2 CROSS_COMPILE= /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-
3 obj-m :=s3c_led.o
4
5 modules:
6 @make -C $(LINUX_SRC) M=`pwd` modules
7 @make clean
8
9 clean:
10 rm -f *.o *mod.c *.order *.symvers
Makefile 编辑结束后,输入make命令,之后会生成s3c_led.ko文件
这个就是我们需要的驱动模块。