2014年4月28日 星期一

Soc的board file中多組的DT_MACHINE_START與device tree之間的對應關係

[本文章重點] 您將會學到 kernel 在開機時如何藉由 device tree 在多組 board file 裡的DT_MACHINE_START 中找到正確的 machine description.

Ps. 本文章以 Marvell PXA1L88 的 source code 為例子

例如: (我以 Marvell PXA1L88 的 board file - mmpx-dt.c 作為例子)
1:  DT_MACHINE_START(PXA1088_DT, "PXA1088")  
2:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
3:       .map_io          = mmp_map_io,  
4:       .init_irq     = pxa988_dt_irq_init,  
5:       .init_time     = mmpx_timer_init,  
6:       .init_machine     = pxa988_dt_init_machine_1,  
7:       .dt_compat     = pxa1088_dt_board_compat,  
8:       .reserve     = pxa988_reserve,  
9:       .restart     = mmp_arch_restart,  
10:  MACHINE_END  
11:  DT_MACHINE_START(PXA1L88_DT, "PXA1L88")  
12:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
13:       .map_io          = mmp_map_io,  
14:       .init_irq     = pxa988_dt_irq_init,  
15:       .init_time     = mmpx_timer_init,  
16:       .init_machine     = pxa988_dt_init_machine_2,  
17:       .dt_compat     = pxa1L88_dt_board_compat,  
18:       .reserve     = pxa988_reserve,  
19:       .restart     = mmp_arch_restart,  
20:  MACHINE_END  
21:  DT_MACHINE_START(PXA1U88_DT, "PXA1U88")  
22:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
23:       .map_io          = mmp_map_io,  
24:       .init_irq     = pxa988_dt_irq_init,  
25:       .init_time     = mmpx_timer_init,  
26:       .init_machine     = pxa988_dt_init_machine_3,  
27:       .dt_compat     = pxa1U88_dt_board_compat,  
28:       .reserve     = pxa988_reserve,  
29:       .restart     = mmp_arch_restart,  
30:  MACHINE_END  

在這麼多組的 machine description 中 , 它是如何正確地選中DT_MACHINE_START(PXA1L88_DT, "PXA1L88") ?
此例一共有三組
DT_MACHINE_START(PXA1088_DT, "PXA1088")
DT_MACHINE_START(PXA1L88_DT, "PXA1L88")
DT_MACHINE_START(PXA1U88_DT, "PXA1U88")

[文章目錄]
  1. 什麼是 DT_MACHINE_START , MACHINE_END?
  2. kernel 如何在多組 machine description 中找到正確的匹配
[什麼是DT_MACHINE_START , MACHINE_END?]
   Device tree 的引進主要就是要解決 ARM Linux 中, arch/arm/plat-xxx和arch/arm/mach-xxx 中有太多不重要的 board level 的描述, 我們希望盡量在單一的 board file 中能夠支援更多 board level 的描述,在透過一些機制去找到正確的 machine description.
   那支援 device tree 的 machine description 是如何宣告?
