2007年12月26日水曜日

Emobile-Wifiルータ

せっかくそれなりに速い速度のアップリンクを持つイーモバイルのD02HWが使えるようになったので、イーモバイルをアップリンクにする無線アクセスポイントをつくってみた。OSはNetBSD。とりあえず手元にあったusb無線LANアダプタ(バッファローのWLI-U2-SG54HP)をつかっている。ボードはALIX.3C

やることは普通のルータの設定と大体同じ。

  • ルータなのでkernelでipフォワードをenableする。kernelコンフィグレーションファイルでoptions GATEWAYと書いておくか、sysctl -w net.inet.ip.forwarding=1としておく。
  • ipfilterかpfでNATをできるように設定する。pfを使うときはkernelのコンフィグレーションファイルでpseudo-device pfとpseudo-device pflogと書いておく。ipfilterはdisableした記憶がなければ使えるはず。あとは/etc/ipf.confとか/etc/pf.confをかいておく。/etc/rc.confにpf=YESなどを書くのを忘れないように。
  • dhcpdをあげる。/etc/dhcpd.confを設定して、/etc/rc.confにdhcpd=YES dhcpd_flags="無線インターフェイス名"を書いておく。nameserverは自分のwifiインターフェイスのアドレスにしておくとよい。
  • namedをあげる。/etc/rc.confにnamed=YESとかいておく
  • pppの設定をする。前のエントリーを参照のこと。
  • 無線インターフェイスの設定をする。/etc/ifconfig.インターフェイス名というファイルに書いておくと便利。たとえば、rum0を使う場合は、ifconfig rum0 media autoselect mode 11g mediaopt hostapなどと打ち込んで、hostapモードで動かす。IPアドレス、SSIDの設定も忘れずに。
最低限これだけ設定すると、ルータとして動き出す。実用に使うにはもっといろいろと作り込んでいかないといけないが、この辺からスタートするとよいんじゃないかな。
もう少し時間と(切実な必要性)ができたらがんばる予定。

PSOC miniprog用コネクタ

PSOCをつかった回路をつくるときに、miniprogとの接続をどうしようか?っていうのはちょっとだけ悩ましい。普通の5pinのシングルラインのヘッダピンでも用は足りるけれど差し込みが安定しないし、ぼーっとしていると逆差ししてしまうかもしれない。Cypressが指定している純正の受け側のコネクタが手に入れば良いのだが秋葉原をちょっと回った感じでは難しそう。

最近気に入っているのは、molexのこのコネクタ。秋葉原の「ネジの西川」こと西川電子部品の2階のコネクタ売り場で探しているときに見つけたのだが、純正のコネクタとだいぶ近い形をしている。
型番は

  • Molex 5045-05A (ストレート)
  • Molex 5046-05A (L字)
とのこと。

このままでは右の写真のようにロック部分が長くてMiniprogと干渉してしまうのだが、


適当に切断すると


ぴたりとはまるようになる。

というわけで、秋葉原にアクセスできる人はこーいう方法もある、ということで。写真ではストレートコネクタを例につかったが、L字型のほうが使うことは多いかな。

NetBSDでEmobile D02HWを使う(設定編)

NetBSDのpppdのemobileにつなげる方法を教えてもらったので設定してみた。設定をまとめておく。
(まだ最適化はされていないので冗長な記述などがあるかもしれない)

設定するファイルは以下の5つ

  • /etc/ppp/peers/emobile
  • /etc/ppp/chat-emobile
  • /etc/ppp/ip-up
  • /etc/ppp/ip-down
  • /etc/ppp/chap-secret
これらを設定して、ppp call emobile を実行すればいい感じにPPPセッションが確立してくれる。

/etc/ppp/peers/emobile

dtyU0 460800 crtscts
lock
nodetach
hide-password
local
noauth
# dns routing
usepeerdns
defaultroute
noipdefault
# disable compressions
novj
noccp
nobsdcomp
# auth
user "em"
# misc? (xxx)
ipcp-restart 8
ipcp-max-configure 50
ipcp-accept-local
ipcp-accept-remote
# connect script
connect '/usr/sbin/chat -v -f /etc/ppp/chat-emobile -T *99***1#'


/etc/ppp/chat-emobile

ABORT "NO CARRIER"
ABORT "NO DIALTONE"
ABORT "ERROR"
ABORT "NO ANSWER"
ABORT "BUSY"
ECHO ON
SAY "Connecting"
TIMEOUT 15
"" "\d\d\dat\r\dat"
TIMEOUT 5
"OK-\Kat-OK" "at"
OK "at&FE1V1X1&D2&C1S0=0"
OK "at+ipr=230400"
SAY "Let's go"
OK "atdt\T"
CONNECT ''


/etc/ppp/ip-up (chmod 0755 /etc/ppp/ip-upしておくこと)

#!/bin/sh
~
f [ -f /etc/ppp/resolv.conf ]; then
chmod 644 /etc/ppp/resolv.conf
cp /etc/resolv.conf /etc/resolv.conf-
if [ ${USEPEERDNS:-0} -eq 1 ]; then
cp /etc/ppp/resolv.conf /etc/resolv.conf
chmod 644 /etc/resolv.conf
fi
fi


/etc/ppp/ip-down (chmod 0755 /etc/ppp/ip-downしておくこと)

#!/bin/sh
#
if [ -f /etc/resolv.conf- -a ${USEPEERDNS:-0} -eq 1 ]; then
mv /etc/resolv.conf /etc/resolv.conf.old
cp /etc/resolv.conf- /etc/resolv.conf
chmod 644 /etc/resolv.conf
fi

if [ -f /etc/ppp/resolv.conf ]; then
mv /etc/ppp/resolv.conf /etc/ppp/resolv.conf-
fi


/etc/ppp/chap-secret (chmod 0600 /etc/ppp/chap-secretしておくこと)

em * em

USBアナライザとD02HW

「NetBSDでD02HWをつかう」では比較的(というかほぼ全部)試行錯誤でデバイスの設定に必要な条件を推測してデバイスドライバへのパッチを作成した。ハードウェアを触るときによく感じることは「見えないものはわからない」ということだ。当たり前のことだが、電気信号は眼にはみえないので、推測するしかなくそれはいつまでも推測のままで推移してしまう。電圧を測るのにテスターが必要で、時間ドメインの波形をみるのにオシロスコープが必要なように、扱う事象を見るための専用の「アナライザ」が必要になることは多い。実はとある目的のためにUSBバスアナライザが手元にあったので、D02HWのデバイスドライバ作成作業を検証してみることにした。

手元にあるのはellisysUSB Explorer 200だ。これは、USB2.0のHighSpeed(480Mbps)に対応したアナライザで、Windowsソフトウェアと組み合わせることでだいたいすべてのUSBイベントを表示してくれる、という便利な逸品である。ソフトウェアは誰でもダウンロードできるようになっていて、USBバスのキャプチャファイルを入手したときに誰でも表示できるのがうれしい(ちなみにキャプチャファイルの拡張子はUFOだ)。
写真でわかるように、アナライザを解析ソフトが入っているPCにつなげて、デバイスとデバイスをつなげるホストの間にはさむようにアナライザを配置する。これで、デバイスとホスト間のデータをインターセプトするという仕組みになっている。
まず、Windowsで動かしてみたときにUSBバスにどんなトランザクションが発生しているかを見てみた。
NetBSDでEmobile D02HWを使う(3)」にあるUSBのインターフェイスとエンドポイントの情報を見ながら、どのエンドポイントとホストがどんな通信をしているかを眺めてみる。アナライザは、

