恩智浦 i.MX8X BSP 的编译

Makefile 主要用于控制: 
1要编译那一些文件
2如何编译这些文件
3如何链接为目标文件及链接顺序

Document /Documentation/kbuild/makefiles.txt 对内核 makefile 的作用,用法
需要编译哪些文件
1. Top make file 决定内核根目录下那些子目录将被编进内核
2. Arch/$(ARCH)/Makefile 决定 arch/$(ARCH)目录下那些文件,目录将被编译进内核
3. 各级子目录下的 makefile 决定此目录下那些文件将被编译进内核,那些将被编译成模块,
进入那些子目录继续调用 它们的 makefile。

one.    顶层 makefile “\Makefile” 
顶层 makefile 将 13 个子目录分为 5 类:
a) init-y := init/ 
b) drivers-y := drivers/ sound/ firmware/ 
c) net-y := net/ 
d) libs-y := lib/ 
e) core-y := usr/ 
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ 
include $(srctree)/arch/$(SRCARCH)/Makefile[arch 目录被直接包含,扩展以上 5 类]
ARCH/CROSS_COMPILE 变量设置
    ARCH ?= $(SUBARCH) 
    CROSS_COMPILE ?=
two.    arch/arm/Makefile 
 head-y := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o[除前面 5 类,
还有一类 head-y,直接以文件名出现,对于有 MMU 的 CPU,使用文件 head.s] 
 core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/[进一步扩展了 core-y 的内
容] 
 machine-$(CONFIG_ARCH_MX6) := mx6[CONFIG_ARCH_MX6 在配置内核中定义,可
以是 y,编译到内核,m,编译为模块,空,不编译] 
 machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y)) 
 platdirs := $(patsubst %,arch/arm/plat-%/,$(plat-y)) 
 core-y += $(machdirs) $(platdirs) 
 libs-y := arch/arm/lib/ $(libs-y) /[进一步扩展了 lib-y 的内容] 
编译内核时,将依次进入 init-y,core-y,libs-y,drivers-y,net-y,所列出的目录中执行他们的 makefile,
每个子目录都会生成一个 built-in.o(libs-y 所列目录会生成 lib.a),最后,head-y 所表示的文件将
和这些 built-in.o,lib.a 一起连接为 vmlinux.’ 
    
three.    各子目录 makefile 
Make menuconfig->.config, TOP makefile include it 
include/config/auto.conf[auto.conf 只是将.config 中的注释去掉,并根据 top makefile 中定义
的变量增加一些变量] 
auto.conf中定义的变量值只有两类,y,m. 各级子目录的 makefile使用这些变量来决定那些文
件编译到内核中,那些编译成模块,通过四种办法来确定。
a) Obj-y,编译进内核:obj-y 中定义的.o 文件由当前目录下的.c,.s 文件编译生成,它们
连同下一层子目录中的 built-in.o 文件一起组合成(使用”$(LD) -r”命令)当然 built-in.o
文件。此文件又被上一层 makefile 使用。
b) Obj-m,编译成可加载模块. 
c) Lib-y,编译成库文件,要把 lib.a 编译时内核,在 top makefile 中的 libs-y 变量列出的
当前目录,并且内核代码一般放在 lib/, arch/$(ARCH)/lib/下。
d) Obj-y,obj-m 还可以用来指定要进入的下一层子目录。

如何编译这些文件
即编译选项,连接选项是什么,这些选项分三类,
1. 全局的,适用于整个内核代码树。定义在 top makefile, arch/$(ARCH)/makefile 中定义,
包括:CFLAGS,AFLAGS,LDFLAGS,ARFLGAS 
2. 局部的,适用于某个 makefile 的所有文件。EXTRA_CFLAGS, EXTRA_AFLAGS, 
EXTRA_LDFLAGS, EXTRA_ARFLGAS 
3. 个体的,只适用于某个文件。包括 CFLAG_XX, AFLAGS_XX

如何链接为目标文件及链接顺序
1. Top makefile, arch/$(ARCH)/makefile 定义了 6 类目录,head-y, init-y, drivers-y, net-y, libs-y, 
core-y.除了 head-y,这样目录的后面直接加上 built-in.o 或 lib.a,表示要连接进内核
 init-y := $(patsubst %/, %/built-in.o, $(init-y))
 core-y := $(patsubst %/, %/built-in.o, $(core-y)) 
 drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) 
 net-y := $(patsubst %/, %/built-in.o, $(net-y)) 
libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) 
libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) 
 libs-y := $(libs-y1) $(libs-y2) 
[patsubst 将 init-y 转换为 init/built-in.o] 
2.
vmlinux-init := $(head-y) $(init-y) 
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y) 
vmlinux-all := $(vmlinux-init) $(vmlinux-main) 
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds 
 vmlinux-all,表示所有构成 vmlinux 的目标文件,按顺序为
head-y,init-y,core-y,libs-y,drivers-y,net-y 即:
arch/arm/kernel/head.o,arch/arm/kernel/init_task.o,init/built-in.o,user/built-in.o 等等
 vmlinux-lds 指定连接脚本为 arch/$(ARCH)/kernel/vmlinux.lds,它是由
arch/arm/kernel/vmlinux.lds.s 文件生成的,规则在 script/makefile.build 中。
SECTIONS 
{ 
 . = 0xC0000000 + 0x00008000; //代码段的起始地址,是一个虚拟地址
 .text.head : { 
 _stext = .; 
 _sinittext = .; 
 *(.text.head) 
 }
 .init : { /* Init code and data */内核初始化的代码与数据
…… 
 } 
 .text : { /* Real text segment */真正的代码段
 _text = .; /* Text and read-only data */代码段和只读数据的开始地址
…… 
 } 
 . = ALIGN((4096)); .rodata : AT(ADDR(.rodata) - 0) { … } . = ALIGN((4096));\\只读数据
 _etext = .; /* End of text and rodata section */代码段和只读数据的结束地址
 . = ALIGN(8192); 
 __data_loc = .; 
 .data : AT(__data_loc) {//数据段
 __data_start = .; /* address in memory *//数据段开始地址
… 
 _edata = .; /数据段结束地址
 } 
 _edata_loc = __data_loc + SIZEOF(.data); 
 .bss : {//bss 段,没有初始化或初值=0 的全局,静态变量
 _end = .; 
 } 
 /* Stabs debugging sections. */调试信息段
 .stab 0 : { *(.stab) } 
} 
总结:
1. 配置文件.config 中定义了一系列的变量,makefile 将结合它们来决定那些文件被编译进内
核,那些被编译模块,涉及那些子目录。
2. 顶层 makefile 和 arch/$(ARCH)/Makefile 决定根目录下那些子目录,arch/$(ARCH)下那些文
件和目录将被编译进内核。
3. 各级子目录下的 Makefile 决定所在目录下那些文件编译进内核,那些编译进模块,进入那
些子目录继续调用它们的 makefile. 
4. Top makefile 和 arch/$(ARCH)/Makefile 设置了可以影响所有文件编译,连接选项。
5. 各级子目录下的 makefile 中可以设置所有文件的编译,连接选项。
6. Top makefile 按照一定的顺序组织文件,根据连接脚本 arch/$(ARCH)/kernel/vmlinux.lds 生
成内核映象文件 vmlinux

