2015年8月11日 星期二

Low latency TTY UART

最近在加速 uart 的傳輸 , 發現少量 資料的時候會比較慢 , 並且慢的有點離譜.
由訊號看來 , UART 的 Rx 傳輸已經結束 ,
但是我的 read(tty)卻 大約慢10 ~ 1 mS 後才會收到.

想說是否 kernel 內部在 context switch 的時候花比較久.
於是 ''超頻" 兩倍看看是否有改善, 結果差不多一樣. !?

詭異了,竟然沒有改善, 我也使用 select () 方式看看是否加快 (之前使用 read() block type),
結果也沒有..... !?

沒法了,只好進 kernel 的 tty driver 來看看了.
TTY 的詳細結構可以參考下列 pdf file , 這個 file 應該是 linux driver (脫韁野馬) 的第 18 章節.
就不在詳細說明 TTY 了.
https://www.google.com.tw/url?sa=t&rct=j&q=&esrc=s&source=web&cd=8&cad=rja&uact=8&ved=0CFwQFjAHahUKEwiKq8vWn6DHAhVHIaYKHXIFAA4&url=https%3A%2F%2Flwn.net%2Fimages%2Fpdf%2FLDD3%2Fch18.pdf&ei=Z4HJVcrbM8fCmAXyioBw&usg=AFQjCNHg8aGyIBkWXmU3itJ1JY5xVD1usQ&sig2=gE3rZ1mU8oDQMsaoT49vEA


 最後發現 UART 收到 data 後會呼叫 tty_flip_buffer_push(tty); 通知 tty layer 有 data 在 buffer 了.

tty_flip_buffer_push(tty) 內容主要有下列這段 code ( 2.6.32.19),

if (tty->low_latency)
        flush_to_ldisc(&tty->buf.work.work);
    else
        schedule_delayed_work(&tty->buf.work, 1);


答案出來了 , 因為每次都排 schedule_delay_work 去收 buffer 的資料.
我們使用的 tick 是 100 , 所以每排一次約  10 mS, 難怪我會測量到 10 ~ 1 mS,並且和 CPU clock 無關.

好 , 那就想辦法設定 tty->low_latency 為 "1" 吧, 這樣就是直接呼叫,不使用排程.

發現 在 serial_core.c 中的 ioctl 有支援修改 serial 的參數 (TIOCSSERIAL), 設定的 flage 中有
UPF_LOW_LATENCY (新版的是使用  ASYNC_LOW_LATENCY, 都是設定相同的 bit ).
測試後發現, 被 CAP_SYS_ADMIN 擋住 , 不過設定完後先 close tty 在 open tty 卻有效.

static int uart_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd,
                               unsigned long arg)

{
.............

    case TIOCSSERIAL:
        ret = uart_set_info(state, uarg);
        break;


.............. 

}



static int uart_set_info(struct uart_state *state,
             struct serial_struct __user *newinfo)

{
.....................

    if (!capable(CAP_SYS_ADMIN)) 
    {
..........
                  goto exit;

.............
                goto check_and_exit;

................
    }


..............
    if (port->tty)
        port->tty->low_latency =
            (uport->flags & UPF_LOW_LATENCY) ? 1 : 0;


..................
}



檢查 tty open 過程後 ,發現其時會將剛剛設定的 ASYNC_LOW_LATENCY 值給帶入,難怪設定後在 close / open 一次就會有效.
 
static int uart_open(struct tty_struct *tty, struct file *filp)
{
............................

    tty->low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
    tty->alt_speed = 0;

................
}

因為我的 AP 並非 root 執行 , 所以也不研究 SYS_CAP_ADMIN的問題了 , AP 端就 open /close 一次就好了 , 下列是 AP 設定 ASYNC_LOW_LATENCY 的方式.

    //---- turn on ASYNC_LOW_LATENCY mode.
        ioctl(fd, TIOCGSERIAL, &ser_info);
        ser_info.flags &= ~ASYNC_LOW_LATENCY;
#if defined(ENABLE_ASYNC_LOW_LATENCY)
        ser_info.flags |= ASYNC_LOW_LATENCY;
#endif
        ioctl(fd, TIOCSSERIAL, &ser_info);
        DEBUG_MSG("%s,Low_latency:%s ",dev_name,
                    (ser_info.flags & ASYNC_LOW_LATENCY) ? "on" : "off");
        close(fd);



修改後再次測量uart 訊號到 read(tty) 的時間 (這次使用 select() 方式去等 data ) ,
改善很多 , 整個 UART 傳輸速度也提升了 !

仔細想想 , 原本 UART Rx IRQ 用 schedule_delay_work() 排程去執行, 符合 Top and Bottom Halves 方式 , 打開 ASYNC_LOW_LATENCY 後卻是 IRQ 就直接做完, 導致有可能 UART 的 Rx IRQ 執行時間攏長.

所以要打開 ASYNC_LOW_LATENCY 要考慮一下整個系統的狀況 !!








沒有留言:

張貼留言