手把手写一个LED驱动(1)

news2024/12/23 20:33:12

目录

1.开启驱动开发之路

1.1、驱动开发的准备工作

1.2、驱动开发的步骤

1.3、实践

2.最简单的模块源码分析

2.1、常用的模块操作命令

2.2、模块的安装

2.3、模块的版本信息vermagic

2.4、模块卸载

2.5、模块中常用宏(MODULE_xxx这种宏的作用是用来添加模块描述信息)

2.6、函数修饰符

2.7、static

2.8、printk函数

2.9、关于驱动模块中的头文件

2.10、驱动编译的Makefile分析

3.用开发板来调试模块

3.1、设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage

3.2、设置bootargs使开发板从nfs去挂载rootfs(内核配置记得打开使能nfs形式的rootfs)

3.3、修改Makefile中的KERN_DIR使其指向自己建立的内核源码树

3.4、将自己编译好的驱动.ko文件放入nfs共享目录下去

3.5、开发板启动后使用insmod、rmmod、lsmod等去进行模块实验

4.字符设备驱动工作原理

4.1、系统整体工作原理

4.2、file_operations结构体(include/linux/fs.h)

4.3、注册字符设备驱动

4.4、register_chrdev详解(#include )

4.5、内核如何管理字符设备驱动

4.6、/proc文件系统的作用

5.字符设备驱动代码实践

5.1、思路和框架

5.2、如何动手写驱动代码

5.3、开始动手

5.4、注册驱动

5.5、驱动测试

5.6、让内核自动分配主设备号

6.应用程序如何调用驱动

6.1、驱动设备文件的创建

6.2、写应用来测试驱动

6.3、总结

7.添加读写接口

7.1、在驱动中添加.read和.write实际操作方法

7.2、在应用中添加read,write

7.3、测试

7.4、应用和驱动之间的数据交换

8.读写接口实践

8.1、完成write和read函数

8.2、读写回环测试

8.3、总结

9.驱动中如何操控硬件

9.1、还是那个硬件

9.2、驱动操作硬件和裸机操作硬件哪里不同了?

9.3、内核的虚拟地址映射方法

9.4、如何选择虚拟地址映射方法

10.静态映射操作LED

10.1、关于静态映射要说的

10.2、三星版本内核中的静态映射表

10.3、添加LED操作代码

10.4、实践测试

10.5、将代码移动到open和close函数中去

10.6、添加驱动中的写函数

10.7、写应用来测试写函数

11.动态映射操作LED

11.1、如何建立动态映射

11.2、如何销毁动态映射

11.3、代码实践


1.开启驱动开发之路

1.1、驱动开发的准备工作

(1)正常运行linux系统的开发板。要求开发板中的linux的zImage必须是自己编译的,不能是别人编译的。

(2)内核源码树,其实就是一个经过了配置编译之后的内核源码。

(3)开发板使用nfs挂载主机rootfs,主机ubuntu中必须搭建一个nfs服务器。

1.2、驱动开发的步骤

(1)驱动源码编写、Makefile编写、编译

(2)insmod装载模块、测试、rmmod卸载模块

1.3、实践

(1)使用九鼎提供的内核

make x210ii_qt_defconfig配置内核,make编译内核

完成后得到了:1、内核源码树。2、编译ok的zImage(arch/arm/boot/目录下)

(2)fastboot将第1步中得到的zImage烧录到开发板中去启动(或者将zImage丢到tftp的共享目录,uboot启动时tftp下载启动),将来驱动编译好后,就可以在这个内核中去测试。因为这个zImage和内核源码树是一伙的,所以驱动安装时版本校验不会出错。

编写驱动

module_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit

// 模块安装函数
static int __init chrdev_init(void)
{    
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //等价于
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");

    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("chm");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

makefile

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 开发板的linux内核的源码树目录
KERN_DIR = /root/driver/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	

2.最简单的模块源码分析

2.1、常用的模块操作命令

(1)lsmod(list module,将模块列表显示),功能是打印出当前内核中已经安装的模块列表

(2)insmod(install module,安装模块),功能是向当前内核中去安装一个模块,用法是insmod xxx.ko

(3)modinfo(module information,模块信息),功能是打印出一个模块源文件的自带信息。,用法是modinfo xxx.ko

license:许可证 对应源代码中的MODULE_LICENSE("GPL");这个宏

depengs:依赖

vermagic:版本信息

(4)rmmod(remove module,卸载模块),功能是从当前内核中卸载一个已经安装了的模块,用法是rmmod xxx(注意卸载模块时只需要输入模块名即可,不能加.ko后缀)

(5)剩下的后面再说,暂时用不到(如modprobe、depmod等)

2.2、模块的安装

(1)先lsmod再insmod看安装前后系统内模块记录。实践测试标明内核会将最新安装的模块放在lsmod显示的最前面。

(2)insmod与module_init宏。模块源代码中用module_init宏声明chrdev_init这个函数,作用是将声明的函数和insmod命令绑定起来,也就是说当我们insmod module_test.ko时,insmod命令内部实际执行的操作就是帮我们调用module_init所声明的函数,即调用chrdev_init。所以实际安装模块操作是要靠自己去写的,而不是OS帮我们安装。

照此分析,那insmod时就应该能看到chrdev_init中使用printk打印出来的一个chrdev_init字符串,但是实际没看到。原因是ubuntu中拦截了,要怎么才能看到呢?在ubuntu中使用dmesg命令就可以看到了。

(3)模块安装时insmod内部除了帮我们调用module_init宏所声明的函数外,实际还做了一些别的事(譬如lsmod能看到多了一个模块也是insmod帮我们在内部做了记录),但是我们就不用管了。

2.3、模块的版本信息vermagic

(1)使用modinfo查看模块的版本信息

(2)内核zImage中也有一个确定的版本信息

(3)insmod时模块的vermagic必须和内核的相同,否则不能安装,报错信息为:insmod: ERROR: could not insert module module_test.ko: Invalid module format

(4)模块的版本信息是为了保证模块和内核的兼容性,是一种安全措施

(5)如何保证模块的vermagic和内核的vermagic一致?

编译模块的内核源码树就是我们编译正在运行的这个内核的那个内核源码树即可。

说白了就是模块和内核要同出一门。

2.4、模块卸载

(1)module_exit和rmmod的对应关系

(2)lsmod查看rmmod前后系统的模块记录变化

2.5、模块中常用宏(MODULE_xxx这种宏的作用是用来添加模块描述信息)

(1)MODULE_LICENSE,模块的许可证。一般声明为GPL许可证,而且最好不要少,否则可能会出现莫名其妙的错误(譬如一些明显存在的函数提示找不到)。

(2)MODULE_AUTHOR,描述模块的作者

(3)MODULE_DESCRIPTION,描述模块的介绍信息,描述这个模块是干嘛的

(4)MODULE_ALIAS,描述模块的别名信息

2.6、函数修饰符

(1)__init,本质上是个宏定义,在内核源代码中就有#define __init xxxx,在include/linux/init.h中定义。这个__init的作用就是将被他修饰的函数放入.init.text段中去(本来默认情况下函数是被放入.text段中)。

#define         __init                    __section(.init.text) __cold notrace

#define         __section(S)         attribute ((section(#S)))

 

整个内核中的所有的这类函数都会被链接器链接放入.init.text段中,所以所有的内核模块的__init修饰的函数其实是被统一放在一起的。好处是insmod加载完.init.text段中的这些模块安装函数后,就可以把这个函数从这个段释放掉以节省内存。

(2)__exit

同上

2.7、static

修饰函数或全局变量时表示,将作用域由全局到文件

2.8、printk函数

(1)printk在内核源码中用来打印信息的函数,用法和printf非常相似。

(2)printk和printf最大的差别:printf是C库函数,是在应用层编程中使用的,基于API函数之上工作,不能在linux内核源代码中使用;printk是linux内核源代码中自己封装出来的一个打印函数,是内核源码中的一个普通函数,只能在内核源码范围内使用,不能在应用编程中使用。

(3)printk相比printf来说还多了个:打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭,一般用条件编译来实现(DEBUG宏),但是在内核中,因为内核非常庞大,打印信息非常多,有时候整体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。所以才有了打印级别这个概念。

(4)操作系统的命令行中也有一个打印信息级别属性,值为0-7。当前操作系统中执行printk的时候会去对比printk中的打印级别和我的命令行中设置的打印级别,小于我的命令行设置级别的信息会被放行打印出来,大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4,那么printk中设置的级别比4小的就能打印出来,比4大的就不能打印出来。

用以下命令查看打印级别

(5)ubuntu中这个printk的打印级别控制没法实践,ubuntu中不管你把级别怎么设置都不能直接打印出来,必须dmesg命令去查看。

2.9、关于驱动模块中的头文件

#include // module_init module_exit

#include // __init __exit

(1)驱动源代码中包含的头文件和原来应用编程程序中包含的头文件不是一回事。应用编程中包含的头文件是应用层的头文件,是应用程序的编译器带来的(譬如gcc的头文件路径在 /usr/include下,这些东西是和操作系统无关的)。驱动源码属于内核源码的一部分,驱动源码中的头文件其实就是内核源代码目录下的include目录下的头文件。

2.10、驱动编译的Makefile分析

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build    

        
# 开发板的linux内核的源码树目录
KERN_DIR = /root/driver/kernel

obj-m    += module_test.o

all:
    make -C $(KERN_DIR) M=`pwd` modules 

cp:
    cp *.ko /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean    
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean

3.用开发板来调试模块

3.1、设置bootcmd使开发板通过tftp下载自己建立的内核源码树编译得到的zImage

set bootcmd 'tftp 0x30008000 zImage;bootm 0x30008000'

3.2、设置bootargs使开发板从nfs去挂载rootfs(内核配置记得打开使能nfs形式的rootfs)

setenv bootargs root=/dev/nfs nfsroot=192.168.1.30:/root/porting_x210/rootfs/rootfs ip=192.168.1.20:192.168.1.30:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200

3.3、修改Makefile中的KERN_DIR使其指向自己建立的内核源码树

3.4、将自己编译好的驱动.ko文件放入nfs共享目录下去

3.5、开发板启动后使用insmod、rmmod、lsmod等去进行模块实验

4.字符设备驱动工作原理

4.1、系统整体工作原理

(1)应用层->API->设备驱动->硬件

(2)API:open、read、write、close等

(3)驱动源码中提供真正的open、read、write、close等函数实体

4.2、file_operations结构体(include/linux/fs.h)

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};

API open-》file_operations open函数指针-》驱动中的open函数

(1)元素主要是函数指针,用来挂接实体函数地址

(2)每个设备驱动都需要一个该结构体类型的变量

(3)设备驱动向内核注册时提供该结构体类型的变量

我儿子出生,向国家注册身份证,国家提供民政局让我办身份证

我编写的驱动,向内核注册,函数提供注册函数然我调用

4.3、注册字符设备驱动

(1)为何要注册驱动,让内核知道这个驱动的存在

(2)谁去负责注册,我

(3)向谁注册,内核

(4)注册函数从哪里来,内核提供register_chrdev注册函数,这个函数在fs.h中

(5)注册前怎样?注册后怎样?注册产生什么结果?

注册前:

内核查不到,应用程序也不能调用

注册后:

就可以了

4.4、register_chrdev详解(#include <linux/fs.h> )

static inline int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
{
    return __register_chrdev(major, 0, 256, name, fops);
}

(1)作用,驱动向内核注册自己的file_operations结构体

(2)参数

major:主设备号,用来表征当前这个设备的编号,可以由自己指定,也可以让内核自动分配

name:当前设备驱动的名字

fops:是一个指向file_operations类型的指针,将来会指向file_operations类型的变量

(3)inline和static

之所以用inline修饰的原因:

1.函数体太短,为了减少函数调用的开销

2.这个函数定义在头文件中,如果不加inline的话,容易由于被多个C文件包含而造成重复定义的错误,加上inline后就不会有这个问题

4.5、内核如何管理字符设备驱动

(1)内核中有一个数组用来存储注册的字符设备驱动,数组大小为255

(2)register_chrdev内部将我们要注册的驱动的信息(主要是 )存储在数组中相应的位置

(数组元素和主设备号是相对应的)

(3)cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)

(4)好好理解主设备号(major)的概念

1.是设备的编号

2.是内核用来管理驱动的数组的下标

4.6、/proc文件系统的作用

虚拟文件系统,并不是真实存在硬盘上面的文件,它里面的文件都是内核用数据结构虚拟出来的文件,通过cat来读取这些文件,其实是在读取这些数据结构的内容

比如:cat /proc/devices用来读取内核中管理设备驱动的数组的内容

5.字符设备驱动代码实践

5.1、思路和框架

(1)目的:给空模块添加驱动壳子

(2)核心工作量:file_operations及其元素填充、注册驱动

5.2、如何动手写驱动代码

(1)脑海里先有框架,知道自己要干嘛

(2)细节代码不需要一个字一个字敲,可以到内核中去寻找参考代码复制过来改

(3)写下的所有代码必须心里清楚明白,不能似懂非懂

5.3、开始动手

(1)先定义file_operations结构体变量

(2)open和close函数原型确定、内容填充

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>    //struct file_operations

#define MY_MAJOR 200
#define MY_NAME "test"

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    ret = register_chrdev (MY_MAJOR, MY_NAME, &test_fops);
    if(ret)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success...\n");
    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动

}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

5.4、注册驱动

(1)主设备号的选择---选择没有使用的

(2)返回值的检测

5.5、驱动测试

(1)编译等 make && make cp

(2)insmod并且查看设备注册的现象

(3)rmmod并且查看设备注销的现象

rmmod成功,并且lsmod没有找到该模块,但是cat /proc/devices,仍然存在test这个设备

原因是因为我代码中并没有编写注销驱动

当我修改源代码,编写了注销驱动代码后

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>    //struct file_operations

#define MY_MAJOR 200
#define MY_NAME "test"

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    ret = register_chrdev (MY_MAJOR, MY_NAME, &test_fops);
    if(ret)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success...\n");
    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(MY_MAJOR,MY_NAME);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

再去insmod这个驱动模块

仍然显示失败,原因是上次实验中主设备号200对应的驱动已经被占用,没有释放

重启开发板,重新insmod即可

5.6、让内核自动分配主设备号

(1)为什么要让内核自动分配

自定义主设备号容易其它模块产生冲突

(2)如何实现?

register_chrdev的主设备号参数传个0进去就可以了,主设备号是从1~254的,0表示让内核帮我们自动分配

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>    //struct file_operations

#define MY_MAJOR 200
#define MY_NAME "test"

int mymajor;

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

(3)测试

6.应用程序如何调用驱动

