🖱

Rustでマウスドライバを実装する【MikanOS/day06C】

2023/04/07に公開

ようやく自作OS上でマウスカーソルが動かせるようになりました!

Cursor

初めに

RustでMikanOSに挑戦しています。
今回はDay06cの範囲を実装しました。
https://github.com/elm-register/mikanos-rs

ちなみに、MikanOSとは"ゼロからの OS 自作入門"という書籍上で実装するOSの名前です。
http://zero.osdev.jp/

day06cではマウスの動きを検知してカーソルの描画を行えるようにします。
マウスとの通信処理はMikanOSの作者が実装したマウスドライバを使うのですが、C++で書かれているためそのまま使うことができませんでした。
Rustで使えるようにビルドスクリプトを書いてもよかったのですが、折角なので自作することにしました。

ちなみに、実装に当たって基本的には以下の書籍を参考にし、それだけでは理解できない箇所は仕様書で補いました。
こちらの書籍は1000円以下とかなりリーズナブルな価格のため、ドライバ開発に興味のある方は是非ご購入ください。

https://booth.pm/ja/items/1056355

https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf

クラス図

全体

一部省略していますが、大体こんな感じです。
MikanOSのコードを参考にしていますが、結構構造が変わりました。

class_diagram

レジスタを取り扱う型にはトレイト境界を指定しています。

pub struct XhcController<Register, .., ..>
where
    Register: RegistersOperation
        + InterrupterSetRegisterAccessible
        + PortRegistersAccessible
        + DoorbellRegistersAccessible
        + 'static,
   
{
    registers: Rc<RefCell<Register>>,
    // ... その他省略
}

元々はレジスタのアクセス周りも自前で実装していましたが、途中で外部クレートを使用するようにしました。後々自前のものに差し替えられるようにレジスタ周りはトレイト化しています。(ちなみにまだ未対応です。)

Externalはxhciという外部クレートを使用した構造体です。
registers

https://github.com/rust-osdev/xhci

苦戦した点など

USBの仕様周りで苦戦した箇所などは下記スクラップにまとめているため、興味がある方は覗いてみてください。

https://zenn.dev/elm/scraps/e9e7bbbe2abfbe

メモリアロケータについて

スクラップには書いていないのですが、最初はグローバルアロケータを実装しないままドライバ開発をしようと思っていました。(メモリ管理はDay08で行うため)
そのためスマートポインタやVector等も使用できないのですが、特に問題になったのがRCのような参照カウント式のスマートポインタが使えないことです。
例えばxHCに通知を送るためにドアベルレジスタというものを使うのですが、これはコマンドリングやイベントリング、コントロールパイプなど数多くの構造体から必要とされます。
doorbell

途中までは以下のコードのように引数で受けるようにしていたのですが、メソッドが増える度に引数を書くのが面倒臭かったり、引数のバケツリレーになるのがつらかったため、グローバルアロケーターを実装することにしました。
アロケーターにはひとまずlinked-list-allocatorという外部クレートを使用することにしました。
自前での実装はday08で行う予定です。

pub struct CommandRingDummy{}

impl CommandRingDummy{
    pub fn push_enable_slot(&mut self, doorbell: &mut impl DoorbellRegisterAccessible){}

    pub fn push_noop(&mut self, doorbell: &mut impl DoorbellRegisterAccessible){}

    // メソッド1つ1つに引数を指定しないといけない!
    pub fn push_address(&mut self, doorbell: &mut impl DoorbellRegisterAccessible){}
}

https://github.com/rust-osdev/linked-list-allocator

あと、バッファの確保の際には専用のアロケータを実装して返されたアドレスから生ポインタにキャストしているのですが、Vectorが使えるためもっといいやり方があるかもしれません。余裕があったらここら辺も修正したいです。

pub trait MemoryAllocatable {

    unsafe fn allocate_with_align(
        &mut self,
        bytes: usize,
        align: usize,
        bounds: usize,
    ) -> Option<AlignedAddress>;
}

fn example_allocate(allocator: &mut impl MemoryAllocatable){
    let aligned_address: u64 = allocator
    .allocate_with_align(32 * size_of::<u128>(), 64, 4096)
    .unwrap()
    .address()
    .unwrap();

    let buffer = aligned_address as *mut u8;
}

単純にアラインするだけなら下のStackOverflowの回答のような実装でいけそうですが、アライン + メモリ境界の指定でメモリ確保しているためもう少し調べる必要がありそうです。(あるいはアラインを境界にする?)

https://stackoverflow.com/questions/60180121/how-do-i-allocate-a-vecu8-that-is-aligned-to-the-size-of-the-cache-line

Discussion