12/27/2011

Linux ハードウェア デバッグコマンド その2

昨日は力尽きたので、今日投稿するっす。
先週に引き続き、Linuxでユーザーランドからの物理メモリの読み書きコマンドです。
mmapを使って仮想メモリ経由で読み書きするようにしてみました。

例によってソースを晒しておきます。


/*
 * mem.c
 * ver.0.1:  Dec 26, 2011  S.Ishihara
 */


#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>




#define DEV_PATH "/dev/mem"


int
main(int argc, char *argv[])
{
    int             opt;
    extern char     *optarg;
    extern int      optind, opterr;
    int             width = 1;  /* default byte access */
    unsigned int    memaddr, wdata;
    unsigned int    pgoffset, pgaddr;
    unsigned int    pagesize = sysconf(_SC_PAGESIZE);
    unsigned char   *p;
    int             fd;


    while ((opt = getopt(argc, argv, "w:")) != -1) {
        if (opt == 'w') {
            width = atoi(optarg);
        } else {
            goto error;
        }
    }


    argc -= optind;
    argv += optind;


    fd = open(DEV_PATH, O_RDWR);
    if (fd <= 0) {
        fprintf(stderr, "open error: %s\n", DEV_PATH);
        return 1;
    }


    if (argc == 1) {
        /* Read Mem */
        memaddr  = strtoul(argv[0], NULL, 16);
        pgoffset = memaddr & (pagesize -1);
        pgaddr   = memaddr & ~(pagesize -1);
        p = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, pgaddr);
        if (p < 0) {
            fprintf(stderr, "mmap error\n");
            return 1;
        }
        if (width == 1) {
            printf("0x%08x: 0x%02x\n", memaddr, *(p + pgoffset));
        } else if (width == 2) {
            printf("0x%08x: 0x%04x\n", memaddr, *((unsigned short *)(p + pgoffset)));
        } else if (width == 4) {
            printf("0x%08x: 0x%08x\n", memaddr, *((unsigned int *)(p + pgoffset)));
        } else {
            goto error;
        }
    } else if (argc == 2) {
        /* Write Mem */
        memaddr  = strtoul(argv[0], NULL, 16);
        pgoffset = memaddr & (pagesize -1);
        pgaddr   = memaddr & ~(pagesize -1);
        p = mmap(NULL, pagesize, PROT_WRITE, MAP_SHARED, fd, pgaddr);
        if (p < 0) {
            fprintf(stderr, "mmap error\n");
            return 1;
        }
        wdata  = strtoul(argv[1], NULL, 16);
        if (width == 1) {
            *(p + pgoffset) = (unsigned char)wdata;
        } else if(width == 2) {
            *((unsigned short *)(p + pgoffset)) = (unsigned short)wdata;
        } else if(width == 4) {
            *((unsigned int *)(p + pgoffset)) = (unsigned int)wdata;
        } else {
            goto error;
        }
    } else {
        goto error;
    }
    munmap(p, pagesize);
    close(fd);
    return 0;


error:
    printf("Usage: Mem [-w WIDTH] ADDRESS [DATA]\n"
            "Mem read or write.\n"
            "  -w        number of byte width. permit 1(default), 2, 4\n"
            "\n"
            "This command executable only root user.\n"
            "Mem address possible range 32bit.\n"
            "\n"
            "Examples:\n"
            "  Mem a0000               Read memory from address 0xa0000.\n"
            "  Mem a0000 31            Write memory address 0xa0000 to 0x31.\n"
            "  Mem -w4 20000 5a5a5a5a  Write memory address 0x20000 to 0x5a5a5a5a.\n"
            "\n");
    return 1;
}


試行錯誤しながら、ごそっと書いたので、冗長だったり、手抜きな部分もありますが、ぼちぼち動作するので、とりあえずよしとします。
目的としては、PCIデバイスのMMIOを直接読み書きしたいわけなんですが、あんまし真面目に動作確認できてないっす。使いながら確認してみます。

