2017年8月3日 星期四

message queue in shell/bash


    最近在寫些測試程式(Shell on busybox),發現有些動作需要到背景執行,但是一些執行結果要回傳給主程序,或是一些共用變數都很麻煩,背景的Shell (背景可能很多個,分測試不同項目) 和主程序是兩個獨力的Task ,要互通只有用IPC了,當然可以用檔案方式,但是感覺不是很優!
    Shell 要使用 IPC ,有些麻煩了!還好Open Source 就有好處,google 了一些後發現下列網頁:

https://blog.garage-coding.com/2016/02/05/bash-fifo-jobqueue.html

    其中使用Message queue 的方式比較適用我的狀況!
     ipcmd 需要到GitHub 去下載Source code,編譯後就可以用了!

https://github.com/nathanweeks/ipcmd

    整個機本原理就是用C 完成message queue 的動做(ipcmd) ,然後在 shell 中使用。

2016年7月11日 星期一

dmix of ALSA library.

最近工作上需要讓 不同的兩個 AP 都能透過 ALSA 同時撥 .WAV file, 也就是需要兩個來源混音.

Google 了一下, 發現 ALSA Lib 就有內建 dmix (direct mixing) 的功能.

http://www.alsa-project.org/main/index.php/Asoundrc

These days we have a native plugin for ALSA called the dmix (direct mixing) plugin. It allows software mixing in an easy to use syntax and without the hassle of installing/understanding a new application first.


也有很多網頁說明要填寫 /etc/asound.conf , 我按照網頁的填寫方式後用 aplay 去驗證發現下列的問題:

ALSA lib pcm_direct.c:812:(snd_pcm_direct_initialize_slave) requested or auto-format is not available
ALSA lib pcm_dmix.c:831:(snd_pcm_dmix_open) unable to initialize slave 


追蹤 了一下 error code 的來源發生在下列片段內, 主要是沒有辦法設定格式,檢查了aousnd.conf 也看不出任何錯誤.

