当前位置: 首页 > news >正文

Linux中tty与8250-uart的虐恋(包括双中断发送接收机制)

串口通用驱动文件在哪里?

drivers/tty/serial/

哪一个是正确的compatible

  • arch/arm64/boot/dts/rockchip/rk3568.dtsi
uart3: serial@fe670000 {compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";reg = <0x0 0xfe670000 0x0 0x100>;interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;clocks = <&cru SCLK_UART3>, <&cru PCLK_UART3>;clock-names = "baudclk", "apb_pclk";reg-shift = <2>;reg-io-width = <4>;dmas = <&dmac0 6>, <&dmac0 7>;pinctrl-names = "default";pinctrl-0 = <&uart3m0_xfer>;status = "disabled";
};
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 */ }
};
MODULE_DEVICE_TABLE(of, dw8250_of_match);
  • dw8250_platform_driver 属于platform_driver,相当于drv->probe
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)

dw8250_probe

  • uart_add_one_port 把一个 已经初始化好的 uart_port 结构体 绑定到 已经注册好的 uart_driver 上,从而让它成为 Linux 内核可见的一个串口设备(/dev/ttySx/dev/ttyAMA0 等)
dw8250_probeuart_8250_port uartresource *regs = platform_get_resource//从设备树中获取 8250 控制器寄存器物理基地址uart_port *p = &uart.portdw8250_data *datap->handle_irq  = dw8250_handle_irqp->serial_in    = dw8250_serial_in;p->serial_out   = dw8250_serial_out;p->membase = devm_ioremap(dev, regs->start, resource_size(regs))//内存映射得在Linux用虚拟地址device_property_read_u32device_property_read_booldevm_clk_getdw8250_quirks(p, data)//特定平台配置和兼容性问题of_alias_get_id(np, "serial")p->serial_inp->serial_outdata->line = serial8250_register_8250_port(&uart)uart_add_one_portplatform_set_drvdata(pdev, data)

serial8250_init

  • uart_register_driver 先注册 driver
  • uart_register_driver “先填申报户口资料”,uart_add_one_port 是“依照资料给每块 UART 上户口”;只有上完户口,内核里才会出现对应的 /dev/tty* 设备
  • tty_register_driver创建 tty 设备节点(/dev/ttyS0ttyS1 …)
module_init(serial8250_init)serial8250_isa_init_ports//实现请求端口、添加和注册串口端口serial8250_init_portuart_8250_port port->ops = &serial8250_popsuart_register_driver(&serial8250_reg)//uart_driver 转 tty_driver 注册ttynormal = alloc_tty_driver(drv->nr)tty_set_operations(normal, &uart_ops)port->ops = &uart_port_ops//tty_port *port = &uart_state->porttty_register_driver(normal)put_tty_driver(normal)serial8250_isa_devs = platform_device_alloc("serial8250",PLAT8250_DEV_LEGACY)platform_device_add(serial8250_isa_devs)serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);platform_driver_register(&serial8250_isa_driver)
uart_driverstruct uart_state	*state;struct tty_port        portstruct uart_port   *uart_portstruct tty_driver	*tty_driver;
static struct uart_driver serial8250_reg = {.owner			= THIS_MODULE,.driver_name		= "serial",.dev_name		= "ttyS",.major			= TTY_MAJOR,.minor			= 64,.cons			= SERIAL8250_CONSOLE,
};
static const struct tty_operations uart_ops = {.install	= uart_install,.open		= uart_open,.close		= uart_close,.write		= uart_write,.put_char	= uart_put_char,.flush_chars	= uart_flush_chars,.write_room	= uart_write_room,.chars_in_buffer= uart_chars_in_buffer,.flush_buffer	= uart_flush_buffer,.ioctl		= uart_ioctl,.throttle	= uart_throttle,.unthrottle	= uart_unthrottle,.send_xchar	= uart_send_xchar,.set_termios	= uart_set_termios,.set_ldisc	= uart_set_ldisc,.stop		= uart_stop,.start		= uart_start,.hangup		= uart_hangup,.break_ctl	= uart_break_ctl,.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS.proc_show	= uart_proc_show,
#endif.tiocmget	= uart_tiocmget,.tiocmset	= uart_tiocmset,.get_icount	= uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL.poll_init	= uart_poll_init,.poll_get_char	= uart_poll_get_char,.poll_put_char	= uart_poll_put_char,
#endif
};
static const struct tty_port_operations uart_port_ops = {.carrier_raised = uart_carrier_raised,.dtr_rts	= uart_dtr_rts,.activate	= uart_port_activate,.shutdown	= uart_tty_port_shutdown,
};