kernel Kconfig
Make menuconfig 读取 arch/$(ARCH)/Kconfig 来生成配置界面,这个文件是所有 Kconfig 的入口,
包括了其它目录下的 Kconfig 文件。Kconfig 用于配置内核,它是各种配置界面的源文件,最后生
成配置文件.config,其语法参考\documentation\kbuild\kconfig-language.txt.
恩智浦 BSP 的内核初始化过程

初始化的汇编代码
    确定内核是否支持当前 cpu
    创建一级页表以建立虚拟地址到物理地址的映射关系
    跳转到 C 代码运行
初始化的 C 代码      start_kernel (\init\Main.c) 从此,内核启动进入第二阶段
    初始化中断
    初始化串口
    启动init内核线程继续初始化//Init()函数负责完成根文件系统的挂接、初始化设备驱动程序和启动用户空间的 init 进程等重要工作。
    初始化硬件
总结:
 通过分析恩智浦 i.MX6X 内核驱动代码的目录结构与启动流程,我们了解到:
1. 恩智浦 i.MX6X(包括 i.MX6Q/D/DL/S/SL)都可以使用同一下内核镜像来支持,使用 DTB
来传递板级相关信息。
2. 恩智浦 i.MX6X 开发板的板级信息通过 machine_desc 数据结构链接在固定的内存地址,然
后内核通过 uboot 传递过来的板级信息来查找相对应的开发板的 machine_desc 数据结构,然后使
用这个数据结构中的函数来初始化相应的开发板的资源,包括注册相关的驱动。
3. 因为以上原因,将恩智浦 i.MX6X 的 BSP 移植到自己板上所要做的工作,将不用修改 make 
menuconfig 来增减驱动,而是创建或修改 machine_desc 数据结构,其中最重要的工作是修改其
dts 文件,来完成对此板所需要驱动的注册工作。
4. i.MX6X 的 io 管脚配置,集中在 imx6qdl-sabresd.dts 中配置。
    
恩智浦 BSP 的内核定制
关于 i.MX8X BSP 的情况,请参考 BSP 用户手册” 
imx-yocto-L4.14.98_2.0.0_ga\i.MX_Reference_Manual.pdf”,关于一个基本定制和用户手册中没有涉
及到的部分,请参考此章。
 i.MX8X 的集成度比较高,需要修改的驱动包括三类:
1. 一般如 GPU/VPU/ASRC/DSP 等是属于芯片内部模块,基本不需要修改。
2. i.MX8QXP 自己支持的一些外设驱动,比如说 usb/uart/display,一般只需要简单修改。
3. 另外需要调试的外设可能包括: 网口的 phy, audio codec(另有文档描述),屏,电容或电阻式 i2C
接口触摸屏,i2c 的其它接口外设如 RTC 芯片等,此类驱动除了 i.MX8QXP MEK 板原来设计
就含有的,就需要重新开发
DDR 修改
根据文档《MX8X_4.14.98_ga_BootLoader_V5-20190903_chn.pdf》所述,DDR 的修改主要是
在 bootloader 中进行,而且 bootloader 会将 memory 的大小参数传递给内核,所以内核不需要去修
改 memory 大小 ,如下:
 \linux-imx\arch\arm64\boot\dts\freescale\fsl-imx8dx.dtsi 
memory@80000000 { 
 device_type = "memory"; 
 reg = <0x00000000 0x80000000 0 0x10000000>; /* reg = <0x00000000 0x80000000 0 0x40000000>; 
johnli change to 256MB*/ 
 /* DRAM space - 1, size : 1 GB DRAM */ memory开始地址是0x80000000,大小为1GB,事实上内核
会用uboot传过来的3GB的大小。
 }; 
但是针对应用不同,对 reserved memory 和 cma 大小是可以调节的,这个主要是在内核 DTS
中配置,我们考虑一种极限情况,比如说 V2X 应用,没有 GPU/VPU/DSP/显示要求,那相关驱动
的 reserved memory 可以完全去掉:
 \linux-imx\arch\arm64\boot\dts\freescale\fsl-imx8dx.dtsi 