12/19/2011

Linux ハードウェア デバッグコマンド その1

今日は、Tx50の開発はお休みして、表題の件にちょっと時間を割くことにしました。

なんというか、本業のほうで、やっかいなデバッグをやることになりまして、
Linuxからハードウェアの状態を手軽に知る術が欲しくなって、コマンド作ることにしました。本業では結果を出すのに追われて、なかなか腰を据えてこういった開発補助ツールを作ることに時間が割けないわけです。

ユーザーランドからハードウェアに直接アクセスすることは、なにかと問題があったりで、本来ドライバを作るべきですが、開発段階でハードウェアの状態をちょっと確認したいというときは、さくっとコマンドで済ませたいところです。

というわけで、今回はユーザーランドから直接I/OとPCIのコンフィグレーション空間を読み書きできるコマンドを作成します。ホントは物理メモリとPCI Expressの拡張コンフィグレーション空間の読み書きコマンドも作りたかったんですが、時間が足りなかったので、それはまた今度にします。

で、おもむろにソースです。たぶん組み込み開発でLinux使ったプロジェクトのハード屋さんには、需要があるんじゃなかろうかと思いソースを晒しときます。(自分が会社からソースをゲットする目的もありますが)


/*
 * io.c
 * ver.0.1:  Dec 18, 2011  S.Ishihara
 */


#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>


int
main(int argc, char *argv[])
{
    int             opt;
    extern char     *optarg;
    extern int      optind, opterr;
    int             width = 1;  /* default byte access */
    unsigned short  ioaddr;
    unsigned int    wdata;


    while ((opt = getopt(argc, argv, "w:")) != -1) {
        if (opt == 'w') {
            width = atoi(optarg);
        } else {
            goto error;
        }
    }


    argc -= optind;
    argv += optind;


    iopl(3);


    if (argc == 1) {
        /* Read I/O */
        ioaddr = strtoul(argv[0], NULL, 16);
        if (width == 1) {
            printf("0x%04x: 0x%02x\n", ioaddr, inb(ioaddr));
        } else if (width == 2) {
            printf("0x%04x: 0x%04x\n", ioaddr, inw(ioaddr));
        } else if (width == 4) {
            printf("0x%04x: 0x%08x\n", ioaddr, inl(ioaddr));
        } else {
            goto error;
        }
    } else if (argc == 2) {
        /* Write I/O */
        ioaddr = strtoul(argv[0], NULL, 16);
        wdata  = strtoul(argv[1], NULL, 16);
        if (width == 1) {
            outb(wdata, ioaddr);
        } else if(width == 2) {
            outw(wdata, ioaddr);
        } else if(width == 4) {
            outl(wdata, ioaddr);
        } else {
            goto error;
        }
    } else {
        goto error;
    }
    return 0;


error:
    printf("Usage: io [-w WIDTH] ADDRESS [DATA]\n"
            "I/O read or write.\n"
            "  -w        number of byte width. permit 1(default), 2, 4\n"
            "\n"
            "This command executable only root user.\n"
            "I/O address possible range 16bit.\n"
            "\n"
            "Examples:\n"
            "  io 3f8               Read I/O from address 0x3f8.\n"
            "  io 3f8 0x31          Write I/O address 0x3f8 to 0x31.\n"
            "  io -w4 cf8 80000000  Write I/O address 0xcf8 to 0x80000000 double word.\n"
            "\n");
    return 1;
}