uart_ops serial8250_popsuart_port 什么区别

uart_8250_portuart_8250_dma *dmauart_8250_ops *opsuart_port port(*serial_in)(*serial_out)(*set_termios)(*set_ldisc)(*handle_irq)uart_state  *statetty_port   portuart_port   *uart_portuart_ops *ops(*startup)(*start_tx)(*stop_tx)(*stop_rx)(*ioctl)
static const struct uart_ops serial8250_pops = {.tx_empty	= serial8250_tx_empty,.set_mctrl	= serial8250_set_mctrl,.get_mctrl	= serial8250_get_mctrl,.stop_tx	= serial8250_stop_tx,.start_tx	= serial8250_start_tx,.throttle	= serial8250_throttle,.unthrottle	= serial8250_unthrottle,.stop_rx	= serial8250_stop_rx,.enable_ms	= serial8250_enable_ms,.break_ctl	= serial8250_break_ctl,.startup	= serial8250_startup,.shutdown	= serial8250_shutdown,.set_termios	= serial8250_set_termios,.set_ldisc	= serial8250_set_ldisc,.pm		= serial8250_pm,.type		= serial8250_type,.release_port	= serial8250_release_port,.request_port	= serial8250_request_port,.config_port	= serial8250_config_port,.verify_port	= serial8250_verify_port,
#ifdef CONFIG_CONSOLE_POLL.poll_get_char = serial8250_get_poll_char,.poll_put_char = serial8250_put_poll_char,
#endif
};
  • univ8250_setup_irq硬件中断号都没有(port->irq == 0,常见于早期板级代码或虚拟端口),则完全采用 纯轮询
  • 如果硬件中断号有效,就调用 serial_link_irq_chain() 把普通中断处理链挂到该 IRQ 上,包括注册 serial8250_interrupt()
static const struct uart_8250_ops univ8250_driver_ops = {.setup_irq	= univ8250_setup_irq,.release_irq	= univ8250_release_irq,
};

serial_in serial_out 读取发送 如何 实现的?

  • ALTERNATIVE 是内核提供的一个宏,利用 ELF .altinstructions 段 机制
  • 在内核启动早期 会扫描这个段,根据 ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE 的布尔值,把 ldrb 原地 patchldarb,或者在不需要时保持 ldrb,保证 读操作之前的所有读写都不能重排到它之后,用于解决 Device-nGRE/Device-nGnRE 映射上可能出现的加载乱序或重试 bug
serial_in(struct uart_8250_port *up, int offset)up->port.serial_in(&up->port, offset);dw8250_serial_inreadb(p->membase + (offset << p->regshift))readb_relaxedasm volatile(ALTERNATIVE("ldrb %w0, [%1]","ldarb %w0,[%1]",ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE): "=r" (val) : "r" (addr));serial_out(struct uart_8250_port *up, int offset, int value)up->port.serial_out(&up->port, offset, value);dw8250_serial_outwriteb(value, p->membase + (offset << p->regshift))writeb_relaxedasm volatile("strb %w0, [%1]" : : "rZ" (val), "r" (addr))

dw8250_handle_irq -> serial8250_handle_irq

  • serial8250_handle_irq 真正处理函数
univ8250_setup_irqrequest_irqserial8250_interruptp->handle_irq  = dw8250_handle_irqdw8250_handle_irqserial8250_handle_irqif (iir & UART_IIR_NO_INT) //  把假中断挡在门外serial_port_in(port, UART_LSR) //一次读出所有线路/错误状态位skip_rx = true //自动 RTS/CTS 硬件流控把 RX 堵住//只要有数据 (`DR`) 或 Break (`BI`) 并且未被上一阶段跳过,就处理接收dma_err = handle_rx_dma(up, iir) //先尝试 DMA 接收 status = serial8250_rx_chars(up, status) //DMA 失败或无 DMA落到 PIO 接收serial8250_modem_status(up)//读取 `UART_MSR` 寄存器,检测 CTS/DSR/RI/DCD 变化  //CTS 变高/低、载波丢失等事件上报 TTYserial8250_tx_chars(up)//serial8250_tx_chars把 TTY 环形缓冲区里的字节写入UART TXFIFOpr_err //现 Overrun/Parity/Frame/Break 错误,就打印一行提示