6.1、驱动设备文件的创建

(1)何为设备文件

应用层和驱动层之间的锁链

对应用来说,应用打开一个设备文件就可以和驱动挂钩

对驱动来说,驱动把自己包装成一个设备文件让应用操作

(2)设备文件的关键信息是:设备号 = 主设备号 + 次设备号,

为什么设备文件能找到驱动?

应用层(/dev/xxx)-》APP调用api操作/dev/xxx-》找到对应的file_operations结构体

主设备号:内核用来管理驱动的数组的下标,用来区分不同类的设备,譬如led和蜂鸣器

次设备号:用来区分同一类设备,譬如一个开发板上的四个led

应用层通过open一个设备文件,设备文件中包含主设备号和次设备号的信息,open函数通过这些信息找到对应的file_operations结构体

(3)使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号

c:表示字符设备驱动的设备文件

使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。

6.2、写应用来测试驱动

(1)应用还是原来的应用

(2)都是使用open、write、read、close等

 

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV_NUM    "/dev/test"        //刚才mknod创建的设备文件名

int main(void)
{
    int fd = -1;
    fd = open(DEV_NUM,O_RDWR);
    if(fd < 0)
    {
        printf("open %s error.\n",DEV_NUM);
        return -1;
    }
    printf("open %s success.\n",DEV_NUM);
    
    //读写文件
    
    //关闭文件
    close(fd);
    return 0;
}

makefile

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 开发板的linux内核的源码树目录
KERN_DIR = /root/driver/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	arm-linux-gcc app.c -o app
	
cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	cp app  /root/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm app 

(3)实验现象预测和验证

6.3、总结

(1)整体流程梳理、注意分层

(2)后续工作:添加读写接口

 

7.添加读写接口

7.1、在驱动中添加.read和.write实际操作方法

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>    //struct file_operations

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    return 0;
}

static ssize_t test_chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    printk(KERN_INFO "test_chrdev_read\n");
    
    return 0;
}

static ssize_t test_chrdev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "test_chrdev_write\n");
    
    return 0;
}
//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

7.2、在应用中添加read,write

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV_NUM    "/dev/test"        //刚才mknod创建的设备文件名
char buf[100];

