はい、miku_JK_Jbです
今回は自作OSでマウスカーソルの描画をできるようにした作業について解説していくよ😉
それじゃあどうやって自作OSにUSBドライバを入れてマウスカーソルを描画できるようにしたのかについて解説していくよ😉
最初に行った作業は比較的新しいPCでも自分の自作OSが動くのか試してみた

左のPCが新しい奴で、右側のPCが今まで動作確認で使っていたPC
シリアルログを見るためにマザボのCOMポートからRS232CのD-Sub9ピンに変換する奴を

そしてD-SubピンとUSB変換のケーブルをSEX!!
これでシリアルログを取る準備は完了!!

自作OSの入ったUSBメモリを指して電源オン
あとはセキュアブートの設定してOK。
セキュアブートの設定は見せるのはNG(見せもんじゃねーぞ)
しかも見て!!
文字がめっちゃ綺麗だ!!
やっぱり新しい方が綺麗だね!!
GOPなども新しいからそりゃそうか😅
ってことで新しいPCでも自作OSが起動できることがわかったから、今後は古いPCと新しいPCの両方で動作確認をしていくよ😉
両者のスペックはこんな感じ
【新しいPCのスペック】 【古いPCのスペック】
マザボ:msi b550 gaming plus マザボ:ASUS P8Z77-V
CPU:AMD Ryzen3 4100 CPU:intel Corei7 3770K
メモリ:DDR4 32GB メモリ:DDR3 32GB
グラボ : Radeon RX5600XT グラボ:CPU内蔵グラフィックス
それじゃあ自作OSのマウスカーソルを描画できるようになった作業について解説していくよ😉
今回はUSBマウスが自作OSの起動時に刺さっていた場合と、自作OSが動作中にUSBマウスが刺された時にマウスカーソルを描画できるようにしていくよ
なので最初に行った作業は自作OSに高校生の間に作成したxHCI USBドライバをぶち込んだよ。
次に行ったのはマウスカーソルの形の作成をしたよ
↑マウスカーソルの作成中の写真
次にマウスカーソルを描画できるようにしたよ
行った作業はこんな感じ
・カーネルのメインループで USB をポーリング
・USB HID レポートをマウス状態に変換
・ユーザ空間の Desktop がマウス状態を取得
・描画前にカーソル下の背景を保存し、前回分を復元
・カーソル形状をピクセル単位で描く
これがマウスカーソルを描画できるようにする作業だ
マウスカーソルが描画される条件は、自作OS起動時にUSBポートにUSBマウスが刺されていた時、自作OSが動作中にUSBポートにマウスが刺されたときに描画される
xHCIなどのUSBドライバを自前で実装するなら、最初にマウスカーソルなどが描画されるかのテストを行うことで、ちゃんと自作のUSBドライバが動いているのかなどが視覚的に分かりやすいからおすすめ
それじゃあマウスカーソルが描画されるか見ていこう!!
マウスカーソルは描画されなかった
原因は?
USBマウスがHIDとして列挙される前に xHCI 側で失敗していた。
シリアルログを確認するとPORTSCのspeed bits が0になっていて、USBポートは接続検出されているのに、速度がまだ確定していない/読めていない段階で列挙を諦めていた。諦めんなよ!!もっと熱くなれよ!!
これはxHCIドライバの仕様としては完全にバグなので直す必要があるね。
また、検証しているUSBマウスのUSBは2.0と思われるので、リセット前に speed bits が未確定なのが正常動作なので、そこで落とすと USB2 マウスは原理的に列挙できなくなるので、そこも修正が必要そうだ
それでは修正していこう
行った修正はこんな感じだ
・列挙結果を bool から3値に変更
・ポート列挙関数の返り値を3値化
・speed==0 を即失敗にしない処理を追加
・ScanRootPorts 側を Failed のみラッチする方向に変更
・デバイス記述子の一時バッファを拡張
それでは順番に解説していこう
まずは列挙結果を bool から3値に変更について
これは列挙結果をSuccess / Retry / Failed で返すように変更した。
これで、速度未確定・reset直後・ホットプラグ直後みたいな一時的な状態を、致命的失敗として扱わなくなる。
次にポート列挙関数の返り値を3値化した修正について
これはxHCIがResultPortEnum を返すようになり、接続なしは Retry、明確な異常は Failed として返すようにした。
これまでは false しか返せなかったので、呼び出し元から見ると、「一時的にまだ使えない」なのか「本当にぶっ壊れてる」のかの区別がつかなかったが、3値化したことにより、区別がつくようになった。
次にspeed==0 を即失敗にしない処理を追加したことについて
これはUSB2 attach直後の未確定状態を Retry 扱いにし、必要時に ResetPort を試し、なお 0 なら再試行に回すようにした。
speed == 0 は「USB2ではない」とか「失敗」ではなく、だいたいはまだ速度が確定していない状態で、主に古いマザボ、ホットプラグ、reset直後では、PORTSCを読んだ瞬間に速度フィールドがまだ0のことがあるため、ここで即失敗にすると、列挙してくれなくなりやすい。そのために、少しだけ待ってあげて、それでも未確定ならリトライするようにした。
次にScanRootPorts 側を Failed のみラッチする方向に変更したことについて
これはResultPortEnum を受け、Failed のときだけ port_failed を立てる形に変更した。Retry まで port_failed にしてしまうと、一時的な失敗でもそのポートが使えない判定になるので、Failed だけをラッチするようにして、起動時に刺さっていたUSBマウスや、動作中に刺したUSBマウスが、タイミングの問題で1回失敗しても次のポーリングで復帰できるようにした。
次にデバイス記述子の一時バッファを拡張したことについて
これは18 から 64 バイトに拡張した。
拡張することで、バッファ越え、古いデータが残る、Descriptor判定ミス、スタック破壊、謎の列挙失敗を起こさないように安定化した。
それじゃあマウスカーソルが描画されるか見ていこう!!
変わらずマウスカーソルは描画されず。
シリアルログがとんでもないことに
うん。大丈夫そ?
よし、直そう。
シリアルログに大量に書いてあるのはUSB2.0 port reset failedだ。
主張がずいぶんと激しいね。目立ちたいのかな?
原因はxHCI のPORTSC レジスタを書き戻すときに読んだport_statusをほぼそのまま OR していた。
このせいで意図せずポートをdisableしたり、変化ビットを潰したりして、USB2リセット後にPortEnabled が立たずにポートのリセットが失敗していた。
それでは修正していこう
行った修正はこんな感じ
・speed < 4だけでUSB2判定するのをやめて、xHCIのSupported Protocol Capabilityを読むようにした
・固定のkPollCyclesPerMillisecond = 3000000をやめた
・Supported ProtocolでUSB2 portだと分かっている場合、speed == 0でもresetを試すようにした
それでは順番に解説していく
まずはspeed < 4だけでUSB2判定するのをやめて、xHCIのSupported Protocol Capabilityを読むようにした修正について
これはspeed < 4だけでUSB2判定するのをやめて各ポートごとのUSB2.0/USB3.0を保存して、Capability が取れない環境だけspeed < 4にfallbackするようにした。
次に固定のkPollCyclesPerMillisecond = 3000000をやめた修正について
これはCPUID 0x15 / 0x16 からTSC周波数を取り、debounce / retry / HID poll / operational wait に使うようにした。取れないCPUでは従来の3GHz相当へfallbackするようにした。
次にSupported ProtocolでUSB2 portだと分かっている場合、speed == 0でもresetを試すようにした修正について
これはUSB2 portだと分かっている場合、speed == 0 でもresetを試すようにした。
この修正は実機安定化のため。QEMUとかで動かすなら必要ないかも?まあ自分は実機でしか使わないので修正した。
この修正らを行ったことで、USB2.0ポートでもxHCIドライバが動く&USB3.0ポートなのに間違えてUSB2.0のポートとして処理して速度が遅くならなくなったり、ホットプラグ時の回復性も上がり、速度の遅いCPUやVMでの待機時間がズレにくくなる。
それではマウスカーソルが描画されるか見てみよう
クラッシュしたwww
原因はxHCIの初期化でクラッシュしている模様
それじゃあ修正していこう
行った修正は、xHCIのコントローラーのリセット後にslot配列をページアロケータから確保してゼロ初期化するように変更した。
これを行うことによって、xHCIドライバ全体の現在状態を入れているグローバル変数の肥大化を防いだ
それではマウスカーソルが描画されるかを見ていこう!!
結果は?
マウスカーソルが描画された!!
けど
色が変だし下の部分にモザイクが掛かってる!!
まあ、とりあえずもう一個作った円形のマウスカーソルも見ていこうか
は?(;゚Д゚)
なにこれ
よし、形を整えよう。
形の整えるやり方は、ほぼマウスカーソルの形作りと一緒
どこが悪いのかとかも考えて直す

マウスカーソルを直す時のイメージ(マウスカーソルの形作りの写真の使い回し)
形を整えた結果
普通の矢印形のマウスカーソル

円形のマウスカーソル

素晴らしい!!
これでいい!!
ただ、若干矢印形のマウスカーソルは修正してもいいかもしれないが
今までは新しめのPCRyzen3 4100が入ったPCを使って検証していたので
今度は古いPC(Corei7 3770K)を使って、マウスカーソルが描画されるかを見ていく写真の右側の奴

マウスカーソルが描画されない😩
原因はASUS P8Z77-Vのような古いIntel 7-series環境では、USB2/USB3のroot port routingとxHCI側の再検出処理が弱く、USBマウス本体が接続されているUSB2側ではなく、USB3側のportを叩き続けていた。
その結果、デバイスへアドレスを割り当てた後のdescriptor取得がtimeoutし、HIDマウスとして認識されず、OS側のマウス接続状態がConnectedにならなかった。
さらにUSB2側へ進めた後も、EP0 Control Transferの設定やdescriptor取得後の検証が不十分で、壊れたdescriptor headerとして扱われ、列挙が途中で止まっていた。
それでは修正していこう
行った修正
・古いIntel環境向けにUSB2/USB3のport routing処理を追加
・xHCI初期化後にroot portを再検出する処理を追加
・xHCIのprotocol情報を読み、各root portがUSB2側かUSB3側か判定するように変更
・USB3側でEP0 timeoutを繰り返すportを一定回数で失敗扱いし、USB2側の列挙へ進めるように変更
・port状態レジスタの書き込みを安全化し、状態bitやchange bitを壊さないように修正
・SuperSpeed系のreset処理で、reset完了や状態変化を待つように修正
・EP0 Control Transferの完了イベント判定を環境差に強くした
・EP0で失敗した場合に、endpoint停止、ring位置の巻き戻し、転送再開位置の再設定を行うように修正
・Control Transfer用TRBの不要な連結指定を外した
・descriptor取得を複数回リトライするように変更
・descriptor取得前に受信用bufferをゼロクリアするように変更
・descriptor headerが壊れていた場合でも、同じデバイス状態のまま取り直すように変更
・アドレス割り当て後、USB2/低速系デバイスでは少し待ってからdescriptor取得するように変更
・ring、デバイス管理領域、scratchpad、slot context、転送bufferを明示的にゼロ初期化するように変更
かなりてんこ盛りw
それでは順番に解説していく
古いIntel環境向けにUSB2/USB3のport routing処理を追加
これは古いIntel 7-series環境では、USB2/USB3のroutingが正しく切り替わらないと、USBマウス本体がいるUSB2側ではなくUSB3側を叩き続けてしまう。
そのため、USB2/USB3のroutingを明示的に制御し、USB2側のroot portへ正しく進めるようにした。
次にxHCI初期化後にroot portを再検出する処理を追加
xHCI初期化直後は、port状態がまだ安定していないことがある。
そこで初期化後にroot portを再検出し、起動時から刺さっているUSBマウスや、routing変更後に見えるようになったUSB2側portを拾えるようにした。
次にprotocol情報でUSB2/USB3を判定
単純なport番号や速度情報だけでは、古い環境でUSB2側かUSB3側かを誤判定しやすい。
そこでコントローラーが公開しているprotocol情報を読み、各root portがUSB2.0側なのかUSB3.0側なのかを判定するようにした。
次にUSB3側のtimeoutを失敗扱いできるように変更
USB3側で存在しない、または正しく応答しないportを叩き続けると、USB2側へ進めない。
そのため、EP0 timeoutを繰り返すportは一定回数で失敗扱いし、別の正しいUSB2側portの列挙へ進めるようにした。
次にport状態レジスタの書き込みを安全化
port状態レジスタには、通常の状態bitだけでなく、書き込み自体に意味を持つbitがある。
そのため、読んだ値をそのまま書き戻すのを避け、必要なbitだけを安全に書き込むようにして、port状態を壊さないようにした。
次にSuperSpeed系のreset処理を修正
SuperSpeed系のresetを即完了扱いにすると、portがまだ安定していない状態で次の処理へ進んでしまう。
そこでreset完了や状態変化を待つようにし、USB3側のreset処理を安定化した。
次にEP0 Control Transferの完了イベント判定を環境差に強くした
環境によって、EP0の完了イベントの見え方に差が出ることがある。
その差を吸収し、EP0 Control Transferの完了イベントを正しく拾えるようにした。
次にEP0 recoveryを強化
EP0でtimeoutやstallが起きた場合に、そのままring状態を引きずると次のdescriptor取得も失敗する。
そのため、endpoint停止、ring位置の巻き戻し、転送再開位置の再設定を行い、EP0を復旧して再試行できるようにした。
次にControl Transfer用TRBの不要な連結指定を外した
Control Transfer用TRBに不要な連結指定があると、controllerが転送列を意図と違う形で解釈する可能性がある。
不要な連結指定を外し、EP0 Control Transferを仕様に近い形で安定させた。
次にdescriptor取得を複数回リトライ
descriptor取得は、reset直後や古いcontrollerでは一度失敗することがある。
そこで一度の失敗で諦めず、複数回リトライするようにして、起動時接続やホットプラグ時の認識率を上げた。
次にdescriptor取得前にbufferをゼロクリア
bufferに古いデータが残っていると、失敗したdescriptor取得後に壊れたheaderを正しいものとして判定してしまう可能性がある。
取得前にbufferをゼロクリアし、古いデータ混入による誤判定を防いだ。
次にdescriptor headerが壊れていた場合は同じ状態のまま取り直す
descriptor headerが壊れていた場合でも、すぐにデバイス状態を破棄するのではなく、同じ状態のままdescriptorを取り直すようにした。
これにより、一時的な転送失敗で列挙全体が止まらないようにした。
次にアドレス割り当て後に少し待ってからdescriptor取得
USB2/低速系デバイスでは、アドレス割り当て直後にdescriptorを取りに行くと、デバイス側の準備が間に合わないことがある。
そのため少し待ってからdescriptor取得に進むようにし、古い環境や低速デバイスでの安定性を上げた。
次に各種bufferやcontextを明示的にゼロ初期化
ring、デバイス管理領域、scratchpad、slot context、転送bufferを明示的にゼロ初期化するようにした。
これにより、未初期化データや前回状態の残骸による謎の列挙失敗を防いだ。
それではマウスカーソルが描画されるか見ていこう!!
ただし
マウスカーソルが描画されない!!
原因はP8Z77-VみたいなIntel 7-series世代だと、USB3.0ポートはxHCI側で見えても、USB2.0ポートはEHCI側にぶら下がる構成だった。つまり、USB2.0ポートに刺したマウスはxHCIだけでは拾えないことだった。
まあ、いいだろう。少し前にEHCIドライバの骨組みだけは作ってた(xHCIで十分だろと思ってたからそこまで作ってない)奴があるからそれを使おう
それでは作っていこう
行った実装はこんな感じだ
・EHCIコントローラーの初期化処理を追加
・BIOSからOSへのEHCI ownership移行処理を追加
・EHCI用のDMA構造体を安全に確保、整列、ゼロ初期化
・Async Scheduleを固定Head方式に変更
・Control Transfer用の転送構造を毎回安全にlink/unlinkするように変更
・失敗した転送構造を即再利用しない方向に修正
・USB2.0 Hubのdescriptor取得、port power、port status取得を実装
・Hub配下portのresetと接続デバイス列挙を実装
・Full-Speed USBマウスをHub配下で扱えるようにSplit Transfer対応を追加
・HID Mouseとして認識したら、OS側のマウス接続状態をConnectedにするようにした
・抜き差し時に古い転送状態を引きずらないように安全化した
まずはEHCIコントローラーの初期化
EHCIコントローラーをPCIから検出し、MMIO領域を読み、EHCIのCapability情報とOperational registerを扱えるようにした。
その後、controller reset、USB2.0 portの有効化、scheduleの準備を行い、EHCI側でUSB2.0デバイスを扱える状態にした。
次にBIOS ownership移行
古いPCでは、起動直後にBIOSがEHCIを握っていることがある。
この状態のままOS側がEHCIを操作すると、port reset失敗、転送timeout、実機フリーズの原因になる。
そのため、BIOSからOSへEHCIの所有権を移し、OSが安全にEHCIを制御できるようにした。
次にDMA構造体の安全化
EHCIはQHやqTDなどの転送構造をDMAで読む。
そのため、これらを正しいalignmentで確保し、物理アドレスを使い、未初期化データが混ざらないようにゼロ初期化した。
これにより、実機だけで起こる謎の転送失敗やメモリ破壊を防ぐようにした。
次にAsync Scheduleを固定Head方式に変更
最初はControl TransferごとにAsync Scheduleの先頭を差し替える形だと不安定になりやすかった。
そこで、Async Scheduleには常駐する固定Headを置き、その下にControl Transfer用の転送構造をlink/unlinkする方式にした。
これにより、EHCI controllerが見るschedule構造が安定し、HubへのControl Transferが通るようになった。
次にControl Transferの安定化
USB列挙では、descriptor取得やHub status取得など、Control Transferが大量に使われる。
そこで、SETUP、DATA、STATUSの各段階を正しい順番とtoggleで組み、失敗時には転送構造を安全に外してから次の転送へ進むようにした。
これにより、失敗したqTDやQHの状態を次の転送に引きずらないようにした。
次にUSB2.0 Hub対応
USB2.0ポートにマウスを刺しても、実際には内蔵USB2.0 Hub配下に接続されていることがある。
そのため、Hub descriptorを取得し、Hub portへ電源を入れ、各portの状態を読み、接続されたportを見つける処理を実装した。
これにより、EHCIがHub本体で止まらず、その先のUSBマウス本体まで進めるようになった。
次にHub配下デバイスの列挙
Hub portにデバイス接続を検出した後、そのportをresetし、速度を判定し、descriptor取得、address割り当て、configuration取得へ進むようにした。
これにより、USB2.0 root port直下だけでなく、内蔵Hub配下のUSBマウスも認識できるようになった。
次にFull-Speedマウス対応
USB2.0ポートに刺したUSBマウスでも、マウス本体はFull-Speedデバイスのことが多い。
High-Speed Hub配下のFull-SpeedデバイスをEHCIで扱うには、HubのTransaction Translatorを通す必要がある。
そのため、Hub配下port番号や親Hub情報を転送設定に反映し、Full-Speed USBマウスのControl TransferとInterrupt Transferに進めるようにした。
次にHID Mouse認識
デバイスdescriptor、configuration、interface、endpoint情報を読み、HID Mouseとして判定できた場合、OS側のマウス接続状態をConnectedにするようにした。
これにより、デスクトップ側がUSBマウス接続を検知し、マウスカーソルを描画できるようになった。
次に抜き差し耐性の安全化
USBは抜き差し中に転送が残ったり、controller側から遅れて完了通知が来ることがある。
そのため、disconnect時に古いslot、転送状態、Hub port状態を引きずらないようにし、再接続時に新しい列挙として扱えるようにした。
なぜかマウスカーソルが描画されない。
クラッシュする
原因はEHCIはxHCIと違い、QHやqTDなどの転送構造をcontrollerが直接DMAで読み続ける。そのため、失敗した転送構造をすぐ再利用したり、Async Scheduleから安全に外す前に書き換えたりすると、controllerが古い状態を読んで転送失敗やメモリ破壊を起こしやすい。また、ASUS P8Z77-VではUSB2.0ポートの先にIntel内蔵USB2.0 Hubがあり、EHCIがHub本体を認識した後、そのHub配下portの状態取得で失敗していた。
なので、EHCI自体は起動していても、Hub配下のUSBマウス本体まで進めず、HID Mouse認識まで到達できていなかった。
実際に起こった症状はこんな感じだ
・USB2.0ポートにUSBマウスを刺してもカーソルが描画されない
・EHCIは内蔵USB2.0 Hubまでは認識するが、その先のport status取得で失敗する
・Hub status取得のControl Transferで転送エラーが出る
・失敗した転送状態を次の転送に引きずる
・USBを抜き差ししていると急に電源が落ちる、または再起動する
・USB3.0ポートではxHCIが動作するが、USB2.0ポートではEHCIが安定しない
・一時的にxHCI側まで動かなくなることもあった
それでは修正していこう
行った修正はこんな感じ
・EHCIのControl Transfer処理を見直し
・Async Scheduleを固定Head方式に変更
・転送用構造体を安全にlink/unlinkするように修正
・失敗した転送構造を即再利用しない方向に変更
・Async Scheduleから外した後、controllerが参照しなくなるまで待つように修正
・Hub status / port status取得処理を強化
・Hub port power、port reset、change bit clearの処理を整理
・Hub配下デバイスの速度判定と列挙処理を追加
・Full-SpeedマウスをHigh-Speed Hub配下で扱うための設定を追加
・抜き差し時に古い転送状態やデバイス状態を引きずらないように修正
・xHCIとEHCIが同時に存在しても、互いの状態を壊さないように整理
Control Transferの不安定さを修正
EHCIではdescriptor取得やHub status取得など、基本的な処理の多くがControl Transferで行われる。
最初はここが不安定で、Hubへport statusを取りに行く段階で転送エラーになっていた。
そこで、転送ごとのSETUP、DATA、STATUS段階を見直し、失敗した転送状態を次回に引きずらないようにした。
次にAsync Scheduleの扱いを修正
当初はControl TransferごとにAsync Scheduleの状態が不安定になりやすく、失敗したQHやqTDの状態を次の転送に引きずっていた。
そこで、Async Scheduleに固定Headを置き、その下に転送用の構造を接続し、完了または失敗後に安全に外す方式へ変更した。
これにより、controllerが参照している最中の構造を書き換えてしまう危険を減らした。
次にHub処理の不安定さを修正
USB2.0ポートに刺したマウスは、直接EHCI root portに見えるのではなく、内蔵USB2.0 Hub配下に見える構成だった。
そのため、Hub descriptor取得、Hub port power、Hub port status取得、Hub port reset、change bit clearを正しく行う必要があった。
ここを整理したことで、EHCIがHub本体で止まらず、その先のUSBマウス本体まで進めるようになった。
次にFull-Speedマウス対応を追加
USB2.0ポートに刺したマウスでも、マウス本体はFull-Speedデバイスのことが多い。
High-Speed Hub配下のFull-SpeedデバイスをEHCIで扱うには、Hub情報とport番号を転送設定に反映する必要がある。
この対応により、内蔵Hub配下のUSBマウスをdescriptor取得、address割り当て、HID認識まで進められるようになった。
次に抜き差し時のクラッシュ対策
USBデバイスを抜いた後でも、controller側では古い転送が残っていることがある。
その状態で転送構造やデバイス状態をすぐ再利用すると、controllerが古い物理アドレスを読んでメモリ破壊を起こす可能性がある。
そのため、disconnect時は古い転送やデバイス状態をすぐ使い回さず、安全に停止・切り離ししてから次の列挙へ進むようにした。
次にxHCIとの共存調整
途中でEHCI側の修正がxHCI側の動作にも影響し、一時的にUSB3.0ポート側のマウスカーソル描画まで失敗することがあった。
そのため、USB3.0ポートはxHCI、USB2.0ポートはEHCIという役割分担を崩さないようにし、互いの状態を不用意に消さないように調整した。
それでは修正と調整を行ったので、マウスカーソルが描画されるか見ていこう
マウスカーソルが描画された!!
これでUSB2.0とUSB3.0に対応したって言っていいね。
まあ、家にUSB3.0とUSB2.0のUSBハブが無かったので、USBハブの動作は確認出来てないので、次回以降に確認したい
現状のUSBドライバの動作はこんな感じ
xHCI → EHCI → xHCI
基本的にはxHCIドライバが優先して動作する USB2.0などxHCIが対応しきれないところにEHCIドライバを介入させている
これが自作OSにUSBドライバを入れて、マウスカーソルを描画できるようにした作業だった。
今後はUSBを刺したら音が出るようにしたり、UIをもっと綺麗にしていきたい
ということで以上miku_JK_Jbでした。
















