上一篇博客分析了串口驅動初始化部分,下面逐步分析串口驅動中的打開串口,數據發送和接收!
初始化主要工作流程:
先來分析串口打開操作流程,還是先上圖:
這裏分析還是離不開上篇博客中的兩張重要的圖:
串口操作重要的數據結構:
由上一篇串口驅動分析可知在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;
}
還是表示鴨梨山大!任重道遠!