たとえばこんな情報が表示されて、トランザクションのタイプと中身のデコード結果をだしてくれるので、それとUSB2.0の仕様書(このへんにある)を眺めながら追っていく。赤裸々にどんなトランザクションをやりとりしているかを見ていてわかったことは以下の3つ。

  • NetBSDにつくったパッチは正しい。
  • ATコマンドいろいろ
  • D02HWは実はさらに別のインターフェイスをもっている。
最初のは、結局あのシーケンスを送らないとデバイスは言うことをきいてくれないみたいだ、ということを追認できたということだ。ちゃんとUSB2.0の仕様書を読み込むと、あのメッセージは
  • デバイスへのスタンダードのリクエスト(SET_FEATURE)でHost→Device
  • リクエストタイプはDEVICE_REMOTE_WAKEUP
  • wIndexの0x2はendpoint2を意味する
ということだった。アナライザでの結果をみるとendpoint2はモデムの主通信用のパイプなので、これで意味がわかってすっきりした。
2つめのATコマンドいろいろはたぶんWindowsとかのモデム定義ファイルをみればわかる情報なので大した追加情報ではない。問題は3つめで、確かに「NetBSDでEmobile D02HWを使う(3)」では、モデムとして認識されるようになると3つのインターフェイスディスクリプタが見えている。ひとつはモデムでひとつはUSBストレージデバイスだ、ということもわかっていたが、実はなぜもうひとつみえるのか、は謎のまま残っていた。まだ実際にコードを書いて叩いてみたわけではないのだが、USBのBUSトランザクションを見る限り、

INTERFACE descriptor 1:
bLength=9 bDescriptorType=interface(4) bInterfaceNumber=1 bAlternateSetting=0
bNumEndpoints=2 bInterfaceClass=255 bInterfaceSubClass=255
bInterfaceProtocol=255 iInterface=3(Data Interface)

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=5-in
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=5-out
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

のendpoint5はもう一本のモデム制御用のパイプらしい。まだ憶測だが、endpoint2でデータ通信しながらでもモデムが管理しているHSDPA由来の制御情報などを並列してとれるのではないかと予想している。これを扱うデバイスドライバかくのはどうしたもんか、と悩みがふえてしまった(しばらくはきっとやらないけどね)。

(12/28追記:実験してみたらモデムモードに入った後は3本ともシリアルらしい。2本目をはやすハックをしてみた、あとで詳しく書く予定)
(12/30追記:やっぱり3本目はusb mass strageだとおもうなあ。コードかいてみるか。。)
(1/4追記:usb mass strageでした。)

というわけで、アナライザを使うとみたいことも見えるけど、まったく気が付いていなかったようなことも見えてしまう、ってことで。今回はおしまい。

2007年12月25日火曜日

NetBSDでEmobile D02HWを使う(4)

Windowsやmacのドライバでは抜き差しせずとも使えるということは、なんらかの方法でこの2つのモードの切り替えができるはずだ。機能の切り替え、はデバイス毎の特有の操作なので一般的な方法は存在しない。USBのバスアナライザとかがあれば、windowsのドライバの挙動などを観察して切り替えるための魔法の言葉を見つけることもできるのだが、休日に家で作業をしていたので手元にそんな便利なものはなかった。
というわけでいろいろと試行錯誤してみることにする。

  • 試行1:D02HW(E220)のファームウェアの立ち上がりが遅くてうまく処理ができていないのでは無いかと思って、attachルーチンのあちこちにdelayを入れてみる。あちこちにusbd_delay_ms(dev, 500); などと書いてみて、挙動がどう変わるか地道な作業をがんばる(2時間くらい)。


/* Find the endpoints */
usbd_delay_ms(dev, 2000);
id = usbd_get_interface_descriptor(sc->sc_iface);
sc->sc_iface_number = id->bInterfaceNumber;

あたりに2000msくらいのdelayをいれると、認識しない→差し直すと認識→次に刺し直すと認識しない→また差し直すと認識、と安定してツンとデレを繰り返すようになった。しかしディレイを入れるだけではどうもこれ以上は進まないようだ。次の方法を考えないといけない、ネットワークで識者(USBシリアルの偉い人)と相談するととりあえずVENDORリクエストなどで適当な値をおくってデバイスをつつくかくすぐるかしてやるのがいい、と助言をうけた。設計している人も人の子なので別に難しい値ではなく0とか1とか2とかそんな値を送ると動き出すことがあるらしい。

というわけで、sys/dev/usb/uplcom.cを参考にして実験用にubsa_reset()という関数をでっちあげてみた。


static usbd_status
ubsa_reset(struct ubsa_softc *sc)
{
usb_device_request_t req;
usbd_status err;
#define UBSA_SET_REQUEST 0x1

req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
req.bRequest = UBSA_SET_REQUEST;
USETW(req.wValue, 0);
USETW(req.wIndex, sc->sc_iface_number);
USETW(req.wLength, 0);

err = usbd_do_request(sc->sc_udev, &req, 0);
if (err) {
printf("%s: send reset fail\n", __func__);
return (EIO);
}

return (0);
}


  • 試行2: ubsa_reset()関数をattachの適当な場所においてみて挙動を見る。が、デバイスからお断りのエラーが返ってくるのでどうもうまくいかないらしい。
  • 試行3: bmRequestType を UT_WRITE_DEVICEにしてbRequest = UR_SET_FEATUREにしてやってみることにする。これはUSBの標準機能なので失敗することはないはず。コードは適当に改変する。
ここで変化が起こった。なんとリクエストを送りつけると、抜き差ししてもモードの変換が発生せずに常にモデムとして見えない状態になる。「逆行してるじゃん」と思うのは素人だ。なにかアクションが返ってきたと言うことは、その近くに正解が隠れている可能性が高い。試行錯誤を繰り返して問題を追い込んでいくことにする。ここまで7時間近く作業をしていてどうもだれぎみだったのが、ちょっとテンションがあがってきた記憶がある。脳内麻薬でもでていたんじゃないかな。しばらくするとリクエストのwIndexの値を0x2にすると、デバイス側がUSBリセットをかけてきて一度デタッチされ、再度OSがアタッチするときにはモデムに化けている、という挙動をするということが判明。ようやくつねにデレにするマジックワードをゲットできた。

あとはコードをまとめるだけだが、最終的には、

/*
* Hauwei E220 needs special request to enable modem function
* XXX: is there more smart methods?
*/
Static usbd_status
ubsa_e220_modechange_request(struct ubsa_softc *sc)
{
#define UBSA_E220_MODE_CHANGE_REQUEST 0x2
usb_device_request_t req;
usbd_status err;

req.bmRequestType = UT_WRITE_DEVICE;
req.bRequest = UR_SET_FEATURE;
USETW(req.wValue, 0x1);
USETW(req.wIndex, UBSA_E220_MODE_CHANGE_REQUEST);
USETW(req.wLength, 0);

DPRINTF(("%s: send e220 mode change request\n", __func__));
err = usbd_do_request(sc->sc_udev, &req, 0);
if (err) {
DPRINTF(("%s: E220 mode change fail\n", __func__));
return (EIO);
}

return (0);
#undef UBSA_E220_MODE_CHANGE_REQUEST
}

みたいな呼び出しをUBSA_ATTACH()の中で

/* Find the endpoints */
/* Hauwei E220 need special request to change its mode to modem */
if (sc->sc_devtype_e220) {
err = ubsa_e220_modechange_request(sc);
if (err) {
printf("%s: failed to change mode: %s\n",
devname, usbd_errstr(err));
sc->sc_dying = 1;
goto error;
}
}

id = usbd_get_interface_descriptor(sc->sc_iface);
sc->sc_iface_number = id->bInterfaceNumber;

こんなふうに呼び出す実装にしてみた。このカーネルでは、


