嵌入式Linux开发-串口驱动

0.前言

在今天终于离职了,办完了所有的手续,感觉一身轻松,在上一家公司,作为一名程序员,已经在偏离写代码开发的歪门邪道上越走越远了,现在疫情比较严重,非常时间,大家还是要少出门,注意安全,所以就趁这段时间总结一下之前工作中的问题和经验吧。

1.tty与串口驱动

tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或者虚拟终端。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。
有三种类型的tty’驱动程序:控制台、串口和pty。控制台和pty驱动程序已经被编写好饿了,而且可能也不必为这两类tty驱动程序编写其他的驱动程序。这使得任何使用tty核心与用户和系统交互的新驱动程序都可被看成是串口驱动程序。

2.关于tty

Linux tty驱动程序的核心紧挨着标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端类型设备使用。内核负责控制通过tty设备的数据流,并且格式化这些数据。这使得tty驱动程序把重点放在处理流向或者流出硬件的数据上,而不必重点考虑使用常规方法与用户空间的交互。为了控制数据流,有许多不同的线路规程(line discipline)可虚拟地“插入”任何的tty设备上,这由不同的tty线路规程驱动程序实现。
在这里插入图片描述
tty核心从用户那里得到将被发往tty设备的数据,然后把数据发送给tty线路规程驱动程序,该驱动程序负责把数据传递给tty驱动程序。tty驱动程序对数据进行格式化,然后才能发送给硬件。从tty硬件那里接收到的数据将回溯到tty驱动程序,然后流入tty线路规程驱动程序,接着是tty核心,最后用户从tty核心哪里得到数据。有时tty驱动程序直接与tty核心通信,tty核心将数据直接发送给tty驱动程序,但通常是tty线路规程驱动程序修改在二者之间流动的数据。

3.添加Device

在arch/arm/boot/dts下的.dtsi文件下添加串口相关的device信息
在这里插入图片描述
在imx.c下会通过MODULE_DEVICE_TABLE()去匹配一个device
在这里插入图片描述

4.添加Driver

串口设备驱动核心结构体为uart_driver,在文件linux/drivers/serial/imx.c

static struct uart_driver imx_uart_uart_driver = {
	.owner          = THIS_MODULE,
	.driver_name    = DRIVER_NAME,                 //driver name
	.dev_name       = DEV_NAME,                    //device name
	.major          = SERIAL_IMX_MAJOR,            //主设备号
	.minor          = MINOR_START,                 //次设备号
	.nr             = ARRAY_SIZE(imx_uart_ports),
	.cons           = IMX_CONSOLE,
};

串口驱动注册

static int __init imx_uart_init(void)
{
	int ret = uart_register_driver(&imx_uart_uart_driver);         //用uart核心层注册一个驱动程序

	if (ret)
		return ret;

	ret = platform_driver_register(&imx_uart_platform_driver);    //调用platform_driver_register向内核注册
	if (ret != 0)
		uart_unregister_driver(&imx_uart_uart_driver);

	return ret;
}

uart_register_driver()函数,在drivers/tty/serial/serial_core.c中。
任何tty驱动程序的主要数据结构是结构tty_driver,被用来向tty核心注册和注销驱动程序

int uart_register_driver(struct uart_driver *drv)
{
	struct tty_driver *normal;
	int i, retval = -ENOMEM;

	...
	//每个端口对应一个state
	drv->state = kzalloc(sizeof(struct uart_state)* drv->nr, GFP_KERNEL);
	...
	//分配该串口驱动对应的tty_drvier,tty_driver结构将根据tty驱动程序的需求,用正确的信息初始化
	normal = alloc_tty_driver(drv->nr);
	...
	//让drv->tty_driver字段指向这个tty_driver
	drv->tty_driver = normal;
	...
	//使用tty_set_operation函数拷贝在驱动程序定义的一系列操作函数
	tty_set_operation(normal, &uart_ops);
	...
	//注册tty驱动程序
	retval = tty_register_driver(normal);

}

