Linux内核移植与调试指南

文档信息

  • 平台: RV1126B (ARM Cortex-A53, 64位)
  • 内核版本: Linux 6.1
  • 内核源码路径: /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel

目录

  1. Linux内核的作用
  2. 内核移植步骤
  3. 内核调试方法
  4. 针对执法记录仪项目的定制
  5. 驱动开发指南
  6. 常见问题与解决方案

一、Linux内核的作用

1.1 什么是Linux内核

Linux内核是操作系统的核心,在系统启动流程中的位置:

上电 → MiniLoader → U-Boot → Linux Kernel → 用户空间
                                    ↑
                            操作系统核心

启动流程详解:

阶段 名称 主要功能
1 U-Boot 加载内核镜像和设备树
2 Linux Kernel 初始化硬件、启动系统服务
3 Init进程 启动用户空间程序
4 应用程序 执法记录仪应用

1.2 内核的核心职责

1. 硬件抽象与管理

内核提供统一的硬件访问接口:

// 内核管理的硬件资源
├── 处理器管理
│   ├── 进程调度
│   ├── CPU频率调节(CPUFreq)
│   └── 电源管理(PM)
│
├── 内存管理
│   ├── 虚拟内存
│   ├── 物理内存分配
│   └── DMA缓冲区
│
├── 设备驱动
│   ├── 摄像头(V4L2)
│   ├── 音频(ALSA)
│   ├── 网络(5G/WiFi)
│   ├── 存储(eMMC)
│   └── 传感器(I2C/SPI)
│
└── 文件系统
    ├── VFS虚拟文件系统
    ├── ext4/squashfs
    └── /proc、/sys接口
2. 系统调用接口

内核为用户空间提供系统调用:

// 常用系统调用
open()    // 打开文件/设备
read()    // 读取数据
write()   // 写入数据
ioctl()   // 设备控制
mmap()    // 内存映射
3. 进程管理
内核调度器
├── CFS调度器(普通进程)
├── 实时调度器(RT进程)
└── 进程间通信(IPC)

1.3 RV1126B平台内核特性

Rockchip RV1126B专用特性:

  1. ISP图像处理: 支持IMX415等MIPI摄像头
  2. MPP硬件编解码: H.264/H.265硬件编码
  3. RGA图像加速: 2D图形加速
  4. NPU神经网络: AI加速(可选)
  5. RKVENC: Rockchip视频编码器

二、内核移植步骤

2.1 获取内核源码

方式一: 使用瑞芯微厂家提供的 SDK(推荐)
# SDK中已包含内核源码
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
ls -la
# 输出: Linux 6.1内核源码

为什么使用BSP内核?

对比项 主线内核 Rockchip BSP内核
ISP驱动 ❌ 不支持 ✅ 完整支持
MPP编解码 ❌ 不支持 ✅ 硬件加速
RGA加速 ❌ 不支持 ✅ 2D加速
稳定性 ⚠️ 需要大量调试 ✅ 厂商验证
方式二: 从主线内核移植(不推荐)
# 仅作学习参考
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v6.1

2.2 配置内核

步骤1: 选择defconfig
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel

# 查看可用的defconfig
ls arch/arm/configs/ | grep rv1126b

# 使用正点原子提供的配置
make ARCH=arm rv1126b_defconfig

defconfig文件位置:

arch/arm/configs/rv1126b_defconfig
步骤2: 定制配置(menuconfig)
# 打开图形化配置界面
make ARCH=arm menuconfig

执法记录仪项目必须启用的配置:

设备驱动包括音频驱动、 USB 驱动、存储设备驱动、网络设备驱动。

[*] Networking support
    [*] Wireless
        <M> cfg80211 - wireless configuration API
        <M> Generic IEEE 802.11 Networking Stack (mac80211)

[*] Device Drivers
    [*] Multimedia support
        [*] Media USB Adapters
        <*> V4L2 platform devices
        <*> Rockchip ISP2 driver

    [*] Sound card support
        <*> Advanced Linux Sound Architecture (ALSA)
        <*> ALSA for SoC audio support

    [*] USB support
        <*> USB Mass Storage support
        <M> USB Serial Converter support
        <M> USB driver for GSM and CDMA modems

    [*] MMC/SD/SDIO card support
        <*> Synopsys DesignWare Memory Card Interface
        <*> Rockchip SD/MMC controller support

    [*] Network device support
        <M> USB Network Adapters
            <M> CDC Ethernet support
            <M> CDC NCM support