reserved-memory {… 
 /* 
 * reserved-memory layout 
 * 0x8800_0000 ~ 0x8FFF_FFFF is reserved for M4
 * Shouldn't be used at A core and Linux side. 
 * 
 */ //此段reserved内存是在bootloader中定义的,请参考
MX8X_4.14.98_ga_BootLoader_V5-20190903_chn.pdf文档如何去掉。
 /* 
 decoder_boot:… 
 encoder_boot:… 
decoder_rpc:… 
 encoder_rpc:… 
 encoder_reserved:… //VPU相关reserved memory,没有VPU要求可以注掉,注意一下调用处和VPU驱动最好也一
起注掉。
 rpmsg_reserved:… 
 rpmsg_dma_reserved:…//rpmsg使用,没有M4或PCIe(PCIe也用到了此段内存)可以注掉,注意一下调
用处和PCIe驱动最好也一起注掉。
 
 dsp_reserved:… // audio DSP使用,没有DSP可以注掉,注意一下调用处和DSP驱动最好也一起注掉。
 
 */ 
 如下为CMA的配置,如果没有比较大的CMA应用,比如说GPU/VPU/显示,可以缩小,
或是内存比较小的,也要缩小,我们是定义为整个内存的1/3大小 :
linux,cma { 
 compatible = "shared-dma-pool"; 
 reusable; 
 size = <0 0x5000000>; 
 alloc-ranges = <0 0x8fb00000 0 0x5000000>; /*johnli set the dma on high 256MB, size=80MB*///
考虑极限情况下只有256MB内存的情况下,把CMA放在最高位置,大小为80MB 
 /* 
 size = <0 0x3c000000>; 
 alloc-ranges = <0 0x96000000 0 0x3c000000>;//内存启始地址是0x80000000, i.MX8QXP MEK
板3GB LPPDR4,CMA定义的内存启始地址是0x96000000,大小是980MB 
 */ 
 linux,cma-default; 
 }; 
以下为 i.MX8QXP MEK 配置为 256MB 内存,去掉大部分驱动后启动的内存分配情况:
root@imx8qxpmek:~# cat /proc/meminfo 
MemTotal: 221076 kB 
MemFree: 67456 kB 
MemAvailable: 53516 kB 
…
IO 管脚配置与 Pinctrl 驱动
i.MX8QXP MEK 板的管脚配置
 i.MX8QXP MEK 板的 IO 管脚配置大部分定义在: 
arch\arm64\boot\dts\freescale\fsl-imx8qxp-mek.dtsi
恩智浦已经把每一个 IO 管脚可能使用到的 IOmux 功能,以及与此功能相对应的 IOPAD 属性
都已经准备好,定义在文件 include\dt-bingings\pinctrl\pads-imx8qxp.h 中,所以只需要直接在数组
中包括我们想使用的 IO 管脚及功能的宏就可以了,比如说: 
SC_P_MCLK_OUT0_ADMA_ACM_MCLK_OUT0 0x0600004c 
#define SC_P_MCLK_OUT0_ADMA_ACM_MCLK_OUT0 SC_P_MCLK_OUT0 0
#define SC_P_MCLK_OUT0 76 /* ADMA.ACM.MCLK_OUT0, ADMA.ESAI0.TX_HF_CLK, ADMA.LCDIF.CLK, ADMA.SPI2.SDO, LSIO.GPIO0.IO20 */ 其中 SC_P_MCLK_OUT0_ADMA 表示这个管脚名,MCLK_OUT0 表示这个管脚 IOmux 的功能,然后此功能附带的 IOpad 属性使用 0x0600004c 宏来决定。
pin controller 相关的 DTS 描述 
 类似其他的硬件,pin controller 这个 HW block 需要是 device tree 中的一个节点。此
外,各个其他的 HW block 在驱动之前也需要先配置其引脚复用功能,因此,这些 device(我称 pin controller 是 host,那么这些使用 pin controller 进行引脚配置的 device 叫做 client 
device)也需要在它自己的 device tree node 中描述 pin control 的相关内容
一个典型的 device tree 中的外设 node 定义如下:
device-node-name { 
 定义该 device 自己的属性 
 pinctrl-names = "sleep", "active";------(1) 
 pinctrl-0 = <pin-config-0-a>;--------------(2) 
 pinctrl-1 = <pin-config-1-a pin-config-1-b>; 
 };
(1)pinctrl-names 定义了一个 state 列表。那么什么是 state 呢?具体说应该是 pin state,对于一个
client device,它使用了一组 pin,这一组 pin 应该同时处于某种状态,毕竟这些 pin 是属于一个具
体的设备功能。state 的定义和电源管理关系比较紧密,例如当设备 active 的时候,我们需要 pin 
controller 将相关的一组 pin 设定为具体的设备功能,而当设备进入 sleep 状态的时候,需要 pin 
controller 将相关的一组 pin 设定为普通 GPIO,并精确的控制 GPIO 状态以便节省系统的功耗。state
有两种,标识,一种就是 pinctrl-names 定义的字符串列表,另外一种就是 ID。ID 从 0 开始,依次
加一。根据例子中的定义,state ID 等于 0(名字是 active)的 state 对应 pinctrl-0 属性,state ID 等 于 1(名字是 idle)的 state 对应 pinctrl-1 属性。具体设备 state 的定义和各个设备相关,具体参考
在自己的 device bind。 
(2)pinctrl-x 的定义。pinctrl-x 是一个句柄(phandle)列表,每个句柄指向一个 pin configuration。
有时候,一个 state 对应多个 pin configure。例如在 active 的时候,I2C 功能有两种配置,可以从不
同的 pad iomux 出来
以 uart 为例: 
&uart1 { 
 pinctrl-names = "default"; 
 pinctrl-0 = <&pinctrl_uart1>;
 status = "okay"; 
}; 
 该 serial device 只定义了一个 state 就是 default,对应 pinctrl-0 属性定义。pinctrl-0
是一个句柄(phandle)列表,每个句柄指向一个 pin configuration,这儿用到的是
pinctrl_uart1.
pin controller 驱动的初始化 
 根据device tree代码分析,我们知道,在系统初始化的时候,dts 描述的 device node 会形成一
个树状结构,在 machine 初始化的过程中,会 scan device node 的树状结构,将真正的硬件 device 
node 变成一个个的设备模型中的 device 结构(比如 struct platform_device)并加入到系统中。我们
看看具体 imx6qdl 描述 pin controller 的 dts code,如下:
 //arch/arm/boot/dts/imx6qdl.dtsi 
 iomuxc: iomuxc@020e0000 { 
 compatible = "fsl,imx6dl-iomuxc", "fsl,imx6q-iomuxc"; 
 reg = <0x020e0000 0x4000>; 
 }; 
reg 属性描述 pin controller 硬件的地址信息
当然,: iomuxc@020e0000 这个 device node 也会变成一个 platform device 加入系统。有了
device,那么对应的 platform driver 是如何注册到系统中的呢?代码如下:
static struct of_device_id imx6q_pinctrl_of_match[] = { 
 { .compatible = "fsl,imx6q-iomuxc", }, 
 { /* sentinel */ } 
}; 
static int imx6q_pinctrl_probe(struct platform_device *pdev) 
{ 
 return imx_pinctrl_probe(pdev, &imx6q_pinctrl_info); 
} 
static struct platform_driver imx6q_pinctrl_driver = {
.driver = { 
 .name = "imx6q-pinctrl", 
 .owner = THIS_MODULE, 
 .of_match_table = imx6q_pinctrl_of_match,// 匹配列表
 }, 
 .probe = imx6q_pinctrl_probe, //该 driver 的初始化函数
 .remove = imx_pinctrl_remove, 
}; 
 probe 过程(driver 初始化过程): 
int imx_pinctrl_probe(struct platform_device *pdev, 
 struct imx_pinctrl_soc_info *info) 
{ 
 struct imx_pinctrl *ipctl; 
 struct resource *res; 
 int ret; 
 if (!info || !info->pins || !info->npins) { 
 dev_err(&pdev->dev, "wrong pinctrl info\n"); 
 return -EINVAL; 
 } 
 info->dev = &pdev->dev; 
 /* Create state holders etc for this driver */ 
 ipctl = devm_kzalloc(&pdev->dev, sizeof(*ipctl), GFP_KERNEL); 
 if (!ipctl) 
 return -ENOMEM; 
/*
devm_kzalloc 函数是为 struct imx_pinctrl 数据结构分配内存。每当 driver probe 一个具体的 device 实例的时候,都
需要建立一些私有的数据结构来保存该 device 的一些具体的硬件信息(本场景中,这个数据结构就是 struct imx_pinctr)。
*/ 
 info->pin_regs = devm_kzalloc(&pdev->dev, sizeof(*info->pin_regs) * 
 info->npins, GFP_KERNEL); 
 if (!info->pin_regs) 
 return -ENOMEM; 
 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);// 分配 memory 资源
 ipctl->base = devm_ioremap_resource(&pdev->dev, res); 
 if (IS_ERR(ipctl->base)) 
 return PTR_ERR(ipctl->base); 
/* 
分配了 struct imx_pinctrl 数据结构的内存,当然下一步就是初始化这个数据结构了。我们先看看 imx 的
pin controller driver 是如何定义该数据结构的
struct imx_pinctrl { 
 struct device *dev;// 和 platform device 建立联系
 struct pinctrl_dev *pctl;// 向 core driver 的 pin controller class device
 void __iomem *base;// 访问硬件寄存器的基地址
 const struct imx_pinctrl_soc_info *info; 
}; 
struct imx_pinctrl_soc_info { 
 struct device *dev; 
 const struct pinctrl_pin_desc *pins;// 指向 pin control subsystem 中 core driver 中抽象的
 unsigned int npins; 
 struct imx_pin_reg *pin_regs; 
 struct imx_pin_group *groups;// 描述 imx pin controller 中 pin groups 的信息
 unsigned int ngroups;// 描述 imx pin controller 中 pin groups 的数目
 struct imx_pmx_func *functions;// 描述 imx pin controller 中 function 信息
 unsigned int nfunctions;// 描述 imx pin controller 中 function 的数目
 unsigned int flags; 
}; 
struct pinctrl_desc 和 struct pinctrl_dev 都是 pin control subsystem 中 core driver 的概念。各个具体硬
件的 pin controller 可能会各不相同,但是可以抽取其共同的部分来形成一个 HW independent 的数据结
构,这个数据就是 pin controller 描述符,在 core driver 中用 struct pinctrl_desc 表示,具体该数据结构的
定义如下:
struct pinctrl_desc { 
 const char *name; 
 struct pinctrl_pin_desc const *pins;---指向 npins 个 pin 描述符,每个描述符描述一个 pin 
 unsigned int npins;------------该 pin controller 中有多少个可控的 pin 
 const struct pinctrl_ops *pctlops;------callback 函数,是 core driver 和底层 driver 的接口 
 const struct pinmux_ops *pmxops;-----callback 函数,是 core driver 和底层 driver 的接口 
 const struct pinconf_ops *confops;-----callback 函数,是 core driver 和底层 driver 的接口 
 struct module *owner; 
}; 
其实整个初始化过程的核心思想就是 low level 的 driver 定义一个 pinctrl_desc ,设定 pin 相关的定义和
callback 函数,注册到 pin control subsystem 中。我们用引脚描述符(pin descriptor)来描述一个 pin。 在 pin control subsystem 中,struct pinctrl_pin_desc 用来描述一个可以控制的引脚,我们称之引脚描述
符,代码如下: 
struct pinctrl_pin_desc { 
 unsigned number;-------ID,在本 pin controller 中唯一标识该引脚 
 const char *name;-------user friedly name 
 void *drv_data; 
}; 
*/ 
 imx_pinctrl_desc.name = dev_name(&pdev->dev); 
 imx_pinctrl_desc.pins = info->pins; 
 imx_pinctrl_desc.npins = info->npins; 
 ret = imx_pinctrl_probe_dt(pdev, info); 
/* 
|-> imx_pinctrl_parse_functions 
| |-> imx_pinctrl_parse_groups 
| |-> 
/* 
 * the binding format is fsl,pins = <PIN_FUNC_ID CONFIG ...>, 
 * do sanity check and calculate pins number 
 */ 
 list = of_get_property(np, "fsl,pins", &size); 
以下代码具体设置 mux/conf/input 寄存器
 for (i = 0; i < grp->npins; i++) { 
 u32 mux_reg = be32_to_cpu(*list++); 
 u32 conf_reg; 
 unsigned int pin_id;
struct imx_pin_reg *pin_reg; 
 struct imx_pin *pin = &grp->pins[i]; 
 if (info->flags & SHARE_MUX_CONF_REG) 
 conf_reg = mux_reg; 
 else 
 conf_reg = be32_to_cpu(*list++); 
 pin_id = mux_reg ? mux_reg / 4 : conf_reg / 4; 
 pin_reg = &info->pin_regs[pin_id]; 
 pin->pin = pin_id; 
 grp->pin_ids[i] = pin_id; 
 pin_reg->mux_reg = mux_reg; 
 pin_reg->conf_reg = conf_reg; 
 pin->input_reg = be32_to_cpu(*list++); 
 pin->mux_mode = be32_to_cpu(*list++); 
 pin->input_val = be32_to_cpu(*list++); 
 /* SION bit is in mux register */ 
 config = be32_to_cpu(*list++); 
 if (config & IMX_PAD_SION) 
 pin->mux_mode |= IOMUXC_CONFIG_SION; 
 pin->config = config & ~IMX_PAD_SION; 
 dev_dbg(info->dev, "%s: %d 0x%08lx", info->pins[i].name, 
 pin->mux_mode, pin->config); 
 } 
*/ 
 if (ret) { 
 dev_err(&pdev->dev, "fail to probe dt properties\n"); 
 return ret; 
 } 
 ipctl->info = info; 
 ipctl->dev = info->dev; 
 platform_set_drvdata(pdev, ipctl);
ipctl->pctl = pinctrl_register(&imx_pinctrl_desc, &pdev->dev, ipctl); 
 if (!ipctl->pctl) { 
 dev_err(&pdev->dev, "could not register IMX pinctrl driver\n"); 
 return -EINVAL; 
 } 
 dev_info(&pdev->dev, "initialized IMX pinctrl driver\n"); 
 return 0; 
}
新板 bringup
移植 BSP 时,在修改完 Iomux 与 GPIO 之后,就需要先了解自己的板子与恩智浦
i.MX8QXPMEK 板的区别,如果只是修改或减少驱动,只需要把不需要的驱动在 dts 中删除掉或
是设置为 disabled,然后需要修改的驱动,修改其驱动资源参数中的值,比如说 GPIO,I2C 地址
等内容就可以了。
比如如果是使用 UUU 下载,只保留调试串口,eMMC/SD 驱动与 USB 相关驱动就可以了,
其它的涉及的外设部分的都可以暂时先移掉,来保证初始化通过:
//arch\arm64\boot\dts\freescale\fsl-imx8qxp-mek.dtsi 
 brcmfmac: brcmfmac { //disable wifi/bt/modem 
status = "disabled"; 
 }; 
 modem_reset: modem-reset { status = "disabled";}; 
 