int main(void)
{
    int fd = -1;
    fd = open(DEV_NUM,O_RDWR);
    if(fd < 0)
    {
        printf("open %s error.\n",DEV_NUM);
        return -1;
    }
    printf("open %s success.\n",DEV_NUM);
    
    //读写文件
    write(fd, "helloworld", 10);
    read(fd,buf,10);
    //关闭文件
    close(fd);
    return 0;
}

7.3、测试

7.4、应用和驱动之间的数据交换

(1)copy_from_user,用来将数据从用户空间复制到内核空间,涉及内存复制

(2)copy_to_user,用来将数据从内核空间复制到用户空间,涉及内存复制

注意:复制是和mmap的映射相对应去区分的

mmap不涉及内存复制

8.读写接口实践

8.1、完成write和read函数

(1)copy_from_user函数的返回值定义,和常规有点不同。返回值如果成功复制则返回0,如果 不成功复制则返回尚未成功复制剩下的字节数。

 modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    return 0;
}

// 模块下载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

8.2、读写回环测试

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV_NUM    "/dev/test"        //刚才mknod创建的设备文件名
char buf[100];

int main(void)
{
    int fd = -1;
    fd = open(DEV_NUM,O_RDWR);
    if(fd < 0)
    {
        printf("open %s error.\n",DEV_NUM);
        return -1;
    }
    printf("open %s success.\n",DEV_NUM);
    
    //读写文件
    write(fd, "helloworld", 10);
    read(fd,buf,10);
    printf("buf:%s\n",buf);
    //关闭文件
    close(fd);
    return 0;
}

测试:

8.3、总结

(1)目前为止应用已经能够读写驱动(中的内存)

(2)后续工作:添加硬件操作代码

9.驱动中如何操控硬件

9.1、还是那个硬件

(1)硬件本身物理原理不变

(2)硬件操作接口(寄存器)不变

(3)硬件操作代码不变

9.2、驱动操作硬件和裸机操作硬件哪里不同了?

(1)寄存器地址不同。原来是直接用物理地址,现在需要用该物理地址在内核虚拟地址空间相对应的虚拟地址。寄存器的物理地址是CPU设计时决定的,从datasheet中查找到的。

(2)编程方法不同。裸机中习惯直接用函数指针操作寄存器地址,

而kernel中习惯用封装好的io读写函数来操作寄存器,以实现最大程度可移植性。

9.3、内核的虚拟地址映射方法

(1)为什么需要虚拟地址映射

(2)内核中有2套虚拟地址映射方法:动态和静态

(3)静态映射方法的特点:

内核移植时以代码的形式硬编码(也就是TTB),如果要更改必须改源代码后重新编译内核

在内核启动时建立静态映射表,到内核关机时销毁,中间一直有效

对于移植好的内核,你用不用他都在那里

(4)动态映射方法的特点:

驱动程序中根据需要随时动态的建立映射、使用、销毁映射

动态映射是短期临时的

9.4、如何选择虚拟地址映射方法

(1)2种映射并不排他,可以同时使用

(2)静态映射类似于C语言中全局变量,动态方式类似于C语言中malloc堆内存

(3)静态映射的好处是执行效率高,系统开机建立,系统结束关闭,想用始终可以使用,坏处是始终占用虚拟地址空间;动态映射的好处是按需使用虚拟地址空间,坏处是每次使用前后都需要代码去建立映射&销毁映射(还得学会使用那些内核函数的使用)

 

10.静态映射操作LED

10.1、关于静态映射要说的

(1)不同版本内核中静态映射表位置、文件名可能不同

(2)不同SoC的静态映射表位置、文件名可能不同

这些映射表一般都在头文件中,并且在/arch/arm/plat-xxx...目录下的map-xxx.h

(3)所谓映射表其实就是头文件中的宏定义

10.2、三星版本内核中的静态映射表

(1)主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h

/* linux/arch/arm/plat-s5p/include/plat/map-s5p.h
 *
 * Copyright (c) 2010 Samsung Electronics Co., Ltd.
 *        http://www.samsung.com/
 *
 * S5P - Memory map definitions
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/

#ifndef __ASM_PLAT_MAP_S5P_H
#define __ASM_PLAT_MAP_S5P_H __FILE__

#define S5P_VA_CHIPID        S3C_ADDR(0x00700000)
#define S5P_VA_GPIO        S3C_ADDR(0x00500000)
#define S5P_VA_SYSTIMER        S3C_ADDR(0x01200000)
#define S5P_VA_SROMC        S3C_ADDR(0x01100000)
#define S5P_VA_AUDSS        S3C_ADDR(0X01600000)

#define S5P_VA_UART0        (S3C_VA_UART + 0x0)
#define S5P_VA_UART1        (S3C_VA_UART + 0x400)
#define S5P_VA_UART2        (S3C_VA_UART + 0x800)
#define S5P_VA_UART3        (S3C_VA_UART + 0xC00)

#define S3C_UART_OFFSET        (0x400)

#define VA_VIC(x)        (S3C_VA_IRQ + ((x) * 0x10000))
#define VA_VIC0            VA_VIC(0)
#define VA_VIC1            VA_VIC(1)
#define VA_VIC2            VA_VIC(2)
#define VA_VIC3            VA_VIC(3)

#endif /* __ASM_PLAT_MAP_S5P_H */

CPU在安排寄存器地址时不是随意乱序分布的,而是按照模块去区分的。每一个模块内部的很多个寄存器的地址是连续的。所以内核在定义寄存器地址时都是先找到基地址,然后再用基地址+偏移量来寻找具体的一个寄存器。

map-s5p.h中定义的就是要用到的几个模块的寄存器基地址。

map-s5p.h中定义的是模块的寄存器基地址的虚拟地址。

(2)虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h

#define S3C_ADDR_BASE (0xFD000000) // 三星移植时确定的静态映射表的基地址,表中的所有虚拟地址都是以这个地址+偏移量来指定的

