首先要明白字符设备驱动注册的基本流程

当我们调用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驱动的更多相关文章

  1. 3.字符设备led驱动

    1.硬件原理图 由图可知,led1,led2,led3,led4,分别对应GPB5,GPB6,GPB7,GPB8,由s3c2440芯片手册可得到如下图所示,分别配置GPBCON和GPBDAT即可 2. ...

  2. 字符设备驱动另一种写法—mmap方法操作LED

    最近在看韦老师的视频,讲解了很多种字符设备的驱动写法.经过自己的研究之后,我发现还有另外一种写法,直接在应用层操作,省去了内核中的地址映射部分,使得用户可以在应用层直接操作LED.        mm ...

  3. 一步步理解linux字符设备驱动框架(转)

    /* *本文版权归于凌阳教育.如转载请注明 *原作者和原文链接 http://blog.csdn.net/edudriver/article/details/18354313* *特此说明并保留对其追 ...

  4. 谈谈Linux字符设备驱动的实现

    @ 目录 字符设备驱动基础 申请设备号 创建设备节点 在驱动中实现操作方法 文件IO调用驱动中的操作 应用程序与驱动的数据交互 内核驱动如何控制外设 控制LED的简单驱动实例 驱动程序的改进 框架复盘 ...

  5. Linux内核分析(五)----字符设备驱动实现

    原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...

  6. Linux字符设备中的两个重要结构体(file、inode)

    对于Linux系统中,一般字符设备和驱动之间的函数调用关系如下图所示 上图描述了用户空间应用程序通过系统调用来调用程序的过程.一般而言在驱动程序的设计中,会关系 struct file 和 struc ...

  7. fl2440 platform总线led字符设备驱动

    首先需要知道的是,设备跟驱动是分开的.设备通过struct device来定义,也可以自己将结构体封装到自己定义的device结构体中: 例如:struct platform_device: 在inc ...

  8. 字符设备驱动之Led驱动学习记录

    一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...

  9. Tiny6410 LED字符设备驱动

    1.查看用户手册 led1.led2.led3.led4 连接的分别是 GPK4.GPK5.GPK6.GPK7 2.查询6410芯片手册 下面还需要3个步骤: 1.设置GPIO为OUTPUT. 将GP ...

随机推荐

  1. 智能合约安全-parity多重签名钱包安全漏洞

    漏洞原因: 因为initWallet函数是公开函数( public function) , 攻击者调用initWallet,重新初始化钱包会把之前合约钱包所有者覆盖, 即可改变钱包所有者. 漏洞代码: ...

  2. Codeforces 950E Data Center Maintenance 强连通分量

    题目链接 题意 有\(n\)个信息中心,每个信息中心都有自己的维护时间\((0\leq t\lt h)\),在这个时刻里面的信息不能被获得. 每个用户的数据都有两份备份,放在两个相异的信息中心(维护时 ...

  3. MSP432P401R时钟入门

    拿到msp432的板子差不多一年了,刚刚进行了开机点亮LED工程:         首先是msp432的时钟模块(CS),个人理解msp432最特色的功能应该是超低功耗和高性能的组合.432系列的时钟 ...

  4. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  5. UVALIVE 4330 Timer

    Description   Recently, some archaeologists discovered an ancient relic on a small island in the Pac ...

  6. ubuntu下wireshark+scapy+tcpreply

    安装wireshark命令: sudo apt-get install wireshark 运行打开wireshark命令: sudo wireshark(一定要以超级权限打开) 正确打开的窗口应该默 ...

  7. 原型和JS内置对象

    原型 1.定义 每一个对象都有原型 原型仍然是一个对象 模拟实现面向对象的继承性 2.原型链 对象的原型还有原型 对象除了可以使用自有属性还可以继承原型上的属性 3.获取原型 对象.__proto__ ...

  8. sql 获取字符串首字母,循环

    //字符串首字母 CREATE FUNCTION GetInitialLetter(@ChineseString NVARCHAR()) RETURNS NVARCHAR() AS BEGIN DEC ...

  9. 【C++】多重继承

    1. 多重继承时的二义性 当使用多重继承时,如果多个父类都定义了相同名字的变量,则会出现二义性.解决方法:使用 :: 声明作用域 #include <iostream> using nam ...

  10. 训练指南 UVALive - 3523 (双联通分量 + 二分图染色)

    layout: post title: 训练指南 UVALive - 3523 (双联通分量 + 二分图染色) author: "luowentaoaa" catalog: tru ...