livaの雑記帳

livaの雑記帳

OSとか作ってみたい

USBキーボードのデバッグ

 

 

近いうちにきちんと記事にする

2017/3

 

書き溜めた知見(というよりは独り言)を転記

 

 

3/18

  • 割り込み周りのデバッグをする場合は、HPETを使おう
    • HPETならFSB割り込みとI/O APIC経由の割り込みの両方をサポート
    • PCIで割り込みが出ない場合、デバイスPCIバイス)ドライバのバグの可能性が捨てきれないが、単純なHPETを使う事で、問題の切り分けができる
  • ACPIから取得したPCIバイスIRQが間違ってるっぽい
    • FreeBSDと比較すると明らかに違う
  • DSDTの出力、FreeBSD
    • # acpidump -o dump.aml
      # iasl -d dump.aml

    • これでdump.dslが得られる
    • acpidump -dt dump.amlからのiasl -f dump.amlだとなんかエラーが出る
    • linux版はこっち
  • DSDTを眺めてたら、APIC modeの時とPIC modeの時で異なるPCIの割り込みテーブルを返している事に気づいた
    • DSDTによると、今実験中のマシンではAPIC modeではPCI Routing Table(_PRT)はPCI Interrupt Linkデバイスを返すのではなく、直接IRQを返す模様
      • ココらへんの話を理解するにはここ
    • ぐぐってみると、どうやら手動でAPIC modeに切り替えなければならないらしい
    • 戻り値の処理について、こんな事も書かれている
    • で、_PICメソッドを実行したんだけど、やっぱりPIC modeで返ってるような。。。
    • 実機の場合、_PICメソッドの戻り値はAE_SUCCESSになるはずで、freebsdでもそうなってるのだけど、残念な事にRaph_KernelからはAE_NOT_FOUNDが返ってくる
      • AcpiEnableSubsystem()等の前に実行してた。そりゃ動かないわ

    • FreeBSDIRQが一致した。バンザイ!
      • APICモードでもInterrupt Linkデバイスが帰るケースはあっても良いと思うのだけど、その時の実装はまだ微妙。。。
        • 面倒なのでまた何時かデバッグする
        • こういうのが積もりに積もった結果、今こうして泣きながらデバッグしている事はよくわかってる
  • これまでのまとめ
    • PCIの割り込み(Legacy)を有効にすると、めちゃくちゃ大量の割り込みが来る
    • PCI側かUHCI側でEOIを発行できてないのではないかと推測
    • いろいろ調べた結果、たしかにEOIやdeassertは発行していなかった
    • それでもめちゃくちゃ大量の割り込みが来る
    • PCIの割り込みをdisableにして、I/O APICの設定だけをしてみた
      • それでも割り込みが来た
    • 結論。PCI以外のハードウェアから割り込みが来てる
    • IRQ番号間違ってるのでは・・・?
    • ACPIからのIRQ取得を見直したら動いた
  • 最近、調べ事をしたら自分のブログに当たる事が多くなってきた
    • 以前の知見を脳内に復活させる事ができて大変良い
    • ので、これからもこまめに記録をつけていかねば

 

3/17

  • Level Triggerな割り込みの処理について
    • on I/O APIC
    • LinuxFreeBSDのソースを読むと、どうやらEOIレジスタがI/O APICにはついているらしい
      • そんなもの、I/O APICのデータシートにはない
      • ICH8のI/O APICの項目には確かに書かれてる
      • どうやらバージョンが0x20以上だと存在するらしい
      • で、0x20より小さい場合は、level triggerを一度edge triggerに変更し、また戻す事でEOI相当にするらしい。。。
        • なんだこのキモい動作

 

3/14

  • 昨日の続き
    • とある実機の話
      • PICの初期化をしないと15番に割り込みが入る
      • PICのベクタオフセットを設定するとそれだけずれる
      • これは恐らくPIC(8259A)からのSpurious Interrupt
      • PS/2キーボードからの割り込みはI/O APICのredirection tableの通りにやってくる
      • てことはやっぱりsymmetric I/O modeになってるっぽい
      • 8259からのSpurious Interruptは一回だけだから無視してOK?
      •  とりあえず、8259からの割り込みか、I/O APICからの割り込みかを区別した方が良い
        • これまではどうせ同じIRQだと思って、敢えて8259のベクタオフセットとI/O APICのredirection table上のベクタが重なるように設定していた
        • 今後の調査で8259からの割り込みがこの1回しか来ない事が分かれば、無視しても良さそう
    • symmetric I/O modeではLINT1:0はやっぱり初期化してなくても良いのでは説
      • 今までHPETからの割り込みのテストをマルチコアの初期化前(=virtual wire mode?)でやっていたから、LINT1:0の初期化が必要だった模様
      • むしろsymmetric I/O modeではLINT1:0をマスクしないと変な割り込みが来る
    • またしてもHPETからのFSB割り込みが来なくなった。なんで???
    • 割り込み周りを修正してたら、これまで8042(PS/2キーボード、エミュレーションモード)から割り込みが来なかったマシンでも割り込みが来るようになってしまった
      • 数ヶ月の努力、水疱に帰す
      • I/O APICの設定がちょっとおかしい
        • CPU0に向けて割り込みを発生させているつもりなのに、CPU1で割り込みが入る
          • 1回目の割り込みはCPU0で発生し、2回目の割り込みはCPU1で発生する
          • 割り込みの分散が起きている模様
          • でも、Destination ModeはPhysicalなので、割り込みが分散するはずがないのだが。。。
        • Delivery ModeがLowだったので、これをFixedにしたら直った
          • Loweset Priorityって勝手に割り込み分散するんだろうか
          • いずれにせよ、最近のアーキテクチャではLowest Priorityは使えないらしいという事をどこかで読んだので、今後は全ての割り込みはFixedにする事にする
    • PCI
      • これによればPCI割り込み(not MSI)はlevel trigger, low activeに設定するのが良いらしい
        • I/O APICの設定の話
        • けど、ICH8のデータシート内のAPICの項目ではこの値は常に0なんだよなぁ

 

3/13

  • MP Specificationを改めて読み返してみる
    • 3.6.2.2 Virtual Wire Modeを読むと、8259経由で割り込み転送する場合はLINT0:1を使う事が明記されている。

      • LINT0:1をプログラムしてなくて動かなかった事の理由かなぁ

      • でもそれだと今はVirtual Wire ModeかつI/O APICを使えてないという事になる
      • どうやったらSymmetric I/O modeにできるんだ?
      • やはりsymmetric I/O modeだとLINT0:1はマスクしてても問題ないような気がするので、symmetric I/O modeになってないのかもしれない
      • Appendix Bの方法の通りに初期化しているので、実はsymmetric I/O modeになっているのではないか、という説
      • マルチコアの初期化をデバドラの前に持ってくる事で、symmetric I/O mode状態で割り込みの設定ができるはずだと信じ、そうなるようにした

 

3/12

  • ベアメタルプログラミングをやっていながら、これまでLINT1とLINT0をマスクしていた事に気づく
  • とりあえずAPIC経由のHPET割り込みは来るようになった。直接の解決要因がよくわからない
    • Tn_32MODE_CNFを立て忘れた説が濃厚

    • FSB経由はまだ来ない
  • TN_INT_TYPE_CNFが上がってたので、下げたらFSB経由でも割り込みが来た
    • まあそりゃlevel triggerで来るわけはないのだけど、このフラグ以前から立ってたかなぁ・・・?
  • 結局LINT1:0の初期化忘れが問題だったっぽい
    • というわけで、この2つはマスクせずに、NMIとExtIntが届くようにしましょう
  • Tn_FSB_EN_CNFってR/Wなフラグだと思うんだけど、書き込んでもフラグが下がらないマシンがある・・・ような・・・?

 

3/11

  • とある物の実装をしていて、割り込み周りが実は全く動いてないのではないかという事で調査中
    • 実機の話。QEMUでは完璧に動いている
    • まずはRTCからの割り込みを無効化する
  • いい加減実機でのデバッグの進捗がなさすぎるので、freebsd環境を構築し、レジスタ値の比較をする事にした
    • USB3.0メモリに構築したけど、本当はSSDを使った方が良いんだよなぁ
    • 自分用、freebsdカーネルを高速にコンパイルするコマンド
      • make -DNO_CLEAN -j8 kernel KERNCONF=GENERIC INSTALL=”install -C”

      • install -Cって効果あるんだろうか・・・
      • このコマンドでもUSBメモリではおそすぎてイライラしたので、HDDに変えたら爆速になった

 

3/5

自作OSに10GbE NICを移植する

(この記事は古いサイトから移行したものです)

 

f:id:liva_h:20170413193555j:plain

 

Raph Kernelでintel X540-T2を動かすために、slank君と一緒にixgbeドライバを実装しました。

10GbEってのはやはり夢がありますね。ボトルネックを筋肉で解決する感じの脳筋プレイ、嫌いじゃないです。大艦巨砲主義バンザイ。

 

独自OS向けに10GbEデバドラを用意するのは1ミリも新規性はないのですが、まあこれはあくまで僕の備忘録という事で。新規性を出すのはこの後ですね。

 