(3)GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h

表中是GPIO的各个端口的基地址的定义

(4)GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

10.3、添加LED操作代码

(1)宏定义

(2)在init和exit函数中分别点亮和熄灭LED

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>    //S5PV210_GPJ0CON    S5PV210_GPJ0DAT

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

#define GPJ0CON        S5PV210_GPJ0CON    //GPJ0CON对应的虚拟地址    fd500240
#define GPJ0DAT        S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址    fd500244

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    
    //insmod执行的硬件操作
    rGPJ0CON = 0x11111111;
    rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
    printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
    
    rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

10.4、实践测试

(1)insmod和rmmod时观察LED亮灭变化

(2)打印出寄存器的值和静态映射表中的分析相对比

10.5、将代码移动到open和close函数中去

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>    //S5PV210_GPJ0CON    S5PV210_GPJ0DAT

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

#define GPJ0CON        S5PV210_GPJ0CON    //GPJ0CON对应的虚拟地址    fd500240
#define GPJ0DAT        S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址    fd500244

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    rGPJ0CON = 0x11111111;
    rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    
    //insmod执行的硬件操作
    //rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
    printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
    
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

 

10.6、添加驱动中的写函数

(1)先定义好应用和驱动之间的控制接口,这个是由自己来定义的。譬如定义为:应用向驱动写"on"则驱动让LED亮,应用向驱动写"off",驱动就让LED灭

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>    //S5PV210_GPJ0CON    S5PV210_GPJ0DAT
#include <linux/string.h>    //memset

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

#define GPJ0CON        S5PV210_GPJ0CON    //GPJ0CON对应的虚拟地址    fd500240
#define GPJ0DAT        S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址    fd500244

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    memset(kbuf, 0, sizeof(kbuf));
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    if(!strcmp(kbuf, "on"))
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(!strcmp(kbuf, "off"))
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    
    //insmod执行的硬件操作
    //rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
    printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
    
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV_NUM    "/dev/test"        //刚才mknod创建的设备文件名
char buf[100];

int main(void)
{
    int fd = -1;
    fd = open(DEV_NUM,O_RDWR);
    if(fd < 0)
    {
        printf("open %s error.\n",DEV_NUM);
        return -1;
    }
    printf("open %s success.\n",DEV_NUM);
    
    //读写文件
    write(fd, "on", 2);
    //read(fd,buf,10);
    //printf("buf:%s\n",buf);
    sleep(4);
    write(fd, "off", 3);
    sleep(4);
    write(fd, "on", 2);
    //关闭文件
    close(fd);
    return 0;
}

测试:

灯亮4s、灭4s、再亮4s

(2)应用和驱动的接口定义做的尽量简单,譬如用1个字目来表示。譬如定义为:应用写"1"表示灯亮,写"0"表示让灯灭。

 modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>    //S5PV210_GPJ0CON    S5PV210_GPJ0DAT
#include <linux/string.h>    //memset

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

#define GPJ0CON        S5PV210_GPJ0CON    //GPJ0CON对应的虚拟地址    fd500240
#define GPJ0DAT        S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址    fd500244

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    memset(kbuf, 0, sizeof(kbuf));
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    if(kbuf[0] == '1')
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(kbuf[0] == '0')
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
/*
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    if(!strcmp(kbuf, "on"))
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(!strcmp(kbuf, "off"))
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
 */
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    
    //insmod执行的硬件操作
    //rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
    printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
    
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV_NUM    "/dev/test"        //刚才mknod创建的设备文件名
char buf[100];

int main(void)
{
    int fd = -1;
    fd = open(DEV_NUM,O_RDWR);
    if(fd < 0)
    {
        printf("open %s error.\n",DEV_NUM);
        return -1;
    }
    printf("open %s success.\n",DEV_NUM);
/*
    //读写文件
    write(fd, "on", 2);
    //read(fd,buf,10);
    //printf("buf:%s\n",buf);
    sleep(4);
    write(fd, "off", 3);
    sleep(4);
    write(fd, "on", 2);
 */ 
    write(fd, "1", 1);
    sleep(4);
    write(fd, "0", 1);
    sleep(4);
    write(fd, "1", 1);
    //关闭文件
    close(fd);
    return 0;
}

测试:

灯亮4s、灭4s、再亮4s

10.7、写应用来测试写函数

 app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define DEV_NUM    "/dev/test"        //刚才mknod创建的设备文件名
char buf[100];

int main(void)
{
    int fd = -1;
    int i;
    fd = open(DEV_NUM,O_RDWR);
    if(fd < 0)
    {
        printf("open %s error.\n",DEV_NUM);
        return -1;
    }
    printf("open %s success.\n",DEV_NUM);
/*
    //读写文件
    write(fd, "on", 2);
    //read(fd,buf,10);
    //printf("buf:%s\n",buf);
    sleep(4);
    write(fd, "off", 3);
    sleep(4);
    write(fd, "on", 2);
 */ 
/*
    write(fd, "1", 1);
    sleep(4);
    write(fd, "0", 1);
    sleep(4);
    write(fd, "1", 1);
    //关闭文件
    close(fd);
 */
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        printf("select on/off/flash/quit\n");
        scanf("%s",buf);
        if(!strcmp(buf, "on"))
        {
            write(fd, "1", 1);
        }
        else if(!strcmp(buf, "off"))
        {
            write(fd, "0", 1);
        }
        else if(!strcmp(buf, "flash"))
        {
            for(i = 0; i<3; i++)
            {
                write(fd, "1", 1);
                sleep(1);
                write(fd, "0", 1);
            }
        }
        else if(!strcmp(buf, "quit"))
        {
            break;
        }
    }
    return 0;
}

