Linux下tty串口驱动数据的发送、接收过程源码实例详解
簡介
本文主要時講解tty驅(qū)動文件的打開、數(shù)據(jù)的發(fā)送、數(shù)據(jù)的接收相關(guān)的源碼解析,從用戶層到硬件層的源碼追蹤過程。
具體的讀寫操作可先大致看一下流程圖,下面的源碼分析也依然是圍繞該流程進(jìn)行函數(shù)追蹤。
一、tty數(shù)據(jù)接收流程分析:
對于tty設(shè)備的打開操作,即用戶調(diào)用read函數(shù)來讀取設(shè)備的文件的數(shù)據(jù),首先經(jīng)過vfs層、字符設(shè)備驅(qū)動層,到達(dá)tty_open()函數(shù),經(jīng)過tty_core層、serial_core等層后,主要完成一下工作:
1、首先通過dev_t,再tty_driver鏈表中查找對應(yīng)的驅(qū)動,并返回本設(shè)備在該driver中的index即序號;
2、初始化tty_struct,建立tty_struct和tty_driver之間的關(guān)聯(lián),經(jīng)tty_struct->ldisc、tty_struct->port進(jìn)行賦值,最后調(diào)用tty_struct->ld->ops->open()設(shè)置通訊參數(shù):速率speed、校驗(yàn)位flag等等;
3、初始化tty_file_private,并將priv添加到tty_struct->tty_files;
4、調(diào)用tty_struct.ops.open()即n_tty_ops.open
=n_tty_open完成設(shè)備的打開工作,此時已在tty線路規(guī)程,最終調(diào)用uart_startup();
打開文件流程圖如下:
從流程圖中分析,再tty_read()中,代碼先通過tty_struct()得到調(diào)用線路規(guī)程中的read操作:ld->ops->read(),即n_tty_read(),具體代碼如下:
該函數(shù)就是典型的等待隊(duì)列使用套路:先查詢數(shù)據(jù)是否可用,不可用就讓出cpu,等待其他線程(即終端ISR中間調(diào)用flush_to_ldisc())來喚醒。當(dāng)硬件接收到數(shù)據(jù)后,在中斷處理之后,會將數(shù)據(jù)投遞都ldata.read_buf[]中,然后喚醒該線程,之后本線程就調(diào)用copy_from_read_buf()完成數(shù)據(jù)成ldisc層(ldata.read_buf[])至應(yīng)用程序緩沖區(qū)的復(fù)制。
static int s3c24xx_serial_startup(struct uart_port *port) {struct s3c24xx_uart_port *ourport = to_ourport(port);int ret;dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n",port->mapbase, port->membase);rx_enabled(port) = 1; // 使能接收ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,s3c24xx_serial_portname(port), ourport); // 為數(shù)據(jù)接收注冊中斷程序if (ret != 0) {printk(KERN_ERR "cannot get irq %d\n", ourport->rx_irq);return ret;}ourport->rx_claimed = 1; // 使能發(fā)送dbg("requesting tx irq...\n");tx_enabled(port) = 1;ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,s3c24xx_serial_portname(port), ourport); // 為數(shù)據(jù)發(fā)送注冊中斷程序if (ret) {printk(KERN_ERR "cannot get irq %d\n", ourport->tx_irq);goto err;}ourport->tx_claimed = 1;dbg("s3c24xx_serial_startup ok\n");/* the port reset code should have done the correct* register setup for the port controls */if (port->line == 2) {s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2);s3c2410_gpio_pullup(S3C2410_GPH(6), 1);s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2);s3c2410_gpio_pullup(S3C2410_GPH(7), 1);}return ret;err:s3c24xx_serial_shutdown(port);return ret; }上面函數(shù)主要完成下面的工作:
1、使能接收rx_enabled
2、為數(shù)據(jù)接收注冊中斷程序request_irq
3、使能發(fā)送tx_enabled
4、為數(shù)據(jù)發(fā)送注冊中斷程序request_irq
中斷處理程序喚醒底層線程:
串口接收數(shù)據(jù)是通過s3c24xx_serial_rx_chars在中斷里面進(jìn)行的
該部分源碼分析:
1、讀取UFCON寄存器;
2、讀取UFSTAT寄存器;
3、然后讀取接收fifo的數(shù)據(jù)量s3c24xx_serial_rx_fifocnt(ourport, ufstat),如果數(shù)據(jù)量為0,則退出處理;
4、讀取錯誤狀態(tài)寄存器uerstat = rd_regl(port, S3C2410_UERSTAT);
5、然后取出接收到的字符ch = rd_regb(port, S3C2410_URXH);
6、if (port->flags & UPF_CONS_FLOW),這一段代碼其實(shí)是在做流控的處理;
7、if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) ,這段代碼判斷錯誤發(fā)生的類型;
8、if (uart_handle_sysrq_char(port, ch))如果接收到的是sysrq這個特殊字符,則進(jìn)行特殊處理;
9、uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,ch, flag);這一步把接收到的字符送到串口驅(qū)動的buf中,這里面大循環(huán)就結(jié)束了;
10、tty_flip_buffer_push(tty);把串口驅(qū)動中的數(shù)據(jù)送到read_buf中;
tty_flip_buffer_push 有兩種方式調(diào)用到 flush_to_ldisc ,一種直接調(diào)用,另一種使用延時工作隊(duì)列,在很久很久以前,我們初始化了這么一個工作隊(duì)列~(tty_open 初始化 tty_struct 時前面有提到)
void tty_flip_buffer_push(struct tty_struct *tty) {unsigned long flags;spin_lock_irqsave(&tty->buf.lock, flags);if (tty->buf.tail != NULL)tty->buf.tail->commit = tty->buf.tail->used;spin_unlock_irqrestore(&tty->buf.lock, flags);if (tty->low_latency)flush_to_ldisc(&tty->buf.work.work);elseschedule_delayed_work(&tty->buf.work, 1); }二、數(shù)據(jù)發(fā)送過程分析:
寫過程也可以分為兩個線程,但與讀過程又有區(qū)別。
1、對于傳統(tǒng)的串口硬件驅(qū)動,發(fā)送中斷平時都是關(guān)閉的,只有用戶執(zhí)行寫操作后,才會主動打開TX中斷,同時,TX-ISR里自動檢測xmit狀態(tài),為空則主動關(guān)閉TX中斷;
2、對于由用戶發(fā)起的寫進(jìn)程:首先將數(shù)據(jù)從用戶buf填充至uart_state->xmit.buf[];
3、如果用戶buf數(shù)據(jù)過大(>page_size),則wait_woken()該線程,與此同時,只要xmit已滿或者用戶buf被填充完畢,就開始flush數(shù)據(jù)至TX-FIFO;
4、對于TX-ISR線程:每發(fā)出一個bite,就檢查xmit數(shù)據(jù)狀態(tài),當(dāng)xmit為空時,關(guān)閉TX中斷,當(dāng)xmit不夠時,喚醒上面的寫進(jìn)場,繼續(xù)往xmit填充數(shù)據(jù),具體流程如下:
具體函數(shù)實(shí)現(xiàn)如下:
首先時tty_core層的調(diào)用
然后調(diào)用線路規(guī)程的n_tty_write:
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);int c;ssize_t retval = 0;// 將當(dāng)前進(jìn)程添加到等待隊(duì)列add_wait_queue(&tty->write_wait, &wait);while (1) {// 設(shè)置當(dāng)前進(jìn)程為可中斷的set_current_state(TASK_INTERRUPTIBLE);if (signal_pending(current)) {retval = -ERESTARTSYS;break;}if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {retval = -EIO;break;}/* 自行定義了輸出方式 */if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {....} else {while (nr > 0) {/* 調(diào)用到 uart_write */c = tty->ops->write(tty, b, nr);if (c < 0) {retval = c;goto break_out;}if (!c)break;b += c;nr -= c;}}if (!nr)break;if (file->f_flags & O_NONBLOCK) {retval = -EAGAIN;break;}// 進(jìn)程調(diào)度 開始休眠schedule();} }最終調(diào)用uart_write函數(shù):
static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count) {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 (!uart_circ_empty(&state->xmit) && state->xmit.buf &&!tty->stopped && !tty->hw_stopped)/* 調(diào)用到最底層的 start_tx */port->ops->start_tx(port); }uart_write 又調(diào)用到了最底層的 uart_port->ops->start_tx 函數(shù)
友情鏈接:
關(guān)于tty串口驅(qū)動、設(shè)備的注冊可點(diǎn)擊該鏈接
總結(jié)
以上是生活随笔為你收集整理的Linux下tty串口驱动数据的发送、接收过程源码实例详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安全防御——防病毒网关
- 下一篇: linux 其他常用命令