regulators { //remove the gpio regulators, except sdcard and usbotg1 
… 
 reg_can_en: regulator-can-gen { status = "disabled";}; 
 reg_can_stby: regulator-can-stby {status = "disabled";}; 
 reg_fec2_supply: fec2_nvcc {status = "disabled";}; 
 reg_usdhc2_vmmc: usdhc2_vmmc {…}; 
 epdev_on: fixedregulator@100 {status = "disabled";}; 
 reg_usb_otg1_vbus: regulator@0 {…}; 
 reg_audio: fixedregulator@2 {status = "disabled";}; 
 }; 
sound: sound {//remove all sound device
status = "disabled" 
} 
sound-amix-sai { status = "disabled"} 
sound-cs42888 { status = "disabled"} 
… 
&acm {status = "disabled"}; 
&amix {status = "disabled"}; 
&asrc0{status = "disabled"}; 
&esai0 {status = "disabled"}; 
&sai4{status = "disabled"};; 
&sai5 {status = "disabled"}; 
.. 
&sai1 { status = "disabled"}; 
lvds_backlight0/1: lvds_backlight@0/1 {//remove all the pwm backligh 
status = "disabled" 
} 
lcdif_backlight: lcdif_backlight { status = "disabled"} 
… 
&pwm_mipi_lvds1 { status = "disabled"} 
//uart 保留 debug uart0 和底板上的 uart2,其它的去掉
&lpuart1/3 { status = "disabled"} 
&lpuart3 { status = "disabled"} 
//fec 去掉
&fec1/2 { status = "disabled"} 
//can 去掉
&flexcan1/2 { status = "disabled"} 
//spi 去掉
&flexspi0 { status = "disabled"} 
//i2c 去掉
&i2c0_cm40 { status = "disabled"} 
//pcie 去掉
&pcieb{ status = "disabled"} 
//camera 去掉
&mipi_csi_0 { status = "disabled"} 
&cameradev { status = "disabled"} 
&parallel_csi { status = "disabled"} 
&isi_0/1/2/3 { status = "disabled"} 
&i2c0_csi0 { status = "disabled" 
 max9286_mipi@6A { status = "disabled"}} 
//显示接口去掉
&i2c0_mipi_lvds0/1 { status = "disabled"} 
&ldb1/2_phy { status = "disabled"} 
&ldb1/2 {status = "disabled"} 
&mipi_dsi_phy1/2 {status = "disabled"}; 
&mipi_dsi1/2 {status = "disabled"} 
&mipi_dsi_bridge1/2 {status = "disabled"} 
如果使用 sdcard 启动,则把编译出来的最小系统的 fsl-imx8qxp-mek.dtb 替换掉 Boot imx8qx
分区中的 fsl-imx8qxp-mek.dtb,插上 sdcard,即可以启动最小系统。
如果没有设计 sdcard,直接使用 UUU 下载运行结果如下:
1. download i.mx8qxp mek image from: 
https://www.nxp.com/products/processors-and-microcontrollers/arm-based-processors-and-mcus/i.mx-applications-processor
s/i.mx-8-processors/i.mx-software-and-development-tool:IMX-SW
Linux Binary Demo Files - i.MX 8QXPlus MEK
 Unzip it。
2. download uuu 1.2.91 from 
https://github.com/NXPmicro/mfgtools/releases
 put the uuu.exe in the same folder with demo image. 
3. Copy the demo image\samples\example_kernel_emmc.uuu to beyond folder rename to 
example_kernel_emmc_John.uuu, same with demo image, and modify it as follow(just 
Change _flash.bin, _Image, _board.dtb, _initramfs.cpio.gz.uboot, emmc boot device 
number, remove optee, rootfs),to the right one in the demo image. 
example_kernel_emmc_John.uuu: 
uuu_version 1.0.1 
# Please Replace below items with actually file names 
# @_flash.bin | boot loader 
# @_Image | kernel image, arm64 is Image, arm32 it is zImage
# @_board.dtb | board dtb file 
# @_initramfs.cpio.gz.uboot | mfgtool init ramfs 
# @_rootfs.tar.bz2 | rootfs 
# @_uTee.tar | optee image 
SDP: boot -f imx-boot-imx8qxpmek-sd.bin-flash 
# This command will be run when use SPL 
SDPU: write -f imx-boot-imx8qxpmek-sd.bin-flash -offset 0x57c00 
SDPU: jump 
# This command will be run when ROM support stream mode 
SDPS: boot -f imx-boot-imx8qxpmek-sd.bin-flash
# use uboot burn bootloader to eMMC 
# becaue difference chip, offset is difference 
# you can use kernel to do that for specific boards 
FB: ucmd setenv fastboot_dev mmc 
FB: ucmd setenv mmcdev ${emmc_dev} 
FB: flash bootloader imx-boot-imx8qxpmek-sd.bin-flash
FB: ucmd setenv emmc_cmd mmc partconf ${emmc_dev} 0 1 0; 
FB: ucmd if test "${emmc_skip_fb}" != "yes"; then run emmc_cmd; fi 
FB: ucmd setenv emmc_cmd mmc bootbus ${emmc_dev} 2 2 1; 
FB: ucmd if test "${emmc_skip_fb}" != "yes"; then run emmc_cmd; fi 
FB: ucmd setenv fastboot_buffer ${loadaddr} 
FB: download -f Image-imx8qxpmek.bin
FB: ucmd setenv fastboot_buffer ${fdt_addr} 
FB: download -f Image-fsl-imx8qxp-mek.dtb 
FB: ucmd setenv fastboot_buffer ${initrd_addr} 
FB: download -f fsl-image-mfgtool-initramfs-imx_mfgtools.cpio.gz.u-boot
#FB: ucmd setenv bootargs console=${console},${baudrate} earlycon=${earlycon},${baudrate} 
FB: acmd ${kboot} ${loadaddr} ${initrd_addr} ${fdt_addr} 
# get mmc dev number from kernel command line 
# Wait for emmc 
FBK: ucmd while [ ! -e /dev/mmcblk0boot0 ]; do sleep 1; echo "wait for /dev/mmcblk*boot* appear"; done;
# serach emmc device number, if your platform have more than two emmc chip, please echo dev number >/tmp/mmcdev 
FBK: ucmd dev=`ls /dev/mmcblk0boot0`; dev=($dev); dev=${dev[0]}; dev=${dev#/dev/mmcblk}; dev=${dev%boot0}; echo 
$dev > /tmp/mmcdev; 
# create partition 
FBK: ucmd mmc=`cat /tmp/mmcdev`; PARTSTR=$'10M,500M,0c\n600M,,83\n'; echo "$PARTSTR" | sfdisk --force 
/dev/mmcblk${mmc} 
FBK: ucmd mmc=`cat /tmp/mmcdev`; dd if=/dev/zero of=/dev/mmcblk${mmc} bs=1k seek=4096 count=1 
FBK: ucmd sync 
# you can enable below command to write boot partition. but offset is difference at difference platform 
#FBK: ucmd mmc=`cat /tmp/mmcdev`; echo 0 > /sys/block/mmcblk${mmc}boot0/force_ro 
#FBK: ucp _flash.bin t:/tmp 
#FBK: ucmd mmc=`cat /tmp/mmcdev`; dd if=/tmp/_flash.bin of=/dev/mmc${mmc}boot0 bs=1K seek=32 
#FBK: ucmd mmc=`cat /tmp/mmcdev`; echo 1 > /sys/block/mmcblk${mmc}boot0/force_ro 
FBK: ucmd mmc=`cat /tmp/mmcdev`; while [ ! -e /dev/mmcblk${mmc}p1 ]; do sleep 1; done 
FBK: ucmd mmc=`cat /tmp/mmcdev`; mkfs.vfat /dev/mmcblk${mmc}p1 
FBK: ucmd mmc=`cat /tmp/mmcdev`; mkdir -p /mnt/fat 
FBK: ucmd mmc=`cat /tmp/mmcdev`; mount -t vfat /dev/mmcblk${mmc}p1 /mnt/fat 
FBK: ucp Image-imx8qxpmek.bin t:/mnt/fat 
FBK: ucp Image-fsl-imx8qxp-mek.dtb t:/mnt/fat 
#FBK: ucp _uTee.tar t:/tmp/op.tar 
#FBK: ucmd tar -xf /tmp/op.tar -C /mnt/fat 
FBK: ucmd umount /mnt/fat 
FBK: ucmd mmc=`cat /tmp/mmcdev`; mkfs.ext3 -F -E nodiscard /dev/mmcblk${mmc}p2 
FBK: ucmd mkdir -p /mnt/ext3 
FBK: ucmd mmc=`cat /tmp/mmcdev`; mount /dev/mmcblk${mmc}p2 /mnt/ext3 
FBK: acmd export EXTRACT_UNSAFE_SYMLINKS=1; tar -jx -C /mnt/ext3 
FBK: ucp fsl-image-validation-imx-imx8qxpmek.tar.bz2 t:- 
FBK: Sync 
FBK: ucmd umount /mnt/ext3 
FBK: DONE 
4. 将编译出来的 fsl-imx8qxp-mek.dtb 文件替换掉 demo image 中的
Image-fsl-imx8qxp-mek.dtb 文件。
5. jump the i.mx8qxp mek board to download mode. 0001. 
6. link the usb type c to pc, link the usb serial port to pc.
7. run command in command window: uuu.exe example_kernel_emmc_John.uuu 
8. usb port information as follows: 
C:\D\imx\imx8x\nxpwebsite\4.14.98_ga\L4.14.98_2.0.0_ga_images_MX8QXPMEK>uuu.exe 
example_kernel_emmc_John.uuu 
uuu (Universal Update Utility) for nxp imx chips -- libuuu_1.2.135-0-gacaf035 
Success 1 Failure 0 
1:18 20/20 [Done ] FBK: DONE 
1:9 1/ 1 [=================100%=================] SDPS: boot -f 
imx-boot-imx8qxpmek-sd.bin-flash 
C:\D\imx\imx8x\nxpwebsite\4.14.98_ga\L4.14.98_2.0.0_ga_images_MX8QXPMEK> 
9. serial port information as follows: 
Detect USB boot. Will enter fastboot mode! 
Fastboot: Normal 
Boot from USB for mfgtools 
Use default environment for mfgtools 
Run bootcmd_mfg 
… 
# Checking Image at 83100000 ... 
Unknown image format! 
Run fastboot ... 
1 setufp mode 0 
1 cdns3_uboot_initmode 0 
Detect USB boot. Will enter fastboot mode! 
flash target is MMC:1 
MMC: no card present 
MMC card init failed! 
MMC: no card present 
** Block device MMC 1 not supported 
Detect USB boot. Will enter fastboot mode! 
flash target is MMC:0 
status: -104 ep 'ep1in' trans: 0 
Starting download of 932864 bytes 
....... 
downloading of 932864 bytes finished 
writing to partition 'bootloader' 
support sparse flash partition for bootloader
Initializing 'bootloader' 
switch to partitions #1, OK 
mmc0(part 1) is current device 
Writing 'bootloader' 
MMC write: dev # 0, block # 64, count 1882 ... 1882 blocks written: OK 
Writing 'bootloader' DONE! 
status: -104 ep 'ep1in' trans: 0 
Detect USB boot. Will enter fastboot mode! 
status: -104 ep 'ep1in' trans: 0 
Detect USB boot. Will enter fastboot mode! 
Detect USB boot. Will enter fastboot mode! 
Detect USB boot. Will enter fastboot mode! 
Detect USB boot. Will enter fastboot mode! 
Starting download of 23163392 bytes 
..... 
downloading of 23163392 bytes finished 
status: -104 ep 'ep1in' trans: 0 
Detect USB boot. Will enter fastboot mode! 
Starting download of 84164 bytes 
downloading of 84164 bytes finished 
Detect USB boot. Will enter fastboot mode! 
Starting download of 10798655 bytes 
status: -104 ep 'ep1in' trans: 0 
.......................................................................... 
........ 
downloading of 10798655 bytes finished… 
[ 5.257452] Freeing unused kernel memory: 1280K 
Found New UDC: ci_hdrc.0 
ci_hdrc.0 0 
Found New UDC: gadget-cdns3 
gadget-cdns3 1 
ffs.utp0 
[ 5.319329] file system registered 
ffs.utp1
[ 5.338016] Mass Storage Function, version: 2009/09/11 
[ 5.343262] LUN: removable file: (no medium) 
[ 5.347577] Mass Storage Function, version: 2009/09/11 
run utp at /dev/usb-utp0/ep0[ 5.352844] LUN: removable file: (no medium) 
. 
uuu fastboot client 1.0.0 [built Dec 4 2018 12:07:26] 
Start[ 5.359527] read descriptors 
init usb 
[ 5.368194] read descriptors 
run utp at /dev/usb-utp1/ep0 
uuu fastboot client 1.0.0 [built De[ 5.368220] read strings 
c 4 2018 12:07:26] 
Start init usb 
write string 
Start handle c[ 5.380206] read strings 
ommand 
uuc /dev/utp1 
write string 
uuc 0.5 [built Dec 4 2018 12:07:26] 
Start handle command 
UTP: Waiting for /dev/utp1 to appear 
[ 5.503521] configfs-gadget gadget: super-speed config #1: c 
[ 5.535063] random: fast init done 
run shell cmd: while [ ! -e /dev/mmcblk0boot0 ]; do sleep 1; echo "wait for /dev/mmcblk*boot* appear 
"; done; 
run shell cmd: dev=`ls /dev/mmcblk0boot0`; dev=($dev); dev=${dev[0]}; dev=${dev#/dev/mmcblk}; dev=${ 
dev%boot0}; echo $dev > /tmp/mmcdev; 
run shell cmd: mmc=`cat /tmp/mmcdev`; PARTSTR=$'10M,500M,0c\n600M,,83\n'; echo "$PARTSTR" | sfdisk - 
-force /dev/mmcblk${mmc} 
[ 6.015705] mmcblk0: p1 p2 
uuc /dev/utp 
uuc 0.5 [built Dec 4 2018 12:07:26] 
UTP: Waiting for /dev/utp to appear 
Partition #1 contains a vfat signature. 
Partition #2 contains a ext3 signature.
[ 7.295661] mmcblk0: p1 p2 
run shell cmd: mmc=`cat /tmp/mmcdev`; dd if=/dev/zero of=/dev/mmcblk${mmc} bs=1k seek=4096 count=1 
1+0 records in 
1+0 records out 
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.000997625 s, 1.0 MB/s 
run shell cmd: sync 
run shell cmd: mmc=`cat /tmp/mmcdev`; while [ ! -e /dev/mmcblk${mmc}p1 ]; do sleep 1; done 
run shell cmd: mmc=`cat /tmp/mmcdev`; mkfs.vfat /dev/mmcblk${mmc}p1 
run shell cmd: mmc=`cat /tmp/mmcdev`; mkdir -p /mnt/fat 
run shell cmd: mmc=`cat /tmp/mmcdev`; mount -t vfat /dev/mmcblk${mmc}p1 /mnt/fat 
WOpen:/mnt/fat 
WOpen:/mnt/fat/Image-imx8qxpmek.bin 
WOpen:/mnt/fat 
WOpen:/mnt/fat/Image-fsl-imx8qxp-mek.dtb 
run shell cmd: umount /mnt/fat 
run shell cmd: mmc=`cat /tmp/mmcdev`; mkfs.ext3 -F -E nodiscard /dev/mmcblk${mmc}p2 
mke2fs 1.43.8 (1-Jan-2018) 
[ 12.222155] random: crng init done 
run shell cmd: mkdir -p /mnt/ext3 
run shell cmd: mmc=`cat /tmp/mmcdev`; mount /dev/mmcblk${mmc}p2 /mnt/ext3 
[ 22.243037] EXT4-fs (mmcblk0p2): mounting ext3 file system using the ext4 subsystem 
[ 22.255624] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null) 
run shell cmd: export EXTRACT_UNSAFE_SYMLINKS=1; tar -jx -C /mnt/ext3 
WOpen:- 
wait for async proccess finish 
run shell cmd: umount /mnt/ext3
更改调试串口
i.MX8QXP MEK 板默认的调试串口设计如下:从 UART0_TX/RX 3.3V 管脚,经过一个带开关功能的电平转换器(没有做电平转换),然后连接到 UART to USB 桥上。

 status = "okay"; 
}; 
//arch\arm64\boot\dts\freescale\fsl-imx8dx.dtsi 
 lpuart0: serial@5a060000 { 
 compatible = "fsl,imx8qm-lpuart"; 
 reg = <0x0 0x5a060000 0x0 0x1000>; 
 interrupts = <GIC_SPI 345 IRQ_TYPE_LEVEL_HIGH>; 
 interrupt-parent = <&wu>; 
 clocks = <&clk IMX8QXP_UART0_CLK>, 
 <&clk IMX8QXP_UART0_IPG_CLK>; 
 clock-names = "per", "ipg"; 
 assigned-clocks = <&clk IMX8QXP_UART0_CLK>; 
 assigned-clock-rates = <80000000>; 
 power-domains = <&pd_dma_lpuart0>; 
 status = "disabled"; 
 }; 
