linux驱动开发⼀般步骤(以S5PV210开发板为例)
1、驱动的价值就在于实现API
2、驱动是内核的⼀部分
3、驱动开发的步骤
1)驱动源码的编写、Makefile编写、编译
关于端午节简短祝福语大全2)insmod装载模块、⽤应⽤程序测试、rmmod卸载模块
4、在linux中⽤find来查某个⽂件所在的路径
find . -name ‘x210ii_qt_defconfig’ //在当前⽂件夹下⾯查x210ii_qt_defconfig的路径
⼀般的xxx_defconfig⽂件在arch/arm/config/⽂件夹下
5、常⽤的模块操作指令
lsmod 将当前内核中已经安装的模块打印出来
insmod 向当前内核中去安装⼀个模块,⽤法是insmod xxx.ko
modinfo 打印出⼀个模块的⾃带信息,⽤法是modinfo xxx.ko
rmmod 从当前内核中卸载⼀个模块,⽤法是rmmod xxx(注意,不能加.ko)
6、__init(下载模块使⽤)是函数的修饰符,本质上是⼀个宏定义,在内核源代码中就有#define __init xxx。这个__init的作⽤就是将被他修饰的函数放⼊到.段中去(本来默认情况下函数是被放到.text段中)。整个内核中的所有的这类
函数都会被链接器放⼊到.段中,所以所有的内核模块的__init修饰的函数其实是被统⼀放在⼀起的。内核
启动时统⼀加载.段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存。
__exit(卸载模块使⽤),原理和__init相似。
7、驱动源代码中包含的头⽂件是内核源码⽬录下的include⽬录下的头⽂件。
8、要保证模块的vermagic(版本信息)和内核的vermagic的⼀致,否则模块不能挂接到内核中去。
如何保证两者相等呢:需要编译模块的内核就是将来我们要挂载的内核。
9、file_operations结构体(⾥⾯的元素都是函数指针)的作⽤就是,做API和驱动之间的桥梁。
API中的:open、read、write、close等函数接⼝,其函数的实体在驱动源码中。file_operations结构体
就是充当实体和接⼝之间的连接桥梁。
每个驱动设备都需要⼀个该结构体类型的变量。
在使⽤file_operations结构体的时候⼀定要包含头⽂件<linux/fs.h>
10、register_chrdev是内核开发者所编写的注册函数。
11、驱动通过register_chrdev函数向内核注册⾃⼰的file_operations结构体。
register_chrdev函数的详解:
函数体:
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
参数解析:
static ⽤来防⽌和其它⽂件冲突
inline ⽤来提⾼效率
major 主设备号,传0进去表⽰让内核给我们⾃动分配⼀个设备号(也可以⾃⼰指定)
name 设备的名字
fops 把我们⾃⼰的file_operations结构体的变量,通过指针传给这个函数,然后这个函数向内核注册这个驱动。
256 的意思是最多装载256个设备游戏排行榜网络游戏
成功返回0,失败返回⼀个负整数
12、unregister_chrdev函数与上⾯的注册函数相对应,⽤来卸载。
原型:
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
13、cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)
14、close在file_operations中的原型是release。
15、驱动的编写顺序⼤致为:
1)file_operations结构体中,函数指针元素所对应的各个函数实体的编写
2)file_operations结构体的元素填充
3)安装模块chrdev_init函数的编写
4)卸载模块chrdev_exit函数的编写
16、在file_operations结构体中,第⼀句.owner = THIS_MODULE,是惯例,直接写就⾏。
17、使⽤mknod /dev/xxx(xxx为设备⽂件名) c 主设备号 从设备号(没有的话为0)创建设备⽂件;
创建设备⽂件的⽬的是:以后就是通过设备⽂件来进⾏应⽤程序控制调⽤驱动的。
18、可以⽤dmesg查看所有被ubuntu拦截的所有打印信息。
19、应⽤层调⽤谁,谁在驱动层对应的实体函数才会被执⾏。
20、copy_from_user()函数:⽤来将数据从⽤户空间复制到内核空间(在应⽤层对应于write函数,也就是说在应⽤层向内核写数据,应⽤层⽅⾯⽤的是write函数,在驱动层(内核层)⽅⾯⽤的是copy_from_user()函数)。
函数原型:
static inline unsigned long__must_check copy_from_user(void *to, const void__user *from, unsigned long n) {
…
}
to =>kernel
from =>user
n =>多少个
成功返回0,失败返回尚未成功复制剩下的字节数。
不能使⽤memcpy()函数进⾏复制,因为⼆者不在同⼀地址空间。
22、copy_to_user()函数:将数据从内核空间复制到⽤户空间(在应⽤层对应于read函数…)
函数原型:
static inline unsigned long__must_check copy_to_user(void__user *to, const void *from, unsigned long n)
{
…
}
to =>user
from =>kernel
n =>多少个
23、写驱动⽤的是虚拟地址。内核中⼀般⽤封装号的IO读写函数来操作寄存器。
24、因为编写驱动程序需要⽤到虚拟地址,并且多个虚拟地址可以对应于同⼀个物理地址,所以这⾥
就需要虚拟地址映射的⽅法。
两种映射⽅法:
1)静态映射
内核移植的时候⽤代码的形式硬编码(直接写死),如果需要更改必须改源代码之后重新编译内核,在
内核启动时建⽴映射表,到内核关机时销毁。类似于C语⾔中的全局变量
好处是执⾏效率⾼,坏处是始终占⽤虚拟地址空间。
2)动态映射
驱动程序根据需要随时动态的建⽴映射、使⽤、销毁,是临时的。类似于C语⾔中malloc堆内存
好处是俺需使⽤虚拟地址空间,坏处是每次使⽤前后都需要⽤代码进⾏建⽴和销毁。
两种⽅法是可以同时使⽤的。
25、不同版本的内核、SOC的静态映射表的位置、⽂件名可能不同,所谓的映射表其实就是头⽂件中的宏定义。
26、GPIO相关的主映射表位于arch/arm/mach-s5pv210/include/mach/regs-gpio.h
GPIO的具体寄存器定义在arch/arm/mach-s5pv210/include/mach/gpio-bank.h
所以在编写程序的时候需要包含mach/regs-gpio.h和mach/gpio-bank.h
27、在应⽤程序⾥⾯写控制硬件的开关时,开关函数写到应⽤层⾥⾯的哪个函数⾥⾯,对应的就把
硬件的控制函数写到对应的驱动层函数⾥⾯。
例⼦:
在应⽤层⾥⾯我把控制LED亮的开关(on、off)写到write函数⾥⾯,那么在驱动层⽅⾯就把控制LED
的寄存器控制操作写到chrdev_write函数⾥⾯。
28、⽤动态映射⽅式,只需要在驱动层安装模块的函数⾥⾯建⽴动态映射,在卸载模块的函数⾥⾯销毁动态映射。
建⽴动态映射两步:
1)利⽤resuest_mem_region(start, n, name);申请IO内存,成功返回0
2)利⽤ioremap(start, n);实现映射,成功返回值是对应的虚拟地址
三个参数的含义:start是起始的物理地址,n是它的长度占⼏个字节,name就是为这段内存起个名字。
销毁动态映射两步:
1)利⽤iounmap(ioremap函数的返回值);解除映射
2)利⽤release_mem_region(start,n);销毁申请的内存
29、⽤新接⼝进⾏驱动设备的注册
注册分为三步:
1)先注册驱动设备号,
⽤register_chrdev_region()/alloc_chrdev_region()前者是指定设备号,后者是让系统⾃动分配
register_chrdev_region(dev_t from, unsigned count, const char *name)
from的意思是主设备号,count的意思是指定有⼏个次设备号,name为设备的名字
⼀般from来⾃于MKDEV(主设备号,起始次设备号)函数的返回值。
如果⽤alloc_chrdev_region()的话就不⽤MKDEV()函数了。
2)再初始化驱动
利⽤cdev_init(struct cdev *cdev, const struct file_operations *fops)函数来初始化驱动
cdev是cdev结构体的变量(这个在包含<linux/cdev.h>的前提下,直接全局定义声明就可以⽤,真正的实现函数在头⽂件⾥),fops是file_operations结构体的变量。
3)最后注册设备驱动
利⽤cdev_add(struct cdev *cdev, dev_t from, unsigned count);参数含义前⾯都有
注销分为两步:
1)先注销驱动
利⽤cdev_del(struct cdev *cdev);参数含义前⾯都有
2)再注销驱动设备号
利⽤unregister_chrdev_region(dev_t from, unsigned count);参数含义前⾯都有
注:cdev结构体是头⽂件<linux/cdev.h>中包含的结构体,该结构体⾥⾯包含的两个重要的元素是
file_operations结构体和dev_t 类型的设备号变量。
30、使⽤MAJOR()函数和MINJOR()函数从dev_t得到主设备号和次设备号;⽤MKDEV()函数从主设备号和次设备号得到dev_t。三国英传3攻略
31、cdev_alloc函数⽤来创造内存空间的,cdev_alloc()不需要参数,返回值是⼀个cdev结构体类型的指针变量
例⼦:
1)
static struct cdev *pcdev;
pcdev = cdev_alloc();
pcdev -> owner = THIS_MODULE;
pcdev -> ops = &test_fops;(test_fops是file_operations结构体的变量)
cdev_del(pcdev);
2)
static struct cdev *pcdev;
pcdev = cdev_alloc();
cdev_init(pcdev, &test_fops);
cdev_del(pcdev);
32、使⽤mdev应⽤程序,让内核在在注册和注销驱动的时候,⾃动在应⽤层进⾏设备⽂件的创建和删除(以前都是使⽤mknod进⾏创建)。
mdev是应⽤层的⼀个应⽤程序,他的⾃动启动时在我们根⽂件系统中/etc/init.d⽂件夹⾥⾯的rcS⽂件⾥⾯定义;
这个rcS⽂件是在关在根⽂件系统的时候⾃动执⾏的,也就是说在挂载完根⽂件系统之后,mdev应⽤程序就⾃动启动了。
优酷登陆网络错误33、但是应⽤层启动mdev之后还需要配合驱动层的接⼝函数才能实现设备⽂件的⾃动创建和删除。
在驱动层的接⼝函数:
创建设备⽂件函数:(在注册完设备之后使⽤)
class_create()
剪发技巧函数原型:
#define class_create(owner, name) ({static struct lock_class_key_key;
__class_creat(owner, name, &__key);})
device_create()
函数原型:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, …)
{
…
}
删除设备⽂件函数:(在卸载设备之前使⽤)
device_destroy()
函数原型:
void device_destroy(struct class *class, dev_t devt)
{
…
}
class_destroy()
世界上最长的内流河函数原型:
void class_destroy(struct class *cls)
{
…
}
例⼦:
#include<linux/device.h>
#include<linux/cdev.h>
#define MYCNT 1 //次设备号的个数
#define MYNAME "teacher" //驱动设备名字
static dev_t mydev;
static struct cdev *pcdev;
static struct class *test_class; //该类的实体在linux/device.h头⽂件⾥⾯
int ret;
//⾃动分配设备号
ret = alloc_chrdev_region(&mydev, 12, MYCNT, MYNAME); //mydev设备号,包含了主次设备号,12是次设备号的开始
//创建设备⽂件
test_class = class_create(THIS_MODULE, "aston_class"); //该函数是先创建⼀个类,THIS_MODULE是file_operations
//结构体中.owner的值,aston_class是类名
device_create(test_class, NULL, mydev, NULL, "test"); //test就是我们将来要在/dev⽬录下创建的设备⽂件名
.
..
//删除设备⽂件
device_destroy(test_class, mydev);
class_destroy(test_class);
34、内核提供的读写寄存器的接⼝:
writel(要往寄存器⾥⾯写⼊的数值,寄存器地址)
redal(寄存器地址)
35、内核开发者提供⼀些操作设备驱动的⼀些公共接⼝⼀般所在的⽂件时xxx_class.c,xxx_core.c;
驱动开发者调⽤这些接⼝去实现驱动的开发。
36、对于⼀个内核模块来说,分析的时候应该从下向上分析。
37、subsys_initcall(func1)将func1函数放到.initcall4.init段
module_exit(func2)将func2函数放到.initcall6.init段
内核在启动过程中需要顺序的做很多事,内核如何实现按照先后顺序去做很多初始化操作。内核的解决⽅案就是给
内核启动时要调⽤的所有函数归类,然后每个类按照⼀定的次序去调⽤执⾏。这些分类名就叫.initcalln.init,n
的值从0到7(0最先调⽤),内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候
放⼊特定的段,内核启动时按照段顺序去依次执⾏各个段即可。
38、利⽤make menuconfig去添加led驱动框架模块(devices->)。(前提是在根⽬录的drivers⽬录下有这个驱动框架)
39、已有的驱动框架都在/sys/class⽬录下
40、与不⽤驱动框架的驱动程序来说,⽤驱动框架编写的驱动程序,所⾃动创建的设备⽂件在/sys/class/xxx,xxx为
驱动框架名。
41、cat brightness实际会执⾏led-class.c中的led_brightness_show函数,去读取已经写⼊的值。
echo 1 > brightness就会把1写⼊到驱动中去,例如1就是让led亮,那么这个1就先写⼊到led_brightness_stroe函数
中去,影响该函数中state的值;然后这个值在经过结构体led_cdev传⼊到我们在led_cdev结构体中⾃⼰绑定的硬件
操作函数,去控制硬件。
42、内核中提供gpiolib(属于驱动框架的⼀部分)来统⼀管控系统中的所有GPIO,处理GPIO的复⽤问题。
43、xxx_gpiolib_init()就是gpiolib的初始化函数,在这⾥xxx是s5pv210。
44、⼀个端⼝可以包含多个IO⼝,例如:GPA0是个端⼝,GPA0_1、GPA0_2就是IO⼝。
45、内核开发者给我们提供的关于gpiolib的框架接⼝函数主要有(我们常⽤的)位置在源⽬录下drivers/gpio/gpiolib.c:gpio_request()向内核申请⼀个GPIO资源(端⼝/IO⼝)
gpio_direction_input()/gpio_direction_output()将申请到的gpio设置为输⼊或输出模式
gpio_set_value()向gpio中写值,控制硬件,达到⽬的
gpio_free()释放申请的gpio资源
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论