
嵌入式 Linux 内核驱动开发【1】
由于篇幅太大,以章分块,按天写
嵌入式 Linux 内核驱动开发前言
本篇主要讲述嵌入式 Linux 产品开发过程中的内核/驱动开发部分相关内容,包括 Linux 内核裁剪定制、驱动编写和驱动移植等。进行嵌入式 Linux 驱动开发,一些特定外设需要从零开始编写驱动,然而很多外设基本都有可参考驱动,在实际工作中仅需进行移植,本篇特 意给出了 3个驱动移植实例。
本篇一共分 11 章,各章标题和内容概要如下:
第 1 章 Linux 内核裁剪和定制,首先介绍了几种内核源码查看工具,然后对内核目录树和相关文件进行介绍,接着给出了内核配置详情以及裁剪实例;
第 2 章 Linux 设备驱动基础,由浅入深的介绍了 Linux驱动编写相关知识点,从内核模块、字符设备驱动到平台设备驱动都有详细讲解,并给出了相应的范例代码;
第 3 章 LED 子系统和驱动,分析了内核中的 LED 子系统,并给出了相关实现实 例;
第 4 章 GPIO 驱动,分析了内核中的GPIOLIB子系统,并给出了相关实现实例;
第 5 章 输入子系统和按键驱动,分析了内核中的输入子系统,并给出了按键驱动 实现范例;
第 6 章 I2C 总线和外设驱动,分析了内核中的 I2C 子系统,并给出了 I2C 接口 EEPROM 驱动实现范例;
第 7 章 SPI总线和外设驱动,简要分析了 SPI 总线驱动,并实现了两种典型 SPI 设备驱动;
第 8 章 UART 和 SC16IS752驱动,简析了 UART 驱动子系统,并对 SC16IS752 的驱动实现进行了详细分析;
第 9 章 SGTL5000声卡驱动移植,介绍 SGTL5000 在 i.MX283 平台的移植过程;
第 10 章 AP6181 无线网卡驱动移植,介绍 AP6181 无线网卡在i.MX283平台的移植过程;
第 11 章 SIM6360-PCIE 模块驱动移植,介绍SIM6360-PCIE 模块驱动移植和 PPP 拨号上网的过程。
本篇的内容涵盖了嵌入式 Linux 产品开发过程中底层开发的大部分工作,给出的实例也都具有很强的参考意义。
第1章 Linux 内核裁剪和定制
【1】Linux 内核开发简介
这里所说的“Linux 内核开发”仅仅是指嵌入式 Linux 产品开发中内核和驱动相关开发工作,与 Linus所领导的内核开发团队的内核开发有很大不同。 产品开发中对内核进行二次开发,需要开发人员具备如下一些基本技能和背景知识:
- 具备操作系统的基本知识,理解操作系统原理,最好了解 ~~Linux 操作系统~~ ;
- 内核绝大部分都是 C 语言编写的,~~C 语言~~ 是必备技能;
- 内核是用 ~~GNU C 编写的~~ ,尽管符合 ISO C89 标准,但还是使用了一些 GNU 扩展,所以对 GNU C 的一些扩展也必须有所了解;
- 对 ~~Linux 内核源码~~ 基本分布有大致了解;
- 产品级的内核开发通常还包括一些内核驱动工作,对~~外设工作原理和驱动编写~~ 也必须有一定的了解。
【2】 Linux 源码阅读工具
俗话说“工欲善其事,必先利其器”,面对几百兆的 Linux 内核代码,要阅读、查看或者搜索其中的代码,大部分初次接触到 Linux 内核代码的开发人员,都有无从下手的感觉。
下面推荐几个源码阅读和索引工具,能为后续内核开发提供一些便利。
【1.2.1】Source Insight
Source Insight 是 Windows 平台下一款流行度极高的源码阅读和编辑工具。不少 Linux开发人员还是习惯于在 Windows 下进行源码编辑,甚至查看和编辑 Linux 内核源码,依然在 Source Insight 中完成。
注意:蓝色字体的超链接是我改过的为了好看而已
安装 Source Insight 软件后,新建一个工程,取名并指定数据存放位置,如图 1.1 所示。


然后添加源码。浏览选中 Linux 内核源码文件夹后,点击“Add Tree”按钮,将内核源码树的全部文件添加到工程中,如图 1.3 所示。

添加完成,即可在 Source Insight 中进行源码阅读和编辑了,如图 1.4 所示。

【1.2.2 Eclipse】
Eclipse 是一个跨平台 IDE,既能运行于 Windows 平台,也能在 Linux 下运行。不少习惯于 图形界面操作的开发人员,在 Linux 下则习惯于用 Eclipse 来查看和编辑 Linux 源码。 如果仅仅是在 Eclipse 中查看 Linux 内核源码,则可以不必事先安装交叉编译器,否则 则须事先安装好交叉编译器。
创建内核源码工程。点击 FileNewProject,开始创建工程,在工程创建界面选择创建 C 工程,如图 1.5 所示。

点击 Next,在 C Project 界面的 Project name 栏中填写工程名称,去掉“Use default location”的勾,点击 Browse 将 Location 设置为 Linux 内核源码目录,如图 1.6 所示。如果不在 Eclipse中编译内核,则使用 Linux GCC 即可,否则请使用安装好的 Cross GCC

然后点击 Finish,完成 Linux 内核源码导入,在 Eclipse 中即可进行代码阅读和编辑了,如图 1.7 所示。

