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;
}
}
这种机制的优点是:
- 统一的接口
- 驱动层可以自定义实现
- 支持用户空间访问
- 错误处理完善
- 线程安全
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 转发的流程