IMX 平台UART驱动情景分析:write篇--从 TTY 层到硬件驱动的写操作流程解析
往期内容
本专栏往期内容:Uart子系统
- UART串口硬件介绍
- 深入理解TTY体系:设备节点与驱动程序框架详解
- Linux串口应用编程:从UART到GPS模块及字符设备驱动
- 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
- IMX 平台UART驱动情景分析:注册篇
- IMX 平台UART驱动情景分析:open篇
- IMX 平台UART驱动情景分析:read篇–从硬件驱动到行规程的全链路剖析
interrupt子系统专栏:
- 专栏地址:interrupt子系统
- Linux 链式与层级中断控制器讲解:原理与驱动开发
– 末片,有专栏内容观看顺序pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有专栏内容观看顺序

目录
- 往期内容
- 1.内核代码
- 1.write过程分析
- 1.1 流程
- 1.2 总框图
- 1.3 代码详情分析
- 1.3.1 tty层
- 1.3.2 核心层
- 1.3.3 硬件层
- 2.硬件相关的发送
1.内核代码
硬件相关:
-
drivers/tty/serial/imx.c📎imx.c — imx系列的
-
drivers/tty/serial/stm32-usart.c📎stm32-usart.c — stm32系列的
串口核心层:
- drivers/tty/serial/serial_core.c📎serial_core.c
TTY层:
- drivers/tty/tty_io.c📎tty_io.c
本文深入剖析了 Linux 串口子系统中的数据写入过程,重点涵盖 TTY 层、行规程、核心层及硬件驱动层的协作机制。通过对 tty_write、do_tty_write 等关键函数的详细代码解析,逐步追踪数据从用户空间到硬件层的传递路径。文章还探讨了数据写入的分块机制、线程安全处理以及中断与 DMA 方式的硬件发送逻辑。
看之前建议看一下之前关于UART驱动相关结构体的介绍文章:解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
对于TTY体系不了解的可以看:深入理解TTY体系:设备节点与驱动程序框架详解
1.write过程分析
1.1 流程
流程为:
-
APP写
- 使用行规程来写
- 数据最终存入uart_state->xmit的buffer里
-
硬件发送:怎么发送数据?
- 使用硬件驱动中uart_ops->start_tx开始发送
- 具体的发送方法有2种:通过DMA,或通过中断
-
中断方式
- 方法1:直接使能 tx empty中断,一开始tx buffer为空,在中断里填入数据
- 方法2:写部分数据到tx fifo,使能中断,剩下的数据再中断里继续发送
1.2 总框图

