diff --git a/.gitignore b/.gitignore index 260b90f8..a3a8e6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ -/target +target tags **/*.swp **/*.swo **/*~ *.temp *.lock + +test/build +OVMF_CODE.fd +OVMF_VARS.fd diff --git a/Cargo.toml b/Cargo.toml index ae6888c7..36e80599 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,3 @@ -[package] -name = "xhci" -version = "0.9.2" -authors = ["Hiroki Tokunaga "] -edition = "2021" -license = "MIT OR Apache-2.0" -description = "A library to handle xHCI" -repository = "https://github.com/rust-osdev/xhci" -readme = "README.md" -categories = ["no-std", "os"] -keywords = ["no_std", "OS"] - -[dependencies] -accessor = "0.3.0" -bit_field = "0.10.1" -num-derive = { version = "0.3.3", default-features = false } -num-traits = { version = "0.2.14", default-features = false } -paste = "1.0.4" +[workspace] +members = ["lib", "test"] +resolver = "2" diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 00000000..ae6888c7 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "xhci" +version = "0.9.2" +authors = ["Hiroki Tokunaga "] +edition = "2021" +license = "MIT OR Apache-2.0" +description = "A library to handle xHCI" +repository = "https://github.com/rust-osdev/xhci" +readme = "README.md" +categories = ["no-std", "os"] +keywords = ["no_std", "OS"] + +[dependencies] +accessor = "0.3.0" +bit_field = "0.10.1" +num-derive = { version = "0.3.3", default-features = false } +num-traits = { version = "0.2.14", default-features = false } +paste = "1.0.4" diff --git a/src/context/macros.rs b/lib/src/context/macros.rs similarity index 100% rename from src/context/macros.rs rename to lib/src/context/macros.rs diff --git a/src/context/mod.rs b/lib/src/context/mod.rs similarity index 100% rename from src/context/mod.rs rename to lib/src/context/mod.rs diff --git a/src/extended_capabilities/debug.rs b/lib/src/extended_capabilities/debug.rs similarity index 100% rename from src/extended_capabilities/debug.rs rename to lib/src/extended_capabilities/debug.rs diff --git a/src/extended_capabilities/hci_extended_power_management.rs b/lib/src/extended_capabilities/hci_extended_power_management.rs similarity index 100% rename from src/extended_capabilities/hci_extended_power_management.rs rename to lib/src/extended_capabilities/hci_extended_power_management.rs diff --git a/src/extended_capabilities/mod.rs b/lib/src/extended_capabilities/mod.rs similarity index 100% rename from src/extended_capabilities/mod.rs rename to lib/src/extended_capabilities/mod.rs diff --git a/src/extended_capabilities/usb_legacy_support_capability.rs b/lib/src/extended_capabilities/usb_legacy_support_capability.rs similarity index 100% rename from src/extended_capabilities/usb_legacy_support_capability.rs rename to lib/src/extended_capabilities/usb_legacy_support_capability.rs diff --git a/src/extended_capabilities/xhci_extended_message_interrupt.rs b/lib/src/extended_capabilities/xhci_extended_message_interrupt.rs similarity index 100% rename from src/extended_capabilities/xhci_extended_message_interrupt.rs rename to lib/src/extended_capabilities/xhci_extended_message_interrupt.rs diff --git a/src/extended_capabilities/xhci_local_memory.rs b/lib/src/extended_capabilities/xhci_local_memory.rs similarity index 100% rename from src/extended_capabilities/xhci_local_memory.rs rename to lib/src/extended_capabilities/xhci_local_memory.rs diff --git a/src/extended_capabilities/xhci_message_interrupt.rs b/lib/src/extended_capabilities/xhci_message_interrupt.rs similarity index 100% rename from src/extended_capabilities/xhci_message_interrupt.rs rename to lib/src/extended_capabilities/xhci_message_interrupt.rs diff --git a/src/extended_capabilities/xhci_supported_protocol.rs b/lib/src/extended_capabilities/xhci_supported_protocol.rs similarity index 100% rename from src/extended_capabilities/xhci_supported_protocol.rs rename to lib/src/extended_capabilities/xhci_supported_protocol.rs diff --git a/src/lib.rs b/lib/src/lib.rs similarity index 100% rename from src/lib.rs rename to lib/src/lib.rs diff --git a/src/macros.rs b/lib/src/macros.rs similarity index 100% rename from src/macros.rs rename to lib/src/macros.rs diff --git a/src/registers/capability.rs b/lib/src/registers/capability.rs similarity index 100% rename from src/registers/capability.rs rename to lib/src/registers/capability.rs diff --git a/src/registers/doorbell.rs b/lib/src/registers/doorbell.rs similarity index 100% rename from src/registers/doorbell.rs rename to lib/src/registers/doorbell.rs diff --git a/src/registers/mod.rs b/lib/src/registers/mod.rs similarity index 100% rename from src/registers/mod.rs rename to lib/src/registers/mod.rs diff --git a/src/registers/operational.rs b/lib/src/registers/operational.rs similarity index 100% rename from src/registers/operational.rs rename to lib/src/registers/operational.rs diff --git a/src/registers/runtime.rs b/lib/src/registers/runtime.rs similarity index 100% rename from src/registers/runtime.rs rename to lib/src/registers/runtime.rs diff --git a/src/ring/mod.rs b/lib/src/ring/mod.rs similarity index 100% rename from src/ring/mod.rs rename to lib/src/ring/mod.rs diff --git a/src/ring/trb/command.rs b/lib/src/ring/trb/command.rs similarity index 100% rename from src/ring/trb/command.rs rename to lib/src/ring/trb/command.rs diff --git a/src/ring/trb/event.rs b/lib/src/ring/trb/event.rs similarity index 100% rename from src/ring/trb/event.rs rename to lib/src/ring/trb/event.rs diff --git a/src/ring/trb/mod.rs b/lib/src/ring/trb/mod.rs similarity index 100% rename from src/ring/trb/mod.rs rename to lib/src/ring/trb/mod.rs diff --git a/src/ring/trb/transfer.rs b/lib/src/ring/trb/transfer.rs similarity index 100% rename from src/ring/trb/transfer.rs rename to lib/src/ring/trb/transfer.rs diff --git a/test/.cargo/config.toml b/test/.cargo/config.toml new file mode 100644 index 00000000..85d49dcc --- /dev/null +++ b/test/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "x86_64-unknown-uefi" diff --git a/test/Cargo.toml b/test/Cargo.toml new file mode 100644 index 00000000..ef2c91d9 --- /dev/null +++ b/test/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "xhci-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +accessor = "0.3.0" +bit_field = "0.10.1" +byteorder = { version = "1.4.3", default-features = false } +conquer-once = { version = "0.3.2", default-features = false } +crossbeam-queue = { version = "0.3.1", features = [ + "alloc", +], default-features = false } +derive_builder = { version = "0.10.2", default-features = false } +futures-intrusive = { version = "0.4.0", features = [ + "alloc", +], default-features = false } +futures-util = { version = "0.3.15", features = [ + "alloc", +], default-features = false } +linked_list_allocator = "0.10.5" +log = "0.4.14" +num-derive = "0.3.3" +num-traits = { version = "0.2.14", default-features = false } +os_units = "0.4.0" +qemu-exit = "3.0.2" +qemu_print = { version = "0.1.0", default-features = false, features = ["stable"] } +spinning_top = { version = "0.2.4", features = ["nightly"] } +uefi = "0.26.0" +x86_64 = { version = "0.14.11", default-features = false } +xhci = { version = "0.9.2", path = "../lib" } diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..68efb1e3 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,42 @@ +IMG = build/os.img +EFI = ../target/x86_64-unknown-uefi/debug/xhci-test.efi + +.PHONY: test clean + +all: $(IMG) + +test: $(IMG) + qemu-system-x86_64 \ + -drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \ + -drive if=pflash,format=raw,readonly=on,file=OVMF_VARS.fd \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ + -device qemu-xhci,id=xhci\ + -device usb-mouse \ + -serial stdio \ + -no-reboot \ + -display none \ + -drive format=raw,file=$(IMG); \ + if [ $$? -eq 33 ];\ + then\ + echo "Test passed";\ + else\ + echo "Test failed";\ + exit 1;\ + fi + +$(IMG): $(EFI) build + dd if=/dev/zero of=$@ bs=512 count=93750 + mformat -i $@ -h 200 -t 500 -s 144:: + mmd -i $@ ::/efi + mmd -i $@ ::/efi/boot + mcopy -i $@ $(EFI) ::/efi/boot/bootx64.efi + +$(EFI): $(shell find src/) Cargo.toml + cargo build + +build: + mkdir build + +clean: + rm -rf build + cargo clean diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..b8d0bfaa --- /dev/null +++ b/test/README.md @@ -0,0 +1,22 @@ +# Test program for the `xhci` crate + +## What is this? + +This is a tiny program to verify that the `xhci` crate defines data structures, register accessors, etc. correctly, and to demostrate how to use the crate. It is a single UEFI binary, and when it runs, it does the following things: + +- Find the xHCI controller. +- Initialize the controller. +- Enumerate and initialize all ports to which a device is connected. + +This program does not interact with each device because it is beyond the scope of this crate. + +You can use it as a reference for your own implementation, but note the following points: + +- While this program is a single UEFI binary, usually a UEFI binary is used as a bootloader, and interacting with the xHCI controller is done by the OS kernel. +- This program depends on the identity-mapping that is set up by the UEFI firmware, and thus, it uses Rust pointers as physical addresses directly. + +This program is designed based on [eXtensible Host Controller Interface for Universal Serial Bus (xHCI) Requirements Specification May 2019 Revision 1.2](https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf). In the source code, "xHCI spec" refers to this document. + +## Running this program + +Just run `make test` in this directory. diff --git a/test/src/allocator.rs b/test/src/allocator.rs new file mode 100644 index 00000000..a3759b70 --- /dev/null +++ b/test/src/allocator.rs @@ -0,0 +1,32 @@ +use linked_list_allocator::LockedHeap; +use uefi::table::boot; + +#[global_allocator] +static mut ALLOCATOR: LockedHeap = LockedHeap::empty(); + +pub fn init(memory_map: boot::MemoryMap) { + let largest_chunk = find_largest_conventional_memory_chunk(memory_map); + + let heap_start = largest_chunk.phys_start; + let heap_end = heap_start + largest_chunk.page_count * 4096; + let heap_size = heap_end - heap_start; + + unsafe { + ALLOCATOR + .lock() + .init(heap_start as *mut _, heap_size.try_into().unwrap()); + } +} + +fn find_largest_conventional_memory_chunk(memory_map: boot::MemoryMap) -> boot::MemoryDescriptor { + // Only EfiConventionalMemory which is usable both before and after exiting + // the boot service is used because dealing with additional memory types + // involves unnecessary complexity, given the presence of stack space in + // EfiBootServicesData (confirmed by manual testing) and UEFI binaries in + // EfiLoaderData (as specified in UEFI version 2.10, Table 7.6.) + *memory_map + .entries() + .filter(|x| x.ty == boot::MemoryType::CONVENTIONAL) + .max_by_key(|x| x.page_count) + .expect("No conventional memory found") +} diff --git a/test/src/exchanger/command.rs b/test/src/exchanger/command.rs new file mode 100644 index 00000000..0835856d --- /dev/null +++ b/test/src/exchanger/command.rs @@ -0,0 +1,133 @@ +use super::{ + super::structures::ring::command, + receiver::{self, ReceiveFuture}, +}; +use crate::{Futurelock, FuturelockGuard}; +use alloc::sync::Arc; +use command_trb::{AddressDevice, ConfigureEndpoint, EnableSlot, EvaluateContext}; +use conquer_once::spin::OnceCell; +use event::CompletionCode; +use futures_util::task::AtomicWaker; +use spinning_top::Spinlock; +use x86_64::PhysAddr; +use xhci::ring::trb::{command as command_trb, event}; + +static SENDER: OnceCell> = OnceCell::uninit(); + +pub(crate) fn init() { + let ring = Arc::new(Spinlock::new(command::Ring::new())); + + ring.lock().init(); + + SENDER + .try_init_once(|| Futurelock::new(Sender::new(ring), true)) + .expect("`Sender` is initialized more than once.") +} + +pub(crate) async fn enable_device_slot() -> u8 { + lock().await.enable_device_slot().await +} + +pub(crate) async fn address_device(input_cx: PhysAddr, slot: u8) { + lock().await.address_device(input_cx, slot).await; +} + +pub(crate) async fn configure_endpoint(cx: PhysAddr, slot: u8) { + lock().await.configure_endpoint(cx, slot).await; +} + +pub(crate) async fn evaluate_context(cx: PhysAddr, slot: u8) { + lock().await.evaluate_context(cx, slot).await; +} + +async fn lock() -> FuturelockGuard<'static, Sender> { + let s = SENDER.try_get().expect("`SENDER` is not initialized."); + s.lock().await +} + +struct Sender { + channel: Channel, +} +impl Sender { + fn new(ring: Arc>) -> Self { + Self { + channel: Channel::new(ring), + } + } + + async fn enable_device_slot(&mut self) -> u8 { + let t = EnableSlot::default(); + let completion = self.send_and_receive(t.into()).await; + panic_on_error("Enable Device Slot", completion); + if let event::Allowed::CommandCompletion(c) = completion { + c.slot_id() + } else { + unreachable!() + } + } + + async fn address_device(&mut self, input_context_addr: PhysAddr, slot_id: u8) { + let t = *AddressDevice::default() + .set_input_context_pointer(input_context_addr.as_u64()) + .set_slot_id(slot_id); + let c = self.send_and_receive(t.into()).await; + panic_on_error("Address Device", c); + } + + async fn configure_endpoint(&mut self, context_addr: PhysAddr, slot_id: u8) { + let t = *ConfigureEndpoint::default() + .set_input_context_pointer(context_addr.as_u64()) + .set_slot_id(slot_id); + let c = self.send_and_receive(t.into()).await; + panic_on_error("Configure Endpoint", c); + } + + async fn evaluate_context(&mut self, cx: PhysAddr, slot: u8) { + let t = *EvaluateContext::default() + .set_input_context_pointer(cx.as_u64()) + .set_slot_id(slot); + let c = self.send_and_receive(t.into()).await; + panic_on_error("Evaluate Context", c); + } + + async fn send_and_receive(&mut self, t: command_trb::Allowed) -> event::Allowed { + self.channel.send_and_receive(t).await + } +} + +struct Channel { + ring: Arc>, + waker: Arc>, +} +impl Channel { + fn new(ring: Arc>) -> Self { + Self { + ring, + waker: Arc::new(Spinlock::new(AtomicWaker::new())), + } + } + + async fn send_and_receive(&mut self, t: command_trb::Allowed) -> event::Allowed { + let a = self.ring.lock().enqueue(t); + self.register_with_receiver(a); + self.get_trb(a).await + } + + fn register_with_receiver(&mut self, trb_a: PhysAddr) { + receiver::add_entry(trb_a, self.waker.clone()).expect("Sender is already registered."); + } + + async fn get_trb(&mut self, trb_a: PhysAddr) -> event::Allowed { + ReceiveFuture::new(trb_a, self.waker.clone()).await + } +} + +fn panic_on_error(n: &str, c: event::Allowed) { + if let event::Allowed::CommandCompletion(c) = c { + if c.completion_code() != Ok(CompletionCode::Success) { + panic!("{} command failed: {:?}", n, c.completion_code()); + } + } else { + unreachable!("The Command Completion TRB is the only TRB to receive in response to the Command TRBs.") + } +} diff --git a/test/src/exchanger/mod.rs b/test/src/exchanger/mod.rs new file mode 100644 index 00000000..db6d4e16 --- /dev/null +++ b/test/src/exchanger/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod command; +pub(crate) mod receiver; +pub(crate) mod transfer; diff --git a/test/src/exchanger/receiver.rs b/test/src/exchanger/receiver.rs new file mode 100644 index 00000000..6b1283a3 --- /dev/null +++ b/test/src/exchanger/receiver.rs @@ -0,0 +1,142 @@ +use alloc::{collections::BTreeMap, sync::Arc}; +use conquer_once::spin::Lazy; +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use futures_util::task::AtomicWaker; +use spinning_top::{Spinlock, SpinlockGuard}; +use x86_64::PhysAddr; +use xhci::ring::trb::event; + +static RECEIVER: Lazy> = Lazy::new(|| Spinlock::new(Receiver::new())); + +pub(crate) fn add_entry(trb_a: PhysAddr, waker: Arc>) -> Result<(), Error> { + lock().add_entry(trb_a, waker) +} + +pub(crate) fn receive(t: event::Allowed) { + lock().receive(t) +} + +fn lock() -> SpinlockGuard<'static, Receiver> { + RECEIVER + .try_lock() + .expect("Failed to acquire the lock of `RECEIVER`.") +} + +struct Receiver { + trbs: BTreeMap>, + wakers: BTreeMap>>, +} +impl Receiver { + fn new() -> Self { + Self { + trbs: BTreeMap::new(), + wakers: BTreeMap::new(), + } + } + + fn add_entry( + &mut self, + addr_to_trb: PhysAddr, + waker: Arc>, + ) -> Result<(), Error> { + if self.trbs.insert(addr_to_trb, None).is_some() { + return Err(Error::AddrAlreadyRegistered); + } + + if self.wakers.insert(addr_to_trb, waker).is_some() { + return Err(Error::AddrAlreadyRegistered); + } + Ok(()) + } + + fn receive(&mut self, trb: event::Allowed) { + if let Err(e) = self.insert_trb_and_wake_runner(trb) { + panic!("Failed to receive a command completion trb: {:?}", e); + } + } + + fn insert_trb_and_wake_runner(&mut self, trb: event::Allowed) -> Result<(), Error> { + let addr_to_trb = Self::trb_addr(trb); + self.insert_trb(trb)?; + self.wake_runner(addr_to_trb)?; + Ok(()) + } + + fn insert_trb(&mut self, trb: event::Allowed) -> Result<(), Error> { + let addr_to_trb = Self::trb_addr(trb); + *self + .trbs + .get_mut(&addr_to_trb) + .ok_or(Error::NoSuchAddress)? = Some(trb); + Ok(()) + } + + fn wake_runner(&mut self, addr_to_trb: PhysAddr) -> Result<(), Error> { + self.wakers + .remove(&addr_to_trb) + .ok_or(Error::NoSuchAddress)? + .lock() + .wake(); + Ok(()) + } + + fn trb_arrives(&self, addr_to_trb: PhysAddr) -> bool { + match self.trbs.get(&addr_to_trb) { + Some(trb) => trb.is_some(), + None => panic!("No such TRB with the address {:?}", addr_to_trb), + } + } + + fn remove_entry(&mut self, addr_to_trb: PhysAddr) -> Option { + match self.trbs.remove(&addr_to_trb) { + Some(trb) => trb, + None => panic!("No such receiver with TRB address: {:?}", addr_to_trb), + } + } + + fn trb_addr(t: event::Allowed) -> PhysAddr { + PhysAddr::new(match t { + event::Allowed::TransferEvent(e) => e.trb_pointer(), + event::Allowed::CommandCompletion(c) => c.command_trb_pointer(), + _ => todo!(), + }) + } +} + +#[derive(Debug)] +pub(crate) enum Error { + AddrAlreadyRegistered, + NoSuchAddress, +} + +pub(crate) struct ReceiveFuture { + addr_to_trb: PhysAddr, + waker: Arc>, +} +impl ReceiveFuture { + pub(crate) fn new(addr_to_trb: PhysAddr, waker: Arc>) -> Self { + Self { addr_to_trb, waker } + } +} +impl Future for ReceiveFuture { + type Output = event::Allowed; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let waker = self.waker.clone(); + let addr = self.addr_to_trb; + let mut r = lock(); + + waker.lock().register(cx.waker()); + if r.trb_arrives(addr) { + waker.lock().take(); + let trb = r.remove_entry(addr).unwrap(); + Poll::Ready(trb) + } else { + Poll::Pending + } + } +} diff --git a/test/src/exchanger/transfer.rs b/test/src/exchanger/transfer.rs new file mode 100644 index 00000000..73ebc9fe --- /dev/null +++ b/test/src/exchanger/transfer.rs @@ -0,0 +1,256 @@ +use super::receiver::{self, ReceiveFuture}; +use crate::page_box::PageBox; +use crate::structures::{descriptor, registers, ring::transfer}; +use alloc::{sync::Arc, vec::Vec}; +use core::convert::TryInto; +use futures_util::task::AtomicWaker; +use log::debug; +use spinning_top::Spinlock; +use x86_64::PhysAddr; +use xhci::ring::trb::{ + event, transfer as transfer_trb, + transfer::{Direction, Noop, Normal, TransferType}, +}; + +pub(crate) struct Sender { + channel: Channel, +} +impl Sender { + pub(crate) fn new(doorbell_writer: DoorbellWriter) -> Self { + Self { + channel: Channel::new(doorbell_writer), + } + } + + pub(crate) fn ring_addr(&self) -> PhysAddr { + self.channel.ring_addr() + } + + pub(crate) async fn get_max_packet_size_from_device_descriptor(&mut self) -> u16 { + let b = PageBox::from(descriptor::Device::default()); + + let setup = *transfer_trb::SetupStage::default() + .set_transfer_type(TransferType::In) + .clear_interrupt_on_completion() + .set_request_type(0x80) + .set_request(6) + .set_value(0x0100) + .set_length(8); + + let data = *transfer_trb::DataStage::default() + .set_direction(Direction::In) + .set_trb_transfer_length(8) + .clear_interrupt_on_completion() + .set_data_buffer_pointer(b.phys_addr().as_u64()); + + let status = *transfer_trb::StatusStage::default().set_interrupt_on_completion(); + + self.issue_trbs(&[setup.into(), data.into(), status.into()]) + .await; + + b.max_packet_size() + } + + pub(crate) async fn set_configure(&mut self, config_val: u8) { + let setup = *transfer_trb::SetupStage::default() + .set_transfer_type(TransferType::No) + .clear_interrupt_on_completion() + .set_request_type(0) + .set_request(9) + .set_value(config_val.into()) + .set_length(0); + + let status = *transfer_trb::StatusStage::default().set_interrupt_on_completion(); + + self.issue_trbs(&[setup.into(), status.into()]).await; + } + + pub(crate) async fn set_idle(&mut self) { + let setup = *transfer_trb::SetupStage::default() + .set_transfer_type(TransferType::No) + .clear_interrupt_on_completion() + .set_request_type(0x21) + .set_request(0x0a) + .set_value(0) + .set_length(0); + + let status = *transfer_trb::StatusStage::default().set_interrupt_on_completion(); + + self.issue_trbs(&[setup.into(), status.into()]).await; + } + + pub(crate) async fn set_boot_protocol(&mut self) { + let setup = *transfer_trb::SetupStage::default() + .set_transfer_type(TransferType::No) + .clear_interrupt_on_completion() + .set_request_type(0b0010_0001) + .set_request(0x0b) + .set_value(0) + .set_length(0); + + let status = *transfer_trb::StatusStage::default().set_interrupt_on_completion(); + + self.issue_trbs(&[setup.into(), status.into()]).await; + } + + pub(crate) async fn get_configuration_descriptor(&mut self) -> PageBox<[u8]> { + let b = PageBox::new_slice(0, 4096); + + let (setup, data, status) = Self::trbs_for_getting_descriptors( + &b, + DescTyIdx::new(descriptor::Ty::Configuration, 0), + ); + + self.issue_trbs(&[setup, data, status]).await; + debug!("Got TRBs"); + b + } + + pub(crate) async fn issue_normal_trb(&mut self, b: &PageBox) { + let t = *Normal::default() + .set_data_buffer_pointer(b.phys_addr().as_u64()) + .set_trb_transfer_length(b.bytes().as_usize().try_into().unwrap()) + .set_interrupt_on_completion(); + debug!("Normal TRB: {:X?}", t); + self.issue_trbs(&[t.into()]).await; + } + + pub(crate) async fn issue_nop_trb(&mut self) { + let t = Noop::default(); + + self.issue_trbs(&[t.into()]).await; + } + + fn trbs_for_getting_descriptors( + b: &PageBox, + t: DescTyIdx, + ) -> ( + transfer_trb::Allowed, + transfer_trb::Allowed, + transfer_trb::Allowed, + ) { + let setup = *transfer_trb::SetupStage::default() + .set_request_type(0b1000_0000) + .set_request(Request::GetDescriptor as u8) + .set_value(t.bits()) + .set_length(b.bytes().as_usize().try_into().unwrap()) + .set_transfer_type(TransferType::In); + + let data = *transfer_trb::DataStage::default() + .set_data_buffer_pointer(b.phys_addr().as_u64()) + .set_trb_transfer_length(b.bytes().as_usize().try_into().unwrap()) + .set_direction(Direction::In); + + let status = *transfer_trb::StatusStage::default().set_interrupt_on_completion(); + + (setup.into(), data.into(), status.into()) + } + + async fn issue_trbs(&mut self, ts: &[transfer_trb::Allowed]) -> Vec> { + self.channel.send_and_receive(ts).await + } +} + +struct Channel { + ring: transfer::Ring, + doorbell_writer: DoorbellWriter, + waker: Arc>, +} +impl Channel { + fn new(doorbell_writer: DoorbellWriter) -> Self { + Self { + ring: transfer::Ring::new(), + doorbell_writer, + waker: Arc::new(Spinlock::new(AtomicWaker::new())), + } + } + + fn ring_addr(&self) -> PhysAddr { + self.ring.phys_addr() + } + + async fn send_and_receive( + &mut self, + trbs: &[transfer_trb::Allowed], + ) -> Vec> { + let addrs = self.ring.enqueue(trbs); + self.register_with_receiver(trbs, &addrs); + self.write_to_doorbell(); + self.get_trbs(trbs, &addrs).await + } + + fn register_with_receiver(&mut self, ts: &[transfer_trb::Allowed], addrs: &[PhysAddr]) { + for (t, addr) in ts.iter().zip(addrs) { + self.register_trb(t, *addr); + } + } + + fn register_trb(&mut self, t: &transfer_trb::Allowed, a: PhysAddr) { + if t.interrupt_on_completion() { + receiver::add_entry(a, self.waker.clone()).expect("Sender is already registered."); + } + } + + fn write_to_doorbell(&mut self) { + self.doorbell_writer.write(); + } + + async fn get_trbs( + &mut self, + ts: &[transfer_trb::Allowed], + addrs: &[PhysAddr], + ) -> Vec> { + let mut v = Vec::new(); + for (t, a) in ts.iter().zip(addrs) { + v.push(self.get_single_trb(t, *a).await); + } + v + } + + async fn get_single_trb( + &mut self, + t: &transfer_trb::Allowed, + addr: PhysAddr, + ) -> Option { + if t.interrupt_on_completion() { + Some(ReceiveFuture::new(addr, self.waker.clone()).await) + } else { + None + } + } +} + +pub(crate) struct DoorbellWriter { + slot_id: u8, + val: u32, +} +impl DoorbellWriter { + pub(crate) fn new(slot_id: u8, val: u32) -> Self { + Self { slot_id, val } + } + + pub(crate) fn write(&mut self) { + registers::handle(|r| { + r.doorbell.update_volatile_at(self.slot_id.into(), |d| { + d.set_doorbell_target(self.val.try_into().unwrap()); + }) + }); + } +} + +pub(crate) struct DescTyIdx { + ty: descriptor::Ty, + i: u8, +} +impl DescTyIdx { + pub(crate) fn new(ty: descriptor::Ty, i: u8) -> Self { + Self { ty, i } + } + pub(crate) fn bits(self) -> u16 { + (self.ty as u16) << 8 | u16::from(self.i) + } +} + +enum Request { + GetDescriptor = 6, +} diff --git a/test/src/logger.rs b/test/src/logger.rs new file mode 100644 index 00000000..fd469084 --- /dev/null +++ b/test/src/logger.rs @@ -0,0 +1,20 @@ +use qemu_print::qemu_println; + +static LOGGER: MyLogger = MyLogger; + +struct MyLogger; +impl log::Log for MyLogger { + fn enabled(&self, _: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + qemu_println!("[{}] {}", record.level(), record.args()); + } + + fn flush(&self) {} +} + +pub fn init() { + log::set_logger(&LOGGER).unwrap(); +} diff --git a/test/src/main.rs b/test/src/main.rs new file mode 100644 index 00000000..31927c1f --- /dev/null +++ b/test/src/main.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] +// A workaround for the `derive_builder` crate. +#![allow(clippy::default_trait_access)] + +extern crate alloc; + +use futures_intrusive::sync::{GenericMutex, GenericMutexGuard}; +use multitask::{executor::Executor, task::Task}; +use pci::config::bar; +use qemu_exit::QEMUExit; +use qemu_print::qemu_println; +use spinning_top::RawSpinlock; +use structures::{extended_capabilities, registers, ring::event}; +use uefi::{ + table::{boot::MemoryType, Boot, SystemTable}, + Handle, +}; +use x86_64::PhysAddr; + +pub(crate) type Futurelock = GenericMutex; +pub(crate) type FuturelockGuard<'a, T> = GenericMutexGuard<'a, RawSpinlock, T>; + +mod allocator; +mod exchanger; +mod logger; +mod mapper; +mod multitask; +mod page_box; +mod pci; +mod port; +mod structures; +mod xhc; + +#[uefi::entry] +fn main(h: Handle, st: SystemTable) -> uefi::Status { + let (_, mmap) = st.exit_boot_services(MemoryType::LOADER_DATA); + + logger::init(); + allocator::init(mmap); + + assert!(xhc::exists(), "xHC does not exist."); + + init(); + + let mut executor = Executor::new(); + executor.run(); +} + +pub(crate) fn init() { + init_and_spawn_tasks(); +} + +fn init_statics() { + let a = iter_xhc().next().expect("xHC does not exist."); + + // SAFETY: BAR 0 address is passed. + unsafe { + registers::init(a); + extended_capabilities::init(a); + } +} + +fn init_and_spawn_tasks() { + init_statics(); + + // In some cases, an OS may need to get ownership of the xHC from the BIOS. + // See 4.22.1 of xHCI spec. + // + // This is not necessary on QEMU, but this line is left for a reference. + xhc::get_ownership_from_bios(); + + xhc::init(); + + spawn_tasks(); +} + +fn spawn_tasks() { + port::spawn_all_connected_port_tasks(); + + multitask::add(Task::new_poll(event::task())); +} + +fn iter_xhc() -> impl Iterator { + pci::iter_devices().filter_map(|device| { + if device.is_xhci() { + Some(device.base_address(bar::Index::new(0))) + } else { + None + } + }) +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let handler = qemu_exit::X86::new(0xf4, 33); + + qemu_println!("{}", info); + + handler.exit_failure(); +} diff --git a/test/src/mapper.rs b/test/src/mapper.rs new file mode 100644 index 00000000..b5f3cd5f --- /dev/null +++ b/test/src/mapper.rs @@ -0,0 +1,12 @@ +use core::num::NonZeroUsize; + +#[derive(Clone, Copy, Debug)] +pub struct Mapper; +impl xhci::accessor::Mapper for Mapper { + // UEFI sets up the identity mapping, so we don't need to do anything here. + unsafe fn map(&mut self, physical_address: usize, _: usize) -> NonZeroUsize { + NonZeroUsize::new(physical_address).expect("physical_address is zero") + } + + fn unmap(&mut self, _: usize, _: usize) {} +} diff --git a/test/src/multitask/executor.rs b/test/src/multitask/executor.rs new file mode 100644 index 00000000..7cf2a87c --- /dev/null +++ b/test/src/multitask/executor.rs @@ -0,0 +1,79 @@ +// The MIT License (MIT) +// +// Copyright (c) 2019 Philipp Oppermann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use super::task; +use alloc::collections::BTreeMap; +use core::task::{Context, Poll, Waker}; +use task::Task; + +pub(crate) struct Executor { + waker_collection: BTreeMap, +} + +impl Executor { + pub(crate) fn new() -> Self { + Self { + waker_collection: BTreeMap::new(), + } + } + + pub(crate) fn run(&mut self) -> ! { + loop { + self.run_woken_tasks(); + } + } + + fn run_woken_tasks(&mut self) { + while let Some(id) = Self::pop_woken_task_id() { + self.run_task(id); + } + } + + fn pop_woken_task_id() -> Option { + task::COLLECTION.lock().pop_woken_task_id() + } + + fn run_task(&mut self, id: task::Id) { + let Self { + waker_collection: _, + } = self; + + let mut task = match task::COLLECTION.lock().remove_task(id) { + Some(task) => task, + None => return, + }; + + let mut context = self.generate_waker(id); + match task.poll(&mut context) { + Poll::Ready(_) => { + task::COLLECTION.lock().remove_task(id); + self.waker_collection.remove(&id); + } + Poll::Pending => Self::add_task_as_pending(task), + } + } + + fn generate_waker(&mut self, id: task::Id) -> Context<'_> { + let Self { waker_collection } = self; + + let waker = waker_collection + .entry(id) + .or_insert_with(|| task::COLLECTION.lock().create_waker(id)); + Context::from_waker(waker) + } + + fn add_task_as_pending(task: Task) { + if task.polling() { + task::COLLECTION.lock().add_task_as_woken(task); + } else { + task::COLLECTION.lock().add_task_as_sleep(task); + } + } +} diff --git a/test/src/multitask/mod.rs b/test/src/multitask/mod.rs new file mode 100644 index 00000000..5560a498 --- /dev/null +++ b/test/src/multitask/mod.rs @@ -0,0 +1,16 @@ +// The MIT License (MIT) +// +// Copyright (c) 2019 Philipp Oppermann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +pub(crate) mod executor; +pub(crate) mod task; + +pub(crate) fn add(task: task::Task) { + task::COLLECTION.lock().add_task_as_woken(task); +} diff --git a/test/src/multitask/task.rs b/test/src/multitask/task.rs new file mode 100644 index 00000000..10caf388 --- /dev/null +++ b/test/src/multitask/task.rs @@ -0,0 +1,146 @@ +// The MIT License (MIT) +// +// Copyright (c) 2019 Philipp Oppermann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, task::Wake}; +use conquer_once::spin::Lazy; +use core::{ + future::Future, + pin::Pin, + sync::atomic::{AtomicU64, Ordering}, + task::{Context, Poll, Waker}, +}; +use crossbeam_queue::ArrayQueue; +use spinning_top::Spinlock; + +pub(super) static COLLECTION: Lazy> = + Lazy::new(|| Spinlock::new(Collection::new())); + +pub(crate) struct Collection { + tasks: BTreeMap, + woken_task_ids: Arc>, +} +impl Collection { + pub(crate) fn new() -> Self { + Self { + tasks: BTreeMap::new(), + woken_task_ids: Arc::new(ArrayQueue::new(100)), + } + } + + pub(crate) fn add_task_as_woken(&mut self, task: Task) { + let id = task.id(); + self.push_task(task); + self.push_woken_task_id(id); + } + + pub(crate) fn add_task_as_sleep(&mut self, task: Task) { + self.push_task(task); + } + + fn push_task(&mut self, task: Task) { + let id = task.id(); + if self.tasks.insert(id, task).is_some() { + panic!("Task ID confliction."); + } + } + + fn push_woken_task_id(&mut self, id: Id) { + self.woken_task_ids + .push(id) + .expect("Woken task id queue is full."); + } + + pub(crate) fn pop_woken_task_id(&mut self) -> Option { + self.woken_task_ids.pop() + } + + pub(crate) fn remove_task(&mut self, id: Id) -> Option { + self.tasks.remove(&id) + } + + pub(crate) fn create_waker(&mut self, id: Id) -> Waker { + Waker::from(Arc::new(TaskWaker::new(id, self.woken_task_ids.clone()))) + } +} + +// task::Waker conflicts with alloc::task::Waker. +#[allow(clippy::module_name_repetitions)] +pub(crate) struct TaskWaker { + id: Id, + woken_task_ids: Arc>, +} + +impl TaskWaker { + pub(crate) fn new(id: Id, woken_task_ids: Arc>) -> Self { + Self { id, woken_task_ids } + } + + fn wake_task(&self) { + self.woken_task_ids + .push(self.id) + .expect("task_queue is full"); + } +} + +impl Wake for TaskWaker { + fn wake(self: Arc) { + self.wake_task(); + } + + fn wake_by_ref(self: &Arc) { + self.wake_task() + } +} + +#[derive(PartialOrd, PartialEq, Ord, Eq, Copy, Clone, Debug)] +pub(crate) struct Id(u64); + +impl Id { + fn new() -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + Id(NEXT_ID.fetch_add(1, Ordering::Relaxed)) + } +} + +pub(crate) struct Task { + id: Id, + future: Pin + Send>>, + polling: bool, +} + +impl Task { + pub(crate) fn new(future: impl Future + 'static + Send) -> Self { + Self { + id: Id::new(), + future: Box::pin(future), + polling: false, + } + } + + pub(crate) fn new_poll(future: impl Future + 'static + Send) -> Self { + Self { + id: Id::new(), + future: Box::pin(future), + polling: true, + } + } + + pub(crate) fn poll(&mut self, context: &mut Context<'_>) -> Poll<()> { + self.future.as_mut().poll(context) + } + + pub(crate) fn polling(&self) -> bool { + self.polling + } + + pub(super) fn id(&self) -> Id { + self.id + } +} diff --git a/test/src/page_box.rs b/test/src/page_box.rs new file mode 100644 index 00000000..451f7d9e --- /dev/null +++ b/test/src/page_box.rs @@ -0,0 +1,159 @@ +use core::alloc::Layout; +use core::fmt; +use core::fmt::Debug; +use core::fmt::Formatter; +use core::marker::PhantomData; +use core::ops::Deref; +use core::ops::DerefMut; +use core::slice; +use os_units::Bytes; +use x86_64::PhysAddr; +use x86_64::VirtAddr; + +/// A `Box`-like type that locates the inner value at a 4K bytes page boundary. +/// +/// xHCI specification prohibits some structures from crossing the page +/// boundary. Here, the size of a page is determined by Page Size Register (See +/// 5.4.3 of the spec). However, the minimum size of a page is 4K bytes, meaning +/// that keeping a structure within a 4K bytes page is always safe. It is very +/// costly, but at least it works. +pub struct PageBox { + addr: VirtAddr, + layout: Layout, + _marker: PhantomData, +} +impl PageBox { + pub fn from_layout_zeroed(layout: Layout) -> Self { + assert!( + layout.size() > 0, + "The size of the layout must be greater than 0." + ); + + let addr = unsafe { alloc::alloc::alloc(layout) }; + + // SAFETY: Safe as the address is well-aligned. + unsafe { core::ptr::write_bytes(addr as *mut u8, 0, layout.size()) }; + + Self { + addr: VirtAddr::new(addr as u64), + layout, + _marker: PhantomData, + } + } + + pub fn phys_addr(&self) -> PhysAddr { + // We assume the identity mapping set up by UEFI firmware. + PhysAddr::new(self.addr.as_u64()) + } + + pub fn bytes(&self) -> Bytes { + Bytes::from(self.layout.size()) + } +} +impl PageBox<[T]> { + pub fn new_slice(init: T, len: usize) -> Self { + let bytes = Bytes::from(len * core::mem::size_of::()); + let align = 4096.max(core::mem::align_of::()); + + let layout = Layout::from_size_align(bytes.as_usize(), align); + let layout = layout.unwrap_or_else(|_| { + panic!( + "Failed to create a layout for {} bytes with {} bytes alignment", + bytes.as_usize(), + align + ) + }); + + // SAFETY: `Layout::from_size_align` guarantees that the layout is valid. + let addr = unsafe { alloc::alloc::alloc(layout) }; + + // SAFETY: Safe as the address is well-aligned. + unsafe { + let mut slice = slice::from_raw_parts_mut(addr as *mut T, len); + for i in 0..len { + slice[i] = init.clone(); + } + }; + + Self { + addr: VirtAddr::new(addr as u64), + layout, + _marker: PhantomData, + } + } +} +impl Deref for PageBox { + type Target = T; + fn deref(&self) -> &Self::Target { + // SAFETY: Safe as the address is well-aligned. + unsafe { &*self.addr.as_ptr() } + } +} +impl Deref for PageBox<[T]> { + type Target = [T]; + fn deref(&self) -> &Self::Target { + let len = self.bytes().as_usize() / core::mem::size_of::(); + + // SAFETY: Safe as the address is well-aligned and the memory is allocated. + unsafe { slice::from_raw_parts(self.addr.as_ptr(), len) } + } +} +impl DerefMut for PageBox { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: Safe as the address is well-aligned. + unsafe { &mut *self.addr.as_mut_ptr() } + } +} +impl DerefMut for PageBox<[T]> { + fn deref_mut(&mut self) -> &mut Self::Target { + let len = self.bytes().as_usize() / core::mem::size_of::(); + + // SAFETY: Safe as the address is well-aligned and the memory is allocated. + unsafe { slice::from_raw_parts_mut(self.addr.as_mut_ptr(), len) } + } +} +impl From for PageBox { + fn from(inner: T) -> Self { + let bytes = Bytes::from(core::mem::size_of::()); + let align = 4096.max(core::mem::align_of::()); + + let layout = Layout::from_size_align(bytes.as_usize(), align); + let layout = layout.unwrap_or_else(|_| { + panic!( + "Failed to create a layout for {} bytes with {} bytes alignment", + bytes.as_usize(), + align + ) + }); + + // SAFETY: `Layout::from_size_align` guarantees that the layout is valid. + let addr = unsafe { alloc::alloc::alloc(layout) }; + + // SAFETY: Safe as the address is well-aligned. + unsafe { core::ptr::write(addr as *mut T, inner) }; + + Self { + addr: VirtAddr::new(addr as u64), + layout, + _marker: PhantomData, + } + } +} +impl Default for PageBox { + fn default() -> Self { + let x: T = Default::default(); + + Self::from(x) + } +} +impl Debug for PageBox { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.deref().fmt(f) + } +} +impl Drop for PageBox { + fn drop(&mut self) { + // SAFETY: `Layout::from_size_align` guarantees that the layout is valid. + unsafe { alloc::alloc::dealloc(self.addr.as_mut_ptr(), self.layout) } + } +} diff --git a/test/src/pci/config/bar.rs b/test/src/pci/config/bar.rs new file mode 100644 index 00000000..30952770 --- /dev/null +++ b/test/src/pci/config/bar.rs @@ -0,0 +1,79 @@ +use super::RegisterIndex; +use core::{ + convert::{From, TryFrom}, + ops::Add, +}; +use x86_64::PhysAddr; + +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct Bar(u32); + +impl Bar { + pub(crate) fn new(bar: u32) -> Self { + Self(bar) + } + + pub(crate) fn base_addr(self, upper: Option) -> Option { + match upper { + Some(upper) => match self.ty() { + BarType::Bar64Bit => self.base_addr_64(upper), + BarType::Bar32Bit => self.base_addr_32(), + }, + None => self.base_addr_32(), + } + } + + fn ty(self) -> BarType { + let ty_raw = (self.0 >> 1) & 0b11; + if ty_raw == 0 { + BarType::Bar32Bit + } else if ty_raw == 0x02 { + BarType::Bar64Bit + } else { + unreachable!(); + } + } + + fn base_addr_64(self, upper: Bar) -> Option { + match self.ty() { + BarType::Bar32Bit => None, + BarType::Bar64Bit => Some(PhysAddr::new( + (u64::from(self.0 & !0xf)) | ((u64::from(upper.0)) << 32), + )), + } + } + + fn base_addr_32(self) -> Option { + match self.ty() { + BarType::Bar32Bit => Some(PhysAddr::new(u64::from(self.0 & !0xf))), + BarType::Bar64Bit => None, + } + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq)] +pub(crate) struct Index(u32); +impl Index { + pub(crate) fn new(index: u32) -> Self { + assert!(index < 6); + Self(index) + } +} +impl From for RegisterIndex { + fn from(bar_index: Index) -> Self { + RegisterIndex::new(usize::try_from(bar_index.0 + 4).unwrap()) + } +} +impl Add for Index { + type Output = Self; + + fn add(self, rhs: u32) -> Self::Output { + Self::new(self.0 + rhs) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum BarType { + Bar32Bit, + Bar64Bit, +} diff --git a/test/src/pci/config/common.rs b/test/src/pci/config/common.rs new file mode 100644 index 00000000..57824423 --- /dev/null +++ b/test/src/pci/config/common.rs @@ -0,0 +1,89 @@ +use super::{RegisterIndex, Registers}; +use bit_field::BitField; +use core::convert::{TryFrom, TryInto}; + +#[derive(Debug)] +pub(super) struct Common<'a> { + registers: &'a Registers, +} +impl<'a> Common<'a> { + pub(super) fn new(registers: &'a Registers) -> Self { + Self { registers } + } + + pub(super) fn is_xhci(&self) -> bool { + self.class().is_xhci() + } + + pub(super) fn bridge_type(&self) -> BridgeType { + self.header_type().bridge_type() + } + + fn class(&self) -> Class<'_> { + Class::new(self.registers) + } + + fn header_type(&self) -> HeaderType { + HeaderType::new(self.registers) + } +} + +#[derive(Debug, Copy, Clone)] +struct HeaderType(u8); +impl HeaderType { + fn new(register: &Registers) -> Self { + let header = u8::try_from((register.get(RegisterIndex::new(3)) >> 16) & 0xff).unwrap(); + + Self(header) + } + + fn bridge_type(self) -> BridgeType { + match self.0 & 0x7f { + 0 => BridgeType::NonBridge, + 1 => BridgeType::PciToPci, + 2 => BridgeType::PciToCardbus, + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +pub(super) enum BridgeType { + NonBridge, + PciToPci, + PciToCardbus, +} + +#[derive(Debug)] +struct Class<'a> { + registers: &'a Registers, +} +impl<'a> Class<'a> { + fn new(registers: &'a Registers) -> Self { + Self { registers } + } + + fn is_xhci(&self) -> bool { + self.as_tuple() == (0x0c, 0x03, 0x30) + } + + fn as_tuple(&self) -> (u8, u8, u8) { + (self.base(), self.sub(), self.interface()) + } + + fn base(&self) -> u8 { + self.raw().get_bits(24..=31).try_into().unwrap() + } + + fn sub(&self) -> u8 { + self.raw().get_bits(16..=23).try_into().unwrap() + } + + fn interface(&self) -> u8 { + self.raw().get_bits(8..=15).try_into().unwrap() + } + + fn raw(&self) -> u32 { + self.registers.get(RegisterIndex::new(2)) + } +} diff --git a/test/src/pci/config/mod.rs b/test/src/pci/config/mod.rs new file mode 100644 index 00000000..71f437b7 --- /dev/null +++ b/test/src/pci/config/mod.rs @@ -0,0 +1,172 @@ +pub(crate) mod bar; +mod common; +pub(crate) mod type_spec; + +use self::common::Common; +use bar::Bar; +use core::{convert::TryFrom, ops::Add}; +use type_spec::TypeSpec; +use x86_64::{ + structures::port::{PortRead, PortWrite}, + PhysAddr, +}; + +#[derive(Debug)] +pub(crate) struct Space { + registers: Registers, +} + +impl Space { + pub(crate) fn new(bus: Bus, device: Device) -> Option { + Some(Self { + registers: Registers::new(bus, device)?, + }) + } + + pub(crate) fn is_xhci(&self) -> bool { + self.common().is_xhci() + } + + pub(crate) fn base_address(&self, index: bar::Index) -> PhysAddr { + self.type_spec().base_address(index) + } + + fn type_spec(&self) -> TypeSpec<'_> { + TypeSpec::new(&self.registers, &self.common()) + } + + fn common(&self) -> Common<'_> { + Common::new(&self.registers) + } +} + +#[derive(Debug)] +pub(crate) struct Registers { + bus: Bus, + device: Device, +} +impl Registers { + fn new(bus: Bus, device: Device) -> Option { + if Self::valid(bus, device) { + Some(Self { bus, device }) + } else { + None + } + } + + fn valid(bus: Bus, device: Device) -> bool { + let config_addr = ConfigAddress::new(bus, device, Function::zero(), RegisterIndex::zero()); + let id = unsafe { config_addr.read() }; + + id != !0 + } + + fn get(&self, index: RegisterIndex) -> u32 { + let accessor = ConfigAddress::new(self.bus, self.device, Function::zero(), index); + unsafe { accessor.read() } + } +} + +struct ConfigAddress { + bus: Bus, + device: Device, + function: Function, + register: RegisterIndex, +} + +impl ConfigAddress { + const PORT_CONFIG_ADDR: u16 = 0xcf8; + const PORT_CONFIG_DATA: u16 = 0xcfc; + + #[allow(clippy::too_many_arguments)] + fn new(bus: Bus, device: Device, function: Function, register: RegisterIndex) -> Self { + Self { + bus, + device, + function, + register, + } + } + + fn as_u32(&self) -> u32 { + const VALID: u32 = 0x8000_0000; + let bus = self.bus.as_u32(); + let device = self.device.as_u32(); + let function = self.function.as_u32(); + let register = u32::try_from(self.register.as_usize()).unwrap(); + + VALID | bus << 16 | device << 11 | function << 8 | register << 2 + } + + /// SAFETY: `self` must contain the valid config address. + unsafe fn read(&self) -> u32 { + PortWrite::write_to_port(Self::PORT_CONFIG_ADDR, self.as_u32()); + PortRead::read_from_port(Self::PORT_CONFIG_DATA) + } +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct Bus(u32); +impl Bus { + pub(crate) const MAX: u32 = 256; + pub(crate) fn new(bus: u32) -> Self { + assert!(bus < Self::MAX); + Self(bus) + } + + fn as_u32(self) -> u32 { + self.0 + } +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct Device(u32); +impl Device { + pub(crate) const MAX: u32 = 32; + pub(crate) fn new(device: u32) -> Self { + assert!(device < Self::MAX); + Self(device) + } + + fn as_u32(self) -> u32 { + self.0 + } +} + +#[derive(Copy, Clone)] +pub(crate) struct Function(u32); +impl Function { + pub(crate) fn zero() -> Self { + Self(0) + } + + pub(crate) fn as_u32(self) -> u32 { + self.0 + } +} + +#[derive(Debug, Copy, Clone)] +pub(crate) struct RegisterIndex(usize); +impl RegisterIndex { + const MAX: usize = 64; + pub(crate) fn new(offset: usize) -> Self { + assert!(offset < Self::MAX, "Too large register index: {}", offset); + Self(offset) + } + + fn zero() -> Self { + Self(0) + } + + fn as_usize(self) -> usize { + self.0 + } +} + +impl Add for RegisterIndex { + type Output = RegisterIndex; + + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} diff --git a/test/src/pci/config/type_spec/mod.rs b/test/src/pci/config/type_spec/mod.rs new file mode 100644 index 00000000..3433da27 --- /dev/null +++ b/test/src/pci/config/type_spec/mod.rs @@ -0,0 +1,27 @@ +mod non_bridge; + +use super::{ + bar, + common::{BridgeType, Common}, + Bar, RegisterIndex, Registers, +}; +use x86_64::PhysAddr; + +#[derive(Debug)] +pub(super) enum TypeSpec<'a> { + NonBridge(non_bridge::TypeSpec<'a>), +} + +impl<'a> TypeSpec<'a> { + pub(super) fn new(registers: &'a Registers, common: &Common<'_>) -> Self { + match common.bridge_type() { + BridgeType::NonBridge => TypeSpec::NonBridge(non_bridge::TypeSpec::new(registers)), + e => panic!("Not implemented: {:?}\ncommon:{:?}", e, common), + } + } + + pub(super) fn base_address(&self, index: bar::Index) -> PhysAddr { + let TypeSpec::NonBridge(non_bridge) = self; + non_bridge.base_addr(index) + } +} diff --git a/test/src/pci/config/type_spec/non_bridge.rs b/test/src/pci/config/type_spec/non_bridge.rs new file mode 100644 index 00000000..50180de7 --- /dev/null +++ b/test/src/pci/config/type_spec/non_bridge.rs @@ -0,0 +1,34 @@ +use super::{bar, Bar, RegisterIndex, Registers}; +use log::debug; +use x86_64::PhysAddr; + +#[derive(Debug)] +pub(crate) struct TypeSpec<'a> { + registers: &'a Registers, +} + +impl<'a> TypeSpec<'a> { + pub(crate) fn new(registers: &'a Registers) -> Self { + Self { registers } + } + + pub(crate) fn base_addr(&self, index: bar::Index) -> PhysAddr { + let upper = if index == bar::Index::new(5) { + None + } else { + Some(self.bar(index + 1)) + }; + + for i in 0..6 { + debug!("Bar{}: {:?}", i, self.bar(bar::Index::new(i))); + } + + self.bar(index) + .base_addr(upper) + .expect("Could not calculate Base Address.") + } + + fn bar(&self, index: bar::Index) -> Bar { + Bar::new(self.registers.get(RegisterIndex::from(index))) + } +} diff --git a/test/src/pci/mod.rs b/test/src/pci/mod.rs new file mode 100644 index 00000000..1475b2ff --- /dev/null +++ b/test/src/pci/mod.rs @@ -0,0 +1,40 @@ +pub(crate) mod config; + +use config::{Bus, Device}; + +pub(crate) fn iter_devices() -> impl Iterator { + IterPciDevices::new(0, 0) +} + +struct IterPciDevices { + bus: u32, + device: u32, +} + +impl IterPciDevices { + fn new(bus: u32, device: u32) -> Self { + assert!(device < 32); + Self { bus, device } + } +} + +impl Iterator for IterPciDevices { + type Item = config::Space; + + fn next(&mut self) -> Option { + for bus in self.bus..Bus::MAX { + for device in self.device..Device::MAX { + if let Some(space) = config::Space::new(Bus::new(bus), Device::new(device)) { + self.bus = bus; + self.device = device + 1; + + return Some(space); + } + } + + self.device = 0; + } + + None + } +} diff --git a/test/src/port/class_driver/keyboard.rs b/test/src/port/class_driver/keyboard.rs new file mode 100644 index 00000000..73e52b6b --- /dev/null +++ b/test/src/port/class_driver/keyboard.rs @@ -0,0 +1,87 @@ +use crate::{ + page_box::PageBox, + port::init::fully_operational::FullyOperational, + structures::descriptor::{Configuration, Descriptor}, +}; +use alloc::{string::String, vec::Vec}; +use log::info; +use spinning_top::Spinlock; +use xhci::context::EndpointType; + +const LOWER_ALPHABETS: &str = "abcdefghijklmnopqrstuvwxyz"; + +static STR: Spinlock = Spinlock::new(String::new()); + +pub(in crate::port) async fn task(eps: FullyOperational) { + let mut k = Keyboard::new(eps); + k.configure().await; + + info!("Set the Boot protocol."); + k.set_boot_protocol().await; + info!("Set."); + + loop { + k.get_packet().await; + k.store_key(); + } +} + +pub(crate) struct Keyboard { + ep: FullyOperational, + buf: PageBox<[u8; 8]>, +} +impl Keyboard { + pub(in crate::port) fn new(ep: FullyOperational) -> Self { + Self { + ep, + buf: [0; 8].into(), + } + } + + async fn configure(&mut self) { + let d = self.configuration_descriptor(); + self.ep.set_configure(d.config_val()).await; + } + + async fn set_boot_protocol(&mut self) { + self.ep.set_boot_protocol().await; + } + + async fn get_packet(&mut self) { + self.issue_normal_trb().await; + } + + async fn issue_normal_trb(&mut self) { + self.ep + .issue_normal_trb(&self.buf, EndpointType::InterruptIn) + .await + .expect("Failed to send a Normal TRB"); + } + + fn configuration_descriptor(&self) -> Configuration { + *self + .ep + .descriptors() + .iter() + .filter_map(|x| { + if let Descriptor::Configuration(c) = x { + Some(c) + } else { + None + } + }) + .collect::>()[0] + } + + fn store_key(&self) { + for c in self.buf.iter().skip(2) { + if *c >= 4 && *c <= 0x1d { + STR.lock() + .push(LOWER_ALPHABETS.chars().nth((c - 4).into()).unwrap()); + } else if *c == 0x28 { + info!("{}", STR.lock()); + *STR.lock() = String::new(); + } + } + } +} diff --git a/test/src/port/class_driver/mass_storage/mod.rs b/test/src/port/class_driver/mass_storage/mod.rs new file mode 100644 index 00000000..3ae8dbe2 --- /dev/null +++ b/test/src/port/class_driver/mass_storage/mod.rs @@ -0,0 +1,191 @@ +mod scsi; + +use crate::{ + page_box::PageBox, + port::init::fully_operational::FullyOperational, + structures::descriptor::{Configuration, Descriptor}, +}; +use alloc::vec::Vec; +use log::info; +use scsi::{ + command_data_block, + response::{Inquiry, Read10, ReadCapacity10}, + CommandBlockWrapper, CommandBlockWrapperHeaderBuilder, CommandStatusWrapper, +}; +use xhci::context::EndpointType; + +pub(in crate::port) async fn task(eps: FullyOperational) { + let mut m = MassStorage::new(eps); + info!("This is the task of USB Mass Storage."); + + m.configure().await; + info!("USB Mass Storage is configured."); + + let b = m.inquiry().await; + info!("Inquiry Command: {:?}", b); + + let b = m.read_capacity_10().await; + info!("Read Capacity: {:?}", b); + + m.read10().await; + + m.write10().await; +} + +struct MassStorage { + ep: FullyOperational, +} +impl MassStorage { + fn new(ep: FullyOperational) -> Self { + Self { ep } + } + + async fn configure(&mut self) { + let d = self.configuration_descriptor(); + self.ep.set_configure(d.config_val()).await; + } + + fn configuration_descriptor(&self) -> Configuration { + *self + .ep + .descriptors() + .iter() + .filter_map(|x| { + if let Descriptor::Configuration(c) = x { + Some(c) + } else { + None + } + }) + .collect::>()[0] + } + + async fn inquiry(&mut self) -> Inquiry { + const LEN: u16 = 0x24; + + let header = CommandBlockWrapperHeaderBuilder::default() + .transfer_length(LEN.into()) + .flags(scsi::Flags::In) + .lun(0) + .command_len(6) + .build() + .expect("Failed to build an inquiry command block wrapper."); + let data = command_data_block::Inquiry::new(LEN); + let mut wrapper = PageBox::from(CommandBlockWrapper::new(header, data.into())); + + let (response, status): (PageBox, _) = self.send_scsi_command(&mut wrapper).await; + + status.check_corruption(); + *response + } + + async fn read_capacity_10(&mut self) -> ReadCapacity10 { + let header = CommandBlockWrapperHeaderBuilder::default() + .transfer_length(8) + .flags(scsi::Flags::In) + .lun(0) + .command_len(10) + .build() + .expect("Failed to build a read capacity command block wrapper"); + let data = command_data_block::ReadCapacity::default(); + let mut wrapper = PageBox::from(CommandBlockWrapper::new(header, data.into())); + + let (response, status): (PageBox, _) = + self.send_scsi_command(&mut wrapper).await; + + status.check_corruption(); + *response + } + + async fn read10(&mut self) -> PageBox { + let header = CommandBlockWrapperHeaderBuilder::default() + .transfer_length(0x8000) + .flags(scsi::Flags::In) + .lun(0) + .command_len(0x0a) + .build() + .expect("Failed to build a read 10 command block wrapper."); + let data = command_data_block::Read10::new(0, 64); + let mut wrapper = PageBox::from(CommandBlockWrapper::new(header, data.into())); + + let (response, status): (PageBox, _) = self.send_scsi_command(&mut wrapper).await; + + status.check_corruption(); + response + } + + async fn write10(&mut self) { + let header = CommandBlockWrapperHeaderBuilder::default() + .transfer_length(0x0008) + .flags(scsi::Flags::Out) + .lun(0) + .command_len(0x0a) + .build() + .expect("Failed to build a write 10 command block wrapper."); + let data = command_data_block::Write10::new(0, 64); + let mut wrapper = PageBox::from(CommandBlockWrapper::new(header, data.into())); + + let content = PageBox::from(0x334_usize); + + let status = self.send_scsi_command_for_out(&mut wrapper, &content).await; + status.check_corruption(); + } + + async fn send_scsi_command( + &mut self, + c: &mut PageBox, + ) -> (PageBox, PageBox) + where + T: Default, + { + self.send_command_block_wrapper(c).await; + let response = self.receive_command_response().await; + let status = self.receive_command_status().await; + (response, status) + } + + async fn send_scsi_command_for_out( + &mut self, + c: &mut PageBox, + d: &PageBox, + ) -> PageBox { + self.send_command_block_wrapper(c).await; + self.send_additional_data(d).await; + self.receive_command_status().await + } + + async fn send_command_block_wrapper(&mut self, c: &mut PageBox) { + self.ep + .issue_normal_trb(c, EndpointType::BulkOut) + .await + .expect("Failed to send a SCSI command."); + } + + async fn receive_command_response(&mut self) -> PageBox + where + T: Default, + { + let c = PageBox::default(); + self.ep + .issue_normal_trb(&c, EndpointType::BulkIn) + .await + .expect("Failed to receive a SCSI command reponse."); + c + } + + async fn send_additional_data(&mut self, d: &PageBox) { + self.ep + .issue_normal_trb(d, EndpointType::BulkOut) + .await + .expect("Failed to send a data."); + } + + async fn receive_command_status(&mut self) -> PageBox { + let b = PageBox::default(); + self.ep + .issue_normal_trb(&b, EndpointType::BulkIn) + .await + .expect("Failed to receive a SCSI status."); + b + } +} diff --git a/test/src/port/class_driver/mass_storage/scsi/command_data_block.rs b/test/src/port/class_driver/mass_storage/scsi/command_data_block.rs new file mode 100644 index 00000000..2323158f --- /dev/null +++ b/test/src/port/class_driver/mass_storage/scsi/command_data_block.rs @@ -0,0 +1,107 @@ +use byteorder::{BigEndian, ByteOrder}; + +#[derive(Copy, Clone)] +pub(in super::super) enum CommandDataBlock { + Inquiry(Inquiry), + ReadCapacity(ReadCapacity), + Read10(Read10), + Write10(Write10), +} +impl From for [u8; 16] { + fn from(cdb: CommandDataBlock) -> Self { + match cdb { + CommandDataBlock::Inquiry(i) => i.0, + CommandDataBlock::ReadCapacity(r) => r.0, + CommandDataBlock::Read10(r) => r.0, + CommandDataBlock::Write10(w) => w.0, + } + } +} + +macro_rules! command { + ($name:ident) => { + #[derive(Copy, Clone)] + pub(in super::super) struct $name([u8; 16]); + impl $name { + fn set_command(&mut self) -> &mut Self { + self.0[0] = Command::$name.into(); + self + } + } + impl Default for $name { + fn default() -> Self { + *Self([0; 16]).set_command() + } + } + impl From<$name> for CommandDataBlock { + fn from(c: $name) -> CommandDataBlock { + CommandDataBlock::$name(c) + } + } + }; +} + +command!(Inquiry); +impl Inquiry { + pub(in super::super) fn new(length: u16) -> Self { + *Self::default().set_length(length) + } + + fn set_length(&mut self, l: u16) -> &mut Self { + BigEndian::write_u16(&mut self.0[3..=4], l); + self + } +} + +command!(ReadCapacity); + +command!(Read10); +impl Read10 { + pub(in super::super) fn new(lba: u32, num_of_blocks: u16) -> Self { + *Self::default() + .set_lba(lba) + .set_num_of_blocks(num_of_blocks) + } + + fn set_lba(&mut self, l: u32) -> &mut Self { + BigEndian::write_u32(&mut self.0[2..6], l); + self + } + + fn set_num_of_blocks(&mut self, n: u16) -> &mut Self { + BigEndian::write_u16(&mut self.0[7..=8], n); + self + } +} + +command!(Write10); +impl Write10 { + pub(in super::super) fn new(lba: u32, num_of_blocks: u16) -> Self { + *Self::default() + .set_lba(lba) + .set_num_of_blocks(num_of_blocks) + } + + fn set_lba(&mut self, l: u32) -> &mut Self { + BigEndian::write_u32(&mut self.0[2..6], l); + self + } + + fn set_num_of_blocks(&mut self, n: u16) -> &mut Self { + BigEndian::write_u16(&mut self.0[7..=8], n); + self + } +} + +#[repr(u8)] +enum Command { + Inquiry = 0x12, + ReadCapacity = 0x25, + Read10 = 0x28, + Write10 = 0x2a, +} +impl From for u8 { + fn from(c: Command) -> Self { + c as u8 + } +} diff --git a/test/src/port/class_driver/mass_storage/scsi/mod.rs b/test/src/port/class_driver/mass_storage/scsi/mod.rs new file mode 100644 index 00000000..e45be3e5 --- /dev/null +++ b/test/src/port/class_driver/mass_storage/scsi/mod.rs @@ -0,0 +1,74 @@ +pub(super) mod command_data_block; +pub(super) mod response; + +use command_data_block::CommandDataBlock; +use derive_builder::Builder; +use num_derive::FromPrimitive; + +#[repr(C, packed)] +pub(super) struct CommandBlockWrapper { + header: CommandBlockWrapperHeader, + data: [u8; 16], +} +impl CommandBlockWrapper { + pub(super) fn new(header: CommandBlockWrapperHeader, data: CommandDataBlock) -> Self { + Self { + header, + data: data.into(), + } + } +} + +#[repr(C, packed)] +#[derive(Builder)] +#[builder(no_std)] +pub(super) struct CommandBlockWrapperHeader { + #[builder(default = "CommandBlockWrapperHeader::SIGNATURE")] + signature: u32, + #[builder(default = "0")] + tag: u32, + transfer_length: u32, + flags: Flags, + lun: u8, + command_len: u8, +} +impl CommandBlockWrapperHeader { + const SIGNATURE: u32 = 0x4342_5355; +} + +#[repr(u8)] +#[derive(Copy, Clone)] +pub(super) enum Flags { + Out = 0, + In = 0x80, +} + +#[repr(C, packed)] +#[derive(Copy, Clone, Default)] +pub(super) struct CommandStatusWrapper { + signature: u32, + tag: u32, + data_residue: u32, + status: u8, +} +impl CommandStatusWrapper { + pub(super) fn check_corruption(&self) { + const USBS: u32 = 0x5342_5355; + let signature = self.signature; + + assert_eq!( + signature, USBS, + "The signature of the Command Status Wrapper is wrong." + ); + } +} + +#[derive(Copy, Clone, Debug, FromPrimitive)] +pub(super) enum Status { + Good = 0, +} +impl Default for Status { + fn default() -> Self { + Self::Good + } +} diff --git a/test/src/port/class_driver/mass_storage/scsi/response.rs b/test/src/port/class_driver/mass_storage/scsi/response.rs new file mode 100644 index 00000000..210a2b13 --- /dev/null +++ b/test/src/port/class_driver/mass_storage/scsi/response.rs @@ -0,0 +1,44 @@ +use byteorder::{BigEndian, ByteOrder}; +use core::fmt; + +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub(crate) struct Inquiry([u8; 36]); +impl Default for Inquiry { + fn default() -> Self { + Self([0; 36]) + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C)] +pub(crate) struct ReadCapacity10 { + lba: [u8; 4], + block_size: [u8; 4], +} +impl ReadCapacity10 { + fn lba(self) -> u32 { + BigEndian::read_u32(&self.lba) + } + + fn block_size(self) -> u32 { + BigEndian::read_u32(&self.block_size) + } +} +impl fmt::Debug for ReadCapacity10 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ReadCapacity10") + .field("lba", &self.lba()) + .field("block_size", &self.block_size()) + .finish() + } +} + +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub(crate) struct Read10([u8; 32768]); +impl Default for Read10 { + fn default() -> Self { + Self([0; 32768]) + } +} diff --git a/test/src/port/class_driver/mod.rs b/test/src/port/class_driver/mod.rs new file mode 100644 index 00000000..defb1ea1 --- /dev/null +++ b/test/src/port/class_driver/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod keyboard; +pub(super) mod mass_storage; +pub(crate) mod mouse; diff --git a/test/src/port/class_driver/mouse.rs b/test/src/port/class_driver/mouse.rs new file mode 100644 index 00000000..62c3db4d --- /dev/null +++ b/test/src/port/class_driver/mouse.rs @@ -0,0 +1,89 @@ +use crate::{ + page_box::PageBox, + port::init::fully_operational::FullyOperational, + structures::descriptor::{Configuration, Descriptor}, +}; +use alloc::vec::Vec; +use log::info; +use xhci::context::EndpointType; + +pub(in super::super) async fn task(eps: FullyOperational) { + let mut m = Mouse::new(eps); + + m.configure().await; + info!("Configuration completed."); + + m.set_boot_protocol().await; + info!("Boot protocol is set."); + + m.set_idle().await; + info!("Set Idle completed."); + + loop { + m.get_packet().await; + m.print_buf(); + } +} + +pub(crate) struct Mouse { + ep: FullyOperational, + buf: PageBox<[i8; 4]>, +} +impl Mouse { + pub(super) fn new(ep: FullyOperational) -> Self { + Self { + ep, + buf: [0; 4].into(), + } + } + + async fn configure(&mut self) { + let d = self.configuration_descriptor(); + self.ep.set_configure(d.config_val()).await; + } + + async fn set_idle(&mut self) { + self.ep.set_idle().await; + } + + async fn set_boot_protocol(&mut self) { + self.ep.set_boot_protocol().await; + } + + fn configuration_descriptor(&self) -> Configuration { + *self + .ep + .descriptors() + .iter() + .filter_map(|x| { + if let Descriptor::Configuration(c) = x { + Some(c) + } else { + None + } + }) + .collect::>()[0] + } + + async fn get_packet(&mut self) { + self.issue_normal_trb().await; + } + + async fn issue_normal_trb(&mut self) { + self.ep + .issue_normal_trb(&self.buf, EndpointType::InterruptIn) + .await + .expect("Failed to send a Normal TRB."); + } + + fn print_buf(&self) { + info!( + "Button: {} {} {}, X: {}, Y: {}", + self.buf[0] & 1 == 1, + self.buf[0] & 2 == 2, + self.buf[0] & 4 == 4, + self.buf[1], + self.buf[2] + ); + } +} diff --git a/test/src/port/endpoint.rs b/test/src/port/endpoint.rs new file mode 100644 index 00000000..59ff688a --- /dev/null +++ b/test/src/port/endpoint.rs @@ -0,0 +1,73 @@ +use crate::{exchanger::transfer, page_box::PageBox, structures::descriptor}; +use x86_64::PhysAddr; +use xhci::context::EndpointType; + +pub(super) struct Default { + sender: transfer::Sender, +} +impl Default { + pub(super) fn new(sender: transfer::Sender) -> Self { + Self { sender } + } + + pub(super) fn ring_addr(&self) -> PhysAddr { + self.sender.ring_addr() + } + + pub(super) async fn get_max_packet_size(&mut self) -> u16 { + self.sender + .get_max_packet_size_from_device_descriptor() + .await + } + + pub(super) async fn get_raw_configuration_descriptors(&mut self) -> PageBox<[u8]> { + self.sender.get_configuration_descriptor().await + } + + pub(super) async fn set_configuration(&mut self, config_val: u8) { + self.sender.set_configure(config_val).await; + } + + pub(super) async fn set_idle(&mut self) { + self.sender.set_idle().await; + } + + pub(super) async fn set_boot_protocol(&mut self) { + self.sender.set_boot_protocol().await; + } + + pub(super) async fn issue_nop_trb(&mut self) { + self.sender.issue_nop_trb().await; + } +} + +pub(super) struct NonDefault { + desc: descriptor::Endpoint, + sender: transfer::Sender, +} +impl NonDefault { + pub(super) fn new(desc: descriptor::Endpoint, sender: transfer::Sender) -> Self { + Self { desc, sender } + } + + pub(super) fn descriptor(&self) -> descriptor::Endpoint { + self.desc + } + + pub(super) fn transfer_ring_addr(&self) -> PhysAddr { + self.sender.ring_addr() + } + + pub(super) fn ty(&self) -> EndpointType { + self.desc.ty() + } + + pub(super) async fn issue_normal_trb(&mut self, b: &PageBox) { + self.sender.issue_normal_trb(b).await + } +} + +#[derive(Debug)] +pub(crate) enum Error { + NoSuchEndpoint(EndpointType), +} diff --git a/test/src/port/init/descriptor_fetcher.rs b/test/src/port/init/descriptor_fetcher.rs new file mode 100644 index 00000000..95c01b89 --- /dev/null +++ b/test/src/port/init/descriptor_fetcher.rs @@ -0,0 +1,99 @@ +use super::{ + endpoints_initializer::EndpointsInitializer, max_packet_size_setter::MaxPacketSizeSetter, +}; +use crate::{ + page_box::PageBox, + port::endpoint, + structures::{context::Context, descriptor, descriptor::Descriptor}, +}; +use alloc::{sync::Arc, vec::Vec}; +use log::debug; +use spinning_top::Spinlock; + +pub(super) struct DescriptorFetcher { + port_number: u8, + slot_number: u8, + cx: Arc>, + ep0: endpoint::Default, +} +impl DescriptorFetcher { + pub(super) fn new(s: MaxPacketSizeSetter) -> Self { + let port_number = s.port_number(); + let slot_number = s.slot_number(); + let cx = s.context(); + let ep0 = s.ep0(); + + Self { + port_number, + slot_number, + cx, + ep0, + } + } + + pub(super) async fn fetch(mut self) -> EndpointsInitializer { + let r = self.get_raw_descriptors().await; + let ds = RawDescriptorParser::new(r).parse(); + EndpointsInitializer::new(self, ds) + } + + pub(super) fn context(&self) -> Arc> { + self.cx.clone() + } + + pub(super) fn port_number(&self) -> u8 { + self.port_number + } + + pub(super) fn slot_number(&self) -> u8 { + self.slot_number + } + + pub(super) fn ep0(self) -> endpoint::Default { + self.ep0 + } + + async fn get_raw_descriptors(&mut self) -> PageBox<[u8]> { + self.ep0.get_raw_configuration_descriptors().await + } +} + +struct RawDescriptorParser { + raw: PageBox<[u8]>, + current: usize, + len: usize, +} +impl RawDescriptorParser { + fn new(raw: PageBox<[u8]>) -> Self { + let len = raw.len(); + + Self { + raw, + current: 0, + len, + } + } + + fn parse(&mut self) -> Vec { + let mut v = Vec::new(); + while self.current < self.len && self.raw[self.current] > 0 { + match self.parse_first_descriptor() { + Ok(t) => v.push(t), + Err(e) => debug!("Unrecognized USB descriptor: {:?}", e), + } + } + v + } + + fn parse_first_descriptor(&mut self) -> Result { + let raw = self.cut_raw_descriptor(); + Descriptor::from_slice(&raw) + } + + fn cut_raw_descriptor(&mut self) -> Vec { + let len: usize = self.raw[self.current].into(); + let v = self.raw[self.current..(self.current + len)].to_vec(); + self.current += len; + v + } +} diff --git a/test/src/port/init/endpoints_initializer.rs b/test/src/port/init/endpoints_initializer.rs new file mode 100644 index 00000000..a3ad429a --- /dev/null +++ b/test/src/port/init/endpoints_initializer.rs @@ -0,0 +1,279 @@ +use super::{descriptor_fetcher::DescriptorFetcher, fully_operational::FullyOperational}; +use crate::{ + exchanger, + exchanger::transfer, + port::endpoint, + structures::{context::Context, descriptor, descriptor::Descriptor, registers}, +}; +use alloc::{sync::Arc, vec::Vec}; +use bit_field::BitField; +use core::convert::TryInto; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use spinning_top::Spinlock; +use transfer::DoorbellWriter; +use x86_64::PhysAddr; +use xhci::context::{EndpointHandler, EndpointType}; + +pub(super) struct EndpointsInitializer { + cx: Arc>, + descriptors: Vec, + endpoints: Vec, + ep0: endpoint::Default, + port_number: u8, + slot_number: u8, +} +impl EndpointsInitializer { + pub(super) fn new(f: DescriptorFetcher, descriptors: Vec) -> Self { + let cx = f.context(); + let endpoints = descriptors_to_endpoints(&f, &descriptors); + let port_number = f.port_number(); + let slot_number = f.slot_number(); + let ep0 = f.ep0(); + + Self { + cx, + descriptors, + endpoints, + ep0, + port_number, + slot_number, + } + } + + pub(super) async fn init(mut self) -> FullyOperational { + self.init_contexts(); + self.set_context_entries(); + self.configure_endpoint().await; + FullyOperational::new(self) + } + + pub(super) fn descriptors(&self) -> Vec { + self.descriptors.clone() + } + + pub(super) fn endpoints(self) -> (endpoint::Default, Vec) { + (self.ep0, self.endpoints) + } + + fn init_contexts(&mut self) { + for e in &mut self.endpoints { + ContextInitializer::new( + &mut self.cx.lock(), + &e.descriptor(), + e.transfer_ring_addr(), + self.port_number, + ) + .init() + } + } + + fn set_context_entries(&mut self) { + let mut cx = self.cx.lock(); + cx.input.device_mut().slot_mut().set_context_entries(31); + } + + async fn configure_endpoint(&mut self) { + let a = self.cx.lock().input.phys_addr(); + exchanger::command::configure_endpoint(a, self.slot_number).await; + } +} + +struct ContextInitializer<'a> { + cx: &'a mut Context, + ep: &'a descriptor::Endpoint, + transfer_ring_addr: PhysAddr, + port_number: u8, +} +impl<'a> ContextInitializer<'a> { + #[allow(clippy::too_many_arguments)] // TODO + fn new( + cx: &'a mut Context, + ep: &'a descriptor::Endpoint, + transfer_ring_addr: PhysAddr, + port_number: u8, + ) -> Self { + Self { + cx, + ep, + transfer_ring_addr, + port_number, + } + } + + fn init(mut self) { + self.set_aflag(); + self.init_ep_context(); + } + + fn set_aflag(&mut self) { + let dci: usize = self.calculate_dci().into(); + let c = self.cx.input.control_mut(); + + c.set_add_context_flag(0); + c.clear_add_context_flag(1); // See xHCI dev manual 4.6.6. + c.set_add_context_flag(dci); + } + + fn calculate_dci(&self) -> u8 { + let a = self.ep.endpoint_address; + 2 * a.get_bits(0..=3) + a.get_bit(7) as u8 + } + + fn init_ep_context(&mut self) { + self.set_interval(); + + let ep_ty = self.ep.ty(); + self.ep_cx().set_endpoint_type(ep_ty); + + // TODO: This initializes the context only for USB2. Branch if the version of a device is + // USB3. + match ep_ty { + EndpointType::Control => self.init_for_control(), + EndpointType::BulkOut | EndpointType::BulkIn => self.init_for_bulk(), + EndpointType::IsochOut + | EndpointType::IsochIn + | EndpointType::InterruptOut + | EndpointType::InterruptIn => self.init_for_isoch_or_interrupt(), + EndpointType::NotValid => unreachable!("Not Valid Endpoint should not exist."), + } + } + + fn init_for_control(&mut self) { + assert_eq!( + self.ep.ty(), + EndpointType::Control, + "Not the Control Endpoint." + ); + + let sz = self.ep.max_packet_size; + let a = self.transfer_ring_addr; + let c = self.ep_cx(); + + c.set_max_packet_size(sz); + c.set_error_count(3); + c.set_tr_dequeue_pointer(a.as_u64()); + c.set_dequeue_cycle_state(); + } + + fn init_for_bulk(&mut self) { + assert!(self.is_bulk(), "Not the Bulk Endpoint."); + + let sz = self.ep.max_packet_size; + let a = self.transfer_ring_addr; + let c = self.ep_cx(); + + c.set_max_packet_size(sz); + c.set_max_burst_size(0); + c.set_error_count(3); + c.set_max_primary_streams(0); + c.set_tr_dequeue_pointer(a.as_u64()); + c.set_dequeue_cycle_state(); + } + + fn is_bulk(&self) -> bool { + let t = self.ep.ty(); + + [EndpointType::BulkOut, EndpointType::BulkIn].contains(&t) + } + + fn init_for_isoch_or_interrupt(&mut self) { + let t = self.ep.ty(); + assert!( + self.is_isoch_or_interrupt(), + "Not the Isochronous or the Interrupt Endpoint." + ); + + let sz = self.ep.max_packet_size; + let a = self.transfer_ring_addr; + let c = self.ep_cx(); + + c.set_max_packet_size(sz & 0x7ff); + c.set_max_burst_size(((sz & 0x1800) >> 11).try_into().unwrap()); + c.set_mult(0); + + if let EndpointType::IsochOut | EndpointType::IsochIn = t { + c.set_error_count(0); + } else { + c.set_error_count(3); + } + c.set_tr_dequeue_pointer(a.as_u64()); + c.set_dequeue_cycle_state(); + } + + fn is_isoch_or_interrupt(&self) -> bool { + let t = self.ep.ty(); + [ + EndpointType::IsochOut, + EndpointType::IsochIn, + EndpointType::InterruptOut, + EndpointType::InterruptIn, + ] + .contains(&t) + } + + // TODO: Is this calculation correct? + fn set_interval(&mut self) { + let s = self.port_speed(); + let t = self.ep.ty(); + let i = self.ep.interval; + + let i = if let PortSpeed::FullSpeed | PortSpeed::LowSpeed = s { + if let EndpointType::IsochOut | EndpointType::IsochIn = t { + i + 2 + } else { + i + 3 + } + } else { + i - 1 + }; + + self.ep_cx().set_interval(i); + } + + fn port_speed(&self) -> PortSpeed { + FromPrimitive::from_u8(registers::handle(|r| { + r.port_register_set + .read_volatile_at((self.port_number - 1).into()) + .portsc + .port_speed() + })) + .expect("Failed to get the Port Speed.") + } + + fn ep_cx(&mut self) -> &mut dyn EndpointHandler { + let ep_i: usize = self.ep.endpoint_address.get_bits(0..=3).into(); + let is_input: usize = self.ep.endpoint_address.get_bit(7) as _; + let dpi = 2 * ep_i + is_input; + + self.cx.input.device_mut().endpoint_mut(dpi) + } +} + +#[derive(Copy, Clone, FromPrimitive)] +enum PortSpeed { + FullSpeed = 1, + LowSpeed = 2, + HighSpeed = 3, + SuperSpeed = 4, + SuperSpeedPlus = 5, +} + +fn descriptors_to_endpoints( + f: &DescriptorFetcher, + descriptors: &[Descriptor], +) -> Vec { + descriptors + .iter() + .filter_map(|desc| { + let _ = &f; + if let Descriptor::Endpoint(e) = desc { + let d = DoorbellWriter::new(f.slot_number(), e.doorbell_value()); + let s = transfer::Sender::new(d); + Some(endpoint::NonDefault::new(*e, s)) + } else { + None + } + }) + .collect() +} diff --git a/test/src/port/init/fully_operational.rs b/test/src/port/init/fully_operational.rs new file mode 100644 index 00000000..23260db2 --- /dev/null +++ b/test/src/port/init/fully_operational.rs @@ -0,0 +1,86 @@ +use super::endpoints_initializer::EndpointsInitializer; +use crate::{ + page_box::PageBox, + port::{ + endpoint, + endpoint::{Error, NonDefault}, + }, + structures::descriptor::Descriptor, +}; +use alloc::vec::Vec; +use core::slice; +use log::debug; +use xhci::context::EndpointType; + +pub(in crate::port) struct FullyOperational { + descriptors: Vec, + def_ep: endpoint::Default, + eps: Vec, +} +impl FullyOperational { + pub(super) fn new(i: EndpointsInitializer) -> Self { + let descriptors = i.descriptors(); + let (def_ep, eps) = i.endpoints(); + + debug!("Endpoints collected"); + + Self { + descriptors, + def_ep, + eps, + } + } + + pub(in super::super) fn ty(&self) -> (u8, u8, u8) { + for d in &self.descriptors { + if let Descriptor::Interface(i) = d { + return i.ty(); + } + } + + unreachable!("HID class must have at least one interface descriptor"); + } + + pub(in super::super) async fn issue_normal_trb( + &mut self, + b: &PageBox, + ty: EndpointType, + ) -> Result<(), Error> { + for ep in &mut self.eps { + if ep.ty() == ty { + ep.issue_normal_trb(b).await; + return Ok(()); + } + } + + Err(Error::NoSuchEndpoint(ty)) + } + + pub(in super::super) async fn issue_nop_trb(&mut self) { + self.def_ep.issue_nop_trb().await; + } + + pub(in super::super) async fn set_configure(&mut self, config_val: u8) { + self.def_ep.set_configuration(config_val).await; + } + + pub(in super::super) async fn set_idle(&mut self) { + self.def_ep.set_idle().await; + } + + pub(in super::super) async fn set_boot_protocol(&mut self) { + self.def_ep.set_boot_protocol().await; + } + + pub(in super::super) fn descriptors(&self) -> &[Descriptor] { + &self.descriptors + } +} +impl<'a> IntoIterator for &'a mut FullyOperational { + type Item = &'a mut NonDefault; + type IntoIter = slice::IterMut<'a, NonDefault>; + + fn into_iter(self) -> Self::IntoIter { + self.eps.iter_mut() + } +} diff --git a/test/src/port/init/max_packet_size_setter.rs b/test/src/port/init/max_packet_size_setter.rs new file mode 100644 index 00000000..ea465a71 --- /dev/null +++ b/test/src/port/init/max_packet_size_setter.rs @@ -0,0 +1,72 @@ +use super::{ + descriptor_fetcher::DescriptorFetcher, slot_structures_initializer::SlotStructuresInitializer, +}; +use crate::{exchanger, port::endpoint, structures::context::Context}; +use alloc::sync::Arc; +use spinning_top::Spinlock; + +pub(super) struct MaxPacketSizeSetter { + ep: endpoint::Default, + cx: Arc>, + port_number: u8, + slot_number: u8, +} +impl MaxPacketSizeSetter { + pub(super) fn new(i: SlotStructuresInitializer) -> Self { + let cx = i.context(); + let port_number = i.port_number(); + let slot_number = i.slot_number(); + let ep = i.ep0(); + + Self { + ep, + cx, + port_number, + slot_number, + } + } + + pub(super) async fn set(mut self) -> DescriptorFetcher { + let s = self.max_packet_size().await; + self.set_max_packet_size(s); + self.evaluate_context().await; + + DescriptorFetcher::new(self) + } + + pub(super) fn port_number(&self) -> u8 { + self.port_number + } + + pub(super) fn slot_number(&self) -> u8 { + self.slot_number + } + + pub(super) fn context(&self) -> Arc> { + self.cx.clone() + } + + pub(super) fn ep0(self) -> endpoint::Default { + self.ep + } + + async fn max_packet_size(&mut self) -> u16 { + self.ep.get_max_packet_size().await + } + + fn set_max_packet_size(&mut self, s: u16) { + let mut cx = self.cx.lock(); + let ep_0 = cx.input.device_mut().endpoint_mut(1); + + ep_0.set_max_packet_size(s); + } + + async fn evaluate_context(&self) { + let mut cx = self.cx.lock(); + let i = &mut cx.input; + + i.control_mut().set_add_context_flag(1); + + exchanger::command::evaluate_context(i.phys_addr(), self.slot_number).await + } +} diff --git a/test/src/port/init/mod.rs b/test/src/port/init/mod.rs new file mode 100644 index 00000000..95130585 --- /dev/null +++ b/test/src/port/init/mod.rs @@ -0,0 +1,18 @@ +use fully_operational::FullyOperational; +use resetter::Resetter; + +mod descriptor_fetcher; +mod endpoints_initializer; +pub(super) mod fully_operational; +mod max_packet_size_setter; +mod resetter; +mod slot_structures_initializer; + +pub(super) async fn init(port_number: u8) -> FullyOperational { + let resetter = Resetter::new(port_number); + let slot_structures_initializer = resetter.reset().await; + let max_packet_size_setter = slot_structures_initializer.init().await; + let descriptor_fetcher = max_packet_size_setter.set().await; + let endpoints_initializer = descriptor_fetcher.fetch().await; + endpoints_initializer.init().await +} diff --git a/test/src/port/init/resetter.rs b/test/src/port/init/resetter.rs new file mode 100644 index 00000000..dcbb17ab --- /dev/null +++ b/test/src/port/init/resetter.rs @@ -0,0 +1,56 @@ +use super::slot_structures_initializer::SlotStructuresInitializer; +use crate::structures::registers; +use xhci::registers::PortRegisterSet; + +pub(super) struct Resetter { + port_number: u8, +} +impl Resetter { + pub(super) fn new(port_number: u8) -> Self { + Self { port_number } + } + + pub(super) fn port_number(&self) -> u8 { + self.port_number + } + + pub(super) async fn reset(self) -> SlotStructuresInitializer { + self.start_resetting(); + self.wait_until_reset_is_completed(); + SlotStructuresInitializer::new(self).await + } + + fn start_resetting(&self) { + self.update_port_register(|r| { + r.portsc.set_port_reset(); + }); + } + + fn wait_until_reset_is_completed(&self) { + while !self.reset_completed() {} + } + + fn reset_completed(&self) -> bool { + self.read_port_register(|r| r.portsc.port_reset_change()) + } + + fn read_port_register(&self, f: T) -> U + where + T: FnOnce(&PortRegisterSet) -> U, + { + registers::handle(|r| { + f(&r.port_register_set + .read_volatile_at((self.port_number - 1).into())) + }) + } + + fn update_port_register(&self, f: T) + where + T: FnOnce(&mut PortRegisterSet), + { + registers::handle(|r| { + r.port_register_set + .update_volatile_at((self.port_number - 1).into(), f) + }) + } +} diff --git a/test/src/port/init/slot_structures_initializer.rs b/test/src/port/init/slot_structures_initializer.rs new file mode 100644 index 00000000..a3a6ec64 --- /dev/null +++ b/test/src/port/init/slot_structures_initializer.rs @@ -0,0 +1,150 @@ +use super::{max_packet_size_setter::MaxPacketSizeSetter, resetter::Resetter}; +use crate::{ + exchanger, + port::endpoint, + structures::{context::Context, dcbaa, registers}, +}; +use alloc::sync::Arc; +use exchanger::{transfer, transfer::DoorbellWriter}; +use spinning_top::Spinlock; +use xhci::context::EndpointType; + +pub(super) struct SlotStructuresInitializer { + port_number: u8, + slot_number: u8, + cx: Arc>, + ep: endpoint::Default, +} +impl SlotStructuresInitializer { + pub(super) async fn new(r: Resetter) -> Self { + let slot_number = exchanger::command::enable_device_slot().await; + let cx = Arc::new(Spinlock::new(Context::default())); + let dbl_writer = DoorbellWriter::new(slot_number, 1); + + Self { + port_number: r.port_number(), + slot_number, + cx, + ep: endpoint::Default::new(transfer::Sender::new(dbl_writer)), + } + } + + pub(super) async fn init(self) -> MaxPacketSizeSetter { + self.init_input_context(); + self.init_endpoint0_context(); + self.register_with_dcbaa(); + self.issue_address_device().await; + + MaxPacketSizeSetter::new(self) + } + + pub(super) fn port_number(&self) -> u8 { + self.port_number + } + + pub(super) fn slot_number(&self) -> u8 { + self.slot_number + } + + pub(super) fn context(&self) -> Arc> { + self.cx.clone() + } + + pub(super) fn ep0(self) -> endpoint::Default { + self.ep + } + + fn init_input_context(&self) { + InputContextInitializer::new(&mut self.cx.lock(), self.port_number).init() + } + + fn init_endpoint0_context(&self) { + Ep0ContextInitializer::new(&mut self.cx.lock(), self.port_number, &self.ep).init() + } + + fn register_with_dcbaa(&self) { + let a = self.cx.lock().output.phys_addr(); + dcbaa::register_device_context_addr(self.slot_number.into(), a); + } + + async fn issue_address_device(&self) { + let cx_addr = self.cx.lock().input.phys_addr(); + exchanger::command::address_device(cx_addr, self.slot_number).await; + } +} + +struct InputContextInitializer<'a> { + context: &'a mut Context, + port_number: u8, +} +impl<'a> InputContextInitializer<'a> { + fn new(context: &'a mut Context, port_number: u8) -> Self { + Self { + context, + port_number, + } + } + + fn init(&mut self) { + self.init_input_control(); + self.init_input_slot(); + } + + fn init_input_control(&mut self) { + let input_control = self.context.input.control_mut(); + input_control.set_add_context_flag(0); + input_control.set_add_context_flag(1); + } + + fn init_input_slot(&mut self) { + let slot = self.context.input.device_mut().slot_mut(); + slot.set_context_entries(1); + slot.set_root_hub_port_number(self.port_number); + } +} + +struct Ep0ContextInitializer<'a> { + cx: &'a mut Context, + port_number: u8, + ep: &'a endpoint::Default, +} +impl<'a> Ep0ContextInitializer<'a> { + fn new(cx: &'a mut Context, port_number: u8, ep: &'a endpoint::Default) -> Self { + Self { + cx, + port_number, + ep, + } + } + + fn init(self) { + let s = self.get_max_packet_size(); + let ep_0 = self.cx.input.device_mut().endpoint_mut(1); + + ep_0.set_endpoint_type(EndpointType::Control); + ep_0.set_max_packet_size(s); + ep_0.set_tr_dequeue_pointer(self.ep.ring_addr().as_u64()); + ep_0.set_dequeue_cycle_state(); + ep_0.set_error_count(3); + } + + // TODO: This function does not check the actual port speed, instead it uses the normal + // correspondence between PSI and the port speed. + // The actual port speed is listed on the xHCI supported protocol capability. + // Check the capability and fetch the actual port speed. Then return the max packet size. + fn get_max_packet_size(&self) -> u16 { + let psi = registers::handle(|r| { + r.port_register_set + .read_volatile_at((self.port_number - 1).into()) + .portsc + .port_speed() + }); + + match psi { + 1 | 3 => 64, + 2 => 8, + 4 => 512, + _ => unimplemented!("PSI: {}", psi), + } + } +} diff --git a/test/src/port/mod.rs b/test/src/port/mod.rs new file mode 100644 index 00000000..b48538d7 --- /dev/null +++ b/test/src/port/mod.rs @@ -0,0 +1,110 @@ +use super::structures::registers; +use crate::multitask::{self, task::Task}; +use alloc::collections::VecDeque; +use conquer_once::spin::Lazy; +use core::{future::Future, pin::Pin, task::Poll}; +use futures_util::task::AtomicWaker; +use init::fully_operational::FullyOperational; +use log::{info, warn}; +use qemu_exit::{QEMUExit, X86}; +use qemu_print::qemu_println; +use spinning_top::Spinlock; +use uefi::table::cfg::HAND_OFF_BLOCK_LIST_GUID; + +mod class_driver; +mod endpoint; +mod init; +mod spawner; + +static CURRENT_RESET_PORT: Lazy> = + Lazy::new(|| Spinlock::new(ResetPort::new())); + +struct ResetPort { + resetting: bool, + wakers: VecDeque, +} +impl ResetPort { + fn new() -> Self { + Self { + resetting: false, + wakers: VecDeque::new(), + } + } + + fn complete_reset(&mut self) { + self.resetting = false; + if let Some(w) = self.wakers.pop_front() { + w.wake(); + } + } + + fn resettable(&mut self, waker: AtomicWaker) -> bool { + if self.resetting { + self.wakers.push_back(waker); + false + } else { + self.resetting = true; + true + } + } +} + +pub(crate) fn try_spawn(port_idx: u8) -> Result<(), spawner::PortNotConnected> { + spawner::try_spawn(port_idx) +} + +async fn main(port_number: u8) { + qemu_println!("Port {} is connected.", port_number); + + let mut fully_operational = init_port_and_slot_exclusively(port_number).await; + + fully_operational.issue_nop_trb().await; + + qemu_println!("Port {} is fully operational.", port_number); + + let exit_handler = X86::new(0xf4, 33); + + exit_handler.exit_success(); +} + +async fn init_port_and_slot_exclusively(port_number: u8) -> FullyOperational { + let reset_waiter = ResetWaiterFuture; + reset_waiter.await; + + let fully_operational = init::init(port_number).await; + CURRENT_RESET_PORT.lock().complete_reset(); + info!("Port {} reset completed.", port_number); + fully_operational +} + +pub(crate) fn spawn_all_connected_port_tasks() { + spawner::spawn_all_connected_ports(); +} + +fn max_num() -> u8 { + registers::handle(|r| r.capability.hcsparams1.read_volatile().number_of_ports()) +} + +fn connected(port_number: u8) -> bool { + registers::handle(|r| { + r.port_register_set + .read_volatile_at((port_number - 1).into()) + .portsc + .current_connect_status() + }) +} + +struct ResetWaiterFuture; +impl Future for ResetWaiterFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + let waker = AtomicWaker::new(); + waker.register(cx.waker()); + if CURRENT_RESET_PORT.lock().resettable(waker) { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} diff --git a/test/src/port/spawner.rs b/test/src/port/spawner.rs new file mode 100644 index 00000000..23c579f0 --- /dev/null +++ b/test/src/port/spawner.rs @@ -0,0 +1,46 @@ +use crate::multitask; +use alloc::collections::BTreeSet; +use multitask::task::Task; +use spinning_top::Spinlock; + +static SPAWN_STATUS: Spinlock> = Spinlock::new(BTreeSet::new()); + +pub(crate) fn spawn_all_connected_ports() { + let n = super::max_num(); + for i in 0..n { + let _ = try_spawn(i + 1); + } +} + +pub(crate) fn try_spawn(port_number: u8) -> Result<(), PortNotConnected> { + if spawnable(port_number) { + spawn(port_number); + Ok(()) + } else { + Err(PortNotConnected) + } +} + +fn spawn(p: u8) { + mark_as_spawned(p); + add_task_for_port(p); +} + +fn add_task_for_port(p: u8) { + multitask::add(Task::new(super::main(p))); +} + +fn spawnable(p: u8) -> bool { + super::connected(p) && !spawned(p) +} + +fn spawned(p: u8) -> bool { + SPAWN_STATUS.lock().contains(&p.into()) +} + +fn mark_as_spawned(p: u8) { + SPAWN_STATUS.lock().insert(p.into()); +} + +#[derive(Debug)] +pub(crate) struct PortNotConnected; diff --git a/test/src/structures/context.rs b/test/src/structures/context.rs new file mode 100644 index 00000000..4b30a706 --- /dev/null +++ b/test/src/structures/context.rs @@ -0,0 +1,76 @@ +use crate::page_box::PageBox; + +use super::registers; +use alloc::boxed::Box; +use x86_64::PhysAddr; +use xhci::context::{ + Device32Byte, Device64Byte, DeviceHandler, Input32Byte, Input64Byte, InputControlHandler, + InputHandler, +}; + +pub(crate) struct Context { + pub(crate) input: Input, + pub(crate) output: PageBox, +} +impl Default for Context { + fn default() -> Self { + Self { + input: Input::default(), + output: Device::default().into(), + } + } +} + +pub(crate) enum Input { + Byte64(PageBox), + Byte32(PageBox), +} +impl Input { + pub(crate) fn control_mut(&mut self) -> &mut dyn InputControlHandler { + match self { + Self::Byte32(b32) => b32.control_mut(), + Self::Byte64(b64) => b64.control_mut(), + } + } + + pub(crate) fn device_mut(&mut self) -> &mut dyn DeviceHandler { + match self { + Self::Byte32(b32) => b32.device_mut(), + Self::Byte64(b64) => b64.device_mut(), + } + } + + pub(crate) fn phys_addr(&self) -> PhysAddr { + match self { + Self::Byte32(b32) => b32.phys_addr(), + Self::Byte64(b64) => b64.phys_addr(), + } + } +} +impl Default for Input { + fn default() -> Self { + if csz() { + Self::Byte64(Input64Byte::default().into()) + } else { + Self::Byte32(Input32Byte::default().into()) + } + } +} + +pub(crate) enum Device { + Byte64(Box), + Byte32(Box), +} +impl Default for Device { + fn default() -> Self { + if csz() { + Self::Byte64(Device64Byte::default().into()) + } else { + Self::Byte32(Device32Byte::default().into()) + } + } +} + +fn csz() -> bool { + registers::handle(|r| r.capability.hccparams1.read_volatile().context_size()) +} diff --git a/test/src/structures/dcbaa.rs b/test/src/structures/dcbaa.rs new file mode 100644 index 00000000..6565950e --- /dev/null +++ b/test/src/structures/dcbaa.rs @@ -0,0 +1,48 @@ +use crate::page_box::PageBox; + +use super::registers; +use conquer_once::spin::OnceCell; +use core::ops::DerefMut; +use spinning_top::Spinlock; +use x86_64::PhysAddr; + +static DCBAA: OnceCell>> = OnceCell::uninit(); + +pub fn init() { + DCBAA.init_once(|| Spinlock::new(PageBox::new_slice(PhysAddr::zero(), array_len()))); + + registers::handle(|r| { + r.operational.dcbaap.update_volatile(|d| { + d.set(lock().phys_addr().as_u64()); + }) + }) +} + +pub fn register_device_context_addr(port_id: usize, a: PhysAddr) { + assert_ne!(port_id, 0, "A port ID must be greater than 0."); + + lock()[port_id] = a; +} + +pub fn register_scratchpad_addr(a: PhysAddr) { + lock()[0] = a; +} + +fn lock() -> impl DerefMut> { + DCBAA + .try_get() + .expect("`DCBAA` is not initialized.") + .try_lock() + .expect("Failed to lock `DCBAA`.") +} + +fn array_len() -> usize { + registers::handle(|r| { + r.capability + .hcsparams1 + .read_volatile() + .number_of_device_slots() + + 1 + }) + .into() +} diff --git a/test/src/structures/descriptor.rs b/test/src/structures/descriptor.rs new file mode 100644 index 00000000..a76c4c61 --- /dev/null +++ b/test/src/structures/descriptor.rs @@ -0,0 +1,160 @@ +use bit_field::BitField; +use core::{convert::TryInto, ptr}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use xhci::context::EndpointType; + +#[derive(Copy, Clone, Debug)] +pub(crate) enum Descriptor { + Device(Device), + Configuration(Configuration), + Str, + Interface(Interface), + Endpoint(Endpoint), + Hid, +} +impl Descriptor { + pub(crate) fn from_slice(raw: &[u8]) -> Result { + assert_eq!(raw.len(), raw[0].into()); + match FromPrimitive::from_u8(raw[1]) { + Some(t) => { + let raw: *const [u8] = raw; + match t { + // SAFETY: This operation is safe because the length of `raw` is equivalent to the + // one of the descriptor. + Ty::Device => Ok(Self::Device(unsafe { ptr::read(raw.cast()) })), + Ty::Configuration => Ok(Self::Configuration(unsafe { ptr::read(raw.cast()) })), + Ty::Str => Ok(Self::Str), + Ty::Interface => Ok(Self::Interface(unsafe { ptr::read(raw.cast()) })), + Ty::Endpoint => Ok(Self::Endpoint(unsafe { ptr::read(raw.cast()) })), + Ty::Hid => Ok(Self::Hid), + } + } + None => Err(Error::UnrecognizedType(raw[1])), + } + } +} + +#[derive(Copy, Clone, Default, Debug)] +#[repr(C, packed)] +pub(crate) struct Device { + len: u8, + descriptor_type: u8, + cd_usb: u16, + class: u8, + subclass: u8, + protocol: u8, + max_packet_size0: u8, + vendor: u16, + product_id: u16, + device: u16, + manufacture: u8, + product: u8, + serial_number: u8, + num_configurations: u8, +} +impl Device { + pub(crate) fn max_packet_size(&self) -> u16 { + if let (3, _) = self.version() { + 2_u16.pow(self.max_packet_size0.into()) + } else { + self.max_packet_size0.into() + } + } + + fn version(&self) -> (u8, u8) { + let cd_usb = self.cd_usb; + + ( + (cd_usb >> 8).try_into().unwrap(), + (cd_usb & 0xff).try_into().unwrap(), + ) + } +} + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C, packed)] +pub(crate) struct Configuration { + length: u8, + ty: u8, + total_length: u16, + num_interfaces: u8, + config_val: u8, + config_string: u8, + attributes: u8, + max_power: u8, +} +impl Configuration { + pub(crate) fn config_val(&self) -> u8 { + self.config_val + } +} + +#[derive(Copy, Clone, Default, Debug)] +#[repr(C, packed)] +pub(crate) struct Interface { + len: u8, + descriptor_type: u8, + interface_number: u8, + alternate_setting: u8, + num_endpoints: u8, + interface_class: u8, + interface_subclass: u8, + interface_protocol: u8, + interface: u8, +} +impl Interface { + pub(crate) fn ty(&self) -> (u8, u8, u8) { + ( + self.interface_class, + self.interface_subclass, + self.interface_protocol, + ) + } +} + +#[derive(Copy, Clone, Default, Debug)] +#[repr(C, packed)] +pub(crate) struct Endpoint { + len: u8, + descriptor_type: u8, + pub(crate) endpoint_address: u8, + pub(crate) attributes: u8, + pub(crate) max_packet_size: u16, + pub(crate) interval: u8, +} +impl Endpoint { + pub(crate) fn ty(self) -> EndpointType { + EndpointType::from_u8(if self.attributes == 0 { + 4 + } else { + self.attributes.get_bits(0..=1) + + if self.endpoint_address.get_bit(7) { + 4 + } else { + 0 + } + }) + .expect("EndpointType must be convertible from `attributes` and `endpoint_address`.") + } + + pub(crate) fn doorbell_value(self) -> u32 { + 2 * u32::from(self.endpoint_address.get_bits(0..=3)) + + self.endpoint_address.get_bit(7) as u32 + } +} + +#[derive(FromPrimitive)] +pub(crate) enum Ty { + Device = 1, + Configuration = 2, + Str = 3, + Interface = 4, + Endpoint = 5, + Hid = 33, +} + +#[derive(Debug)] +pub(crate) enum Error { + UnrecognizedType(u8), +} diff --git a/test/src/structures/extended_capabilities.rs b/test/src/structures/extended_capabilities.rs new file mode 100644 index 00000000..f457c7aa --- /dev/null +++ b/test/src/structures/extended_capabilities.rs @@ -0,0 +1,40 @@ +use super::registers; +use crate::mapper::Mapper; +use conquer_once::spin::OnceCell; +use core::convert::TryInto; +use spinning_top::Spinlock; +use x86_64::PhysAddr; +use xhci::{extended_capabilities, ExtendedCapability}; + +static EXTENDED_CAPABILITIES: OnceCell>>> = + OnceCell::uninit(); + +/// # Safety +/// +/// `mmio_base` must be the correct one. +pub(crate) unsafe fn init(mmio_base: PhysAddr) { + let hccparams1 = registers::handle(|r| r.capability.hccparams1.read_volatile()); + + EXTENDED_CAPABILITIES + .try_init_once(|| { + Spinlock::new(extended_capabilities::List::new( + mmio_base.as_u64().try_into().unwrap(), + hccparams1, + Mapper, + )) + }) + .expect("Failed to initialize `EXTENDED_CAPABILITIES`."); +} + +pub(crate) fn iter() -> Option< + impl Iterator, extended_capabilities::NotSupportedId>>, +> { + Some( + EXTENDED_CAPABILITIES + .try_get() + .expect("`EXTENDED_CAPABILITIES` is not initialized.`") + .lock() + .as_mut()? + .into_iter(), + ) +} diff --git a/test/src/structures/mod.rs b/test/src/structures/mod.rs new file mode 100644 index 00000000..6093081f --- /dev/null +++ b/test/src/structures/mod.rs @@ -0,0 +1,7 @@ +pub(crate) mod context; +pub(crate) mod dcbaa; +pub(crate) mod descriptor; +pub(super) mod extended_capabilities; +pub(super) mod registers; +pub(crate) mod ring; +pub(crate) mod scratchpad; diff --git a/test/src/structures/registers.rs b/test/src/structures/registers.rs new file mode 100644 index 00000000..c12d3f90 --- /dev/null +++ b/test/src/structures/registers.rs @@ -0,0 +1,35 @@ +use crate::mapper::Mapper; +use conquer_once::spin::OnceCell; +use core::convert::TryInto; +use spinning_top::Spinlock; +use x86_64::PhysAddr; +use xhci::Registers; + +static REGISTERS: OnceCell>> = OnceCell::uninit(); + +/// # Safety +/// +/// `mmio_base` must be the correct one. +pub(crate) unsafe fn init(mmio_base: PhysAddr) { + let mmio_base: usize = mmio_base.as_u64().try_into().unwrap(); + + REGISTERS + .try_init_once(|| Spinlock::new(Registers::new(mmio_base, Mapper))) + .expect("Failed to initialize `REGISTERS`.") +} + +/// Handle xHCI registers. +/// +/// To avoid deadlocking, this method takes a closure. Caller is supposed not to call this method +/// inside the closure, otherwise a deadlock will happen. +/// +/// Alternative implementation is to define a method which returns `impl Deref`, but this will expand the scope of the mutex guard, increasing the possibility of +/// deadlocks. +pub(crate) fn handle(f: T) -> U +where + T: FnOnce(&mut Registers) -> U, +{ + let mut r = REGISTERS.try_get().unwrap().lock(); + f(&mut r) +} diff --git a/test/src/structures/ring/command/mod.rs b/test/src/structures/ring/command/mod.rs new file mode 100644 index 00000000..efd9d4f4 --- /dev/null +++ b/test/src/structures/ring/command/mod.rs @@ -0,0 +1,141 @@ +use super::CycleBit; +use crate::{page_box::PageBox, registers}; +use trb::Link; +use x86_64::{ + structures::paging::{PageSize, Size4KiB}, + PhysAddr, +}; +use xhci::ring::{trb, trb::command}; + +#[allow(clippy::cast_possible_truncation)] +const NUM_OF_TRBS: usize = Size4KiB::SIZE as usize / trb::BYTES; + +pub(crate) struct Ring { + raw: Raw, +} +impl Ring { + pub(crate) fn new() -> Self { + Self { raw: Raw::new() } + } + + pub(crate) fn init(&mut self) { + Initializer::new(self).init(); + } + + pub(crate) fn enqueue(&mut self, trb: command::Allowed) -> PhysAddr { + let a = self.raw.enqueue(trb); + Self::notify_command_is_sent(); + a + } + + fn phys_addr(&self) -> PhysAddr { + self.raw.head_addr() + } + + fn notify_command_is_sent() { + registers::handle(|r| { + r.doorbell.update_volatile_at(0, |r| { + r.set_doorbell_target(0); + }); + }) + } +} +impl Default for Ring { + fn default() -> Self { + Self::new() + } +} + +struct Raw { + raw: PageBox<[[u32; 4]]>, + enq_p: usize, + c: CycleBit, +} +impl Raw { + fn new() -> Self { + Self { + raw: PageBox::new_slice([0; 4], NUM_OF_TRBS), + enq_p: 0, + c: CycleBit::new(true), + } + } + + fn enqueue(&mut self, mut trb: command::Allowed) -> PhysAddr { + self.set_cycle_bit(&mut trb); + self.write_trb(trb); + let trb_a = self.enq_addr(); + self.increment(); + trb_a + } + + fn write_trb(&mut self, trb: command::Allowed) { + self.raw[self.enq_p] = trb.into_raw(); + } + + fn increment(&mut self) { + self.enq_p += 1; + if !self.enq_p_within_ring() { + self.enq_link(); + self.move_enq_p_to_the_beginning(); + } + } + + fn enq_p_within_ring(&self) -> bool { + self.enq_p < self.len() - 1 + } + + fn enq_link(&mut self) { + // Don't call `enqueue`. It will return an `Err` value as there is no space for link TRB. + let t = *Link::default().set_ring_segment_pointer(self.head_addr().as_u64()); + let mut t = command::Allowed::Link(t); + self.set_cycle_bit(&mut t); + self.raw[self.enq_p] = t.into_raw(); + } + + fn move_enq_p_to_the_beginning(&mut self) { + self.enq_p = 0; + self.c.toggle(); + } + + fn enq_addr(&self) -> PhysAddr { + self.head_addr() + trb::BYTES * self.enq_p + } + + fn head_addr(&self) -> PhysAddr { + self.raw.phys_addr() + } + + fn len(&self) -> usize { + self.raw.len() + } + + fn set_cycle_bit(&self, trb: &mut command::Allowed) { + if self.c == CycleBit::new(true) { + trb.set_cycle_bit(); + } else { + trb.clear_cycle_bit(); + } + } +} + +struct Initializer<'a> { + ring: &'a Ring, +} +impl<'a> Initializer<'a> { + fn new(ring: &'a Ring) -> Self { + Self { ring } + } + + fn init(&mut self) { + registers::handle(|r| { + let a = self.ring.phys_addr(); + + // Do not split this closure to avoid read-modify-write bug. Reading fields may return + // 0, this will cause writing 0 to fields. + r.operational.crcr.update_volatile(|c| { + c.set_command_ring_pointer(a.as_u64()); + c.set_ring_cycle_state(); + }); + }) + } +} diff --git a/test/src/structures/ring/event/mod.rs b/test/src/structures/ring/event/mod.rs new file mode 100644 index 00000000..eb2c6dbd --- /dev/null +++ b/test/src/structures/ring/event/mod.rs @@ -0,0 +1,282 @@ +use super::CycleBit; +use crate::{exchanger::receiver, page_box::PageBox, port, structures::registers}; +use alloc::vec::Vec; +use bit_field::BitField; +use conquer_once::spin::OnceCell; +use core::{ + convert::TryInto, + pin::Pin, + task::{Context, Poll}, +}; +use futures_util::{stream::Stream, StreamExt}; +use log::{debug, info, warn}; +use qemu_print::qemu_println; +use segment_table::SegmentTable; +use spinning_top::Spinlock; +use x86_64::{ + structures::paging::{PageSize, Size4KiB}, + PhysAddr, +}; +use xhci::ring::{ + trb, + trb::event::{self, CompletionCode}, +}; + +mod segment_table; + +static EVENT_RING: OnceCell> = OnceCell::uninit(); + +pub fn init() { + let ring = Spinlock::new(Ring::new()); + ring.lock().init(); + + EVENT_RING + .try_init_once(|| ring) + .expect("`EVENT_RING` is initialized more than once."); +} + +pub(crate) async fn task() { + debug!("This is the Event ring task."); + + while let Some(trb) = EVENT_RING + .get() + .expect("The event ring is not initialized") + .try_lock() + .expect("Failed to lock the event ring.") + .next() + .await + { + if let event::Allowed::CommandCompletion(x) = trb { + assert_eq!(x.completion_code(), Ok(CompletionCode::Success)); + + receiver::receive(trb); + } else if let event::Allowed::TransferEvent(x) = trb { + assert_eq!(x.completion_code(), Ok(CompletionCode::Success)); + + receiver::receive(trb); + } else if let event::Allowed::PortStatusChange(p) = trb { + let _ = port::try_spawn(p.port_id()); + } + } +} + +#[allow(clippy::cast_possible_truncation)] +const MAX_NUM_OF_TRB_IN_QUEUE: u16 = Size4KiB::SIZE as u16 / trb::BYTES as u16; + +pub(crate) struct Ring { + segment_table: SegmentTable, + raw: Raw, +} +impl Ring { + pub(crate) fn new() -> Self { + let max_num_of_erst = registers::handle(|r| { + r.capability + .hcsparams2 + .read_volatile() + .event_ring_segment_table_max() + }); + + Self { + segment_table: SegmentTable::new(max_num_of_erst.into()), + raw: Raw::new(), + } + } + + pub(crate) fn init(&mut self) { + self.init_dequeue_ptr(); + self.init_tbl(); + } + + fn init_dequeue_ptr(&mut self) { + self.raw.update_deq_p_with_xhci() + } + + fn phys_addr_to_segment_table(&self) -> PhysAddr { + self.segment_table.phys_addr() + } + + fn init_tbl(&mut self) { + SegTblInitializer::new(self).init(); + } + + fn try_dequeue(&mut self) -> Option { + self.raw.try_dequeue() + } + + fn ring_addrs(&self) -> Vec { + self.raw.head_addrs() + } + + fn iter_tbl_entries_mut(&mut self) -> impl Iterator { + self.segment_table.iter_mut() + } +} +impl Stream for Ring { + type Item = event::Allowed; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Pin::into_inner(self) + .try_dequeue() + .map_or_else(|| Poll::Pending, |trb| Poll::Ready(Some(trb))) + } +} + +struct Raw { + rings: Vec>, + c: CycleBit, + deq_p_seg: usize, + deq_p_trb: usize, +} +impl Raw { + fn new() -> Self { + let rings = Self::new_rings(); + Self { + rings, + c: CycleBit::new(true), + deq_p_seg: 0, + deq_p_trb: 0, + } + } + + fn new_rings() -> Vec> { + let mut v = Vec::new(); + for _ in 0..Self::max_num_of_erst() { + v.push(PageBox::new_slice([0; 4], MAX_NUM_OF_TRB_IN_QUEUE.into())); + } + + v + } + + fn max_num_of_erst() -> u16 { + registers::handle(|r| { + r.capability + .hcsparams2 + .read_volatile() + .event_ring_segment_table_max() + }) + } + + fn try_dequeue(&mut self) -> Option { + if self.empty() { + None + } else { + self.dequeue() + } + } + + fn empty(&self) -> bool { + self.c_bit_of_next_trb() != self.c + } + + fn c_bit_of_next_trb(&self) -> CycleBit { + let t = self.rings[self.deq_p_seg][self.deq_p_trb]; + CycleBit::new(t[3].get_bit(0)) + } + + fn dequeue(&mut self) -> Option { + let t = self.get_next_trb().ok(); + self.increment(); + t + } + + fn get_next_trb(&self) -> Result { + let r = self.rings[self.deq_p_seg][self.deq_p_trb]; + let t = r.try_into(); + if t.is_err() { + warn!("Unrecognized ID: {}", r[3].get_bits(10..=15)); + } + t + } + + fn increment(&mut self) { + self.deq_p_trb += 1; + if self.deq_p_trb >= MAX_NUM_OF_TRB_IN_QUEUE.into() { + self.deq_p_trb = 0; + self.deq_p_seg += 1; + + if self.deq_p_seg >= self.num_of_erst() { + self.deq_p_seg = 0; + self.c.toggle(); + } + } + } + + fn num_of_erst(&self) -> usize { + self.rings.len() + } + + fn update_deq_p_with_xhci(&self) { + registers::handle(|r| { + let _ = &self; + + r.interrupter_register_set + .interrupter_mut(0) + .erdp + .update_volatile(|r| { + r.set_event_ring_dequeue_pointer(self.next_trb_addr().as_u64()) + }); + }); + } + + fn next_trb_addr(&self) -> PhysAddr { + self.rings[self.deq_p_seg].phys_addr() + trb::BYTES * self.deq_p_trb + } + + fn head_addrs(&self) -> Vec { + self.rings.iter().map(PageBox::phys_addr).collect() + } +} + +struct SegTblInitializer<'a> { + ring: &'a mut Ring, +} +impl<'a> SegTblInitializer<'a> { + fn new(ring: &'a mut Ring) -> Self { + Self { ring } + } + + fn init(&mut self) { + self.write_addrs(); + self.register_tbl_sz(); + self.enable_event_ring(); + } + + fn write_addrs(&mut self) { + let addrs = self.ring.ring_addrs(); + for (entry, addr) in self.ring.iter_tbl_entries_mut().zip(addrs) { + entry.set(addr, MAX_NUM_OF_TRB_IN_QUEUE); + } + } + + fn register_tbl_sz(&mut self) { + registers::handle(|r| { + let l = self.tbl_len(); + + r.interrupter_register_set + .interrupter_mut(0) + .erstsz + .update_volatile(|r| r.set(l.try_into().unwrap())); + }) + } + + fn enable_event_ring(&mut self) { + registers::handle(|r| { + let a = self.tbl_addr(); + + r.interrupter_register_set + .interrupter_mut(0) + .erstba + .update_volatile(|r| { + r.set(a.as_u64()); + }) + }); + } + + fn tbl_addr(&self) -> PhysAddr { + self.ring.phys_addr_to_segment_table() + } + + fn tbl_len(&self) -> usize { + self.ring.segment_table.len() + } +} diff --git a/test/src/structures/ring/event/segment_table.rs b/test/src/structures/ring/event/segment_table.rs new file mode 100644 index 00000000..dfae168a --- /dev/null +++ b/test/src/structures/ring/event/segment_table.rs @@ -0,0 +1,70 @@ +use core::{ + ops::{Index, IndexMut}, + slice, +}; +use qemu_print::qemu_println; +use x86_64::PhysAddr; + +use crate::page_box::PageBox; + +#[derive(Debug)] +pub struct SegmentTable(PageBox<[Entry]>); +impl SegmentTable { + pub fn new(len: usize) -> Self { + qemu_println!("SegmentTable::new({})", len); + Self(PageBox::new_slice(Entry::null(), len)) + } + + pub fn phys_addr(&self) -> PhysAddr { + self.0.phys_addr() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } +} +impl Index for SegmentTable { + type Output = Entry; + + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} +impl IndexMut for SegmentTable { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} +impl<'a> IntoIterator for &'a mut SegmentTable { + type Item = &'a mut Entry; + type IntoIter = slice::IterMut<'a, Entry>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +#[repr(C, packed)] +#[derive(Copy, Clone, Debug)] +pub struct Entry { + base_address: u64, + segment_size: u64, +} +impl Entry { + // Although the size of segment_size is u64, bits 16:63 are reserved. + pub fn set(&mut self, addr: PhysAddr, size: u16) { + self.base_address = addr.as_u64(); + self.segment_size = size.into(); + } + + fn null() -> Self { + Self { + base_address: 0, + segment_size: 0, + } + } +} diff --git a/test/src/structures/ring/mod.rs b/test/src/structures/ring/mod.rs new file mode 100644 index 00000000..e33f4772 --- /dev/null +++ b/test/src/structures/ring/mod.rs @@ -0,0 +1,20 @@ +pub(crate) mod command; +pub(crate) mod event; +pub(crate) mod transfer; + +#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] +pub struct CycleBit(bool); +impl CycleBit { + pub fn new(val: bool) -> Self { + Self(val) + } + + fn toggle(&mut self) { + self.0 = !self.0; + } +} +impl From for bool { + fn from(cycle_bit: CycleBit) -> Self { + cycle_bit.0 + } +} diff --git a/test/src/structures/ring/transfer/mod.rs b/test/src/structures/ring/transfer/mod.rs new file mode 100644 index 00000000..4c90bb0e --- /dev/null +++ b/test/src/structures/ring/transfer/mod.rs @@ -0,0 +1,99 @@ +use super::CycleBit; +use crate::page_box::PageBox; +use alloc::vec::Vec; +use trb::Link; +use x86_64::PhysAddr; +use xhci::ring::{trb, trb::transfer}; + +const SIZE_OF_RING: usize = 256; + +pub(crate) struct Ring { + raw: Raw, +} +impl Ring { + pub(crate) fn new() -> Self { + Self { raw: Raw::new() } + } + + pub(crate) fn phys_addr(&self) -> PhysAddr { + self.raw.phys_addr() + } + + pub(crate) fn enqueue(&mut self, trbs: &[transfer::Allowed]) -> Vec { + self.raw.enqueue_trbs(trbs) + } +} + +struct Raw { + ring: PageBox<[[u32; 4]]>, + enq_p: usize, + c: CycleBit, +} +impl Raw { + fn new() -> Self { + Self { + ring: PageBox::new_slice([0; 4], SIZE_OF_RING), + enq_p: 0, + c: CycleBit::new(true), + } + } + + fn enqueue_trbs(&mut self, trbs: &[transfer::Allowed]) -> Vec { + trbs.iter().map(|t| self.enqueue(*t)).collect() + } + + fn enqueue(&mut self, mut trb: transfer::Allowed) -> PhysAddr { + self.set_cycle_bit(&mut trb); + self.write_trb_on_memory(trb); + let addr_to_trb = self.addr_to_enqueue_ptr(); + self.increment_enqueue_ptr(); + + addr_to_trb + } + + fn write_trb_on_memory(&mut self, trb: transfer::Allowed) { + self.ring[self.enq_p] = trb.into_raw(); + } + + fn addr_to_enqueue_ptr(&self) -> PhysAddr { + self.phys_addr() + trb::BYTES * self.enq_p + } + + fn phys_addr(&self) -> PhysAddr { + self.ring.phys_addr() + } + + fn increment_enqueue_ptr(&mut self) { + self.enq_p += 1; + if self.enq_p < self.len() - 1 { + return; + } + + self.append_link_trb(); + self.move_enqueue_ptr_to_the_beginning(); + } + + fn len(&self) -> usize { + self.ring.len() + } + + fn append_link_trb(&mut self) { + let t = *Link::default().set_ring_segment_pointer(self.phys_addr().as_u64()); + let mut t = transfer::Allowed::Link(t); + self.set_cycle_bit(&mut t); + self.ring[self.enq_p] = t.into_raw(); + } + + fn move_enqueue_ptr_to_the_beginning(&mut self) { + self.enq_p = 0; + self.c.toggle(); + } + + fn set_cycle_bit(&self, trb: &mut transfer::Allowed) { + if self.c == CycleBit::new(true) { + trb.set_cycle_bit(); + } else { + trb.clear_cycle_bit(); + } + } +} diff --git a/test/src/structures/scratchpad.rs b/test/src/structures/scratchpad.rs new file mode 100644 index 00000000..c2f93a36 --- /dev/null +++ b/test/src/structures/scratchpad.rs @@ -0,0 +1,91 @@ +use super::dcbaa; +use crate::page_box::PageBox; +use crate::registers; +use alloc::vec::Vec; +use conquer_once::spin::OnceCell; +use core::alloc::Layout; +use core::convert::TryInto; +use os_units::Bytes; +use x86_64::PhysAddr; + +static SCRATCHPAD: OnceCell = OnceCell::uninit(); + +pub(crate) fn init() { + if Scratchpad::needed() { + init_static(); + } +} + +fn init_static() { + let mut scratchpad = Scratchpad::new(); + scratchpad.init(); + scratchpad.register_with_dcbaa(); + + SCRATCHPAD.init_once(|| scratchpad) +} + +struct Scratchpad { + arr: PageBox<[PhysAddr]>, + bufs: Vec>, +} +impl Scratchpad { + fn new() -> Self { + let len: usize = Self::num_of_buffers().try_into().unwrap(); + + Self { + arr: PageBox::new_slice(PhysAddr::zero(), len), + bufs: Vec::new(), + } + } + + fn needed() -> bool { + Self::num_of_buffers() > 0 + } + + fn init(&mut self) { + self.allocate_buffers(); + self.write_buffer_addresses(); + } + + fn register_with_dcbaa(&self) { + dcbaa::register_device_context_addr(0, self.arr.phys_addr()); + } + + fn allocate_buffers(&mut self) { + let layout = + Layout::from_size_align(Self::page_size().as_usize(), Self::page_size().as_usize()); + let layout = layout.unwrap_or_else(|_| { + panic!( + "Failed to create a layout for {} bytes with {} bytes alignment", + Self::page_size().as_usize(), + Self::page_size().as_usize() + ) + }); + + for _ in 0..Self::num_of_buffers() { + let b = PageBox::from_layout_zeroed(layout); + + self.bufs.push(b); + } + } + + fn write_buffer_addresses(&mut self) { + let page_size: u64 = Self::page_size().as_usize().try_into().unwrap(); + for (x, buf) in self.arr.iter_mut().zip(self.bufs.iter()) { + *x = buf.phys_addr().align_up(page_size); + } + } + + fn num_of_buffers() -> u32 { + registers::handle(|r| { + r.capability + .hcsparams2 + .read_volatile() + .max_scratchpad_buffers() + }) + } + + fn page_size() -> Bytes { + Bytes::new(registers::handle(|r| r.operational.pagesize.read_volatile().get()).into()) + } +} diff --git a/test/src/xhc.rs b/test/src/xhc.rs new file mode 100644 index 00000000..420e8b36 --- /dev/null +++ b/test/src/xhc.rs @@ -0,0 +1,132 @@ +use super::structures::{extended_capabilities, registers}; +use crate::{ + exchanger, + structures::{dcbaa, ring::event, scratchpad}, +}; +use xhci::extended_capabilities::ExtendedCapability; + +pub(super) fn exists() -> bool { + super::iter_xhc().next().is_some() +} + +/// Initializes the host controller according to 4.2 of the xHCI specification. +pub(crate) fn init() { + stop_and_reset(); + set_num_of_enabled_slots(); + + dcbaa::init(); + scratchpad::init(); + exchanger::command::init(); + event::init(); + + run(); + ensure_no_error_occurs(); +} + +pub(crate) fn run() { + registers::handle(|r| { + let o = &mut r.operational; + o.usbcmd.update_volatile(|u| { + u.set_run_stop(); + }); + while o.usbsts.read_volatile().hc_halted() {} + }); +} + +pub(crate) fn ensure_no_error_occurs() { + registers::handle(|r| { + let s = r.operational.usbsts.read_volatile(); + + assert!(!s.hc_halted(), "HC is halted."); + assert!( + !s.host_system_error(), + "An error occured on the host system." + ); + assert!(!s.host_controller_error(), "An error occured on the xHC."); + }); +} + +pub(crate) fn get_ownership_from_bios() { + if let Some(iter) = extended_capabilities::iter() { + for c in iter.filter_map(Result::ok) { + if let ExtendedCapability::UsbLegacySupport(mut u) = c { + let l = &mut u.usblegsup; + l.update_volatile(|s| { + s.set_hc_os_owned_semaphore(); + }); + + while l.read_volatile().hc_bios_owned_semaphore() + || !l.read_volatile().hc_os_owned_semaphore() + {} + } + } + } +} + +fn stop_and_reset() { + stop(); + wait_until_halt(); + reset(); +} + +fn stop() { + registers::handle(|r| { + r.operational.usbcmd.update_volatile(|u| { + u.clear_run_stop(); + }); + }) +} + +fn wait_until_halt() { + registers::handle(|r| while !r.operational.usbsts.read_volatile().hc_halted() {}) +} + +fn reset() { + start_resetting(); + wait_until_reset_completed(); + wait_until_ready(); +} + +fn start_resetting() { + registers::handle(|r| { + r.operational.usbcmd.update_volatile(|u| { + u.set_host_controller_reset(); + }) + }) +} + +fn wait_until_reset_completed() { + registers::handle( + |r| { + while r.operational.usbcmd.read_volatile().host_controller_reset() {} + }, + ) +} + +fn wait_until_ready() { + registers::handle( + |r| { + while r.operational.usbsts.read_volatile().controller_not_ready() {} + }, + ) +} + +fn set_num_of_enabled_slots() { + // We choose the maximum number of device slots for simplicity. + let n = num_of_max_device_slots(); + + registers::handle(|r| { + r.operational.config.update_volatile(|c| { + c.set_max_device_slots_enabled(n); + }); + }) +} + +fn num_of_max_device_slots() -> u8 { + registers::handle(|r| { + r.capability + .hcsparams1 + .read_volatile() + .number_of_device_slots() + }) +} diff --git a/test/x86_64-unknown-ramen.json b/test/x86_64-unknown-ramen.json new file mode 100644 index 00000000..5ceb8be5 --- /dev/null +++ b/test/x86_64-unknown-ramen.json @@ -0,0 +1,22 @@ +{ + "arch": "x86_64", + "":"See http://llvm.org/docs/LangRef.html#data-layout to know what data-layout represents.", + "data-layout": "e-m:e-i64:64-n8:16:32:64-S128", + "llvm-target": "x86_64-unknown-none", + "executables": true, + "features": "-sse,+soft-float", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "code-model": "kernel", + "relocation-model": "static", + "archive-format": "gnu", + "target-env": "gnu", + "no-compiler-rt": false, + "panic-strategy": "abort", + "linker-flavor": "ld", + "linker-is-gnu": true, + "disable-redzone": true, + "eliminate-frame-pointer": false +}