11.动态映射操作LED

动态映射包含一个:建议映射、使用映射、销毁映射的过程

11.1、如何建立动态映射

(1)request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。

(2)ioremap,真正用来实现映射,传给他物理地址他给你映射返回一个虚拟地址

11.2、如何销毁动态映射

(1)iounmap,销毁映射

(2)release_mem_region,释放资源

注意:映射建立时,是要先申请再映射;然后使用;使用完要解除映射时要先解除映射再释放申请。

11.3、代码实践

(1)2个寄存器分开独立映射

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>    //S5PV210_GPJ0CON    S5PV210_GPJ0DAT
#include <linux/string.h>    //memset
#include <linux/ioport.h>    //request_mem_region
#include <asm/io.h>    //ioremap

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

#define GPJ0CON        S5PV210_GPJ0CON    //GPJ0CON对应的虚拟地址    fd500240
#define GPJ0DAT        S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址    fd500244

#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

#define GPJ0CON_PA        0xE0200240    //GPJ0CON对应的物理地址    0xE0200240
#define GPJ0DAT_PA        0xE0200244  //GPJ0DAT对应的物理地址    0xE0200244

unsigned int *pGPJ0CON = NULL;
unsigned int *pGPJ0DAT = NULL;

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    //rGPJ0CON = 0x11111111;
    //使用静态映射方式操作寄存器
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    //使用动态映射方式操作寄存器
    *pGPJ0CON = 0x11111111;
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    memset(kbuf, 0, sizeof(kbuf));
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    //使用动态映射方式操作寄存器
    if(kbuf[0] == '1')
    {
        *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(kbuf[0] == '0')
    {
        *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
    //使用静态映射方式操作寄存器
/*
    if(kbuf[0] == '1')
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(kbuf[0] == '0')
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
 */
/*
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    if(!strcmp(kbuf, "on"))
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(!strcmp(kbuf, "off"))
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
 */
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    
    //insmod执行的硬件操作
    //rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
    printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
    
    //使用动态映射的方式来操作寄存器
    //1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
    if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
            return -EBUSY;
    }
    if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
            return -EBUSY;
    }
    //2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
    pGPJ0CON = ioremap(GPJ0CON_PA, 4);
    pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
    
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    
    //解除动态映射
    //1.iounmap,解除映射
    iounmap(pGPJ0CON);
    iounmap(pGPJ0DAT);
    //2.release_mem_region,释放资源
    release_mem_region(GPJ0CON_PA, 4);
    release_mem_region(GPJ0DAT_PA, 4);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

(2)2个寄存器在一起映射

modules_test.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>        //struct file_operations
#include <asm/uaccess.h>    //copy_from_user copy_to_user
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>    //S5PV210_GPJ0CON    S5PV210_GPJ0DAT
#include <linux/string.h>    //memset
#include <linux/ioport.h>    //request_mem_region
#include <asm/io.h>    //ioremap

#define MY_MAJOR 200
#define MY_NAME "test"

static int mymajor;
static char kbuf[100];    //内核空间的buf

#define GPJ0CON        S5PV210_GPJ0CON    //GPJ0CON对应的虚拟地址    fd500240
#define GPJ0DAT        S5PV210_GPJ0DAT //GPJ0DAT对应的虚拟地址    fd500244
#define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)

//#define GPJ0CON_PA        0xE0200240    //GPJ0CON对应的物理地址    0xE0200240
//#define GPJ0DAT_PA        0xE0200244  //GPJ0DAT对应的物理地址    0xE0200244
//unsigned int *pGPJ0CON = NULL;
//unsigned int *pGPJ0DAT = NULL;

#define GPJ0_BASE_PA    0xE0200240    //GPJ0类寄存器的基地址
unsigned int *pGPJ0BASE = NULL;