步骤3: 保存配置
# 保存为新的defconfig
make ARCH=arm savedefconfig

# 将defconfig复制到configs目录
cp defconfig arch/arm/configs/alientek_rv1126_defconfig

2.3 修改设备树

设备树描述硬件连接关系,是内核移植的关键。

设备树文件位置
arch/arm/boot/dts/
├── rv1126.dtsi              # 芯片级公共配置
├── rv1126-pinctrl.dtsi      # 引脚复用配置
└── rv1126-alientek.dts      # 板级配置(需要创建)
创建板级设备树
cd arch/arm/boot/dts/
cp rv1126-evb.dts rv1126-alientek.dts

编辑 rv1126-alientek.dts:

/dts-v1/;
#include "rv1126.dtsi"
#include "rv1126-pinctrl.dtsi"

/ {
    model = "Alientek RV1126B Law Enforcement Recorder";
    compatible = "alientek,rv1126b-recorder", "rockchip,rv1126";

    chosen {
        bootargs = "earlycon=uart8250,mmio32,0xff570000 console=ttyFIQ0,1500000n8 root=/dev/mmcblk0p7 rootwait rw";
    };

    // 5G模组电源控制
    vcc_5g: vcc-5g-regulator {
        compatible = "regulator-fixed";
        regulator-name = "vcc_5g";
        gpio = <&gpio1 RK_PA2 GPIO_ACTIVE_HIGH>;
        enable-active-high;
        regulator-boot-on;
    };

    // WiFi模组电源控制
    vcc_wifi: vcc-wifi-regulator {
        compatible = "regulator-fixed";
        regulator-name = "vcc_wifi";
        gpio = <&gpio1 RK_PA3 GPIO_ACTIVE_HIGH>;
        enable-active-high;
    };
};

// 摄像头配置(IMX415)
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    imx415: imx415@1a {
        compatible = "sony,imx415";
        reg = <0x1a>;
        clocks = <&cru CLK_MIPICSI_OUT>;
        clock-names = "xvclk";
        power-domains = <&power RV1126_PD_VI>;
        pinctrl-names = "default";
        pinctrl-0 = <&mipicsi_clk>;

        reset-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
        pwdn-gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;

        port {
            imx415_out: endpoint {
                remote-endpoint = <&mipi_in_ucam0>;
                data-lanes = <1 2 3 4>;
            };
        };
    };
};

// 陀螺仪配置(ICM20948)
&i2c2 {
    status = "okay";
    clock-frequency = <400000>;

    icm20948: icm20948@68 {
        compatible = "invensense,icm20948";
        reg = <0x68>;
        interrupt-parent = <&gpio0>;
        interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>;
        mount-matrix = "1", "0", "0",
                       "0", "1", "0",
                       "0", "0", "1";
    };
};

// eMMC配置
&sdmmc {
    status = "okay";
    bus-width = <8>;
    cap-mmc-highspeed;
    mmc-hs200-1_8v;
    supports-emmc;
    non-removable;
    vmmc-supply = <&vcc_3v3>;
    vqmmc-supply = <&vcc_1v8>;
};

// USB配置(5G模组)
&usb_host0_ehci {
    status = "okay";
};

&usb_host0_ohci {
    status = "okay";
};

// SDIO配置(WiFi模组RTL8733)
&sdio {
    status = "okay";
    bus-width = <4>;
    cap-sd-highspeed;
    cap-sdio-irq;
    keep-power-in-suspend;
    non-removable;
    vmmc-supply = <&vcc_wifi>;
};

// 音频配置
&i2s0_8ch {
    status = "okay";
    rockchip,clk-trcm = <1>;
    #sound-dai-cells = <0>;
};

// ISP配置
&isp_vir0 {
    status = "okay";
};

&rkisp_vir0 {
    status = "okay";
    port {
        #address-cells = <1>;
        #size-cells = <0>;

        isp0_in: endpoint@0 {
            reg = <0>;
            remote-endpoint = <&csidphy0_out>;
        };
    };
};

// MIPI CSI配置
&csi_dphy0 {
    status = "okay";
    ports {
        port@1 {
            reg = <1>;
            csidphy0_out: endpoint {
                remote-endpoint = <&isp0_in>;
            };
        };
    };
};
编译设备树
# 编译单个设备树
make ARCH=arm rv1126-alientek.dtb

# 或者在编译内核时自动编译
# 需要修改 arch/arm/boot/dts/Makefile

