嵌入式linux学习笔记(代码片段)

qlexcel qlexcel     2023-01-16     454

关键词:

常用操作&相关知识

压缩解压

解压.tar.bz2
tar -vxjf linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2
压缩.tar.bz2
tar -vcjf alientek_uboot.tar.bz2 alientek_uboot

编译

将前面编译出来的 led.o 文件链接到 0X87800000 这个地址,使用如下命令:
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件

led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还需要将 led.elf 文件转换为.bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具了。

arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为led.bin 文件,命令如下:
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。

大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令:
arm-linux-gnueabihf-objdump -D led.elf > led.dis
上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件
可以打开 led.dis 文件看一下,看看是不是汇编代码

从图 可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以 0X87800000 为起始地址的区域。

makefile

objs := start.o main.o

ledc.bin:$(objs)
	arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
	
%.o:%.s
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
	
%.o:%.S
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
	
%.o:%.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
	
clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

第 1 行定义了一个变量 objs, objs 包含着要生成 ledc.bin 所需的材料: start.o 和 main.o,也就是当前工程下的 start.s 和 main.c 这两个文件编译后的.o 文件。这里要注意 start.o 一定要放到最前面!因为在后面链接的时候 start.o 要在最前面,因为 start.o 是最先要执行的文件!
第 3 行就是默认目标,目的是生成最终的可执行文件 ledc.bin, ledc.bin 依赖 start.o 和 main.o如果当前工程没有 start.o 和 main.o 的时候就会找到相应的规则去生成 start.o 和 main.o。比如start.o 是 start.s 文件编译生成的,因此会执行第 8 行的规则。
第 4 行是使用 arm-linux-gnueabihf-ld 进行链接,链接起始地址是 0X87800000,但是这一行用到了自动变量“$^”,“$^”的意思是所有依赖文件的集合,在这里就是 objs 这个变量的值:start.o 和 main.o。链接的时候 start.o 要链接到最前面,因为第一行代码就是 start.o 里面的,因此这一行就相当于:
arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf start.o main.o
第 5 行使用 arm-linux-gnueabihf-objcopy 来将 ledc.elf 文件转为 ledc.bin,本行也用到了自动变量“$@”,“$@”的意思是目标集合,在这里就是“ledc.bin”,那么本行就相当于:
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin
第 6 行使用 arm-linux-gnueabihf-objdump 来反汇编,生成 ledc.dis 文件。
第 8~15 行就是针对不同的文件类型将其编译成对应的.o 文件,其实就是汇编.s(.S)和.c 文件,比如 start.s 就会使用第 8 行的规则来生成对应的 start.o 文件。第 9 行就是具体的命令,这行也用到了自动变量“$@”和“$<”,其中“$<”的意思是依赖目标集合的第一个文件。比如start.s 要编译成 start.o 的话第 8 行和第 9 行就相当于:

start.o:start.s
    arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o start.o start.s

第 17 行就是工程清理规则,通过命令“make clean”就可以清理工程。

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第 1 行, KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,大家根据自己的实际情况填写即可。
第 2 行, CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取当前所处路径。
第 3 行, obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。
第 8 行,具体的编译命令,后面的 modules 表示编译模块, -C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。 M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。

Makefile 编写好以后输入“make”命令编译驱动模块

链接脚本

SECTIONS
	. = 0X10000000;
	.text : *(.text)
	. = 0X30000000;
	.data ALIGN(4) :  *(.data) 
	.bss ALIGN(4) :  *(.bss) 

第 1 行我们先写了一个关键字“SECTIONS”,后面跟了一个大括号,这个大括号和第 7 行的大括号是一对,这是必须的。看起来就跟 C 语言里面的函数一样。
第 2 行对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位计数器为 0。我们要求代码链接到以0X10000000 为起始地址的地方,因此这一行给“.”赋值0X10000000,表示以 0X10000000 开始,后面的文件或者段都会以0X10000000 为起始地址开始链接。
第 3 行的“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链接到“.text”这个段里面的所有文件,“*(.text)”中的“*”是通配符,表示所有输入文件的.text段都放到“.text”中。
第 4 行,我们的要求是数据放到 0X30000000 开始的地方,所以我们需要重新设置定位计数器“.”,将其改为 0X30000000。如果不重新设置的话会怎么样?假设“.text”段大小为 0X10000,那么接下来的.data 段开始地址就是0X10000000+0X10000=0X10010000,这明显不符合我们的要求。所以我们必须调整定位计数器为 0X30000000。
第 5 行跟第 3 行一样,定义了一个名为“.data”的段,然后所有文件的“.data”段都放到这里面。但是这一行多了一个“ALIGN(4)”,这是什么意思呢?这是用来对“.data”这个段的起始地址做字节对齐的, ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐。
第 6 行定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数据就是那些定义了但是没有被初始化的变量。

上面就是链接脚本最基本的语法格式,我们接下来就按照这个基本的语法格式来编写我们本试
验的链接脚本,我们本试验的链接脚本要求如下:
①、链接起始地址为 0X87800000。
②、 start.o 要被链接到最开始的地方,因为 start.o 里面包含这第一个要执行的命令。
根据要求,在 Makefile 同目录下新建一个名为“imx6ul.lds”的文件,然后在此文件里面输入如下所示代码:

SECTIONS
	. = 0X87800000;
	.text :
	
		start.o
		main.o
		*(.text)
	
	.rodata ALIGN(4) : *(.rodata*)
	.data ALIGN(4) :  *(.data) 
	__bss_start = .;
	.bss ALIGN(4) :  *(.bss) *(COMMON) 
	__bss_end = .;

第 2 行设置定位计数器为0X87800000,因为我们的链接地址就是0X87800000。
第5行设置链接到开始位置的文件为start.o,因为 start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。
第 6 行是 main.o这个文件,其实可以不用写出来,因为 main.o 的位置就无所谓了,可以由编译器自行决定链接位置。
在第 11、 13 行有“__bss_start”和“__bss_end”这两个东西?这个是什么呢?“__bss_start”和“__bss_end”是符号,第 11、 13 这两行其实就是对这两个符号进行赋值,其值为定位符“.”,这两个符号用来保存.bss 段的起始地址和结束地址。前面说了.bss 段是定义了但是没有被初始化的变量,我们需要手动对.bss 段的变量清零的,因此我们需要知道.bss 段的起始和结束地址,这样我们直接对这段内存赋 0 即可完成清零。通过第 11、 13 行代码, .bss 段的起始地址和结束地址就保存在了“__bss_start”和“__bss_end”中,我们就可以直接在汇编或者 C 文件里面使用这两个符号。

arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^
改为:
arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
其实就是将-T 后面的 0X87800000 改为 imx6ul.lds,表示使用 imx6ul.lds 这个链接脚本文件。

内核目录

linux内核编译过程

make xxx_defconfig命令配置 Linux 的时候如下两行命令会执行脚本scripts/Makefile.build:

@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig

%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。
此软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下的.config 文件。

使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all”命令进行编译。顶层 Makefile 有如下代码:

125 PHONY := _all
126 _all:
......
192 PHONY += all
193 ifeq ($(KBUILD_EXTMOD),)
194 _all: all
195 else
196 _all: modules
197 endif
......
608 all: vmlinux

第 126 行, _all 是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配。
第 193 行,如果 KBUILD_EXTMOD 为空的话 194 行的代码成立。
第 194 行,默认目标_all 依赖 all。
第 608 行,目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!

要分析 Linux 启动流程,同样需要先编译一下 Linux 源码,因为有很多文件是需要编译才会生成的。首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到 Linux 内核的第一行程序是从哪里执行的。 vmlinux.lds 中有如下代码:

492 OUTPUT_ARCH(arm)
493 ENTRY(stext)
494 jiffies = jiffies_64;

第 493 行的 ENTRY 指明了了 Linux 内核入口,入口为 stext, stext 定义在文件arch/arm/kernel/head.S 中 , 因 此 要 分 析 Linux 内 核 的 启 动 流 程 , 就 得 先 从 文 件arch/arm/kernel/head.S 的 stext 处开始分析。

linux内核移植步骤

1、设置目标架构和交叉编译器

同 uboot一样, Linux编译的时候需要设置目标板架构ARCH 和交叉编译器 CROSS_COMPILE,在顶层 Makefile 中代码如下:

ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)

为了方便,一般直接修改顶层 Makefile 中的 ARCH 和 CROSS_COMPILE,直接将其设置为对应的架构和编译器,比如本教程将 ARCH 设置为为 arm, CROSS_COMPILE 设置为 armlinux-gnueabihf-,如下所示:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-

设置好以后我们就可以使用如下命令编译 Linux 了:

make xxx_defconfig //使用默认配置文件配置 Linux
make menuconfig //启动图形化配置界面
make -j16 //编译 Linux

内核启动测试

在上一小节我们已经得到了 NXP 官方 I.MX6ULL EVK 开发板对应的 zImage 和 imx6ull-14x14-evk.dtb 这两个文件。这两个文件能不能在正点原子的 I.MX6U-ALPHA EMMC 版开发板上启动呢?测试一下不就知道了,在测试之前确保 uboot 中的环境变量 bootargs 内容如下:
console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw
将上一小节编译出来的 zImage 和 imx6ull-14x14-evk.dtb 复制到 Ubuntu 中的 tftp 目录下,因为我们要在 uboot 中使用 tftp 命令将其下载到开发板中,拷贝命令如下:

cp arch/arm/boot/zImage /home/zuozhongkai/linux/tftpboot/ -f
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /home/zuozhongkai/linux/tftpboot/ -f

拷贝完成以后就可以测试了,启动开发板,进入 uboot 命令行模式,然后输入如下命令将zImage 和 imx6ull-14x14-evk.dtb 下载到开发板中并启动:

tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000

Linux 内核启动以后是需要根文件系统的,根文件系统存在哪里是由 uboot 的 bootargs 环境变量指定 , bootargs 会传递给 Linux 内核作为命令行参数 。 比如上面设置root=/dev/mmcblk1p2,也就是说根文件系统存储在/dev/mmcblk1p2 中,也就是 EMMC 的分区 2中。这是因为正点原子的 EMMC 版本开发板出厂的时候已经 EMMC 的分区 2 中烧写好了根文件系统,所以设置 root=/dev/mmcblk1p2。

在 Linux 中添加自己的开发板

通过编译 NXP 官方 I.MX6ULL EVK 开发板对应的 Linux 内核,发现其可以在正点原子的 EMMC 版本开发板启动,所以我们就参考 I.MX6ULL EVK 开发板的设置,在 Linux 内核中添加正点原子的 I.MX6U-ALPHA 开发板。

