外部記憶装置

脳みそ小さすぎるからメモしとく

Android-x86でLinux Kernelのドライバにパッチを当てた

この記事は、自作OS Advent Calendar 2018

adventar.org

の 12/20 の記事として書かれました。

はじめに

怒涛の2018年もあと残すところ10日ほどになりました。皆様いかがお過ごしでしょうか?

僕は10月から大学の講義が始まって以来、講義やサークルのロボット製作などが忙しく、自作OSの方はxv6をx86-64ネイティブに完全移植する作業を開始した事ぐらいしか進捗はありません。

今回の自作OS Advent Calendar 2018では、xv6を64bitUEFIで起動したことやNICのデバドラ・プロトコルスタックの実装をしたことを書いても良かったのですが、どれも中途半端な進展なので あえてアドカレに書くために新しくネタを見つけてきました。

Android-x86とは?

Android-x86とは、その名前の通りAndroidx86のプロセッサを積んだPCで動かそうというプロジェクトです。

www.android-x86.org

Android自体はオープンソースなプロジェクトでかつ、Linux Kernelを改造したものが用いられているため、 x86で動かすこと自体は困難ではないようです。 が、x86とはいっても主に使われるハードウェアがWindowsを搭載したタブレットや2in1ノートのような、 ハードウェア構成が特殊なものが多いため全ての機能を完動させることは難しいようです。

今回利用したタブレットも中国製のWindows搭載のタブレットCube iWork10(いわゆる中華Winタブ)で、タッチパネルのドライバに問題がありました。 折角の機会なので、今回はこのタッチパネルを正常に動作させるためのパッチを作成しました。

今回の環境

今回の環境は以下のとおりです。

  • ハード: Cube iWork10
  • OS: Android-x86 8.1-rc2
  • Kernel: Linux Kernel 4.18.14

問題の症状

今回発生したタッチパネルの問題とは、タッチ位置と認識される位置が反転していることです。 横方向をx,縦方向をyとすると、x座標が反転している感じです。

f:id:PiBVT:20181220144704p:plain
問題の症状

問題となるドライバの調査

まず、タッチパネルに使われているドライバ(kernel module)を探すことにします。

幸いなことに、Android-x86にはターミナルエミュレータがあり、各種コマンドでタッチパネルのドライバを調査しました。

f:id:PiBVT:20181220124959p:plain
lsmod
f:id:PiBVT:20181220124949p:plain
modinfo goodix
以上より、タッチパネルのドライバはgoodixであるようなので、goodix.cにパッチをあてることにします。

linux/drivers/input/touchscreen/goodix.cの140行目付近にある

/*
 * Those tablets have their coordinates origin at the bottom right
 * of the tablet, as if rotated 180 degrees
 */
static const struct dmi_system_id rotated_screen[] = {
#if defined(CONFIG_DMI) && defined(CONFIG_X86)
    {
        .ident = "WinBook TW100",
        .matches = {
            DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
            DMI_MATCH(DMI_PRODUCT_NAME, "TW100")
        }
    },
    {
        .ident = "WinBook TW700",
        .matches = {
            DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
            DMI_MATCH(DMI_PRODUCT_NAME, "TW700")
        },
    },
#endif
    {}
};

や、700行目付近の

if (dmi_check_system(rotated_screen)) {
        ts->prop.invert_x = true;
        ts->prop.invert_y = true;
        dev_dbg(&ts->client->dev,
            "Applying '180 degrees rotated screen' quirk\n");
}

あたりが参考になりそうです。これは、タッチパネルを180°回転させるための処理のようなので、この処理を参考にx座標だけを反転させる処理を追加することにします。

パッチの方針

以下の方針でパッチを適用することにしました。

  1. rotated_screenと同様にDMI(SMBIOS)を利用した端末認識を行う
  2. invert_x=true,invert_y=falseの処理をrotated_screenと同様の処理で行う。

DMI(SMBIOS)の確認

DMI(SMBIOS)はハードのベンダーや型番などの固有情報をもつ領域らしいです。

Linuxから確認する場合はdmidecodeが使えるようなのですが、Android-x86にはインストールされていなかったので、Windows 10側から確認することにします。

Windowsの場合は、DirectX診断ツール(dxdiag)から確認できます。 スタートメニューの検索欄で「dxdiag」と検索することで起動できます。