さて、ixgbeシリーズも基本的にはe1000(8254x/8257x)シリーズと殆ど同じ構造をしています。もちろん10Gbpsでも性能が出せるよう細かい部分が異なりますが、とりあえず動かすだけならe1000の構造を把握していればixgbeの実装で悩むことはないはずです。

 

livaは以前e1000 NIC独自実装freebsdドライバの移植をした事があるので、比較的容易でした。今回も独自実装ではなく、freebsdドライバを移植したのですが、その他のタスクと並行して作業しても二週間程度で終わりました。たぶん移植に全リソースを使えるならデバッグ含めても一週間あれば十分でしょう。

 

余談ですが、e1000の実装をした事がない人は、8254x NIC 仕様書の3.2 Packet Reception, 3.3 Packet Transmission, 3.4 Transmit Descriptor Ring Structure辺りを読むと大枠がつかめて良いと思います。他の項目は大体細かい設定周りの話なので、実装するよりも既存のデバドラコードを読んでしまった方が早いかもしれません。

3.2〜3.4に書かれていることは、

  1. 下図のようなキュー(リングバッファ)をOS側で受信、送信用にそれぞれ用意してあげて、特定のレジスタにその物理アドレスをセットする
  2. 受信キューの場合は、リングバッファの各エントリにさらにOS側で別途確保したメモリ領域の物理アドレスを設定する。HeadとTailがレジスタから取れ、その間のエントリはハードウェアによって受信されたパケットが格納されたエントリとなる。エントリを再設定した上で、レジスタを動かし、エントリをハードウェアが使えるようにする
  3. 送信キューの場合は、Tailが指すエントリに、パケットが格納されている物理メモリをセットし、Tailを動かす

といった内容です。e1000e(8257x)になるとマルチキューとかが出てくるのだけど、基本的には同じ話なので割愛。

f:id:liva_h:20170413193613p:plain

話が逸れました。freebsdのixgbeドライバを移植するにあたり、e1000ドライバとの違いは、

  1. pollingがない(ixgbe_pollが未実装)
  2. Descriptor Write Backの有効化が前提となっているので、その対応

ってな感じです。

1については、em_pollを見つつ、それっぽい感じで実装すれば動きます。定期的にixgbe_handle_linkを呼んで、リンクステータスチェックをしましょう。

2については、下に貼った図の通りで、受信キューでパケット受け取ると、エントリを各種情報で上書きするという物です。当然、エントリをこのまま使いまわす事ができないので、ixgbe_refresh_mbufs内でバッファアドレスを再度書き込むという処理をきちんと移植する必要があります。移植コスト自体は全く高くないのですが、理解してないとリングバッファ内にセットした物理アドレスが意味不明な値に上書きされるように見えるため、メモリ周りのエラーを疑い出して泥沼にハマります。

 

f:id:liva_h:20170413193609p:plain

 

まあそんな感じで頑張って、一日くらい夜を潰してデバッグした結果、以下のようにARPが返ってくるようになりました。

(見づらい写真でごめんなさい)

 

f:id:liva_h:20170413193550j:plain

 

下のMacBook(192.168.100.100)からarp-scanを掛けて、上のRaph Kernel(192.168.100.104)が応答しています。

ちなみに実験に使用したL2スイッチはNETGEAR ProSafe XS708T-100AJSです。ケーブルは一応Cat6e。Cat5でも2M程度なら10Gが出てしまうのですが、まあ流石にね。

 

 

デバッグで疲れたので、ベンチマークはまた別の記事(を書くかはわからないけれど)で。

2016/8

書き溜めた知見(というよりは独り言)を転記

 

8/28

  • cpuid 1における謎な例外発生は単純なプログラミングミスだった。関数ポインタのポインタでコールバック呼び出しをしていたのだけど、実体となる関数ポインタ変数自体がスタックに置かれているので、そのポインタを取ってしまうと事故る、という話。
    • これ自体は些細なバグなのだけど、これによって変な場所を関数callしてしまい、invalid operand例外が発生した。ユーザーランドプログラミングだとinvalid operandになった場合はまあ関数ポインタの向き先を間違えた、と分かるのだけど、カーネル開発だとメモリ破壊を含めたいろいろな可能性を考慮しなければいけないから、バグの特定が面倒。

 

8/27

  • 現状AHCI実装の進捗生産を阻んでいるのは、cpuid 1(割り込み処理CPU)における謎な例外発生現象。一つの可能性として、カーネルスタックのオーバーフローによるメモリ破壊が起きていると仮定。カーネルスタックの両端に未割当ページを配置する事で、オーバーフロー時にページフォルトが発生するようにした。(Raphine/Raph_Kernel#81
    • ついでに、カーネルスタックの先頭にカーネルスレッド情報を格納する構造体を配置。CPU ID等を格納しておく事で、ハードウェアアクセスがなくなり、性能劣化を防げる。
      • なんでこんな事をしたかというと、APIC ID取得命令って結構遅いから。デッドロック検出のためにスピンロック確保時はCPU IDによる簡易チェックが走るようになっているのだけど、その度にハードウェアアクセスすると、馬鹿にならない程の性能低下が起きる。一回一回は大した事が無くても、スピンロックは結構呼ばれるので、これはよろしくない。
  • カーネルスタックの未割当ページガードを実装したものの、どうやら問題はここではないらしい。まあスタックガードはいずれ実装しなければならなかったし、これで一つ問題の切り分けができたので、良しとする。続きの実装はまた明日。

 

8/25

  • カーネルスタックって、仮想アドレスで(スレッド毎に)同じ場所に配置するのと、別々に配置するのだったらどちらが良いのだろう?
    • 前者だとTSSやIDTを全スレッドで共通化できる。(物理ページレベルで)ただし、別のスレッドの変数を参照すると事故る。(そもそもそんな事をするべきでないけど、万が一の場合)
    • 後者だとカーネル空間を完全に共通化する事ができる。ただし、コンテキストスイッチの際にTSSやIDTを毎回切替えないといけない
    • I/O permissionをスレッド毎に切り替える(かどうかは未定)とすると、結局TSSはスレッド毎に必要なので、後者で良いのでは
  • ラズパイ3移植の際に参考になりそうなページを発見。http://d.hatena.ne.jp/cupnes/20160529/1464513206
  • とりあえずgoogle検索にこのサイトが引っかかるようになった(→8/24)

 

8/24

  • リアルの生活が忙しくていろいろ滞り気味だけど、半分くらい解消したので、少しづつ記事も書いていかねば。
  • まずはgoogle検索に引っかかるよう、登録した。といってもまだ中身ないから引っ掛かってもしょうがないんだけどね

 

8/21

  • カーネルスタックオーバーフローを検知できるようにしよう。今はオーバーフローするとそのまま何に使われているかよく分からないメモリ領域を破壊し続けるので、謎なバグが発生してしまう。対応方針としては、カーネルスタックの直上にマップされていないページを配置し、カーネルスタックが伸びたらページフォルトが起きるようにする。Raphine/Raph_Kernel#79

 

8/19

 

8/17

  • カーネル解説のページがどんどん増えていって、執筆が追いつかない
  • カーネル開発、別プロジェクトの活動、本業、でやる事多すぎててんやわんや
  • BSDのDEVMETHODに対する理解が少し進んだ。結果、設計が崩壊して、bsddev_pci_ctrlとかいう謎グローバル変数が誕生する事になった。後でもう少し綺麗にしなくちゃなぁ。。。
  • ↑のように一瞬思ったけど、BSDではparent側に定義されているDEVMETHODをchild側に定義するだけでかなり綺麗な設計になる気がしてる。これで勝った!

 

 

8/14

  • self mergeとかforce pushを検出できるようにしたい。webhookでなんとかなるかな?

 

8/12

  • 昨日、ストックからカーネル解説を書くと言ったが、2記事書いた所でストックが切れた。本当にわずかなストックだった。ここからは0から書かなければならないのか。。。はぁ。

 

8/11

  • Raph Libraryにスマートポインタを追加した。(issue:L26)
    • 実装的にこれで大丈夫か怪しいが、ひとまずうちのプロジェクトで使う分には問題なさそう。マルチスレッド対応にもしてある。
  • LEDiAさんのお陰で環境構築方法のページが出来上がってきた。
  • カーネル解説、とりあえず時間もないから、以前書いたテキスト(非公開)を流用しよう。手抜き、大事。

 

8/10

  • 昨日このサイトを作ったが、暫くは未完成のままになりそう。早くいろいろコンテンツを作らないと。
    • TODO: カーネルに関する解説を上げていく
    • WordPressデフォルトテーマ(Twenty Ten)がダサいので、何かお洒落なのに変えたい
  • cpuidを毎回取得するボトルネックを減らすために、Taskにcpuidを突っ込む設計にしよう。関数ポインタの扱いがなかなか難しいけれど、良い案はないものか。。。
  • slack的なチャットシステムを作りたい。
    • slackがもうちょっとオープンになったようなものがいいなぁ
    • IRCよりもナウい奴で(?)