Monday, March 23, 2009

Android on x86

因為之後的需要,這兩天開始看起 android,尤其是在 x86 的情境。不過手邊沒有硬體可以用,打算先用模擬器測試。可能是 cupcake 在我動工的前兩天 merge 到 master 時的部份變更,雖然很快就編好 image,可以進到 console,但是 UI 起不來。又多花了一天時間才順利完工。



在 android 的 Get source 頁面有編譯 ARM 版本的方式。關於 android on x86,最重要的參考資料則是 這個討論串,利用 google 也可以找到更詳細的步驟。前面提到的 cupcake merge 同時造成了 opencore 的編譯問題,解法一樣要去看最開頭提到的 merge 到 master 討論串。不過我在 Debian testing (AMD64) 上做的時候,還需要先找到 build/core/combo/linux-x86.mk 的這兩行
$(combo_target)GLOBAL_CFLAGS := $($(combo_target)GLOBAL_CFLAGS) -m32
$(combo_target)GLOBAL_LDFLAGS := $($(combo_target)GLOBAL_LDFLAGS) -m32

把 -m32 參數拿掉,然後再多安裝 {gcc,g++}-multilib 套件。前者是說不要把 host binary 預設編成 32bit,後者則是讓某些在 Android.mk 又加上 -m32 的模組可以編譯成功。

一開始我是用 VirtualBox 2.1.4,不過因為 UI 起不來,想要用 adb 來除錯,所以後來改用 QEMU 0.10.0 (x86 版),並利用它的 -redir 功能讓 host 可以 adb 進去模擬出來的機器。給 QEMU 用的 kernel 有幾件事要注意一下:為方便起見,最好把
CONFIG_NE2K_PCI=y
CONFIG_FB_VESA=y
CONFIG_FRAMEBUFFER_CONSOLE=y

直接編進 kernel,並設定 vga=xxx。如果 kernel 跑到一半停住,並且在倒數幾行出現
MP-BIOS bug: 8254 timer not connected to IO-APIC

訊息,可以在 cmdline 加上 noapic 參數解決。

前面一直提到的 UI 問題可以參考這個 dirty patch 做修改。這樣應該就完成了。

Sunday, March 1, 2009

編譯 Wayland

Wayland 是一個極精簡的 display server。它是由 Kristian Høgsberg 在工作之餘所進行的實驗性計畫。與 X server 不同,Wayland client 要負責所有的繪圖動作,server 只處理最後的合成與顯示。這邊的繪圖動作還包含視窗邊框,這在 X 的世界裡是由 window manager 完成。

client 必須把它想顯示的畫面畫在 GEM buffer 上。GEM buffer 是 kernel 在管理的資源,它在不同的時間點可能會存在於不同的地方,例如它可能在系統記憶體或者是顯示卡的記憶體上。client 想要繪圖時可以利用 mmap 取得 GEM buffer 的指標,直接對它進行操作。client 與 server 的資料交換,圖形以外的部份是透過 unix socket;圖形部份因為使用 GEM buffer,server 可以直接取得。server 最後再透過 compositor 把 client 的畫面合成並顯示到螢幕上。

Wayland 還在早期的階段,這邊提到的編譯方法可能很快就不適用 (或被簡化)。編譯 Wayland 之所以困難,主要原因在於它用到的 git repository 不好找。先讓我們找到 DRM 並且安裝起來
$ git clone git://anongit.freedesktop.org/git/mesa/drm
$ cd drm
$ ./autogen.sh --prefix=/opt/gfx
$ make
$ make install
$ export PKG_CONFIG_PATH=/opt/gfx/lib/pkgconfig
$ export LD_LIBRARY_PATH=/opt/gfx/lib


再來安裝 Mesa 跟 DRI driver (這邊選用 i915)
$ git clone git://anongit.freedesktop.org/git/mesa/mesa
$ cd mesa
$ git remote add krh git://people.freedesktop.org/~krh/mesa
$ git fetch krh
$ git checkout -b eagle krh/eagle
$ ./autogen.sh --prefix=/opt/gfx --with-dri-drivers=i915 \
--disable-gallium --disable-glw --disable-glut --disable-glu \
--without-demos
$ make
$ make install