f:id:PiBVT:20181220125517p:plain
DirectX診断ツール
このように各種情報が取得できました。今回必要な情報である、製造元は「cube」,型番は「i15-T」であることが分かります。

パッチ作成

rotated_screenを参考に以下のパッチを作成しました。

diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
index 6a4ffe800194..4f646607e3f7 100644
--- a/drivers/input/touchscreen/goodix.c
+++ b/drivers/input/touchscreen/goodix.c
@@ -145,6 +145,18 @@ static const struct dmi_system_id rotated_screen[] = {
        {}
 };
 
+static const struct dmi_system_id x_inverted_screen[] = {
+#if defined(CONFIG_DMI) && defined(CONFIG_X86)
+       {
+               .ident = "cube i15-T",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "cube"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "i15-T")
+               }
+       },
+#endif
+       {}
+};

@@ -707,6 +719,11 @@ static int goodix_configure_dev(struct goodix_ts_data *ts)
                ts->prop.invert_y = true;
                dev_dbg(&ts->client->dev,
                        "Applying '180 degrees rotated screen' quirk\n");
+       }else if (dmi_check_system(x_inverted_screen)) {
+               ts->prop.invert_x = true;
+               ts->prop.invert_y = false;
+               dev_dbg(&ts->client->dev,
+                       "Applying 'x-axsis mirrored  screen' quirk\n");
        }
 
        error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,

処理の内容としては、DMIの製造元(DMI_SYS_VENDOR)がcube,型番(DMI_PRODUCT_NAME)がi15-Tに一致する場合は、タッチスクリーンのx座標を反転するという内容です。

Android-x86をビルドする

いよいよ、パッチをあてたLinux Kernelを含めたAndroid-x86をビルドします。 詳細な手順は

www.android-x86.org

ここにあります。

ビルド環境の構築

まずは、ビルド環境を構築します。 大量のソースコードコンパイルする必要があるので、今回は逸般の誤家庭には必ず1台あるラックサーバーを使うことにしました。

  • ハード:VMWare ESXi on FUJITSU RX300S7
  • CPU: Xeon E5-2630相当 10コア
  • Memory:24GB
  • HDD:200GB *OS: Ubuntu 18.10

ソースコードは約40GBほどあるので、余裕をみて200GBほどHDDを確保しておくことをおすすめします。 ビルドにあたり、以下のパッケージをインストールする必要があります。

repo
git
build-essential
libncurses5
m4
curl
openjdk-8-jdk
bison
libc6:i386
libncurses5:i386
libstdc++6:i386
bison:i386
libssl-dev
python-mako
libxml2-utils
isohybrid

インストールします。

$ sudo dpkg --add-architecture i386
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install repo git build-essential libncurses5 m4 curl openjdk-8-jdk bison libssl-dev python-mako libxml2-utils isohybrid
$ sudo apt install libc6:i386 libncurses5:i386 libstdc++6:i386 bison:i386

以上でビルド環境の構築は終了です。

ソースコードのダウンロード

公式の手順に従って、ソースコードをダウンロードします

$ mkdir android-x86
$ cd android-x86
$ repo init -u git://git.osdn.net/gitroot/android-x86/manifest -b $branch
$ repo sync --no-tags --no-clone-bundle

約30〜40GBほどダウンロードするので、3時間程かかります。就寝前など時間があるときにダウンロードしましょう。

パッチの適用

Linux Kernelは、android_x86/kernelにあるので、android_x86/kernel/drivers/input/touchscreen/goodix.c に先程のパッチを適用します。

ビルド

Android-x86をビルドします。 ビルドオプションには、eng,user,userdebugの3種類ありますが、今回はuserdebugでビルドすることにします。当初はuserでビルドしたのですが、永久に再起動をする症状が出てしまいました。

$ . build/envsetup.sh
$ lunch android_x86_64-userdebug
$ m -j10 iso_img

f:id:PiBVT:20181220010306p:plain
ビルド開始
これでビルドが始まります。これも大体3.5時間かかるので、時間があるときに行いましょう。

ビルドしたイメージを実機で動かす

f:id:PiBVT:20181220144714p:plain
ビルド完了
ビルドが完了すると、完成したイメージはandroid_x86/out/target/product/x86_64/android_x86_64.isoに保存されます。 このイメージを適当にdd等でUSBメモリに書き込み、タブレットで起動してみましょう。

正常にタッチパネルが動作するようになりました!

まとめ

自作OSについて何か書くつもりだったのですが、Linuxの話になってしまいました。 自作OSも楽しいのですが、既存のOSを改造する(今回はドライバでしたが)というのも楽しいもので、勉強になります。 来年はネットワークスタックを書き上げてHTTPサーバーを動作させたり、ファイルシステムの実装をしたいと思っています。 2018年も残すところわずかとなりましたが、年末の休みを利用してまた何か楽しいことをやるつもりです。

64bitUEFIでxv6を動かす方法(xv6-前編)

どうも、PiBVTです。今回は、ブートローダーから起動したxv6側でCompatibility modeからProtected modeへ移行する部分を解説していきたいと思います。 前編ではカーネルのmain関数の直前までで、後編はmain関数からの解説となります。

続きを読む

64bitUEFIでxv6を動かす方法(ブートローダー編)

セキュリティキャンプ2018で32bitのxv6を64bitUEFIから起動したPiBVTです。(長い)

セキュリティキャンプの参戦記録ではなく、どのようにしてxv6を64bitUEFIから起動したのか書いていきたいと思います。 ブートローダー編とxv6編の2本立てで、今日はブートローダー編です。

続きを読む

リレー式計算機で円周率を計算する 番外編

どうもお久しぶりです。PiBVTです。約4ヶ月ぶりの更新となります。

リレー計算機について

リレー計算機についてなのですが、一旦このプロジェクトは凍結したいと思います。 理由は以下のとおりです。

  • サークルのロボコンが忙しくなった。
  • Seccamp参加後、OS開発のほうが楽しくなっちゃった。

身勝手な理由であることは重々承知しているのですが、ご了承ください。

リレー計算機プロジェクトの再開時期としては、来年の春休み(2,3月)あたりを考えています。 それまでは、しばしお待ちください。

リレー式計算機で円周率を計算する その2

丸々1ヶ月ぶりの更新です。命令セットの決定とエミュレーターの作成は進めていたのですが、ブログ更新は完全に放置していました。 早速ですが、今回は計算アルゴリズムの大まかなフロー作成です。

ライプニッツの公式へ変更

前回の記事でマチンの公式を利用すると言っていたのですが、計画を変更しライプニッツの公式を利用することにしました。 理由は以下の計算結果が出たからです。

以下の結果は前回のN(分母)を1000に変更して計算したものです。

./leibniz
Step:252
3148

./machin
Step:5
3152

以上のような結果になったわけです。 何が問題かというと、前回分母を10000に設定したのはリレー計算機を16bitで作成すると考えていたからです。

しかし、8bitでも大変なのに16bitとなるとえげつない量のリレーが必要となり計画の達成が困難になることは明白です。 「多倍長計算」なども一応決めた命令セットをもとにアセンブラで書いてみたのですが、除算の手間を考えると現実的ではありませんでした。 そのため、Nを1000に変更してもライプニッツの公式で314が計算可能であることが分かったため、データバス幅を減らすべく、ライプニッツの公式を利用することにしました。

計算アルゴリズムの大まかなフロー作成

というわけで、ライプニッツの公式を利用することになりました。前回の計算プログラムから無駄な部分を省き改良したものが以下のとおりです。

int main(){
  int result=0;
  int u=1;
  int l=1;
  int step = 1;
  while(u != 0 && l != 0){
    u = 4000/(step*2-1);
    l = 4000/(step*2+1);
    result = u - l + result;
    step += 2;
  }
return result;
}

このプログラムを実行するために必要な命令は大きく分けて、「加算」・「減算」・「乗算」・「減算」・「分岐」 の5つです。1つずつ考えていくことにします。

加算

この命令に関しては何も言う必要は無いと思います。今回の計算に関しては4000以下つまり符号なしで12bitまでの加算が出来ることが求められます。

減算

加算と同様基本的な演算です。今回はデータバス幅の関係上、符号なしの数も扱いたいので補数による減算ではなく、減算器をハード的に実装することになります・

乗算

乗算は加算とシフト、AND命令で全て実現できるのですが、プログラムはかなり長いものになります。 そのため、今回は小さな乗算器をハード実装してソフトウェアで大きな数も扱えるようにする方法でいきます。