为了向tty核心注册这个驱动程序,必须将tty_driver结构传递给tty_register_driver函数。
在注册自身后,驱动程序使用tty_register_device函数注册它所控制的设备,该函数有三个参数:

  • 属于该设备的tty_driver结构指针
  • 设备的次设备号
  • 指向该tty设备所绑定的device结构指针,如果tty设备没有绑定任何device结构,该参数为NULL
 int tty_register_driver(struct tty_driver *driver)
 {
 	...
 	for (i = 0; i < driver->num; i++) {
			d = tty_register_device(driver, i, NULL);
			...
	}
	...
 }

5.platform机制

在driver和device匹配成功之后,会调用probe函数
在这里插入图片描述

static int imx_uart_probe(struct platform_device *pdev)
{
	struct imx_port *sport;
	void __iomem *base;
	int ret = 0;
	u32 ucr1;
	struct resource *res;
	int txirq, rxirq, rtsirq;

	...
	
	sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
	
	...
	
	//添加一个端口结构
	return uart_add_one_port(&imx_uart_uart_driver, &sport->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设备
	tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
			uport->line, uport->dev, port, uport->tty_groups);
	if (!IS_ERR(tty_dev)) {
		device_set_wakeup_capable(tty_dev, 1);
	} else {
		dev_err(uport->dev, "Cannot register tty device on line %d\n",
		       uport->line);
	}

	...

	return ret;
}

6.串口数据流

在这里插入图片描述

  • open函数
static int tty_open(struct inode *inode, struct file *filp)
{
	struct tty_struct *tty;
	int noctty, retval;
	dev_t device = inode->i_rdev;              //表示设备文件的结点,这个域实际上包含了设备号
	unsigned saved_flags = filp->f_flags;

	...

	if (tty->ops->open)
		retval = tty->ops->open(tty, filp);    //调用uart_open函数
	else
		retval = -ENODEV;
	filp->f_flags = saved_flags;

	...
	
	return 0;
}
static int uart_open(struct tty_struct *tty, struct file *filp)
{
	struct uart_state *state = tty->driver_data;
	int retval;

	retval = tty_port_open(&state->port, tty, filp);
	if (retval > 0)
		retval = 0;

	return retval;
}

当用户使用open打开由驱动程序分配的设备节点时,tty核心将调用open函数。tty核心使用分配给该设备tty_struct结构的指针,以及一个文件描述符作为参数调用该函数。

  • release函数
int tty_release(struct inode *inode, struct file *filp)
{
	struct tty_struct *tty = file_tty(filp);
	struct tty_struct *o_tty = NULL;
	int	do_sleep, final;
	int	idx;
	long	timeout = 0;
	int	once = 1;

	...
	
	tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count);

	if (tty->ops->close)
		tty->ops->close(tty, filp);   //调用uart_close函数

	/* If tty is pty master, lock the slave pty (stable lock order) */
	tty_lock_slave(o_tty);
	
	...

	tty_release_struct(tty, idx);
	return 0;
}
static void uart_close(struct tty_struct *tty, struct file *filp)
{
	struct uart_state *state = tty->driver_data;

	if (!state) {
		struct uart_driver *drv = tty->driver->driver_state;
		struct tty_port *port;

		state = drv->state + tty->index;
		port = &state->port;
		spin_lock_irq(&port->lock);
		--port->count;
		spin_unlock_irq(&port->lock);
		return;
	}

	pr_debug("uart_close(%d) called\n", tty->index);

	tty_port_close(tty->port, tty, filp);
}