… 
 pd_dma_lpuart0: PD_DMA_UART0 { 
 reg = <SC_R_UART_0>; 
 #power-domain-cells = <0>; 
 power-domains = <&pd_dma>; 
 wakeup-irq = <345>; 
 };
如果我们的设计不是使用这个串口做为调试串口,比如以下,我们以 i.MX8QXP MEK 板的底
板上的 J37 为调试串口的,他的硬件设计为:

 

则此串口为 UART2,软件按照以下方法设置 UART2 为调试串口:
//arch\arm64\boot\dts\freescale\fsl-imx8qxp-mek.dtsi 
chosen { 
 bootargs = "console=ttyLP2,115200 earlycon=lpuart32,0x5a080000,115200"; 
 stdout-path = &lpuart2; 
 }; 
… 
 pinctrl_lpuart2: lpuart2grp { 
 fsl,pins = < 
 SC_P_UART2_TX_ADMA_UART2_TX 0x06000020 
 SC_P_UART2_RX_ADMA_UART2_RX 0x06000020 
 >; 
 }; 
&pd_dma_lpuart2 { 
 debug_console; 
}; 
&lpuart2 { 
 pinctrl-names = "default"; 
 pinctrl-0 = <&pinctrl_lpuart2>; 
 status = "okay"; 
}; 
//arch\arm64\boot\dts\freescale\fsl-imx8dx.dtsi 
 lpuart2: serial@5a080000 { 
 compatible = "fsl,imx8qm-lpuart"; 
 reg = <0x0 0x5a080000 0x0 0x1000>; 
 interrupts = <GIC_SPI 347 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&wu>; 
 clocks = <&clk IMX8QXP_UART2_CLK>, 
 <&clk IMX8QXP_UART2_IPG_CLK>; 
 clock-names = "per", "ipg"; 
 assigned-clocks = <&clk IMX8QXP_UART2_CLK>; 
 assigned-clock-rates = <80000000>; 
 power-domains = <&pd_dma_lpuart2>; /* 调试串口去掉 DMA PM <&pd_dma2_chan13>;*/ 
 /* dma-names = "tx","rx"; */ /*调试串口去掉 DMA 功能*/ 
 /* dmas = <&edma2 13 0 0>, 
 <&edma2 12 0 1>; */ 
 status = "disabled"; 
 };… 
