Bootstrap

uart驱动学习

1. 整体结构图(来自gpt)

在这里插入图片描述

说明:

TTY 核心层 (TTY Core Layer):

充当用户空间和驱动之间的桥梁,负责管理 TTY 实例并与驱动交互。
它为用户空间的 TTY 操作(如读写)提供统一接口。

8250 核心驱动 (8250 Core Driver):

提供 UART (Universal Asynchronous Receiver-Transmitter) 的核心功能实现。
定义了许多通用的 API 和逻辑,用于支持兼容 8250/16550 的硬件。

DesignWare 8250 驱动 (DesignWare 8250 Driver):

继承 8250 核心驱动,并在其上实现了针对特定硬件(如 DesignWare SoC)的附加功能。
它通过调用 8250 核心驱动的 API,实现特定硬件的初始化和操作。
TTY 子系统为用户和硬件驱动提供抽象接口,而 8250 核心驱动提供通用串口支持,DesignWare 8250 驱动则负责特定硬件的配置与支持。

2. 源码分析

2.1 tty_io.c - TTY核心层实现

/*
- 'tty_io.c' 提供了TTY设备的核心功能实现,包括:
- TTY设备的注册和注销
- TTY设备的打开/关闭操作
- TTY设备的读写操作
- TTY设备的IO控制
- TTY设备的缓冲区管理
 */

主要功能:
实现TTY核心层框架
管理TTY设备的生命周期
提供标准的字符设备接口
处理TTY设备的输入输出
管理行规程(Line Discipline)

2.2 8250_core.c - 8250/16550串口核心驱动

/*
- 通用/传统的8250/16550类型串口驱动
- 支持:
- ISA兼容的8250/16550端口
- PNP 8250/16550端口 
- 早期串口设置
- 用户空间可配置的"虚拟"端口
- "serial8250"平台设备
 */

主要功能:
实现8250/16550串口的基础驱动功能
管理串口资源(IO端口、中断等)
提供串口操作接口
支持多种类型的8250兼容设备
处理串口中断和数据传输

2.3 8250_dw.c - DesignWare 8250驱动

/*
- Synopsys DesignWare 8250驱动
- 特点:
- 支持LCR忙检测
- 支持分数波特率
- 支持FIFO访问
- 支持DMA传输
 */

主要功能:
实现DesignWare特定的8250串口功能
支持平台设备和ACPI设备
实现电源管理
支持设备树配置
处理硬件特定的寄存器和功能

2.4 三者的关系:

tty_io.c 是最上层的TTY核心框架
8250_core.c 在tty_io.c的基础上实现通用串口驱动
8250_dw.c 是8250_core.c的一个具体实现,专门支持DesignWare硬件

这种分层设计使得:
驱动代码结构清晰
便于维护和扩展
支持不同的硬件实现
提供统一的接口
代码复用性好

3.1 驱动注册的流程(引用原文)

设备树中默认打开了串口9,打开kernel/arch/arm64/boot/dts/rockchip/rk3568.dtsi设备树文件,串口9控制器的设备树节点如下所示:

uart9: serial@fe6d0000 {
        compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
        reg = <0x0 0xfe6d0000 0x0 0x100>;
        interrupts = <GIC_SPI 125 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru SCLK_UART9>, <&cru PCLK_UART9>;
        clock-names = "baudclk", "apb_pclk";
        reg-shift = <2>;
        reg-io-width = <4>;
        dmas = <&dmac0 18>, <&dmac0 19>;
        pinctrl-names = "default";
        pinctrl-0 = <&uart9m0_xfer>;
        status = "disabled";
};

重点看一下第 2 行 compatible 属性值为“snps,dw-apb-uart”。在 Linux 源码中搜索这个值即可找到对应的 UART 驱动文件,此文件为 drivers/tty/serial/8250/8250_dw.c,在此文件中可以找到如下内容:

static const struct of_device_id dw8250_of_match[] = {
        { .compatible = "snps,dw-apb-uart" },
        { .compatible = "cavium,octeon-3860-uart" },
        { .compatible = "marvell,armada-38x-uart" },
        { .compatible = "renesas,rzn1-uart" },
        { /* Sentinel */ }
};
......
static struct platform_driver dw8250_platform_driver = {
        .driver = {
                .name                = "dw-apb-uart",
                .pm                = &dw8250_pm_ops,
                .of_match_table        = dw8250_of_match,
                .acpi_match_table = dw8250_acpi_match,
        },
        .probe                        = dw8250_probe,
        .remove                        = dw8250_remove,
};
 
module_platform_driver(dw8250_platform_driver);

可以看出瑞芯微的 UART 本质上是一个 platform 驱动,当节点匹配成功之后,执行dw_probe函数,函数内容如下所示:

static int dw8250_probe(struct platform_device *pdev)
{
        struct uart_8250_port uart = {}; // 初始化一个uart_8250_port结构体
        struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取设备资源信息
        int irq = platform_get_irq(pdev, 0); // 获取中断号
        struct uart_port *p = &uart.port; // 获取uart_port指针
        struct device *dev = &pdev->dev; // 获取设备指针
        struct dw8250_data *data; // 定义dw8250_data结构体指针
        int err;
        u32 val;

        // 检查是否获取到了设备资源信息
        if (!regs) {
                dev_err(dev, "no registers defined\n");
                return -EINVAL;
        }

        // 检查是否获取到了中断号
        if (irq < 0) {
                if (irq != -EPROBE_DEFER)
                        dev_err(dev, "cannot get irq\n");
                return irq;
        }

        spin_lock_init(&p->lock); // 初始化锁
        p->mapbase        = regs->start; // 设置设备的物理地址
        p->irq                = irq; // 设置设备的中断号
        p->handle_irq        = dw8250_handle_irq; // 设置中断处理函数
        p->pm                = dw8250_do_pm; // 设置设备的电源管理函数
        p->type                = PORT_8250; // 设置设备类型
        p->flags        = UPF_SHARE_IRQ | UPF_FIXED_PORT; // 设置设备标志
        p->dev                = dev; // 设置设备指针
        p->iotype        = UPIO_MEM; // 设置IO类型
        p->serial_in        = dw8250_serial_in; // 设置读函数
        p->serial_out        = dw8250_serial_out; // 设置写函数
        p->set_ldisc        = dw8250_set_ldisc; // 设置行规则函数
        p->set_termios        = dw8250_set_termios; // 设置终端参数函数

        // 将设备的物理地址映射到内存空间
        p->membase = devm_ioremap(dev, regs->start, resource_size(regs));
        if (!p->membase)
                return -ENOMEM;

        // 分配dw8250_data结构体内存空间
        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;

        data->dma.fn = dw8250_fallback_dma_filter; // 设置DMA函数指针
        data->usr_reg = DW_UART_USR; // 设置UART状态寄存器地址
#ifdef CONFIG_ARCH_ROCKCHIP
        data->irq        = irq; // 设置中断号
#endif
        p->private_data = data; // 设置设备的私有数据指针

        // 读取设备属性"snps,uart-16550-compatible",判断是否兼容16550
        data->uart_16550_compatible = device_property_read_bool(dev, "snps,uart-16550-compatible");

        // 读取设备属性"reg-shift",获取地址偏移值
        err = device_property_read_u32(dev, "reg-shift", &val);
        if (!err)
                p->regshift = val;

        // 读取设备属性"reg-io-width",获取IO宽度
        err = device_property_read_u32(dev, "reg-io-width", &val);
        if (!err && val == 4) {
                p->iotype = UPIO_MEM32; // 设置IO类型为32位
                p->serial_in = dw8250_serial_in32; // 设置读函数为32位
                p->serial_out = dw8250_serial_out32; // 设置写函数为32位
        }

        // 如果属性"dcd-override"存在,则始终将DCD状态设置为活动状态
        if (device_property_read_bool(dev, "dcd-override")) {
                data->msr_mask_on |= UART_MSR_DCD;
                data->msr_mask_off |= UART_MSR_DDCD;
        }

        // 如果属性"dsr-override"存在,则始终将DSR状态设置为活动状态
        if (device_property_read_bool(dev, "dsr-override")) {
                data->msr_mask_on |= UART_MSR_DSR;
                data->msr_mask_off |= UART_MSR_DDSR;
        }

        // 如果属性"cts-override"存在,则始终将CTS状态设置为活动状态
        if (device_property_read_bool(dev, "cts-override")) {
                data->msr_mask_on |= UART_MSR_CTS;
                data->msr_mask_off |= UART_MSR_DCTS;
        }

        // 如果属性"ri-override"存在,则始终将RI状态设置为非活动状态
        if (device_property_read_bool(dev, "ri-override")) {
                data->msr_mask_off |= UART_MSR_RI;
                data->msr_mask_off |= UART_MSR_TERI;
        }
 
#ifdef CONFIG_ARCH_ROCKCHIP
        // 如果属性"wakeup-source"存在,则使能唤醒功能
        if (device_property_read_bool(p->dev, "wakeup-source"))
                data->enable_wakeup = 1;
        else
                data->enable_wakeup = 0;
#endif

        // 读取属性"clock-frequency",获取时钟频率
        device_property_read_u32(dev, "clock-frequency", &p->uartclk);

        // 如果存在"baudclk"时钟,则从中获取时钟频率
        data->clk = devm_clk_get(dev, "baudclk");
        if (IS_ERR(data->clk) && PTR_ERR(data->clk) != -EPROBE_DEFER)
                data->clk = devm_clk_get(dev, NULL);
        if (IS_ERR(data->clk) && PTR_ERR(data->clk) == -EPROBE_DEFER)
                return -EPROBE_DEFER;
        if (!IS_ERR_OR_NULL(data->clk)) {
                err = clk_prepare_enable(data->clk);
                if (err)
                        dev_warn(dev, "could not enable optional baudclk: %d\n",
                                 err);
                else
                        p->uartclk = clk_get_rate(data->clk);
        }

        // 如果没有定义时钟频率,则失败
        if (!p->uartclk) {
                dev_err(dev, "clock rate not defined\n");
                err = -EINVAL;
                goto err_clk;
        }

        // 获取APB时钟
        data->pclk = devm_clk_get(dev, "apb_pclk");
        if (IS_ERR(data->pclk) && PTR_ERR(data->pclk) == -EPROBE_DEFER) {
                err = -EPROBE_DEFER;
                goto err_clk;
        }
        if (!IS_ERR(data->pclk)) {
                err = clk_prepare_enable(data->pclk);
                if (err) {
                        dev_err(dev, "could not enable apb_pclk\n");
                        goto err_clk;
                }
        }

        // 获取复位控制
        data->rst = devm_reset_control_get_optional_exclusive(dev, NULL);
        if (IS_ERR(data->rst)) {
                err = PTR_ERR(data->rst);
                goto err_pclk;
        }
        reset_control_deassert(data->rst);

        // 应用特定的quirks
        dw8250_quirks(p, data);

        // 如果设备不兼容16550,则不处理忙标志
        if (data->uart_16550_compatible)
                p->handle_irq = NULL;

        // 如果不跳过自动配置,则进行端口配置
        if (!data->skip_autocfg)
                dw8250_setup_port(p);

        // 如果有有效的FIFO大小,则尝试连接DMA
        if (p->fifosize) {
                data->dma.rxconf.src_maxburst = p->fifosize / 4;
                data->dma.txconf.dst_maxburst = p->fifosize / 4;
                uart.dma = &data->dma;
        }

        // 注册8250端口
        data->line = serial8250_register_8250_port(&uart);
        if (data->line < 0) {
                err = data->line;
                goto err_reset;
        }
 
#ifdef CONFIG_ARCH_ROCKCHIP
        // 如果使能唤醒功能,则初始化唤醒
        if (data->enable_wakeup)
                device_init_wakeup(&pdev->dev, true);
#endif

        // 设置平台设备的私有数据
        platform_set_drvdata(pdev, data);

        // 设置电源管理为激活状态
        pm_runtime_set_active(dev);
        pm_runtime_enable(dev);

        return 0;
 
err_reset:
        reset_control_assert(data->rst);
 
err_pclk:
        if (!IS_ERR(data->pclk))
                clk_disable_unprepare(data->pclk);
 
err_clk:
        if (!IS_ERR(data->clk))
                clk_disable_unprepare(data->clk);

        return err;
}
其中170-175行代码使用serial8250_register_8250_port函数注册了8250端口,serial8250_register_8250_port函数内容如下所示:

int serial8250_register_8250_port(struct uart_8250_port *up)
{
        struct uart_8250_port *uart;
        int ret = -ENOSPC;

        if (up->port.uartclk == 0)
                return -EINVAL;

        mutex_lock(&serial_mutex);

        uart = serial8250_find_match_or_unused(&up->port);
        if (uart && uart->port.type != PORT_8250_CIR) 
.............
    serial8250_apply_quirks(uart);
        ret = uart_add_one_port(&serial8250_reg,&uart->port);// 向tty核心层注册一个UART端口
}
其中serial8250_find_match_or_unused函数内容如下所示:

static struct uart_8250_port *serial8250_find_match_or_unused(struct uart_port *port)
{
        int i;

        /*
         * 首先,查找一个匹配的端口条目。
         */
        for (i = 0; i < nr_uarts; i++)
                if (uart_match_port(&serial8250_ports[i].port, port))
                        return &serial8250_ports[i];

        /* 如果还有空闲的端口号,尝试使用 */
        i = port->line;
        if (i < nr_uarts && serial8250_ports[i].port.type == PORT_UNKNOWN &&
                        serial8250_ports[i].port.iobase == 0)
                return &serial8250_ports[i];

        /*
         * 如果没有找到匹配的条目,则查找第一个空闲的条目。
         * 我们搜索一个以前未使用过的条目(iobase为0表示未使用)。
         */
        for (i = 0; i < nr_uarts; i++)
                if (serial8250_ports[i].port.type == PORT_UNKNOWN &&
                    serial8250_ports[i].port.iobase == 0)
                        return &serial8250_ports[i];

        /*
         * 如果仍然没有找到,最后的尝试是找到一个没有实际端口相关联的条目。
         */
        for (i = 0; i < nr_uarts; i++)
                if (serial8250_ports[i].port.type == PORT_UNKNOWN)
                        return &serial8250_ports[i];

        return NULL;
}
其中第15行uart_add_one_port函数向tty核心层注册一个UART端口,uart_add_one_port函数内容如下所示:

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
        struct uart_state *state;
        struct tty_port *port;
        int ret = 0;
        struct device *tty_dev;
        int num_groups;
.........
        tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
                        uport->line, uport->dev, port, uport->tty_groups);
.........
        return ret;
}
tty_port_register_device_attr_serdev函数内容如下所示:

/*
- 注册一个tty设备到tty核心层,如果注册的设备是serdev设备,则不创建cdev。
*/
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
                struct tty_driver *driver, unsigned index,
                struct device *device, void *drvdata,
                const struct attribute_group **attr_grp)
{
        struct device *dev;

        // 将tty端口链接到设备
        tty_port_link_device(port, driver, index);

        // 注册serdev设备
        dev = serdev_tty_port_register(port, device, driver, index);
        if (PTR_ERR(dev) != -ENODEV) {
                /* 如果注册的是serdev设备,则不创建cdev */
                return dev;
        }

        // 如果注册的不是serdev设备,则创建cdev
        return tty_register_device_attr(driver, index, device, drvdata,
                        attr_grp);
}
EXPORT_SYMBOL_GPL(tty_port_register_device_attr_serdev);
该函数用于注册一个tty设备到tty核心层,如果注册的设备是serdev设备,则不创建cdev。首先,它将tty端口链接到设备,然后尝试注册serdev设备。如果注册的是serdev设备,则直接返回注册结果;否则,通过调用 tty_register_device_attr() 函数创建cdev并注册该设备到tty核心层。tty_register_device_attr函数内容如下所示:

/*
- 注册一个tty设备到tty核心层,包括创建cdev,并添加属性组。
*/
struct device *tty_register_device_attr(struct tty_driver *driver,
                                   unsigned index, struct device *device,
                                   void *drvdata,
                                   const struct attribute_group **attr_grp)
{
        char name[64]; // 设备名称缓冲区
        dev_t devt = MKDEV(driver->major, driver->minor_start) + index; // 计算设备号
        struct ktermios *tp;
        struct device *dev;
        int retval;

        // 检查索引是否超出范围
        if (index >= driver->num) {
                pr_err("%s: Attempt to register invalid tty line number (%d)\n",
                       driver->name, index);
                return ERR_PTR(-EINVAL);
        }

        // 根据驱动类型生成设备名称
        if (driver->type == TTY_DRIVER_TYPE_PTY)
                pty_line_name(driver, index, name);
        else
                tty_line_name(driver, index, name);

        // 分配设备结构体内存空间
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev)
                return ERR_PTR(-ENOMEM);

        // 设置设备结构体的各个属性
        dev->devt = devt;
        dev->class = tty_class;
        dev->parent = device;
        dev->release = tty_device_create_release;
        dev_set_name(dev, "%s", name); // 设置设备名称
        dev->groups = attr_grp; // 设置属性组
        dev_set_drvdata(dev, drvdata);

        dev_set_uevent_suppress(dev, 1); // 设置抑制uevent

        // 注册设备到内核
        retval = device_register(dev);
        if (retval)
                goto err_put;

        if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
                /*
                 * 如果驱动不是动态分配的,则释放任何保存的终端参数数据,
                 * 这样当重用一个次设备号时,终端参数状态将被重置。
                 */
                tp = driver->termios[index];
                if (tp) {
                        driver->termios[index] = NULL;
                        kfree(tp);
                }

                // 添加cdev到tty核心层
                retval = tty_cdev_add(driver, devt, index, 1);
                if (retval)
                        goto err_del;
        }

        dev_set_uevent_suppress(dev, 0); // 取消抑制uevent
        kobject_uevent(&dev->kobj, KOBJ_ADD); // 发送uevent通知设备已添加

        return dev;
 
err_del:
        device_del(dev); // 删除设备
err_put:
        put_device(dev); // 释放设备结构体内存空间

        return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(tty_register_device_attr);

该函数用于注册一个tty设备到tty核心层,包括创建cdev,并添加属性组。首先,它会根据驱动类型生成设备名称,并分配设备结构体内存空间,并设置设备的各个属性。然后,它注册设备到内核,并根据驱动是否动态分配来决定是否添加cdev到tty核心层。最后,取消抑制uevent并发送uevent通知设备已添加,并返回设备结构体指针。

 添加cdev到tty核心层使用tty_cdev_add函数,内容如下所示:

/*
- 向tty核心层添加cdev。
*/
static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
                unsigned int index, unsigned int count)
{
        int err;

        /* 在这里初始化,因为重用的cdev会导致崩溃 */
        driver->cdevs[index] = cdev_alloc(); // 分配一个cdev结构体
        if (!driver->cdevs[index]) // 如果分配失败则返回错误
                return -ENOMEM;
        driver->cdevs[index]->ops = &tty_fops; // 设置cdev的操作
        driver->cdevs[index]->owner = driver->owner; // 设置cdev的所有者
        err = cdev_add(driver->cdevs[index], dev, count); // 添加cdev到内核
        if (err)
                kobject_put(&driver->cdevs[index]->kobj); // 添加失败时释放资源
        return err;
}
该函数用于向tty核心层添加cdev。首先,它分配一个cdev结构体,并设置其操作和所有者。然后,它通过调用 cdev_add() 函数将cdev添加到内核中。如果添加失败,则释放已分配的资源。

在上面代码第13行中,tty_fops 是 tty 驱动中的文件操作结构体,它定义了 tty 设备文件的操作函数。这些函数包括了对 tty 设备文件进行打开、关闭、读取、写入、控制等操作的实现。一般来说,这些函数会调用相应的 tty 核心层函数来完成对底层 tty 设备的操作。 tty_fops结构体如下所示:

static const struct file_operations tty_fops = {
        .llseek                = no_llseek,
        .read                = tty_read,
        .write                = tty_write,
        .poll                = tty_poll,
        .unlocked_ioctl        = tty_ioctl,
        .compat_ioctl        = tty_compat_ioctl,
        .open                = tty_open,
        .release        = tty_release,
        .fasync                = tty_fasync,
        .show_fdinfo        = tty_show_fdinfo,
};
当用户空间对 tty 设备文件进行操作时,实际上是在调用对应的 tty_fops 中的操作函数。下面是一个简单的示例,展示了如何在用户空间对 tty 设备文件进行操作

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
 
int main() {
    int fd;
    char buffer[128];

    // 打开 tty 设备文件
    fd = open("/dev/ttyS0", O_RDWR);
    if (fd < 0) {
        perror("Failed to open tty device");
        return -1;
    }

    // 向 tty 设备文件写入数据
    write(fd, "Hello, tty device!", 18);

    // 从 tty 设备文件读取数据
    read(fd, buffer, sizeof(buffer));
    printf("Received data: %s\n", buffer);

    // 关闭 tty 设备文件
    close(fd);

    return 0;
}

4. tiocmget函数详

从代码中可以看到,获取串口状态主要通过 tty_tiocmget 函数实现。让我详细解释这个机制:
tty_tiocmget 函数实现:

static int tty_tiocmget(struct tty_struct *tty, int __user *p)
{
    int retval = -ENOTTY;
    if (tty->ops->tiocmget) {
        // 调用具体驱动的tiocmget操作
        retval = tty->ops->tiocmget(tty);
        if (retval >= 0)
            // 将结果复制到用户空间
            retval = put_user(retval, p);
    }
    return retval;
}
1. 返回的状态位含义:
#define TIOCM_LE    0x001   /* Line Enable */
#define TIOCM_DTR   0x002   /* Data Terminal Ready */
#define TIOCM_RTS   0x004   /* Request to Send */
#define TIOCM_CTS   0x020   /* Clear to Send */
#define TIOCM_CAR   0x040   /* Carrier Detect (DCD) */
#define TIOCM_CD    TIOCM_CAR
#define TIOCM_RNG   0x080   /* Ring Indicator */
#define TIOCM_DSR   0x100   /* Data Set Ready */
使用示例:
// 在用户空间
int status;
if (ioctl(fd, TIOCMGET, &status) == 0) {
    if (status & TIOCM_CAR)
        printf("Carrier detected (DCD on)\n");
    if (status & TIOCM_CTS)
        printf("Clear to send (CTS on)\n");
    if (status & TIOCM_DSR)
        printf("Data set ready (DSR on)\n");
}
在内核空间:
struct tty_struct *tty = ...;
int status;
// 直接调用
status = tty_tiocmget(tty, NULL);
if (status >= 0) {
    if (status & TIOCM_CAR)
        printk("Carrier detected\n");
}
2. 主要的检查项:
DCD (TIOCM_CAR): 数据载波检测,表示物理连接状态
CTS (TIOCM_CTS): 清除发送,表示对方准备好接收数据
DSR (TIOCM_DSR): 数据设备就绪,表示对方设备已准备
RNG (TIOCM_RNG): 振铃指示
DTR (TIOCM_DTR): 数据终端就绪
RTS (TIOCM_RTS): 请求发送
状态获取流程:
用户程序
   ↓
ioctl(TIOCMGET)
   ↓
tty_tiocmget()
   ↓
tty->ops->tiocmget()
   ↓
具体串口驱动实现
错误处理:
if (retval < 0) {
    switch (retval) {
        case -ENOTTY:
            // 设备不支持此操作
            break;
        case -EINVAL:
            // 参数无效
            break;
        default:
            // 其他错误
            break;
    }
}

这种机制的优点是:

  1. 统一的接口
  2. 驱动层可以自定义实现
  3. 支持用户空间访问
  4. 错误处理完善
  5. 线程安全

5.1 使用 ioctl 获取调制解调器信号状态

串口的连接状态通常由调制解调器控制信号(Modem Control Signals)表示,例如 DCD、DSR、CTS、RI 等。可以使用 TIOCMGET 命令通过 ioctl 获取这些信号状态。

#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <sys/ioctl.h>#include <linux/serial.h>#include <termios.h>int main() {
int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);if (fd < 0) {
        perror("Failed to open serial port");return 1;
    }
int status;if (ioctl(fd, TIOCMGET, &status) < 0) {
        perror("ioctl TIOCMGET failed");
        close(fd);return 1;
    }
if (status & TIOCM_CAR) {printf("DCD (Data Carrier Detect) is active\n");
    } else {printf("DCD is not active\n");
    }
if (status & TIOCM_DSR) {printf("DSR (Data Set Ready) is active\n");
    } else {printf("DSR is not active\n");
    }
if (status & TIOCM_CTS) {printf("CTS (Clear To Send) is active\n");
    } else {printf("CTS is not active\n");
    }
if (status & TIOCM_RNG) {printf("RI (Ring Indicator) is active\n");
    } else {printf("RI is not active\n");
    }
    close(fd);return 0;
}

关键点

  • 信号含义:
    • TIOCM_CAR(DCD):数据载波检测,表示物理连接是否存在。
    • TIOCM_DSR(DSR):设备是否已准备好。
    • TIOCM_CTS(CTS):远程设备是否允许发送数据。
    • TIOCM_RNG(RI):振铃指示,通常用于电话线调制解调器。
  • 用法:
    • 如果 TIOCM_CAR 和 TIOCM_DSR 为活动状态,则通常表示串口设备已经连接。

2. 使用串口的 serial_icounter_struct 结构体

可以通过 TIOCGICOUNT 命令获取串口的统计信息,包括调制解调器信号的变化计数。这对于监控串口连接状态的变化非常有用。

#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <sys/ioctl.h>#include <linux/serial.h>int main() {
int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);if (fd < 0) {
        perror("Failed to open serial port");return 1;
}
struct serial_icounter_struct icount;if (ioctl(fd, TIOCGICOUNT, &icount) < 0) {
        perror("ioctl TIOCGICOUNT failed");
        close(fd);return 1;
}
printf("DCD changes: %d\n", icount.dcd);printf("DSR changes: %d\n", icount.dsr);
printf("CTS changes: %d\n", icount.cts);printf("RI changes: %d\n", icount.rng);
    close(fd);return 0;
}

关键点

  • 字段说明:
    • icount.dcd:DCD 状态改变的次数。
    • icount.dsr:DSR 状态改变的次数。
    • icount.cts:CTS 状态改变的次数。
    • icount.rng:RI 状态改变的次数。
  • 用途:
    • 如果这些计数器的值不断变化,可以推测串口连接或信号发生了变动。
      https://github.com/Fazecast/jSerialComm
      https://github.com/nyholku/purejavacomm
      一些 Java 串口库(如 jSerialComm、RxTx 或 purejavacomm)可以简化串口操作,但均不适用于安卓

6 无法获取串口连接状态和原因分析

上述设备树中对 UART9(串口设备)的描述,主要包括了以下信息:

compatible:指定设备的兼容性字符串,表示此串口设备兼容于 rockchip,rk3568-uart 和 snps,dw-apb-uart 两种串口控制器。这有助于设备树绑定相应的驱动程序。
reg:指定串口设备的地址和大小,0xfe6d0000 是串口设备的基地址,0x100 表示地址空间的大小。
interrupts:指定串口设备的中断信息,包括中断类型和中断号。
clocks:指定串口设备所使用的时钟源,包括波特率时钟和 APB 时钟。
clock-names:指定时钟源的名称,用于与时钟源的具体配置相匹配。
reg-shift:表示地址偏移量的位数,即每个寄存器的偏移量是按字节还是按字(byte)。
reg-io-width:表示对设备地址和数据的访问宽度,这里是 4 表示 4 字节宽度。
dmas:指定串口设备使用的 DMA 控制器以及 DMA 通道号,用于数据传输的 DMA 操作。
dma-names:可选值为“tx”“rx”“!tx”“!rx”,“tx”打开tx dma、“rx”打开rx dma

仅仅只有rx表示收到数据
而缺省下都是高电平
主要是缺乏串口状态反馈机制
串口通信中,状态信息(如设备是否断开、信号是否丢失)需要通过硬件引脚(如CTS/RTS信号)或协议实现。

7. sercd库源码分析

7.1 select的优势
7.2 转发的流程

;