/*
 * pci.c
 * ver.0.1:  Dec 18, 2011  S.Ishihara
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/io.h>

#define PCI_INDEX 0xcf8
#define PCI_DATA  0xcfc

unsigned char
pciRead8(unsigned char bus, unsigned char dev, unsigned char fnc, unsigned char reg)
{
    outl((1 << 31) + (bus << 16) + ((dev & 0x1f) << 11) +\
            ((fnc & 0x07) << 8) + (reg & ~(0x03)), PCI_INDEX);
    return inb(PCI_DATA + (reg & 0x03));
}

unsigned short
pciRead16(unsigned char bus, unsigned char dev, unsigned char fnc, unsigned char reg)
{
    outl((1 << 31) + (bus << 16) + ((dev & 0x1f) << 11) +\
            ((fnc & 0x07) << 8) + (reg & ~(0x03)), PCI_INDEX);
    return inw(PCI_DATA + (reg & 0x02));
}

unsigned int
pciRead32(unsigned char bus, unsigned char dev, unsigned char fnc, unsigned char reg)
{
    outl((1 << 31) + (bus << 16) + ((dev & 0x1f) << 11) +\
            ((fnc & 0x07) << 8) + (reg & ~(0x03)), PCI_INDEX);
    return inl(PCI_DATA);
}

void
pciWrite8(unsigned char bus, unsigned char dev, unsigned char fnc, unsigned char reg, unsigned char value)
{
    outl((1 << 31) + (bus << 16) + ((dev & 0x1f) << 11) +\
            ((fnc & 0x07) << 8) + (reg & ~(0x03)), PCI_INDEX);
    outb(value, PCI_DATA + (reg & 0x03));
}

void
pciWrite16(unsigned char bus, unsigned char dev, unsigned char fnc, unsigned char reg, unsigned short value)
{
    outl((1 << 31) + (bus << 16) + ((dev & 0x1f) << 11) +\
            ((fnc & 0x07) << 8) + (reg & ~(0x03)), PCI_INDEX);
    outw(value, PCI_DATA + (reg & 0x02));
}

void
pciWrite32(unsigned char bus, unsigned char dev, unsigned char fnc, unsigned char reg, unsigned int value)
{
    outl((1 << 31) + (bus << 16) + ((dev & 0x1f) << 11) +\
            ((fnc & 0x07) << 8) + (reg & ~(0x03)), PCI_INDEX);
    outl(value, PCI_DATA);
}


int
main(int argc, char *argv[])
{
    int             opt;
    extern char     *optarg;
    extern int      optind, opterr;
    int             width = 1;  /* default byte access */
    unsigned char   bus, dev, fnc, reg;
    unsigned int    wdata;

    while ((opt = getopt(argc, argv, "w:")) != -1) {
        if (opt == 'w') {
            width = atoi(optarg);
        } else {
            goto error;
        }
    }

    argc -= optind;
    argv += optind;

    iopl(3);

    if (argc == 4) {
        /* Read Pci */
        bus = strtoul(argv[0], NULL, 16);
        dev = strtoul(argv[1], NULL, 16);
        fnc = strtoul(argv[2], NULL, 16);
        reg = strtoul(argv[3], NULL, 16);
        if (width == 1) {
            printf("B:0x%02x/ D:0x%02x/ F:0x%02x/ R:0x%02x: 0x%02x\n",
                    bus, dev, fnc, reg, pciRead8(bus, dev, fnc, reg));
        } else if (width == 2) {
            printf("B:0x%02x/ D:0x%02x/ F:0x%02x/ R:0x%02x: 0x%04x\n",
                    bus, dev, fnc, reg, pciRead16(bus, dev, fnc, reg));
        } else if (width == 4) {
            printf("B:0x%02x/ D:0x%02x/ F:0x%02x/ R:0x%02x: 0x%08x\n",
                    bus, dev, fnc, reg, pciRead32(bus, dev, fnc, reg));
        } else {
            goto error;
        }
    } else if (argc == 5) {
        /* Write Pci */
        bus   = strtoul(argv[0], NULL, 16);
        dev   = strtoul(argv[1], NULL, 16);
        fnc   = strtoul(argv[2], NULL, 16);
        reg   = strtoul(argv[3], NULL, 16);
        wdata = strtoul(argv[4], NULL, 16);
        if (width == 1) {
            pciWrite8(bus, dev, fnc, reg, wdata);
        } else if(width == 2) {
            pciWrite16(bus, dev, fnc, reg, wdata);
        } else if(width == 4) {
            pciWrite32(bus, dev, fnc, reg, wdata);
        } else {
            goto error;
        }
    } else {
        goto error;
    }
    return 0;

