Compare commits
7 commits
c1ab41bbad
...
592b5b468f
| Author | SHA1 | Date | |
|---|---|---|---|
| 592b5b468f | |||
| 8b022a6b24 | |||
| d5d7e2c7ab | |||
| 22b43de6dc | |||
| b9cd550a63 | |||
| 1a48911745 | |||
| 311755c812 |
71 changed files with 3751 additions and 199 deletions
42
.forgejo/workflows/ci.yml
Normal file
42
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
name: Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Should speed up builds.
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
|
# Should reduce the size of ./target to improve cache load/store.
|
||||||
|
CARGO_PROFILE_TEST_DEBUG: 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check Rust
|
||||||
|
runs-on: docker
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: https://codeberg.org/wackbyte/rust-toolchain@trunk
|
||||||
|
with:
|
||||||
|
toolchain: nightly-2025-10-02
|
||||||
|
components: rustfmt, clippy, rust-src
|
||||||
|
- name: Cache
|
||||||
|
uses: https://github.com/Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
# Don't cache ~/.cargo/bin since we restore the cache after we install things there
|
||||||
|
cache-bin: "false"
|
||||||
|
workspaces: "backend"
|
||||||
|
- name: "Check Format"
|
||||||
|
run: cargo fmt --check
|
||||||
|
working-directory: rust
|
||||||
|
- name: "Lint"
|
||||||
|
run: |
|
||||||
|
rustup component add clippy
|
||||||
|
cargo clippy --locked -- -D warnings
|
||||||
|
working-directory: rust
|
||||||
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2025-10-02"
|
||||||
|
components = ["rustfmt", "rust-analyzer", "clippy", "rust-src"]
|
||||||
70
rust/Cargo.lock
generated
70
rust/Cargo.lock
generated
|
|
@ -2,12 +2,6 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "autocfg"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitfield-struct"
|
name = "bitfield-struct"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -19,6 +13,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitfield-struct"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8769c4854c5ada2852ddf6fd09d15cf43d4c2aaeccb4de6432f5402f08a6003b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
|
@ -32,7 +37,7 @@ dependencies = [
|
||||||
name = "denali"
|
name = "denali"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfield-struct",
|
"bitfield-struct 0.8.0",
|
||||||
"mammoth",
|
"mammoth",
|
||||||
"pci",
|
"pci",
|
||||||
"yellowstone-yunq",
|
"yellowstone-yunq",
|
||||||
|
|
@ -69,11 +74,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -88,15 +92,15 @@ dependencies = [
|
||||||
name = "pci"
|
name = "pci"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfield-struct",
|
"bitfield-struct 0.8.0",
|
||||||
"mammoth",
|
"mammoth",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.20"
|
version = "0.2.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn",
|
"syn",
|
||||||
|
|
@ -104,18 +108,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.86"
|
version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.36"
|
version = "1.0.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
@ -137,9 +141,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.72"
|
version = "2.0.111"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
|
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -161,21 +165,21 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mammoth",
|
"mammoth",
|
||||||
"victoriafalls",
|
"victoriafalls",
|
||||||
"voyageurs",
|
"voyageurs_client",
|
||||||
"yellowstone-yunq",
|
"yellowstone-yunq",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.12"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.11.0"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "victoriafalls"
|
name = "victoriafalls"
|
||||||
|
|
@ -189,9 +193,26 @@ dependencies = [
|
||||||
"yunqc",
|
"yunqc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "volatile"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af8ca9a5d4debca0633e697c88269395493cebf2e10db21ca2dbde37c1356452"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voyageurs"
|
name = "voyageurs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bitfield-struct 0.12.1",
|
||||||
|
"mammoth",
|
||||||
|
"pci",
|
||||||
|
"volatile",
|
||||||
|
"yellowstone-yunq",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "voyageurs_client"
|
||||||
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mammoth",
|
"mammoth",
|
||||||
"yellowstone-yunq",
|
"yellowstone-yunq",
|
||||||
|
|
@ -206,7 +227,6 @@ dependencies = [
|
||||||
"denali_client",
|
"denali_client",
|
||||||
"mammoth",
|
"mammoth",
|
||||||
"victoriafalls",
|
"victoriafalls",
|
||||||
"voyageurs",
|
|
||||||
"yellowstone-yunq",
|
"yellowstone-yunq",
|
||||||
"yunq",
|
"yunq",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"lib/client/denali_client", "lib/fs/ext2",
|
"lib/client/denali_client",
|
||||||
"lib/mammoth", "lib/pci",
|
"lib/client/voyageurs_client",
|
||||||
"lib/voyageurs",
|
"lib/fs/ext2",
|
||||||
|
"lib/mammoth",
|
||||||
|
"lib/pci",
|
||||||
"lib/yellowstone",
|
"lib/yellowstone",
|
||||||
"lib/yunq",
|
"lib/yunq",
|
||||||
"lib/yunq-test",
|
"lib/yunq-test",
|
||||||
"sys/denali",
|
"sys/denali",
|
||||||
"sys/teton",
|
"sys/teton",
|
||||||
"sys/victoriafalls",
|
"sys/victoriafalls",
|
||||||
|
"sys/voyageurs",
|
||||||
"sys/yellowstone",
|
"sys/yellowstone",
|
||||||
"usr/testbed",
|
"usr/testbed",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
12
rust/lib/client/voyageurs_client/Cargo.toml
Normal file
12
rust/lib/client/voyageurs_client/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "voyageurs_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mammoth = { path = "../../mammoth" }
|
||||||
|
yellowstone-yunq = { path = "../../yellowstone" }
|
||||||
|
yunq = { path = "../../yunq" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
yunqc = { path = "../../../../yunq/rust" }
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let input_file = "../../../sys/voyageurs/lib/voyageurs/voyageurs.yunq";
|
let input_file = "../../../../sys/voyageurs/lib/voyageurs/voyageurs.yunq";
|
||||||
|
|
||||||
println!("cargo::rerun-if-changed={input_file}");
|
println!("cargo::rerun-if-changed={input_file}");
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ use mammoth::thread;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||||
enum Keycode {
|
enum Keycode {
|
||||||
UnknownKeycode = 0x0,
|
Unknown = 0x0,
|
||||||
|
|
||||||
A = 0x1,
|
A = 0x1,
|
||||||
B = 0x2,
|
B = 0x2,
|
||||||
|
|
@ -135,7 +135,7 @@ impl Keycode {
|
||||||
0x37 => Keycode::Period,
|
0x37 => Keycode::Period,
|
||||||
0x38 => Keycode::FSlash,
|
0x38 => Keycode::FSlash,
|
||||||
0x39 => Keycode::Esc,
|
0x39 => Keycode::Esc,
|
||||||
_ => Keycode::UnknownKeycode,
|
_ => Keycode::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ impl Ext2Driver {
|
||||||
/// Updates the cached inode tables to contain the inode table for
|
/// Updates the cached inode tables to contain the inode table for
|
||||||
/// a specific group.
|
/// a specific group.
|
||||||
fn populate_inode_table_if_none(&mut self, block_group_num: usize) {
|
fn populate_inode_table_if_none(&mut self, block_group_num: usize) {
|
||||||
if let None = self.inode_table_map[block_group_num] {
|
if self.inode_table_map[block_group_num].is_none() {
|
||||||
debug!(
|
debug!(
|
||||||
"Cache MISS on inode table for block_group {}",
|
"Cache MISS on inode table for block_group {}",
|
||||||
block_group_num
|
block_group_num
|
||||||
|
|
@ -148,17 +148,15 @@ impl Ext2Driver {
|
||||||
let dbl_indr_block_mem =
|
let dbl_indr_block_mem =
|
||||||
MemoryRegion::from_cap(self.reader.read(block_num, 1).unwrap()).unwrap();
|
MemoryRegion::from_cap(self.reader.read(block_num, 1).unwrap()).unwrap();
|
||||||
|
|
||||||
let dbl_indr_blocks: &[u32] = dbl_indr_block_mem.slice();
|
let dbl_indr_blocks: &[u32] = &dbl_indr_block_mem.slice()[0..num_dbl_indr];
|
||||||
|
|
||||||
let mut blocks_to_read = Vec::new();
|
let mut blocks_to_read = Vec::new();
|
||||||
|
|
||||||
for i in 0..num_dbl_indr {
|
for (i, dbl_indr_block) in dbl_indr_blocks.iter().enumerate() {
|
||||||
let num_blocks_in_single = min(num_blocks - (256 * i), 256);
|
let num_blocks_in_single = min(num_blocks - (256 * i), 256);
|
||||||
blocks_to_read.append(
|
blocks_to_read.append(
|
||||||
&mut self.get_blocks_from_single_indirect(
|
&mut self
|
||||||
dbl_indr_blocks[i] as u64,
|
.get_blocks_from_single_indirect(*dbl_indr_block as u64, num_blocks_in_single),
|
||||||
num_blocks_in_single,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +174,7 @@ impl Ext2Driver {
|
||||||
|
|
||||||
let mut blocks = Vec::new();
|
let mut blocks = Vec::new();
|
||||||
|
|
||||||
while let Some(block) = iter.next() {
|
for block in iter {
|
||||||
if block as u64 == (curr_block.lba + curr_block.size) {
|
if block as u64 == (curr_block.lba + curr_block.size) {
|
||||||
curr_block.size += 1;
|
curr_block.size += 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,9 @@ pub struct Inode {
|
||||||
|
|
||||||
const _: () = assert!(size_of::<Inode>() == 128);
|
const _: () = assert!(size_of::<Inode>() == 128);
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const EXT2_FT_FILE: u8 = 0x1;
|
pub const EXT2_FT_FILE: u8 = 0x1;
|
||||||
|
#[allow(dead_code)]
|
||||||
pub const EXT2_FT_DIR: u8 = 0x2;
|
pub const EXT2_FT_DIR: u8 = 0x2;
|
||||||
|
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use core::ffi::c_void;
|
||||||
|
|
||||||
use crate::zion::{self, z_cap_t, ZError};
|
use crate::zion::{self, z_cap_t, ZError};
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn syscall<T>(id: u64, req: &T) -> Result<(), ZError> {
|
fn syscall<T>(id: u64, req: &T) -> Result<(), ZError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let resp = zion::SysCall1(id, req as *const T as *const c_void);
|
let resp = zion::SysCall1(id, req as *const T as *const c_void);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::cap::Capability;
|
use crate::cap::Capability;
|
||||||
use crate::debug;
|
|
||||||
use crate::init;
|
use crate::init;
|
||||||
use crate::syscall;
|
use crate::syscall;
|
||||||
use crate::zion::ZError;
|
use crate::zion::ZError;
|
||||||
|
|
@ -237,11 +236,9 @@ fn load_program_segment(
|
||||||
let page_offset = prog_header.vaddr & 0xFFF;
|
let page_offset = prog_header.vaddr & 0xFFF;
|
||||||
let mem_size = page_offset + prog_header.mem_size;
|
let mem_size = page_offset + prog_header.mem_size;
|
||||||
|
|
||||||
let mem_object = crate::mem::MemoryRegion::new(mem_size)?;
|
let mut mem_object = crate::mem::MemoryRegion::new(mem_size)?;
|
||||||
|
|
||||||
for i in mem_object.mut_slice() {
|
mem_object.zero_region();
|
||||||
*i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let file_start = prog_header.offset as usize;
|
let file_start = prog_header.offset as usize;
|
||||||
let file_end = file_start + prog_header.file_size as usize;
|
let file_end = file_start + prog_header.file_size as usize;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub fn parse_init_port(port_cap: z_cap_t) {
|
||||||
let mut caps: [u64; 1] = [0];
|
let mut caps: [u64; 1] = [0];
|
||||||
|
|
||||||
let resp = syscall::port_poll(&init_port, &mut bytes, &mut caps);
|
let resp = syscall::port_poll(&init_port, &mut bytes, &mut caps);
|
||||||
if let Err(_) = resp {
|
if resp.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ mod cap_syscall;
|
||||||
pub mod elf;
|
pub mod elf;
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
|
pub mod physical_box;
|
||||||
pub mod port;
|
pub mod port;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,20 @@ use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Writer {
|
pub struct Writer {
|
||||||
int_vec: Vec<u8>,
|
int_vec: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Writer {
|
impl Writer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Writer::default()
|
||||||
int_vec: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<String> for Writer {
|
impl From<Writer> for String {
|
||||||
fn into(self) -> String {
|
fn from(value: Writer) -> Self {
|
||||||
String::from_utf8(self.int_vec).expect("Failed to convert")
|
String::from_utf8(value.int_vec).expect("Failed to convert")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::syscall;
|
||||||
use crate::zion::ZError;
|
use crate::zion::ZError;
|
||||||
use alloc::slice;
|
use alloc::slice;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use core::ptr::{addr_of, addr_of_mut};
|
use core::ptr::{addr_of, addr_of_mut, read_volatile, write_volatile, NonNull};
|
||||||
|
|
||||||
#[cfg(feature = "hosted")]
|
#[cfg(feature = "hosted")]
|
||||||
use linked_list_allocator::LockedHeap;
|
use linked_list_allocator::LockedHeap;
|
||||||
|
|
@ -29,6 +29,7 @@ pub fn init_heap() {
|
||||||
pub struct MemoryRegion {
|
pub struct MemoryRegion {
|
||||||
mem_cap: Capability,
|
mem_cap: Capability,
|
||||||
virt_addr: u64,
|
virt_addr: u64,
|
||||||
|
// TODO: This should be a usize probably.
|
||||||
size: u64,
|
size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +77,10 @@ impl MemoryRegion {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vaddr(&self) -> usize {
|
||||||
|
self.virt_addr as usize
|
||||||
|
}
|
||||||
|
|
||||||
pub fn slice<T>(&self) -> &[T] {
|
pub fn slice<T>(&self) -> &[T] {
|
||||||
unsafe {
|
unsafe {
|
||||||
slice::from_raw_parts(
|
slice::from_raw_parts(
|
||||||
|
|
@ -85,7 +90,7 @@ impl MemoryRegion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mut_slice<T>(&self) -> &mut [T] {
|
pub fn mut_slice<T>(&mut self) -> &mut [T] {
|
||||||
unsafe {
|
unsafe {
|
||||||
slice::from_raw_parts_mut(
|
slice::from_raw_parts_mut(
|
||||||
self.virt_addr as *mut T,
|
self.virt_addr as *mut T,
|
||||||
|
|
@ -94,13 +99,52 @@ impl MemoryRegion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn zero_region(&mut self) {
|
||||||
|
for i in self.mut_slice() {
|
||||||
|
*i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn raw_ptr_at_offset<T>(&self, offset: u64) -> *const T {
|
pub fn raw_ptr_at_offset<T>(&self, offset: u64) -> *const T {
|
||||||
// TODO: Come up with a better safety check here.
|
|
||||||
// We can't use the size of T because it might not be sized.
|
|
||||||
assert!(offset + size_of::<T>() as u64 <= self.size);
|
assert!(offset + size_of::<T>() as u64 <= self.size);
|
||||||
(self.virt_addr + offset) as *const T
|
(self.virt_addr + offset) as *const T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mut_ptr_at_offset<T>(&self, offset: usize) -> *mut T {
|
||||||
|
assert!(offset + size_of::<T>() <= self.size as usize);
|
||||||
|
(self.virt_addr as usize + offset) as *mut T
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a reference from a given offset.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// - Caller must ensure that the memory pointed to by this
|
||||||
|
/// pointer must not get mutated while the reference exists.
|
||||||
|
pub unsafe fn as_ref_at_offset<T>(&self, offset: usize) -> &T {
|
||||||
|
let ptr: *const T = self.raw_ptr_at_offset(offset as u64);
|
||||||
|
assert!(ptr.is_aligned(), "");
|
||||||
|
// SAFETY:
|
||||||
|
// - We checked alignment.
|
||||||
|
// - self.vaddr + offset can't be null.
|
||||||
|
// - It is dereferenceable because it is entirely within this memory region.
|
||||||
|
&*self.raw_ptr_at_offset::<T>(offset as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a reference from a given offset.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// - Caller must ensure that this is the only reference to the memory pointed
|
||||||
|
/// to by this pointer.
|
||||||
|
pub unsafe fn as_mut_ref_at_offset<T>(&mut self, offset: usize) -> &mut T {
|
||||||
|
let ptr: *const T = self.raw_ptr_at_offset(offset as u64);
|
||||||
|
assert!(ptr.is_aligned(), "");
|
||||||
|
// SAFETY:
|
||||||
|
// - We checked alignment.
|
||||||
|
// - self.vaddr + offset can't be null.
|
||||||
|
// - It is dereferenceable because it is entirely within this memory region.
|
||||||
|
&mut *self.mut_ptr_at_offset::<T>(offset)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cap(&self) -> &Capability {
|
pub fn cap(&self) -> &Capability {
|
||||||
&self.mem_cap
|
&self.mem_cap
|
||||||
}
|
}
|
||||||
|
|
@ -137,28 +181,22 @@ impl Drop for MemoryRegion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Volatile<T> {
|
#[repr(transparent)]
|
||||||
/// TODO: This should maybe be MaybeUninit.
|
pub struct Volatile<T: Copy>(T);
|
||||||
data: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Volatile<T> {
|
impl<T: Copy> Volatile<T> {
|
||||||
pub fn read(&self) -> T
|
pub fn read(&self) -> T {
|
||||||
where
|
unsafe { read_volatile(addr_of!(self.0)) }
|
||||||
T: Copy,
|
|
||||||
{
|
|
||||||
unsafe { addr_of!(self.data).cast::<T>().read_volatile() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, data: T) {
|
pub fn write(&mut self, data: T) {
|
||||||
unsafe {
|
unsafe {
|
||||||
addr_of_mut!(self.data).cast::<T>().write_volatile(data);
|
write_volatile(addr_of_mut!(self.0), data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update<F>(&mut self, func: F)
|
pub fn update<F>(&mut self, func: F)
|
||||||
where
|
where
|
||||||
T: Copy,
|
|
||||||
F: Fn(&mut T),
|
F: Fn(&mut T),
|
||||||
{
|
{
|
||||||
let mut data = self.read();
|
let mut data = self.read();
|
||||||
|
|
@ -176,17 +214,49 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! read_unaligned_volatile {
|
||||||
|
($struct_ptr:expr, $field:ident) => {
|
||||||
|
unsafe {
|
||||||
|
let field_ptr = core::ptr::addr_of!((*$struct_ptr).$field);
|
||||||
|
core::ptr::read_volatile(field_ptr as *const _)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! write_unaligned_volatile {
|
||||||
|
($struct_ptr:expr, $field:ident, $value:expr) => {
|
||||||
|
unsafe {
|
||||||
|
let field_ptr = core::ptr::addr_of!((*$struct_ptr).$field);
|
||||||
|
core::ptr::write_volatile(field_ptr as *mut _, $value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! map_unaligned_volatile {
|
||||||
|
($struct_ptr:expr, $field:ident, $func:expr) => {
|
||||||
|
unsafe {
|
||||||
|
let field_ptr = core::ptr::addr_of!((*$struct_ptr).$field);
|
||||||
|
let value = core::ptr::read_volatile(field_ptr as *const _);
|
||||||
|
core::ptr::write_volatile(field_ptr as *mut _, ($func)(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map_cap_and_leak(mem_cap: Capability) -> u64 {
|
pub fn map_cap_and_leak(mem_cap: Capability) -> u64 {
|
||||||
let vaddr = syscall::address_space_map(&mem_cap).unwrap();
|
let vaddr = syscall::address_space_map(&mem_cap).unwrap();
|
||||||
mem_cap.release();
|
mem_cap.release();
|
||||||
vaddr
|
vaddr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_direct_physical_and_leak(paddr: u64, size: u64) -> u64 {
|
pub fn map_direct_physical_and_leak<T>(paddr: usize, size: usize) -> NonNull<T> {
|
||||||
let mem_cap = syscall::memory_object_direct_physical(paddr, size).unwrap();
|
let mem_cap = syscall::memory_object_direct_physical(paddr as u64, size as u64).unwrap();
|
||||||
let vaddr = syscall::address_space_map(&mem_cap).unwrap();
|
let vaddr = syscall::address_space_map(&mem_cap).unwrap();
|
||||||
mem_cap.release();
|
mem_cap.release();
|
||||||
vaddr
|
// UNWRAP: The kernel guarantees this is valid.
|
||||||
|
NonNull::new(vaddr as *mut T).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_physical_and_leak(size: u64) -> (u64, u64) {
|
pub fn map_physical_and_leak(size: u64) -> (u64, u64) {
|
||||||
|
|
|
||||||
159
rust/lib/mammoth/src/physical_box.rs
Normal file
159
rust/lib/mammoth/src/physical_box.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
use core::{
|
||||||
|
marker::PhantomData,
|
||||||
|
ops::{Deref, DerefMut, Index, IndexMut},
|
||||||
|
ptr::NonNull,
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::{boxed::Box, slice, vec::Vec};
|
||||||
|
|
||||||
|
use crate::mem::MemoryRegion;
|
||||||
|
|
||||||
|
pub struct PhysicalBox<T: ?Sized> {
|
||||||
|
data: NonNull<T>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
region: MemoryRegion,
|
||||||
|
physical_address: usize,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PhysicalBox<T> {
|
||||||
|
pub fn new(data: T) -> Self {
|
||||||
|
let (memory_region, paddr) =
|
||||||
|
MemoryRegion::contiguous_physical(size_of::<T>() as u64).expect("Failed to allocate");
|
||||||
|
// UNWRAP: We know this isn't null.
|
||||||
|
let ptr = NonNull::new(memory_region.mut_ptr_at_offset(0)).unwrap();
|
||||||
|
unsafe { ptr.write(data) };
|
||||||
|
Self {
|
||||||
|
data: ptr,
|
||||||
|
region: memory_region,
|
||||||
|
physical_address: paddr as usize,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> PhysicalBox<T> {
|
||||||
|
pub fn physical_address(&self) -> usize {
|
||||||
|
self.physical_address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Deref for PhysicalBox<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
// SAFETY:
|
||||||
|
// - Alignment: This is page aligned.
|
||||||
|
// - Dereferenceable: Guaranteed in same allocation.
|
||||||
|
// - Aliasing: The borrow rules ensure this
|
||||||
|
unsafe { self.data.as_ref() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> DerefMut for PhysicalBox<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
// SAFETY:
|
||||||
|
// - Alignment: This is page aligned.
|
||||||
|
// - Dereferenceable: Guaranteed in same allocation.
|
||||||
|
// - Aliasing: The borrow rules ensure this
|
||||||
|
unsafe { self.data.as_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PhysicalBox<[T]> {
|
||||||
|
pub fn default_with_count(default: T, len: usize) -> Self
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
let layout = core::alloc::Layout::array::<T>(len).expect("Layout overflow");
|
||||||
|
|
||||||
|
// TODO: Implement a function like alloc that takes a layout.
|
||||||
|
let (memory_region, paddr) =
|
||||||
|
MemoryRegion::contiguous_physical(layout.size() as u64).expect("Failed to allocate");
|
||||||
|
|
||||||
|
let ptr: *mut T = memory_region.mut_ptr_at_offset(0);
|
||||||
|
for i in 0..len {
|
||||||
|
unsafe {
|
||||||
|
ptr.add(i).write(default.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let slice_ptr = core::ptr::slice_from_raw_parts_mut(ptr, len);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// UNWRAP: We know this isn't null.
|
||||||
|
data: NonNull::new(slice_ptr).unwrap(),
|
||||||
|
region: memory_region,
|
||||||
|
physical_address: paddr as usize,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(vec: Vec<T>) -> Self {
|
||||||
|
let len = vec.len();
|
||||||
|
let layout = core::alloc::Layout::array::<T>(len).expect("Layout overflow");
|
||||||
|
|
||||||
|
// TODO: Implement a function like alloc that takes a layout.
|
||||||
|
let (memory_region, paddr) =
|
||||||
|
MemoryRegion::contiguous_physical(layout.size() as u64).expect("Failed to allocate");
|
||||||
|
|
||||||
|
let ptr: *mut T = memory_region.mut_ptr_at_offset(0);
|
||||||
|
for (i, item) in vec.into_iter().enumerate() {
|
||||||
|
unsafe {
|
||||||
|
ptr.add(i).write(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let slice_ptr = core::ptr::slice_from_raw_parts_mut(ptr, len);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// UNWRAP: We know this isn't null.
|
||||||
|
data: NonNull::new(slice_ptr).unwrap(),
|
||||||
|
region: memory_region,
|
||||||
|
physical_address: paddr as usize,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
(**self).len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, T> Index<I> for PhysicalBox<[T]>
|
||||||
|
where
|
||||||
|
I: slice::SliceIndex<[T]>,
|
||||||
|
{
|
||||||
|
type Output = I::Output;
|
||||||
|
|
||||||
|
fn index(&self, index: I) -> &Self::Output {
|
||||||
|
&(**self)[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, T> IndexMut<I> for PhysicalBox<[T]>
|
||||||
|
where
|
||||||
|
I: slice::SliceIndex<[T]>,
|
||||||
|
{
|
||||||
|
fn index_mut(&mut self, index: I) -> &mut Self::Output {
|
||||||
|
&mut (**self)[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SAFETY: We are the only owner of this pointer.
|
||||||
|
unsafe impl<T: ?Sized> Send for PhysicalBox<T> where Box<T>: Send {}
|
||||||
|
|
||||||
|
/// SAFETY: You must have a mutable reference to this
|
||||||
|
/// type to modify the data at the pointer.
|
||||||
|
unsafe impl<T: ?Sized> Sync for PhysicalBox<T> where Box<T>: Sync {}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Drop for PhysicalBox<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY:
|
||||||
|
// - We own this data.
|
||||||
|
unsafe { core::ptr::drop_in_place(self.data.as_ptr()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ impl<T> DerefMut for MutexGuard<'_, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Mutex<T> {
|
impl<'a, T> Mutex<T> {
|
||||||
pub fn new(data: T) -> Mutex<T> {
|
pub fn new(data: T) -> Mutex<T> {
|
||||||
Mutex {
|
Mutex {
|
||||||
cap: syscall::mutex_create().unwrap(),
|
cap: syscall::mutex_create().unwrap(),
|
||||||
|
|
@ -58,7 +58,7 @@ impl<T> Mutex<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lock(&self) -> MutexGuard<T> {
|
pub fn lock(&'a self) -> MutexGuard<'a, T> {
|
||||||
syscall::mutex_lock(&self.cap).unwrap();
|
syscall::mutex_lock(&self.cap).unwrap();
|
||||||
|
|
||||||
MutexGuard { mutex: self }
|
MutexGuard { mutex: self }
|
||||||
|
|
@ -70,3 +70,12 @@ impl<T> Drop for MutexGuard<'_, T> {
|
||||||
syscall::mutex_release(&self.mutex.cap).unwrap();
|
syscall::mutex_release(&self.mutex.cap).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Mutex<T>
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ use core::ffi::c_void;
|
||||||
#[cfg(feature = "hosted")]
|
#[cfg(feature = "hosted")]
|
||||||
use core::panic::PanicInfo;
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn syscall<T>(id: u64, req: &T) -> Result<(), ZError> {
|
fn syscall<T>(id: u64, req: &T) -> Result<(), ZError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let resp = zion::SysCall1(id, req as *const T as *const c_void);
|
let resp = zion::SysCall1(id, req as *const T as *const c_void);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ struct TaskWaker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskWaker {
|
impl TaskWaker {
|
||||||
fn new(task_id: TaskId, task_queue: Arc<Mutex<VecDeque<TaskId>>>) -> Waker {
|
fn create_waker(task_id: TaskId, task_queue: Arc<Mutex<VecDeque<TaskId>>>) -> Waker {
|
||||||
Waker::from(Arc::new(TaskWaker {
|
Waker::from(Arc::new(TaskWaker {
|
||||||
task_id,
|
task_id,
|
||||||
task_queue,
|
task_queue,
|
||||||
|
|
@ -69,6 +69,7 @@ impl Wake for TaskWaker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Executor {
|
pub struct Executor {
|
||||||
tasks: Arc<Mutex<BTreeMap<TaskId, Task>>>,
|
tasks: Arc<Mutex<BTreeMap<TaskId, Task>>>,
|
||||||
// TODO: Consider a better datastructure for this.
|
// TODO: Consider a better datastructure for this.
|
||||||
|
|
@ -78,11 +79,7 @@ pub struct Executor {
|
||||||
|
|
||||||
impl Executor {
|
impl Executor {
|
||||||
pub fn new() -> Executor {
|
pub fn new() -> Executor {
|
||||||
Executor {
|
Executor::default()
|
||||||
tasks: Arc::new(Mutex::new(BTreeMap::new())),
|
|
||||||
task_queue: Arc::new(Mutex::new(VecDeque::new())),
|
|
||||||
waker_cache: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(&mut self, task: Task) {
|
pub fn spawn(&mut self, task: Task) {
|
||||||
|
|
@ -100,7 +97,7 @@ impl Executor {
|
||||||
let waker = self
|
let waker = self
|
||||||
.waker_cache
|
.waker_cache
|
||||||
.entry(task_id)
|
.entry(task_id)
|
||||||
.or_insert_with(|| TaskWaker::new(task_id, self.task_queue.clone()));
|
.or_insert_with(|| TaskWaker::create_waker(task_id, self.task_queue.clone()));
|
||||||
let mut ctx = Context::from_waker(waker);
|
let mut ctx = Context::from_waker(waker);
|
||||||
match task.poll(&mut ctx) {
|
match task.poll(&mut ctx) {
|
||||||
Poll::Ready(()) => {
|
Poll::Ready(()) => {
|
||||||
|
|
@ -125,6 +122,7 @@ impl Executor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Spawner {
|
pub struct Spawner {
|
||||||
tasks: Arc<Mutex<BTreeMap<TaskId, Task>>>,
|
tasks: Arc<Mutex<BTreeMap<TaskId, Task>>>,
|
||||||
task_queue: Arc<Mutex<VecDeque<TaskId>>>,
|
task_queue: Arc<Mutex<VecDeque<TaskId>>>,
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ where
|
||||||
let raw_main = Box::into_raw(Box::new(main));
|
let raw_main = Box::into_raw(Box::new(main));
|
||||||
let proc_cap = Capability::take_copy(unsafe { crate::init::SELF_PROC_CAP }).unwrap();
|
let proc_cap = Capability::take_copy(unsafe { crate::init::SELF_PROC_CAP }).unwrap();
|
||||||
let cap = syscall::thread_create(&proc_cap).unwrap();
|
let cap = syscall::thread_create(&proc_cap).unwrap();
|
||||||
syscall::thread_start(&cap, entry_point as u64, raw_main as u64, 0).unwrap();
|
syscall::thread_start(&cap, entry_point as usize as u64, raw_main as u64, 0).unwrap();
|
||||||
|
|
||||||
JoinHandle { cap }
|
JoinHandle { cap }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ use alloc::vec::Vec;
|
||||||
use mammoth::{cap::Capability, mem::MemoryRegion, syscall, zion::ZError};
|
use mammoth::{cap::Capability, mem::MemoryRegion, syscall, zion::ZError};
|
||||||
|
|
||||||
use crate::header::{
|
use crate::header::{
|
||||||
PciCapabilityPointer, PciDeviceHeader, PciHeaderType, PciMsiCapability, PciMsiControl,
|
PciCapabilityPointer, PciDeviceHeader, PciHeaderType, PciMsiCapability, get_header_type,
|
||||||
get_header_type,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PciDevice {
|
pub struct PciDevice {
|
||||||
|
|
@ -11,7 +10,7 @@ pub struct PciDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PciDevice {
|
impl PciDevice {
|
||||||
pub fn from(mut memory_region: MemoryRegion) -> Result<Self, ZError> {
|
pub fn from(memory_region: MemoryRegion) -> Result<Self, ZError> {
|
||||||
match get_header_type(&memory_region)? {
|
match get_header_type(&memory_region)? {
|
||||||
PciHeaderType::Device => {}
|
PciHeaderType::Device => {}
|
||||||
t => {
|
t => {
|
||||||
|
|
@ -72,15 +71,15 @@ impl PciDevice {
|
||||||
control.capable_address_64(),
|
control.capable_address_64(),
|
||||||
"We don't handle the non-64bit case for MSI yet."
|
"We don't handle the non-64bit case for MSI yet."
|
||||||
);
|
);
|
||||||
assert!(
|
|
||||||
control.multi_message_capable() == 0,
|
if control.multi_message_capable() != 0 {
|
||||||
"We don't yet handle multi-message capable devices."
|
mammoth::debug!("WARN: We don't yet handle multi-message capable devices.");
|
||||||
);
|
}
|
||||||
|
|
||||||
// FIXME: These probably need to be volatile writes.
|
// FIXME: These probably need to be volatile writes.
|
||||||
let header: &mut PciDeviceHeader = self.memory_region.as_mut();
|
let header: &mut PciDeviceHeader = self.memory_region.as_mut();
|
||||||
header.command = header.command.with_interrupt_disable(true);
|
header.command = header.command.with_interrupt_disable(true);
|
||||||
msi_cap.msi_control = control.with_msi_enable(true);
|
msi_cap.msi_control = control.with_msi_enable(true).with_multi_message_enable(0);
|
||||||
|
|
||||||
// For setting addr and data field, see intel ref
|
// For setting addr and data field, see intel ref
|
||||||
// Vol 3. Section 11.11
|
// Vol 3. Section 11.11
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "voyageurs"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
mammoth = { path = "../mammoth" }
|
|
||||||
yellowstone-yunq = { path = "../yellowstone" }
|
|
||||||
yunq = {path = "../yunq"}
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
yunqc = {path = "../../../yunq/rust"}
|
|
||||||
|
|
||||||
|
|
@ -10,10 +10,12 @@ static mut YELLOWSTONE_INIT: Option<YellowstoneClient> = None;
|
||||||
|
|
||||||
pub fn from_init_endpoint() -> &'static mut YellowstoneClient {
|
pub fn from_init_endpoint() -> &'static mut YellowstoneClient {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let None = YELLOWSTONE_INIT {
|
#[allow(static_mut_refs)]
|
||||||
|
if YELLOWSTONE_INIT.is_none() {
|
||||||
YELLOWSTONE_INIT = Some(YellowstoneClient::new(Capability::take(INIT_ENDPOINT)));
|
YELLOWSTONE_INIT = Some(YellowstoneClient::new(Capability::take(INIT_ENDPOINT)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(static_mut_refs)]
|
||||||
YELLOWSTONE_INIT.as_mut().unwrap()
|
YELLOWSTONE_INIT.as_mut().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_serialization() -> Result<(), ZError> {
|
fn basic_serialization() -> Result<(), ZError> {
|
||||||
let basic = Basic { unsigned_int: 82, signed_int: -1234, strn: "abc".to_string() };
|
let basic = Basic {
|
||||||
|
unsigned_int: 82,
|
||||||
|
signed_int: -1234,
|
||||||
|
strn: "abc".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut buf = ByteBuffer::<1024>::new();
|
let mut buf = ByteBuffer::<1024>::new();
|
||||||
let mut caps = Vec::new();
|
let mut caps = Vec::new();
|
||||||
|
|
@ -27,7 +31,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_serialization_as_request() -> Result<(), ZError> {
|
fn basic_serialization_as_request() -> Result<(), ZError> {
|
||||||
let basic = Basic { unsigned_int: 82, signed_int: -1234, strn: "abc".to_string() };
|
let basic = Basic {
|
||||||
|
unsigned_int: 82,
|
||||||
|
signed_int: -1234,
|
||||||
|
strn: "abc".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut buf = ByteBuffer::<1024>::new();
|
let mut buf = ByteBuffer::<1024>::new();
|
||||||
let mut caps = Vec::new();
|
let mut caps = Vec::new();
|
||||||
|
|
@ -63,7 +71,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repeated_serialization() -> Result<(), ZError> {
|
fn repeated_serialization() -> Result<(), ZError> {
|
||||||
let rep = Repeated { unsigned_ints: vec![0, 1, 3],};
|
let rep = Repeated {
|
||||||
|
unsigned_ints: vec![0, 1, 3],
|
||||||
|
};
|
||||||
|
|
||||||
let mut buf = ByteBuffer::<1024>::new();
|
let mut buf = ByteBuffer::<1024>::new();
|
||||||
let mut caps = Vec::new();
|
let mut caps = Vec::new();
|
||||||
|
|
@ -79,9 +89,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_serialization() -> Result<(), ZError> {
|
fn nested_serialization() -> Result<(), ZError> {
|
||||||
let nested = Nested {
|
let nested = Nested {
|
||||||
basic: Basic { unsigned_int: 82, signed_int: -1234, strn: "abc".to_string() },
|
basic: Basic {
|
||||||
cap1: Cap { cap: 37},
|
unsigned_int: 82,
|
||||||
cap2: Cap { cap: 39},
|
signed_int: -1234,
|
||||||
|
strn: "abc".to_string(),
|
||||||
|
},
|
||||||
|
cap1: Cap { cap: 37 },
|
||||||
|
cap2: Cap { cap: 39 },
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = ByteBuffer::<1024>::new();
|
let mut buf = ByteBuffer::<1024>::new();
|
||||||
|
|
@ -90,7 +104,6 @@ mod tests {
|
||||||
|
|
||||||
let parsed = Nested::parse(&buf, 0, &caps)?;
|
let parsed = Nested::parse(&buf, 0, &caps)?;
|
||||||
|
|
||||||
|
|
||||||
assert!(parsed == nested);
|
assert!(parsed == nested);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -99,9 +112,19 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn repeated_nested_serialization() -> Result<(), ZError> {
|
fn repeated_nested_serialization() -> Result<(), ZError> {
|
||||||
let nested = RepeatedNested {
|
let nested = RepeatedNested {
|
||||||
basics: vec![Basic { unsigned_int: 82, signed_int: -1234, strn: "abc".to_string(),},
|
basics: vec![
|
||||||
Basic { unsigned_int: 21, signed_int: -8, strn: "def".to_string(), },],
|
Basic {
|
||||||
caps: vec![Cap{ cap: 123}, Cap {cap: 12343}],
|
unsigned_int: 82,
|
||||||
|
signed_int: -1234,
|
||||||
|
strn: "abc".to_string(),
|
||||||
|
},
|
||||||
|
Basic {
|
||||||
|
unsigned_int: 21,
|
||||||
|
signed_int: -8,
|
||||||
|
strn: "def".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
caps: vec![Cap { cap: 123 }, Cap { cap: 12343 }],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = ByteBuffer::<1024>::new();
|
let mut buf = ByteBuffer::<1024>::new();
|
||||||
|
|
@ -110,12 +133,8 @@ mod tests {
|
||||||
|
|
||||||
let parsed = RepeatedNested::parse(&buf, 0, &caps)?;
|
let parsed = RepeatedNested::parse(&buf, 0, &caps)?;
|
||||||
|
|
||||||
|
|
||||||
assert!(parsed == nested);
|
assert!(parsed == nested);
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,19 @@ pub struct ByteBuffer<const N: usize> {
|
||||||
buffer: Box<[u8; N]>,
|
buffer: Box<[u8; N]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> ByteBuffer<N> {
|
impl<const N: usize> Default for ByteBuffer<N> {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer: Box::new([0; N]),
|
buffer: Box::new([0; N]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> ByteBuffer<N> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ByteBuffer::default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> u64 {
|
pub fn size(&self) -> u64 {
|
||||||
N as u64
|
N as u64
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +61,7 @@ impl<const N: usize> ByteBuffer<N> {
|
||||||
if (len + offset) > N {
|
if (len + offset) > N {
|
||||||
return Err(ZError::BUFFER_SIZE);
|
return Err(ZError::BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
Ok(alloc::str::from_utf8(&self.buffer[offset..offset + len])
|
alloc::str::from_utf8(&self.buffer[offset..offset + len])
|
||||||
.map_err(|_| ZError::INVALID_ARGUMENT)?)
|
.map_err(|_| ZError::INVALID_ARGUMENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,5 +34,5 @@ pub fn call_endpoint<Req: YunqMessage, Resp: YunqMessage, const N: usize>(
|
||||||
return Err(ZError::from(resp_code));
|
return Err(ZError::from(resp_code));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Resp::parse_from_request(&byte_buffer, &cap_buffer)?)
|
Resp::parse_from_request(byte_buffer, &cap_buffer)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub fn parse_repeated_message<T: YunqMessage, const N: usize>(
|
||||||
buf: &ByteBuffer<N>,
|
buf: &ByteBuffer<N>,
|
||||||
mut offset: usize,
|
mut offset: usize,
|
||||||
len: usize,
|
len: usize,
|
||||||
caps: &Vec<z_cap_t>,
|
caps: &[z_cap_t],
|
||||||
) -> Result<Vec<T>, ZError> {
|
) -> Result<Vec<T>, ZError> {
|
||||||
let mut repeated = Vec::new();
|
let mut repeated = Vec::new();
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
|
|
@ -43,18 +43,18 @@ pub fn parse_repeated_message<T: YunqMessage, const N: usize>(
|
||||||
pub fn serialize_repeated<T: Copy, const N: usize>(
|
pub fn serialize_repeated<T: Copy, const N: usize>(
|
||||||
buf: &mut ByteBuffer<N>,
|
buf: &mut ByteBuffer<N>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
data: &Vec<T>,
|
data: &[T],
|
||||||
) -> Result<usize, ZError> {
|
) -> Result<usize, ZError> {
|
||||||
for i in 0..data.len() {
|
for (i, val) in data.iter().enumerate() {
|
||||||
buf.write_at(offset + (i * size_of::<T>()), data[i])?;
|
buf.write_at(offset + (i * size_of::<T>()), val)?;
|
||||||
}
|
}
|
||||||
Ok(offset + (data.len() * size_of::<T>()))
|
Ok(offset + size_of_val(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_repeated_message<T: YunqMessage, const N: usize>(
|
pub fn serialize_repeated_message<T: YunqMessage, const N: usize>(
|
||||||
buf: &mut ByteBuffer<N>,
|
buf: &mut ByteBuffer<N>,
|
||||||
mut offset: usize,
|
mut offset: usize,
|
||||||
data: &Vec<T>,
|
data: &[T],
|
||||||
caps: &mut Vec<z_cap_t>,
|
caps: &mut Vec<z_cap_t>,
|
||||||
) -> Result<usize, ZError> {
|
) -> Result<usize, ZError> {
|
||||||
for item in data {
|
for item in data {
|
||||||
|
|
@ -76,14 +76,14 @@ pub trait YunqMessage {
|
||||||
fn parse<const N: usize>(
|
fn parse<const N: usize>(
|
||||||
buf: &ByteBuffer<N>,
|
buf: &ByteBuffer<N>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
caps: &Vec<z_cap_t>,
|
caps: &[z_cap_t],
|
||||||
) -> Result<Self, ZError>
|
) -> Result<Self, ZError>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
|
||||||
fn parse_from_request<const N: usize>(
|
fn parse_from_request<const N: usize>(
|
||||||
buf: &ByteBuffer<N>,
|
buf: &ByteBuffer<N>,
|
||||||
caps: &Vec<z_cap_t>,
|
caps: &[z_cap_t],
|
||||||
) -> Result<Self, ZError>
|
) -> Result<Self, ZError>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
|
@ -92,7 +92,7 @@ pub trait YunqMessage {
|
||||||
return Err(ZError::INVALID_RESPONSE);
|
return Err(ZError::INVALID_RESPONSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::parse(&buf, 16, &caps)?)
|
Self::parse(buf, 16, caps)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize<const N: usize>(
|
fn serialize<const N: usize>(
|
||||||
|
|
@ -109,7 +109,7 @@ pub trait YunqMessage {
|
||||||
caps: &mut Vec<z_cap_t>,
|
caps: &mut Vec<z_cap_t>,
|
||||||
) -> Result<usize, ZError> {
|
) -> Result<usize, ZError> {
|
||||||
buf.write_at(0, SENTINEL)?;
|
buf.write_at(0, SENTINEL)?;
|
||||||
buf.write_at(8, request_id as u64)?;
|
buf.write_at(8, request_id)?;
|
||||||
|
|
||||||
let length = self.serialize(buf, 16, caps)?;
|
let length = self.serialize(buf, 16, caps)?;
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ impl YunqMessage for Empty {
|
||||||
fn parse<const N: usize>(
|
fn parse<const N: usize>(
|
||||||
_buf: &ByteBuffer<N>,
|
_buf: &ByteBuffer<N>,
|
||||||
_offset: usize,
|
_offset: usize,
|
||||||
_caps: &Vec<z_cap_t>,
|
_caps: &[z_cap_t],
|
||||||
) -> Result<Self, ZError>
|
) -> Result<Self, ZError>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ pub trait YunqServer {
|
||||||
.expect("Failed to reply"),
|
.expect("Failed to reply"),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
crate::message::serialize_error(&mut byte_buffer, err);
|
crate::message::serialize_error(&mut byte_buffer, err);
|
||||||
syscall::reply_port_send(reply_port_cap, &byte_buffer.slice(0x10), &[])
|
syscall::reply_port_send(reply_port_cap, byte_buffer.slice(0x10), &[])
|
||||||
.expect("Failed to reply w/ error")
|
.expect("Failed to reply w/ error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,11 +84,11 @@ where
|
||||||
.at::<u64>(8)
|
.at::<u64>(8)
|
||||||
.expect("Failed to access request length.");
|
.expect("Failed to access request length.");
|
||||||
let self_clone = self.clone();
|
let self_clone = self.clone();
|
||||||
spawner.spawn(Task::new((async move || {
|
spawner.spawn(Task::new(async move {
|
||||||
self_clone
|
self_clone
|
||||||
.handle_request_and_response(method, byte_buffer, cap_buffer, reply_port_cap)
|
.handle_request_and_response(method, byte_buffer, cap_buffer, reply_port_cap)
|
||||||
.await;
|
.await
|
||||||
})()));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,12 +113,10 @@ where
|
||||||
.expect("Failed to reply"),
|
.expect("Failed to reply"),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
crate::message::serialize_error(&mut byte_buffer, err);
|
crate::message::serialize_error(&mut byte_buffer, err);
|
||||||
syscall::reply_port_send(reply_port_cap, &byte_buffer.slice(0x10), &[])
|
syscall::reply_port_send(reply_port_cap, byte_buffer.slice(0x10), &[])
|
||||||
.expect("Failed to reply w/ error")
|
.expect("Failed to reply w/ error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
[toolchain]
|
|
||||||
channel = "nightly"
|
|
||||||
|
|
@ -95,7 +95,7 @@ impl Command {
|
||||||
command: SataCommand::DmaReadExt,
|
command: SataCommand::DmaReadExt,
|
||||||
lba,
|
lba,
|
||||||
sector_cnt: lba_count,
|
sector_cnt: lba_count,
|
||||||
paddr: paddr,
|
paddr,
|
||||||
memory_region: None,
|
memory_region: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use core::ffi::c_void;
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use mammoth::{
|
use mammoth::{
|
||||||
cap::Capability,
|
cap::Capability,
|
||||||
|
|
@ -26,8 +28,11 @@ impl AhciController {
|
||||||
pub fn new(pci_memory: Capability) -> Self {
|
pub fn new(pci_memory: Capability) -> Self {
|
||||||
let pci_device = PciDevice::from_cap(pci_memory).unwrap();
|
let pci_device = PciDevice::from_cap(pci_memory).unwrap();
|
||||||
|
|
||||||
let hba_vaddr =
|
let hba_vaddr = mem::map_direct_physical_and_leak::<c_void>(
|
||||||
mem::map_direct_physical_and_leak(pci_device.header().bars[5] as u64, 0x1100);
|
pci_device.header().bars[5] as usize,
|
||||||
|
0x1100,
|
||||||
|
)
|
||||||
|
.as_ptr() as u64;
|
||||||
let hba = unsafe { (hba_vaddr as *mut AhciHba).as_mut().unwrap() };
|
let hba = unsafe { (hba_vaddr as *mut AhciHba).as_mut().unwrap() };
|
||||||
let mut controller = Self {
|
let mut controller = Self {
|
||||||
pci_device: Mutex::new(pci_device),
|
pci_device: Mutex::new(pci_device),
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,8 @@ impl PortController {
|
||||||
};
|
};
|
||||||
|
|
||||||
// This leaves space for 8 prdt entries.
|
// This leaves space for 8 prdt entries.
|
||||||
for i in 0..32 {
|
for (i, header) in command_list.iter_mut().enumerate() {
|
||||||
command_list[i].command_table_base_addr =
|
header.command_table_base_addr = (command_paddr + 0x500) + (0x100 * (i as u64));
|
||||||
(command_paddr + 0x500) + (0x100 * (i as u64));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let command_slots = array::from_fn(|_| Arc::new(Mutex::new(CommandStatus::Empty)));
|
let command_slots = array::from_fn(|_| Arc::new(Mutex::new(CommandStatus::Empty)));
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mammoth = { path = "../../lib/mammoth" }
|
mammoth = { path = "../../lib/mammoth" }
|
||||||
victoriafalls = { path = "../victoriafalls" }
|
victoriafalls = { path = "../victoriafalls" }
|
||||||
voyageurs = { path = "../../lib/voyageurs" }
|
voyageurs_client = { path = "../../lib/client/voyageurs_client/" }
|
||||||
yellowstone-yunq = { path = "../../lib/yellowstone" }
|
yellowstone-yunq = { path = "../../lib/yellowstone" }
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,16 @@ impl Framebuffer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_pixel(&self, row: u32, col: u32, pixel: u32) {
|
fn draw_pixel(&mut self, row: u32, col: u32, pixel: u32) {
|
||||||
let index = row * (self.fb_info.pitch as u32 / 4) + col;
|
let index = row * (self.fb_info.pitch as u32 / 4) + col;
|
||||||
self.memory_region.mut_slice()[index as usize] = pixel;
|
self.memory_region.mut_slice()[index as usize] = pixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_glyph(&self, glyph: &[u8], row: u32, col: u32) {
|
pub fn draw_glyph(&mut self, glyph: &[u8], row: u32, col: u32) {
|
||||||
let gl_width = 8;
|
let gl_width = 8;
|
||||||
let gl_height = 16;
|
let gl_height = 16;
|
||||||
|
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
for r in 0..gl_height {
|
for r in 0..gl_height {
|
||||||
for c in 0..gl_width {
|
for c in 0..gl_width {
|
||||||
if ((glyph[r] >> c) % 2) == 1 {
|
if ((glyph[r] >> c) % 2) == 1 {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ mod psf;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
|
|
||||||
use mammoth::{debug, define_entry, zion::z_err_t};
|
use mammoth::{debug, define_entry, zion::z_err_t};
|
||||||
use voyageurs::listener;
|
use voyageurs_client::listener;
|
||||||
|
|
||||||
define_entry!();
|
define_entry!();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ pub struct Psf {
|
||||||
|
|
||||||
impl Psf {
|
impl Psf {
|
||||||
pub fn new(path: &str) -> Result<Self, ZError> {
|
pub fn new(path: &str) -> Result<Self, ZError> {
|
||||||
let file = File::open(&path)?;
|
let file = File::open(path)?;
|
||||||
|
|
||||||
let header = file.slice()[0..core::mem::size_of::<PsfHeader>()]
|
let header = file.slice()[0..core::mem::size_of::<PsfHeader>()]
|
||||||
.as_ptr()
|
.as_ptr()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use alloc::{
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
};
|
};
|
||||||
use victoriafalls::dir;
|
use victoriafalls::dir;
|
||||||
use voyageurs::listener::KeyboardHandler;
|
use voyageurs_client::listener::KeyboardHandler;
|
||||||
|
|
||||||
pub struct Terminal {
|
pub struct Terminal {
|
||||||
console: Console,
|
console: Console,
|
||||||
|
|
@ -62,10 +62,8 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_line(&mut self, line: &str) {
|
fn write_line(&mut self, line: &str) {
|
||||||
let mut col = 0;
|
for (col, c) in line.chars().enumerate() {
|
||||||
for c in line.chars() {
|
self.console.write_char(c, self.row, col as u32);
|
||||||
self.console.write_char(c, self.row, col);
|
|
||||||
col += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.row += 1
|
self.row += 1
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ static mut VFS_CLIENT: Option<VFSClient> = None;
|
||||||
|
|
||||||
fn get_client() -> &'static mut VFSClient {
|
fn get_client() -> &'static mut VFSClient {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let None = VFS_CLIENT {
|
#[allow(static_mut_refs)]
|
||||||
|
if VFS_CLIENT.is_none() {
|
||||||
let endpoint_cap = yellowstone_yunq::from_init_endpoint()
|
let endpoint_cap = yellowstone_yunq::from_init_endpoint()
|
||||||
.get_endpoint(&yellowstone_yunq::GetEndpointRequest {
|
.get_endpoint(&yellowstone_yunq::GetEndpointRequest {
|
||||||
endpoint_name: "victoriafalls".to_string(),
|
endpoint_name: "victoriafalls".to_string(),
|
||||||
|
|
@ -21,6 +22,7 @@ fn get_client() -> &'static mut VFSClient {
|
||||||
|
|
||||||
VFS_CLIENT = Some(VFSClient::new(Capability::take(endpoint_cap.endpoint)));
|
VFS_CLIENT = Some(VFSClient::new(Capability::take(endpoint_cap.endpoint)));
|
||||||
}
|
}
|
||||||
|
#[allow(static_mut_refs)]
|
||||||
VFS_CLIENT.as_mut().unwrap()
|
VFS_CLIENT.as_mut().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ impl VFSServerHandler for VictoriaFallsServerImpl {
|
||||||
|
|
||||||
let mut inode_num = 2; // Start with root.
|
let mut inode_num = 2; // Start with root.
|
||||||
|
|
||||||
while let Some(path_token) = tokens.next() {
|
for path_token in tokens {
|
||||||
inode_num = self.find_path_in_dir(inode_num, path_token)?;
|
inode_num = self.find_path_in_dir(inode_num, path_token)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl VFSServerHandler for VictoriaFallsServerImpl {
|
||||||
|
|
||||||
let mut inode_num = 2; // Start with root.
|
let mut inode_num = 2; // Start with root.
|
||||||
|
|
||||||
while let Some(path_token) = tokens.next() {
|
for path_token in tokens {
|
||||||
inode_num = self.find_path_in_dir(inode_num, path_token)?;
|
inode_num = self.find_path_in_dir(inode_num, path_token)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
rust/sys/voyageurs/Cargo.toml
Normal file
15
rust/sys/voyageurs/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "voyageurs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitfield-struct = "0.12"
|
||||||
|
mammoth = { path = "../../lib/mammoth/" }
|
||||||
|
pci = { path = "../../lib/pci" }
|
||||||
|
volatile = "0.6.1"
|
||||||
|
yellowstone-yunq = { version = "0.1.0", path = "../../lib/yellowstone" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["debug"]
|
||||||
|
debug = []
|
||||||
51
rust/sys/voyageurs/src/main.rs
Normal file
51
rust/sys/voyageurs/src/main.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
mod xhci;
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use mammoth::{
|
||||||
|
cap::Capability,
|
||||||
|
debug, define_entry,
|
||||||
|
sync::Mutex,
|
||||||
|
task::{Executor, Task},
|
||||||
|
zion::z_err_t,
|
||||||
|
};
|
||||||
|
use pci::PciDevice;
|
||||||
|
use xhci::driver::XHCIDriver;
|
||||||
|
|
||||||
|
define_entry!();
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
extern "C" fn main() -> z_err_t {
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
debug!("Voyageurs Starting.");
|
||||||
|
|
||||||
|
let yellowstone = yellowstone_yunq::from_init_endpoint();
|
||||||
|
|
||||||
|
let xhci_info = yellowstone
|
||||||
|
.get_xhci_info()
|
||||||
|
.expect("Failed to get XHCI info from yellowstone.");
|
||||||
|
|
||||||
|
let pci_device = PciDevice::from_cap(Capability::take(xhci_info.xhci_region)).unwrap();
|
||||||
|
|
||||||
|
let xhci_driver = Arc::new(XHCIDriver::from_pci_device(pci_device));
|
||||||
|
|
||||||
|
let executor = Arc::new(Mutex::new(Executor::new()));
|
||||||
|
|
||||||
|
let driver_clone = xhci_driver.clone();
|
||||||
|
let spawner = executor.clone().lock().new_spawner();
|
||||||
|
let interrupt_thread = mammoth::thread::spawn(move || driver_clone.interrupt_loop(spawner));
|
||||||
|
|
||||||
|
executor
|
||||||
|
.clone()
|
||||||
|
.lock()
|
||||||
|
.spawn(Task::new(async move { xhci_driver.startup().await }));
|
||||||
|
|
||||||
|
executor.clone().lock().run();
|
||||||
|
interrupt_thread.join().unwrap();
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
71
rust/sys/voyageurs/src/xhci/data_structures/command_trb.rs
Normal file
71
rust/sys/voyageurs/src/xhci/data_structures/command_trb.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
|
||||||
|
use crate::xhci::data_structures::{TransferRequestBlock, TrbType};
|
||||||
|
|
||||||
|
pub struct EnableSlotTrb {}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct EnableSlotCommand {
|
||||||
|
__: u64,
|
||||||
|
__: u32,
|
||||||
|
#[bits(10)]
|
||||||
|
__: u16,
|
||||||
|
#[bits(6, default=TrbType::EnableSlotCommand)]
|
||||||
|
trb_type: TrbType,
|
||||||
|
#[bits(5)]
|
||||||
|
slot_type: u8,
|
||||||
|
#[bits(11)]
|
||||||
|
__: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EnableSlotCommand> for CommandTrb {
|
||||||
|
fn from(value: EnableSlotCommand) -> Self {
|
||||||
|
Self(value.into_bits().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct AddressDeviceCommand {
|
||||||
|
pub input_context_pointer: u64,
|
||||||
|
__: u32,
|
||||||
|
#[bits(9)]
|
||||||
|
__: u16,
|
||||||
|
pub block_set_address_request: bool,
|
||||||
|
#[bits(6, default=TrbType::AddressDeviceCommand)]
|
||||||
|
trb_typ: TrbType,
|
||||||
|
__: u8,
|
||||||
|
pub slot_id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AddressDeviceCommand> for CommandTrb {
|
||||||
|
fn from(value: AddressDeviceCommand) -> Self {
|
||||||
|
Self(value.into_bits().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct NoOpCommand {
|
||||||
|
__: u64,
|
||||||
|
__: u32,
|
||||||
|
cycle: bool,
|
||||||
|
#[bits(9)]
|
||||||
|
__: u16,
|
||||||
|
#[bits(6, default = TrbType::NoOpCommand)]
|
||||||
|
trb_type: TrbType,
|
||||||
|
__: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NoOpCommand> for CommandTrb {
|
||||||
|
fn from(value: NoOpCommand) -> Self {
|
||||||
|
Self(value.into_bits().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple type to ensure we are only sending commands to command rings.
|
||||||
|
pub struct CommandTrb(TransferRequestBlock);
|
||||||
|
|
||||||
|
impl From<CommandTrb> for TransferRequestBlock {
|
||||||
|
fn from(value: CommandTrb) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
use crate::xhci::data_structures::{EndpointContext, SlotContext};
|
||||||
|
|
||||||
|
#[repr(C, align(64))]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DeviceContext {
|
||||||
|
slot_context: SlotContext,
|
||||||
|
endpoint_context_0: EndpointContext,
|
||||||
|
endpoint_contexts: [EndpointContext; 30],
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<DeviceContext>() == 0x400);
|
||||||
206
rust/sys/voyageurs/src/xhci/data_structures/endpoint_context.rs
Normal file
206
rust/sys/voyageurs/src/xhci/data_structures/endpoint_context.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
use bitfield_struct::{bitenum, bitfield};
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[bitenum]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EndpointState {
|
||||||
|
/// The endpoint is not operationa.
|
||||||
|
Disabled = 0,
|
||||||
|
/// The endpoint is operational, either waiting for a doorbell ring or processing TDs.
|
||||||
|
Running = 1,
|
||||||
|
/// The endpoint is halted due to a Halt condition detected on the USB. SW shall issue
|
||||||
|
/// Reset Endpoint Command to recover from the Halt condition and transition to the Stopped
|
||||||
|
/// state. SW may manipulate the Transfer Ring while in this state
|
||||||
|
Halted = 2,
|
||||||
|
/// The endpoint is not running due to a Stop Endpoint Command or recovering
|
||||||
|
/// from a Halt condition. SW may manipulate the Transfer Ring while in this state.
|
||||||
|
Stopped = 3,
|
||||||
|
/// The endpoint is not running due to a TRB Error. SW may manipulate the Transfer
|
||||||
|
/// Ring while in this state.
|
||||||
|
Error = 4,
|
||||||
|
#[fallback]
|
||||||
|
Unknown = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[bitenum]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EndpointType {
|
||||||
|
#[fallback]
|
||||||
|
NotValid = 0,
|
||||||
|
IsochOut = 1,
|
||||||
|
BulkOut = 2,
|
||||||
|
InterruptOut = 3,
|
||||||
|
Control = 4,
|
||||||
|
IsochIn = 5,
|
||||||
|
BulkIn = 6,
|
||||||
|
InterruptIn = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
pub struct EndpointContextFields {
|
||||||
|
/// Endpoint State (EP State). The Endpoint State identifies the current operational state of the
|
||||||
|
/// endpoint.
|
||||||
|
///
|
||||||
|
/// As Output, a Running to Halted transition is forced by the xHC if a STALL condition is detected
|
||||||
|
/// on the endpoint. A Running to Error transition is forced by the xHC if a TRB Error condition is
|
||||||
|
/// detected.
|
||||||
|
///
|
||||||
|
/// As Input, this field is initialized to ‘0’ by software.
|
||||||
|
///
|
||||||
|
/// Refer to section 4.8.3 for more information on Endpoint State.
|
||||||
|
#[bits(3)]
|
||||||
|
pub endpoint_state: EndpointState,
|
||||||
|
#[bits(5)]
|
||||||
|
__: u8,
|
||||||
|
/// Mult. If LEC = ‘0’, then this field indicates the maximum number of bursts within an Interval that
|
||||||
|
/// this endpoint supports. Mult is a “zero-based” value, where 0 to 3 represents 1 to 4 bursts,
|
||||||
|
/// respectively. The valid range of values is ‘0’ to ‘2’.111 This field shall be ‘0’ for all endpoint types
|
||||||
|
/// except for SS Isochronous.
|
||||||
|
///
|
||||||
|
/// If LEC = ‘1’, then this field shall be RsvdZ and Mult is calculated as:
|
||||||
|
/// ROUNDUP(Max ESIT Payload / Max Packet Size / (Max Burst Size + 1)) - 1
|
||||||
|
#[bits(2)]
|
||||||
|
pub mult: u8,
|
||||||
|
/// Max Primary Streams (MaxPStreams). This field identifies the maximum number of Primary
|
||||||
|
/// Stream IDs this endpoint supports. Valid values are defined below. If the value of this field is ‘0’,
|
||||||
|
/// then the TR Dequeue Pointer field shall point to a Transfer Ring. If this field is > '0' then the TR
|
||||||
|
/// Dequeue Pointer field shall point to a Primary Stream Context Array. Refer to section 4.12 for
|
||||||
|
/// more information.
|
||||||
|
///
|
||||||
|
/// A value of ‘0’ indicates that Streams are not supported by this endpoint and the Endpoint
|
||||||
|
/// Context TR Dequeue Pointer field references a Transfer Ring.
|
||||||
|
///
|
||||||
|
/// A value of ‘1’ to ‘15’ indicates that the Primary Stream ID Width is MaxPstreams+1 and the
|
||||||
|
/// Primary Stream Array contains 2MaxPStreams+1 entries.
|
||||||
|
///
|
||||||
|
/// For SS Bulk endpoints, the range of valid values for this field is defined by the MaxPSASize field
|
||||||
|
/// in the HCCPARAMS1 register (refer to Table 5-13).
|
||||||
|
///
|
||||||
|
/// This field shall be '0' for all SS Control, Isoch, and Interrupt endpoints, and for all non-SS
|
||||||
|
/// endpoints.
|
||||||
|
#[bits(5)]
|
||||||
|
pub max_primary_streams: u8,
|
||||||
|
/// Linear Stream Array (LSA). This field identifies how a Stream ID shall be interpreted.
|
||||||
|
/// Setting this bit to a value of ‘1’ shall disable Secondary Stream Arrays and a Stream ID shall be
|
||||||
|
/// interpreted as a linear index into the Primary Stream Array, where valid values for MaxPStreams
|
||||||
|
/// are ‘1’ to ‘15’.
|
||||||
|
///
|
||||||
|
/// A value of ‘0’ shall enable Secondary Stream Arrays, where the low order (MaxPStreams+1) bits
|
||||||
|
/// of a Stream ID shall be interpreted as a linear index into the Primary Stream Array, where valid
|
||||||
|
/// values for MaxPStreams are ‘1’ to ‘7’. And the high order bits of a Stream ID shall be interpreted
|
||||||
|
/// as a linear index into the Secondary Stream Array.
|
||||||
|
///
|
||||||
|
/// If MaxPStreams = ‘0’, this field RsvdZ.
|
||||||
|
///
|
||||||
|
/// Refer to section 4.12.2 for more information
|
||||||
|
pub linear_stream_array: bool,
|
||||||
|
/// Interval. The period between consecutive requests to a USB endpoint to send or receive data.
|
||||||
|
/// Expressed in 125 μs. increments. The period is calculated as 125 μs. * 2Interval; e.g., an Interval
|
||||||
|
/// value of 0 means a period of 125 μs. (20 = 1 * 125 μs.), a value of 1 means a period of 250 μs. (21
|
||||||
|
/// = 2 * 125 μs.), a value of 4 means a period of 2 ms. (24 = 16 * 125 μs.), etc. Refer to Table 6-12
|
||||||
|
/// for legal Interval field values. See further discussion of this field below. Refer to section 6.2.3.6
|
||||||
|
/// for more information.
|
||||||
|
pub interval: u8,
|
||||||
|
/// Max Endpoint Service Time Interval Payload High (Max ESIT Payload Hi). If LEC = '1', then this
|
||||||
|
/// field indicates the high order 8 bits of the Max ESIT Payload value. If LEC = '0', then this field
|
||||||
|
/// shall be RsvdZ. Refer to section 6.2.3.8 for more information.
|
||||||
|
pub max_esit_payload_hi: u8,
|
||||||
|
__: bool,
|
||||||
|
/// Error Count (CErr)112. This field defines a 2-bit down count, which identifies the number of
|
||||||
|
/// consecutive USB Bus Errors allowed while executing a TD. If this field is programmed with a
|
||||||
|
/// non-zero value when the Endpoint Context is initialized, the xHC loads this value into an internal
|
||||||
|
/// Bus Error Counter before executing a USB transaction and decrements it if the transaction fails.
|
||||||
|
/// If the Bus Error Counter counts from ‘1’ to ‘0’, the xHC ceases execution of the TRB, sets the
|
||||||
|
/// endpoint to the Halted state, and generates a USB Transaction Error Event for the TRB that
|
||||||
|
/// caused the internal Bus Error Counter to decrement to ‘0’. If system software programs this field
|
||||||
|
/// to ‘0’, the xHC shall not count errors for TRBs on the Endpoint’s Transfer Ring and there shall be
|
||||||
|
/// no limit on the number of TRB retries. Refer to section 4.10.2.7 for more information on the
|
||||||
|
/// operation of the Bus Error Counter.
|
||||||
|
///
|
||||||
|
/// Note: CErr does not apply to Isoch endpoints and shall be set to ‘0’ if EP Type = Isoch Out ('1') or
|
||||||
|
/// Isoch In ('5').
|
||||||
|
#[bits(2)]
|
||||||
|
pub error_count: u8,
|
||||||
|
/// Endpoint Type (EP Type). This field identifies whether an Endpoint Context is Valid, and if so,
|
||||||
|
/// what type of endpoint the context defines.
|
||||||
|
#[bits(3)]
|
||||||
|
pub endpoint_type: EndpointType,
|
||||||
|
__: bool,
|
||||||
|
/// Host Initiate Disable (HID). This field affects Stream enabled endpoints, allowing the Host
|
||||||
|
/// Initiated Stream selection feature to be disabled for the endpoint. Setting this bit to a value of
|
||||||
|
/// ‘1’ shall disable the Host Initiated Stream selection feature. A value of ‘0’ will enable normal
|
||||||
|
/// Stream operation. Refer to section 4.12.1.1 for more information.
|
||||||
|
pub host_initiate_disable: bool,
|
||||||
|
/// Max Burst Size. This field indicates to the xHC the maximum number of consecutive USB
|
||||||
|
/// transactions that should be executed per scheduling opportunity. This is a “zero-based” value,
|
||||||
|
/// where 0 to 15 represents burst sizes of 1 to 16, respectively. Refer to section 6.2.3.4 for more
|
||||||
|
/// information.
|
||||||
|
pub max_burst_size: u8,
|
||||||
|
/// Max Packet Size. This field indicates the maximum packet size in bytes that this endpoint is
|
||||||
|
/// capable of sending or receiving when configured. Refer to section 6.2.3.5 for more information
|
||||||
|
pub max_packet_size: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
pub struct TRDequeuePointer {
|
||||||
|
/// Dequeue Cycle State (DCS). This bit identifies the value of the xHC Consumer Cycle State (CCS)
|
||||||
|
/// flag for the TRB referenced by the TR Dequeue Pointer. Refer to section 4.9.2 for more
|
||||||
|
/// information. This field shall be ‘0’ if MaxPStreams > ‘0’
|
||||||
|
pub dequeue_cycle_state: bool,
|
||||||
|
#[bits(3)]
|
||||||
|
__: u8,
|
||||||
|
/// TR Dequeue Pointer. As Input, this field represents the high order bits of the 64-bit base
|
||||||
|
/// address of a Transfer Ring or a Stream Context Array associated with this endpoint. If
|
||||||
|
/// MaxPStreams = '0' then this field shall point to a Transfer Ring. If MaxPStreams > '0' then this
|
||||||
|
/// field shall point to a Stream Context Array.
|
||||||
|
///
|
||||||
|
/// As Output, if MaxPStreams = ‘0’ this field shall be used by the xHC to store the value of the
|
||||||
|
/// Dequeue Pointer when the endpoint enters the Halted or Stopped states, and the value of the
|
||||||
|
/// this field shall be undefined when the endpoint is not in the Halted or Stopped states. if
|
||||||
|
/// MaxPStreams > ‘0’ then this field shall point to a Stream Context Array.
|
||||||
|
/// The memory structure referenced by this physical memory pointer shall be aligned to a 16-byte
|
||||||
|
/// boundary.
|
||||||
|
#[bits(60)]
|
||||||
|
tr_deque_pointer: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TRDequeuePointer {
|
||||||
|
pub fn pointer(self) -> u64 {
|
||||||
|
self.tr_deque_pointer() << 4
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pointer(&mut self, tr_deque_pointer: u64) {
|
||||||
|
self.set_tr_deque_pointer(tr_deque_pointer >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_pointer(self, tr_deque_pointer: u64) -> Self {
|
||||||
|
self.with_tr_deque_pointer(tr_deque_pointer >> 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct AdditionalFields {
|
||||||
|
/// Average TRB Length. This field represents the average Length of the TRBs executed by this
|
||||||
|
/// endpoint. The value of this field shall be greater than ‘0’. Refer to section 4.14.1.1 and the
|
||||||
|
/// implementation note TRB Lengths and System Bus Bandwidth for more information.
|
||||||
|
/// The xHC shall use this parameter to calculate system bus bandwidth requirements
|
||||||
|
pub average_trb_length: u16,
|
||||||
|
/// Max Endpoint Service Time Interval Payload Low (Max ESIT Payload Lo). This field indicates
|
||||||
|
/// the low order 16 bits of the Max ESIT Payload. The Max ESIT Payload represents the total
|
||||||
|
/// number of bytes this endpoint will transfer during an ESIT. This field is only valid for periodic
|
||||||
|
/// endpoints. Refer to section 6.2.3.8 for more information.
|
||||||
|
pub max_esit_payload_lo: u16,
|
||||||
|
__: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EndpointContext {
|
||||||
|
pub fields: EndpointContextFields,
|
||||||
|
pub tr_deque_pointer: TRDequeuePointer,
|
||||||
|
additional_fields: AdditionalFields,
|
||||||
|
__: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<EndpointContext>() == 0x20);
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
use core::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
use mammoth::physical_box::PhysicalBox;
|
||||||
|
|
||||||
|
use crate::xhci::data_structures::TrbRingSegment;
|
||||||
|
|
||||||
|
#[repr(align(64))]
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct EventRingSegmentTableEntry {
|
||||||
|
/// Ring Segment Base Address Hi and Lo. These fields represent the high order bits of the 64-bit
|
||||||
|
/// base address of the Event Ring Segment.
|
||||||
|
/// The memory structure referenced by this physical memory pointer shall begin on a 64-byte
|
||||||
|
/// address boundary.
|
||||||
|
pub ring_segment_base_address: u64,
|
||||||
|
/// Ring Segment Size. This field defines the number of TRBs supported by the ring segment, Valid
|
||||||
|
/// values for this field are 16 to 4096, i.e. an Event Ring segment shall contain at least 16 entries
|
||||||
|
pub ring_segment_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventRingSegmentTableEntry {
|
||||||
|
pub fn update_from_trb_ring(&mut self, trb_ring: &TrbRingSegment) {
|
||||||
|
mammoth::debug!("RSTE: {:0x}", self as *const _ as usize);
|
||||||
|
self.ring_segment_base_address = trb_ring.physical_address() as u64;
|
||||||
|
assert!(self.ring_segment_base_address.is_multiple_of(64));
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_volatile(
|
||||||
|
&mut self.ring_segment_size as *mut u64,
|
||||||
|
trb_ring.len() as u64,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert!(self.ring_segment_size >= 16);
|
||||||
|
assert!(self.ring_segment_size <= 4096);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventRingSegmentTable(PhysicalBox<[EventRingSegmentTableEntry]>);
|
||||||
|
|
||||||
|
impl EventRingSegmentTable {
|
||||||
|
pub fn new(size: usize) -> Self {
|
||||||
|
Self(PhysicalBox::default_with_count(
|
||||||
|
EventRingSegmentTableEntry::default(),
|
||||||
|
size,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn physical_address(&self) -> usize {
|
||||||
|
self.0.physical_address()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for EventRingSegmentTable {
|
||||||
|
type Output = EventRingSegmentTableEntry;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.0[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<usize> for EventRingSegmentTable {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
&mut self.0[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
139
rust/sys/voyageurs/src/xhci/data_structures/event_trb.rs
Normal file
139
rust/sys/voyageurs/src/xhci/data_structures/event_trb.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
use bitfield_struct::{bitenum, bitfield};
|
||||||
|
|
||||||
|
use crate::xhci::data_structures::{TransferRequestBlock, TrbType};
|
||||||
|
|
||||||
|
#[bitenum]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum CommandCompletionCode {
|
||||||
|
#[fallback]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Invalid = 0,
|
||||||
|
Success = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct TransferEvent {
|
||||||
|
pub transfer_trb_pointer: u64,
|
||||||
|
|
||||||
|
#[bits(24)]
|
||||||
|
pub trb_transfer_lenght: u32,
|
||||||
|
|
||||||
|
/// Completion Code. This field encodes the completion status of the command that generated the
|
||||||
|
/// event. Refer to the respective command definition for a list of the possible Completion Codes
|
||||||
|
/// associated with the command. Refer to section 6.4.5 for an enumerated list of possible error
|
||||||
|
/// conditions.
|
||||||
|
#[bits(8)]
|
||||||
|
pub completion_code: CommandCompletionCode,
|
||||||
|
#[bits(10)]
|
||||||
|
__: u16,
|
||||||
|
/// TRB Type. This field identifies the type of the TRB. Refer to Table 6-91 for the definition of the
|
||||||
|
/// Command Completion Event TRB type ID
|
||||||
|
#[bits(6, default=TrbType::TransferEvent)]
|
||||||
|
pub trb_type: TrbType,
|
||||||
|
|
||||||
|
#[bits(5)]
|
||||||
|
pub endpoint_id: u8,
|
||||||
|
#[bits(3)]
|
||||||
|
__: u8,
|
||||||
|
|
||||||
|
pub slot_id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct CommandCompletionEvent {
|
||||||
|
/// Command TRB Pointer Hi and Lo. This field represents the high order bits of the 64-bit address
|
||||||
|
/// of the Command TRB that generated this event. Note that this field is not valid for some
|
||||||
|
/// Completion Code values. Refer to Table 6-90 for specific cases.
|
||||||
|
///
|
||||||
|
/// The memory structure referenced by this physical memory pointer shall be aligned on a 16-byte
|
||||||
|
/// address boundary.
|
||||||
|
pub command_trb_pointer: u64,
|
||||||
|
/// Command Completion Parameter. This field may optionally be set by a command. Refer to
|
||||||
|
/// section 4.6.6.1 for specific usage. If a command does not utilize this field it shall be treated as
|
||||||
|
/// RsvdZ.
|
||||||
|
#[bits(24)]
|
||||||
|
pub command_completion_parameter: u64,
|
||||||
|
/// Completion Code. This field encodes the completion status of the command that generated the
|
||||||
|
/// event. Refer to the respective command definition for a list of the possible Completion Codes
|
||||||
|
/// associated with the command. Refer to section 6.4.5 for an enumerated list of possible error
|
||||||
|
/// conditions.
|
||||||
|
#[bits(8)]
|
||||||
|
pub completion_code: CommandCompletionCode,
|
||||||
|
/// Cycle bit (C). This bit is used to mark the Dequeue Pointer of an Event Ring
|
||||||
|
pub cycle_bit: bool,
|
||||||
|
#[bits(9)]
|
||||||
|
__: u16,
|
||||||
|
/// TRB Type. This field identifies the type of the TRB. Refer to Table 6-91 for the definition of the
|
||||||
|
/// Command Completion Event TRB type ID
|
||||||
|
#[bits(6, default=TrbType::CommandCompletionEvent)]
|
||||||
|
pub trb_type: TrbType,
|
||||||
|
/// VF ID. The ID of the Virtual Function that generated the event. Note that this field is valid only if
|
||||||
|
/// Virtual Functions are enabled. If they are not enabled this field shall be cleared to ‘0’.
|
||||||
|
pub vf_id: u8,
|
||||||
|
/// Slot ID. The Slot ID field shall be updated by the xHC to reflect the slot associated with the
|
||||||
|
/// command that generated the event, with the following exceptions:
|
||||||
|
///
|
||||||
|
/// - The Slot ID shall be cleared to ‘0’ for No Op, Set Latency Tolerance Value, Get Port Bandwidth,
|
||||||
|
/// and Force Event Commands.
|
||||||
|
///
|
||||||
|
/// - The Slot ID shall be set to the ID of the newly allocated Device Slot for the Enable Slot
|
||||||
|
/// Command.
|
||||||
|
///
|
||||||
|
/// - The value of Slot ID shall be vendor defined when generated by a vendor defined command.
|
||||||
|
///
|
||||||
|
/// This value is used as an index in the Device Context Base Address Array to select the Device
|
||||||
|
/// Context of the source device. If this Event is due to a Host Controller Command, then this field
|
||||||
|
/// shall be cleared to ‘0’.
|
||||||
|
pub slot_id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct PortStatusChangeEvent {
|
||||||
|
#[bits(24)]
|
||||||
|
__: u32,
|
||||||
|
pub port_id: u8,
|
||||||
|
__: u32,
|
||||||
|
#[bits(24)]
|
||||||
|
__: u32,
|
||||||
|
#[bits(8)]
|
||||||
|
pub completion_code: CommandCompletionCode,
|
||||||
|
#[bits(10)]
|
||||||
|
__: u16,
|
||||||
|
#[bits(6, default=TrbType::PortStatusChangeEvent)]
|
||||||
|
trb_type: TrbType,
|
||||||
|
__: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EventTrb {
|
||||||
|
Transfer(TransferEvent),
|
||||||
|
CommandCompletion(CommandCompletionEvent),
|
||||||
|
PortStatusChange(PortStatusChangeEvent),
|
||||||
|
BandwidthRequest(TransferRequestBlock),
|
||||||
|
Doorbell(TransferRequestBlock),
|
||||||
|
HostController(TransferRequestBlock),
|
||||||
|
DeviceNotification(TransferRequestBlock),
|
||||||
|
MFINDEXWrap(TransferRequestBlock),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TransferRequestBlock> for EventTrb {
|
||||||
|
fn from(value: TransferRequestBlock) -> Self {
|
||||||
|
match value.trb_type() {
|
||||||
|
TrbType::TransferEvent => {
|
||||||
|
EventTrb::Transfer(TransferEvent::from_bits(value.into_bits()))
|
||||||
|
}
|
||||||
|
TrbType::CommandCompletionEvent => {
|
||||||
|
EventTrb::CommandCompletion(CommandCompletionEvent::from_bits(value.into_bits()))
|
||||||
|
}
|
||||||
|
TrbType::PortStatusChangeEvent => {
|
||||||
|
EventTrb::PortStatusChange(PortStatusChangeEvent::from_bits(value.into_bits()))
|
||||||
|
}
|
||||||
|
TrbType::BandwidthRequestEvent => EventTrb::BandwidthRequest(value),
|
||||||
|
TrbType::DoorbellEvent => EventTrb::Doorbell(value),
|
||||||
|
TrbType::HostControllerEvent => EventTrb::HostController(value),
|
||||||
|
TrbType::DeviceNotificationEvent => EventTrb::DeviceNotification(value),
|
||||||
|
TrbType::MFINDEXWrapEvent => EventTrb::MFINDEXWrap(value),
|
||||||
|
t => panic!("Unknown trb type on event ring: {:?}", t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
rust/sys/voyageurs/src/xhci/data_structures/input_context.rs
Normal file
33
rust/sys/voyageurs/src/xhci/data_structures/input_context.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
|
||||||
|
use crate::xhci::data_structures::{EndpointContext, SlotContext};
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct InputControlContextSettings {
|
||||||
|
configuration_value: u8,
|
||||||
|
interface_number: u8,
|
||||||
|
alternate_setting: u8,
|
||||||
|
__: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct InputControlContext {
|
||||||
|
pub drop_context_flags: u32,
|
||||||
|
pub add_context_flags: u32,
|
||||||
|
__: [u32; 5],
|
||||||
|
settings: InputControlContextSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<InputControlContext>() == 0x20);
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct InputContext {
|
||||||
|
pub input_control_context: InputControlContext,
|
||||||
|
pub slot_context: SlotContext,
|
||||||
|
pub endpoint_context_0: EndpointContext,
|
||||||
|
pub endpoint_contexts: [EndpointContext; 30],
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<InputContext>() == 0x420);
|
||||||
19
rust/sys/voyageurs/src/xhci/data_structures/mod.rs
Normal file
19
rust/sys/voyageurs/src/xhci/data_structures/mod.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
mod command_trb;
|
||||||
|
mod device_context;
|
||||||
|
mod endpoint_context;
|
||||||
|
mod event_ring_segment_table;
|
||||||
|
mod event_trb;
|
||||||
|
mod input_context;
|
||||||
|
mod slot_context;
|
||||||
|
mod trb;
|
||||||
|
mod trb_ring_segment;
|
||||||
|
|
||||||
|
pub use command_trb::*;
|
||||||
|
pub use device_context::*;
|
||||||
|
pub use endpoint_context::*;
|
||||||
|
pub use event_ring_segment_table::*;
|
||||||
|
pub use event_trb::*;
|
||||||
|
pub use input_context::*;
|
||||||
|
pub use slot_context::*;
|
||||||
|
pub use trb::*;
|
||||||
|
pub use trb_ring_segment::*;
|
||||||
134
rust/sys/voyageurs/src/xhci/data_structures/slot_context.rs
Normal file
134
rust/sys/voyageurs/src/xhci/data_structures/slot_context.rs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct SlotContextFields {
|
||||||
|
/// Route String. This field is used by hubs to route packets to the correct downstream port. The
|
||||||
|
/// format of the Route String is defined in section 8.9 the USB3 specification.
|
||||||
|
/// As Input, this field shall be set for all USB devices, irrespective of their speed, to indicate their
|
||||||
|
/// location in the USB topology.
|
||||||
|
#[bits(20)]
|
||||||
|
pub route_string: u32,
|
||||||
|
/// Speed. This field is deprecated in this version of the specification and shall be Reserved.
|
||||||
|
/// This field indicates the speed of the device. Refer to the PORTSC Port Speed field in Table 5-27
|
||||||
|
/// for the definition of the valid values
|
||||||
|
#[bits(4)]
|
||||||
|
pub speed: u8,
|
||||||
|
__: bool,
|
||||||
|
/// Multi-TT (MTT). This flag is set to '1' by software if this is a High-speed hub that supports
|
||||||
|
/// Multiple TTs and the Multiple TT Interface has been enabled by software, or if this is a Low-
|
||||||
|
/// /Full-speed device or Full-speed hub and connected to the xHC through a parent108 High-speed
|
||||||
|
/// hub that supports Multiple TTs and the Multiple TT Interface of the parent hub has been
|
||||||
|
/// enabled by software, or ‘0’ if not.
|
||||||
|
pub multi_tt: bool,
|
||||||
|
/// Hub. This flag is set to '1' by software if this device is a USB hub, or '0' if it is a USB function
|
||||||
|
pub hub: bool,
|
||||||
|
/// Context Entries. This field identifies the index of the last valid Endpoint Context within this
|
||||||
|
/// Device Context structure. The value of ‘0’ is Reserved and is not a valid entry for this field. Valid
|
||||||
|
/// entries for this field shall be in the range of 1-31. This field indicates the size of the Device
|
||||||
|
/// Context structure. For example, ((Context Entries+1) * 32 bytes) = Total bytes for this structure.
|
||||||
|
///
|
||||||
|
/// Note, Output Context Entries values are written by the xHC, and Input Context Entries values are
|
||||||
|
/// written by software.
|
||||||
|
#[bits(5)]
|
||||||
|
pub context_entries: u8,
|
||||||
|
/// Max Exit Latency. The Maximum Exit Latency is in microseconds, and indicates the worst case
|
||||||
|
/// time it takes to wake up all the links in the path to the device, given the current USB link level
|
||||||
|
/// power management settings.
|
||||||
|
///
|
||||||
|
/// Refer to section 4.23.5.2 for more information on the use of this field.
|
||||||
|
pub max_exit_latency: u16,
|
||||||
|
/// Root Hub Port Number. This field identifies the Root Hub Port Number used to access the USB
|
||||||
|
/// device. Refer to section 4.19.7 for port numbering information.
|
||||||
|
///
|
||||||
|
/// Note: Ports are numbered from 1 to MaxPorts
|
||||||
|
pub root_hub_port_number: u8,
|
||||||
|
/// Number of Ports. If this device is a hub (Hub = ‘1’), then this field is set by software to identify
|
||||||
|
/// the number of downstream facing ports supported by the hub. Refer to the bNbrPorts field
|
||||||
|
/// description in the Hub Descriptor (Table 11-13) of the USB2 spec. If this device is not a hub (Hub
|
||||||
|
/// = ‘0’), then this field shall be ‘0
|
||||||
|
pub number_of_ports: u8,
|
||||||
|
/// Parent Hub Slot ID. If this device is Low-/Full-speed and connected through a High-speed hub,
|
||||||
|
/// then this field shall contain the Slot ID of the parent High-speed hub109.
|
||||||
|
///
|
||||||
|
/// For SS and SSP bus instance, if this device is connected through a higher rank hub110 then this
|
||||||
|
/// field shall contain the Slot ID of the parent hub. For example, a Gen1 x1 connected behind a
|
||||||
|
/// Gen1 x2 hub, or Gen1 x2 device connected behind Gen2 x2 hub.
|
||||||
|
///
|
||||||
|
/// This field shall be ‘0’ if any of the following are true:
|
||||||
|
/// Device is attached to a Root Hub port
|
||||||
|
/// Device is a High-Speed device
|
||||||
|
/// Device is the highest rank SS/SSP device supported by xHCI
|
||||||
|
pub parent_hub_slot_id: u8,
|
||||||
|
/// Parent Port Number. If this device is Low-/Full-speed and connected through a High-speed
|
||||||
|
/// hub, then this field shall contain the number of the downstream facing port of the parent High-
|
||||||
|
/// speed hub109.
|
||||||
|
/// For SS and SSP bus instance, if this device is connected through a higher rank hub110 then this
|
||||||
|
/// field shall contain the number of the downstream facing port of the parent hub. For example, a
|
||||||
|
/// Gen1 x1 connected behind a Gen1 x2 hub, or Gen1 x2 device connected behind Gen2 x2 hub.
|
||||||
|
/// This field shall be ‘0’ if any of the following are true:
|
||||||
|
/// Device is attached to a Root Hub port
|
||||||
|
/// Device is a High-Speed device
|
||||||
|
/// Device is the highest rank SS/SSP device supported by xH
|
||||||
|
pub parent_port_number: u8,
|
||||||
|
/// TT Think Time (TTT). If this is a High-speed hub (Hub = ‘1’ and Speed = High-Speed), then this
|
||||||
|
/// field shall be set by software to identify the time the TT of the hub requires to proceed to the
|
||||||
|
/// next full-/low-speed transaction.
|
||||||
|
/// Value Think Time
|
||||||
|
/// 0 TT requires at most 8 FS bit times of inter-transaction gap on a full-/low-speed
|
||||||
|
/// downstream bus.
|
||||||
|
/// 1 TT requires at most 16 FS bit times.
|
||||||
|
/// 2 TT requires at most 24 FS bit times.
|
||||||
|
/// 3 TT requires at most 32 FS bit times.
|
||||||
|
/// Refer to the TT Think Time sub-field of the wHubCharacteristics field description in the Hub
|
||||||
|
/// Descriptor (Table 11-13) and section 11.18.2 of the USB2 spec for more information on TT
|
||||||
|
/// Think Time. If this device is not a High-speed hub (Hub = ‘0’ or Speed != High-speed), then this
|
||||||
|
/// field shall be ‘0’.
|
||||||
|
#[bits(2)]
|
||||||
|
pub tt_think_time: u8,
|
||||||
|
#[bits(4)]
|
||||||
|
__: u8,
|
||||||
|
/// Interrupter Target. This field defines the index of the Interrupter that will receive Bandwidth
|
||||||
|
/// Request Events and Device Notification Events generated by this slot, or when a Ring Underrun
|
||||||
|
/// or Ring Overrun condition is reported (refer to section 4.10.3.1). Valid values are between 0 and
|
||||||
|
/// MaxIntrs-1
|
||||||
|
#[bits(10)]
|
||||||
|
pub interrupter_target: u16,
|
||||||
|
/// USB Device Address. This field identifies the address assigned to the USB device by the xHC,
|
||||||
|
/// and is set upon the successful completion of a Set Address Command. Refer to the USB2 spec
|
||||||
|
/// for a more detailed description.
|
||||||
|
///
|
||||||
|
/// As Output, this field is invalid if the Slot State = Disabled or Default.
|
||||||
|
/// As Input, software shall initialize the field to ‘0’.
|
||||||
|
pub usb_device_address: u8,
|
||||||
|
|
||||||
|
#[bits(19)]
|
||||||
|
__: u32,
|
||||||
|
|
||||||
|
/// Slot State. This field is updated by the xHC when a Device Slot transitions from one state to
|
||||||
|
/// another.
|
||||||
|
/// Value Slot State
|
||||||
|
/// 0 Disabled/Enabled
|
||||||
|
/// 1 Default
|
||||||
|
/// 2 Addressed
|
||||||
|
/// 3 Configured
|
||||||
|
/// 31-4 Reserved
|
||||||
|
///
|
||||||
|
/// Slot States are defined in section 4.5.3.
|
||||||
|
///
|
||||||
|
/// As Output, since software initializes all fields of the Device Context data structure to ‘0’, this field
|
||||||
|
/// shall initially indicate the Disabled state.
|
||||||
|
///
|
||||||
|
/// As Input, software shall initialize the field to ‘0’.
|
||||||
|
/// Refer to section 4.5.3 for more information on Slot State.
|
||||||
|
#[bits(5)]
|
||||||
|
pub slot_state: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SlotContext {
|
||||||
|
pub fields: SlotContextFields,
|
||||||
|
__: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<SlotContext>() == 0x20);
|
||||||
120
rust/sys/voyageurs/src/xhci/data_structures/trb.rs
Normal file
120
rust/sys/voyageurs/src/xhci/data_structures/trb.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
use bitfield_struct::{bitenum, bitfield};
|
||||||
|
|
||||||
|
#[bitenum]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum TrbType {
|
||||||
|
#[fallback]
|
||||||
|
Reserved = 0,
|
||||||
|
Normal = 1,
|
||||||
|
SetupStage = 2,
|
||||||
|
DataStage = 3,
|
||||||
|
StatusStage = 4,
|
||||||
|
Isoch = 5,
|
||||||
|
Link = 6,
|
||||||
|
EventData = 7,
|
||||||
|
NoOp = 8,
|
||||||
|
EnableSlotCommand = 9,
|
||||||
|
DisableSlotCommand = 10,
|
||||||
|
AddressDeviceCommand = 11,
|
||||||
|
ConfigureEndpointCommand = 12,
|
||||||
|
EvaluateContextCommand = 13,
|
||||||
|
ResetEndpointCommand = 14,
|
||||||
|
StopEndpointCommand = 15,
|
||||||
|
SetTRDequeuePointerCommand = 16,
|
||||||
|
ResetDeviceCommand = 17,
|
||||||
|
ForceEventCommand = 18,
|
||||||
|
NegotiateBandwidthCommand = 19,
|
||||||
|
SetLatencyToleranceValueCommand = 20,
|
||||||
|
GetPortBandwidthCommand = 21,
|
||||||
|
ForceHeaderCommand = 22,
|
||||||
|
NoOpCommand = 23,
|
||||||
|
GetExtendedPropertyCommand = 24,
|
||||||
|
SetExtendedPropertyCommand = 25,
|
||||||
|
TransferEvent = 32,
|
||||||
|
CommandCompletionEvent = 33,
|
||||||
|
PortStatusChangeEvent = 34,
|
||||||
|
BandwidthRequestEvent = 35,
|
||||||
|
DoorbellEvent = 36,
|
||||||
|
HostControllerEvent = 37,
|
||||||
|
DeviceNotificationEvent = 38,
|
||||||
|
MFINDEXWrapEvent = 39,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct TransferRequestBlock {
|
||||||
|
pub parameter: u64,
|
||||||
|
pub status: u32,
|
||||||
|
pub cycle: bool,
|
||||||
|
evaluate_next: bool,
|
||||||
|
flag_2: bool,
|
||||||
|
flag_3: bool,
|
||||||
|
flag_4: bool,
|
||||||
|
flag_5: bool,
|
||||||
|
flag_6: bool,
|
||||||
|
flag_7: bool,
|
||||||
|
flag_8: bool,
|
||||||
|
flag_9: bool,
|
||||||
|
#[bits(6)]
|
||||||
|
pub trb_type: TrbType,
|
||||||
|
control: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransferRequestBlock {}
|
||||||
|
|
||||||
|
pub trait TypedTrb
|
||||||
|
where
|
||||||
|
Self: Into<u128> + From<u128> + Copy,
|
||||||
|
{
|
||||||
|
fn from_trb(trb: TransferRequestBlock) -> Self {
|
||||||
|
trb.into_bits().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_trb(self) -> TransferRequestBlock {
|
||||||
|
Into::<u128>::into(self).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u128)]
|
||||||
|
pub struct TrbLink {
|
||||||
|
/// Ring Segment Pointer Hi and Lo. These fields represent the high order bits of the 64-bit base
|
||||||
|
/// address of the next Ring Segment.
|
||||||
|
/// The memory structure referenced by this physical memory pointer shall begin on a 16-byte
|
||||||
|
/// address boundary.
|
||||||
|
pub ring_segment_pointer: u64,
|
||||||
|
#[bits(22)]
|
||||||
|
__: u32,
|
||||||
|
/// Interrupter Target. This field defines the index of the Interrupter that will receive Transfer
|
||||||
|
/// Events generated by this TRB. Valid values are between 0 and MaxIntrs-1.
|
||||||
|
/// This field is ignored by the xHC on Command Rings.
|
||||||
|
#[bits(10)]
|
||||||
|
pub interrupter_target: u16,
|
||||||
|
/// Cycle bit (C). This bit is used to mark the Enqueue Pointer location of a Transfer or Command
|
||||||
|
/// Ring.
|
||||||
|
pub cycle: bool,
|
||||||
|
/// Toggle Cycle (TC). When set to ‘1’, the xHC shall toggle its interpretation of the Cycle bit. When
|
||||||
|
/// cleared to ‘0’, the xHC shall continue to the next segment using its current interpretation of the
|
||||||
|
/// Cycle bit.
|
||||||
|
pub toggle_cycle: bool,
|
||||||
|
__: bool,
|
||||||
|
__: bool,
|
||||||
|
/// Chain bit (CH). Set to ‘1’ by software to associate this TRB with the next TRB on the Ring. A
|
||||||
|
/// Transfer Descriptor (TD) is defined as one or more TRBs. The Chain bit is used to identify the
|
||||||
|
/// TRBs that comprise a TD. Refer to section 4.11.7 for more information on Link TRB placement
|
||||||
|
/// within a TD. On a Command Ring this bit is ignored by the xHC.
|
||||||
|
#[bits(default = true)]
|
||||||
|
chain: bool,
|
||||||
|
/// Interrupt On Completion (IOC). If this bit is set to ‘1’, it specifies that when this TRB completes,
|
||||||
|
/// the Host Controller shall notify the system of the completion by placing an Event TRB on the
|
||||||
|
/// Event ring and sending an interrupt at the next interrupt threshold.
|
||||||
|
pub interrupt_on_completion: bool,
|
||||||
|
#[bits(4)]
|
||||||
|
__: u8,
|
||||||
|
/// TRB Type. This field is set to Link TRB type. Refer to Table 6-91 for the definition of the Type
|
||||||
|
/// TRB IDs.
|
||||||
|
#[bits(6, default = TrbType::Link)]
|
||||||
|
trb_type: TrbType,
|
||||||
|
__: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypedTrb for TrbLink {}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
use core::{
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
slice::SliceIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mammoth::physical_box::PhysicalBox;
|
||||||
|
|
||||||
|
use crate::xhci::data_structures::TransferRequestBlock;
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct TrbRingSegment(PhysicalBox<[TransferRequestBlock]>);
|
||||||
|
|
||||||
|
impl TrbRingSegment {
|
||||||
|
pub fn new(size: usize) -> Self {
|
||||||
|
Self(PhysicalBox::default_with_count(
|
||||||
|
TransferRequestBlock::default(),
|
||||||
|
size,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn physical_address(&self) -> usize {
|
||||||
|
self.0.physical_address()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Index<I> for TrbRingSegment
|
||||||
|
where
|
||||||
|
I: SliceIndex<[TransferRequestBlock]>,
|
||||||
|
{
|
||||||
|
type Output = I::Output;
|
||||||
|
|
||||||
|
fn index(&self, index: I) -> &Self::Output {
|
||||||
|
&self.0[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> IndexMut<I> for TrbRingSegment
|
||||||
|
where
|
||||||
|
I: SliceIndex<[TransferRequestBlock]>,
|
||||||
|
{
|
||||||
|
fn index_mut(&mut self, index: I) -> &mut Self::Output {
|
||||||
|
&mut self.0[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
113
rust/sys/voyageurs/src/xhci/device_context_base_array.rs
Normal file
113
rust/sys/voyageurs/src/xhci/device_context_base_array.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use mammoth::physical_box::PhysicalBox;
|
||||||
|
|
||||||
|
use crate::xhci::{
|
||||||
|
data_structures::{
|
||||||
|
DeviceContext, EndpointContextFields, EndpointState, EndpointType, InputContext,
|
||||||
|
TRDequeuePointer, TransferEvent,
|
||||||
|
},
|
||||||
|
registers::DoorbellPointer,
|
||||||
|
trb_ring::TrbRing,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeviceContextBaseArray(PhysicalBox<[u64]>);
|
||||||
|
|
||||||
|
impl DeviceContextBaseArray {
|
||||||
|
pub fn new(max_slots: u8) -> Self {
|
||||||
|
Self(PhysicalBox::default_with_count(0, max_slots as usize + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceSlot {
|
||||||
|
device_context: PhysicalBox<DeviceContext>,
|
||||||
|
endpoint_0_transfer_ring: TrbRing<TransferEvent>,
|
||||||
|
doorbell: DoorbellPointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceSlot {
|
||||||
|
fn new(doorbell: DoorbellPointer) -> Self {
|
||||||
|
Self {
|
||||||
|
device_context: PhysicalBox::new(DeviceContext::default()),
|
||||||
|
endpoint_0_transfer_ring: TrbRing::new(),
|
||||||
|
doorbell,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn send_control_command(&mut self) {
|
||||||
|
self.doorbell.ring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DeviceSlotManager {
|
||||||
|
device_context_base_array: DeviceContextBaseArray,
|
||||||
|
slots: Box<[Option<DeviceSlot>]>,
|
||||||
|
doorbells: Box<[Option<DoorbellPointer>]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceSlotManager {
|
||||||
|
pub fn new(max_slots: u8, doorbells: Box<[DoorbellPointer]>) -> Self {
|
||||||
|
assert!(
|
||||||
|
doorbells.len() == max_slots as usize,
|
||||||
|
"Got an incorrect doorbell slice size."
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
device_context_base_array: DeviceContextBaseArray::new(max_slots),
|
||||||
|
slots: core::iter::repeat_with(|| None)
|
||||||
|
.take(max_slots as usize)
|
||||||
|
.collect(),
|
||||||
|
doorbells: doorbells.into_iter().map(|d| Some(d)).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn device_context_base_array_physical_address(&self) -> usize {
|
||||||
|
self.device_context_base_array.0.physical_address()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a slot and an input context for an address device command.
|
||||||
|
///
|
||||||
|
/// Follows section 4.6.5 of the XHCI spec.
|
||||||
|
pub fn prep_slot_for_address_device(
|
||||||
|
&mut self,
|
||||||
|
slot_id: u8,
|
||||||
|
port_number: u8,
|
||||||
|
) -> PhysicalBox<InputContext> {
|
||||||
|
// TODO: Ensure alignment
|
||||||
|
let device_slot = DeviceSlot::new(
|
||||||
|
self.doorbells[(slot_id - 1) as usize]
|
||||||
|
.take()
|
||||||
|
.expect("Slot already allocated."),
|
||||||
|
);
|
||||||
|
let mut input_context = PhysicalBox::new(InputContext::default());
|
||||||
|
|
||||||
|
// The Add Context flags for the Slot Context and the Endpoint 0 Context shall be set to ‘1’.
|
||||||
|
input_context.input_control_context.add_context_flags = 0x3;
|
||||||
|
|
||||||
|
// See XHCI 4.5.2 for information
|
||||||
|
input_context.slot_context.fields = input_context
|
||||||
|
.slot_context
|
||||||
|
.fields
|
||||||
|
.with_root_hub_port_number(port_number)
|
||||||
|
.with_route_string(0)
|
||||||
|
.with_context_entries(1)
|
||||||
|
.with_interrupter_target(0);
|
||||||
|
|
||||||
|
// The Endpoint 0 Context data structure in the
|
||||||
|
// Input Context shall define valid values for the TR Dequeue Pointer, EP Type, Error
|
||||||
|
// Count (CErr), and Max Packet Size fields. The MaxPStreams, Max Burst Size, and
|
||||||
|
// EP State values shall be cleared to '0'
|
||||||
|
input_context.endpoint_context_0.tr_deque_pointer = TRDequeuePointer::new()
|
||||||
|
.with_pointer(device_slot.endpoint_0_transfer_ring.physical_base_address() as u64)
|
||||||
|
.with_dequeue_cycle_state(true);
|
||||||
|
|
||||||
|
input_context.endpoint_context_0.fields = EndpointContextFields::new()
|
||||||
|
.with_endpoint_type(EndpointType::Control)
|
||||||
|
.with_max_primary_streams(0)
|
||||||
|
.with_max_burst_size(0)
|
||||||
|
.with_endpoint_state(EndpointState::Disabled);
|
||||||
|
|
||||||
|
self.device_context_base_array.0[slot_id as usize] =
|
||||||
|
device_slot.device_context.physical_address() as u64;
|
||||||
|
self.slots[slot_id as usize - 1] = Some(device_slot);
|
||||||
|
|
||||||
|
input_context
|
||||||
|
}
|
||||||
|
}
|
||||||
282
rust/sys/voyageurs/src/xhci/driver.rs
Normal file
282
rust/sys/voyageurs/src/xhci/driver.rs
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use mammoth::sync::Mutex;
|
||||||
|
use mammoth::task::Spawner;
|
||||||
|
use mammoth::task::Task;
|
||||||
|
|
||||||
|
use super::registers::{self};
|
||||||
|
use crate::xhci::data_structures::AddressDeviceCommand;
|
||||||
|
use crate::xhci::data_structures::CommandCompletionCode;
|
||||||
|
use crate::xhci::data_structures::CommandCompletionEvent;
|
||||||
|
use crate::xhci::data_structures::CommandTrb;
|
||||||
|
use crate::xhci::data_structures::EnableSlotCommand;
|
||||||
|
use crate::xhci::data_structures::EventTrb;
|
||||||
|
use crate::xhci::data_structures::NoOpCommand;
|
||||||
|
use crate::xhci::data_structures::PortStatusChangeEvent;
|
||||||
|
use crate::xhci::device_context_base_array::DeviceSlotManager;
|
||||||
|
use crate::xhci::interrupter::Interrupter;
|
||||||
|
use crate::xhci::registers::DoorbellPointer;
|
||||||
|
use crate::xhci::registers::HostControllerOperationalWrapper;
|
||||||
|
use crate::xhci::registers::InterrupterRegisterSet;
|
||||||
|
use crate::xhci::registers::PortStatusAndControl;
|
||||||
|
use crate::xhci::trb_ring::CommandRing;
|
||||||
|
|
||||||
|
pub struct XHCIDriver {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pci_device: pci::PciDevice,
|
||||||
|
capabilities: registers::HostControllerCapabilities,
|
||||||
|
operational: HostControllerOperationalWrapper,
|
||||||
|
command_ring: Mutex<CommandRing>,
|
||||||
|
// TODO: Add multiple interrupters.
|
||||||
|
interrupter: Mutex<Interrupter>,
|
||||||
|
device_slot_manager: Mutex<DeviceSlotManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XHCIDriver {
|
||||||
|
pub fn from_pci_device(mut pci_device: pci::PciDevice) -> Self {
|
||||||
|
let address =
|
||||||
|
((pci_device.header().bars[1] as usize) << 32) | (pci_device.header().bars[0] as usize);
|
||||||
|
let irq_port_cap = pci_device.register_msi().unwrap();
|
||||||
|
|
||||||
|
let (operational, capabilities) = HostControllerOperationalWrapper::new(address);
|
||||||
|
|
||||||
|
let max_slots = capabilities.params_1.max_device_slots();
|
||||||
|
let doorbell_physical = address + capabilities.doorbell_offset as usize;
|
||||||
|
let (command_doorbell, slot_doorbells) =
|
||||||
|
DoorbellPointer::create_command_and_slots(doorbell_physical, max_slots);
|
||||||
|
|
||||||
|
// Offset to skip the mfindex register.
|
||||||
|
let interrupter_registers = mammoth::mem::map_direct_physical_and_leak(
|
||||||
|
address + capabilities.runtime_register_space_offset as usize,
|
||||||
|
size_of::<InterrupterRegisterSet>() * 2,
|
||||||
|
);
|
||||||
|
let interrupter_registers = unsafe { interrupter_registers.add(1) };
|
||||||
|
|
||||||
|
let mut driver = Self {
|
||||||
|
pci_device,
|
||||||
|
capabilities,
|
||||||
|
operational,
|
||||||
|
command_ring: Mutex::new(CommandRing::new(command_doorbell)),
|
||||||
|
interrupter: Mutex::new(Interrupter::new(interrupter_registers, irq_port_cap)),
|
||||||
|
device_slot_manager: Mutex::new(DeviceSlotManager::new(max_slots, slot_doorbells)),
|
||||||
|
};
|
||||||
|
driver.initialize();
|
||||||
|
driver
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(&mut self) {
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Stopping XHCI Controller.");
|
||||||
|
|
||||||
|
// Stop the host controller.
|
||||||
|
self.operational
|
||||||
|
.update_command(|cmd| cmd.with_run_stop(false));
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Waiting for controller to halt.");
|
||||||
|
|
||||||
|
// Sleep until the controller is halted.
|
||||||
|
let mut status = self.operational.read_status();
|
||||||
|
while !status.host_controller_halted() {
|
||||||
|
// TODO: Sleep for how long?
|
||||||
|
mammoth::syscall::thread_sleep(50).unwrap();
|
||||||
|
status = self.operational.read_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Resetting Controller.");
|
||||||
|
|
||||||
|
self.operational
|
||||||
|
.update_command(|cmd| cmd.with_host_controller_reset(true));
|
||||||
|
|
||||||
|
let mut command: registers::UsbCommand = self.operational.read_command();
|
||||||
|
while command.host_controller_reset() {
|
||||||
|
// TODO: Sleep for how long?
|
||||||
|
mammoth::syscall::thread_sleep(50).unwrap();
|
||||||
|
command = self.operational.read_command();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("XHCI Controller Reset, waiting ready.");
|
||||||
|
|
||||||
|
let mut status = self.operational.read_status();
|
||||||
|
while status.controller_not_ready() {
|
||||||
|
// TODO: Sleep for how long?
|
||||||
|
mammoth::syscall::thread_sleep(50).unwrap();
|
||||||
|
status = self.operational.read_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("XHCI Controller Ready.");
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Setting Command Ring");
|
||||||
|
|
||||||
|
self.operational.set_command_ring_dequeue_pointer(
|
||||||
|
self.command_ring.lock().trb_ring.physical_base_address(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Setting DCBA.");
|
||||||
|
|
||||||
|
self.operational
|
||||||
|
.set_device_context_base_address_array_pointer(
|
||||||
|
self.device_slot_manager
|
||||||
|
.lock()
|
||||||
|
.device_context_base_array_physical_address(),
|
||||||
|
);
|
||||||
|
// We tell the controller that we can support as many slots as it does because
|
||||||
|
// we allocate a full 4K page to the DCBA, which is 256 entries and the max
|
||||||
|
// slots are 255.
|
||||||
|
self.operational.update_configure(|cfg| {
|
||||||
|
cfg.with_max_device_slots_enabled(self.capabilities.params_1.max_device_slots())
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
self.capabilities.params_2.max_scratchpad_buffers() == 0,
|
||||||
|
"Unsupported scratchpad buffers."
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Resetting event ring.");
|
||||||
|
// SAFETY: The HC is stopped.
|
||||||
|
unsafe { self.interrupter.lock().reset() };
|
||||||
|
|
||||||
|
self.operational
|
||||||
|
.update_command(|cmd| cmd.with_run_stop(true).with_interrupter_enable(true));
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Enabled interrupts and controller.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_loop(self: Arc<Self>, spawner: Spawner) {
|
||||||
|
let completion_handler = |trb| {
|
||||||
|
self.clone().handle_completion(spawner.clone(), trb);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.interrupter.lock().interrupt_loop(completion_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_completion(self: Arc<XHCIDriver>, spawner: Spawner, trb: EventTrb) {
|
||||||
|
match trb {
|
||||||
|
EventTrb::Transfer(trb) => {
|
||||||
|
mammoth::debug!("Unhandled transfer event: {:?}", trb);
|
||||||
|
}
|
||||||
|
EventTrb::CommandCompletion(trb) => {
|
||||||
|
self.command_ring
|
||||||
|
.lock()
|
||||||
|
.trb_ring
|
||||||
|
.handle_completion(trb, trb.command_trb_pointer() as usize);
|
||||||
|
}
|
||||||
|
EventTrb::PortStatusChange(trb) => {
|
||||||
|
let self_clone = self.clone();
|
||||||
|
spawner.spawn(Task::new(async move {
|
||||||
|
self_clone.port_status_change(trb).await
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
EventTrb::BandwidthRequest(trb) => {
|
||||||
|
mammoth::debug!("Unhandled bandwidth request event: {:?}", trb);
|
||||||
|
}
|
||||||
|
EventTrb::Doorbell(trb) => {
|
||||||
|
mammoth::debug!("Unhandled doorbell event: {:?}", trb);
|
||||||
|
}
|
||||||
|
EventTrb::HostController(trb) => {
|
||||||
|
mammoth::debug!("Unhandled host controller event: {:?}", trb);
|
||||||
|
}
|
||||||
|
EventTrb::DeviceNotification(trb) => {
|
||||||
|
mammoth::debug!("Unhandled device notification event: {:?}", trb);
|
||||||
|
}
|
||||||
|
EventTrb::MFINDEXWrap(trb) => {
|
||||||
|
mammoth::debug!("Unhandled MFINDEX wrap event: {:?}", trb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_command(&self, trb: CommandTrb) -> CommandCompletionEvent {
|
||||||
|
// Split the future and the await so the lock is dropped before we await.
|
||||||
|
let future = { self.command_ring.lock().enqueue_command(trb) };
|
||||||
|
future.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn startup(&self) {
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Sending no op command.");
|
||||||
|
|
||||||
|
let result = self.send_command(NoOpCommand::new().into()).await;
|
||||||
|
|
||||||
|
assert_eq!(result.completion_code(), CommandCompletionCode::Success);
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Successfully tested no op command.");
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Resetting all connected ports.");
|
||||||
|
for port_index in 0..self.operational.num_ports() {
|
||||||
|
self.operational
|
||||||
|
.update_port_status(port_index, |p| p.clear_change_bits());
|
||||||
|
}
|
||||||
|
|
||||||
|
for port_index in 0..self.operational.num_ports() {
|
||||||
|
let status = self.operational.get_port(port_index).status_and_control;
|
||||||
|
if status.port_power() && status.current_connect_status() {
|
||||||
|
mammoth::debug!("Resetting port {}", port_index);
|
||||||
|
self.operational.update_port_status(port_index, |_| {
|
||||||
|
PortStatusAndControl::new()
|
||||||
|
.with_port_reset(true)
|
||||||
|
.with_port_power(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn port_status_change(self: Arc<Self>, status_change: PortStatusChangeEvent) {
|
||||||
|
// Ports are indexed from 1.
|
||||||
|
let port_id = status_change.port_id();
|
||||||
|
let port_index = (port_id - 1) as usize;
|
||||||
|
|
||||||
|
let port_status = self.operational.get_port(port_index).status_and_control;
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Port status change for port {}", port_id);
|
||||||
|
|
||||||
|
if !port_status.port_reset_change() {
|
||||||
|
mammoth::debug!(
|
||||||
|
"Unknown port status event, not handling. status= {:?}",
|
||||||
|
port_status
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.operational
|
||||||
|
.update_port_status(port_index, |s| s.clear_change_bits());
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Enabling slot.");
|
||||||
|
|
||||||
|
let resp = self.send_command(EnableSlotCommand::new().into()).await;
|
||||||
|
assert_eq!(resp.completion_code(), CommandCompletionCode::Success);
|
||||||
|
|
||||||
|
let slot = resp.slot_id();
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Creating slot data structures in slot {}.", slot);
|
||||||
|
|
||||||
|
let input_context = self
|
||||||
|
.device_slot_manager
|
||||||
|
.lock()
|
||||||
|
.prep_slot_for_address_device(slot, port_id);
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
mammoth::debug!("Sending address device.");
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.send_command(
|
||||||
|
AddressDeviceCommand::new()
|
||||||
|
.with_slot_id(slot)
|
||||||
|
.with_input_context_pointer(input_context.physical_address() as u64)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.completion_code(), CommandCompletionCode::Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
rust/sys/voyageurs/src/xhci/event_ring.rs
Normal file
70
rust/sys/voyageurs/src/xhci/event_ring.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::xhci::{
|
||||||
|
data_structures::{EventRingSegmentTable, EventTrb, TransferRequestBlock, TrbRingSegment},
|
||||||
|
trb_ring::TrbPointer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct EventRing {
|
||||||
|
segment_table: EventRingSegmentTable,
|
||||||
|
segments: Vec<TrbRingSegment>,
|
||||||
|
cycle_bit: bool,
|
||||||
|
trb_pointer: TrbPointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventRing {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
// Software maintains an Event Ring Consumer Cycle State (CCS) bit, initializing it
|
||||||
|
// to ‘1’...
|
||||||
|
let cycle_bit = true;
|
||||||
|
let mut event_ring = Self {
|
||||||
|
segment_table: EventRingSegmentTable::new(1),
|
||||||
|
segments: [TrbRingSegment::new(100)].into(),
|
||||||
|
cycle_bit,
|
||||||
|
trb_pointer: TrbPointer::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
event_ring.segment_table[0].update_from_trb_ring(&event_ring.segments[0]);
|
||||||
|
|
||||||
|
event_ring
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_table(&self) -> &EventRingSegmentTable {
|
||||||
|
&self.segment_table
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erdp_physical_address(&self) -> usize {
|
||||||
|
self.segments[self.trb_pointer.segment_index].physical_address()
|
||||||
|
+ self.trb_pointer.segment_physical_offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_trb(&self) -> TransferRequestBlock {
|
||||||
|
// TODO: These should be volatile reads.
|
||||||
|
self.segments[self.trb_pointer.segment_index][self.trb_pointer.segment_offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_pointer(&mut self) {
|
||||||
|
self.trb_pointer.segment_offset += 1;
|
||||||
|
|
||||||
|
if self.trb_pointer.segment_offset == self.segments[self.trb_pointer.segment_index].len() {
|
||||||
|
self.trb_pointer.segment_index += 1;
|
||||||
|
self.trb_pointer.segment_offset = 0;
|
||||||
|
|
||||||
|
if self.trb_pointer.segment_index == self.segments.len() {
|
||||||
|
// Wrap around to front.
|
||||||
|
self.trb_pointer.segment_index = 0;
|
||||||
|
self.cycle_bit = !self.cycle_bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_next(&mut self) -> Option<EventTrb> {
|
||||||
|
let curr = self.current_trb();
|
||||||
|
if curr.cycle() != self.cycle_bit {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.increment_pointer();
|
||||||
|
Some(curr.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
rust/sys/voyageurs/src/xhci/interrupter.rs
Normal file
60
rust/sys/voyageurs/src/xhci/interrupter.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
use mammoth::cap::Capability;
|
||||||
|
|
||||||
|
use crate::xhci::{
|
||||||
|
data_structures::{EventTrb, TransferRequestBlock},
|
||||||
|
event_ring::EventRing,
|
||||||
|
registers::{InterrupterModeration, InterrupterRegisterSet, InterrupterRegisters},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Interrupter {
|
||||||
|
event_ring: EventRing,
|
||||||
|
register_set: InterrupterRegisters,
|
||||||
|
irq_port_cap: Capability,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interrupter {
|
||||||
|
pub fn new(
|
||||||
|
interrupter_register_set: NonNull<InterrupterRegisterSet>,
|
||||||
|
irq_port_cap: Capability,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
event_ring: EventRing::new(),
|
||||||
|
register_set: InterrupterRegisters::new(interrupter_register_set),
|
||||||
|
irq_port_cap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - HC Must be halted for interrupter 0.
|
||||||
|
pub unsafe fn reset(&mut self) {
|
||||||
|
// SAFETY:
|
||||||
|
// - THe segment table is size 1.
|
||||||
|
unsafe {
|
||||||
|
self.register_set.set_event_ring(
|
||||||
|
self.event_ring.segment_table(),
|
||||||
|
self.event_ring.erdp_physical_address(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.register_set.set_moderation(
|
||||||
|
InterrupterModeration::new()
|
||||||
|
.with_interrupt_moderation_interval(4000)
|
||||||
|
.with_interrupt_moderation_counter(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.register_set.enable_interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_loop(&mut self, completion_handler: impl Fn(EventTrb)) {
|
||||||
|
loop {
|
||||||
|
let _ = mammoth::syscall::port_recv(&self.irq_port_cap, &mut [], &mut []).unwrap();
|
||||||
|
while let Some(trb) = self.event_ring.get_next() {
|
||||||
|
completion_handler(trb);
|
||||||
|
}
|
||||||
|
self.register_set
|
||||||
|
.update_dequeue_pointer_clearing_busy(self.event_ring.erdp_physical_address());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
rust/sys/voyageurs/src/xhci/mod.rs
Normal file
7
rust/sys/voyageurs/src/xhci/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod data_structures;
|
||||||
|
mod device_context_base_array;
|
||||||
|
pub mod driver;
|
||||||
|
mod event_ring;
|
||||||
|
mod interrupter;
|
||||||
|
mod registers;
|
||||||
|
mod trb_ring;
|
||||||
344
rust/sys/voyageurs/src/xhci/registers/capabilities.rs
Normal file
344
rust/sys/voyageurs/src/xhci/registers/capabilities.rs
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct HostControllerCapabilitiesLengthAndVersion {
|
||||||
|
/// This register is used as an offset to add to register base to find the beginning of
|
||||||
|
/// the Operational Register Space.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub cap_length: u8,
|
||||||
|
__: u8,
|
||||||
|
/// This is a two-byte register containing a BCD encoding of the xHCI specification
|
||||||
|
/// revision number supported by this host controller. The most significant byte of
|
||||||
|
/// this register represents a major revision and the least significant byte contains
|
||||||
|
/// the minor revision extensions. e.g. 0100h corresponds to xHCI version 1.0.0, or
|
||||||
|
/// 0110h corresponds to xHCI version 1.1.0, etc
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub hci_version: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct HCSParams1 {
|
||||||
|
/// Number of Device Slots (MaxSlots). This field specifies the maximum number of Device
|
||||||
|
/// Context Structures and Doorbell Array entries this host controller can support. Valid values are
|
||||||
|
/// in the range of 1 to 255. The value of ‘0’ is reserved.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub max_device_slots: u8,
|
||||||
|
|
||||||
|
/// Number of Interrupters (MaxIntrs). This field specifies the number of Interrupters implemented
|
||||||
|
/// on this host controller. Each Interrupter may be allocated to a MSI or MSI-X vector and controls
|
||||||
|
/// its generation and moderation.
|
||||||
|
///
|
||||||
|
/// The value of this field determines how many Interrupter Register Sets are addressable in the
|
||||||
|
/// Runtime Register Space (refer to section 5.5). Valid values are in the range of 1h to 400h. A ‘0’ in
|
||||||
|
/// this field is undefined.
|
||||||
|
#[bits(11, access=RO)]
|
||||||
|
pub max_interrupters: u16,
|
||||||
|
|
||||||
|
#[bits(5)]
|
||||||
|
__: u8,
|
||||||
|
/// Number of Ports (MaxPorts). This field specifies the maximum Port Number value, i.e. the
|
||||||
|
/// highest numbered Port Register Set that are addressable in the Operational Register Space
|
||||||
|
/// (refer to Table 5-18). Valid values are in the range of 1h to FFh.
|
||||||
|
///
|
||||||
|
/// The value in this field shall reflect the maximum Port Number value assigned by an xHCI
|
||||||
|
/// Supported Protocol Capability, described in section 7.2. Software shall refer to these capabilities
|
||||||
|
/// to identify whether a specific Port Number is valid, and the protocol supported by the
|
||||||
|
/// associated Port Register Set.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub max_ports: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct HCSParams2 {
|
||||||
|
/// Isochronous Scheduling Threshold (IST). Default = implementation dependent. The value in
|
||||||
|
/// this field indicates to system software the minimum distance (in time) that it is required to stay
|
||||||
|
/// ahead of the host controller while adding TRBs, in order to have the host controller process
|
||||||
|
/// them at the correct time. The value shall be specified in terms of number of
|
||||||
|
/// frames/microframes.
|
||||||
|
///
|
||||||
|
/// If bit [3] of IST is cleared to '0', software can add a TRB no later than IST[2:0] Microframes
|
||||||
|
/// before that TRB is scheduled to be executed.
|
||||||
|
///
|
||||||
|
/// If bit [3] of IST is set to '1', software can add a TRB no later than IST[2:0] Frames before that TRB
|
||||||
|
/// is scheduled to be executed.
|
||||||
|
///
|
||||||
|
/// Refer to Section 4.14.2 for details on how software uses this information for scheduling
|
||||||
|
/// isochronous transfers.
|
||||||
|
#[bits(4, access=RO)]
|
||||||
|
pub isochronous_scheduling_threshold: u8,
|
||||||
|
/// Event Ring Segment Table Max (ERST Max). Default = implementation dependent. Valid values
|
||||||
|
/// are 0 – 15. This field determines the maximum value supported the Event Ring Segment Table
|
||||||
|
/// Base Size registers (5.5.2.3.1), where:
|
||||||
|
///
|
||||||
|
/// The maximum number of Event Ring Segment Table entries = 2 ERST Max.
|
||||||
|
/// e.g. if the ERST Max = 7, then the xHC Event Ring Segment Table(s) supports up to 128 entries,
|
||||||
|
/// 15 then 32K entries, etc.
|
||||||
|
#[bits(4, access=RO)]
|
||||||
|
pub event_ring_segment_table_max: u8,
|
||||||
|
|
||||||
|
#[bits(13)]
|
||||||
|
__: u16,
|
||||||
|
|
||||||
|
/// Max Scratchpad Buffers (Max Scratchpad Bufs Hi). Default = implementation dependent. This
|
||||||
|
/// field indicates the high order 5 bits of the number of Scratchpad Buffers system software shall
|
||||||
|
/// reserve for the xHC. Refer to section 4.20 for more information.
|
||||||
|
#[bits(5, access=RO)]
|
||||||
|
max_scratchpad_buffers_hi: u16,
|
||||||
|
|
||||||
|
/// Scratchpad Restore (SPR). Default = implementation dependent. If Max Scratchpad Buffers is >
|
||||||
|
/// ‘0’ then this flag indicates whether the xHC uses the Scratchpad Buffers for saving state when
|
||||||
|
/// executing Save and Restore State operations. If Max Scratchpad Buffers is = ‘0’ then this flag
|
||||||
|
/// shall be ‘0’. Refer to section 4.23.2 for more information.
|
||||||
|
///
|
||||||
|
/// A value of ‘1’ indicates that the xHC requires the integrity of the Scratchpad Buffer space to be
|
||||||
|
/// maintained across power events.
|
||||||
|
///
|
||||||
|
/// A value of ‘0’ indicates that the Scratchpad Buffer space may be freed and reallocated between
|
||||||
|
/// power events.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub scratchpad_restore: bool,
|
||||||
|
|
||||||
|
/// Max Scratchpad Buffers (Max Scratchpad Bufs Lo). Default = implementation dependent. Valid
|
||||||
|
/// values for Max Scratchpad Buffers (Hi and Lo) are 0-1023. This field indicates the low order 5
|
||||||
|
/// bits of the number of Scratchpad Buffers system software shall reserve for the xHC. Refer to
|
||||||
|
/// section 4.20 for more information
|
||||||
|
#[bits(5, access=RO)]
|
||||||
|
max_scratchpad_buffers_lo: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HCSParams2 {
|
||||||
|
pub fn max_scratchpad_buffers(&self) -> u16 {
|
||||||
|
(self.max_scratchpad_buffers_hi()) << 5 | self.max_scratchpad_buffers_lo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct HCSParams3 {
|
||||||
|
/// U1 Device Exit Latency. Worst case latency to transition a root hub Port Link State (PLS) from
|
||||||
|
/// U1 to U0. Applies to all root hub ports.
|
||||||
|
/// The following are permissible values:
|
||||||
|
///
|
||||||
|
/// Value Description
|
||||||
|
/// 00h Zero
|
||||||
|
/// 01h Less than 1 μs
|
||||||
|
/// 02h Less than 2 μs.
|
||||||
|
/// …
|
||||||
|
/// 0Ah Less than 10 μs
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub u1_device_exit_latency: u8,
|
||||||
|
/// U2 Device Exit Latency. Worst case latency to transition from U2 to U0. Applies to all root hub
|
||||||
|
/// ports.
|
||||||
|
/// The following are permissible values:
|
||||||
|
/// Value Description
|
||||||
|
/// 0000h Zero
|
||||||
|
/// 0001h Less than 1 μs.
|
||||||
|
/// 0002h Less than 2 μs.
|
||||||
|
/// …
|
||||||
|
/// 07FFh Less than 2047 μs.
|
||||||
|
/// 0800-FFFFh Reserved
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub u2_device_exit_latency: u8,
|
||||||
|
__: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct HCCParams1 {
|
||||||
|
/// 64-bit Addressing Capability (AC64). This flag documents the addressing range capability of
|
||||||
|
/// this implementation. The value of this flag determines whether the xHC has implemented the
|
||||||
|
/// high order 32 bits of 64 bit register and data structure pointer fields. Values for this flag have the
|
||||||
|
/// following interpretation:
|
||||||
|
///
|
||||||
|
/// Value Description
|
||||||
|
/// 0 32-bit address memory pointers implemented
|
||||||
|
/// 1 64-bit address memory pointers implemented
|
||||||
|
///
|
||||||
|
/// If 32-bit address memory pointers are implemented, the xHC shall ignore the high order 32 bits
|
||||||
|
/// of 64 bit data structure pointer fields, and system software shall ignore the high order 32 bits of
|
||||||
|
/// 64 bit xHC registers.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub supports_64_bit: bool,
|
||||||
|
/// BW Negotiation Capability (BNC). This flag identifies whether the xHC has implemented the
|
||||||
|
/// Bandwidth Negotiation. Values for this flag have the following interpretation:
|
||||||
|
///
|
||||||
|
/// Value Description
|
||||||
|
/// 0 BW Negotiation not implemented
|
||||||
|
/// 1 BW Negotiation implemented
|
||||||
|
///
|
||||||
|
/// Refer to section 4.16 for more information on Bandwidth Negotiation.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub bandwidth_negotiation: bool,
|
||||||
|
/// Context Size (CSZ). If this bit is set to ‘1’, then the xHC uses 64 byte Context data structures. If
|
||||||
|
/// this bit is cleared to ‘0’, then the xHC uses 32 byte Context data structures.
|
||||||
|
/// Note: This flag does not apply to Stream Contexts.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub context_size: bool,
|
||||||
|
/// Port Power Control (PPC). This flag indicates whether the host controller implementation
|
||||||
|
/// includes port power control. A ‘1’ in this bit indicates the ports have port power switches. A ‘0’ in
|
||||||
|
/// this bit indicates the port do not have port power switches. The value of this flag affects the
|
||||||
|
/// functionality of the PP flag in each port status and control register (refer to Section 5.4.8)
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub port_power_control: bool,
|
||||||
|
/// Port Indicators (PIND). This bit indicates whether the xHC root hub ports support port indicator
|
||||||
|
/// control. When this bit is a ‘1’, the port status and control registers include a read/writeable field
|
||||||
|
/// for controlling the state of the port indicator. Refer to Section 5.4.8 for definition of the Port
|
||||||
|
/// Indicator Control field.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub port_indicators: bool,
|
||||||
|
/// Light HC Reset Capability (LHRC). This flag indicates whether the host controller implementation
|
||||||
|
/// supports a Light Host Controller Reset. A ‘1’ in this bit indicates that Light Host Controller Reset is
|
||||||
|
/// supported. A ‘0’ in this bit indicates that Light Host Controller Reset is not supported. The value
|
||||||
|
/// of this flag affects the functionality of the Light Host Controller Reset (LHCRST) flag in the
|
||||||
|
/// USBCMD register (refer to Section 5.4.1).
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub light_hc_reset_capability: bool,
|
||||||
|
/// Latency Tolerance Messaging Capability (LTC). This flag indicates whether the host controller
|
||||||
|
/// implementation supports Latency Tolerance Messaging (LTM). A ‘1’ in this bit indicates that LTM
|
||||||
|
/// is supported. A ‘0’ in this bit indicates that LTM is not supported. Refer to section 4.13.1 for more
|
||||||
|
/// information on LTM
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub latency_tolerance_messaging_capability: bool,
|
||||||
|
/// No Secondary SID Support (NSS). This flag indicates whether the host controller
|
||||||
|
/// implementation supports Secondary Stream IDs. A ‘1’ in this bit indicates that Secondary Stream
|
||||||
|
/// ID decoding is not supported. A ‘0’ in this bit indicates that Secondary Stream ID decoding is
|
||||||
|
/// supported. (refer to Sections 4.12.2 and 6.2.3)
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub no_secondary_sid_support: bool,
|
||||||
|
/// Parse All Event Data (PAE). This flag indicates whether the host controller implementation
|
||||||
|
/// Parses all Event Data TRBs while advancing to the next TD after a Short Packet, or it skips all but
|
||||||
|
/// the first Event Data TRB. A ‘1’ in this bit indicates that all Event Data TRBs are parsed. A ‘0’ in this
|
||||||
|
/// bit indicates that only the first Event Data TRB is parsed (refer to section 4.10.1.1).
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub parse_all_event_data: bool,
|
||||||
|
/// Stopped - Short Packet Capability (SPC). This flag indicates that the host controller
|
||||||
|
/// implementation is capable of generating a Stopped - Short Packet Completion Code. Refer to
|
||||||
|
/// section 4.6.9 for more information
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub stopped_short_packet_capability: bool,
|
||||||
|
/// Stopped EDTLA Capability (SEC). This flag indicates that the host controller implementation
|
||||||
|
/// Stream Context support a Stopped EDTLA field. Refer to sections 4.6.9, 4.12, and 6.4.4.1 for more
|
||||||
|
/// information.
|
||||||
|
/// Stopped EDTLA Capability support (i.e. SEC = '1') shall be mandatory for all xHCI 1.1 and xHCI 1.2
|
||||||
|
/// compliant xHCs.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub stopped_edtla_capability: bool,
|
||||||
|
/// Contiguous Frame ID Capability (CFC). This flag indicates that the host controller
|
||||||
|
/// implementation is capable of matching the Frame ID of consecutive Isoch TDs. Refer to section
|
||||||
|
/// 4.11.2.5 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub contiguous_frame_id_capability: bool,
|
||||||
|
/// Maximum Primary Stream Array Size (MaxPSASize). This fields identifies the maximum size
|
||||||
|
/// Primary Stream Array that the xHC supports. The Primary Stream Array size = 2MaxPSASize+1. Valid
|
||||||
|
/// MaxPSASize values are 0 to 15, where ‘0’ indicates that Streams are not supported
|
||||||
|
#[bits(4, access=RO)]
|
||||||
|
pub maximum_primary_stream_array_size: u8,
|
||||||
|
/// xHCI Extended Capabilities Pointer (xECP). This field indicates the existence of a capabilities list.
|
||||||
|
/// The value of this field indicates a relative offset, in 32-bit words, from Base to the beginning of
|
||||||
|
/// the first extended capability.
|
||||||
|
///
|
||||||
|
/// For example, using the offset of Base is 1000h and the xECP value of 0068h, we can calculated
|
||||||
|
/// the following effective address of the first extended capability:
|
||||||
|
/// 1000h + (0068h << 2) -> 1000h + 01A0h -> 11A0h
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub xhci_extended_capabilities_pointer: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct HCCParams2 {
|
||||||
|
/// U3 Entry Capability (U3C) - RO. This bit indicates whether the xHC Root Hub ports support port
|
||||||
|
/// Suspend Complete notification. When this bit is '1', PLC shall be asserted on any transition of
|
||||||
|
/// PLS to the U3 State. Refer to section 4.15.1 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub u3_entry_capability: bool,
|
||||||
|
/// Configure Endpoint Command Max Exit Latency Too Large Capability (CMC) - RO. This bit
|
||||||
|
/// indicates whether a Configure Endpoint Command is capable of generating a Max Exit Latency
|
||||||
|
/// Too Large Capability Error. When this bit is '1', a Max Exit Latency Too Large Capability Error
|
||||||
|
/// may be returned by a Configure Endpoint Command. When this bit is '0', a Max Exit Latency Too
|
||||||
|
/// Large Capability Error shall not be returned by a Configure Endpoint Command. This capability
|
||||||
|
/// is enabled by the CME flag in the USBCMD register. Refer to sections 4.23.5.2 and 5.4.1 for more
|
||||||
|
/// information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub configure_endpoint_command_max_exit_latency_too_large_capability: bool,
|
||||||
|
/// Force Save Context Capability (FSC) - RO. This bit indicates whether the xHC supports the
|
||||||
|
/// Force Save Context Capability. When this bit is '1', the Save State operation shall save any
|
||||||
|
/// cached Slot, Endpoint, Stream or other Context information to memory. Refer to
|
||||||
|
/// Implementation Note “FSC and Context handling by Save and Restore”, and sections 4.23.2 and
|
||||||
|
/// 5.4.1 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub force_save_context_capability: bool,
|
||||||
|
/// Compliance Transition Capability (CTC) - RO. This bit indicates whether the xHC USB3 Root
|
||||||
|
/// Hub ports support the Compliance Transition Enabled (CTE) flag. When this bit is ‘1’, USB3 Root
|
||||||
|
/// Hub port state machine transitions to the Compliance substate shall be explicitly enabled
|
||||||
|
/// software. When this bit is ‘0’, USB3 Root Hub port state machine transitions to the Compliance
|
||||||
|
/// substate are automatically enabled. Refer to section 4.19.1.2.4.1 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub compliance_transition_capability: bool,
|
||||||
|
/// Large ESIT Payload Capability (LEC) - RO. This bit indicates whether the xHC supports ESIT
|
||||||
|
/// Payloads greater than 48K bytes. When this bit is ‘1’, ESIT Payloads greater than 48K bytes are
|
||||||
|
/// supported. When this bit is ‘0’, ESIT Payloads greater than 48K bytes are not supported. Refer to
|
||||||
|
/// section 6.2.3.8 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub large_esit_payload_capability: bool,
|
||||||
|
/// Configuration Information Capability (CIC) - RO. This bit indicates if the xHC supports
|
||||||
|
/// extended Configuration Information. When this bit is 1, the Configuration Value, Interface
|
||||||
|
/// Number, and Alternate Setting fields in the Input Control Context are supported. When this bit is
|
||||||
|
/// 0, the extended Input Control Context fields are not supported. Refer to section 6.2.5.1 for more
|
||||||
|
/// information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub configuration_information_capability: bool,
|
||||||
|
/// Extended TBC Capability78 (ETC) - RO. This bit indicates if the TBC field in an Isoch TRB
|
||||||
|
/// supports the definition of Burst Counts greater than 65535 bytes. When this bit is ‘1’, the
|
||||||
|
/// Extended EBC capability is supported by the xHC. When this bit is ‘0’, it is not. Refer to section
|
||||||
|
/// 4.11.2.3 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub extended_tbc_capability: bool,
|
||||||
|
/// Extended TBC TRB Status Capability (ETC_TSC) - RO. This bit indicates if the TBC/TRBSts field
|
||||||
|
/// in an Isoch TRB indicates additional information regarding TRB in the TD. When this bit is ‘1’, the
|
||||||
|
/// Isoch TRB TD Size/TBC field presents TBC value and TBC/TRBSts field presents the TRBSts
|
||||||
|
/// value. When this bit is ‘0’ then the ETC/ETE values defines the TD Size/TBC field and TBC/RsvdZ
|
||||||
|
/// field. This capability shall be enabled only if LEC = ‘1’ and ETC=’1’. Refer to section 4.11.2.3 for
|
||||||
|
/// more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub extended_tbc_trb_status_capability: bool,
|
||||||
|
/// Get/Set Extended Property Capability (GSC) – RO. This bit indicates support for the Set
|
||||||
|
/// Extended Property and Get Extended Property commands. When this bit is ‘1’, the xHC supports
|
||||||
|
/// the Get Extended Property and Set Extended Property commands defined in section 4.6.17 and
|
||||||
|
/// section 4.6.18. When this bit is ‘0’, the xHC does not support the Get Extended Property and Set
|
||||||
|
/// Extended Property commands and the xHC does not support any of the associated Extended
|
||||||
|
/// Capabilities.
|
||||||
|
///
|
||||||
|
/// This bit shall only be set to ‘1’ if the xHC supports one or more extended capabilities that
|
||||||
|
/// require the Get Extended Property and Set Extended Property commands.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub get_set_extended_property_capability: bool,
|
||||||
|
/// Virtualization Based Trusted I/O Capability (VTC) – RO. This bit when set to 1, indicates that
|
||||||
|
/// the xHC supports the Virtualization based Trusted IO (VTIO) Capability. When this bit is 0, the
|
||||||
|
/// VTIO Capability is not supported. This capability is enabled by the VTIOE flag in the USBCMD
|
||||||
|
/// register.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub virtualization_based_trusted_io_capability: bool,
|
||||||
|
|
||||||
|
#[bits(22)]
|
||||||
|
__: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XHCI Spec Section 5.3
|
||||||
|
/// Note that for 64 bit implementations, the controller requires qword (32bit) accesses.
|
||||||
|
/// Hence the grouping of parameters here.
|
||||||
|
///
|
||||||
|
/// These registers are located at the addresses specified in BAR0 and BAR1 in the PCI Header.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct HostControllerCapabilities {
|
||||||
|
pub cap_length_and_version: HostControllerCapabilitiesLengthAndVersion,
|
||||||
|
pub params_1: HCSParams1,
|
||||||
|
pub params_2: HCSParams2,
|
||||||
|
pub params_3: HCSParams3,
|
||||||
|
pub cap_params_1: HCCParams1,
|
||||||
|
/// This register defines the offset of the Doorbell Array base address from the Base. (RO)
|
||||||
|
pub doorbell_offset: u32,
|
||||||
|
/// This register defines the offset of the xHCI Runtime Registers from the Base.
|
||||||
|
pub runtime_register_space_offset: u32,
|
||||||
|
pub cap_params_2: HCCParams2,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<HostControllerCapabilities>() == 0x20);
|
||||||
106
rust/sys/voyageurs/src/xhci/registers/doorbell.rs
Normal file
106
rust/sys/voyageurs/src/xhci/registers/doorbell.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
use volatile::VolatileRef;
|
||||||
|
|
||||||
|
/// The Doorbell Array is organized as an array of up to 256 Doorbell Registers. One
|
||||||
|
/// 32-bit Doorbell Register is defined in the array for each Device Slot. System
|
||||||
|
/// software utilizes the Doorbell Register to notify the xHC that it has Device Slot
|
||||||
|
/// related work for the xHC to perform.
|
||||||
|
///
|
||||||
|
/// The number of Doorbell Registers implemented by a particular instantiation of a
|
||||||
|
/// host controller is documented in the Number of Device Slots (MaxSlots) field of
|
||||||
|
/// the HCSPARAMS1 register (section 5.3.3).
|
||||||
|
///
|
||||||
|
/// These registers are pointed to by the Doorbell Offset Register (DBOFF) in the
|
||||||
|
/// xHC Capability register space. The Doorbell Array base address shall be Dword
|
||||||
|
/// 430 aligned and is calculated by adding the value in the DBOFF register (section
|
||||||
|
/// 5.3.7) to “Base” (the base address of the xHCI Capability register address space).
|
||||||
|
/// Refer to section 4.7 for more information on Doorbell registers.
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct Doorbell {
|
||||||
|
/// DB Target – RW. Doorbell Target. This field defines the target of the doorbell reference. The
|
||||||
|
/// table below defines the xHC notification that is generated by ringing the doorbell. Note that
|
||||||
|
/// Doorbell Register 0 is dedicated to Command Ring and decodes this field differently than the
|
||||||
|
/// other Doorbell Registers.
|
||||||
|
///
|
||||||
|
/// Device Context Doorbells (1-255)
|
||||||
|
/// Value Definition
|
||||||
|
/// 0 Reserved
|
||||||
|
/// 1 Control EP 0 Enqueue Pointer Update
|
||||||
|
/// 2 EP 1 OUT Enqueue Pointer Update
|
||||||
|
/// 3 EP 1 IN Enqueue Pointer Update
|
||||||
|
/// 4 EP 2 OUT Enqueue Pointer Update
|
||||||
|
/// 5 EP 2 IN Enqueue Pointer Update
|
||||||
|
/// … ...
|
||||||
|
/// 30 EP 15 OUT Enqueue Pointer Update
|
||||||
|
/// 31 EP 15 IN Enqueue Pointer Update
|
||||||
|
/// 32:247 Reserved
|
||||||
|
/// 248:255 Vendor Defined
|
||||||
|
///
|
||||||
|
/// Host Controller Doorbell (0)
|
||||||
|
/// Value Definition
|
||||||
|
/// 0 Command Doorbell
|
||||||
|
/// 1:247 Reserved
|
||||||
|
/// 248:255 Vendor Defined
|
||||||
|
///
|
||||||
|
/// This field returns ‘0’ when read and should be treated as “undefined” by software.
|
||||||
|
/// When the Command Doorbell is written, the DB Stream ID field shall be cleared to ‘0’.
|
||||||
|
db_target: u8,
|
||||||
|
_reserved: u8,
|
||||||
|
|
||||||
|
/// DB Stream ID - RW. Doorbell Stream ID. If the endpoint of a Device Context Doorbell defines
|
||||||
|
/// Streams, then this field shall be used to identify which Stream of the endpoint the doorbell
|
||||||
|
/// reference is targeting. System software is responsible for ensuring that the value written to this
|
||||||
|
/// field is valid.
|
||||||
|
///
|
||||||
|
/// If the endpoint defines Streams (MaxPStreams > 0), then 0, 65535 (No Stream) and 65534
|
||||||
|
/// (Prime) are reserved Stream ID values and shall not be written to this field.
|
||||||
|
///
|
||||||
|
/// If the endpoint does not define Streams (MaxPStreams = 0) and a non-'0' value is written to this
|
||||||
|
/// field, the doorbell reference shall be ignored.
|
||||||
|
///
|
||||||
|
/// This field only applies to Device Context Doorbells and shall be cleared to ‘0’ for Host Controller
|
||||||
|
/// Command Doorbells.
|
||||||
|
///
|
||||||
|
/// This field returns ‘0’ when read
|
||||||
|
db_stream_id: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct DoorbellPointer(VolatileRef<'static, Doorbell>);
|
||||||
|
|
||||||
|
impl DoorbellPointer {
|
||||||
|
// Construct a new doorbell pointer.
|
||||||
|
fn new(doorbell: NonNull<Doorbell>) -> Self {
|
||||||
|
// SAFETY:
|
||||||
|
// - We allocate this memory in the create
|
||||||
|
Self(unsafe { VolatileRef::new(doorbell) })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_command_and_slots(
|
||||||
|
doorbell_physical: usize,
|
||||||
|
max_slots: u8,
|
||||||
|
) -> (Self, Box<[Self]>) {
|
||||||
|
// Add one for the command doorbell.
|
||||||
|
let doorbell_cnt = max_slots as usize + 1;
|
||||||
|
let doorbell_array_size = size_of::<Doorbell>() * doorbell_cnt;
|
||||||
|
let doorbells: NonNull<Doorbell> =
|
||||||
|
mammoth::mem::map_direct_physical_and_leak(doorbell_physical, doorbell_array_size);
|
||||||
|
let first = DoorbellPointer::new(doorbells);
|
||||||
|
let remainder = (1..=max_slots)
|
||||||
|
.map(|offset| {
|
||||||
|
// SAFETY: We just allocated the array of this size above.
|
||||||
|
DoorbellPointer::new(unsafe { doorbells.add(offset as usize) })
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(first, remainder)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ring(&mut self, target: u8) {
|
||||||
|
self.0
|
||||||
|
.as_mut_ptr()
|
||||||
|
.write(Doorbell::new().with_db_target(target))
|
||||||
|
}
|
||||||
|
}
|
||||||
505
rust/sys/voyageurs/src/xhci/registers/host_controller.rs
Normal file
505
rust/sys/voyageurs/src/xhci/registers/host_controller.rs
Normal file
|
|
@ -0,0 +1,505 @@
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
use mammoth::{mem::map_direct_physical_and_leak, sync::Mutex};
|
||||||
|
use volatile::{VolatilePtr, VolatileRef, map_field};
|
||||||
|
|
||||||
|
use crate::xhci::registers::{
|
||||||
|
HostControllerCapabilities, HostControllerUsbPort, PortStatusAndControl,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct UsbCommand {
|
||||||
|
/// Run/Stop (R/S) – RW. Default = ‘0’. ‘1’ = Run. ‘0’ = Stop. When set to a ‘1’, the xHC proceeds with
|
||||||
|
/// execution of the schedule. The xHC continues execution as long as this bit is set to a ‘1’. When
|
||||||
|
/// this bit is cleared to ‘0’, the xHC completes any current or queued commands or TDs, and any
|
||||||
|
/// USB transactions associated with them, then halts.
|
||||||
|
///
|
||||||
|
/// Refer to section 5.4.1.1 for more information on how R/S shall be managed.
|
||||||
|
///
|
||||||
|
/// The xHC shall halt within 16 ms. after software clears the Run/Stop bit if the above conditions
|
||||||
|
/// have been met.
|
||||||
|
///
|
||||||
|
/// The HCHalted (HCH) bit in the USBSTS register indicates when the xHC has finished its pending
|
||||||
|
/// pipelined transactions and has entered the stopped state. Software shall not write a ‘1’ to this
|
||||||
|
/// flag unless the xHC is in the Halted state (i.e. HCH in the USBSTS register is ‘1’). Doing so may
|
||||||
|
/// yield undefined results. Writing a ‘0’ to this flag when the xHC is in the Running state (i.e. HCH =
|
||||||
|
/// ‘0’) and any Event Rings are in the Event Ring Full state (refer to section 4.9.4) may result in lost
|
||||||
|
/// events.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only controls the run state of
|
||||||
|
/// the xHC instance presented by the selected VF. Refer to section 8 for more information.
|
||||||
|
pub run_stop: bool,
|
||||||
|
/// Host Controller Reset (HCRST) – RW. Default = ‘0’. This control bit is used by software to reset
|
||||||
|
/// the host controller. The effects of this bit on the xHC and the Root Hub registers are similar to a
|
||||||
|
/// Chip Hardware Reset.
|
||||||
|
///
|
||||||
|
/// When software writes a ‘1’ to this bit, the Host Controller resets its internal pipelines, timers,
|
||||||
|
/// counters, state machines, etc. to their initial value. Any transaction currently in progress on the
|
||||||
|
/// USB is immediately terminated. A USB reset shall not be driven on USB2 downstream ports,
|
||||||
|
/// however a Hot or Warm Reset79 shall be initiated on USB3 Root Hub downstream ports.
|
||||||
|
///
|
||||||
|
/// PCI Configuration registers are not affected by this reset. All operational registers, including port
|
||||||
|
/// registers and port state machines are set to their initial values. Software shall reinitialize the
|
||||||
|
/// host controller as described in Section 4.2 in order to return the host controller to an
|
||||||
|
/// operational state.
|
||||||
|
///
|
||||||
|
/// This bit is cleared to ‘0’ by the Host Controller when the reset process is complete. Software
|
||||||
|
/// cannot terminate the reset process early by writing a ‘0’ to this bit and shall not write any xHC
|
||||||
|
/// Operational or Runtime registers until while HCRST is ‘1’. Note, the completion of the xHC reset
|
||||||
|
/// process is not gated by the Root Hub port reset process.
|
||||||
|
///
|
||||||
|
/// Software shall not set this bit to ‘1’ when the HCHalted (HCH) bit in the USBSTS register is a ‘0’.
|
||||||
|
/// Attempting to reset an actively running host controller may result in undefined behavior.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only resets the xHC instance
|
||||||
|
/// presented by the selected VF. Refer to section 8 for more information
|
||||||
|
pub host_controller_reset: bool,
|
||||||
|
/// Interrupter Enable (INTE) – RW. Default = ‘0’. This bit provides system software with a means of
|
||||||
|
/// enabling or disabling the host system interrupts generated by Interrupters. When this bit is a ‘1’,
|
||||||
|
/// then Interrupter host system interrupt generation is allowed, e.g. the xHC shall issue an interrupt
|
||||||
|
/// at the next interrupt threshold if the host system interrupt mechanism (e.g. MSI, MSI-X, etc.) is
|
||||||
|
/// enabled. The interrupt is acknowledged by a host system interrupt specific mechanism.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only enables the set of
|
||||||
|
/// Interrupters assigned to the selected VF. Refer to section 7.7.2 for more information.
|
||||||
|
pub interrupter_enable: bool,
|
||||||
|
/// Host System Error Enable (HSEE) – RW. Default = ‘0’. When this bit is a ‘1’, and the HSE bit in
|
||||||
|
/// the USBSTS register is a ‘1’, the xHC shall assert out-of-band error signaling to the host. The
|
||||||
|
/// signaling is acknowledged by software clearing the HSE bit. Refer to section 4.10.2.6 for more
|
||||||
|
/// information.
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the effect of the assertion of this bit on
|
||||||
|
/// the Physical Function (PF0) is determined by the VMM. Refer to section 8 for more information
|
||||||
|
pub host_system_error_enable: bool,
|
||||||
|
#[bits(3)]
|
||||||
|
__: u8,
|
||||||
|
/// Light Host Controller Reset (LHCRST) – RO or RW. Optional normative. Default = ‘0’. If the Light
|
||||||
|
/// HC Reset Capability (LHRC) bit in the HCCPARAMS1 register is ‘1’, then this flag allows the driver
|
||||||
|
/// to reset the xHC without affecting the state of the ports.
|
||||||
|
///
|
||||||
|
/// A system software read of this bit as ‘0’ indicates the Light Host Controller Reset has completed
|
||||||
|
/// and it is safe for software to re-initialize the xHC. A software read of this bit as a ‘1’ indicates the
|
||||||
|
/// Light Host Controller Reset has not yet completed.
|
||||||
|
///
|
||||||
|
/// If not implemented, a read of this flag shall always return a ‘0’.
|
||||||
|
///
|
||||||
|
/// All registers in the Aux Power well shall maintain the values that had been asserted prior to the
|
||||||
|
/// Light Host Controller Reset. Refer to section 4.23.1 for more information.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only generates a Light Reset to
|
||||||
|
/// the xHC instance presented by the selected VF, e.g. Disable the VFs’ device slots and set the
|
||||||
|
/// associated VF Run bit to Stopped. Refer to section 8 for more information.
|
||||||
|
pub light_host_controller_reset: bool,
|
||||||
|
/// Controller Save State (CSS) - RW. Default = ‘0’. When written by software with ‘1’ and HCHalted
|
||||||
|
/// (HCH) = ‘1’, then the xHC shall save any internal state (that may be restored by a subsequent
|
||||||
|
/// Restore State operation) and if FSC = '1' any cached Slot, Endpoint, Stream, or other Context
|
||||||
|
/// information (so that software may save it). When written by software with ‘1’ and HCHalted
|
||||||
|
/// (HCH) = ‘0’, or written with ‘0’, no Save State operation shall be performed. This flag always
|
||||||
|
/// returns ‘0’ when read. Refer to the Save State Status (SSS) flag in the USBSTS register for
|
||||||
|
/// information on Save State completion. Refer to section 4.23.2 for more information on xHC
|
||||||
|
///
|
||||||
|
/// Save/Restore operation. Note that undefined behavior may occur if a Save State operation is
|
||||||
|
/// initiated while Restore State Status (RSS) = ‘1’.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only controls saving the state of
|
||||||
|
/// the xHC instance presented by the selected VF. Refer to section 8 for more information.
|
||||||
|
pub controller_save_state: bool,
|
||||||
|
/// Controller Restore State (CRS) - RW. Default = ‘0’. When set to ‘1’, and HCHalted (HCH) = ‘1’,
|
||||||
|
/// then the xHC shall perform a Restore State operation and restore its internal state. When set to
|
||||||
|
/// ‘1’ and Run/Stop (R/S) = ‘1’ or HCHalted (HCH) = ‘0’, or when cleared to ‘0’, no Restore State
|
||||||
|
/// operation shall be performed. This flag always returns ‘0’ when read. Refer to the Restore State
|
||||||
|
/// Status (RSS) flag in the USBSTS register for information on Restore State completion. Refer to
|
||||||
|
/// section 4.23.2 for more information. Note that undefined behavior may occur if a Restore State
|
||||||
|
/// operation is initiated while Save State Status (SSS) = ‘1’.
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only controls restoring the state
|
||||||
|
/// of the xHC instance presented by the selected VF. Refer to section 8 for more information.
|
||||||
|
pub controller_restore_state: bool,
|
||||||
|
/// Enable Wrap Event (EWE) - RW. Default = ‘0’. When set to ‘1’, the xHC shall generate a MFINDEX
|
||||||
|
/// Wrap Event every time the MFINDEX register transitions from 03FFFh to 0. When cleared to ‘0’
|
||||||
|
/// no MFINDEX Wrap Events are generated. Refer to section 4.14.2 for more information.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the generation of MFINDEX Wrap
|
||||||
|
/// Events to VFs shall be emulated by the VMM.
|
||||||
|
pub enable_wrap_event: bool,
|
||||||
|
/// Enable U3 MFINDEX Stop (EU3S) - RW. Default = ‘0’. When set to ‘1’, the xHC may stop the
|
||||||
|
/// MFINDEX counting action if all Root Hub ports are in the U3, Disconnected, Disabled, or
|
||||||
|
/// Powered-off state. When cleared to ‘0’ the xHC may stop the MFINDEX counting action if all
|
||||||
|
/// Root Hub ports are in the Disconnected, Disabled, Training, or Powered-off state. Refer to
|
||||||
|
/// section 4.14.2 for more information
|
||||||
|
pub enable_u3_mfindex_stop: bool,
|
||||||
|
___: bool,
|
||||||
|
/// CEM Enable (CME) - RW. Default = '0'. When set to '1', a Max Exit Latency Too Large Capability
|
||||||
|
/// Error may be returned by a Configure Endpoint Command. When cleared to '0', a Max Exit
|
||||||
|
/// Latency Too Large Capability Error shall not be returned by a Configure Endpoint Command.
|
||||||
|
/// This bit is Reserved if CMC = ‘0’. Refer to section 4.23.5.2.2 for more information.
|
||||||
|
pub cem_enable: bool,
|
||||||
|
/// Extended TBC Enable (ETE). This flag indicates that the host controller implementation is
|
||||||
|
/// enabled to support Transfer Burst Count (TBC) values greater that 4 in isoch TDs. When this bit
|
||||||
|
/// is ‘1’, the Isoch TRB TD Size/TBC field presents the TBC value, and the TBC/RsvdZ field is RsvdZ.
|
||||||
|
/// When this bit is ‘0’, the TDSize/TCB field presents the TD Size value, and the TBC/RsvdZ field
|
||||||
|
/// presents the TBC value. This bit may be set only if ETC = ‘1’. Refer to section 4.11.2.3 for more
|
||||||
|
/// information.
|
||||||
|
pub extended_tbc_enable: bool,
|
||||||
|
/// Extended TBC TRB Status Enable (TSC_EN). This flag indicates that the host controller
|
||||||
|
/// implementation is enabled to support ETC_TSC capability. When this is ‘1’, TRBSts field in the
|
||||||
|
/// TRB updated to indicate if it is last transfer TRB in the TD. This bit may be set only if
|
||||||
|
/// ETC_TSC=’1’. Refer to section 4.11.2.3 for more information.
|
||||||
|
pub extended_tbc_trb_status_enable: bool,
|
||||||
|
/// VTIO Enable (VTIOE) – RW. Default = ‘0’. When set to ‘1’, XHCI HW will enable its VTIO
|
||||||
|
/// capability and begin to use the information provided via that VTIO Registers to determine its
|
||||||
|
/// DMA-ID. When cleared to ‘0’, XHCI HW will use the Primary DMA-ID for all accesses. This bit
|
||||||
|
/// may be set only if VTC = ‘1’.
|
||||||
|
pub vtio_enable: bool,
|
||||||
|
#[bits(15)]
|
||||||
|
____: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct UsbStatus {
|
||||||
|
/// HCHalted (HCH) – RO. Default = ‘1’. This bit is a ‘0’ whenever the Run/Stop (R/S) bit is a ‘1’. The
|
||||||
|
/// xHC sets this bit to ‘1’ after it has stopped executing as a result of the Run/Stop (R/S) bit being
|
||||||
|
/// cleared to ‘0’, either by software or by the xHC hardware (e.g. internal error).
|
||||||
|
///
|
||||||
|
/// If this bit is '1', then SOFs, microSOFs, or Isochronous Timestamp Packets (ITP) shall not be
|
||||||
|
/// generated by the xHC, and any received Transaction Packet shall be dropped.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit only reflects the Halted state of
|
||||||
|
/// the xHC instance presented by the selected VF. Refer to section 8 for more information
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub host_controller_halted: bool,
|
||||||
|
__: bool,
|
||||||
|
/// Host System Error (HSE) – RW1C. Default = ‘0’. The xHC sets this bit to ‘1’ when a serious error
|
||||||
|
/// is detected, either internal to the xHC or during a host system access involving the xHC module.
|
||||||
|
/// (In a PCI system, conditions that set this bit to ‘1’ include PCI Parity error, PCI Master Abort, and
|
||||||
|
/// PCI Target Abort.) When this error occurs, the xHC clears the Run/Stop (R/S) bit in the USBCMD
|
||||||
|
/// register to prevent further execution of the scheduled TDs. If the HSEE bit in the USBCMD
|
||||||
|
/// register is a ‘1’, the xHC shall also assert out-of-band error signaling to the host. Refer to section
|
||||||
|
/// 4.10.2.6 for more information.
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the assertion of this bit affects all VFs
|
||||||
|
/// and reflects the Host System Error state of the Physical Function (PF0). Refer to section 8 for
|
||||||
|
/// more information.
|
||||||
|
pub host_system_error: bool,
|
||||||
|
/// Event Interrupt (EINT) – RW1C. Default = ‘0’. The xHC sets this bit to ‘1’ when the Interrupt
|
||||||
|
/// Pending (IP) bit of any Interrupter transitions from ‘0’ to ‘1’. Refer to section 7.1.2 for use.
|
||||||
|
/// Software that uses EINT shall clear it prior to clearing any IP flags. A race condition may occur if
|
||||||
|
/// software clears the IP flags then clears the EINT flag, and between the operations another IP ‘0’
|
||||||
|
/// to '1' transition occurs. In this case the new IP transition shall be lost.
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), this bit is the logical 'OR' of the IP bits
|
||||||
|
/// for the Interrupters assigned to the selected VF. And it shall be cleared to ‘0’ when all associated
|
||||||
|
/// interrupter IP bits are cleared, i.e. all the VF’s Interrupter Event Ring(s) are empty. Refer to
|
||||||
|
/// section 8 for more information
|
||||||
|
pub event_interrupt: bool,
|
||||||
|
/// Port Change Detect (PCD) – RW1C. Default = ‘0’. The xHC sets this bit to a ‘1’ when any port has
|
||||||
|
/// a change bit transition from a ‘0’ to a ‘1’.
|
||||||
|
///
|
||||||
|
/// This bit is allowed to be maintained in the Aux Power well. Alternatively, it is also acceptable
|
||||||
|
/// that on a D3 to D0 transition of the xHC, this bit is loaded with the OR of all of the PORTSC
|
||||||
|
/// change bits. Refer to section 4.19.3.
|
||||||
|
///
|
||||||
|
/// This bit provides system software an efficient means of determining if there has been Root Hub
|
||||||
|
/// port activity. Refer to section 4.15.2.3 for more information.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
|
||||||
|
/// bit as a function of the Root Hub Ports associated with the Device Slots assigned to the selected
|
||||||
|
/// VF. Refer to section 8 for more information.
|
||||||
|
pub port_change_detect: bool,
|
||||||
|
#[bits(3)]
|
||||||
|
__: u8,
|
||||||
|
/// Save State Status (SSS) - RO. Default = ‘0’. When the Controller Save State (CSS) flag in the
|
||||||
|
/// USBCMD register is written with ‘1’ this bit shall be set to ‘1’ and remain 1 while the xHC saves
|
||||||
|
/// its internal state. When the Save State operation is complete, this bit shall be cleared to ‘0’.
|
||||||
|
/// Refer to section 4.23.2 for more information.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
|
||||||
|
/// bit as a function of the saving the state for the selected VF. Refer to section 8 for more
|
||||||
|
/// information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub save_state_status: bool,
|
||||||
|
/// Restore State Status (RSS) - RO. Default = ‘0’. When the Controller Restore State (CRS) flag in
|
||||||
|
/// the USBCMD register is written with ‘1’ this bit shall be set to ‘1’ and remain 1 while the xHC
|
||||||
|
/// restores its internal state. When the Restore State operation is complete, this bit shall be
|
||||||
|
/// cleared to ‘0’. Refer to section 4.23.2 for more information.
|
||||||
|
///
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
|
||||||
|
/// bit as a function of the restoring the state for the selected VF. Refer to section 8 for more
|
||||||
|
/// information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub restore_state_status: bool,
|
||||||
|
/// Save/Restore Error (SRE) - RW1C. Default = ‘0’. If an error occurs during a Save or Restore
|
||||||
|
/// operation this bit shall be set to ‘1’. This bit shall be cleared to ‘0’ when a Save or Restore
|
||||||
|
/// operation is initiated or when written with ‘1’. Refer to section 4.23.2 for more information.
|
||||||
|
/// When this register is exposed by a Virtual Function (VF), the VMM determines the state of this
|
||||||
|
/// bit as a function of the Save/Restore completion status for the selected VF. Refer to section 8
|
||||||
|
/// for more information.
|
||||||
|
pub save_restore_error: bool,
|
||||||
|
/// Controller Not Ready (CNR) – RO. Default = ‘1’. ‘0’ = Ready and ‘1’ = Not Ready. Software shall
|
||||||
|
/// not write any Doorbell or Operational register of the xHC, other than the USBSTS register, until
|
||||||
|
/// CNR = ‘0’. This flag is set by the xHC after a Chip Hardware Reset and cleared when the xHC is
|
||||||
|
/// ready to begin accepting register writes. This flag shall remain cleared (‘0’) until the next Chip
|
||||||
|
/// Hardware Reset.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub controller_not_ready: bool,
|
||||||
|
/// Host Controller Error (HCE) – RO. Default = 0. 0’ = No internal xHC error conditions exist and ‘1’
|
||||||
|
/// = Internal xHC error condition. This flag shall be set to indicate that an internal error condition
|
||||||
|
/// has been detected which requires software to reset and reinitialize the xHC. Refer to section
|
||||||
|
/// 4.24.1 for more information.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub host_controller_error: bool,
|
||||||
|
#[bits(19)]
|
||||||
|
__: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsbStatus {
|
||||||
|
// Returns a copy of this object that can be written without overwritting flags that are RW1C.
|
||||||
|
fn preserving_flags(&self) -> UsbStatus {
|
||||||
|
self.with_host_system_error(false)
|
||||||
|
.with_event_interrupt(false)
|
||||||
|
.with_port_change_detect(false)
|
||||||
|
.with_save_restore_error(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal data structure to ensure 64 bit reads and writes.
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct CommandAndStatus {
|
||||||
|
#[bits(32)]
|
||||||
|
usb_command: UsbCommand,
|
||||||
|
#[bits(32)]
|
||||||
|
usb_status: UsbStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandAndStatus {
|
||||||
|
fn update_command(&self, f: impl Fn(UsbCommand) -> UsbCommand) -> CommandAndStatus {
|
||||||
|
CommandAndStatus::new()
|
||||||
|
.with_usb_command(f(self.usb_command()))
|
||||||
|
.with_usb_status(self.usb_status().preserving_flags())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_status(&self, f: impl Fn(UsbStatus) -> UsbStatus) -> CommandAndStatus {
|
||||||
|
self.with_usb_status(f(self.usb_status()).preserving_flags())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct PageSize {
|
||||||
|
///Page Size – RO. Default = Implementation defined. This field defines the page size supported by
|
||||||
|
/// the xHC implementation. This xHC supports a page size of 2^(n+12) if bit n is Set. For example, if
|
||||||
|
/// bit 0 is Set, the xHC supports 4k byte page sizes.
|
||||||
|
///
|
||||||
|
/// For a Virtual Function, this register reflects the page size selected in the System Page Size field
|
||||||
|
/// of the SR-IOV Extended Capability structure. For the Physical Function 0, this register reflects
|
||||||
|
/// the implementation dependent default xHC page size.
|
||||||
|
///
|
||||||
|
/// Various xHC resources reference PAGESIZE to describe their minimum alignment requirements.
|
||||||
|
///
|
||||||
|
/// The maximum possible page size is 128M.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
page_size: u32,
|
||||||
|
__: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct DeviceNotificationControl {
|
||||||
|
__: u32,
|
||||||
|
/// This register is used by software to enable or disable the reporting of the
|
||||||
|
/// reception of specific USB Device Notification Transaction Packets. A Notification
|
||||||
|
/// Enable (Nx, where x = 0 to 15) flag is defined for each of the 16 possible de vice
|
||||||
|
/// notification types. If a flag is set for a specific notification type, a Device
|
||||||
|
/// Notification Event shall be generated when the respective notification packet is
|
||||||
|
/// received. After reset all notifications are disabled. Refer to section 6.4.2.7
|
||||||
|
device_notification_control: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
pub struct UsbConfigure {
|
||||||
|
/// Max Device Slots Enabled (MaxSlotsEn) – RW. Default = ‘0’. This field specifies the maximum
|
||||||
|
/// number of enabled Device Slots. Valid values are in the range of 0 to MaxSlots. Enabled Devices
|
||||||
|
/// Slots are allocated contiguously. e.g. A value of 16 specifies that Device Slots 1 to 16 are active.
|
||||||
|
///
|
||||||
|
/// A value of ‘0’ disables all Device Slots. A disabled Device Slot shall not respond to Doorbell
|
||||||
|
/// Register references.
|
||||||
|
///
|
||||||
|
/// This field shall not be modified by software if the xHC is running (Run/Stop (R/S) = ‘1’)
|
||||||
|
pub max_device_slots_enabled: u8,
|
||||||
|
/// U3 Entry Enable (U3E) – RW. Default = '0'. When set to '1', the xHC shall assert the PLC flag ('1')
|
||||||
|
/// when a Root Hub port transitions to the U3 State. Refer to section 4.15.1 for more information.
|
||||||
|
pub u3_entry_enable: bool,
|
||||||
|
/// Configuration Information Enable (CIE) - RW. Default = '0'. When set to '1', the software shall
|
||||||
|
/// initialize the Configuration Value, Interface Number, and Alternate Setting fields in the Input
|
||||||
|
/// Control Context when it is associated with a Configure Endpoint Command. When this bit is '0',
|
||||||
|
/// the extended Input Control Context fields are not supported. Refer to section 6.2.5.1 for more
|
||||||
|
/// information.
|
||||||
|
pub configuration_information_enable: bool,
|
||||||
|
|
||||||
|
#[bits(22)]
|
||||||
|
__: u32,
|
||||||
|
|
||||||
|
// Pad to 64 bits for the purposes of reads and writes.
|
||||||
|
__: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XHCI Spec Section 5.4
|
||||||
|
///
|
||||||
|
/// The base address of this register space is referred to as Operational Base. The
|
||||||
|
/// Operational Base shall be Dword aligned and is calculated by adding the value
|
||||||
|
/// of the Capability Registers Length (CAPLENGTH) register (refer to Section 5.3.1)
|
||||||
|
/// to the Capability Base address. All registers are multiples of 32 bits in length
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct HostControllerOperational {
|
||||||
|
command_and_status: CommandAndStatus,
|
||||||
|
page_size: PageSize,
|
||||||
|
device_notification_control: DeviceNotificationControl,
|
||||||
|
/// Bit 0: Ring Cycle State (RW)
|
||||||
|
/// Bit 1: Command Stop (RW1S)
|
||||||
|
/// Bit 2: Command Abort (RW1S)
|
||||||
|
/// Bit 3: Command Ring Running (RO)
|
||||||
|
command_ring_control: u64,
|
||||||
|
__: u64,
|
||||||
|
___: u64,
|
||||||
|
/// The Device Context Base Address Array Pointer Register identifies the base
|
||||||
|
/// address of the Device Context Base Address Array.
|
||||||
|
/// The memory structure referenced by this physical memory pointer is assumed to
|
||||||
|
/// be physically contiguous and 64-byte aligned.
|
||||||
|
device_context_base_address_array_pointer: u64,
|
||||||
|
configure: UsbConfigure,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<HostControllerOperational>() == 0x40);
|
||||||
|
|
||||||
|
pub struct HostControllerOperationalWrapper {
|
||||||
|
operational: Mutex<VolatileRef<'static, HostControllerOperational>>,
|
||||||
|
// TODO: This should maybe be its own structure.
|
||||||
|
ports: Vec<Mutex<VolatileRef<'static, HostControllerUsbPort>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl HostControllerOperationalWrapper {
|
||||||
|
pub fn new(mmio_address: usize) -> (Self, HostControllerCapabilities) {
|
||||||
|
const MAP_SIZE: usize = 0x1000;
|
||||||
|
let caps_ptr: NonNull<HostControllerCapabilities> =
|
||||||
|
map_direct_physical_and_leak(mmio_address, MAP_SIZE);
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
|
// - The pointer is valid.
|
||||||
|
// - No other thread has access in this block.
|
||||||
|
let capabilities = unsafe { VolatilePtr::new(caps_ptr).read() };
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
capabilities.cap_params_1.supports_64_bit(),
|
||||||
|
"We only support 64 bit XHCI"
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: I don't think we acutally handle this properly.
|
||||||
|
// SAFETY: XHCI Spec says that this resides in a single page of memory which we mapped
|
||||||
|
// above.
|
||||||
|
//
|
||||||
|
// BAR0 Size Allocation
|
||||||
|
// If virtualization is supported, the Capability and Operational Register sets, and
|
||||||
|
// the Extended Capabilities may reside in a single page of virtual memory,
|
||||||
|
let cap_length_and_version = capabilities.cap_length_and_version;
|
||||||
|
let operational_ptr = unsafe {
|
||||||
|
(caps_ptr.as_ptr() as *mut u8).add(cap_length_and_version.cap_length() as usize)
|
||||||
|
as *mut HostControllerOperational
|
||||||
|
};
|
||||||
|
|
||||||
|
const PORT_OFFSET: usize = 0x400;
|
||||||
|
|
||||||
|
// FIXME: This calculation is cursed.
|
||||||
|
let ports_addr = unsafe { (operational_ptr as *mut u8).add(PORT_OFFSET) as usize };
|
||||||
|
let ports_space = MAP_SIZE - cap_length_and_version.cap_length() as usize - PORT_OFFSET;
|
||||||
|
let max_ports_we_support = ports_space / size_of::<HostControllerUsbPort>();
|
||||||
|
let max_ports = capabilities.params_1.max_ports();
|
||||||
|
assert!(
|
||||||
|
max_ports as usize <= max_ports_we_support,
|
||||||
|
"TODO: Support more ports."
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut ports = Vec::new();
|
||||||
|
let ports_addr = ports_addr as *mut HostControllerUsbPort;
|
||||||
|
for port_index in 0..max_ports {
|
||||||
|
ports.push(unsafe {
|
||||||
|
Mutex::new(VolatileRef::new(
|
||||||
|
NonNull::new(ports_addr.add(port_index as usize)).unwrap(),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let operational = Self {
|
||||||
|
operational: Mutex::new(unsafe {
|
||||||
|
VolatileRef::new(NonNull::new(operational_ptr).unwrap())
|
||||||
|
}),
|
||||||
|
ports,
|
||||||
|
};
|
||||||
|
|
||||||
|
(operational, capabilities)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_command(&self) -> UsbCommand {
|
||||||
|
let locked = self.operational.lock();
|
||||||
|
let op = locked.as_ptr();
|
||||||
|
map_field!(op.command_and_status).read().usb_command()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_command(&self, f: impl Fn(UsbCommand) -> UsbCommand) {
|
||||||
|
let mut locked = self.operational.lock();
|
||||||
|
let op = locked.as_mut_ptr();
|
||||||
|
map_field!(op.command_and_status).update(|c_and_s| c_and_s.update_command(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_status(&self) -> UsbStatus {
|
||||||
|
let locked = self.operational.lock();
|
||||||
|
let op = locked.as_ptr();
|
||||||
|
map_field!(op.command_and_status).read().usb_status()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_status(&self, f: impl Fn(UsbStatus) -> UsbStatus) {
|
||||||
|
let mut locked = self.operational.lock();
|
||||||
|
let op = locked.as_mut_ptr();
|
||||||
|
map_field!(op.command_and_status).update(|c_and_s| c_and_s.update_status(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_device_context_base_address_array_pointer(&self, pointer: usize) {
|
||||||
|
let mut locked = self.operational.lock();
|
||||||
|
let op = locked.as_mut_ptr();
|
||||||
|
map_field!(op.device_context_base_address_array_pointer).write(pointer as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_command_ring_dequeue_pointer(&self, pointer: usize, cycle_bit: bool) {
|
||||||
|
// TODO: Assert that the command ring is not running here.
|
||||||
|
let mut locked = self.operational.lock();
|
||||||
|
let op = locked.as_mut_ptr();
|
||||||
|
map_field!(op.command_ring_control).write(pointer as u64 | cycle_bit as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_configure(&self) -> UsbConfigure {
|
||||||
|
let locked = self.operational.lock();
|
||||||
|
let op = locked.as_ptr();
|
||||||
|
map_field!(op.configure).read()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_configure(&self, f: impl Fn(UsbConfigure) -> UsbConfigure) {
|
||||||
|
let mut locked = self.operational.lock();
|
||||||
|
let op = locked.as_mut_ptr();
|
||||||
|
map_field!(op.configure).update(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_port(&self, index: usize) -> HostControllerUsbPort {
|
||||||
|
self.ports[index].lock().as_ptr().read()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_port_status(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
f: impl Fn(PortStatusAndControl) -> PortStatusAndControl,
|
||||||
|
) {
|
||||||
|
let mut port_ref = self.ports[index].lock();
|
||||||
|
let ptr = port_ref.as_mut_ptr();
|
||||||
|
map_field!(ptr.status_and_control).update(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_ports(&self) -> usize {
|
||||||
|
self.ports.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
415
rust/sys/voyageurs/src/xhci/registers/host_controller_port.rs
Normal file
415
rust/sys/voyageurs/src/xhci/registers/host_controller_port.rs
Normal file
|
|
@ -0,0 +1,415 @@
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
|
||||||
|
/// A host controller shall implement one or more port registers. The number of
|
||||||
|
/// port registers implemented by a particular instantiation of a host controller is
|
||||||
|
/// documented in the HCSPARAMS1 register (Section 5.3.3). Software uses this
|
||||||
|
/// information as an input parameter to determine how many ports need to be
|
||||||
|
/// serviced.
|
||||||
|
///
|
||||||
|
/// XHCI Spec 5.4.8
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct PortStatusAndControl {
|
||||||
|
/// Current Connect Status (CCS) – ROS. Default = ‘0’. ‘1’ = A device is connected81 to the port. ‘0’ =
|
||||||
|
/// A device is not connected. This value reflects the current state of the port, and may not
|
||||||
|
/// correspond directly to the event that caused the Connect Status Change (CSC) bit to be set to ‘1’.
|
||||||
|
///
|
||||||
|
/// Refer to sections 4.19.3 and 4.19.4 for more details on the Connect Status Change (CSC)
|
||||||
|
/// assertion conditions.
|
||||||
|
/// This flag is ‘0’ if PP is ‘0’.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub current_connect_status: bool,
|
||||||
|
|
||||||
|
/// Port Enabled/Disabled (PED) – RW1CS. Default = ‘0’. ‘1’ = Enabled. ‘0’ = Disabled.
|
||||||
|
/// Ports may only be enabled by the xHC. Software cannot enable a port by writing a ‘1’ to this flag.
|
||||||
|
///
|
||||||
|
/// A port may be disabled by software writing a ‘1’ to this flag.
|
||||||
|
///
|
||||||
|
/// This flag shall automatically be cleared to ‘0’ by a disconnect event or other fault condition.
|
||||||
|
/// Note that the bit status does not change until the port state actually changes. There may be a
|
||||||
|
/// delay in disabling or enabling a port due to other host controller or bus events.
|
||||||
|
///
|
||||||
|
/// When the port is disabled (PED = ‘0’) downstream propagation of data is blocked on this port,
|
||||||
|
/// except for reset.
|
||||||
|
///
|
||||||
|
/// For USB2 protocol ports:
|
||||||
|
/// When the port is in the Disabled state, software shall reset the port (PR = ‘1’) to transition PED to
|
||||||
|
/// ‘1’ and the port to the Enabled state.
|
||||||
|
///
|
||||||
|
/// For USB3 protocol ports:
|
||||||
|
/// When the port is in the Polling state (after detecting an attach), the port shall automatically
|
||||||
|
/// transition to the Enabled state and set PED to ‘1’ upon the completion of successful link training.
|
||||||
|
/// When the port is in the Disabled state, software shall write a ‘5’ (RxDetect) to the PLS field to
|
||||||
|
/// transition the port to the Disconnected state. Refer to section 4.19.1.2.
|
||||||
|
///
|
||||||
|
/// PED shall automatically be cleared to ‘0’ when PR is set to ‘1’, and set to ‘1’ when PR transitions
|
||||||
|
/// from ‘1’ to ‘0’ after a successful reset. Refer to Port Reset (PR) bit for more information on how
|
||||||
|
/// the PED bit is managed.
|
||||||
|
///
|
||||||
|
/// Note that when software writes this bit to a ‘1’, it shall also write a ‘0’ to the PR bit.
|
||||||
|
/// This flag is ‘0’ if PP is ‘0’.
|
||||||
|
pub port_enabled_disabled: bool,
|
||||||
|
|
||||||
|
__: bool,
|
||||||
|
|
||||||
|
/// Over-current Active (OCA) – RO. Default = ‘0’. ‘1’ = This port currently has an over-current
|
||||||
|
/// condition. ‘0’ = This port does not have an over-current condition. This bit shall automatically
|
||||||
|
/// transition from a ‘1’ to a ‘0’ when the over-current condition is removed.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub over_current_active: bool,
|
||||||
|
|
||||||
|
/// Port Reset (PR) – RW1S. Default = ‘0’. ‘1’ = Port Reset signaling is asserted. ‘0’ = Port is not in
|
||||||
|
/// Reset. When software writes a ‘1’ to this bit generating a ‘0’ to ‘1’ transition, the bus reset
|
||||||
|
/// sequence is initiated83; USB2 protocol ports shall execute the bus reset sequence as defined in
|
||||||
|
/// the USB2 Spec. USB3 protocol ports shall execute the Hot Reset sequence as defined in the
|
||||||
|
/// USB3 Spec. PR remains set until reset signaling is completed by the root hub.
|
||||||
|
///
|
||||||
|
/// Note that software shall write a ‘1’ to this flag to transition a USB2 port from the Polling state to
|
||||||
|
/// the Enabled state. Refer to sections 4.15.2.3 and 4.19.1.1.
|
||||||
|
///
|
||||||
|
/// This flag is ‘0’ if PP is ‘0’.
|
||||||
|
pub port_reset: bool,
|
||||||
|
|
||||||
|
/// Port Link State (PLS) – RWS. Default = RxDetect (‘5’). This field is used to power manage the port
|
||||||
|
/// and reflects its current link state.
|
||||||
|
///
|
||||||
|
/// When the port is in the Enabled state, system software may set the link U state by writing this
|
||||||
|
/// field. System software may also write this field to force a Disabled to Disconnected state
|
||||||
|
/// transition of the port.
|
||||||
|
///
|
||||||
|
/// Write Value Description
|
||||||
|
/// 0 The link shall transition to a U0 state from any of the U states.
|
||||||
|
/// 285 USB2 protocol ports only. The link should transition to the U2 State.
|
||||||
|
/// 384 The link shall transition to a U3 state from the U0 state. This action
|
||||||
|
/// selectively suspends the device connected to this port. While the Port
|
||||||
|
/// Link State = U3, the hub does not propagate downstream-directed
|
||||||
|
/// traffic to this port, but the hub shall respond to resume signaling from
|
||||||
|
/// the port.
|
||||||
|
/// 5 USB3 protocol ports only. If the port is in the Disabled state (PLS =
|
||||||
|
/// Disabled, PP = 1), then the link shall transition to a RxDetect state and
|
||||||
|
/// the port shall transition to the Disconnected state, else ignored.
|
||||||
|
/// 10 USB3 protocol ports only. Shall enable a link transition to the
|
||||||
|
/// Compliance state, i.e. CTE = ‘1’. Refer to section 4.19.1.2.4.1 for more
|
||||||
|
/// information.
|
||||||
|
/// 185,4,6-9,11-14 Ignored.
|
||||||
|
/// 15 USB2 protocol ports only. If the port is in the U3 state (PLS = U3), then
|
||||||
|
/// the link shall remain in the U3 state and the port shall transition to the
|
||||||
|
/// Resume substate, else ignored. Refer to section 4.15.2 for more
|
||||||
|
/// information.
|
||||||
|
///
|
||||||
|
/// Note: The Port Link State Write Strobe (LWS) shall also be set to ‘1’ to write this
|
||||||
|
/// field.
|
||||||
|
///
|
||||||
|
/// For USB2 protocol ports: Writing a value of '2' to this field shall request LPM, asserting L1
|
||||||
|
/// signaling on the USB2 bus. Software may read this field to determine if the transition to the U2
|
||||||
|
/// state was successful. Writing a value of '0' shall deassert L1 signaling on the USB. Writing a value
|
||||||
|
/// of '1' shall have no effect. The U1 state shall never be reported by a USB2 protocol port.
|
||||||
|
///
|
||||||
|
/// Read Value Meaning
|
||||||
|
/// 0 Link is in the U0 State
|
||||||
|
/// 1 Link is in the U1 State
|
||||||
|
/// 2 Link is in the U2 State
|
||||||
|
/// 3 Link is in the U3 State (Device Suspended)
|
||||||
|
/// 4 Link is in the Disabled State86
|
||||||
|
/// 5 Link is in the RxDetect State87
|
||||||
|
/// 6 Link is in the Inactive State88
|
||||||
|
/// 7 Link is in the Polling State
|
||||||
|
/// 8 Link is in the Recovery State
|
||||||
|
/// 9 Link is in the Hot Reset State
|
||||||
|
/// 10 Link is in the Compliance Mode State
|
||||||
|
/// 11 Link is in the Test Mode89 State
|
||||||
|
/// 12-14 Reserved
|
||||||
|
/// 15 Link is in the Resume State90
|
||||||
|
///
|
||||||
|
/// This field is undefined if PP = ‘0’.
|
||||||
|
///
|
||||||
|
/// Note: Transitions between different states are not reflected until the transition is complete. Refer
|
||||||
|
/// to section 4.19 for PLS transition conditions.
|
||||||
|
/// 409
|
||||||
|
///
|
||||||
|
/// Refer to sections 4.15.2 and 4.23.5 for more information on the use of this field. Refer to the
|
||||||
|
/// USB2 LPM ECR for more information on USB link power management operation. Refer to section
|
||||||
|
/// 7.2 for supported USB protocols
|
||||||
|
#[bits(4)]
|
||||||
|
pub port_link_status: u8,
|
||||||
|
/// Port Power (PP) – RWS. Default = ‘1’. This flag reflects a port's logical, power control state.
|
||||||
|
/// Because host controllers can implement different methods of port power switching, this flag may
|
||||||
|
/// or may not represent whether (VBus) power is actually applied to the port. When PP equals a '0'
|
||||||
|
/// the port is nonfunctional and shall not report attaches, detaches, or Port Link State (PLS)
|
||||||
|
/// changes. However, the port shall report over-current conditions when PP = ‘0’ if PPC = ‘0’. After
|
||||||
|
/// modifying PP, software shall read PP and confirm that it is reached its target state before
|
||||||
|
/// modifying it again91, undefined behavior may occur if this procedure is not followed.
|
||||||
|
///
|
||||||
|
/// 0 = This port is in the Powered-off state.
|
||||||
|
/// 1 = This port is not in the Powered-off state.
|
||||||
|
///
|
||||||
|
/// If the Port Power Control (PPC) flag in the HCCPARAMS1 register is '1', then xHC has port power
|
||||||
|
/// control switches and this bit represents the current setting of the switch ('0' = off, '1' = on).
|
||||||
|
///
|
||||||
|
/// If the Port Power Control (PPC) flag in the HCCPARAMS1 register is '0', then xHC does not have
|
||||||
|
/// port power control switches and each port is hard wired to power, and not affected by this bit.
|
||||||
|
/// When an over-current condition is detected on a powered port, the xHC shall transition the PP
|
||||||
|
/// bit in each affected port from a ‘1’ to ‘0’ (removing power from the port).
|
||||||
|
///
|
||||||
|
/// Note: If this is an SSIC Port, then the DSP Disconnect process is initiated by '1' to '0' transition of
|
||||||
|
/// PP. After an SSIC USP disconnect process, the port may be disabled by setting PED = 1. As noted,
|
||||||
|
/// the SSIC spec does not define a mechanism for the USP to request DSP to be re-enabled for a
|
||||||
|
/// subsequent re-connect. If PED is set to 1 without a prior negotiated disconnect with the USP,
|
||||||
|
/// subsequent re-enabling of the port requires DSP to issue a WPR to bring USP back to Rx.Detect.
|
||||||
|
///
|
||||||
|
/// Refer to section 5.1.2 in the SSIC Spec for more information.
|
||||||
|
/// Refer to section 4.19.4 for more information.
|
||||||
|
pub port_power: bool,
|
||||||
|
/// Port Speed (Port Speed) – ROS. Default = ‘0’. This field identifies the speed of the connected
|
||||||
|
/// USB Device. This field is only relevant if a device is connected (CCS = ‘1’) in all other cases this
|
||||||
|
/// field shall indicate Undefined Speed. Refer to section 4.19.3.
|
||||||
|
///
|
||||||
|
/// Value Meaning
|
||||||
|
/// 0 Undefined Speed
|
||||||
|
/// 1 -15 Protocol Speed ID (PSI), refer to section 7.2.1 for the definition of PSIV
|
||||||
|
/// field in the PSI Dword
|
||||||
|
///
|
||||||
|
/// Note: This field is invalid on a USB2 protocol port until after the port is reset.
|
||||||
|
#[bits(4)]
|
||||||
|
pub port_speed: u8,
|
||||||
|
/// Port Indicator Control (PIC) – RWS. Default = 0. Writing to these bits has no effect if the Port
|
||||||
|
/// Indicators (PIND) bit in the HCCPARAMS1 register is a ‘0’. If PIND bit is a ‘1’, then the bit
|
||||||
|
/// encodings are:
|
||||||
|
///
|
||||||
|
/// Value Meaning
|
||||||
|
/// 0 Port indicators are off
|
||||||
|
/// 1 Amber
|
||||||
|
/// 2 Green
|
||||||
|
/// 3 Undefined
|
||||||
|
///
|
||||||
|
/// Refer to the USB2 Specification section 11.5.3 for a description on how these bits shall be used.
|
||||||
|
/// This field is ‘0’ if PP is ‘0’
|
||||||
|
#[bits(2)]
|
||||||
|
pub port_indicator_control: u8,
|
||||||
|
/// Port Link State Write Strobe (LWS) – RW. Default = ‘0’. When this bit is set to ‘1’ on a write
|
||||||
|
/// reference to this register, this flag enables writes to the PLS field. When ‘0’, write data in PLS field
|
||||||
|
/// is ignored. Reads to this bit return ‘0’
|
||||||
|
pub port_link_state_write_strobe: bool,
|
||||||
|
/// Connect Status Change (CSC) – RW1CS. Default = ‘0’. ‘1’ = Change in CCS. ‘0’ = No change. This
|
||||||
|
/// flag indicates a change has occurred in the port’s Current Connect Status (CCS) or Cold Attach
|
||||||
|
/// Status (CAS) bits. Note that this flag shall not be set if the CCS transition was due to software
|
||||||
|
/// setting PP to ‘0’, or the CAS transition was due to software setting WPR to ‘1’. The xHC sets this
|
||||||
|
/// bit to ‘1’ for all changes to the port device connect status92, even if system software has not
|
||||||
|
/// cleared an existing Connect Status Change. For example, the insertion status changes twice
|
||||||
|
/// before system software has cleared the changed condition, root hub hardware will be “setting”
|
||||||
|
/// an already-set bit (i.e., the bit will remain ‘1’). Software shall clear this bit by writing a ‘1’ to it.
|
||||||
|
/// Refer to section 4.19.2 for more information on change bit usage.
|
||||||
|
pub connect_status_change: bool,
|
||||||
|
/// Port Enabled/Disabled Change (PEC) – RW1CS. Default = ‘0’. ‘1’ = change in PED. ‘0’ = No
|
||||||
|
/// change. Note that this flag shall not be set if the PED transition was due to software setting PP to
|
||||||
|
/// ‘0’. Software shall clear this bit by writing a ‘1’ to it. Refer to section 4.19.2 for more information
|
||||||
|
/// on change bit usage.
|
||||||
|
///
|
||||||
|
/// For a USB2 protocol port, this bit shall be set to ‘1’ only when the port is disabled due to the
|
||||||
|
/// appropriate conditions existing at the EOF2 point (refer to section 11.8.1 of the USB2
|
||||||
|
///
|
||||||
|
/// Specification for the definition of a Port Error).
|
||||||
|
/// For a USB3 protocol port, this bit shall never be set to ‘1’.
|
||||||
|
pub port_enabled_disabled_change: bool,
|
||||||
|
/// Warm Port Reset Change (WRC) – RW1CS/RsvdZ. Default = ‘0’. This bit is set when Warm Reset
|
||||||
|
/// processing on this port completes. ‘0’ = No change. ‘1’ = Warm Reset complete. Note that this
|
||||||
|
/// flag shall not be set to ‘1’ if the Warm Reset processing was forced to terminate due to software
|
||||||
|
/// clearing PP or PED to '0'. Software shall clear this bit by writing a '1' to it. Refer to section 4.19.5.1.
|
||||||
|
/// Refer to section 4.19.2 for more information on change bit usage.
|
||||||
|
///
|
||||||
|
/// This bit only applies to USB3 protocol ports. For USB2 protocol ports it shall be RsvdZ.
|
||||||
|
pub warm_port_reset_change: bool,
|
||||||
|
/// Over-current Change (OCC) – RW1CS. Default = ‘0’. This bit shall be set to a ‘1’ when there is a ‘0’
|
||||||
|
/// to ‘1’ or ‘1’ to ‘0’ transition of Over-current Active (OCA). Software shall clear this bit by writing a
|
||||||
|
/// ‘1’ to it. Refer to section 4.19.2 for more information on change bit usage.
|
||||||
|
pub over_current_change: bool,
|
||||||
|
/// Port Reset Change (PRC) – RW1CS. Default = ‘0’. This flag is set to ‘1’ due to a '1' to '0' transition
|
||||||
|
/// of Port Reset (PR). e.g. when any reset processing (Warm or Hot) on this port is complete. Note
|
||||||
|
/// that this flag shall not be set to ‘1’ if the reset processing was forced to terminate due to software
|
||||||
|
/// clearing PP or PED to '0'. ‘0’ = No change. ‘1’ = Reset complete. Software shall clear this bit by
|
||||||
|
/// writing a '1' to it. Refer to section 4.19.5. Refer to section 4.19.2 for more information on change
|
||||||
|
/// bit usage
|
||||||
|
pub port_reset_change: bool,
|
||||||
|
/// Port Link State Change (PLC) – RW1CS. Default = ‘0’. This flag is set to ‘1’ due to the following
|
||||||
|
/// PLS transitions:
|
||||||
|
///
|
||||||
|
/// Transition Condition
|
||||||
|
/// U3 -> Resume: Wakeup signaling from a device
|
||||||
|
/// Resume -> Recovery -> U0: Device Resume complete (USB3 protocol ports
|
||||||
|
/// only)
|
||||||
|
/// Resume -> U0: Device Resume complete (USB2 protocol ports
|
||||||
|
/// only)
|
||||||
|
/// U3 -> Recovery -> U0: Software Resume complete (USB3 protocol ports
|
||||||
|
/// only)
|
||||||
|
/// U3 -> U0: Software Resume complete (USB2 protocol ports
|
||||||
|
/// only)
|
||||||
|
/// U2 -> U0: L1 Resume complete (USB2 protocol ports only)93
|
||||||
|
/// U0 -> U0: L1 Entry Reject (USB2 protocol ports only)93
|
||||||
|
/// Any state -> Inactive: Error (USB3 protocol ports only).
|
||||||
|
/// Note: PLC is asserted only on the first LTSSM
|
||||||
|
/// SS.Inactive.Disconnect.Detect to SS.Inactive.Quiet
|
||||||
|
/// substate transition after entering the SS.Inactive
|
||||||
|
/// state.
|
||||||
|
/// Any State -> U3: U3 Entry complete. Note: PLC is asserted only if
|
||||||
|
/// U3E = ‘1’.
|
||||||
|
///
|
||||||
|
/// Note that this flag shall not be set if the PLS transition was due to software
|
||||||
|
/// setting PP to ‘0’. Refer to section 4.23.5 for more information. '0' = No
|
||||||
|
/// change. '1' = Link Status Changed. Software shall clear this bit by
|
||||||
|
/// writing a '1' to it. Refer to “PLC Condition:” references in section 4.19.1
|
||||||
|
/// for the specific port state transitions that set this flag. Refer to section
|
||||||
|
/// 4.19.2 for more information on change bit usage.
|
||||||
|
pub port_link_state_change: bool,
|
||||||
|
/// Port Config Error Change (CEC) – RW1CS/RsvdZ. Default = ‘0’. This flag indicates that the port
|
||||||
|
/// failed to configure its link partner. 0 = No change. 1 = Port Config Error detected. Software shall
|
||||||
|
/// clear this bit by writing a '1' to it. Refer to section 4.19.2 for more information on change bit
|
||||||
|
/// usage.
|
||||||
|
///
|
||||||
|
/// Note: This flag is valid only for USB3 protocol ports. For USB2 protocol ports this bit shall be
|
||||||
|
/// RsvdZ.
|
||||||
|
pub port_config_error_change: bool,
|
||||||
|
/// Cold Attach Status (CAS) – RO. Default = ‘0’. ‘1’ = Far-end Receiver Terminations were detected
|
||||||
|
/// in the Disconnected state and the Root Hub Port State Machine was unable to advance to the
|
||||||
|
/// Enabled state. Refer to sections 4.19.8 for more details on the Cold Attach Status (CAS) assertion
|
||||||
|
/// conditions. Software shall clear this bit by writing a '1' to WPR or the xHC shall clear this bit if CCS
|
||||||
|
/// transitions to ‘1’.
|
||||||
|
/// This flag is ‘0’ if PP is ‘0’ or for USB2 protocol ports
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub cold_attach_status: bool,
|
||||||
|
/// Wake on Connect Enable (WCE) – RWS. Default = ‘0’. Writing this bit to a ‘1’ enables the port to
|
||||||
|
/// be sensitive to device connects as system wake-up events96. Refer to section 4.15 for operational
|
||||||
|
/// model.
|
||||||
|
pub wake_on_connect_enable: bool,
|
||||||
|
/// Wake on Disconnect Enable (WDE) – RWS. Default = ‘0’. Writing this bit to a ‘1’ enables the port
|
||||||
|
/// to be sensitive to device disconnects as system wake-up events. Refer to section 4.15 for
|
||||||
|
/// operational model.
|
||||||
|
pub wake_on_disconnect_enable: bool,
|
||||||
|
/// Wake on Over-current Enable (WOE) – RWS. Default = ‘0’. Writing this bit to a ‘1’ enables the
|
||||||
|
/// port to be sensitive to over-current conditions as system wake-up events96. Refer to section 4.15
|
||||||
|
/// for operational model.
|
||||||
|
pub wake_on_overcurrent_enable: bool,
|
||||||
|
__: bool,
|
||||||
|
__: bool,
|
||||||
|
/// Device Removable97 (DR) - RO. This flag indicates if this port has a removable device attached.
|
||||||
|
/// ‘1’ = Device is non-removable. ‘0’ = Device is removable.
|
||||||
|
#[bits(access=RO)]
|
||||||
|
pub device_removable: bool,
|
||||||
|
/// Warm Port Reset (WPR) – RW1S/RsvdZ. Default = ‘0’. When software writes a ‘1’ to this bit, the
|
||||||
|
/// Warm Reset sequence as defined in the USB3 Specification is initiated and the PR flag is set to ‘1’.
|
||||||
|
/// Once initiated, the PR, PRC, and WRC flags shall reflect the progress of the Warm Reset
|
||||||
|
/// sequence. This flag shall always return ‘0’ when read. Refer to section 4.19.5.1.
|
||||||
|
/// This flag only applies to USB3 protocol ports. For USB2 protocol ports it shall be RsvdZ.
|
||||||
|
pub warm_port_reset: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortStatusAndControl {
|
||||||
|
pub fn clear_change_bits(&self) -> Self {
|
||||||
|
PortStatusAndControl::new()
|
||||||
|
.with_connect_status_change(true)
|
||||||
|
.with_port_enabled_disabled_change(true)
|
||||||
|
.with_warm_port_reset_change(true)
|
||||||
|
.with_over_current_change(true)
|
||||||
|
.with_port_reset_change(true)
|
||||||
|
.with_port_link_state_change(true)
|
||||||
|
.with_port_config_error_change(true)
|
||||||
|
.with_port_power(self.port_power())
|
||||||
|
.with_wake_on_connect_enable(self.wake_on_connect_enable())
|
||||||
|
.with_wake_on_disconnect_enable(self.wake_on_disconnect_enable())
|
||||||
|
.with_wake_on_overcurrent_enable(self.wake_on_overcurrent_enable())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XHCI Spec 5.4.9
|
||||||
|
/// NOTE: This definition is USB3 only.
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct PortPowerManagementStatusAndControl {
|
||||||
|
/// U1 Timeout – RWS. Default = ‘0’. Timeout value for U1 inactivity timer. If equal to FFh, the port
|
||||||
|
/// is disabled from initiating U1 entry. This field shall be set to ‘0’ by the assertion of PR to ‘1’. Refer
|
||||||
|
/// to section 4.19.4.1 for more information on U1 Timeout operation. The following are
|
||||||
|
/// permissible values:
|
||||||
|
///
|
||||||
|
/// Value Description
|
||||||
|
/// 00h Zero (default)
|
||||||
|
/// 01h 1 μs.
|
||||||
|
/// 02h 2 μs.
|
||||||
|
/// …
|
||||||
|
/// 7Fh 127 μs.
|
||||||
|
/// 80h–FEh Reserved
|
||||||
|
/// FFh Infinite
|
||||||
|
u1_timeout: u8,
|
||||||
|
/// U2 Timeout – RWS. Default = ‘0’. Timeout value for U2 inactivity timer. If equal to FFh, the port
|
||||||
|
/// is disabled from initiating U2 entry. This field shall be set to ‘0’ by the assertion of PR to ‘1’. Refer
|
||||||
|
/// to section 4.19.4.1 for more information on U2 Timeout operation. The following are
|
||||||
|
/// permissible values:
|
||||||
|
///
|
||||||
|
/// Value Description
|
||||||
|
/// 00h Zero (default)
|
||||||
|
/// 01h 256 μs
|
||||||
|
/// 02h 512 μs
|
||||||
|
/// …
|
||||||
|
/// FEh 65,024 ms
|
||||||
|
/// FFh Infinite
|
||||||
|
///
|
||||||
|
/// A U2 Inactivity Timeout LMP shall be sent by the xHC to the device connected on this port when
|
||||||
|
/// this field is written. Refer to Sections 8.4.3 and 10.4.2.10 of the USB3 specification for more
|
||||||
|
/// details
|
||||||
|
u2_timeout: u8,
|
||||||
|
/// Force Link PM Accept (FLA) - RW. Default = ‘0’. When this bit is set to ‘1’, the port shall generate
|
||||||
|
/// a Set Link Function LMP with the Force_LinkPM_Accept bit asserted (‘1’). When this bit is cleared
|
||||||
|
/// to ‘0’, the port shall generate a Set Link Function LMP with the Force_LinkPM_Accept bit de-
|
||||||
|
/// asserted (‘0’).
|
||||||
|
/// This flag shall be set to ‘0’ by the assertion of PR to ‘1’ or when CCS = transitions from ‘0’ to ‘1’.
|
||||||
|
/// Writes to this flag have no effect if PP = ‘0’.
|
||||||
|
/// The Set Link Function LMP is sent by the xHC to the device connected on this port when this bit
|
||||||
|
/// transitions from ‘0’ to ‘1’ or ‘1’ to ‘0’. Refer to Sections 8.4.2 and 10.14.2.2 of the USB3
|
||||||
|
/// specification for more details.
|
||||||
|
/// Improper use of the SS Force_LinkPM_Accept functionality can impact the performance of the
|
||||||
|
/// link significantly. This bit shall only be used for compliance and testing purposes. Software shall
|
||||||
|
/// ensure that there are no pending packets at the link level before setting this bit.
|
||||||
|
/// This flag is ‘0’ if PP is ‘0’
|
||||||
|
force_link_pm_accept: bool,
|
||||||
|
#[bits(15)]
|
||||||
|
__: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XHCI Spec 5.4.10
|
||||||
|
///
|
||||||
|
/// NOTE: This definition is USB3 only.
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct PortLinkInfo {
|
||||||
|
/// Link Error Count – RW. Default = ‘0’. This field returns the number of link errors detected by the
|
||||||
|
/// port. This value shall be reset to ‘0’ by the assertion of a Chip Hardware Reset, HCRST, when PR
|
||||||
|
/// transitions from ‘1’ to ‘0’, or when reset by software by writing 0 to it. This register will increment
|
||||||
|
/// by one each time a port transitions from U0 to Recovery to recover an error event and will
|
||||||
|
/// saturate at max
|
||||||
|
link_error_count: u16,
|
||||||
|
/// Rx Lane Count (RLC) - RO. Default = '0'. This field that identifies the number of Receive Lanes
|
||||||
|
/// negotiated by the port. This is a "zero-based" value, where 0 to 15 represents Lane Counts of 1
|
||||||
|
/// to 16, respectively. This value is valid only when CCS = '1'. RLC shall equal '0' for a simplex
|
||||||
|
/// Sublink. Refer to section 7.2.1 for more information.
|
||||||
|
#[bits(4)]
|
||||||
|
rx_lane_count: u8,
|
||||||
|
/// Tx Lane Count (TLC) - RO. Default = '0'. This field that identifies the number of Transmit Lanes
|
||||||
|
/// negotiated by the port. This is a "zero-based" value, where 0 to 15 represents Lane Counts of 1
|
||||||
|
/// to 16, respectively. This value is valid only when CCS = '1'. TLC shall equal '0' for a simplex
|
||||||
|
/// Sublink. Refer to section 7.2.1 for more information.
|
||||||
|
#[bits(4)]
|
||||||
|
tx_lane_count: u8,
|
||||||
|
__: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// XHCI Spec 5.4.8 - 5.4.11
|
||||||
|
///
|
||||||
|
/// These registers are an array of size MAX_PORTS located at offset 0x400
|
||||||
|
/// from the HostControllerOperation address.
|
||||||
|
///
|
||||||
|
/// Where MAX_PORTS is HostControllerCapabilities.params_1.max_ports
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct HostControllerUsbPort {
|
||||||
|
pub status_and_control: PortStatusAndControl,
|
||||||
|
pub power_management_status_and_control: PortPowerManagementStatusAndControl,
|
||||||
|
pub link_info: PortLinkInfo,
|
||||||
|
pub hardware_lpm_control: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<HostControllerUsbPort>() == 0x10);
|
||||||
206
rust/sys/voyageurs/src/xhci/registers/interrupter.rs
Normal file
206
rust/sys/voyageurs/src/xhci/registers/interrupter.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
use bitfield_struct::bitfield;
|
||||||
|
use volatile::{VolatileRef, map_field};
|
||||||
|
|
||||||
|
use crate::xhci::data_structures::EventRingSegmentTable;
|
||||||
|
|
||||||
|
/// The Interrupter Management register allows system software to enable, disable,
|
||||||
|
/// and detect xHC interrupts.
|
||||||
|
///
|
||||||
|
/// XHCI 5.5.2.1
|
||||||
|
#[bitfield(u32)]
|
||||||
|
struct InterrupterManagement {
|
||||||
|
/// Interrupt Pending (IP) - RW1C. Default = ‘0’. This flag represents the current state of the
|
||||||
|
/// Interrupter. If IP = ‘1’, an interrupt is pending for this Interrupter. A ‘0’ value indicates that no
|
||||||
|
/// interrupt is pending for the Interrupter. Refer to section 4.17.3 for the conditions that modify
|
||||||
|
/// the state of this flag.
|
||||||
|
interrupt_pending: bool,
|
||||||
|
|
||||||
|
/// Interrupt Enable (IE) – RW. Default = ‘0’. This flag specifies whether the Interrupter is capable of
|
||||||
|
/// generating an interrupt. When this bit and the IP bit are set (‘1’), the Interrupter shall generate
|
||||||
|
/// an interrupt when the Interrupter Moderation Counter reaches ‘0’. If this bit is ‘0’, then the
|
||||||
|
/// Interrupter is prohibited from generating interrupts
|
||||||
|
interrupt_enabled: bool,
|
||||||
|
|
||||||
|
#[bits(30)]
|
||||||
|
_reserved: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Interrupter Moderation Register controls the “interrupt moderation” feature
|
||||||
|
/// of an Interrupter, allowing system software to throttle the interrupt rate
|
||||||
|
/// generated by the xHC
|
||||||
|
///
|
||||||
|
/// XHCI 5.5.2.2
|
||||||
|
#[bitfield(u32)]
|
||||||
|
pub struct InterrupterModeration {
|
||||||
|
/// Interrupt Moderation Interval (IMODI) – RW. Default = ‘4000’ (~1ms). Minimum inter-interrupt
|
||||||
|
/// interval. The interval is specified in 250ns increments. A value of ‘0’ disables interrupt throttling
|
||||||
|
/// logic and interrupts shall be generated immediately if IP = ‘0’, EHB = ‘0’, and the Event Ring is
|
||||||
|
/// not empty
|
||||||
|
pub interrupt_moderation_interval: u16,
|
||||||
|
|
||||||
|
/// Interrupt Moderation Counter (IMODC) – RW. Default = undefined. Down counter. Loaded with
|
||||||
|
/// the IMODI value whenever IP is cleared to ‘0’, counts down to ‘0’, and stops. The associated
|
||||||
|
/// interrupt shall be signaled whenever this counter is ‘0’, the Event Ring is not empty, the IE and IP
|
||||||
|
/// flags = ‘1’, and EHB = ‘0’.
|
||||||
|
/// This counter may be directly written by software at any time to alter the interrupt rate
|
||||||
|
pub interrupt_moderation_counter: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct ManagementAndModeration {
|
||||||
|
#[bits(32)]
|
||||||
|
management: InterrupterManagement,
|
||||||
|
|
||||||
|
#[bits(32)]
|
||||||
|
moderation: InterrupterModeration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagementAndModeration {
|
||||||
|
fn update_moderation(self, moderation: InterrupterModeration) -> Self {
|
||||||
|
// Update preserving intterupt pending.
|
||||||
|
self.with_management(self.management().with_interrupt_pending(false))
|
||||||
|
.with_moderation(moderation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Event Ring Dequeue Pointer Register is written by software to define the
|
||||||
|
/// Event Ring Dequeue Pointer location to the xHC. Software updates this pointer
|
||||||
|
/// when it is finished the evaluation of an Event(s) on the Event Ring.
|
||||||
|
///
|
||||||
|
/// XHCI 5.5.2.3.3
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct EventRingDequePointer {
|
||||||
|
/// Dequeue ERST Segment Index (DESI) – RW. Default = ‘0’. This field may be used by the xHC to
|
||||||
|
/// accelerate checking the Event Ring full condition. This field is written with the low order 3 bits of
|
||||||
|
/// the offset of the ERST entry which defines the Event Ring segment that the Event Ring Dequeue
|
||||||
|
/// Pointer resides in. Refer to section 6.5 for the definition of an ERST entry.
|
||||||
|
#[bits(3)]
|
||||||
|
dequeue_erst_segment_index: u8,
|
||||||
|
|
||||||
|
/// Event Handler Busy (EHB) - RW1C. Default = ‘0’. This flag shall be set to ‘1’ when the IP bit is set
|
||||||
|
/// to ‘1’ and cleared to ‘0’ by software when the Dequeue Pointer register is written. Refer to
|
||||||
|
/// section 4.17.2 for more information
|
||||||
|
event_handler_busy: bool,
|
||||||
|
|
||||||
|
/// Event Ring Dequeue Pointer - RW. Default = ‘0’. This field defines the high order bits of the 64-
|
||||||
|
/// bit address of the current Event Ring Dequeue Pointer
|
||||||
|
#[bits(60)]
|
||||||
|
event_ring_dequeue_pointer: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventRingDequePointer {
|
||||||
|
fn with_dequeue_pointer_adjusted(self, event_ring_dequeue_pointer: usize) -> Self {
|
||||||
|
assert!(
|
||||||
|
event_ring_dequeue_pointer & 0b1111 == 0,
|
||||||
|
"Bottom four bits of event ring dequeue pointer must be 0"
|
||||||
|
);
|
||||||
|
self.with_event_ring_dequeue_pointer((event_ring_dequeue_pointer >> 4) as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_event_hanlder_busy(self) -> Self {
|
||||||
|
// RW1C
|
||||||
|
self.with_event_handler_busy(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Event Ring Segment Table Size Register defines the number of segments
|
||||||
|
/// supported by the Event Ring Segment Table.
|
||||||
|
///
|
||||||
|
/// XHCI 5.5.2.3.1
|
||||||
|
#[bitfield(u64)]
|
||||||
|
struct EventRingSegmentTableSize {
|
||||||
|
/// Event Ring Segment Table Size – RW. Default = ‘0’. This field identifies the number of valid
|
||||||
|
/// Event Ring Segment Table entries in the Event Ring Segment Table pointed to by the Event Ring
|
||||||
|
/// Segment Table Base Address register. The maximum value supported by an xHC
|
||||||
|
/// implementation for this register is defined by the ERST Max field in the HCSPARAMS2 register
|
||||||
|
/// (5.3.4).
|
||||||
|
/// For Secondary Interrupters: Writing a value of ‘0’ to this field disables the Event Ring. Any events
|
||||||
|
/// targeted at this Event Ring when it is disabled shall result in undefined behavior of the Event
|
||||||
|
/// Ring.
|
||||||
|
/// For the Primary Interrupter: Writing a value of ‘0’ to this field shall result in undefined behavior
|
||||||
|
/// of the Event Ring. The Primary Event Ring cannot be disabled.
|
||||||
|
event_ring_segment_table_size: u16,
|
||||||
|
__: u16,
|
||||||
|
__: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is an array of registers starting at offset 0x20 of the Runtime Base.
|
||||||
|
/// The Runtime Base shall be 32-byte aligned and is calculated by adding the
|
||||||
|
/// value Runtime Register Space Offset register (refer to Section 5.3.8) to
|
||||||
|
/// the Capability Base address. All Runtime registers are multiples of 32 bits in length.
|
||||||
|
///
|
||||||
|
/// XHCI Spec 5.5.2
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct InterrupterRegisterSet {
|
||||||
|
management_and_moderation: ManagementAndModeration,
|
||||||
|
event_ring_segment_table_size: EventRingSegmentTableSize,
|
||||||
|
/// Event Ring Segment Table Base Address Register – RW. Default = ‘0’. This field defines the
|
||||||
|
/// high order bits of the start address of the Event Ring Segment Table.
|
||||||
|
/// Writing this register sets the Event Ring State Machine:EREP Advancement to the Start state.
|
||||||
|
/// Refer to Figure 4-12 for more information.
|
||||||
|
/// For Secondary Interrupters: This field may be modified at any time.
|
||||||
|
/// For the Primary Interrupter: This field shall not be modified if HCHalted (HCH) = ‘0’.
|
||||||
|
///
|
||||||
|
/// NOTE: This must be aligned such that bits 0:5 are 0.
|
||||||
|
///
|
||||||
|
/// XHCI 5.5.2.3.2
|
||||||
|
event_ring_segment_table_base_address: u64,
|
||||||
|
event_ring_deque_pointer: EventRingDequePointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = assert!(size_of::<InterrupterRegisterSet>() == 0x20);
|
||||||
|
|
||||||
|
pub struct InterrupterRegisters(VolatileRef<'static, InterrupterRegisterSet>);
|
||||||
|
|
||||||
|
impl InterrupterRegisters {
|
||||||
|
pub fn new(interrupter_register_set: NonNull<InterrupterRegisterSet>) -> Self {
|
||||||
|
// TODO: Validate safety.
|
||||||
|
unsafe { Self(VolatileRef::new(interrupter_register_set)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SAFETY:
|
||||||
|
/// - For the primary interrupter HC must be halted.
|
||||||
|
/// - The event rings size must be at most ERST_MAX from HCSPARAMS2
|
||||||
|
pub unsafe fn set_event_ring(
|
||||||
|
&mut self,
|
||||||
|
event_ring_segment_table: &EventRingSegmentTable,
|
||||||
|
event_ring_dequeue_pointer: usize,
|
||||||
|
) {
|
||||||
|
// NOTE: We must write the size before the base address otherwise qemu is unhappy.
|
||||||
|
// Not sure if this is required by the spec.
|
||||||
|
|
||||||
|
let internal = self.0.as_mut_ptr();
|
||||||
|
map_field!(internal.event_ring_segment_table_size).write(
|
||||||
|
EventRingSegmentTableSize::new()
|
||||||
|
.with_event_ring_segment_table_size(event_ring_segment_table.len() as u16),
|
||||||
|
);
|
||||||
|
map_field!(internal.event_ring_segment_table_base_address)
|
||||||
|
.write(event_ring_segment_table.physical_address() as u64);
|
||||||
|
map_field!(internal.event_ring_deque_pointer).write(
|
||||||
|
EventRingDequePointer::new().with_dequeue_pointer_adjusted(event_ring_dequeue_pointer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_dequeue_pointer_clearing_busy(&mut self, event_ring_dequeue_pointer: usize) {
|
||||||
|
let internal = self.0.as_mut_ptr();
|
||||||
|
map_field!(internal.event_ring_deque_pointer).update(|ptr| {
|
||||||
|
ptr.with_dequeue_pointer_adjusted(event_ring_dequeue_pointer)
|
||||||
|
.clear_event_hanlder_busy()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_moderation(&mut self, moderation: InterrupterModeration) {
|
||||||
|
let internal = self.0.as_mut_ptr();
|
||||||
|
map_field!(internal.management_and_moderation)
|
||||||
|
.update(|reg| reg.update_moderation(moderation));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_interrupts(&mut self) {
|
||||||
|
let internal = self.0.as_mut_ptr();
|
||||||
|
map_field!(internal.management_and_moderation).update(|reg| {
|
||||||
|
reg.with_management(InterrupterManagement::new().with_interrupt_enabled(true))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
15
rust/sys/voyageurs/src/xhci/registers/mod.rs
Normal file
15
rust/sys/voyageurs/src/xhci/registers/mod.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/// This mod contains XHCI Register Definitions
|
||||||
|
///
|
||||||
|
/// These are generally hardware backed registers
|
||||||
|
/// defined at fixed addresses.
|
||||||
|
mod capabilities;
|
||||||
|
mod doorbell;
|
||||||
|
mod host_controller;
|
||||||
|
mod host_controller_port;
|
||||||
|
mod interrupter;
|
||||||
|
|
||||||
|
pub use capabilities::*;
|
||||||
|
pub use doorbell::*;
|
||||||
|
pub use host_controller::*;
|
||||||
|
pub use host_controller_port::*;
|
||||||
|
pub use interrupter::*;
|
||||||
188
rust/sys/voyageurs/src/xhci/trb_ring.rs
Normal file
188
rust/sys/voyageurs/src/xhci/trb_ring.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
use core::task::{Poll, Waker};
|
||||||
|
|
||||||
|
use alloc::{collections::vec_deque::VecDeque, sync::Arc, vec::Vec};
|
||||||
|
use mammoth::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::xhci::{
|
||||||
|
data_structures::{
|
||||||
|
CommandCompletionEvent, CommandTrb, TransferRequestBlock, TrbLink, TrbRingSegment, TypedTrb,
|
||||||
|
},
|
||||||
|
registers::DoorbellPointer,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrbFutureState<T> {
|
||||||
|
/// Physical Address for the enqueued TRB.
|
||||||
|
/// Used for sanity checking.
|
||||||
|
physical_address: usize,
|
||||||
|
|
||||||
|
waker: Option<Waker>,
|
||||||
|
response: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TrbFuture<T> {
|
||||||
|
state: Arc<Mutex<TrbFutureState<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TrbFuture<T> {
|
||||||
|
fn new(paddr: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(TrbFutureState {
|
||||||
|
physical_address: paddr,
|
||||||
|
waker: None,
|
||||||
|
response: None,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy> Future for TrbFuture<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: core::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut core::task::Context<'_>,
|
||||||
|
) -> core::task::Poll<Self::Output> {
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
match state.response {
|
||||||
|
Some(trb) => Poll::Ready(trb),
|
||||||
|
None => {
|
||||||
|
state.waker = Some(cx.waker().clone());
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone, Debug)]
|
||||||
|
pub struct TrbPointer {
|
||||||
|
/// Index into the vector of trb segments.
|
||||||
|
pub segment_index: usize,
|
||||||
|
/// Index into the specific segment.
|
||||||
|
/// This is a TransferRequestBlock index,
|
||||||
|
/// to get the physical_offset use segment_physical_offset()
|
||||||
|
pub segment_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrbPointer {
|
||||||
|
pub fn segment_physical_offset(&self) -> usize {
|
||||||
|
self.segment_offset * size_of::<TransferRequestBlock>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TrbRing<T> {
|
||||||
|
segments: Vec<TrbRingSegment>,
|
||||||
|
enqueue_pointer: TrbPointer,
|
||||||
|
cycle_bit: bool,
|
||||||
|
pending_futures: VecDeque<TrbFuture<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> TrbRing<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
// TODO: What size and count should this be.
|
||||||
|
segments: alloc::vec![TrbRingSegment::new(100)],
|
||||||
|
enqueue_pointer: TrbPointer::default(),
|
||||||
|
// Start with this as true so we are flipping bits from 0 (default) to 1
|
||||||
|
// to mark the enqueue pointer.
|
||||||
|
cycle_bit: true,
|
||||||
|
pending_futures: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn physical_base_address(&self) -> usize {
|
||||||
|
self.segments[0].physical_address()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physical_address_of_enqueue_pointer(&self) -> usize {
|
||||||
|
self.segments[self.enqueue_pointer.segment_index].physical_address()
|
||||||
|
+ self.enqueue_pointer.segment_physical_offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue_trb(&mut self, trb: TransferRequestBlock) -> TrbFuture<T> {
|
||||||
|
let paddr = self.physical_address_of_enqueue_pointer();
|
||||||
|
*self.next_trb_ref() = trb.with_cycle(self.cycle_bit);
|
||||||
|
self.advance_enqueue_pointer();
|
||||||
|
let future = TrbFuture::new(paddr);
|
||||||
|
self.pending_futures.push_back(future.clone());
|
||||||
|
future
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_trb_ref(&mut self) -> &mut TransferRequestBlock {
|
||||||
|
&mut self.segments[self.enqueue_pointer.segment_index][self.enqueue_pointer.segment_offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_enqueue_pointer(&mut self) {
|
||||||
|
self.enqueue_pointer.segment_offset += 1;
|
||||||
|
|
||||||
|
if self.enqueue_pointer.segment_offset
|
||||||
|
== self.segments[self.enqueue_pointer.segment_index].len() - 1
|
||||||
|
{
|
||||||
|
// We have reached the end of the segment, insert a link trb.
|
||||||
|
|
||||||
|
// Increment the segment index with wrapping.
|
||||||
|
let next_segment_index =
|
||||||
|
if self.enqueue_pointer.segment_index + 1 == self.segments.len() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.enqueue_pointer.segment_index + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let next_segment_pointer = self.segments[next_segment_index].physical_address();
|
||||||
|
let toggle_cycle = next_segment_index == 0;
|
||||||
|
|
||||||
|
*self.next_trb_ref() = TrbLink::new()
|
||||||
|
.with_ring_segment_pointer(next_segment_pointer as u64)
|
||||||
|
.with_cycle(self.cycle_bit)
|
||||||
|
.with_toggle_cycle(toggle_cycle)
|
||||||
|
.to_trb();
|
||||||
|
|
||||||
|
// Flip toggle cycle bit if necessary.
|
||||||
|
self.cycle_bit ^= toggle_cycle;
|
||||||
|
|
||||||
|
self.enqueue_pointer = TrbPointer {
|
||||||
|
segment_index: next_segment_index,
|
||||||
|
segment_offset: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_completion(&mut self, completion_trb: T, physical_address: usize) {
|
||||||
|
let completion = self.pending_futures.pop_front().unwrap();
|
||||||
|
let mut completion = completion.state.lock();
|
||||||
|
// TODO: Handle recovery scenarios here.
|
||||||
|
assert!(
|
||||||
|
completion.physical_address == physical_address,
|
||||||
|
"Got an unexpected command completion. Expected: {:0x}, Got: {:0x}",
|
||||||
|
completion.physical_address,
|
||||||
|
physical_address
|
||||||
|
);
|
||||||
|
completion.response = Some(completion_trb);
|
||||||
|
|
||||||
|
if let Some(waker) = &completion.waker {
|
||||||
|
waker.wake_by_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CommandRing {
|
||||||
|
pub trb_ring: TrbRing<CommandCompletionEvent>,
|
||||||
|
doorbell: DoorbellPointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandRing {
|
||||||
|
pub fn new(doorbell: DoorbellPointer) -> Self {
|
||||||
|
Self {
|
||||||
|
trb_ring: TrbRing::new(),
|
||||||
|
doorbell,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to explicitly return a future her
|
||||||
|
pub fn enqueue_command(&mut self, command: CommandTrb) -> TrbFuture<CommandCompletionEvent> {
|
||||||
|
let fut = self.trb_ring.enqueue_trb(command.into());
|
||||||
|
// Command Doorbell is always 0.
|
||||||
|
self.doorbell.ring(0);
|
||||||
|
fut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,5 @@ edition = "2021"
|
||||||
mammoth = { path = "../../lib/mammoth" }
|
mammoth = { path = "../../lib/mammoth" }
|
||||||
denali_client = { path = "../../lib/client/denali_client" }
|
denali_client = { path = "../../lib/client/denali_client" }
|
||||||
victoriafalls = { path = "../victoriafalls" }
|
victoriafalls = { path = "../victoriafalls" }
|
||||||
voyageurs = { path = "../../lib/voyageurs" }
|
|
||||||
yellowstone-yunq = { path = "../../lib/yellowstone" }
|
yellowstone-yunq = { path = "../../lib/yellowstone" }
|
||||||
yunq = { path = "../../lib/yunq" }
|
yunq = { path = "../../lib/yunq" }
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,8 @@ impl YellowstoneServerContext {
|
||||||
|
|
||||||
pub fn wait(&self, service: &str) -> Result<(), ZError> {
|
pub fn wait(&self, service: &str) -> Result<(), ZError> {
|
||||||
loop {
|
loop {
|
||||||
match self.service_map.lock().get(service) {
|
if self.service_map.lock().get(service).is_some() {
|
||||||
Some(_) => return Ok(()),
|
return Ok(());
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
self.registration_semaphore.wait().unwrap();
|
self.registration_semaphore.wait().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
||||||
"arch": "x86_64",
|
"arch": "x86_64",
|
||||||
"target-endian": "little",
|
"target-endian": "little",
|
||||||
"target-pointer-width": "64",
|
"target-pointer-width": 64,
|
||||||
"target-c-int-width": "32",
|
"target-c-int-width": 32,
|
||||||
"os": "none",
|
"os": "none",
|
||||||
"executables": true,
|
"executables": true,
|
||||||
"linker-flavor": "ld.lld",
|
"linker-flavor": "ld.lld",
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,15 @@ cleanup() {
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
parted -s $dev mklabel gpt mkpart EFI fat32 1MiB 10MiB mkpart ext2 10MiB 100% set 1 esp on
|
parted -s $dev mklabel gpt mkpart BIOS ext2 1MiB 2MiB mkpart EFI fat32 2MiB 11MiB mkpart ext2 11MiB 100% set 1 bios_grub on set 2 esp on
|
||||||
mkfs.fat -F 12 "${dev}p1"
|
mkfs.fat -F 12 "${dev}p2"
|
||||||
mke2fs "${dev}p2"
|
mke2fs "${dev}p3"
|
||||||
|
|
||||||
limine bios-install "${dev}"
|
limine bios-install "${dev}"
|
||||||
|
|
||||||
|
|
||||||
mkdir -p $EFI_DIR
|
mkdir -p $EFI_DIR
|
||||||
mount "${dev}p1" $EFI_DIR
|
mount "${dev}p2" $EFI_DIR
|
||||||
|
|
||||||
mkdir -p $EFI_DIR/EFI/BOOT
|
mkdir -p $EFI_DIR/EFI/BOOT
|
||||||
cp /usr/share/limine/BOOTX64.EFI $EFI_DIR/EFI/BOOT
|
cp /usr/share/limine/BOOTX64.EFI $EFI_DIR/EFI/BOOT
|
||||||
|
|
@ -52,7 +52,7 @@ cp $REPO_ROOT/sysroot/bin/denali $EFI_DIR/sys/denali
|
||||||
cp $REPO_ROOT/sysroot/bin/victoriafalls $EFI_DIR/sys/victoriafalls
|
cp $REPO_ROOT/sysroot/bin/victoriafalls $EFI_DIR/sys/victoriafalls
|
||||||
|
|
||||||
mkdir -p $SYSROOT
|
mkdir -p $SYSROOT
|
||||||
mount "${dev}p2" $SYSROOT
|
mount "${dev}p3" $SYSROOT
|
||||||
rsync -a "$REPO_ROOT/sysroot" $BUILD_DIR
|
rsync -a "$REPO_ROOT/sysroot" $BUILD_DIR
|
||||||
ls $SYSROOT
|
ls $SYSROOT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ if [[ $1 == "debug" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use machine q35 to access PCI devices.
|
# Use machine q35 to access PCI devices.
|
||||||
qemu-system-x86_64 -machine q35 -d guest_errors -m 1G -serial stdio -hda ${BUILD_DIR}/disk.img ${QEMU_ARGS} -device nec-usb-xhci,id=xhci -device usb-kbd,bus=xhci.0
|
~/.local/bin/qemu-system-x86_64 -machine q35 -d guest_errors -m 1G -serial stdio -hda ${BUILD_DIR}/disk.img ${QEMU_ARGS} -device nec-usb-xhci,id=xhci -device usb-kbd,bus=xhci.0
|
||||||
popd
|
popd
|
||||||
|
|
||||||
# Extra options to add to this script in the future.
|
# Extra options to add to this script in the future.
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ fn parse_field(field: &Field) -> TokenStream {
|
||||||
let rep_offset = buf.at::<u32>(yunq::message::field_offset(offset, #ind))?;
|
let rep_offset = buf.at::<u32>(yunq::message::field_offset(offset, #ind))?;
|
||||||
let rep_len = buf.at::<u32>(yunq::message::field_offset(offset, #ind) + 4)?;
|
let rep_len = buf.at::<u32>(yunq::message::field_offset(offset, #ind) + 4)?;
|
||||||
|
|
||||||
yunq::message::parse_repeated_message(buf, offset + rep_offset as usize, rep_len as usize, &caps)?
|
yunq::message::parse_repeated_message(buf, offset + rep_offset as usize, rep_len as usize, caps)?
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +174,7 @@ fn generate_serialize(message: &Message) -> TokenStream {
|
||||||
&self,
|
&self,
|
||||||
buf: &mut yunq::ByteBuffer<N>,
|
buf: &mut yunq::ByteBuffer<N>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
caps: &mut alloc::vec::Vec<z_cap_t>,
|
caps: &mut Vec<z_cap_t>,
|
||||||
) -> Result<usize, ZError> {
|
) -> Result<usize, ZError> {
|
||||||
let num_fields = #num_fields;
|
let num_fields = #num_fields;
|
||||||
let core_size: u32 = (yunq::message::MESSAGE_HEADER_SIZE + 8 * num_fields) as u32;
|
let core_size: u32 = (yunq::message::MESSAGE_HEADER_SIZE + 8 * num_fields) as u32;
|
||||||
|
|
@ -183,10 +183,10 @@ fn generate_serialize(message: &Message) -> TokenStream {
|
||||||
|
|
||||||
#(#serializers)*
|
#(#serializers)*
|
||||||
|
|
||||||
buf.write_at(offset + 0, yunq::message::MESSAGE_IDENT)?;
|
buf.write_at(offset, yunq::message::MESSAGE_IDENT)?;
|
||||||
buf.write_at(offset + 4, core_size)?;
|
buf.write_at(offset + 4, core_size)?;
|
||||||
buf.write_at(offset + 8, next_extension)?;
|
buf.write_at(offset + 8, next_extension)?;
|
||||||
buf.write_at(offset + 12, 0 as u32)?;
|
buf.write_at(offset + 12, 0_u32)?;
|
||||||
Ok(next_extension as usize)
|
Ok(next_extension as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,12 +200,12 @@ fn generate_parse(message: &Message) -> TokenStream {
|
||||||
fn parse<const N: usize>(
|
fn parse<const N: usize>(
|
||||||
buf: &yunq::ByteBuffer<N>,
|
buf: &yunq::ByteBuffer<N>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
caps: &alloc::vec::Vec<z_cap_t>,
|
caps: &[z_cap_t],
|
||||||
) -> Result<Self, ZError>
|
) -> Result<Self, ZError>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
if buf.at::<u32>(offset + 0)? != yunq::message::MESSAGE_IDENT {
|
if buf.at::<u32>(offset)? != yunq::message::MESSAGE_IDENT {
|
||||||
mammoth::debug!("Expected IDENT at offest {:#x}, got {:#x}", offset, buf.at::<u32>(offset)?);
|
mammoth::debug!("Expected IDENT at offest {:#x}, got {:#x}", offset, buf.at::<u32>(offset)?);
|
||||||
return Err(ZError::INVALID_ARGUMENT);
|
return Err(ZError::INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +303,7 @@ fn generate_server_case(method: &Method) -> TokenStream {
|
||||||
#id => {
|
#id => {
|
||||||
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
||||||
let resp = self.handler.#name(req)?;
|
let resp = self.handler.#name(req)?;
|
||||||
cap_buffer.resize(0, 0);
|
cap_buffer.clear();
|
||||||
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
||||||
Ok(resp_len)
|
Ok(resp_len)
|
||||||
},
|
},
|
||||||
|
|
@ -312,7 +312,7 @@ fn generate_server_case(method: &Method) -> TokenStream {
|
||||||
#id => {
|
#id => {
|
||||||
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
||||||
self.handler.#name(req)?;
|
self.handler.#name(req)?;
|
||||||
cap_buffer.resize(0, 0);
|
cap_buffer.clear();
|
||||||
// TODO: Implement serialization for EmptyMessage so this is less hacky.
|
// TODO: Implement serialization for EmptyMessage so this is less hacky.
|
||||||
yunq::message::serialize_error(byte_buffer, ZError::from(0));
|
yunq::message::serialize_error(byte_buffer, ZError::from(0));
|
||||||
Ok(0x10)
|
Ok(0x10)
|
||||||
|
|
@ -321,7 +321,7 @@ fn generate_server_case(method: &Method) -> TokenStream {
|
||||||
(None, Some(_)) => quote! {
|
(None, Some(_)) => quote! {
|
||||||
#id => {
|
#id => {
|
||||||
let resp = self.handler.#name()?;
|
let resp = self.handler.#name()?;
|
||||||
cap_buffer.resize(0, 0);
|
cap_buffer.clear();
|
||||||
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
||||||
Ok(resp_len)
|
Ok(resp_len)
|
||||||
},
|
},
|
||||||
|
|
@ -403,7 +403,7 @@ fn generate_async_server_case(method: &Method) -> TokenStream {
|
||||||
#id => {
|
#id => {
|
||||||
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
||||||
let resp = self.handler.#name(req).await?;
|
let resp = self.handler.#name(req).await?;
|
||||||
cap_buffer.resize(0, 0);
|
cap_buffer.clear();
|
||||||
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
||||||
Ok(resp_len)
|
Ok(resp_len)
|
||||||
},
|
},
|
||||||
|
|
@ -412,7 +412,7 @@ fn generate_async_server_case(method: &Method) -> TokenStream {
|
||||||
#id => {
|
#id => {
|
||||||
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
let req = #req::parse_from_request(byte_buffer, cap_buffer)?;
|
||||||
self.handler.#name(req).await?;
|
self.handler.#name(req).await?;
|
||||||
cap_buffer.resize(0, 0);
|
cap_buffer.clear();
|
||||||
// TODO: Implement serialization for EmptyMessage so this is less hacky.
|
// TODO: Implement serialization for EmptyMessage so this is less hacky.
|
||||||
yunq::message::serialize_error(byte_buffer, ZError::from(0));
|
yunq::message::serialize_error(byte_buffer, ZError::from(0));
|
||||||
Ok(0x10)
|
Ok(0x10)
|
||||||
|
|
@ -421,7 +421,7 @@ fn generate_async_server_case(method: &Method) -> TokenStream {
|
||||||
(None, Some(_)) => quote! {
|
(None, Some(_)) => quote! {
|
||||||
#id => {
|
#id => {
|
||||||
let resp = self.handler.#name().await?;
|
let resp = self.handler.#name().await?;
|
||||||
cap_buffer.resize(0, 0);
|
cap_buffer.clear();
|
||||||
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
let resp_len = resp.serialize_as_request(0, byte_buffer, cap_buffer)?;
|
||||||
Ok(resp_len)
|
Ok(resp_len)
|
||||||
},
|
},
|
||||||
|
|
@ -547,6 +547,7 @@ pub fn generate_code(ast: &[Decl]) -> String {
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use mammoth::zion::z_cap_t;
|
use mammoth::zion::z_cap_t;
|
||||||
use mammoth::zion::ZError;
|
use mammoth::zion::ZError;
|
||||||
|
#[allow(unused_imports)]
|
||||||
use yunq::ByteBuffer;
|
use yunq::ByteBuffer;
|
||||||
use yunq::YunqMessage;
|
use yunq::YunqMessage;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ void DriverManager::WriteMessage(uint64_t irq_num, IpcMessage&& message) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbgln("IRQ offset {x}", offset);
|
||||||
|
|
||||||
driver_list_[offset]->Send(glcr::Move(message));
|
driver_list_[offset]->Send(glcr::Move(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue