日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux下tty串口驱动数据的发送、接收过程源码实例详解

發(fā)布時間:2024/8/1 linux 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux下tty串口驱动数据的发送、接收过程源码实例详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

簡介

本文主要時講解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(),具體代碼如下:

/*** n_tty_read - read function for tty* @tty: tty device* @file: file object* @buf: userspace buffer pointer* @nr: size of I/O** Perform reads for the line discipline. We are guaranteed that the* line discipline will not be closed under us but we may get multiple* parallel readers and must handle this ourselves. We may also get* a hangup. Always called in user context, may sleep.** This code must be sure never to sleep through a hangup.*/static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,unsigned char __user *buf, size_t nr) {unsigned char __user *b = buf;DECLARE_WAITQUEUE(wait, current);int c;int minimum, time;ssize_t retval = 0;ssize_t size;long timeout;unsigned long flags;int packet;do_it_again:BUG_ON(!tty->read_buf);c = job_control(tty, file);if (c < 0)return c;minimum = time = 0;timeout = MAX_SCHEDULE_TIMEOUT;if (!tty->icanon) { //默認(rèn)為icanon則該if不會執(zhí)行time = (HZ / 10) * TIME_CHAR(tty);minimum = MIN_CHAR(tty);if (minimum) {if (time)tty->minimum_to_wake = 1;else if (!waitqueue_active(&tty->read_wait) ||(tty->minimum_to_wake > minimum))tty->minimum_to_wake = minimum;} else {timeout = 0;if (time) {timeout = time;time = 0;}tty->minimum_to_wake = minimum = 1;}}/** Internal serialization of reads.*/if (file->f_flags & O_NONBLOCK) {if (!mutex_trylock(&tty->atomic_read_lock))return -EAGAIN;} else {if (mutex_lock_interruptible(&tty->atomic_read_lock))return -ERESTARTSYS;}packet = tty->packet; //=0,未初始化該成員//等待隊(duì)列的操作add_wait_queue(&tty->read_wait, &wait);while (nr) {/* First test for status change. */if (packet && tty->link->ctrl_status) {//packet為0,if不滿足unsigned char cs;if (b != buf)break;spin_lock_irqsave(&tty->link->ctrl_lock, flags);cs = tty->link->ctrl_status;tty->link->ctrl_status = 0;spin_unlock_irqrestore(&tty->link->ctrl_lock, flags);if (tty_put_user(tty, cs, b++)) {retval = -EFAULT;b--;break;}nr--;break;}/* This statement must be first before checking for inputso that any interrupt will set the state back toTASK_RUNNING. */set_current_state(TASK_INTERRUPTIBLE);if (((minimum - (b - buf)) < tty->minimum_to_wake) &&((minimum - (b - buf)) >= 1))tty->minimum_to_wake = (minimum - (b - buf));//此if保證用戶空間的讀進(jìn)程一直要等待數(shù)據(jù)可讀,否則一直睡眠if (!input_available_p(tty, 0)) {if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {retval = -EIO;break;}if (tty_hung_up_p(file))break;if (!timeout)break;if (file->f_flags &O_NONBLOCK) {//無數(shù)據(jù)可讀、且非阻塞打開,代表設(shè)備正“忙”,則直接返回退出retval = -EAGAIN;break;}if (signal_pending(current)) {//本線程是因?yàn)樾盘柖粏拘?#xff0c;不是由于數(shù)據(jù)可讀,也要直接返回退出retval = -ERESTARTSYS;break;}/* FIXME: does n_tty_set_room need locking ? */n_tty_set_room(tty);timeout = schedule_timeout(timeout);continue;}__set_current_state(TASK_RUNNING);/* Deal with packet mode. */if (packet && b == buf) {if (tty_put_user(tty, TIOCPKT_DATA, b++)) {retval = -EFAULT;b--;break;}nr--;}if (tty->icanon) {/* N.B. avoid overrun if nr == 0 */while (nr && tty->read_cnt) {int eol;eol = test_and_clear_bit(tty->read_tail,tty->read_flags);c = tty->read_buf[tty->read_tail];spin_lock_irqsave(&tty->read_lock, flags);tty->read_tail = ((tty->read_tail+1) &(N_TTY_BUF_SIZE-1));tty->read_cnt--;if (eol) {/* this test should be redundant:* we shouldn't be reading data if* canon_data is 0*/if (--tty->canon_data < 0)tty->canon_data = 0;}spin_unlock_irqrestore(&tty->read_lock, flags);if (!eol || (c != __DISABLED_CHAR)) {if (tty_put_user(tty, c, b++)) {retval = -EFAULT;b--;break;}nr--;}if (eol) {tty_audit_push(tty);break;}}if (retval)break;} else {int uncopied;/* The copy function takes the read lock and handleslocking internally for this case */uncopied = copy_from_read_buf(tty, &b, &nr);uncopied += copy_from_read_buf(tty, &b, &nr);//投遞數(shù)據(jù)到用戶空間if (uncopied) {retval = -EFAULT;break;}}/* If there is enough space in the read buffer now, let the* low-level driver know. We use n_tty_chars_in_buffer() to* check the buffer, as it now knows about canonical mode.* Otherwise, if the driver is throttled and the line is* longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,* we won't get any more characters.*/if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {n_tty_set_room(tty);check_unthrottle(tty);}if (b - buf >= minimum)//由于minimum=0,因此只要有1個以上的數(shù)據(jù)就返回break;if (time)timeout = time;}mutex_unlock(&tty->atomic_read_lock);remove_wait_queue(&tty->read_wait, &wait);//刪除隊(duì)列if (!waitqueue_active(&tty->read_wait))tty->minimum_to_wake = minimum;__set_current_state(TASK_RUNNING);size = b - buf;if (size) {retval = size;if (nr)clear_bit(TTY_PUSH, &tty->flags);} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))goto do_it_again;n_tty_set_room(tty);return retval; }

該函數(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)行的

static irqreturn_t s3c24xx_serial_rx_chars(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);ufstat = rd_regl(port, S3C2410_UFSTAT);if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)break;uerstat = rd_regl(port, S3C2410_UERSTAT);ch = rd_regb(port, S3C2410_URXH);if (port->flags & UPF_CONS_FLOW) {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;uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN,ch, flag);ignore_char:continue;}tty_flip_buffer_push(tty);out:return IRQ_HANDLED; }

該部分源碼分析:
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)用

static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {struct tty_struct *tty;struct inode *inode = file->f_path.dentry->d_inode;ssize_t ret;struct tty_ldisc *ld;tty = (struct tty_struct *)file->private_data;ld = tty_ldisc_ref_wait(tty);if (!ld->ops->write)ret = -EIO;else/* 調(diào)用 線路規(guī)程 n_tty_write 函數(shù) */ret = do_tty_write(ld->ops->write, tty, file, buf, count);tty_ldisc_deref(ld);return ret; }

然后調(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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。