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 進行一些流程控制了.





2014年6月3日 星期二

EPERM error of settimeofday() , using capset() & capget() modify CAP_SYS_TIME.

最近發現 settimeofday() 沒有辦法執行 , 回報錯誤為 -1 (EPERM), 看一下 man 說明,發現沒有打開 CAP_SYS_TIME, 所以在 euid >= 1000 的時候不會允許設定系統時間. 
下列是 man settimeofday() 的說明:


       EPERM  The calling process has insufficient privilege  to  call  settimeofday();  
                     under  Linux  the  CAP_SYS_TIME  capability  is   required.

好吧 , 打開一下後門 ,  google 了一下,發現可以使用 capget() & capset來設定.
大致說明一下 CAP 的設定, 首先要先了解三個CAP 的等級.
A. effective => 有效的, 這個有設定就可以允許 操作相對應的東西 , 
                        例如 CAP_SYS_TIME 有設定就可以設定系統時間.
B. permitted => 這是允許的內容 , 如果這個資料內的 CAP_SYS_TIME 沒有設定 , 
                         那設定 effective 也沒用.
C. inheritable => 是否可被繼承 ,如果有設定 , fork() + exec() 的 子 process 也可以繼續
                          被允許對應的設定.

           typedef struct __user_cap_data_struct {
              __u32 effective;
              __u32 permitted;
              __u32 inheritable;
           } *cap_user_data_t;
 

來個實際例子吧, 目前子程序的 P (permitted) 是全部被允許的 , 
不過E(effective)沒有打開 ,利用下列的範例將其打開 , 就可以使用 settimeofday() 設定系統時間了.

        if (geteuid() > 0 )
        {
        //---- Not Root ,need turn on CAP_SYS_TIME.
                header.version = _LINUX_CAPABILITY_VERSION_1;
                header.pid = getpid();
                retval = capget(&header, &cap);
                if(retval)
                        goto capget_fail;

//                      printf("\t[%s] PID:%d E:%x P:%x I:%x\n", __FUNCTION__,
//                                      header.pid, cap.effective, cap.permitted,cap.inheritable);

                header.version = _LINUX_CAPABILITY_VERSION_1;
                header.pid = 0;

                if ( enable )
                        cap.effective   |=  (1 << CAP_SYS_TIME);
                else
                        cap.effective   &=  ~(1 << CAP_SYS_TIME);

                retval = capset(&header, &cap);
                if(retval)
                        goto capset_fail;
        }





PS. 這段 function call 我想要打開 CAP_SYS_TIME 設定系統時間後就關閉(我提供一個特別的 SO API , 所以使用者還是不能 設定 系統時間,除非透過這個 API .
       所以先判別是否為 root (利用 EUID) , 如果不是就可以enable / disable . 如果是 root , 就不理會(root 本來就可以)