Email me: jollen # jollen.org

more: Jollen 的 Embedded Linux 教育訓練

« January 2007 | (回到Blog入口) | March 2007 »

February 2007 歸檔

February 2, 2007

Linux 驅動程式的 I/O, #4: fops->ioctl 實作

延續前一篇文章的介紹,在了解 copy_to_user() copy_from_user() 二個 API 後,接著 Jollen 將由 Linux device driver 的架構層來討論 user-space 與 kernel-space 的 I/O 機制。 同時,也延續在「架構層」系列教學專欄的 debug card 範例。

基本觀念

需要由 user-space 讀取資料,或是寫入資料給 user-space 的主要 3 個 driver method 為:read、write 與 ioctl。指向 user-space 資料空間(buffer)的指標是 kernel 回呼 driver method 時所傳遞進來的,我們由 read、write 與 ioctl 的函數原型宣告來說明如何撰寫程式:

˙int card_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
˙ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp);
˙ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp);

fops->ioctl 的參數 argfops->writefops->read 的參數 buff 是指向 user-space 資料的指標。撰寫程式時,要注意資料型別上的不同。

實作 fops->ioctl

ioctl 代表 input/output control 的意思,故名思義,ioctl system call 是用來控制 I/O 讀寫用的,並且是支援user application存取裝置的重要 system call。因此,在 Linux 驅動程式設計上,我們會實作 ioctl system call 以提供 user application 讀寫(input/output)裝置的功能。

依此觀念,當 user application 需要將數字顯示到 debug card 時。範例 debug card 0.1.0 便需要實作 ioctl system call,然後在 fops->ioctl 裡呼叫 outb() 將 user application 所指定的數字輸出至 I/O port 80H。

User application 使用 GNU LIBC 的 ioctl() 函數呼叫 device driver 所提供的命令來「控制」裝置,因此驅動程式必須實作 fops->ioctl以提供「命令」給使用者。

fops->ioctl 函數原型如下:

int ioctl(struct inode *, struct file *, unsigned int, unsigned long);

Linux 驅動程式以一個唯一且不重覆的數字來代表 ioctl 的命令,設計 Linux 驅動程式時,我們必須使用 kernel 所提供的巨集來宣告命令。根據命令的「方向」,kernel 提供以下 4 個巨集供我們宣告 ioctl 命令:,

  • _IO(type,nr):表示此 ioctl 命令不指定資料向方
  • _IOR(type,nr,dataitem):此 ioctl 命令由裝置 (driver) 讀取資料
  • _IOW(type,nr,dataitem):此 ioctl 命令將資料寫入裝置
  • _IOWR(type,nr,dataitem):此 ioctl 命令同時讀寫資料

若 user application 呼叫到驅動程式未提供的命令,則回傳 -ENOTTY 錯誤代碼。

debug card 0.1.0 範例裡,我們宣告了一個 IOCTL_WRITE 命令,當 user application 呼叫此命令後,驅動程式會將 user application 所指定的數字顯示在 debug card 上。由於我們的資料方向為「寫入裝置」,因此使用的宣告巨集為 _IOW

Debug card 0.1.0 實作 fops->ioctl 的完整程式片斷如下:

#include <linux/ioctl.h>

#define	DEV_MAJOR	121
#define	DEV_NAME	"debug"
#define  	DEV_IOCTLID	0xD0
#define	IOCTL_WRITE	_IOW(DEV_IOCTLID, 10, int)

unsigned long IOPort = 0x80;

void write_card(unsigned int);

void write_card(unsigned int num)
{
	outb((unsigned char)num, IOPort);
}

int card_ioctl(struct inode *inode, struct file *filp,
	  unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
		case IOCTL_WRITE:
			write_card((unsigned int)arg);
			break;
		default:
			return -1;
	}
    	return 0;
}

struct file_operation 的定義並未列出,不過請別忘了在 fops 裡加上 ioctl system call 的欄位。

User-space

以 debug card 0.1.0 驅動程式為例,user-space 的測試程式寫法如下:

int main(int argc, char *argv[])
{
    int devfd;
    int num = 0;

    if (argc > 1) num = atoi(argv[1]);
    if (num < 0) num = 0xff;

    devfd = open("/dev/debug", O_RDONLY);
    if (devfd == -1) {
	printf("Can't open /dev/debug\n");
	return -1;
    }

    printf("Write 0x%02x...\n", num);
    ioctl(devfd, IOCTL_WRITE, num);
    printf("Done. Wait 5 seconds...\n");
    sleep(5);
    close(devfd);

    return 0;
}
Also See

