2012年7月11日 星期三

Linux Kernel Portability-Data type & Data Structure in Linux kernel (二)

上一篇著重於Linux Kernel 的Portability。這一篇將著重於Linux Kernel的Data Structure。這裡要介紹的Data Structure包括了Linked lists、Queues、Maps、及Binary trees。

  • Linked List
這裡直接講用法。Linux Kernel的Linked List是Double Linked List,而且List裡面的元素必須是list_head的structure。舉個例好了,我們要做到如下圖(A B C都是struct sample好了):
struct sample{
    struct list_head link;
    vold *content;
}
struct sample *A,*B,*C;
現在我們要把ABC串起來。首先我們建立一個叫list_head的Linked list的變數,並初始化它。常見的方法有兩種,結果是一樣的。
/**
*1. LIST_HEAD(name)
*2. INIT_LIST_HEAD
*/
//method 1
static LIST_HEAD(list_head);

//method 2
struct list_head list_head;
INIT_LIST_HEAD(&list_head);
接著假設我們要先加入A和C,利用list_add()將A加入到list_head後面。然後在將C加入到A後面。
//Add A after list_head,list_head is the head of the list
//e.g list_head->A
list_add(&A->link,&list_head);

//Add C after A e.g list_head->A->C
list_add(&C->link,&A->link);
接著,我們要利用list_add_tail將B加在C之前
//Add B before C e.g list_head->A->B->C
list_add_tail(&B->link,&C->link);
加完了之後我們要怎麼列出List裡面的項目呢?可以利用list_for_each來達成。
//list_for_each(struct list_head *cursor, struct list_head *list)
list_for_each(&A->link,&list_head){

}
更多的用法可見<linux/list.h>

  • Queue 
Linux Kernel的queue叫做kfifo實做在kernel/kfifo.c 宣告在<linux/kfifo.h>。與我們所知的queue相同,kfifo支援enqueue(函式命名成inqueue)跟dequeue。同樣的,kfifo有兩個offset分別是記錄下一個插入位置的enqueue offset跟下一個取出位置的dequeue offset。
接下來介紹kfifo的使用方式分為 1. Declare  2. Enqueue/Dequeue 3. Other operation
Declare 
a. 利用kfifo_alloc() 動態宣告   
struct kfifo fifo;
int ret;
      //宣告一個大小為PAGE_SIZE的queue
ret = kfifo_alloc(&kifo, PAGE_SIZE, GFP_KERNEL);
if (ret)
return ret;

b. 利用DECLARE_KFIFO(name, size)建立 INIT_KFIFO(name)初始化。
Enqueue/Dequeue
kfifo_in(fifo, buf, n) 將n byte的buf讓入fifo裡。如果fifo free的大小<n 則只會copy free 的size的資料 
kfifo_out(fifo, buf, n) copy n byte到buf中。如果fifo的大小<n 則只會copy fifo大小的data
kfifo_out_peek(fifo, buf, n) kfifo_out執行後data就會從fifo中刪除。如果想保留可以使用kfifo_out_peek
Other Operation 
kfifo_size(fifo) 取得fifo大小
kfifo_len(fifo) 取得fifo已使用大小
kfifo_avail(fifo) 取得fifo可使用大小
kfifo_is_empty(fifo) 判斷fifo是否為空
kfifo_is_full(fifo) 判斷fifo是否已滿
kfifo_reset(fifo) 清空fifo。
kfifo_free(fifo) free掉fifo
實際的操作範例
struct kfifo fifo;
int ret;

//宣告一個大小為PAGE_SIZE的queue
ret = kfifo_alloc(&kifo, PAGE_SIZE, GFP_KERNEL);
if (ret)
    return ret;
int i;
//插入32個size為sizeof(int)的[0,31]到fifo中
for (i = 0; i < 32; i++)
    kfifo_in(fifo, &i; sizeof(i));

unsigned int val;
int ret;

//從fifo讀取一個integer到val 中。資料不會被刪除
ret = kfifo_out_peek(fifo, &val, sizeof(val));
if (ret != sizeof(val))
    return -EINVAL;

printk(KERN_INFO “%u\n”, val); /* should print 0 */


/* while there is data in the queue ... */
while (kfifo_avail(fifo)) {
    unsigned int val;
    int ret;
    /* ... read it, one integer at a time */
    ret = kfifo_out(fifo, &val, sizeof(val));
    if (ret != sizeof(val))
        return -EINVAL;

    printk(KERN_INFO “%u\n”, val);
}
  • Red-Black Tree
紅黑樹是一種semi-balanced binary tree(自平衡二元樹)。主要有以下六種性質:
  1. 節點不是紅色就是黑色。
  2. 根節點是黑色。
  3. 所有葉子都是黑色(葉子是NIL節點 e.g不包含資料)。
  4. 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
  5. 從任一節點到其每個葉子的所有最短路徑都包含相同數目的黑色節點。