修改 arch/arm/boot/dts/Makefile:

dtb-$(CONFIG_ARCH_ROCKCHIP) += \
    rv1126-evb.dtb \
    rv1126-alientek.dtb

2.4 编译内核

步骤1: 设置交叉编译环境
# 设置交叉编译工具链
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# 或者使用SDK提供的工具链
export PATH=/home/alientek/atk_dlrv1126b_linux6.1_sdk/prebuilts/gcc/linux-x86/arm/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin:$PATH
export CROSS_COMPILE=arm-none-linux-gnueabihf-
步骤2: 编译内核
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel

# 清理之前的编译
make distclean

# 加载配置
make rv1126_defconfig

# 开始编译(-j8表示使用8个CPU核心)
make -j8

# 编译完成后生成的文件:
# arch/arm/boot/zImage          - 压缩的内核镜像
# arch/arm/boot/dts/*.dtb       - 设备树二进制文件

编译输出:

  LD      vmlinux
  SORTTAB vmlinux
  SYSMAP  System.map
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  GZIP    arch/arm/boot/compressed/piggy.gzip
  CC      arch/arm/boot/compressed/misc.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready
  DTC     arch/arm/boot/dts/rv1126-alientek.dtb
步骤3: 编译内核模块
# 编译所有模块
make modules -j8

# 安装模块到指定目录
make INSTALL_MOD_PATH=./modules_install modules_install

# 模块安装在:
# ./modules_install/lib/modules/6.1.0/

2.5 打包内核镜像

Rockchip平台使用FIT格式打包内核、设备树和资源文件。

创建boot.img
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk

# 使用SDK提供的打包脚本
./build.sh kernel

# 或者手动打包
./mkfirmware.sh

boot.img结构:

boot.img (FIT格式)
├── kernel (zImage)
├── fdt (设备树DTB)
└── resource (启动Logo等)

2.6 烧录内核

方式一: 使用upgrade_tool烧录
# 进入烧录模式
# 按住Recovery键,然后上电

# 烧录boot.img
sudo upgrade_tool di -b ./out/boot.img

# 重启设备
sudo upgrade_tool rd
方式二: 通过U-Boot更新
# 在U-Boot命令行中
# 1. 通过TFTP下载新内核
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.200
tftp 0x40000000 boot.img

# 2. 写入eMMC
mmc dev 0
mmc write 0x40000000 0x4000 0x8000

# 3. 重启
reset

三、内核调试方法

3.1 串口调试

串口是内核调试最基本也是最重要的手段。

配置串口输出

内核配置:

Kernel hacking
    [*] Kernel debugging
    [*] Early printk
        [*] Early printk via UART
    [*] Kernel low-level debugging functions
        (0xff570000) Physical base address of debug UART

bootargs配置:

# 在U-Boot中设置
setenv bootargs "console=ttyFIQ0,1500000n8 earlyprintk=uart8250,mmio32,0xff570000"
查看内核日志
# 实时查看内核日志
dmesg -w

# 查看启动日志
dmesg | less

# 过滤特定驱动的日志
dmesg | grep imx415
dmesg | grep usb

3.2 内核启动日志分析

正常启动日志示例:

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 6.1.0 (alientek@ubuntu) (arm-linux-gnueabihf-gcc 10.3.0)
[    0.000000] CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7), cr=10c5387d
[    0.000000] Machine model: Alientek RV1126B Law Enforcement Recorder
[    0.000000] Memory policy: Data cache writealloc
[    0.000000] Reserved memory: created DMA memory pool at 0x10000000
[    0.000000] OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool
[    0.000000] Zone ranges:
[    0.000000]   Normal   [mem 0x0000000000000000-0x000000001fffffff]
[    0.000000]   HighMem  empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000000000000-0x000000001fffffff]

... (省略中间日志)

[    2.156789] mmc0: new HS200 MMC card at address 0001
[    2.157234] mmcblk0: mmc0:0001 8GTF4R 7.28 GiB
[    2.234567] mmcblk0: p1 p2 p3 p4 p5 p6 p7 p8

[    3.456789] EXT4-fs (mmcblk0p7): mounted filesystem with ordered data mode
[    3.456890] VFS: Mounted root (ext4 filesystem) on device 179:7
[    3.567890] Freeing unused kernel memory: 1024K
[    3.678901] Run /sbin/init as init process

关键启动阶段:

时间戳 阶段 说明
0.000000 内核启动 CPU初始化
0.000000 内存初始化 DDR配置
1.000000 驱动加载 各设备驱动probe
2.156789 eMMC识别 存储设备就绪
3.456789 挂载根文件系统 文件系统挂载
3.678901 启动init 进入用户空间

3.3 使用printk调试

printk日志级别
#define KERN_EMERG   "<0>"  // 系统不可用
#define KERN_ALERT   "<1>"  // 必须立即采取行动
#define KERN_CRIT    "<2>"  // 严重错误
#define KERN_ERR     "<3>"  // 错误
#define KERN_WARNING "<4>"  // 警告
#define KERN_NOTICE  "<5>"  // 正常但重要的信息
#define KERN_INFO    "<6>"  // 信息
#define KERN_DEBUG   "<7>"  // 调试信息
在驱动中使用printk
// 推荐使用dev_xxx系列函数
dev_info(&pdev->dev, "IMX415 probe success\n");
dev_err(&pdev->dev, "Failed to request GPIO: %d\n", ret);
dev_dbg(&pdev->dev, "Register value: 0x%x\n", val);

// 或使用pr_xxx系列
pr_info("5G module detected\n");
pr_err("I2C transfer failed: %d\n", ret);

3.4 动态调试(Dynamic Debug)

动态调试允许运行时开关调试信息。

启用动态调试

内核配置:

Kernel hacking
    [*] Dynamic printk() support
使用动态调试
# 查看所有可调试的位置
cat /sys/kernel/debug/dynamic_debug/control

# 启用特定文件的调试
echo 'file imx415.c +p' > /sys/kernel/debug/dynamic_debug/control

# 启用特定函数的调试
echo 'func imx415_probe +p' > /sys/kernel/debug/dynamic_debug/control

# 启用特定模块的调试
echo 'module 8733bu +p' > /sys/kernel/debug/dynamic_debug/control

# 禁用调试(-p)
echo 'file imx415.c -p' > /sys/kernel/debug/dynamic_debug/control

3.5 使用ftrace跟踪

ftrace是内核内置的强大跟踪工具。

启用ftrace

内核配置:

Kernel hacking
    [*] Tracers
        [*] Kernel Function Tracer
        [*] Kernel Function Graph Tracer
        [*] Interrupts-off Latency Tracer
使用ftrace
# 挂载debugfs
mount -t debugfs none /sys/kernel/debug

cd /sys/kernel/debug/tracing

# 查看可用的跟踪器
cat available_tracers
# 输出: function function_graph nop

# 启用函数跟踪
echo function > current_tracer

# 设置过滤器(只跟踪特定函数)
echo imx415_* > set_ftrace_filter

# 启动跟踪
echo 1 > tracing_on

# 查看跟踪结果
cat trace

# 停止跟踪
echo 0 > tracing_on

# 清空跟踪缓冲区
echo > trace

ftrace输出示例:

# tracer: function
#
#           TASK-PID    CPU#    TIMESTAMP  FUNCTION
#              | |       |          |         |
         v4l2_open-1234  [000]  1234.567890: imx415_s_power <-v4l2_subdev_call
         v4l2_open-1234  [000]  1234.567891: imx415_write_reg <-imx415_s_power

3.6 使用KGDB调试

KGDB允许使用GDB远程调试内核。

配置KGDB

内核配置:

Kernel hacking
    [*] KGDB: kernel debugger
        [*] KGDB: use kgdb over the serial console

bootargs配置:

setenv bootargs "console=ttyFIQ0,1500000 kgdboc=ttyFIQ0,1500000 kgdbwait"
使用KGDB
# 在开发主机上
arm-linux-gnueabihf-gdb vmlinux
(gdb) target remote /dev/ttyUSB0
(gdb) continue

# 设置断点
(gdb) break imx415_probe
(gdb) continue

# 查看变量
(gdb) print ret
(gdb) print *pdev

# 查看调用栈
(gdb) backtrace

3.7 内核崩溃分析

Oops/Panic信息分析