/*调试串口去掉 DMA PM*/ 
 pd_dma_lpuart2: PD_DMA_UART2 { 
 reg = <SC_R_UART_2>; 
 #power-domain-cells = <0>; 
 power-domains = <&pd_dma>; 
 /* #address-cells = <1>; 
 #size-cells = <0>; */ 
 wakeup-irq = <347>; 
/* 
 pd_dma2_chan12: PD_UART2_RX { 
 reg = <SC_R_DMA_2_CH12>; 
 power-domains =<&pd_dma_lpuart2>; 
 #power-domain-cells = <0>; 
 #address-cells = <1>; 
 #size-cells = <0>; 
 pd_dma2_chan13: PD_UART2_TX { 
 reg = <SC_R_DMA_2_CH13>; 
 power-domains =<&pd_dma2_chan12>; 
 #power-domain-cells = <0>; 
 #address-cells = <1>; 
 #size-cells = <0>; 
 };
 }; 
*/ 
 };
 如下将 UART0 改为普通串口:增加 DMA 功能:
   //arch\arm64\boot\dts\freescale\fsl-imx8qxp-mek.dtsi 
/* 去掉 UART0 的 debug 串口功能,这个必须要修改
&pd_dma_lpuart0 { 
    debug_console; 
}; 
*/ 
   //arch\arm64\boot\dts\freescale\fsl-imx8dx.dtsi 
 lpuart0: serial@5a060000 { 
     compatible = "fsl,imx8qm-lpuart"; 
     reg = <0x0 0x5a060000 0x0 0x1000>; 
     interrupts = <GIC_SPI 345 IRQ_TYPE_LEVEL_HIGH>; 
     interrupt-parent = <&wu>; 
     clocks = <&clk IMX8QXP_UART0_CLK>, 
              <&clk IMX8QXP_UART0_IPG_CLK>; 
     clock-names = "per", "ipg"; 
     assigned-clocks = <&clk IMX8QXP_UART0_CLK>; 
     assigned-clock-rates = <80000000>; 
     power-domains = <&pd_dma2_chan9>; /* <&pd_dma_lpuart0>; */ 
/*add dma support*/ 
dmas = <&edma2 9 0 0>, 
     <&edma2 8 0 1>; 
     status = "disabled"; 
   }; 
