2012年7月4日 星期三

ioremap的理由

參考資料內說得非常好,有許多理由
1. 一般io access, 有所謂的IO bus或使用memory mapped IO, 以I386而言,   它有in/out系列的instruction來做IO bus access, 而ARM則沒有. 
2. 對於IO bus access, 一般driver只要有access right就可, 而由於一般而   言C/C++ 無法generate in/out系列的instruction, 所以要以inline   assembly來達成這個目的, 而一般的inb(), inw(), outb(), outw()...等   等的functions或macros會可以做到這件事. 
3. 對於memory mapped IO, 由於是直接access memory, 必須跟OS的memory   system結合, 所以就會有一些access right, non-cacheable, virtual   address等的問題. 
4. 對PCI而言, 還有所謂的PCI bus address, 而一般PCI bus driver在啟動   後會enumerate上面的card, 了解它們的memory regions的size, 並assign   PCI bus address給每一memory region, 至於如何由system bus address   對到PCI bus address要看SOC的設計. 在access時, 是virtual address->   physical address->PCI bus address. 
5. OS並不會知道那一塊被拿來當memory mapped IO, 所以, 往往是只有開放   RAM的部份的space, 其實一般startup code其MMU的部份還會打開SOC的   register space或一些local bus banks的space, 以相同的virtual address   來使用對應的physical address, 但是, startup code一般我們希望不要   放太多變動性太大的東西, 這樣也會把太多枝枝葉葉的codes放入主要程式   中, 另外, 與PCI相關的考量放入startup code, 也會變得很繁瑣. 所以   ioremap很重要的是就是讓driver可以為它要使用的空間找到一塊virtual   address的space. 
6. 有一些扁平式的設計, 直接一開始全部對應好, 將4G virtual address對應   到等值的physical address, 所以你不用ioremap也可使用正常, 不過, 使   用ioremap也沒甚麼不好吧, 反而是養成良好的習慣. 而通常, 有virtual   memory的MMU, 一般都是只開有限的區域, 即使它們只是直接對應等值的   address, 因為這樣可以加強系統的debug功能, report非法的memory access. 
7. 一般ARM有分TLB或protection unit的, protection unit只做保護和cache/   write buffer的控制, TLB則可以實現virtual memory.

因為一般在kernel模式下操作的是virtual address,透過MMU的page table來處理virtual address與physical address,但是實體io port或者memory,要是直接存取會產生兩個問題,一個是受限於特殊硬體架構,另外一個是不應直接處理physical address。於是乎ioremap就是透過修改page table,讓physical address對應到virtual address(等於在virtual address規劃出一區塊,使OS知道使用該區塊等同在使用某區塊的physical address),這樣一來就某種程度隔開特殊的硬體架構,也避免直接使用physical address。

跟著由於硬體的特性,雖然ioreamp讓程式設計師使用實體記憶體如同使用一般memory一樣,但是因為有時硬體可能只是個io port,訊號隨著時間一直變動(有點類似volatile變數一樣),所以在讀取跟寫入的時候最好透過特殊函數如writel或者readl之類,不要直接用assign operator(等號)

http://www.programmer-club.com/showSameTitleN/embedded/1820.html
http://blog.csdn.net/do2jiang/article/details/5450839

2012年7月1日 星期日

inode、file以及container_of

回顧LDD3的第一個character device範例,其實感覺裡面一次給出了太多東西,會令人暈頭轉向,其實在宋寶華的書中給出一個比較簡單的範例


  • LDD3一次配置多個device,對多個device視為同一類,使用同一個module
  • 宋寶華一開始只有一個device,只給定一個module


兩者之間的差異決定的對程式展現的方式,其中以container_of這個macro最為經典,建議先看完宋寶華的範例,再瀏覽LDD3的範例,可以知道這個macro的關鍵性作用


LDD3展示了,一個module可以同時為同一類的裝置提供驅動程式,讓device(裝置)與operations(操作),可以結合,同時可以分開,一個module提供多個裝置,相同的操作,其中隱含了物件或者說ADT(abstract data type)的概念,這個在driver開方上很重要,udev更是把這種概念發揚光大