典型的Oops信息:

Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c0004000
[00000000] *pgd=00000000
Internal error: Oops: 5 [#1] PREEMPT SMP ARM
Modules linked in: 8733bu(O) icm20948
CPU: 0 PID: 1234 Comm: v4l2_app Tainted: G           O    6.1.0 #1
Hardware name: Alientek RV1126B
PC is at imx415_read_reg+0x24/0x80
LR is at imx415_probe+0x120/0x300
pc : [<c0456789>]    lr : [<c0456abc>]    psr: 60000013
sp : c1234e00  ip : 00000000  fp : c1234e24
r10: 00000000  r9 : 00000000  r8 : 00000000
r7 : c0789abc  r6 : c0789def  r5 : 00000000  r4 : c1234567
r3 : 00000000  r2 : 00000001  r1 : 00003008  r0 : 00000000

Call trace:
[<c0456789>] (imx415_read_reg) from [<c0456abc>] (imx415_probe+0x120/0x300)
[<c0456abc>] (imx415_probe) from [<c0345678>] (i2c_device_probe+0x100/0x200)
[<c0345678>] (i2c_device_probe) from [<c0234567>] (driver_probe_device+0x80/0x100)
使用addr2line定位代码
# 将地址转换为源代码行号
arm-linux-gnueabihf-addr2line -e vmlinux c0456789

# 输出:
# drivers/media/i2c/imx415.c:234
常见崩溃原因
错误类型 原因 解决方法
NULL pointer dereference 空指针访问 检查指针是否为NULL
Unable to handle kernel paging request 非法内存访问 检查内存地址是否有效
BUG: scheduling while atomic 原子上下文中睡眠 使用GFP_ATOMIC或改用工作队列
BUG: spinlock lockup 死锁 检查锁的使用顺序

四、针对执法记录仪项目的定制

4.1 摄像头驱动集成

IMX415驱动配置

内核配置:

Device Drivers
    <*> Multimedia support
        [*] Media controller API
        [*] V4L2 sub-device userspace API
        <*> Rockchip ISP2 driver
        <M> Sony IMX415 sensor support

验证摄像头:

# 查看V4L2设备
v4l2-ctl --list-devices

# 输出:
# rkisp-vir0 (platform:rkisp-vir0):
#     /dev/video0
#     /dev/video1

# 查看支持的格式
v4l2-ctl -d /dev/video0 --list-formats-ext

# 采集一帧图像测试
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap --stream-count=1 --stream-to=test.yuv

4.2 5G模组驱动集成

USB网络驱动配置

内核配置:

Device Drivers
    [*] Network device support
        <M> USB Network Adapters
            <M> Multi-purpose USB Networking Framework
            <M> CDC Ethernet support
            <M> CDC NCM support
    [*] USB support
        <M> USB Serial Converter support
            <M> USB driver for GSM and CDMA modems
添加RM500Q设备ID

编辑 drivers/net/usb/cdc_ether.c:

static const struct usb_device_id products[] = {
    // ... 现有设备ID ...

    // 添加RM500Q
    {
        USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x0800,
                                      USB_CLASS_COMM,
                                      USB_CDC_SUBCLASS_ETHERNET,
                                      USB_CDC_PROTO_NONE),
        .driver_info = (unsigned long)&wwan_info,
    },
    { },
};

验证5G模组:

# 查看USB设备
lsusb | grep 2c7c

# 查看网络接口
ifconfig -a | grep wwan

# 查看串口设备
ls /dev/ttyUSB*

# 测试AT命令
echo -e "AT\r\n" > /dev/ttyUSB2
cat /dev/ttyUSB2

4.3 WiFi驱动集成

RTL8733驱动配置

内核配置:

Device Drivers
    [*] Network device support
        [*] Wireless LAN
            <M> Realtek 8733BU USB WiFi
    [*] MMC/SD/SDIO card support
        <*> Secure Digital Host Controller Interface support

编译WiFi驱动模块:

# 驱动源码通常在SDK的external目录
cd /home/alientek/atk_dlrv1126b_linux6.1_sdk/external/rkwifibt/drivers/rtl8733bu

# 编译模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=/path/to/kernel

# 安装模块
cp 8733bu.ko /path/to/rootfs/lib/modules/6.1.0/

验证WiFi:

# 加载驱动
modprobe 8733bu

# 查看无线接口
ifconfig wlan0

# 扫描WiFi
iw dev wlan0 scan | grep SSID

4.4 陀螺仪驱动集成

ICM20948驱动配置

内核配置:

Device Drivers
    <*> Industrial I/O support
        <*> Enable buffer support within IIO
        <*> Industrial I/O buffering based on kfifo
        Inertial measurement units
            <M> InvenSense ICM20948 IMU driver

验证陀螺仪:

# 查看IIO设备
ls /sys/bus/iio/devices/

# 读取陀螺仪数据
cat /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw
cat /sys/bus/iio/devices/iio:device0/in_anglvel_y_raw
cat /sys/bus/iio/devices/iio:device0/in_anglvel_z_raw

# 读取加速度计数据
cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw
cat /sys/bus/iio/devices/iio:device0/in_accel_y_raw
cat /sys/bus/iio/devices/iio:device0/in_accel_z_raw

4.5 音频驱动配置

内核配置:

Device Drivers
    <*> Sound card support
        <*> Advanced Linux Sound Architecture (ALSA)
            <*> ALSA for SoC audio support
                <*> Rockchip I2S Device Driver
                <*> ASoC Simple sound card support

验证音频:

# 查看声卡
aplay -l
arecord -l

# 录音测试
arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 test.wav

# 播放测试
aplay test.wav

五、驱动开发指南

5.1 字符设备驱动示例

创建一个简单的字符设备驱动。

示例代码 (simple_char_dev.c):

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "simple_dev"
#define CLASS_NAME "simple_class"

static int major_number;
static struct class *dev_class = NULL;
static struct device *dev_device = NULL;
static char message[256] = {0};
static short message_size;

// 设备打开
static int dev_open(struct inode *inodep, struct file *filep) {
    pr_info("simple_dev: Device opened\n");
    return 0;
}

// 设备读取
static ssize_t dev_read(struct file *filep, char *buffer,
                        size_t len, loff_t *offset) {
    int error_count = 0;

    error_count = copy_to_user(buffer, message, message_size);

    if (error_count == 0) {
        pr_info("simple_dev: Sent %d characters to user\n", message_size);
        return (message_size = 0);
    } else {
        pr_err("simple_dev: Failed to send %d characters\n", error_count);
        return -EFAULT;
    }
}

// 设备写入
static ssize_t dev_write(struct file *filep, const char *buffer,
                         size_t len, loff_t *offset) {
    sprintf(message, "%s(%zu letters)", buffer, len);
    message_size = strlen(message);
    pr_info("simple_dev: Received %zu characters from user\n", len);
    return len;
}

// 设备关闭
static int dev_release(struct inode *inodep, struct file *filep) {
    pr_info("simple_dev: Device closed\n");
    return 0;
}

static struct file_operations fops = {
    .open = dev_open,
    .read = dev_read,
    .write = dev_write,
    .release = dev_release,
};

// 模块初始化
static int __init simple_dev_init(void) {
    pr_info("simple_dev: Initializing\n");

    // 注册字符设备
    major_number = register_chrdev(0, DEVICE_NAME, &fops);
    if (major_number < 0) {
        pr_err("simple_dev: Failed to register major number\n");
        return major_number;
    }
    pr_info("simple_dev: Registered with major number %d\n", major_number);

    // 创建设备类
    dev_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(dev_class)) {
        unregister_chrdev(major_number, DEVICE_NAME);
        pr_err("simple_dev: Failed to create device class\n");
        return PTR_ERR(dev_class);
    }

    // 创建设备节点
    dev_device = device_create(dev_class, NULL, MKDEV(major_number, 0),
                               NULL, DEVICE_NAME);
    if (IS_ERR(dev_device)) {
        class_destroy(dev_class);
        unregister_chrdev(major_number, DEVICE_NAME);
        pr_err("simple_dev: Failed to create device\n");
        return PTR_ERR(dev_device);
    }

    pr_info("simple_dev: Device created successfully\n");
    return 0;
}

// 模块退出
static void __exit simple_dev_exit(void) {
    device_destroy(dev_class, MKDEV(major_number, 0));
    class_unregister(dev_class);
    class_destroy(dev_class);
    unregister_chrdev(major_number, DEVICE_NAME);
    pr_info("simple_dev: Module unloaded\n");
}

