2015年9月2日 星期三

在一般 AP 中模擬 kernel 的 module_init() 方式註冊功能.

    最近要寫一堆測試程式, 並且將來可能會增加 , 也會隨著某些機種不同而, 有些測試項目可能會移除,增加.

    我們的機器雖然是 linux , 但並非相同 (亂改了很多東西)  , 最頭痛的就是 key pad input 介面.這個介面不是使用 linux 的 input 方式, 反而有點像一般 MCU 的 getkey() 方式.
所以就比較難寫 AP. 要不時的檢查有沒有 key input , 導致正個 Test AP 結構混亂.

所以我想了一個方式, 這個方式有點像是 event-driven 方式, 每個測試程式當作一個 Frame (因為有自己的選單, 操作介面 , 所以我暫時稱為 Frame). 每個 Frame 有固定下列幾個 even-driven 介面: 


       void *frame_data;

        int (*init_func)(void *arg);
        int (*event_func)(void *arg,int key);
        int (*timer_tick)(void *arg);
        int (*exit_func)(void *arg);


簡單說明一下:
     init_func => initial frame ,當這個 frame 被致能 (active) 的時候會先呼叫這個 initial .

     event_func => 當有 key input 的時候會將 key value 透過這個 function 傳到 active 的 frame.

      timer_tick  => 每 一小段時間 (不是很準) 就會驅動這個 tick function , 可以在這個 function 中作一些 polling  , timeout wait 等動作.

     exit_func  => 當這個 Frame 被 inactive 的時候會呼叫這個 function , 可以釋放 一些 resource .

    主題來了, 每個測試 Frame 可能會增加 , 抽換 . 我想要做到每個測試 frame 都獨立, 並可以在 compile 的時候選擇要不要加入.想了想好像 kernel 的 driver init  動作 (module_init() function ). 如果我可以在 AP 中模擬 kernel 的 module_init() 方式 , 或許可以解決我的問題.

    網路上google 了很多有關 kernel module_init() 的方式和 Trace 了一下 kernel source code 後, 發現也蠻簡單. 其實就是利用 compiler 的時候將 init 的 function point 放到一個 特定的 section 中, booting 過程由這 section 中取出 function point 然後執行它即可.


    首先定義 frame_init() 的 macro function , 如下:

  typedef void (*initlist_func)(void);
#define __initlist      __attribute__((section("initlist")))
#define frame_init(fn) \
        initlist_func __initlist_##fn##__               __initlist = fn


extern initlist_func __start_initlist;
extern initlist_func __stop_initlist;


    要使用 __attribute__ + section, section 的名稱最好前面不要有 .xxxx , 通常 .xxxx 為預定的
section , 如 .code .text .data 等.
另外宣告兩個 start & stop point , 一般 GCC 會自動產生這兩個 point, 只要宣告拿來用即可.

    使用上就是將init function 用 farme_init() 來宣告, 內容將 event-driven 的 function call 放入即可  , 如下:


 void main_frame_register(void)
{
struct frame_function info;

        DEBUG_MSG("");

//      info.frame_index = -1;                  // Not need specifity.
        info.frame_layer = 0;
        info.frame_name = "MAIN_FRAME",
        info.frame_item = NULL,                 // main menu is first frame , not need title.

//---- functions of main frame.
        info.frame_data = (void *)&main_menu_data,
        info.init_func  = main_init,
        info.event_func = main_event,
        info.timer_tick = NULL,
        info.exit_func  = main_exit,


        Register_Frame(&info);
}

frame_init(main_frame_register);



     在程式啟動的時候進行這些 frame_init() code 的執行, code 如下:

         for (init_fc_p = &__start_initlist ;
                         init_fc_p != &__stop_initlist ; init_fc_p ++)
                        (*init_fc_p)();


    這樣就可以模擬 kernel 的 module_init() 的功能了,接下來就是處理如何 切換 frame , 和 派送 event-driven 對應的 function call 了.







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 要考慮一下整個系統的狀況 !!








2015年1月5日 星期一

ioctl command EVIOCGRAB of linux input layer.

     最近這一週都在處理有關 Linux input 問題,詳細就不說了,主要這次對 /dev/input/eventX 做了稍微詳細的測試一下.

     有關取 key 的方式就是open & read ,這也是一般方式,問題是open 兩次得到兩個handle id 分別對這兩個去讀取會得到啥呢?一個按鍵的值會給哪一個呢? 結果是兩者都會收到 , 並且先 open 先收到 , 這樣說可能不是很清楚 , 舉個例子說明吧. 
    先利用  open  開啟 input device , 並且不要關閉(close) ,然後輸入 A,B,C,D & E , 接著再open input device 一次 , 然後 輸入 F,G,H,U,J & K , 接著讀取兩個 handle file ID . 第一個 open 的會讀取到 A,B,C,D,E,F,G,H,I,J & K  , 第二次 open 的會讀取到 F,G,H,I,J & K . 這樣應該了解我說的先 open 先讀到的意思了吧 !  想一想, 這樣的邏輯也對 , open 一次就是新的一個 file , 之前已經過去的 key ( open 之前) 當然收不到 . 
  
    問題又來了 , 這樣會導致兩個 task 都收到 key , 這樣的情況下 ,  我能只限定某個 task 收到 , 另外一個不要收到嗎 ?? 這就是今天的主題了.

    可以利用今天說的主題 EVIOCGRAB ioctl command 來進行這樣的控制 , 對於想要 "獨佔" 的 device file 下 EVIOCGRAB command , 就可以確保只有它收到 , 其它有 open 的 device 不會收到. 如果兩者都下 EVIOCGRAB command , 就看誰先了, 先的就 "獨占" .

    下面就是我測試的  sample code (部份), 會發現只有第二次 open 的會讀到輸入值.

    devfd_1 = open(key_dev, O_RDONLY | O_NONBLOCK);
    devfd = open(key_dev, O_RDONLY | O_NONBLOCK);

    ioctl(devfd, EVIOCGRAB,1);
    ioctl(devfd_1, EVIOCGRAB,1);

    for ( i = 0 ; i < 20 ; i ++ )
    {
        readlen = read(devfd,&inputdata,sizeof(struct input_event));
        if ( readlen == sizeof(struct input_event)  && (inputdata.type == EV_KEY) )
        {
            printf("Keypad code:%d",inputdata.code);

            if (inputdata.value == 1)
                printf(" Down!\n");
            else if (inputdata.value == 0)
               printf(" Up!\n");
            else if (inputdata.value == 2 )
                printf(" PRESSED !\n");
           
        }

        readlen = read(devfd_1,&inputdata,sizeof(struct input_event));
        if ( readlen == sizeof(struct input_event)  && (inputdata.type == EV_KEY) )
        {
            printf("2 Keypad code:%d",inputdata.code);
            if (inputdata.value == 1)
                printf(" Down!\n");
            else if (inputdata.value == 0)
                printf(" Up!\n");
            else if (inputdata.value == 2 )
                printf(" PRESSED !\n");
         }

    }

    close(devfd);
    close(devfd_1);