除算

除算をハードで実装するとなるとかなり大変なので、これは最初からソフトウェアでエミュレーションすることにします。 幸いなことに多倍長計算は扱わないので比較的に簡単にできそうな気はします。

分岐

このプログラムにおいて最も重要な命令です。whileループの条件式にNon Zero判定を利用し、 除算のソフトウェアエミュレーションでは2数の大小比較の分岐命令を必要とします。 大小比較は比較器によって行うことができますが、今回はリレー数も考慮して 減算命令と正数分岐、負数分岐、0分岐命令を利用することにします。

まとめ

エミュレーターアセンブリで計算プログラムを書いてみた結果、以上のような方針で進めることにしました。 命令セットは大方完成しているのですが、さらに必要・不必要な命令がないか精査して正式に決定したあとで記事にしたいと思います。 エミュレーターも作っていたのですが、初めて作ったこともありスパゲティ状態なので0から作り直したいと思います。

リレー式計算機で円周率を計算する その1

サークルでの作業やら何やらで更新が遅れてしまいました。今回は使用する円周率計算公式の選定です。

円周率計算公式の候補

ライプニッツの公式

ライプニッツの公式(Wikipedia)

別名マータヴァ・ライプニッツ級数。円周率を求めるための公式の一つであるが、収束が非常に遅い(Wikipediaより)

1-\frac{1}{3}+\frac{1}{5}-\frac{1}{7}+\frac{1}{9}-\cdot\cdot\cdot = \frac{\pi}{4}

\sum_{n=0}^{\infty} \frac{(-1) ^ {n}}{2n+1} = \frac{\pi}{4}

マチンの公式

マチンの公式(Wikipedia)

グレゴリー級数を利用した、非常に収束の速い級数。1兆2411億桁まで計算したという記録もある。(Wikipediaより)

4\arctan\frac{1}{5} - \arctan\frac{1}{239} = \frac{\pi}{4}

をグレゴリー級数

\arctan x = \sum_{n=0}^{\infty} \frac{(-1)^{n}}{2n+1} x^{2n+1} = x - \frac{1}{3}x^{3} + \frac{1}{5}x^{5} - \frac{1}{7}x^{7} + \cdot\cdot\cdot

を利用することによって計算する。

実際に計算してみた

実際にテストプログラムを書き、計算してみました。固定小数点演算を前提とした計算のため、予め分子を10000倍し、項が1未満になった時点で計算を終了します。

ライプニッツの公式

プログラム

#include <stdio.h>
#define N 10000
#define MAX_STEP 200
int main(){
  int result=0;
  int step = 1;
  int u=1;
  int l=1;
  while(u >= 1 && l >= 1){
    u = (int)(N/(step*2-1));
    l = (int)(N/(step*2+1));
    result = u - l + result;
    step+=2;
    if(result*4 >= 31400 && result*4 < 31500){
      break;
    }
  }
  printf("Step:%d\n",(step+1)/2);
  printf("%d\n",result*4);
}

結果

Step:210
31400

目標値3.14を計算するまでに210ステップでした。

マチンの公式

プログラム

#include <stdio.h>
#define N 10000
#define MAX_STEP 200
int step=0;   
int test(int x){
    int a=x;
    int b=1;
    int pos=1;
    int ans;
    step++;
    ans=N/x;
    printf("Step:%d\n",step);
    int flag=1;
    while(pos>=1){
          a=a*x*x;
      b+=2;
      pos=N/(b*a);
      if(flag==0){
        ans+=pos;
            flag = 1;
      }else{
        ans-=pos;
            flag = 0;
      }
      step++;
      printf("Step:%d\n",step);
    };
    return ans;
}


int main(){
    printf("%d", 4*(4 * test(5) - test(239)));
    return 0;
}

結果

Step:5
31420

1つの項あたりの計算量がかなり異なるため一概には言えませんが、収束はかなり速いことが分かります。 計算誤差も桁数が増加した場合はマチンの公式のほうが有利であると考えられます。

まとめ

多倍長計算や乗算・除算の計算コストなどを全く考慮していないため、どちらの公式がいいとは言えませんが、桁数が増加した場合の誤差を考慮し、CPUエミュレータ作成まではマチンの公式を利用する方針とします。

(と言っても、利用する四則演算は変わらないんですけどね...)