int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params)
{


........................................................
    ret = snd_pcm_hw_params_set_format(spcm, hw_params, params->format);
    if (ret < 0) {
        static const snd_pcm_format_t dmix_formats[] = {
            SND_PCM_FORMAT_S32,
            SND_PCM_FORMAT_S32 ^ SND_PCM_FORMAT_S32_LE ^ SND_PCM_FORMAT_S32_BE,
            SND_PCM_FORMAT_S16,
            SND_PCM_FORMAT_S16 ^ SND_PCM_FORMAT_S16_LE ^ SND_PCM_FORMAT_S16_BE,
            SND_PCM_FORMAT_S24_3LE,
        };
         snd_pcm_format_t format;
        unsigned int i;

        for (i = 0; i < sizeof dmix_formats / sizeof dmix_formats[0]; ++i) {
            format = dmix_formats[i];
            ret = snd_pcm_hw_params_set_format(spcm, hw_params, format);
            if (ret >= 0)
                break;
        }
        if (ret < 0 && dmix->type != SND_PCM_TYPE_DMIX) {
            /* TODO: try to choose a good format */
            ret = INTERNAL(snd_pcm_hw_params_set_format_first)(spcm, hw_params, &format);
        }
        if (ret < 0) {
            SNDERR("requested or auto-format is not available");
            return ret;
        }
        params->format = format;

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



Google 上也沒有找到說明很清楚這樣的問題點出現在哪部份.

經過幾天的 trial and error 發現, 主要是我的 CODEC driver 並不支援dmix 所需要的 format.
dmix 只支援下列格式(由 code可以知道) .

SND_PCM_FORMAT_S32,
SND_PCM_FORMAT_S32 ^ SND_PCM_FORMAT_S32_LE ^ SND_PCM_FORMAT_S32_BE,
SND_PCM_FORMAT_S16,
SND_PCM_FORMAT_S16 ^ SND_PCM_FORMAT_S16_LE ^ SND_PCM_FORMAT_S16_BE,
SND_PCM_FORMAT_S24_3LE,



所以就先修改我的CODEC driver , 讓它支援 S16_LE (之前只有 支援 U16_LE ).
這個 bug 就解決了 , 並且 asound.conf 也可以讓 "default" 順利的轉向到 dmix 上.

所以 dmix 如果出現
ALSA lib pcm_direct.c:812:(snd_pcm_direct_initialize_slave) requested or auto-format is not available

錯誤 , 要先看看 codec driver 是否支援 dmix 需要的格式.


PS: 有關 asound.conf 可以參考下列網頁 , 我認為寫的比較清楚的.

       http://forums.gentoo.tw/viewtopic.php?f=18&t=44507







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);





2014年6月10日 星期二

The pthread_cond_signal() VS pthread_cond_broadcast()

上一篇說到了 pthread_cond_wati() , 現在說說 pthread_cond_signal() & pthread_cond_brocast() 吧 .

基本上這兩個 都是傳 condiction 給 ptherad , 讓 pthread 由 wait 狀態 wakeup 起來 . 不過有些差別 pthread_cond_signal() 只會讓一個 pthread wakeup (等待同一個 condition 的 pthread) ,至於是哪一個 pthread wakeup 呢 ? 目前我沒有去詳細 研究 , 不過這個signal 不能確定(指定)那一個 pthread , 所以使用上要小心點. 而 boradcast 是讓所有 pthread 都 wakeup 起來 .

所以依照所需選擇合適的function. 我通常都只有一個 ptherad 在等待 , 所以我都用 brocast , 確保我的 pthread 可以 wakeup .

下面有參考範例 ,



Reference :

    http://publib.boulder.ibm.com/iseries/v5r1/ic2987/index.htm?info/apis/users_76.htm
    http://publib.boulder.ibm.com/iseries/v5r1/ic2987/index.htm?info/apis/users_73.htm

PS. 目前比較忙沒有時間按照範例測試.

2014年6月4日 星期三

The pthread_cond_wait() and pthread_cond_timedwait()

最近在弄 power daemon ,需要使用大量 thread , 並且不能讓這些 thread " free running " , 所以再次研究一下 pthread_cond_wait() & pthread_cond_timedwait() !

先來看看 pthread_cond_wait() 的說明吧 ! (下列 藍色文字擷取於 man 說明)

pthread_cond_wait() 的動作會先取得 cond 的內容 , 然後 unlock 讓 ptherad_cond_signal() 可以更改 cond 的內容,接著再 lock , 並判斷是否需要繼續 ptherad , 如果沒有就 unlock 並停下 pthread, 相反就 lock 且接著 pthread 的內容.

        pthread_cond_wait(mutex, cond):
                  value = cond->value; /* 1 */
                  pthread_mutex_unlock(mutex); /* 2 */
                  pthread_mutex_lock(cond->mutex); /* 10 */
                  if (value == cond->value) { /* 11 */
                      me->next_cond = cond->waiter;
                      cond->waiter = me;
                      pthread_mutex_unlock(cond->mutex);
                      unable_to_run(me);
                  } else
                      pthread_mutex_unlock(cond->mutex); /* 12 */
                  pthread_mutex_lock(mutex); /* 13 */


我們再看看 pthread_cond_signal() 的動作吧.
首先會先 lock , 如果 lock 失敗會繼續等在 lock 階段, lock 成功會更動 cond 內容,並且叫醒 pthread. 然後 unlock 讓 pthread 可以繼續執行.


         pthread_cond_signal(cond):
                  pthread_mutex_lock(cond->mutex); /* 3 */
                  cond->value++; /* 4 */
                  if (cond->waiter) { /* 5 */
                      sleeper = cond->waiter; /* 6 */
                      cond->waiter = sleeper->next_cond; /* 7 */
                      able_to_run(sleeper); /* 8 */
                  }
                  pthread_mutex_unlock(cond->mutex); /* 9 */


說這麼多 ,來個範例吧 : 
下列兩個範例 , 一個是 pthread 本身 , 做完事情後等著 pthread_cond_signal() 來進行離開.
所以可以看到  pthread 執行時先 lock , 最後停在 pthread_cond_wait() . 等另外 function 
送出 pthread_cond_signal() , pthread 就會繼續執行,並且離開 pthread.
 

void *ps_event_handler ( void *argc )
{


   pthread_mutex_lock(&thread_mutex);

    /* Loop forever */
    for ( ps_thread_status = 1; ps_thread_status;  )
    {
          //---- TO DO.


            pthread_cond_wait(&thread_cond, &thread_mutex);
    
     }   /* End of for() */

   pthread_mutex_unlock(&thread_mutex);
  
   pthread_exit ( 0 );
}


void ps_thread_destory(void)
{
    ps_thread_status = 0;

    pthread_cond_signal(&thread_cond);      // force weakup thread , then stop itself.

    pthread_join(thread_ps_id, NULL);       // wait for thread end.

    pthread_mutex_destroy(&thread_mutex);
    pthread_cond_destroy(&thread_cond);
}


如果想要讓 pthread 暫停一段時間 後繼續執行 , 那就需要 pthread_cond_timedwait() 了.
範例如下 , 每一秒執行 for loop 一次.(紅色部分 是修改)
利用 pthread_cond_timedwait() return 的 value , 就可以判別是 timeout 還是被 signal 叫醒.


void *ps_event_handler ( void *argc )
{


   pthread_mutex_lock(&thread_mutex);

    /* Loop forever */
    for ( ps_thread_status = 1; ps_thread_status;  )
    {
          //---- TO DO.


            gettimeofday(&tv, &tz);
            ts.tv_sec  = tv.tv_sec + 1 ;
            ts.tv_nsec = tv.tv_usec*1000;
            retval = pthread_cond_timedwait(&thread_cond, &thread_mutex, &ts);


        if ( retval  == ETIMEDOUT)
        {
        //==== time out.

        //---- TO DO.


        }
        else
        {
        //==== Got signal.

        //---- TO DO.


        }
    
     }   /* End of for() */

   pthread_mutex_unlock(&thread_mutex);
  
   pthread_exit ( 0 );
}



這樣的架構下就可以控制 pthread 進行一些流程控制了.