ubsa0 at uhub0 port 2
ubsa0: HUAWEI Technologies HUAWEI Mobile, rev 1.10/0.00, addr 2
ubsa0: HUAWEI E220 need to re-attach to enable modem function
ubsa0: at uhub0 port 2 (addr 2) disconnected
ubsa0 detached
ubsa0 at uhub0 port 2
ubsa0: HUAWEI Technologies HUAWEI Mobile, rev 1.10/0.00, addr 2
ucom0 at ubsa0

みたいに自動的にucom0がアタッチされるようになる。いったんdettachされるのが美しいか?みたいな議論をするともめそうだけど、デバイスの仕様ってことで許してくれないかなーとコッソリ期待している。

tipでucom0につなげてみると、こんな感じにみえる。userland-PPPでemにもつながったので、たぶんこれでサポートのためのハックは完了。実はまだNetBSDのkernel ppp(pppd)の設定がうまくいかないので、設定方法を教えてくれる人も募集中。


#tip dtyU0
connected
at
OK
atI
Manufacturer: huawei
Model: D02HW
Revision: 11.005.08.00.168
IMEI: 3560600XXXXXXXX
+GCAP: +CGSM,+DS,+ES

OK


というわけで、D02HWを動かせるようになるまでに実際にどんな作業をしたかを書いてみた。この手の情報はできた後のコード解説程度が多いが、作業をした過程とか、どんな事を考えて作業したかとか、どんな失敗をしたか、って情報の方が、自分で同じような作業をするときに役に立つ気がしている。
あと、半年たつと自分でも何やったのか忘れることがあるので備忘録にもなることを期待しながら、できる限りこんな感じの作業記録をとってみたいなあーと考えている。

2007年12月24日月曜日

NetBSDでEmobile D02HWを使う(3)

インスタントに「NetBSDでイーモバイルを使う方法」が書いてあるかというとそうでもなくて残念だと思う人もいるかもしれない。今回書いたpatchはすでにNetBSDには送ってあるのだがこれが採用されるかどうかはまだ不明なので、最近の-currentをCVSチェックアウトしただけでは現状では使えない。今回も「どうやってUSBデバイスを動かすようにしたか」というネタが続いているのでその辺に興味があるならおつきあい頂きたい。


現状のubsaドライバではどうもうまくD02HW(E220)をアタッチできないので、もう少し先人達の知恵を借りることにした。FreeBSD系のメーリングリストを眺めるとこんな記述があった:「いったん接続して認識に失敗してから、ゆっくりとUSBコネクタを引き抜いてデタッチされた瞬間にもう一度指し直せば認識する(かも)」(意訳)、最初みたときは何のおまじないかと思っていたのだが、再度好意的に解釈し直すと「電気的な接続を切らずに信号線だけ切りはなして再接続するといんじゃない?」と言っているようにも思える。というわけでやってみた。数回トライすると、本当に認識してucom0が生えてきた。なんで?と思うとこの状態ではさっきまで見えなかったエンドポイントが複数見えるようになっており、その中にインタラプトエンドポイントが含まれている。

usbctlというコマンドでUSBのデバイスの情報を取得してみると、差し直しの前後で全然別物のデバイスになってしまったようにみえる。長いけど抜粋してみるとこんな感じ。

元々の情報


DEVICE addr 2
DEVICE descriptor:
bLength=18 bDescriptorType=device(1) bcdUSB=1.10 bDeviceClass=0 bDeviceSubClass=
0
bDeviceProtocol=0 bMaxPacketSize=64 idVendor=0x12d1 idProduct=0x1003 bcdDevice=0
iManufacturer=1(HUAWEI Technologies) iProduct=2(HUAWEI Mobile) iSerialNumber=0()
bNumConfigurations=1

CONFIGURATION descriptor 0:
bLength=9 bDescriptorType=config(2) wTotalLength=32 bNumInterface=1
bConfigurationValue=1 iConfiguration=0() bmAttributes=a0 bMaxPower=500 mA

INTERFACE descriptor 0:
bLength=9 bDescriptorType=interface(4) bInterfaceNumber=0 bAlternateSetting=0
bNumEndpoints=2 bInterfaceClass=8 bInterfaceSubClass=6
bInterfaceProtocol=80 iInterface=0()

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=3-in
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=4-out
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

current configuration 1

差しなおした後の情報


DEVICE addr 2
DEVICE descriptor:
bLength=18 bDescriptorType=device(1) bcdUSB=1.10 bDeviceClass=0 bDeviceSubClass=
0
bDeviceProtocol=0 bMaxPacketSize=64 idVendor=0x12d1 idProduct=0x1003 bcdDevice=0
iManufacturer=1(HUAWEI Technologies) iProduct=2(HUAWEI Mobile) iSerialNumber=0()
bNumConfigurations=1

CONFIGURATION descriptor 0:
bLength=9 bDescriptorType=config(2) wTotalLength=85 bNumInterface=3
bConfigurationValue=1 iConfiguration=0() bmAttributes=a0 bMaxPower=500 mA

INTERFACE descriptor 0:
bLength=9 bDescriptorType=interface(4) bInterfaceNumber=0 bAlternateSetting=0
bNumEndpoints=3 bInterfaceClass=255 bInterfaceSubClass=255
bInterfaceProtocol=255 iInterface=3(Data Interface)

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=1-in
bmAttributes=interrupt wMaxPacketSize=16 bInterval=128

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=2-in
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=2-out
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

INTERFACE descriptor 1:
bLength=9 bDescriptorType=interface(4) bInterfaceNumber=1 bAlternateSetting=0
bNumEndpoints=2 bInterfaceClass=255 bInterfaceSubClass=255
bInterfaceProtocol=255 iInterface=3(Data Interface)

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=5-in
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=5-out
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

INTERFACE descriptor 2:
bLength=9 bDescriptorType=interface(4) bInterfaceNumber=2 bAlternateSetting=0
bNumEndpoints=2 bInterfaceClass=8 bInterfaceSubClass=6
bInterfaceProtocol=80 iInterface=0()

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=3-in
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=4-out
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

current configuration 1


つまり、差しなおしたあとのデバイスには元々見えなかったインターフェイスが生成されていて、それがみえると今回の目的であるモデムとして利用できるようになる、ということらしい。裏技みたいな「ゆっくりやさしくぬいて、素早くさす」みたいなことを何回もやるのは大変なので、USBのプロトコル的にうまくできないか?という方針のまま作業をすすめていくことにする。

続く

NetBSDでEmobile D02HWを使う(2)

CS5536を扱った時は十分に詳しい仕様書が入手できて先がみえる作業(つまり作ればできる)だったが、今回のD02HW(E220)に関しては外部の仕様書が存在しないため、いろいろと試行錯誤が必要となる。幸いにも、「ごにょごにょすると動く」とか「不十分だけど動くかも」といった情報があるし、Linuxでは動いているらしいのであまり深刻にならずにやってみることにしよう(ま、楽しみでやっているわけだし)。
前回のエントリでも出したが、この段階では接続するとこんなメッセージがkernelから出力される。

ubsa0 at uhub0 port 2
ubsa0: HUAWEI Technologies HUAWEI Mobile, rev 1.10/0.00, addr 2
usbd_set_config_index: illegal index
ubsa0: failed to set configuration: INVAL

問題となっている部分として目につくのが"usbd_set_condig_index: illegal index"の部分だ。ベースとなるsys/dev/usb/ubsa.cを開くと、その部分はUSB_ATTACH(ubsa)の中のこんなコードだった。