在 Eclipse 中进行源码跟踪,只需选择函数、变量或者宏定义后按 F3 即可。更多的操作可在 Navigate 中找到。
【1.2.3】 vim+ctags+cscope
Vi/Vim 是一个文本编辑器,在 Vim 中能高效的实现代码编辑。但 Vim 的功能不仅仅是一个文本编辑器,借助 ctags 和cscope 的配合,Vim 能实现堪比图形 IDE 环境的源码编辑和阅读功能,在某种程度上甚至比图形 IDE 更方便。 Vi/Vim 的安装不再介绍了。如果不是通过远程登录在远程服务器上工作,而是在本地桌面系统操作,还可以用 gvim 启动 Vi 编辑器。
1. Taglist
Taglist 是 Vim 的一个源码浏览插件,可从http://www.vim.org 网站获得。下载到压缩包后,在本地解压,然后将解压得到目录中的 plugin 目录复制到~/.vim 目录。如果用户主目录 下没有.vim目录,则建立命令mkdir .vim一个这样的目录即可。
2. Ctags
Ctags 是一个用于产生 tags 文件的软件,可以下载源码进行编译安装,在 Ubuntu 下, 可通过 apt-get 进行安装:
- $ sudo apt-get install exuberant-ctags
复制代码 3. 源码阅读和跟踪
进入准备查看的源码所在目录,首先生成 tags 文件:
执行时间长短取决于源码数量的多少,执行完毕,在当前目录下可看到一个 tags 文件。 源码越多,执行时间越长,产生的 tags文件也越大。
注意:如果修改了源码,代码行号发生了变化,需要重新生成 tags 文件。
(1)查看函数等定义。用 Vi/Vim 打开一个 C 文件。若想知道某个函数、变量、结构 或者宏定义在什么地方定义,先将光标移动到函数(变量、结构或者宏定义)上,然后按 CTRL+]即可。查看后,按 CTRL+o
可回到原来所在位置。
(2)查看文件函数列表。打开C 文件后,在Vi/Vim的命令状态下输入:TlistToggle(Vi/Vim的命令输入支持补全),在 Vi/Vim 左边就会出现函数列表侧栏,如图 1.8 所示。按 CTRL+ww (2 次> w),可在列表和代码查看区间切换。

如果在本地桌面,用 Gvim 打开 C 文件,使用起来比较接近 IDE 集成环境。用鼠标双击函数即可跳转到函数定义的地方,CTRL+鼠标右键即可回退到原来所在位置。更多实用特性,还需要在实际操作中体验。
【1.2.4】 LXR
LXR 是 Linux Cross Referencer 的缩写,是一个比较流行的 Linux 源码查看工具,当然也不仅仅局限于查看 Linux 源码。LXR 的下载地址为:http://lxr.sourceforge.net,参考该网站
的安装说明,很容易在本机搭建一个本地 LXR 用于源码查看。
如果不想搭建本地 LXR,可以直接浏览已经搭好的 LXR 网站,推荐两个网站:一个是开源中国网站提供的 Linux 源码在线阅读 http://lxr.oss.org.cn,另一个是http://lxr.free-electrons.com 网站,前者速度较快,但是提供的 Linux 内核版本较少,后者则提供的版本较多。网站提供了源码阅读、关键字搜索和自由文本搜索功能。两者的网页快照分别如图 1.9 和图 1.10 所示。


【3】Linux 内核源码
【1.3.1 目录树概览】
解压 Linux 内核源码压缩包,将得到内核源码。内核源码很复杂,包含多级目录,形成 一个庞大的树状结构,通常称为 Linux源码目录树。进入源码所在目录,可以看到目录树顶 层通常包含如下目录和文件:

各个目录文件的简要说明如表 1.1 所列。
目录内容arch/包含各体系结构特定的代码,如 arm、x86、ia64、mips 等,在每个体系结构目录下通常都有: -boot 内核需要的特定平台代码 -kernel 体系结构特有的代码 -lib 通用函数在特定体系结构的实现 -math-emu 模拟 FPU 的代码,在 ARM 中,使用 mach-xxx 代替 -mm 特定体系结构的内存管理实现 -include 特定体系的头文件block/存放块设备相关代码crypto/存放加密、压缩、CRC 校验等算法相关代码Documentation/存放相关说明文档,很多实用文档,包括驱动编写等drivers/存放 Linux 内核设备驱动程序源码。驱动源码在 Linux 内核源码中站了很大比例,常见外设几乎都有可参考源码,对驱动开发而言,该目录非常重要。该目录包含众多驱动,目录按照设备类别进行分类,如 char、block、input、i2c、spi、pci、usb 等firmware/存放处理器相关的一些特殊固件fs/存放所有文件系统代码,如 fat、ext2、ext3、ext4、ubifs、nfs、sysfs 等include/存放内核所需、与平台无关的头文件,与平台相关的头文件已经被移动到 arch 平台的include 目录,如 ARM 的头文件目录 init/包含内核初始化代码ipc/存放进程间通信代码kernel/包含 Linux 内核管理代码lib/库文件代码实现mm/存放内存管理代码net/存放网络相关代码samples/存放提供的一些内核编程范例,如 kfifo;后者相关用户态编程范例,如 hidraw srcipts/存放一些脚本文件,如 menuconfig 脚本security/存放系统安全性相关代码sound存放声音、声卡相关驱动tools/编译过程中一些主机必要工具usr/cpio 相关实现virt/内核虚拟机 KVM Linux 内核源码数量很庞大,解压后大约好几百兆字节,要能在如此庞大的源码中找到 有效代码,熟悉 Linux 源码目录树的结构是基本要求。每个目录所包含的代码量差异也很大, 下面是从 http://www.kernel.org 下载的一份源码解压后的统计结果,其中drivers目录几乎占了源 码总量的一半,arch 目录也差不多有 1/4:

【1.3.2】 快速确定主板关联代码
拿到一份源码和一块评估板,如何快速找到与这块板相关的源码,是很多研发人员都曾遇到过的问题。如果对内核源码结构有大概了解,要完成这些事情也不难,通常可按照基础代码、驱动代码和其它代码等方面来梳理。
1. 基础代码
Linux
移植通常分为体系结构级别移植、处理器级别移植和板级移植,各级别移植难易程度差异很大,工作量和调试方式也各不相同。一般的产品开发人员所进行的内核移植,通常都是板级移植,这是几个级别中最简单的。
从代码层面来看,通常把能让一个主板最小系统能运行的代码称为基础代码,这部分代码通常包含体系结构移植代码、处理器核心代码以及板级支持包的部分代码。理清了这部分代码,对于了解和掌握整个主板相关代码具有重要意义。
确定主板名称和默认配置文件。
例如,对于 EPC-28x 工控板,其对应的默认内核配置 文件为”表示,如图 1.11 所示。