紅黑樹在Linux Kernel實作的地方是lib/rbtree.c宣告的地方在<linux/rbtree.h>。這裡直接以 driver/media/video/tegra/avp/avp.c裡面的實作來解釋rbtree的使用。avp.c定義了一個tegra_avp_info的struct,以下擷取其實做。要使用rbtree必須要宣告一個rb_root的變數如下:
struct tegra_avp_info {
 ...
 struct rb_root  endpoints;
        ...
};
要開始使用endpoints這個rbtree之前必須給他初始的值"RB_ROOT"如下:
static int tegra_avp_probe(struct platform_device *pdev)
{
    struct   *avp;
    avp = kzalloc(sizeof(struct tegra_avp_info), GFP_KERNEL);
    ...
    avp->endpoints = RB_ROOT;
    ...
}
Linux Kernel的rbtree不包含插入及搜尋的函式,下面同樣以avp的"插入"來說明。
static struct remote_info *remote_find(struct tegra_avp_info *avp, u32 local_id)
{
 struct rb_node *n = avp->endpoints.rb_node;
 struct remote_info *rinfo;

 while (n) {
  rinfo = rb_entry(n, struct remote_info, rb_node);

  if (local_id < rinfo->loc_id)
   n = n->rb_left;
  else if (local_id > rinfo->loc_id)
   n = n->rb_right;
  else
   return rinfo;
 }
 return NULL;
}
以上例,remote_find()實作搜尋的功能。while loop會traverse整個tree 直到找到相同的id。
static int remote_insert(struct tegra_avp_info *avp, struct remote_info *rinfo)
{
 struct rb_node **p;
 struct rb_node *parent;
 struct remote_info *tmp;

 p = &avp->endpoints.rb_node;
 parent = NULL;
 while (*p) {
  parent = *p;
  tmp = rb_entry(parent, struct remote_info, rb_node);

  if (rinfo->loc_id < tmp->loc_id)
   p = &(*p)->rb_left;
  else if (rinfo->loc_id > tmp->loc_id)
   p = &(*p)->rb_right;
  else {
   pr_info("%s: avp endpoint id=%x (%s) already exists\n",
    __func__, rinfo->loc_id,
    trpc_name(rinfo->trpc_ep));
   return -EEXIST;
  }
 }
 rb_link_node(&rinfo->rb_node, parent, p);
 rb_insert_color(&rinfo->rb_node, &avp->endpoints);
 rinfo_get(rinfo);
 return 0;
}

以上例,while loop會traverse rbtree去尋找適合的頁節點,然後透過rb_link_node()去link parent。接著再透過rb_insert_color()上色。
static void remote_remove(struct tegra_avp_info *avp, struct remote_info *rinfo)
{
 rb_erase(&rinfo->rb_node, &avp->endpoints);
 rinfo_put(rinfo);
}
刪除節點很簡單,只要呼叫rb_erase()即可。

  • Radix Tree
Radix Tree使用相對於Red Block Tree來說簡單許多。這裡以之前提過得irq實做為例看看Radix Tree怎麼使用。
Radix Tree實做在lib/radix- tree.c,下面以kernel/irqdesc.c來了解。
//Declare a radix tree called irq_desc_tree
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);

static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
{
        //Insert  into irq_desc_tree
 radix_tree_insert(&irq_desc_tree, irq, desc);
}

struct irq_desc *irq_to_desc(unsigned int irq)
{
        //取得key為irq的value
 return radix_tree_lookup(&irq_desc_tree, irq);
}

static void delete_irq_desc(unsigned int irq)
{
        //刪除key為irq的pair
 radix_tree_delete(&irq_desc_tree, irq);
}

  • Reference
上一篇:Linux Kernel Portability-Data type & Data Structure in Linux kernel (一)

2012年7月10日 星期二

Linux Kernel Portability-Data type & Data Structure in Linux kernel (一)

  • 前言
Linux作為一個跨平台(處理器)的作業系統必須兼容許多不同的CPU 架構設計。也因此,Linux Kernel裡面有許多是為了Portability而產生的包含衍生的資料型態及提供的資料結構。
為了讓我們的Kernel 的module可以順利在我們的Target上或在各平台上順利執行,我們勢必就需要了解哪些方面會是Platform-Independent,Linux Kernel如何解決這個問題。
  • The Size of Data Types
在說明Kernel專有的資料型態前,我們先了解標準C資料型態在各平台的差異。以下圖為例,在常見的平台上,char、short、int、long long大小是一致的。但是long、ptr、word的大小則會因平台不同而有所差別。因此,如何在各平台確切的得到我們希望的變數大小就非常重要了。換句話說,假設我們希望某個變數他是8個bits,他就不會因為平台的不同而變成立如4個bits。
因此,Linux Kernel就定義了幾個標準大小的資料型態在include/linux/types.h。在include/linux/types.h裡他include了asm/types.h。以ARM平台為例,arch/arm/include/asm/types.h include了asm-generic/int-ll64.h。在int-ll64.h就定義了: 
u8    unsigned byte (8 bits)
u16   unsigned word (16 bits)
u32   unsigned 32-bit value
u64   unsigned 64-bit value
s8    signed byte (8 bits)
s16   signed word (16 bits)
s32   signed 32-bit value
s64   signed 64-bit value
u代表的是unsigned而s則是代表signed分別定義了8~64bits的標準大小型態。假設在user space的程式要使用固定長度的型別,記得前面要加上__例如"__u8"。