error:
    printf("Usage: pci [-w WIDTH] BUS DEVICE FUNCTION REGISTER [DATA]\n"
            "Pci configuration space read or write.\n"
            "  -w        number of byte width. permit 1(default), 2, 4\n"
            "\n"
            "This command executable only root user.\n"
            "\n"
            "Examples:\n"
            "  pci 00 1f 00 00          Read pci from bus 0x00 dev 0x1f func 0x00 reg 0x00.\n"
            "  pci -w2 00 1f 00 10 500  Write pci bus 0x00 dev 0x1f func 0x00 reg 0x10 to 0x500.\n"
            "\n");
    return 1;
}


とりあえず、ばさっと書いただけで、細かく動作確認していなかったり、処理内容もちょっとエラー処理が手抜きぎみだったり、usageがカタコト英語だったりと、つっこみどころはありますが、まあ、個人利用レベルではなんとか使えそうな感じです。

*このブログをみて、プログラムを利用しようとしている方へ
    まったくもって、動作は保証しません。自己責任で使ってください。使用方法はコマンドのUsage参照
     バグに関する報告は歓迎します。それからプログラムの性格上、使いようによっては、
     簡単にシステムを破壊できてしまいます。I/OやPciコンフィグレーション空間について
     知識を有していない方は、使わないほうが懸命です。
     このプログラムを使ってあなたのPCや基板が燃えたり、レンガになっても責任は負いかねます。
   

ビルドはgccが使えるLinux環境で

$ gcc -o io io.c
$ gcc -o pci pci.c

って感じです。あれ、組み込みだとLinuxのファームにコンパイラ入ってなくて別の環境でビルドすることもあると思います。そんときにcライブラリの問題があったりするので、「-static」オプションをつけてビルドすることで、回避できることがあったりなかったりです。


ああ、明日もデバッグもりもりやります。

12/12/2011

TX-50開発キットで遊ぶ その12

熱燗がおいしい季節になりました。最近、なにかと忙しくてあまりTX-50弄る時間が取れなかったんですが、久しぶりに投稿します。シリアルドライバはちょっと行き詰まりぎみなので、保留ぎみで。年末休みとかで、がっつり時間とれるときに腰を据えてやろうかな。

そうそう、久々にIntelのサイトを覗いたらBLDKがアップデートされてました。CrownBay向けのパッケージは現在CB-EDKII-PostGold-2.3.6.7というのが最新です。しかもなんと、Linux向けのパッケージがリリースされてるじゃないですか。素敵です。Linuxだとgccでビルドできるっぽいです。しかし、デバッガとかどうなるんですかねえ、WinDbgベースのIntel UDK Debuggerは使えないでしょうし。今度、試してみます。
http://www.intel.com/p/ja_JP/embedded/hwsw/software/bldk?iid=subhdr-JP+hwsw_bldk#download


というわけで、ソースもいい感じでぐちゃぐちゃになって来たところなので、最新版で仕切りなおしました。動作上目立つところといえば、EfiShellが2.0になってますね。
ああ、あと、今までやったことを適用しても画面映らなかったっす。色々、思考錯誤した結果、CrownBayPlatformPkg.dscの中のgCrownBayPlatformTokenSpaceGuid.PcdGopBltEnable | 0x239A | FALSE というパラメータが原因のようです。これをTRUEにすることでVGAから画面出力でました。

