fl2440字符设备led驱动
首先要明白字符设备驱动注册的基本流程
当我们调用insomd命令加载驱动后,驱动程序从module_init函数开始执行:硬件初始化 -> 申请主次设备号 -> 定义fops(file_operations)结构体 -> 申请cdev结构体并把fops结构体嵌入cdev结构体中与之绑定 -> cdev字符设备的注册。
有一点需要明确的是,在Linux内核中,所有的设备都是以文件。我们对设备的操作即是对Linux内核中文件的操作。设备都在/dev目录下。而inode则是设备索引节点,每个文件产生后都会有相应的inode来标示。
驱动加载完成后,file_operations结构体中的成员可以为应用程序提供对设备进行各种操作的函数指针:open,read,write等。
cdev结构体是用来描述字符设备的,内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:
linux-2.6./include/linux/cdev.h
struct cdev {
struct kobject kobj; // 每个 cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
s3c_led代码:
/*********************************************************************************
* Copyright: (C) 2012 Guo Wenxue<guowenxue@gmail.com>
* All rights reserved.
*
* Filename: s3c_led.c
* Description: This file
*
* Version: 1.0.0(07/26/2012~)
* Author: Guo Wenxue <guowenxue@gmail.com>
* ChangeLog: 1, Release initial version on "07/26/2012 10:03:40 PM"
*
********************************************************************************/ #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
//#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */ #define DRV_AUTHOR "Guo Wenxue <guowenxue@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 //定义默认的设备号为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 //定义GPIO输入模式用0x00代替
#define GPIO_OUTPUT 0x01 //定义GPIO输出模式用0x01代替 #define PLATDRV_MAGIC 0x60 //定义一个魔术字
//魔术字有着特殊的功能,定义一个系统未用的魔术字,然后让魔术字生成我们定义的LED_OFF与LED_ON,这样我们的定义就不会和系统中别的宏定义相同了
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19) #define S3C_GPB_BASE 0x56000010 //定义GPB管脚控制寄存器的基址地址 #define GPBCON_OFFSET 0 //定义GPBCON的偏移地址,用来选定引脚并设置输入输出模式
//GPBDAT用于读写引脚数据。输入模式时,读此寄存器可知相应引脚的电平是低还是高;输出模式写此引脚可设置输出高低电平
#define GPBDAT_OFFSET 4 //定义GPBDAT的偏移地址
/*GPBUP的某位为1时,相应引脚无内部上拉电阻;为0时相应引脚使用内部上拉电阻。
上拉电阻的作用是当GPIO引脚出去第三态时,即既不是输出高电平也不是输出低电平,
而是呈现高阻态,相当于没有接芯片。它的电平状态由上拉电阻下拉电阻决定。*/
#define GPBUP_OFFSET 8
#define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */ //GPB寄存器内存地址总长度 int led[LED_NUM] = {,,,}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */ static void __iomem *s3c_gpb_membase; //定义一个指向一个IO内存空间的指针,后面做虚拟内存的映射 #define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase) //将val的值写入reg地址中
#define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase) //读取地址为reg中的值 int dev_count = ARRAY_SIZE(led); //设备结构体中设备的个数
int dev_major = LED_MAJOR; //主设备号赋值给dev_major
int dev_minor = ; //次设备号为0
int debug = DISABLE; //出错定义赋值 static 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内存空间,失败返回0
{
return -EBUSY;
} if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) ) //物理地址映射到虚拟地址,此时开启MMU
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //映射失败则释放申请的IO内存空间····
return -ENOMEM;
} for(i=; i<dev_count; i++)
{
/* Set GPBCON register, set correspond GPIO port as input or output mode */
gpb_con = s3c_gpio_read(GPBCON_OFFSET);
gpb_con &= ~(0x3<<(*led[i])); /* Clear the currespond LED GPIO configure register */
gpb_con |= GPIO_OUTPUT<<(*led[i]); /* Set the currespond LED GPIO as output mode */
s3c_gpio_write(gpb_con, GPBCON_OFFSET); /* 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 ;
} static void turn_led(int which, unsigned int cmd) //调用ioct控制单个led灯的亮灭
{
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);
} static void s3c_hw_term(void) //调用LED结束后释放内存所占用的资源及设置相应的GPIO引脚关闭LED
{
int i;
volatile unsigned long gpb_dat; for(i=; 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); //释放指定的IO内存资源
iounmap(s3c_gpb_membase); //取消ioremap所做的映射
} static int led_open(struct inode *inode, struct file *file) //驱动功能函数
{
int minor = iminor(inode); //通过索引节点获得次设备号 file->private_data = (void *)minor; //将次设备号保存到private_data中,方便在函数中调用
//private_data在系统调用期间保存各种状态信息
printk(KERN_DEBUG "/dev/led%d opened.\n", minor); //打印成功信息
return ;
} static int led_release(struct inode *inode, struct file *file) //关闭led函数
{
printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode)); return ;
} 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 long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) //led中断控制函数
{
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: //如果传进来的既不是ON也不是OFF则打印出错并打印HELP结构体
printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
print_help();
break;
} return ;
} //结构体file_operationgs用来存储驱动内核模块提供的对设备进行各种操作的函数的指针
static struct file_operations led_fops =
{
.owner = THIS_MODULE, //指向拥有该结构体模块的指针,避免正在操作时被卸载。
.open = led_open, //传递led_open函数打开设备
.release = led_release, //传递led_release函数关闭设备
.unlocked_ioctl = led_ioctl, //传递中断函数,不使用BLK的文件系统,将使用此种函数的指针代替ioctl
}; static int __init s3c_led_init(void) //底层函数
{
int result;
dev_t devno; if( != s3c_hw_init() ) //初始化led硬件并判断是否初始化成功
{
printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
return -ENODEV;
} /* Alloc the device for driver */
if ( != dev_major) /* Static */
{
devno = MKDEV(dev_major, ); //通过主设备号与次设备号构建32位设备号
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); //根据设备号devno获得主设备号
} /* Alloc for device major failure */
if (result < )
{
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;
}
//cdev_alloc主要完成空间的申请和简单的初始化操作,分配一个cdev如果失败则打印错误信息并释放分配的设备号。 led_cdev->owner = THIS_MODULE; //指明设备所属模块,这里永远指向THIS_MODULE
//初始化cdev的file_operations,使字符设备与操作的函数绑定,led_fops结构体中的成员指向驱动提供的功能函数
cdev_init(led_cdev, &led_fops); //cdev_add为注册设备函数,通常发生在驱动模块的加载函数中并将返回值传递给result,为0则表示成功。
result = cdev_add(led_cdev, devno, dev_count);
if ( != 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 ; 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 ;
} /* These two functions defined in <linux/init.h> */
module_init(s3c_led_init); //insmod加载内核模块定义的宏
module_exit(s3c_led_exit); //rmmod卸载模块定义的宏,退出内核模块 module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO); MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
在此驱动中,第一步装载驱动调用module_init函数 -> 调用s3c_led_init函数 -> 调用s3c_hw_init函数初始化硬件;第二步s3c_led_init函数 -> 调用MKDEV或者alloc_chrdev_region函数分配主次设备号;第三步调用file_operations结构体定义led_fops结构体;第四步s3c_led_init函数 -> 调用cdev_alloc函数创建一个cdev的结构体 -> 调用cdev_init函数将字符设备与led_fops函数绑定;第五步调用cdev_add函数注册cdev字符设备。
驱动程序写好了那么fl2440开发板上已经有了led的驱动,这个时候需要一个应用程序来控制LED的亮灭:
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h> #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, j;
char devname[DEVNAME_LEN] = {}; for(i = ; i < LED_NUM; i++)
{
snprintf(devname, sizeof(devname), "/dev/led%i",i);
fd[i] = open(devname, O_RDWR,);
if(fd[i] < )
{
printf("Can not open %s: %s", devname, strerror(errno));
for(j = ; j < i; j++)
{
if(fd[j] > )
close(fd[j]);
}
return -;
}
} while()
{
for(i = ; i < LED_NUM; i++)
{
ioctl(fd[i], LED_ON);
sleep();
ioctl(fd[i], LED_OFF);
}
} for(i = ; i < LED_NUM; i++)
{
close(fd[i]);
}
return ;
}
因为这个应用程序是要在fl2440开发板上面跑的,所以Linux自带的gcc编译器编译出来的可执行文件是不行的,这个时候我们需要用交叉编译器来完成这个操作了:
[xiaohexiansheng@centos6 led_s3c2440]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc ledapp.c -o ledapp
[xiaohexiansheng@centos6 led_s3c2440]$ ls
ledapp ledapp.c Makefile s3c_led.c s3c_led.ko
将ledapp与s3c_led.ko用tftp拷贝到开发板上面,下面是在开发板上面的操作:
>: tftp -gr s3c_led.ko 192.168.1.38
s3c_led.ko 100% |*******************************| 6214 0:00:00 ETA
>: tftp -gr ledapp 192.168.1.38
ledapp 100% |*******************************| 6005 0:00:00 ETA
>: chmod 777 ledapp
>: insmod s3c_led.ko
S3C led driver[major=252] version 1.0.0 installed successfully! (这里我们可以获取到注册的主设备号是【252】)
>: Dec 31 17:01:19 root kern.debug kernel: S3C led driver use major 252
Dec 31 17:01:19 root kern.err kernel: S3C led driver[major=252] version 1.0.0 installed successfully
因为在驱动程序中没有自动的创建次设备节点,所以在这里需要手动创建次设备节点。
>: mknod /dev/led0 c 252 0
>: mknod /dev/led1 c 252 1
>: mknod /dev/led2 c 252 2
>: mknod /dev/led3 c 252 3
>: ./ledapp
fl2440字符设备led驱动的更多相关文章
- 3.字符设备led驱动
1.硬件原理图 由图可知,led1,led2,led3,led4,分别对应GPB5,GPB6,GPB7,GPB8,由s3c2440芯片手册可得到如下图所示,分别配置GPBCON和GPBDAT即可 2. ...
- 字符设备驱动另一种写法—mmap方法操作LED
最近在看韦老师的视频,讲解了很多种字符设备的驱动写法.经过自己的研究之后,我发现还有另外一种写法,直接在应用层操作,省去了内核中的地址映射部分,使得用户可以在应用层直接操作LED. mm ...
- 一步步理解linux字符设备驱动框架(转)
/* *本文版权归于凌阳教育.如转载请注明 *原作者和原文链接 http://blog.csdn.net/edudriver/article/details/18354313* *特此说明并保留对其追 ...
- 谈谈Linux字符设备驱动的实现
@ 目录 字符设备驱动基础 申请设备号 创建设备节点 在驱动中实现操作方法 文件IO调用驱动中的操作 应用程序与驱动的数据交互 内核驱动如何控制外设 控制LED的简单驱动实例 驱动程序的改进 框架复盘 ...
- Linux内核分析(五)----字符设备驱动实现
原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...
- Linux字符设备中的两个重要结构体(file、inode)
对于Linux系统中,一般字符设备和驱动之间的函数调用关系如下图所示 上图描述了用户空间应用程序通过系统调用来调用程序的过程.一般而言在驱动程序的设计中,会关系 struct file 和 struc ...
- fl2440 platform总线led字符设备驱动
首先需要知道的是,设备跟驱动是分开的.设备通过struct device来定义,也可以自己将结构体封装到自己定义的device结构体中: 例如:struct platform_device: 在inc ...
- 字符设备驱动之Led驱动学习记录
一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...
- Tiny6410 LED字符设备驱动
1.查看用户手册 led1.led2.led3.led4 连接的分别是 GPK4.GPK5.GPK6.GPK7 2.查询6410芯片手册 下面还需要3个步骤: 1.设置GPIO为OUTPUT. 将GP ...
随机推荐
- Sublime Text 3 遇到的一些小坑的解决方法
1.[不停弹出更新框]Sublime Text 3 软件会弹出“Update Available”对话框,点击“Cancel”按钮取消:取消之后还是会频繁出现 解决方法:点击菜单栏“Preferenc ...
- TCP/IP Http的区别
TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据. 关于TCP/IP和HTTP协议的关系,网络有一段比较容易理解的介绍:“我们在传输数据时,可以 ...
- Swift开发学习(二):Playground
http://blog.csdn.net/powerlly/article/details/29674253 Swift开发学习:Playground 关于 对于软件用户.游戏玩家,大家一直都在提倡用 ...
- Jasper语音助理
1. 介绍 Jasper是一款基于树莓派的开源语音控制助理, 使用Python语言开发. Jasper工作原理主要是设备被动监听麦克风, 当收到唤醒关键字时进入主动监听模式, 此时收到语音指令后进行语 ...
- 在opensuse 中安装视频解码器
最近由于需要32位的linux系统使用,很多版本的linux都不再发布32的安装镜像了,有一些又不是很熟悉,我熟悉的manjaro发布的32镜像又不是kde桌面,最后发现opensuse的滚动版本,即 ...
- 【洛谷P3709】大爷的字符串题
看这题网上居然还没人写blog,怕是都去看洛谷自带的了-- 你才是字符串!你全家都是字符串!这题跟字符串没多大关系,只是出题人lxl想要吐槽某中学而已--... 其实这题说白了就是问区间里出现最多的数 ...
- 《Java编程思想》笔记 第二十一章 并发
1.定义任务 实现Runnable 接口的类就是任务类(任务类不一定是实现Runnable接口的类). 实现Runnable 接口,重写run()方法,run方法的返回值只能是 void 任务类就是表 ...
- [mysql] 表去重
select *, count(distinct content) from comment2 group by content
- Kattis - boxes (dfn序)
Boxes There are N boxes, indexed by a number from 1 to N . Each box may (or not may not) be put into ...
- yum保留安装包方法,以及存放路径
修改/etc/yum.conf里面的参数keepcache,从0改为1. 安装包保存在/var/cache/yum文件夹下