当用户使用先前由open函数创建的文件句柄作为参数调用close时,tty核心调用close函数指针,此时该设备将被关闭。

  • write函数
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;

	if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
		return -EIO;
	if (!tty || !tty->ops->write ||	tty_io_error(tty))
			return -EIO;
	/* Short term debug to catch buggy drivers */
	if (tty->ops->write_room == NULL)
		tty_err(tty, "missing write_room method\n");
	ld = tty_ldisc_ref_wait(tty); 
	if (!ld)
		return hung_up_tty_write(file, buf, count, ppos);
	/*调用线路规程操作函数集中的n_tty_write()函数*/
	if (!ld->ops->write)
		ret = -EIO;
	else
		ret = do_tty_write(ld->ops->write, tty, file, buf, count);
	tty_ldisc_deref(ld);
	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;
	DEFINE_WAIT_FUNC(wait, woken_wake_function);
	int c;
	ssize_t retval = 0;

	/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
	if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
		retval = tty_check_change(tty);
		if (retval)
			return retval;
	}

	down_read(&tty->termios_rwsem);

	/* Write out any echoed characters that are still pending */
	process_echoes(tty);

	add_wait_queue(&tty->write_wait, &wait);
	while (1) {
	
			...
			/*调用uart_flush_chars向硬件发送数据*/
			if (tty->ops->flush_chars)
				tty->ops->flush_chars(tty);
		} else {
			struct n_tty_data *ldata = tty->disc_data;

			while (nr > 0) {
				mutex_lock(&ldata->output_lock);
				c = tty->ops->write(tty, b, nr);   //或者调用uart_write函数
				mutex_unlock(&ldata->output_lock);
				if (c < 0) {
					retval = c;
					goto break_out;
				}
				if (!c)
					break;
				b += c;
				nr -= c;
			}
		}
		
		...
		
	}
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;
}
static void uart_flush_chars(struct tty_struct *tty)
{
	uart_start(tty);
}
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;

	/*
	 * This means you called this function _after_ the port was
	 * closed.  No cookie for you.
	 */
	if (!state) {
		WARN_ON(1);
		return -EL3HLT;
	}

	port = uart_port_lock(state, flags);
	circ = &state->xmit;
	if (!circ->buf) {
		uart_port_unlock(port, flags);
		return 0;
	}

	while (port) {
		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);
		circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
		buf += c;
		count -= c;
		ret += c;
	}

	__uart_start(tty);
	uart_port_unlock(port, flags);
	return ret;
}
static void uart_start(struct tty_struct *tty)
{
	struct uart_state *state = tty->driver_data;
	struct uart_port *port;
	unsigned long flags;

	port = uart_port_lock(state, flags);
	__uart_start(tty);
	uart_port_unlock(port, flags);
}
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);   //调用imx.c中的imx_uart_start_tx函数发送数据
}

当数据要被发送给硬件时,用户调用write函数,首先tty核心接收到了该调用,然后内核将数据发送给tty驱动程序的write函数。tty核心同时也告诉tty驱动程序发功数据的大小。

  • read函数
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
			loff_t *ppos)
{
	int i;
	struct inode *inode = file_inode(file);
	struct tty_struct *tty = file_tty(file);
	struct tty_ldisc *ld;

	if (tty_paranoia_check(tty, inode, "tty_read"))
		return -EIO;
	if (!tty || tty_io_error(tty))
		return -EIO;

	/* We want to wait for the line discipline to sort out in this
	   situation */
	ld = tty_ldisc_ref_wait(tty);
	if (!ld)
		return hung_up_tty_read(file, buf, count, ppos);
	/*调用线路规程操作函数集中的n_tty_read()函数*/
	if (ld->ops->read)
		i = ld->ops->read(tty, file, buf, count);
	else
		i = -EIO;
	tty_ldisc_deref(ld);

	if (i > 0)
		tty_update_time(&inode->i_atime);

	return i;
}
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
			 unsigned char __user *buf, size_t nr)
{
	struct n_tty_data *ldata = tty->disc_data;
	unsigned char __user *b = buf;
	DEFINE_WAIT_FUNC(wait, woken_wake_function);
	int c;
	int minimum, time;
	ssize_t retval = 0;
	long timeout;
	int packet;
	size_t tail;

	...
	
	while (nr) {
		
		...
		/*如果配置了icanon,那么就按canonical模式拷贝读取的数据*/
		if (ldata->icanon && !L_EXTPROC(tty)) {
			retval = canon_copy_from_read_buf(tty, &b, &nr);
			if (retval)
				break;
		} else {
			int uncopied;

			/* Deal with packet mode. */
			if (packet && b == buf) {
				if (put_user(TIOCPKT_DATA, b)) {
					retval = -EFAULT;
					break;
				}
				b++;
				nr--;
			}

			uncopied = copy_from_read_buf(tty, &b, &nr);
			uncopied += copy_from_read_buf(tty, &b, &nr);
			if (uncopied) {
				retval = -EFAULT;
				break;
			}
		}

		n_tty_check_unthrottle(tty);

		if (b - buf >= minimum)
			break;
		if (time)
			timeout = time;
	}
	
	...

	return retval;
}
  1. 如果配置了串口中断,串口在接收到数据后,触发中断
