Linux 系统移植

序言

Linux 学了有些时间了,手上的开发板很多功能都提前做好了,对初学者挺友好的,但是为了学到更到东西,就来试试移植下新的 Linux 系统到开发板上。

环境

硬件环境

  • 开发板型号100ask_imx6ull_pro 开发板
  • 处理器类型:NXP IMX6ULL
  • **处理器架构:**恩单核 Cortex-A7
  • **处理器主频:**800MHZ
  • 内存容量:512 MB DDR3
  • 存储介质:4GB eMMC

软件环境

  • 宿主机
    • 宿主机操作系统:Ubuntu 18.04
    • 交叉编译器:100ask 提供的工具链 arm-buildroot-linux-gnueabihf- 支持的最低内核版本:4.9.0
  • 开发板

Linux 系统移植过程概述

Linux 系统移植需要以下三个部分:

  • bootloader:用于启动 Linux 内核。常用 bootloader:U-Boot
  • Linux 内核:一般使用芯片原厂提供的源码
  • 根文件系统:存放一些常用的命令和文件

U-Boot、Linux kernel 和 根文件系统三者一起构成了一个完整的 Linux 系统。

步骤

  • 移植 U-Boot
  • 移植 Linux 内核:编译内核可以选择是否生成设备树
  • 根文件系统

移植 Linux 之前我们需要先移植一个 bootloader 代码,这个 bootloader 代码用于启动 Linux 内核,bootloader 有很多,常用的就是 U-Boot。移植好 U-Boot 以后再移植 Linux 内核,移植完 Linux 内核以后 Linux 还不能正常启动,还需要再移植一个根文件系统(rootfs),根文件系统里面包含了一些最常用的命令和文件。所以 U-Boot、Linux kernel 和 rootfs 这三者一起构成了一个完整的 Linux 系统,一个可以正常使用、功能完善的 Linux 系统。在本篇我们就来讲解 U-Boot、Linux Kernel 和 rootfs 的移植,与其说是“移植”,倒不如说是“适配”,因为大部分的移植工作都由 NXP 完成了,我们这里所谓的“移植”主要是使其能够在I.MX6U-ALPHA 开发板上跑起来。

系统移植(环境搭建、uboot、Linux内核、根文件系统)-CSDN博客

U-Boot

U-Boot 的全称是 Universal Boot Loader,U-Boot 是一个遵循 GPL 协议的开源软件,U-Boot 是一个裸机代码,可以看作是一个裸机综合例程

Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段 bootloader 程序。这段bootloader 程序会先初始化 DDR 等外设,然后将 Linux 内核从 flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核

U-Boot 我们一般不是使用 U-Boot 官方的源码,而是采用开发板对应芯片厂商的定制版本,一般分为三种:

厂商 描述
U-Boot 官方的 U-Boot 代码 由 U-Boot 官方维护开发的 U-Boot 版本,版本更新快,基本包含所有常用的芯片。
半导体厂商的 U-Boot 代码 半导体厂商维护的一个 U-Boot,专门针对自家的芯片,在对自家芯片支持上要比 U-Boot 官方的好。
开发板厂商的 U-Boot 代码 开发板厂商在半导体厂商提供的 U-Boot 基础上加入了对自家开发板的支持。

U-boot 官方 U-Boot 源码下载页面:http://ftp.denx.de/pub/uboot/
NXP 官方 U-Boot 源码 Git 地址:https://source.codeaurora.org/external/imx/ubootimx

U-Boot 移植

uboot 移植的一般流程:

  • 在 uboot 中找到参考的开发平台,一般是原厂的开发板。
  • 参考原厂开发板移植 uboot 到我们所使用的开发板上。

一般 uboot 中需要解决串口、NAND、EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot 的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。

PS:这里使用 NXP 官方提供的 uboot 按照正点原子教程移植到 100ask 开发板初步测试成功,但是后续启动 Linux 内核时会出现 data abort 的问题,目前暂未解决,换成 100ask 提供的 uboot 则能正常启动 Linux 内核,这里依旧是使用 NXP 官方提供的 uboot 源码进行修改测试。

编译 NXP 官方开发板的 U-Boot

找到 NXP 官方 I.MX6ULL EVK 开发板对应的默认配置文件以后就可以编译一下,这里我们选择 EMMC 版本的 mx6ull_14x14_evk_emmc_defconfig ,CROSS_COMPILE 选择自己的交叉编译器。

使用如下命令编译 uboot:

1
2
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- -j16

编译成功结果如下:

image-20240702145611903

烧录到 SD 卡测试

将 SD 卡插入查看 /dev/ 目录下新增哪个目录,就是 SD 卡的目录。

**我们编译出来的 .bin 文件不能直接烧写到 SD 卡中,需要在 .bin 文件前面加上 IVT、Boot Data 和 DCD 这三个数据块。**这三个数据块是有指定格式的我们必须按照格式填写,然后将其放到 .bin 文件前面,最终合成的才是可以直接烧写到 SD 卡中的文件。

方法一:使用 imxdownload 程序

使用 imxdownload 程序将 .bin文件转换为 .imx 烧录进 SD 卡。

image-20240702153906291

方法二:使用 dd 命令
1
dd iflag=dsync oflag=dsync if=u-boot-dtb.imx of=/dev/mmcblk0 seek=2

烧录完成后,将开发板启动方式转换为 SD 卡启动,然后复位开发板,即可看见串口打印 uboot 信息如下:

image-20240702155829913

imxdownload 头文件和源文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#ifndef _IMXDOWNLOAD_H
#define _IMXDOWNLOAD_H
/* IMX6U IVT DCD表信息 暂时定义为1K Bytes,此表是读取的u-boot.imx前1K Bytes
* imx6_ivedcd_table[9]是指明代码长度的,本应该根据实际的代码长度来修改
* 这里为了方便,就直接定义为2M Bytes,即
*/

const int imx6_512mb_ivtdcd_table[256] = {
0X402000D1,0X87800000,0X00000000,0X877FF42C,0X877FF420,0X877FF400,0X00000000,0X00000000,
0X877FF000,0X00200000,0X00000000,0X40E801D2,0X04E401CC,0X68400C02,0XFFFFFFFF,0X6C400C02,
0XFFFFFFFF,0X70400C02,0XFFFFFFFF,0X74400C02,0XFFFFFFFF,0X78400C02,0XFFFFFFFF,0X7C400C02,
0XFFFFFFFF,0X80400C02,0XFFFFFFFF,0XB4040E02,0X00000C00,0XAC040E02,0X00000000,0X7C020E02,
0X30000000,0X50020E02,0X30000000,0X4C020E02,0X30000000,0X90040E02,0X30000000,0X88020E02,
0X30000C00,0X70020E02,0X00000000,0X60020E02,0X30000000,0X64020E02,0X30000000,0XA0040E02,
0X30000000,0X94040E02,0X00000200,0X80020E02,0X30000000,0X84020E02,0X30000000,0XB0040E02,
0X00000200,0X98040E02,0X30000000,0XA4040E02,0X30000000,0X44020E02,0X30000000,0X48020E02,
0X30000000,0X1C001B02,0X00800000,0X00081B02,0X030039A1,0X0C081B02,0X0B000300,0X3C081B02,
0X44014801,0X48081B02,0X302C4040,0X50081B02,0X343E4040,0X1C081B02,0X33333333,0X20081B02,
0X33333333,0X2C081B02,0X333333F3,0X30081B02,0X333333F3,0XC0081B02,0X09409400,0XB8081B02,
0X00080000,0X04001B02,0X2D000200,0X08001B02,0X3030331B,0X0C001B02,0XF3526B67,0X10001B02,
0X630B6DB6,0X14001B02,0XDB00FF01,0X18001B02,0X40172000,0X1C001B02,0X00800000,0X2C001B02,
0XD2260000,0X30001B02,0X23106B00,0X40001B02,0X4F000000,0X00001B02,0X00001884,0X90081B02,
0X00004000,0X1C001B02,0X32800002,0X1C001B02,0X33800000,0X1C001B02,0X31800400,0X1C001B02,
0X30802015,0X1C001B02,0X40800004,0X20001B02,0X00080000,0X18081B02,0X27020000,0X04001B02,
0X2D550200,0X04041B02,0X06100100,0X1C001B02,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000
};