UART_LSR 是什么寄存器?

  • Line Status Register(线路状态寄存器)一次读即可得到发送/接收单元当前状态
#define UART_LSR	5	/* In:  Line Status Register */
bit7  UART_LSR_FIFOE  FIFO 数据错误
bit6  UART_LSR_TEMT   发送移位器+发送保持器都空
bit5  UART_LSR_THRE   发送保持器 空(可继续写下一个字节)
bit4  UART_LSR_BI     Break 中断
bit3  UART_LSR_FE     帧错误
bit2  UART_LSR_PE     奇偶错误
bit1  UART_LSR_OE     Overrun 错误
bit0  UART_LSR_DR     接收数据就绪

up->dma->txchan 是什么?

  • 发送 DMA 通道:由 dma_request_slave_channel_compat()DMA Engine 子系统 申请得到
  • 不为 NULL → 用 DMA 搬数据到 UART
  • NULL → 退回到传统 PIOCPU 轮询)方式发送

UART_IIR_THRI 是什么?

  • UART_IIR_THRI = “Transmit Holding Register Empty Interrupt”
  • 当发送 FIFO(或保持寄存器)里的最后一个字节被移位器取走后,硬件置位 THREUART_LSR bit5),并同时把 UART_IIR_THRI 填进 IIR,表示“你可以继续写下一个字节了”

为什么 p->handle_irq dw8250_handle_irq 要兼顾 DesignWare UART “假超时”和“BUSY”处理?什么是 DesignWare UART ?

  • DesignWare UART(官方文档中称为 DW_apb_uart)是 Synopsys公司推出的、基于 AMBA APB 总线的可综合 IP 核,目标是给 SoC 提供一套 兼容传统 16550/8250 的通用异步串行收发器(UART
  • dw8250_handle_irq 复用了 8250 核心逻辑,又补上了芯片特有的“假超时”和“BUSY”处理
阶段作用
读取 IIR获知当前中断类型
DesignWare UART RX TIMEHO​UT 假中断检查USR/LSR/RFL,必要时空读 UART_RX 清假超时
标准中断交给 serial8250_handle_irq()
DesignWare UART BUSY 中断USRBUSY状态
结束返回已处理标志

自动 RTS/CTS 流控是什么?

自动 RTS/CTS 流控 = 硬件级双向握手,用两根线 RTS(Request To Send) CTS(Clear To Send)自动启停对方的发送,防止 FIFO 溢出。

  • RTS 由 本地 UART 输出:
    “我准备好接收了,你可以发”。
  • CTS 由 对方 UART 输出:
    “我准备好了,你可以发”。

工作方式(全双工场景):

  • 本地接收侧

    • 当本地 RX FIFO 快满(可编程阈值)时,UART 自动把 RTS 拉高 → 对方看到 RTS=1 就 暂停发送
    • FIFO 被读空后,RTS 自动拉低 → 对方继续发。
  • 本地发送侧

    • 只有检测到 CTS=0(对方准备好)时,UART 才会把 TX FIFO 里的数据真正发出;
    • CTS=1 则硬件暂停发送

dma初始化位置在哪里?两个函数是什么?

serial8250_startupserial8250_do_startupserial8250_request_dmadma->txchan = dma_request_slave_channel_compatdma->tx_addr = dma_map_single
serial8250_initserial8250_isa_init_portsserial8250_set_defaultsup->dma->tx_dma = serial8250_tx_dmaup->dma->rx_dma = serial8250_rx_dma
  • serial8250_tx_dma
struct uart_8250_dma *dma = p->dma;                // 拿到 DMA 私有结构
struct circ_buf *xmit = &p->port.state->xmit;      // 指向 TTY 层的发送环形缓冲区
struct dma_async_tx_descriptor *desc;               // DMA 描述符
int ret;                                            // 返回值if (dma->tx_running)                                // 如果上一次 DMA 还没完成,直接退出return 0;if (uart_tx_stopped(&p->port) || uart_circ_empty(xmit)) {   // 上层已暂停或缓冲区为空serial8250_rpm_put_tx(p);                       // 发送端置空闲,降低功耗return 0;
}dma->tx_size = CIRC_CNT_TO_END(xmit->head, xmit->tail, UART_XMIT_SIZE);  // 计算本次可搬的字节数
#ifdef CONFIG_ARCH_ROCKCHIP
if (dma->tx_size < MAX_TX_BYTES) {                  // Rockchip:字节数太少就别 DMA,用 PIOret = -EBUSY;goto err;
}
#endifdesc = dmaengine_prep_slave_single(                 // 构造 DMA 描述符dma->txchan,                                // 发送通道dma->tx_addr + xmit->tail,                  // 源地址:环形缓冲区尾指针dma->tx_size,                               // 长度DMA_MEM_TO_DEV,                             // 方向:内存 -> 设备DMA_PREP_INTERRUPT | DMA_CTRL_ACK);         // 完成后中断 + 立即应答
if (!desc) {                                        // 描述符申请失败ret = -EBUSY;goto err;
}dma->tx_running = 1;                                // 置标志:DMA 正在跑
desc->callback = __dma_tx_complete;                 // DMA 完成回调
desc->callback_param = p;                           // 回调参数:uart_8250_port 指针dma->tx_cookie = dmaengine_submit(desc);            // 把描述符提交给 DMA 引擎dma_sync_single_for_device(                         // 刷新 cache,确保 DMA 看到最新数据dma->txchan->device->dev,dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE);dma_async_issue_pending(dma->txchan);               // 真正启动 DMA 传输if (dma->tx_err) {                                  // 如果之前出过错dma->tx_err = 0;                                // 清错误if (p->ier & UART_IER_THRI) {                   // 关 THRE 中断,避免 PIO 与 DMA 冲突p->ier &= ~UART_IER_THRI;
#ifdef CONFIG_ARCH_ROCKCHIPp->ier &= ~UART_IER_PTIME;
#endifserial_out(p, UART_IER, p->ier);}
}
return 0;                                           // 成功启动 DMAerr:
dma->tx_err = 1;                                    // 记录错误,下次退到 PIO
return ret;
  • serial8250_rx_dma