inode跟file兩個重要的結構提供了不同的作用,inode大多存在於kernel space,file則是user/kernel space都有(使用者一般操作file),同時也是支撐VFS (virtual file system)的兩個重要結構


  • file結構有兩個重要成員,一個是file_operations,也就是操作file的functions,另外一個是private_data指標,通常module會將對應的device資料的記憶體位置交給private_data
  • inode則擁有i_cdev或者i_bdev,分別表示character device或者block device這兩個指標,雖然一般開發者會重新包裝自己的結構,將struct cdev包裝起來,以支援定義的驅動程式,如LDD3範例


struct scull_dev {
struct scull_qset *data;  /* Pointer to first quantum set */
int quantum;              /* the current quantum size */
int qset;                 /* the current array size */
unsigned long size;       /* amount of data stored here */
unsigned int access_key;  /* used by sculluid and scullpriv */
struct semaphore sem;     /* mutual exclusion semaphore     */
struct cdev cdev;  /* Char device structure */
};

struct scull_dev *scull_devices; /* allocated in scull_init_module */
最後一個scull_devices表示一堆同類的devices

有了以上的瞭解之後來看open、read函數
static int open(struct inode *inode,struct file *filp){
...
}


static ssize_t read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){
...
}

在LDD3的範例在init函數配置好了所有的scull dev(因為不只一個device),但是在open函數的時候要如何找到對應的scull_devices中的哪一個?因為我們想把他assign給file.private_data方便以後給read函數使用
一般而言,由於inode包含了cdev這個變數,我們知道cdev來自於scull_dev裡面,所以會想藉此找出scull_dev,裡面包含了如何計算cdev應該在scull_dev內的位移,配合位移,藉由調整記憶體的address,我們就可以找到scull_dev了,而此時container_of就可以幫我們忙

struct scull_dev *dev; /* device information */


dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
constainer_of這個macro會自動完成計算位移以及轉型的動作,將目前打開的device所指向的scull_dev變數找出來。也就是只有scull_device.cdev結構成員,卻可以找到scull_dev的結構變數。做了那麼多工作,就是為了把scull_device配置給filp->private_data,後面read函數就可以應用她了

如果今天只有一個device,那麼其實也不用那麼麻煩,就如宋寶華的範例,直接將struct scull_dev onlyone設定為global變數,然後直接在open函數的時候assign給filp->private_data即可


由上面可以了解到,LDD3在一個範例內展示了

  • VFS這樣的抽象操作需要file以及inode配合
  • scull這個module可以同時支援多個一樣的裝置
  • 如何註冊character device,以及要注意的事項
  • 如何實作file operations
  • 隱含在Linux Device Driver中分層以及分工的架構
  • ...

不細看還真的很難體驗這個module竟然有如此多的東西,也感嘆這章節竟然寫得如是簡單/簡潔,真的不知道LDD3的作者是故意的還是假設讀者的Linux背景很夠

如果有興趣了解container_of到底如何運作,可以參考參考資料連結

參考資料:
http://space.itpub.net/14805538/viewspace-445624
http://tw.myblog.yahoo.com/hughes-blog/article?mid=88&prev=-1&next=87

2012年6月30日 星期六

Hello World Module

不能免俗的一個hello world等級的module,基本上就是學習編譯技巧跟觀察,一點作用都沒有
檔案為hello.c位於某個版本的kernel source下的drivers/hello目錄內,比如說/home/account/friendarm/kernel/linux-2.6.32.2/hello,底下有兩個檔案hello.c跟Makefile
Makefile只有兩行,一行指定compiler,另外一行就是module的相依行囉
編譯指令為
make -C /home/account/friendarm/kernel/linux-2.6.32.2 M='drivers/hello' modules
-C後面的參數為kernel source tree位置,後面則是要編譯的module的位置

