Linux串口驱动(5) - read详解
生活随笔
收集整理的這篇文章主要介紹了
Linux串口驱动(5) - read详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1. 用戶空間read的操作實現
static ssize_t tty_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {int i;struct tty_struct *tty = file_tty(file);struct tty_ldisc *ld;ld = tty_ldisc_ref_wait(tty); /* 獲取tty對應的線路規程ldisc,和tty_write是一樣的,可以回看《Linux串口驅動(4) - write詳解》 */if (ld->ops->read)i = (ld->ops->read)(tty, file, buf, count); /* ld->ops->read即n_tty_read */elsei = -EIO;tty_ldisc_deref(ld);return i; }static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,unsigned char __user *buf, size_t nr) {struct n_tty_data *ldata = tty->disc_data;unsigned char __user *b = buf;DECLARE_WAITQUEUE(wait, current);if (!ldata->icanon) /*icanon默認為1,icanon表示標準模式*/······add_wait_queue(&tty->read_wait, &wait);while (nr) {set_current_state(TASK_INTERRUPTIBLE);if (!input_available_p(tty, 0)) { /*沒有可讀數據時會休眠*/timeout = schedule_timeout(timeout); /*定時休眠*/continue;}__set_current_state(TASK_RUNNING);if (ldata->icanon && !L_EXTPROC(tty)) {while (nr && ldata->read_cnt) {int eol;eol = test_and_clear_bit(ldata->read_tail,ldata->read_flags);c = ldata->read_buf[ldata->read_tail]; /*從tty線路規程的緩沖區ldata->read_buf讀數據*/ldata->read_tail = ((ldata->read_tail+1) &(N_TTY_BUF_SIZE-1));ldata->read_cnt--;raw_spin_unlock_irqrestore(&ldata->read_lock, flags);if (!eol || (c != __DISABLED_CHAR)) {if (tty_put_user(tty, c, b++)) { /*將讀到的數據放入用戶buffer*/retval = -EFAULT;b--;raw_spin_lock_irqsave(&ldata->read_lock, flags);break;}nr--;}raw_spin_lock_irqsave(&ldata->read_lock, flags);}raw_spin_unlock_irqrestore(&ldata->read_lock, flags);} }remove_wait_queue(&tty->read_wait, &wait);return retval; }????????write的數據為什么是從tty線路規程的buffer里讀取,這一點可以回看《Linux串口驅動(3) - open詳解》的分析線路2-3部分。
2. 總結
2.1 DMA
????????關于DMA搬運地址的配置,在open時會同時配置發送消息時DMA搬運的目標地址和接收消息時DMA搬運的源地址。因為發送數據時要將數據搬運到哪個地址是確定的,但是從哪個地址搬運數據是不確定的,接收數據時則反之。
????????不確定的那個地址,會在啟動DMA搬運的時候進行配置。
2.2 write和read的不同
????????write是SOC端主動發起的動作,所以DMA搬運的啟動操作是在write函數的底層操作里調用的;而read是SOC端的被動操作,所以串口在open的時候就要啟動DMA搬運,將數據搬運到tty 線路規程的一個buffer里,用戶讀的時候不用去底層讀取,去線路規程的buffer里讀取即可。
3.?DMA模式
static int start_rx_dma(struct imx_port *sport) {struct dma_chan *chan = sport->dma_chan_rx;struct dma_async_tx_descriptor *desc;desc = dmaengine_prep_dma_cyclic(chan, sport->rx_buf.dmaaddr,sport->rx_buf.buf_len, sport->rx_buf.period_len,DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);desc->callback = dma_rx_callback; //DMA完成一次搬運后,會調用這個回調函數dmaengine_submit(desc); //將該描述符插入dmaengine驅動的傳輸隊列dma_async_issue_pending(chan); //啟動對應DMA通道上的傳輸sport->dma_is_rxing = 1;return 0; }static void dma_rx_callback(void *data) {dma_rx_work(sport); }static void dma_rx_work(struct imx_port *sport) {struct tty_struct *tty = sport->port.state->port.tty;unsigned int cur_idx = sport->rx_buf.cur_idx;dma_rx_push_data(sport, tty, 0, cur_idx); / *最終串口接收到的數據都被放到了tty ldisc的一個buffer里,用戶空間讀的時候會從這個buffer里取* / }4. 非DMA模式
//如果在open階段置位了中斷使能位,所以當RX FIFO有數據的時候會觸發中斷 static irqreturn_t imx_int(int irq, void *dev_id) {struct imx_port *sport = dev_id;unsigned int sts;unsigned int sts2;sts = readl(sport->port.membase + USR1);if ((sts & USR1_RRDY || sts & USR1_AGTIM) &&!sport->dma_is_enabled) {if (sts & USR1_AGTIM)writel(USR1_AGTIM, sport->port.membase + USR1);imx_rxint(irq, dev_id);}if (sts & USR1_TRDY &&readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN)imx_txint(irq, dev_id);return IRQ_HANDLED; }static irqreturn_t imx_rxint(int irq, void *dev_id) {struct imx_port *sport = dev_id;unsigned int rx, flg, ignored = 0;struct tty_port *port = &sport->port.state->port;unsigned long flags, temp;spin_lock_irqsave(&sport->port.lock, flags);while (readl(sport->port.membase + USR2) & USR2_RDR) {flg = TTY_NORMAL;sport->port.icount.rx++;//讀取RX FIFO中的數據rx = readl(sport->port.membase + URXD0);temp = readl(sport->port.membase + USR2);if (temp & USR2_BRCD) {writel(USR2_BRCD, sport->port.membase + USR2);if (uart_handle_break(&sport->port))continue;}if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))continue;//將讀取的單個字節數據放到port的緩沖區中tty_insert_flip_char(port, rx, flg);}out:spin_unlock_irqrestore(&sport->port.lock, flags);//將port緩沖區中的數據全部拷貝至tty線路規程的緩沖區tty_flip_buffer_push(port); return IRQ_HANDLED; }總結
以上是生活随笔為你收集整理的Linux串口驱动(5) - read详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数学建模(一)—— 人口增长模型的确定
- 下一篇: 【Linux驱动开发】串口