unsigned int rfl, i = 0, fcr = 0, cur_index = 0;    // 局部变量
unsigned char buf[MAX_FIFO_SIZE];                   // 临时缓存,放本次接收字节
struct uart_port *port = &p->port;                  // 方便引用
struct tty_port *tty_port = &p->port.state->port;   // TTY 端口
struct dma_tx_state state;                          // DMA 状态结构
struct uart_8250_dma *dma = p->dma;                 // DMA 私有结构fcr = UART_FCR_ENABLE_FIFO | UART_FCR_T_TRIG_10 | UART_FCR_R_TRIG_11; // 开 FIFO,设高触发
serial_port_out(port, UART_FCR, fcr);               // 立即写到硬件do {                                                // 轮询直到 DMA 余量是整 burst 倍数dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state);cur_index = dma->rx_size - state.residue;       // 已搬运字节
} while (cur_index % dma->rxconf.src_maxburst);     // 对齐到 burst 大小rfl = serial_port_in(port, UART_RFL_16550A);        // 读出 FIFO 当前字节数
while (i < rfl)                                     // 把 FIFO 里剩余字节全部读出buf[i++] = serial_port_in(port, UART_RX);__dma_rx_complete(p);                               // 复位 DMA 描述符,准备下一轮tty_insert_flip_string(tty_port, buf, i);           // 把 i 个字节塞进 TTY flip buffer
p->port.icount.rx += i;                             // 统计接收字节
tty_flip_buffer_push(tty_port);                     // 通知上层“有数据可读”if (fcr)                                            // 恢复原始 FCR 设置serial_port_out(port, UART_FCR, p->fcr);
return 0;

为什么叫“8250”驱动?

  • 历史原因:Intel1981 年推出了 NMOS 8250 UART 芯片,用来给早期 IBM PC/XT 提供串口 COM1/COM2
  • 后续 1645016550A1675016850 等芯片都把 寄存器布局 做成了 8250 的超集,于是 Linux 就把支持这一系列芯片的驱动统称为 8250 驱动

tty8250驱动是什么关系?

  • 用户空间对 tty 设备文件进行操作时,实际上是在调用对应的 tty_fops 中的操作函数