添加开发板默认配置文件

将 arch/arm/configs 目 录 下 的 imx_v7_mfg_defconfig 重 新 复 制 一 份 , 命 名 为imx_alientek_emmc_defconfig,命令如下:

cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig

以后 imx_alientek_emmc_defconfig 就是正点原子的 EMMC 版开发板默认配置文件了。
以后就可以使用如下命令来配置正点原子 EMMC 版开发板对应的 Linux 内核了:
make imx_alientek_emmc_defconfig

添加开发板对应的设备树文件

添加适合正点原子 EMMC 版开发板的设备树文件,进入目录 arch/arm/boot/dts 中,复制一份 imx6ull-14x14-evk.dts,然后将其重命名为 imx6ull-alientek-emmc.dts,命令如下:

cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts

.dts 是设备树源码文件,编译 Linux 的时候会将其编译为.dtb 文件。imx6ull-alientek-emmc.dts创建好以后我们还需要修改文件 arch/arm/boot/dts/Makefile ,找到“dtb-$(CONFIG_SOC_IMX6ULL)”配置项,在此配置项中加入“imx6ull-alientek-emmc.dtb”
这样编译 Linux 的时候就可以从 imx6ull-alientekemmc.dts 编译出 imx6ull-alientek-emmc.dtb 文件了。

编译测试

可以创建一个编译脚本 ,imx6ull_alientek_emmc.sh,脚本内容如下:

1 #!/bin/sh
2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihfimx_alientek_emmc_defconfig
4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

第 2 行,清理工程。
第 3 行,使用默认配置文件 imx_alientek_emmc_defconfig 来配置 Linux 内核。
第 4 行,打开 Linux 的图形配置界面,如果不需要每次都打开图形配置界面可以删除此行。
第 5 行,编译 Linux。

执行 shell 脚本 imx6ull_alientek_emmc.sh 编译 Linux 内核, 命令如下:

chmod 777 imx6ull_alientek_emmc.sh //给予可执行权限
./imx6ull_alientek_emmc.sh //执行 shell 脚本编译内核

编译完成以后就会在目录 arch/arm/boot 下生成 zImage 镜像文件。在 arch/arm/boot/dts 目录下生成 imx6ull-alientek-emmc.dtb 文件。

CPU 主频修改

执行cat /proc/cpuinfo

有 BogoMIPS 这一条,此时 BogoMIPS 为 3.00, BogoMIPS 是 Linux 系统中衡量处理器运行速度的一个“尺子”,处理器性能越强,主频越高, BogoMIPS 值就越大。BogoMIPS 只是粗略的计算 CPU 性能,并不十分准确。但是我们可以通过 BogoMIPS 值来大致的判断当前处理器的性能。在图中并没有看到当前 CPU 的工作频率,那我们就转变另一种方法查看当前 CPU 的工作频率。进入到目录/sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下会有很多文件

此目录中记录了 CPU 频率等信息,这些文件的含义如下:

  • cpuinfo_cur_freq:当前 cpu 工作频率,从 CPU 寄存器读取到的工作频率。
  • cpuinfo_max_freq:处理器所能运行的最高工作频率(单位: KHz)。
  • cpuinfo_min_freq :处理器所能运行的最低工作频率(单位: KHz)。
  • cpuinfo_transition_latency:处理器切换频率所需要的时间(单位:ns)。
  • scaling_available_frequencies:处理器支持的主频率列表(单位: KHz)。
  • scaling_available_governors:当前内核中支持的所有 governor(调频)类型。
  • scaling_cur_freq:保存着 cpufreq 模块缓存的当前 CPU 频率,不会对 CPU 硬件寄存器进行检查。
  • scaling_driver:该文件保存当前 CPU 所使用的调频驱动。
  • scaling_governor: governor(调频)策略, Linux 内核一共有 5 种调频策略,
    ①、 Performance,最高性能,直接用最高频率,不考虑耗电。
    ②、 Interactive,一开始直接用最高频率,然后根据 CPU 负载慢慢降低。
    ③、 Powersave,省电模式,通常以最低频率运行,系统性能会受影响,一般不会用这个!
    ④、 Userspace,可以在用户空间手动调节频率。
    ⑤、 Ondemand,定时检查负载,然后根据负载来调节频率。负载低的时候降低 CPU 频率,这样省电,负载高的时候提高 CPU 频率,增加性能。
  • scaling_max_freq: governor(调频)可以调节的最高频率。
  • cpuinfo_min_freq: governor(调频)可以调节的最低频率。

stats 目录下给出了 CPU 各种运行频率的统计情况,比如 CPU 在各频率下的运行时间以及变频次数。
使用如下命令查看当前 CPU 频率:
cat cpuinfo_cur_freq

可以看出,当前 CPU 频率为 198MHz,工作频率很低!其他的值如下:

cpuinfo_cur_freq = 198000
cpuinfo_max_freq = 792000
cpuinfo_min_freq = 198000
scaling_cur_freq = 198000
scaling_max_freq = 792000
cat scaling_min_freq = 198000
scaling_available_frequencies = 198000 396000 528000 792000
cat scaling_governor = ondemand

可以看出,当前 CPU 支持 198MHz、 396MHz、 528Mhz 和 792000 四种频率切换,其中调频策略为 ondemand,也就是定期检查负载,然后根据负载情况调节 CPU 频率。因为当前我们开发板并没有做什么工作,因此 CPU 频率降低为 198MHz 以省电。如果开发板做一些高负载的工作,比如播放视频等操作那么 CPU 频率就会提升上去。
查看 stats 目录下的 time_in_state 文件可以看到 CPU 在各频率下的工作时间,命令如下:
cat /sys/bus/cpu/devices/cpu0/cpufreq/stats/time_in_state

从图中可以看出, CPU 在 198MHz、 396MHz、 528MHz 和 792MHz 都工作过,其中 198MHz 的工作时间最长!假如我们想让 CPU 一直工作在 792MHz 那该怎么办?很简单,配置 Linux 内核,将调频策略选择为 performance。或者修改imx_alientek_emmc_defconfig 文件,此文件中有下面几行:

41 CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
42 CONFIG_CPU_FREQ_GOV_POWERSAVE=y
43 CONFIG_CPU_FREQ_GOV_USERSPACE=y
44 CONFIG_CPU_FREQ_GOV_INTERACTIVE=y

第 41 行,配置 ondemand 为默认调频策略。
第 42 行,使能 powersave 策略。
第 43 行,使能 userspace 策略。
第 44 行,使能 interactive 策略。
将第 41 行屏蔽掉,然后在 44 行后面添加:

CONFIG_CPU_FREQ_GOV_ONDEMAND=y

结果下所示:

41 #CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
42 CONFIG_CPU_FREQ_GOV_POWERSAVE=y
43 CONFIG_CPU_FREQ_GOV_USERSPACE=y
44 CONFIG_CPU_FREQ_GOV_INTERACTIVE=y
45 CONFIG_CPU_FREQ_GOV_ONDEMAND=y

修改完成以后重新编译 Linux 内核,编译之前先清理一下工程!因为我们重新修改过默认配置文件了 , 编译完成以后使用新的 zImage镜像文件重新 启动 Linux 。 再 次 查 看/sys/devices/system/cpu/cpu0/cpufreq/ cpuinfo_cur_freq 文件的值,如图

可以看出,当前 CPU 频率为 792MHz 了。查看 scaling_governor 文件看一下当前的调频策略,如图

可以看出当前的 CPU 调频策略为 preformance,也就是高性能模式,一直以最高主频运行。
我们再来看一下如何通过图形化界面配置 Linux 内核的 CPU 调频策略,输入“ make menuconfig”打开 Linux 内核的图形化配置界面,进入如下路径:

CPU Power Management
	-> CPU Frequency scaling
		-> Default CPUFreq governor

打开默认调频策略选择界面,选择“performance”。选择以后退出图形化配置界面,然后编译 Linux内核,一定不要清理工程!否则的话我们刚刚的设置就会被清理掉。编译完成以后使用新的zImage 重启 Linux,查看当前 CPU 的工作频率和调频策略。
我们学习的时候为了高性能,大家可以使用 performance 模式。但是在以后的实际产品开发中,从省电的角度考虑,建议大家使用 ondemand 模式,一来可以省电,二来可以减少发热。

超频&修改频率列表

超频设置其实很简单,修改一下设备树文件 arch/arm/boot/dts/imx6ull.dtsi 即可,打开imx6ull.dtsi,找到下面代码:

54 cpu0: cpu@0 
55 	compatible = "arm,cortex-a7";
56 	device_type = "cpu";
57 	reg = <0>;
58 	clock-latency = <61036>; /* two CLK32 periods */
59 	operating-points = <
60 		/* kHz uV */
61 		996000 1275000
62 		792000 1225000
63 		528000 1175000
64 		396000 1025000
65 		198000 950000
66 	>;
67 	fsl,soc-operating-points = <
68 		/* KHz uV */
69 		996000 1175000
70 		792000 1175000
71 		528000 1175000
72 		396000 1175000
73 		198000 1175000
74 	>;

上面代码就是设置 CPU 频率的,第 61-65 行和第 69-73 行就是 I.MX6ULL 所支持的频率,单位为 KHz,可以看出 I.MX6ULL(视具体型号而定)支持 996MHz、 792MHz、 528MHz、396MHz 和 198MHz。在上一小节中,我们知道 Linux 内核默认支持 198MHz、 396MHz、 528MHz和 792MHz, 如果是 MCIMX6Y2CVM05AB 这颗芯片的话,默认最高只能运行在 528MHz, 我
们在示例代码中加入针对 696MHz 的支持,修改以后代码如下:

54 cpu0: cpu@0 
55	 compatible = "arm,cortex-a7";
56	 device_type = "cpu";
57	 reg = <0>;
58 	clock-latency = <61036>; /* two CLK32 periods */
59	 operating-points = <
60		 /* kHz uV */
61		 996000 1275000
62		 792000 1225000
63		 696000 1225000
64		 528000 1175000
65		 396000 1025000
66		 198000 950000
67	 >;
68	 fsl,soc-operating-points = <
69		 /* KHz uV */
70 		996000 1175000
71		 792000 1175000
72		 696000 1175000
73		 528000 1175000
74		 396000 1175000
75		 198000 1175000
76	 >;