1.3 代码详情分析
1.3.1 tty层
\Linux-4.9.88\drivers\tty\tty_io.c
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,
};
和read差不多,直接锁定file_operations中的write函数,也就是tty_write:
\Linux-4.9.88\drivers\tty\tty_io.c
/*** tty_write - tty 设备文件的写入方法* @file: 指向 tty 文件的指针* @buf: 要写入的用户数据缓冲区* @count: 要写入的数据字节数* @ppos: 未使用的文件偏移指针** 通过行规程(line discipline)将数据写入 tty 设备。** 锁机制:* - 根据需要对行规程加锁。* - 通过 atomic_write_lock 使写入 tty 驱动的操作串行化。* - 每个设备的行规程写入方法不会并行调用,写入操作被分块处理。*/static ssize_t tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
{struct tty_struct *tty = file_tty(file); // 从文件结构中获取 tty 结构struct tty_ldisc *ld; // tty 的行规程结构指针ssize_t ret; // 返回值:表示写入的字节数或错误码// 检查 tty 的有效性和可能的错误if (tty_paranoia_check(tty, file_inode(file), "tty_write"))return -EIO; // 如果检测到错误,返回 -EIO// 如果 tty 无效、无写入操作或存在 I/O 错误,返回 -EIOif (!tty || !tty->ops->write || tty_io_error(tty))return -EIO;// 临时调试信息:检查是否定义了 write_room 方法if (tty->ops->write_room == NULL)tty_err(tty, "missing write_room method\n");// 获取 tty 的行规程(line discipline)引用ld = tty_ldisc_ref_wait(tty);if (!ld)return hung_up_tty_write(file, buf, count, ppos); // 如果行规程不可用,处理挂起情况// 如果行规程未定义写入方法,返回 -EIO;否则调用写入操作if (!ld->ops->write)ret = -EIO;elseret = do_tty_write(ld->ops->write, tty, file, buf, count);// 释放行规程引用tty_ldisc_deref(ld);return ret; // 返回写入的字节数或错误码
}
其中对行规进行写入操作就是do_tty_write(ld->ops->write, tty, file, buf, count)函数,检查行规程的 write 方法是否存在。如果存在,则调用 do_tty_write 函数执行写入操作,成功时返回写入的字节数,否则返回 -EIO。
其中又涉及到了struct tty_ldisc ld->ops->write,在read也有提到过,就是下下面的n_tty_write函数。
\Linux-4.9.88\drivers\tty\n_tty.c
static struct tty_ldisc_ops n_tty_ops = {.magic = TTY_LDISC_MAGIC,.name = "n_tty",.open = n_tty_open,.close = n_tty_close,.flush_buffer = n_tty_flush_buffer,.read = n_tty_read,.write = n_tty_write,.ioctl = n_tty_ioctl,.set_termios = n_tty_set_termios,.poll = n_tty_poll,.receive_buf = n_tty_receive_buf,.write_wakeup = n_tty_write_wakeup,.receive_buf2 = n_tty_receive_buf2,
};
暂时不进入n_tty_write去看其实现,先继续进入do_tty_write函数:
\Linux-4.9.88\drivers\tty\tty_io.c
/** 将写操作拆分成合适的块大小,以避免* 拒绝服务(denial-of-service)类型的攻击*/
static inline ssize_t do_tty_write(ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),struct tty_struct *tty,struct file *file,const char __user *buf,size_t count)
{ssize_t ret, written = 0; // ret 存储返回值,written 存储已写入的字节数unsigned int chunk; // 当前写入块的大小// 锁定 tty 以保证线程安全,获取写锁ret = tty_write_lock(tty, file->f_flags & O_NDELAY);if (ret < 0)return ret; // 如果获取锁失败,返回错误码/** 将写操作分成临时缓冲区。这* 简化了低级驱动的操作,因为它们* 不再需要处理锁问题和用户模式访问。** 但如果设置了 TTY_NO_WRITE_SPLIT,则应使用* 更大的块大小。** 默认块大小为 2kB,因为 NTTY* 层对更大块有问题。它会声称能够处理* 更多字符,但实际上不能。** FIXME:这个限制可能可以去掉,但 64K 的块* 仍然可能会失败,除非切换到 vmalloc...*/chunk = 2048; // 默认块大小为 2KBif (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))chunk = 65536; // 如果设置了标志,块大小为 64KBif (count < chunk)chunk = count; // 确保块大小不超过要写入的字节数// write_buf/write_cnt 受 atomic_write_lock 互斥锁保护if (tty->write_cnt < chunk) {unsigned char *buf_chunk;if (chunk < 1024)chunk = 1024; // 确保块大小至少为 1KBbuf_chunk = kmalloc(chunk, GFP_KERNEL); // 分配临时缓冲区if (!buf_chunk) {ret = -ENOMEM; // 如果内存分配失败,返回 -ENOMEMgoto out; // 跳转到清理代码}kfree(tty->write_buf); // 释放旧的缓冲区tty->write_cnt = chunk; // 更新当前写入块大小tty->write_buf = buf_chunk; // 设置新的写入缓冲区}// 开始写入操作for (;;) {size_t size = count; // 当前要写入的大小if (size > chunk)size = chunk; // 确保不超过块大小ret = -EFAULT; // 默认错误码为 EFAULTif (copy_from_user(tty->write_buf, buf, size)) // 从用户空间复制数据到缓冲区break; // 如果失败,跳出循环ret = write(tty, file, tty->write_buf, size); // 调用写入函数if (ret <= 0) // 如果写入失败或没有写入字节,跳出循环break;written += ret; // 累加已写入字节数buf += ret; // 更新用户缓冲区指针count -= ret; // 更新剩余字节数if (!count) // 如果没有剩余字节,结束写入break;ret = -ERESTARTSYS; // 设置重启错误if (signal_pending(current)) // 如果有信号挂起,跳出循环break;cond_resched(); // 允许调度其他任务}if (written) {tty_update_time(&file_inode(file)->i_mtime); // 更新文件的最后修改时间ret = written; // 设置返回值为已写入字节数}out:tty_write_unlock(tty); // 解锁 ttyreturn ret; // 返回结果
}
do_tty_write 函数负责将用户数据写入 tty 设备。为了避免拒绝服务(DoS)攻击,该函数将写操作拆分成适当大小的块。这种分块机制可以防止过大的写入请求消耗过多资源。 其中调用了 ret = write(tty, file, tty->write_buf, size)写入操作,这个write函数就是传进来的ld->ops->write,也就是n_tty_write:
\Linux-4.9.88\drivers\tty\n_tty.c
/*** n_tty_write - tty 设备的写函数* @tty: tty 设备* @file: 文件对象* @buf: 用户空间缓冲区指针* @nr: I/O 的大小** 终端设备的写函数。此函数与其他写调用是串行化的,* 但不与 termios 变化、读取和其他事件串行化。* 由于接收代码会回显字符,从而调用驱动的写方法,* 因此在此处调用的输出处理函数以及回显处理函数中使用 output_lock 来保护列状态和缓冲区剩余空间。** 这段代码必须确保在挂起时绝对不会进入睡眠状态。** 锁定: output_lock 保护列状态和剩余空间* (注意,process_output*() 函数本身会获取此锁)*/static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,const unsigned char *buf, size_t nr)
{const unsigned char *b = buf; // 将用户缓冲区指针赋值给 bDEFINE_WAIT_FUNC(wait, woken_wake_function); // 定义等待队列int c; // 当前字符ssize_t retval = 0; // 返回值初始化/* 作业控制检查 -- 必须在开始时进行 (POSIX.1 7.1.1.4). */if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {retval = tty_check_change(tty); // 检查 tty 状态变化if (retval)return retval; // 如果检查失败,返回错误}down_read(&tty->termios_rwsem); // 获取读取锁,保护 termios 数据结构/* 写出任何尚未回显的字符 */process_echoes(tty); // 处理待回显的字符add_wait_queue(&tty->write_wait, &wait); // 将当前进程添加到写等待队列while (1) {if (signal_pending(current)) { // 检查当前进程是否有挂起信号retval = -ERESTARTSYS; // 设置重启错误break; // 跳出循环}if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {retval = -EIO; // 如果 tty 设备挂起或无有效链接,返回 I/O 错误break;}if (O_OPOST(tty)) { // 检查是否启用了输出处理while (nr > 0) {ssize_t num = process_output_block(tty, b, nr); // 处理输出块if (num < 0) {if (num == -EAGAIN)break; // 如果返回值是 EAGAIN,跳出循环retval = num; // 记录错误返回值goto break_out; // 跳转到退出处理}b += num; // 更新缓冲区指针nr -= num; // 减少待写入的字节数if (nr == 0)break; // 如果没有剩余字节,结束循环c = *b; // 获取当前字符if (process_output(c, tty) < 0) // 处理当前字符输出break; // 如果处理出错,跳出循环b++; nr--; // 更新缓冲区指针和剩余字节数}if (tty->ops->flush_chars) // 如果定义了字符冲刷函数tty->ops->flush_chars(tty); // 调用冲刷函数} else {struct n_tty_data *ldata = tty->disc_data; // 获取 n_tty 数据while (nr > 0) {mutex_lock(&ldata->output_lock); // 获取输出锁c = tty->ops->write(tty, b, nr); // 调用 tty 写操作mutex_unlock(&ldata->output_lock); // 释放输出锁if (c < 0) {retval = c; // 记录错误返回值goto break_out; // 跳转到退出处理}if (!c) // 如果没有写入字符,结束循环break;b += c; // 更新缓冲区指针nr -= c; // 更新剩余字节数}}if (!nr) // 如果没有剩余字节,结束写入break;if (file->f_flags & O_NONBLOCK) { // 如果是非阻塞模式retval = -EAGAIN; // 设置 EAGAIN 错误break; // 跳出循环}up_read(&tty->termios_rwsem); // 释放读取锁wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); // 允许其他任务调度down_read(&tty->termios_rwsem); // 重新获取读取锁}
break_out:remove_wait_queue(&tty->write_wait, &wait); // 从等待队列中移除当前进程if (nr && tty->fasync) // 如果有剩余字节并且异步写入被启用set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); // 设置写入唤醒标志up_read(&tty->termios_rwsem); // 释放读取锁return (b - buf) ? b - buf : retval; // 返回实际写入的字节数或错误码
}
其中c = tty->ops->write(tty, b, nr),调用tty写操作,此时的tty是tty_struct结构体类型,tty->ops是tty_operations结构体类型,此时已经对行规进行过写入操作,那么可以猜到tty->ops->write(tty, b, nr)肯定就是将数据写入到tty设备。
1.3.2 核心层
可是在tty层我找不到这个函数定义???此时就要去核心层了,因为对行规的写操作是已经结束了,在\Linux-4.9.88\Linux-4.9.88\drivers\tty\serial\serial_core.c搜索可以找到:
\Linux-4.9.88\Linux-4.9.88\drivers\tty\serial\serial_core.c
static const struct tty_operations uart_ops = {.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_fops = &uart_proc_fops,
#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
};
那么就可以知道tty->ops->write调用的就是uart_write函数:
static int uart_write(struct tty_struct *tty,const unsigned char *buf, int count)
{struct uart_state *state = tty->driver_data; // 从 tty 结构获取 uart_statestruct uart_port *port; // 声明 uart_port 指针struct circ_buf *circ; // 声明循环缓冲区指针unsigned long flags; // 用于保存锁定标志int c, ret = 0; // c 用于记录当前写入的字节数,ret 用于返回总写入字节数/** 这意味着你在端口关闭后调用了此函数。* 没有机会进行写入操作。*/if (!state) {WARN_ON(1); // 打印警告信息return -EL3HLT; // 返回错误,表示端口已关闭}circ = &state->xmit; // 获取当前 uart_state 的发送循环缓冲区if (!circ->buf) // 检查缓冲区是否有效return 0; // 如果无效,返回 0port = uart_port_lock(state, flags); // 锁定 uart_port,并获取相关标志while (port) { // 进入循环,处理写入操作c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); // 计算可用的缓冲区空间if (count < c) // 如果待写入字节数小于可用空间c = count; // 将 c 设置为待写入字节数if (c <= 0) // 如果没有可写字节break; // 跳出循环memcpy(circ->buf + circ->head, buf, c); // 将数据复制到循环缓冲区circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1); // 更新循环缓冲区头指针buf += c; // 更新源缓冲区指针count -= c; // 更新待写入字节数ret += c; // 更新总写入字节数}__uart_start(tty); // 启动 UART 传输uart_port_unlock(port, flags); // 解锁 uart_portreturn ret; // 返回成功写入的字节数
}
对数据处理完后,就调用了__uart_start(tty);启动uart传输,在之前对open进行分析过,其实是类似的,最后肯定是会进入到硬件驱动相关程序,调用struct uart_ops XXX中的函数去启动uart传输:
\Linux-4.9.88\drivers\tty\serial\serial_core.c
static void __uart_start(struct tty_struct *tty)
{struct uart_state *state = tty->driver_data;struct uart_port *port = state->uart_port;if (port && !uart_tx_stopped(port))port->ops->start_tx(port); //这里,不就调用到了
}
1.3.3 硬件层
port->ops->start_tx(port)可以看出符合猜想了,那么就进入相关硬件层看看,\Linux-4.9.88\Linux-4.9.88\drivers\tty\serial\imx.c:
static const struct uart_ops imx_pops = {.tx_empty = imx_tx_empty,.set_mctrl = imx_set_mctrl,.get_mctrl = imx_get_mctrl,.stop_tx = imx_stop_tx,.start_tx = imx_start_tx, //这个函数.stop_rx = imx_stop_rx,.enable_ms = imx_enable_ms,.break_ctl = imx_break_ctl,.startup = imx_startup,.shutdown = imx_shutdown,.flush_buffer = imx_flush_buffer,.set_termios = imx_set_termios,.type = imx_type,.config_port = imx_config_port,.verify_port = imx_verify_port,
#if defined(CONFIG_CONSOLE_POLL).poll_init = imx_poll_init,.poll_get_char = imx_poll_get_char,.poll_put_char = imx_poll_put_char,
#endif
};
port->ops->start_tx(port)调用的就是imx_start_tx。
imx_start_tx 函数用于启动 UART 设备的发送操作。根据 RS485 和 DMA 的设置,它配置 UART 硬件,准备发送数据。
分析到这里就差不多了,从tty层,到核心层,再到硬件层,其实很好懂的,主要牢记这个顺序就行了。
2.硬件相关的发送
有兴趣的可以继续看看它是怎么实现硬件发送的