/* Move the device into the configured state. */
err = usbd_set_config_index(dev, UBSA_CONFIG_INDEX, 1);
if (err) {
printf("%s: failed to set configuration: %s\n",
devname, usbd_errstr(err));
sc->sc_dying = 1;
goto error;
}

usbd_set_config_index()の実体はsys/deb/usb/usb_subr.cにあるので見てみると、USBのデバイス的に「そんなINDEXはないよ」ということを意味している。ここでデバイスに頑張れといっても無い袖を振ってくれるわけでもないので、ここはUBSA_CONFIG_INDEXの値があっていないのだろうと考えるのが正しそう。もともとの値は1だったので、とりあえず0にしたkernelを作って試してみる。

すると別のメッセージが出るようになる。ubsa.cをみると、それなりにコードの先のほうまで進んでいるようだ。

ubsa0 at uhub1 port 2
ubsa0: HUAWEI Technologies HUAWEI Mobile, rev 1.10/0.00, addr 2
ubsa0: Could not find interrupt in

該当部分のコードは、さっきと同じUSB_ATTACH(ubsa)の中の、

if (sc->sc_intr_number == -1) {
printf("%s: Could not find interrupt in\n", devname);
sc->sc_dying = 1;
goto error;
}

である。ちょっと前の方にもどって追ってみると、USBデバイス(D02HW)からエンドポイントを取得してみたけど必要な割り込み用のエンドポイントをもらえなかった、のであきらめちゃう、ということらしい。割り込み用のエンドポイントがもらえないのは一大事なのでデバイスドライバとしてはあきらめるのは当然だとは思うが、動かしたい人としてはそれでは困る。
可能性としては、

  • このデバイスは割り込みエンドポイントがない変なシリアルデバイスである
  • 本来は割り込みポイントがあるが何らかの問題で見えていない
のどちらかだが、「ガシガシさしなおしていたら見えたことがある」とかいうWebなどでの報告を信じる限り、どちらかというと後者の可能性が高い。もし前者だったとしたらシリアル通信のドライバとして全然別のロジックが必要になるから偶然で動くなんて考えづらいからだ。つまり、
  • 残りのコードはおおむねあっていて、うまくデバイスとしてコンフィグレーションできていない
じゃないかと予測して次のステップに進むことにした。

続く

2007年12月22日土曜日

NetBSDでEmobile D02HWを使う(1)

イーモバイルD02HWが手元に来たので、NetBSDで動かせるようにしてみた。今回はそのあたりの話を書いてみることにする。
D02HWはD01HWとほとんど同じハードウェアで、HUAWEIE220のOEM製品である。今回も手をつける前にとりあえずgoogleとかしてみると、動いたとか動かないとかどうも歯切れがわるい。NetBSDの場合ではcurrent-usersという最新版の開発ブランチのkernelに関する話題を扱うメーリングリストがあるのだが、いくつかのスレッドがあった割には実際のコードには反映されていない状態だった。
まあ、手元に実際のデバイスがあるから、と手をつけてみることにした。

D02HWはUSBのHSDPAモデムだが、モデムの機能の前にデフォルトではUSBストレージデバイスとして認識されるようになっている。たとえば、なんの設定もしていないNetBSDではumassデバイスとして認識・アタッチされて、CDROMとして中身をmountできるようになる。中身はwindows用のデバイスドライバ群で、NetBSDでモデムとして使おうとして思っているときには何の役にもたたない。差し込みのたびにumassとして認識されるのもうるさかったので、まずそのへんをdisableする。umassのdevice driverはsys/dev/usb/umass.cあたりにある。中身をざっと見てみると特定のデバイス毎の特別な設定を書くためのhookが用意されていて、sys/dev/usb/umass_quirks.cに追加できるようになっている。今回は単純にattachされないようにするだけなので、Static const struct umass_quirk umass_quirks[] = {の中に

{ { USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E220 },
0, 0, 0, 0, 0, NULL, NULL
},

というエントリーを追加した。これで、USBのベンダIDがHUAWEIでプロダクトIDがE220のデバイスはUSBストレージデバイスとして認識されなくなる。NetBSDでは、専用のドライバが存在しないデバイスはugen(汎用USBデバイス)としてattachされるので、umassをdisableすると次からはD02HW(E220)はugenとして認識されるようになる。

このあたりでugenとして認識されたD02HWをユーザーランドからlibusbをつかってなんか操作できないかと、数時間時間を費やしてみたが、得たものは

  • NetBSDのlibusbはugenとして認識されたデバイスしか触れないということを認識
  • libusbを使った基本的なプログラムの書き方と経験
だけで、今回のデバイスドライバつくりに特に役に立つことは(その時点では)なかった。今思えば、あの段階でうまくugen&libusbを使うことで不明なUSBのコマンドを探すような場合に役にたつかも、と思えるようになったので、まあ費やした時間は無駄ではなかった(ことにした)。

と、ここからが本題となる。まずは先人達が到達したところまで追い付かなければならない。ベースとなっているUSBシリアルドライバはubsaドライバ(sys/dev/usb/ubsa.c)なので、このドライバがD02HWのドライバとなるように、Static const struct usb_devno ubsa_devs[] = {


/* HUAWEI E220 / Emobile D0[12]HW */
{ USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E220 }

と追加する。この段階でkernelをつくってD02HWを接続すると

ubsa0 at uhub0 port 2
ubsa0: HUAWEI Technologies HUAWEI Mobile, rev 1.10/0.00, addr 2
usbd_set_config_index: illegal index
ubsa0: failed to set configuration: INVAL

と表示される。確かに問題がある、というところで概ねこのデバイスを触る準備が整った気になる。ちょっと一休み。

具体的な料理の方法は次のエントリーで説明する予定。

ALIXとNetBSD(補足)

そういえば、ALIXボードでNetBSDを動かしているときに不可解なハングアップに陥る状況があった。カーネルを触っている時期だったので、ずいぶん「自分のコードがわるい」んだとおもって問題点を探すために時間をつかってしまった。
ハングアップするのは、「CS5536のMFGPTのIO空間にアクセスしてなにかを書き込んだとき」であったが、どう考えてもそれだけで落ちるとは考えられない、と思えるようになるまでデバグを続けて最後に確認したのが、BIOSの設定。
ALIXのTinybiosには [MFGPT workaround] というメニューがある。これはCS5536のアクセスでマシンがハングアップ*しないように*ある機能らしいので、役に立つことはあっても害にはならないだろうと思っていたのが、この機能をdisableすると問題なくMFGPTの空間への書き込みができるようになった。

もしかしてはまる人がいるかもしれないので、メモとして残しておく。

NetBSDでWatchdog timerを使う(for ALIX) (4)

Watchdogタイマとして動かすためにsysmonフレームワークから呼び出される関数は、

  • int glxpcib_wdog_setmode(struct sysmon_wdog *smw);
  • int glxpcib_wdog_tickle(struct sysmon_wdog *smw);
の2つ。glxpcib_wdog_setmode()はWatchdogタイマのenable/disableや、最大時間を設定の際に呼び出される関数。glxpcib_wdog_tickle()は正常動作時に定期的に呼び出される関数だ。ちなみにsrc/sys/dev/sysmon/sysmon_wdog.cをみると、

/*
* sysmon_wdog_ktickle:
*
* Kernel watchdog tickle routine.
*/
void
sysmon_wdog_ktickle(void *arg)
{
[中略]
callout_reset(&sysmon_wdog_callout,
WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
sysmon_wdog_ktickle, NULL);
}
mutex_exit(&sysmon_wdog_mtx);
}

と書いてあって、基本的には(カーネルモードのwatchdogの場合は)設定した時間の半分の周期でタイマを更新することになっていた。

今回はこれらの2つの関数を書くにあたって、共通的な要素を行う基本関数として
  • glxpcib_wdog_disable()
  • glxpcib_wdog_enable()
  • glxpcib_wdog_reset()
の3つを用意した。最初の2つはタイマーの動作を止めたり、動かしたり、カウンターが回りきったときのアクション(この場合はシステムリセット)を止めたり、動かしたりするために利用する。3つめカウンターを0にリセットするために利用する。これらを組み合わせると、
  • glxpcib_wdog_setmode()はdisable(), enable(),reset()の組み合わせとタイマ値の設定
  • glxpcib_wdog_tickle()はreset()を呼び出す
ことで実現できる。sysmon_wdogの時間しては秒単位で、カウンターに設定する値も秒単位なので特に変換のための計算も必要ない。参考までにglxpcib_wdog_setmode()をつけておく。

int
glxpcib_wdog_setmode(struct sysmon_wdog *smw)
{
struct glxpcib_softc *sc = smw->smw_cookie;

if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
mutex_enter(&sc->sc_mtx);
glxpcib_wdog_disable(sc);
mutex_exit(&sc->sc_mtx);
return 0;
}

if (smw->smw_period == WDOG_PERIOD_DEFAULT)
smw->smw_period = 32;
else if (smw->smw_period > 0xffff) /* too big */
return EINVAL;

mutex_enter(&sc->sc_mtx);
glxpcib_wdog_enable(sc);
glxpcib_wdog_reset(sc);
mutex_exit(&sc->sc_mtx);

return 0;
}