それから、ちょっと開発手法を変えました。以前にVisualStudioでバッチファイルを使ってビルドする方法を紹介しましたが(TX-50開発キットで遊ぶ その3 )、あれ、どうやら問題がありまして、Intel BLDK Development Applicationで変更したパラメータはバッチファイルでビルドしたときに反映されないことが判りました。
どうやらIntel BLDK Development Applicationでパラメータ変更してアプリからビルドすることで、ソースを変更せずに、ビルド時にパラメータを適用しているようです。このパラメータってのは、すなわちCrownBayPlatformPkg.dscに記述されているPCDという仕組みで定義されているパラメータたちです。先述のgCrownBayPlatformTokenSpaceGuid.PcdGopBltEnable もそのひとつです。

ということは逆に、Intel BLDK Development Applicationを使わずに、CrownBayPlatformPkg.dscを直接編集すればバッチファイルからビルドしても設定値が反映されるというわけです。そもそもIntel BLDK Development Applicationは設定項目限られてるし、使いづらいし、TX-50の構成に完全に合致しているわけでもないので、使わないことにしました。

そして、ビルドシステムの仕組みが概ね、解ってきて、CrownBayPlatformPkgというのはそもそも、CrownBay特有のドライバ等のソースの集合体でして、汎用ドライバとかの、それ以外のモジュールはその他のパッケージからもってきて、ファームウェアを構成しています。それをコントロールしているのがCrownBayPlatformPkg.dscというわけです。で、BLDKはEDKII(http://sourceforge.net/apps/mediawiki/tianocore/index.php?title=EDK2)がベースとなっているんですが、EDKIIのソースを見ると、たとえば、ArmPkgや、Omap35xxPkg等、プラットフォームを構成するパッケージが見受けられます。で、ビルドしたいパッケージの.dscファイルを指定することで、ベースパッケージとか汎用ドライバを共有しつつ、そのプラットフォームのファームウェアや、モジュール単体をビルドできるようになってます。

つまり、今回Tx-50というプラットフォームへの移植を行うにあったって、Tx50Pkgというパッケージを作って、そんで、Tx50Pkg.dscを作るという手があるわけです。そうすることで、CrownBayPlatformPkg内のソースを一切変更することなく、必要なモジュールだけTx50Pkg.dscに記述してもってくればスマートです。そしてTx-50特有の部分だけパッケージ内にコピーして編集するなり、新規作成するなりして、Tx50Pkg.dscでそのモジュールを指定すればコントロールもしやすいし、完全に他のパッケージと分離された形で開発できて、いい感じなのです。ただし、この方法を使うとIntel BLDK Development ApplicationはCrownBayPlatformPkg内でしかビルドできないっぽいので、使おうと思っても完全に使えなくなります。まあ、未練はあんまりないんだけどね。あと、Tx50PkgのみをBldkに追加するだけでよくなるので、再配布もし易くなりますね。(需要があるかは解りませんが・・)

ちゅうことで、現在は、ソースのトップレベルにTx50Pkgディレクトリを作ってCrownBayPlatformPkgから.dec、.dsc、.fdfファイルをコピーしてTx50向けに直接編集しちゃいました。そんでカスタムiegd.efiを格納したGopBinaryモジュールと現在コーディング中のPciSerialDxeを追加して、ビルド用のバッチファイルをTx50Pkgをビルドするように編集しちゃって、VisualStudioでプロジェクト作って、VisualStuidoからビルドするという開発スタイルにしました。うーん。なかなか快適です。

C:\BLDKTX50\TX50PKG
│  features.txt
│  Tx50Pkg.dec
│  Tx50Pkg.dsc
│  Tx50Pkg.fdf

├─GopBinary
│      iegd.efi

└─PciSerialDxe
        ComponentName.c
        PciSerialDxe.inf
        Serial.c
        Serial.h


ああ、今日は文章ばっかりになっちゃいました。よし、とりあえず、シリアルドライバはほったらかして、来週は、ぼちぼちOS起動に挑戦しようかなー。