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

目录

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");            // 描述模块的别名信息

HD2010操作说明(HD2010 operating instructions) ⅰ.软件安装(Installed software) 双击光盘HD2010 V2.0目录下的安装文件图标 ,将控制系统安装到个人电脑.如下图2-1 (Open Profile HD2010 V2.0 in the compact disk,Dblclick Installed the HD2010 V2.0 in your personal computer.As shown in Figure 2-1) 图(Figure)2-1 点击确定后进入下一步如图2-2 (Click确定intro- next step.As shown in Figure 2-2 ) 图(Figure)2-2 点击下一步后进入下一步(英文环境)在中文环境直接点击下一步完成安装)如图2-3 2-4 2-5 2-6 2-7 (Click next intro- next step.As shown in Figure 2-3 2-4 2-5 2-6 2-7) 图(Figure)2-3 注意:发送的文件保存在安装目录下的ProjFile文件夹里,点击下一步完成安装 (Announcements:The file what you send saved in the ProjFile in path of Install.And click next to install and finish) 图(Figure)2-4 图(Figure)2-5 图(Figure)2-6 安装最后一步如图2-7 (The final step of install. As shown in Figure 2-7) 图(Figure)2-7 点击完成打开软件主界面如图2-8 (Click finish open main interface of the software. As shown in Figure 2-8) ⅱ.软件设置(Setting software) ⅱ.ⅰ主界面如图2-8(Main interface.As shown in Figure 2-8) 图(Figure)2-8 ⅱ.ⅱ软件属性(Software properties) 1.文件菜单(File menu) a.新建--新建一个新的显示屏(New—Create a new screen) b.打开--打开一个显示屏(Open—Open a exist screen) c.保存--保存建立的文件(Save—Save file) d.另存为--保存副本(Save as—save a copy) e.导出.hds--导出.hds文件,用于u盘读取文件 (Export.hds—Export.hds file,for usb reading) f.退出--退出软件(Exit—Exit the software) 2.设置菜单(Settings menu) a.屏参设置--设置显示屏属性 (Screen settings—Display Screen configuration attributes) b.通信设置--通信端口/方式设置 (Communication settings—Set Communication prot and fashion) c.系统设置--配置系统默认项 (System settings—Set the acquice properties of software) 3.操作菜单(Operate menu) a.发送项目--发送节目(Send project—send the programmes) b.导出到U盘--把节目导入到U盘 (Export to U disk—send the programme to your U disk) c.时间设置--时间校对 (Time setting—get the right time im your computer) d.亮度设置--选择亮度模式 (Luminance setting—Choice the module of Luminance) e.固件更新--固件升级(Update Firmware—Update your controller Firmware) 4.语言菜单(Language menu) a.简体中文(Simplified Chinese) b.繁体中文(Chinese Traditional) c.英语(English) ⅱ.ⅲ节目编辑(Edit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值