修改好以后保存,并且编译设备树,在 Linux 内核源码根目录下输入如下命令编译设备树:
make dtbs
命令“make dtbs”只编译设备树文件,也就是将.dts 编译为.dtb,编译完成以后使用新的设备 树 文 件 imx6ull-alientek_emmc.dtb 启 动 Linux 。 重 启 以 后 查 看 文 件/sys/devices/system/cpu/cpu0/cpufreq/ scaling_available_frequencies 的内容,如图

使能8线EMMC驱动

正点原子 EMMC 版本核心板上的 EMMC 采用的 8 位数据线

Linux 内核驱动里面 EMMC 默认是 4 线模式的, 4 线模式肯定没有 8 线模式的速度快,所以本节我们将 EMMC 的驱动修改为 8 线模式。修改方法很简单,直接修改设备树即可,打开文件 imx6ull-alientek-emmc.dts,找到如下所示内容

734 &usdhc2 
735 	pinctrl-names = "default";
736 	pinctrl-0 = <&pinctrl_usdhc2>;
737 	non-removable;
738	 	status = "okay";
739 ;

改为如下代码即可:

734 &usdhc2 
735 	pinctrl-names = "default", "state_100mhz", "state_200mhz";
736 	pinctrl-0 = <&pinctrl_usdhc2_8bit>;
737 	pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
738 	pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
739 	bus-width = <8>;
740 	non-removable;
741 	status = "okay";
742 ;

修改完成以后保存一下 imx6ull-alientek-emmc.dts,然后使用命令“make dtbs”重新编译一下设备树,编译完成以后使用新的设备树重启 Linux 系统即可。

修改网络驱动

因为在后面学习 Linux 驱动开发的时候要用到网络调试驱动,所以必须要把网络驱动调试好。在讲解 uboot 移植的时候就已经说过了,正点原子开发板的网络和 NXP 官方的网络硬件上不同,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A,两个网络 PHY 芯片的复位 IO 也不同。所以 Linux 内核自带的网络驱动是驱动不起来 I.MX6U-ALPHA 开发板上的网络的,需要做修改。

1、修改 LAN8720 的复位以及网络时钟引脚驱动

ENET1 复位引脚 ENET1_RST 连接在 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。 ENET2的复位引脚 ENET2_RST 连接在 I.MX6ULL 的 SNVS_TAMPER8 上。打开设备树文件 imx6ullalientek-emmc.dts,找到如下代码:

584 pinctrl_spi4: spi4grp 
585 			fsl,pins = <
586 				MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10 0x70a1
587 				MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 0x70a1
588 				MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x70a1
589 				MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x80000000
590 			>;
591 ;

示例代码中第 588 和 589 行就是初始化 SNVS_TAMPER7 和 SNVS_TAMPER8 这两个引脚的,不过看样子好像是作为了 SPI4 的 IO,这不是我们想要的,所以将 588 和 589 这两行删除掉!删除掉以后继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:

125 spi4 
126 	compatible = "spi-gpio";
127 	pinctrl-names = "default";
128 	pinctrl-0 = <&pinctrl_spi4>;
129 	pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
......
133 	cs-gpios = <&gpio5 7 0>;

第 129 行,设置 GPIO5_IO08 为 SPI4 的一个功能引脚(我也不清楚具体作为什么功能用),而 GPIO5_IO08 就是 SNVS_TAMPER8 的 GPIO 功能引脚。
第 133 行,设置 GPIO5_IO07 作为 SPI4 的片选引脚,而 GPIO5_IO07 就是 SNVS_TAMPER7的 GPIO 功能引脚。
现在我们需要 GPIO5_IO07 和 GPIO5_IO08 分别作为 ENET1 和 ENET2 的复位引脚,而不是 SPI4 的什么功能引脚,因此将示例代码 37.4.3.2 中的第 129 行和第 133 行处的代码删除掉!!否则会干扰到网络复位引脚!
在 imx6ull-alientek-emmc.dts 里面找到名为“iomuxc_snvs”的节点(就是直接搜索),然后在此节点下添加网络复位引脚信息,添加完成以后的“iomuxc_snvs”的节点内容如下:

1 &iomuxc_snvs 
2 	pinctrl-names = "default_snvs";
3 	pinctrl-0 = <&pinctrl_hog_2>;
4 	imx6ul-evk 
5
...... /*省略掉其他*/
43
44 		/*enet1 reset zuozhongkai*/
45 		pinctrl_enet1_reset: enet1resetgrp 
46 			fsl,pins = <
47 				/* used for enet1 reset */
48 				MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
49		 	>;
50 		;
51
52 		/*enet2 reset zuozhongkai*/
53 		pinctrl_enet2_reset: enet2resetgrp 
54 			fsl,pins = <
55 				/* used for enet2 reset */
56 				MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
57 			>;
58 		;
59	 ;
60 ;

第 1 行, imx6ull-alientek-emmc.dts 文件中 iomuxc_snvs 节点。
第 45~50 行, ENET1 网络复位引脚配置信息。
第 53~58 行, ENET2 网络复位引脚配置信息。
最后还需要修改一下 ENET1 和 ENET2 的网络时钟引脚配置, 继续在 imx6ull-alientekemmc.dts 中找到如下所示代码:

309 pinctrl_enet1: enet1grp 
310 	fsl,pins = <
311 		MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
312 		MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
313 		MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
314 		MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
315 		MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
316 		MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
317 		MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
318 		MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
319 	>;
320 ;
321
322 pinctrl_enet2: enet2grp 
323 	fsl,pins = <
324 		MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0
325 		MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0
326 		MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0
327 		MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0
328 		MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
329 		MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
330 		MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0
331 		MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
332 		MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
333 		MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009
334 	>;
335 ;

第 318 和 333 行, 分别为 ENET1 和 ENET2 的网络时钟引脚配置信息,将这两个引脚的电气属性值改为 0x4001b009,原来默认值为 0x4001b031。
修改完成以后记得保存一下 imx6ull-alientek-emmc.dts,网络复位以及时钟引脚驱动就修改好了。

2、修改 fec1 和 fec2 节点的 pinctrl-0 属性

在 imx6ull-alientek-emmc.dts 文件中找到名为“fec1”和“fec2”的这两个节点,修改其中的“pinctrl-0”属性值,修改以后如下所示

1 &fec1 
2 	pinctrl-names = "default";
3 	pinctrl-0 = <&pinctrl_enet1
4 	&pinctrl_enet1_reset>;
5 	phy-mode = "rmii";
......
9	 status = "okay";
10 ;
11
12 &fec2 
13	 pinctrl-names = "default";
14	 pinctrl-0 = <&pinctrl_enet2
15 	&pinctrl_enet2_reset>;
16	 phy-mode = "rmii";
......
36 ;

第 3~4 行,修改后的 fec1 节点“pinctrl-0”属性值。
第 14~15 行,修改后的 fec2 节点“pinctrl-0”属性值。

3、修改 LAN8720A 的 PHY 地址

在 uboot 移植章节中,我们说过 ENET1 的 LAN8720A 地址为 0x0, ENET2 的 LAN8720A地址为 0x1。在 imx6ull-alientek-emmc.dts 中找到如下代码:

171 &fec1 
172		pinctrl-names = "default";
......
175		phy-handle = <&ethphy0>;
176		status = "okay";
177 ;
178
179 &fec2 
180 	pinctrl-names = "default";
......
183		phy-handle = <&ethphy1>;
184		status = "okay";
185
186 	mdio 
187 		#address-cells = <1>;
188 		#size-cells = <0>;
189
190 		ethphy0: ethernet-phy@0 
191 			compatible = "ethernet-phy-ieee802.3-c22";
192 			reg = <2>;
193 		;
194
195 		ethphy1: ethernet-phy@1 
196 			compatible = "ethernet-phy-ieee802.3-c22";
197 			reg = <1>;
198 		;
199 	;
200 ;

第 171~177 行, ENET1 对应的设备树节点。
第 179~200 行, ENET2 对应的设备树节点。但是第 186~198 行的 mdio 节点描述了 ENET1和 ENET2 的 PHY 地址信息。将示例代码 37.4.3.6 改为如下内容:

171 &fec1 
172 	pinctrl-names = "default";
173 	pinctrl-0 = <&pinctrl_enet1
174 	&pinctrl_enet1_reset>;
175 	phy-mode = "rmii";
176 	phy-handle = <&ethphy0>;
177 	phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
178 	phy-reset-duration = <200>;
179 	status = "okay";
180 ;
181
182 &fec2 
183 	pinctrl-names = "default";
184 	pinctrl-0 = <&pinctrl_enet2
185 	&pinctrl_enet2_reset>;
186 	phy-mode = "rmii";
187 	phy-handle = <&ethphy1>;
188 	phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
189 	phy-reset-duration = <200>;
190 	status = "okay";
191
192 	mdio 
193 		#address-cells = <1>;
194 		#size-cells = <0>;
195
196 		ethphy0: ethernet-phy@0 
197 			compatible = "ethernet-phy-ieee802.3-c22";
198 			smsc,disable-energy-detect;
199 			reg = <0>;
200 		;
201
202 		ethphy1: ethernet-phy@1 
203 			compatible = "ethernet-phy-ieee802.3-c22";
204 			smsc,disable-energy-detect;
205 			reg = <1>;
206 		;
207 	;
208 ;

第 177 和 178 行,添加了 ENET1 网络复位引脚所使用的 IO 为 GPIO5_IO07,低电平有效。复位低电平信号持续时间为 200ms。
第 188 和 189 行, ENET2 网络复位引脚所使用的 IO 为 GPIO5_IO08,同样低电平有效,持续时间同样为 200ms。
第 198 和 204 行,“smsc,disable-energy-detect”表明 PHY 芯片是 SMSC 公司的,这样 Linux内核就会找到 SMSC 公司的 PHY 芯片驱动来驱动 LAN8720A。
第 196 行,注意“ethernet-phy@”后面的数字是 PHY 的地址, ENET1 的 PHY 地址为 0,所以“@”后面是 0(默认为 2)。
第 199 行, reg 的值也表示 PHY 地址, ENET1 的 PHY 地址为 0,所以 reg=0。
第 202 行, ENET2 的 PHY 地址为 1,因此“@”后面为 1。
第 205 行,因为 ENET2 的 PHY 地址为 1,所以 reg=1。
至此, LAN8720A 的 PHY 地址就改好了,保存一下 imx6ull-alientek-emmc.dts 文件。然后使用“make dtbs”命令重新编译一下设备树。

3、修改 fec_main.c 文件