(kernel/arch/arm/include/asm/mach/arch.h)
1:  #define DT_MACHINE_START(_name, _namestr)          \  
2:  static const struct machine_desc __mach_desc_##_name     \  
3:   __used                                   \  
4:   __attribute__((__section__(".arch.info.init"))) = {     \  
5:       .nr          = ~0,                    \  
6:       .name          = _namestr,  
7:  #endif  
從這巨集得知, 它將 struct machine_desc 的物件放在 .arch.info.init 這個 section 中, DT_MACHINE_START(PXA1L88_DT, "PXA1L88") 展開就會是

1:  [.arch.info.init]  
2:    struct machine_desc __mach_desc_PXA1L88_DT = {  
3:          .nr       = ~0,  
4:          .name   = "PXA1L88",  
5:          ...  
6:    }  

我們可以透過 System.map 去驗證
1:  c0a21598 T __arch_info_begin  
2:  c0a21598 t __mach_desc_PXA1U88_DT  
3:  c0a215e8 t __mach_desc_PXA1L88_DT  
4:  c0a21638 t __mach_desc_PXA1088_DT  
5:  c0a21688 T __arch_info_end  
所以從 System.map 得知, board file 裡的所有 DT_MACHINE_START( ) 都被 build 進去並放在arch.info.init 這 section 內
(補充: kernel/arch/arm/kernel/vmlinux.lds.S)
1:  .init.arch.info : {  
2:            __arch_info_begin = .;  
3:            *(.arch.info.init)  
4:            __arch_info_end = .;  
5:       }d  

[kernel 如何在多組 machine description 中找到正確的匹配]
   從上一段文章得知所有的 DT_MACHINE_START( )宣告的 machine description 都被 build 進去了, 那 kernel 如何做出正確的選擇呢?  答案就透過 "compatible" 這關鍵字.
 
    我們先談一下 device tree , 當我們在 build Android images 時就會選擇 TARGET_PRODUCT , 接下來就會知道
1:  KERNEL_DEFCONFIG := pxa988_defconfig  
2:  ###!!!! the order of the dtb file here need to align with the code in u-boot###  
3:  KERNEL_DTB_FILE := pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-l7-v10.dtb  
Linux kernel 會利用 dtc (device tree compiler) build 出 .dtb 檔 (device tree blob)
(kernel/arch/arm/boot/dts/Makefile)
1:  dtb-$(CONFIG_ARCH_MMP) += edenconcord.dtb edenfpga.dtb pxa988-dkb.dtb pxa1088dkb.dtb\  
2:                  pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-v10.dtb\  
3:                  pxa1U88-dkb.dtb pxa1986sdk.dtb pxa1L88-tablet-l7-v10.dtb  
雖然 kernel 會 build 出很多 .dtb 檔, 但是只有以下特定的 .dtb 檔才會被放進 images 裡
1:  KERNEL_DTB_FILE := pxa1L88-dkb-v10.dtb pxa1L88-dkb-v20.dtb pxa1L88-tablet-l7-v10.dtb  
Marvell的作法是將這些 .dtb 檔直接放在 u-boot.bin 的尾端, u-boot 會告訴 kernel 關於 Flattened Device Tree blob 的 address, kernel 再從這 address 將這些 device tree blob載到memory 特定的位址上.

    在 .dts 檔中, 我們會看到 compatible 這些關鍵字, 例如:
在 pxa1L88-dkb-v10.dts 中, compatible = "mrvl,pxa1L88-dkb-v10", "mrvl,pxa1L88";
在 pxa1L88-dkb-v20.dts 中, compatible = "mrvl,pxa1L88-dkb-v20", "mrvl,pxa1L88";
在 pxa1L88-tablet-l7-v10.dts 中, compatible = "mrvl,pxa1L88-tablet-l7-v10", "mrvl,pxa1L88";

   我們再回到 board file - mmpx-dt.c ,
1:  static const char *pxa1L88_dt_board_compat[] __initdata = {  
2:       "mrvl,pxa1L88-dkb-v10",  
3:       "mrvl,pxa1L88-dkb-v20",  
4:       "mrvl,pxa1L88-tablet-v10",  
5:       "mrvl,pxa1L88-tablet-l7-v10",  
6:       NULL,  
7:  };  
8:  DT_MACHINE_START(PXA1L88_DT, "PXA1L88")  
9:       .smp_init     = smp_init_ops(mmp_smp_init_ops),  
10:       .map_io          = mmp_map_io,  
11:       .init_irq     = pxa988_dt_irq_init,  
12:       .init_time     = mmpx_timer_init,  
13:       .init_machine     = pxa988_dt_init_machine_2,  
14:       .dt_compat     = pxa1L88_dt_board_compat,  
15:       .reserve     = pxa988_reserve,  
16:       .restart     = mmp_arch_restart,  
17:  MACHINE_END  

      我們可以看到 .dt_compat = pxa1L88_dt_board_compat, 而 pxa1L88_dt_board_compat[] 裡面的字串是不是跟剛才的那些 .dts 檔案的 compatible 裡的 value 值一樣 ( .dts 檔案裡面就是紀錄一堆 key = value 這樣的資料並排成樹狀結構). Linux kernel 在開機做初始化時, 會將 u-boot 告訴它的 device tree 與 kernel image 本身 build 進去的 machine description ( 就是 DT_MACHINE_START( ) 所宣告並放在 .arch.info.init section中) 去做字串比較, 進而找到相符的 machine description. 之後在Linux kernel 在做後續的初始化時,就會執行該 machine description 的 .init_machine 所指的function去展開 device tree. 所以, 此例就會去展開 DT_MACHINE_START(PXA1L88_DT, "PXA1L88").
1:  start_kernel( ) [kernel/init/main.c]  
2:    -> setup_arch( ) [kernel/arch/arm/kernel/setup.c]  
3:      -> setup_machine_fdt( ) [kernel/arch/arm/kernel/devtree.c]  
4:           devtree = phys_to_virt(dt_phys); /*這時候MMU已經起來了,所以要轉成 virtual mem addr 才可以存取的到*/  
5:           /* check device tree validity */  
6:           if (be32_to_cpu(devtree->magic) != OF_DT_HEADER)  
7:              return NULL;  
8:            /* Search the mdescs for the 'best' compatible value match */  
9:            initial_boot_params = devtree;  
10:           dt_root = of_get_flat_dt_root(); /*指到 u-boot 傳給 kernel 的 device three*/  
11:           for_each_machine_desc(mdesc) {  /*歷遍 .arch.info.init 中的 struct machine_desc*/  
12:              score = of_flat_dt_match(dt_root, mdesc->dt_compat);  
13:              if (score > 0 && score < mdesc_score) {  
14:                  mdesc_best = mdesc; /*如果找到了, 就將 mdesc_best 指到該結構物件*/  
15:                  mdesc_score = score;  
16:              }  
17:            }  
18:            -> of_flat_dt_match( ) [kernel/drivers/of/fdt.c]  
19:              -> of_fdt_match( )  
20:                -> of_fdt_is_compatible( )  
21:                    cp = of_fdt_get_property(blob, node, "compatible", &cplen); /*取出 compatible 的 value 值*/  
22:                    ...  
23:                    while (cplen > 0) {  
24:                          score++;  
25:                          if (of_compat_cmp(cp, compat, strlen(compat)) == 0) /*透過 strncmp() 做字串的比較*/  
26:                               return score;  
27:                          ...  
28:                     }  
29:  
補充: 關於device tree 會用到的functions 大都可以在 kernel/drivers/of 目錄下找到, 而且這些functions 都是以 of_XXX( ) 命名. ( 例如: of_fdt_match( ), of_fdt_is_compatible( ) ...)

2014年4月14日 星期一

DAPM - snd_soc_dapm_add_route() 與 snd_soc_dapm_path 的建立

此系列文章主要是在閱讀 http://blog.csdn.net/azloong 之後,再針對我有興趣的部分,再做深入探討.

[本篇文章重點] 希望各位能透過本篇文章了解到path (struct snd_soc_dapm_path)的建立,以及它與dapm kcontrol之間的關係,並為未來dapm觸發的流程鋪路.

在snd_soc_dapm_add_route()中會一一將route map轉換成struct snd_soc_dapm_path並新增至snd_soc_card裡的paths串列(list)裡面. 當dapm觸發時,會去走遍所有widget,並透過paths串列找出同時有連到output end point與input end point的widget,並將該widget的電源打開. 換句話說,dapm希望達到在音訊通道上的所有widget電源是開著,其餘用不到的widget電源是關閉的. 在音訊通道上,最常看到的widget型態就是非switch與mixer莫屬了. 所以接下將利用mixer來說明path的建立過程.


1:  static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {  
2:  SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
3:  SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
4:  SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
5:  SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
6:  SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
7:  };   

1:  static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {  
2:  ...  
3:  SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,  
4:              wm8900_loutmix_controls,  
5:              ARRAY_SIZE(wm8900_loutmix_controls)),  
6:  ...  
7:  };  