然而,C99規範了標準大小的資料型態uintN_t及intN_t對應著uN及sN定義在stdint.h。N代表8、16、 32、64。因此,不管是Kernel Module或是user space 的program特別是如果你的程式是想跨平台(OS)的,就盡量使用C99的資料型態而非Linux Kernel的資料型態。
  • Interface-Specific Types
Linux Kernel為了移植性也定義了許多_t結尾的變數在 <include/types.h>。舉例來說常見的size_t、ssize_t、pid_t、time_t等等根據特定意義的資料型態就稱為Interface-Specific Type。以process 的id pid_t為例,在x86來說是 int,但是假設我們直接將例如getpid()的回傳值用 int用 接收,哪天另外一個平台的pid是long long的話勢必我們程式必須作修改才能正確運行。因此,Interface-Specific Type可以避免移植的問題。 
  • Data Alignment
大多數的CPU都會要求變數擺在特定的記憶體上。例如Intel x86的CPU要求4 byte的integer是放置在4的倍數的記憶體位置。這個就叫做Memory Alignment。但假設不是擺在4的倍數上呢?那就必須要讀取兩次才能得到這個變數。大多數的CPU都可以自己處理這個狀況,但是有些CPU沒辦法處理這個問題,必須要產生exception靠exception handler來處理這個問題,但是這樣效率會非常的慢。有個方法是使用get_unaligned(ptr)取得資料及set_unaligned(val,ptr)寫入資料。例如,在driver/power/bq27x00_battery.c裡面
if (!single)
    ret = get_unaligned_le16(data);
else
    ret = data[0];
如果資料是1個byte就直接讀取否則用get_unaligned_le16()。le代表little-endian

順帶一提,compiler在做最佳化的時候可能會自動做Memory Aligned的動作。但也許我們定義的資料結構變數間的位址是必須連續的(的動作可能會在變數間填補一些變數讓memory可以對齊),這個時候我們可以利用__attribute__ ((packed))告訴編譯器不要亂動 。
struct{
    u16 uid;
    u64 gid;
    u16 val0;
    u32 val1;
}__attribute__ ((packed))structA;
  • Byte Order
假設有一個4 byte的int不同的CPU可能會有不同的記憶體擺法,例如低位的值先放(Little-Endian)還是大的先放(Big-Endian)?舉例來說,假設有一個整數值是0x12345678 Little-Endian的擺法就會是:
address        value
0x00000001     0x78
0x00000002     0x56
0x00000003     0x34
0x00000004     0x12
而如果是Big-Endian的話則是:
address        value
0x00000001     0x12
0x00000002     0x34
0x00000003     0x56
0x00000004     0x78
如果我們要得到Little-Endian順序的值可以使用u32 cpu_to_le32(u32),更多的轉換函式可以在找到。
  • Time
HZ定義了一秒鐘有幾次 Interrupt。他可以是100、250、300或是1000。預設的值可能會隨著 Kernel的版本或編譯Kernel 的設定而改變。因此,我們不可以假設HZ==1000(舊版的Kernel預設是1000)。
順帶一提,Tick是HZ的倒數,因此假設HZ=1000則一個tick就是1毫秒。
Jiffies是Linux Kernel用來紀錄系統自開機以來觸發了幾次Interrupt(開機時並非歸0)。Linux Kernelchewing每秒鐘會將jiffies加1HZ次。我們可以利用Linux Kernel定義的macro(timer_after、time_after_eq、time_before和time_before_eq)來判斷時間。因此,假設我們要判斷時間是否過了三秒可以:
//設定時間是現在的jiffies加上3秒的HZ次
unsigned long timeout = jiffies + (3*HZ);  
while (hwgroup->busy) {  
     ...
  /*如果迴圈超過三秒(也就是說現在的jiffies數超過三秒前的jiffies+3*HZ次)
   *則return -EBUSY
   */
  if (time_after(jiffies, timeout)) {  
    return -EBUSY;  
  }  
  ...  
}  
return SUCCESS; 
  • Page Size
不要假設Page Size是4KB。用Linux Kernel提供的PAGE_SIZE 來取得系統的page size及PAGE_SHIFT取得 要左移的bits數達到其memory page的number。

下一篇:Linux Kernel Portability-Data type & Data Structure in Linux kernel (二)

2012年7月5日 星期四

Linux Interrupt 整理 (一)

  • 前言