module_init(simple_dev_init);
module_exit(simple_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
MODULE_DESCRIPTION("Simple character device driver");
MODULE_VERSION("1.0");

Makefile:

obj-m += simple_char_dev.o

KDIR := /home/alientek/atk_dlrv1126b_linux6.1_sdk/kernel
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

编译和测试:

# 编译驱动
make

# 加载驱动
insmod simple_char_dev.ko

# 查看设备节点
ls -l /dev/simple_dev

# 测试写入
echo "Hello Driver" > /dev/simple_dev

# 测试读取
cat /dev/simple_dev

# 卸载驱动
rmmod simple_char_dev

5.2 平台设备驱动示例

平台设备驱动用于SoC内部外设。

示例代码 (platform_driver.c):

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>

struct my_platform_data {
    struct gpio_desc *gpio_led;
    int value;
};

static int my_platform_probe(struct platform_device *pdev)
{
    struct my_platform_data *pdata;
    struct device *dev = &pdev->dev;

    dev_info(dev, "Probing platform device\n");

    // 分配私有数据
    pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
    if (!pdata)
        return -ENOMEM;

    // 获取GPIO
    pdata->gpio_led = devm_gpiod_get(dev, "led", GPIOD_OUT_LOW);
    if (IS_ERR(pdata->gpio_led)) {
        dev_err(dev, "Failed to get LED GPIO\n");
        return PTR_ERR(pdata->gpio_led);
    }

    // 保存私有数据
    platform_set_drvdata(pdev, pdata);

    // 点亮LED
    gpiod_set_value(pdata->gpio_led, 1);

    dev_info(dev, "Platform device probed successfully\n");
    return 0;
}

static int my_platform_remove(struct platform_device *pdev)
{
    struct my_platform_data *pdata = platform_get_drvdata(pdev);

    // 熄灭LED
    gpiod_set_value(pdata->gpio_led, 0);

    dev_info(&pdev->dev, "Platform device removed\n");
    return 0;
}

static const struct of_device_id my_platform_of_match[] = {
    { .compatible = "alientek,my-platform-device", },
    { }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);

static struct platform_driver my_platform_driver = {
    .probe = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
        .name = "my-platform-driver",
        .of_match_table = my_platform_of_match,
    },
};

module_platform_driver(my_platform_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
MODULE_DESCRIPTION("Platform device driver example");

设备树配置:

/ {
    my_device: my-platform-device {
        compatible = "alientek,my-platform-device";
        led-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

5.3 I2C设备驱动示例

I2C设备驱动框架。

示例代码 (i2c_driver.c):

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>

#define DEVICE_REG_ID    0x00
#define DEVICE_REG_CTRL  0x01

struct my_i2c_data {
    struct i2c_client *client;
    u8 device_id;
};

// I2C读取寄存器
static int my_i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
    int ret;
    struct i2c_msg msgs[2];

    msgs[0].addr = client->addr;
    msgs[0].flags = 0;
    msgs[0].len = 1;
    msgs[0].buf = &reg;

    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = 1;
    msgs[1].buf = val;

    ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret != 2) {
        dev_err(&client->dev, "I2C read failed: %d\n", ret);
        return ret < 0 ? ret : -EIO;
    }

    return 0;
}

// I2C写入寄存器
static int my_i2c_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
    u8 buf[2] = {reg, val};
    int ret;

    ret = i2c_master_send(client, buf, 2);
    if (ret != 2) {
        dev_err(&client->dev, "I2C write failed: %d\n", ret);
        return ret < 0 ? ret : -EIO;
    }

    return 0;
}

static int my_i2c_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct my_i2c_data *data;
    int ret;
    u8 device_id;

    dev_info(&client->dev, "Probing I2C device at 0x%02x\n", client->addr);

    // 分配私有数据
    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->client = client;
    i2c_set_clientdata(client, data);

    // 读取设备ID验证
    ret = my_i2c_read_reg(client, DEVICE_REG_ID, &device_id);
    if (ret) {
        dev_err(&client->dev, "Failed to read device ID\n");
        return ret;
    }

    data->device_id = device_id;
    dev_info(&client->dev, "Device ID: 0x%02x\n", device_id);

    // 初始化设备
    ret = my_i2c_write_reg(client, DEVICE_REG_CTRL, 0x01);
    if (ret) {
        dev_err(&client->dev, "Failed to initialize device\n");
        return ret;
    }

    dev_info(&client->dev, "I2C device probed successfully\n");
    return 0;
}

static int my_i2c_remove(struct i2c_client *client)
{
    dev_info(&client->dev, "I2C device removed\n");
    return 0;
}

static const struct of_device_id my_i2c_of_match[] = {
    { .compatible = "alientek,my-i2c-device", },
    { }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);

