先说推论,能,肯定能,必须能!
然而,问题重点在于坚持,程序员这一行,上班回去通常都要10点了,再刷两个小时无趣的学习视频,我想大多数人是坚持不出来的。
然而linux虚拟机,我要说并且,"linux驱动开发似乎并不难,难的是市面上没有靠谱的书籍和教学视频",就似乎是你向一个半瓶水的模拟电路工程师讨教电路设计原理,张嘴就是经验值,问就是他人都是如此设计的,能用就行,他能给你讲明白才有鬼了。
之所以说linux驱动开发不难是由于内核中早已实现了一套十分简约,通用的驱动框架,自打2.6版本之后就没如何变过,足以说明该驱动框架的优秀。而目前市面上的书籍和教学视频根本没有足够注重讲解驱动框架的内容usb驱动 linux,只是硬扣单个驱动的细节。作为单片机工程师,你跟linux驱动工程师之间差的就只是一个驱动框架而已。
说了那么多,是时侯上干货了。还是坚持我一贯的理念,学习任何新鲜东西都应当是由远及近,先整体把握全局,再深入探究细节。原则上,你只要认真看完下边的内容,就差不多算是入门了。屁话不多说,上菜。
后置知识:
2.对象一般采用结构体的方式描述,结构体中的变量表示对象属性,函数表针表示对象行为。
3.对象之间的承继关系采用内嵌父类结构体对象的方式彰显。
4.驱动中的同类对象通常采用数组的方式串联在一起,数组使用内嵌structlist_head的方式表示。
5.内核中大量使用container_of宏,实现通过已知结构体对象内部元素的地址获取整个结构体起始地址的功能。
例:承继实现方式
/* 父类 */
struct ANIMAL {
int age;
int weight;
};
/* 子类 */
struct DOG {
struct ANIMAL animal; /* 通过内嵌父类对象,来实现继承关系 */
int variety;
};
/* 通过dog对象中animal对象的地址获取dog对象的起始地址 */
struct DOG *dog = container_of(ptr_animal, struct DOG, animal);例:数组的使用方式
struct xxx_dev {
int id;
int num;
struct list_head node; /* 通过在对象中嵌入struct list_head节点,来实现链表功能 */
};
/* 遍历链表的时候,已知node地址,借助container_of宏可以获取到外层对象xxx_dev的起始地址。*/
struct xxx_dev *dev = container_of(prt_node, struct xxx_dev, node);核心驱动框架:
linux内核中对不同的组成部份高度具象,采用"总线-设备-驱动"模型来组织某一层驱动代码,多层之间可以叠加。模型结构如下,总线作为桥梁和纽带,联接设备和对应的驱动。
(核心驱动框架)
设备:挂载在各个总线上的的硬件设备或则虚拟设备,例如挂在I2C总线上的温温度传感,挂载在平台总线上I2C控制器等,都被具象描述成设备对象。使用结构体structdevice表示设备泛型,拿来描述硬件设备的各类参数,具体各种设备可以通过内嵌子类对象,实现承继和扩充。
驱动:记录硬件设备状态的变量和控制硬件工作的函数的集合,负责对硬件设备进行初始化,并向下层代码提供操作插口,例如SOC中各种总线控制器驱动,以及外挂的总线设备驱动等,使用结构体structdevice_driver表示驱动泛型,具体各种驱动可以通过内嵌子类对象,实现承继和扩充。
总线:表示各类数学或则虚拟总线。总线作为桥梁和纽带,拿来联接设备和驱动,并提供驱动注册,设备发觉,设备注册/卸载等功能。常见的总线诸如:平台总线,I2C总线,SPI总线,USB总线等。其中平台总线是驱动工程师最长接触的总线类型,有些书籍把平台总线称作虚拟总线,说是这些没有实际化学总线的都归类为平台总线,我觉得这个说法不对。平台总线应当是指SOC中这些内部互联用的总线,例如ARMSOC中的AXI,AHB,APB总线等,这种总线上联接的大量的控制器,都可以通过地址映射直接访问,所以这种控制器通常被称为平台设备,联接的总线被称为平台总线。使用structbus_type表示总线泛型,具体各种总线可以通过内嵌泛型,实现承继和扩充。
驱动框架承继关系:
如前所述,Linux驱动框架中分别定义了"总线-设备-驱动"各对象的泛型,其他各泛型都是从泛型承继而至,承继关系如右图:
(承继关系)
用户插口:
所有的硬件都是为了实现个别具体功能而生的,驱动程序操作硬件设备就是为了给下层应用提供服务,然而linux内核为了安全,把运行空间分成了内核空间(kernelspace)和用户空间(userspace)两部份,其中内核代码运行在内核空间,应用程序运行在用户空间。应用程序通过系统调用插口,调用内核以及驱动提供的各类服务,示意图如下:
(系统调用示意图)
然而硬件种类多种多样,对应的驱动数目也不胜枚举,并且还在不断的变化中,不可能为每种驱动都提供系统调用插口,好在多数设备的操作步骤都很类似,主要可以概括为:初始化,读,写,关掉等基本步骤。依据设备的功能属性和使用方法不同,内核中把设备大体分为:字符设备,块设备和网路设备三个大类。其中字符设备和块设备由于操作步骤跟文件操作很相像,所以复用了VFS(虚拟文件系统)提供的系统调用插口(open,release,read,write,ioctl等插口),其在内核中分别使用structcdev和structblock_device表示,在用户空间以特殊文件方式存在于/dev目录下,使用ls-ls/dev可以查看各文件的属性,其中属性crw-rw-rw-以'c'打头的表示字符设备,属性brw-rw----以b打头的表示块设备。
cros@cros-pc:~$ ls -ls /dev/
total 0
0 crw------- 1 root root 5, 1 5月 28 00:04 console
0 crw-rw-rw- 1 root root 1, 7 5月 28 00:04 full
0 crw-rw---- 1 root kvm 10, 232 5月 28 00:04 kvm
0 brw-rw---- 1 root disk 8, 0 5月 28 00:04 sda
0 brw-rw---- 1 root disk 8, 1 5月 28 00:04 sda1
0 brw-rw---- 1 root disk 8, 2 5月 28 00:04 sda2网路设备由于操作方法不同,难以复用VFS的系统插口,所以只能单独提供几个独享的系统调用插口,如下SYSCALL_DEFINEx宏的第一个参数就是系统调用的名子:
cros@cros-pc:~/home/cros/kernel$ grep -rn "SYSCALL_DEFINE*" net/socket.c
1213:SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
1254:SYSCALL_DEFINE4(socketpair, int, family, int, type, int, protocol,
1363:SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
1392:SYSCALL_DEFINE2(listen, int, fd, int, backlog)
1425:SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
1506:SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
1524:SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
1556:SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr,
1587:SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr,
1619:SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
1663:SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len,
1675:SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
1720:SYSCALL_DEFINE4(recv, int, fd, void __user *, ubuf, size_t, size,
1731:SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname,
1765:SYSCALL_DEFINE5(getsockopt, int, fd, int, level, int, optname,
1795:SYSCALL_DEFINE2(shutdown, int, fd, int, how)
1988:SYSCALL_DEFINE3(sendmsg, int, fd, struct user_msghdr __user *, msg, unsigned int, flags)
2057:SYSCALL_DEFINE4(sendmmsg, int, fd, struct mmsghdr __user *, mmsg,
2154:SYSCALL_DEFINE3(recvmsg, int, fd, struct user_msghdr __user *, msg,
2272:SYSCALL_DEFINE5(recvmmsg, int, fd, struct mmsghdr __user *, mmsg,
2317:SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)另外,内核为了便捷用户层操作设备,引入了sys文件系统,坐落/sys目录下,其分别从总线,设备,类等不同角度描述整个驱动框架,如下所示:
cros@cros-pc:~$ ls -ls /sys/
total 0
0 drwxr-xr-x 2 root root 0 6月 2 10:25 block
0 drwxr-xr-x 43 root root 0 6月 2 10:25 bus
0 drwxr-xr-x 68 root root 0 6月 2 10:25 class
0 drwxr-xr-x 4 root root 0 6月 2 10:25 dev
0 drwxr-xr-x 24 root root 0 5月 28 00:04 devices
0 drwxr-xr-x 6 root root 0 5月 28 00:04 firmware
0 drwxr-xr-x 10 root root 0 5月 28 00:04 fs
0 drwxr-xr-x 2 root root 0 6月 2 10:25 hypervisor
0 drwxr-xr-x 15 root root 0 5月 28 00:04 kernel
0 drwxr-xr-x 182 root root 0 6月 2 10:25 module
0 drwxr-xr-x 3 root root 0 6月 2 10:25 power完整的用户插口如右图:
(用户插口框架)
代码模板:
内核模块模板:
内核驱动模块基本通过如下模板,注册初始化函数和卸载函数,作为驱动代码的入口和出口。
/* 内核模块初始化函数 */
static int __init xxx_init(void)
{
}
/* 内核模块注销函数 */
static void __exit xxx_exit(void)
{
}
/* 注册初始化函数,使得自动或者手动安装驱动时,自动执行初始化函数 */
module_init(xxx_init);
/* 注册注销函数,使得自动或者手动安装驱动时,自动执行注销函数 */
module_exit(xxx_exit);总线代码模板:
/* 总线类型结构体 */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
/* 内核模块初始化函数 */
int __init platform_bus_init(void)
{
int error;
/* 这里以平台总线为例,演示总线注册流程 */
error = bus_register(&platform_bus_type);
return error;
}
/* 注册初始化函数,使得自动或者手动安装驱动时,自动执行初始化函数 */
module_init(platform_bus_init);
/* 因为平台总线是内核中必不可少的基础总线,所以没有提供卸载函数 */驱动代码模板:
/* 设备驱动结构体 */
static struct platform_driver at91_twi_driver = {
/* probe函数负责解析设备对象提供的参数,进行硬件初始化,并向上层提供操作接口 */
.probe = at91_twi_probe,
/* 设备卸载执行的操作 */
.remove = at91_twi_remove,
.id_table = at91_twi_devtypes
.driver = {
.name = "at91_i2c",
/* 用于跟设备匹配用的字段 */
.of_match_table = of_match_ptr(atmel_twi_dt_ids),
.pm = at91_twi_pm_ops,
},
};
/* 内核模块初始化函数 */
static int __init at91_twi_init(void)
{
/* 以平台设备驱动为例,演示驱动注册过程 */
return platform_driver_register(&at91_twi_driver);
}
/* 内核模块注销函数 */
static void __exit at91_twi_exit(void)
{
/* 以平台设备驱动为例,演示驱动卸载过程 */
platform_driver_unregister(&at91_twi_driver);
}
/* 注册初始化函数,使得自动或者手动安装驱动时,自动执行初始化函数 */
module_init(at91_twi_init);
/* 注册注销函数,使得自动或者手动安装驱动时,自动执行注销函数 */
module_exit(at91_twi_exit);设备代码模板:
PS:新版本内核中由于引入了设备树,绝大多数设备都在设备树中描述了,内核初始化过程中会手动解析设备树,生成并注册设备,所以一下代码目前极少见了,此处只是为了解释原理和基本流程。
/* 平台设备结构体 */
static struct platform_device s3c24xx_uart_device0 = {
.id = 0,
};
static struct platform_device s3c24xx_uart_device1 = {
.id = 1,
};
static struct platform_device s3c24xx_uart_device2 = {
.id = 2,
};
static struct platform_device s3c24xx_uart_device3 = {
.id = 3,
};
struct platform_device *s3c24xx_uart_src[4] = {
&s3c24xx_uart_device0,
&s3c24xx_uart_device1,
&s3c24xx_uart_device2,
&s3c24xx_uart_device3,
};
/* 模块初始化函数 */
static int __init s3c_arch_init(void)
{
int ret;
/* 以平台设备为例,演示设备注册过程 */
ret = platform_add_devices(s3c24xx_uart_src, nr_uarts);
return ret;
}
/* 注册模块初始化函数,类似功能的宏还有很多,名字各不相同 */
arch_initcall(s3c_arch_init);平台设备驱动框架:
平台设备驱动是开发人员接触最多,也是更改最多的一类驱动,由于其主要包括SOC外置的各类总线控制器,以及PWM,RTC,WDT等外置功能模块。基本都是跟芯片强相关的内容,所以每位SOC都须要单独开发对应驱动。
(平台设备驱动举例)
总线设备驱动框架:
总线设备驱动相比于平台设备设备来说更复杂一些,通常包含两层驱动,底层是总线控制器驱动,下层是总线设备驱动。另外,由于总线控制器多种多样,为了统一下层的编程插口,驱动中会在中间降低core层,实现对总线控制器的具象,并对下层提供统一的总线操作插口,类似于设计模式中的适配器模式。典型如I2C驱动框架中的structi2c_adapterlinux启动盘制作工具,以及SPI驱动框架中的structspi_master。如下是I2C驱动框架,你们可以仔细品一下。
(I2C设备驱动框架)
内核中还有好多支持热拔插的设备驱动,比如USB驱动,同一个USB插口,可能接了设备,也可能没有接设备,可能接了个U盘,也可能接了个键盘。诸如mmc驱动,mmc插口可能插了个MMC卡usb驱动 linux,也可能插了个SD卡,还可能插了个SDIO网卡。我们没法假定插口上究竟接的是哪些设备,并且我们可以通过电平讯号判定是否接了设备。为了才能判定插口上接的是哪些设备,以及设备具有如何的参数,通常对应的商会就会指定一套建立的合同标准(比如USB合同,SD合同)。驱动代码中只要根据合同规定,跟设备进行通讯,获取到对方提供的信息,之后按照合同进行解析,就可以获得所接硬件的详尽信息。之后加载对应的驱动就可以正常使用硬件了。以下是mmc驱动框架,相比于I2C驱动框架,主要是多了合同解析部份,你再细品!
(MMC驱动框架)
总结:
还是重点指出一点,学习新东西,一定是要由远及近,逐步深入。先晓得每位模块是干哪些的,之后在学会如何使用,最后才是深入去研究工作原理,以及怎样更改。学习驱动开发更是这样,熟悉基本的驱动框架和各个模块的具体框架才是你第一步须要做的,剩下工作就是配置寄存器,初始化硬件设备了,这不就是单片机工程师如今正在做的事情吗?
见到这,你学废了吗?