驱动开发之模块与外部编译
驱动开发一:
概要:
1、模块、外部编译
2、字符设备框架(函数接口和结构体的关系)
3、字符设备框架、platform框架
4、设备树、led驱动、蜂鸣器驱动
5、内核中断子系统,按键驱动,中断上下半部。
6、adc驱动,内核的IO模型(阻塞、非阻塞、异步通知、多路复用)
7、I2C总线驱动、I2C设备驱动
8、输入子系统
知识补充:追内核:
make tags
vi -t xxx
一、什么是驱动?driver老司机
可以操作硬件,同时还会给应用程序提供交互的接口。
二、上层的程序如何操作硬件
1.系统调用:本质上是一个函数接口
2.函数的声明:存放在指定头文件中
3.函数的定义:在内核空间中
4.函数的调用:应用程序中使用
三、模块基本特性
1、什么是模块?
运行在内核空间中的一段代码
应用程序 模块
运行空间 用户空间 内核空间
入口函数 main 加载函数
调用接口 库函数或者系统调用 内核函数(主要是fs提供)
空间释放 自动释放 手动释放
模块不是驱动,它是实现驱动的一种方法。
在内核中驱动、文件系统、网络协议栈都可以用模块来实现。
2、模块的三要素:模块的声明、加载函数、卸载函数
模块的声明:MODULE_LICENSE("协议名称");
常见的协议:GPL BSD
加载函数:
-------->默认加载函数
1 int init_module(void);//源码是由驱动的开发者定义的。
-------->自定义加载函数
297 #define module_init(initfn) \
298 static inline initcall_t __inittest(void) \
299 { return initfn; } \
300 int init_module(void) __attribute__((alias(#initfn)));
/*给默认加载函数去别名,叫做initfn*/
1 分析:
2 int a[2][3];
3 int (*p)[3]; <==> int (*)[3] p;
4
5 int (*p)(void) <==> int (*)(void) p;
6
7 int (*initcall_t)(void);<==> typedef int (*)(void) initcall_t 函数指针类型
8 initfn是一个函数名,类型和initcall_t类型一致。
9 initfn的返回值为int,形参为void
卸载函数:
--------->默认卸载函数
void cleanup_module(void);
--------->自定义卸载函数
1 303 #define module_exit(exitfn)
2 304 static inline exitcall_t __exittest(void) //inline 内联函数,不进行入栈出栈操作,操作几次占几份内存(避免循环使用)
3 305 { return exitfn; }
4 306 void cleanup_module(void) __attribute__((alias(#exitfn)));
5
6 typedef void (*exitcall_t)(void);
3、模块的编译方法
内部编译:驱动源码存放在内核的指定目录下进行编译
1 a.cp demo.c drivers/char
2 b.vi drivers/char/Kconfig
3 ----->添加选项
4 config DEMO
5 tristate "选项名称"
6 c.vi drivers/char/Makefile
7 添加:obj-$(CONFIG_DEMO) += demo.o
8 d.进入menuconfig中,找到选项选为M
9 e.make modules
10 f.cp drivers/char/demo.ko /rootfs
11 g.开发板启动后在开发板上执行insmod demo.ko
外部编译:驱动源码不在内核指定目录下(比内部编译方法方便)
1 #include <linux/init.h>
2 #include <linux/module.h>
3 MODULE_LICENSE("GPL");
4
5 //如果不使用__init加载函数直接被编译到.text分段中
6 //如果使用__init加载函数会被编译到.text.init分段中
7 int __init demo_init(void)//自定义加载函数
8 {
9
10 printk("demo_init\n");
11 return 0;
12 }
13 module_init(demo_init);//给默认加载函数取别名
14
15 //如果不使用__exit,当将驱动代码直接编译到内核中时,卸载函数也会参与编译
16 //如果使用__exit,当驱动代码直接编译到内核中,卸载函数不会参与编译
17 void __exit demo_exit(void)
18 {
19 printk("demo_exit\n");
20 }
21 module_exit(demo_exit);
1、自己写Makefile
2、调用到内核提供的模块编译方法
3、通知内核模块编译方法哪些源文件参与编译
内核顶层目录Makefile中:
1248 # The following are the only valid targets when building external
1249 # modules.
1250 # make M=dir clean Delete all automatically generated files
1251 # make M=dir modules Make all modules in specified dir
1252 # make M=dir Same as 'make M=dir modules'
自己的Makefile:
1 ifeq ($(KERNELRELEASE),) /*避免死循环执行makefile*/ 2 PWD = $(shell pwd) /*当前路径模块*/ 3 #KERNEL_DIR = /home/linux/linux-3.14/ /*用这个目录,需要将模块文件拷贝到开发板上执行*/ 4 KERNEL_DIR = /lib/modules/$(shell uname -r)/build/ /*需要在ubuntu中使用模块文件*/ 5 6 7 modules: 8 make -C $(KERNEL_DIR) M=$(PWD) modules /*-C进入路径KERNEL_DIR,找到寻找modules目标,最后回来*/
9
11 clean:
12 make -C $(KERNEL_DIR) M=$(PWD) clean
13 else
14
obj-m += demo1.o //(-m模块 -y -n)
//xxx-objs := ogj1.o,obj2.o
//obj-m += xxx.o (打包生成xxx.ko文件)
15 endif
模块符号表导出:
符号本质就是一个函数名或者变量名。
1 EXPORT_SYMBOL_GPL();
2 EXPORT_SYMBOL();
3 功能:将符号信息存放到Module.symvers文件中
假设B模块要使用A模块中的一个函数。
1、模块A的函数下调用EXPORT_SYMBOL_GPL(函数名);
2、编译模块A
3、加载模块A
4、拷贝模块A的Module.symvers文件给模块B
5、编译模块B
6、加载模块B
运行过程:
1、执行自己的Makefile
2、进入内核顶层目录的Makefile,寻找modules目标
3、进入scripts/Makefile.modpost
4、回到自己的Makefile
4、模块的命令
1 加载模块:insmod xxx.ko
2 卸载模块:rmmod xxx
3 dmesg 显示内核打印信息到终端上
4 dmesg -c清空内核打印信息
printk(printf)的级别问题:
消息级别: 0 1 2 3 4 5 6 7
控制台级别: 1 2 3 4 5 6 7 8
----------》数字越小级别越高《-------------------
如果需要直接打印数据到终端上,那么必须保证消息级别大于控制台级别。
ubuntu内核: 4(当前控制台级别) 4(当前消息级别) 1(控制台级别的最小值) 7(默认的控制台级别)
Linux—3.14内核: 7 4 1 7
地址映射函数:
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
功能:地址映射
参数1:物理地址
参数2:映射的字节数
返回值:虚拟地址
/----------------------------------------作业部分-------------------------------------/
实验点亮exynos4412-fs4412的led2
1.vi dome.c
1 #include <linux/module.h>
2 #include <linux/init.h>
3 #include <asm/io.h> /* vi -t 追到两个相关头文件(/include/asm-generic/io.h)如果使用此头文件,会报错*/
4 MODULE_LICENSE("GPL");
5 int __init demo_init(void)
6 {
7 void __iomem *CON = (void __iomem*)ioremap(0x11000c20,4);
8 void __iomem *DAT = (void __iomem*)ioremap(0x11000c24,4); //也可通过地址偏移来实现(void *DAT = CON + 4)
9 //*CON = (*CON & (~(0xf << 0))) | (1 << 0);
writel(readl(CON &(~(0xf << 0))) | (1 << 0)),CON);//驱动程序的写法,(从内存映射的i/o空间读/写数据)
10 //*DAT = *DAT | (1 << 0);
writel(readl(DAT | (1 << 0)),DAT); //readl中--->l:4byte w:2byte b:1byte
11 printk("This is sb!\n");
12 return 0;
13 }
14 module_init(demo_init);
15
16 void __exit demo_exit(void)
17 {
18
19 }
20 module_exit(demo_exit);
2.vi makefile
1 ifeq ($(KERNELRELEASE),)
2 PWD = $(shell pwd)
3 KERNEL_DIR = /home/linux/linux-3.14/
4 modules:
5 make -C $(KERNEL_DIR) M=$(PWD) modules
6 clean:
7 make -C $(KERNEL_DIR) M=$(PWD) clean
8 else
9 obj-m += demo.o
10 endif
3.make
linux@ubuntu:~/lxq/class/drivers/1day$ make
make -C /home/linux/linux-3.14/ M=/home/linux/lxq/class/drivers/1day modules
make[1]: Entering directory `/home/linux/linux-3.14'
CC [M] /home/linux/lxq/class/drivers/1day/demo.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/linux/lxq/class/drivers/1day/demo.mod.o
LD [M] /home/linux/lxq/class/drivers/1day/demo.ko /*生成成功*/
4. cp ~/lxq/class/drivers/1day/demo.ko /rootfs/
5.make clean
6.开发板启动后在开发板上执行insmod demo.ko -------------->led灯点亮
--------------------------------->学习路漫漫<------------------------------