const int imx6_256mb_ivtdcd_table[256] = {
0X402000D1,0X87800000,0X00000000,0X877FF42C,0X877FF420,0X877FF400,0X00000000,0X00000000,
0X877FF000,0X00076000,0X00000000,0X40E801D2,0X04E401CC,0X68400C02,0XFFFFFFFF,0X6C400C02,
0XFFFFFFFF,0X70400C02,0XFFFFFFFF,0X74400C02,0XFFFFFFFF,0X78400C02,0XFFFFFFFF,0X7C400C02,
0XFFFFFFFF,0X80400C02,0XFFFFFFFF,0XB4040E02,0X00000C00,0XAC040E02,0X00000000,0X7C020E02,
0X30000000,0X50020E02,0X30000000,0X4C020E02,0X30000000,0X90040E02,0X30000000,0X88020E02,
0X30000C00,0X70020E02,0X00000000,0X60020E02,0X30000000,0X64020E02,0X30000000,0XA0040E02,
0X30000000,0X94040E02,0X00000200,0X80020E02,0X30000000,0X84020E02,0X30000000,0XB0040E02,
0X00000200,0X98040E02,0X30000000,0XA4040E02,0X30000000,0X44020E02,0X30000000,0X48020E02,
0X30000000,0X1C001B02,0X00800000,0X00081B02,0X030039A1,0X0C081B02,0X04000000,0X3C081B02,
0X3C013C01,0X48081B02,0X38324040,0X50081B02,0X28304040,0X1C081B02,0X33333333,0X20081B02,
0X33333333,0X2C081B02,0X333333F3,0X30081B02,0X333333F3,0XC0081B02,0X09409400,0XB8081B02,
0X00080000,0X04001B02,0X2D000200,0X08001B02,0X3030331B,0X0C001B02,0XF352433F,0X10001B02,
0X630B6DB6,0X14001B02,0XDB00FF01,0X18001B02,0X40172000,0X1C001B02,0X00800000,0X2C001B02,
0XD2260000,0X30001B02,0X23104300,0X40001B02,0X47000000,0X00001B02,0X00001883,0X90081B02,
0X00004000,0X1C001B02,0X32800002,0X1C001B02,0X33800000,0X1C001B02,0X31800400,0X1C001B02,
0X30802015,0X1C001B02,0X40800004,0X20001B02,0X00080000,0X18081B02,0X27020000,0X04001B02,
0X2D550200,0X04041B02,0X06100100,0X1C001B02,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,0X00000000,
};

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "imxdownload.h"

#define SHELLCMD_LEN (200)
#define BIN_OFFSET (3072)

/* 此宏指明是否打印u-boot.imx的IVT DCD表信息,不同的开发板其IVT和DCD
* 表的数据是不同的,因此需要获取所使用的开发板的IVT和DCD表信息,最
* 简单的方法就是读取开发板配套资料里面的u-boot.imx的前1KB数据,理论上
* 应该读取3KB的数据,但是表信息远远没有3K这么多,因此读1KB即可
*/
#define PRINT_TAB 0
/*
* 介绍: 此软件是针对NXP的IMX6U系列芯片的,软件用来烧写bin文件到SD卡里面,
* 本软件会自动添加IVT、DCD等信息到原始的bin文件里面,主要用于裸机和uboot的烧写。
* 使用方法: 1、编译好原始的二进制bin文件,如,u-boot.bin等,并将编译好的.bin文件和本
* 软件放置到同一个目录下!!!!
* 2、执行命令sudo ./imxdownload <soucre_bin> <sd_device>
* 如烧写u-boot.bin到/dev/sdd中即可使用如下所示命令:
* sudo ./imxdownload u-boot.bin /dev/sdd
*/

/*
* 输出一些信息
*/
void message_print(void)
{
printf("I.MX6ULL bin download software\r\n");
printf("Edit by:zuozhongkai\r\n");
printf("Date:2019/6/10\r\n");
printf("Version:V1.1\r\n");
printf("log:V1.0 initial version,just support 512MB DDR3\r\n");
printf(" V1.1 and support 256MB DDR3\r\n");
}