February 5, 2007

研究 Dynamic Loader, #1: dlopen

學到 ELF 的格式,並了解 .text/.data/.bss section 後,接下來絕對不能錯過「ELF loader」領域最經典的題目 - dynamic loader。不把 dynamic loader 從頭到腳好好研究一遍的話,實在是可惜了!

要學習 dynamic loader 的議題,並深入核心實作,最好可以由 ld.so 程式設計的主題切入。ld.so 是 Linux 的 dynamic loader,如果 man ld.so 的話,可以得到以下解釋:

DESCRIPTION
       ld.so loads the shared libraries needed by a program, prepares the pro-
       gram to run, and then runs it.  Unless  explicitly  specified  via  the
       -static  option to ld during compilation, all Linux programs are incom-
       plete and require further linking at run time.

與 dynamic loader 有關的系統管理面議題如下,這些是基本功課,應先行了解:

  • /etc/ld.so.conf(ld.so 的設定檔)
  • ldconfig
  • LD_LIBRARY_PATH

ld.so 其實就是我們所熟悉的 /lib/ld-linux.so.2 執行檔。至於 ld.so 的程式設計,則是由以下 3 個主要的函數切入:

#include <dlfcn.h>
void *dlopen(const char *filename, int flag); 
void *dlsym(void *handle, const char *symbol); 
int dlclose(void *handle); 

這個部份可以 'man dlopen',便能得到非常詳盡的說明,同時還有一個範例程式。以下的範例是由 dlopen 的 man page 節錄出來的,我在裡頭加上了註解供您參考:

/* Filename: dl_call.c */
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main(void) 
{
    void *handle;	/* shared library 的 'handle' 指標 */
    double (*cosine)(double);   /* 指向 shared library 裡的函數 */
    char *error;	/* 記錄 dynamic loader 的錯誤訊息 */

    /* 開啟 shared library 'libm' */
    handle = dlopen ("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf (stderr, "%s\n", dlerror());
        exit(1);
    }

    dlerror();    /* Clear any existing error */

    /* 在 handle 指向的 shared library 裡找到 "cos" 函數,
     * 並傳回他的 memory address 
     */
    cosine = dlsym(handle, "cos");
    if ((error = dlerror()) != NULL)  {
        fprintf (stderr, "%s\n", error);
        exit(1);
    }

    /* indirect function call (函數指標呼叫),
     * 呼叫所指定的函數
     */
    printf ("%f\n", (*cosine)(2.0));
    dlclose(handle);
    return 0;
}

dlopen()RTLD_LAZY 參數說明如下(節錄自 man page):

RTLD_LAZY
    Perform lazy binding. Only resolve symbols as the code that references 
them is executed. If the symbol is never referenced, then it is never 
resolved. (Lazy binding is only performed for function references; references 
to variables are always immediately bound when the library is loaded.)

由於使用到 dlopen() 函數,編譯時請與 libdl 做連結:

$ gcc -o dl_call dl_call.c -ldl

以上的做法等於:

 printf ("%f\n", cos(2.0));

直接呼叫 libm 的函數時,則是與 libm 做連結:

$ gcc -o dl_call dl_call.c -lm

單純由程式設計的角度來看,利用 dlopen()+dlsym() 來「呼叫函數」與「直接呼叫函數」有什麼應用上的差異呢?事實上,libdl 的使用是很普遍的,許多軟體將自己的功能模組(modules)做成 shared library,並以 dlopen() 載入後使用,由於 shared library 是「可抽換」的,因此可透過 libdl 來做出「Plug-ins」這樣的功能。

February 7, 2007

Embedded Linux / ARM9 課程的範例:root filesystem

提供 Jollen 的 Embedded Linux / ARM9 課程的範例,本課程重心放在 root filesystem 的建構技術與觀念解說。第 1~2 天的課程實作產出範例如下:

第一份是「bootstrap root filesystgem」的製作、第二份是 cross compile「Hello World」for ARM9 的實例、第三份是 madplay project 與 nano-X project(MP3 player 與 AVI player)的實例。

第一份範例的內容可參考 Jollen 的「製作 ARM9 的 Bootstrap Root Filesystem」教學文件,第二份範例與第三份範例的教學文件尚在調校中,未來也將會發表在 Jollen's Blog 與大家分享。良好的觀念,能得到組織結構較良好的實作,並且也會讓整個實作過程比較系統化;希望將我們上課的內容與大家分享。