\Linux-4.9.88\drivers\tty\serial\imx.c
/** interrupts disabled on entry*/
static void imx_start_tx(struct uart_port *port)
{struct imx_port *sport = (struct imx_port *)port; // 将 uart_port 强制转换为 imx_port 类型unsigned long temp; // 临时变量用于存储寄存器值// 检查 RS485 相关设置if (port->rs485.flags & SER_RS485_ENABLED) {temp = readl(port->membase + UCR2); // 读取 UCR2 寄存器的当前值if (port->rs485.flags & SER_RS485_RTS_ON_SEND)imx_port_rts_inactive(sport, &temp); // 设置 RTS 为非活动状态elseimx_port_rts_active(sport, &temp); // 设置 RTS 为活动状态// 如果不允许在发送期间接收,则禁用接收功能if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))temp &= ~UCR2_RXEN; // 清除 RXEN 位以禁用接收writel(temp, port->membase + UCR2); // 写回修改后的值到 UCR2 寄存器// 启用发送器和移位器空的中断temp = readl(port->membase + UCR4); // 读取 UCR4 寄存器temp |= UCR4_TCEN; // 设置 TCEN 位以启用空闲中断writel(temp, port->membase + UCR4); // 写回修改后的值到 UCR4 寄存器}// 检查 DMA 是否被启用if (!sport->dma_is_enabled) {temp = readl(sport->port.membase + UCR1); // 读取 UCR1 寄存器writel(temp | UCR1_TXMPTYEN, sport->port.membase + UCR1); // 启用 TXMPTYEN 位}if (sport->dma_is_enabled) {if (sport->port.x_char) {/* 如果有 X-char 要发送,启用 TX IRQ 并禁用 TX DMA */temp = readl(sport->port.membase + UCR1); // 读取 UCR1 寄存器temp &= ~UCR1_TDMAEN; // 清除 TDMAEN 位以禁用 DMAtemp |= UCR1_TXMPTYEN; // 设置 TXMPTYEN 位以启用空闲中断writel(temp, sport->port.membase + UCR1); // 写回修改后的值到 UCR1 寄存器return; // 发送 X-char 后返回}// 如果发送缓冲区不为空且发送未停止,则调度 DMA 任务if (!uart_circ_empty(&port->state->xmit) &&!uart_tx_stopped(port))schedule_work(&sport->tsk_dma_tx);return; // 返回}
}
启用中断以发送 X-char。如果发送缓冲区不为空且发送未停止,则调度 DMA 任务。该函数用于启动 UART 的发送操作。它根据当前的发送状态和配置(如 RS-485 的设置和 DMA 是否启用)配置 UART 硬件,准备好进行数据发送。 通常在发送数据的初始阶段调用,例如在 UART 驱动程序准备发送数据之前,或者在设置完 UART 的发送相关寄存器后。
那么既然是写,那肯定也需要中断,就有对应的中断的处理函数:
\Linux-4.9.88\drivers\tty\serial\imx.cstatic irqreturn_t imx_txint(int irq, void *dev_id)
{struct imx_port *sport = dev_id; // 获取传递的设备指针unsigned long flags;spin_lock_irqsave(&sport->port.lock, flags); // 上锁以保护共享资源imx_transmit_buffer(sport); // 调用函数发送数据spin_unlock_irqrestore(&sport->port.lock, flags); // 解锁return IRQ_HANDLED; // 返回中断处理完成
}static inline void imx_transmit_buffer(struct imx_port *sport)
{struct circ_buf *xmit = &sport->port.state->xmit; // 获取发送缓冲区unsigned long temp;if (sport->port.x_char) {/* 如果有字符待发送 */writel(sport->port.x_char, sport->port.membase + URTX0); // 发送该字符sport->port.icount.tx++; // 发送计数加1sport->port.x_char = 0; // 清空待发送字符return;}// 如果发送缓冲区为空或发送被停止,停止发送if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) {imx_stop_tx(&sport->port);return;}// 处理 DMA 发送if (sport->dma_is_enabled) {/** 刚刚发送了 X-char,确保启用 TX DMA,并禁用 TX IRQ。*/temp = readl(sport->port.membase + UCR1); // 读取 UCR1 寄存器temp &= ~UCR1_TXMPTYEN; // 清除 TXMPTYEN 位if (sport->dma_is_txing) {temp |= UCR1_TDMAEN; // 启用 DMA 传输writel(temp, sport->port.membase + UCR1); // 写回 UCR1 寄存器} else {writel(temp, sport->port.membase + UCR1); // 仅写回schedule_work(&sport->tsk_dma_tx); // 调度 DMA 任务}}// 从发送缓冲区发送数据while (!uart_circ_empty(xmit) &&!(readl(sport->port.membase + uts_reg(sport)) & UTS_TXFULL)) {/* 发送 xmit->buf[xmit->tail] 到端口 */writel(xmit->buf[xmit->tail], sport->port.membase + URTX0); // 发送数据xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); // 更新尾部索引sport->port.icount.tx++; // 发送计数加1}// 如果发送缓冲区中待发送字符少于阈值,则唤醒写入操作if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)uart_write_wakeup(&sport->port);// 如果发送缓冲区为空,停止发送if (uart_circ_empty(xmit))imx_stop_tx(&sport->port);
}
imx_txint该函数是一个中断处理程序,它在 UART 发送中断触发时被调用。主要职责是调用 imx_transmit_buffer 函数来处理发送缓冲区的数据。当 UART 控制器准备好发送下一个字符时,生成一个中断,触发 imx_txint 函数执行。它会确保在发送操作中对共享资源进行适当的锁定。
- 流程:
imx_start_tx负责准备发送操作并可能启动 DMA 或设置相关寄存器,而imx_txint则在 UART 发送中断到达时执行,负责从发送缓冲区中提取数据并进行发送。 - 调用链:在某些情况下,
imx_start_tx会在数据准备好发送时被调用,而imx_txint会响应 UART 发送硬件的状态变化(如发送缓冲区的空闲),从而进行数据发送。
因此,这两个函数一起工作,以确保 UART 数据发送的顺畅和高效。imx_start_tx 用于设置和启动发送,而 imx_txint 则在硬件准备好时进行实际的数据发送。
相关文章:
IMX 平台UART驱动情景分析:write篇--从 TTY 层到硬件驱动的写操作流程解析
往期内容 本专栏往期内容:Uart子系统 UART串口硬件介绍深入理解TTY体系:设备节点与驱动程序框架详解Linux串口应用编程:从UART到GPS模块及字符设备驱动 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解IMX 平台UART驱…...
网络安全拟态防御技术
一. 拟态防御 拟态现象(Mimic Phenomenon, MP)是指一种生物如果能够在色彩、纹理和形状等特征上模拟另一种生物或环境,从而使一方或双方受益的生态适应现象。按防御行为分类可将其列入基于内生机理的主动防御范畴,又可称之为拟…...
灵活开源低代码平台——Microi吾码(一)
开源低代码平台-Microi吾码-平台简介1. 什么是低代码平台?2. 它能做什么?3. 它的优点是什么? 平台预览图平台亮点版本区别成功案例源码目录说明Microi吾码 - 系列文档 开源低代码平台-Microi吾码-平台简介 技术框架:.NET8 Redis …...
frida_hook_libart(简单解释)
一:直接取代码 //frida -U -f com.xingin.xhs -l hook_art.js -o xhsart.log //frida -U -f com.tencent.mobileqq -l hook_art.js -o qqart.logconst STD_STRING_SIZE 3 * Process.pointerSize; class StdString {constructor() {this.handle Memory.alloc(STD_S…...
计算机网络八股整理(二)
计算机网络八股整理(二) 应用层 1:dns的全称了解过吗? dns全称domain-name-system,翻译过来就是域名系统,是在计算机网络中将域名转换成ip地址的分布式数据库系统; 域名服务器的层级类似一个树…...
强化学习off-policy进化之路(PPO->DPO->KTO->ODPO->ORPO->simPO)
需要LLM在训练过程中做生成的方法是 On Policy,其余的为Off Policy。 On Policy是包含了反馈机制,Off Policy不包含反馈机制。 若进行环境交互的模型与被更新的模型是相同的模型,通常这种更新策略被称为on-policy的策略。on-policy的方法会有…...
Linux 如何创建逻辑卷并使用
一、逻辑卷的介绍 生成环境中逻辑卷使用率很高 逻辑卷的诞生:如果对磁盘直接使用fdisk分区,那么这中分区,我们叫做Linux的标准分区,Linux的标准分区格式化成文件系统之后,挂载使用,那么一旦文件系统的空间…...
java实现将图片插入word文档
插入图片所用依赖 private static void insertImage(XWPFDocument document, String path) {List<XWPFParagraph> paragraphs document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {CTP ctp paragraph.getCTP();for (int dwI 0; dwI < ctp.sizeO…...
初识java(3)
大家好,今天我们来讲讲我们的老伙计-变量,在哪一门编程语言中,变量的作用都是不可或缺的,那么下面我们就来详细了解一下java中的变量。 一.变量概念 在程序中,除了有始终不变的常量外,有些内容可能会经常…...
coqui-ai TTS 初步使用
项目地址:https://github.com/coqui-ai/TTS 1. 创建一个新的conda环境,如果自己会管理python环境也可以用其他方法 克隆项目下来 pip install -r requirements.txt # 安装依赖 pip install coqui-tts # 只要命令行工具的话 下载自己想要的模型 …...
matlab代码--卷积神经网络的手写数字识别
1.cnn介绍 卷积神经网络(Convolutional Neural Network, CNN)是一种深度学习的算法,在图像和视频识别、图像分类、自然语言处理等领域有着广泛的应用。CNN的基本结构包括输入层、卷积层、池化层(Pooling Layer)、全连…...
Scala—Map用法详解
Scala—Map用法详解 在 Scala 中,Map 是一种键值对的集合,其中每个键都是唯一的。Scala 提供了两种类型的 Map:不可变 Map 和可变 Map。 1. 不可变集合(Map) 不可变 Map 是默认的 Map 实现,位于 scala.co…...
极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【六】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
ES6 、ESNext 规范、编译工具babel
ES6 、ESNext 规范、编译工具简介 ES6ES(ECMAScript) vs JS常量进一步探讨 obj对象的扩展面试:使对象属性也不能更改——Object.freeze(obj) 解构deconstruction变量的解构赋值:数组解构赋值:对象解构赋值:…...
DeepSpeed 配置文件(DeepSpeed Configuration Files)详解:中英文解释
中文版 本文详细介绍 DeepSpeed 配置文件,结合 4 卡 3090 的实际使用场景,重点解释各个参数的含义,并提供应对爆显存的方案。 DeepSpeed 配置文件详解:从基础到实战 DeepSpeed 是用于加速大规模分布式训练的重要工具,…...
前端JavaScript(一)---基本介绍
Javascript是一种由Netscape(网景)的LiveScript发展而来的原型化继承的面向对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如Perl,遗留的速度问题,为客户提供更流畅的浏览效果。当时服务端需要对…...
文本处理之sed
1、概述 sed是文本编辑器,作用是对文本的内容进行增删改查。 和vim不一样,sed是按行进行处理。 sed一次处理一行内容,处理完一行之后紧接着处理下一行,一直到文件的末尾 模式空间:临时储存,修改的结果临…...
uniapp在App端定义全局弹窗,当打开关闭弹窗会触发onShow、onHide生命周期怎么解决?
在uniapp(App端)中实现自定义弹框,可以通过创建一个透明页面来实现。点击进入当前页面时,页面背景会变透明,用户可以根据自己的需求进行自定义,最终效果类似于弹框。 遇到问题:当打开弹窗(进入弹窗页面)就会触发当前页…...
计算机网络 实验七 NAT配置实验
一、实验目的 通过本实验理解网络地址转换的原理和技术,掌握扩展NAT/NAPT设计、配置和测试。 二、实验原理 NAT配置实验的原理主要基于网络地址转换(NAT)技术,该技术用于将内部私有网络地址转换为外部公有网络地址,从…...
数据结构——排序算法第二幕(交换排序:冒泡排序、快速排序(三种版本) 归并排序:归并排序(分治))超详细!!!!
文章目录 前言一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本 快排1.2.2 挖坑法 快排1.2.3 lomuto前后指针 快排 二、归并排序总结 前言 继上篇学习了排序的前面两个部分:直接插入排序和选择排序 今天我们来学习排序中常用的交换排序以及非常稳定的归并排序 快排可是有多…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