Wayland 會用到 udev 136 之後提供的 libudev,雖然這邊只安裝 libudev,但要注意它可能會覆蓋系統上本來的檔案
$ git clone git://git.kernel.org/pub/scm/linux/hotplug/udev.git
$ cd udev
$ ./autogen.sh
$ cd udev/lib
$ make
$ make install


Wayland 目前的 compositor 是採用 eagle 這個 EGL 實作
$ git clone git://anongit.freedesktop.org/~krh/eagle
$ cd eagle
$ autoreconf -vif
$ ./configure --prefix=/opt/gfx
$ make
$ make install
$ export EAGLE_DRIVER_PATH=/opt/gfx/lib/dri


大部份的 example client 用到了 cairo-drm,這是一個直接以 GEM buffer 為 surface backend 的 cairo 分支
$ git clone git://anongit.freedesktop.org/git/cairo
$ cd cairo
$ git remote add ickle git://anongit.freedesktop.org/~ickle/cairo
$ git fetch ickle
$ git checkout -b drm ickle/drm
$ ./autogen.sh --prefix=/opt/gfx --disable-xlib
$ make
$ make install


最後,Wayland
$ git clone git://people.freedesktop.org/~krh/wayland.git
$ cd wayland
$ autoreconf -vif
$ ./configure --prefix=/opt/gfx
$ make
$ make install


Wayland 需要 kernel modesetting 的支援 (linux kernel >= 2.6.29)。再克服這一關,一個晚上折騰的結果

Friday, February 27, 2009

objdump 反組譯

objdump 反組譯是一個很實用的功能,但有時候我們拿到的不是執行檔,而是 binary image 或者是自行從記憶體 dump 下來的內容。在這種情況,要手動給 objdump 足夠的資訊,它才能完成它的工作。事實上,它需要的只是簡單的:
$ arm-angstrom-linux-gnueabi-objdump -b binary -m arm -D bootrom.bin

或者也可以先利用 objcopy 將 binary image 轉成 ELF 格式:
$ arm-angstrom-linux-gnueabi-objcopy -I binary -O elf32-littlearm -B arm --rename-section .data=.text bootrom.bin bootrom.elf

這樣也可以照本來的習慣去使用 objdump。

Friday, January 30, 2009

一千零一夜之 Console I/O

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

當一個程式被執行時,它的 stdin/stdout/stderr 會從父程序繼承。如果沒有特別指定,它們通常會指向同一個諸如 /dev/ttyX 或 /dev/pts/X 的 TTY device。很難不去好奇,當我們 getchar() 或 printf() 時,對一個 TTY device 的 I/O,在 kernel 裡頭會發生什麼事?



口語裡的 console,指的通常是圖中 VT。VT 是 virtual terminal 的縮寫,對 userspace 來說,它就像是一部真實存在著的終端機;在 kernel 裡頭,VT(的功能之一)是一個 TTY driver,它是所有 /dev/ttyX 裝置的驅動程式。對 TTY device 的 I/O 動作,會經由 TTY core 送給 TTY driver。資料往來於 TTY 與 TTY driver 的過程中,或者說圖中連結 TTY 與 VT 的線,被稱做 TTY ldisc,TTY line discipline。

之前的文章中我們看到鍵盤向 TTY 送出的資料。來自於鍵盤的輸入,可能是特別的按鍵組合,目的並不是讓 userspace 去取得這些輸入。例如,我們會敲下 Ctrl-C 來中斷目前的程式;Ctrl-Z 來暫停目前的程式。又或者,我們不希望輸入的密碼會出現在螢幕上。這些需求都在 line discipline 中被滿足。因為所有的 I/O 都會經由 line discipline,於是它可以在使用者輸入特定字元時,對目前的程式送出 SIGINT 或 SIGTSTP;它也可以選擇是否要把使用者的輸入再 echo 回 VT。從這邊也可以看到,line discipline 不只可以對資料做緩衝與處理,它還被允計針對特定的輸入做特定的行為。當我們從 VT 換到實體的 COM port 時,且當與 COM port 連結的裝置不是終端機,而是數據機時,line discipline 甚至可以拒絕所有來自 userspace 的 I/O,轉而從 PPP network device 與 userspace 做資料交換。實際上,TTY device 只是一個 transport layer,可以跟它連結的裝置千變萬化。line discipline 於是也被設計成可以抽換;當從終端機換成數據機時,line discipline 也從 N_TTY 換成 N_PPP。