Bootstrap root filesystem 的實作與觀念是 Embedded Linux 的基本功,建議同學們在建立基本觀念後,再反覆練習,直到熟練為止,才能得到最大學習成效!

February 8, 2007

(無關 Embedded Linux)修改論壇註冊方式

雖然 Linux 的技術發展快速,不過,spam 的技術發展更是驚人。許多 anti-spam 的軟體似乎都敗陣下來了,還好我的 mailbox 還挺得住。

前幾天,Jollen's forum 被大量的 spam 機器人註冊並發送垃圾信,造成「Forum 訂閱會員」被大量的 spam 間接攻擊。在這裡先向 Jollen's forum 會員們道歉,前幾天,被 spam bot 瘋狂註冊我的 forum,並張貼大量的廣告信,造成 forum 會員無端收到由 forum.jollen.org 發出的 spam,造成您的諸多困擾,深感抱歉。

目前已將 forum 的註冊方式改為「經 email 認證後,由管理員手動開啟權限」,為加速您的權限啟用速度,請在註冊表單的「學員碼」欄,輸入「iamnotspam」,以加速您的帳號啟用速度(如下圖)。未填寫者,需要透過「人腦 spam 辨識」流程,在啟用速度上,可能會慢些。還請多加見諒。

iamnotspam.JPG

目前,用來對付 spam bot 的方法之一是「手動輸入驗證碼」(如下圖),這是流行許久的方法,不過已經被攻破了。Jollen's forum 就是苦主之一。

anti_spam_is_hard.JPG

February 12, 2007

Embedded Linux 測試:Full root filesystem for ARM9 階段《NFS Mount》

Bootstrap root filesystem 是一個基本且可開機的 root filesystem,針對 ARM9 的 root filesystem 建構與開發,要如何有效率的進行測試與發展是必修的一門功課。傳統的 Embedded Linux 應用,大多是以 NFS 的方式來測試 target device 的完整 root filesystem(full root filesystem);另外還有一種較「先進」的測試方式是使用 sshfs 的方式來進行,可參考 Jserv 兄的 blog「sshfs 在 Embedded Linux 開發的應用」。

以 NFS 進行 Embedded Linux 的開發測試,主要是針對 target device 的 full root filesystem 做「立即(right now)」的系統執行測試(run-time),免除「不斷打包 image file、開機」的惡夢。這是一種流行很久的 Embedded Linux 系統測試與開發方式,其概念如下:

1. 將 target 的完整 root filesystem(例如 ARM9 root filesystem)建置後,存放於 host 端的某個目錄下,例如 /home/rootfs
2. 為 target 製作一個「NFS root filesystem」,也就是 bootstrap root filesystem + NFS functionality,並使用 NFS root filesystem 將 target device 開機。
3. 設定 host 端為 NFS server。
4. 以 NFS mount 方式將 server 上的 root filesystem 目錄「mount」進來,即可在 target device 上執行 full root filesystem 裡的應用程式。

這種方式不但簡單,而且方便,需要的基礎建設如下:

1. Target device 使用的 kernel 必須支援 NFS。
2. 製作 bootstrap root filesystem 時,需要加入 mount 指令,並且開啟 mount 指令的 NFS 功能。
3. 加入 NFS functionality 至 bootstrap root filesystem。
4. 當然,您必須知道如何設定 NFS server。

項目 3. 為 NFS root filesystem 的製作,Jollen 提供一個事先建立好的 NFS root filesystem 供大家取用,請下載「nfsroot_arm.img」,這是 U-Boot 格式的 image file,請使用 U-Boot 開機。ARM9 的平臺,都能使用 nfsroot_arm.img

簡單的使用案例(scenario)

原本老王是使用 Busybox 的 tftp 指令來下載程式至 target device,可是老王覺得這樣太麻煩了,所以老王就設定 NFS server,將 /tftpboot 目錄整個 mount 在 target device 的 root filesystem 下,這樣就方便多了。首先,老王要先修改 NFS 的 export 設定檔:

# vi /etc/exports

加入以下內容:

/tftpboot *(ro,sync)

然後啟動 NFS server,以 Red Hat Linux 9 來說,應該執行以下指令:

# /etc/rc.d/init.d/nfs start

Ubuntu 的使用者,必須安裝 nfs-server 套件,接著再啟動 NFS server::

$ sudo apt-get install nfs-server
$ sudo /etc/init.d/nfs-kernel-server start

再把 /etc/exports 的內容「輸出」,Red Hat Linux 9/Ubuntu 都是執行以下指令:

# exportfs -a
# exportfs (做檢查)
/tftpboot <world>

實際畫面如下圖。我以 nfsroot_arm.img 開機,然後設定 IP address,接著再啟動 portmap service。

nfs_root_1.jpg

nfs_root_2.jpg

接著,利用 nfsroot_arm.img 將 target device 開機,設定好 target device 的 IP 後,「請務必啟動 portmap」再做 NFS mount:

# portmap
# mkdir /nfs
# mount -t nfs 10.1.36.4:/tftpboot /nfs

我們將 NFS server 上的 /tftpboot 掛載到 target device裡,如下圖。

nfs_root_3.jpg

成功後,便能在 /nfs 目錄下看到「host 端 /tftpboot 目錄裡的檔案」,恭喜老王學會了一種非常基本,而且方便的 root filesystem for cross development 測試與開發方法。

更多有關 NFS mount

另外一種應用是「把整個 root filesystem」掛進來,這時就會用到在「Embedded Linux 測試:Bootstrap root filesystem(x86)階段《程式執行測試》」日記中所提到的 chroot 指令。

再舉一例 scenario 做說明。例如,我把 ARM9 的完整 root filesystem 放在 host 端的 /home/rootfs 目錄,所以先修改 NFS server 設定:

/home/rootfs *(rw,sync,no_root_squash)

接下來一樣要用 NFS root filesystem 將 target device 開機,並在做完 NFS mount 後,立即以 chroot(for kernel 2.4)指令切換根目錄:

# mkdir /nfs
# mount -t nfs 10.1.36.4:/home/rootfs /nfs
# chroot /nfs /bin/sh

'chroot' 做法需要注意幾個細節:

1. 執行時期(run-time)目錄的問題,像是 /dev/sys(kernel 2.6)與 /proc
2. Initial script 要手動執行;或是 chroot 時,將第一個執行的動作指定為 init process。
3. 有時我只會 mount root filesystem 裡的特定目錄。

只 mount root filesystem 裡的特定目錄,實務上有時頗有用。例如,修改 NFS server 設定如下:

/home/rootfs/bin *(ro,sync)
/home/rootfs/sbin *(ro,sync)

開機後做 NFS mount:

# mount -t nfs 10.1.36.4:/home/rootfs/bin /bin
# mount -t nfs 10.1.36.4:/home/rootfs/sbin /sbin

另外,有同學問到「portmap」service 的問題,這是使用 NFS 必要的 service,請務必加入 NFS root filesystem,並在 mount 前啟動 portmap!

February 14, 2007

Linux 2.6.20 報馬仔

Linux 2.6.20 早在 2007-02-04 就釋出了,不過 Jollen 一直到今天才去看 Linux 2.6.20 的 Changelog,順便在這裡做一點筆記。

2 個星期前釋出的 kernel v2.6.20 又多了許多功能與 device driver,詳細的 Changelog 說明可參考 kernelnewbies.org 的 [LinuxChanges]。此次的更新,幾個與我的工作較密切的有:

1. Linux 2.6.20 在 arch/powerpc/ 裡新增了 Sony Platstation 3 的支援。

2. Networking device driver 方面,更新了 Tsi 108/109 的 driver。Tsi 108/109 是 Tundra 的 north bridge for PowerPC 750 晶片。

3. 在 Workqueue API 方面也做了更新。

4. MMC 方面,加入了 high speed(50 Hz)SD card 的支援、v4 high speed mode 與 v4 wide-bus mode;另外,SDHCI high speed 的支援也加入了。

附帶一提,KVM 驅動程式果然也正式加入 Linux 2.6.20 了。

延伸閱讀

February 17, 2007

OpenMoko 釋出原始碼了

OpenMoko 於台北時間 2007-02-15 正式開放原始碼下載!如果您還不曉得 OpenMoko 專案的重要性,可先行閱讀以下幾則消息:

OpenMoko 是史上頭一遭完全開放原始碼的 Linux 手機,OpenMoko 專案強調 100% 的開放與社群;隨著這個專案的正式問世,未來的 Linux mobile phone 生態想必將會出現大變化。以下是 OpenMoko announce 的 mail,為了見證這重要的歷史時刻,我將收到的 mailing-list 全文刊載:

日期: Wed, 14 Feb 2007 18:02:37 +0100   
寄件者: Harald Welte          
回給: contact@openmoko.org
收件者: announce@lists.openmoko.org
副本: community@lists.openmoko.org, openmoko-devel@lists.openmoko.org
主旨: [openmoko-announce] [ANNOUNCE] openmoko.org goes public
   
