1. 程式人生 > >串列埠驅動程式設計詳解---串列埠開啟、傳送、接收(下)

串列埠驅動程式設計詳解---串列埠開啟、傳送、接收(下)

上一篇部落格分析了串列埠驅動初始化部分,下面逐步分析串列埠驅動中的開啟串列埠,資料傳送和接收!

初始化主要工作流程:


先來分析串列埠開啟操作流程,還是先上圖:


這裡分析還是離不開上篇部落格中的兩張重要的圖:


串列埠操作重要的資料結構:


由上一篇串列埠驅動分析可知在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;
}
還是表示鴨梨山大!任重道遠!