串口驱动

Posted 四季帆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了串口驱动相关的知识,希望对你有一定的参考价值。

0. 前言        

        经过前面三个部分的初始化,后面的操作就是直接使用前面的配置了。

1. 用户空间write的操作实现

tty_write
	-->ld = tty_ldisc_ref_wait(tty);
		-->wait_event(tty_ldisc_wait, (ld = tty_ldisc_try(tty)) != NULL);         //tty_ldisc_wait等待队列一直等待直到等待的条件成立,所以接下来的关键要看tty_ldisc_try(tty)这个函数
			-->tty_ldisc_try(tty)
				-->ld = tty->ldisc
	-->do_tty_write(ld->ops->write, tty, file, buf, count);
		-->n_tty_write		/*这里的ld->ops等于tty_ldisc_N_TTY,调用n_tty_write并将传进来的buf传给它*/		
			-->add_wait_queue(&tty->write_wait, &wait);
			-->tty->ops->write(tty, b, nr);				//即uart_write
				-->uart_start(tty);
					-->__uart_start(tty);
						-->port->ops->start_tx(port);   //即imx_start_tx
							-->schedule_delayed_work(&sport->tsk_dma_tx, 0);
								-->dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);      //流式DMA映射
								-->sg_set_buf(sgl, xmit->buf + xmit->tail,UART_XMIT_SIZE - xmit->tail);  //将环形缓冲区的地址配置到sgl。
								-->desc = dmaengine_prep_slave_sg(chan, sgl, sport->dma_tx_nents,DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);   //初始化DMA描述符,将sgl中环形缓冲区的地址赋值到DMA的配置中
								-->desc->callback = dma_tx_callback;  //DMA完成一次搬运后,回调用这个回调函数
								-->dmaengine_submit(desc);           //将该描述符插入dmaengine驱动的传输队列
								-->dma_async_issue_pending(chan);    //启动对应DMA通道上的传输
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);
 	struct tty_ldisc *ld;
	ssize_t ret;

	ld = tty_ldisc_ref_wait(tty);    /* ---> */
	if (!ld->ops->write)
		ret = -EIO;
	else
		ret = do_tty_write(ld->ops->write, tty, file, buf, count);/* 调用n_tty_write并将传进来的buf传给它 ---> */
	tty_ldisc_deref(ld);
	return ret;
}

struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
{
	struct tty_ldisc *ld;
	/* wait_event is a macro 阻塞直至(ld = tty_ldisc_try(tty)) != NULL */
	wait_event(tty_ldisc_wait, (ld = tty_ldisc_try(tty)) != NULL);
	return ld;
}

static struct tty_ldisc *tty_ldisc_try(struct tty_struct *tty)
{
	unsigned long flags;
	struct tty_ldisc *ld;

	if (test_bit(TTY_LDISC, &tty->flags) && tty->ldisc) {
		ld = tty->ldisc;  //从tty中取出线路规程ldisc,该ldisc是在串口驱动(三)的分析线路2-1中放到tty中的
		atomic_inc(&ld->users);
	}
	return ld;
}
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;
	unsigned int chunk;
	/* Do the write .. */
	for (;;) {
		if (copy_from_user(tty->write_buf, buf, size))	/* 把应用层要发送的内容复制到tty->write_buf */
			break;
		ret = write(tty, file, tty->write_buf, size);	/* 回调函数ld->ops->write,即调用n_tty_write()*/
	}
	return ret;
}

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;
	DECLARE_WAITQUEUE(wait, current);	/*将等待队列项wait初始化为current对应的任务结构*/
	int c;
	ssize_t retval = 0;

	add_wait_queue(&tty->write_wait, &wait);	/*将当前进程加入write_wait队列*/
	while (1) {
		set_current_state(TASK_INTERRUPTIBLE);
		if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {	/*OPOST:启用输出处理*/
			······
		} else {		
			struct n_tty_data *ldata = tty->disc_data;			/*使用原始输出,使数据能不经过处理、过滤地完整地输出到串口接口*/

			while (nr > 0) {
				c = tty->ops->write(tty, b, nr);		/*tty->ops即uart_ops(不明白的可以看前面几篇的注释),即调用uart_write() --->*/
				if (c < 0) {
					retval = c;
					goto break_out;
				}
				if (!c)
					break;
				b += c;
				nr -= c;		/*用户数据没写完,nr是剩余字节,当DMA传输完成一次以后就会唤醒这里,继续将剩余内容写到环形缓冲区*/
			}
		}
		schedule();		/*休眠*/
	}
}

static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
	struct uart_state *state = tty->driver_data;
	struct uart_port *port;
	struct circ_buf *circ;
	unsigned long flags;
	int c, ret = 0;

	port = state->uart_port;
	circ = &state->xmit;

	if (!circ->buf)		/*如果没有分配缓冲区空间就返回*/
		return 0;

	while (1) {
		c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);		/*得到环形缓冲区中剩余的空间 */
		if (count < c)
			c = count;		/*如果用户要写的数据小于剩余空间则全部写入,否则只写入部分*/
		if (c <= 0)
			break;
		memcpy(circ->buf + circ->head, buf, c);		/*将用户数据拷贝到uart_state.xmit环形缓冲区*/
		circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
		buf += c;
		count -= c;
		ret += c;			/*已经写入到环形缓冲区的字节数*/
	}

	uart_start(tty);		/*--->*/
	return ret;
}

static void uart_start(struct tty_struct *tty)
{
	__uart_start(tty);		/*--->*/
}

static void __uart_start(struct tty_struct *tty)
{
	struct uart_state *state = tty->driver_data;
	struct uart_port *port = state->uart_port;

	if (port->ops->wake_peer)			/*未定义*/
		port->ops->wake_peer(port);

	if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
	    !tty->stopped && !tty->hw_stopped)
		port->ops->start_tx(port);		/*即imx_start_tx, 在串口驱动(二)中的serial_imx_probe函数中做的交接 --->*/
}

static void imx_start_tx(struct uart_port *port)
{
	struct imx_port *sport = (struct imx_port *)port;
	unsigned long temp;

	/* Clear any pending ORE flag before enabling interrupt */
	temp = readl(sport->port.membase + USR2);
	writel(temp | USR2_ORE, sport->port.membase + USR2);
 //这个中间有很多寄存器读写操作被删掉了······
	if (sport->dma_is_enabled) {
		schedule_delayed_work(&sport->tsk_dma_tx, 0);  /* 回看串口驱动(三)的imx_startup函数可知,sport->tsk_dma_tx对应dma_tx_work() ---> */
		return;
	}

	if (readl(sport->port.membase + uts_reg(sport)) & UTS_TXEMPTY)
		imx_transmit_buffer(sport);
}

static void dma_tx_work(struct work_struct *w)
{
	struct delayed_work *delay_work = to_delayed_work(w);
	struct imx_port *sport = container_of(delay_work, struct imx_port, tsk_dma_tx);
	struct circ_buf *xmit = &sport->port.state->xmit;
	struct scatterlist *sgl = sport->tx_sgl;
	struct dma_async_tx_descriptor *desc;
	struct dma_chan	*chan = sport->dma_chan_tx;
	struct device *dev = sport->port.dev;
	unsigned long flags;
	int ret;

	spin_lock_irqsave(&sport->port.lock, flags);
	sport->tx_bytes = uart_circ_chars_pending(xmit);

	if (sport->tx_bytes > 0) {
		if (xmit->tail > xmit->head && xmit->head > 0) {
			sport->dma_tx_nents = 2;
			sg_init_table(sgl, 2);
			sg_set_buf(sgl, xmit->buf + xmit->tail,
					UART_XMIT_SIZE - xmit->tail);   //将环形缓冲区的地址配置到sgl。
			sg_set_buf(sgl + 1, xmit->buf, xmit->head);
		} else {
			sport->dma_tx_nents = 1;
			sg_init_one(sgl, xmit->buf + xmit->tail, sport->tx_bytes);
		}
		spin_unlock_irqrestore(&sport->port.lock, flags);

		ret = dma_map_sg(dev, sgl, sport->dma_tx_nents, DMA_TO_DEVICE);  //流式DMA映射
		if (ret == 0) {
			dev_err(dev, "DMA mapping error for TX.\\n");
			goto err_out;
		}
		desc = dmaengine_prep_slave_sg(chan, sgl, sport->dma_tx_nents,
						DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);  //初始化DMA描述符,将sgl中环形缓冲区的地址赋值到DMA的配置中
		if (!desc) {
			dev_err(dev, "We cannot prepare for the TX slave dma!\\n");
			goto err_out;
		}
		desc->callback = dma_tx_callback;   //DMA完成一次搬运后,会调用这个回调函数
		desc->callback_param = sport;

		dev_dbg(dev, "TX: prepare to send %lu bytes by DMA.\\n",
				uart_circ_chars_pending(xmit));
		/* fire it */
		sport->dma_is_txing = 1;
		dmaengine_submit(desc);   //将该描述符插入dmaengine驱动的传输队列
		dma_async_issue_pending(chan);  //启动对应DMA通道上的传输
		return;
	};
}

2. 总结

        1) 只要环形缓冲区不为空,DMA就会一直搬运,这一实现就是通过在DMA回调函数中调度dma_tx_work来启动DMA搬运,在将环形缓冲区搬运完之前,会一直在dma_tx_work<=>dma_tx_callback之间循环运行。

        2) write是会阻塞的,因为用户态的数据并不是总能够全部写入到环形缓冲区的(环形缓冲区大小是有限的!),如果用户数据没有全部写到环形缓冲区,n_tty_write()进程会休眠,等环形缓冲区有空间以后再唤醒,这一步是在DMA回调函数中实现的。

以上是关于串口驱动的主要内容,如果未能解决你的问题,请参考以下文章

串口驱动

2021-06-09 STC8单片机串口驱动函数

2021-06-09 STC8单片机串口驱动函数

2021-06-09 STC8单片机串口驱动函数

2021-06-09 STC8单片机串口驱动函数

STM8单片机串口驱动的深度解析