07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-LED字符设备驱动
2017-04-28 21:20
477 查看
一、嵌入式linux字符设备驱动框架
写应用程序的人 不应该去看电路图,但是如何操作硬件呢:调用驱动程序里的open,read,write等来实现。C库里实现了 open 、read、write上层函数
调用open等:swi val—引发一个异常中断,进入内核异常处理函数。
系统调用接口:根据发生中断的原因,调用处理函数(sys_open,sys_read等sys_open等函数会执行与open相关各种初始化函数,通知调用自己写好的open函数,这里注意应用程序的open不仅仅是调用驱动中的open,其他函数类似,是调用sys_open,sys_open里包含了驱动中实现的open)。
sys_open:根据调用的不同设备,执行不同的函数。
总结:
APP应用程序 调用open read(c库中实现,swi),swi val—引发一个异常中断,进入内核异常处理函数,根据发生中断的原因,调用处理函数(sys_open,sys_read等)。sys_open:根据调用的不同设备的属性,执行不同的函数。
问题:
App应用程序调用open等,最终调用的是相应设备的open,是如何对应起来的呢?依赖于驱动程序框架。
1.简单的字符驱动程序框架
主要包括以下几个部分,这一节我们先看看这几部分都有什么。第三节有完整的驱动框架代码。
(1)写出led_open,led_read等
static int first_drv_open(struct inode *inode, struct file *file) { printk("first_drv_open\n"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk("first_drv_write\n"); return 0; }
(2)怎么告诉内核呢?定义一个file_operations结构体,填充它:
static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .write = first_drv_write, };(3)怎么用起来这个结构体,就是怎么告诉内核?
//注册驱动程序
int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 printk("first_drv_init\n"); return 0; }
static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 }
参数:主设备号,名字,结构体
(4)谁来调用这个函数呢:驱动的入口函数,需要一个宏来修饰一下:
module_init(first_drv_init);
module_exit(first_drv_exit);//函数指针
二、函数对应过程分析:
查看下设备节点信息:C表示字符设备 4 主设备号 63次设备号
-常规文件
D目录.
当APP使用open的时候 首先判断设备属性是字符设备,然后根据主设备号 去数组中找到对应的file_operation结构体。
用设备类型+设备号(字符设备+主设备号)就可以找到fileoperation结构
register_chrdev最简单的实现方式:以Major为索引,把file_operation写进去,实现注册。
VFS如何根据要打开的东西,找到驱动程序:
VFS:根据字符设备数组,根据设备号找到file_operation结构
file_operation里:
入口函数里调用 regist_chrdev根据主设备号把file_operation放到数组里。
三、简单的字符驱动程序框架如下:
(1)可挂载驱动程序框架代码#include <linux/module.h>(2)Makefile
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static int first_drv_open(struct inode *inode, struct file *file) { printk("first_drv_open\n"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk("first_drv_write\n"); return 0; }
static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .write = first_drv_write, };
int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 printk("first_drv_init\n"); return 0; }
static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 }
module_init(first_drv_init);
module_exit(first_drv_exit);
KERN_DIR = /home/book/yangfei/linux-2.6.22.6 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += first_drv.o驱动编译依赖于内核文件,因此需要提前解压内核源码并编译。
(3)执行make,生成.ko文件 并拷贝至NFS文件夹
make
cp first_drv.ko/work/nfs_root/drivers_and_test/
(4)手动加载驱动程序
cat /proc/devices :
insmod first_drv.ko
(5)编写驱动测试应用程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* firstdrvtest on * firstdrvtest off */ int main(int argc, char **argv) { int fd; int val = 1; fd = open("/dev/xxx", O_RDWR); if (fd < 0) { printf("can't open!\n"); } write(fd, &val, 4); return 0; }
arm-linux-gcc -ofirstdrvtest firstdrvtest.c
cp firstdrvtest/work/nfs_root/
挂载nfs服务器
mount -t nfs -o nolock192.168.1.102:/work/nfs_root /mnt
创建设备节点:
# mknod /dev/xxx c 252 0 ---》/dev会出现xxx设备
ls /dev/
测试环境:
格式化nand,烧写光盘自带linux2.26、qt yaffs、uboot1.1.6,gcc:3.4.5.
四、思考与改进
(1)主设备号怎么定cat/proc/devices 找一个没用的 手工指定主设备号
写0,由系统分配 自动分配主设备号
(2)dev/xxx怎么来的
应用程序
打开某个设备:open(dev/xxx)
首先要:
手工创建设备节点:mknod /dev/xxx c 252 0
但是当我们使用自动分配设备号的时候 ,我们都要cat /proc/devices查看主设备号名字,然后再创建设备节点吗?这样显然不合适。
自动创建设备节点,udex机制(mdev机制)
注册设备的时候,会在/sys下生成设备信息,
mdev:自动根据系统信息创建设备节点。
驱动程序里,提供设备信息,mdev就可以自动创建设备节点。
如何提供设备信息呢:
入口函数,先创建一个类—》类下面创建一个设备(提供系统信息)
class_create会在/sys下创建 firstdrv这个类,class_create类下创建xyz这个设备,mdev会自动创建一个dev/xyz设备节点
出口函数:
编译报错:
解决方法:
五、嵌入式linux led字符设备驱动
前面写好了字符设备驱动程序框架,现在实现led字符设备驱动:1、需要做什么
搭框架
完善硬件操作
(1)opne中实现端口输入输出配置
(2)入口函数:完成虚拟地址映射
(3)write中实现LED操作
copy_from_user 用户空间到内核空间传递数据。
copy_to_user(); 内核空间到用户空间传递数据
2代码实现
(1)驱动完整代码:
#include <linux/module.h>(2)编译程序,加载驱动
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配置GPF4,5,6为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .write = first_drv_write, };
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpfcon);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
3 改进
当我们想实现对硬件的多种情况操作时:
(1)根据传入参数,执行多种操作
(2)创建多个设备节点,执行不同的硬件操作
代码参考:
drivers_and_test\leds\myleds.c
/* * 执行insmod命令时就会调用这个函数 */ static int __init s3c24xx_leds_init(void) //static int __init init_module(void) { int ret; int minor = 0; gpio_va = ioremap(0x56000000, 0x100000); if (!gpio_va) { return -EIO; } /* 注册字符设备 * 参数为主设备号、设备名字、file_operations结构; * 这样,主设备号就和具体的file_operations结构联系起来了, * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数 * LED_MAJOR可以设为0,表示由内核自动分配主设备号 */ ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops); if (ret < 0) { printk(DEVICE_NAME " can't register major number\n"); return ret; } leds_class = class_create(THIS_MODULE, "leds"); if (IS_ERR(leds_class)) return PTR_ERR(leds_class); leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds"); /* /dev/leds */ for (minor = 1; minor < 4; minor++) /* /dev/led1,2,3 */ { leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor); if (unlikely(IS_ERR(leds_class_devs[minor]))) return PTR_ERR(leds_class_devs[minor]); } printk(DEVICE_NAME " initialized\n"); return 0; }
驱动测试应用程序:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* * ledtest <dev> <on|off> */ void print_usage(char *file) { printf("Usage:\n"); printf("%s <dev> <on|off>\n",file); printf("eg. \n"); printf("%s /dev/leds on\n", file); printf("%s /dev/leds off\n", file); printf("%s /dev/led1 on\n", file); printf("%s /dev/led1 off\n", file); } int main(int argc, char **argv) { int fd; char* filename; char val; if (argc != 3) { print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("error, can't open %s\n", filename); return 0; } if (!strcmp("on", argv[2])) { // 亮灯 val = 0; write(fd, &val, 1); } else if (!strcmp("off", argv[2])) { // 灭灯 val = 1; write(fd, &val, 1); } else { print_usage(argv[0]); return 0; } return 0; }
到此,我们实现了简单的字符设备驱动框架,并在框架上加入了led硬件操作,实现了led字符设备驱动的编写与驱动应用程序的编写。
六、需要注意的几点
(1)以下两个函数实现了在内核中加入设备信息,mdev就可以根据/sys下生成的设备信息,自动创建设备节点firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
(2)查看设备驱动设备:cat /proc/devices (主设备号+设备名称)
查看设备节点:ls /dev/xxx
(3)卸载驱动
rmmod first_drv
(4)卸载设备节点
rm /dev/xxx
(5)APP应用程序打开的open函数,打开的设备节点,而不是驱动程序。
(6)arm单板和虚拟机之间通过网络文件系统NFS传递文件:
mount -t nfs -o nolock192.168.1.102:/work/nfs_root /mnt
(7)编译驱动程序的内核和单板运行的内核要一致
编译驱动测试程序的工具链版本和文件系统的版本要一致,不然应用程序可能会缺少某些库而无法运行
(8)驱动程序框架有自己的函数,就是这么用,记住就可以,所以入门应该是比较简单的。
(9)应用程序的open是如何找到驱动程序的open并执行的。
注册驱动程序:在数组中以主设备号为索引,file_operation为内容,进行注册
open的时候,根据要打开的设备节点的类型,主设备号在数组中找到对应的file_operation结构体,并使用里面对应的open函数。
(11)自动创建设备节点的时候,会在/sys下产生设备信息,以便以创建设备节点。
相关文章推荐
- 07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序之异步通知机制+原子操作+互斥信号量+阻塞与非阻塞+定时器去抖
- 07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-查询+中断+引入poll机制的按键驱动程序
- 嵌入式Linux字符设备入门之--LED驱动详解
- 嵌入式Linux驱动学习之路(十)字符设备驱动-my_led
- 嵌入式Linux字符设备驱动LED驱动编写
- 11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结
- Linux嵌入式驱动初体验(七)--- LED驱动之字符设备篇
- 嵌入式Linux字符设备驱动LED驱动编写
- linux-3.0.1下ok6410的led字符设备驱动
- s3c2440基于linux的gpio led字符设备驱动实践
- 【嵌入式Linux驱动开发】三、字符设备驱动(二)
- Linux字符设备驱动之LED驱动
- 手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动
- Linux驱动开发-OK6410-LED字符设备驱动实现过程
- 手把手教你学linux驱动开发 OK6410系列之03---LED字符设备驱动
- “手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动
- s3c2440基于linux的button和led字符设备驱动
- “手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动 .
- 一步一步学习 Linux 驱动之字符设备 LED
- Linux驱动程序开发(4) - 字符设备驱动(3)-LED设备驱动和应用程序