子菜单的菜单项则由config来定义,随后的“bool”、“default”、“help”等都是该菜单项的属性:
- # Intel/Marvell Dev Platforms
- obj-$(CONFIG_ARCH_LUBBOCK) += lubbock.o
- obj-$(CONFIG_MACH_MAINSTONE) += mainstone.o
- obj-$(CONFIG_MACH_ZYLONITE300) += zylonite.o zylonite_pxa300.o
复制代码 这两行语句定义了一个bool选项,在.config中的配置变量名称为CONFIG_DEVKMEM,选项提示信息为“/dev/kmem virtual device support”,在内核配置界面的实际表现为:
- VERSION = 2
- PATCHLEVEL = 6
- SUBLEVEL = 35
- EXTRAVERSION =3
复制代码 由于设置其默认属性 default 为 y,所以该选项默认选中。
help 引出帮助信息,在内核配置界面,选择选项后,通过可以查看帮助信息。
2. 属性
类型定义:每个菜单项都必须定义类型,可选类型有:bool、tristate、string、hex 和 int, 各类型描述如表 1.3所列。

定义选项的类型后面可以加菜单信息,用引号(“”)给出,留空则不加提示信息。
对于布尔型选项,在配置界面用[ ]表示:
- VERSION = 2
- PATCHLEVEL = 6
- SUBLEVEL = 35
- EXTRAVERSION =3
复制代码
表示选中,对应 CONFIG_XXX=y,[ ]则表示未选中。
对于三态选项,在配置界面用< >表示:
表示选中,对应 CONFIG_XXXx=y,表示编译为模块,对应 CONFIG_XXX=m, < >表示未选中。 子菜单也可同时设置类型,如下列代码在定义 PWM 菜单的同时定义了菜单属性为三态:
- SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
- -e s/arm.*/arm/ -e s/sa110/arm/ \
- -e s/s390x/s390/ -e s/parisc64/parisc/ \
- -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
- -e s/sh[234].*/sh/ )
复制代码 在配置界面表现为:
说明:子菜单的配置值会影响其子选项的可能值。例如三态子菜单配置为,则其三 态子选项依旧可有 3种可能值,即可配置为、或者不选中;而三态子菜单配置为, 则其子选项只有和不选中两种状态可用。
默认值:有写选项可以设置默认值,无论是哪种类型,都可以通过 default 设置其默认值,例如:
- CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
- ……
- AS = $(CROSS_COMPILE)as
- LD = $(CROSS_COMPILE)ld
- CC = $(CROSS_COMPILE)gcc
- CPP = $(CC) –E
- AR = $(CROSS_COMPILE)ar
- NM = $(CROSS_COMPILE)nm
- STRIP = $(CROSS_COMPILE)strip
- OBJCOPY = $(CROSS_COMPILE)objcopy
- OBJDUMP = $(CROSS_COMPILE)objdump
复制代码 选中:前面这个示例的 select,表示了一种选中关系,即选中某个选项后,会自动选中 某个或者某些选项。
前面这个示例表明,选中 ARM后,会自动选中HAVE_AOUT。 依赖关系:如果一个选项能否生效与否与其它选项的设置有关,则必须通过 depends on 来声明这种依赖关系。例如,只有使能了 SMP 才能设置 CPU 个数变量 NR_CPUS,在Kconfig中则写成:
- $ make ARCH=arm CROSS_COMPILE= arm-linux-gnueabihf-
复制代码 帮助:通过 help 关键字引入帮助,帮助的正文必须另起一行。
菜单选项属性的每个关键字,必须用 TAB 键与行首隔开,不能用等数的空格替代。
3. 目录层次迭代
通过source可以直接引用下级目录的 Kconfig 文件,形成新的菜单项或者子菜单,这样方便每个目录独立管理各自的配置内容。“source "drivers/tty/Kconfig"”就是直接引用 文件,形成更多菜单(项)。
【1.5.2】 配置项和配置开关
通过config 定义的菜单配置项,在内核配置后会产生一个以“CONFIG_”开头的配置开关变量,该开关变量可在·Makefile 中或者源代码中使用。
例如:“config BAR”将会产生一个开关变量 CONFIG_BAR,在 Makefile 中可以这么使用:
- CROSS_COMPILE = arm-linux-gnueabihf-
复制代码 在源代码中可用这个开关变量来进行一些条件处理,例如:
- CROSS_COMPILE =/home/ctools/linux-devkit/bin/arm-linux-gnueabihf-
复制代码 如果定义的 BAR 是三态变量,则还可以根据需要这样使用:
- obj-y += usb-host.o # 默认编译 usb-host.c 文件
- obj-y += gpio/ # 默认编译 gpio 目录
复制代码 【6】 配置和编译 Linux 内核
对内核进行正确配置后,才能进行编译。配置不当的内核,很有可能编译出错,或者不能正确运行。
【1.6.1】 快速配置内核
进入 Linux 内核源码数顶层目录,输入make menuconfig命令,可进入如图 1.12 所示的 基于 Ncurses 的 Linux 内核配置主界面(注意:主机须安装 ncurses 相关库才能正确运行该 命令并出现配置界面)。如果没有在 Makefile 中指定 ARCH,则须在命令行中指定:
- obj-$(CONFIG_WDT) += wdt.o # wdt.c 编译控制
- obj-$(CONFIG_PCI) += pci/ # pci 目录编译控制
复制代码

配置完毕,将光标移动到配置界面末尾,选中“Save an Alternate Configuration File”后回车,保存当前内核配置,默认配置文件名为.config,如图 1.13 所示。

保存完毕,选择退出内核配置界面,回到终端命令行。
当然,也可以将配置文件命名为其它文件名,如 config-bak 等,但该配置不会被 Makefile
文件使用,Makefile 默认使用文件名为.config 的配置文件,所以重新命名配置文件通常在保留或者备份内核配置信息时使用。 也可以不用“Save an Alternate Configuration File”操作,连按 ESC 或选择退出内核配置界面,将会出现如图 1.14 所示的保存配置提示信息,选择后回车,内核配置将会被保存为.config 文件。