应用层│├── read()/write()/ioctl()│
tty 层(tty_io.c, n_tty.c, tty_port.c)│   ① 负责线路规程、回显、缓冲、termios├── tty_struct├── tty_port└── tty_ldisc│
serial_core(serial_core.c)│   ② 把“tty 端口”与“uart_driver”粘合在一起└── uart_driver / uart_port│
8250 驱动族(8250_core.c, 8250_port.c, 8250_dw.c …)│   ③ 真正把字节写进 UART 寄存器└── struct uart_ops│
硬件 UART(16550, DesignWare, QCOM …)

tty 如何调用 8250

open("/dev/ttyS0",)tty_open()tty_port_open(struct tty_port *port, struct tty_struct *tty,struct file *filp)port->ops->activateuart_port_activateuart_startupretval = uport->ops->startup(uport);uart_state->uart_port->ops->startupuart_ops.startupserial8250_startup

cat /proc/interrupts

![[Pasted image 20250814173636.png]]

cat /proc/tty/drivers

![[Pasted image 20250814145902.png]]

cat /proc/tty/ldiscs

  • 行规程

![[Pasted image 20250814152218.png]]

有了early boot of_platform_default_populate_init,为什么serial8250_init需要platform_device_add?

  • 因为serial8250_isa_devs 并不是“从设备树里解析出来的普通串口”,而是 8250/16550 驱动为了兼容 ISA/Legacy UART 专门创建的一个“虚拟”平台设备

  • 给“非设备树、非 ACPI”的老式串口一个统一的 platform_device 句柄

  • 那些固定地址(0x3F8、0x2F8…)的 ISA UART 在设备树里往往根本没有节点,或者节点被禁用

  • 驱动必须手动把它们包装成一个 platform_device,才能继续沿用 platform_driverprobe 流程

  • 驱动注册时如果找不到任何 device 就会无事可做;因此驱动自己先 platform_device_alloc() / platform_device_add() 创建一个名字为 "serial8250"、ID 为 PLAT8250_DEV_LEGACY 的设备,确保 serial8250_isa_driver 能匹配到它并进入 probe

platform_device_add调用 bus 匹配,drv 进行 probe

  • serial8250_initplatform_device_add()device_add()bus_probe_device()__device_attach()bus_for_each_drv()__device_attach_driver()driver_match_device()bus_type->match()
  • __device_attach_driver()driver_probe_device -> really_probe -> drv->probe
__device_attach_driverdriver_match_devicebus_type->match()driver_probe_devicedrv->probe
  • 参考文档43
    module_platform_driver 与 module_init_module platform driver-CSDN博客

  • 参考文档44
    uart驱动学习-CSDN博客

  • 参考45
    深入浅出UART驱动开发与调试:从基础调试到虚拟驱动实现-CSDN博客

http://www.lryc.cn/news/621241.html

相关文章:

  • 前端包管理工具
  • hive加载csv中字段含有换行符的处理方法
  • Spring-cloud-openfeign-设置超时时间
  • 数据结构:用两个栈模拟队列(Queue Using 2 Stacks)
  • 8.14网络编程——TCP通信基础
  • 【22-决策树】
  • 零基础-动手学深度学习-10.3. 注意力评分函数
  • 20道CSS相关前端面试题及答案
  • torch.nn中Sequential的使用
  • 【代码随想录day 20】 力扣 538.把二叉搜索树转换为累加树
  • CMake语法与Bash语法的区别
  • 扩展用例-失败的嵌套
  • 流式数据服务端怎么传给前端,前端怎么接收?
  • jenkins在windows配置sshpass
  • 设计模式笔记_行为型_状态模式
  • 【JavaEE】多线程 -- 线程状态
  • 纸箱拆垛:物流自动化中的“开箱密码”与3D视觉的智能革命
  • 面试题之项目中灰度发布是怎么做的
  • Flink on YARN启动全流程深度解析
  • 会议通信系统核心流程详解(底稿1)
  • Linux软件编程:进程和线程
  • C#面试题及详细答案120道(01-10)-- 基础语法与数据类型
  • Flink Stream API 源码走读 - socketTextStream
  • 2025H1手游市场:SLG领涨、休闲爆发,何为出海新航道?
  • 广告灯的左移右移
  • Day43 复习日
  • FPGA+护理:跨学科发展的探索(五)
  • Kotlin Data Classes 快速上手
  • 【深度学习】深度学习基础概念与初识PyTorch
  • 报数游戏(我将每文更新tips)