int main(int argc, char *argv[])
{
FILE *fp;
unsigned char *buf;
unsigned char *cmdbuf;
int nbytes, filelen;
int i = 0, j = 0;
int ddrsize = 0; /* 0为512MB,1为256MB,2为128MB...... */

message_print();

if((argc != 3) && (argc != 4)){
printf("Error Usage! Reference Below:\r\n");
printf("sudo ./%s <-512m or -256m> <source_bin> <sd_device>\r\n", argv[0]);
return -1;
}

/* 查找参数,获取DDR容量 */
for(i = 0; i < argc; i++)
{
char *param = argv[i];
if(param[0] != '-')
continue;
if(strcmp(param, "-256m") == 0) /* 256MB */
ddrsize = 1;
else if(strcmp(param, "-512m") == 0) /* 512MB */
ddrsize = 0;
}
if(argc == 3) /* 三个参数,也就是不输入DDR容量的话默认为512MB */
ddrsize = 0;

/* 打开bin文件 */
fp = fopen(argv[1], "rb"); /* 以二进制只读方式打开bin文件 */
if(fp == NULL){
printf("Can't Open file %s\r\n", argv[1]);
return -1;
}

/* 获取bin文件长度 */
fseek(fp, 0L, SEEK_END);
filelen = ftell(fp);
fseek(fp, 0L, SEEK_SET);
printf("file %s size = %dBytes\r\n", argv[1], filelen);

/* 读取bin文件到缓冲区buf中 */
buf = malloc(filelen + BIN_OFFSET);
if(buf == NULL){
printf("Mem Malloc Failed!\r\n");
fclose(fp);
return -1;
}
memset(buf, 0, filelen + BIN_OFFSET); /* 清零 */
/* 读取bin源码文件 */
fread(buf + BIN_OFFSET, 1, filelen, fp);

/* 关闭文件 */
fclose(fp);

#if PRINT_TAB
printf("IVT DCD Table:\r\n");
for(i = 0; i < 1024/32; i++){
for(j = 0; j < 8; j++)
{
printf("0X%08X,",*(int *)(buf + BIN_OFFSET + (((i * 8) + j) * 4)));
}
printf("\r\n");
}
free(buf);
return 0;
#endif

/* 添加IVT DCD等表信息到bin文件里面 */
if(ddrsize == 0) { /* 512MB */
printf("Board DDR SIZE: 512MB\r\n");
memcpy(buf, imx6_512mb_ivtdcd_table, sizeof(imx6_512mb_ivtdcd_table));
}
else if (ddrsize == 1) { /* 256MB */
printf("Board DDR SIZE: 256MB\r\n");
memcpy(buf, imx6_256mb_ivtdcd_table, sizeof(imx6_256mb_ivtdcd_table));
}

/* 现在我们已经在buf中构建好了可以用于下载的bin文件,将buf中的数据保存到
* 到一个文件中,文件命名为load.imx
*/
printf("Delete Old load.imx\r\n");
system("rm -rf load.imx"); /* 先删除旧的load.imx文件 */

printf("Create New load.imx\r\n");
system("touch load.imx"); /* 创建新的load.imx文件 */
fp = fopen("load.imx", "wb"); /* 打开laod.imx */
if(fp == NULL){
printf("Cant't Open load.imx!!!\r\n");
free(buf);
return -1;
}
nbytes = fwrite(buf, 1, filelen + BIN_OFFSET, fp);
if(nbytes != (filelen + BIN_OFFSET)){
printf("File Write Error!\r\n");
free(buf);
fclose(fp);
return -1;
}
free(buf);
fclose(fp);

/* 构建烧写的shell命令 */
cmdbuf = malloc(SHELLCMD_LEN);
//sprintf(cmdbuf, "sudo dd iflag=dsync oflag=dsync if=load.imx of=%s bs=512 seek=2",argv[2]);
sprintf(cmdbuf, "dd iflag=dsync oflag=dsync if=load.imx of=%s bs=512 seek=2",argv[2]);
printf("Download load.imx to %s ......\r\n", argv[2]);

/* 执行上面的shell命令 */
system(cmdbuf);
free(cmdbuf);
return 0;
}

i.MX6ULL裸机开发 三:imxdownload 源码分析-CSDN博客

检查驱动

检查 uboot 的驱动,一般是检查 SD 卡、EMMC、LCD、网络这几方面的驱动。根据下面检查后可知,NXP 官方I.MX6ULL EVK 开发板的 uboot 在 100ask 开发板的运行情况:

  • uboot 启动正常,DRAM 识别正确,SD 卡和 EMMC 驱动正常。
  • uboot 默认 LCD 驱动是 4.3 寸 480*272 分辨率的,若使用其他屏幕需要修改参数。
  • 网络不能工作,需要修改驱动。
SD 卡和 EMMC 驱动检查
1
2
3
mmc list	// 列出当前的 MMC 设备
mmc dev 0 // 转换到 MMC 设备 0
mmc info // 打印 MMC 设备信息

image-20240702154709026

从打印信息看出,mmc 设备 0 是 SD 卡,容量为 942.5 MB,与我使用的 SD 卡相符,说明 SD 卡驱动正常。

检查 mmc 设备 1,输入

1
2
3
mmc list	// 列出当前的 MMC 设备
mmc dev 1 // 转换到 MMC 设备 1
mmc info // 打印 MMC 设备信息

image-20240702155007588

从打印信息看出,mmc 设备 1 是 EMMC 卡,容量为 3.6 GB,说明 EMMC 卡驱动正常。

LCD 驱动检查

由上面 uboot 启动信息可知,默认设置的 LCD 屏幕参数为 NXP 官方 I.MX6ULL 开发板的屏幕:4.3 寸 480x272 分辨率的,因此需要修改驱动。

image-20240702155427332

网络驱动

由上面 uboot 启动信息可知,网络驱动有问题,这是因为 100ask 开发板的网络芯片复位引脚和 NXP 官方开发板不一样,因此需要修改驱动。

在 U-Boot 中添加自己的开发板

NXP 官方 uboot 中默认都是 NXP 自己的开发板,虽说我们可以直接在官方的开发板上直接修改,使 uboot 可以完整的运行在我们的板子上。但是为了学习更多东西,接下来学习如何在 uboot 中添加我们的开发板或开发平台。

添加开发板默认配置文件

在 uboot 源码下的 configs 目录下创建默认配置文件。

1
2
cd configs
cp mx6ull_14x14_evk_emmc_defconfig mx6ull_obito_emmc_defconfig

对 CONFIG_SYS_EXTRA_OPTIONS 和 CONFIG_TARGET_ 修改:

1
2
3
4
5
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_obito_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK" 
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_OBITO_EMMC=y
CONFIG_CMD_GPIO=y
添加开发板对应的头文件

在 uboot 源码下的 include/configs 目录下添加自己开发板对应的头文件:

1
cp include/configs/mx6ullevk.h include/configs/mx6ull_obito_emmc.h

打开头文件修改宏定义:跟文件名相同,该文件主要是用来配置或裁剪 uboot。

1
2
3
4
5
6
7
// 修改前
#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H

// 修改后
#ifndef __MX6ULL_OBITO_EMMC_CONFIG_H
#define __MX6ULL_OBITO_EMMC_CONFIG_H
添加开发板对应的板级文件夹

uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。NXP 的 I.MX 系列芯片的所有板级文件夹都存放在 board/freescale 目录下,在这个目录下有个名为 mx6ullevk 的文件夹,这个文件夹就是 NXP 官方 I.MX6ULL EVK 开发板的板级文件夹。复制 mx6ullevk,将其重命名为自己开发板的名字 mx6ull_obito_emmc,命令如下

1
2
cd board/freescale/
cp mx6ullevk/ -r mx6ull_obito_emmc

文件夹下有以下文件:

image-20240702204622788

输入命令:

1
grep -nr "mx6ullevk"

image-20240702204827826

除了 ixmimage_lpddr2.cfg 文件我们将其他文件中的 mx6ullevk 字符串转换成自己给板子起的名字:我的是 mx6ull_obito_emmc,然后打开 Kconfig 文件进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 修改前
if TARGET_MX6ULL_14X14_EVK || TARGET_MX6ULL_9X9_EVK

config SYS_BOARD
default "mx6ullevk"

config SYS_VENDOR
default "freescale"

config SYS_CONFIG_NAME
default "mx6ullevk"

endif

// 修改后
if TARGET_MX6ULL_OBITO_EMMC

config SYS_BOARD
default "mx6ull_obito_emmc"

config SYS_VENDOR
default "freescale"

config SYS_SOC
default "mx6"

config SYS_CONFIG_NAME
default "mx6ull_obito_emmc"

endif
修改 U-Boot 图形界面配置文件

修改文件 arch/arm/cpu/armv7/mx6/Kconfig,在 207 行添加如下内容:

1
2
3
4
5
config TARGET_MX6ULL_OBITO_EMMC
bool "Support mx6ull_obito_emmc"
select MX6ULL
select DM
select DM_THERMAL

在最后一行的 #endif 的前一行添加如下内容:

1
source "board/freescale/mx6ull_obito_emmc/Kconfig"

image-20240702210111971

编译新的 U-Boot 测试

uboot 源码根目录下新建默认配置文件: mx6ull_obito_emmc_defconfig

1
2
3
4
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- mx6ull_obito_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- -j16

修改权限并执行脚本文件

1
2
chmod 777 mx6ull_obito_emmc.sh 	//给予可执行权限,一次即可
./mx6ull_obito_emmc.sh //运行脚本编译uboot

新建默认配置文件是为了方便后续修改编译 uboot,编译成功后显示:

image-20240702210559495

修改 LCD 驱动

修改 LCD 驱动一般注意几点:

  • LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
  • LCD 背光引脚 GPIO 的配置。
  • LCD 配置参数是否正确。

100ask 开发板 LCD 原理图和 NXP 官方I.MX6ULL 开发板一致,也就是 LCD 的 IO 和背光 IO 都一样的,所以 IO 部分就不用修改了。只需要修改 LCD 配置参数即可。

打开之前新建的板级文件夹 board/freescale/mx6ull_obito_emmc,找到 display_info_t 结构体,修改 LCD 参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 修改前
struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "TFT43AB",
.xres = 480,
.yres = 272,
.pixclock = 108695,
.left_margin = 8,
.right_margin = 4,
.upper_margin = 2,
.lower_margin = 4,
.hsync_len = 41,
.vsync_len = 10,
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };

// 修改后
struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "TFT7016",
.xres = 1024,
.yres = 600,
.pixclock = 19531,
.left_margin = 140, //HBPD
.right_margin = 160, //HFPD
.upper_margin = 20, //VBPD
.lower_margin = 12, //VFBD
.hsync_len = 20, //HSPW
.vsync_len = 3, //VSPW
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };

打开开发板对应的头文件:include/configs/mx6ull_obito_emmc.h

将 panel=TFT43AB 全部修改为 panel=TFT7016,panel 的值要与 display_info_t 结构体的 .name 一致。

修改有线网络驱动

我们要修改ENET1 网络驱动的话重点就三点:
①、ENET1 复位引脚初始化。
②、LAN8720A 的器件ID。
③、LAN8720 驱动

IMX6ULL有两个网络外设,分别为ENET1和ENET2,100ask开发板使用LAN8720A作为PHY芯片,接在了ENET2上,LAN8720A芯片有一个地址引脚,RXER/PHYAD0,在开发板上接到了高电平,所以 LAN8720A 的网络PHY地址为1,RESET引脚接在了SNVS_TAMPER6上,由于PHY网络芯片有规定,所有PHY芯片前16个寄存器都是一样的功能,并且使用这16个寄存器就可以让网络正常工作,所以需要修改三个地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

#ifdef CONFIG_CMD_NET
#define CONFIG_CMD_PING
#define CONFIG_CMD_DHCP
#define CONFIG_CMD_MII
#define CONFIG_FEC_MXC
#define CONFIG_MII
#define CONFIG_FEC_ENET_DEV 1

#if (CONFIG_FEC_ENET_DEV == 0)
#define IMX_FEC_BASE ENET_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR 0x2
#define CONFIG_FEC_XCV_TYPE RMII
#elif (CONFIG_FEC_ENET_DEV == 1)
#define IMX_FEC_BASE ENET2_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR 0x1
#define CONFIG_FEC_XCV_TYPE RMII
#endif
#define CONFIG_ETHPRIME "FEC"

#define CONFIG_PHYLIB
#define CONFIG_PHY_MICREL
#endif


setenv ipaddr 192.168.1.7
setenv ethaddr 00:04:9f:04:d2:35
setenv gatewayip 192.168.1.1
setenv netmask 255.255.255.0
setenv serverip 192.168.1.5
saveenv

1
2
3
4
5
6
setenv ipaddr 192.168.1.7 		//开发板IP地址
setenv ethaddr 00:04:9f:04:d2:35 //开发板网卡MAC地址
setenv gatewayip 192.168.1.1 //开发板默认网关
setenv netmask 255.255.255.0 //开发板子网掩码
setenv serverip 192.168.1.5 //服务器地址,也就是Ubuntu 地址
saveenv //保存环境变量

注意!只能在uboot 中ping 其他的机器,其他机器不能ping uboot,因为uboot 没有对ping
命令做处理,如果用其他的机器ping uboot 的话会失败!

再次编译烧录 U-Boot

uboot 启动以后会先从 EMMC 中读取环境变量,如果 EMMC 中没有环境变量的话才会使用 mx6ull_obito_emmc.h 的默认环境变量。

在 uboot 命令模式下输入如下命令修改环境变量:

1
2
setenv panel TFT7016
saveenv

保存重启 uboot,LCD 驱动就能工作正常了。

U-Boot 一些重要的环境变量

bootcmd 环境变量

bootcmd 保存 U-Boot 启动时执行的的命令序列,一般用来启动内核和加载文件系统,比如读取 EMMC 或者 NAND Flash 中的 Linux 内核镜像文件和设备树文件到 DRAM 中,然后启动 Linux 内核。若 EMMC 或者 NAND 中没有保存 bootcmd 的值,则 uboot 就会从 include/env_default.h 文件中读取 bootcmd 的默认值。

例如 uboot 使用 run 命令来运行 findfdt 变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define CONFIG_BOOTCOMMAND \
"run findfdt;" \
"mmc dev ${mmcdev};" \
"mmc dev ${mmcdev}; if mmc rescan; then " \
"if run loadbootscript; then " \
"run bootscript; " \
"else " \
"if run loadimage; then " \
"run mmcboot; " \
"else run netboot; " \
"fi; " \
"fi; " \
"else run netboot; fi"
#endif

findfdt 是 NXP 自行添加的环境变量,用来查找开发板对应的设备树文件,内容如下:

1
2
3
4
5
6
7
8
9
"findfdt="\
"if test $fdt_file = undefined; then " \
"if test $board_name = EVK && test $board_rev = 9X9; then " \
"setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \
"if test $board_name = EVK && test $board_rev = 14X14; then " \
"setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \
"if test $fdt_file = undefined; then " \
"echo WARNING: Could not determine dtb to use; fi; " \
"fi;\0" \
  • mmc dev ${mmcdev} 用于切换 mmc 设备,mmcdev 为 1,mmc dev 1 切换到 EMMC 上。
  • mmc rescan 扫描看有没有 SD 卡或者 EMMC 存在
  • mmc 设备存在的话则运行 loadbootscript 环境变量:
1
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};

mmcdev = 1,mmcpart = 1,loadaddr = 0x80800000,script = boot.scr,展开就是:

1
loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;

loadbootscript 就是从 mmc1 的分区 1 中读取文件 boot.src 到 DRAM 的 0X80800000 处,加载成功则运行 bootscript 环境变量:

1
2
bootscript=echo Running bootscript from mmc ...;
source

如果 loadbootscript 没有找到 boot.src 则运行环境变量 loadimage:

1
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}

mmcdev=1,mmcpart=1,loadaddr=0x80800000,image = zImage,展开就是:

1
loadimage=fatload mmc 1:1 0x80800000 zImage

