戻る

snom105でsideshowを動かしてみようという試み。 ※新しいものは下に追加されます。

Vista SideShowの概要調査 (07-2-21)

snom105にkernel-2.6移植 (07-3-30)


2.6.21-rc1でpowerpc/8xxなカーネルでダイナミックリンクが動作しない

hello, world!なダイナミックリンクelfバイナリを作ると実行後だんまりになる。 しかしダイナミックリンクでも動作するバイナリもあるし、/lib/tls/ld.so.1を使うと動作したりする。
    Stand-alone shell (version 3.7)
    > strace.s -ELD_DEBUG=all ./hello
    execve("./hello", ["./hello"], [/* 3 vars */]) = 0
    ...
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
      = 0x3001a0000
    <<ここで死亡>>
    12:    relocation processing: /lib/tls/libc.so.6 (lazy)
    12:    symbol=_res;  lookup in file=./hello
    12:    symbol=_res;  lookup in file=/lib/tls/libc.so.6
で、これたぶんTLSメモリの確保の部分、つまりこのあたりで落ちている。
    /* Allocate.  */
    GL(dl_tls_dtv_slotinfo_list) = (struct dtv_slotinfo_list *)
      malloc (sizeof (struct dtv_slotinfo_list)
              + nelem * sizeof (struct dtv_slotinfo));   //これがmmapコール
    /* No need to check the return value.  If memory allocation failed
       the program would have been terminated.  */
    slotinfo = memset (GL(dl_tls_dtv_slotinfo_list)->slotinfo, '\0',
                       nelem * sizeof (struct dtv_slotinfo));
powerpcはキャッシュラインサイズdcache_bsize (32bit版ではdtsではなくarch/powerpc/kernel/cputable.c中の固定値)を、 elf起動時のmain(argc,argv,env,aux-vector)の aux-vectorの中でAT_DCACHEBSIZEとして渡している。 これは正常にdcache_bsize=16になっている。 でglibc4のsysdeps/powerpc/elf/libc-start.cで__cache_line_size大域変数に代入、sysdeps/powerpc/powerpc32/memset.Sでこれを見て高速に動作するようになっている。

で、ld.so自体のmemsetにはこれがうまく効いていないのかと思い、ld.soを変更して調べてみる。

    ps3$ apt-get source libc6
    ps3$ cd glibc-2.3.6.ds1
    ps3$ dpkg-buildpackage -rfakeroot -uc -b   …とりあえず再コンパイル。4時間程度かかる。
    ps3$ vi build-tree/glibc-2.3.6/elf/rtld.c
    ps3$ rm stamp-dir/build_libc
    ps3$ debian/rules build_libc               …5分ぐらい
    ps3$ strip build-tree/powerpc-libc/elf/ld.so
やっぱりmemsetで刺さっているのだが、memset直前に表示させた__cache_line_sizeは16で問題ない。memsetを自前のforループで置き換えても問題ない。

それぞれのアドレスを表示してみる。

    malloc(?)…{
      mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
      = 0x30019000    …最初のmallocでページ確保。
    }
    …
    malloc(8+512)…{
      mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
      = 0x3001a000    …領域が足りなくなったので次のページ確保。
    } = 0x30019f58
    memset(0x30019f58+8, 0, 512) …ここで刺さる
これでピンときた。このmemsetはページをまたいでクリアしている。 しかも確保した領域0x3001a000はmmap直後であり、TLBに存在していない。
つまり、memset中で使われている高速化のための特殊な命令がページフォルトした場合、TLBセット後にこの命令を正しく再開できないのではないか? これで動くバイナリがあったりほかのld.soを使うと動作する理由も説明できる。
ためしにmemset直前に0x3001a000ページに通常アクセスしてTLBをロードしておくとmemsetでこけなくなる。

というわけでmemset.Sを眺めたら、dcbtstとdcbz命令が使われている。 で、2.6.21-rc1のhead_8xx.Sのページフォルトのコメントを意訳すると、

     /* 補足: フォルトアドレスはDARレジスタに入っている。でもdcbi命令じゃだめ。
        一応カーネル内部ではPAGE_HWWRITEを設定しておけば回避できるけどね。
        そんなわけでカーネル空間以外ではdcbi命令は使えないから、残念。 */
ああああやっぱり。
で、memset.Sは__cache_line_sizeが0ならdcbz命令を使わないので、elf起動時にaux-vectorにAT_DCACHEBSIZEをセットしなければいいはず。カーネルのinclude/asm-powerpc/elf.hのARCH_DLINFOを書き換えて対応完了。

__cache_line_sizeが0でもdcbtst命令は使われているなあ、と dcbz dcbi darでググると、 dcbst,dcbz,dcbi,dcbf,dcbt命令が問題らしい。freescaleの公開errataには情報ないぞ。 CONFIG_8xx_DCBxFIXEDというパッチがあるみたいなんで試してみるのはあとまわし。

PnP-Xでネットワークに表示 (07-4-8)


UMDFでインストール (07-5-6)

《内項目はvs2008に適用。》 デバッグ出力をファイルに書き出していた(mailslotもnamedpipeもumdfからはセキュリティの関係上か動かない)のだけど、 socket-udpで他のマシンに投げるようにした。 wudfhostにデバッガをアタッチすればDebugOutputStringで出せるけど面倒すぎるしね。
SideShowへのコマンドはOpen→Ioctl→Close、のセットで連続同時にやってくる。IQueueCallbackCreateやIFileCallbackCleanupは実装不要。
Windows 2008で動作させる場合は、PnP-X IP bus enumeratorサービスを自動起動にすることと、ワイヤレスLAN機能をサーバマネージャから追加すること。

WSDデバイスへの通信 (07-5-18)

クライアントからは以下のように通信する。これはUMDF中のdllに限らず、一般アプリケーションからでも同様になる。
  1. WSDCreateDeviceProxy("urn:uuid:DEVICE-GUID", "urn:uuid:GENERATED-MY-GUID", コンテキスト, &host);
    この関数実行時点でsoap.udp3702/Resolveが投げられ、 urn:uuid:DEVICE-GUIDが <wsa:EndpointReference><wsa:Address> と一致するホストが取得される。大文字小文字は区別される。 さらにResolveMatchしたアドレス(通常はsoap.http5357)にGetが投げられ (この時のwsa:fromはurn:uuid:GENERATED-MY-GUID)、 デバイスに関するメタデータが取得される。
    なんでResolveしているのかっつーと、ネットワークデバイスはDHCPでアドレス変わりうるから。ちなみに物理アドレスURLを渡すこともできる。
  2. host->GetServiceProxyById("uri:SERVICE-NAME", &service);
    uri:SERVICE-NAMEがメタデータの <wsdp:Hosted><wsdp:ServiceId> と一致するサービスが選択される。大文字小文字は区別される。
  3. service->GetEndpointProxy(&endp);
    サービスのエンドポイントが <wsdp:Hosted><wsa:EndpointReference><wsa:Address> から取得される。これはsoap.http5357でもかまわない。
  4. endp->SendTwoWayRequest(....);
    この関数で実際にsoap.httpが実行される。Body構造をOpFormatInt32等で指定するのが手で書くと面倒だけど。
で、UMDF dll中から使うためには、インストールされたデバイスに対応したDEVICE-GUIDを取得する必要がある。この方法が不明。 一応、正しそうな方法は以下のようだと思われる。
  1. device->RetrieveDeviceInstanceId(...)でDEVICE-INSTANCE-ID取得
    これは実際には
    URN:UUID:DEVICE-GUID\UMB\3&320DABCD&6&URN:UUID:DEVICE-GUID/URI:SERVICE-NAME
    のような、DEVICE-GUIDとSERVICE-NAMEを組み合わせたものを大文字にしたものになっている。
  2. CoCreateInstance(__uuidof(FunctionDiscovery), 0, CLSCTX_INPROC_SERVER, __uuidof(IFunctionDiscovery), &disc);
  3. disc->CreateInstanceCollectionQuery(FCTN_CATEGORY_PNP, 0, 0, 0, 0, &query);
  4. query->AddQueryConstraint(FD_QUERYCONSTRAINT_PROVIDERINSTANCEID,DEVICE-INSTANCE-ID);
  5. query->Execute(&collect);
  6. collect->Item(0, &inst); まででIFunctionInstance取得。
  7. inst->OpenPropertyStore(0, &prop);
  8. prop->GetValue(PKEY_PNPX_GlobalIdentity, &DEVICE-GUID); でDEVICE-GUID取得。
しかし、この方法をOnDeviceAddやOnPrepareHardwareから実行するとOpenPropertyStoreがブロックされエラーになる。 デバイス中ですでにPropertyStoreを開いているためだと思われる。 device->RetrieveDevicePropertyStoreで代替できそうだけど 第一引数に渡す名前が不明。 UMBでもdevice->GetDefaultIoTargetとかでIWDFUsbTargetDeviceインタフェースみたいなのが取得できればラクなんだけど。 device->RetrieveDeviceInstanceIdで取得したDEVICE-INSTANCE-IDからDEVICE-GUID部分を取り出して小文字に変えてWSDCreateDeviceProxyに渡すか、 HKLM\SYSEM\CurrentControlSet\Enum\DEVICE-INSTANCE-ID開いてLocationInformationキーの値に直接通信するしかないのか。
とサポートフォーラムで尋ねたら以下の方法で解決。ちなみにSetupDiGetDevicePropertiesを用いるとやっぱりブロックされる。
  1. dev= SetupDiGetClassDevs(&GUID_DEVCLASS_SIDESHOW, 0, 0, DIGCF_PRESENT);
  2. SetupDiOpenDeviceInfo(dev, DEVICE-INSTANCE-ID, 0, 0, &info);
  3. SetupDiGetDeviceProperty(dev, &info, &PKEY_PNPX_GlobalIdentity, &prop, &DEVICE-GUID, ...);

.NETリモーティングとWCF (07-6-3)

しかしSOAPでのイベント受信の方法が不明。IWSDServiceProxy::SubscribeToOperationはさっぱり使い方がわからん。 wsdcodegenがLonghorn preview SDKでやっと公開された…wsdlファイルが必要…ASP.NET webサービスで適当にこさえる…ASP.NET webサービスってイベント定義できない…WCFでできるかなー…WCFで吐くwsdlって使えねー…こうしてインプリメントが進まないのであった。

でもちょっとWCFさわったのでせっかくだから.NET Remotingとの対比メモを残すぜ。

私の好きな.NET remotingではクライアント側で定義ファイル(app.config等)で指定されたオブジェクトが自動的にサーバ側で作られる。 作った後は通常のオブジェクトとまったく同じ取り扱いが可能。 サーバ側からクライアント側の関数を呼び返したい場合は、 サーバオブジェクトにイベント定義してもいいし、

public sub server_myfunc(byval cli as object)
  cli.callclientcallback("I'm callbacking")
end sub
程度で自由に呼び返せたりする。VisualBasic万歳。

しかし.NET remotingでは、クライアント側で呼び出すサーバオブジェクトの実装が必要となる。定義ではない。 つまり上記のようにコールバックする場合には、サーバ側のほうでもcliオブジェクトの実装が必要。 イベント定義にした場合でもAddressofに渡すオブジェクトの実装がサーバ側に必要。 soapsudsなどを使ってカラの実装を作ることもできるが面倒。で、インタフェース経由で呼び出すことにより実装の共有を避けることができる。

public sub server_myfunc(byval cli as Iclientcallback)
                implements Iserver.server_myfunc
  cli.callclientcallback("I'm callbacking")
end sub
インタフェースの共有(公開)は必要ですが大したことじゃありません。

で、WCF。インタフェースをほぼ必ず使う.NET remoting、だったりする。 違う点は以下の通り…WCF、面倒なだけで利点ないじゃん。 .NET remotingを使い続けよっと。

…自前でwse:EventSource="true"なwsdl書く…あっさりwsdcodegenがコード吐いてくれた。 コールバックはhttp://*:5357/GUID、がendpoint。 http202は返すけど、ReferenceParametersの項目(wse:Identifier)とsoap:Bodyの順番があってなかったり省略したりするとRequestStubFunctionを呼び返してくれない。 IUnknown*はAddRef/Releaseだけ呼ばれる。 で、コールバック後腐るので強引に__stdcallにしたらやっと動いた。

net.tcpフォーマット (09-12-9)

さらに余談。WCFにnettcpBindingがあって、バイナリフォーマットで効率がいいらしい。 実態はsoap(wshttp)のxml(文字列ベース)をバイナリ化したもので、 XmlDictionaryWriter.CreateBinaryWriterの拡張。 まずTOKENの表があり、上から順に01、03、05、07…と番号が振られる。 これはtcpコネクション内で再利用され、 同じ関数を呼び出した場合TOKEN表の長さは基本的には0になる。
06    ws-soapであることを示す
cc 01 全体の長さ
84 01 TOKEN表の長さ
1c "http://tempuri.org/ICalc/Add"
48 "net.tcp://192.168.0.1:999/ServiceModelSamples/Services/CalculatorService"
03 "Add"
13 "http://tempuri.org/"
02 "n1"
02 "n2"
長さは128以上は 80+下位7bit 上位8bit で表す。 表にない一般的なTOKENには偶数の値が既定で振られている。 それを利用して以下のようにwsなsoapをエンコードしている。
56 02      <s:Envelope
0b 01 73 04    xmlns:s="http://www.w3.org/2003/05/soap-envelope"
0b 01 61 06    xmlns:a="http://www.w3.org/2005/08/addressing">
56 08        <s:Header>
44 0a          <a:Action
1e 00 82           s:mustUnderstand="1">
ab 01            http://tempuri.org/ICalc/Add </>
44 1a          <a:MessageID>
ad 12 34...xx    urn:uuid:1234-xxxxx </>
44 2c          <a:ReplyTo>
44 2a            <a:Address>
ab 14              http://www.w3.org/2005/08/addressing/anonymous </>
01             </a:ReplyTo>
44 0c          <a:To
1e 00 82           s:mustUnderstand="1">
ab 03            net.tcp://192.168.0.1:999/ServiceModelSamples... </>
01           </s:Header>
56 0e        <s:Body>
42 05          <Add
0a 07              xmlns="http://tempuri.org/">
42 09            <n1>
89 0a              10</>
42 0b            <n2>
89 14              20</>
01             </Add>
01           </s:Body>
01         </s:Envelope>
ノードは44でa:、45でb:、56でs:、42で接頭辞なし、のようになっていてTOKENが続く。 アトリビュートは0b 数 "?"でxmlns:?=、0aでxmlns=、0c TOKENでa:XX=、0d TOKENでb:XX=、1e TOKENでs:XX=、のようになる。
データは81で0、83で1、85でFalse、87でTrue、 89 XXで-128〜127、8b LL HHでShort、8d X4でInteger、8f X8でLong、 91 X4でSingle、93 X8でDouble、 99 数 "xxx"で255文字まで、9b LL HH "xxx"で65535文字まで、9d X4 "xxx"で65536文字以上、 ab TOKENでトークン文字列のようになっている。 最下位ビットはそのノードが閉じるときは1、閉じないときは0であり、空文字列や空配列はデータをおかずにそのまま01(閉じる)を使用する。

設計と実装(07-7-7)

さて、と。実験はだいたい終了。設計と実装にうつるべ。

デバイスドライバをWS-Dから導入したらSideshowコンパネに出てこない。 「ハードウェアの追加」からなら大丈夫なんだけど。 権限がらみなのか、下位(UMBus)が悪さしてるのか…CoInstallerで別インストールすればいいのはわかるがそんなの書きたくないしのう。
…とフォーラムで尋ねてほっといたのだがVista SP1のWindowsUpdate(08-5-16)で1年越しに解決。SP1プレビューでもSP1手動当てでも解決しなかったのに謎。

残る問題点。 PnPXでインストールして、機器の電源切って再起動やスリープ解除するとドライバがアンインストールされるのはいいんだが、その後機器の電源入れても復旧しないことかな。 その状態で再起動や再スリープ解除すると自動的に再インストールしてくれるんだが。 Helloを機器側から投げても駄目。定期的にProbe投げて見つかったら再インストールしてくれ。