跟來自鍵盤的輸入一樣,使用者對 TTY device 寫入的資料,會經過 line discipline,VT,再進入螢幕。在 drivers/char/n_tty.c 的 do_output_char 函數中
switch (c) {
...
case '\r':
if (O_ONOCR(tty) && tty->column == 0)
return 0;
if (O_OCRNL(tty)) {
c = '\n';
if (O_ONLRET(tty))
tty->canon_column = tty->column = 0;
break;
}
tty->canon_column = tty->column = 0;
break;
case '\t':
spaces = 8 - (tty->column & 7);
if (O_TABDLY(tty) == XTABS) {
if (space <>column += spaces;
tty->ops->write(tty, " ", spaces);
return spaces;
}
tty->column += spaces;
break;
...
}

可以看到 line discipline 會對特殊字元,像是換行符號或 tab 等做排版。更多關於 N_TTY 這個 line discipline 的功能可以參考 stty(1)。

VT 的架構與實體的 terminal 與 host computer 架構是對應的。要把現在手上正在上網的電腦想像成它是 terminal 與 host computer 的組成可能不是那麼容易。一個相對常見、具體的情境,是用一條線把手上的電腦與 wifi router 或手機的 UART console 連結。上圖中的 Computer B 可以理解成手上的電腦,Computer A 則是跑著 linux 的嵌入式系統。

Tuesday, January 27, 2009

一千零一夜之 keyboard in console

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

按下或放開按鍵的時候,鍵盤會產生一段長度不等的 scancode,這段 scancode 會被 keyboard driver (像是 drivers/input/keyboard/atkbd.c) 處理,轉換成類型為 EV_MSC 與 EV_KEY 的 input event。EV_MSC 事件的內容是 scancode 本身,與硬體直接相關且不容易處理,除非有特殊需求,通常我們比較關心的是 EV_KEY input event。想要處理這些事件的其它子系統,可以向 input layer 註冊 input handler。例如 evdev 會註冊一個 input handler 讓 userspace 可以從 /dev/input/eventX 直接取得鍵盤事件。kernel 的 console driver 也會向 input layer 註冊一個 handler 來取得鍵盤事件,這是我們這次想要比較深入了解的項目。

console 跟鍵盤的交互作用可以在 drivers/input/char/keyboard.c 看到。在 console 底下,鍵盤有四種模式,

#define VC_XLATE 0 /* translate keycodes using keymap */
#define VC_MEDIUMRAW 1 /* medium raw (keycode) mode */
#define VC_RAW 2 /* raw (scancode) mode */
#define VC_UNICODE 3 /* Unicode mode */

可以利用 kbd_mode 命令來切換。前面有提到 keyboard driver 會送出 EV_MSC 與 EV_KEY 兩種 input event,其中前者送出的 scancode 被用來實作 VC_RAW 模式;後者送出的 keycode,則可以用來實作 VC_MEDIUMRAW 模式。剩下的兩種模式都是 keycode 的再加工。

scancode 到 keycode 的轉換透過了 keyboard driver 定義的 keycode table 完成。在 atkbd.c 裡,如果忽略掉其它細節,它只是簡單的

keycode = atkbd->keycode[code];

一行程式碼。有些多媒體鍵盤會有一些按鍵的 scancode 不在預設的 keycode table 裡,當這些按鍵被按下或放開的時候,kernel 會印出類似

atkbd.c: Unknown key pressed (translated set 2, code 0x1e on isa0060/serio0).
atkbd.c: Use 'setkeycodes 1e <keycode>' to make it known.

的訊息。依照這段訊息提示的方法去做

# setkeycodes <scancode> <keycode>

就可以新增一筆對映的資料。setkeycodes 與 getkeycodes 命令即是用來設定或取得目前的 keycode table。值得注意的是,大部份的 keycode 有對應的 symbolic name,例如 keycode 30 的 symbolic name 是 KEY_A,keycode 48 的是 KEY_B,keycode 46 的是 KEY_C 等 (見 /usr/include/linux/input.h)。這些 symbolic name 是照美式鍵盤定義而來,拿到其它語言的鍵盤上並沒有意義。

console 下 keyboard 預設的模式是 VC_XLATE 或 VC_UNICODE。從 input layer 拿到 keycode 後,console 會呼叫 kbd_event 函數做處理。在這兩個模式下,keycode 會被轉換成 keysym

key_map = key_maps[shift_final];
...
keysym = key_map[keycode];

然後 queue 到 console buffer。stdin 接到 console 的程式即是由這邊取得使用者的輸入。這個動作會經過 line discipline,不過我們這邊不去討論。

一個 keysym 是兩個 byte,分別可以由 KTYP(keysym) 跟 KVAL(keysym) 取得它的 type 與 value。當 type 小於 0xf0 時,整個 keysym 代表一個 UCS2 的 codepoint,keysym 會被轉成 UTF-8 放進 console buffer;當 type >= 0xf0 時,與 type 相關的 handler 被呼叫,其 prototype 為

typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value,
char up_flag);

其中 value 就是剛剛看到的 KVAL(keysym)。有些 handler 會把處理過的 value 寫入 console buffer,有些則不會,這樣設計的目的以例子來看最容易理解。我們知道 shift 按著的時候,所有的字母大小寫會互換。當 shift 的按下時,鍵盤控制器送出中斷,keyboard driver 收到後把 scancode 讀出並轉成 keycode,送出 input event。console 收到 event 後利用 keymap 把 keycode 轉成 keysym。shift 的 keysym type 是 0xf7,value 是 0x00,送到對應的 handler 中

if (up_flag) {
if (shift_down[value])
shift_down[value]--;
} else
shift_down[value]++;

if (shift_down[value])
shift_state |= (1 << value);
else
shift_state &= ~(1 << value);

可以發現 shift_state 的第一個 bit 被設為 1。shift_state 是 keyboard.c 裡的 file-scope 變數,當任何一個按鍵再被按下時,kbd_event 裡這一段程式碼

shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
key_map = key_maps[shift_final];

選取了不同的 keymap,導致或許不一樣的 keysym 被送出,本來是小寫的英文字母也因而變成大寫。

在 kernel 內部實作,console 使用的是 UCS2 編碼。不管是使用者列印到 console 的字串,或者是 keysym 的 value 都會先重新編碼成 UCS2 再做進一步處理。在 VC_UNICODE 模式底下,UCS2 會以 UTF-8 形式被送進 console buffer;在 VC_XLATE 模式,則會嘗試再編碼回原來的 value 送到 console buffer。

相對於 keymap 複雜的機制,它的操作卻非常簡單。在 userspace 可以利用 loadkeys 與 dumpkeys 命令來操作 keymaps

# loadkeys de # 使用德式鍵盤 layout
# loadkeys us # 使用美式鍵盤 layout

進一步的資訊可以參考 keymaps(5)。

這篇文章我們看到了鍵盤在 console 的運作。如果換到 X(org) server,情形有點不同,而且也因驅動程式而異。例如 xf86-input-keyboard 雖然是從 console 取得鍵盤事件,但它會把 console 設為 VC_RAW 模式,自行處理 scancode。而 xf86-input-evdev 則是直接從 /dev/input/eventX 取得鍵盤事件。以後有機會也可以看看 X(org) 下的鍵盤運作。

Friday, December 26, 2008

一千零一夜之 BitBake Data class

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

曾經使用過 OpenEmbedded 的開發者應該可以想像,當 bitbake my-app 被執行時,它會去做哪些事。例如,它可能會去讀 conf 檔,然後利用這資料 (e.g., PREFERRED_PROVIDER of my-app) 決定要用哪個 bb 檔。當然 bb 檔可能會 inherit 某個 class 檔或者 include 某個 inc 檔。等到需要的資料都讀進來了,bitbake 就可以開始 unpack、pack、configure 等去逐一執行這個 bb 檔所指定的各 task。如果暫時不去理會 cache、相依性或者某 bb 檔指定的某檔案是怎麼抓下來,這個想像也就是 bitbake 核心的功能之一,也是這篇文章感興趣的項目:BitBake Data class。

以 glib-2.0-native_2.16.1.bb 為例,

require glib-2.0_${PV}.bb

FILESPATH = "${FILE_DIRNAME}/glib-2.0-${PV}:${FILE_DIRNAME}/files"
DEPENDS = "gtk-doc-native"
PR = "r2"

inherit native

do_configure_prepend() {
if [ -e ${S}/${TARGET_SYS}-libtool ] ; then
echo "${TARGET_SYS}-libtool already present"
else
cp ${STAGING_BINDIR}/${TARGET_SYS}-libtool ${S}
fi
}

do_stage () {
...
}

...

可能看出 bb 檔在做的事,主要是設定變數以及定義函數 (python or shell script)。Data class 就是用來紀綠 bitbake 從各設定檔跟 bb 檔讀進來各個變數的值。特別值得注意的是,對 Data class 來說,函數跟變數並沒有太大的不同。或者說,函數只是比較特別一點的變數。在上面的例子裡,do_stage() 其實只是定義了一個叫做 do_stage 的變數,而它的值剛好是一段 shell script。bb 檔裡還會出現一些 keyword (require, inherit 等),parser 在看到它們的時候,不一定是利用 Data class 來紀錄這些資訊。

在了解 bitbake 怎麼處理變數前我們必須先知道 Data class 提供了哪些機制。我們知道 Data class 是用來紀錄 key-value pairs 用的。又,上面提到有些變數是特殊的,所以每個 key 勢必還要有 flag 來描述其性質 (是不是可以執行,可執行的話是 python or shell script 等)。為了達到這個需求,實作上 Data class 是一個 dict of dict。也就是說,

DEPENDS = "gtk-doc-native"
S = "${WORKDIR}/git"
FILES_${PN}-locale = "${datadir}/locale"

會以

d.dict['DEPENDS']['content'] = 'gtk-doc-native'
d.dict['S']['content'] = '${WORKDIR}/git'
d.dict['FILES_${PN}-locale']['content'] = '${datadir}/locale'

的形式存到 d (an instance of class Data) 裡面。而

do_stage () { some_code }

則會變成

d.dict['do_stage']['content'] = 'some_code'
d.dict['do_stage']['func'] = '1'

利用 dict of dict,Data class 輕易地為任何的 key 提供任意多的性質。

我們知道了 Data class 讓我們可以為任意的 key 標上任意的性質,但 bitbake 的需求不僅僅只是如此。更重要的是,bitbake 想提供的是變數的處理。從上面的例子可以看到,S = "${WORKDIR}/git" 進到 Data class 裡是 d['S']['content'] = '${WORKDIR}/git' 的形式,WORKDIR 這個變數並沒有被展開。Data class 在變數展開方面採用的是 lazy 的策略。也就是說變數的展開,會在讀值的時候發生。所以要等到稍後某個 task 取 S 的值時
cd ${S}

WORKDIR 才會被即時展開。

變數除了可以出現在 value 外也可以出現在 key 的部份 (如 FILES_${PN}-locale)。不同的是,key 的變數展開發生在 d.expandKeys(),而且是不可復原的。它的目的跟我們最後想看的 prepend/append/override 有關。

在 bb 檔裡頭不難看到

do_configure_prepend() { some_code }
do_install_append() { some_other_code }
TARGET_FPU_arm = "soft"

等語法。這讓開發者可以很輕易地在 task 開始或結束的地方加上一段 script,或者讓開發者可以有條件地設定某個變數。例如,我們可能希望當目標架構是 arm 時把 TARGET_FPU 設成 soft。上面這些語法在 Data class (不完全正確) 的作法是

d.dict['do_configure']['_prepend'].append('some_code')
d.dict['do_install']['_append'].append('some_other_code')
d.dict['TARGET_FPU_arm']['content'] = 'soft'


而 prepend/append/override 發生的時機,不像變數展開是在取值時即時發生,也不像變數展開並不會去修改 d 的值,prepend/append/override 只有在 d.update_data() 時才會發生,而且修改也不可復原。例如當目標架構是 arm 時,TARGET_FPU 會被 override 成 TARGET_FPU_arm 的值

d.dict['TARGET_FPU'] = d.dict['TARGET_FPU_arm']

Override 機制比這邊討論的略為複雜一些。有興趣的人建議可以直接參考實作。

BitBake parser 規範了 bb 檔的語法。但部份 parser 允許的操作並不被 Data class 直接支援。最後,不妨猜猜看 ?=, :=, += 等操作跟 Data class 如何相互作用?

Sunday, December 14, 2008

一千零一夜之 DRI

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。


DRI 讓 X clients 可以直接利用硬體來做繪圖,這點可以利用下面指令看出來:

olvaffe@m500:~$ glxgears &
[1] 24684
olvaffe@m500:~$ cat /proc/24684/maps
08048000-0804b000 r-xp 00000000 08:03 80591 /usr/bin/glxgears
0804b000-0804c000 rw-p 00002000 08:03 80591 /usr/bin/glxgears
...
b38fa000-b58fa000 rw-s e6000000 00:0d 7315 /dev/dri/card0
b58fa000-b62fa000 rw-s e5000000 00:0d 7315 /dev/dri/card0
b62fa000-b6cfa000 rw-s e4000000 00:0d 7315 /dev/dri/card0
b6cfa000-b733a000 rw-s 2c200000 00:0d 7315 /dev/dri/card0
b733a000-b797a000 rw-s 2c200000 00:0d 7315 /dev/dri/card0
...
b79a0000-b7bbd000 r-xp 00000000 08:03 347365 /usr/lib/dri/i915_dri.so
b7bbd000-b7bd1000 rw-p 0021c000 08:03 347365 /usr/lib/dri/i915_dri.so
b7c00000-b7c07000 r-xp 00000000 08:03 66335 /usr/lib/libdrm.so.2.3.1
b7c07000-b7c08000 rw-p 00006000 08:03 66335 /usr/lib/libdrm.so.2.3.1
b7eb5000-b7f11000 r-xp 00000000 08:03 69724 /usr/lib/libGL.so.1.2
b7f11000-b7f17000 rw-p 0005b000 08:03 69724 /usr/lib/libGL.so.1.2
...

我們可以看到,glxgears 把 i915_dri.so (intel 的 driver) 載進來並且開啟了 /dev/dri/card0。而在不使用 DRI 的時候:

olvaffe@m500:~$ LIBGL_ALWAYS_INDIRECT=1 glxgears &
[1] 24834
olvaffe@m500:~$ cat /proc/24834/maps
08048000-0804b000 r-xp 00000000 08:03 80591 /usr/bin/glxgears
0804b000-0804c000 rw-p 00002000 08:03 80591 /usr/bin/glxgears
...
b7bd3000-b7bda000 r-xp 00000000 08:03 66335 /usr/lib/libdrm.so.2.3.1
b7bda000-b7bdb000 rw-p 00006000 08:03 66335 /usr/lib/libdrm.so.2.3.1
...
b7e88000-b7ee4000 r-xp 00000000 08:03 69724 /usr/lib/libGL.so.1.2
b7ee4000-b7eea000 rw-p 0005b000 08:03 69724 /usr/lib/libGL.so.1.2
...

則少了上述的檔案。這是因為繪圖方面都轉送給 X server 去處理。如果用 CPU usage 去看,也可以看出在這兩個情形下,比較忙的可能是 server 或著是 client。而就像 client 可以利用 DRI 做加速一樣,X server 在收到 client 的請求時,也可以利用 DRI 去完成這些請求,這個技術被稱為 AIGLX (Accelerated Indirect GLX)。

從上面的例子我們可以看出 DRI 這個架講的基本組成份子:提供 /dev/dri/card0 的 i915 kernel module、存取這個裝置用到的 libdrm、Mesa 提供的 libGL 與 i915_dri.so、最後 X server 那邊提供了 GLX 跟 DRI server extensions。值得一提的是,X server 這邊雖然沒有用到 libGL,但它其實用到了部份 Mesa 的 source 來實作 GLX 跟 DRI server extensions,在 AIGLX 的情況下,甚至會 dlopen i915_dri.so 來用。