驱动开发之模块与外部编译

驱动开发一:

概要:

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
");
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
");
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! "); 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灯点亮

 

--------------------------------->学习路漫漫<------------------------------

原文地址:https://www.cnblogs.com/hslixiqian/p/9637409.html