备份内核配置,在命令行下将.config 文件复制为其它文件名来得更简单快捷:
- menu "Character devices"
-
- source "drivers/tty/Kconfig"
-
- config DEVKMEM
- bool "/dev/kmem virtual device support"
- default y
- help
- Say Y here if you want to support the /dev/kmem device. The
- /dev/kmem device is rarely used, but can be used for certain
- kind of kernel debugging operations.
- When in doubt, say "N".
- ……
- endmenu
复制代码 装载某个配置文件,可在配置界面选中“Load an Alternate Configuration File”,然后填 入已存在的配置文件名称。也可在命令行下将配置文件复制为.config:
- config DEVKMEM
- bool "/dev/kmem virtual device support"
复制代码 在目录下有很多*_defconfig 文件,这些都是内核的预设配置文件,分别对应各种不同的参考板。如果要使用其中的配置文件作为内核编译配置,可用“make xxx_defconfig”命令来完成。对于已经设定好的内核配置,也可以命名为某个文件名,放到 目录下,在以后直接用 make 来调用该配置即可。例如将当前配置命名为 m3352_defconfig 并放到目录下,后续只需执行下列命令即可使用当前配 置:
- [*] /dev/kmem virtual device support
复制代码 【1.6.2】 内核配置详情
Linux 内核配置菜单比较复杂,下面对一些比较重要的配置界面进行介绍,更多的详细配置,建议进行实际操作。另外,由于 Linux 内核版本差异,实际看到的内核配置界面可能与本节的介绍有所差异。
图 1.12 所示的内核配置主界面,实际包含了如表 1.4 所列的各项一级菜单。



一级菜单下的每一项几乎都有复杂的下级子菜单,各自的配置选项也很丰富,每项的意义也各不相同,如果逐一进行描述,将会是一件非常繁琐的事。而实际产品开发中,并不需要完全了解内核的每一个配置项,通常只需要了解其中一些相关项即可。
1. 通用设置
进入 General setup 是内核通用设置菜单界面,菜单选项众多,通常可以关注表 1.5 所列 选项。

2. 内核特性
Kernel Features 是内核特性配置菜单,常用选项介绍如表 1.6 所列。


3. 启动选项
启动选项一般关心内核启动参数设置即可,可设置默认启动参数和内核参数类型。 默认启动参数通过“Default kernel command string”设置,例如:
- [*] /dev/kmem virtual device support
复制代码 内核参数类型通过 Kernel command line type 来设置,可选值:
- <*> Kernel .config support
复制代码 如果设置为“Always use the default kernel command string”则只能使用默认内核启动参数,通常会设置为“Use bootloader kernel arguments if available”,可接受 Bootloader 传递的参数启动。
4. 网络支持
网络支持部分,包括了以太网、CAN、红外、蓝牙、无线等各种网络的支持配置选项。 网络选项配置。从 Networking support Networking options,可进入网络选项配置界面, 网络的配置很复杂,常用的一些配置选项和说明如表 1.7 所列。


通常来说,使用Linux的系统都会用到网络,而使用网络又往往离不开 TCP/TP,故建 议在配置中选中 TCP/IP选项,并选中下级全部选项,配置后的 TCP/IP 选项如程序清单 1.2 所示。
程序清单 1.2 TCP/IP 配置
- menuconfig GENERIC_PWM
- tristate "PWM Support"
- default n
- help
- Enables PWM device support implemented via a generic
- framework. If unsure, say N.
复制代码 这些配置中,三态选项也可以配置为,在需要的时候再插入模块。
对于 IPv6,现在已经有不少应用需求,建议配置为,并选中配置菜单中的全部选项,在需要的时候再插入模块。
特别说明一下CAN的配置选项。CAN-Bus 相关协议支持以及 CAN 设备驱动配置项都在这里,并没有将 CAN 设备驱动放在 drivers 配置菜单中。CAN-Bus 子系统配置界面如图
1.15 所示。

其中的“CAN Device Drivers”子菜单下可选择具体的 CAN 设备,如图 1.16 所示。具体选择哪个 CAN 设备驱动,与具体的硬件平台相关。

5. 设备驱动
Linux 内核支持众多外设,设备驱动程序很多,配置界面也很复杂,有众多配置项,如 表 1.8 所列。





6. 文件系统
进入 File systems,是内核文件系统配置界面,可以看到很多文件系系统配置选项,如 图 1.17 所示。

一个完整的嵌入式 Linux 系统往往会支持多种文件系统,但绝非“File systems”菜单下的全部。这里仅对当前主流系统比较常用的一些文件系统配置项进行介绍,如表 1.9 所列。



【1.6.3】 编译内核
内核配置完成,输入 make 命令即可开始编译内核。如果没有修改 Makefile 文件并指定 ARCH 和 CROSS_COMPILE参数,则须在命令行中指定:
目前大多数主机都是多核处理器,为了加快编译进度,可以开启多线程编译,在 make 的时候加上“-jN”即可,N 的值为处理器核心数目的 2倍。例如对于 I7 4 核处理器,可将 N 设置为 8:
采用多线程编译的优点是能加快编译进度,缺点是如果内核中有错误,某个编译线程遇到错误终止了编译,而其它编译线程却还在继续,出错线程的错误提示通常会被其它编译线 程的输出信息淹没,不利于排查。对于这种情况,则建议改为单线程编译,直到错误排除。
如果编译不出错,编译完成,会生成 vmlinux、Image、zImage 等文件,各文件说明如 表 1.10 所列。

1. zImage
zImage是通常情况下默认的压缩内核,可以直接加载到内存地址并开始执行。它从 文件经过 objcopy 处理得到。在 ARM Linux 下最终生成 zImage 的各个参数记录在文件中。AM3352 内核生成 zImage 的
实际参数为:
- config NR_CPUS
- int "Maximum number of CPUs (2-32)"
- range 2 32
- depends on SMP
- default "4"
复制代码 说明 1:路径信息与实际具体编译环境有关。
说明 2:如果在 64 位 ubuntu 下编译 Linux 内核,在编译过程中很有可能出现
“arm-fsl-linux-gnueabi/bin/as: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory”这样的错误,这是因为没有正确安装libz库所致,
可“sudo apt-get install zlib1g:i386”命令安装解决。
2. uImage
对于 ARM Linux 系统,大多数采用U-Boot引导,很少直接使用 zImage 映像,实际上更多的是 uImage。uImage 是 U-Boot 默认采用的内核映像文件,它是在 zImage 内核映像之前加上了一个长度为 64 字节信息头的映像。这 64 字节信息头包括映像文件的类型、加载位置、生成时间、大小等信息(可参考 U-Boot 源码文件的 image_header_t 数据结构定义)。进入目录,用 ls 命令查看,uImage 文件大小比 zImage 大 64字节:
- obj-$(CONFIG_BAR) += file_bar.o
复制代码 在 U-Boot 下,通过 bootm 命令可以引导 uImage 映像文件启动。
3. mkimage 工具
从zImage生成uImage需要 用到 mkimage 工具。该工具可在编译 U-Boot 源码后从 tools 目录下获得,复制到系统/usr/bin 目录即可;对于 Ubuntu 系统,还可用 sudo apt-get install u-boot-tools 命令安装得到。进入 mkimage 文件所在目录执行该文件,或者在安装 mkimage 工具后,直接在终端输入 mkimage 命令,可以得到 mkimage 工具的用法:
- #if defined (CONFIG_BAR)
- 实际处理代码
- #endif
复制代码 使用 mkimage 工具根据 zImage 制作 uImage 映像文件的命令如下:
- #if defined (CONFIG_BAR) || defined (CONFIG_BAR_MODULE)
- 实际处理代码
- #endif
复制代码 命令参数中需要指定体系结构、操作系统类型、压缩方式和入口地址等信息,各参数说明如表 1.11 所列。