loadimage 就是从 mmc1 的分区中读取 zImage 到内存的 0X80800000 处,加载 linux 镜像文件 zImage 成功就运行环境变量 mmcboot:mmcargs 是用来设置 bootargs 环境变量的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"mmcboot=echo Booting from mmc ...; " \
"run mmcargs; " \
"if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
"if run loadfdt; then " \
"bootz ${loadaddr} - ${fdt_addr}; " \
"else " \
"if test ${boot_fdt} = try; then " \
"bootz; " \
"else " \
"echo WARN: Cannot load the DT; " \
"fi; " \
"fi; " \
"else " \
"bootz; " \
"fi;\0" \

环境变量 loadfdt:

1
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}

展开就是:从mmc1 的分区 1 中读取 imx6ull-14x14-evk.dtb 文件并放到内存的 0x83000000 处d

1
loadfdt=fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb

读取设备树文件成功则调用命令 bootz 启动 linux:

1
bootz ${loadaddr} - ${fdt_addr}

展开就是:

1
bootz 0x80800000 - 0x83000000 (注意‘-’前后要有空格)

**至此 Linux 内核启动完毕,分析可知步骤如下:**4 行精华

1
2
3
4
mmc dev 1 											// 切换到EMMC
fatload mmc 1:1 0x80800000 zImage // 读取 zImage到 0x80800000 处
fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb // 读取设备树到 0x83000000 处
bootz 0x80800000 - 0x83000000 // 启动Linux

NXP 官方将 CONFIG_BOOTCOMMAND 写复杂是为了兼容多个板子,当我们明确找到自己所使用的板子就可以简化为:

1
2
3
4
5
#define CONFIG_BOOTCOMMAND \
"mmc dev 1;" \
"fatload mmc 1:1 0x80800000 zImage;" \
"fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb;" \
"bootz 0x80800000 - 0x83000000;"

或者可以直接在 uboot 启动时设置 bootcmd 的值,这个值是直接保存到 EMMC 中的:

1
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-14x14-evk.dtb; bootz 80800000 - 83000000;'

bootargs 环境变量

bootargs 保存 uboot 传递给 linux 内核的参数,这些参数可以控制内核的启动行为,例如内存设置、控制台配置、文件系统类型等。

bootargs 环境变量是由 mmcargs 设置的:

1
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}

console=ttymxc0,baudrate=115200,mmcroot=/dev/mmcblk1p2 rootwait rw,展开就是:

1
mmcargs=setenv bootargs console=ttymxc0, 115200 root=/dev/mmcblk1p2 rootwait rw
  • console=ttymxc0, 115200:设置 ttymxc0(串口 1)作为 linux 终端,波特率为 115200。
  • root=/dev/mmcblk1p2 rootwait rw:表示根文件系统存放在 mmcblk1 设备的分区 2 中,rootwait rw 表示等待 mmc 设备初始化完成以后再挂载,rw 表示根文件系统可以读写。

bootargs 就是设置了很多的参数的值,给 linux 内核用,常用参数有:

  • console:linux 终端,也叫控制台,一般 uboot 默认都有设置串口作为 linux 终端,这样就可以通过串口在 PC 机使用终端软件进行交互了。设置 console 为 ttymxc0,这是因为 linux 启动后 开发板串口 1 在 linux 下的设备文件就是 /dev/ttymxc0。
  • root 用来设置根文件系统的位置。
  • rootfstype 用于指定根文件系统类型,若根文件子系统为 ext 格式,则不用设置。

U-Boot 启动 Linux 测试

网络启动

网络启动系统主要是为了方便测试,不用频繁地烧写 EMMC,加快开发速度。一般使用 tftp 服务加载 zImage 和 dtb 文件,设置 bootcmd 和 bootargs 环境变量:

1
2
3
4
5
6
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 100ask_imx6ull-14x14.dtb; bootz
80800000 - 83000000'

saveenv

EMMC 启动

EMMC 启动的话,需要向 EMMC 分区提前存放已知好的 zImage 和 dtb 文件,然后设置 bootcmd 和 bootargs 环境变量:

1
2
3
4
5
6
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'

setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000
100ask_imx6ull-14x14.dtb; bootz 80800000 - 83000000;'

saveenv

Linux 内核移植

一开始用的 4.1.15 版本,后面发现 100ask 交叉编译器支持不了该版本,导致根文件系统挂载时,内核崩溃,后改用 4.9.88 版本,这些 Linux 源码都是 NXP 改版适配后的源码。

nxp-imx/linux-imx at imx_4.9.88_2.0.0_ga (github.com)

该版本下没有带 mfg 的 imx_v7_mfg_defconfig 文件,只有 imx_v7_defconfig 文件。

编译 NXP 官方开发板的 Linux 内核

编译测试

1
2
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- imx_v7_defconfig // 配置linux内核
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- -j16

编译完成后会在 arch/arm/boot 目录下生成 zImage 镜像文件,如果使用设备树的话还会在 arh/arm/boot/dts 目录下生成开发板对应的设备树文件。

  • Linux 内核镜像文件:zImage
  • 设备树文件:imx6ull-14x14-evk.dtb

将两个文件复制到 Ubuntu 的 tftp 目录下:

1
2
cp arch/arm/boot/zImage ~/tftpboot/
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb ~/tftpboot/

uboot 命令行模式下使用 tftp 加载 zImage 和 imx6ull-14x14-evk.dtb

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

因为还没有根文件系统,所以会显示以下信息并卡住——Linux 内核崩溃,重启进入 uboot 即可。

image-20240704163256225

也就是提示内核崩溃,因为 VFS(虚拟文件系统)不能挂载根文件系统,因为根文件系统目录不存在。即使根文件系统目录存在,如果根文件系统目录里面是空的依旧会提示内核崩溃。这个就是根文件系统缺失导致的内核崩溃,但是内核是启动了的,只是根文件系统不存在而已。

添加自己的开发板

添加开发板默认配置文件

复制 imx_v7_defconfig 文件重新命名。

1
2
cd arch/arm/configs
cp imx_v7_defconfig imx_obito_emmc_defconfig

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

复制 imx6ull-14x14-evk.dts 文件重新命名。

1
2
cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-obito-emmc.dts

.dts 是设备树源码,编译后变为 .dtb 文件,因此需要在 arch/arm/boot/dts/Makefile 找到 dtb- $(CONFIG_SOC_IMX6ULL) 配置项,在此配置项中加入 imx6ull-obito-emmc.dtb

编译测试

放了方便调试,新建一个编译脚本文件 imx6ull_obito_emmc.sh

1
2
3
4
#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- imx_obito_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- -j16

添加权限后执行脚本文件编译:

1
2
chmod 777 imx6ull_obito_emmc.sh
./imx6ull_obito_emmc.sh

将编译生成 zImage 和设备树复制到 tftp 目录:

1
2
cp arch/arm/boot/zImage ~/tftpboot/
cp arch/arm/boot/dts/imx6ull-obito-emmc.dtb ~/tftpboot/

使用 tftp 加载 zImage 和 设备树

1
2
3
tftp 80800000 zImage
tftp 83000000 imx6ull-obito-emmc.dtb
bootz 80800000 - 83000000