經過一連串的宣告後會建立此關係,接下來是route map的宣告

1:  /* Target, Path, Source */  
2:  static const struct snd_soc_dapm_route wm8900_dapm_routes[] = {  
3:  ...  
4:  {"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},  
5:  {"Left Output Mixer", "AUX Bypass Switch", "AUX"},  
6:  {"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},  
7:  {"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"},  
8:  {"Left Output Mixer", "DACL Switch", "DACL"},  
9:  ...  
10:  }  

我們可以看到routes順序是 { sink, control, source}, 所以,以這為例
{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"},
sink = "Left Output Mixer"
control = "LINPUT3 Bypass Switch"
source = "LINPUT3"
換成文字上的說明就是 audio stream 從 "LINPUT3"過來,經過 "LINPUT3 Bypass Switch", 接著流向 "Left Output Mixer"

1:  snd_soc_dapm_add_routes()  
2:   -->snd_soc_dapm_add_route() (添加一個snd_soc_dapm_mixer類型的route)  
3:      -->配置snd_soc_dapm_path記憶體,令path->source=source; path->sink=sink  
4:      -->如果sink是switch或mixer這類型的widget  
5:        -->dapm_connect_mixer(dapm, source, sink, path, control);  
6:          --> 檢查control是否也可以在sink->kcontol_news[i].name中找到  
7:             if (!strcmp(control_name, sink->kcontrol_news[i].name))  
8:          -->如果找到,則將path分別加到card->paths,sink與source widget串列中,
                    並將path的name設定成sink->kcontrol_news[i].name
9:                  list_add(&path->list, &dapm->card->paths);  
10:                 list_add(&path->list_sink, &sink->sources);  
11:                 list_add(&path->list_source, &source->sinks);  
12:                 path->name = sink->kcontrol_news[i].name;  
13:                 --> dapm_set_path_status(sink, path, i);  
14:                    -->取出 sink->kcontrol_news[i].private_value 的值,  
15:                       如果該值表示是unmute時(對於mixer來說,unmute就是代表connected),
16:                       則 path->connect = 1,反之亦然
17:      /*NOTE:為了與route map的解說有一貫性,我將souece code裡的dest換成sink;src換成了source.*/  













(為了方便說明與排版,將不會精準地畫上指標(pointer)真正指向的記憶體位置, 例如: prev指標是指向snd_soc_dapm_path->list->next開頭的記憶體位置上,而不是snd_soc_dapm_path->list->prev, 此外,像name指標是指到存到字池(String Literal Pool)裡的字串)上圖說明了snd_soc_dapm_path建立好之後都全都被加進了snd_soc_card裡的paths串列裡面.













上圖說明了sink widget也把snd_soc_dapm_path的list_sink加到自己的sources串列裡面.換句話說,widget (此時當sink角色時)會知道可以連到它的source path資訊.再回顧開頭所說的,當要判斷該widget是否有連到input end point時(is_connected_input_ep( )),那該widget的sources串列就扮演資訊來源的角色




同樣地,上圖說明了source widget也把snd_soc_dapm_path的list_source加到自己的sinks串列裡面.換句話說,widget (此時當source角色時)會知道它可以連到的sink path資訊.當要判斷該widget是否有連到output end point時(is_connected_output_ep( )),那該widget的sinks串列就扮演資訊來源的角色.

    至於snd_soc_dapm_path的long_name是如何產生的? 以及它與dapm kcontrol之間的關係是如何?這篇文章已有說明 http://blog.csdn.net/azloong/article/details/6334922. 到目前為止,都是在codec probe的時候所做的事,當audio codec probe時的audio codec register此時的值就直接影響了snd_soc_dapm_path ->connect的狀態,所以,大部分的audio codec driver都會maintain一個initial table,在執行audio codec probe時會將initial table裡的值先寫到audio codec register裡,再做kcontrol,widget與route map的註冊.之後就會依use case透過amixer去控制kcontrol而改變相對應的snd_soc_dapm_path ->connect的狀態,在http://blog.csdn.net/azloong/article/details/6428885中有說明.

    關於kcontrol, widget與route map的註冊,有些audio codec driver已經移到soc-core.c裡的snd_soc_instantiate_card( )或soc_probe_codec( )或其他相關的function去做了.
所以有些audio codec driver改成下述方式達成
1:  static struct snd_soc_codec_driver soc_codec_dev_wm8900 = {  
2:       .probe =     wm8900_probe,  
3:       .remove =     wm8900_remove,  
4:       .suspend =     wm8900_suspend,  
5:       .resume =     wm8900_resume,  
6:       .set_bias_level = wm8900_set_bias_level,  
7:       .volatile_register = wm8900_volatile_register,  
8:       .reg_cache_size = ARRAY_SIZE(wm8900_reg_defaults),  
9:       .reg_word_size = sizeof(u16),  
10:      .reg_cache_default = wm8900_reg_defaults,  
11:      .controls = wm8900_snd_controls,  
12:      .num_controls = ARRAY_SIZE(wm8900_snd_controls),  
13:      .dapm_widgets = wm8900_dapm_widgets,  
14:      .num_dapm_widgets = ARRAY_SIZE(wm8900_dapm_widgets),  
15:      .dapm_routes = wm8900_dapm_routes,  
16:      .num_dapm_routes = ARRAY_SIZE(wm8900_dapm_routes),  
17:  };  

 但是關於snd_soc_dapm_path的long_name的產生還是由snd_soc_dapm_new_widget( )來負責,在我的code base裡,它的call stack是snd_soc_register_card( )->snd_soc_instantiate_card( )-> soc_post_component_init( ) ->snd_soc_dapm_new_widget( ),它會走訪所有widget,如果是mixer或switch類型的widget就會call dapm_new_mixer( ),而dapm_new_mixer( )有兩個重要的工作,就是為連到該widget的所有path賦予long_name與建立kcontrol.

1:  snd_soc_dapm_new_widgets()  
2:       它會走訪所有widget,如果是mixer或switch類型的widget就會call   
3:       dapm_new_mixer( ) , 如果是其他型態,會call相對應的function  
4:    -->dapm_new_mixer( )  
5:       1.每個widget都有sources串列記錄著連到它的所有path的資訊,而每個  
6:        path->name都是分別指到相對應的control的name  
7:        (回顧一下: path->name = sink->kcontrol_news[i].name; )  
8:        所以,在這裡再把這關係找出來  
9:        snd_soc_dapm_mixer : path->long_name=sink->name + sink->kcontrol_news[i].name  
10:       snd_soc_dapm_mixer_named_ctl: path->long_name= sink->kcontrol_news[i].name  
11:      2. path->kcontrol = snd_soc_cnew(&sink->kcontrol_news[i],  
12:                                   sink, path->long_name, prefix);  
13:        為該path建立一個snd_kcontrol物件,而這物件保有了sink->kcontrol_news[i]的資訊. 再回顧一下,kcontrol的宣告  
14:        SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
15:        這暗示了該path將會控制到相對應的kcontrol的register的值 .  
16:      3. snd_ctl_add(card, path->kcontrol);  
17:        把path->kcontrol加到card (snd_card)的controls串列裡面  

經過上述步驟之後,我們就可以透過alsa_amixer來切換path. 先透過alsa_amixer controls列出所有的kcontrols
 ~ # alsa_amixer controls  
 ......省略......  
 numid=57,iface=MIXER,name='Left Output Mixer AUX Bypass Switch'  
 numid=60,iface=MIXER,name='Left Output Mixer DACL Switch'  
 numid=56,iface=MIXER,name='Left Output Mixer LINPUT3 Bypass Switch'  
 numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch'  
 numid=59,iface=MIXER,name='Left Output Mixer Right Input Mixer Switch'  

再透過alsa_amixer cset去切換path的狀態
 alsa_amixer cset numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch' 1   

接下來,我們看一下alsa_amix如何改變kcontrol相對應的register
1:  alsa_amixer - @user space   
2:    (alsa_amixer cset name='Left Output Mixer Left Input Mixer Switch' 1)   
3:    |-> snd_ctl_ioctl( ) cmd ==SNDRV_IOCTL_ELEM_WRITE   
4:      |-> snd_ctl_elem_write_user( )   
5:        |-> snd_ctl_elem_write( )   
6:          |-> snd_ctl_find_id( ) 透過 list_for_each_entry(kctl, &card->controls, list)  
7:            遍歷kcontrol,找到相對應的kcontrol (其中包含透過kctl->id.name的字串比較與numid的比較)    
8:            |-> kctl->put( ) 呼叫當初kcontrol宣告時,所註冊的put( )函式   
9:              |-> snd_soc_dapm_put_volsw( ) 根據user所設定的值去判斷相對   
10:                應的register是否需要 更新(透過nsd_soc_test_bits( )去檢查) 與   
11:                connect狀態, 如果需要更新   
12:               |-> dapm_mixer_update_power(widget, kcontrol, connect)   
13:                 |-> 遍歷所有path, 如果該path->kcontrol與user修改的kcontrol  
14:                   一致, 則 path->connect = connect;   
15:                   |-> dapm_power_widgets( )    
16:                     |-> dapm_generic_check_power( ) 找出是否有同時連接到  
17:                       output end point與input end point的widget, 如果有,則打  
18:                       開電源. 不過,如果現在user沒有在播音樂, audio interface   
19:                       active為 0,則不會有打開電源的情況發生, 這我們之後再討論.  

再回顧一下,當初dapm kcontrol是如何註冊的,
1:  static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {  
2:  SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
3:  SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
4:  SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
5:  SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
6:  SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
7:  };  
所以當初在註冊時,就把相對應的register給帶進去了

SOC_DAPM_SINGLE在soc-dapm.h中的定義, 我們可以看到 .put = snd_soc_dapm_put_volsw
1:  /* dapm kcontrol types */  
2:  #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \  
3:  {     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \  
4:       .info = snd_soc_info_volsw, \  
5:       .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \  
6:       .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert)  
7:   }  

[結論]從這篇文章,我們可以知道path是如何被建立, path與kcontrol之間的關係, user如何透過alsa_amixer控制dapm kcontrol並更新kcontrol對應的register,以及如何影響path的connect狀態.