static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
{
	struct imx_port *sport = dev_id;
	unsigned int rx, flg, ignored = 0;
	struct tty_port *port = &sport->port.state->port;

	spin_lock(&sport->port.lock);

	while (imx_uart_readl(sport, USR2) & USR2_RDR) {
		
		...

		if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))
			continue;

		if (unlikely(rx & URXD_ERR)) {
			
			...
			/*读取寄存器的数据*/
			rx &= (sport->port.read_status_mask | 0xFF);

			if (rx & URXD_BRK)
				flg = TTY_BREAK;
			else if (rx & URXD_PRERR)
				flg = TTY_PARITY;
			else if (rx & URXD_FRMERR)
				flg = TTY_FRAME;
			if (rx & URXD_OVRRUN)
				flg = TTY_OVERRUN;

#ifdef SUPPORT_SYSRQ
			sport->port.sysrq = 0;
#endif
		}

		if (sport->port.ignore_status_mask & URXD_DUMMY_READ)
			goto out;

		if (tty_insert_flip_char(port, rx, flg) == 0)
			sport->port.icount.buf_overrun++;
	}

out:
	spin_unlock(&sport->port.lock);
	tty_flip_buffer_push(port);
	return IRQ_HANDLED;
}

调用tty_insert_flip_char将把tty驱动程序获得的、准备发给用户的字符添加到交替缓冲区中。第一个参数是保存数据的tty_struct结构,第二个参数是需要保存的数据,第三个参数是为此字符设置的标志位。如果接收到的字符是常规字符,标志位应该被设置为TTY_NORMAL。

  1. 如果配置了DMA模式,从DMA拷贝数据
static int imx_uart_start_rx_dma(struct imx_port *sport)
{
	struct scatterlist *sgl = &sport->rx_sgl;
	struct dma_chan	*chan = sport->dma_chan_rx;
	struct device *dev = sport->port.dev;
	struct dma_async_tx_descriptor *desc;
	int ret;

	sport->rx_ring.head = 0;
	sport->rx_ring.tail = 0;
	sport->rx_periods = RX_DMA_PERIODS;

	sg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);
	
	...
	
	desc->callback = imx_uart_dma_rx_callback;
	desc->callback_param = sport;

	dev_dbg(dev, "RX: prepare for the DMA.\n");
	sport->dma_is_rxing = 1;
	sport->rx_cookie = dmaengine_submit(desc);
	dma_async_issue_pending(chan);
	return 0;
}

tty核心和tty_driver结构并未提供read函数,当tty驱动程序接收到数据后,它将负责把从硬件获取到的任何数据传递给tty核心,而不是使用传统的read函数。tty核心将缓冲数据直到接到来自用户的请求。由于tty核心已提供了缓冲逻辑,因此没有必要为每个tty驱动程序实现它们自己的缓冲区逻辑。当用户要求驱动程序开始或者停止传输数据时,tty核心将通知tty驱动程序。

7.DMA和中断的配置

static int imx_uart_startup(struct uart_port *port)
{
	struct imx_port *sport = (struct imx_port *)port;
	int retval, i;
	unsigned long flags;
	int dma_is_inited = 0;
	u32 ucr1, ucr2, ucr4;

	...
	/*如果dma_is_inited为真,则配置DMA模式,否则使能串口中断*/
	if (dma_is_inited) {
		imx_uart_enable_dma(sport);
		imx_uart_start_rx_dma(sport);
	} else {
		ucr1 = imx_uart_readl(sport, UCR1);
		ucr1 |= UCR1_RRDYEN;
		imx_uart_writel(sport, ucr1, UCR1);

		ucr2 = imx_uart_readl(sport, UCR2);
		ucr2 |= UCR2_ATEN;
		imx_uart_writel(sport, ucr2, UCR2);
	}

	spin_unlock_irqrestore(&sport->port.lock, flags);

	return 0;
}

参考资料:
《LINUX设备驱动程序第三版》
linux-5.4.9

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值