2015年1月30日 星期五

Using Binder to Vibrate - Android Binder 實戰 (Client Side, First Part)

    這次要示範的是如何使用 AOSP 裡面的 Binder 相關 API,來跟 system_server 裏的 VibratorService 通訊,使用震動器。

    我不是要解析 Binder 內部原理,解析 Android 原始碼的文章去 CSDN 隨便搜都一大堆(大陸人超喜歡解析原始碼),本文後面附的參考書籍也不錯,我在這邊單純只是要示範如何使用而已。以下講解的 App,已經放上 Github 了:
https://github.com/mshockwave/android-binder-demo-with-vibrator

架構

    我本來的計劃是想直接載入 libhardware(或者 libhardware_legacy) 來操作 vibrator,但是想也知道:這種不用在 Manifest 聲明權限就可以使用硬體的好康,要是現在還存在的話 Google 早就不用玩了,process 本身權限也不夠。當然如果你有 root 權限的話,可以寫一個像是 MediaServer 一樣的 native service 來做前述的事。在不 root 的情況下,似乎就只剩下乖乖跟 system_server 進行通訊來使用 vibrator 了。
    因此這個 App 就是製作一個「Native 版 AIDL 檔」來跟 system_server 裏的 VibratorService 溝通。而後面也會講到,事實上 AIDL 檔只是 Binder 的 Java 綁定的一個包裝罷了。下列文中會用到一些 AOSP 的 header files 或是 shared library,礙於篇幅關係,就先不在這邊說明如何引入了。

(一)Binder 初識

    Binder 乍看之下還頗複雜的,但仔細一看才發現,看起來複雜的地方只是「包裝」而已,它核心的部分也是簡單的訊息傳遞。藉由哪邊傳遞訊息呢?答案是 kernel ,Binder 負責訊息傳遞的組件事實上是一個 kernel module,我剛開始非常好奇為什麼要搞那麼複雜,但後來有稍微理解了一點:kernel 的記憶體空間也是每個 process 共享的,跟 Sys V 的 shared memory IPC 道理一樣是共用記憶體。

    再來我們要有的概念就是 Binder 可以分成「業務」以及「通訊」兩部分。上面剛提到的,Binder 的包裝看起來很複雜,事實上他裡面就是把業務以及通訊融合在一起,傳統的 IPC (像是我們熟悉的 Sys V 系列) 都是要手動將資料做讀寫,而 Binder 則是將物件導向裡的介面以及繼承發揮得淋漓盡致,把資料的讀寫全部藏起來。

(二)業務介面:IInterface

    先來從最簡單的業務介面介紹起。(IMyVibrator.h)
class IMyVibrator : public IInterface {

public:
    DECLARE_META_INTERFACE(MyVibrator);

    virtual bool hasVibrator(void) = 0;

    virtual void vibrate(int32_t uid, 
                         String16& opPkg, 
                         int64_t milliseconds, int32_t usageHint, 
                         sp<IBinder>& token) = 0;

    virtual void vibratePattern(int32_t uid, 
                                String16& opPkg, 
                                int64_t pattern[], int32_t repeat, int32_t usageHint,
                                sp<IBinder>& token) = 0;

    virtual void cancelVibrate(sp<IBinder>& token) = 0;

};
請先忽略函式參數的意思。這邊定義的就是最終客戶端操作的介面,其中最有趣的莫過於 DECLARE_META_INTERFACE 這個 macro 了,他定義於 IInterface 裡,目前 5.0.0 (因為最近幾個版本 AOSP 檔案路徑變動很多) 座落在 frameworks/native/include/binder/IInterface.h。
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();                                            \

上方只是先單純的做一些宣告,descriptor 顧名思義就是用來辨識這個 Binder 節點,要注意的是不要跟後面講到 ServiceManager 裡 addService/getService 的那個服務名稱搞混,這邊的 descriptor 是 Binder 驅動裡的辨識符,而後者的服務名稱只適用於 ServiceManager。asInterface 則是要等一下才會講到,但如果有用過 AIDL 的朋友就會想起,使用 AIDL 時也有 asInterface 這個函式 ( 例:IMyInterface.Stub.asInterface() ),而我在這邊就先告訴您:他們兩者做的事是一樣的。

(三)客戶端通訊介面:BpInterface

剛剛的業務介面可能會讓你一頭霧水,感覺沒在做什麼,但沒錯,就如字面上的意思:他只是個介面,所以先請耐心地看下去,看看負責通訊的 BpInterface 類。BpInterface 我們在這邊要知道的,首先是他繼承了 BpRefBase。
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);

protected:
    virtual IBinder*            onAsBinder();
};
而 BpRefBase 裡有一個成員變數 mRemote 以及跟他相關的 remote() 函式。
class BpRefBase : public virtual RefBase
{
protected:
                            BpRefBase(const sp<IBinder>& o);
    virtual                 ~BpRefBase();
    virtual void            onFirstRef();
    virtual void            onLastStrongRef(const void* id);
    virtual bool            onIncStrongAttempted(uint32_t flags, const void* id);

    inline  IBinder*        remote()                { return mRemote; }
    inline  IBinder*        remote() const          { return mRemote; }

private:
                            BpRefBase(const BpRefBase& o);
    BpRefBase&              operator=(const BpRefBase& o);

    IBinder* const          mRemote;
    RefBase::weakref_type*  mRefs;
    volatile int32_t        mState;
};
我們透過 BpInterface 的建構子來設定 mRemote,這點請稍微留意,到時候則會呼叫 mRemote 的 transact() 函式來傳送處理過後的訊息,而這邊也是本篇文章討論 Binder 部分的最底層,再往下如何跟 Binder 驅動溝通,就留給各位了,您也可以參見本篇文章後面附上的參考書籍,裡面有非常詳盡的解說。

(三)通訊 + 業務: BpXXXX

    到了這步驟,我們當然就要把業務以及通訊部分融合在一起啦。(MyVibratorProxy.cpp)
using namespace android;

//Client proxy interface
class BpMyVibrator : public BpInterface<IMyVibrator> {

public:
    BpMyVibrator(const sp<IBinder>& remote) : BpInterface<IMyVibrator>(remote) {
        ALOGD("BpMyVibrator constructor");
    }

    virtual bool hasVibrator(void) {
        ALOGD("BpMyVibrator::hasVibrator invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        remote() -> transact(TRANSACTION_hasVibrator, _data, &_reply);
        /*
        * Must read the exception before fetching return data!!
        * Since methods in AIDL file will always throw RemoteException
        */
        ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
        int retInt32 = _reply.readInt32();
        //ALOGD("Return in int: %d", retInt32);

        return (bool)(0 != retInt32);
    }

    virtual void vibrate(int32_t uid, 
                         String16& opPkg, 
                         int64_t milliseconds, int32_t usageHint, 
                         sp<IBinder>& token) {
        ALOGD("BpMyVibrator::vibrate invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        _data.writeInt32(uid);
        _data.writeString16(opPkg);
        _data.writeInt64(milliseconds);
        _data.writeInt32(usageHint);
        _data.writeStrongBinder(token);

        remote() -> transact(TRANSACTION_vibrate, _data, &_reply);
        //ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
    }

    virtual void vibratePattern(int32_t uid, 
                                String16& opPkg, 
                                int64_t pattern[], int32_t repeat, int32_t usageHint, 
                                sp<IBinder>& token) {
        ALOGD("BpMyVibrator::vibratePattern invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        _data.writeInt32(uid);
        _data.writeString16(opPkg);
        size_t len = (size_t)(sizeof(pattern) / sizeof(pattern[0]));
        _data.write((const void*)pattern, len);
        _data.writeInt32(repeat);
        _data.writeInt32(usageHint);
        _data.writeStrongBinder(token);

        remote() -> transact(TRANSACTION_vibratePattern, _data, &_reply);
        //ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
    }

    virtual void cancelVibrate(sp<IBinder>& token) {
        ALOGD("BpMyVibrator::cancelVibrate invoked");
        Parcel _data, _reply;
        _data.writeInterfaceToken(IMyVibrator::getInterfaceDescriptor());
        _data.writeStrongBinder(token);

        remote() -> transact(TRANSACTION_cancelVibrate, _data, &_reply);
        //ALOGD("%s exception code: %d", __func__, _reply.readExceptionCode());
    }
};

IMPLEMENT_META_INTERFACE(MyVibrator, "android.os.IVibratorService");
先從建構子開始看,前面有提到,我們是用建構子來傳遞通訊用的 IBinder 物件,所以一定要呼叫父建構子。接下來每一個函式都是我們在 IMyVibrator 裡宣告的業務函式,看看每一個函式都在做什麼?看起來就是把函式參數稍微處理一下,打包到 Parcel 型態的包裹裡,然後用前面提過的 remote()  與 transact() 函式傳送出去。到這邊您大概就猜的出來 BpInterface 的那個 "p" 是什麼的縮寫了:proxy。

    沒錯,在 BpMyVibrator 裡我們做的工作就像一個 proxy,代理人,當客戶端在使用 IMyVibrator 時,其實他們是呼叫這些代理函式。還有另一個問題就是,Parcel 有提供像是 int32, float 等基本型態的封裝,那麼較複雜的物件呢?答案是你的類別必須實作 Parcelable 介面,把類別可以變成 Parcel 攜帶,這部分 Android 官方文件介紹的很清楚(雖然是 java 的 Parcelable,但大同小異),這邊就不提了。另外 transact() 函式第一個參數是用來辨識傳送給哪一個函式,要注意的是 AIDL 檔案在生成的時候,是照函式的順序由上而下編號的,例如我這次傳送的對象,IVibratorService,AIDL 檔 (位於 /frameworks/base/core/java/android/os/IVibratorService.aidl) 長這樣:
interface IVibratorService
{
    boolean hasVibrator();
    void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
    void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
    void cancelVibrate(IBinder token);
}

所以我在客戶端這邊的編號就要按照他的順序:(IMyVibrator.h)
#define TRANSACTION_hasVibrator (IBinder::FIRST_CALL_TRANSACTION + 0)
#define TRANSACTION_vibrate (IBinder::FIRST_CALL_TRANSACTION + 1)
#define TRANSACTION_vibratePattern  (IBinder::FIRST_CALL_TRANSACTION + 2)
#define TRANSACTION_cancelVibrate  (IBinder::FIRST_CALL_TRANSACTION + 3)
FIRST_CALL_TRANSACTION 預設的數值是 1。

    在代理函式實作步驟裡面,有幾個重點。第一,Parcel 包以什麼順序寫入,在另一端就會以什麼順序解開,Parcel 並沒有像 Bundle 那樣是以 key-value 對儲存。第二,您應該有發現,傳送出去的包,第一個寫入的一定是前面提到的 descriptor,以供 Binder 辨識對象。第三,Parcel 原生支援打包 IBinder 物件,也就是 writeStrongBinder() 函式 (應該說是打包 strong pointer 型態的 IBinder,也就是 sp<IBinder>),因此可以看到 AOSP 裡常常把一個 IBinder 傳送給對方,當作單純的 token,就是不想再多創造一個 Parcelable 的物件,而多利用原生支持的 IBinder 傳送。第四,如上方程式碼註解的,如果對方有丟出 Exception,例如我們這個 App 因為對方是用 java 的 AIDL,一定會丟出 RemoteException,那我們必須先調用 readExceptionCode() 讀取例外,接著才能讀取到正確的返回值 (我那時在這邊卡很久)。

    在 MyVibratorProxy.cpp 最後一行有一個 IMPLEMENT_META_INTERFACE 的 macro,是不是很眼熟呢?沒錯,他就是 DECLARE_META_INTERFACE 的「另一半」,實作那些宣告。直接來看看他的內容:(也是座落於 IInterface.h)
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }                                   \
我們最困惑的 asInterface() 函式,終於在這邊獲得解答了。先看看第12行到第14行在做什麼吧,queryLocalInterface 宣告在 IBinder 類別裡,他的註解說:
/**
* Check if this IBinder implements the interface named by
* descriptor. If it does, the base pointer to it is returned,
* which you can safely static_cast<> to the concrete C++ interface.
*/
看來就是說,如果這個「業務 + 通訊 綜合體」有實作 descriptor 對應的業務介面,就把該業務介面回傳。但是 BpInterface 與 BnInterface 這兩者貌似只有 BnInterface 有實作這個函式,因此那三行現在對我們來說是沒用的,會回傳 NULL。再看下去,發現原來 asInterface 在這邊的作用單純只是 new 一個 BpInterface!那新的 BpInterface 怎麼通訊?想起前面說要稍微留意一下 BpInterface 的建構子嗎?看看這邊,obj 這個 IBinder 物件就是通訊用的,也就是會變成 BpInterface 中的 mRemote 值,可以透過 remote()  得到。這下您應該可以理解這小段的標題為什麼有一個 BpXXXX 了吧:一定要把 「業務 + 通訊 綜合體」命名成 BpXXXX 才行。

Summary

    Binder 美的地方就在於他把 IPC 向上提升一個境界,將物件導向的概念融合於自身,Firefox OS(B2G) 也有使用這個觀念,他們使用 local socket,但是用一些 IDL(介面描述語言) 加以包裝,讓開發者與使用者不用再直接處理通訊的事情。
    Second Part 將會分析比較 AIDL 與 此篇的原生 Binder 的實作,希望能夠更融會貫通,不管是 Native 對 Native 的通訊,還是如本 App 一樣 Native 對 Java 的通訊,都可以得心應手。

Book

《 深入理解 Android,卷 I 》(簡),鄧凡平,機械工業出版社,2011。
Note: 作者是 Android Media 領域的專家,所以有關 MediaServer 等等的部分書裡都介紹的不錯,唯一可惜的是 HAL 層提到的比較少,然後作者有時候用的比喻台灣人可能比較難以理解(笑。

   

2015年1月29日 星期四

How To Use Android Internal APIs

    When I was working on this demo(exercise, actually), I want to use the ServiceManager java class(which is hidden in Android SDK) since the demo is aim to build a simple native service which has a role like the system_server ( accessing some crucial system parts like hardware )except that it isn't live in the system_server process. So neither the bindService routine, which is designed for "App Service", nor the getService method, which is created for the services live in system_server, will work.

    Here are the steps:
  1. We need to build the AOSP, at least some specific framework parts, which located mostly in framework/base. You can go there and invoked the mma or mm command to build the module instead of the whole Android system.
       
  2. Assume your output dir is out, then go to out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/, copy the classes.jar to ${YOUR_SDK_DIR}/platforms/android-${YOUR_API_LEVEL}/ .
       
  3. In the dir ${YOUR_SDK_DIR}/platforms/android-${YOUR_API_LEVEL}, unpack both the classes.jar we've just copy and the android.jar since a jar file is simply an archive file.
       
  4. This step depends on your need. Since I only wants the ServiceManager class, I only have to copy content in the android folder in the output of the classes.jar, which holds the android.* classes we normally seen in the java import statements, into the corresponding android folder in the output of the android.jar. By doing this, you can still keep some libraries like dalvik.* in the origin SDK but not exist in the AOSP framework part.
       
  5. Change dir to the modified android.jar output dir. Repack them using the command:
    jar -cf ./your_new_android.jar *
    Remember to cascade your new android.jar file path RIGHT AFTER the -f option.
       
  6. Then you will got the new android.jar which can used in the App development. I also kept the original android.jar and wrote a simple script to switch between the origin one and the "Hacked" one:
    #!/bin/bash
    
    USAGE="Usage: ${0} <aosp|sdk>"
    
    case "$1" in 
    "aosp") ln -sf android_aosp.jar android.jar
     ;;
    
    "sdk") ln -sf android_sdk_origin.jar android.jar
     ;;
    *) echo $USAGE
     ;;
    
    esac
    
    exit 0
    
    In the old Eclipse era, we can add the new android.jar to the build path and raise the priority among the dependency libraries. However, since Android Studio is highly integrated with Android SDK, we can't modify the actual SDK files in the IDE.

2014年8月31日 星期日

Embedded Linux on Cubieboard2 (II): Kernel

本篇Outline


  1. Kernel 開機所需檔案
  2. Building Kernel

Kernel 開機所需檔案

    前一篇最後講完了 U-boot 的事,但如果要讓 cb2 開機,還需要一些設定檔。第一個要講的是 script.bin。
 
    script.bin 基本上是 cb2 自己的硬體設定檔,供設定的參數非常的多種,它也是個 binary 檔,原來的文本不用自己寫,官方已經有寫好了,可從 linux sunxi 的 github 抓取。
git clone https://github.com/linux-sunxi/sunxi-boards.git
script.bin 原來的文本副檔名是 .fex,而 cb2 的 fex 檔位於 sunxi-boards/sys_config/a20/cubieboard2.fex。所有可設定的硬體參數都在這個官方文件裡,在這邊我要提起的一個設定是 MAC address 問題。cb2 本身有 EMAC 的功能,簡單來說就是動態獲取 MAC address,當然你也可以把它關掉使用靜態 address,但必須要注意的是 fex 檔裡絕對不能鬧雙包,也就是同時使用 EMAC 及靜態 address,鬧雙包的話他不會噴錯,但核心網路界面就會莫名其妙的不見了。如果你要使用靜態 MAC (預設是 EMAC ),就在 fex 檔裡加入下列文字。
[dynamic]
MAC="你的address"
接著找到檔案裡的 emac_para 區段(也就是找到"[emac_para]"),把第一行 "emac_used=1" 改成 "emac_used=0"。
接下來要把 fex 檔轉換成 binary 檔,需要 linux sunxi 的工具。
git clone https://github.com/linux-sunxi/sunxi-tools.git
切換到 sunxi-tools 目錄之後,建置 fex2bin。
make fex2bin
轉換 fex 檔。
fex2bin cubieboard2.fex ./script.bin

    接著要講boot.scr。boot.scr 是 U-boot 開機時會讀取的 binary 設定檔,它原來的文本( boot.cmd )是一個腳本,可以在裡面寫你要執行的 U-boot 指令。boot.scr 並非必要,沒有它 U-boot 還是有自己的預設值,雖然如此,我還是親手來寫一遍比較有感覺吧。我在 boot.cmd 裡第一條要寫的是設定 kernel argument。cb2 完整的 kernel arguments 在這個網站,但在這邊,我只要設定最重要的幾個。
setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait panic=10
" setenv bootargs" 是 U-boot 的指令,後面接的那些才是真正的 kernel arguments,上例是用於把 boot 分割區獨立畫出來的情況,所以 root 參數,也就是根檔案系統的位置,是在 p2,如果是沒分開來的情況就要寫 root=/dev/mmcblk0p1。接下來要寫的就是載入核心以及 script.bin,這裡採用的方法是先把他們載到一個記憶體位址再用 bootm 指令開機。
fatload mmc 0 0x43000000 script.bin
fatload mmc 0 0x48000000 uImage
fatload 顧名思義就是從 FAT 型態檔案系統裡載入所需檔案,因此,如果你沒有把 boot 分割區特別畫出來,核心等檔案放在 boot 資料夾且檔案系統是用 ext2 的話,就要這樣寫。
ext2load mmc 0 0x43000000 boot/script.bin
ext2load mmc 0 0x48000000 uImage boot/uImage
接著就是開機的指令了。
bootm 0x48000000
上面講的就是要寫進 boot.cmd 的指令。最後一步是把文本轉成 binary 檔,這裡需要 mkimage 工具,沒有意外的話是包含在 U-boot 原碼裡,跟 U-boot 主體一起編譯好了,位於 U-boot 目錄底下的 tools 資料夾。
mkimage -C none -A arm -T script -d ./boot.cmd boot.scr
boot.scr 也是放在 boot 分割區(或是 boot 資料夾)裡,U-boot 開機時自己會去找。如果你之後開機時出現 machine id 不被 kernel 接受的錯誤,就先記下接受的 machine id,接著用下方指令在 boot.cmd 中設定。(假設 kernel 接受的 id 是 0xf0)
setenv machid 0xf0

Building Kernel

    編譯 kernel 這個步驟相較於前面,顯的非常簡單。首先把官方改過得 kernel 抓下來。
git clone https://github.com/cubieboard2/linux-sunxi.git -b sunxi-3.4-cb2
要注意的是不要用 cb2 的 3.3 kernel,不僅問題很多(我連開機都開不起來),貌似也沒有繼續維護了。

接著設定環境。
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
套用 cb2 預設 config。
make cubieboard2_defconfig
祈禱不會出錯吧
make all modules
建置 uImage。要注意建置 uImage 需要 mkimage 工具喔。
make uImage
基本的 kernel 建置就這麼簡單,下一篇會開始講 user space 的事。

2014年8月5日 星期二

Embedded Linux on Cubieboard2 (I): Toolchain 與 Bootloader

系列前言

    Cubieborad2 是深圳 Cubie Tech 公司 Cubieborad 系列ARM架構開發版的第二代。搭載了Allwinner A20(ARM Cortex-A7)@1GHz Dual Core處理器,1GB DDR3 記憶體,其他外部配備有 3.4GB NAND Flash,最高支援到 64GB 的 micro SD 插槽,HDMI,10/100 Ethernet,2 x USB 2.0 HOST,1 x mini USB OTG,SATA3 。
    Cubieboard 最吸引我的是它完全開源的軟硬體與廣大社群,除了頁面整理亂到不行+不定期死機的Wiki以外。
    本系列文章是紀錄我個人在移植一個基本且盡量輕小的 Linux 系統到 Cubieboard2 (簡稱cb2)時的一點紀錄。
    作者本身才剛踏入嵌入式系統這個領域,文章裡如有哪邊需要修正請不用客氣的在留言中提出。

本篇Outline

  1. Cross compile toolchain
  2. 製作 Bootloader

Cross Compile Toolchain

    我 toolchain 用的是大名鼎鼎的 linaro toolchain。基本上把 binary 載下來就好了,但是由於不明原因,編譯 cb2 的 kernel 時請用 gcc 4.7 的,至於其他像是 bootloader 就可以用 gcc 4.8。以本篇為例,我 cross compiler 的 gcc 名字就是 arm-linux-gnueabihf-gcc,值得一提的是 gnueaibhf 的 "hf" 代表的是 hard float,簡單來說就是用硬體運算浮點數,以前比較舊的處理器運算浮點數的時候是用整數的指令來做運算,後來才有專門是作浮點數運算的硬體指令出現,速度自然是比較快的。

製作 Bootloader

    Cubieboard2 使用的是 U-boot 當作他的 bootloader,我沒那麼厲害可以從零開始移植 U-boot 到這塊板子,所以我就先下載官方移植好的 U-boot 自己做編譯和安裝。基本上像 bootloader 這麼底層的東西,文件和原始碼都在 Allwinner 的 wiki 和 github 裡。
首先,下載 U-boot 原始碼。
git clone https://github.com/linux-sunxi/u-boot-sunxi.git
sunxi 是 Allwinner 底下 ARM SoC 的代稱。U-boot 設定 cross compiler 的方式非常方便,跟眾多大型專案一樣,把環境變數 CROSS_COMPILE 設定成 toolchain 名字的 prefix 就可以了。
export CROSS_COMPILE=arm-linux-gnueabihf-
注意最後面有一個"-",因為在 Makefile 裡它是這樣展開(示意):
CC = $(CROSS_COMPILE)gcc
 接下來設定 U-boot:
make Cubieboard2_config
至於全部裝置的列表是在 boards.cfg 裡,所以如果是 cb1 的 U-boot 的話就是 make Cubieboard_config。 config 完之後就大力的輸入 make 吧。

接著,你會在 U-boot 的目錄下看到一個 u-boot-sunxi-with-spl.bin 的檔案,那就是我們要燒進去的 bootloader。事實上,上面的檔案是由兩個東西組成的:U-boot 和 SPL,SPL 就是 Second Program Loader,簡單來說就是負責載入第三階段的 U-boot,描述了許多載入時所需要的硬體資訊,如果要分開燒的話 SPL 在底下的 spl 資料夾的 u-boot-spl.bin。

    下圖是 cb2 SD 卡開機區段的 layout。


SD 卡的分區我是把 boot 與主系統分開來,這樣的優點是主系統可以使用 ext4 檔案系統,因為這個官方提供的 U-boot 在讀取kernel等開機所需檔案時,目前只能支援 fat32, ext2 等比較老的檔案系統(mainline 的 U-boot 已經支援 ext4 囉),所以像是另外一種沒有把 boot 分開來的切割方式,就只能使用有支援的檔案系統。切割工具的部份,我個人試過很多像是 fdisk, sfdisk 等,最後還是覺得:用 GParted 就可以了,又快又方便。

boot 分割區我是把它格式化成 fat32(下次再來試試 ext2 看有什麼差別)外加 LBA,在 fdisk 裡面 fat32(LBA) 的型態代碼是 0x0C,但奇怪的是我用最新版的 fdisk 怎麼這種型態不見了只剩下普通 fat32,至於在 GParted 裡,LBA 是一個旗標,跟 boot 旗標一樣,要按右鍵管理旗標的那個地方。要注意的是,依照上表,磁碟前 1MB 是要保留的,雖然現在的工具都會保留 1MB,在 fdisk 裡,通常就是從第 2048 sector 開始割(通常 1 sector 是 512 bytes,0.5 KB),最後記得 boot 分割區要加 boot 旗標。我的磁碟分割就如下圖。


(畫紅底線的就是磁碟旗標)

在燒錄前,我們先要依 layout 把前 1MB先清乾淨,依本人慘痛的經驗,如果把 partition table 也清掉的話將會是一場災難,雖然 partition table 不一定如 layout 說的有 8KB 那麼大,但我們還是乖乖的保留前 8KB 吧。
dd if=/dev/zero of=/dev/sdX bs=1024 count=1016 seek=8
請把上面的 sdX 改成你自己 SD 卡的裝置檔。接著,把 bootloader 燒進去。
dd if=/path/to/u-boot-sunxi-with-spl.bin of=/dev/sdx bs=1024 seek=8
Bootloader 的準備到這邊基本上就告一個段落了。

2013年5月18日 星期六

[Linux] NVIDIA Optimus 救星 - Bumblebee

自從換成ubuntu之後,顯卡驅動的問題就一直困擾著我,最主要就是我筆電的Nvidia Geforce 610M 有 Optimus ,也就是混合模式,會自動幫你在 Intel 和 Nvidia 之間切換 比較省電,而ubuntu預設的 nvidia 驅動,nouveau,當然沒有這項功能,也少了很多nvidia原廠的額外功能(廢話XD)像溫度控制等(避免筆電燒起來)。
後來費盡了千辛萬苦,終於把原廠驅動裝成功,結果重開機發現:怎麼螢幕只有800x600? QAQ"
拜了stackoverflow大神後才知道,nvidia 沒有出任何有 Optimus 顯卡的Linux驅動 :(
好再有一位大大在回覆文底端提到Bumblebee project這東西,他第1行就寫了很清楚了:
"...a project aiming to support NVIDIA Optimus technology under Linux."
太好了來試試吧!

首先,要加入他的ppa

sudo add-apt-repository ppa:bumblebee/stable
sudo apt-get update
安裝....
apt-get install bumblebee-nvidia

<!--update 13/05/26-->
要注意的是,要先裝nvidia-current,當然安裝之前要載linux headers

結果我發現,他好像不會智慧切換顯卡與Intel,平時他一定是把顯卡關掉,這已經是一大福祉了:),如果你要用顯卡的時候,再用下列指令執行:
optirun <程式名稱>
像是我要開顯卡用vlc看電影:
optirun vlc
就這麼簡單

而且他也支援ubuntu 的3D加速,但我要再這裡警告一下:ubuntu 3D耗的電非常大,筆電族請三思,如果想關掉可以用ccsm這個可以config compiz(unity 3D主要程式)的工具,但他的設定屬進階,請謹慎使用


現在我的筆電續航力可以飆到4小時了!