要 在 I.MX6ULL 上 使 用 LAN8720A , 需 要 修 改 一 下 Linux 内 核 源 码 , 打 开drivers/net/ethernet/freescale/fec_main.c,找到函数 fec_probe,在 fec_probe 中加入如下代码:

3438 static int
3439 fec_probe(struct platform_device *pdev)
3440 
3441 	struct fec_enet_private *fep;
3442 	struct fec_platform_data *pdata;
3443 	struct net_device *ndev;
3444 	int i, irq, ret = 0;
3445 	struct resource *r;
3446 	const struct of_device_id *of_id;
3447 	static int dev_id;
3448 	struct device_node *np = pdev->dev.of_node, *phy_node;
3449 	int num_tx_qs;
3450 	int num_rx_qs;
3451
3452	 /* 设置 MX6UL_PAD_ENET1_TX_CLK 和 MX6UL_PAD_ENET2_TX_CLK
3453	 * 这两个 IO 的复用寄存器的 SION 位为 1。
3454 	*/
3455	 void __iomem *IMX6U_ENET1_TX_CLK;
3456 	void __iomem *IMX6U_ENET2_TX_CLK;
3457
3458	 IMX6U_ENET1_TX_CLK = ioremap(0X020E00DC, 4);
3459	 writel(0X14, IMX6U_ENET1_TX_CLK);
3460
3461	 IMX6U_ENET2_TX_CLK = ioremap(0X020E00FC, 4);
3462	 writel(0X14, IMX6U_ENET2_TX_CLK);
3463
......
3656	 return ret;
3657 

第 3455~3462 就是新加入的代码,如果要在 I.MX6ULL 上使用 LAN8720A 就需要设置ENET1 和 ENET2 的 TX_CLK 引脚复位寄存器的 SION 位为 1。

4、配置 Linux 内核,使能 LAN8720 驱动

输入命令“make menuconfig”,打开图形化配置界面,选择使能 LAN8720A 的驱动,路径如下:

-> Device Drivers
	-> Network device support
		-> PHY Device support and infrastructure
			-> Drivers for SMSC PHYs


图中选择将“Drivers for SMSC PHYs”编译到 Linux 内核中,因此“<>”里面变为了“*”。 LAN8720A 是 SMSC 公司出品的,因此勾选这个以后就会编译 LAN8720 驱动,配置好以后退出配置界面,然后重新编译一下 Linux 内核。

5、修改 smsc.c 文件

在修改 smsc.c 文件之前先说点题外话,那就是我是怎么确定要修改 smsc.c 这个文件的。在写本书之前我并没有修改过 smsc.c 这个文件,都是使能 LAN8720A 驱动以后就直接使用。但是我在测试 NFS 挂载文件系统的时候发现文件系统挂载成功率很低!老是提示 NFS 服务器找不到,三四次就有一次挂载失败!很折磨人。 NFS 挂载就是通过网络来挂载文件系统,这样做的好处就是方便我们后续调试 Linux 驱动。既然老是挂载失败那么可以肯定的是网络驱动有问题,网络驱动分两部分:内部 MAC+外部 PHY,内部 MAC 驱动是由 NXP 提供的,一般不会出问题,否则的话用户早就给 NXP 反馈了。而且我用 NXP 官方的开发板测试网络是一直正常的,但是 NXP 官方的开发板所使用的 PHY 芯片为 KSZ8081。所以只有可能是外部 PHY,也就是LAN8720A 的驱动可能出问题了。鉴于 LAN8720A 有“前车之鉴”,那就是在 uboot 中需要对LAN8720A 进行一次软复位,要设置 LAN8720A 的 BMCR(寄存器地址为 0)寄存器 bit15 为 1。所以我猜测,在 Linux 中也需要对 LAN8720A 进行一次软复位。
首先需要找到 LAN8720A 的驱动文件, LAN8720A 的驱动文件是 drivers/net/phy/smsc.c,在此文件中有个叫做 smsc_phy_reset 的函数,看名字都知道这是 SMSC PHY 的复位函数,因此, LAN8720A 肯定也会使用到这个复位函数, 修改此函数的内容,修改以后的 smsc_phy_reset函数内容如下所示:

6、网络驱动测试

修改好设备树和 Linux 内核以后重新编译一下,得到新的 zImage 镜像文件和 imx6ullalientek-emmc.dtb 设备树文件,使用网线将 I.MX6U-ALPHA 开发板的两个网口与路由器或者电脑连接起来,最后使用新的文件启动 Linux 内核。启动以后使用“ifconfig”命令查看一下当前活动的网卡有哪些,结果如图 37.4.3.2 所示:

从图 37.4.3.2 可以看出,当前没有活动的网卡。输入命令“ifconfig -a”来查看一下开发板中存在的所有网卡,结果如图 37.4.3.3 所示:

嵌入式linux学习笔记(代码片段)

常用操作&相关知识压缩解压解压.tar.bz2tar-vxjflinux-imx-4.1.15-2.1.0-g8a006db.tar.bz2压缩.tar.bz2tar-vcjfalientek_uboot.tar.bz2alientek_uboot编译将前面编译出来的led.o文件链接到0X87800000这个地址,使用如下命令:arm-linux-gnueab 查看详情

嵌入式linux学习笔记fbtft使用笔记(代码片段)

前言在断断续续学习了比较长时间的嵌入式Linux之后,我着手准备自己设计一款Linux的小电脑,在设计硬件之前,我需要掌握一些软硬件的使用,比如fbtft用于驱动小LCD屏幕,usb摄像头,2.4Gwifi模块等我对Linu... 查看详情

linux学习笔记——常用命令(代码片段)

...系统的强大,也慢慢不局限于做一些小作品。目标是嵌入式Linux 查看详情

yocto学习笔记(代码片段)

...含两个组件:ebuild和emerge),目的为构建定制化的嵌入式Linux发行版提供一系列模板、工具和方法。BitBake使用和Portage构建脚本相同的 查看详情

设备树学习笔记(代码片段)

学习正点原子《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》个人笔记设备树编译  设备树源文件扩展名为.dts,DTS是设备树源码文件,DTB是将DTS编译以后得到的二进制文件。将.dts编译为.dtb需要用到DTC工具,... 查看详情

linux学习笔记(代码片段)

Linux学习笔记(一)Author:akynazhBlog:akynazh.siteLinux开机CMOS是记录各项硬件参数且嵌入在主板上面的储存器BIOS则是一个写入到主板上的一个固件(固件就是写入到硬件上的一个软件程序)。这个BIOS就是在开机的时候,计算... 查看详情

markdown[ubuntu系统常见故障]#学习笔记#嵌入式系统#ubuntu(代码片段)

查看详情

markdown[ubuntu系统常用指令]#学习笔记#嵌入式系统#ubuntu(代码片段)

查看详情

linux系列笔记---------g++/gcc应该这样学!(代码片段)

作者:嵌入式历练者ID:Eterlove记下相关笔记,记录我的学习生活!站在巨人的肩上StandingonShouldersofGiants!该文章为原创,转载请注明出处和作者:https://blog.csdn.net/Eterlove/article/details/120929816linux下的Gcc故事1.g+ 查看详情

大数据开发工程师基本功修炼之史上最全linux学习笔记(建议收藏)(代码片段)

...操作系统的种类1.2.1桌面操作系统1.2.2服务器操作系统1.2.3嵌入式操作系统1.2.4移动设备操作系统1.2发展史1.3内核1.4发行版2系统安装目标2.1VM虚拟机2.1.1概述2.1.2安装虚拟机2.1.3配置网卡目标 查看详情

linux学习笔记一(代码片段)

linux学习笔记一文章目录linux学习笔记一Linuxpackageoperationoflookingfilesoperationhelpcommandsowncommandsechotunnelechoagainuserorrootprocessingaliasagainfinding这个是我在学习linux系统的时候的一点的小小的总结,希望对大家有一定的在帮助啦。Linux 查看详情

cs224w图机器学习笔记3-节点嵌入(代码片段)

节点嵌入课程和PPT主页图表示学习图表示学习(GraphRepresentationLearning)使得图机器学习摆脱了传统图机器学习对特征工程的依赖。图表示学习的目标是为图机器学习高效地学习出独立于特定下游任务的特征表示(节点嵌入)... 查看详情

嵌入式linux/android疑难杂症工作笔记(代码片段)

嵌入式Linux/Android疑难杂症工作笔记Android系统Cortex-A57内核压力测试连续震荡性内存泄漏导致OOMKiller硬件平台公司自研ARMCortex-A574核SOC产品板软件环境系统:Android-PLinux内核版本:4.9运行公司内部的kernel_submit内核压力测试脚本,通... 查看详情

linux学习笔记——常用命令(代码片段)

...系统的强大,也慢慢不局限于做一些小作品。目标是嵌入式Linux,利用Python库做科学计算处理。暂且定一个小项目,用Linux主板做偏振测量仪器的中心处理,加图像显示。Linux终端常用命令正点原子命令描述补充ls... 查看详情

linux学习笔记——常用命令(代码片段)

...系统的强大,也慢慢不局限于做一些小作品。目标是嵌入式Linux,利用Python库做科学计算处理。暂且定一个小项目,用Linux主板做偏振测量仪器的中心处理,加图像显示。Linux终端常用命令正点原子命令描述补充ls... 查看详情

omapl138调试笔记(代码片段)

title:Linux嵌入式使用tags:新建,模板,小书匠grammar_cjkRuby:trueCopyright(C)@2018WidicFilename:File-function:Cre_ID:@Widic2018-1-23Mod_ID:三学习过程20170617uart2作为调试串口bandrate115200串口调试终端采用ZOC3.开发板启动开官如下:SD卡挂载boot分区挂载... 查看详情

步进电机驱动在3d打印应用的学习笔记(代码片段)

点击上方「嵌入式云IOT技术圈」,选择「置顶公众号」第一时间查看嵌入式笔记!本文参考以下网友的文章,然后自行补充了一些内容,算是自己学习过程的积累。http://www.log4cpp.com/diy/3dprinter/46.html1、3D打印机计算步进电机脉冲的... 查看详情

linux驱动开发笔记:helloworld驱动源码编写makefile编写以及驱动编译基本流程(代码片段)

...记,本篇是描述了一个字符驱动的基础开发流程,以便做嵌入式开发多年的应用或者系统学习驱动开发。 笔者自身情况  笔者拥有硬件基础,单片机软硬基础,linux系统基础等各种,就是没有linux驱动框架基础,未做过linux... 查看详情