启动成功说明已经成功在 NXP 提供的 Linux 内核源码添加了自己的开发板。同样因为还没有根文件系统,重启进入 uboot 即可。

修改驱动

CPU 主频

CPU 主频这里不作修改,可以使用 make menuconfig 进入 Linux 内核图形化界面:

1
2
3
CPU Power Management
-> CPU Frequency scaling
-> Default CPUFreq governor

CPU 支持 198MHz、396MHz、528MHz 和 792MHz 四个模式:

  • Performance:最高性能,直接用最高频率,不考虑耗电。
  • Interactive:一开始直接用最高频率,然后根据 CPU 负载慢慢降低。
  • Powersave:省电模式,通常以最低频率运行,系统性能会受影响,一般不会用这个!
  • Userspace:可以在用户空间手动调节频率。
  • Ondemand:定时检查负载,然后根据负载来调节频率。负载低的时候降低 CPU 频率,这样省电,负载高的时候提高 CPU 频率,增加性能。

修改 EMMC 驱动

由开发板核心板原理图可知用到的 EMMC 采用 8 根数据线,而 Linux 内核驱动里面 EMMC 默认是 4 线模式,这样 EMMC 读写速度会慢很多,而我们只需要修改设备树 imx6ull-obito-emmc.dts 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 修改前
&usdhc2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc2>;
non-removable;
status = "okay";
};

// 修改后
&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
no-1-8-v;
status = "okay";
};
  • bus-width = <8>:表示 8 根线
  • no-1-8-v:关闭EMMC 1.8 V 供电选项,防止内核运行时用 1.8 V 去驱动 EMMC

修改网络驱动

为什么 uboot 有网络驱动了,还要修改内核的网络驱动呢?

uboot 的网络驱动主要用于实现网络启动(BOOTP 或 DHCP 协议)或通过 TFTP 等协议进行文件的传输,驱动相对简单,目的在于方便调试之类的。

内核的网络驱动复杂,支持各种网络协议(TCP/IP、Ethernet、Wi-Fi 等),需要处理完整的网络堆栈和多种网络硬件。

添加复位引脚

由于 PHY 芯片不同,由前面移植 uboot 时可知修改网络驱动主要是在设备树添加 PHY 芯片的复位引脚,

添在 iomuxc_sncs 节点下添加网络复位引脚信息:

1
2
3
4
5
6
7
// 添加网络复位引脚
pinctrl_enet_reset: enetresetgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x1b0b0 /* enet1 reset */
MX6ULL_PAD_SNVS_TAMPER6__GPIO5_IO06 0x1b0b0 /* enet2 reset */
>;
};

正点原子教程中还修改了网络时钟引脚配置信息,将 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 的值从默认值 0x4001b031 修改为 0x4001b009,但是我看 100ask 提供的设备树是没有修改默认值的,而是将 pinctrl_enet1节点的内容放到 pinctrl_enet2 中。

修改 fec1 和 fec2 节点

修改 fec1 节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 修改前
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
status = "okay";
};

&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2>;
phy-mode = "rmii";
phy-handle = <&ethphy1>;
status = "okay";

mdio {
#address-cells = <1>;
#size-cells = <0>;

ethphy0: ethernet-phy@2 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <2>;
};

ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
};
};
};


&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
phy-reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
phy-reset-duration = <26>;
status = "okay";
};

// 修改后
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2>;
phy-mode = "rmii";
phy-handle = <&ethphy1>;
phy-reset-gpios = <&gpio5 6 GPIO_ACTIVE_LOW>;
phy-reset-duration = <26>;
status = "okay";

mdio {
#address-cells = <1>;
#size-cells = <0>;

ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <0>;
};
ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
smsc,disable-energy-detect;
reg = <1>;
};
};
};
  • phy-reset-duration = <26>:复位低电平信号持续时间为 26 ms
  • smsc,disable-energy-detect:表示 PHY 芯片是 SMSC 公司的,Linux 内核通过此信息找到对应 PHY 芯片驱动来驱动 LAN8720A
  • ethernet-phy@:后面数字表示 PHY 的地址
  • reg:PHY 的地址
配置 Linux 内核,使能 LAN8720 驱动

Linux 内核已经有多个 PHY 芯片的驱动了,但默认是不编译的,我们用到的是 LAN8720A(SMSC 公司的),因此我们输入 make menuconfig 进入 Linux 图形界面配置使能 LAN8720 驱动,按以下路径进入找到并选择使能 SMSC PHY 芯片驱动。

1
2
3
4
-> Device Drivers
-> Network device support
-> PHY Device support and infrastructure
-> Drivers for SMSC PHYs

根文件系统构建

根文件系统概述

根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如 rcS,inittab 等。

Linux操作系统的根目录(通常表示为"/")是文件系统的最顶层目录,它包含了所有其他目录和文件。以下是一些常见的根目录下子目录及其一般用途的解释:

目录 描述
/bin 存放系统命令的二进制文件,比如lscp
/boot 包含启动 Linux 系统所需的文件,如内核和引导加载程序。
/dev 包含设备文件,这些文件代表计算机上的硬件设备。
/etc 存放系统配置文件,比如网络配置、服务配置等。
/home 用户的个人目录,每个用户都有一个以用户名命名的子目录。
/lib 存放系统库文件,这些是程序运行时需要的共享代码。
/media 用于自动挂载可移动设备,如 USB 驱动器。
/mnt 用于临时挂载文件系统。
/opt 用于存放第三方软件包。
/proc 虚拟文件系统,包含系统和进程信息,/proc 是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件。
/root 系统管理员(root 用户)的家目录。
/sbin 存放系统管理命令的二进制文件,通常只有 root 用户才能访问。
/srv 存放系统服务的数据。
/tmp 存放临时文件。
/usr Unix Shared Resources(共享资源) 的缩写,用户相关的应用程序和文件,通常分为 /bin, /lib, /sbin 等子目录。
/var 存放经常变化的文件,如日志文件。
/sys 包含系统设备和驱动程序的接口信息,类似于 /proc。

构建根文件系统有许多工具:BusyBox、Buildroot、

BusyBox 构建根文件系统

BusyBox 是一个集成了大量 Linux 命令和工具的软件,使用时只需要下载其源码,然后配置选择自己需要的功能,编译即可。

BusyBox Downloads

一般在开发前期都是通过 nfs 挂载根文件系统的,等最后完善测试稳定才烧录到 EMMC 或 NAND 中的,因此我们在自己 Ubuntu 的 nfs 服务器目录下添加一个子目录(例如 busybox1.29.0_rootfs )来存放自己根文件系统。

初步编译 BusyBox

修改顶层 Makefile

在 BusyBox 顶层目录修改 Makefile 文件,添加 ARCH 和 CROSS_COMPILE,搜索 164 行和 190 行修改成自己的编译器存放目录:

1
2
3
CROSS_COMPILE ?= /home/router2/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-buildroot-linux-gnueabihf-

ARCH ?= arm
BusyBox 中文字符支持

新版本的 busybox 默认不支持中文字符显示,中文字符都会显示为 ‘?’,所以我们需要修改 busybox 源码,打开文件 libbb/printable_string.c ,找到函数 printable_string