ここまでで、デバイスドライバ部分は全部できあがったのだが、ここではたと気がついたことがある。「これってどうやって正しく動いていることを検証すればいいんだろうか?」と。
Watchdogタイマはシステムが動かなかったときにリセットするために使われるわけだが、任意にシステムを止めたりする方法はあまりしらない(別のHWのデバイスドライバをごにょごにょすればできないこともないがめんどくさい)。しょうがないから、
  1. printfをいれて関数の呼び出し状態をみてみる
  2. ユーザランドで指定した値と独立してある特定の時間(10秒)までカウントが進めばリセットするカーネルを作ってみる
の2つで対処してみた。(1)で10秒と設定した場合に5秒ごとに_tickle()が呼び出されて、カウンタが0になっていることがわかる。また(2)では18秒と設定して9秒後に_tickle()が動けばリセットされないが、20秒と設定して10秒後まで_tickle()が動かないようにするとリセットがかかる、といったようにタイマが正しく動いていてかつリセット動作までつながる、ことを検証できる。

というわけで、CS5536のMFGPTをつかってWatchdogタイマを動かすデバイスドライバは書けて、正しく動いているっぽいことが分かった。最後に、NetBSDでWatchdogタイマを使うように設定する方法を書いておく。
使うコマンドは、
  • wdogctl
である。詳しいオプションはman wdogctlで見ることをお勧めするが、

# wdogctl
Available watchdog timers:
cs5536wdt, 32 second period
# wdogctl -k -p 10 cs5536wdt
# wdogctl
Available watchdog timers:
cs5536wdt, 10 second period [armed, kernel tickle]
# wdogctl -d
# wdogctl
Available watchdog timers:
cs5536wdt, 10 second period

のように使う。引数なしでwdogctlを呼ぶと使えるタイマの名前とwatchdogが起動する長さが表示される。2行目は10秒でwatchdogがかかるようにkernelがタイマを更新するモードでcs5536wdt(今回つくったタイマ)を用いてwatchdogを実行するように指示する。もう一度引数なしwdogctlを呼ぶと、設定が反映されて動いていることがわかる。
途中でwatchdogタイマを止めたいときは-dオプションをつけてwdogctlを実行すればよい。

もし、起動時に設定したい場合は/etc/rc.confにwdogctl=YES wdogctl_flags="-k -p 10 cs5536wdt"などとと書いておけば動く。wdogctl_flagsの中身はwdogctlに渡す引数と同じでよい。

というわけで、PC EnginesのALIXボードが届いてから、2日ほど楽しくカーネル遊びをする時間をとれた。いままでGPIOとかWatchdogとかを使ったことがなかったが、動くようにするために結構深い理解ができたような気がする。でも、次にこの知識が役にたつのはいつだろう(とちょっとだけ不安)。

さて、次は一番ほしい機能であるI2Cバスのサポートに手をつけたいなーと思っている。
が、まだ何もやっていないので本当にできるのか?いつになるかは?は不明。とりあえずこの3連休は手元にI2Cデバイスもロジックアナライザもないので難しそうだ。

NetBSDでWatchdog timerを使う(for ALIX) (3)

なんだかデバイスドライバ作成講座みたいになってきて、UNIX系雑誌の原稿を書いているみたいな気分になってきた。まあいいか。もし変なところとか、質問があったらコメントしてもらえれば対応できることもあるかもしれない、ところが雑誌とは違うと納得することにした。あと、タイマのデバイスドライバは操作する場所もすくないし、滅多なことで変なこともおこらないので入門用としてはちょうどいいネタかもしれないので、一度書いておくとあとあと便利かも。

とりあえず、今回の話は手元にCS5536のデータブックを置いておくと少しはわかるかもしれない。650ページもあるドキュメントであんまり画面上で参照したい大きさじゃない。昔みたいに印刷物でくばってくれないかなあ、とこのデバイスを触っている最中よく思った。MFGPTの仕様はあちこちに分散されているけど、今回一番必要になるのは制御用のレジスタマップと書き込むべき内容が書いてある「6.17 Multi-Function General Purpose Timer Register Descriptions」(p527)あたりだ。タイマにしてはできることが多いのでたくさん設定できることがあるけれど、今回つかうのは

  • 6.17.2.4 MFGPT[x] Setup (MFGPT[x]_SETUP) (p522)
  • 6.17.2.3 MFGPT[x] Up Counter (MFGPT[x]_CNT) (p521)
  • 6.17.2.2 MFGPT[x] Comparator 2 (MFGPT[x]_CMP2) (p520)
の3つだけ。上から、タイマのカウンターの動作設定、カウンターの値、カウンターの上限値をそれぞれ設定するレジスタになる。
6.17.2.4をじーっと眺めると、MFGPT0_SETUPレジスタのビット構造の図がある。
このレジスタは16bitで各ビットの値を設定するとタイマのカウンタの動作を定義できる。注意しないといけないのは、いくつかのビットは最初の一回しか書き込みできない点で、それ以降はそのビットへの変更は反映されない。たとえば、bit0-3のMFGPT_SCALEはカウンタのプリスケーラーの値を設定するビットフィールドだが一度設定すると途中でプリスケーラーの値を変更できない(らしい)。MFGPT_CLKSELはカウンタのクロックを32KHzか14.328MHzか選択するために使うがこれも同様に最初に設定したら変更できない。
Watchdogタイマはms単位ではあまり使わないだろうということと、比較的長い周期が設定できたほうがいいだろう、という判断でCLKSELでは32KHzを、プリスケーラは32K分周を選択することにした。つまり32KHzを32K分周するためカウンタは1Hzで駆動される。プログラムも簡単になるし、16ビットカウンタの最大値の65535秒=約18.2時間までの長周期を設定できる、ようになる。

初期設定は、glxpcib_attach()の中で行う。レジスタの値を変更するためには、PCIのアドレス空間をアクセスできるようにしておかないといけない、GPIOの時と同様にbus_space_map()を使ってio handleを取得しておく。該当部分のコードはこんな感じ。

/* Attach the watchdog timer */
wdtbase = rdmsr(MSR_LBAR_MFGPT) & 0xffff;
if (bus_space_map(sc->sc_iot, wdtbase, 64, 0, &sc->sc_ioh)) {
aprint_error("%s: can't map memory space for WDT",
sc->sc_dev.dv_xname);
} else {
mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_HIGH);
bus_space_write_2(sc->sc_iot, sc->sc_ioh,
AMD5536_MFGPT0_SETUP,
AMD5536_MFGPT_CNT_EN | AMD5536_MFGPT_CMP2EV |
AMD5536_MFGPT_CMP2 | AMD5536_MFGPT_DIV_MASK);

ここでは、IO空間をマップしてから、MFGPT0_SETUPレジスタに「カウンタを動かす+フルカウンタになったらリセットをする+入力クロック=32KHz+プリスケーラー=32K」を設定するところまでが含まれている。
その後は、

sc->sc_smw.smw_name = "cs5536wdt";
sc->sc_smw.smw_cookie = sc;
sc->sc_smw.smw_setmode = glxpcib_wdog_setmode;
sc->sc_smw.smw_tickle = glxpcib_wdog_tickle;
sc->sc_smw.smw_period = 32;
aprint_normal("%s: watchdog", sc->sc_dev.dv_xname);
wdt = 1;
}
/* Register Watchdog timer to SMW */
if (wdt) {
if (sysmon_wdog_register(&sc->sc_smw) != 0)
aprint_error("%s: can not register wdog\n",
sc->sc_dev.dv_xname);
}

sysmonのフレームワークに必要な設定(smw構造体にいろいろと書く)をして、sysmon_wdog_register()でMFGPT0タイマをsysmonのwatchdogタイマとして登録すれば、attach処理は完了する。

あとは、sysmonのwdogフレームワークがMFGPT0タイマを制御するための制御関数を記述すれば完了である。記述していないうちは、関数ポインタとしてNULLをいれておけば、カーネルの作成して連携部分のチェックまではできるはず。実際に開発しているときは、周りだけ作って中身がない状態から、すこしづつ中身を埋めていってつくっていた。

次は、watchdogタイマとしてMFGPTを動かすため操作関数について書いてみる予定。

2007年12月21日金曜日

NetBSDでWatchdog timerを使う(for ALIX) (2)

今回扱うWatchdogタイマはハードウェアサポートを前提としたものだ。ハードウェアサポートのWatchdogタイマは、CPUの演算やプログラムとは独立して動いているタイマと、そのタイマが一定の値になるとCPUを強制的にリセットできる機構が組み合わさって実現される。
つまり、今回CS5536でWatchdogタイマを実装するためには、CS5536のMFGPTがある一定の値になるとシステムをリセットするように設定し、かつカーネルやユーザランドが正しく動いている限り一定間隔でタイマの更新を続けるソフトウェアを書くこと、と等価である。
NetBSDのWatchdogタイマサポートは、

  • デバイスドライバ部分(glxpcib.c)はタイマーの設定と更新のためのデバイス依存の部分
  • カーネルのsysmonフレームワークがその他残りのデバイス非依存の部分
の2層にわかれている。これはハードウェアを扱うデバイスドライバとOSのサービスが協調して動作する機構の一般的な構造だ。ユーザランドへのAPIやサービスの共通的な手続きはデバイスが提供する機能を一般化・抽象化して扱っておき、具体的なデバイスの処理はその抽象的なアクションから呼び出されるデバイス依存のデバイスドライバ部分のコードが対応することになる。基本的にハードウェアデバイスはデバイス毎に設計も扱い方も違うけれど、共通部分をうまく切り出しておけばデバイス依存・非依存部分をきれいに分離できる、というわけだ。

今回つかうデバイス非依存のサービスはsysmonフレームワークによって提供されている。これはsrc/sys/dev/sysmon/以下で実装されているフレームワークでコンピュータのハードウェアの電源やセンサなどを扱うための一般的な機能を提供している。Watchdogタイマもsysmon/sysmon_wdog.cをつかえば、他の実装のwatchdogと同じユーザランドプログラムと協調できるようになっている。

と、ここまでで全体の大雑把に構造を理解し(たつもりになっ)てから、デバイス依存の部分をやっつけてしまうことにする。glxpcib.cの中には、デバイス依存つまりCS5536特有な操作を扱う
  • MFGPTの初期設定(周波数やプリスケーラーの値)する関数
  • MFGPTの動作を制御するための関数
  • sysmon_wdogとインターフェイスするための関数群
を実装をしなければならない。sysmon_wdogから呼び出される関数はsysmon/sysmonvar.hの中のsysmon_wdog構造体を参照するとわかる。

struct sysmon_wdog {
const char *smw_name; /* watchdog device name */

LIST_ENTRY(sysmon_wdog) smw_list;

void *smw_cookie; /* for watchdog back-end */
int (*smw_setmode)(struct sysmon_wdog *);
int (*smw_tickle)(struct sysmon_wdog *);
u_int smw_period; /* timer period (in seconds) */
int smw_mode; /* timer mode */
u_int smw_refcnt; /* references */
pid_t smw_tickler; /* last process to tickle */
};

関数ポインタであるsmw_setmode()とsmw_tickle()を実装してやれば、sysmon-wdogで扱えるwatchdogタイマになるだろう、という見通しを立てて進めていく。

次のエントリーでデバイス操作について説明してみる予定。

NetBSDでWatchdog timerを使う(for ALIX) (1)

Watchdogタイマはシステムがハングアップしたときに自動的に復帰するための監視機構だ。原理は簡単で、独立したタイマが動いていて、そのタイマが設定した値に到達するとシステムリセットがかかるようにしておく。システムが正しく動いているときはそのタイマの値を定期的に0にもどし、リセットがかからないように管理する、というものだ。
ALIXで動かすNetBSDでGPIOをサポートするコードを書くついでにwatchdogタイマも生かしてみることにした。最近は組込機器っぽい用途でNetBSDを使うこともあっていつか機会があればwatchdogタイマを使ってみようと思っていたので、ちょうどよい。
AMDのCS5536には、MFGPT(Multi Function General Purpose Timer)というすごい名前のタイマ群が実装されている。そのうちの一つMFGPT0をWatchdogタイマとして使う、というのが元々のOpenBSDのglxpcib.cでの実装だ。問題はNetBSDとOpenBSDではwatchdogタイマを使うためのフレームワークが異なっている点で、そのままでは動かない。そこで、NetBSDが使っているsysmonフレームワークに合わせて書き直すことにした。というわけで、やるべきことは、

  1. AMD CS5536のMFGPTの仕組みを理解
  2. NetBSD sysmonフレームワークを理解&sysmon wdogtimerの使い方を理解
  3. コードを書く
ということになる。MFGPT自体はGPIOと同様にCS5536のPCI-ISAブリッジデバイスの一部として実装されているため、glppcib.cの一部として扱えばmatch/attachルーチンは特に必要ない。純粋にデバイスの設定とsysmonフレームワークとの連携がうまくかければ動くだろう、ということでやってみた。

具体的な話は次のエントリーで。

NetBSDでGPIOを使う(for ALIX) (4)

実はNetBSDでgpioを使おうと思ったのは今回が初めてだったので、どうやればアクセスできるのか?というあたりから調べないといけなかった。Unixということで、とりあえず man -k gpioなどと打ち込んでみると、

  • gpio (4) - General Porpose Input/Output
  • gpioctl (8) - control GPIO device
  • gpioow (4) - 1-Wire bus bit-banging through GPIO pin
が引っかかった。取り急ぎ動作確認のためのコマンドがほしかったのでgpioctlのmanを読んでみると、これでgpioの各pinの値を制御できるらしい。ALIX.2/3のマニュアルをみるとLED D1-3あたりがGPIOで制御されているとのこと。
LED2,3を操作して点灯すれば、GPIOはまあ動いていることにしても良いだろう、ということでやってみた。





# gpioctl
/dev/gpio0: 32pin
# gpioctl -d /dev/gpio0 25 0
pin 25: state 1 -> 0
# gpioctl -d /dev/gpio0 27 0
pin 27: state 1 -> 0

無事に両方ついてくれた。あとは、必要に応じてgpio(4)のAPIをつかって制御すれば動くだろう、ということで、GPIOサポートは一通り終了。本当はLPCポートに配線されているgpioポートを利用できるか?というあたりを試してみないといけないのだが、実際にgpioを使う気になるまでは放置しておくことにしよう。

NetBSDでGPIOを使う(for ALIX) (3)

カーネル関係の移植作業は、カーネルの大きな構造を理解していれば、あとは単純な作業になる。が、「カーネルの大きな構造の理解」は短期間に取得することは比較的難しい気がする。なんか長い間小さい作業を進めているといつかわかっている、みたいな理解の仕方をする人が多いのではないだろうか。少なくとも私はそうだった。
細かい作業の履歴はとっていないので、ここではglxpcib.cの変更点の要点を列挙して注釈をつけてみることにする。もし、万が一カーネルに手を出そうとする人の参考になればうれしいな。ちなみにNetBSDにsend-prしたglxpcib.cとOpenBSDのglxpcib.cとの差分はdiff -uでみたところ393行だった。OpenBSDが383行、NetBSDが484行になったので、純増で101行。結構差分が大きかったことに調べてびっくり。

やらないといけないことは、大きく分けると

  1. PCI deviceとして検出してアタッチできるようにする
  2. カーネルの既存のフレームワークと協調できるようする
  3. デバイスとして動くようにする
という感じ。1ができるようになるためには、コンパイルが通ってkernelを作成できるようになっていないといけないので、とりあえず中身は空でもいいからkernelを作成できるように外側から攻めていく方が楽だと思う。

PCIバスは昔のISAなどと比べて、デバイスの検出、リソース管理の部分がよくできているので、OS側で扱うのも非常に簡単だ。PCIデバイスにはPCIコンフィグレーションレジスタがかならず実装されていて、そこにグローバルユニークなベンダーID、プロダクトID、デバイスクラスなどが記録されているのでそれを読めば大体そのPCI空間にいるデバイスがなにかを判断できる。OSではその値を比較して具体的な処理を進めれば間違いは無い。NetBSDでは、hogehoge_match()とhogehoge_attach()という関数を使うのが普通なので、いろいろなPCIデバイスのドライバのコードをみればすぐに理解できると思う。CS5536のデバイス検出のコードは以下のようになる。

int
glxpcib_match(struct device *parent, struct cfdata *match, void *aux)
{
struct pci_attach_args *pa = aux;

if (PCI_CLASS(pa->pa_class) != PCI_CLASS_BRIDGE ||
PCI_SUBCLASS(pa->pa_class) != PCI_SUBCLASS_BRIDGE_ISA)
return (0);

if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD &&
PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_AMD_CS5536_PCIB)
return (2); /* supersede pcib(4) */

return 0;
}

PCI_VENDOR,PCI_PRODUCTなどをみて適切なデバイスを認識できれば処理が進む。NetBSDではベンダーコードなどはsrc/sys/dev/pci/pcidevsというファイルに列挙されている。新しいデバイスを登録するときはそこにエントリーを追加してmake -f Makefile.pcidevsでデータベースを生成しなければならない。

*_match, *_attachをカーネルのデバイスコンフィギュレーションフェーズに登録するためのマクロとして、

CFATTACH_DECL(glxpcib, sizeof(struct glxpcib_softc),
glxpcib_match, glxpcib_attach, NULL, NULL);

を使う。最初は、おまじないだとおもってコードの最初の方にかいておくと良いだろう。この辺はPCIデバイスなら全部同じなので特に悩むことはない。デバイス依存のコードは実際にデバイスをアタッチするところから始まる。デバイス毎の情報を保存するsoftc構造体(今回はstruce glxpcib_softc)を設定し、デバイスをアクセスするためのPCI空間の調整やメモリーへのマップを設定して、具体的なデバイスの初期化を行わなければならない。
glxpcib_attach()の中を全部説明すると大変なので、PCIデバイス一般な説明をしておく。OSがPCIのバスの管理をしてくれているので、デバイスドライバは自分が使うメモリ空間にだけ気をつけておけばそれなりに使える。bus_space_map()をつかってそのPCIデバイスのIO空間をメモリにマップし、bus_space_write/read_*()でその空間の読み書きを行うというイメージでデバイスにアクセスできる。
あとは仕様書をみて、必要な機能のレジスタを読んだり書いたりする方法を実装すればいい。

CS5536はたくさんの機能が1チップにまとまっている。これは物理的な1チップに複数のPCIデバイスが含まれている=複数のデバイスIDがある、ということを意味しているのだが、各PCIデバイスにも複数の機能が含まれてもいる。今扱っているPCI-ISAブリッジにもPCI-ISAブリッジ以外にGPIOやMFGPT(Multi function General Purpose Timer)などの機能がある。PCI-ISAブリッジとしてmatch/attachをする際にGPIOやMFGPTをつかったWatchdog timerも設定してあげなければ使えるようにならない。
たとえば、GPIOの空間は以下に示す用に、GPIOのIO空間のベースアドレスなどをつかってio handleを取得してそのio handoleをつかって読み書きをする、という感じ。うまく設定できればconfig_found_ioをつかって、gpioバスができたことをOSに通知してあげると、OSのGPIOフレームワークがそれ以降の処理を引き取ってくれる。

/* map GPIO I/O space */
sc->sc_gpio_iot = pa->pa_iot;
gpiobase = rdmsr(MSR_LBAR_GPIO) & 0xffff;
if (!bus_space_map(sc->sc_gpio_iot, gpiobase, 0xff, 0,
&sc->sc_gpio_ioh)) {
aprint_normal(", gpio\n");
[このへんでいろいろ設定]
gpio = 1;
}
/* Attach GPIO framework */
if (gpio)
config_found_ia(&sc->sc_dev, "gpiobus", &gba, gpiobus_print);


全部うまく動くと、NetBSDがブートするときに

glxpcib0 at pci0 dev 15 function 0
glxpcib0: Advanced Micro Devices CS5536 PCI-ISA Bridge (rev. 0x03)
timecounter: Timecounter "CS5536" frequency 3579545 Hz quality 1000
glxpcib0: watchdog, gpio
gpio0 at glxpcib0: 32 pins
gpioow0 at gpio0 pins 6: DATA[6] open-drain pull-up
onewire0 at gpioow0

という表示が拝めて、うれしい気分になる、というのがこの作業の最初の到達点だ。
パッチはすでにNetBSDにsend-prしてある。kern/37577に全ソースがついているので興味があったら参照してほしい。(ある程度時間がたっていればNetBSDにマージされていることを期待)

使い方は次回のエントリーで。

NetBSDでGPIOを使う(for ALIX) (2)

今回はベースとなるファイルがあるので気を楽にして取り組むことにする。とりあえず手元に用意するものは、

  • OpenBSDのglxpcib.c
  • NetBSD-currentのhead tree
  • OpenBSDのsys source tree
  • AMD CS5536のデータシート
  • 実機(ALIX)
だけ。NetBSD-currentのhead tree、おもむろにopenbsdのglxpcib.cをNetBSDのソースツリーのsrc/sys/arch/i386/pciの下にコピーする。このあたりに新しいファイルを追加したときには、src/sys/arch/i386/conf/files.i386にエントリーを追加する。これをしないとconfigしたときに認識してくれないし依存関係を記述することができない。とりあえず今回は


# AMD Geode CS5536 PCI-ISA bridge
device glxpcib: isabus, sysmon_wdog, gpiobus
attach glxpcib at pci
file arch/i386/pci/glxpcib.c glxpcib

と追加。デバイス名はopenbsdと同じglxpcib。sysmon_wdog, gpiobusは後で説明するけれど、NetBSDのwatchdogおよびgpio busの共通部分のフレームワークを使うことを意味している。glxpcibはpciバス上にアタッチされて、ドライバの実体はglxpcib.cである、という意味になる。

これで、kernelのconfigurationファイルにglxpcibという名前が出てきてもconfigコマンドが処理できるようになる。configファイルには、


glxpcib* at pci? dev ? function ? # AMD CS5536 PCI-ISA bridge w/ GPIO and WDT support
gpio* at glxpcib?
isa0 at glxpcib?


を追加。pciバス上にglxpcibを生成して、その上にisaバスとgpioバスを生やす、という意味になる。
今回はALIXというconfigファイルをつくってそれで作業することにする。
で、おもむろにsrc/sys/arch/i386/confの下でconfig ALIX; cd ../compile/ALIX; make depend; makeなどと打ち込むとエラーメッセージがばりばりと出てくるので(あたりまえ)、それを見ながら今後の方針を考えて作業をする、というのが移植作業の一般的な進み方(だとおもう)。

というわけで、glxpcib.cをどうやって調理するかは続きで。

NetBSDでGPIOを使う(for ALIX) (1)

PC EnginesのALIXを使いこなすためにNetBSD kernelを触っている。ALIXのハードウェア的には実装されているけれどNetBSD-current (4.99.42: 2007/12/20)ではまだ扱えない機能がいくつかあってとりあえずそいつらを見えるようにしよう、とこの2日間ほど作業をしてみた。

まだ扱えない機能でサポートしたいなあ、と強く思ったのは、とりあえず
  • GPIO
  • Watchdog timer
  • I2C bus
の3つだ。カーネルからGPIOが使えたら外付けの回路を触る場合に大変便利だし、watchdogタイマーは組込用途のシステムでは便利だ。I2Cは普通のI2Cセンサーをつなげたり、PSOCなどのマイコンと協調して動くシステムを作る際にきっと役に立つだろう。

カーネルを触るときにはまず先人達の知恵を検索してみるところから始まる。OpenBSDのグループはAMD Geode/LXシリーズに愛があるので、とりあえずその辺で動いているものが無いかと探してみると案の定src/sys/arch/i386/glxpcib.cというファイルがあるのを発見した。中身をざっとみると、ALIXで使われているAMD CS5536のPCI-ISA host bridgeのドライバで、GPIOとWatchdog timerをサポートするコードがはいっている。OpenBSDとNetBSDは比較的カーネルのコンパチビリティが高い(部分が多い)ので、とりあえずこのコードをベースに作業を進めることにした。I2Cはそれらが動いてからでいいだろう。

具体的な作業は次のエントリーで。


2007年12月20日木曜日

ALIX3C.2 (PC Engines)

PC EnginesのALIX3C.2YTWから買ってみた。これは、AMDのLX800+CS5536が乗った160mm*100mmくらいのワンボードPCで、拡張性が高い(MiniPCI*2, USB2.0*2)割に大変お安い(18000円くらい)というお得なボードだ。

最近LX800ベースの機材をつかっていたので、試しに買ってみたのだがなかなかいい感じにうごくので気に入っている。単にOSを入れるだけなら一瞬で動くし、ハードウェア素材として使おうとしてもCS5536などの資料は完璧なものがWebで入手できるので扱い易い。今はNetBSD-current(4.99.42)のカーネルを拡張して遊んでいる。
1チップマイコンも便利だけど、たとえばネットワークスタックを動かそうとすると大変すぎるのでこのくらいのボードも使えるようになっていると便利。

PSoC SMP(Switch Mode Pomp)


電源電圧を自分で昇圧してくれるSMPの実験をしてみました。
ところでこれって、MCUの機能なのに MCUのための電源作れるって面白い回路だな。

あまりにも簡単なサンプルそのままの回路で動いたので回路図は無しですが、一瞬はまったところが1箇所だけ。
VCCをSMP経由で与えるので引き回さないんですが、書き込み器はもちろん普通にMiniProgの電源を与えないといけないので、その配線がなくて、なんで書き込めないんだ!と3分ほどはまりました。配線は極力短くとか言われてましたが、思ったよりも動くもんですね。

2007年12月12日水曜日

キャラクタ型液晶モジュールの電源配線


秋月などで売られているキャラクタ型液晶モジュールの電源は、なぜか1番、2番でVccとGNDが逆になっているタイプがあります。完成品つくる場合には採用する液晶モジュールによって電源を配置すればいいんですが、実験用の汎用基板だと両方に対応させられたら便利だよなーと思って、どうすればいいか考えてたんですがこれがたぶん一番簡単な方法じゃないかと思います。ピンヘッダ2x3とジャンパを2つ使って組み替えられるようにしました。いつもの手書きの配線図です。クロスしている部分はラップ線などでクロスにジャンプさせておきます。
縦と横でVCC/GNDのピンを入れ替えられます。変則的にジャンパを入れるとショートするので注意しましょう。できあがりはこんな感じになりました。それぞれ、縦・横に使うことで、2種類の液晶を使い分けられます。

2007年12月11日火曜日

USB加速度レコーダ(1)


まずは回路図です。今回使ったのはstrawberry-linuxのUSB PSOCモジュールです。チップは24794でUSBのFull-speedに対応しています。
簡単にUSBでシリアルやHIDが作れるこころが特徴です。
加速度センサーは秋月電子の KMX52を使った8pinの3軸加速度センサーモジュールです。たぶん簡単に素人が手にはいる3軸加速度センサーとしては最安値だと思います。
あとは、加速度とは関係ないのですがいつもの温度センサーLM35DZも準備します。

Port0にはアナログ入力の切り替えスイッチがつながっているので、こちらにセンサーを配置します。0,1,2でz,y,x 軸を配置しました。これは 加速度センサーモジュールのピン配置を考えて、ジャンプしない配線を実現するためにZ,Y,Xの順番になっています。
さらに、Port0-3に温度センサをつなぎます。

センサーとしては以上なのですが、動作状況がわかりにくかったのでPort2-7にLEDを配置して、インジケータとして利用します。

電源は、USB-PSoCのモジュールから取り出せますのでそちらを使います。
あとはデカップリング用コンデンサなどは適時配置するとよいでしょう。
最近のお気に入りは 2012サイズのチップコンデンサで、これを裏のランド面にランド間をまたぐように
貼り付けています。
http://akizukidenshi.com/catalog/items2.php?q=%22P-00355%22&s=popularity&p=1&r=&page=

以上でハードウェアの準備が終了です。

追記: 回路図が貼れてなかったので追加しました orz...

2007年12月8日土曜日

マルチチャンネルレコーダー


3軸加速度センサと温度センサからのデータを収集するために、USB PSoCを使ってシリアルからデータが流れてくるシステムを作ってみました。

とくに変わった所はないのですが、サンプリングレートを向上させるためにインクリメンタル型ではなく、sigma-delta 型のADコンバータを使っています。
1ポイント4回サンプリングで4chのデータを 32Hzの割り込みで取り出しています。サンプルレートだけなら、128Hzあたりはとれてもおかしくないんですけど、アナログスイッチの切り替えやシリアル通信があるからなのかできても64Hzでそれ以上はちょっと無理な感じですね。

今日は気力がないので完成品の写真だけですが、また元気なときにプロジェクトともどもアップしていきます。