我跟著是放到micro2440上面執行(如果在桌上型,Makefile內的CC可以省略,直接用系統內建就可以了),因為micro2440上面預設把所有訊息打開了,所以可以在console底下看到訊息輸出,不然一般應該在/var/log/messages檔案內

2012年6月28日 星期四

linux input subsystem

linux input的subsystem已經發展許久,為了簡化driver,系統發展出了input core的部分,不過也是因為這樣,使用者在使用方便之餘,已經搞不清楚到底事件的觸發是如何從source to destination了,還好linux是開放原始碼,所以trace source code還是可以瞧出端倪的

很多書本不知道是為了哪種理由,其實這部分大多很含糊的略過,只簡單提到使用input_register_device()去註冊由input_allocate_device()配置出來的device,然後跟著使用input_event()跟input_sync()完成工作,最多再給出個虛擬的滑鼠的範例,結束

看完之後我還是搞不懂,事件如何導向消化的地方,我可否自行寫一個輸入系統,再自行寫一個handler來處理呢?直覺上是可行的,但是如果不了解如何運作,恐怕也是很難做到

透過drivers/input/input.c的source code解析可以得到答案,不過網路上已經有人剖析得很清楚,我就直接借用連結過來吧
http://blog.163.com/wxiongn@126/blog/static/1178820382010724104617786/
http://blog.163.com/wxiongn@126/blog/static/117882038201072410502312/

2012年6月16日 星期六

個人認為的linux驅動程式學習步驟

linux驅動程式開發的門檻比一般程式設計的門檻高,原因在於對於硬體及韌體的了解需要比一般的更多,相較之下,如web service的開發則傾向設計的門檻比較高,比方說MVC的應用,以及類似DB的設計等等,幾乎是不同的走向

linux驅動程式開發最好對硬體有些了解(本人對硬體就很不了解,花了不少時間填補),再者對於一些作業系統的基本元件,如process、semaphore...等等要有概念,接著了解system programming(指那些system call),跟著可以開始作驅動程式開發

  • 如果是嵌入式系統,可以overview各種cpu、chip、memory、hardware interfaces開始;跟著必須補齊如boot loader、kernel、root system等等概念,oreilly的"建構嵌入式linux系統"是本這方面的好書,相對的PC上的linux就比較沒有這方面的疑問,很多都已經非常的標準化了,變化行不如嵌入式系統大
  • 基本的裝置分類方式character、block、queue,學習linux從高階的角度看待各種設備。
  • 驅動程式IO的元素,如blocking/non-blocking、synchronous/asynchronous、critical section的維護、device/device_memory IO、記憶體的操作(kmalloc、__get_free_page...)、interrupt。因為驅動程式主要就是為了輸入、輸出訊號,這些元素都是必備的
  • 接著了解bus/device/driver的架構、分層(layer)、分離、子系統(input、usb...)的應用。這部份有些書本略過了,真的很難消化,網路上資源不少,但是又很少願意以圖形說明,如果只有自己爬code會很累
  • 在貼近各種bus界面,以及硬體的控制,如PCI、USB、I2C...
  • 其他還有debug的技巧

整體linux的學習過程應該要花上好些時間,再者如果每個部份又不是很熟悉又很容易因為一同栽入,見樹不見林,我想提供一個簡單的方向讓有心的人可以稍微有點概念。
我認為最難的兩個部份,一個是linux driver的架構、各種子系統的設計,其實因為發展了許久,所以開始結構化,但是結構化就是簡化程式碼,但是提昇程式碼之間的相關性,再不了解相關性(如子系統的原理),看code就倍感吃力;其次是各種硬體跟界面的經驗,跟界面也都有他的設計的經驗跟原則,當使用者或者一般程式設計師都有OS幫忙處理,所以現在要寫這一層,就比較吃力一點。最後,當然最好的是身邊有個熟悉的人,可以讓你一直問:P但是這真的是看緣份了

最後應該就是硬體的資訊,這方面不屬於韌體工程是的範疇,但是又有點難分割,在業界有vendor廠商提供主要的幫忙,但是了解愈多愈能幫助自己完成工作

最後我想說,相較於四、五年前現在資料真的多許多,主要是拜android風行之賜,相關資料一下子有了爆炸行的成長

2012年6月15日 星期五

linux驅動程式-chapter 7

這個章節看似比較鬆散一點,不過如果從了解中斷的角度應該會比較容易入手一點
先想想中斷,中斷分類可以分成幾種

  • 從來源,分為內部中斷(cpu引起,如除0)跟外部中斷(硬體週邊引起)
  • 從是否可遮罩,分成可遮罩與不可遮罩
  • 從中斷處理函數,分成向量中斷跟非向量中斷

不論如何,基本的處理精神都是希望趕緊將中斷處理完畢跟著把控制權交還給本來的程式,很可惜有的中斷要長時間處理,比方鏡頭要拷貝整個影像,這需要長時間
所以很多OS設計就將中斷分成兩個部份,一個部份是處理引發的事情,讀入一些需要的資料(如暫存器),記憶體拷貝的部份在另外找時間處理,以如此折衷的方式進行。前面稱為中斷的top half,後續的處理過程稱為bottom half。

跟著我們知道有個獨特的中斷為計時中斷,很多軟體的timer也是基於此設計,書中先介紹不使用OS支援的方式實作timer,跟著引入有OS支援的方式

一開始介紹了如何使用jiffies來計算時間,每個jiffiers是一次震盪的時間(不管是cpu或者是主機板上的石英振盪器),一開始書本給的範例是使用busy waiting的方式來計時,也就是讓程式一直消耗cpu,且反覆觀察jiffies的變化,透過time_after、time_before等函數來比較jiffies變數之間的大小,進而達成timer的作法
很明顯busy waiting的方式當然是不好的,與其等待不如把cpu讓給有需要的人。另外就是OS為了服務更多的timer(想像很多process需要定時執行某些事情,這些process需要很多timers),於是OS維護了一個timers的list,每隔一段時間就檢驗這些list,並且將那些時間到了的process放入排程(schedule)之中
而這樣的作法就是將那些process視為half bottom,而kernel中檢驗list中的timer變成了half top,中斷發生的時候就快速篩選timer並且進入排程
一個簡化的版本是呼叫schedule_timeout()該函數中是使用timer_list來維護

接著談到中斷的bottom half的實作方式有tasklet與work queue的方式

2012年6月14日 星期四

Linux之AIO

雖然這已經是很久之前就存在的技術,但是感覺在一般的程式設計書籍並不常見(應該說比例上),但是最近AIO卻是延伸到web server上變得火紅,從node.js可見一斑
主要是因為閱讀蒐集linux driver的資料的時候看到的,因為感覺LDD這本書有點看不懂,可能礙於實際開發經驗,以及程式設計背景知識,即使知道了如何寫出driver,但是感覺並無法實際充分利用。所以我也另外買了一本大陸作者的書籍"Linux 裝置驅動程式之開發詳解(第2版)",前面講解還算通暢,可是到了AIO左右已經開始有點不知所云,轉而向網路搜尋,找到了M. Tim Jones的著作,他並非亞洲人
http://www.ibm.com/developerworks/cn/linux/l-async/
發現原來該書的作者應該是瞟竊他人的著作!!??雖然後來唸到後面有看到一個URL指向該文章,也不知道這個網路文章(已經翻譯成中文)是否為書本作者翻譯的,但是完全不加以註明,讓人覺得書本文章是他自己寫的,書中一段文字幾乎跟網頁文字相同!!再加上範例也很像。
我發現難以理解主要是因為書本作者根本把一些重要的網頁圖片拿掉,如下面的圖片,明白點出blocking/nonblocking跟asynchronous/synchronous之間衍生的關係


建議對書中看得不大懂的讀者,轉向直接閱讀該網頁的內容,其中還有四個圖案講解kernel跟這四種IO之間的關係,簡明易懂,個人看了感覺大有收穫