将字符大于 0x7f 的语句进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{
char *dst;
const char *s;

s = str;
while (1) {
unsigned char c = *s;
if (c == '\0') {
/* 99+% of inputs do not need conversion */
if (stats) {
stats->byte_count = (s - str);
stats->unicode_count = (s - str);
stats->unicode_width = (s - str);
}
return str;

}
if (c < ' ')
break;
// 注释下面两行代码
/* if (c >= 0x7f)
break;
*/
s++;
}

#if ENABLE_UNICODE_SUPPORT
dst = unicode_conv_to_printable(stats, str);
#else
{
char *d = dst = xstrdup(str);
while (1) {
unsigned char c = *d;
if (c == '\0')
break;
// 将 c>=0x7f删除
// if (c < ' ' || c >= 0x7f)
if (c < '')
*d = '?';
d++;
}
if (stats) {
stats->byte_count = (d - dst);
stats->unicode_count = (d - dst);
stats->unicode_width = (d - dst);
}
}
#endif

接着打开文件 libbb/unicode.c,搜索函数 FAST_FUNC unicode_conv_to_printable2,将字符大于 0x7f 显示 ‘?‘ 删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if (unicode_status != UNICODE_ON) {
char *d;
if (flags & UNI_FLAG_PAD) {
d = dst = xmalloc(width + 1);
while ((int)--width >= 0) {
unsigned char c = *src;
if (c == '\0') {
do
*d++ = ' ';
while ((int)--width >= 0);
break;
}
// 修改
// *d++ = (c >= ' ' && c < 0x7f) ? c : '?';
*d++ = (c >= ' ') ? c : '?';
src++;
}
*d = '\0';
} else {
d = dst = xstrndup(src, width);
while (*d) {
unsigned char c = *d;
// 修改
//if (c < ' ' || c >= 0x7f)
if (c < ' ')
*d = '?';
d++;
}
}
if (stats) {
stats->byte_count = (d - dst);
stats->unicode_count = (d - dst);
stats->unicode_width = (d - dst);
}
return dst;
}

配置 busybox

busybox 有三种配置选项:

  • defconfig:缺省配置,也就是默认配置选项。
  • allyesconfig,全选配置,也就是选中 busybox 的所有功能。
  • allnoconfig,最小配置。

这里使用默认配置即可:

1
2
make defconfig
make menuconfig

取消勾选 Build static binary (no shared libs) 选择静态编译还是动态编译,静态编译不需要库文件,但是文件很大,动态编译则要求根文件系统中有动态库,文件小很多,这里使用动态编译的方式。

正点原子教程:这里我们不能采用静态编译!因为采用静态编译的话 DNS 会出问题!无法进行域名解析

image-20240705145539453

选择 vi-style line editing commands

image-20240705145719329

取消勾选 Simplified modutils

image-20240705145759319

确保 mdev 配置项全部选中,默认都是选中的

image-20240705145844766

使能 unicode 编码以支持中文,确保两个选中

image-20240705150020698

编译 busybox

编译 busybox,CONFIG_PREFIX 指定编译结果存放目录。

1
2
make -j8
make install CONFIG_PREFIX=/home/router2/100ask_imx6ull-sdk/nfs_ubuntu/busybox1.29.0_rootfs

编译完成目录下内容如下:

image-20240705150624131

有 bin、sbin 和 usr 三个目录以及 linuxrc 文件。Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 uboot 中 bootargs 环境变量设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。

构建整个根文件系统

因为前面是使用动态编译,所以此时的根文件系统还不能使用,需要添加一些动态库文件等来完善。

添加 lib/ 目录并添加库文件

添加 lib 目录,将交叉编译器目录下的 lib 库文件复制到该目录下。

1
2
3
4
5
6
7
8
// 创建 lib 文件夹
mkdir lib

// 进入目录
cd /home/router2/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/lib

// 拷贝
cp *so* *.a /home/router2/100ask_imx6ull-sdk/nfs_ubuntu/busybox1.29.0_rootfs/lib -d

cp -d 表示拷贝符号连接,相当于 Windows 系统的快捷方式。ld-linux-armhf.so.3 库文件也是符号链接,会连接到其他库文件上,但是 ld-linux-armhf.so.3 不能作为符号链接,否则根文件系统中执行程序无法执行。

我们可以通过先删除该文件,然后重新复制该文件到目录下(不使用 -d 参数),转换前后如下图:一开始作为符号链接时文件很小,去除掉就很大了,是一个实实在在的库文件。

image-20240705152722827

添加 usr/lib 目录并添加库文件
1
2
3
4
5
// 进入目录
cd /home/router2/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib

// 复制文件
cp *so* *.a /home/router2/100ask_imx6ull-sdk/nfs_ubuntu/busybox1.29.0_rootfs/usr/lib -d

使用 du 命令查看目录大小:

1
2
3
4
du lib/ usr/lib/ -sh、

12M lib/
270M usr/lib/

发现 usr/lib 目录很大,进入目录查看有哪些库文件发现有很多第三方库(qt 库、视频解码库等),可以选择性删掉一些以减少文件大小,删除这些库方便后续自己交叉编译库学习,这里手动删除了一些库减少到了 70M。

创建其他文件夹
1
mkdir dev proc mmt sys tmp root
测试根文件系统

uboot 命令行模式下设置 bootargs 环境变量,设置 root 值。

1
root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>
  • /dev/nfs:表示 nfs 挂载
  • server-ip:服务器 IP 地址
  • root-dir:根文件在服务器的存放目录
  • nfs-options:nfs 的其他可选项
  • client-ip:客户端 IP 地址,即开发板的 IP 地址
  • gw-ip:网关地址
  • netmask:子网掩码
  • hostname:主机名,空着即可
  • device:设备名,网卡名:eth0、eth1 等
  • autoconf:自动配置,设置 off 即可
  • dns0-ip:DNS0 服务器 IP 地址,不使用
  • dns1-ip:DNS1服务器 IP 地址,不使用
1
2
3
4
5
6
7
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-obito-emmc.dtb; bootz 80800000 - 83000000'

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 100ask_imx6ull-14x14.dtb; bootz 80800000 - 83000000'

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.5:/home/router2/100ask_imx6ull-sdk/nfs_ubuntu/busybox1.29.0_rootfs,proto=tcp rw ip=192.168.1.7:192.168.1.5:192.168.1.1:255.255.255.0::eth0:off'

saveenv

proto=tcp”表示使用 TCP 协议,“rw”表示 nfs 挂载的根文件系统为可读可写.

1
2
3
tftp 80800000 zImage
tftp 83000000 imx6ull-obito-emmc.dtb
bootz 80800000 - 83000000

image-20240705165000323

挂载成功,但提示 can't run '/etc/init.d/rcS': No such file or directory,这是一个脚本文件,用来规定启动哪些文件的脚本文件,接下来就是完善根文件系统了。

完善根文件系统

在内核挂载根文件系统后,运行的第一个程序是根目录下的 linuxrc,实际是一个指向 /bin/busybox 的链接, 也就是说系统起来后运行的第一个程序是 busybox 本身。先执行 /etc/inittab, 然后调用 /etc/init.d/rcS, 最后是执行 /etc/profile

根文件系统还缺少一些脚本文件,接下来添加几个脚本文件进行完善。

/etc/inittab,/etc/init.d/rcS和/etc/profile分析_linux profile 和inttab谁先调用-CSDN博客

创建 /etc/init.d/rcS 文件

rcS 是一个 shell 脚本文件,rcS 是用来规定 Linux 内核启动哪些文件的脚本文件。

1
mkdir /etc/init.d/rcS

rcS 文件添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH

mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
  • #!/bin/sh:表示这是一个 shell 脚本
  • PATH:PATH 环境变量保存可执行文件可能存在的目录,冒号: 可以添加多个目录
  • LD_LIBRARY_PATH:LD_LIBRARY_PATH 环境变量保存库文件所在的目录
  • export:导出指定的环境变量,相当于声明一些全局变量
  • mount 命令挂载所有的文件系统,这些文件系统由文件 /etc/fstab 来指定
  • 创建 /dev/pts 目录,将 devpts 挂载到 /dev/pts 目录下
  • 最后两行使用 mdev 来管理热插拔设备,让 linux 内核可以在 /dev 目录下自动创建设备节点

最后给 rcS 文件加权限:

1
chmod 777 rcS
创建 /etc/fstab 文件

fstab 在 Linux 开机以后自动配置哪些需要自动挂载的分区,格式如下:

1
<file system> <mount point> <type> <options> <dump> <pass>
  • file system:要挂载的特殊设备
  • mount point:挂载点
  • type:文件系统的类型,例如 ext2、ext3、proc、romfs、tmpfs 等
  • options:挂载选项,一般使用 defaults,包含了 了 rw、suid、 dev、 exec、 auto、 nouser 和 async
  • dump:1 表示允许备份,0 表示不备份,一般不备份
  • pass:磁盘检查设置,0 表示不检查,根目录 ‘/’ 设置为 1,其他分区不能为 1,从 2 开始,一般不在 fstab 中挂载根目录

在 fstab 文件添加以下内容:

1
2
3
4
#<file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
创建 /etc/inittab 文件

init 程序会读取/etc/inittab 这个文件,inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组成,格式如下:

1
<id>:<runlevels>:<action>:<process>
  • id:每个指令的标识符,不能重复
  • runlevels:对 busybox 没用
  • action:动作,用于 process 可能用到的动作
  • process:具体的动作,比如程序、脚本或命令等

busybox 支持的动作如下:

动作 描述
sysinit 在系统初始化的时候 process 才会执行一次。
respawn 当 process 终止以后马上启动一个新的。
askfirst 和 respawn 类似,在运行 process 之前在控制台上显示“Please press Enter to activate this console.”。只要用户按下“Enter”键以后才会执行 process。
wait 告诉 init,要等待相应的进程执行完以后才能继续执行。
once 仅执行一次,而且不会等待 process 执行完成。
restart 当 init 重启的时候才会执行 procee。
ctrlaltdel 当按下 ctrl+alt+del 组合键才会执行 process。
shutdown 关机的时候执行process。

在 inittab 文件添加以下内容:

1
2
3
4
5
6
7
#etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
  • 系统启动以后运行 /etc/init.d/rcS 脚本文件
  • 将 console 作为控制台终端,也就是 ttymxc0,串口
  • 重启时运行 /sbin/init 脚本文件
  • 按下组合键就运行 /sbin/reboot
  • 关机时执行 /bin/umount -a -r 命令,卸载各个文件系统
  • 关机时执行 /sbin/swapoff -a 命令,关闭交换分区

测试根文件系统

测试制作好的根文件系统,测试自己编写的程序是否能运行,是否支持程序开机自启动,中文支持是否正常,能不能链接等功能。

编译测试 hello.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(void)
{
while(1) {
printf("hello world!\r\n");
sleep(2);
}
return 0;
}

执行文件能够正常循环打印,则证明测试成功。

中文显示测试

新建中文名字的目录并在文件中写入中文,看显示是否正常。

image-20240705182649176

可以看到根文件系统可以正常支持中文显示。

开机自启动测试

这里以开机自动执行 hello 程序为例子在 /etc/init.d/rcS 文件添加内容:

1
2
3
4
#添加开启自动程序
cd /drivers
./hello &
cd /

重启开发板,看是否自动运行。

image-20240705183828147

输入 ps 查看 hello 程序的进程 pid 号,输入命令关闭程序:

1
kill -9 pid
网络连接测试

测试开发板能不能上网,对百度官网进行 ping 命令:

1
ping baidu.com

会发现提示 ping: bad address 'baidu.com',这是域名解析错误的原因,因为我们还没有配置域名解析服务器,也就是 DNS 的 IP 地址。在根文件目录下新建 /etc/resolv.conf 文件,添加 DNS 服务器,一般填写自己的网关即可。

1
2
namserver 192.168.1.1
nameserver 114.114.114.114

重新 ping 百度网站测试是否成功。

烧写系统

前面测试系统时都是通过网络连接进行测试,等系统测试稳定后,就将系统烧录到 EMMC 或 NAND 中,就不用每次都进行网络连接了。

image-20240713152901946

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels. The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id == tty to run on, or empty for /dev/console
# runlevels == ignored
# action == one of sysinit, respawn, askfirst, wait, and once
# process == program to run

# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a
::sysinit:/sbin/swapon -a
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
ttymxc0::respawn:/sbin/getty -L ttymxc0 0 vt100 # GENERIC_SERIAL

# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
export PATH=/bin:/sbin:/usr/bin:/usr/sbin

export LD_LIBRARY_PATH=/opt/qt5.15.13/lib:/opt/opencv-4.5.4-arm/lib

if [ "$PS1" ]; then
if [ "`id -u`" -eq 0 ]; then
export PS1='# '
else
export PS1='$ '
fi
fi

export PAGER='/bin/more'
export EDITOR='/bin/vi'

# Source configuration files from /etc/profile.d
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
. $i
fi
done
unset i
#export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0
#export QT_QPA_FONTDIR=/usr/lib/fonts
EVENT=$(cat /proc/bus/input/devices | grep -E 'TSC2007|ft5x0x_ts|goodix-ts' -A4
export TSLIB_ROOT=/opt/tslib1.21
export TSLIB_TSDEVICE=/dev/input/$EVENT
export TSLIB_CONFFILE=/opt/tslib1.21/etc/ts.conf
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_PLUGINDIR=/opt/tslib1.21/lib/ts
export TSLIB_CONSOLEDEVICE=none
export QT_ROOT=/opt/qt5.15.13
export QT_QPA_FONTDIR=$QT_ROOT/lib/fonts
export QT_QPA_PLATFORM_PLUGIN_PATH=$QT_ROOT/plugins
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0

export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1


HOSTNAME='/bin/hostname:'
PS1='\[\e[0;32m\][\u@\h:\w]\$ \[\e[m\]'
shopt -s checkwinsize
resize
export PS1 HOSTNAME
~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/sh


# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
psplash -n &
for i in /etc/init.d/S??* ;do

# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue

case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i start
;;
esac
done

/bin/hostname -F /etc/hostnames