static int test_chrdev_open(struct inode *inode, struct file *file)
{
    //这个函数真正应该放置打开这个设备的硬件操作代码
    printk(KERN_INFO "test_chrdev_open\n");
    //rGPJ0CON = 0x11111111;
    //使用静态映射方式操作寄存器
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    
    //使用动态映射方式操作寄存器(两个寄存器分开独立映射)
    //*pGPJ0CON = 0x11111111;
    
    //使用动态映射方式操作寄存器(两个寄存器一起映射)
    *pGPJ0BASE = 0x11111111;
    return 0;
} 
static int test_chrdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "test_chrdev_release\n");
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    return 0;
}
//读函数的本质:在驱动层将数据从硬件中读出,传给应用层
static ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_read\n");
    ret = copy_to_user(ubuf, kbuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user success...\n");
    return 0;
}
//写函数的本质:从应用层传数据到驱动层,然后在驱动层将其写入硬件
//参数:
//user_buf:指向应用程序空间的buf
// __user:修饰符,表示这个user_buf是用户空间的
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
    int ret = -1;
    printk(KERN_INFO "test_chrdev_write\n");
    //使用这个函数将应用层传过来的ubuf拷贝到驱动层的buf中
    //memcpy(kbuf,ubuf);    不可以,这两个不在一个地址空间中
    memset(kbuf, 0, sizeof(kbuf));
    ret = copy_from_user(kbuf, ubuf, count);//返回值如果成功复制则返回0,如果不成功复制则返回尚未成功复制 剩下的字节数。
    if(ret > 0)
    {
        printk(KERN_ERR "copy_from_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_from_user success...\n");
    //使用动态映射方式操作寄存器(两个寄存器一起映射)
    if(kbuf[0] == '1')
    {
        *(pGPJ0BASE + 1) = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(kbuf[0] == '0')
    {
        *(pGPJ0BASE + 1) = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
    //使用动态映射方式操作寄存器(两个寄存器分开独立映射)
/*
    if(kbuf[0] == '1')
    {
        *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(kbuf[0] == '0')
    {
        *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
 */
    //使用静态映射方式操作寄存器
/*
    if(kbuf[0] == '1')
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(kbuf[0] == '0')
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
 */
/*
    //真正的驱动中,数据从应用层复制到驱动层之后,就要去操作硬件了
    if(!strcmp(kbuf, "on"))
    {
        rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    }else if(!strcmp(kbuf, "off"))
    {
        rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    }
 */
    return 0;
}

//自定义一个file_operations结构体并填充
static const struct file_operations test_fops = {
    .owner        = THIS_MODULE,        //惯例
    .open        = test_chrdev_open,    //应用层open打开设备时实际所调用的函数 就是这个.open所绑定的函数
    .release    = test_chrdev_release,
    .read        = test_chrdev_read,
    .write        = test_chrdev_write,
};
// 模块安装函数
static int __init chrdev_init(void)
{    
    //int ret;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    //printk("<7>" "chrdev_init helloworld init\n");
    //printk("<7> chrdev_init helloworld init\n");
    //注册驱动
    //major传0表示让内核帮我自动分配一个空闲的主设备号
    //成功则会返回内核分配的主设备号,失败则表示255个主设备号都用完了,返回一个负数
    mymajor = register_chrdev (0, MY_NAME, &test_fops);
    if(!mymajor)
    {
        printk(KERN_ERR "register_chrdev fail\n");
        return -EINVAL;
    }
    printk(KERN_ERR "register_chrdev success... mymajor = %d\n",mymajor);
    
    //insmod执行的硬件操作
    //rGPJ0CON = 0x11111111;
    //rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// led亮
    printk(KERN_INFO "S5PV210_GPJ0CON = %p\n",S5PV210_GPJ0CON);
    printk(KERN_INFO "S5PV210_GPJ0DAT = %p\n",S5PV210_GPJ0DAT);
    
    //使用动态映射的方式来操作寄存器(两个寄存器分开独立映射)
/*
    //1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
    if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) {
            return -EBUSY;
    }
    if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT")) {
            return -EBUSY;
    }
    //2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
    pGPJ0CON = ioremap(GPJ0CON_PA, 4);
    pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
 */
 
    //使用动态映射的方式来操作寄存器(两个寄存器一起映射)
    //1.request_mem_region,向内核申请(报告)需要映射的内存资源。防止其它内核程序使用这个资源,造成冲突。
    if (!request_mem_region(GPJ0_BASE_PA, 8, "GPJ0_BASH")) {
            return -EBUSY;
    }
    //2.ioremap,开启映射,传给他物理地址他给你映射返回一个虚拟地址
    pGPJ0BASE = ioremap(GPJ0_BASE_PA, 8);
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    //注销驱动
    unregister_chrdev(mymajor,MY_NAME);
    
    //rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// led灭
    
    //解除动态映射(两个寄存器分开独立映射)
/*
    //1.iounmap,解除映射
    iounmap(pGPJ0CON);
    iounmap(pGPJ0DAT);
    //2.release_mem_region,释放资源
    release_mem_region(GPJ0CON_PA, 4);
    release_mem_region(GPJ0DAT_PA, 4);
 */
    //解除动态映射(两个寄存器一起映射)
    //1.iounmap,解除映射
    iounmap(pGPJ0BASE);
    //2.release_mem_region,释放资源
    release_mem_region(GPJ0_BASE_PA, 8);
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("aston");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/727478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年仪器仪表行业研究报告

第一章 行业概况 仪器仪表行业是指专门从事研究、设计、制造、销售和服务于科学研究、生产和生活中所需的各种仪器、仪表和自动化设备的行业。这些仪器和仪表可以用于测量、指示、记录、调节和控制物理、化学和生物过程中的各种参数。这个行业涵盖了广泛的设备和系统&#xff…

今日分享:Midjourney巧妙地用参考图/垫图来绘画图

大家都知道AI绘画工具每次生成的效果都是随机的&#xff0c;但是现在很多AI绘图工具都提供了利用参考图/垫图的方式出图&#xff0c;这样就可以让让AI画作生成自己想要的布局、场景、色彩等等。 国内的AI绘图工具一般都好操作&#xff0c;国外主流的Midjourney也可以添加参考图…

利用for循环和innerHTML在div中再填入多个div

目录 棋盘给每个小格子加上不同的id加上不同的参数传入 我们设置小格子的点击触发事件 循环填充元素 棋盘 先做棋盘&#xff0c;点击出现 <!DOCTYPE HTML> <html><head><meta charset"utf-8"><style>.sty1{background-color:#aaa;he…

旅游网站制作搭建,为旅行业务带来新机遇

旅游业在全球范围内一直都是蓬勃发展的行业之一。随着互联网的普及以及人们对旅行需求的增加&#xff0c;拥有一个精美而功能强大的旅游网站已经成为了旅行从业者的必备条件。本文旨在简单介绍旅游网站是什么&#xff0c;旅游网站的好处&#xff0c;并提供一些快速制作搭建旅游…

10亿上下文!微软新作,引入LongNet将Transformers上下文长度扩充到10亿

夕小瑶科技说 原创 作者 | python, ZenMogre Transformer处理长序列时较为吃力。因为global attention的存在&#xff0c;模型的时间复杂度是序列长度的2次方级。为了建模更长的上下文&#xff0c;人们也提出了各种稀疏注意力机制。而这次&#xff0c;微软卷到家了&#xff0…

word免费转为pdf怎么转,分享这几个方法给大家!

将Word文档转换为PDF格式是一种常见的需求&#xff0c;因为PDF文件具有广泛的兼容性和安全性。本文将介绍三种免费转换Word为PDF的方法&#xff0c;包括记灵在线工具、使用Word自带功能以及使用Smallpdf。这些方法简单易行&#xff0c;帮助您轻松完成转换&#xff0c;方便与他人…

【尚医通】vue3+ts前端项目开发笔记——项目分析

尚医通开发笔记 一、项目分析 项目在线地址&#xff1a;http://syt.atguigu.cn测试帐号&#xff1a;17720125002 首页 home header 全局组件布局 左&#xff1a;logo 、title中&#xff1a;初始隐藏 搜索框 公共组件显示条件&#xff1a;在页面滚动到页面内搜索框的位置显示…

Go语言中的运算符

Golang 内置的运算符 算术运算符 关系运算符 逻辑运算符 位运算符&#xff08;不常用&#xff09; 赋值运算符 算数运算符 运算符描述相加-相减*相乘/相除%求余 a : 10b : 9fmt.Printf("ab的值为%v\na-b的值为%v\na*b的值为%v\n",ab,a-b,a*b) 除法注意&#xff1a;…

uniapp:粘性布局(吸顶:u-sticky)生效的注意事项

使用场景&#xff1a;要求首次渲染时不需要固定在页面顶部&#xff08;正常布局&#xff09;&#xff0c;当随着页面的滚动&#xff0c;需要将起固定在页面顶部&#xff0c;会使用到可能的有&#xff1a;tab、搜索框、导航、标题、头图…工具&#xff1a;用了uview2的组件<u…

手撕spring04源码(A依赖B)

概述 本章节优化上一章节通过构造方法实例化对象属性填充问题的痛点&#xff0c;并解决A bean依赖B bean的问题 整体设计 知识点补充 spring生命周期 在Spring中&#xff0c;Bean的生命周期包括实例化、初始化和销毁三个阶段。下面是对每个阶段的解释&#xff1a; 实例化…

河南企业级泛域名SSL证书

电脑的普及让网络可以快速发展&#xff0c;紧随网络的发展各个CA认证机构推出了泛域名SSL证书、多域名SSL证书等可以用一张SSL证书保护多个域名网站的SSL数字证书。泛域名SSL证书也叫通配符SSL证书&#xff0c;可以用一张SSL证书保护主域名以及主域名下所有的子域名网站&#x…

NR PDCP(三) data transfer

这篇看下PDCP的data transfer过程&#xff0c;如NR RLC(三) TM and UM mode所述&#xff0c;在UL grant充足的情况下&#xff0c;UM RLC 一直在传输完整的RLC SDU&#xff0c;通过log只能知道UE有在收发data&#xff0c;并不能像LTE似的通过SN去判断UE DL data是否有序接收以及…

平板触控笔要原装的吗?apple pencil的平替笔推荐

如今的电容笔种类越来越多&#xff0c;相信不少人都会在挑选电容笔中踩过坑&#xff0c;例如书写频繁断触&#xff0c;防误触失灵&#xff0c;续航能力欠佳等问题。这样的坑本人也是踩过不少&#xff0c;于是&#xff0c;我决定为大家出一期电容笔详细测评&#xff0c;特意地去…

从程序员的角度看待算法的学习与研究

一&#xff1a;引言 算法的重要性和应用场景&#xff1a; 提高效率&#xff1a;算法可以帮助我们设计和实现高效的解决方案&#xff0c;在有限的资源下&#xff0c;提高计算机程序或系统的执行速度和效率。解决复杂问题&#xff1a;算法可以提供有效的解决方案来解决各种复杂问…

有PMP证书了再考CSPM有必要吗?

先说答案&#xff1a;有必要 首先介绍一下什么是CSPM cspm中文名字是项目管理专业人员能力等级评价&#xff0c;是由中国标准化协会&#xff08;CAS&#xff09;组织开展的&#xff0c;它符合国务院发布的《国家标准化发展纲要》&#xff0c;纲要中明确提出要构建多层次从业人…

【mysql】mysql登录密码忘记重置方法,解决password针对mysql8.0及以上版本失效问题

问题场景&#xff1a; 提示&#xff1a;mysql密码忘记 本人场景&#xff1a;mysql装了很久&#xff0c;一段时间未使用&#xff0c;再次打开发现登录不了了&#xff0c;于是想修改密码。 解决方案&#xff1a; 1、找到自己安装mysql的文件夹。删掉其中的data文件夹&#xff…

Python反爬取访问验证处理

最近爬取数据的时候&#xff0c;遇到反爬取限制&#xff1a;即当访问一定次数后会弹出访问验证如下图所示&#xff1a; 这种验证方式没找到绕过去的方法&#xff0c;那就只能用最笨的办法&#xff0c;弹出验证框后&#xff0c;将等待时间延长&#xff0c;然后手动点击验证。代码…

数据结构--线索二叉树的概念

数据结构–线索二叉树的概念 二叉树的中序遍历序列 void InOrder(BiTree T) {if (T ! NULL){InOrder(T->lchild); //递归遍历左子树visit(T); //访问根结点InOrder(T->rchild); //递归遍历右子树} }中序遍历序列:D G B E A F C ①如何找到指定结点p在中序遍历序列中的前…

Oracle-奇怪的expdp备份报错LPX-00217

问题背景: 接用户报障&#xff0c;数据库每天晚上正常的expdp备份&#xff0c;从2天前开始出现奇怪的备份报错LPX-00217: invalid character 3 问题分析: 检查expdp备份的日志&#xff0c;从2天前晚上开始的备份均出现LPX-00217: invalid character 3的报错&#xff0c;报错均…

CentOS7在线安装MySQL新手小白教程

1、下载并安装MySQL官方的 Yum Repository wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm使用上面的命令下载安装用的Yum Repository yum -y install mysql57-community-release-el7-10.noarch.rpm开始安装MySQL服务器 yum -y install …