static const struct i2c_device_id my_i2c_id[] = {
    { "my-i2c-device", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id);

static struct i2c_driver my_i2c_driver = {
    .driver = {
        .name = "my-i2c-driver",
        .of_match_table = my_i2c_of_match,
    },
    .probe = my_i2c_probe,
    .remove = my_i2c_remove,
    .id_table = my_i2c_id,
};

module_i2c_driver(my_i2c_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
MODULE_DESCRIPTION("I2C device driver example");

六、常见问题与解决方案

6.1 内核无法启动

问题现象:

  • 串口无输出
  • 或输出到某处停止

可能原因及解决方案:

原因 解决方法
bootargs配置错误 检查console参数,确认串口地址和波特率
设备树错误 检查DTS语法,使用dtc编译验证
根文件系统无法挂载 检查root参数,添加rootwait
内核配置缺少驱动 确认eMMC驱动已编译进内核

调试步骤:

# 1. 在U-Boot中检查bootargs
printenv bootargs

# 2. 添加earlyprintk
setenv bootargs "console=ttyFIQ0,1500000 earlyprintk=uart8250,mmio32,0xff570000 root=/dev/mmcblk0p7 rootwait rw"

# 3. 使用init=/bin/sh进入shell
setenv bootargs "console=ttyFIQ0,1500000 root=/dev/mmcblk0p7 rootwait rw init=/bin/sh"

6.2 设备树配置错误

问题现象:

  • 外设无法识别
  • 驱动probe失败

检查设备树:

# 查看编译后的设备树
dtc -I dtb -O dts -o output.dts arch/arm/boot/dts/rv1126-alientek.dtb

# 在运行系统中查看设备树
ls /proc/device-tree/
cat /proc/device-tree/model

常见错误:

// 错误: GPIO编号错误
reset-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_LOW>;  // 应该检查原理图

// 错误: I2C地址错误
reg = <0x1a>;  // 应该与硬件实际地址一致

// 错误: 时钟配置错误
clocks = <&cru CLK_WRONG>;  // 应该使用正确的时钟ID

// 错误: 中断配置错误
interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>;  // 检查中断类型

6.3 驱动加载失败

问题现象:

  • insmod报错
  • dmesg显示probe失败

调试方法:

# 查看详细错误信息
dmesg | tail -50

# 检查模块依赖
modinfo xxx.ko

# 强制加载模块
insmod xxx.ko

# 查看模块参数
cat /sys/module/xxx/parameters/*

常见错误:

# 错误1: 符号未定义
insmod: ERROR: could not insert module xxx.ko: Unknown symbol in module

# 解决: 检查内核配置,确保依赖的功能已启用

# 错误2: 版本不匹配
insmod: ERROR: could not insert module xxx.ko: Invalid module format

# 解决: 使用相同内核版本重新编译模块

# 错误3: 设备已存在
insmod: ERROR: could not insert module xxx.ko: File exists

# 解决: 先卸载旧模块 rmmod xxx

6.4 性能问题

问题现象:

  • 系统响应慢
  • CPU占用率高

分析工具:

# 查看CPU占用
top

# 查看进程状态
ps aux

# 使用perf分析
perf top
perf record -a -g sleep 10
perf report

# 查看中断统计
cat /proc/interrupts

# 查看内存使用
free -h
cat /proc/meminfo

优化建议:

  1. 启用内核抢占: CONFIG_PREEMPT
  2. 调整调度器: 使用实时调度策略
  3. 优化DMA: 减少CPU拷贝
  4. 使用硬件加速: MPP编码,RGA处理

6.5 内存泄漏

检测工具:

# 启用KMEMLEAK
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

# 查看slab分配
cat /proc/slabinfo

# 使用valgrind(用户空间)
valgrind --leak-check=full ./app

预防措施:

// 使用devm_系列函数自动管理资源
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);

// 确保错误路径释放资源
if (error) {
    kfree(data);
    return error;
}

总结

本指南涵盖了Linux内核移植与调试的核心内容:

  1. 内核移植: 从配置、编译到烧录的完整流程
  2. 调试方法: printk、ftrace、KGDB等工具的使用
  3. 项目定制: 摄像头、5G、WiFi等驱动的集成
  4. 驱动开发: 字符设备、平台设备、I2C驱动示例
  5. 问题解决: 常见问题的诊断和解决方案

关键要点:

  • 使用Rockchip BSP内核,充分利用硬件特性
  • 设备树配置是移植的核心,必须与硬件一致
  • 善用内核调试工具,快速定位问题
  • 遵循内核编码规范,使用devm_系列函数
  • 重视错误处理,确保资源正确释放

下一步:

  • 深入学习特定子系统(V4L2、ALSA、网络等)
  • 阅读内核文档: Documentation/目录
  • 参与内核社区,学习最佳实践
  • 针对项目需求优化性能和功耗
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