串口驱动程序设计详解---串口打开、发送、接收(下)

上一篇博客分析了串口驱动初始化部分,下面逐步分析串口驱动中的打开串口,数据发送和接收!

初始化主要工作流程:


先来分析串口打开操作流程,还是先上图:



这里分析还是离不开上篇博客中的两张重要的图:



串口操作重要的数据结构:


由上一篇串口驱动分析可知在samsung.c中模块初始化中有一项工作是注册一个串口驱动,


跳到这个函数中uart_register_driver可以看到有一个函数:

retval = tty_register_driver(normal);

跳到这个函数中,这里贴上源码:

int tty_register_driver(struct tty_driver *driver)
{
	int error;
	int i;
	dev_t dev;
	void **p = NULL;
	struct device *d;

	if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
		p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
		if (!p)
			return -ENOMEM;
	}

	if (!driver->major) {
		error = alloc_chrdev_region(&dev, driver->minor_start,
						driver->num, driver->name);
		if (!error) {
			driver->major = MAJOR(dev);
			driver->minor_start = MINOR(dev);
		}
	} else {
		dev = MKDEV(driver->major, driver->minor_start);
		error = register_chrdev_region(dev, driver->num, driver->name);
	}
	if (error < 0) {
		kfree(p);
		return error;
	}

	if (p) {
		driver->ttys = (struct tty_struct **)p;
		driver->termios = (struct ktermios **)(p + driver->num);
	} else {
		driver->ttys = NULL;
		driver->termios = NULL;
	}

	cdev_init(&driver->cdev, &tty_fops);
	driver->cdev.owner = driver->owner;
	error = cdev_add(&driver->cdev, dev, driver->num);
	if (error) {
		unregister_chrdev_region(dev, driver->num);
		driver->ttys = NULL;
		driver->termios = NULL;
		kfree(p);
		return error;
	}

	mutex_lock(&tty_mutex);
	list_add(&driver->tty_drivers, &tty_drivers);
	mutex_unlock(&tty_mutex);

	if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
		for (i = 0; i < driver->num; i++) {
			d = tty_register_device(driver, i, NULL);
			if (IS_ERR(d)) {
				error = PTR_ERR(d);
				goto err;
			}
		}
	}
	proc_tty_register_driver(driver);
	driver->flags |= TTY_DRIVER_INSTALLED;
	return 0;

err:
	for (i--; i >= 0; i--)
		tty_unregister_device(driver, i);

	mutex_lock(&tty_mutex);
	list_del(&driver->tty_drivers);
	mutex_unlock(&tty_mutex);

	unregister_chrdev_region(dev, driver->num);
	driver->ttys = NULL;
	driver->termios = NULL;
	kfree(p);
	return error;
}
可以看到这个函数内部实现其实就是注册一个字符设备!
看看这一行: cdev_init(&driver->cdev, &tty_fops);

从这个tty_fops找到串口open函数的接口:


可以看到open操作对应的是tty_open(这里的tty_fops就是字符设备的file_operations)

跳到这个函数中可以看到箭头所指向的一行:


这个ops实际上是struct   tty_operations  类型的:


这里总结一下:应用程序空间的打开串口open操作调用了tty_ops中的tty_open,然后tty_open又对应的调用了uart_ops中的uart_open这个函数,这个函数还是tty层次里面的还不涉及驱动层!

下面跳到uart_open这个函数里面:

static int uart_open(struct tty_struct *tty, struct file *filp)
{
	struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
	struct uart_state *state;
	struct tty_port *port;
	int retval, line = tty->index;

	BUG_ON(!tty_locked());
	pr_debug("uart_open(%d) called\n", line);

	state = uart_get(drv, line);
	if (IS_ERR(state)) {
		retval = PTR_ERR(state);
		goto fail;
	}
	port = &state->port;

	tty->driver_data = state;
	state->uart_port->state = state;
	tty->low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
	tty->alt_speed = 0;
	tty_port_tty_set(port, tty);

	/*
	 * If the port is in the middle of closing, bail out now.
	 */
	if (tty_hung_up_p(filp)) {
		retval = -EAGAIN;
		port->count--;
		mutex_unlock(&port->mutex);
		goto fail;
	}

	/*
	 * Make sure the device is in D0 state.
	 */
	if (port->count == 1)
		uart_change_pm(state, 0);

	/*
	 * Start up the serial port.
	 */
	retval = uart_startup(tty, state, 0);

	/*
	 * If we succeeded, wait until the port is ready.
	 */
	mutex_unlock(&port->mutex);
	if (retval == 0)
		retval = tty_port_block_til_ready(port, tty, filp);

fail:
	return retval;

可以看到这个函数调用一个uart_startup函数,这个函数任然是tty里面的还不涉及串口驱动层!


这个函数比较长,截取重要的部分,上篇文章中也有提到过,看第二个红色箭头所指部分:uport的ops的startup

uport类型可以从第一个箭头所指部分看到是struct uart_port类型的,一个uart_port对应的是一个串口,在这个数据结构中是针对这个串口的函数操作集,而这些函数操作集就是由串口驱动来实现的!

所以现在就是要找出串口操作集里面的start_up,而这个就要从驱动里面去找了!

又串口初始化分析可以找到串口初始化中的port是从probe这个函数获取的:


而这个数组结构定义如下:每个port代表一个串口


下面再来看看这个红色箭头所指向的串口驱动操作集里面的内容:



上面截图中信息量比较多,左边和右边的可以对比着看,一个是函数指针,一个是函数指针对应的函数名字!

至此总结一下串口open操作的函数调用关系:

open ---> tty_open(tty_ops里面的) ---> uart_open(uart_ops里面的) ---> uart_start  --->   上图中红色箭头所指部分(这个就是相当于驱动层里面的open)

下面跳转到这个函数中:


代码量不多对照着代码分析总结如下图:


以上就是整个串口打开操作的实现!


下面再来分析串口驱动的发送操作,还是先上图:


整体分析流程和open函数一样!

write---> tty_write ---> n_tty_write(线路规程里面) ---> uart_write ---> uart_start ---> 向上看第四张图,也就是驱动层对应的write操作


这里直接跳到s3c24xx_serial_start_tx这个函数:


从上面的源码中可以看到这里没有操作寄存器的发送部分!这里有个小窍门!关键之处在enable_irq(ourport->tx_irq)这个地方!

当发送中断时会有中断处理程序来处理发送!这里的只是起一个激活中断发送处理程序!


在这个函数中可以看到注册了一个发送中断处理程序!跳到这个函数里面看看


上面的代码中可以看到寄存器操作部分!总体简要总结:应用层的write串口操作最终会调用上面的s3c24xx_serial_start_tx函数,而这个函数仅仅是起一个激活发送中断的功能,具体数据发送过程又是在注册发送中断来实现的!

下面这张图就是根据上面的这个函数实现的总结:




分析完了发送,下面来分析接收read函数对应的操作:


函数整个调用流程对应的和write一样!

有了上面的基础,下面可以来思考下面的两个问题:

1. tty子系统是如何响应用户的读数据请求?

2. 串口驱动又是如何来接收处理的?


其实是同write操作一样!下面还是简要的分析一下:

做为响应用户空间的read函数的第一个结点还是struct  file_operations结构中的tty_read:


下面跳到这个函数里面来看看源码:


红色箭头部分可以看到这一行其实是调用了线路规程里面的read,ops的数据类型:


再来看看read所在的结构体类型:


其实这个被调用的read函数对应的是线路规程里面的read.

下面再来看看线路规程struct tty_ldisc_ops tty_ldisc_N_TTY这个结构:


可以看到这里tty_read又由线路规程里面的n_tty_read来响应!

n_tty_read这个函数代码比较多!这里暂不截全图!只分析其中比较重要的三个部分


箭头所指部分是设置应用程序这个进程为阻塞状态!(这行代码还不会立即阻塞)

然后箭头下面的第二个if语句里面有个判断,input_available_p判断是否有数据读!


当没有数据可读的时候,将会阻塞,不会被CPU调度占用CPU。结合上面的就是如果没数据就让其阻塞生效

如果有数据将会从read_buf中读走数据


看看这个函数内部实现:


其实这个read_buf和驱动是紧密相关的,当驱动里面有数据的时候,驱动就将数据往read_buf里面送!下面再来看驱动是怎么收到数据的!

还是和write函数一样在驱动模块初始化里面的有个注册发送中断函数,然后跳到s3c24xx_serial_startup函数

在这个函数里面有个request_irq函数,这个函数里面其中一个函数指针参数就是s3c24xx_serial_rx_chars函数



分析完了下面来着手撸驱动代码了!!!这个只是在原有代码的基础上根据上面的分析流程来自己实现串口驱动的重要部分:

这里来实现两个最核心的功能:

1. 串口驱动发送中断处理程序

2. 串口驱动接收中断处理程序


打开samsung.c,找到static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)发送中断函数,将里面的代码删除,然后根据上面的分析流程实现!


发送中断处理程序代码:


第一步:1. 判断x_char是否为0,如果不为0,则发送x_char


x_char成员在port结构中,为xon或者xoff(这个是和流控相关的)

