Linux设备驱动第四天(自动创建设备节点、LED驱动程序)
原文地址:http://blog.csdn.net/PZ0605/article/details/53343077
回顾:
与驱动有关的几个重要结构体
1,struct cdev //从软件上代表硬件设备
{
dev_t dev;//设备号 = 主设备号+次设备号
struct file_operations f_ops;
}
2,struct file_operations{
open
read
write
release
ioctrl
mmap
}
3,struct file//对应内核里面的文件表中的一项
{
f_pos;//读写的偏移位置
f_op;//文件的操作函数集合
f_flags;//文件打时传递的参数 读写模式
private_data// 私有数据
…..
}
4,struct inode
{
dev_t i_rdev;//设备号
file_opeartions *i_fop;//操作函数集合,内核会给它赋值
struct cdev *i_cdev;//字符设备集合
….
}
以上两个结构体多用于一套驱动代码驱动多个同类型的设备。如下面的案例。
问题:找到串口驱动程序源码文件
KCofnfig:make menuconfig 时生成菜单选项
Makefile:和源码在同一级目录,决定编译动作
make menuconfig
-> Device Drivers
->Character devices
->Samsung S5PV210 Serail port support
宏: CONFIG_SERIAL_S5PV210
路径:devices/serail/Kconfig确定源码在drivers/serail/目录下
打开该路径下的Makefile查找 CONFIG_SERIAL_S5PV210,找到对应的.c文件
Uart案例:
创建设备节点
mknod /dev/serial0 c 204 64 ==> open(“/dev/serial0”); uart_open
mknod /dev/serial1 c 204 65 ==> open(“/dev/serial1”); uart_open
mknod /dev/serial2 c 204 66 ==> open(“/dev/serial2”); uart_open
mknod /dev/serial3 c 204 67 ==> open(“/dev/serial3”); uart_open
其中open(“/dev/serial0”)为应用程序中的代码;
在uart_open中如何确定初始化哪一个串口?
通过设备号来确定初始化哪一个串口。
如何拿到设备号?
在uart_open方法中可以通过传递进来的inode->i_rdev拿到设备号,通过设备号拿到次设备号
在urat_write中如何确定通过哪串口向外发送数据?
#include<linux/init.h>
#include<linux/module.h>
static int uart_open(struct inode *inode, struct file *file)
{
MINOR(inode -> i_rdev);//次设备号
//private_data这块空间留给驱动开发人员使用的,放什么数据都可以
file->private_data = MINOR(inode -> i_rdev);//记录要操作的设备
//初始化相应寄存器
return 0;
}
static int uart_close(struct inode *inode, struct file *file)
{
printlk("enter uart_close! \n");
return 0;
}
static ssize_t uart_write (struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
return 0;
}
static ssize_t uart_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
return 0;
}
static int uart_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
printlk("enter led_ioctl! \n");
}
//需要实现的函数
static struct file_operations led_fops = {
.owner = THIS_MOUDLE,
.open = uart_open,
.release = uart_close,
.write = uart_write,
.read = uart_read,
.ioctl = uart_ioctl,
};
static int major = 0;
/*1,分配一个cdev */
static struct cdev led_cdev;
static int led_status;//记录灯的状态
static int chardevice_init(void){
dev_t dev;
if(major != 0){//静态分配
dev = MKDEV(major,0);//构建一个设备号
//向内核注册设备号
register_chrdev_region(dev,1,"tarena");
}else{
//动态申请注册设备号
alloc_chrdev_region(&dev,100,1,"test");
major = MAJOR(dev);//获取主设备号 相当于dev>>20
unsigned int minor = MINOR(dev);//获取次设备号 相当于把高20位清0
printk("major = %d ! \n",major);
}
//2, 初始化cdev
cdev_init(&led_cdev,&led_fops);
//3,把cdev添加到内核里面去
cdev_add(&led_cdev,dev,1);
//申请GPIO管脚
gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3
//配置为输出
gpio_dirction_output(S5PV210_GPC1(3),0);
led_status = 0;
return 0;
}
static void chardevice_exit(void){
dev_t dev;
//4,按照对内核产生的影响,逆序销毁cdev
cdev_del(&led_cdev);
dev = MKDEV(major,0);
//释放
unregist_chrdev_region(dev,1);
}
module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
自动创建设备文件
手工创建设备文件:mknode /dev/xxx c major minor
自动创建:在安装模块时自动创建
编程的角度,需要调用以下两个函数:
class_create(….);//生成一个树枝
deivce_create(…)//生成一个果实
class_destroy(...);
device_destroy(...);
其他要完成的工作
1,用busybox添加,要选择支持medv
2,rootfs/ect/rcS添加mount -a
3,rootfs/etc添加fstab文件
4, 在fstab中添加sysfs和proc支持
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
5,在rootfs/ect/rcS中添加
echo /sbin/mdev > / proc/sys/kernel/hotplub
当执行insmod xx.ko时,如果xx_init函数中包含了class_create(THIS_MODULE,”tanena”),生成文件夹:/sys/class/tarena;当执行device_create(cls,NULL,dev,NULL,”leds”),会生成文件夹:/sys/class/tarena/leds;以上操作内核也理解为热插拔事件,产生热插拔事件以后,内核会调用/proc/sys/kernel/hotplug,实则就是调用/sbin/mdev程序,该程序扫描/sys/目录下的变化,根据变化去自动在/dev目录下创建leds文件;
procfs基于内存的文件系统,导出内核的执行信息;如:cat /proc/cpuinfo
sysfs基于内存的文件系统,导出系统中硬件驱动的组织结构;
案例:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/cdev.h>
#include<linux/kernel.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<plat/gpio-cfg.h>
#include<asm/gpio.h>
static struce class *cls;//记录树枝 (其实属于一个设备类)
static int led_open(struct inode *inode, struct file *file)
{
printlk("enter led_open! \n");
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
printlk("enter led_close! \n");
return 0;
}
//假如在用户空间写入:write(fd,buf,1) buf中的值1亮 0 灭
static ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
int cmd;
copy_from_user(&cmd,buf,sizeof(cmd));//将用户空间的数据拷贝到内核空间
if(cmd == 1){
gpio_set_value(S5PV210_GPC1(3),1);//点亮
led_status = 1;
}else{
gpio_set_value(S5PV210_GPC1(3),-);
led_status = 0;
}
return count;
}
//read(fd,buf,cnt)
static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
//把灯的状态返回给用户空间
copy_to_user(buf,&led_status,sizeof(led_status));
return count;
}
static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
printlk("enter led_ioctl! \n");
}
//需要实现的函数
static struct file_operations led_fops = {
.owner = THIS_MOUDLE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
.ioctl = led_ioctl,
};
static int major = 0;
/*1,分配一个cdev */
static struct cdev led_cdev;
static int led_status;//记录灯的状态
static int chardevice_init(void){
dev_t dev;
if(major != 0){//静态分配
dev = MKDEV(major,0);//构建一个设备号
//向内核注册设备号
register_chrdev_region(dev,1,"tarena");
}else{
//动态申请注册设备号
alloc_chrdev_region(&dev,100,1,"test");
major = MAJOR(dev);//获取主设备号 相当于dev>>20
unsigned int minor = MINOR(dev);//获取次设备号 相当于把高20位清0
printk("major = %d ! \n",major);
}
//2, 初始化cdev
cdev_init(&led_cdev,&led_fops);
//3,把cdev添加到内核里面去
cdev_add(&led_cdev,dev,1);
//自动创建设备节点文件
cls = class_create(THIS_MODULE,"tarena");//见到owner就传THIS_MODULE
device_create(cls,NULL,dev,NULL,"leds");//这个设备要挂到哪个枝上(cls),父设备为NULL,
//申请GPIO管脚
gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3
//配置为输出
gpio_dirction_output(S5PV210_GPC1(3),0);
led_status = 0;
return 0;
}
static void chardevice_exit(void){
dev_t dev;
gpio_free(S5pV210_GCP(3));
dev = MKDEV(major,0);
//自动删除设备节点,注意要逆序销毁
device_destory(cls,dev);//相当于从这个树枝(cls)把水果摘掉(dev)
class_destory(cls);//砍树枝
//4,按照对内核产生的影响,逆序销毁cdev
cdev_del(&led_cdev);
//释放
unregist_chrdev_region(dev,1);
}
module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
然后Makefile文件,将ko文件拷贝到开发板,然后在开发板中执行。。。
LED标准程序的写法:
关于uart用户空间编程
串口初始化 xxx_init 或 open
通过串口发送数据 write函数
通过串口接收数据 read函数
修改传输的速率 有效位个数 校验方式 ioctl
led_open(){
gpio_reqiest(….);
gpio_direction_output(…);
}
led_close(){
gpio_free(…);
}
led_ioctl(inode,filep,cmd,val){
if(cmd == 1){//亮
if(val == 1){//led1_on 第一个灯亮
}else if(val == 2 ){//led2_on 第二个灯亮
}
}else if(cmd == 0){
}
}
led_read(){//来获取灯的亮灭状态
int led_status
}
init(){
1,动态申请设备号
2,创建并初始化cdev
3,向内核添加cdev
4,自动创建设备节点文件
}
exit(){
逆序消除init函数中对内核的影响
}
案例:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/cdev.h>
#include<linux/kernel.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<plat/gpio-cfg.h>
#include<asm/gpio.h>
#define LED_ON 0x100001;
#define LED_OFF 0x100002;
#define LED_STATUS 0x100003;
static struce class *cls;//记录树枝 (其实属于一个设备类)
static int led_status;//记录灯的状态
static int led_open(struct inode *inode, struct file *file)
{
//6,申请GPIO管脚
gpio_request(S5PV210_GPC1(3),"LED1");
gpio_request(S5PV210_GPC1(4),"LED2");
//7,配置为输出
gpio_direction_output(S5PV210_GPC1(3),0);
gpio_direction_output(S5PV210_GPC1(4),0);
led_status = 0;
printlk("enter led_open! \n");
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
//8,释放GPIO管脚
gpio_free(S5PV210_GPC1(3));
gpio_free(S5PV210_GPC1(4));
printlk("enter led_close! \n");
return 0;
}
//read(fd,buf,cnt)
static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
int ret;
//把灯的状态返回给用户空间
ret = copy_to_user(buf,&led_status,sizeof(led_status));
return count;
}
/**
*用户空间调用ioctl(fd,cmd,&val);
*产生软中断,调用sys_ioctl
* sys_ioctl(...){
* led_ioctl(inode,file,cmd,val);
* }
***/
static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
int index = 0;
int ret = 0;
int status = 0;
//因为不能直接操作用户空间的地址,
//所以把用户空间的4个字节的数据拷贝到index里面,这就得到了具体操作了哪个灯
ret = copy_from_user(&index,(int *)val,4);
if(index > 1 || index <0 ){//只能为0 1,其他的非法
return -EINVAL;//返回非法参数
}
switch(cmd){
case: LED_ON:
gpio_set_value(S5PV210_GPC(3)+index,1);
led_status |= (0x01<<index);
break;
case: LED_OFF:
gpio_set_value(S5PV210_GPC(3)+index,0);
led_status &= ~(0x01<<index);
break;
case: LED_STATUS:
// printk("status = %d \n",status);
if(led_status & (0x01<<index)){
status = 1;
}else{
status = 0;
}
// printk("status = %d \n",status);
ret = copy_to_user((int*)val,&status,4);
break;
default:
return -EINVAL;
}
return 0;
}
//需要实现的函数
static struct file_operations led_fops = {
.owner = THIS_MOUDLE,
.open = led_open,
.release = led_close,
//.write = led_write,
.read = led_read,
.ioctl = led_ioctl,
};
static int major = 0;
/*2,分配一个cdev */
static struct cdev led_cdev;
static int chardevice_init(void){
dev_t dev;
//1,动态申请注册设备号
alloc_chrdev_region(&dev,100,1,"test");
major = MAJOR(dev);//获取主设备号 相当于dev>>20
// unsigned int minor = MINOR(dev);//获取次设备号 相当于把高20位清0
//3, 初始化cdev
cdev_init(&led_cdev,&led_fops);
//4,把cdev添加到内核里面去
cdev_add(&led_cdev,dev,1);
//5,自动创建设备节点文件
cls = class_create(THIS_MODULE,"tarena");//见到owner就传THIS_MODULE
//"leds" 就是/dev要生成的设备文件的名字
device_create(cls,NULL,dev,NULL,"leds");//这个设备要挂到哪个枝上(cls),父设备为NULL
return 0;
}
static void chardevice_exit(void){
dev_t dev;
dev = MKDEV(major,100);
//自动删除设备节点,注意要逆序销毁
device_destory(cls,dev);//相当于从这个树枝(cls)把水果摘掉(dev)
class_destory(cls);//砍树枝
//10,从内核中删除led_cdev 按照对内核产生的影响,逆序销毁cdev
cdev_del(&led_cdev);
//注销设备号
unregist_chrdev_region(dev,1);
}
module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
对应的测试程序:
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#define LED_ON 0x100001;
#define LED_OFF 0x100002;
#define LED_STATUS 0x100003;
int main(void){
int fd;
int cmd = 0;
int val = ;
fd = open("/dev/leds",o_RDWR);
if(fd<0){
printlf("open /dev/leds failed");
return -1;
}
//LED2 on
cmd = LED_ON;
val = 1;//代表第二个灯
ioctl(fd,cmd,&val);//cmd对应驱动程序ioctl方法中的cmd,val对应驱动程序的val
cmd = LED_STATUS;
ioctl(fd,cmd,&val);//cmd对应驱动程序ioctl方法中的cmd,val对应驱动程序的val
if(val = 1){
printf("LED2 ON \n");
}else if(val = 0){
printf("LED2 OFF \n");
}
sleep(5);
//LED1 OFF
cmd = LED_OFF;
val = 1;
ioctl(fd,cmd,&val);
cmd = LED_STATUS;
ioctl(fd,cmd,&val);
if(val = 0){
printf("LED2 OFF\n");
}else{
printf("LED2 ON\n");
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
作业:计算S5PV210_GPC1(3)的值是多少并验证(打印)
- ').addClass('pre-numbering').hide();