一、嵌入式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_openn");
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_writen");
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_initn");
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 #include #include #include #include #include #include #include #include #include static int first_drv_open(struct inode *inode, struct file *file) { printk("first_drv_openn"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk("first_drv_writen"); 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_initn"); return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 } module_init(first_drv_init); module_exit(first_drv_exit); (2)Makefile 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 #include #include #include /* 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 #include #include #include #include #include #include #include #include #include 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_openn"); /* 配置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_writen"); 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"); (2)编译程序,加载驱动 3 改进 当我们想实现对硬件的多种情况操作时: (1)根据传入参数,执行多种操作 (2)创建多个设备节点,执行不同的硬件操作 代码参考: drivers_and_testledsmyleds.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 numbern"); 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 " initializedn"); return 0; } 驱动测试应用程序: #include #include #include #include /* * ledtest */ void print_usage(char *file) { printf("Usage:n"); printf("%s printf("eg. n"); printf("%s /dev/leds onn", file); printf("%s /dev/leds offn", file); printf("%s /dev/led1 onn", file); printf("%s /dev/led1 offn", 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 %sn", filename); return 0; } if (!strcmp("on", argv[2])) { // 亮灯 val = 0; write(fd, &val, 1);
上一篇:07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序
下一篇:06-S3C2440学习之移植2012u-boot到S3C2440(移植过程五--最终)
推荐阅读最新更新时间:2024-11-09 10:39
推荐帖子
- 【基于人脸识别的自动打卡健走计时系统】Maixbit/MaixPy 坑!音频播放函数阻塞调用
- 问题:阻塞循环播放音频文件,屏幕无法实时刷新摄像头采集的图像(有一段卡顿的画面)~~~尝试采用状态机一边播放,边刷新LCD,结果~~~播放的语音就不完整了(采集+LCD的时间大于44.1K播放调用的最小周期~~~)槽点:400MHz的CPU,400MHz的KPU~~~这样的效果,的确有点意外~~~究其原因,还是Sipeed/MaixPy~在micropython实现底层时,应该没有用DMA或者考虑多线程方式运行~~~,平台:Maixbit/MaixPy任务:
- alanlan86 DigiKey得捷技术专区
- MOTOROLA 单片机M68HC05 原理与应用大全
- 作者:刚寒冰,齐秋群主编出版社:北京理工大学出版社出版时间:1994页数:1006页 MOTOROLA单片机M68HC05原理与应用大全
- wzt 单片机
- 问下2个光耦,EL357跟LTV354的区别
- 问下EL357跟LTV354的区别,能不能直接替换哦问下2个光耦,EL357跟LTV354的区别如果管脚封装一样,要看手册区别在哪里,应该可以互换吧是LTV-354T吧?两者除封装尺寸和引脚排列相同外,论细节谁都不敢说一定能互换。两者最大的差别就是,EL357是单向驱动,而LTV-354T是双向的,而且允许驱动电流低至1mA时,仍能保持有≧20%的CTR。
- sky999 PCB设计
- SM1191 FM-AM 收音机集成电路
- 概述SM1191是一款用在盒式收录机上的单片集成电路。特点◆消耗电流低(VCC=3V)FM:ID=5.3mA(Typ)AM:ID=3.4mA(Typ)◆内建FM/AM选择开关◆具有较大的AF放大输出VCC=6V,EIJAoutput=500mW(Tpy)当负载阻抗为8Ω时
- rain 模拟电子
- 几个转换电路,,,,,
- 本帖最后由qwqwqw2088于2015-10-518:02编辑 此介绍3种电源转换器的设计原理图3.3V→5V电平转换器可以直接构成电平转换,往往是采用集成方案。有不同性能的电平转换器,有双向和单相配置、不同电压转换和不同速度的,用户根据需要选择最好的方案。器件间板级通信(如MCU到外设)往往靠SPI或I2C。对于SPI,采用单向电平转换器是合适的,而对于I2C,必须采用双向方案。图1说明了这两种方案。图1电平转换器3
- qwqwqw2088 模拟与混合信号
- 有在深圳做WINCE开发吗?找兼职
- 有在深圳做WINCE开发吗?我公司现在准备找一个做这方面的人来给我们开发一个程序,兼职方式,联系QQ:396638838,邮件:jzy123791@163.com有在深圳做WINCE开发吗?找兼职 顶up
- 明枫 WindowsCE
设计资源 培训 开发板 精华推荐
- 具有 75ms 软启动的 LTC3638HMSE 低输出电压纹波 250mA 稳压器的典型应用电路
- C2985579_基于ZTLC5的ZigBee模组验证板
- 单片机抢答器 原件清单&原理图
- EVAL-AD8019EB,用于 xDSL 调制解调器/DSLAM 的以太网电源驱动器评估板
- 使用 Aimtec 的 AM2M-1205D-NZ 的参考设计
- 使用 ROHM Semiconductor 的 BU4322 的参考设计
- 用于计量的 2V 差分放大器
- ADZS-CM403F-EZBRD、基于混合信号控制处理器 (DSP) 的 ADSP-CM40x EZ-Board 评估系统
- 汽车15W无线充电发射器
- LT6656BIDC-4.096、4.096V 微控制器电压基准和稳压器的典型应用