CPU的速度很快,假設每每都必須等待週邊的硬體動作完成CPU才能繼續工作的話,效率未免太低了。因此,我們的系統設計了中斷(Interrupt),當有 event 發生(例如 工作完成)就觸發一個Interrupt告訴CPU,這時候CPU就會"馬上"去處理。這麼一來就可以避免等待時間的浪費。
我們可以針對裝置的Interrupt建立Handler,讓不同的Device Interrupt發生後去執行相對應的"程序" ,這個程序就稱為 Interrupt Service Routine(ISR)。如果沒有相對應的ISR則僅會回覆ACK而不作任何事。
傳統電腦是利用兩個Programmable Interrupt Controller (PIC)提供15組Interrupt Line連接Device和CPU。Device 觸發中斷請求(Interrupt Request)給PIC然後在傳至CPU,也因為數量有限所以很寶貴。現代的設計是使用Advanced Programmable Interrupt Controller(APIC) 
在Linux Kernel 中存在一個資料結構叫irq_desc 實作在kernel/irq用來鍊結IRQ及ISR,另外也建立Radix treer叫irq_desc_tree來存放irq_desc結構的變數。
  • 建立IRQ
要使用IRQ必須要向核心註冊。註冊的方式則是利用request_irq長相如下:
//include/linux/interrupt.h
/**
 *第一個參數是IRQ的號碼
 *第二個參數是ISR的pointer
 *第三個參數
 *    IRQF_SHARED         Interrupt is shared
 *    IRQF_SAMPLE_RANDOM  代表這個interrupt可以作為系統random的依據(有固定週期的interrupt
 *                        不適合當成random依據)
 *    IRQF_TRIGGER_*      Specify active edge(s) or level
 *第四個參數是顯示在例如/proc/interrupts的名稱
 *第五個參數用途可以參考http://www.cnblogs.com/ITmelody/archive/2012/05/12/2497018.html
 *一般直接填NULL
*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
     const char *name, void *dev)
{
    //request_threaded_irq第三個參數是irq_handler_t thread_fn
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
從這裡我們也可以發現其實request_irq()會直接call request_threaded_irq()。因此我們也會看到有人就直接使用request_threaded_irq()。直接舉nVidia的mmc的例子。
//driver/mmc/host/sdhci-tegra.c
static irqreturn_t carddetect_irq(int irq, void *data)
{
 ...
}

static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
{
    struct sdhci_host *host;
    ...
    rc = request_threaded_irq(gpio_to_irq(plat->cd_gpio), NULL,
     carddetect_irq,
     IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
     mmc_hostname(host->mmc), host);
    ...
}
上述的例子是利用GPIO的變化來當成Interrupt的條件。IRQ number是利用gpio_to_irq取得對應的IRQ;ISR是carddetect_irq(),觸發的條件是當此GPIO rising/falling都會觸發Interrupt。

當我們remove module的時候,我們可能需要free掉這個IRQ,這個時候就可以呼叫free_irq來達成。
static int __devexit sdhci_tegra_remove(struct platform_device *pdev)
{
    ...
    if (gpio_is_valid(plat->cd_gpio)) {
        free_irq(gpio_to_irq(plat->cd_gpio), host);
        ...
    }
    ...
}
  • ISR Top and Bottom Halves
Interrupt設計的目的就是不希望卡在攏長的處理。因此,收到中斷後,我們也會希望可以快速處理完要作的事情。然而,常常我們需要作的事情就是這麼多無法快速的做完造成系統的遲鈍。因此,Linux Kernel提出了將ISR拆成top half 跟bottom half。當Interrupt發生後一定要馬上作的事情就屬於Top half直接寫在原本的ISR,而可以晚點做的就屬於Bottom half。我們可以利用SoftIRQTaskletWorkqueu來排程bottom half的部份。下表比較了三種的差異。
下面我們分別介紹Tasklet及Workqueue。
 Tasklet
Tasklets 與Linux Kernel的timer類似,他們都在Interrupt time(Atomic time)執行而且會執行在schedule他的CPU上。不同於timer,我們無法要求tasklet的task在我們期望的時間執行 。同樣的,我們使用nVidia的mmc driver為例來說明Tasklet。
首先,我們必須先註冊一個tasklet,指定這個tasklet的名字、func及data。
//mmc/host/sdhci.c
static void sdhci_tasklet_card(unsigned long param)
{
   ...
}

int sdhci_add_host(struct sdhci_host *host){
   ...
/*
 * Init tasklet.
 *void tasklet_init(struct tasklet_struct *t,
 *                  void (*func)(unsigned long), unsigned long data)
 *簡單來說就是tasklet_init(name, func,data)
*/
    tasklet_init(&host->card_tasklet,
                 sdhci_tasklet_card, (unsigned long)host);
 
   ...
}

接著,這個task必須要在呼叫schedule_tasklet之後才會被排程執行。理所當然,我們會在top half執行完之後去啟動bottom half的部份。因此我們回到top half(carddetect_irq())看看我們如何去啟動bottom half。
//driver/mmc/host/sdhci-tegra.c
static irqreturn_t carddetect_irq(int irq, void *data)
{
    ...
    tasklet_schedule(&sdhost->card_tasklet);
    
    return IRQ_HANDLED;
};


