前面分析了fl2440的led驱动代码,现在就基于之前的LED驱动代码完成应用程序(跑马灯)的实现,并且在linux系统下手动创建设备节点,运行跑马灯程序。
首先来看 用户空间下跑马灯程序的实现
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LED_NUM 4
#define DEVNAME_LEN 10
#define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
int main(int argc, char **argv)
{
int fd[LED_NUM];
int i;
int j;
char devname[DEVNAME_LEN] = {0};
for(i = 0; i < LED_NUM; i++)
{
snprintf(devname, sizeof(devname), "/dev/led%d",i);
fd[i] = open(devname, O_RDWR,755);
if(fd[i] < 0)
{
printf("Can not open %s: %s", devname, strerror(errno));
for(j = 0; j < i; j++) //如果某一设备不能打开,则要关掉之前已经打开的设备
{
if(fd[j] > 0)
close(fd[j]);
}
return -1;
}
}
while(1)
{
for(i = 0; i < LED_NUM; i++)
{
ioctl(fd[i], LED_ON); //打开led灯
sleep(1); //延时1s
ioctl(fd[i], LED_OFF);//关闭led灯
}
}
for(i = 0; i < LED_NUM; i++)
{
close(fd[i]); //关掉设备文件
}
return 0;
}
[lwn@localhost s3c-led]$ /opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-gcc ledtest.c
/opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-gcc是我交叉编译器的目录下的交叉编译工具,
编译之后生成a.out可执行文件
/*********************************************************************************
* Copyright: (C) 2011 Guo Wenxue
* All rights reserved.
*
* Filename: plat_button.c
* Description: This is the common button driver runs on S3C2440
*
* Version: 1.0.0(4/27/2017~)
* Author: Li Wanneng
* ChangeLog: 1, Release initial version on "4/27/2017 11:39:10 AM"
*
********************************************************************************/
#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
//#include /* Define log level KERN_DEBUG, no need include here */
#define DRV_AUTHOR "Li Wanneng "
#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
/*防止ioctl传入的命令与其他参数冲突*/
#define PLATDRV_MAGIC 0x60 //魔术字
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
/*GPB有3个寄存器,GPBCON, GPBDATA和GPBUP寄存器,下面是其对应的物理地址*/
#define S3C_GPB_BASE 0x56000010//s3c2440GPB寄存器的基地址
#define GPBCON_OFFSET 0 //s3c2440GPBCON寄存器偏移地址0x56000010
#define GPBDAT_OFFSET 4 //s3c2440GPBDATA寄存器偏移地址 0x56000014
#define GPBUP_OFFSET 8 //s3c2440GPBUP寄存器的偏移地址 0x56000018
#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; //__iomem表示指针s3c_gpb_membase是指向一个I/O的内存空间
// #define _raw_readl(reg) *(volatile unsigned int *)(reg)
#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; //debug = 0,上面宏定义DISABLE为0
/*struct cdev 是表示字符设备的内核内部结构,当innod指向一个字符设备文件时,该字段包含了指向struct cdev 结构的指针*/
static struct cdev *led_cdev; //定义cdev结构体指针,led内核结构
static int s3c_hw_init(void)//硬件初始化,设置相应GPIO口为output模式
{
int i;
volatile unsigned long gpb_con, gpb_dat, gpb_up;
/*为s3c2440_led申请一片物理地址,其起始地址为S3C_GPB_BASE,大小为s3c_GPB_LEN;如果申请失败,返回-EBUSY */
if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))
{
return -EBUSY;
}
/*如果物理地址申请成功,将其映射到相应的虚拟地址。以后操作寄存器一律操作虚拟地址即s3c_gpb_membase的地址*/
if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )//ioremap函数作用为将物理地址映射到虚拟地址
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);//如果映射失败,一定要将物理地址释放
return -ENOMEM;//申请失败返回错误信息-ENOMEM
}
/*1,设置相应gpio端口为输出模式
2,disable上拉寄存器哦
3,设置gpio数据寄存器为1,即使相应GPIO端口输出高电平,默认关闭LED灯
*/
for(i=0; iprivate_data = (void *)minor;//在file结构体中,private_data是一个空类型指针
printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
return 0;
}
static int led_release(struct inode *inode, struct file *file)//"关闭led"函数
{
printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
return 0;
}
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;
}
//ioctl(fd, LED_ON, 0)
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)//ioctl()函数会用到,其中参数为用户程序空间传过来的参数
{
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 = //定义fop结构体,针对该驱动提供的系统调用和操作
{
.owner = THIS_MODULE,//.owener的值一般为THIS_MODULE
.open = led_open,//open为指向led_open()的函数指针,调用open()函数时会用到
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int __init s3c_led_init(void)//led初始化函数
{
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);
if(NULL == (led_cdev=cdev_alloc()) )//分配cdev结构体,如果分配失败,打印如下信息
{
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结构体添加进内核
cdev_init(led_cdev, &led_fops);//连接led_cdev和led_fops
/*只有当cdev_add()函数执行之后,才能在 /dev/目录下看到设备节点*/
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);
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;
}
static void __exit s3c_led_exit(void)//led卸载函数
{
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 ;
}
/* These two functions defined in */
module_init(s3c_led_init);//Led驱动首先应该找到module_init函数
module_exit(s3c_led_exit);
module_param(debug, int, S_IRUGO);//使能驱动的debug功能,debug为全局变量
module_param(dev_major, int, S_IRUGO);//函数传参,insmod时通过参数动态修改主设备号
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
[lwn@localhost s3c-led]vim Makefile
Makefile
1 LINUX_SRC?=../../kernel/linux-lwn-3.0.1
2 CROSS_COMPILE=/opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-
3
4 obj-m := s3c_led.o
5
6 modules:
7 @make -C $(LINUX_SRC) M=`pwd` modules
8 @make clean
9
10 clean:
11 rm -f *.ko.* *.o *mod.c *.order *.symvers
12
[lwn@localhost s3c-led]$ ls
a.out ledtest.c Makefile s3c_led.c s3c_led.ko
在主机上面生成了a.out和s3c_led.ko之后,使用 tftp 工具将其下载到开发板。
现在开发板上只是有了s3c_led.ko文件,但是如果要使用驱动,还需要安装驱动,创建设备节点,运行应用程序。
使用insmod命令安装驱动
下面是在开发板上的操作
执行a.out之后在开发板上可以看到led已经跑起来了。到此,整个LED驱动和用户程序已经成功执行。