4. UART串口驱动

任务目标

在RK3588 Linux环境下,编写UART串口驱动程序,实现数据收发与波特率配置,并通过用户空间程序完成回环通信验证。本案例聚焦于设备树配置、串口驱动框架及用户空间访问路径,不涉及DMA或硬件流控等高级特性。

环境与依赖

  • RK3588开发板,内核源码树已就绪(路径:~/kernel)
  • 交叉编译工具链:aarch64-linux-gnu-gcc
  • 硬件连接:将UART2的TX(引脚GPIO4_B4)与RX(GPIO4_B5)短接,形成物理回环

设备树配置

在设备树文件(如rk3588-evb.dts)中确认或添加UART2节点配置,确保引脚复用正确且未与其他外设冲突:

&uart2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart2m0_xfer>;
};

驱动实现

RK3588的UART控制器由8250_dw驱动覆盖,本案例通过设备树使能现有驱动,并编写一个轻量级平台驱动用于演示数据收发与波特率设置接口。核心代码如下:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/serial_core.h>
#include <linux/tty.h>
#include <linux/serial_8250.h>

#define DRV_NAME "rk_uart_demo"

static struct uart_port demo_port;

static int demo_uart_probe(struct platform_device *pdev)
{
    struct resource *res;
    int ret;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;

    memset(&demo_port, 0, sizeof(demo_port));
    demo_port.membase = devm_ioremap(&pdev->dev, res->start,
                                     resource_size(res));
    if (!demo_port.membase)
        return -ENOMEM;

    demo_port.mapbase = res->start;
    demo_port.irq = platform_get_irq(pdev, 0);
    demo_port.uartclk = 24000000;  /* 24MHz APB时钟 */
    demo_port.fifosize = 64;
    demo_port.ops = &serial8250_port_ops;
    demo_port.flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP;
    demo_port.dev = &pdev->dev;
    demo_port.type = PORT_16550A;
    demo_port.iotype = UPIO_MEM32;
    demo_port.regshift = 2;

    ret = serial8250_register_8250_port(&demo_port);
    if (ret < 0)
        return ret;

    platform_set_drvdata(pdev, &demo_port);
    dev_info(&pdev->dev, "UART demo port registered at 0x%llx\n",
             (u64)res->start);
    return 0;
}

static int demo_uart_remove(struct platform_device *pdev)
{
    serial8250_unregister_port(demo_port.line);
    return 0;
}

static const struct of_device_id demo_uart_of_match[] = {
    { .compatible = "rockchip,rk3588-uart-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, demo_uart_of_match);

static struct platform_driver demo_uart_driver = {
    .probe  = demo_uart_probe,
    .remove = demo_uart_remove,
    .driver = {
        .name = DRV_NAME,
        .of_match_table = demo_uart_of_match,
    },
};
module_platform_driver(demo_uart_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("RK3588 Driver Course");
MODULE_DESCRIPTION("RK3588 UART demo driver");

将上述代码保存为rk_uart_demo.c,并添加到内核drivers/tty/serial/Makefile:

obj-m += rk_uart_demo.o

编译与加载

# 编译驱动模块
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=drivers/tty/serial modules

# 将生成的 rk_uart_demo.ko 拷贝至开发板,加载模块
insmod rk_uart_demo.ko

# 确认设备节点生成(应显示 /dev/ttyS1 或类似名称)
ls /dev/ttyS*

用户空间回环测试

编写测试程序,打开UART设备,设置波特率为115200,发送数据并读取回环数据:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <string.h>

int main(void)
{
    int fd;
    struct termios tty;
    char tx_buf[] = "RK3588 UART Loopback Test";
    char rx_buf[64] = {0};

    fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        perror("open");
        return -1;
    }

    memset(&tty, 0, sizeof(tty));
    cfsetospeed(&tty, B115200);
    cfsetispeed(&tty, B115200);
    tty.c_cflag = CS8 | CLOCAL | CREAD;
    tty.c_iflag = 0;
    tty.c_oflag = 0;
    tty.c_lflag = 0;
    tcsetattr(fd, TCSANOW, &tty);

    write(fd, tx_buf, strlen(tx_buf));
    usleep(100000);  /* 等待数据回环 */
    read(fd, rx_buf, sizeof(rx_buf));

    printf("TX: %s\n", tx_buf);
    printf("RX: %s\n", rx_buf);

    if (strcmp(tx_buf, rx_buf) == 0)
        printf("PASS: Loopback data matches\n");
    else
        printf("FAIL: Data mismatch\n");

    close(fd);
    return 0;
}

编译并运行:

aarch64-linux-gnu-gcc -o uart_test uart_test.c
./uart_test

验证判定标准

  • 驱动加载后,dmesg输出包含“UART demo port registered”信息
  • /dev/ttyS1节点存在且可打开
  • 测试程序输出TX与RX内容一致,并显示PASS字样
  • 使用stty -F /dev/ttyS1 -a可查看当前波特率确为115200

若测试失败,请检查TX/RX引脚是否短接正确,以及设备树中uart2m0_xfer引脚复用是否与其他节点冲突。