Linux 内核移植与裁剪

一、移植流程

板级资料
  ├── 原理图
  ├── Reference Manual
  └── Boot 模式说明
        ↓
选择 SoC 系列(arm/mach-imx 或 arch/arm64/boot/dts/...)
        ↓
复制参考板级 .dts → 改名为 myboard.dts
  ├── 修改 memory reg
  ├── 改 chosen 节点(console、bootargs)
  ├── 调整 pinctrl-0
  ├── 改 /soc/<ip> 的 reg / interrupt / status
  └── 加自创设备节点
        ↓
复制参考 board/ 目录(如 board/freescale/mx6ull_myboard/)
  ├── Kconfig
  ├── MAINTAINERS
  ├── Makefile
  └── board.c(init_early、init_machine)
        ↓
复制参考 defconfig → myboard_defconfig
  └── 配置 CONFIG_* 选项
        ↓
make myboard_defconfig
make menuconfig
make -j$(nproc) LOADADDR=0x80008000 uImage dtbs modules

二、设备树定制(重点)

1. 参考板设备树

ls arch/arm/boot/dts/
imx6ull.dtsi                  # SoC 通用
imx6ull-14x14-evk.dts         # 评估板

2. 自定义设备树

/dts-v1/;
#include "imx6ull.dtsi"

/ {
    model = "MY Board";
    compatible = "myvendor,myboard", "fsl,imx6ull";

    chosen {
        stdout-path = &uart1;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;   // 512MB
    };

    leds {
        compatible = "gpio-leds";
        led-status {
            label = "status";
            gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
            default-state = "off";
            linux,default-trigger = "heartbeat";
        };
    };
};

&uart1 {
    pinctrl-0 = <&pinctrl_uart1>;
    pinctrl-names = "default";
    status = "okay";
};

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

&usdhc1 {
    pinctrl-0 = <&pinctrl_usdhc1>;
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
    status = "okay";
};

三、裁剪配置

1. 减少内核大小

# 配置
CONFIG_LOCALVERSION="-myboard"
CONFIG_LOG_BUF_SHIFT=14
CONFIG_PRINTK=n                  # 减内核体积(保留 printk 函数)

# 关闭不需要的功能
CONFIG_INPUT_EVDEV=n            # 不用输入设备
CONFIG_SOUND=n                  # 不用音频
CONFIG_WLAN=n                   # 不用 WiFi
CONFIG_USB=n                    # 不用 USB(需要时再开)
CONFIG_RTC=n                    # 不用 RTC 驱动

# 用动态加载
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y

2. 内核裁剪建议

选项 大小 推荐
PREEMPT_NONE 不需要实时
PREEMPT_VOLUNTARY 一般应用
PREEMPT 桌面级
PREEMPT_RT 实时

3. 用 localmodconfig(自动裁剪)

# 根据当前使用的模块生成最小 config
make localmodconfig

四、添加板级支持(旧方式,mach-xxx)

现代内核(5.x+)已逐步废弃,但 i.MX 等还在用

// arch/arm/mach-imx/mach-myboard.c
static void __init myboard_init(void)
{
    // 板级 init
}

static const struct machine_desc myboard_desc __initconst = {
    .name       = "MY Board",
    .atag_offset = 0x100,
    .init_machine = myboard_init,
    .dt_compat = myboard_dt_compat,
};

DT_MACHINE_START(MYBOARD, "MY Board")
    .init_machine = myboard_init,
    .dt_compat    = myboard_dt_compat,
MACHINE_END

推荐新代码直接用设备树驱动模型,不再写 machine_desc。

五、补丁管理

# 在内核源码中打补丁
git am /path/to/patches/*.patch

# 或手动
patch -p1 < xxx.patch

# 生成补丁
git format-patch -1 HEAD

补丁目录结构:

patches/
├── 0001-myserial-add-driver.patch
├── 0002-mygpio-fix-interrupt-handling.patch
└── series

六、驱动集成

1. 静态编入内核

# drivers/<subsystem>/Kconfig
config MY_DRIVER
    tristate "My Driver"
    depends on SPI_MASTER
    help
      Enable this to support My Device.

# drivers/<subsystem>/Makefile
obj-$(CONFIG_MY_DRIVER) += my_driver.o

2. 单独编译为模块

# drivers/<subsystem>/Makefile
obj-m += my_driver.o

七、内核与驱动协调

八、Buildroot 集成(推荐)

把内核源码放在 board/myvendor/myboard/

board/myvendor/myboard/
├── kernel-patches/
├── linux.config         # defconfig 覆盖
├── readme.txt
└── rootfs_overlay/      # 自定义 rootfs

Buildroot 重新生成内核非常方便。

九、常见问题

现象 解决
启动卡在 "Starting kernel" bootargs / 串口配置错
内核 panic(VFS) rootfs 不对、root= 错
No filesystem 文件系统没编进内核
时钟不对 device tree 时钟没配