Rustで自作OS上でキーボード入力できるようにする【MikanOS/Day12】
Day12ではキーボード入力をするとウィンドウ上に入力文字が描画されるようにしました。
画像はDay14時点のもの
前回 Rustで自作OS上に簡易的なウィンドウを描画する
USBドライバをキーボードに対応
Day06で製作したUSBドライバをキーボードに対応させます。
複数デバイスの初期化
既に実装しているマウスと、今回実装するキーボードの両方に対応させたいのですが、複数デバイスデバイスの初期化に対応できていなかったため、まずはそこから対応する必要がありました。
ポートリセットからアドレスTBRの発行までは複数のポートで平行して行ってはならず、他のポートは待機する必要があるようです。
最初のポート初期化処理を変更し、先頭のポートだけをリセットして他のポートは待機用のキューにエントリーさせておくようにします。
pub fn reset_port(&mut self) -> PciResult {
let connect_ports = self
.registers
.borrow()
.connecting_ports();
if connect_ports.is_empty() {
return Ok(());
}
self.registers
.borrow_mut()
.reset_port_at(connect_ports[0])?;
// Since it is necessary to process one by one up to the address command,
// other ports are saved in the reserved area.
for port_id in connect_ports
.into_iter()
.skip(1)
{
self.waiting_ports
.push(port_id);
}
Ok(())
}
Address Commandに対応するCompletion Eventが発行されたらデバイスの初期化処理を行いますが、その直前に待機しているポートをリセットします。
fn init_device(&mut self, completion: CommandCompletion) -> PciResult {
self.reset_waiting_port_if_need()?;
self.device_manager
.start_initialize_at(completion.slot_id())?;
Ok(())
}
fn reset_waiting_port_if_need(&mut self) -> PciResult {
if let Some(port_id) = self.waiting_ports.pop() {
self.registers
.borrow_mut()
.reset_port_at(port_id)?;
}
Ok(())
}
これで複数デバイスの初期化に対応できました!
キーボードクラスドライバの作成
KeyboardSubscribable
はモディファイアキーとキーコードを引数に任意の処理を行うコールバックです。
KeyboardDriver
はデータバッファを保持しており、キーボード入力が検出されたときにバッファに書き込まれます。
検出される度にon_data_received
が呼び出され、そのメソッド内でKeyboardSubscribable
の処理を実行します。
キーボードサブスクライバ
pub(crate) type BoxedKeyboardSubscriber = Rc<dyn KeyboardSubscribable>;
pub const LEFT_CONTROL: u8 = 0b000_00001;
pub const LEFT_SHIFT: u8 = 0b0000_0010;
pub const LEFT_ALT: u8 = 0b0000_0100;
pub const LEFT_GUI: u8 = 0b0000_1000;
pub const RIGHT_CONTROL: u8 = 0b0001_0000;
pub const RIGHT_SHIFT: u8 = 0b0010_0000;
pub const RIGHT_ALT: u8 = 0b0100_0000;
pub const RIGHT_GUI: u8 = 0b1000_0000;
pub trait KeyboardSubscribable {
/// This Function is called whenever a keyboard action occurs.
///
/// ## ModifierBits
///
/// The modifier keys being pressed is represented by a 1 byte (8 bits).
/// There may be multiple of those keys, and each corresponding bit is set
/// to 1.
///
/// See below for the corresponding bit for each key.
///
/// - 0b0000_0001 = Left Control
/// - 0b0000_0010 = Left Right
/// - 0b0000_0100 = Left Alt
/// - 0b0000_1000 = Left Gui
/// - 0b0001_0000 = Right Control
/// - 0b0010_0000 = Right Shift
/// - 0b0100_0000 = Right Alt
/// - 0b1000_0000 = Right Gui
fn subscribe(&self, modifier_bits: u8, keycode: char);
}
impl<F> KeyboardSubscribable for F
where
F: Fn(u8, char),
{
fn subscribe(&self, modifier_bit: u8, keycode: char) {
self(modifier_bit, keycode)
}
}
キーボードドライバ
#[derive(Clone)]
pub struct KeyboardDriver {
prev_buf: [u8; 8],
data_buff: [u8; 8],
auto_upper: bool,
subscribe: BoxedKeyboardSubscriber,
}
impl KeyboardDriver {
pub(crate) fn new(auto_upper: bool, subscribe: BoxedKeyboardSubscriber) -> KeyboardDriver {
Self {
prev_buf: [0; 8],
data_buff: [0; 8],
auto_upper,
subscribe,
}
}
fn keycodes(&self) -> Vec<char> {
self.data_buff[2..]
.iter()
.filter(|key| !self.prev_buf[2..].contains(key))
.filter_map(|key| self.keycode(*key))
.collect()
}
fn keycode(&self, b: u8) -> Option<char> {
if self.auto_upper && self.pushing_shift() {
Keycode::new(b).upper_char()
} else {
Keycode::new(b).char()
}
}
fn pushing_shift(&self) -> bool {
(self.data_buff[0] & (LEFT_SHIFT | RIGHT_SHIFT)) != 0
}
}
impl ClassDriverOperate for KeyboardDriver {
fn on_data_received(&mut self) -> PciResult {
self.keycodes()
.iter()
.for_each(|key| {
self.subscribe
.subscribe(self.data_buff[0], *key);
});
self.prev_buf
.copy_from_slice(&self.data_buff);
Ok(())
}
fn data_buff_addr(&self) -> u64 {
self.data_buff.as_ptr() as u64
}
fn data_buff_len(&self) -> u32 {
8
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use crate::class_driver::keyboard::builder::Builder;
use crate::class_driver::keyboard::subscribe::{LEFT_SHIFT, RIGHT_SHIFT};
#[test]
fn it_keycodes() {
let mut keyboard = Builder::new().mock();
keyboard.data_buff = [0, 0, 0x04, 0x2F, 0, 0, 0, 0];
assert_eq!(keyboard.keycodes().len(), 2);
assert_eq!(keyboard.keycodes(), vec!['a', '{']);
}
#[test]
fn it_keycodes_upper_case() {
let mut keyboard = Builder::new()
.auto_upper_if_shift()
.mock();
keyboard.data_buff = [
LEFT_SHIFT | RIGHT_SHIFT,
0,
0x04,
0x2F,
0x05,
0,
0x06,
0,
];
assert_eq!(keyboard.keycodes().len(), 4);
assert_eq!(keyboard.keycodes(), vec!['A', '{', 'B', 'C']);
}
}
キーボードドライバは以下のテーブルに対応するインターフェースディスクリプタが存在する場合に生成されます。
プロパティ | 値 | 説明 |
---|---|---|
interface_class | 0x03 | HID |
interface_sub_class | 0x01 | ブートインターフェース |
interface_protocol | 0x01 | キーボード |
インターフェースディスクリプタ
#[derive(Debug, Clone)]
#[repr(packed)]
pub struct InterfaceDescriptor {
pub length: u8,
pub descriptor_type: u8,
pub interface_number: u8,
pub alternate_setting: u8,
pub num_endpoints: u8,
pub interface_class: u8,
pub interface_sub_class: u8,
pub interface_protocol: u8,
pub interface_id: u8,
}
pub const INTERFACE_DESCRIPTOR_TYPE: u8 = 4;
impl InterfaceDescriptor {
pub fn is_mouse(&self) -> bool {
self.interface_class == 3 && self.interface_sub_class == 1 && self.interface_protocol == 2
}
pub fn is_keyboard(&self) -> bool {
self.interface_class == 3 && self.interface_sub_class == 1 && self.interface_protocol == 1
}
}
class_driver
メソッドでクラスドライバが生成されます。
HIDデバイスディスクリプタ
pub struct HidDeviceDescriptors {
interface: InterfaceDescriptor,
endpoint: EndpointDescriptor,
}
impl HidDeviceDescriptors {
pub fn new(interface: InterfaceDescriptor, endpoint: EndpointDescriptor) -> Self {
Self {
interface,
endpoint,
}
}
pub fn class_driver(
&self,
mouse: &MouseDriver,
keyboard: &KeyboardDriver,
) -> Option<Box<dyn ClassDriverOperate>> {
if self.interface.is_mouse() {
return Some(Box::new(mouse.clone()));
}
if self.interface.is_keyboard() {
return Some(Box::new(keyboard.clone()));
}
None
}
pub fn interface(&self) -> InterfaceDescriptor {
self.interface.clone()
}
pub fn endpoint_config(&self) -> EndpointConfig {
EndpointConfig::new(&self.endpoint)
}
}
Interrupt Inエンドポイントの有効化
この実装はDay14で行いましたが、キーボードドライバの範囲になるためまとめて記載します。
本家によるxHCのイベントの処理の流れは以下になります。
- ユーザーがキーボード入力
- 割り込み発生
- イベントリングが空になるまでイベント処理
- 割り込みが発生するまで待機
しかしながら、現在のUSBドライバではNormalTRBの送信方法が本家のものとは異なり、デフォルトコントロールパイプからGET_REPORT(HIDのデバイスクラスリクエストの一種)を発行しています。この方法だと同期的にデータをやりとりするそうで、発行直後にイベントリングにイベントがエントリーされ、リングが空になりませんでした。
つまり、3.
の処理においてイベントリングが空になることがなく、無限ループになってしまいます。 そのため、非同期的にイベントを受け取るためにInterrupt Inエンドポイントをする必要があります。
ただ一つ問題がおきまして、有効化しNormalTRBを発行するところまでは実装しましたが、キーボードを押下してもイベントが来ませんでした。
仕様書のInterrupt Moderation
の項と同じく仕様書の428ページのEvent Handler Busy(EHB)
の説明から、イベントリングデキューポインタを書き込んだあとにEHBを0にクリアする必要があるとのことだったため、該当の処理を下記のように変更したところ正常に動作しました。
ただ、本家のドライバでは特にEHBの書き込みはしていなかったような気がするため、実は他に根本的な原因があるのかもしれません...。
fn write_event_ring_dequeue_pointer_at(
&mut self,
index: usize,
event_ring_segment_addr: u64,
) -> PciResult {
self.registers_mut()
.interrupter_register_set
.interrupter_mut(index)
.erdp
.update_volatile(|erdp| erdp.set_event_ring_dequeue_pointer(event_ring_segment_addr));
self.clear_event_handler_busy_at(index);
Ok(())
}
fn clear_event_handler_busy_at(&mut self, index: usize) {
self.0
.interrupter_register_set
.interrupter_mut(index)
.erdp
.update_volatile(|erdp| {
erdp.clear_event_handler_busy();
});
}
次回
次回(Day13,14)はタスクコンテキストスイッチを使い、プリエンプティブなマルチタスクを実現させます。(実装済み)
Discussion