对于EPC-28x处理器,内存起始地址为 0x40000000,从 zImage 生成 uImage 映像文件的命令实际操作范例:
- quiet_cmd_uimage = UIMAGE $@
- cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \
- -C none -a $(LOADADDR) -e $(STARTADDR) \
- -n 'Linux-$(KERNELRELEASE)' -d $< $@
复制代码 说明:内存地址与处理器相关,在不同处理器上可能有差异。
mkimage 除了可以制作 uImage 映像文件之外,还可以查看一个 uImage 映像文件的文件头信息,用法:
例如,用 mkimage 工具查看 EPC-28x 工控主板的 uImage 内核映像,可以得到如下信息:
如果只有zImage内核映像文件,需要转换成 uImage 映像文件,则只能通过上述命令来实现。但是如果有内核源码,那生成 uImage 的方法就简单很多。实际上,Linux 内核已经支持直接生成 uImage 格式映像文件,在文件中给出了uImage的生
成规则:
- $ make m3352_defconfig 或者
- $ make ARCH=arm CROSS_COMPILE= arm-linux-gnueabihf- m3352_defconfig
复制代码 生成 uImage 的编译命令为 make uImage:
- < > PWM Support ---> -j8 uImage
复制代码 在 ARM Linux 下最终生成uImage的各个参数记录在文件中。对于在 EPC-28x 的 Linux 内核,实际参数为:
- ( ) Use bootloader kernel arguments if available
- ( ) Extend bootloader kernel arguments
- ( ) Always use the default kernel command string
复制代码 4. 编译内核模块
如果内核中有配置为的模块或者驱动,需要在编译内核后再通过 make modules 命 令编译这些模块或者驱动:
- < > PWM Support ---> modules
复制代码 编译得到的内核模块文件以“.ko”结尾,这些可以通过insmod命令插入到运行的内核中。
有的模块编译得到单一的“.ko”文件,且不依赖于其它模块,这样的模块可以直接用insmod 命令插入系统而不会出现错误。
有的模块则可能编译后得到多个“.ko”文件,或者依赖于其它模块文件,且各文件插入还有顺序要求,这就是常说的模块依赖。对于这样的情况,用 insmod 命令手工尝试得到依赖关系,然后按顺序插入也是可以的,但不推荐这样做,毕竟很麻烦。
建议编译模块后,再通过 make modules_install 命令安装模块,可将编译得到的全部模块安装到某一目录下,并且还会生成模块的依赖关系文件。默认情况下会将内核模块安装到编译机器的“/”目录下,这一方面需要root权限,另一方面容易与主机文件混淆。建议通过INSTALL_MOD_PATH参数指定模块安装路径:
- $ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
复制代码 安装后将在安装目录下生成“lib/modules/内核版本/”目录,该目录下通常有下列文件 和目录:

所有的内核模块都在kernel目录下,modules.dep 是全部模块的依赖关系文件。将“lib/modules/内核版本/”复制到目标系统后根目录后,就可以用 modprobe 命令进行模块安装,而不用手工逐一加载各个模块。该文件内容多少与内核模块多少相关,现在摘取一个实例片段进行说明:
- $ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- -j8
复制代码 每行开头至冒号(:)之前的表示一个内核模块,冒号之后的表示该模块所依赖的其它模块, 必须先加载后面的模块才能加载该模块文件。冒号后面为空则表示该模块没有依赖关系。 第(1)行表示模块文件 bonding.ko 没有依赖关系,可以直接用 insmod 命令加载到内核中:
- cmd_arch/arm/boot/zImage := /home/ctools/i686-arago-linux/usr/bin/arm-linux-gnueabihf-objcopy -O binary
- -R .comment -S arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage
复制代码 用 insmod 加载模块,必须指明文件路径,否则不能加载。用 modprobe 命令加载则无需 带路径:
- $ cd arch/arm/boot
- $ ls -la Image zImage uImage
- -rwxrwxr-x 1 chenxibing chenxibing 6460852 Jul 25 09:24 Image
- -rw-rw-r-- 1 chenxibing chenxibing 3135544 Jul 25 09:24 uImage
- -rwxrwxr-x 1 chenxibing chenxibing 3135480 Jul 25 09:24 zImage
复制代码 第(2)和(3)则共同说明了模块文件ftdi_sio.ko依赖于usbserial.ko文件,usbserial.ko 没有依赖文件。用 insmod 命令用法如下:
- $ ./mkimage 或者 mkimage
- Usage: ./mkimage -l image
- -l ==> list image header information
- ./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
- -A ==> set architecture to 'arch'
- -O ==> set operating system to 'os'
- -T ==> set image type to 'type'
- -C ==> set compression type 'comp'
- -a ==> set load address to 'addr' (hex)
- -e ==> set entry point to 'ep' (hex)
- -n ==> set image name to 'name'
- -d ==> use image data from 'datafile'
- -x ==> set XIP (execute in place)
- ./mkimage [-D dtc_options] -f fit-image.its fit-image
- ./mkimage -V ==> print version information and exit
复制代码 用 modprobe 命令就简单了:
- $ mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
复制代码 1.6.4 运行内核
得到 uImage 映像文件后,将 uImage 加载到内存地址ep-0x40处,通过 bootm 命令即可 运行内核:
- $ mkimage -A arm -O linux -T kernel -C none -a 0x40008000 -e 0x40008000 -n 'Linux-2.6.35' -d
- arch/arm/boot/zImage arch/arm/boot/uImage
复制代码 uImage 启动会打印文件头信息并进行校验和计算,校验通过后开始内核自解压并运行:
【7】 Linux 内核裁剪实例
从零开始配置内核是不明智的,建议在某一个默认配置的基础上进行修改,以达到自己 产品的实际需求。 裁剪和配置内核的基本原则:
- $ mkimage -l uImage
- Image Name: Linux-2.6.35.3-571-gcca29a0-g191
- Created: Tue Nov 17 11:57:47 2015
- Image Type: ARM Linux Kernel Image (uncompressed)
- Data Size: 2572336 Bytes = 2512.05 kB = 2.45 MB
- Load Address: 40008000
- Entry Point: 40008000
复制代码 下面给出一些常见功能的配置裁剪实例,很多功能与所采用的主板硬件相关,与其它不 同主板的内核配置上不一定完全相同,但还是有一些参考意义。
【1.7.1】 GPIO 子系统配置
Linux 2.6 以上内核引入了子系统, GPIO 子系统将全部 GPIO 的操作接口都通过 “/sys/class/gpio/” 目录导出,非常方便用户使用。 输入下列命令,进入内核配置菜单:
- obj-$(CONFIG_WDT) += wdt.o # wdt.c 编译控制
- obj-$(CONFIG_PCI) += pci/ # pci 目录编译控制
复制代码 在主菜单界面中选择“Device Drivers”:
- $ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- -j8 uImage
复制代码 进入“Device Drivers”界面,选择并进入“GPIO Support”:
- cmd_arch/arm/boot/uImage := /bin/bash /home/vmuser/prj/m28x/kernel/linux-2.6.35.3/scripts/mkuboot.sh -A arm
- -O linux -T kernel -C none -a 0x40008000 -e 0x40008000 -n 'Linux-2.6.35.3-571-gcca29a0-g1914ba0' -d
- arch/arm/boot/zImage arch/arm/boot/uImage
复制代码 在“GPIO Support”中选中“/sys/class/gpio…”:
- $ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- modules
复制代码 配置后重新编译内核,使用新内核的系统即可通过“/sys/class/gpio/”访问系统的 GPIO了。
【1.7.2 】LED 子系统配置
Linux LED 子系统提供了“/sys/class/leds/”的访问接口,启用 LED 子系统能很方便地 操作系统的 LED 资源。
在“Device Drivers”配置界面,选中“LED Support”支持:
- $ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabiINSTALL_MOD_PATH=/home/chenxibing/work/rootfs
- modules_install
复制代码 进入“LED Support”子菜单,选中 LED 类支持和 LED 触发器支持,并根据需要设置触发器:
- kernel/drivers/net/bonding/bonding.ko: (1)
- kernel/drivers/usb/serial/usbserial.ko: (2)
- kernel/drivers/usb/serial/ftdi_sio.ko:kernel/drivers/usb/serial/usbserial.ko (3)
复制代码 只要将系统的 LED 设备驱动添加到 LED 子系统中,即可通过“/sys/class/leds/”接口来进行访问。
【1.7.3】 串口配置
串口是嵌入式 Linux 必不可少的外设,默认控制台通常就是串口,所以必须在内核中使能串口以及串口控制台支持。
在“Device Drivers”配置界面,选择“Character devices”:
- # insmod kernel/drivers/net/bonding/bonding.ko
复制代码 进入“Character devices”配置菜单,选择“Serial drivers”:
- VERSION = 2
- PATCHLEVEL = 6
- SUBLEVEL = 35
- EXTRAVERSION =3 Serial drivers ---> [ ] ARM JTAG DCC console
复制代码 进入“Serial drivers”,在配置界面进行串口控制器配置。嵌入式 Linux 默认控制台是串口,所以还需使能串口控制台支持。串口控制器与具体处理器相关,需要根据硬件进行选择,很多处理器移植代码会默认选中自身的串口驱动支持,例如 EPC-28x,已经默认选中了“i.MXS Application serial port support”:
- # indmod kernel/drivers/usb/serial/usbserial.ko
- # insmod kernel/drivers/usb/serial/ftdi_sio.ko
复制代码 【1.7.4】 USB Host 驱动配置
USB 可以外接多种设备,不同设备的驱动配置也是不同的。下面以常用的 U 盘、USB鼠标键盘配置为例进行介绍。
1. 使用 U 盘
U 盘在 Linux 系统下被认为是 SCSI 设备,所以必须在内核中选择支持 SCSI。在主菜单 界面选择“Device Drivers”,进入设备驱动配置界面,选择“SCSI device support”:
进入“SCSI device support”配置界面,进行如下配置:
- # tftp 40007fc0 uImage
- # bootm 40007fc0
复制代码 然后在驱动中配置 USB 控制器。进入“Device Drivers”,选中“USB support”:
- ## Booting kernel from Legacy Image at 40007fc0 ...
- Image Name: Linux-2.6.35.3-571-gcca29a0-gd43
- Image Type: ARM Linux Kernel Image (uncompressed)
- Data Size: 2653928 Bytes = 2.5 MB
- Load Address: 40008000
- Entry Point: 40008000
- Verifying Checksum ... OK
- Loading Kernel Image ... OK
- OK
-
- Starting kernel ...
-
- Uncompressing Linux... done, booting the kernel.
- ……
- 以下省略
复制代码 进入“USB support”菜单,选中“Support for Host-side USB”,并根据处理器的控制器 情况配置 USB 控制器。下面是 EPC-28x 处理器 USB 控制器的配置:
- 基于某一个最接近的主板配置来修改;
- 必须的、能确定的选项选中;
- 不能确定的则不要改变原来配置;
- 可选可不选的,建议根据 help 信息决定或者不选;
- 一次改动不要太多,渐进式修改和验证;
- 注意及时备份配置文件,出现意外可以回退恢复。
复制代码 使用 U 盘,必须使能 USB 大容量类支持,选中“USB Mass Storage support”:
- $ make ARCH=arm menuconfig
复制代码 大多数情况下,U 盘都在用 FAT 格式,为了能正常使用 U 盘,还需在内核中使能 FAT支持。菜单路径和配置如下:
- [*] Networking support --->
- Device Drivers --->
- File systems --->
- Kernel hacking --->
复制代码 保存配置,重新编译内核,基于新内核的系统就能使用 U 盘了。
2. 使用 USB 键盘和鼠标
使用 USB 键盘或者鼠标,需要在内核中使能 HID 支持。在“Device Drivers”菜单界面,选中“HID Devices”:
- [*] SPI support --->
- PPS support --->
- PTP clock support
- -*- GPIO Support --->
- <*> PWM Support --->
复制代码 进入“HID Devices”,选中“USB Human Interface Device (full HID) support”
- --- GPIO Support
- [*] /sys/class/gpio/... (sysfs interface)
- *** Memory mapped GPIO drivers: ***
复制代码 另外,还需使能 Event 支持。在“Device Drivers”配置界面,选择“Input device support”:
- <*> MMC/SD/SDIO card support --->
- < > Sony MemoryStick card support (EXPERIMENTAL) --->
- [*] LED Support --->
- [ ] Accessibility support --->
复制代码 进入“Input device support”,选中“Event interface”:
- --- LED Support
- [*] LED Class Support
- *** LED drivers ***
- ...
- [*] LED Trigger support
- *** LED Triggers ***
- <*> LED Timer Trigger
- <*> LED Heartbeat Trigger
- < > LED backlight Trigger
- <*> LED GPIO Trigger
- <*> LED Default ON Trigger
复制代码- Input device support --->
- Character devices --->
- -*- I2C support --->
复制代码 【1.7.5】 USB Gadget 驱动配置
USB Gadget 能通过 USB Device 实现诸如 U 盘模拟、USB 串口、USB 网卡等多种功能。 首先在内核配置使能“USB Gadget Support”:
- [*] /dev/kmem virtual device support
- Serial drivers --->
- [ ] ARM JTAG DCC console
复制代码 然后进入“USB Gadget Support”,根据实际情况选择 USB 控制器,如下是 AM335x 的USB 控制器选择:
- <M> 8250/16550 and compatible serial support
- *** Non-8250 serial port support ***
- <*> i.MXS debug serial port support
- <*> i.MXS Application serial port support
复制代码 最后在配置菜单中,根据实际需要选择配置相应的功能。对于 USB Gadget 的功能,建议编译为模块,在需要的时候插入模块,用完后将模块卸载:
- [*] Block devices --->
- [*] Misc devices --->
- SCSI device support --->
- < > Serial ATA and Parallel ATA drivers --->
复制代码 【1.7.6】 SD/MMC 驱动配置
在“Device Drivers”菜单中使能“MMC/SD/SDIO card support”:
- < > RAID Transport Class
- <*> SCSI device support
- < > SCSI target support
- [*] legacy /proc/scsi/ support
- *** SCSI support type (disk, tape, CD-ROM) ***
- <*> SCSI disk support
- < > SCSI tape support
- < > SCSI OnStream SC-x0 tape support
复制代码 进入“MMC/SD/SDIO card support”,使能“MMC block device driver”。在“MMC/SD/SDIO Host Controller Drivers”下选择处理器对应的 SD/MMC 控制器:
- < > Sound card support --->
- [ ] HID Devices --->
- [*] USB support --->
- <*> MMC/SD/SDIO card support --->
复制代码 另外还需根据 SD/MMC 卡的文件系统格式在内核中配置相应的文件系统支持。如果是FAT 格式,请参考前面“使用 U 盘”中 VFAT 的配置;如果采用 Ext2/3/4 格式,则进行如下配置:
- --- USB support
- <*> Support for Host-side USB
- [*] USB device filesystem (DEPRECATED)
- [*] USB device class-devices (DEPRECATED)
- [*] USB runtime power management (suspend/resume and wakeup)
- <*> EHCI HCD (USB 2.0) support
- [*] Support for Freescale controller
- [*] Support for Host1 port on Freescale controller
- [*] Support for DR host port on Freescale controller
- [*] Root Hub Transaction Translators
复制代码 【1.7.7】 网卡驱动配置
配置网卡首先要在主菜单中使能网络,即选中“Networking support”:
- …
- <*> USB Mass Storage support
- [ ] USB Mass Storage verbose debug
复制代码- File systems --->
- DOS/FAT/NT Filesystems --->
- <*> MSDOS fs support
- <*> VFAT (Windows-95) fs support
- (437) Default codepage for FAT
- (iso8859-1) Default iocharset for FAT
- < > NTFS file system support
复制代码- <*> Sound card support --->
- [*] HID Devices --->
- [*] USB support --->
- <*> MMC/SD/SDIO card support --->
复制代码 只有开启“Networking support”支持后才能在“Device Drivers”菜单中看到“Network device support”子菜单:
- --- HID Devices
- -*- Generic HID support
- [ ] /dev/hidraw raw HID device support
- *** USB Input Devices ***
- <*> USB Human Interface Device (full HID) support
- [ ] PID device support
- [ ] /dev/hiddev raw HID device support
- Special HID drivers --->
复制代码 选中“Network device support”并进入,根据主板实际硬件,在“Ethernet driver support”中配置物理网卡。如下是基于 EPC-28x 的主板网卡配置示例:
- < > Telephony support --->
- Input device support --->
- Character devices --->
- -*- I2C support --->
复制代码 【1.7.8】 NFS Client 配置
使用 NFS 文件系统,首先需要保证网卡可用,且在内核已经配置了网卡。在“File system”配置界面使能“Network File Systems”并进行配置:
- < > Joystick interface
- <*> Event interface
- < > Event debugging
复制代码 选中“Root file system on NFS”,能够通过 NFS 挂载服务器上的根文件系统,这点在裁剪文件系统中特别有用。
【1.7.9】 PPP 拨号配置
PPP 拨号配置,在“Device Drivers”的“Network device support”菜单下。选中并使能“PPP (point-to-point protocol) support”及子选项即可使用 PPP 拨号功能。这里以模块方式编译:
- `当然,还需要 USB Host 支持,参考前面“使用 U 盘”配置部分配置好 USB 控制器。
- 保存配置并编译内核,使用新内核的系统即可支持 USB 键盘和鼠标。`
复制代码 编译内核后通过 make modules 编译模块,在目录下会生成 slhc.ko、pppox.ko、pppoe.ko 等模块。将这些模块复制到目标系统中,然后按照下列顺序依次插入模块:
- Device Drivers --->
- [*] USB support --->
- <*> USB Gadget Support --->
复制代码- <*> USB Peripheral Controller (Inventra HDRC USB Peripheral (TI, ADI, ...)) --->
复制代码 【1.7.10】 MTD 配置
在内核配置主菜单界面,进入“Device Drivers”界面,选择“Memory Technology Device (MTD) support”:
- <M> USB Gadget Drivers
- < > Gadget Zero (DEVELOPMENT)
- < > Audio Gadget (EXPERIMENTAL)
- <M> Ethernet Gadget (with CDC Ethernet support)
- [*] RNDIS support
- ...
- <M> File-backed Storage Gadget (DEPRECATED)
- [ ] File-backed Storage Gadget testing version
- <M> Mass Storage Gadget
- < > Serial Gadget (with CDC ACM and CDC OBEX support)
复制代码 并进入“Memory Technology Device (MTD) support”,进行如下配置:
- Device Drivers --->
- <*> MMC/SD/SDIO card support --->
复制代码 进入“NAND Device Support”,对系统 NAND 控制器进行选择:
- --- MMC/SD/SDIO card support
- ...
- *** MMC/SD/SDIO Card Drivers ***
- <*> MMC block device driver
- (8) Number of minors per block device
- [*] Use bounce buffer for simple hosts
- < > SDIO UART/GPS class support
- < > MMC host test driver
- *** MMC/SD/SDIO Host Controller Drivers ***
- ...
- [*] Freescale i.MX Secure Digital Host Controller Interface
- <*> MXS MMC support
复制代码 保存配置,编译内核。采用新内核启动的系统,在驱动无误的情况下,可以看到系统的MTD 分区信息。如下是 EPC-28x 进入系统后,可通过/proc/mtd 文件查看:
- File systems --->
- <*> Second extended fs support
- <*> Ext3 journalling file system support
- <*> The Extended 4 (ext4) filesystem
复制代码 【1.7.11】 UBIFS 文件系统配置
UBIFS 是工作于 UBI 子系统之上的文件系统,而 UBI 又工作于 MTD 设备上,所以首先需要在 MTD 中使能 UBI:
- Power management options --->
- [*] Networking support --->
- Device Drivers --->
- File systems --->
复制代码 进入“Enable UBI - Unsorted block images”,对 UBI 进行配置:
- 为了正常使用网络,通常还需在“`Networking options`”中配置 `TCP/IP`:
复制代码 其中“Percentage of reserved eraseblocks for bad eraseblockshandling”设置用于坏块管理 的保留块的百分比,默认是 1%,可以适当调整大一些,不过必须与 U-Boot 中的设置一致。 还需在文件系统设置中使能和配置 UBIFS:
- [*] Networking support --->
- Networking options --->
- <*> Packet socket
- <*> Unix domain sockets
- < > PF_KEY sockets
- [*] TCP/IP networking
- [*] IP: multicasting
- [ ] IP: advanced router
- [*] IP: kernel level autoconfiguration
- [*] IP: DHCP support
- [*] IP: BOOTP support
- [*] IP: RARP support
复制代码 【1.7.12】 CAN 驱动配置
前面已经提到过,CAN 设备驱动的配置路径不在“Devie Drivers”下,而是在“Networking support”中。进入内核配置主菜单,选择“Networking support”:
- …
- <*> USB Mass Storage support
- [ ] USB Mass Storage verbose debug
复制代码 选中或者模块编译“CAN bus subsystem support”:
- [*] --- Ethernet (10 or 100Mbit) --->
- …
- <*> FEC ethernet controller (of ColdFire and some i.MX CPUs)
- [*] Second FEC ethernet controller (on some ColdFire CPUs)
复制代码 进入“CAN bus subsystem support”,选中“Raw CAN Protocol”和“Broadcast Manager CAN Protocol”:
- File systems --->
- [*] Network File Systems --->
- <*> NFS client support
- [*] NFS client support for NFS version 3
- [*] NFS client support for the NFSv3 ACL protocol extension
- [*] NFS client support for NFS version 4
- [*] NFS client support for NFSv4.1 (EXPERIMENTAL)
- [*] Root file system on NFS
复制代码 然后进入“CAN Device Drivers”,对 CAN 设备驱动进行配置,如下:
- Device Drivers --->
- [*] Network device support --->
- <M> PPP (point-to-point protocol) support
- <M> PPP BSD-Compress compression
- <M> PPP Deflate compression
- [*] PPP filtering
- <M> PPP MPPE compression (encryption) (EXPERIMENTAL)
- [*] PPP multilink support (EXPERIMENTAL)
- <M> PPP over Ethernet (EXPERIMENTAL)
- <M> PPP support for async serial ports
- <M> PPP support for sync tty ports
复制代码 【8】 EPC-28x 平台内核快速编译
从 EPC-28x光盘内获取内核源码包EPC-28x.xxxxx.tar.bz2,然后把它拷贝到Ubuntu 下面,执行如下步骤:
1. 解压缩
- #insmod slhc.ko
- #insmod ppp_generic.ko
- #insmod pppox.ko
- #insmod pppoe.ko
复制代码 解压后产生内核目录 linux-2.6.35.3。
2. 编译内核
EPC-28x 内核源码的 Makefile 文件内已经配置好了 ARCH 和 CROSS_COMPILE,所以 在 make 时无需指定ARCH 和CROSS_COMPILE。 EPC-28x 的源码已经包含配置好的.config 文件,无需 makemenuconfig 配置即可使用默 认配置,除非需要改动内核配置。
- 插入模块后,生成/dev/ppp 设备节点,通过 ppp 拨号脚本即可进行拨号了。
复制代码 编译完成,将得到内核文件。 另外,在执行 make distclean 或者 make mrproper 之前,请先将.config 配置文件备份, 如:
- < > Connector - unified userspace <-> kernelspace linker --->
- <*> Memory Technology Device (MTD) support --->
- Device Tree and Open Firmware support --->
- < > Parallel port support --->
复制代码 第二章,等你

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |