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

IMX 平台UART驱动情景分析:write篇--从 TTY 层到硬件驱动的写操作流程解析

往期内容

本专栏往期内容:Uart子系统

  1. UART串口硬件介绍
  2. 深入理解TTY体系:设备节点与驱动程序框架详解
  3. Linux串口应用编程:从UART到GPS模块及字符设备驱动
  4. 解UART 子系统:Linux Kernel 4.9.88 中的核心结构体与设计详解
  5. IMX 平台UART驱动情景分析:注册篇
  6. IMX 平台UART驱动情景分析:open篇
  7. IMX 平台UART驱动情景分析:read篇–从硬件驱动到行规程的全链路剖析

interrupt子系统专栏:

  1. 专栏地址:interrupt子系统
  2. Linux 链式与层级中断控制器讲解:原理与驱动开发
    – 末片,有专栏内容观看顺序

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

目录

  • 往期内容
  • 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 总框图

img

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->opstty_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.硬件相关的发送

有兴趣的可以继续看看它是怎么实现硬件发送的

img

\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 层到硬件驱动的写操作流程解析

往期内容 本专栏往期内容&#xff1a;Uart子系统 UART串口硬件介绍深入理解TTY体系&#xff1a;设备节点与驱动程序框架详解Linux串口应用编程&#xff1a;从UART到GPS模块及字符设备驱动 解UART 子系统&#xff1a;Linux Kernel 4.9.88 中的核心结构体与设计详解IMX 平台UART驱…...

网络安全拟态防御技术

一. 拟态防御 拟态现象&#xff08;Mimic Phenomenon, MP&#xff09;是指一种生物如果能够在色彩、纹理和形状等特征上模拟另一种生物或环境&#xff0c;从而使一方或双方受益的生态适应现象。按防御行为分类可将其列入基于内生机理的主动防御范畴&#xff0c;又可称之为拟…...

灵活开源低代码平台——Microi吾码(一)

开源低代码平台-Microi吾码-平台简介1. 什么是低代码平台&#xff1f;2. 它能做什么&#xff1f;3. 它的优点是什么&#xff1f; 平台预览图平台亮点版本区别成功案例源码目录说明Microi吾码 - 系列文档 开源低代码平台-Microi吾码-平台简介 技术框架&#xff1a;.NET8 Redis …...

frida_hook_libart(简单解释)

一&#xff1a;直接取代码 //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…...

计算机网络八股整理(二)

计算机网络八股整理&#xff08;二&#xff09; 应用层 1&#xff1a;dns的全称了解过吗&#xff1f; dns全称domain-name-system&#xff0c;翻译过来就是域名系统&#xff0c;是在计算机网络中将域名转换成ip地址的分布式数据库系统&#xff1b; 域名服务器的层级类似一个树…...

强化学习off-policy进化之路(PPO->DPO->KTO->ODPO->ORPO->simPO)

需要LLM在训练过程中做生成的方法是 On Policy&#xff0c;其余的为Off Policy。 On Policy是包含了反馈机制&#xff0c;Off Policy不包含反馈机制。 若进行环境交互的模型与被更新的模型是相同的模型&#xff0c;通常这种更新策略被称为on-policy的策略。on-policy的方法会有…...

Linux 如何创建逻辑卷并使用

一、逻辑卷的介绍 生成环境中逻辑卷使用率很高 逻辑卷的诞生&#xff1a;如果对磁盘直接使用fdisk分区&#xff0c;那么这中分区&#xff0c;我们叫做Linux的标准分区&#xff0c;Linux的标准分区格式化成文件系统之后&#xff0c;挂载使用&#xff0c;那么一旦文件系统的空间…...

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)

大家好&#xff0c;今天我们来讲讲我们的老伙计-变量&#xff0c;在哪一门编程语言中&#xff0c;变量的作用都是不可或缺的&#xff0c;那么下面我们就来详细了解一下java中的变量。 一.变量概念 在程序中&#xff0c;除了有始终不变的常量外&#xff0c;有些内容可能会经常…...

coqui-ai TTS 初步使用

项目地址&#xff1a;https://github.com/coqui-ai/TTS 1. 创建一个新的conda环境&#xff0c;如果自己会管理python环境也可以用其他方法 克隆项目下来 pip install -r requirements.txt # 安装依赖 pip install coqui-tts # 只要命令行工具的话 下载自己想要的模型 …...

matlab代码--卷积神经网络的手写数字识别

1.cnn介绍 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;是一种深度学习的算法&#xff0c;在图像和视频识别、图像分类、自然语言处理等领域有着广泛的应用。CNN的基本结构包括输入层、卷积层、池化层&#xff08;Pooling Layer&#xff09;、全连…...

Scala—Map用法详解

Scala—Map用法详解 在 Scala 中&#xff0c;Map 是一种键值对的集合&#xff0c;其中每个键都是唯一的。Scala 提供了两种类型的 Map&#xff1a;不可变 Map 和可变 Map。 1. 不可变集合&#xff08;Map&#xff09; 不可变 Map 是默认的 Map 实现&#xff0c;位于 scala.co…...

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【六】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...

ES6 、ESNext 规范、编译工具babel

ES6 、ESNext 规范、编译工具简介 ES6ES&#xff08;ECMAScript&#xff09; vs JS常量进一步探讨 obj对象的扩展面试&#xff1a;使对象属性也不能更改——Object.freeze(obj) 解构deconstruction变量的解构赋值&#xff1a;数组解构赋值&#xff1a;对象解构赋值&#xff1a;…...

DeepSpeed 配置文件(DeepSpeed Configuration Files)详解:中英文解释

中文版 本文详细介绍 DeepSpeed 配置文件&#xff0c;结合 4 卡 3090 的实际使用场景&#xff0c;重点解释各个参数的含义&#xff0c;并提供应对爆显存的方案。 DeepSpeed 配置文件详解&#xff1a;从基础到实战 DeepSpeed 是用于加速大规模分布式训练的重要工具&#xff0c…...

前端JavaScript(一)---基本介绍

Javascript是一种由Netscape(网景)的LiveScript发展而来的原型化继承的面向对象的动态类型的区分大小写的客户端脚本语言&#xff0c;主要目的是为了解决服务器端语言&#xff0c;比如Perl&#xff0c;遗留的速度问题&#xff0c;为客户提供更流畅的浏览效果。当时服务端需要对…...

文本处理之sed

1、概述 sed是文本编辑器&#xff0c;作用是对文本的内容进行增删改查。 和vim不一样&#xff0c;sed是按行进行处理。 sed一次处理一行内容&#xff0c;处理完一行之后紧接着处理下一行&#xff0c;一直到文件的末尾 模式空间&#xff1a;临时储存&#xff0c;修改的结果临…...

uniapp在App端定义全局弹窗,当打开关闭弹窗会触发onShow、onHide生命周期怎么解决?

在uniapp(App端)中实现自定义弹框&#xff0c;可以通过创建一个透明页面来实现。点击进入当前页面时&#xff0c;页面背景会变透明&#xff0c;用户可以根据自己的需求进行自定义&#xff0c;最终效果类似于弹框。 遇到问题&#xff1a;当打开弹窗(进入弹窗页面)就会触发当前页…...

计算机网络 实验七 NAT配置实验

一、实验目的 通过本实验理解网络地址转换的原理和技术&#xff0c;掌握扩展NAT/NAPT设计、配置和测试。 二、实验原理 NAT配置实验的原理主要基于网络地址转换&#xff08;NAT&#xff09;技术&#xff0c;该技术用于将内部私有网络地址转换为外部公有网络地址&#xff0c;从…...

数据结构——排序算法第二幕(交换排序:冒泡排序、快速排序(三种版本) 归并排序:归并排序(分治))超详细!!!!

文章目录 前言一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare版本 快排1.2.2 挖坑法 快排1.2.3 lomuto前后指针 快排 二、归并排序总结 前言 继上篇学习了排序的前面两个部分:直接插入排序和选择排序 今天我们来学习排序中常用的交换排序以及非常稳定的归并排序 快排可是有多…...

【kafka04】消息队列与微服务之Kafka 图形工具

Kafka 在 ZooKeeper 里面的存储结构 topic 结构 /brokers/topics/[topic] partition结构 /brokers/topics/[topic]/partitions/[partitionId]/state broker信息 /brokers/ids/[o...N] 控制器 /controller 存储center controller中央控制器所在kafka broker的信息 消费者 /c…...

剖析前后端 API 接口参数设计:JSON 数据结构化全攻略

在当今软件开发领域&#xff0c;前后端分离架构已成为主流趋势。而 API 接口作为前后端之间数据交互的桥梁&#xff0c;其设计的合理性对系统的可维护性和扩展性起着至关重要的作用。JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&…...

vue3 多种方式接受props,定义ref,reactive

定义props 1 第一种 interface AddType { dialogStudyVisible: boolean; } const props defineProps<AddType>(); 第二种 // const props defineProps({ // dialogStudyVisible:{ // type:Boolean, // default:false // } // }) 第三种 // const …...

逻辑处理器核心指纹修改