Workqueue
Workqueue的部份則利用omap的mmc driver來舉例。如同tasklet,我們必須利用INIT_WORK(name,func)先註冊一個workqueue。以下面的範例,利用schedule_work()將這個workqueue交給一個叫"event"的公用work thread來執行。
首先我們來看註冊 workqueue的部份。
//driver/mmc/omap_hsmmc.c
static void omap_hsmmc_detect(struct work_struct *work)
{
   ...
}

static int __init omap_hsmmc_probe(struct platform_device *pdev)
{
   ...
   INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect);
   ...
}
而去啟動他的部份則是在top half的omap_hsmmc_cd_handler()則是...
//driver/mmc/omap_hsmmc.c

static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id)
{
 struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id;

 if (host->suspended)
  return IRQ_HANDLED;
 //啟動Workqueue 
 schedule_work(&host->mmc_carddetect_work);

 return IRQ_HANDLED;
}

  • Reference
Linux Kernel Development Third Edition 
Linux Device Drivers, Third Edition
Intel APIC Architecture
Interrupt_request
Essential Linux Device Drivers

2012年7月4日 星期三

GPIO整理-GPIOLIB (二)

上一篇介紹了GPIO的基礎,在這篇將詳細介紹 GPIOLIB
  • 前言
在以前,假設我們chip上面都有提供GPIO。假設我們今天要 call soc上面的GPIO我們可以使用gpio_direction_output()。但是如果要使用audio chip上面的GPIO以下圖為,我們可能就得呼叫audio_gpio_direction_output() depends on chip 的driver實作。
為什麼呢?因為每個chip如何去控制他們的GPIO作法可能會不一樣,並且要填的memory位址也不一樣。這麼一來會造成什麼現象呢?如果chip一多,要分別控制上面的GPIO 我們可能就會看到一堆 audio_gpio_direction_output、 pmu_gpio_direction_output、 charger_gpio_direction_output。而且,以gpio_direction_output為例,可能每一個driver的實作都會有一些重複的code例如檢查是否valid等等。
因此,GPIOLIB的引入可以解決這個問題。每一個chip都使用gpio_chip結構來表示,每一個chip driver實作可以定義GPIO的個數、號碼及相關的callback function。 
以gpio_direction_output為例。當我們呼叫gpio_direction_output(number)之後,gpio_direction_output實做在GPIOLIB中,它會去檢查這個number的GPIO是屬於哪個chip然後去call 該chip的driver的request實作。
  • GPIOLIB分析
註冊gpio_chip 
我們從nVidia的git抓下來的kernel裡面隨便找driver/gpio/wm8994-gpio.c為例子trace一下。他的probe如下:
static struct gpio_chip template_chip = {
 .label   = "wm8994",
 .owner   = THIS_MODULE,
/**
 *如果執行gpio_direction_input的GPIO number屬於這個chip
 *則執行wm8994_gpio_direction_in這個callback function
 *以下同義
*/
 .direction_input = wm8994_gpio_direction_in,
 .get   = wm8994_gpio_get,
 .direction_output = wm8994_gpio_direction_out,
 .set   = wm8994_gpio_set,
 .dbg_show  = wm8994_gpio_dbg_show,
 .can_sleep  = 1,
};

static int __devinit wm8994_gpio_probe(struct platform_device *pdev)
{
    struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent);
    //取得platform_device定義的platform_data
    struct wm8994_pdata *pdata = wm8994->dev->platform_data;
    struct wm8994_gpio *wm8994_gpio;
    int ret;
    wm8994_gpio = kzalloc(sizeof(*wm8994_gpio), GFP_KERNEL);
    if (wm8994_gpio == NULL)
 return -ENOMEM;
    
    wm8994_gpio->wm8994 = wm8994;
    /**
     *我們剛剛有說每一個chip使用gpio_chip結構表示。
     *上面宣告了template_chip的gpio_chip結構。
    */
    wm8994_gpio->gpio_chip = template_chip;
    wm8994_gpio->gpio_chip.ngpio = WM8994_GPIO_MAX;
    wm8994_gpio->gpio_chip.dev = &pdev->dev;

    //取得gpio_base。意思是這個chip的GPIO number從多少開始編號 
    if (pdata && pdata->gpio_base)
 wm8994_gpio->gpio_chip.base = pdata->gpio_base;
    else
 wm8994_gpio->gpio_chip.base = -1;
    
    //將這個gpio_chip 註冊進去。待會將分析gpiochip_add
    ret = gpiochip_add(&wm8994_gpio->gpio_chip);
    if (ret < 0) {
 dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
         ret);
 goto err;
    }

    platform_set_drvdata(pdev, wm8994_gpio);
    return ret;

err:
 kfree(wm8994_gpio);
 return ret;
}
接著我們看一下gpiochip_add() 做了什麼事。
//擷取gpio_desc的定義。gpio_desc定義了GPIO是屬於哪個chip,他的flag及label。
struct gpio_desc {
 struct gpio_chip *chip;
 unsigned long  flags;
#ifdef CONFIG_DEBUG_FS
 const char  *label;
#endif
};
//gpio_desc陣列存放著所有gpio的定義
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
int gpiochip_add(struct gpio_chip *chip)
{
 unsigned long flags;
 int  status = 0;
 unsigned id;
 int  base = chip->base;

 if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
   && base >= 0) {
  status = -EINVAL;
  goto fail;
 }

 spin_lock_irqsave(&gpio_lock, flags);

 if (base < 0) {
  base = gpiochip_find_base(chip->ngpio);
  if (base < 0) {
   status = base;
   goto unlock;
  }
  chip->base = base;
 }

 /**
          *these GPIO numbers must not be managed by another gpio_chip 
          *換句話說就是沒有重複到其他gpio_chip定義的號碼
        */
 for (id = base; id < base + chip->ngpio; id++) {
  if (gpio_desc[id].chip != NULL) {
   status = -EBUSY;
   break;
  }
 }
 if (status == 0) {
     for (id = base; id < base + chip->ngpio; id++) {
         //將這個chip上的所有GPIO的定義加入gpio_desc
                gpio_desc[id].chip = chip;

  /* REVISIT:  most hardware initializes GPIOs as
   * inputs (often with pullups enabled) so power
   * usage is minimized.  Linux code should set the
   * gpio direction first thing; but until it does,
   * we may expose the wrong direction in sysfs.
   */
  gpio_desc[id].flags = !chip->direction_input
   ? (1 << FLAG_IS_OUT)
   : 0;
     }
 }

 of_gpiochip_add(chip);

unlock:
 spin_unlock_irqrestore(&gpio_lock, flags);

 if (status)
  goto fail;

 status = gpiochip_export(chip);
 if (status)
  goto fail;

 return 0;
fail:
 /* failures here can mean systems won't boot... */
 pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
  chip->base, chip->base + chip->ngpio - 1,
  chip->label ? : "generic");
 return status;
}
EXPORT_SYMBOL_GPL(gpiochip_add);
到這裡,我們的kernel已經多了wm8994這個chip的GPIO可以使用了。但是從我們呼叫gpio_direction_input是怎麼跑到driver定義的wm8994_gpio_direction_in呢?
GPIOLIB與CHIP DRIVER結合 
我們看一下gpio_direction_input() 的code。
int gpio_direction_input(unsigned gpio)
{
 unsigned long  flags;
 struct gpio_chip *chip;
 struct gpio_desc *desc = &gpio_desc[gpio];
 int   status = -EINVAL;

 spin_lock_irqsave(&gpio_lock, flags);
        //先檢查這個gpio是否valid
 if (!gpio_is_valid(gpio))
  goto fail;
        //找到這個gpio屬於哪個chip的
 chip = desc->chip;
        //如果chip不存在或這個gpio不能設為input則return fail
 if (!chip || !chip->get || !chip->direction_input)
  goto fail;
        
        //下面兩行檢查該GPIO的編號是否在該chip的範圍內
 gpio -= chip->base;
 if (gpio >= chip->ngpio)
  goto fail;
 status = gpio_ensure_requested(desc, gpio);
 if (status < 0)
  goto fail;

 /* now we know the gpio is valid and chip won't vanish */

 spin_unlock_irqrestore(&gpio_lock, flags);

 might_sleep_if(chip->can_sleep);

 if (status) {
                //呼叫chip定義的request檢查是否可用
  status = chip->request(chip, gpio);
  if (status < 0) {
   pr_debug("GPIO-%d: chip request fail, %d\n",
    chip->base + gpio, status);
   /* and it's not available to anyone else ...
    * gpio_request() is the fully clean solution.
    */
   goto lose;
  }
 }
        //如果是可用的,call chip的direction_input()
 status = chip->direction_input(chip, gpio);
 if (status == 0)
  clear_bit(FLAG_IS_OUT, &desc->flags);

 trace_gpio_direction(chip->base + gpio, 1, status);
lose:
 return status;
fail:
 spin_unlock_irqrestore(&gpio_lock, flags);
 if (status)
  pr_debug("%s: gpio-%d status %d\n",
   __func__, gpio, status);
 return status;
}
EXPORT_SYMBOL_GPL(gpio_direction_input);
至此,應該就都串起來了吧!簡單來說就如下圖:
  • Reference

2012年7月3日 星期二

GPIO 整理(一)

  • 什麼是GPIO?
GPIO全名是General Purpose Input/Output。他是一個有彈性的軟體可控制的數位信號。在許多chip例如CPU或是PMU(Power Management Unit)等等的裝置上都提供了GPIO的設計。每一個GPIO用一個bit(0/1) 來表示他的狀態。GPIO可以拿來做什麼事呢?
  1. Output Value
      輸出的值是可寫的(high=1, low=0)。例如果們可以利用 GPIO來控制LED的亮/暗。
  2. Input Value
      輸入的值是可讀的(1/0)。當然我們也可以藉由讀取GPIO來知道現在例如 LED的亮/暗
  3. Used as IRQ signal
      可以把GPIO拿來當IRQ的信號,例如利用GPIO來判斷 SDcard的"插入/拔出"的動作或是按鍵的"按/放"
  4. Alternative Use
      或甚至,我們可以利用GPIO來實做成SPI等等應用
  • 使用GPIO
在Driver裡面如果要使用GPIO可以簡單分成五個步驟:
  1. Include Header
      在Driver裡面include linux/gpio.h
      #include <linux/gpio.h>
  2. Check Valid(Optional)
      每一個chip上面的GPIO數量是有限制的(而且GPIO號碼一定>=0),為了防止指定的GPIO號碼是錯的,可以先使用gpio_is_valid來檢查。
      int gpio_is_valid(int number);//return true if valid.
  3. Request GPIO(Optional, but recommended)
      gpio_request 可以檢查GPIO number是否超出範圍或<0 及 指定的GPIO是否正在使用。有時候別的地方也控制著同一個GPIO 可能會發生程式預期外的結果
      /**
       *return -EINVAL if GPIO is not valid(same as gpio_is_valid)
       *return -EBUSY if GPIO is already used
       *return 0 if GPIO if fine to use
       *因此我們只要檢查回傳質是否>=0就可以判斷是否可用。
       *第一個參數是填入GPIO的號碼、第二個參數是想要對這個GPIO的命名。
       *$ cat /sys/kernel/debug/gpio就可以看到 
      */
      int gpio_request(unsigned gpio, const char *label);
  4. Write/Read Value
      接著我們選擇要讀值還是寫值。
      //return 1 or 0
      int gpio_direction_input(unsigned gpio);
      /**
       *gpio_direction_output會作許多檢查,例如該GPIO是否可以設high?low?
       *也會呼叫gpio_is_valid來檢查是否可用等等
       *第一個參數是GPIO號碼
       *第二個參數是要寫入的值(0/1)
      */
      int gpio_direction_output(unsigned gpio, int value);
  5. Free GPIO
      當使用完GPIO之後利用gpio_free來釋放GPIO 。
      int gpio_free(unsigned gpio);
將上述串起來以對CARD_DETECT_GPIO寫入1為例:
#include <linux/gpio.h>

...

int ret = gpio_request(CARD_DETECT_GPIO, "CARD_DETECT");
if (ret<0)
    pr_err("%s: gpio_request failed for gpio %d\n",
         __func__, CARD_DETECT_GPIO);
else
    gpio_direction_output(CARD_DETECT_GPIO, 1);
gpio_free(CARD_DETECT_GPIO);
  • 其他函式
/**
 *當我們對某個GPIO設定direction_output之後
 *我們可以直接透過gpio_set_value去改變output的值
*/
void gpio_set_value(unsigned gpio, int value)


/**
 *某些裝置像omap允許設置debounce time(不是所有裝置都有支援)。
*/
int gpio_set_debounce(unsigned gpio, unsigned debounce)


/**
 *對於外部的GPIO controller裝置可能是透過SOC的I2C或其他bus來連接,要
 *控制裝置上的GPIO就必須透過I2C/其他bus來傳送命令。如果這個裝置是sleepable
 *這個時候就必須使用_cansleep了。
*/
int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);


/**
 *gpio_request_*簡化了gpio_request和gpio_direction_output/input。
 *透過一個function就可以完成 request及設定direction及初始值。
 *第一個參數和第三個參數同gpio_request, 第二個參數如下:
 *     GPIOF_DIR_IN         - to configure direction as input
 *     GPIOF_OUT_INIT_LOW - configured as output, initial level LOW
 *     GPIOF_OUT_INIT_HIGH - configured as output, initial level HIGH
*/

//request/set_direction one GPIO in a single call
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);


/**
 *如果我們要同時request多個GPIO的話,gpiolib也定義了一個GPIO的struct
 *可讓我們建成一個array丟進gpio_request_array。Struct的組成如下
*/
struct gpio {
    unsigned     gpio;
    unsigned long   flags;
    const char     *label;
};

//request/set_direction multi-GPIO in a single call
int gpio_request_array(struct gpio *array, size_t num);

//free multi-GPIO in a single call
void gpio_free_array(struct gpio *array, size_t num);
下面的範例示範了如何使用gpio_request_one及 同時設定多組GPIO
static struct gpio leds_gpios[] = {
    { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
    { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
    { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
    { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
    { ... },
};
//request/set_direction in a single call
err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
 ...

//request/set_direction multi-GPIO in a single call
err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
    ...
gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

  • Reference:

2012年6月28日 星期四

gcc strict-aliasing是什麼?

  • 前言 
思考下面這一段code
void foo(int *i1, int *i2, int *res)
{
    for(int i=0;i<10;i++){
        *res += *i1 + *i2;
    }
}
如果想要讓這段code變快,我們可能會想要這樣作
void foo(int *i1, int *i2, int *res)
{
    int tmp=*i1 + *i2;
    for(int i=0;i<10;i++)
        *res += tmp;
    }
}
但考慮到當呼叫端是如下就會發生問題:
foo(&v1,&v3,&v3);
我們永遠不知道caller 到底會丟什麼進來。因此,這樣的code是無法最佳化的。那如果是下面這個例子呢?
void foo(int32_t *i1, int32_t *i2, int64_t *res)
{
    for(int i=0;i<10;i++){
        *res += *i1 + *i2;
    }
}
res的type是int64_t理論上應該就不會有問題了吧?如果呼叫端是:
foo(&v1,&v2,(int64_t *)&v3);
就會發生我們預期外的錯誤了。
  • 什麼是strict-aliasing?
為了讓上面這種狀況不會發生最佳化後的結果是我們預期外的,我們就啟用strict-aliasing來避免"不同型態"的指標指向相同的address除非兩種型態是類似的。例如:unsigned int可以alias int但不可以alias double。這麼我們就可以避免上述的情況發生。 
  • 如何啟用strict-aliasing?
在compile時如果我們開啟-O2,-O3,-Os或直接加上-fstrict-aliasing就會啟動fstrict-aliasing的選項。
  • 簡單的方法避免Werror=strict-aliasing
假設我們的程式中需要作轉型例如
int foo() {
    short a[2];
    a[0]=1;
    a[1]=0;
    int *i=(int*)&a;
}
我們可以用union來表達
union union_a {
    int i;
    short s[2];
};
int foo() {
    union union_a u;
    u.i=1;
    u.s[0]=1;
    u.s[1]=0;
}
這樣就不會有warning: dereferencing type-punned pointer will break strict-aliasing rules的warning了。

2012年6月27日 星期三

Compile AOSP with Linaro-4.7 Note

Linaro是一個非營利的組織,主要貢獻了在不同的嵌入式平台的toolchain, kernel, platform移植或最佳化。在應用Linaro對AOSP(Android Opensource Project)最佳化的patch及使用Linaro-4.7的toolchain時會遇到一些問題的整理。
首先,參考Linaro的patch最直接的最佳化方法就是使用-O3以及strict-aliasing來作最佳化如下 :
------------------------ core/combo/TARGET_linux-arm.mk ------------------------
index 9000b86..90719b9 100644
@@ -66,7 +66,7 @@ endif
  
 TARGET_NO_UNDEFINED_LDFLAGS := -Wl,--no-undefined
  
-TARGET_arm_CFLAGS :=    -O2 \
+TARGET_arm_CFLAGS :=    -O3 \
                         -fomit-frame-pointer \
                         -fstrict-aliasing    \
下載Linaro-4.7之後,我們可以利用
$ export TARGET_TOOLS_PREFIX=[Linaro-4.7 path]/bin/arm-eabi-
來讓AOSP切換toolchain編譯。

問題列表:
  1. error: template with C linkage
      原因:錯誤使用 -isystem。例如在AOSP 的build/core/definitions.mk
               Ref.
      解法 :
      - $(addprefix -isystem ,\
      + $(addprefix -I ,\
            $(if $(PRIVATE_NO_DEFAULT_COMPILER_FLAGS),, \
      -         $(filter-out $(PRIVATE_C_INCLUDES), \
      +         $(filter-out bionic/% $(PRIVATE_C_INCLUDES), \
                    $(PRIVATE_TARGET_PROJECT_INCLUDES) \
                    $(PRIVATE_TARGET_C_INCLUDES)))) \
      + $(addprefix -isystem ,\
      +     $(if $(PRIVATE_NO_DEFAULT_COMPILER_FLAGS),, \
      +         $(filter bionic/%, \
      +             $(filter-out $(PRIVATE_C_INCLUDES), \
      +                 $(PRIVATE_TARGET_PROJECT_INCLUDES) \
      +                 $(PRIVATE_TARGET_C_INCLUDES))))) \
  2. unrecognized command line option '-mno-thumb'
      原因: gcc-4.7似乎沒有-mno-thumb 
      解法:替換成-marm 
  3. -Werror=strict-prototypes
      原因: 在C的話,foo()代表允許無窮的參數,而foo(void)代表"沒有"參數。但是在C++這兩種是一樣的。
      解法:
            將foo()替換成foo(void)
      
  4. dereferencing type-punned pointer will break strict-aliasing rules (Werror=strict-aliasing)
  5. 遇到
    error: ‘truncate’ was not declared in this scope
    error: ‘sleep’ was not declared in this scope
    error: ‘pipe’ was not declared in this scope
    error: there are no arguments to 'offsetof' that depend on a template
    parameter, so a declaration of 'offsetof' must be available
      原因:因為很多標準的C++函式庫的header 都沒有 include unistd.h了。           
      Ref.
      解法 : 加入#include <unistd.h>
  6. 遇到
    error: redeclaration of ‘int i’
    error: ‘int i’ previously declared here
      原因: 重複使用變數,例如
      void f(int);
      
      int main()
      {
          for (int i=0;;++i)
          {
            int i=5;
            f(i);
          }
          return 0;
       }
      Ref. 解法: 不要重複使用"i"
  7. unable to find string literal operator
      原因:  參考Ref. 發生的情形例如
      const char *p = "foobar"__TIME__;
      解法:在macro和 字串中間加個空格
陸續補完中...