Hi! 

It is my pleasure to announce that as of now, we have opened public 
access to 

Our main portal: 
    http://www.openmoko.org/ 

Our public wiki: 
    http://wiki.openmoko.org/ 

Our subversion server: 
    http://svn.openmoko.org/ 
    http://svnweb.openmoko.org/ 

Our bugzilla: 
    http://bugzilla.openmoko.org/ 

Our 'developer file dump' 
    http://people.openmoko.org/ 

Our GForge installation 
    http://projects.openmoko.org/ 

Please also note that 
    http://lists.openmoko.org/ 
now has a number of more mailinglists.  To understand which list is used 
for what, I suggest reading 
https://wiki.openmoko.org/wiki/Development_resources#Mailing_Lists 

As indicated before, we are far from a finished end-user ready product. 

Also, please note, that given our current small team size, we will 
probably take quite a bit until we can respond to all your 
suggestions/comments and even contributions.  We hope for your 
understanding and patience. 

Cheers, 
-- 
- Harald Welte                        http://openmoko.org/ 
============================================================================ 
Software for the world's first truly open Free Software mobile phone 

OpenMoko 專案的第一支手機為 Neo1973,可以在 LinuxDevices.com 上的新聞看到他的實體照片 [Linux-powered iPhone killer available online in March]。OpenMoko & Neo1973 若推廣順利,想必許多 end-user 對這個專案的「崛起」可能會感到驚奇又不解。

OpenMoko 的成功與其重要性是可預見的,「宏觀」來看其致勝因素有:

  • 1. 強調 open & community 的定位,而不是打著 Linux 手機的旗號在作戰。
  • 2. OpenMoko 一開始就推出相當完整的社群工具(可參考 www.openmoko.org)。
  • 3. Open source:端給社群愛好者的是很完整的 patch 與 application framework。
  • 4. 從 OpenMoko 第一次 release project 開始,一直到目前實體產品快上市為止,幾近完美的 pre-ecosystem 建立手法與步驟,不但高明而且漂亮。

目前,在 OpenMoko 的 svn 可以找到 GSM 模組的 source code、GPRS 的 kernel patch、MMC/SD 的 kernel/U-Boot patch 與 GSM power management 的 Linux device driver 等。OpenMoko 端出了非常多的好東西給大家,並且也提供 OpenEmbedded 的 distribution 環境設定,真是感動了。

Sean,you're good!迎接 Mobile 2.0 世代的來臨。

February 24, 2007

OpenMoko 的 gsmd:Linux 手機的 GSM Modem 通訊程式

Linux 手機是 Embedded Linux 的重要應用項目之一,要能具備「手機」的功能,最基本的規格之一當然就是要支援 GSM(Global System for Mobile Communications)標準,也就是我們稱之為 2G 的行動電話標準。

GSM 系統使用一種稱之為 GSM modem 的裝置(也就是我們耳熟能詳的「數據機」)來做電話的「外撥(dial out)」與「接聽(answer)」功能。因此,Linux 手機要能打電話與接聽電話,必須將 target device 外接 GSM modem,並透過 GSM modem 的指令集(AT command)來對 GSM modem 下撥號或接聽指令。

GSM modem 與 target device 是以 UART(RS-232)介面連接,因此需要一個與 GSM modem 溝通的 RS-232 通訊程式。在此次 OpenMoko 所釋出的原始碼裡頭,就包含了一個 GSM modem 的通訊管理程式,稱為 gsmd(GSM daemon);此外,gsmd 也包含一個 libgsmd 的 API 程式庫供開發者使用。

OpenMoko 也提供一個 ' libgsmd-tool' 的展示範例,此程式可執行 Power On/Off、answer incoming call、dial out 與 hangup call(掛斷)的基本功能。同時,'libgsmd-tool' 也提供 AT 指令集模式(atcmd mode),讓我們可以「直接」對 GSM modem 下達 GSM 07.07 標準的指令集。

gsmd 的使用說明,已出現在 OpenMoko 的 wiki 裡了,可參考 [OpenMoko's Wiki] 有關 [Gsmd] 的部份。

延伸閱讀

關於 February 2007

此頁面包含了在February 2007發表於Jollen's Blog的所有日記,它們從老到新列出。

前一個存檔 January 2007

後一個存檔 March 2007

更多信息可在 主索引 頁和 歸檔 頁看到。

Top | 授權條款 | Jollen's Forum: Blog 評論、討論與搜尋
Copyright(c) 2006 www.jollen.org