<span style="font-size:18px;">static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
	struct s3c24xx_uart_port *ourport = id;
	struct uart_port *port = &ourport->port;
	struct circ_buf *xmit = &port->state->xmit;//循环缓冲
	int count = 256;
	
	//1. 判断x_char是否为0,如果不为0,则发送x_char
	if(port->x_char)
	{
		wr_regb(port, S3C2410_UTXH,  port->x_char);//发送一个字符实际上就是将数据写到UTXH寄存器里面
		goto out;
	}
	
	//2. 判断发送缓冲是否为空或者驱动被设置为停止发送的状态 则取消发送
	if( (uart_circ_empty(xmit)) || (uart_tx_stopped(port)) )
	{
		s3c24xx_serial_stop_tx(port);
		goto out;
	}
	
	//3. 循环发送,循环条件:发送缓冲不为空
	while( (!uart_circ_empty(xmit)) || (count--) > 0 )
	{
		//3.1 发送fifo如果满,退出发送
		if( rd_regl(port, S3C2410_UFSTAT) & (1 << 14) )//这里要查datasheet UFSTAT寄存器14位
			break;
				
		//3.2 将要发送的字符写入发送寄存器
		wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);//从尾巴里面取出数据
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);//循环,如果到最后一位又从第一位开始发送
		
		//3.3 修改循环缓冲的尾部位置
		port->icount.tx++;//更新发送的统计量
	}
	
	//4. 如果发送缓冲中的剩余数据量uart_circ_chars_pending<256
	//则唤醒之前阻塞的发送进程uart_write_wakeup
	if (uart_circ_chars_pending(xmit) < 256)
		uart_write_wakeup(port);

	
	//5. 如果发送缓冲为空,则关闭发送使能
	if (uart_circ_empty(xmit))
		s3c24xx_serial_stop_tx(port);
		
out:
	
	return IRQ_HANDLED;//函数出口,表示中断已经处理
}</span>
上面的代码除了参考之前的分析流程图还有之前的源码,还有datasheet,这里就不一一截图祥举了!

然后make uImage ARCH=arm COMPELE_CROSS=arm-linux-编译内核源码!将uImage下载到开发板启动内核!会发现这里有个小问题!不过对照内核源码看看可以解决!

串口驱动接收中断处理程序:



s3c24xx_serial_rx_chars1111(int irq, void *dev_id)
{
	struct s3c24xx_uart_port *ourport = dev_id;
	struct uart_port *port = &ourport->port;
	struct tty_struct *tty = port->state->port.tty;
	unsigned int ufcon, ch, flag, ufstat, uerstat;
	int max_count = 64;//一次最多接收的字符数

	while (max_count-- > 0) {
		ufcon = rd_regl(port, S3C2410_UFCON);//1. 读取UPCON寄存器
		ufstat = rd_regl(port, S3C2410_UFSTAT);//2. 读取UPSTAT寄存器
		
		//3. 如果接收fifo里的数据量为0,则退出
		if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
			break;

		uerstat = rd_regl(port, S3C2410_UERSTAT);//4. 读取UERSTAT寄存器
		ch = rd_regb(port, S3C2410_URXH);//取出字符

		if (port->flags & UPF_CONS_FLOW) {//6. 流控制处理
			int txe = s3c24xx_serial_txempty_nofifo(port);

			if (rx_enabled(port)) {
				if (!txe) {
					rx_enabled(port) = 0;
					continue;
				}
			} else {
				if (txe) {
					ufcon |= S3C2410_UFCON_RESETRX;
					wr_regl(port, S3C2410_UFCON, ufcon);
					rx_enabled(port) = 1;
					goto out;
				}
				continue;
			}
		}

		/* insert the character into the buffer */

		flag = TTY_NORMAL;
		port->icount.rx++;

		if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) {
			dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n",
			    ch, uerstat);

			/* check for break */
			if (uerstat & S3C2410_UERSTAT_BREAK) {
				dbg("break!\n");
				port->icount.brk++;
				if (uart_handle_break(port))
				    goto ignore_char;
			}

			if (uerstat & S3C2410_UERSTAT_FRAME)
				port->icount.frame++;
			if (uerstat & S3C2410_UERSTAT_OVERRUN)
				port->icount.overrun++;

			uerstat &= port->read_status_mask;

			if (uerstat & S3C2410_UERSTAT_BREAK)
				flag = TTY_BREAK;
			else if (uerstat & S3C2410_UERSTAT_PARITY)
				flag = TTY_PARITY;
			else if (uerstat & (S3C2410_UERSTAT_FRAME |
					    S3C2410_UERSTAT_OVERRUN))
				flag = TTY_FRAME;
		}

		if (uart_handle_sysrq_char(port, ch))
			goto ignore_char;
		
		//9. 将接收到的字符发送到串口驱动的buf
		uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,
				 ch, flag);

 ignore_char:
		continue;
	}
	//10. 把串口驱动收到的数据发送到线路规程的read_buf
	tty_flip_buffer_push(tty);

 out:
	return IRQ_HANDLED;
}
还是表示鸭梨山大!任重道远!


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值