navigator.hardwareConcurrency的属性,可以用来获取CPU的逻辑处理器核心数。 1、navigator.hardwareConcurrency接口定义&#xff1a; third_party\blink\renderer\core\frame\navigator_concurrent_hardware.idl // https://html.spec.whatwg.org/C/#navigator.hardwarecon…...

如何制作项目网页

一、背景 许多论文里经常会有这样一句话Supplementary material can be found at https://hri-eu.github.io/Lami/&#xff0c;这个就是将论文中的内容或者补充视频放到一个网页上&#xff0c;以更好的展示他们的工作。因此&#xff0c;这里介绍下如何使用前人提供的模板制作我…...

mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?

随笔 从千万粉丝“何同学”抄袭开源项目说起&#xff0c;为何纯技术死路一条&#xff1f; 数据源的统一与拆分 监控报警系统的指标、规则与执行闭环 我们的系统应该配置哪些监控报警项&#xff1f; 监控报警系统如何实现自监控? java 老矣&#xff0c;尚能饭否&#xff…...

1、正则表达式

grep匹配 grep用来过滤文本内容&#xff0c;以匹配要查询的结果。 grep root /etc/passwd&#xff1a;匹配包含root的行 -m 数字&#xff1a;匹配几次后停止 -v&#xff1a;取反-i&#xff1a;忽略字符的大小写&#xff0c;默认的&#xff0c;可以不加-n&#xff1a…...

Airsim安装问题:This project was made with a different version of the Unreal Engine.

本文记录如何在 Ubuntu 18.04 系统中配置 AirSim 和 Unreal Engine 4.27&#xff0c;并成功打开默认的 Blocks 环境项目。 环境说明 系统&#xff1a;Ubuntu 18.04Unreal Engine 版本&#xff1a;4.27AirSim&#xff1a;主分支文件路径&#xff1a; Unreal Engine&#xff1a…...

java八股-分布式服务的接口幂等性如何设计?

文章目录 接口幂等token Redis分布式锁 原文视频链接&#xff1a;讲解的流程特别清晰&#xff0c;易懂&#xff0c;收获巨大 【新版Java面试专题视频教程&#xff0c;java八股文面试全套真题深度详解&#xff08;含大厂高频面试真题&#xff09;】 https://www.bilibili.com/…...

vscode python code runner执行乱码

打开vscode code runner插件配置&#xff0c;如图所示&#xff1a; 然后在setting.json修改运行python的默认命令&#xff1a; 将原来 替换成 "python":"set PYTHONIOENCODINGutf8 && python", 参考&#xff1a;Vscode——python环境输出中文乱…...

wordpress无法找到该页/刷网站排名软件

QQ自带了一个接口&#xff0c;只要是使用手机打开该网址&#xff0c;就会弹出QQ对话框&#xff1a; http://qm.qq.com/cgi-bin/qm/qr?k 使用手机打开该网址可以进行测试&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?k2/5FwXkAy4/UqlMOaqSUVglaDn/RaVy 该脚本HTML源码如下&a…...

可以做旅行计划的网站/网络宣传

当我们平时清理电脑文件时误删了文件&#xff0c;而想要恢复误删文件就不知道怎么处理了。下面与大家分享用diskgenius分区工具来恢复删除文件&#xff0c;一起来看看如何 恢复文件 吧。1、首先&#xff0c;我们提前制作好u启动u盘启动盘。接着将该u盘启动盘插入电脑usb接口&am…...

充值网站制作/最新中高风险地区名单

转载请注明出处&#xff1a;http://blog.csdn.net/u011733020前言&#xff1a;在Android开发中&#xff0c;对于图片的加载可以说是个老生常谈的问题了&#xff0c;图片加载是一个比较坑的地方&#xff0c;处理不好&#xff0c;会有各种奇怪的问题&#xff0c;比如 加载导致界面…...

花生壳 建设网站/北京优化网站建设

OrderedDict 记录的是插入的顺序 实验如下...

企业网站建设专家/怎么申请自己的网络平台

线程&#xff1a;每一个任务称为一个线程&#xff0c;线程不能独立的存在&#xff0c;它必须是进程的一部分单线程&#xff1a;般常见的Java应用程序都是单线程的&#xff0c;比如运行helloworld的程序时&#xff0c;会启动jvm进程&#xff0c;然后运行main方法产生线程&#x…...

用wordpress仿一个网站模板下载/怎样做一个产品营销方案

弱视治疗的基础&#xff0c;是“大脑神经可塑性”。基于这一理论&#xff0c;大龄弱视患者&#xff0c;找到正确的治疗方法&#xff0c;能很快提高视力&#xff0c;甚至立体视。前几期推送的文章&#xff0c;分享了成人弱视治疗案例。弱视患者的视觉训练&#xff0c;主要包括&a…...