… 
         pd_dma_lpuart0: PD_DMA_UART0 { 
             reg = <SC_R_UART_0>; 
             #power-domain-cells = <0>; 
             power-domains = <&pd_dma>; 
             #address-cells = <1>;//增加 DMA 通道的 PM 
             #size-cells = <0>; 
             wakeup-irq = <345>;
         pd_dma2_chan8: PD_UART0_RX { 
             reg = <SC_R_DMA_2_CH8>; 
             power-domains =<&pd_dma_lpuart0>; 
             #power-domain-cells = <0>; 
             #address-cells = <1>; 
             #size-cells = <0>; 
             pd_dma2_chan9: PD_UART0_TX { 
                 reg = <SC_R_DMA_2_CH9>; 
                 power-domains =<&pd_dma2_chan8>; 
                 #power-domain-cells = <0>; 
                 #address-cells = <1>; 
                 #size-cells = <0>; 
             }; 
         }; 
     };
然后启动后停下 uboot,将 uboot 参数变量修改为:(一般 uboot 已经修改) 
 setenv console 'ttyLP2' 
 setenv earlycon 'lpuart32,0x5a080000' 
 sav
pri 
 reset 
测试结果如下:
cat /proc/cmdline 
console=ttyLP2,115200 earlycon=lpuart32,0x5a080000,115200 root=/dev/mmcblk1p2 rootwait rw 
 可以操作 ttyLP0 如下:
echo aaaaa > /dev/ttyLP0

猜你喜欢

转载自blog.csdn.net/Linux_zhicheng/article/details/118611484
BSP