diff --git a/drivers/.gitignore b/drivers/.gitignore new file mode 100644 index 0000000000..d1adf4b7b0 --- /dev/null +++ b/drivers/.gitignore @@ -0,0 +1,10 @@ +target/ + +# Local settings folder for Visual Studio Code +.vscode/ +# Local settings folder for Jetbrains products (RustRover, IntelliJ, CLion) +.idea/ +# Local settings folder for Visual Studio Professional +.vs/ +# Local settings folder for the devcontainer extension that most IDEs support. +.devcontainer/ diff --git a/drivers/.gitlab-ci.yml b/drivers/.gitlab-ci.yml new file mode 100644 index 0000000000..047fd555bf --- /dev/null +++ b/drivers/.gitlab-ci.yml @@ -0,0 +1,19 @@ +image: "redoxos/redoxer:latest" + +stages: + - build + + # TODO? + # - test + +# TODO check if all drivers build + +fmt: + stage: build + needs: [] + script: + - rustup component add rustfmt-preview + # TODO add more packages as they get formatted + - CHECK_ONLY=1 ./fmt.sh + +# TODO: unit tests diff --git a/drivers/.gitlab/issue_templates/Issue_template.md b/drivers/.gitlab/issue_templates/Issue_template.md new file mode 100644 index 0000000000..42d653e2f0 --- /dev/null +++ b/drivers/.gitlab/issue_templates/Issue_template.md @@ -0,0 +1,92 @@ + + + + +- [ ] I agree that I have searched opened and closed issues to prevent duplicates. + +-------------------- + + + +## Description + +Replace me + + + +## Environment info + + + +- Redox OS Release: +0.0.0 Remove me + + +- Operating system: +Replace me +- `uname -a`: +`Replace me` +- `rustc -V`: +`Replace me` +- `git rev-parse HEAD`: +`Replace me` + +- Replace me: +Replace me + + + +## Steps to reproduce + +1. Replace me +2. Replace me +3. ... + + + +## Behavior + + + +- **Expected behavior**: +Replace me + + +- **Actual behavior**: +Replace me + + +``` +Replace me +``` + + +- **Proposed solution**: +Replace me + + + + + +## Optional references + + +Related to: +- #0000 Remove me +- Replace me +- ... + +Blocked by: +- #0000 Remove me +- ... + + + +## Optional extras + +Replace me + + + + + diff --git a/drivers/.gitlab/merge_request_templates/Merge_request_template.md b/drivers/.gitlab/merge_request_templates/Merge_request_template.md new file mode 100644 index 0000000000..be611fa3e4 --- /dev/null +++ b/drivers/.gitlab/merge_request_templates/Merge_request_template.md @@ -0,0 +1,25 @@ +**Problem**: [describe the problem you try to solve with this PR.] + +**Solution**: [describe carefully what you change by this PR.] + +**Changes introduced by this pull request**: + +- [...] +- [...] +- [...] + +**Drawbacks**: [if any, describe the drawbacks of this pull request.] + +**TODOs**: [what is not done yet.] + +**Fixes**: [what issues this fixes.] + +**State**: [the state of this PR, e.g. WIP, ready, etc.] + +**Blocking/related**: [issues or PRs blocking or being related to this issue.] + +**Other**: [optional: for other relevant information that should be known or cannot be described in the other fields.] + +------ + +_The above template is not necessary for smaller PRs._ diff --git a/drivers/COMMUNITY-HW.md b/drivers/COMMUNITY-HW.md new file mode 100644 index 0000000000..62fa6c26ae --- /dev/null +++ b/drivers/COMMUNITY-HW.md @@ -0,0 +1,64 @@ +# Community Hardware + +This document covers the devices from the community that needs a driver. + +Unfortunately we can't know the most sold device models of the world to measure our device porting priority, thus we will use our community data to measure our device priorities, if you find a "device model users" survey (similar to [Debian Popularity Contest](https://popcon.debian.org/) and [Steam Hardware/Software Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam)), please comment. + +If you want to contribute to this table, install [pciutils](https://mj.ucw.cz/sw/pciutils/) on your Linux distribution (it should have a package on your distribution), run `lspci -v` to see your hardware devices, their kernel drivers and give the results of these items on each device: + +- The first field (each device has an unique name for this item) +- Kernel driver in use +- Kernel modules + +If you are unsure of what to do, you can talk with us on the [chat](https://doc.redox-os.org/book/chat.html). + +## Template + +You will use this template to insert your devices on the table. + +``` +| | | | No | +``` + +| **Device model** | **Kernel driver** | **Kernel module** | **There's a Redox driver?** | +|------------------|-------------------|-------------------|-----------------------------| +| Realtek RTL8821CE 802.11ac (Wi-Fi) | rtw_8821ce | rtw88_8821ce | No | +| Intel Ice Lake-LP SPI Controller | intel-spi | spi_intel_pci | No | +| Intel Ice Lake-LP SMBus Controller | i801_smbus | i2c_i801 | No | +| Intel Ice Lake-LP Smart Sound Technology Audio Controller | snd_hda_intel | snd_hda_intel, snd_sof_pci_intel_icl | No | +| Intel Ice Lake-LP Serial IO SPI Controller | intel-lpss | No | No | +| Intel Ice Lake-LP Serial IO UART Controller | intel-lpss | No | No | +| Intel Ice Lake-LP Serial IO I2C Controller | intel-lpss | No | No | +| Ice Lake-LP USB 3.1 xHCI Host Controller | xhci_hcd | No | No | +| Intel Processor Power and Thermal Controller | proc_thermal | processor_thermal_device_pci_legacy | No | +| Intel Device 8a02 | icl_uncore | No | No | +| Iris Plus Graphics G1 (Ice Lake) | i915 | i915 | No | +| Intel Corporation Raptor Lake-P 6p+8e cores Host Bridge/DRAM Controller | No | No | No | +| Intel Corporation Raptor Lake PCI Express 5.0 Graphics Port (PEG010) (prog-if 00 [Normal decode]) | pcieport | No | No | +| Intel Corporation Raptor Lake-P [UHD Graphics] (rev 04) (prog-if 00 [VGA controller]) | i915 | i915 | No | +| Intel Corporation Raptor Lake Dynamic Platform and Thermal Framework Processor Participant | proc_thermal_pci | processor_thermal_device_pci | No | +| Intel Corporation Raptor Lake PCIe 4.0 Graphics Port (prog-if 00 [Normal decode]) | pcieport | No | No | +| Intel Corporation Raptor Lake-P Thunderbolt 4 PCI Express Root Port #0 (prog-if 00 [Normal decode]) | pcieport | No | No | +| Intel Corporation GNA Scoring Accelerator module | No | No | No | +| Intel Corporation Raptor Lake-P Thunderbolt 4 USB Controller (prog-if 30 [XHCI]) | xhci_hcd | xhci_pci | No | +| Intel Corporation Raptor Lake-P Thunderbolt 4 NHI #0 (prog-if 40 [USB4 Host Interface]) | thunderbolt | thunderbolt | No | +| Intel Corporation Raptor Lake-P Thunderbolt 4 NHI #1 (prog-if 40 [USB4 Host Interface]) | thunderbolt | thunderbolt | No | +| Intel Corporation Alder Lake PCH USB 3.2 xHCI Host Controller (rev 01) (prog-if 30 [XHCI]) | xhci_hcd | xhci_pci | No | +| Intel Corporation Alder Lake PCH Shared SRAM (rev 01) | No | No | No | +| Intel Corporation Raptor Lake PCH CNVi WiFi (rev 01) | iwlwifi | iwlwifi | No | +| Intel Corporation Alder Lake PCH Serial IO I2C Controller #0 (rev 01) | intel-lpss | intel_lpss_pci | No | +| Intel Corporation Alder Lake PCH HECI Controller (rev 01) | mei_me | mei_me | No | +| Intel Corporation Device 51b8 (rev 01) (prog-if 00 [Normal decode]) | pcieport | No | No | +| Intel Corporation Alder Lake-P PCH PCIe Root Port #6 (rev 01) (prog-if 00 [Normal decode]) | pcieport | No | No | +| Intel Corporation Raptor Lake LPC/eSPI Controller (rev 01) | No | No | No | +| Intel Corporation Raptor Lake-P/U/H cAVS (rev 01) (prog-if 80) | sof-audio-pci-intel-tgl | snd_hda_intel, snd_sof_pci_intel_tgl | No | +| Intel Corporation Alder Lake PCH-P SMBus Host Controller | i801_smbus | i2c_i801 | No | +| Intel Corporation Alder Lake-P PCH SPI Controller (rev 01) | intel-spi | spi_intel_pci | No | +| NVIDIA Corporation GA107GLM [RTX A1000 6GB Laptop GPU] (rev a1) | nvidia | nouveau, nvidia_drm, nvidia | No | +| SK hynix Platinum P41/PC801 NVMe Solid State Drive (prog-if 02 [NVM Express]) | nvme | nvme | No | +| Realtek Semiconductor Co., Ltd. RTS5261 PCI Express Card Reader (rev 01) | rtsx_pci | rtsx_pci | No | + + diff --git a/drivers/Cargo.lock b/drivers/Cargo.lock new file mode 100644 index 0000000000..e1130c6e7f --- /dev/null +++ b/drivers/Cargo.lock @@ -0,0 +1,1975 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ac97d" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "common", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox-scheme 0.8.2", + "redox_event", + "redox_syscall", + "spin 0.9.8", +] + +[[package]] +name = "acpi" +version = "6.0.1" +source = "git+https://github.com/jackpot51/acpi.git#444e039346b8dfc0b25ed868cf03e2033eee86c1" +dependencies = [ + "bit_field", + "bitflags 2.9.4", + "byteorder", + "log", + "pci_types", + "spinning_top", +] + +[[package]] +name = "acpid" +version = "0.1.0" +dependencies = [ + "acpi", + "amlserde", + "arrayvec", + "common", + "libredox", + "log", + "num-derive", + "num-traits", + "parking_lot 0.12.5", + "plain", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_event", + "redox_syscall", + "ron", + "rustc-hash", + "serde", + "thiserror", +] + +[[package]] +name = "ahcid" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "common", + "driver-block", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alxd" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "libredox", + "redox-daemon", + "redox-scheme 0.8.2", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "amlserde" +version = "0.0.1" +dependencies = [ + "acpi", + "serde", + "toml 0.7.8", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bcm2835-sdhcid" +version = "0.1.0" +dependencies = [ + "common", + "driver-block", + "fdt 0.2.0-alpha1", + "libredox", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "bgad" +version = "0.1.0" +dependencies = [ + "common", + "inputd", + "libredox", + "log", + "orbclient", + "pcid", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_syscall", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +dependencies = [ + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chashmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" +dependencies = [ + "owning_ref", + "parking_lot 0.4.8", +] + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "libredox", + "log", + "redox-log", + "redox_syscall", +] + +[[package]] +name = "console-draw" +version = "0.1.0" +dependencies = [ + "graphics-ipc", + "orbclient", + "ransid", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils 0.8.21", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "driver-block" +version = "0.1.0" +dependencies = [ + "executor", + "futures", + "libredox", + "log", + "partitionlib", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_syscall", +] + +[[package]] +name = "driver-graphics" +version = "0.1.0" +dependencies = [ + "common", + "graphics-ipc", + "inputd", + "libredox", + "log", + "redox-scheme 0.6.2", + "redox_syscall", +] + +[[package]] +name = "driver-network" +version = "0.1.0" +dependencies = [ + "libredox", + "redox-daemon", + "redox-scheme 0.4.0", + "redox_syscall", +] + +[[package]] +name = "e1000d" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "driver-network", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "executor" +version = "0.1.0" +dependencies = [ + "log", + "redox_event", + "slab", +] + +[[package]] +name = "fbbootlogd" +version = "0.1.0" +dependencies = [ + "console-draw", + "graphics-ipc", + "inputd", + "libredox", + "orbclient", + "ransid", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "fbcond" +version = "0.1.0" +dependencies = [ + "common", + "console-draw", + "graphics-ipc", + "inputd", + "libredox", + "log", + "orbclient", + "ransid", + "redox-daemon", + "redox-scheme 0.4.0", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "fdt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" + +[[package]] +name = "fdt" +version = "0.2.0-alpha1" +source = "git+https://github.com/repnop/fdt.git#059bb2383873f8001959456e36ec123228f67642" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gpt" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" +dependencies = [ + "bitflags 2.9.4", + "crc", + "log", + "uuid", +] + +[[package]] +name = "graphics-ipc" +version = "0.1.0" +dependencies = [ + "common", + "libredox", + "log", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hidreport" +version = "0.4.1" +source = "git+https://github.com/jackpot51/hidreport#1cf47ffcd30f1b18b636d699de8ed76a45a813e3" +dependencies = [ + "thiserror", +] + +[[package]] +name = "hwd" +version = "0.1.0" +dependencies = [ + "amlserde", + "common", + "fdt 0.1.5", + "log", + "redox-daemon", + "ron", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ided" +version = "0.1.0" +dependencies = [ + "common", + "driver-block", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "ihdad" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox-scheme 0.8.2", + "redox_event", + "redox_syscall", + "spin 0.9.8", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inputd" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "libredox", + "log", + "orbclient", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_syscall", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "ixgbed" +version = "1.0.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "driver-network", + "libredox", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.4", + "libc", + "redox_syscall", +] + +[[package]] +name = "lived" +version = "0.1.0" +dependencies = [ + "anyhow", + "driver-block", + "libredox", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + +[[package]] +name = "nvmed" +version = "0.1.0" +dependencies = [ + "arrayvec", + "bitflags 2.9.4", + "common", + "driver-block", + "executor", + "futures", + "libredox", + "log", + "parking_lot 0.12.5", + "partitionlib", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", + "smallvec 1.15.1", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "git+https://gitlab.redox-os.org/redox-os/orbclient.git#4ba79212632aedad156de15d8cf7fa779f0ad3c8" +dependencies = [ + "libc", + "libredox", + "sdl2", + "sdl2-sys", +] + +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "parking_lot" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" +dependencies = [ + "owning_ref", + "parking_lot_core 0.2.14", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot_core" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" +dependencies = [ + "libc", + "rand", + "smallvec 0.6.14", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "redox_syscall", + "smallvec 1.15.1", + "windows-link", +] + +[[package]] +name = "partitionlib" +version = "0.1.0" +dependencies = [ + "gpt", + "scroll", + "uuid", +] + +[[package]] +name = "pci_types" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4325c6aa3cca3373503b1527e75756f9fbfe5fd76be4b4c8a143ee47430b8e0" +dependencies = [ + "bit_field", + "bitflags 2.9.4", +] + +[[package]] +name = "pcid" +version = "0.1.0" +dependencies = [ + "bincode", + "common", + "fdt 0.1.5", + "libc", + "libredox", + "log", + "pci_types", + "pico-args", + "plain", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_syscall", + "serde", +] + +[[package]] +name = "pcid-spawner" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "log", + "pcid", + "pico-args", + "redox_syscall", + "serde", + "toml 0.5.11", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ps2d" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "common", + "inputd", + "libredox", + "log", + "orbclient", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "ransid" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e40bb0bbe3ec5efa241ed57fad611b2c1642f1c60550f647201acee2494553" +dependencies = [ + "log", + "vte", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox-daemon" +version = "0.1.3" +source = "git+https://gitlab.redox-os.org/redox-os/redox-daemon.git#31ab115cf17d6fe333515bfe19ac477352a1dcc0" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "redox-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81460b1526438123d16f0c968dbe42ba7f61e99645109b70e57864a8b66710fb" +dependencies = [ + "chrono", + "log", + "smallvec 1.15.1", + "termion", +] + +[[package]] +name = "redox-scheme" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28d292981c8f3338cb772b6024b08bcc597d0dd81020de17080a9a7b470ebb2" +dependencies = [ + "libredox", + "redox_syscall", +] + +[[package]] +name = "redox-scheme" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c00025a04f76fdcf72c15f10c7a12d9f2fdde93e539be9a57d5d632c4158a9e" +dependencies = [ + "libredox", + "redox_syscall", +] + +[[package]] +name = "redox-scheme" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54adf6001069dfc77e8e9f62dc62d4c9c1471fff65ca577ca140f0e9bdf4e10" +dependencies = [ + "libredox", + "redox_syscall", +] + +[[package]] +name = "redox_event" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69609faa5d5992247a4ef379917bb3e39be281405d6a0ccd4f942429400b956f" +dependencies = [ + "bitflags 2.9.4", + "libredox", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "redox_termios" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rehid" +version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/rehid.git#43fe46199b2948cac2d4adee5c4f381d6639ab24" +dependencies = [ + "hidreport", + "log", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.9.4", + "serde", + "serde_derive", +] + +[[package]] +name = "rtcd" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", +] + +[[package]] +name = "rtl8139d" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "driver-network", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "rtl8168d" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "driver-network", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "sb16d" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "libredox", + "log", + "redox-daemon", + "redox-scheme 0.8.2", + "redox_event", + "redox_syscall", + "spin 0.9.8", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sdl2" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "version-compare", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termion" +version = "4.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb" +dependencies = [ + "libc", + "libredox", + "numtoa", + "redox_termios", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "usbctl" +version = "0.1.0" +dependencies = [ + "clap", + "xhcid", +] + +[[package]] +name = "usbhidd" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "inputd", + "log", + "orbclient", + "redox_syscall", + "rehid", + "xhcid", +] + +[[package]] +name = "usbhubd" +version = "0.1.0" +dependencies = [ + "common", + "log", + "redox_syscall", + "xhcid", +] + +[[package]] +name = "usbscsid" +version = "0.1.0" +dependencies = [ + "base64 0.11.0", + "driver-block", + "libredox", + "plain", + "redox-daemon", + "redox_event", + "redox_syscall", + "thiserror", + "xhcid", +] + +[[package]] +name = "utf8parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vboxd" +version = "0.1.0" +dependencies = [ + "common", + "libredox", + "orbclient", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "vesad" +version = "0.1.0" +dependencies = [ + "common", + "driver-graphics", + "graphics-ipc", + "inputd", + "libredox", + "orbclient", + "ransid", + "redox-daemon", + "redox_event", + "redox_syscall", +] + +[[package]] +name = "virtio-blkd" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "driver-block", + "futures", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", + "spin 0.10.0", + "static_assertions", + "thiserror", + "virtio-core", +] + +[[package]] +name = "virtio-core" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.4", + "common", + "crossbeam-queue", + "futures", + "libredox", + "log", + "pcid", + "redox_event", + "redox_syscall", + "static_assertions", + "thiserror", +] + +[[package]] +name = "virtio-gpud" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "driver-graphics", + "futures", + "graphics-ipc", + "inputd", + "libredox", + "log", + "orbclient", + "pcid", + "redox-daemon", + "redox_event", + "redox_syscall", + "spin 0.9.8", + "static_assertions", + "virtio-core", +] + +[[package]] +name = "virtio-netd" +version = "0.1.0" +dependencies = [ + "common", + "driver-network", + "futures", + "libredox", + "log", + "pcid", + "redox-daemon", + "redox_syscall", + "static_assertions", + "virtio-core", +] + +[[package]] +name = "vte" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f42f536e22f7fcbb407639765c8fd78707a33109301f834a594758bedd6e8cf" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if 1.0.4", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.107", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "xhcid" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "chashmap", + "common", + "crossbeam-channel", + "futures", + "lazy_static", + "libredox", + "log", + "pcid", + "plain", + "redox-daemon", + "redox-scheme 0.6.2", + "redox_event", + "redox_syscall", + "regex", + "serde", + "serde_json", + "smallvec 1.15.1", + "thiserror", + "toml 0.5.11", +] + +[[patch.unused]] +name = "mio" +version = "0.6.14" +source = "git+https://gitlab.redox-os.org/redox-os/mio.git?branch=redox-unix#c9a70849ced97387e2607c9c466d23b130ec8901" diff --git a/drivers/Cargo.toml b/drivers/Cargo.toml new file mode 100644 index 0000000000..4a569bd1f5 --- /dev/null +++ b/drivers/Cargo.toml @@ -0,0 +1,59 @@ +[workspace] +members = [ + "common", + "executor", + + "acpid", + "hwd", + "pcid", + "pcid-spawner", + "rtcd", + "vboxd", + "inputd", + "virtio-core", + + "audio/ac97d", + "audio/ihdad", + "audio/sb16d", + + "graphics/bgad", + "graphics/console-draw", + "graphics/fbbootlogd", + "graphics/driver-graphics", + "graphics/fbcond", + "graphics/graphics-ipc", + "graphics/vesad", + "graphics/virtio-gpud", + + "input/ps2d", + "input/usbhidd", + + "net/alxd", + "net/driver-network", + "net/e1000d", + "net/ixgbed", + "net/rtl8139d", + "net/rtl8168d", + "net/virtio-netd", + + "storage/ahcid", + "storage/bcm2835-sdhcid", + "storage/driver-block", + "storage/ided", + "storage/lived", # TODO: not really a driver... + "storage/nvmed", + "storage/usbscsid", + "storage/virtio-blkd", + + "usb/xhcid", + "usb/usbctl", + "usb/usbhubd", +] + +[profile.release] +lto = "fat" + +[patch.crates-io] +mio = { git = "https://gitlab.redox-os.org/redox-os/mio.git", branch = "redox-unix" } +orbclient = { git = "https://gitlab.redox-os.org/redox-os/orbclient.git", version = "0.3.44" } +redox-daemon = { git = "https://gitlab.redox-os.org/redox-os/redox-daemon.git" } diff --git a/drivers/LICENSE b/drivers/LICENSE new file mode 100644 index 0000000000..5deeece4f4 --- /dev/null +++ b/drivers/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Redox OS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/drivers/README.md b/drivers/README.md new file mode 100644 index 0000000000..352100bbce --- /dev/null +++ b/drivers/README.md @@ -0,0 +1,99 @@ +# Drivers + +This document covers the driver details. + +## Hardware Interfaces and Devices + +- ac97d - Realtek audio chipsets +- acpid - ACPI interface +- ahcid - SATA interface +- alxd - Atheros ethernet +- amlserde - a library to provide serialization/deserialization of the AML symbol table from ACPI +- bgad - Bochs emulator and debugger +- block-io-wrapper - Library used by other drivers +- e1000d - Intel Gigabit ethernet +- ided - IDE interface +- ihdad - Intel HD Audio chipsets +- inputd - Multiplexes input from multiple input drivers and provides that to Orbital +- ixgbed - Intel 10 Gigabit ethernet +- nvmed - NVMe interface +- pcid - PCI interface with extensions for PCI Express +- ps2d - PS/2 interface +- rtl8139d - Realtek ethernet +- rtl8168d - Realtek ethernet +- sb16d - Sound Blaster audio +- usbctl - USB control +- usbhidd - USB HID +- usbscsid - USB SCSI +- vboxd - VirtualBox guest +- vesad - VESA interface +- virtio-blkd - VirtIO block device +- virtio-core - VirtIO core +- virtio-gpud - VirtIO GPU device +- virtio-netd - VirtIO Network device +- xhcid - xHCI USB controller + +Some drivers are work-in-progress and incomplete, read [this](https://gitlab.redox-os.org/redox-os/drivers/-/issues/41) tracking issue to verify. + +## System Interfaces + +This section cover the interfaces used by Redox drivers. + +### System Calls + +- `iopl` - syscall that sets the I/O privilege level. x86 has four privilege rings (0/1/2/3), of which the kernel runs in ring 0 and userspace in ring 3. IOPL can only be changed by the kernel, for obvious security reasons, and therefore the Redox kernel needs root to set it. It is unique for each process. Processes with IOPL=3 can access I/O ports, and the kernel can access them as well. + +### Schemes + +- `/scheme/memory/physical` - allows mapping physical memory frames to driver-accessible virtual memory pages, with various available memory types: + - `/scheme/memory/physical`: default memory type (currently writeback) + - `/scheme/memory/physical@wb` writeback cached memory + - `/scheme/memory/physical@uc`: uncacheable memory + - `/scheme/memory/physical@wc`: write-combining memory +- `/scheme/irq` - allows getting events from interrupts. It is used primarily by listening for its file descriptors using the `/scheme/event` scheme. + +## Contribution Details + +### Driver Design + +A device driver on Redox is an user-space daemon that use system calls and schemes to work. + +For operating systems with monolithic kernels, drivers use internal kernel APIs instead of common program APIs. + +If you want to port a driver from a monolithic OS to Redox you will need to rewrite the driver with reverse enginnering of the code logic, because the logic is adapted to internal kernel APIs (it's a hard task if the device is complex, datasheets are more easy). + +### Write a Driver + +Datasheets are preferable (much more easy depending on device complexity), when they are freely available. Be aware that datasheets are often provided under a [Non-Disclosure Agreement](https://en.wikipedia.org/wiki/Non-disclosure_agreement) from hardware vendors, which can affect the ability to create an MIT-licensed driver. + +If you don't have datasheets, we recommend you to do reverse-engineering of available C code on BSD drivers. + +### Libraries + +You should use the [redox-scheme](https://crates.io/crates/redox-scheme) and [redox_event](https://crates.io/crates/redox_event) crates to create your drivers, you can also read the [example driver](https://gitlab.redox-os.org/redox-os/exampled) or read the code of other drivers with the same type of your device. + +Before testing your changes, be aware of [this](https://doc.redox-os.org/book/coding-and-building.html#a-note-about-drivers). + +### References + +If you want to reverse enginner the existing drivers, you can access the BSD code using these links: + +- [FreeBSD drivers](https://github.com/freebsd/freebsd-src/tree/main/sys/dev) +- [NetBSD drivers](https://github.com/NetBSD/src/tree/trunk/sys/dev) +- [OpenBSD drivers](https://github.com/openbsd/src/tree/master/sys/dev) + +## How To Contribute + +To learn how to contribute to this system component you need to read the following document: + +- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) + +## Development + +To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages. + +### How To Build + +To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page. + +This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux. diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml new file mode 100644 index 0000000000..8b162993a5 --- /dev/null +++ b/drivers/acpid/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "acpid" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acpi = { git = "https://github.com/jackpot51/acpi.git" } +log = "0.4" +num-derive = "0.3" +num-traits = "0.2" +parking_lot = "0.12" +plain = "0.2.3" +redox-daemon = "0.1" +redox_syscall = "0.5.6" +redox_event = "0.4.1" +rustc-hash = "1.1.0" +thiserror = "1" +ron = "0.8.1" + +amlserde = { path = "../amlserde" } +common = { path = "../common" } +libredox = "0.1.3" +redox-scheme = "0.6.2" +arrayvec = "0.7.6" +serde = { version = "1.0.228", features = ["derive"] } diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs new file mode 100644 index 0000000000..fc156fff96 --- /dev/null +++ b/drivers/acpid/src/acpi.rs @@ -0,0 +1,852 @@ +use acpi::aml::object::{Object, WrappedObject}; +use rustc_hash::FxHashMap; +use std::convert::{TryFrom, TryInto}; +use std::error::Error; +use std::ops::Deref; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; +use std::{fmt, mem}; +use syscall::PAGE_SIZE; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use common::io::{Io, Pio}; + +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use thiserror::Error; + +use acpi::{ + aml::{namespace::AmlName, AmlError, Interpreter}, + platform::AcpiPlatform, + AcpiTables, +}; +use amlserde::aml_serde_name::aml_to_symbol; +use amlserde::{AmlSerde, AmlSerdeValue}; + +#[cfg(target_arch = "x86_64")] +pub mod dmar; +use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; + +/// The raw SDT header struct, as defined by the ACPI specification. +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct SdtHeader { + pub signature: [u8; 4], + pub length: u32, + pub revision: u8, + pub checksum: u8, + pub oem_id: [u8; 6], + pub oem_table_id: [u8; 8], + pub oem_revision: u32, + pub creator_id: u32, + pub creator_revision: u32, +} +unsafe impl plain::Plain for SdtHeader {} + +impl SdtHeader { + pub fn signature(&self) -> SdtSignature { + SdtSignature { + signature: self.signature, + oem_id: self.oem_id, + oem_table_id: self.oem_table_id, + } + } + pub fn length(&self) -> usize { + self.length + .try_into() + .expect("expected usize to be at least 32 bits") + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SdtSignature { + pub signature: [u8; 4], + pub oem_id: [u8; 6], + pub oem_table_id: [u8; 8], +} + +impl fmt::Display for SdtSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}-{}-{}", + String::from_utf8_lossy(&self.signature), + String::from_utf8_lossy(&self.oem_id), + String::from_utf8_lossy(&self.oem_table_id) + ) + } +} + +#[derive(Debug, Error)] +pub enum TablePhysLoadError { + // TODO: Make syscall::Error implement std::error::Error, when enabling a Cargo feature. + #[error("i/o error: {0}")] + Io(#[from] std::io::Error), + + #[error("invalid SDT: {0}")] + Validity(#[from] InvalidSdtError), +} +#[derive(Debug, Error)] +pub enum InvalidSdtError { + #[error("invalid size")] + InvalidSize, + + #[error("invalid checksum")] + BadChecksum, +} + +struct PhysmapGuard { + virt: *const u8, + size: usize, +} +impl PhysmapGuard { + fn map(page: usize, page_count: usize) -> std::io::Result { + let size = page_count * PAGE_SIZE; + let virt = unsafe { + common::physmap(page, size, common::Prot::RO, common::MemoryType::default()) + .map_err(|error| std::io::Error::from_raw_os_error(error.errno()))? + }; + + Ok(Self { + virt: virt as *const u8, + size, + }) + } +} +impl Deref for PhysmapGuard { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + unsafe { std::slice::from_raw_parts(self.virt as *const u8, self.size) } + } +} +impl Drop for PhysmapGuard { + fn drop(&mut self) { + unsafe { + let _ = libredox::call::munmap(self.virt as *mut (), self.size); + } + } +} + +#[derive(Clone)] +pub struct Sdt(Arc<[u8]>); + +impl Sdt { + pub fn new(slice: Arc<[u8]>) -> Result { + let header = match plain::from_bytes::(&slice) { + Ok(header) => header, + Err(plain::Error::TooShort) => return Err(InvalidSdtError::InvalidSize), + Err(plain::Error::BadAlignment) => panic!( + "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!" + ), + }; + + if header.length() != slice.len() { + return Err(InvalidSdtError::InvalidSize); + } + + let checksum = slice + .iter() + .copied() + .fold(0_u8, |current_sum, item| current_sum.wrapping_add(item)); + + if checksum != 0 { + return Err(InvalidSdtError::BadChecksum); + } + + Ok(Self(slice)) + } + pub fn load_from_physical(physaddr: usize) -> Result { + let physaddr_start_page = physaddr / PAGE_SIZE * PAGE_SIZE; + let physaddr_page_offset = physaddr % PAGE_SIZE; + + // Begin by reading and validating the header first. The SDT header is always 36 bytes + // long, and can thus span either one or two page table frames. + let needs_extra_page = (PAGE_SIZE - physaddr_page_offset) + .checked_sub(mem::size_of::()) + .is_none(); + let page_table_count = 1 + if needs_extra_page { 1 } else { 0 }; + + let pages = PhysmapGuard::map(physaddr_start_page, page_table_count)?; + assert!(pages.len() >= mem::size_of::()); + let sdt_mem = &pages[physaddr_page_offset..]; + + let sdt = plain::from_bytes::(&sdt_mem[..mem::size_of::()]) + .expect("either alignment is wrong, or the length is too short, both of which are already checked for"); + + let total_length = sdt.length(); + let base_length = std::cmp::min(total_length, sdt_mem.len()); + let extended_length = total_length - base_length; + + let mut loaded = sdt_mem[..base_length].to_owned(); + loaded.reserve(extended_length); + + const SIMULTANEOUS_PAGE_COUNT: usize = 4; + + let mut left = extended_length; + let mut offset = physaddr_start_page + page_table_count * PAGE_SIZE; + + let length_per_iteration = PAGE_SIZE * SIMULTANEOUS_PAGE_COUNT; + + while left > 0 { + let to_copy = std::cmp::min(left, length_per_iteration); + let additional_pages = PhysmapGuard::map(offset, to_copy.div_ceil(PAGE_SIZE))?; + + loaded.extend(&additional_pages[..to_copy]); + + left -= to_copy; + offset += to_copy; + } + assert_eq!(left, 0); + + Self::new(loaded.into()).map_err(Into::into) + } + pub fn as_slice(&self) -> &[u8] { + &self.0 + } +} + +impl Deref for Sdt { + type Target = SdtHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes::(&self.0) + .expect("expected already validated Sdt to be able to get its header") + } +} + +impl Sdt { + pub fn data(&self) -> &[u8] { + &self.0[mem::size_of::()..] + } +} + +impl fmt::Debug for Sdt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Sdt") + .field("header", &*self as &SdtHeader) + .field("extra_len", &self.data().len()) + .finish() + } +} + +pub struct Dsdt(Sdt); +pub struct Ssdt(Sdt); + +// Current AML implementation builds the aml_context.namespace at startup, +// but the cache for symbols is lazy-loaded when someone +// reads from the acpi:/symbols scheme. +// If you dynamically add an SDT, you can add to the namespace, but you +// must empty the cache so it is rebuilt. +// If you modify an SDT, you must discard the aml_context and rebuild it. +pub struct AmlSymbols { + aml_context: Option>, + // k = name, v = description + symbol_cache: FxHashMap, + page_cache: Arc>, +} + +impl AmlSymbols { + pub fn new() -> Self { + Self { + aml_context: None, + symbol_cache: FxHashMap::default(), + page_cache: Arc::new(Mutex::new(AmlPageCache::default())), + } + } + + pub fn init(&mut self) -> Result<(), Box> { + if self.aml_context.is_some() { + return Err("AML interpreter already initialized".into()); + } + let format_err = |err| format!("{:?}", err); + let handler = AmlPhysMemHandler::new(Arc::clone(&self.page_cache)); + //TODO: use these parsed tables for the rest of acpid + let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?; + let tables = unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? }; + let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?; + let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?; + self.aml_context = Some(interpreter); + Ok(()) + } + + pub fn aml_context_mut(&mut self) -> &mut Interpreter { + // PCID must be running by this time! + if self.aml_context.is_none() { + match self.init() { + Ok(()) => (), + Err(err) => { + log::error!("failed to initialize AML context: {}", err); + } + } + } + self.aml_context.as_mut().expect("AML context not initialized") + } + + pub fn symbols_cache(&self) -> &FxHashMap { + &self.symbol_cache + } + + pub fn lookup(&self, symbol: &str) -> Option { + if let Some(description) = self.symbol_cache.get(symbol) { + log::trace!("Found symbol in cache, {}, {}", symbol, description); + return Some(description.to_owned()); + } + None + } + + pub fn build_cache(&mut self) { + let aml_context = self.aml_context_mut(); + + let mut symbol_list: Vec<(AmlName, String)> = Vec::with_capacity(5000); + + if aml_context + .namespace + .lock() + .traverse(|level_aml_name, level| { + for (child_seg, handle) in level.values.iter() { + if let Ok(aml_name) = + AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) + { + let name = aml_to_symbol(&aml_name); + symbol_list.push((aml_name, name)); + } else { + log::error!( + "AmlName resolve failed, {:?}:{:?}", + level_aml_name, + child_seg + ); + } + } + Ok(true) + }) + .is_err() + { + log::error!("Namespace traverse failed"); + return; + } + + let mut symbol_cache: FxHashMap = FxHashMap::default(); + + for (aml_name, name) in &symbol_list { + // create an empty entry, in case something goes wrong with serialization + symbol_cache.insert(name.to_owned(), "".to_owned()); + if let Some(ser_value) = AmlSerde::from_aml(aml_context, aml_name) { + if let Ok(ser_string) = ron::ser::to_string_pretty(&ser_value, Default::default()) { + // replace the empty entry + symbol_cache.insert(name.to_owned(), ser_string); + } + } + } + + // Cache the new list + log::trace!("Updating symbols list"); + + self.symbol_cache = symbol_cache; + } +} + +#[derive(Debug, Error)] +pub enum AmlEvalError { + #[error("AML error")] + AmlError(AmlError), + #[error("Failed to serialize argument")] + SerializationError, + #[error("Failed to deserialize")] + DeserializationError, +} +impl From for AmlEvalError { + fn from(value: AmlError) -> Self { + AmlEvalError::AmlError(value) + } +} + +pub struct AcpiContext { + tables: Vec, + dsdt: Option, + fadt: Option, + + aml_symbols: RwLock, + + // TODO: The kernel ACPI code seemed to use load_table quite ubiquitously, however ACPI 5.1 + // states that DDBHandles can only be obtained when loading XSDT-pointed tables. So, we'll + // generate an index only for those. + sdt_order: RwLock>>, + + pub next_ctx: RwLock, +} + +impl AcpiContext { + pub fn aml_eval( + &self, + symbol: AmlName, + args: Vec, + ) -> Result { + let mut symbols = self.aml_symbols.write(); + let interpreter = symbols.aml_context_mut(); + interpreter.acquire_global_lock(16)?; + + let args = args + .into_iter() + .map(|aml_serde_value| { + aml_serde_value + .to_aml_object() + .map(Object::wrap) + .ok_or(AmlEvalError::DeserializationError) + }) + .collect::, AmlEvalError>>()?; + + let result = interpreter.evaluate(symbol, args); + interpreter + .release_global_lock() + .expect("Failed to release GIL!"); //TODO: check if this should panic + + result + .map_err(AmlEvalError::from) + .map(|object| { + AmlSerdeValue::from_aml_value(object.deref()) + .ok_or(AmlEvalError::SerializationError) + }) + .flatten() + } + + pub fn init(rxsdt_physaddrs: impl Iterator) -> Self { + let tables = rxsdt_physaddrs + .map(|physaddr| { + let physaddr: usize = physaddr + .try_into() + .expect("expected ACPI addresses to be compatible with the current word size"); + + log::trace!("TABLE AT {:#>08X}", physaddr); + + Sdt::load_from_physical(physaddr).expect("failed to load physical SDT") + }) + .collect::>(); + + let mut this = Self { + tables, + dsdt: None, + fadt: None, + + // Temporary values + aml_symbols: RwLock::new(AmlSymbols::new()), + + next_ctx: RwLock::new(0), + + sdt_order: RwLock::new(Vec::new()), + }; + + for table in &this.tables { + this.new_index(&table.signature()); + } + + Fadt::init(&mut this); + //TODO (hangs on real hardware): Dmar::init(&this); + + this + } + + pub fn dsdt(&self) -> Option<&Dsdt> { + self.dsdt.as_ref() + } + pub fn ssdts(&self) -> impl Iterator + '_ { + self.find_multiple_sdts(*b"SSDT") + .map(|sdt| Ssdt(sdt.clone())) + } + fn find_single_sdt_pos(&self, signature: [u8; 4]) -> Option { + let count = self + .tables + .iter() + .filter(|sdt| sdt.signature == signature) + .count(); + + if count > 1 { + log::warn!( + "Expected only a single SDT of signature `{}` ({:?}), but there were {}", + String::from_utf8_lossy(&signature), + signature, + count + ); + } + + self.tables + .iter() + .position(|sdt| sdt.signature == signature) + } + pub fn find_multiple_sdts<'a>(&'a self, signature: [u8; 4]) -> impl Iterator { + self.tables + .iter() + .filter(move |sdt| sdt.signature == signature) + } + pub fn take_single_sdt(&self, signature: [u8; 4]) -> Option { + self.find_single_sdt_pos(signature) + .map(|pos| self.tables[pos].clone()) + } + pub fn fadt(&self) -> Option<&Fadt> { + self.fadt.as_ref() + } + pub fn sdt_from_signature(&self, signature: &SdtSignature) -> Option<&Sdt> { + self.tables.iter().find(|sdt| { + sdt.signature == signature.signature + && sdt.oem_id == signature.oem_id + && sdt.oem_table_id == signature.oem_table_id + }) + } + pub fn get_signature_from_index(&self, index: usize) -> Option { + self.sdt_order.read().get(index).copied().flatten() + } + pub fn get_index_from_signature(&self, signature: &SdtSignature) -> Option { + self.sdt_order + .read() + .iter() + .rposition(|sig| sig.map_or(false, |sig| &sig == signature)) + } + pub fn tables(&self) -> &[Sdt] { + &self.tables + } + pub fn new_index(&self, signature: &SdtSignature) { + self.sdt_order.write().push(Some(*signature)); + } + + pub fn aml_lookup(&self, symbol: &str) -> Option { + if let Ok(aml_symbols) = self.aml_symbols() { + aml_symbols.lookup(symbol) + } else { + None + } + } + + pub fn aml_symbols(&self) -> Result, AmlError> { + // return the cached value if it exists + let symbols = self.aml_symbols.read(); + if !symbols.symbols_cache().is_empty() { + return Ok(symbols); + } + // free the read lock + drop(symbols); + + // List has not been initialized, we have to build it + log::trace!("Creating symbols list"); + + let mut aml_symbols = self.aml_symbols.write(); + + aml_symbols.build_cache(); + + // return the cached value + Ok(RwLockWriteGuard::downgrade(aml_symbols)) + } + + /// Discard any cached symbols list. To be called if the AML namespace changes. + pub fn aml_symbols_reset(&self) { + let mut aml_symbols = self.aml_symbols.write(); + aml_symbols.symbol_cache = FxHashMap::default(); + } + + /// Set Power State + /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf + /// - search for PM1a + /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details + pub fn set_global_s_state(&self, state: u8) { + if state != 5 { + return; + } + let fadt = match self.fadt() { + Some(fadt) => fadt, + None => { + log::error!("Cannot set global S-state due to missing FADT."); + return; + } + }; + + let port = fadt.pm1a_control_block as u16; + let mut val = 1 << 13; + + let aml_symbols = self.aml_symbols.read(); + + let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { + Ok(aml_name) => aml_name, + Err(error) => { + log::error!("Could not build AmlName for \\_S5, {:?}", error); + return; + } + }; + + let s5 = match &aml_symbols.aml_context { + Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { + Ok(s5) => s5, + Err(error) => { + log::error!("Cannot set S-state, missing \\_S5, {:?}", error); + return; + } + }, + None => { + log::error!("Cannot set S-state, AML context not initialized"); + return; + } + }; + + let package = match s5.deref() { + acpi::aml::object::Object::Package(package) => package, + _ => { + log::error!("Cannot set S-state, \\_S5 is not a package"); + return; + } + }; + + let slp_typa = match package[0].deref() { + acpi::aml::object::Object::Integer(i) => i.to_owned(), + _ => { + log::error!("typa is not an Integer"); + return; + } + }; + let slp_typb = match package[1].deref() { + acpi::aml::object::Object::Integer(i) => i.to_owned(), + _ => { + log::error!("typb is not an Integer"); + return; + } + }; + + log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); + val |= slp_typa as u16; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); + Pio::::new(port).write(val); + } + + // TODO: Handle SLP_TYPb + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + { + log::error!( + "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", + port, + val + ); + } + + loop { + core::hint::spin_loop(); + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct FadtStruct { + pub header: SdtHeader, + pub firmware_ctrl: u32, + pub dsdt: u32, + + // field used in ACPI 1.0; no longer in use, for compatibility only + reserved: u8, + + pub preferred_power_managament: u8, + pub sci_interrupt: u16, + pub smi_command_port: u32, + pub acpi_enable: u8, + pub acpi_disable: u8, + pub s4_bios_req: u8, + pub pstate_control: u8, + pub pm1a_event_block: u32, + pub pm1b_event_block: u32, + pub pm1a_control_block: u32, + pub pm1b_control_block: u32, + pub pm2_control_block: u32, + pub pm_timer_block: u32, + pub gpe0_block: u32, + pub gpe1_block: u32, + pub pm1_event_length: u8, + pub pm1_control_length: u8, + pub pm2_control_length: u8, + pub pm_timer_length: u8, + pub gpe0_ength: u8, + pub gpe1_length: u8, + pub gpe1_base: u8, + pub c_state_control: u8, + pub worst_c2_latency: u16, + pub worst_c3_latency: u16, + pub flush_size: u16, + pub flush_stride: u16, + pub duty_offset: u8, + pub duty_width: u8, + pub day_alarm: u8, + pub month_alarm: u8, + pub century: u8, + + // reserved in ACPI 1.0; used since ACPI 2.0+ + pub boot_architecture_flags: u16, + + reserved2: u8, + pub flags: u32, +} +unsafe impl plain::Plain for FadtStruct {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct GenericAddressStructure { + address_space: u8, + bit_width: u8, + bit_offset: u8, + access_size: u8, + address: u64, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct FadtAcpi2Struct { + // 12 byte structure; see below for details + pub reset_reg: GenericAddressStructure, + + pub reset_value: u8, + reserved3: [u8; 3], + + // 64bit pointers - Available on ACPI 2.0+ + pub x_firmware_control: u64, + pub x_dsdt: u64, + + pub x_pm1a_event_block: GenericAddressStructure, + pub x_pm1b_event_block: GenericAddressStructure, + pub x_pm1a_control_block: GenericAddressStructure, + pub x_pm1b_control_block: GenericAddressStructure, + pub x_pm2_control_block: GenericAddressStructure, + pub x_pm_timer_block: GenericAddressStructure, + pub x_gpe0_block: GenericAddressStructure, + pub x_gpe1_block: GenericAddressStructure, +} +unsafe impl plain::Plain for FadtAcpi2Struct {} + +#[derive(Clone)] +pub struct Fadt(Sdt); + +impl Fadt { + pub fn acpi_2_struct(&self) -> Option<&FadtAcpi2Struct> { + let bytes = &self.0 .0[mem::size_of::()..]; + + match plain::from_bytes::(bytes) { + Ok(fadt2) => Some(fadt2), + Err(plain::Error::TooShort) => None, + Err(plain::Error::BadAlignment) => unreachable!( + "plain::from_bytes reported bad alignment, but FadtAcpi2Struct is #[repr(packed)]" + ), + } + } +} + +impl Deref for Fadt { + type Target = FadtStruct; + + fn deref(&self) -> &Self::Target { + plain::from_bytes::(&self.0 .0) + .expect("expected FADT struct to already be validated in Deref impl") + } +} + +impl Fadt { + pub fn new(sdt: Sdt) -> Option { + if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::() { + return None; + } + Some(Fadt(sdt)) + } + + pub fn init(context: &mut AcpiContext) { + let fadt_sdt = context + .take_single_sdt(*b"FACP") + .expect("expected ACPI to always have a FADT"); + + let fadt = match Fadt::new(fadt_sdt) { + Some(fadt) => fadt, + None => { + log::error!("Failed to find FADT"); + return; + } + }; + + let dsdt_ptr = match fadt.acpi_2_struct() { + Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| { + usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize") + }), + None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), + }; + + log::debug!("FACP at {:X}", { dsdt_ptr }); + + let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { + Ok(dsdt) => dsdt, + Err(error) => { + log::error!("Failed to load DSDT: {}", error); + return; + } + }; + + context.fadt = Some(fadt.clone()); + context.dsdt = Some(Dsdt(dsdt_sdt.clone())); + + context.tables.push(dsdt_sdt); + } +} + +pub enum PossibleAmlTables { + Dsdt(Dsdt), + Ssdt(Ssdt), +} +impl PossibleAmlTables { + pub fn try_new(inner: Sdt) -> Option { + match &inner.signature { + b"DSDT" => Some(Self::Dsdt(Dsdt(inner))), + b"SSDT" => Some(Self::Ssdt(Ssdt(inner))), + _ => None, + } + } +} +impl AmlContainingTable for PossibleAmlTables { + fn aml(&self) -> &[u8] { + match self { + Self::Dsdt(dsdt) => dsdt.aml(), + Self::Ssdt(ssdt) => ssdt.aml(), + } + } + fn header(&self) -> &SdtHeader { + match self { + Self::Dsdt(dsdt) => dsdt.header(), + Self::Ssdt(ssdt) => ssdt.header(), + } + } +} + +pub trait AmlContainingTable { + fn aml(&self) -> &[u8]; + fn header(&self) -> &SdtHeader; +} + +impl AmlContainingTable for &T +where + T: AmlContainingTable, +{ + fn aml(&self) -> &[u8] { + T::aml(*self) + } + fn header(&self) -> &SdtHeader { + T::header(*self) + } +} + +impl AmlContainingTable for Dsdt { + fn aml(&self) -> &[u8] { + self.0.data() + } + fn header(&self) -> &SdtHeader { + &*self.0 + } +} +impl AmlContainingTable for Ssdt { + fn aml(&self) -> &[u8] { + self.0.data() + } + fn header(&self) -> &SdtHeader { + &*self.0 + } +} diff --git a/drivers/acpid/src/acpi/dmar/drhd.rs b/drivers/acpid/src/acpi/dmar/drhd.rs new file mode 100644 index 0000000000..f33f3bf6e2 --- /dev/null +++ b/drivers/acpid/src/acpi/dmar/drhd.rs @@ -0,0 +1,128 @@ +use std::ops::{Deref, DerefMut}; + +use common::io::Mmio; + +// TODO: Only wrap with Mmio where there are hardware-registers. (Some of these structs seem to be +// ring buffer entries, which are not to be treated the same way). + +pub struct DrhdPage { + virt: *mut Drhd, +} +impl DrhdPage { + pub fn map(base_phys: usize) -> syscall::Result { + assert_eq!( + base_phys % crate::acpi::PAGE_SIZE, + 0, + "DRHD registers must be page-aligned" + ); + + // TODO: Uncachable? Can reads have side-effects? + let virt = unsafe { + common::physmap( + base_phys, + crate::acpi::PAGE_SIZE, + common::Prot::RO, + common::MemoryType::default(), + )? + } as *mut Drhd; + + Ok(Self { virt }) + } +} +impl Deref for DrhdPage { + type Target = Drhd; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.virt } + } +} +impl DerefMut for DrhdPage { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.virt } + } +} +impl Drop for DrhdPage { + fn drop(&mut self) { + unsafe { + let _ = libredox::call::munmap(self.virt.cast(), crate::acpi::PAGE_SIZE); + } + } +} + +#[repr(C, packed)] +pub struct DrhdFault { + pub sts: Mmio, + pub ctrl: Mmio, + pub data: Mmio, + pub addr: [Mmio; 2], + _rsv: [Mmio; 2], + pub log: Mmio, +} + +#[repr(C, packed)] +pub struct DrhdProtectedMemory { + pub en: Mmio, + pub low_base: Mmio, + pub low_limit: Mmio, + pub high_base: Mmio, + pub high_limit: Mmio, +} + +#[repr(C, packed)] +pub struct DrhdInvalidation { + pub queue_head: Mmio, + pub queue_tail: Mmio, + pub queue_addr: Mmio, + _rsv: Mmio, + pub cmpl_sts: Mmio, + pub cmpl_ctrl: Mmio, + pub cmpl_data: Mmio, + pub cmpl_addr: [Mmio; 2], +} + +#[repr(C, packed)] +pub struct DrhdPageRequest { + pub queue_head: Mmio, + pub queue_tail: Mmio, + pub queue_addr: Mmio, + _rsv: Mmio, + pub sts: Mmio, + pub ctrl: Mmio, + pub data: Mmio, + pub addr: [Mmio; 2], +} + +#[repr(C, packed)] +pub struct DrhdMtrrVariable { + pub base: Mmio, + pub mask: Mmio, +} + +#[repr(C, packed)] +pub struct DrhdMtrr { + pub cap: Mmio, + pub def_type: Mmio, + pub fixed: [Mmio; 11], + pub variable: [DrhdMtrrVariable; 10], +} + +#[repr(C, packed)] +pub struct Drhd { + pub version: Mmio, + _rsv: Mmio, + pub cap: Mmio, + pub ext_cap: Mmio, + pub gl_cmd: Mmio, + pub gl_sts: Mmio, + pub root_table: Mmio, + pub ctx_cmd: Mmio, + _rsv1: Mmio, + pub fault: DrhdFault, + _rsv2: Mmio, + pub pm: DrhdProtectedMemory, + pub invl: DrhdInvalidation, + _rsv3: Mmio, + pub intr_table: Mmio, + pub page_req: DrhdPageRequest, + pub mtrr: DrhdMtrr, +} diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs new file mode 100644 index 0000000000..c42b379a10 --- /dev/null +++ b/drivers/acpid/src/acpi/dmar/mod.rs @@ -0,0 +1,528 @@ +//! DMA Remapping Table -- `DMAR`. This is Intel's implementation of IOMMU functionality, known as +//! VT-d. +//! +//! Too understand what all of these structs mean, refer to the "Intel(R) Virtualization +//! Technology for Directed I/O" specification. + +// TODO: Move this code to a separate driver as well? + +use std::convert::TryFrom; +use std::ops::Deref; +use std::{fmt, mem}; + +use common::io::Io as _; + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +use self::drhd::DrhdPage; +use crate::acpi::{AcpiContext, Sdt, SdtHeader}; + +pub mod drhd; + +#[repr(C, packed)] +pub struct DmarStruct { + pub sdt_header: SdtHeader, + pub host_addr_width: u8, + pub flags: u8, + pub _rsvd: [u8; 10], + // This header is followed by N remapping structures. +} +unsafe impl plain::Plain for DmarStruct {} + +/// The DMA Remapping Table +#[derive(Debug)] +pub struct Dmar(Sdt); + +impl Dmar { + fn remmapping_structs_area(&self) -> &[u8] { + &self.0.as_slice()[mem::size_of::()..] + } +} + +impl Deref for Dmar { + type Target = DmarStruct; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(self.0.as_slice()) + .expect("expected Dmar struct to already have checked the length, and alignment issues should be impossible due to #[repr(packed)]") + } +} + +impl Dmar { + // TODO: Again, perhaps put this code into a different driver, and read the table the regular + // way via the acpi scheme? + pub fn init(acpi_ctx: &AcpiContext) { + let dmar_sdt = match acpi_ctx.take_single_sdt(*b"DMAR") { + Some(dmar_sdt) => dmar_sdt, + None => { + log::warn!("Unable to find `DMAR` ACPI table."); + return; + } + }; + let dmar = match Dmar::new(dmar_sdt) { + Some(dmar) => dmar, + None => { + log::error!("Failed to parse DMAR table, possibly malformed."); + return; + } + }; + + log::info!("Found DMAR: {}: {}", dmar.host_addr_width, dmar.flags); + log::debug!("DMAR: {:?}", dmar); + + for dmar_entry in dmar.iter() { + log::debug!("DMAR entry: {:?}", dmar_entry); + match dmar_entry { + DmarEntry::Drhd(dmar_drhd) => { + let drhd = dmar_drhd.map(); + + log::debug!("VER: {:X}", drhd.version.read()); + log::debug!("CAP: {:X}", drhd.cap.read()); + log::debug!("EXT_CAP: {:X}", drhd.ext_cap.read()); + log::debug!("GCMD: {:X}", drhd.gl_cmd.read()); + log::debug!("GSTS: {:X}", drhd.gl_sts.read()); + log::debug!("RT: {:X}", drhd.root_table.read()); + } + _ => (), + } + } + } + + fn new(sdt: Sdt) -> Option { + assert_eq!( + sdt.signature, *b"DMAR", + "signature already checked against `DMAR`" + ); + if sdt.length() < mem::size_of::() { + log::error!( + "The DMAR table was too small ({} B < {} B).", + sdt.length(), + mem::size_of::() + ); + return None; + } + // No need to check alignment for #[repr(packed)] structs. + + Some(Dmar(sdt)) + } + + pub fn iter(&self) -> DmarIter<'_> { + DmarIter(DmarRawIter { + bytes: self.remmapping_structs_area(), + }) + } +} + +/// DMAR DMA Remapping Hardware Unit Definition +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DmarDrhdHeader { + pub kind: u16, + pub length: u16, + + pub flags: u8, + pub _rsv: u8, + pub segment: u16, + pub base: u64, +} +unsafe impl plain::Plain for DmarDrhdHeader {} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DeviceScopeHeader { + pub ty: u8, + pub len: u8, + pub _rsvd: u16, + pub enumeration_id: u8, + pub start_bus_num: u8, + // The variable-sized path comes after. +} +unsafe impl plain::Plain for DeviceScopeHeader {} + +pub struct DeviceScope(Box<[u8]>); + +impl DeviceScope { + pub fn try_new(raw: &[u8]) -> Option { + // TODO: Check ty. + + let header_bytes = match raw.get(..mem::size_of::()) { + Some(bytes) => bytes, + None => return None, + }; + let header = plain::from_bytes::(header_bytes) + .expect("length already checked, and alignment 1 (#[repr(packed)] should suffice"); + + let len = usize::from(header.len); + + if len > raw.len() { + log::warn!("Device scope smaller than len field."); + return None; + } + + Some(Self(raw.into())) + } +} + +impl fmt::Debug for DeviceScope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DeviceScope") + .field("header", &*self as &DeviceScopeHeader) + .field("path", &self.path()) + .finish() + } +} + +impl Deref for DeviceScope { + type Target = DeviceScopeHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0) + .expect("expected length to be sufficient, and alignment (due to #[repr(packed)]") + } +} +impl DeviceScope { + pub fn path(&self) -> &[u8] { + &self.0[mem::size_of::()..] + } +} + +pub struct DmarDrhd(Box<[u8]>); + +impl DmarDrhd { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } + pub fn device_scope_area(&self) -> &[u8] { + &self.0[mem::size_of::()..] + } + pub fn map(&self) -> DrhdPage { + let base = usize::try_from(self.base).expect("expected u64 to fit within usize"); + + DrhdPage::map(base).expect("failed to map DRHD registers") + } +} +impl Deref for DmarDrhd { + type Target = DmarDrhdHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes::(&self.0[..mem::size_of::()]) + .expect("length is already checked, and alignment 1 (#[repr(packed)] should suffice") + } +} +impl fmt::Debug for DmarDrhd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarDrhd") + .field("header", &*self as &DmarDrhd) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR Reserved Memory Region Reporting +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DmarRmrrHeader { + pub kind: u16, + pub length: u16, + pub _rsv: u16, + pub segment: u16, + pub base: u64, + pub limit: u64, + // The device scopes come after. +} +unsafe impl plain::Plain for DmarRmrrHeader {} + +pub struct DmarRmrr(Box<[u8]>); + +impl DmarRmrr { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} +impl Deref for DmarRmrr { + type Target = DmarRmrrHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarRmrr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarRmrr") + .field("header", &*self as &DmarRmrrHeader) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR Root Port ATS Capability Reporting +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DmarAtsrHeader { + kind: u16, + length: u16, + flags: u8, + _rsv: u8, + segment: u16, + // The device scopes come after. +} +unsafe impl plain::Plain for DmarAtsrHeader {} + +pub struct DmarAtsr(Box<[u8]>); + +impl DmarAtsr { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} +impl Deref for DmarAtsr { + type Target = DmarAtsrHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarAtsr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarAtsr") + .field("header", &*self as &DmarAtsrHeader) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR Remapping Hardware Static Affinity +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DmarRhsa { + pub kind: u16, + pub length: u16, + + pub _rsv: u32, + pub base: u64, + pub domain: u32, +} +unsafe impl plain::Plain for DmarRhsa {} +impl DmarRhsa { + pub fn try_new(raw: &[u8]) -> Option { + let bytes = raw.get(..mem::size_of::())?; + + let this = plain::from_bytes(bytes) + .expect("length is already checked, and alignment 1 should suffice (#[repr(packed)])"); + + Some(*this) + } +} + +/// DMAR ACPI Name-space Device Declaration +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DmarAnddHeader { + pub kind: u16, + pub length: u16, + + pub _rsv: [u8; 3], + pub acpi_dev: u8, + // The device scopes come after. +} +unsafe impl plain::Plain for DmarAnddHeader {} + +pub struct DmarAndd(Box<[u8]>); + +impl DmarAndd { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} +impl Deref for DmarAndd { + type Target = DmarAnddHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarAndd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarAndd") + .field("header", &*self as &DmarAnddHeader) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR ACPI Name-space Device Declaration +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct DmarSatcHeader { + pub kind: u16, + pub length: u16, + + pub flags: u8, + pub _rsvd: u8, + pub seg_num: u16, + // The device scopes come after. +} +unsafe impl plain::Plain for DmarSatcHeader {} + +pub struct DmarSatc(Box<[u8]>); + +impl DmarSatc { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} + +impl Deref for DmarSatc { + type Target = DmarSatcHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarSatc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarSatc") + .field("header", &*self as &DmarSatcHeader) + // TODO: print out device scopes + .finish() + } +} + +/// The list of different "Remapping Structure Types". +/// +/// Refer to section 8.2 in the VTIO spec (as of revision 3.2). +#[derive(Clone, Copy, Debug, FromPrimitive)] +#[repr(u16)] +pub enum EntryType { + Drhd = 0, + Rmrr = 1, + Atsr = 2, + Rhsa = 3, + Andd = 4, + Satc = 5, +} + +/// DMAR Entries +#[derive(Debug)] +pub enum DmarEntry { + Drhd(DmarDrhd), + Rmrr(DmarRmrr), + Atsr(DmarAtsr), + Rhsa(DmarRhsa), + Andd(DmarAndd), + + // TODO: "SoC Integrated Address Translation Cache Reporting Structure". + Satc(DmarSatc), + + TooShort(EntryType), + Unknown(u16), +} + +struct DmarRawIter<'sdt> { + bytes: &'sdt [u8], +} + +impl<'sdt> Iterator for DmarRawIter<'sdt> { + type Item = (u16, &'sdt [u8]); + + fn next(&mut self) -> Option { + let type_bytes = match self.bytes.get(..2) { + Some(bytes) => bytes, + None => { + if !self.bytes.is_empty() { + log::warn!("DMAR table ended between two entries."); + } + return None; + } + }; + let len_bytes = match self.bytes.get(2..4) { + Some(bytes) => bytes, + None => { + log::warn!("DMAR table ended between two entries."); + return None; + } + }; + let remainder = &self.bytes[4..]; + + let type_bytes = <[u8; 2]>::try_from(type_bytes) + .expect("expected a 2-byte slice to be convertible to [u8; 2]"); + let len_bytes = <[u8; 2]>::try_from(type_bytes) + .expect("expected a 2-byte slice to be convertible to [u8; 2]"); + + let ty = u16::from_ne_bytes(type_bytes); + let len = u16::from_ne_bytes(len_bytes); + + let len = usize::try_from(len).expect("expected u16 to fit within usize"); + + if len > remainder.len() { + log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); + return None; + } + + let (current, residue) = self.bytes.split_at(len); + self.bytes = residue; + + Some((ty, current)) + } +} + +pub struct DmarIter<'sdt>(DmarRawIter<'sdt>); + +impl Iterator for DmarIter<'_> { + type Item = DmarEntry; + fn next(&mut self) -> Option { + let (raw_type, raw) = self.0.next()?; + + // NOTE: If any of these entries look incorrect, we should simply continue the iterator, + // and instead print a warning. + + let entry_type = match EntryType::from_u16(raw_type) { + Some(ty) => ty, + None => { + log::warn!( + "Encountered invalid entry type {} (length {})", + raw_type, + raw.len() + ); + return Some(DmarEntry::Unknown(raw_type)); + } + }; + + let item_opt = match entry_type { + EntryType::Drhd => DmarDrhd::try_new(raw).map(DmarEntry::Drhd), + EntryType::Rmrr => DmarRmrr::try_new(raw).map(DmarEntry::Rmrr), + EntryType::Atsr => DmarAtsr::try_new(raw).map(DmarEntry::Atsr), + EntryType::Rhsa => DmarRhsa::try_new(raw).map(DmarEntry::Rhsa), + EntryType::Andd => DmarAndd::try_new(raw).map(DmarEntry::Andd), + EntryType::Satc => DmarSatc::try_new(raw).map(DmarEntry::Satc), + }; + let item = item_opt.unwrap_or(DmarEntry::TooShort(entry_type)); + + Some(item) + } +} diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs new file mode 100644 index 0000000000..631c6f7583 --- /dev/null +++ b/drivers/acpid/src/aml_physmem.rs @@ -0,0 +1,432 @@ +use acpi::{aml::AmlError, Handle, PciAddress, PhysicalMapping}; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use common::io::{Io, Pio}; +use num_traits::PrimInt; +use rustc_hash::FxHashMap; +use std::fmt::LowerHex; +use std::mem::size_of; +use std::ptr::NonNull; +use std::sync::{Arc, Mutex}; +use syscall::PAGE_SIZE; + +const PAGE_MASK: usize = !(PAGE_SIZE - 1); +const OFFSET_MASK: usize = PAGE_SIZE - 1; + +struct MappedPage { + phys_page: usize, + virt_page: usize, +} + +impl MappedPage { + fn new(phys_page: usize) -> std::io::Result { + let virt_page = unsafe { + common::physmap( + phys_page, + PAGE_SIZE, + common::Prot::RW, + common::MemoryType::default(), + ) + .map_err(|error| std::io::Error::from_raw_os_error(error.errno()))? + } as usize; + Ok(Self { + phys_page, + virt_page, + }) + } +} + +impl Drop for MappedPage { + fn drop(&mut self) { + log::trace!("Drop page {:#x}", self.phys_page); + if let Err(e) = unsafe { libredox::call::munmap(self.virt_page as *mut (), PAGE_SIZE) } { + log::error!("funmap (phys): {:?}", e); + } + } +} + +#[derive(Default)] +pub struct AmlPageCache { + page_cache: FxHashMap, +} + +impl AmlPageCache { + /// get a virtual address for the given physical page + fn get_page(&mut self, phys_target: usize) -> std::io::Result<&MappedPage> { + let phys_page = phys_target & PAGE_MASK; + if self.page_cache.contains_key(&phys_page) { + log::trace!("re-using cached page {:#x}", phys_page); + + Ok(self + .page_cache + .get(&phys_page) + .expect("could not get page after contains=true")) + } else { + let mapped_page = MappedPage::new(phys_page)?; + log::trace!("adding page {:#x} to cache", mapped_page.phys_page); + self.page_cache.insert(phys_page, mapped_page); + Ok(self + .page_cache + .get(&phys_page) + .expect("can't find page that was just inserted")) + } + } + + /// The offset into the virtual slice of T that matches the physical target + fn sized_index(phys_target: usize) -> usize { + assert_eq!( + phys_target & !(size_of::() - 1), + phys_target, + "address {} is not aligned", + phys_target + ); + (phys_target & OFFSET_MASK) / size_of::() + } + /// Read from the given physical address + fn read_from_phys(&mut self, phys_target: usize) -> std::io::Result { + let mapped_page = self.get_page(phys_target)?; + let page_as_slice = unsafe { + std::slice::from_raw_parts( + mapped_page.virt_page as *const T, + PAGE_SIZE / size_of::(), + ) + }; + // for debugging only + let _virt_ptr = page_as_slice[Self::sized_index::(phys_target)..].as_ptr() as usize; + + let val = page_as_slice[Self::sized_index::(phys_target)]; + + log::trace!( + "read {:#x}, virt {:#x}, val {:#x}", + phys_target, + _virt_ptr, + val + ); + Ok(val) + } + + /// Write to the given physical address + fn write_to_phys( + &mut self, + phys_target: usize, + val: T, + ) -> std::io::Result<()> { + let mapped_page = self.get_page(phys_target)?; + let page_as_slice = unsafe { + std::slice::from_raw_parts_mut( + mapped_page.virt_page as *mut T, + PAGE_SIZE / size_of::(), + ) + }; + // for debugging only + let _virt_ptr = page_as_slice[Self::sized_index::(phys_target)..].as_ptr() as usize; + + page_as_slice[Self::sized_index::(phys_target)] = val; + + log::trace!( + "write {:#x}, virt {:#x}, val {:#x}", + phys_target, + _virt_ptr, + val + ); + Ok(()) + } + + pub fn clear(&mut self) { + log::trace!("Clear page cache"); + self.page_cache.clear(); + } +} + +#[derive(Clone)] +pub struct AmlPhysMemHandler { + page_cache: Arc>, + pci_fd: Arc>, +} + +/// Read from a physical address. +/// Generic parameter must be u8, u16, u32 or u64. +impl AmlPhysMemHandler { + pub fn new(page_cache: Arc>) -> Self { + //TODO: have PCID send a socket? + let pci_fd = Arc::new( + match libredox::Fd::open( + "/scheme/pci/access", + libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, + 0 + ) { + Ok(fd) => Some(fd), + Err(err) => { + log::error!("failed to open /scheme/pci/access: {}", err); + None + } + } + ); + Self { page_cache, pci_fd } + } + + fn pci_call_metadata(kind: u8, addr: PciAddress, off: u16) -> [u64; 2] { + // Segment: u16, at 28 bits + // Bus: u8, 8 bits, 256 total, at 20 bits + // Device: u8, 5 bits, 32 total, at 15 bits + // Function: u8, 3 bits, 8 total, at 12 bits + // Offset: u16, 12 bits, 4096 total, at 0 bits + [ + kind.into(), + (u64::from(addr.segment()) << 28) | + (u64::from(addr.bus()) << 20) | + (u64::from(addr.device()) << 15) | + (u64::from(addr.function()) << 12) | + u64::from(off) + ] + } + + fn read_pci(&self, addr: PciAddress, off: u16, value: &mut [u8]) { + let metadata = Self::pci_call_metadata(1, addr, off); + match &*self.pci_fd { + Some(pci_fd) => match pci_fd.call_ro(value, syscall::CallFlags::empty(), &metadata) { + Ok(_) => {}, + Err(err) => { + log::error!("read pci {addr}@{off:04X}:{:02X}: {}", value.len(), err); + } + }, + None => { + log::error!("read pci {addr}@{off:04X}:{:02X}: pci access not available", value.len()); + } + } + } + + fn write_pci(&self, addr: PciAddress, off: u16, value: &[u8]) { + let metadata = Self::pci_call_metadata(2, addr, off); + match &*self.pci_fd { + Some(pci_fd) => match pci_fd.call_wo(value, syscall::CallFlags::empty(), &metadata) { + Ok(_) => {}, + Err(err) => { + log::error!("write pci {addr}@{off:04X}={value:02X?}: {}", err); + } + } + None => { + log::error!("write pci {addr}@{off:04X}={value:02X?}: pci access not available"); + } + } + } +} + +impl acpi::Handler for AmlPhysMemHandler { + unsafe fn map_physical_region(&self, phys: usize, size: usize) -> PhysicalMapping { + let phys_page = phys & PAGE_MASK; + let offset = phys & OFFSET_MASK; + let pages = (offset + size + PAGE_SIZE - 1) / PAGE_SIZE; + let map_size = pages * PAGE_SIZE; + let virt_page = common::physmap( + phys_page, + map_size, + common::Prot::RW, + common::MemoryType::default(), + ) + .expect("failed to map physical region") as usize; + PhysicalMapping { + physical_start: phys, + virtual_start: NonNull::new((virt_page + offset) as *mut T).unwrap(), + region_length: size, + mapped_length: map_size, + handler: self.clone(), + } + } + fn unmap_physical_region(region: &PhysicalMapping) { + let virt_page = region.virtual_start.addr().get() & PAGE_MASK; + unsafe { + libredox::call::munmap(virt_page as *mut (), region.mapped_length) + .expect("failed to unmap physical region") + } + } + + fn read_u8(&self, address: usize) -> u8 { + log::trace!("read u8 {:X}", address); + if let Ok(mut page_cache) = self.page_cache.lock() { + if let Ok(value) = page_cache.read_from_phys::(address) { + return value; + } + } + log::error!("failed to read u8 {:#x}", address); + 0 + } + fn read_u16(&self, address: usize) -> u16 { + log::trace!("read u16 {:X}", address); + if let Ok(mut page_cache) = self.page_cache.lock() { + if let Ok(value) = page_cache.read_from_phys::(address) { + return value; + } + } + log::error!("failed to read u16 {:#x}", address); + 0 + } + fn read_u32(&self, address: usize) -> u32 { + log::trace!("read u32 {:X}", address); + if let Ok(mut page_cache) = self.page_cache.lock() { + if let Ok(value) = page_cache.read_from_phys::(address) { + return value; + } + } + log::error!("failed to read u32 {:#x}", address); + 0 + } + fn read_u64(&self, address: usize) -> u64 { + log::trace!("read u64 {:X}", address); + if let Ok(mut page_cache) = self.page_cache.lock() { + if let Ok(value) = page_cache.read_from_phys::(address) { + return value; + } + } + log::error!("failed to read u64 {:#x}", address); + 0 + } + + fn write_u8(&self, address: usize, value: u8) { + log::trace!("write u8 {:X} = {:X}", address, value); + if let Ok(mut page_cache) = self.page_cache.lock() { + if page_cache.write_to_phys::(address, value).is_ok() { + return; + } + } + log::error!("failed to write u8 {:#x}", address); + } + fn write_u16(&self, address: usize, value: u16) { + log::trace!("write u16 {:X} = {:X}", address, value); + if let Ok(mut page_cache) = self.page_cache.lock() { + if page_cache.write_to_phys::(address, value).is_ok() { + return; + } + } + log::error!("failed to write u16 {:#x}", address); + } + fn write_u32(&self, address: usize, value: u32) { + log::trace!("write u32 {:X} = {:X}", address, value); + if let Ok(mut page_cache) = self.page_cache.lock() { + if page_cache.write_to_phys::(address, value).is_ok() { + return; + } + } + log::error!("failed to write u32 {:#x}", address); + } + fn write_u64(&self, address: usize, value: u64) { + log::trace!("write u64 {:X} = {:X}", address, value); + if let Ok(mut page_cache) = self.page_cache.lock() { + if page_cache.write_to_phys::(address, value).is_ok() { + return; + } + } + log::error!("failed to write u64 {:#x}", address); + } + + // Pio must be enabled via syscall::iopl + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn read_io_u8(&self, port: u16) -> u8 { + Pio::::new(port).read() + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn read_io_u16(&self, port: u16) -> u16 { + Pio::::new(port).read() + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn read_io_u32(&self, port: u16) -> u32 { + Pio::::new(port).read() + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn write_io_u8(&self, port: u16, value: u8) { + Pio::::new(port).write(value) + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn write_io_u16(&self, port: u16, value: u16) { + Pio::::new(port).write(value) + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn write_io_u32(&self, port: u16, value: u32) { + Pio::::new(port).write(value) + } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn read_io_u8(&self, port: u16) -> u8 { + log::error!("cannot read u8 from port 0x{port:04X}"); + 0 + } + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn read_io_u16(&self, port: u16) -> u16 { + log::error!("cannot read u16 from port 0x{port:04X}"); + 0 + } + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn read_io_u32(&self, port: u16) -> u32 { + log::error!("cannot read u32 from port 0x{port:04X}"); + 0 + } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn write_io_u8(&self, port: u16, value: u8) { + log::error!("cannot write 0x{value:02X} to port 0x{port:04X}"); + } + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn write_io_u16(&self, port: u16, value: u16) { + log::error!("cannot write 0x{value:04X} to port 0x{port:04X}"); + } + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + fn write_io_u32(&self, port: u16, value: u32) { + log::error!("cannot write 0x{value:08X} to port 0x{port:04X}"); + } + + fn read_pci_u8(&self, addr: PciAddress, off: u16) -> u8 { + let mut value = [0u8]; + self.read_pci(addr, off, &mut value); + value[0] + } + fn read_pci_u16(&self, addr: PciAddress, off: u16) -> u16 { + let mut value = [0u8; 2]; + self.read_pci(addr, off, &mut value); + u16::from_le_bytes(value) + } + fn read_pci_u32(&self, addr: PciAddress, off: u16) -> u32 { + let mut value = [0u8; 4]; + self.read_pci(addr, off, &mut value); + u32::from_le_bytes(value) + } + fn write_pci_u8(&self, addr: PciAddress, off: u16, value: u8) { + self.write_pci(addr, off, &[value]); + } + fn write_pci_u16(&self, addr: PciAddress, off: u16, value: u16) { + self.write_pci(addr, off, &value.to_le_bytes()); + } + fn write_pci_u32(&self, addr: PciAddress, off: u16, value: u32) { + self.write_pci(addr, off, &value.to_le_bytes()); + } + + fn nanos_since_boot(&self) -> u64 { + let ts = libredox::call::clock_gettime(libredox::flag::CLOCK_MONOTONIC) + .expect("failed to get time"); + (ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64) + } + + fn stall(&self, microseconds: u64) { + let start = std::time::Instant::now(); + while start.elapsed().as_micros() < microseconds.into() { + std::hint::spin_loop(); + } + } + + fn sleep(&self, milliseconds: u64) { + std::thread::sleep(std::time::Duration::from_millis(milliseconds)); + } + + fn create_mutex(&self) -> Handle { + log::debug!("TODO: Handler::create_mutex"); + Handle(0) + } + + fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> { + log::debug!("TODO: Handler::acquire"); + Ok(()) + } + + fn release(&self, mutex: Handle) { + log::debug!("TODO: Handler::release"); + } +} diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs new file mode 100644 index 0000000000..9628e0a7d3 --- /dev/null +++ b/drivers/acpid/src/main.rs @@ -0,0 +1,148 @@ +use std::convert::TryFrom; +use std::fs::File; +use std::mem; +use std::os::unix::io::AsRawFd; +use std::sync::Arc; + +use event::{EventFlags, RawEventQueue}; +use redox_scheme::{RequestKind, SignalBehavior, Socket}; +use syscall::{EAGAIN, EWOULDBLOCK}; + +mod acpi; +mod aml_physmem; + +mod scheme; + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + common::setup_logging( + "misc", + "acpi", + "acpid", + common::output_level(), + common::file_level(), + ); + + let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt") + .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`") + .into(); + + if rxsdt_raw_data.is_empty() { + log::info!("System doesn't use ACPI"); + daemon.ready().expect("acpid: failed to notify parent"); + std::process::exit(0); + } + + let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT"); + + let mut thirty_two_bit; + let mut sixty_four_bit; + + let physaddrs_iter = match &sdt.signature { + b"RSDT" => { + thirty_two_bit = sdt + .data() + .chunks(mem::size_of::()) + // TODO: With const generics, the compiler has some way of doing this for static sizes. + .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) + .map(|chunk| u32::from_le_bytes(chunk)) + .map(u64::from); + + &mut thirty_two_bit as &mut dyn Iterator + } + b"XSDT" => { + sixty_four_bit = sdt + .data() + .chunks(mem::size_of::()) + .map(|chunk| <[u8; mem::size_of::()]>::try_from(chunk).unwrap()) + .map(|chunk| u64::from_le_bytes(chunk)); + + &mut sixty_four_bit as &mut dyn Iterator + } + _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"), + }; + + let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter); + + // TODO: I/O permission bitmap? + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3"); + + let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop") + .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`"); + + let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue"); + let socket = Socket::nonblock("acpi").expect("acpid: failed to create disk scheme"); + + daemon.ready().expect("acpid: failed to notify parent"); + + //TODO: needs to open /scheme/pci/access later! libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); + + event_queue + .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ) + .expect("acpid: failed to register shutdown pipe for event queue"); + event_queue + .subscribe(socket.inner().raw(), 1, EventFlags::READ) + .expect("acpid: failed to register scheme socket for event queue"); + + let mut scheme = self::scheme::AcpiScheme::new(&acpi_context); + + let mut mounted = true; + while mounted { + let Some(event) = event_queue + .next() + .transpose() + .expect("acpid: failed to read event file") + else { + break; + }; + + if event.fd == socket.inner().raw() { + loop { + let req = match socket.next_request(SignalBehavior::Interrupt) { + Ok(None) => { + mounted = false; + break; + } + Ok(Some(req)) => req, + Err(err) => { + if err.errno == EWOULDBLOCK || err.errno == EAGAIN { + break; + } else { + panic!("acpid: failed to read next request: {}", err); + } + } + }; + + match req.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut scheme); + socket + .write_response(response, SignalBehavior::Restart) + .expect("acpid: failed to write response"); + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + _ => (), + } + } + } else if event.fd == shutdown_pipe.as_raw_fd() as usize { + log::info!("Received shutdown request from kernel."); + mounted = false; + } else { + log::debug!("Received request to unknown fd: {}", event.fd); + continue; + } + } + + drop(shutdown_pipe); + drop(event_queue); + + acpi_context.set_global_s_state(5); + + unreachable!("System should have shut down before this is entered"); +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("acpid: failed to daemonize"); +} diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs new file mode 100644 index 0000000000..fd506f5a4c --- /dev/null +++ b/drivers/acpid/src/scheme.rs @@ -0,0 +1,422 @@ +use acpi::aml::namespace::AmlName; +use amlserde::aml_serde_name::to_aml_format; +use amlserde::AmlSerdeValue; +use core::str; +use parking_lot::RwLockReadGuard; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult}; +use ron::de::SpannedError; +use std::collections::BTreeMap; +use std::convert::{TryFrom, TryInto}; +use std::str::FromStr; +use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; +use syscall::schemev2::NewFdFlags; + +use syscall::data::Stat; +use syscall::error::{Error, Result}; +use syscall::error::{EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; +use syscall::flag::{MODE_DIR, MODE_FILE}; +use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK}; +use syscall::{EOPNOTSUPP, EOVERFLOW, EPERM}; + +use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature}; + +pub struct AcpiScheme<'acpi> { + ctx: &'acpi AcpiContext, + handles: BTreeMap>, + next_fd: usize, +} + +struct Handle<'a> { + kind: HandleKind<'a>, + stat: bool, + allowed_to_eval: bool, +} +enum HandleKind<'a> { + TopLevel, + Tables, + Table(SdtSignature), + Symbols(RwLockReadGuard<'a, AmlSymbols>), + Symbol { name: String, description: String }, +} + +impl HandleKind<'_> { + fn is_dir(&self) -> bool { + match self { + Self::TopLevel => true, + Self::Tables => true, + Self::Table(_) => false, + Self::Symbols(_) => true, + Self::Symbol { .. } => false, + } + } + fn len(&self, acpi_ctx: &AcpiContext) -> Result { + Ok(match self { + // Files + Self::Table(signature) => acpi_ctx + .sdt_from_signature(signature) + .ok_or(Error::new(EBADFD))? + .length(), + Self::Symbol { description, .. } => description.len(), + // Directories + Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, + }) + } +} + +impl<'acpi> AcpiScheme<'acpi> { + pub fn new(ctx: &'acpi AcpiContext) -> Self { + Self { + ctx, + handles: BTreeMap::new(), + next_fd: 0, + } + } +} + +fn parse_hex_digit(hex: u8) -> Option { + let hex = hex.to_ascii_lowercase(); + + if hex >= b'a' && hex <= b'f' { + Some(hex - b'a' + 10) + } else if hex >= b'0' && hex <= b'9' { + Some(hex - b'0') + } else { + None + } +} + +fn parse_hex_2digit(hex: &[u8]) -> Option { + parse_hex_digit(hex[0]) + .and_then(|most_significant| Some((most_significant << 4) | parse_hex_digit(hex[1])?)) +} + +fn parse_oem_id(hex: [u8; 12]) -> Option<[u8; 6]> { + Some([ + parse_hex_2digit(&hex[0..2])?, + parse_hex_2digit(&hex[2..4])?, + parse_hex_2digit(&hex[4..6])?, + parse_hex_2digit(&hex[6..8])?, + parse_hex_2digit(&hex[8..10])?, + parse_hex_2digit(&hex[10..12])?, + ]) +} +fn parse_oem_table_id(hex: [u8; 16]) -> Option<[u8; 8]> { + Some([ + parse_hex_2digit(&hex[0..2])?, + parse_hex_2digit(&hex[2..4])?, + parse_hex_2digit(&hex[4..6])?, + parse_hex_2digit(&hex[6..8])?, + parse_hex_2digit(&hex[8..10])?, + parse_hex_2digit(&hex[10..12])?, + parse_hex_2digit(&hex[12..14])?, + parse_hex_2digit(&hex[14..16])?, + ]) +} + +fn parse_table(table: &[u8]) -> Option { + let signature_part = table.get(..4)?; + let first_hyphen = table.get(4)?; + let oem_id_part = table.get(5..17)?; + let second_hyphen = table.get(17)?; + let oem_table_part = table.get(18..34)?; + + if *first_hyphen != b'-' { + return None; + } + if *second_hyphen != b'-' { + return None; + } + + if table.len() > 34 { + return None; + } + + Some(SdtSignature { + signature: <[u8; 4]>::try_from(signature_part) + .expect("expected 4-byte slice to be convertible into [u8; 4]"), + oem_id: { + let hex = <[u8; 12]>::try_from(oem_id_part) + .expect("expected 12-byte slice to be convertible into [u8; 12]"); + parse_oem_id(hex)? + }, + oem_table_id: { + let hex = <[u8; 16]>::try_from(oem_table_part) + .expect("expected 16-byte slice to be convertible into [u8; 16]"); + parse_oem_table_id(hex)? + }, + }) +} + +impl SchemeSync for AcpiScheme<'_> { + fn open(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result { + let path = path.trim_start_matches('/'); + + let flag_stat = flags & O_STAT == O_STAT; + let flag_dir = flags & O_DIRECTORY == O_DIRECTORY; + + // TODO: arrayvec + let components = { + let mut v = arrayvec::ArrayVec::<&str, 3>::new(); + let it = path.split('/'); + for component in it.take(3) { + v.push(component); + } + + v + }; + + let kind = match &*components { + [""] => HandleKind::TopLevel, + ["tables"] => HandleKind::Tables, + + ["tables", table] => { + let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; + HandleKind::Table(signature) + } + + ["symbols"] => { + if let Ok(aml_symbols) = self.ctx.aml_symbols() { + HandleKind::Symbols(aml_symbols) + } else { + return Err(Error::new(EIO)); + } + } + + ["symbols", symbol] => { + if let Some(description) = self.ctx.aml_lookup(symbol) { + HandleKind::Symbol { + name: (*symbol).to_owned(), + description, + } + } else { + return Err(Error::new(ENOENT)); + } + } + + _ => return Err(Error::new(ENOENT)), + }; + + if kind.is_dir() && !flag_dir && !flag_stat { + return Err(Error::new(EISDIR)); + } else if !kind.is_dir() && flag_dir && !flag_stat { + return Err(Error::new(ENOTDIR)); + } + + let allowed_to_eval = if flags & O_ACCMODE == O_RDONLY || flag_stat { + false + } else if ctx.uid == 0 { + true + } else { + return Err(Error::new(EINVAL)); + }; + + if flags & O_SYMLINK == O_SYMLINK && !flag_stat { + return Err(Error::new(EINVAL)); + } + + let fd = self.next_fd; + self.next_fd += 1; + + self.handles.insert( + fd, + Handle { + stat: flag_stat, + kind, + allowed_to_eval, + }, + ); + + Ok(OpenResult::ThisScheme { + number: fd, + flags: NewFdFlags::POSITIONED, + }) + } + + fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + stat.st_size = handle + .kind + .len(self.ctx)? + .try_into() + .unwrap_or(u64::max_value()); + + if handle.kind.is_dir() { + stat.st_mode = MODE_DIR; + } else { + stat.st_mode = MODE_FILE; + } + + Ok(()) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _fcntl: u32, + _ctx: &CallerCtx, + ) -> Result { + let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; + + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + let src_buf = match &handle.kind { + HandleKind::Table(ref signature) => self + .ctx + .sdt_from_signature(signature) + .ok_or(Error::new(EBADFD))? + .as_slice(), + HandleKind::Symbol { description, .. } => description.as_bytes(), + _ => return Err(Error::new(EINVAL)), + }; + + let offset = std::cmp::min(src_buf.len(), offset); + let src_buf = &src_buf[offset..]; + + let to_copy = std::cmp::min(src_buf.len(), buf.len()); + + buf[..to_copy].copy_from_slice(&src_buf[..to_copy]); + + Ok(to_copy) + } + + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EOPNOTSUPP))?; + + match &handle.kind { + HandleKind::TopLevel => { + const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"]; + + for (idx, name) in TOPLEVEL_ENTRIES + .iter() + .enumerate() + .skip(opaque_offset as usize) + { + buf.entry(DirEntry { + inode: 0, + next_opaque_id: idx as u64 + 1, + name, + kind: DirentKind::Directory, + })?; + } + } + HandleKind::Symbols(aml_symbols) => { + for (idx, (symbol_name, _value)) in aml_symbols + .symbols_cache() + .iter() + .enumerate() + .skip(opaque_offset as usize) + { + buf.entry(DirEntry { + inode: 0, + next_opaque_id: idx as u64 + 1, + name: symbol_name.as_str(), + kind: DirentKind::Regular, + })?; + } + } + HandleKind::Tables => { + for (idx, table) in self + .ctx + .tables() + .iter() + .enumerate() + .skip(opaque_offset as usize) + { + let utf8_or_eio = |bytes| str::from_utf8(bytes).map_err(|_| Error::new(EIO)); + + let mut name = String::new(); + name.push_str(utf8_or_eio(&table.signature[..])?); + name.push('-'); + for byte in table.oem_id.iter() { + std::fmt::write(&mut name, format_args!("{:>02X}", byte)).unwrap(); + } + name.push('-'); + for byte in table.oem_table_id.iter() { + std::fmt::write(&mut name, format_args!("{:>02X}", byte)).unwrap(); + } + + buf.entry(DirEntry { + inode: 0, + next_opaque_id: idx as u64 + 1, + name: &name, + kind: DirentKind::Regular, + })?; + } + } + _ => return Err(Error::new(EIO)), + } + + Ok(buf) + } + + fn write( + &mut self, + _id: usize, + _buf: &[u8], + _offset: u64, + _fcntl: u32, + _ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + fn call(&mut self, id: usize, payload: &mut [u8], _metadata: &[u64]) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + if !handle.allowed_to_eval { + return Err(Error::new(EPERM)); + } + + let Ok(args): Result, SpannedError> = ron::de::from_bytes(payload) + else { + return Err(Error::new(EINVAL)); + }; + + let HandleKind::Symbol { name, .. } = &handle.kind else { + return Err(Error::new(EBADF)); + }; + + let Ok(aml_name) = AmlName::from_str(&to_aml_format(name)) else { + log::error!("Failed to convert symbol name: \"{name}\" to aml name!"); + return Err(Error::new(EBADF)); + }; + + let Ok(result) = self.ctx.aml_eval(aml_name, args) else { + return Err(Error::new(EINVAL)); + }; + + let Ok(serialized_result) = ron::ser::to_string(&result) else { + log::error!("Failed to serialize aml result!"); + return Err(Error::new(EINVAL)); + }; + + let byte_result = serialized_result.as_bytes(); + let result_len = byte_result.len(); + + if result_len > payload.len() { + return Err(Error::new(EOVERFLOW)); + } + + payload[..result_len].copy_from_slice(byte_result); + + Ok(result_len) + } +} + +impl AcpiScheme<'_> { + pub fn on_close(&mut self, id: usize) { + self.handles.remove(&id); + } +} diff --git a/drivers/amlserde/Cargo.toml b/drivers/amlserde/Cargo.toml new file mode 100644 index 0000000000..28d1b56834 --- /dev/null +++ b/drivers/amlserde/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "amlserde" +version = "0.0.1" +authors = ["Ron Williams"] +repository = "https://gitlab.redox-os.org/redox-os/drivers" +description = "Library for serializing AML symbols" +categories = ["hardware-support"] +license = "MIT/Apache-2.0" +edition = "2021" + +[dependencies] +acpi = { git = "https://github.com/jackpot51/acpi.git" } +serde = { version = "1.0", features = ["derive"] } +toml = "0.7.3" diff --git a/drivers/amlserde/src/lib.rs b/drivers/amlserde/src/lib.rs new file mode 100644 index 0000000000..1cf8e3f2c6 --- /dev/null +++ b/drivers/amlserde/src/lib.rs @@ -0,0 +1,475 @@ +use acpi::{ + aml::{ + namespace::AmlName, + object::{ + FieldAccessType, FieldFlags, FieldUnit, FieldUnitKind, FieldUpdateRule, MethodFlags, + Object, ReferenceKind, WrappedObject, + }, + op_region::{OpRegion, RegionSpace}, + Interpreter, + }, + Handle, Handler, +}; +use serde::{Deserialize, Serialize}; +use std::{ + ops::{Deref, Shl}, + str::FromStr, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct AmlSerde { + pub name: String, + pub value: AmlSerdeValue, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AmlSerdeValue { + Uninitialized, + Integer(u64), + String(String), + OpRegion { + region: AmlSerdeRegionSpace, + offset: u64, + length: u64, + parent_device: String, + }, + Field { + kind: AmlSerdeFieldKind, + flags: AmlSerdeFieldFlags, + offset: u64, + length: u64, + }, + Device, + Event(u64), + Method { + arg_count: usize, + serialize: bool, + sync_level: u8, + }, + Buffer(Vec), + BufferField { + offset: u64, + length: u64, + data: Box, + }, + Processor { + id: u8, + pblk_address: u32, + pblk_len: u8, + }, + Mutex { + mutex: u32, + sync_level: u8, + }, + Reference { + kind: AmlSerdeReferenceKind, + inner: Box, + }, + Package { + contents: Vec, + }, + PowerResource { + system_level: u8, + resource_order: u16, + }, + RawDataBuffer, + ThermalZone, + Debug, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AmlSerdeRegionSpace { + SystemMemory, + SystemIo, + PciConfig, + EmbeddedControl, + SMBus, + SystemCmos, + PciBarTarget, + IPMI, + GeneralPurposeIo, + GenericSerialBus, + Pcc, + OemDefined(u8), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AmlSerdeFieldKind { + Normal { + region: Box, + }, + Bank { + region: Box, + bank: Box, + bank_value: u64, + }, + Index { + index: Box, + data: Box, + }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AmlSerdeFieldFlags { + pub access_type: AmlSerdeFieldAccessType, + pub lock_rule: bool, // bit 4 + pub update_rule: AmlSerdeFieldUpdateRule, +} +impl Into for AmlSerdeFieldFlags { + fn into(self) -> u8 { + // bits 0..4 + (self.access_type as u8) + + // bit 4 + (self.lock_rule as u8).shl(4) + + // bits 5..7 + (self.update_rule as u8).shl(5) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[repr(u8)] +pub enum AmlSerdeFieldAccessType { + Any = 0, + Byte = 1, + Word = 2, + DWord = 3, + QWord = 4, + Buffer = 5, +} + +#[derive(Debug, Serialize, Deserialize)] +#[repr(u8)] +pub enum AmlSerdeFieldUpdateRule { + Preserve = 0, + WriteAsOnes = 1, + WriteAsZeros = 2, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum AmlSerdeReferenceKind { + RefOf, + LocalOrArg, + Unresolved, +} + +impl AmlSerde { + pub fn default() -> Self { + Self { + name: "name".to_owned(), + value: AmlSerdeValue::String(String::default()), + } + } + + pub fn from_aml(aml_context: &Interpreter, aml_name: &AmlName) -> Option { + //TODO: why does namespace.get not take a reference to aml_name + let aml_value = if let Ok(aml_value) = aml_context.namespace.lock().get(aml_name.clone()) { + aml_value + } else { + return None; + }; + + let value = if let Some(value) = AmlSerdeValue::from_aml_value(aml_value.deref()) { + value + } else { + return None; + }; + + Some(AmlSerde { + name: aml_name.to_string(), + value, + }) + } +} + +impl AmlSerdeValue { + pub fn default() -> Self { + AmlSerdeValue::String("".to_owned()) + } + + pub fn from_aml_value(aml_value: &Object) -> Option { + Some(match aml_value { + Object::Uninitialized => AmlSerdeValue::Uninitialized, + Object::Integer(n) => AmlSerdeValue::Integer(n.to_owned()), + Object::String(s) => AmlSerdeValue::String(s.to_owned()), + Object::OpRegion(region) => AmlSerdeValue::OpRegion { + region: match region.space { + RegionSpace::SystemMemory => AmlSerdeRegionSpace::SystemMemory, + RegionSpace::SystemIO => AmlSerdeRegionSpace::SystemIo, + RegionSpace::PciConfig => AmlSerdeRegionSpace::PciConfig, + RegionSpace::EmbeddedControl => AmlSerdeRegionSpace::EmbeddedControl, + RegionSpace::SmBus => AmlSerdeRegionSpace::SMBus, + RegionSpace::SystemCmos => AmlSerdeRegionSpace::SystemCmos, + RegionSpace::PciBarTarget => AmlSerdeRegionSpace::PciBarTarget, + RegionSpace::Ipmi => AmlSerdeRegionSpace::IPMI, + RegionSpace::GeneralPurposeIo => AmlSerdeRegionSpace::GeneralPurposeIo, + RegionSpace::GenericSerialBus => AmlSerdeRegionSpace::GenericSerialBus, + RegionSpace::Pcc => AmlSerdeRegionSpace::Pcc, + RegionSpace::Oem(n) => AmlSerdeRegionSpace::OemDefined(n.to_owned()), + }, + offset: region.base, + length: region.length, + parent_device: region.parent_device_path.to_string(), + }, + Object::FieldUnit(field) => AmlSerdeValue::Field { + kind: match &field.kind { + FieldUnitKind::Normal { region } => AmlSerdeFieldKind::Normal { + region: AmlSerdeValue::from_aml_value(region.deref()).map(Box::new)?, + }, + FieldUnitKind::Bank { + region, + bank, + bank_value, + } => AmlSerdeFieldKind::Bank { + region: AmlSerdeValue::from_aml_value(region.deref()).map(Box::new)?, + bank: AmlSerdeValue::from_aml_value(bank.deref()).map(Box::new)?, + bank_value: bank_value.to_owned(), + }, + FieldUnitKind::Index { index, data } => AmlSerdeFieldKind::Index { + index: AmlSerdeValue::from_aml_value(index.deref()).map(Box::new)?, + data: AmlSerdeValue::from_aml_value(data.deref()).map(Box::new)?, + }, + }, + flags: AmlSerdeFieldFlags { + access_type: match field.flags.access_type() { + Ok(FieldAccessType::Any) => AmlSerdeFieldAccessType::Any, + Ok(FieldAccessType::Byte) => AmlSerdeFieldAccessType::Byte, + Ok(FieldAccessType::Word) => AmlSerdeFieldAccessType::Word, + Ok(FieldAccessType::DWord) => AmlSerdeFieldAccessType::DWord, + Ok(FieldAccessType::QWord) => AmlSerdeFieldAccessType::QWord, + Ok(FieldAccessType::Buffer) => AmlSerdeFieldAccessType::Buffer, + _ => return None, + }, + lock_rule: field.flags.lock_rule(), + update_rule: match field.flags.update_rule() { + FieldUpdateRule::Preserve => AmlSerdeFieldUpdateRule::Preserve, + FieldUpdateRule::WriteAsOnes => AmlSerdeFieldUpdateRule::WriteAsOnes, + FieldUpdateRule::WriteAsZeros => AmlSerdeFieldUpdateRule::WriteAsZeros, + }, + }, + offset: field.bit_index as u64, + length: field.bit_length as u64, + }, + Object::Device => AmlSerdeValue::Device, + Object::Event(event) => AmlSerdeValue::Event(event.load(Ordering::Relaxed)), + Object::Method { flags, code: _ } => AmlSerdeValue::Method { + arg_count: flags.arg_count(), + serialize: flags.serialize(), + sync_level: flags.sync_level(), + }, + //TODO: distinguish from Method? + Object::NativeMethod { f: _, flags } => AmlSerdeValue::Method { + arg_count: flags.arg_count(), + serialize: flags.serialize(), + sync_level: flags.sync_level(), + }, + Object::Buffer(buffer_data) => AmlSerdeValue::Buffer(buffer_data.to_owned()), + Object::BufferField { + buffer, + offset, + length, + } => AmlSerdeValue::BufferField { + offset: offset.to_owned() as u64, + length: length.to_owned() as u64, + data: AmlSerdeValue::from_aml_value(buffer.deref()).map(Box::new)?, + }, + Object::Processor { + proc_id, + pblk_address, + pblk_length, + } => AmlSerdeValue::Processor { + id: proc_id.to_owned(), + pblk_address: pblk_address.to_owned(), + pblk_len: pblk_length.to_owned(), + }, + Object::Mutex { mutex, sync_level } => AmlSerdeValue::Mutex { + mutex: mutex.0, + sync_level: sync_level.to_owned(), + }, + Object::Reference { kind, inner } => AmlSerdeValue::Reference { + kind: match kind { + ReferenceKind::RefOf => AmlSerdeReferenceKind::RefOf, + ReferenceKind::LocalOrArg => AmlSerdeReferenceKind::LocalOrArg, + ReferenceKind::Unresolved => AmlSerdeReferenceKind::Unresolved, + }, + inner: AmlSerdeValue::from_aml_value(inner.deref()).map(Box::new)?, + }, + Object::Package(aml_contents) => AmlSerdeValue::Package { + contents: aml_contents + .iter() + .filter_map(|item| AmlSerdeValue::from_aml_value(item)) + .collect(), + }, + Object::PowerResource { + system_level, + resource_order, + } => AmlSerdeValue::PowerResource { + system_level: system_level.to_owned(), + resource_order: resource_order.to_owned(), + }, + Object::RawDataBuffer => AmlSerdeValue::RawDataBuffer, + Object::ThermalZone => AmlSerdeValue::ThermalZone, + Object::Debug => AmlSerdeValue::Debug, + }) + } + pub fn to_aml_object(self) -> Option { + Some(match self { + AmlSerdeValue::Uninitialized => Object::Uninitialized, + AmlSerdeValue::Integer(n) => Object::Integer(n), + AmlSerdeValue::String(s) => Object::String(s), + AmlSerdeValue::OpRegion { + region, + offset, + length, + parent_device, + } => Object::OpRegion(OpRegion { + space: match region { + AmlSerdeRegionSpace::PciConfig => RegionSpace::PciConfig, + AmlSerdeRegionSpace::EmbeddedControl => RegionSpace::EmbeddedControl, + AmlSerdeRegionSpace::SMBus => RegionSpace::SmBus, + AmlSerdeRegionSpace::SystemCmos => RegionSpace::SystemCmos, + AmlSerdeRegionSpace::PciBarTarget => RegionSpace::PciBarTarget, + AmlSerdeRegionSpace::IPMI => RegionSpace::Ipmi, + AmlSerdeRegionSpace::GeneralPurposeIo => RegionSpace::GeneralPurposeIo, + AmlSerdeRegionSpace::GenericSerialBus => RegionSpace::GenericSerialBus, + AmlSerdeRegionSpace::SystemMemory => RegionSpace::SystemMemory, + AmlSerdeRegionSpace::SystemIo => RegionSpace::SystemIO, + AmlSerdeRegionSpace::Pcc => RegionSpace::Pcc, + AmlSerdeRegionSpace::OemDefined(n) => RegionSpace::Oem(n), + }, + base: offset, + length, + // + parent_device_path: AmlName::from_str(&parent_device).ok()?, // TODO: Error value hidden + }), + AmlSerdeValue::Field { + kind, + flags, + offset, + length, + } => Object::FieldUnit(FieldUnit { + kind: match kind { + AmlSerdeFieldKind::Normal { region } => FieldUnitKind::Normal { + region: region.to_aml_object()?.wrap(), + }, + AmlSerdeFieldKind::Bank { + region, + bank, + bank_value, + } => FieldUnitKind::Bank { + region: region.to_aml_object()?.wrap(), + bank: bank.to_aml_object()?.wrap(), + bank_value: bank_value.to_owned(), + }, + AmlSerdeFieldKind::Index { index, data } => FieldUnitKind::Index { + index: index.to_aml_object()?.wrap(), + data: data.to_aml_object()?.wrap(), + }, + }, + flags: FieldFlags(flags.into()), + bit_index: offset as usize, + bit_length: length as usize, + }), + AmlSerdeValue::Device => Object::Device, + AmlSerdeValue::Event(event) => Object::Event(Arc::new(AtomicU64::new(event))), + AmlSerdeValue::Method { + arg_count, + serialize, + sync_level, + } => Object::Method { + code: (return None), //TODO figure out what to do here + //TODO check specs to see if all bit patterns are allowed + flags: MethodFlags( + (arg_count as u8).clamp(0, 7) + + (serialize as u8).shl(3) + + sync_level.clamp(0, 15).shl(4), + ), + }, + //TODO: handle native method? + AmlSerdeValue::Buffer(buffer_data) => Object::Buffer(buffer_data), + AmlSerdeValue::BufferField { + data, + offset, + length, + } => Object::BufferField { + offset: offset as usize, + length: length as usize, + buffer: data.to_aml_object()?.wrap(), + }, + AmlSerdeValue::Processor { + id, + pblk_address, + pblk_len, + } => Object::Processor { + proc_id: id, + pblk_address, + pblk_length: pblk_len, + }, + AmlSerdeValue::Mutex { mutex, sync_level } => Object::Mutex { + mutex: Handle(mutex), + sync_level: sync_level, + }, + AmlSerdeValue::Reference { kind, inner } => Object::Reference { + kind: match kind { + AmlSerdeReferenceKind::RefOf => ReferenceKind::RefOf, + AmlSerdeReferenceKind::LocalOrArg => ReferenceKind::LocalOrArg, + AmlSerdeReferenceKind::Unresolved => ReferenceKind::Unresolved, + }, + inner: inner.to_aml_object()?.wrap(), + }, + AmlSerdeValue::Package { contents } => Object::Package( + contents + .into_iter() + .map(|item| item.to_aml_object().map(Object::wrap)) // TODO: see if errors should be ignored here + .collect::>>()?, + ), + AmlSerdeValue::PowerResource { + system_level, + resource_order, + } => Object::PowerResource { + system_level: system_level.to_owned(), + resource_order: resource_order.to_owned(), + }, + AmlSerdeValue::RawDataBuffer => Object::RawDataBuffer, + AmlSerdeValue::ThermalZone => Object::ThermalZone, + AmlSerdeValue::Debug => Object::Debug, + }) + } +} + +pub mod aml_serde_name { + use acpi::aml::namespace::AmlName; + + /// Add a leading backslash to make the name a valid + /// namespace reference + pub fn to_aml_format(pretty_name: &String) -> String { + format!("\\{}", pretty_name) + } + + /// convert a string from AML namespace style to + /// acpi symbol style + pub fn to_symbol(aml_style_name: &String) -> String { + let mut name = aml_style_name.to_owned(); + + // remove leading slash + name = name.trim_start_matches("\\").to_owned(); + // remove unnecessary underscores + while let Some(index) = name.find("_.") { + name.remove(index); + } + while name.len() > 0 && &name[name.len() - 1..] == "_" { + name.pop(); + } + name.shrink_to_fit(); + name + } + + /// Convert to string and remove + /// trailing underscores from each name segment + pub fn aml_to_symbol(aml_name: &AmlName) -> String { + to_symbol(&aml_name.as_string()) + } +} diff --git a/drivers/audio/ac97d/Cargo.toml b/drivers/audio/ac97d/Cargo.toml new file mode 100644 index 0000000000..3eea94c5bd --- /dev/null +++ b/drivers/audio/ac97d/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ac97d" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "1" +common = { path = "../../common" } +libredox = "0.1.3" +log = "0.4" +redox-daemon = "0.1" +redox_event = "0.4.1" +redox_syscall = "0.5" +spin = "0.9" + +pcid = { path = "../../pcid" } +redox-scheme = "0.8.2" diff --git a/drivers/audio/ac97d/config.toml b/drivers/audio/ac97d/config.toml new file mode 100644 index 0000000000..106ce703a3 --- /dev/null +++ b/drivers/audio/ac97d/config.toml @@ -0,0 +1,5 @@ +[[drivers]] +name = "AC97 Audio" +class = 0x04 +subclass = 0x01 +command = ["ac97d"] diff --git a/drivers/audio/ac97d/src/device.rs b/drivers/audio/ac97d/src/device.rs new file mode 100644 index 0000000000..da73fb54be --- /dev/null +++ b/drivers/audio/ac97d/src/device.rs @@ -0,0 +1,328 @@ +#![allow(dead_code)] + +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use common::io::Pio; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::CallerCtx; +use redox_scheme::OpenResult; +use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT}; +use syscall::schemev2::NewFdFlags; +use syscall::EWOULDBLOCK; + +use common::{ + dma::Dma, + io::{Io, Mmio}, +}; +use spin::Mutex; + +const NUM_SUB_BUFFS: usize = 32; +const SUB_BUFF_SIZE: usize = 2048; + +enum Handle { + Todo, +} + +#[allow(dead_code)] +struct MixerRegs { + /* 0x00 */ reset: Pio, + /* 0x02 */ master_volume: Pio, + /* 0x04 */ aux_out_volume: Pio, + /* 0x06 */ mono_volume: Pio, + /* 0x08 */ master_tone: Pio, + /* 0x0A */ pc_beep_volume: Pio, + /* 0x0C */ phone_volume: Pio, + /* 0x0E */ mic_volume: Pio, + /* 0x10 */ line_in_volume: Pio, + /* 0x12 */ cd_volume: Pio, + /* 0x14 */ video_volume: Pio, + /* 0x16 */ aux_in_volume: Pio, + /* 0x18 */ pcm_out_volume: Pio, + /* 0x1A */ record_select: Pio, + /* 0x1C */ record_gain: Pio, + /* 0x1E */ record_gain_mic: Pio, + /* 0x20 */ general_purpose: Pio, + /* 0x22 */ control_3d: Pio, + /* 0x24 */ audio_int_paging: Pio, + /* 0x26 */ powerdown: Pio, + /* 0x28 */ extended_id: Pio, + /* 0x2A */ extended_ctrl: Pio, + /* 0x2C */ vra_pcm_front: Pio, +} + +impl MixerRegs { + fn new(bar0: u16) -> Self { + Self { + reset: Pio::new(bar0 + 0x00), + master_volume: Pio::new(bar0 + 0x02), + aux_out_volume: Pio::new(bar0 + 0x04), + mono_volume: Pio::new(bar0 + 0x06), + master_tone: Pio::new(bar0 + 0x08), + pc_beep_volume: Pio::new(bar0 + 0x0A), + phone_volume: Pio::new(bar0 + 0x0C), + mic_volume: Pio::new(bar0 + 0x0E), + line_in_volume: Pio::new(bar0 + 0x10), + cd_volume: Pio::new(bar0 + 0x12), + video_volume: Pio::new(bar0 + 0x14), + aux_in_volume: Pio::new(bar0 + 0x16), + pcm_out_volume: Pio::new(bar0 + 0x18), + record_select: Pio::new(bar0 + 0x1A), + record_gain: Pio::new(bar0 + 0x1C), + record_gain_mic: Pio::new(bar0 + 0x1E), + general_purpose: Pio::new(bar0 + 0x20), + control_3d: Pio::new(bar0 + 0x22), + audio_int_paging: Pio::new(bar0 + 0x24), + powerdown: Pio::new(bar0 + 0x26), + extended_id: Pio::new(bar0 + 0x28), + extended_ctrl: Pio::new(bar0 + 0x2A), + vra_pcm_front: Pio::new(bar0 + 0x2C), + } + } +} + +#[allow(dead_code)] +struct BusBoxRegs { + /// Buffer descriptor list base address + /* 0x00 */ + bdbar: Pio, + /// Current index value + /* 0x04 */ + civ: Pio, + /// Last valid index + /* 0x05 */ + lvi: Pio, + /// Status + /* 0x06 */ + sr: Pio, + /// Position in current buffer + /* 0x08 */ + picb: Pio, + /// Prefetched index value + /* 0x0A */ + piv: Pio, + /// Control + /* 0x0B */ + cr: Pio, +} + +impl BusBoxRegs { + fn new(base: u16) -> Self { + Self { + bdbar: Pio::new(base + 0x00), + civ: Pio::new(base + 0x04), + lvi: Pio::new(base + 0x05), + sr: Pio::new(base + 0x06), + picb: Pio::new(base + 0x08), + piv: Pio::new(base + 0x0A), + cr: Pio::new(base + 0x0B), + } + } +} + +#[allow(dead_code)] +struct BusRegs { + /// PCM in register box + /* 0x00 */ + pi: BusBoxRegs, + /// PCM out register box + /* 0x10 */ + po: BusBoxRegs, + /// Microphone register box + /* 0x20 */ + mc: BusBoxRegs, +} + +impl BusRegs { + fn new(bar1: u16) -> Self { + Self { + pi: BusBoxRegs::new(bar1 + 0x00), + po: BusBoxRegs::new(bar1 + 0x10), + mc: BusBoxRegs::new(bar1 + 0x20), + } + } +} + +#[repr(C, packed)] +pub struct BufferDescriptor { + /* 0x00 */ addr: Mmio, + /* 0x04 */ samples: Mmio, + /* 0x06 */ flags: Mmio, +} + +pub struct Ac97 { + mixer: MixerRegs, + bus: BusRegs, + bdl: Dma<[BufferDescriptor; NUM_SUB_BUFFS]>, + buf: Dma<[u8; NUM_SUB_BUFFS * SUB_BUFF_SIZE]>, + handles: Mutex>, + next_id: AtomicUsize, +} + +impl Ac97 { + pub unsafe fn new(bar0: u16, bar1: u16) -> Result { + let mut module = Ac97 { + mixer: MixerRegs::new(bar0), + bus: BusRegs::new(bar1), + bdl: Dma::zeroed( + //TODO: PhysBox::new_in_32bit_space(bdl_size)? + )? + .assume_init(), + buf: Dma::zeroed( + //TODO: PhysBox::new_in_32bit_space(buf_size)? + )? + .assume_init(), + handles: Mutex::new(BTreeMap::new()), + next_id: AtomicUsize::new(0), + }; + + module.init()?; + + Ok(module) + } + + fn init(&mut self) -> Result<()> { + //TODO: support other sample rates, or just the default of 48000 Hz + { + // Check if VRA is supported + if !self.mixer.extended_id.readf(1 << 0) { + println!("ac97d: VRA not supported and is currently required"); + return Err(Error::new(ENOENT)); + } + + // Enable VRA + self.mixer.extended_ctrl.writef(1 << 0, true); + + // Attempt to set sample rate for PCM front to 44100 Hz + let desired_sample_rate = 44100; + self.mixer.vra_pcm_front.write(desired_sample_rate); + + // Read back real sample rate + let real_sample_rate = self.mixer.vra_pcm_front.read(); + println!("ac97d: set sample rate to {}", real_sample_rate); + + // Error if we cannot set the sample rate as desired + if real_sample_rate != desired_sample_rate { + println!( + "ac97d: sample rate is {} but only {} is supported", + real_sample_rate, desired_sample_rate + ); + return Err(Error::new(ENOENT)); + } + } + + // Ensure PCM out is stopped + self.bus.po.cr.writef(1, false); + + // Reset PCM out + self.bus.po.cr.writef(1 << 1, true); + while self.bus.po.cr.readf(1 << 1) { + // Spinning on resetting PCM out + //TODO: relax + } + + // Initialize BDL for PCM out + for i in 0..NUM_SUB_BUFFS { + self.bdl[i] + .addr + .write((self.buf.physical() + i * SUB_BUFF_SIZE) as u32); + self.bdl[i] + .samples + .write((SUB_BUFF_SIZE / 2/* Each sample is i16 or 2 bytes */) as u16); + self.bdl[i] + .flags + .write(1 << 15 /* Interrupt on completion */); + } + self.bus.po.bdbar.write(self.bdl.physical() as u32); + + // Enable interrupt on completion + self.bus.po.cr.writef(1 << 4, true); + + // Start bus master + self.bus.po.cr.writef(1 << 0, true); + + // Set master volume to 0 db (loudest output, DANGER!) + self.mixer.master_volume.write(0); + + // Set PCM output volume to 0 db (medium) + self.mixer.pcm_out_volume.write(0x808); + + Ok(()) + } + + pub fn irq(&mut self) -> bool { + let ints = self.bus.po.sr.read() & 0b11100; + if ints != 0 { + self.bus.po.sr.write(ints); + true + } else { + false + } + } +} + +impl SchemeSync for Ac97 { + fn open(&mut self, _path: &str, _flags: usize, ctx: &CallerCtx) -> Result { + if ctx.uid == 0 { + let id = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.lock().insert(id, Handle::Todo); + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::empty(), + }) + } else { + Err(Error::new(EACCES)) + } + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _flags: u32, + _ctx: &CallerCtx, + ) -> Result { + { + let mut handles = self.handles.lock(); + let _handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; + } + + if buf.len() != SUB_BUFF_SIZE { + return Err(Error::new(EINVAL)); + } + + let civ = self.bus.po.civ.read() as usize; + let mut lvi = self.bus.po.lvi.read() as usize; + if lvi == (civ + 3) % NUM_SUB_BUFFS { + // Block if we already are 3 buffers ahead + Err(Error::new(EWOULDBLOCK)) + } else { + // Fill next buffer + lvi = (lvi + 1) % NUM_SUB_BUFFS; + for i in 0..SUB_BUFF_SIZE { + self.buf[lvi * SUB_BUFF_SIZE + i] = buf[i]; + } + self.bus.po.lvi.write(lvi as u8); + + Ok(SUB_BUFF_SIZE) + } + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let mut handles = self.handles.lock(); + let _handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + let mut i = 0; + let scheme_path = b"/scheme/audiohw"; + while i < buf.len() && i < scheme_path.len() { + buf[i] = scheme_path[i]; + i += 1; + } + Ok(i) + } + + fn on_close(&mut self, id: usize) { + let _ = self.handles.lock().remove(&id); + } +} diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs new file mode 100644 index 0000000000..0e786d5d64 --- /dev/null +++ b/drivers/audio/ac97d/src/main.rs @@ -0,0 +1,140 @@ +//#![deny(warnings)] + +extern crate bitflags; +extern crate event; +extern crate spin; +extern crate syscall; + +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; +use std::usize; + +use event::{user_data, EventQueue}; +use pcid_interface::PciFunctionHandle; +use redox_scheme::wrappers::ReadinessBased; +use redox_scheme::Socket; +use std::cell::RefCell; + +pub mod device; + +fn main() { + let pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_ac97"); + + let bar0 = pci_config.func.bars[0].expect_port(); + let bar1 = pci_config.func.bars[1].expect_port(); + + let irq = pci_config + .func + .legacy_interrupt_line + .expect("ac97d: no legacy interrupts supported"); + + println!(" + ac97 {}", pci_config.func.display()); + + // Daemonize + redox_daemon::Daemon::new(move |daemon| { + common::setup_logging( + "audio", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + common::acquire_port_io_rights() + .expect("ac97d: failed to set I/O privilege level to Ring 3"); + + let mut irq_file = irq.irq_handle("ac97d"); + + let device = RefCell::new(unsafe { + device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") + }); + let socket = Socket::nonblock("audiohw").expect("ac97d: failed to create socket"); + let mut readiness_based = ReadinessBased::new(&socket, 16); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = + EventQueue::::new().expect("ac97d: Could not create event queue."); + event_queue + .subscribe( + irq_file.as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + socket.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + daemon.ready().expect("ac97d: failed to signal readiness"); + + libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace"); + + let all = [Source::Irq, Source::Scheme]; + for event in all + .into_iter() + .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data)) + { + match event { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.read(&mut irq).unwrap(); + + if !device.borrow_mut().irq() { + continue; + } + irq_file.write(&mut irq).unwrap(); + + readiness_based + .poll_all_requests(|| device.borrow_mut()) + .expect("ac97d: failed to poll requests"); + + /* + let next_read = device_irq.next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + Source::Scheme => { + if !readiness_based + .read_requests() + .expect("ac97d: failed to read from socket") + { + break; + } + readiness_based.process_requests(|| device.borrow_mut()); + if !readiness_based + .write_responses() + .expect("ac97d: failed to write to socket") + { + break; + } + + /* + let next_read = device.borrow().next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + } + } + + std::process::exit(0); + }) + .expect("ac97d: failed to daemonize"); +} diff --git a/drivers/audio/ihdad/Cargo.toml b/drivers/audio/ihdad/Cargo.toml new file mode 100644 index 0000000000..83fc34df63 --- /dev/null +++ b/drivers/audio/ihdad/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ihdad" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "2" +libredox = "0.1.3" +log = "0.4" +redox-daemon = "0.1" +redox_event = "0.4.1" +redox_syscall = "0.5" +spin = "0.9" + +common = { path = "../../common" } +pcid = { path = "../../pcid" } +redox-scheme = "0.8.2" diff --git a/drivers/audio/ihdad/config.toml b/drivers/audio/ihdad/config.toml new file mode 100644 index 0000000000..8be0418577 --- /dev/null +++ b/drivers/audio/ihdad/config.toml @@ -0,0 +1,5 @@ +[[drivers]] +name = "Intel HD Audio" +class = 0x04 +subclass = 0x03 +command = ["ihdad"] diff --git a/drivers/audio/ihdad/src/hda/cmdbuff.rs b/drivers/audio/ihdad/src/hda/cmdbuff.rs new file mode 100644 index 0000000000..f968f0dcce --- /dev/null +++ b/drivers/audio/ihdad/src/hda/cmdbuff.rs @@ -0,0 +1,501 @@ +use common::dma::Dma; +use common::io::{Io, Mmio}; +use common::timeout::Timeout; +use syscall::error::{Error, Result, EIO}; + +use super::common::*; + +// CORBCTL +const CMEIE: u8 = 1 << 0; // 1 bit +const CORBRUN: u8 = 1 << 1; // 1 bit + +// CORBSIZE +const CORBSZCAP: (u8, u8) = (4, 4); +const CORBSIZE: (u8, u8) = (0, 2); + +// CORBRP +const CORBRPRST: u16 = 1 << 15; + +// RIRBWP +const RIRBWPRST: u16 = 1 << 15; + +// RIRBCTL +const RINTCTL: u8 = 1 << 0; // 1 bit +const RIRBDMAEN: u8 = 1 << 1; // 1 bit + +const CORB_OFFSET: usize = 0x00; +const RIRB_OFFSET: usize = 0x10; +const ICMD_OFFSET: usize = 0x20; + +// ICS +const ICB: u16 = 1 << 0; +const IRV: u16 = 1 << 1; + +// CORB and RIRB offset + +const COMMAND_BUFFER_OFFSET: usize = 0x40; +const CORB_BUFF_MAX_SIZE: usize = 1024; + +struct CommandBufferRegs { + corblbase: Mmio, + corbubase: Mmio, + corbwp: Mmio, + corbrp: Mmio, + corbctl: Mmio, + corbsts: Mmio, + corbsize: Mmio, + rsvd5: Mmio, + + rirblbase: Mmio, + rirbubase: Mmio, + rirbwp: Mmio, + rintcnt: Mmio, + rirbctl: Mmio, + rirbsts: Mmio, + rirbsize: Mmio, + rsvd6: Mmio, +} + +struct CorbRegs { + corblbase: Mmio, + corbubase: Mmio, + corbwp: Mmio, + corbrp: Mmio, + corbctl: Mmio, + corbsts: Mmio, + corbsize: Mmio, + rsvd5: Mmio, +} + +struct Corb { + regs: &'static mut CorbRegs, + corb_base: *mut u32, + corb_base_phys: usize, + corb_count: usize, +} + +impl Corb { + pub fn new(regs_addr: usize, corb_buff_phys: usize, corb_buff_virt: *mut u32) -> Corb { + unsafe { + Corb { + regs: &mut *(regs_addr as *mut CorbRegs), + corb_base: corb_buff_virt, + corb_base_phys: corb_buff_phys, + corb_count: 0, + } + } + } + + //Intel 4.4.1.3 + pub fn init(&mut self) -> Result<()> { + self.stop()?; + //Determine CORB and RIRB size and allocate buffer + + //3.3.24 + let corbsize_reg = self.regs.corbsize.read(); + let corbszcap = (corbsize_reg >> 4) & 0xF; + + let mut corbsize_bytes: usize = 0; + let mut corbsize: u8 = 0; + + if (corbszcap & 4) == 4 { + corbsize = 2; + corbsize_bytes = 1024; + + self.corb_count = 256; + } else if (corbszcap & 2) == 2 { + corbsize = 1; + corbsize_bytes = 64; + + self.corb_count = 16; + } else if (corbszcap & 1) == 1 { + corbsize = 0; + corbsize_bytes = 8; + + self.corb_count = 2; + } + + assert!(self.corb_count != 0); + let addr = self.corb_base_phys; + self.set_address(addr); + self.regs.corbsize.write((corbsize_reg & 0xFC) | corbsize); + + self.reset_read_pointer()?; + let old_wp = self.regs.corbwp.read(); + self.regs.corbwp.write(old_wp & 0xFF00); + + Ok(()) + } + + pub fn start(&mut self) { + self.regs.corbctl.writef(CORBRUN, true); + } + + #[inline(never)] + pub fn stop(&mut self) -> Result<()> { + let timeout = Timeout::from_secs(1); + while self.regs.corbctl.readf(CORBRUN) { + self.regs.corbctl.writef(CORBRUN, false); + timeout.run().map_err(|()| { + log::error!("timeout on clearing CORBRUN"); + Error::new(EIO) + })?; + } + Ok(()) + } + + pub fn set_address(&mut self, addr: usize) { + self.regs.corblbase.write((addr & 0xFFFFFFFF) as u32); + self.regs.corbubase.write(((addr as u64) >> 32) as u32); + } + + pub fn reset_read_pointer(&mut self) -> Result<()> { + // 3.3.21 + + self.stop()?; + + // Set CORBRPRST to 1 + log::trace!("CORBRP {:X}", self.regs.corbrp.read()); + self.regs.corbrp.writef(CORBRPRST, true); + log::trace!("CORBRP {:X}", self.regs.corbrp.read()); + + { + // Wait for it to become 1 + let timeout = Timeout::from_secs(1); + while !self.regs.corbrp.readf(CORBRPRST) { + self.regs.corbrp.writef(CORBRPRST, true); + timeout.run().map_err(|()| { + log::error!("timeout on setting CORBRPRST"); + Error::new(EIO) + })?; + } + } + + // Clear the bit again + self.regs.corbrp.writef(CORBRPRST, false); + + { + // Read back the bit until zero to verify that it is cleared. + let timeout = Timeout::from_secs(1); + loop { + if !self.regs.corbrp.readf(CORBRPRST) { + break; + } + self.regs.corbrp.writef(CORBRPRST, false); + timeout.run().map_err(|()| { + log::error!("timeout on clearing CORBRPRST"); + Error::new(EIO) + })?; + } + } + + Ok(()) + } + + fn send_command(&mut self, cmd: u32) -> Result<()> { + { + // wait for the commands to finish + let timeout = Timeout::from_secs(1); + while (self.regs.corbwp.read() & 0xff) != (self.regs.corbrp.read() & 0xff) { + timeout.run().map_err(|()| { + log::error!("timeout on CORB command"); + Error::new(EIO) + })?; + } + } + let write_pos: usize = ((self.regs.corbwp.read() as usize & 0xFF) + 1) % self.corb_count; + unsafe { + *self.corb_base.offset(write_pos as isize) = cmd; + } + + self.regs.corbwp.write(write_pos as u16); + + log::trace!("Corb: {:08X}", cmd); + Ok(()) + } +} + +struct RirbRegs { + rirblbase: Mmio, + rirbubase: Mmio, + rirbwp: Mmio, + rintcnt: Mmio, + rirbctl: Mmio, + rirbsts: Mmio, + rirbsize: Mmio, + rsvd6: Mmio, +} + +struct Rirb { + regs: &'static mut RirbRegs, + rirb_base: *mut u64, + rirb_base_phys: usize, + rirb_rp: u16, + rirb_count: usize, +} + +impl Rirb { + pub fn new(regs_addr: usize, rirb_buff_phys: usize, rirb_buff_virt: *mut u64) -> Rirb { + unsafe { + Rirb { + regs: &mut *(regs_addr as *mut RirbRegs), + rirb_base: rirb_buff_virt, + rirb_rp: 0, + rirb_base_phys: rirb_buff_phys, + rirb_count: 0, + } + } + } + //Intel 4.4.1.3 + pub fn init(&mut self) -> Result<()> { + self.stop()?; + + let rirbsize_reg = self.regs.rirbsize.read(); + let rirbszcap = (rirbsize_reg >> 4) & 0xF; + + let mut rirbsize_bytes: usize = 0; + let mut rirbsize: u8 = 0; + + if (rirbszcap & 4) == 4 { + rirbsize = 2; + rirbsize_bytes = 2048; + + self.rirb_count = 256; + } else if (rirbszcap & 2) == 2 { + rirbsize = 1; + rirbsize_bytes = 128; + + self.rirb_count = 8; + } else if (rirbszcap & 1) == 1 { + rirbsize = 0; + rirbsize_bytes = 16; + + self.rirb_count = 2; + } + + assert!(self.rirb_count != 0); + + let addr = self.rirb_base_phys; + self.set_address(addr); + + self.reset_write_pointer(); + self.rirb_rp = 0; + + self.regs.rintcnt.write(1); + + Ok(()) + } + + pub fn start(&mut self) { + self.regs.rirbctl.writef(RIRBDMAEN | RINTCTL, true); + } + + pub fn stop(&mut self) -> Result<()> { + let timeout = Timeout::from_secs(1); + while self.regs.rirbctl.readf(RIRBDMAEN) { + self.regs.rirbctl.writef(RIRBDMAEN, false); + timeout.run().map_err(|()| { + log::error!("timeout on clearing RIRBDMAEN"); + Error::new(EIO) + })?; + } + Ok(()) + } + + pub fn set_address(&mut self, addr: usize) { + self.regs.rirblbase.write((addr & 0xFFFFFFFF) as u32); + self.regs.rirbubase.write(((addr as u64) >> 32) as u32); + } + + pub fn reset_write_pointer(&mut self) { + self.regs.rirbwp.writef(RIRBWPRST, true); + } + + fn read_response(&mut self) -> Result { + { + // wait for response + let timeout = Timeout::from_secs(1); + while (self.regs.rirbwp.read() & 0xff) == (self.rirb_rp & 0xff) { + timeout.run().map_err(|()| { + log::error!("timeout on RIRB response"); + Error::new(EIO) + })?; + } + } + let read_pos: u16 = (self.rirb_rp + 1) % self.rirb_count as u16; + + let res: u64; + unsafe { + res = *self.rirb_base.offset(read_pos as isize); + } + self.rirb_rp = read_pos; + log::trace!("Rirb: {:08X}", res); + Ok(res) + } +} + +struct ImmediateCommandRegs { + icoi: Mmio, + irii: Mmio, + ics: Mmio, + rsvd7: [Mmio; 6], +} + +pub struct ImmediateCommand { + regs: &'static mut ImmediateCommandRegs, +} + +impl ImmediateCommand { + pub fn new(regs_addr: usize) -> ImmediateCommand { + unsafe { + ImmediateCommand { + regs: &mut *(regs_addr as *mut ImmediateCommandRegs), + } + } + } + + pub fn cmd(&mut self, cmd: u32) -> Result { + { + // wait for ready + let timeout = Timeout::from_secs(1); + while self.regs.ics.readf(ICB) { + timeout.run().map_err(|()| { + log::error!("timeout on immediate command"); + Error::new(EIO) + })?; + } + } + + // write command + self.regs.icoi.write(cmd); + + // set ICB bit to send command + self.regs.ics.writef(ICB, true); + + { + // wait for IRV bit to be set to indicate a response is latched + let timeout = Timeout::from_secs(1); + while !self.regs.ics.readf(IRV) { + timeout.run().map_err(|()| { + log::error!("timeout on immediate response"); + Error::new(EIO) + })?; + } + } + + // read the result register twice, total of 8 bytes + // highest 4 will most likely be zeros (so I've heard) + let mut res: u64 = self.regs.irii.read() as u64; + res |= (self.regs.irii.read() as u64) << 32; + + // clear the bit so we know when the next response comes + self.regs.ics.writef(IRV, false); + + Ok(res) + } +} + +pub struct CommandBuffer { + // regs: &'static mut CommandBufferRegs, + corb: Corb, + rirb: Rirb, + icmd: ImmediateCommand, + + use_immediate_cmd: bool, + mem: Dma<[u8; 0x1000]>, +} + +impl CommandBuffer { + pub fn new(regs_addr: usize, mut cmd_buff: Dma<[u8; 0x1000]>) -> CommandBuffer { + let corb = Corb::new( + regs_addr + CORB_OFFSET, + cmd_buff.physical(), + cmd_buff.as_mut_ptr().cast(), + ); + let rirb = Rirb::new( + regs_addr + RIRB_OFFSET, + cmd_buff.physical() + CORB_BUFF_MAX_SIZE, + cmd_buff + .as_mut_ptr() + .cast::() + .wrapping_add(CORB_BUFF_MAX_SIZE) + .cast(), + ); + + let icmd = ImmediateCommand::new(regs_addr + ICMD_OFFSET); + + let cmdbuff = CommandBuffer { + corb, + rirb, + icmd, + + use_immediate_cmd: false, + + mem: cmd_buff, + }; + + cmdbuff + } + + pub fn init(&mut self, use_imm_cmds: bool) -> Result<()> { + self.corb.init()?; + self.rirb.init()?; + self.set_use_imm_cmds(use_imm_cmds)?; + Ok(()) + } + + pub fn stop(&mut self) -> Result<()> { + self.corb.stop()?; + self.rirb.stop()?; + Ok(()) + } + + pub fn cmd12(&mut self, addr: WidgetAddr, command: u32, data: u8) -> Result { + let mut ncmd: u32 = 0; + + ncmd |= (addr.0 as u32 & 0x00F) << 28; + ncmd |= (addr.1 as u32 & 0x0FF) << 20; + ncmd |= (command & 0xFFF) << 8; + ncmd |= (data as u32 & 0x0FF) << 0; + self.cmd(ncmd) + } + pub fn cmd4(&mut self, addr: WidgetAddr, command: u32, data: u16) -> Result { + let mut ncmd: u32 = 0; + + ncmd |= (addr.0 as u32 & 0x000F) << 28; + ncmd |= (addr.1 as u32 & 0x00FF) << 20; + ncmd |= (command & 0x000F) << 16; + ncmd |= (data as u32 & 0xFFFF) << 0; + self.cmd(ncmd) + } + + pub fn cmd(&mut self, cmd: u32) -> Result { + if self.use_immediate_cmd { + self.cmd_imm(cmd) + } else { + self.cmd_buff(cmd) + } + } + + pub fn cmd_imm(&mut self, cmd: u32) -> Result { + self.icmd.cmd(cmd) + } + + pub fn cmd_buff(&mut self, cmd: u32) -> Result { + self.corb.send_command(cmd)?; + self.rirb.read_response() + } + + pub fn set_use_imm_cmds(&mut self, use_imm: bool) -> Result<()> { + self.use_immediate_cmd = use_imm; + + if self.use_immediate_cmd { + self.corb.stop()?; + self.rirb.stop()?; + } else { + self.corb.start(); + self.rirb.start(); + } + Ok(()) + } +} diff --git a/drivers/audio/ihdad/src/hda/common.rs b/drivers/audio/ihdad/src/hda/common.rs new file mode 100644 index 0000000000..c2d5215e94 --- /dev/null +++ b/drivers/audio/ihdad/src/hda/common.rs @@ -0,0 +1,195 @@ +use std::fmt; +use std::mem::transmute; + +pub type HDANodeAddr = u16; +pub type HDACodecAddr = u8; + +pub type NodeAddr = u16; +pub type CodecAddr = u8; + +pub type WidgetAddr = (CodecAddr, NodeAddr); +/* +impl fmt::Display for WidgetAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:01X}:{:02X}\n", self.0, self.1) + } +}*/ + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum HDAWidgetType { + AudioOutput = 0x0, + AudioInput = 0x1, + AudioMixer = 0x2, + AudioSelector = 0x3, + PinComplex = 0x4, + Power = 0x5, + VolumeKnob = 0x6, + BeepGenerator = 0x7, + + VendorDefined = 0xf, +} + +impl fmt::Display for HDAWidgetType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Debug, PartialEq)] +#[repr(u8)] +pub enum DefaultDevice { + LineOut = 0x0, + Speaker = 0x1, + HPOut = 0x2, + CD = 0x3, + SPDIF = 0x4, + DigitalOtherOut = 0x5, + ModemLineSide = 0x6, + ModemHandsetSide = 0x7, + LineIn = 0x8, + AUX = 0x9, + MicIn = 0xA, + Telephony = 0xB, + SPDIFIn = 0xC, + DigitalOtherIn = 0xD, + Reserved = 0xE, + Other = 0xF, +} + +#[derive(Debug)] +#[repr(u8)] +pub enum PortConnectivity { + ConnectedToJack = 0x0, + NoPhysicalConnection = 0x1, + FixedFunction = 0x2, + JackAndInternal = 0x3, +} + +#[derive(Debug)] +#[repr(u8)] +pub enum GrossLocation { + ExternalOnPrimary = 0x0, + Internal = 0x1, + SeperateChasis = 0x2, + Other = 0x3, +} + +#[derive(Debug)] +#[repr(u8)] +pub enum GeometricLocation { + NA = 0x0, + Rear = 0x1, + Front = 0x2, + Left = 0x3, + Right = 0x4, + Top = 0x5, + Bottom = 0x6, + Special1 = 0x7, + Special2 = 0x8, + Special3 = 0x9, + Resvd1 = 0xA, + Resvd2 = 0xB, + Resvd3 = 0xC, + Resvd4 = 0xD, + Resvd5 = 0xE, + Resvd6 = 0xF, +} + +#[derive(Debug)] +#[repr(u8)] +pub enum Color { + Unknown = 0x0, + Black = 0x1, + Grey = 0x2, + Blue = 0x3, + Green = 0x4, + Red = 0x5, + Orange = 0x6, + Yellow = 0x7, + Purple = 0x8, + Pink = 0x9, + Resvd1 = 0xA, + Resvd2 = 0xB, + Resvd3 = 0xC, + Resvd4 = 0xD, + White = 0xE, + Other = 0xF, +} + +pub struct ConfigurationDefault { + value: u32, +} + +impl ConfigurationDefault { + pub fn from_u32(value: u32) -> ConfigurationDefault { + ConfigurationDefault { value: value } + } + + pub fn color(&self) -> Color { + unsafe { transmute(((self.value >> 12) & 0xF) as u8) } + } + + pub fn default_device(&self) -> DefaultDevice { + unsafe { transmute(((self.value >> 20) & 0xF) as u8) } + } + + pub fn port_connectivity(&self) -> PortConnectivity { + unsafe { transmute(((self.value >> 30) & 0x3) as u8) } + } + + pub fn gross_location(&self) -> GrossLocation { + unsafe { transmute(((self.value >> 28) & 0x3) as u8) } + } + + pub fn geometric_location(&self) -> GeometricLocation { + unsafe { transmute(((self.value >> 24) & 0x7) as u8) } + } + + pub fn is_output(&self) -> bool { + match self.default_device() { + DefaultDevice::LineOut + | DefaultDevice::Speaker + | DefaultDevice::HPOut + | DefaultDevice::CD + | DefaultDevice::SPDIF + | DefaultDevice::DigitalOtherOut + | DefaultDevice::ModemLineSide => true, + _ => false, + } + } + + pub fn is_input(&self) -> bool { + match self.default_device() { + DefaultDevice::ModemHandsetSide + | DefaultDevice::LineIn + | DefaultDevice::AUX + | DefaultDevice::MicIn + | DefaultDevice::Telephony + | DefaultDevice::SPDIFIn + | DefaultDevice::DigitalOtherIn => true, + _ => false, + } + } + + pub fn sequence(&self) -> u8 { + (self.value & 0xF) as u8 + } + + pub fn default_association(&self) -> u8 { + ((self.value >> 4) & 0xF) as u8 + } +} + +impl fmt::Display for ConfigurationDefault { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:?} {:?} {:?} {:?}", + self.default_device(), + self.color(), + self.gross_location(), + self.geometric_location() + ) + } +} diff --git a/drivers/audio/ihdad/src/hda/device.rs b/drivers/audio/ihdad/src/hda/device.rs new file mode 100755 index 0000000000..d48ed0def8 --- /dev/null +++ b/drivers/audio/ihdad/src/hda/device.rs @@ -0,0 +1,1082 @@ +#![allow(dead_code)] + +use std::cmp; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::fmt::Write; +use std::str; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::task::Poll; +use std::thread; +use std::time::Duration; + +use common::dma::Dma; +use common::io::{Io, Mmio}; +use common::timeout::Timeout; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::CallerCtx; +use redox_scheme::OpenResult; +use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, ENODEV, EWOULDBLOCK}; + +use spin::Mutex; +use syscall::schemev2::NewFdFlags; + +use super::common::*; +use super::BitsPerSample; +use super::BufferDescriptorListEntry; +use super::CommandBuffer; +use super::HDANode; +use super::OutputStream; +use super::StreamBuffer; +use super::StreamDescriptorRegs; + +// GCTL - Global Control +const CRST: u32 = 1 << 0; // 1 bit +const FNCTRL: u32 = 1 << 1; // 1 bit +const UNSOL: u32 = 1 << 8; // 1 bit + +// CORBCTL +const CMEIE: u8 = 1 << 0; // 1 bit +const CORBRUN: u8 = 1 << 1; // 1 bit + +// CORBSIZE +const CORBSZCAP: (u8, u8) = (4, 4); +const CORBSIZE: (u8, u8) = (0, 2); + +// CORBRP +const CORBRPRST: u16 = 1 << 15; + +// RIRBWP +const RIRBWPRST: u16 = 1 << 15; + +// RIRBCTL +const RINTCTL: u8 = 1 << 0; // 1 bit +const RIRBDMAEN: u8 = 1 << 1; // 1 bit + +// ICS +const ICB: u16 = 1 << 0; +const IRV: u16 = 1 << 1; + +// CORB and RIRB offset + +const COMMAND_BUFFER_OFFSET: usize = 0x40; + +const NUM_SUB_BUFFS: usize = 32; +const SUB_BUFF_SIZE: usize = 2048; + +enum Handle { + Todo, + Pcmout(usize, usize, usize), // Card, index, block_ptr + Pcmin(usize, usize, usize), // Card, index, block_ptr + StrBuf(Vec), +} + +#[repr(C, packed)] +#[allow(dead_code)] +struct Regs { + gcap: Mmio, + vmin: Mmio, + vmaj: Mmio, + outpay: Mmio, + inpay: Mmio, + gctl: Mmio, + wakeen: Mmio, + statests: Mmio, + gsts: Mmio, + rsvd0: [Mmio; 6], + outstrmpay: Mmio, + instrmpay: Mmio, + rsvd1: [Mmio; 4], + intctl: Mmio, + intsts: Mmio, + rsvd2: [Mmio; 8], + walclk: Mmio, + rsvd3: Mmio, + ssync: Mmio, + rsvd4: Mmio, + + corblbase: Mmio, + corbubase: Mmio, + corbwp: Mmio, + corbrp: Mmio, + corbctl: Mmio, + corbsts: Mmio, + corbsize: Mmio, + rsvd5: Mmio, + + rirblbase: Mmio, + rirbubase: Mmio, + rirbwp: Mmio, + rintcnt: Mmio, + rirbctl: Mmio, + rirbsts: Mmio, + rirbsize: Mmio, + rsvd6: Mmio, + + icoi: Mmio, + irii: Mmio, + ics: Mmio, + rsvd7: [Mmio; 6], + + dplbase: Mmio, // 0x70 + dpubase: Mmio, // 0x74 +} + +pub struct IntelHDA { + vend_prod: u32, + + base: usize, + regs: &'static mut Regs, + + //corb_rirb_base_phys: usize, + cmd: CommandBuffer, + + codecs: Vec, + + outputs: Vec, + inputs: Vec, + + widget_map: HashMap, + + output_pins: Vec, + input_pins: Vec, + + beep_addr: WidgetAddr, + + buff_desc: Dma<[BufferDescriptorListEntry; 256]>, + + output_streams: Vec, + + buffs: Vec>, + + int_counter: usize, + handles: Mutex>, + next_id: AtomicUsize, +} + +impl IntelHDA { + pub unsafe fn new(base: usize, vend_prod: u32) -> Result { + let regs = &mut *(base as *mut Regs); + + let buff_desc = Dma::<[BufferDescriptorListEntry; 256]>::zeroed() + .expect("Could not allocate physical memory for buffer descriptor list.") + .assume_init(); + + log::debug!( + "Virt: {:016X}, Phys: {:016X}", + buff_desc.as_ptr() as usize, + buff_desc.physical() + ); + + let cmd_buff = Dma::<[u8; 0x1000]>::zeroed() + .expect("Could not allocate physical memory for CORB and RIRB.") + .assume_init(); + + log::debug!( + "Virt: {:016X}, Phys: {:016X}", + cmd_buff.as_ptr() as usize, + cmd_buff.physical() + ); + let mut module = IntelHDA { + vend_prod, + base, + regs, + + cmd: CommandBuffer::new(base + COMMAND_BUFFER_OFFSET, cmd_buff), + + beep_addr: (0, 0), + + widget_map: HashMap::::new(), + + codecs: Vec::::new(), + + outputs: Vec::::new(), + inputs: Vec::::new(), + + output_pins: Vec::::new(), + input_pins: Vec::::new(), + + buff_desc, + + output_streams: Vec::::new(), + + buffs: Vec::>::new(), + + int_counter: 0, + handles: Mutex::new(BTreeMap::new()), + next_id: AtomicUsize::new(0), + }; + + module.init()?; + + module.info(); + module.enumerate()?; + + module.configure()?; + log::debug!("IHDA: Initialization finished."); + Ok(module) + } + + pub fn init(&mut self) -> Result<()> { + self.reset_controller()?; + + let use_immediate_command_interface = match self.vend_prod { + 0x8086_2668 => false, + _ => true, + }; + + self.cmd.init(use_immediate_command_interface)?; + self.init_interrupts(); + + Ok(()) + } + + pub fn init_interrupts(&mut self) { + // TODO: provide a function to enable certain interrupts + // This just enables the first output stream interupt and the global interrupt + + let iss = self.num_input_streams(); + self.regs + .intctl + .write((1 << 31) | /* (1 << 30) |*/ (1 << iss)); + } + + pub fn irq(&mut self) -> bool { + self.int_counter += 1; + + self.handle_interrupts() + } + + pub fn int_count(&self) -> usize { + self.int_counter + } + + pub fn read_node(&mut self, addr: WidgetAddr) -> Result { + let mut node = HDANode::new(); + let mut temp: u64; + + node.addr = addr; + + temp = self.cmd.cmd12(addr, 0xF00, 0x04)?; + + node.subnode_count = (temp & 0xff) as u16; + node.subnode_start = ((temp >> 16) & 0xff) as u16; + + if addr == (0, 0) { + return Ok(node); + } + temp = self.cmd.cmd12(addr, 0xF00, 0x04)?; + + node.function_group_type = (temp & 0xff) as u8; + + temp = self.cmd.cmd12(addr, 0xF00, 0x09)?; + node.capabilities = temp as u32; + + temp = self.cmd.cmd12(addr, 0xF00, 0x0E)?; + + node.conn_list_len = (temp & 0xFF) as u8; + + node.connections = self.node_get_connection_list(&node)?; + + node.connection_default = self.cmd.cmd12(addr, 0xF01, 0x00)? as u8; + + node.config_default = self.cmd.cmd12(addr, 0xF1C, 0x00)? as u32; + + Ok(node) + } + + pub fn node_get_connection_list(&mut self, node: &HDANode) -> Result> { + let len_field: u8 = (self.cmd.cmd12(node.addr, 0xF00, 0x0E)? & 0xFF) as u8; + + // Highest bit is if addresses are represented in longer notation + // lower 7 is actual count + + let count: u8 = len_field & 0x7F; + let use_long_addr: bool = (len_field >> 7) & 0x1 == 1; + + let mut current: u8 = 0; + + let mut list = Vec::::new(); + + while current < count { + let response: u32 = (self.cmd.cmd12(node.addr, 0xF02, current)? & 0xFFFFFFFF) as u32; + + if use_long_addr { + for i in 0..2 { + let addr_field = ((response >> (16 * i)) & 0xFFFF) as u16; + let addr = addr_field & 0x7FFF; + + if addr == 0 { + break; + } + + if (addr_field >> 15) & 0x1 == 0x1 { + for i in list.pop().unwrap().1..(addr + 1) { + list.push((node.addr.0, i)); + } + } else { + list.push((node.addr.0, addr)); + } + } + } else { + for i in 0..4 { + let addr_field = ((response >> (8 * i)) & 0xff) as u16; + let addr = addr_field & 0x7F; + + if addr == 0 { + break; + } + + if (addr_field >> 7) & 0x1 == 0x1 { + for i in list.pop().unwrap().1..(addr + 1) { + list.push((node.addr.0, i)); + } + } else { + list.push((node.addr.0, addr)); + } + } + } + + current = list.len() as u8; + } + + Ok(list) + } + + pub fn enumerate(&mut self) -> Result<()> { + self.output_pins.clear(); + self.input_pins.clear(); + + let codec: u8 = 0; + + let root = self.read_node((codec, 0))?; + + log::debug!("{}", root); + + let root_count = root.subnode_count; + let root_start = root.subnode_start; + + //FIXME: So basically the way this is set up is to only support one codec and hopes the first one is an audio + for i in 0..root_count { + let afg = self.read_node((codec, root_start + i))?; + log::debug!("{}", afg); + let afg_count = afg.subnode_count; + let afg_start = afg.subnode_start; + + for j in 0..afg_count { + let mut widget = self.read_node((codec, afg_start + j))?; + widget.is_widget = true; + match widget.widget_type() { + HDAWidgetType::AudioOutput => self.outputs.push(widget.addr), + HDAWidgetType::AudioInput => self.inputs.push(widget.addr), + HDAWidgetType::BeepGenerator => self.beep_addr = widget.addr, + HDAWidgetType::PinComplex => { + let config = widget.configuration_default(); + if config.is_output() { + self.output_pins.push(widget.addr); + } else if config.is_input() { + self.input_pins.push(widget.addr); + } + } + _ => {} + } + + log::debug!("{}", widget); + self.widget_map.insert(widget.addr(), widget); + } + } + + Ok(()) + } + + pub fn find_best_output_pin(&mut self) -> Result { + let outs = &self.output_pins; + if outs.len() == 1 { + return Ok(outs[0]); + } else if outs.len() > 1 { + //TODO: change output based on "unsolicited response" interrupts + // Check for devices in this order: Headphone, Speaker, Line Out + for supported_device in &[DefaultDevice::HPOut, DefaultDevice::Speaker] { + for &out in outs { + let widget = self.widget_map.get(&out).unwrap(); + let cd = widget.configuration_default(); + if cd.sequence() == 0 && &cd.default_device() == supported_device { + // Check for jack detect bit + let pin_caps = self.cmd.cmd12(widget.addr, 0xF00, 0x0C)?; + if pin_caps & (1 << 2) != 0 { + // Check for presence + let pin_sense = self.cmd.cmd12(widget.addr, 0xF09, 0)?; + if pin_sense & (1 << 31) == 0 { + // Skip if nothing is plugged in + continue; + } + } + return Ok(out); + } + } + } + } + Err(Error::new(ENODEV)) + } + + pub fn find_path_to_dac(&self, addr: WidgetAddr) -> Option> { + let widget = self.widget_map.get(&addr).unwrap(); + if widget.widget_type() == HDAWidgetType::AudioOutput { + Some(vec![addr]) + } else { + let connection = widget.connections.get(widget.connection_default as usize)?; + let mut path = self.find_path_to_dac(*connection)?; + path.insert(0, addr); + Some(path) + } + } + + /* + Here we update the buffers and split them into 128 byte sub chunks + because each BufferDescriptorList needs to be 128 byte aligned, + this makes it so each of the streams can have up to 128/16 (8) buffer descriptors + */ + /* + Vec of a Vec was doing something weird and causing the driver to hang. + So now we have a set of variables instead. + + + Fixed? + */ + + pub fn update_sound_buffers(&mut self) { + /* + for i in 0..self.buffs.len(){ + for j in 0.. min(self.buffs[i].len(), 128/16 ) { + self.buff_desc[i * 128/16 + j].set_address(self.buffs[i][j].phys()); + self.buff_desc[i * 128/16 + j].set_length(self.buffs[i][j].length() as u32); + self.buff_desc[i * 128/16 + j].set_interrupt_on_complete(true); + } + }*/ + + let r = self.get_output_stream_descriptor(0).unwrap(); + + self.output_streams + .push(OutputStream::new(NUM_SUB_BUFFS, SUB_BUFF_SIZE, r)); + + let o = self.output_streams.get_mut(0).unwrap(); + + for i in 0..NUM_SUB_BUFFS { + self.buff_desc[i].set_address((o.phys() + o.block_size() * i) as u64); + self.buff_desc[i].set_length(o.block_size() as u32); + self.buff_desc[i].set_interrupt_on_complete(true); + } + } + + pub fn configure(&mut self) -> Result<()> { + let outpin = self.find_best_output_pin()?; + + log::debug!("Best pin: {:01X}:{:02X}", outpin.0, outpin.1); + + let path = self.find_path_to_dac(outpin).unwrap(); + + let dac = *path.last().unwrap(); + let pin = *path.first().unwrap(); + + log::debug!("Path to DAC: {:X?}", path); + + // Set power state 0 (on) for all widgets in path + for &addr in &path { + self.set_power_state(addr, 0)?; + } + + // Pin enable (0x80 = headphone amp enable, 0x40 = output enable) + self.cmd.cmd12(pin, 0x707, 0xC0)?; + + // EAPD enable + self.cmd.cmd12(pin, 0x70C, 2)?; + + // Set DAC stream and channel + self.set_stream_channel(dac, 1, 0)?; + + self.update_sound_buffers(); + + log::debug!( + "Supported Formats: {:08X}", + self.get_supported_formats((0, 0x1))? + ); + log::debug!("Capabilities: {:08X}", self.get_capabilities(path[0])?); + + // Create output stream + let output = self.get_output_stream_descriptor(0).unwrap(); + output.set_address(self.buff_desc.physical()); + output.set_pcm_format(&super::SR_44_1, BitsPerSample::Bits16, 2); + output.set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); // number of bytes + output.set_stream_number(1); + output.set_last_valid_index((NUM_SUB_BUFFS - 1) as u16); + output.set_interrupt_on_completion(true); + + // Set DAC converter format + self.set_converter_format(dac, &super::SR_44_1, BitsPerSample::Bits16, 2)?; + + // Get DAC converter format + //TODO: should validate? + self.cmd.cmd12(dac, 0xA00, 0)?; + + // Unmute and set gain to 0db for input and output amplifiers on all widgets in path + for &addr in &path { + // Read widget capabilities + let caps = self.cmd.cmd12(addr, 0xF00, 0x09)?; + + //TODO: do we need to set any other indexes? + let left = true; + let right = true; + let index = 0; + let mute = false; + + // Check for input amp + if (caps & (1 << 1)) != 0 { + // Read input capabilities + let in_caps = self.cmd.cmd12(addr, 0xF00, 0x0D)?; + let in_gain = (in_caps & 0x7f) as u8; + // Set input gain + let output = false; + let input = true; + self.set_amplifier_gain_mute( + addr, output, input, left, right, index, mute, in_gain, + )?; + log::debug!("Set {:X?} input gain to 0x{:X}", addr, in_gain); + } + + // Check for output amp + if (caps & (1 << 2)) != 0 { + // Read output capabilities + let out_caps = self.cmd.cmd12(addr, 0xF00, 0x12)?; + let out_gain = (out_caps & 0x7f) as u8; + // Set output gain + let output = true; + let input = false; + self.set_amplifier_gain_mute( + addr, output, input, left, right, index, mute, out_gain, + )?; + log::debug!("Set {:X?} output gain to 0x{:X}", addr, out_gain); + } + } + + //TODO: implement hda-verb? + + output.run(); + { + log::debug!("Waiting for output 0 to start running..."); + let timeout = Timeout::from_secs(1); + while output.control() & (1 << 1) == 0 { + timeout.run().map_err(|()| { + log::error!("timeout on output running"); + Error::new(EIO) + })?; + } + } + + log::debug!( + "Output 0 CONTROL {:#X} STATUS {:#X} POS {:#X}", + output.control(), + output.status(), + output.link_position() + ); + Ok(()) + } + /* + + pub fn configure_vbox(&mut self) { + + let outpin = self.find_best_output_pin().expect("IHDA: No output pins?!"); + + log::debug!("Best pin: {:01X}:{:02X}", outpin.0, outpin.1); + + let path = self.find_path_to_dac(outpin).unwrap(); + log::debug!("Path to DAC: {:X?}", path); + + // Pin enable + self.cmd.cmd12((0,0xC), 0x707, 0x40); + + + // EAPD enable + self.cmd.cmd12((0,0xC), 0x70C, 2); + + self.set_stream_channel((0,0x3), 1, 0); + + self.update_sound_buffers(); + + + log::debug!("Supported Formats: {:08X}", self.get_supported_formats((0,0x1))); + log::debug!("Capabilities: {:08X}", self.get_capabilities((0,0x1))); + + let output = self.get_output_stream_descriptor(0).unwrap(); + + output.set_address(self.buff_desc_phys); + + output.set_pcm_format(&super::SR_44_1, BitsPerSample::Bits16, 2); + output.set_cyclic_buffer_length((NUM_SUB_BUFFS * SUB_BUFF_SIZE) as u32); + output.set_stream_number(1); + output.set_last_valid_index((NUM_SUB_BUFFS - 1) as u16); + output.set_interrupt_on_completion(true); + + + self.set_power_state((0,0x3), 0); // Power state 0 is fully on + self.set_converter_format((0,0x3), &super::SR_44_1, BitsPerSample::Bits16, 2); + + + self.cmd.cmd12((0,0x3), 0xA00, 0); + + // Unmute and set gain for pin complex and DAC + self.set_amplifier_gain_mute((0,0x3), true, true, true, true, 0, false, 0x7f); + self.set_amplifier_gain_mute((0,0xC), true, true, true, true, 0, false, 0x7f); + + output.run(); + + self.beep(1); + + } + + */ + + pub fn dump_codec(&self, codec: u8) -> String { + let mut string = String::new(); + + for (_, widget) in self.widget_map.iter() { + let _ = writeln!(string, "{}", widget); + } + + string + } + + // BEEP!! + pub fn beep(&mut self, div: u8) { + let addr = self.beep_addr; + if addr != (0, 0) { + let _ = self.cmd.cmd12(addr, 0xF0A, div); + } + } + + pub fn reset_controller(&mut self) -> Result<()> { + self.cmd.stop()?; + + self.regs.statests.write(0x7FFF); + + // 3.3.7 + { + let timeout = Timeout::from_secs(1); + self.regs.gctl.writef(CRST, false); + loop { + if !self.regs.gctl.readf(CRST) { + break; + } + timeout.run().map_err(|()| { + log::error!("failed to start reset"); + Error::new(EIO) + })?; + } + } + + thread::sleep(Duration::from_millis(1)); + + { + let timeout = Timeout::from_secs(1); + self.regs.gctl.writef(CRST, true); + loop { + if self.regs.gctl.readf(CRST) { + break; + } + timeout.run().map_err(|()| { + log::error!("failed to finish reset"); + Error::new(EIO) + })?; + } + } + + thread::sleep(Duration::from_millis(2)); + + let mut ticks: u32 = 0; + while self.regs.statests.read() == 0 { + ticks += 1; + if ticks > 10000 { + break; + } + } + + let statests = self.regs.statests.read(); + log::debug!("Statests: {:04X}", statests); + + for i in 0..15 { + if (statests >> i) & 0x1 == 1 { + self.codecs.push(i as CodecAddr); + } + } + Ok(()) + } + + pub fn num_output_streams(&self) -> usize { + let gcap = self.regs.gcap.read(); + ((gcap >> 12) & 0xF) as usize + } + + pub fn num_input_streams(&self) -> usize { + let gcap = self.regs.gcap.read(); + ((gcap >> 8) & 0xF) as usize + } + + pub fn num_bidirectional_streams(&self) -> usize { + let gcap = self.regs.gcap.read(); + ((gcap >> 3) & 0xF) as usize + } + + pub fn num_serial_data_out(&self) -> usize { + let gcap = self.regs.gcap.read(); + ((gcap >> 1) & 0x3) as usize + } + + pub fn info(&self) { + log::debug!( + "Intel HD Audio Version {}.{}", + self.regs.vmaj.read(), + self.regs.vmin.read() + ); + log::debug!("IHDA: Input Streams: {}", self.num_input_streams()); + log::debug!("IHDA: Output Streams: {}", self.num_output_streams()); + log::debug!( + "IHDA: Bidirectional Streams: {}", + self.num_bidirectional_streams() + ); + log::debug!("IHDA: Serial Data Outputs: {}", self.num_serial_data_out()); + log::debug!("IHDA: 64-Bit: {}", self.regs.gcap.read() & 1 == 1); + } + + fn get_input_stream_descriptor( + &self, + index: usize, + ) -> Option<&'static mut StreamDescriptorRegs> { + unsafe { + if index < self.num_input_streams() { + Some(&mut *((self.base + 0x80 + index * 0x20) as *mut StreamDescriptorRegs)) + } else { + None + } + } + } + + fn get_output_stream_descriptor( + &self, + index: usize, + ) -> Option<&'static mut StreamDescriptorRegs> { + unsafe { + if index < self.num_output_streams() { + Some( + &mut *((self.base + 0x80 + self.num_input_streams() * 0x20 + index * 0x20) + as *mut StreamDescriptorRegs), + ) + } else { + None + } + } + } + + fn get_bidirectional_stream_descriptor( + &self, + index: usize, + ) -> Option<&'static mut StreamDescriptorRegs> { + unsafe { + if index < self.num_bidirectional_streams() { + Some( + &mut *((self.base + + 0x80 + + self.num_input_streams() * 0x20 + + self.num_output_streams() * 0x20 + + index * 0x20) as *mut StreamDescriptorRegs), + ) + } else { + None + } + } + } + + fn set_dma_position_buff_addr(&mut self, addr: u64) { + let addr_val = addr & !0x7F; + self.regs.dplbase.write((addr_val & 0xFFFFFFFF) as u32); + self.regs.dpubase.write((addr_val >> 32) as u32); + } + + fn set_stream_channel(&mut self, addr: WidgetAddr, stream: u8, channel: u8) -> Result<()> { + let val = ((stream & 0xF) << 4) | (channel & 0xF); + self.cmd.cmd12(addr, 0x706, val)?; + Ok(()) + } + + fn set_power_state(&mut self, addr: WidgetAddr, state: u8) -> Result<()> { + self.cmd.cmd12(addr, 0x705, state & 0xF)?; + Ok(()) + } + + fn get_supported_formats(&mut self, addr: WidgetAddr) -> Result { + Ok(self.cmd.cmd12(addr, 0xF00, 0x0A)? as u32) + } + + fn get_capabilities(&mut self, addr: WidgetAddr) -> Result { + Ok(self.cmd.cmd12(addr, 0xF00, 0x09)? as u32) + } + + fn set_converter_format( + &mut self, + addr: WidgetAddr, + sr: &super::SampleRate, + bps: BitsPerSample, + channels: u8, + ) -> Result<()> { + let fmt = super::format_to_u16(sr, bps, channels); + self.cmd.cmd4(addr, 0x2, fmt)?; + Ok(()) + } + + fn set_amplifier_gain_mute( + &mut self, + addr: WidgetAddr, + output: bool, + input: bool, + left: bool, + right: bool, + index: u8, + mute: bool, + gain: u8, + ) -> Result<()> { + let mut payload: u16 = 0; + + if output { + payload |= 1 << 15; + } + if input { + payload |= 1 << 14; + } + if left { + payload |= 1 << 13; + } + if right { + payload |= 1 << 12; + } + if mute { + payload |= 1 << 7; + } + payload |= ((index as u16) & 0x0F) << 8; + payload |= (gain as u16) & 0x7F; + + self.cmd.cmd4(addr, 0x3, payload)?; + Ok(()) + } + + pub fn write_to_output(&mut self, index: u8, buf: &[u8]) -> Poll> { + let output = self.get_output_stream_descriptor(index as usize).unwrap(); + let os = self.output_streams.get_mut(index as usize).unwrap(); + + //let sample_size:usize = output.sample_size(); + let open_block = (output.link_position() as usize) / os.block_size(); + + //log::trace!("Status: {:02X} Pos: {:08X} Output CTL: {:06X}", output.status(), output.link_position(), output.control()); + + if os.current_block() == (open_block + 3) % NUM_SUB_BUFFS { + // Block if we already are 3 buffers ahead + Poll::Pending + } else { + Poll::Ready(os.write_block(buf)) + } + } + + pub fn handle_interrupts(&mut self) -> bool { + let intsts = self.regs.intsts.read(); + if ((intsts >> 31) & 1) == 1 { + // Global Interrupt Status + if ((intsts >> 30) & 1) == 1 { + // Controller Interrupt Status + self.handle_controller_interrupt(); + } + + let sis = intsts & 0x3FFFFFFF; + if sis != 0 { + self.handle_stream_interrupts(sis); + } + } + intsts != 0 + } + + pub fn handle_controller_interrupt(&mut self) {} + + pub fn handle_stream_interrupts(&mut self, sis: u32) { + let iss = self.num_input_streams(); + let oss = self.num_output_streams(); + let bss = self.num_bidirectional_streams(); + + for i in 0..iss { + if ((sis >> i) & 1) == 1 { + let input = self.get_input_stream_descriptor(i).unwrap(); + input.clear_interrupts(); + } + } + + for i in 0..oss { + if ((sis >> (i + iss)) & 1) == 1 { + let output = self.get_output_stream_descriptor(i).unwrap(); + output.clear_interrupts(); + } + } + + for i in 0..bss { + if ((sis >> (i + iss + oss)) & 1) == 1 { + let bid = self.get_bidirectional_stream_descriptor(i).unwrap(); + bid.clear_interrupts(); + } + } + } + + fn validate_path(&mut self, path: &Vec<&str>) -> bool { + log::debug!("Path: {:?}", path); + let mut it = path.iter(); + match it.next() { + Some(card_str) if (*card_str).starts_with("card") => { + match usize::from_str_radix(&(*card_str)[4..], 10) { + Ok(card_num) => { + log::debug!("Card# {}", card_num); + match it.next() { + Some(codec_str) if (*codec_str).starts_with("codec#") => { + match usize::from_str_radix(&(*codec_str)[6..], 10) { + Ok(_codec_num) => { + //let id = self.next_id.fetch_add(1, Ordering::SeqCst); + //self.handles.lock().insert(id, Handle::Disk(disk.clone(), 0)); + true + } + _ => false, + } + } + Some(pcmout_str) if (*pcmout_str).starts_with("pcmout") => { + match usize::from_str_radix(&(*pcmout_str)[6..], 10) { + Ok(pcmout_num) => { + log::debug!("pcmout {}", pcmout_num); + true + } + _ => false, + } + } + Some(pcmin_str) if (*pcmin_str).starts_with("pcmin") => { + match usize::from_str_radix(&(*pcmin_str)[6..], 10) { + Ok(pcmin_num) => { + log::debug!("pcmin {}", pcmin_num); + true + } + _ => false, + } + } + _ => false, + } + } + _ => false, + } + } + Some(cards_str) if *cards_str == "cards" => true, + _ => false, + } + } +} + +impl Drop for IntelHDA { + fn drop(&mut self) { + log::debug!("IHDA: Deallocating IHDA driver."); + } +} + +impl SchemeSync for IntelHDA { + fn open(&mut self, path: &str, _flags: usize, ctx: &CallerCtx) -> Result { + //let path: Vec<&str>; + /* + match str::from_utf8(_path) { + Ok(p) => { + path = p.split("/").collect(); + if !self.validate_path(&path) { + return Err(Error::new(EINVAL)); + + }, + Err(_) => {return Err(Error::new(EINVAL));}, + }*/ + + // TODO: + if ctx.uid != 0 { + return Err(Error::new(EACCES)); + } + let handle = match path.trim_matches('/') { + //TODO: allow multiple codecs + "codec" => Handle::StrBuf(self.dump_codec(0).into_bytes()), + _ => Handle::Todo, + }; + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + self.handles.lock().insert(id, handle); + + // TODO: always positioned? + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::POSITIONED, + }) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let handles = self.handles.lock(); + let Some(Handle::StrBuf(strbuf)) = handles.get(&id) else { + return Err(Error::new(EBADF)); + }; + + let src = usize::try_from(offset) + .ok() + .and_then(|o| strbuf.get(o..)) + .unwrap_or(&[]); + let len = src.len().min(buf.len()); + buf[..len].copy_from_slice(&src[..len]); + Ok(len) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let index = { + let mut handles = self.handles.lock(); + match handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::Todo => 0, + _ => return Err(Error::new(EBADF)), + } + }; + + //log::debug!("Int count: {}", self.int_counter); + + match self.write_to_output(index, buf) { + Poll::Ready(r) => r, + Poll::Pending => Err(Error::new(EWOULDBLOCK)), + } + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let mut handles = self.handles.lock(); + let _handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + let mut i = 0; + let scheme_path = b"/scheme/audiohw"; + while i < buf.len() && i < scheme_path.len() { + buf[i] = scheme_path[i]; + i += 1; + } + Ok(i) + } + + fn on_close(&mut self, id: usize) { + let _ = self.handles.lock().remove(&id); + } +} diff --git a/drivers/audio/ihdad/src/hda/mod.rs b/drivers/audio/ihdad/src/hda/mod.rs new file mode 100644 index 0000000000..7f01daf8ca --- /dev/null +++ b/drivers/audio/ihdad/src/hda/mod.rs @@ -0,0 +1,16 @@ +#![allow(dead_code)] +pub mod cmdbuff; +pub mod common; +pub mod device; +pub mod node; +pub mod stream; + +pub use self::node::*; +pub use self::stream::*; + +pub use self::cmdbuff::*; +pub use self::device::IntelHDA; +pub use self::stream::BitsPerSample; +pub use self::stream::BufferDescriptorListEntry; +pub use self::stream::StreamBuffer; +pub use self::stream::StreamDescriptorRegs; diff --git a/drivers/audio/ihdad/src/hda/node.rs b/drivers/audio/ihdad/src/hda/node.rs new file mode 100644 index 0000000000..06c5121fd7 --- /dev/null +++ b/drivers/audio/ihdad/src/hda/node.rs @@ -0,0 +1,108 @@ +use super::common::*; +use std::{fmt, mem}; + +#[derive(Clone)] +pub struct HDANode { + pub addr: WidgetAddr, + + // 0x4 + pub subnode_count: u16, + pub subnode_start: u16, + + // 0x5 + pub function_group_type: u8, + + // 0x9 + pub capabilities: u32, + + // 0xE + pub conn_list_len: u8, + + pub connections: Vec, + + pub connection_default: u8, + + pub is_widget: bool, + + pub config_default: u32, +} + +impl HDANode { + pub fn new() -> HDANode { + HDANode { + addr: (0, 0), + subnode_count: 0, + subnode_start: 0, + function_group_type: 0, + capabilities: 0, + conn_list_len: 0, + + config_default: 0, + is_widget: false, + connections: Vec::::new(), + connection_default: 0, + } + } + + pub fn widget_type(&self) -> HDAWidgetType { + unsafe { mem::transmute(((self.capabilities >> 20) & 0xF) as u8) } + } + + pub fn device_default(&self) -> Option { + if self.widget_type() != HDAWidgetType::PinComplex { + None + } else { + Some(unsafe { mem::transmute(((self.config_default >> 20) & 0xF) as u8) }) + } + } + + pub fn configuration_default(&self) -> ConfigurationDefault { + ConfigurationDefault::from_u32(self.config_default) + } + + pub fn addr(&self) -> WidgetAddr { + self.addr + } +} + +impl fmt::Display for HDANode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.addr == (0, 0) { + write!( + f, + "Addr: {:02X}:{:02X}, Root Node.", + self.addr.0, self.addr.1 + ) + } else if self.is_widget { + match self.widget_type() { + HDAWidgetType::PinComplex => write!( + f, + "Addr: {:02X}:{:02X}, Type: {:?}: {:?}, Inputs: {}/{}: {:X?}.", + self.addr.0, + self.addr.1, + self.widget_type(), + self.device_default().unwrap(), + self.connection_default, + self.conn_list_len, + self.connections + ), + _ => write!( + f, + "Addr: {:02X}:{:02X}, Type: {:?}, Inputs: {}/{}: {:X?}.", + self.addr.0, + self.addr.1, + self.widget_type(), + self.connection_default, + self.conn_list_len, + self.connections + ), + } + } else { + write!( + f, + "Addr: {:02X}:{:02X}, AFG: {}, Widget count {}.", + self.addr.0, self.addr.1, self.function_group_type, self.subnode_count + ) + } + } +} diff --git a/drivers/audio/ihdad/src/hda/stream.rs b/drivers/audio/ihdad/src/hda/stream.rs new file mode 100644 index 0000000000..caa3c36445 --- /dev/null +++ b/drivers/audio/ihdad/src/hda/stream.rs @@ -0,0 +1,387 @@ +use common::dma::Dma; +use common::io::{Io, Mmio}; +use std::cmp::min; +use std::ptr::copy_nonoverlapping; +use std::result; +use syscall::error::{Error, Result, EIO}; +use syscall::PAGE_SIZE; + +extern crate syscall; + +pub enum BaseRate { + BR44_1, + BR48, +} + +pub struct SampleRate { + base: BaseRate, + mult: u16, + div: u16, +} + +use self::BaseRate::{BR44_1, BR48}; + +pub const SR_8: SampleRate = SampleRate { + base: BR48, + mult: 1, + div: 6, +}; +pub const SR_11_025: SampleRate = SampleRate { + base: BR44_1, + mult: 1, + div: 4, +}; +pub const SR_16: SampleRate = SampleRate { + base: BR48, + mult: 1, + div: 3, +}; +pub const SR_22_05: SampleRate = SampleRate { + base: BR44_1, + mult: 1, + div: 2, +}; +pub const SR_32: SampleRate = SampleRate { + base: BR48, + mult: 2, + div: 3, +}; + +pub const SR_44_1: SampleRate = SampleRate { + base: BR44_1, + mult: 1, + div: 1, +}; +pub const SR_48: SampleRate = SampleRate { + base: BR48, + mult: 1, + div: 1, +}; +pub const SR_88_1: SampleRate = SampleRate { + base: BR44_1, + mult: 2, + div: 1, +}; +pub const SR_96: SampleRate = SampleRate { + base: BR48, + mult: 2, + div: 1, +}; +pub const SR_176_4: SampleRate = SampleRate { + base: BR44_1, + mult: 4, + div: 1, +}; +pub const SR_192: SampleRate = SampleRate { + base: BR48, + mult: 4, + div: 1, +}; + +#[repr(u8)] +pub enum BitsPerSample { + Bits8 = 0, + Bits16 = 1, + Bits20 = 2, + Bits24 = 3, + Bits32 = 4, +} + +pub fn format_to_u16(sr: &SampleRate, bps: BitsPerSample, channels: u8) -> u16 { + // 3.3.41 + + let base: u16 = match sr.base { + BaseRate::BR44_1 => 1 << 14, + BaseRate::BR48 => 0, + }; + + let mult = ((sr.mult - 1) & 0x7) << 11; + + let div = ((sr.div - 1) & 0x7) << 8; + + let bits = (bps as u16) << 4; + + let chan = ((channels - 1) & 0xF) as u16; + + let val: u16 = base | mult | div | bits | chan; + + val +} + +#[repr(C, packed)] +pub struct StreamDescriptorRegs { + ctrl_lo: Mmio, + ctrl_hi: Mmio, + status: Mmio, + link_pos: Mmio, + buff_length: Mmio, + last_valid_index: Mmio, + resv1: Mmio, + fifo_size_: Mmio, + format: Mmio, + resv2: Mmio, + buff_desc_list_lo: Mmio, + buff_desc_list_hi: Mmio, +} + +impl StreamDescriptorRegs { + pub fn status(&self) -> u8 { + self.status.read() + } + + pub fn set_status(&mut self, status: u8) { + self.status.write(status); + } + + pub fn control(&self) -> u32 { + let mut ctrl = self.ctrl_lo.read() as u32; + ctrl |= (self.ctrl_hi.read() as u32) << 16; + ctrl + } + + pub fn set_control(&mut self, control: u32) { + self.ctrl_lo.write((control & 0xFFFF) as u16); + self.ctrl_hi.write(((control >> 16) & 0xFF) as u8); + } + + pub fn set_pcm_format(&mut self, sr: &SampleRate, bps: BitsPerSample, channels: u8) { + // 3.3.41 + + let val = format_to_u16(sr, bps, channels); + self.format.write(val); + } + + pub fn fifo_size(&self) -> u16 { + self.fifo_size_.read() + } + + pub fn set_cyclic_buffer_length(&mut self, length: u32) { + self.buff_length.write(length); + } + + pub fn cyclic_buffer_length(&self) -> u32 { + self.buff_length.read() + } + + pub fn run(&mut self) { + let val = self.control() | (1 << 1); + self.set_control(val); + } + + pub fn stop(&mut self) { + let val = self.control() & !(1 << 1); + self.set_control(val); + } + + pub fn stream_number(&self) -> u8 { + ((self.control() >> 20) & 0xF) as u8 + } + + pub fn set_stream_number(&mut self, stream_number: u8) { + let val = (self.control() & 0x00FFFF) | (((stream_number & 0xF) as u32) << 20); + self.set_control(val); + } + + pub fn set_address(&mut self, addr: usize) { + self.buff_desc_list_lo.write((addr & 0xFFFFFFFF) as u32); + self.buff_desc_list_hi + .write((((addr as u64) >> 32) & 0xFFFFFFFF) as u32); + } + + pub fn set_last_valid_index(&mut self, index: u16) { + self.last_valid_index.write(index); + } + + pub fn link_position(&self) -> u32 { + self.link_pos.read() + } + + pub fn set_interrupt_on_completion(&mut self, enable: bool) { + let mut ctrl = self.control(); + if enable { + ctrl |= 1 << 2; + } else { + ctrl &= !(1 << 2); + } + self.set_control(ctrl); + } + + pub fn buffer_complete(&self) -> bool { + self.status.readf(1 << 2) + } + + pub fn clear_interrupts(&mut self) { + self.status.write(0x7 << 2); + } + + // get sample size in bytes + pub fn sample_size(&self) -> usize { + let format = self.format.read(); + let chan = (format & 0xF) as usize; + let bits = ((format >> 4) & 0xF) as usize; + match bits { + 0 => 1 * (chan + 1), + 1 => 2 * (chan + 1), + _ => 4 * (chan + 1), + } + } +} + +pub struct OutputStream { + buff: StreamBuffer, + + desc_regs: &'static mut StreamDescriptorRegs, +} + +impl OutputStream { + pub fn new( + block_count: usize, + block_length: usize, + regs: &'static mut StreamDescriptorRegs, + ) -> OutputStream { + OutputStream { + buff: StreamBuffer::new(block_length, block_count).unwrap(), + + desc_regs: regs, + } + } + + pub fn write_block(&mut self, buf: &[u8]) -> Result { + self.buff.write_block(buf) + } + + pub fn block_size(&self) -> usize { + self.buff.block_size() + } + + pub fn block_count(&self) -> usize { + self.buff.block_count() + } + + pub fn current_block(&self) -> usize { + self.buff.current_block() + } + + pub fn addr(&self) -> usize { + self.buff.addr() + } + + pub fn phys(&self) -> usize { + self.buff.phys() + } +} + +#[repr(C, packed)] +pub struct BufferDescriptorListEntry { + addr_low: Mmio, + addr_high: Mmio, + len: Mmio, + ioc_resv: Mmio, +} + +impl BufferDescriptorListEntry { + pub fn address(&self) -> u64 { + (self.addr_low.read() as u64) | ((self.addr_high.read() as u64) << 32) + } + + pub fn set_address(&mut self, addr: u64) { + self.addr_low.write(addr as u32); + self.addr_high.write((addr >> 32) as u32); + } + + pub fn length(&self) -> u32 { + self.len.read() + } + + pub fn set_length(&mut self, length: u32) { + self.len.write(length) + } + + pub fn interrupt_on_completion(&self) -> bool { + (self.ioc_resv.read() & 0x1) == 0x1 + } + + pub fn set_interrupt_on_complete(&mut self, ioc: bool) { + self.ioc_resv.writef(1, ioc); + } +} + +pub struct StreamBuffer { + mem: Dma<[u8]>, + + block_cnt: usize, + block_len: usize, + + cur_pos: usize, +} + +impl StreamBuffer { + pub fn new( + block_length: usize, + block_count: usize, + ) -> result::Result { + let page_aligned_size = (block_length * block_count).next_multiple_of(PAGE_SIZE); + let mem = unsafe { + Dma::zeroed_slice(page_aligned_size) + .map_err(|_| "Could not allocate physical memory for buffer.")? + .assume_init() + }; + + Ok(StreamBuffer { + mem, + block_len: block_length, + block_cnt: block_count, + cur_pos: 0, + }) + } + + pub fn length(&self) -> usize { + self.block_len * self.block_cnt + } + + pub fn addr(&self) -> usize { + self.mem.as_ptr() as usize + } + + pub fn phys(&self) -> usize { + self.mem.physical() + } + + pub fn block_size(&self) -> usize { + self.block_len + } + + pub fn block_count(&self) -> usize { + self.block_cnt + } + + pub fn current_block(&self) -> usize { + self.cur_pos + } + + pub fn write_block(&mut self, buf: &[u8]) -> Result { + if buf.len() != self.block_size() { + return Err(Error::new(EIO)); + } + let len = min(self.block_size(), buf.len()); + + //log::trace!("Phys: {:X} Virt: {:X} Offset: {:X} Len: {:X}", self.phys(), self.addr(), self.current_block() * self.block_size(), len); + unsafe { + copy_nonoverlapping( + buf.as_ptr(), + (self.addr() + self.current_block() * self.block_size()) as *mut u8, + len, + ); + } + + self.cur_pos += 1; + self.cur_pos %= self.block_count(); + + Ok(len) + } +} +impl Drop for StreamBuffer { + fn drop(&mut self) { + log::debug!("IHDA: Deallocating buffer."); + } +} diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs new file mode 100755 index 0000000000..687b7745d5 --- /dev/null +++ b/drivers/audio/ihdad/src/main.rs @@ -0,0 +1,146 @@ +extern crate bitflags; +extern crate event; +extern crate spin; +extern crate syscall; + +use redox_scheme::wrappers::ReadinessBased; +use redox_scheme::Socket; +use std::cell::RefCell; +use std::fs::File; +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; +use std::usize; + +use event::{user_data, EventQueue}; +use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; +use pcid_interface::PciFunctionHandle; + +pub mod hda; + +/* +VEND:PROD +Virtualbox 8086:2668 +QEMU ICH9 8086:293E +82801H ICH8 8086:284B +*/ + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let mut pcid_handle = PciFunctionHandle::connect_default(); + + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_ihda"); + + common::setup_logging( + "audio", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + log::info!("IHDA {}", pci_config.func.display()); + + let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; + + let mut irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad"); + + { + let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16) + | (pci_config.func.full_device_id.device_id as u32); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = + EventQueue::::new().expect("ihdad: Could not create event queue."); + let device = RefCell::new(unsafe { + hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device") + }); + let socket = Socket::nonblock("audiohw").expect("ihdad: failed to create socket"); + let mut readiness_based = ReadinessBased::new(&socket, 16); + + daemon.ready().expect("ihdad: failed to signal readiness"); + + event_queue + .subscribe( + socket.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + irq_file.irq_handle().as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("ihdad: failed to enter null namespace"); + + let all = [Source::Irq, Source::Scheme]; + + for event in all + .into_iter() + .chain(event_queue.map(|e| e.expect("failed to get next event").user_data)) + { + match event { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.irq_handle().read(&mut irq).unwrap(); + + if !device.borrow_mut().irq() { + continue; + } + irq_file.irq_handle().write(&mut irq).unwrap(); + + readiness_based + .poll_all_requests(|| device.borrow_mut()) + .expect("ihdad: failed to poll requests"); + + /* + let next_read = device_irq.next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + Source::Scheme => { + if !readiness_based + .read_requests() + .expect("ihdad: failed to read from socket") + { + break; + } + readiness_based.process_requests(|| device.borrow_mut()); + if !readiness_based + .write_responses() + .expect("ihdad: failed to write to socket") + { + break; + } + + /* + let next_read = device.borrow().next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + } + } + + std::process::exit(0); + } +} + +fn main() { + // Daemonize + redox_daemon::Daemon::new(daemon).expect("ihdad: failed to daemonize"); +} diff --git a/drivers/audio/sb16d/Cargo.toml b/drivers/audio/sb16d/Cargo.toml new file mode 100644 index 0000000000..7e6112edaf --- /dev/null +++ b/drivers/audio/sb16d/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sb16d" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "2" +common = { path = "../../common" } +libredox = "0.1.3" +log = "0.4" +redox-daemon = "0.1" +redox_event = "0.4.1" +redox_syscall = "0.5" +spin = "0.9" +redox-scheme = "0.8.2" diff --git a/drivers/audio/sb16d/src/device.rs b/drivers/audio/sb16d/src/device.rs new file mode 100644 index 0000000000..ae9f4a0f04 --- /dev/null +++ b/drivers/audio/sb16d/src/device.rs @@ -0,0 +1,229 @@ +#![allow(dead_code)] + +use std::collections::BTreeMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{thread, time}; + +use common::io::{Io, Pio, ReadOnly, WriteOnly}; + +use redox_scheme::scheme::SchemeSync; +use redox_scheme::CallerCtx; +use redox_scheme::OpenResult; +use syscall::error::{Error, Result, EACCES, EBADF, ENODEV}; +use syscall::schemev2::NewFdFlags; + +use spin::Mutex; + +const NUM_SUB_BUFFS: usize = 32; +const SUB_BUFF_SIZE: usize = 2048; + +enum Handle { + Todo, +} + +#[allow(dead_code)] +pub struct Sb16 { + handles: Mutex>, + next_id: AtomicUsize, + pub(crate) irqs: Vec, + dmas: Vec, + // Regs + /* 0x04 */ mixer_addr: WriteOnly>, + /* 0x05 */ mixer_data: Pio, + /* 0x06 */ dsp_reset: WriteOnly>, + /* 0x0A */ dsp_read_data: ReadOnly>, + /* 0x0C */ dsp_write_data: WriteOnly>, + /* 0x0C */ dsp_write_status: ReadOnly>, + /* 0x0E */ dsp_read_status: ReadOnly>, +} + +impl Sb16 { + pub unsafe fn new(addr: u16) -> Result { + let mut module = Sb16 { + handles: Mutex::new(BTreeMap::new()), + next_id: AtomicUsize::new(0), + irqs: Vec::new(), + dmas: Vec::new(), + // Regs + mixer_addr: WriteOnly::new(Pio::new(addr + 0x04)), + mixer_data: Pio::new(addr + 0x05), + dsp_reset: WriteOnly::new(Pio::new(addr + 0x06)), + dsp_read_data: ReadOnly::new(Pio::new(addr + 0x0A)), + dsp_write_data: WriteOnly::new(Pio::new(addr + 0x0C)), + dsp_write_status: ReadOnly::new(Pio::new(addr + 0x0C)), + dsp_read_status: ReadOnly::new(Pio::new(addr + 0x0E)), + }; + + module.init()?; + + Ok(module) + } + + fn mixer_read(&mut self, index: u8) -> u8 { + self.mixer_addr.write(index); + self.mixer_data.read() + } + + fn mixer_write(&mut self, index: u8, value: u8) { + self.mixer_addr.write(index); + self.mixer_data.write(value); + } + + fn dsp_read(&mut self) -> Result { + // Bit 7 must be 1 before data can be sent + while !self.dsp_read_status.readf(1 << 7) { + //TODO: timeout! + std::thread::yield_now(); + } + + Ok(self.dsp_read_data.read()) + } + + fn dsp_write(&mut self, value: u8) -> Result<()> { + // Bit 7 must be 0 before data can be sent + while self.dsp_write_status.readf(1 << 7) { + //TODO: timeout! + std::thread::yield_now(); + } + + self.dsp_write_data.write(value); + Ok(()) + } + + fn init(&mut self) -> Result<()> { + // Perform DSP reset + { + // Write 1 to reset port + self.dsp_reset.write(1); + + // Wait 3us + thread::sleep(time::Duration::from_micros(3)); + + // Write 0 to reset port + self.dsp_reset.write(0); + + //TODO: Wait for ready byte (0xAA) using read status + thread::sleep(time::Duration::from_micros(100)); + + let ready = self.dsp_read()?; + if ready != 0xAA { + log::error!("ready byte was 0x{:02X} instead of 0xAA", ready); + return Err(Error::new(ENODEV)); + } + } + + // Read DSP version + { + self.dsp_write(0xE1)?; + + let major = self.dsp_read()?; + let minor = self.dsp_read()?; + log::info!("DSP version {}.{:02}", major, minor); + + if major != 4 { + log::error!("Unsupported DSP major version {}", major); + return Err(Error::new(ENODEV)); + } + } + + // Get available IRQs and DMAs + { + self.irqs.clear(); + let irq_mask = self.mixer_read(0x80); + if (irq_mask & (1 << 0)) != 0 { + self.irqs.push(2); + } + if (irq_mask & (1 << 1)) != 0 { + self.irqs.push(5); + } + if (irq_mask & (1 << 2)) != 0 { + self.irqs.push(7); + } + if (irq_mask & (1 << 3)) != 0 { + self.irqs.push(10); + } + + self.dmas.clear(); + let dma_mask = self.mixer_read(0x81); + if (dma_mask & (1 << 0)) != 0 { + self.dmas.push(0); + } + if (dma_mask & (1 << 1)) != 0 { + self.dmas.push(1); + } + if (dma_mask & (1 << 3)) != 0 { + self.dmas.push(3); + } + if (dma_mask & (1 << 5)) != 0 { + self.dmas.push(5); + } + if (dma_mask & (1 << 6)) != 0 { + self.dmas.push(6); + } + if (dma_mask & (1 << 7)) != 0 { + self.dmas.push(7); + } + + log::info!("IRQs {:02X?} DMAs {:02X?}", self.irqs, self.dmas); + } + + // Set output sample rate to 44100 Hz (Redox OS standard) + { + let rate = 44100u16; + self.dsp_write(0x41)?; + self.dsp_write((rate >> 8) as u8)?; + self.dsp_write(rate as u8)?; + } + + Ok(()) + } + + pub fn irq(&mut self) -> bool { + //TODO + false + } +} + +impl SchemeSync for Sb16 { + fn open(&mut self, _path: &str, _flags: usize, ctx: &CallerCtx) -> Result { + if ctx.uid == 0 { + let id = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.lock().insert(id, Handle::Todo); + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::empty(), + }) + } else { + Err(Error::new(EACCES)) + } + } + + fn write( + &mut self, + _id: usize, + _buf: &[u8], + _offset: u64, + _flags: u32, + _ctx: &CallerCtx, + ) -> Result { + //TODO + Err(Error::new(EBADF)) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let mut handles = self.handles.lock(); + let _handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + let mut i = 0; + let scheme_path = b"/scheme/audiohw"; + while i < buf.len() && i < scheme_path.len() { + buf[i] = scheme_path[i]; + i += 1; + } + Ok(i) + } + + fn on_close(&mut self, id: usize) { + let _ = self.handles.lock().remove(&id); + } +} diff --git a/drivers/audio/sb16d/src/main.rs b/drivers/audio/sb16d/src/main.rs new file mode 100644 index 0000000000..4c47f46d1d --- /dev/null +++ b/drivers/audio/sb16d/src/main.rs @@ -0,0 +1,117 @@ +//#![deny(warnings)] + +use libredox::{flag, Fd}; +use redox_scheme::wrappers::ReadinessBased; +use redox_scheme::Socket; +use std::cell::RefCell; +use std::{env, usize}; + +use event::{user_data, EventQueue}; + +pub mod device; + +fn main() { + let mut args = env::args().skip(1); + + let addr_str = args.next().unwrap_or("220".to_string()); + let addr = u16::from_str_radix(&addr_str, 16).expect("sb16: failed to parse address"); + + println!(" + sb16 at 0x{:X}\n", addr); + + // Daemonize + redox_daemon::Daemon::new(move |daemon| { + common::setup_logging( + "audio", + "pci", + "sb16", + common::output_level(), + common::file_level(), + ); + + common::acquire_port_io_rights().expect("sb16d: failed to acquire port IO rights"); + + let device = RefCell::new(unsafe { + device::Sb16::new(addr).expect("sb16d: failed to allocate device") + }); + let socket = Socket::nonblock("audiohw").expect("sb16d: failed to create socket"); + let mut readiness_based = ReadinessBased::new(&socket, 16); + + //TODO: error on multiple IRQs? + let irq_file = match device.borrow().irqs.first() { + Some(irq) => Fd::open(&format!("/scheme/irq/{}", irq), flag::O_RDWR, 0) + .expect("sb16d: failed to open IRQ file"), + None => panic!("sb16d: no IRQs found"), + }; + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = + EventQueue::::new().expect("sb16d: Could not create event queue."); + event_queue + .subscribe(irq_file.raw(), Source::Irq, event::EventFlags::READ) + .unwrap(); + event_queue + .subscribe( + socket.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + daemon.ready().expect("sb16d: failed to signal readiness"); + + libredox::call::setrens(0, 0).expect("sb16d: failed to enter null namespace"); + + let all = [Source::Irq, Source::Scheme]; + + for event in all + .into_iter() + .chain(event_queue.map(|e| e.expect("sb16d: failed to get next event").user_data)) + { + match event { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.read(&mut irq).unwrap(); + + if !device.borrow_mut().irq() { + continue; + } + irq_file.write(&mut irq).unwrap(); + + readiness_based + .poll_all_requests(|| device.borrow_mut()) + .expect("sb16d: failed to poll requests"); + + /* + let next_read = device_irq.next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + Source::Scheme => { + if !readiness_based + .read_requests() + .expect("sb16d: failed to read from socket") + { + break; + } + readiness_based.process_requests(|| device.borrow_mut()); + if !readiness_based + .write_responses() + .expect("sb16d: failed to write to socket") + { + break; + } + } + } + } + + std::process::exit(0); + }) + .expect("sb16d: failed to daemonize"); +} diff --git a/drivers/common/Cargo.toml b/drivers/common/Cargo.toml new file mode 100644 index 0000000000..8cea98c907 --- /dev/null +++ b/drivers/common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2021" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libredox = "0.1.3" +log = "0.4" +redox_syscall = { version = "0.5.10", features = ["std"] } +redox-log = "0.1.2" diff --git a/drivers/common/src/dma.rs b/drivers/common/src/dma.rs new file mode 100644 index 0000000000..98f7abc23d --- /dev/null +++ b/drivers/common/src/dma.rs @@ -0,0 +1,265 @@ +use std::mem::{self, size_of, MaybeUninit}; +use std::ops::{Deref, DerefMut}; +use std::ptr; +use std::sync::LazyLock; + +use libredox::call::MmapArgs; +use libredox::{error::Result, flag, Fd}; +use syscall::PAGE_SIZE; + +use crate::{MemoryType, VirtaddrTranslationHandle}; + +/// Defines the platform-specific memory type for DMA operations +/// +/// - On x86 systems, DMA uses Write-back memory ([MemoryType::Writeback]) +/// - On aarch64 systems, DMA uses uncacheable memory ([MemoryType::Uncacheable]) +const DMA_MEMTY: MemoryType = { + if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + // x86 ensures cache coherence with DMA memory + MemoryType::Writeback + } else if cfg!(target_arch = "aarch64") { + // aarch64 currently must map DMA memory without caching to ensure coherence + MemoryType::Uncacheable + } else if cfg!(target_arch = "riscv64") { + // FIXME check this out more + MemoryType::Uncacheable + } else { + panic!("invalid arch") + } +}; + +/// Returns a file descriptor for zeroized physically-contiguous DMA memory. +/// +/// # Returns +/// +/// A [Result] containing: +/// - '[Ok]' - A [Fd] (file descriptor) to zeroized, physically continuous DMA usable memory +/// - '[Err]' - The error returned by the provider of the /scheme/memory/zeroed scheme. +/// +/// # Errors +/// +/// This function can return an error in the following case: +/// +/// - The request for the physical memory fails. +pub(crate) fn phys_contiguous_fd() -> Result { + Fd::open( + &format!("/scheme/memory/zeroed@{DMA_MEMTY}?phys_contiguous"), + flag::O_CLOEXEC, + 0, + ) +} + +/// Allocates a chunk of physical memory for DMA, and then maps it to virtual memory. +/// +/// # Arguments +/// 'length: [usize]' - The length of the memory region. Must be a multiple of [PAGE_SIZE] +/// +/// # Returns +/// +/// This function returns a [Result] containing the following: +/// - A '[Ok]([usize], *[mut] ())' containing a Tuple of the physical address of the region, and a raw pointer to that region in virtual memory. +/// - An '[Err]' - containing the error for the operation. +/// +/// # Errors +/// +/// This function asserts if: +/// - length is not a multiple of [PAGE_SIZE] +/// +/// This function returns an error if: +/// - A file descriptor to physically contiguous memory of type [DMA_MEMTY] could not be acquired +/// - A virtual mapping for the physically contiguous memory could not be created +/// - The virtual address returned by the memory manager was invalid. +fn alloc_and_map(length: usize, handle: &VirtaddrTranslationHandle) -> Result<(usize, *mut ())> { + assert_eq!(length % PAGE_SIZE, 0); + unsafe { + let fd = phys_contiguous_fd()?; + let virt = libredox::call::mmap(MmapArgs { + fd: fd.raw(), + offset: 0, // ignored + addr: core::ptr::null_mut(), // ignored + length, + flags: flag::MAP_PRIVATE, + prot: flag::PROT_READ | flag::PROT_WRITE, + })?; + let phys = handle.translate(virt as usize)?; + for i in 1..length.div_ceil(PAGE_SIZE) { + debug_assert_eq!( + handle.translate(virt as usize + i * PAGE_SIZE), + Ok(phys + i * PAGE_SIZE), + "NOT CONTIGUOUS" + ); + } + Ok((phys, virt as *mut ())) + } +} + +/// A safe accessor for DMA memory. +pub struct Dma { + /// The physical address of the memory + phys: usize, + /// The page-aligned length of the memory. Will be a multiple of [PAGE_SIZE] + aligned_len: usize, + /// The pointer to the Dma memory in the virtual address space. + virt: *mut T, +} + +impl Dma { + /// [Dma] constructor that allocates and initializes a region of DMA memory with the page-aligned + /// size and initial value of some T + /// + /// # Arguments + /// 'value: T' - The initial value to write to the allocated region + /// + /// # Returns + /// + /// This function returns a [Result] containing the following: + /// + /// - A '[Ok] (`[Dma]`)' containing the initialized region + /// - An '[Err]' containing an error. + pub fn new(value: T) -> Result { + unsafe { + let mut zeroed = Self::zeroed()?; + zeroed.as_mut_ptr().write(value); + Ok(zeroed.assume_init()) + } + } + + /// [Dma] constructor that allocates and zeroizes a memory region of the page-aligned size of T + /// + /// # Returns + /// + /// This function returns a [Result] containing the following: + /// + /// - A '[Ok] (`[Dma]<[MaybeUninit]>`)' containing the allocated and zeroized memory + /// - An '[Err]' containing an error. + pub fn zeroed() -> Result>> { + let aligned_len = size_of::().next_multiple_of(PAGE_SIZE); + let (phys, virt) = alloc_and_map(aligned_len, &*VIRTTOPHYS_HANDLE)?; + Ok(Dma { + phys, + virt: virt.cast(), + aligned_len, + }) + } +} + +impl Dma> { + /// Assumes that possibly uninitialized DMA memory has been initialized, and returns a new + /// instance of an object of type `[Dma]`. + /// + /// # Returns + /// - `[Dma]` - The original structure without the [MaybeUninit] wrapper around its contents. + /// + /// # Notes + /// - This is unsafe because it assumes that the memory stored within the `[Dma]` is a valid + /// instance of T. If it isn't (for example -- if it was initialized with [Dma::zeroed]), + /// then the underlying memory may not contain the expected T structure. + pub unsafe fn assume_init(self) -> Dma { + let Dma { + phys, + aligned_len, + virt, + } = self; + mem::forget(self); + + Dma { + phys, + aligned_len, + virt: virt.cast(), + } + } +} +impl Dma { + /// Returns the physical address of the physical memory that this [Dma] structure references. + /// + /// # Returns + /// [usize] - The physical address of the memory. + pub fn physical(&self) -> usize { + self.phys + } +} +// TODO: there should exist a "context" struct that drivers create at start, which would be passed +// to the respective functions +static VIRTTOPHYS_HANDLE: LazyLock = LazyLock::new(|| { + VirtaddrTranslationHandle::new().expect("failed to acquire virttophys translation handle") +}); + +impl Dma<[T]> { + /// Returns a [Dma] object containing a zeroized slice of T with a given count. + /// + /// # Arguments + /// + /// - 'count: [usize]' - The number of elements of type T in the allocated slice. + pub fn zeroed_slice(count: usize) -> Result]>> { + let aligned_len = count + .checked_mul(size_of::()) + .unwrap() + .next_multiple_of(PAGE_SIZE); + let (phys, virt) = alloc_and_map(aligned_len, &*VIRTTOPHYS_HANDLE)?; + + Ok(Dma { + phys, + aligned_len, + virt: ptr::slice_from_raw_parts_mut(virt.cast(), count), + }) + } + + /// Casts the slice from type T to type U. + /// + /// # Returns + /// '`[DMA]`' - A cast handle to the Dma memory. + pub unsafe fn cast_slice(self) -> Dma<[U]> { + let Dma { + phys, + virt, + aligned_len, + } = self; + core::mem::forget(self); + + Dma { + phys, + virt: virt as *mut [U], + aligned_len, + } + } +} +impl Dma<[MaybeUninit]> { + /// See [`Dma>::assume_init`] + pub unsafe fn assume_init(self) -> Dma<[T]> { + let &Dma { + phys, + aligned_len, + virt, + } = &self; + mem::forget(self); + + Dma { + phys, + aligned_len, + virt: virt as *mut [T], + } + } +} + +impl Deref for Dma { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.virt } + } +} + +impl DerefMut for Dma { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.virt } + } +} + +impl Drop for Dma { + fn drop(&mut self) { + unsafe { + ptr::drop_in_place(self.virt); + let _ = libredox::call::munmap(self.virt as *mut (), self.aligned_len); + } + } +} diff --git a/drivers/common/src/io.rs b/drivers/common/src/io.rs new file mode 100644 index 0000000000..b79d4cabe8 --- /dev/null +++ b/drivers/common/src/io.rs @@ -0,0 +1,97 @@ +use core::{ + cmp::PartialEq, + ops::{BitAnd, BitOr, Not}, +}; + +mod mmio; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod pio; + +pub use mmio::*; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use pio::*; + +/// IO abstraction +pub trait Io { + /// Value type for IO, usually some unsigned number + type Value: Copy + + PartialEq + + BitAnd + + BitOr + + Not; + + /// Read the underlying valu2e + fn read(&self) -> Self::Value; + /// Write the underlying value + fn write(&mut self, value: Self::Value); + + /// Check whether the underlying value contains bit flags + #[inline(always)] + fn readf(&self, flags: Self::Value) -> bool { + (self.read() & flags) as Self::Value == flags + } + + /// Enable or disable specific bit flags + #[inline(always)] + fn writef(&mut self, flags: Self::Value, value: bool) { + let tmp: Self::Value = match value { + true => self.read() | flags, + false => self.read() & !flags, + }; + self.write(tmp); + } +} + +/// Read-only IO +#[repr(transparent)] +pub struct ReadOnly { + inner: I, +} + +impl ReadOnly { + /// Wraps IO + pub const fn new(inner: I) -> ReadOnly { + ReadOnly { inner } + } +} + +impl ReadOnly { + /// Calls [Io::read] + #[inline(always)] + pub fn read(&self) -> I::Value { + self.inner.read() + } + + /// Calls [Io::readf] + #[inline(always)] + pub fn readf(&self, flags: I::Value) -> bool { + self.inner.readf(flags) + } +} + +#[repr(transparent)] +/// Write-only IO +pub struct WriteOnly { + inner: I, +} + +impl WriteOnly { + /// Wraps IO + pub const fn new(inner: I) -> WriteOnly { + WriteOnly { inner } + } +} + +impl WriteOnly { + /// Calls [Io::write] + #[inline(always)] + pub fn write(&mut self, value: I::Value) { + self.inner.write(value) + } + + #[inline(always)] + /// Calls [Io::writef] + pub fn writef(&mut self, flags: I::Value, value: bool) { + self.inner.writef(flags, value) + } +} diff --git a/drivers/common/src/io/mmio.rs b/drivers/common/src/io/mmio.rs new file mode 100644 index 0000000000..1edb71014c --- /dev/null +++ b/drivers/common/src/io/mmio.rs @@ -0,0 +1,173 @@ +use core::{mem::MaybeUninit, ptr}; + +use super::Io; + +/// MMIO abstraction +#[repr(C, packed)] +pub struct Mmio { + value: MaybeUninit, +} + +impl Mmio { + /// Creates a zeroed instance + pub unsafe fn zeroed() -> Self { + Self { + value: MaybeUninit::zeroed(), + } + } + + /// Creates an unitialized instance + pub unsafe fn uninit() -> Self { + Self { + value: MaybeUninit::uninit(), + } + } + + /// Creates a new instance + pub const fn new(value: T) -> Self { + Self { + value: MaybeUninit::new(value), + } + } +} + +// Generic implementation (WARNING: requires aligned pointers!) +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +impl Io for Mmio +where + T: Copy + + PartialEq + + core::ops::BitAnd + + core::ops::BitOr + + core::ops::Not, +{ + type Value = T; + + fn read(&self) -> T { + unsafe { ptr::read_volatile(ptr::addr_of!(self.value).cast::()) } + } + + fn write(&mut self, value: T) { + unsafe { ptr::write_volatile(ptr::addr_of_mut!(self.value).cast::(), value) }; + } +} + +// x86 u8 implementation +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl Io for Mmio { + type Value = u8; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::(); + core::arch::asm!( + "mov {}, [{}]", + out(reg_byte) value, + in(reg) ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::(); + core::arch::asm!( + "mov [{}], {}", + in(reg) ptr, + in(reg_byte) value, + ); + } + } +} + +// x86 u16 implementation +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl Io for Mmio { + type Value = u16; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::(); + core::arch::asm!( + "mov {:x}, [{}]", + out(reg) value, + in(reg) ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::(); + core::arch::asm!( + "mov [{}], {:x}", + in(reg) ptr, + in(reg) value, + ); + } + } +} + +// x86 u32 implementation +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl Io for Mmio { + type Value = u32; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::(); + core::arch::asm!( + "mov {:e}, [{}]", + out(reg) value, + in(reg) ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::(); + core::arch::asm!( + "mov [{}], {:e}", + in(reg) ptr, + in(reg) value, + ); + } + } +} + +// x86 u64 implementation (x86_64 only) +#[cfg(target_arch = "x86_64")] +impl Io for Mmio { + type Value = u64; + + fn read(&self) -> Self::Value { + unsafe { + let value: Self::Value; + let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::(); + core::arch::asm!( + "mov {:r}, [{}]", + out(reg) value, + in(reg) ptr + ); + value + } + } + + fn write(&mut self, value: Self::Value) { + unsafe { + let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::(); + core::arch::asm!( + "mov [{}], {:r}", + in(reg) ptr, + in(reg) value, + ); + } + } +} diff --git a/drivers/common/src/io/pio.rs b/drivers/common/src/io/pio.rs new file mode 100644 index 0000000000..187e4734c3 --- /dev/null +++ b/drivers/common/src/io/pio.rs @@ -0,0 +1,89 @@ +use core::{arch::asm, marker::PhantomData}; + +use super::Io; + +/// Generic PIO +#[derive(Copy, Clone)] +pub struct Pio { + port: u16, + value: PhantomData, +} + +impl Pio { + /// Create a PIO from a given port + pub const fn new(port: u16) -> Self { + Pio:: { + port, + value: PhantomData, + } + } +} + +/// Read/Write for byte PIO +impl Io for Pio { + type Value = u8; + + /// Read + #[inline(always)] + fn read(&self) -> u8 { + let value: u8; + unsafe { + asm!("in al, dx", in("dx") self.port, out("al") value, options(nostack, nomem, preserves_flags)); + } + value + } + + /// Write + #[inline(always)] + fn write(&mut self, value: u8) { + unsafe { + asm!("out dx, al", in("dx") self.port, in("al") value, options(nostack, nomem, preserves_flags)); + } + } +} + +/// Read/Write for word PIO +impl Io for Pio { + type Value = u16; + + /// Read + #[inline(always)] + fn read(&self) -> u16 { + let value: u16; + unsafe { + asm!("in ax, dx", in("dx") self.port, out("ax") value, options(nostack, nomem, preserves_flags)); + } + value + } + + /// Write + #[inline(always)] + fn write(&mut self, value: u16) { + unsafe { + asm!("out dx, ax", in("dx") self.port, in("ax") value, options(nostack, nomem, preserves_flags)); + } + } +} + +/// Read/Write for doubleword PIO +impl Io for Pio { + type Value = u32; + + /// Read + #[inline(always)] + fn read(&self) -> u32 { + let value: u32; + unsafe { + asm!("in eax, dx", in("dx") self.port, out("eax") value, options(nostack, nomem, preserves_flags)); + } + value + } + + /// Write + #[inline(always)] + fn write(&mut self, value: u32) { + unsafe { + asm!("out dx, eax", in("dx") self.port, in("eax") value, options(nostack, nomem, preserves_flags)); + } + } +} diff --git a/drivers/common/src/lib.rs b/drivers/common/src/lib.rs new file mode 100644 index 0000000000..275caec413 --- /dev/null +++ b/drivers/common/src/lib.rs @@ -0,0 +1,301 @@ +//! This crate provides various abstractions for use by all drivers in the Redox drivers repo. +//! +//! This includes direct memory access via [dma], and Scatter-Gather List support via [sgl]. It also +//! provides various memory management structures for use with drivers, and some logging support. +#![warn(missing_docs)] + +use libredox::call::MmapArgs; +use libredox::flag::{self, O_CLOEXEC, O_RDONLY, O_RDWR, O_WRONLY}; +use libredox::{errno::EINVAL, error::*, Fd}; +use syscall::{ProcSchemeVerb, PAGE_SIZE}; + +/// The Direct Memory Access (DMA) API for drivers +pub mod dma; +/// MMIO utilities +pub mod io; +mod logger; +/// The Scatter Gather List (SGL) API for drivers. +pub mod sgl; +/// Low latency timeout for driver loops +pub mod timeout; + +pub use logger::{output_level, file_level, setup_logging}; + +/// Specifies the write behavior for a specific region of memory +/// +/// These types indicate to the driver how writes to a specific memory region are handled by the +/// system. This usually refers to the caching behavior that the processor or I/O device responsible +/// for that memory implements. +/// +/// aarch64 and x86 have very different cache-coherency rules, so this API as written is likely +/// not sufficient to describe the memory caching behavior in a cross-platform manner. As such, +/// consider this API unstable. +#[derive(Clone, Copy, Debug)] +pub enum MemoryType { + /// A region of memory that implements Write-back caching. + /// + /// In write-back caching, the processor will first store data in its local cache, and then + /// flush it to the actual storage location at regular intervals, or as applications access + /// the data. + Writeback, + /// A region of memory that does not implement caching. Writes to these regions are immediate. + Uncacheable, + /// A region of memory that implements write combining. + /// + /// Write combining memory regions store all writes in a temporary buffer called a Write + /// Combine Buffer. Multiple writes to the location are stored in a single buffer, and then + /// released to the memory location in an unspecified order. Write-Combine memory does not + /// guarantee that the order at which you write to it is the order at which those writes are + /// committed to memory. + WriteCombining, + /// Memory stored in an intermediate Write Combine Buffer and released later + /// Memory-Mapped I/O. This is an aarch64-specific term. + DeviceMemory, +} +impl Default for MemoryType { + fn default() -> Self { + Self::Writeback + } +} + +/// Represents the protection level of an area of memory. +/// +/// This structure shouldn't be used directly -- instead, use the [Prot::RO] (Read-Only), +/// [Prot::WO] (Write-Only) and [Prot::RW] (Read-Write) constants to specify the memory's protection +/// level. +#[derive(Clone, Copy, Debug)] +pub struct Prot { + /// The memory is readable + pub read: bool, + /// The memory is writeable + pub write: bool, +} + +/// Implements the memory protection level constants +impl Prot { + /// A constant representing Read-Only memory. + pub const RO: Self = Self { + read: true, + write: false, + }; + + /// A constant representing Write-Only memory + pub const WO: Self = Self { + read: false, + write: true, + }; + + /// A constant representing Read-Write memory + pub const RW: Self = Self { + read: true, + write: true, + }; +} + +// TODO: Safe, as the kernel ensures it doesn't conflict with any other memory described in the +// memory map for regular RAM. +/// Maps physical memory to virtual memory +/// +/// # Arguments +/// +/// * 'base_phys: [usize]' - The base address of the physical memory to map. +/// * 'len: [usize]' - The length of the physical memory to map (Should be a multiple of [PAGE_SIZE] +/// * '_: [Prot]' - The memory protection level of the mapping. +/// * 'type: [MemoryType]' - The caching behavior specification of the memory. +/// +/// # Returns +/// +/// A '[Result]<*mut ()>' which is: +/// - '[Ok]' containing a raw pointer to the mapped memory. +/// - '[Err]' which contains an error on failure. +/// +/// # Errors +/// +/// This function will return an error if: +/// - An invalid value is provided to 'read' or 'write' +/// - The system could not open a file descriptor to the memory scheme for the specified [MemoryType]. +/// - The system failed to map the physical address to a virtual address. See [libredox::call::mmap] +/// +/// +/// # Notes +/// - This function is unsafe, and upon using it you will be responsible for freeing the memory with +/// [libredox::call::munmap]. If you want a safe accessor, use [PhysBorrowed] instead. +/// - The MemoryType specified is used to tell the function which memory scheme to access. (i.e +/// /scheme/memory/physical@wb, /scheme/memory/physical@uc, etc). +pub unsafe fn physmap( + base_phys: usize, + len: usize, + Prot { read, write }: Prot, + ty: MemoryType, +) -> Result<*mut ()> { + // TODO: arraystring? + + //Return an error rather than potentially crash the kernel. + if base_phys == 0 { + return Err(Error::new(EINVAL)); + } + + let path = format!( + "/scheme/memory/physical@{}", + match ty { + MemoryType::Writeback => "wb", + MemoryType::Uncacheable => "uc", + MemoryType::WriteCombining => "wc", + MemoryType::DeviceMemory => "dev", + } + ); + let mode = match (read, write) { + (true, true) => O_RDWR, + (true, false) => O_RDONLY, + (false, true) => O_WRONLY, + (false, false) => return Err(Error::new(EINVAL)), + }; + let mut prot = 0; + if read { + prot |= flag::PROT_READ; + } + if write { + prot |= flag::PROT_WRITE; + } + + let fd = Fd::open(&path, O_CLOEXEC | mode, 0)?; + Ok(libredox::call::mmap(MmapArgs { + fd: fd.raw(), + offset: base_phys as u64, + length: len.next_multiple_of(PAGE_SIZE), + flags: flag::MAP_SHARED, + prot, + addr: core::ptr::null_mut(), + })? as *mut ()) +} + +impl std::fmt::Display for MemoryType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Writeback => "wb", + Self::Uncacheable => "uc", + Self::WriteCombining => "wc", + Self::DeviceMemory => "dev", + } + ) + } +} + +/// A safe virtual mapping to physical memory that unmaps the memory when the structure goes out +/// of scope. +/// +/// This function provides a safe binding to [physmap]. It implements Drop to free the mapped memory +/// when the structure goes out of scope. +pub struct PhysBorrowed { + mem: *mut (), + len: usize, +} +impl PhysBorrowed { + /// Constructs a PhysBorrowed instance. + /// + /// # Arguments + /// See [physmap] for a description of the parameters. + /// + /// # Returns + /// A '[Result]' which contains the following: + /// - A '[PhysBorrowed]' which represents the newly mapped region. + /// - An 'Err' if a memory mapping error occurs. + /// + /// # Errors + /// See [physmap] for a description of the error cases. + pub fn map(base_phys: usize, len: usize, prot: Prot, ty: MemoryType) -> Result { + let mem = unsafe { physmap(base_phys, len, prot, ty)? }; + Ok(Self { + mem, + len: len.next_multiple_of(PAGE_SIZE), + }) + } + + /// Gets a raw pointer to the borrowed region. + /// + /// # Returns + /// - self.mem - A pointer to the mapped region in virtual memory. + /// + /// # Notes + /// - The pointer may live beyond the lifetime of [PhysBorrowed], so dereferences to the pointer + /// must be treated as unsafe. + /// + pub fn as_ptr(&self) -> *mut () { + self.mem + } + + /// Gets the length of the mapped region. + /// + /// # Returns + /// - self.len - The length of the mapped region. It should be a multiple of [PAGE_SIZE] + pub fn mapped_len(&self) -> usize { + self.len + } +} + +impl Drop for PhysBorrowed { + /// Frees the mapped memory region. + fn drop(&mut self) { + unsafe { + let _ = libredox::call::munmap(self.mem, self.len); + } + } +} + +// TODO: temporary wrapper in redox_syscall? +unsafe fn sys_call(fd: usize, buf: &mut [u8], metadata: &[u64]) -> Result { + Ok(syscall::syscall5( + syscall::SYS_CALL, + fd, + buf.as_mut_ptr() as usize, + buf.len(), + metadata.len(), + metadata.as_ptr() as usize, + )?) +} + +/// Instructs the kernel to enable I/O ports for this (usermode) process (x86-specific). +/// +/// On Redox, x86 privilege ring 3 represents userspace. Most Redox drivers run in userspace to +/// prevent system instability caused by a faulty driver. Processes with (bitmap-enabled) IO port +/// rights can use the IN/OUT instructions. This is not the same as IOPL 3; the CLI instruction is +/// still not allowed. +pub fn acquire_port_io_rights() -> Result<()> { + extern "C" { + fn redox_cur_thrfd_v0() -> usize; + } + let kernel_fd = syscall::dup(unsafe { redox_cur_thrfd_v0() }, b"open_via_dup")?; + let res = unsafe { sys_call(kernel_fd, &mut [], &[ProcSchemeVerb::Iopl as u64]) }; + let _ = syscall::close(kernel_fd); + res?; + Ok(()) +} + +/// Kernel handle for translating virtual addresses in the current address space, to their +/// underlying physical addresses. +/// +/// It is currently unspecified whether this handle is specific to the address space at the time it +/// was created, or whether all calls reference the currently active address space. +pub struct VirtaddrTranslationHandle { + fd: Fd, +} + +impl VirtaddrTranslationHandle { + /// Create a new handle, requires uid=0 but this may change. + pub fn new() -> Result { + Ok(Self { + fd: Fd::open("/scheme/memory/translation", O_CLOEXEC, 0)?, + }) + } + /// Translate physical => virtual. + pub fn translate(&self, physical: usize) -> Result { + let mut buf = physical.to_ne_bytes(); + unsafe { + sys_call(self.fd.raw(), &mut buf, &[])?; + } + Ok(usize::from_ne_bytes(buf)) + } +} diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs new file mode 100644 index 0000000000..20e090d9f7 --- /dev/null +++ b/drivers/common/src/logger.rs @@ -0,0 +1,59 @@ +use redox_log::{OutputBuilder, RedoxLogger}; + +pub fn output_level() -> log::LevelFilter { + //TODO: adjust with bootloader environment + log::LevelFilter::Info +} + +pub fn file_level() -> log::LevelFilter { + log::LevelFilter::Info +} + +/// Configures logging for a single driver. +#[cfg_attr(not(target_os = "redox"), allow(unused_variables, unused_mut))] +pub fn setup_logging( + category: &str, + subcategory: &str, + logfile_base: &str, + output_level: log::LevelFilter, + file_level: log::LevelFilter, +) { + let mut logger = RedoxLogger::new().with_output( + OutputBuilder::stderr() + .with_filter(output_level) // limit global output to important info + .with_ansi_escape_codes() + .flush_on_newline(true) + .build(), + ); + + #[cfg(target_os = "redox")] + match OutputBuilder::in_redox_logging_scheme( + category, + subcategory, + format!("{logfile_base}.log"), + ) { + Ok(b) => { + logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build()) + } + Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error), + } + + #[cfg(target_os = "redox")] + match OutputBuilder::in_redox_logging_scheme( + category, + subcategory, + format!("{logfile_base}.ansi.log"), + ) { + Ok(b) => { + logger = logger.with_output( + b.with_filter(file_level) + .with_ansi_escape_codes() + .flush_on_newline(true) + .build(), + ) + } + Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error), + } + + logger.enable().expect("failed to set default logger"); +} diff --git a/drivers/common/src/sgl.rs b/drivers/common/src/sgl.rs new file mode 100644 index 0000000000..5a55f40b02 --- /dev/null +++ b/drivers/common/src/sgl.rs @@ -0,0 +1,130 @@ +use std::num::NonZeroUsize; + +use libredox::call::MmapArgs; +use libredox::errno::EINVAL; +use libredox::error::{Error, Result}; +use libredox::flag::{MAP_PRIVATE, PROT_READ, PROT_WRITE}; +use syscall::{MAP_FIXED, PAGE_SIZE}; + +use crate::dma::phys_contiguous_fd; +use crate::VirtaddrTranslationHandle; + +/// A Scatter-Gather List data structure +/// +/// See: +#[derive(Debug)] +pub struct Sgl { + /// A raw pointer to the SGL in virtual memory + virt: *mut u8, + /// The length of the allocated memory, guaranteed to be a multiple of [PAGE_SIZE]. + aligned_length: usize, + /// The length of the allocated memory. This value is NOT guaranteed to be a multiple of [PAGE_SIZE] + unaligned_length: NonZeroUsize, + /// The vector of chunks tracked by this [Sgl] object. This is the sparsely-populated vector in the SGL algorithm. + chunks: Vec, +} + +/// A structure representing a chunk of memory in the sparsely-populated vector of the SGL +#[derive(Debug)] +pub struct Chunk { + /// The offset of the chunk in the sparsely-populated vector. + pub offset: usize, + /// The physical address of the chunk + pub phys: usize, + /// A raw pointer to the chunk in virtual memory + pub virt: *mut u8, + /// The length of the chunk in bytes. + pub length: usize, +} + +impl Sgl { + /// Constructor for the scatter/gather list. + /// + /// # Arguments + /// + /// 'unaligned_length: [usize]' - The length of the SGL, not necessarily aligned to the nearest + /// page. + pub fn new(unaligned_length: usize) -> Result { + let unaligned_length = NonZeroUsize::new(unaligned_length).ok_or(Error::new(EINVAL))?; + + // TODO: Both PAGE_SIZE and MAX_ALLOC_SIZE should be dynamic. + let aligned_length = unaligned_length.get().next_multiple_of(PAGE_SIZE); + const MAX_ALLOC_SIZE: usize = 1 << 22; + + unsafe { + let virt = libredox::call::mmap(MmapArgs { + flags: MAP_PRIVATE, + prot: PROT_READ | PROT_WRITE, + length: aligned_length, + + offset: 0, + fd: !0, + addr: core::ptr::null_mut(), + })? + .cast::(); + + let mut this = Self { + virt, + aligned_length, + unaligned_length, + chunks: Vec::new(), + }; + + // TODO: SglContext to avoid reopening these fds? + let phys_contiguous_fd = phys_contiguous_fd()?; + let virttophys_handle = VirtaddrTranslationHandle::new()?; + + let mut offset = 0; + while offset < aligned_length { + let preferred_chunk_length = (aligned_length - offset) + .min(MAX_ALLOC_SIZE) + .next_power_of_two(); + let chunk_length = if preferred_chunk_length > aligned_length - offset { + preferred_chunk_length / 2 + } else { + preferred_chunk_length + }; + libredox::call::mmap(MmapArgs { + addr: virt.add(offset).cast(), + flags: MAP_PRIVATE | (MAP_FIXED.bits() as u32), + prot: PROT_READ | PROT_WRITE, + length: chunk_length, + fd: phys_contiguous_fd.raw(), + + offset: 0, + })?; + let phys = virttophys_handle.translate(virt as usize + offset)?; + this.chunks.push(Chunk { + offset, + phys, + length: (unaligned_length.get() - offset).min(chunk_length), + virt: virt.add(offset), + }); + offset += chunk_length; + } + + Ok(this) + } + } + /// Returns an immutable reference to the vector of chunks + pub fn chunks(&self) -> &[Chunk] { + &self.chunks + } + + /// Returns a raw pointer to the vector of chunks in virtual memory + pub fn as_ptr(&self) -> *mut u8 { + self.virt + } + /// Returns the length of the scatter-gather list. + pub fn len(&self) -> usize { + self.unaligned_length.get() + } +} + +impl Drop for Sgl { + fn drop(&mut self) { + unsafe { + let _ = libredox::call::munmap(self.virt.cast(), self.aligned_length); + } + } +} diff --git a/drivers/common/src/timeout.rs b/drivers/common/src/timeout.rs new file mode 100644 index 0000000000..ec226d1629 --- /dev/null +++ b/drivers/common/src/timeout.rs @@ -0,0 +1,45 @@ +use std::{thread, time::{Duration, Instant}}; + +pub struct Timeout { + instant: Instant, + duration: Duration, +} + +impl Timeout { + #[inline] + pub fn new(duration: Duration) -> Self { + Self { + instant: Instant::now(), + duration, + } + } + + #[inline] + pub fn from_micros(micros: u64) -> Self { + Self::new(Duration::from_micros(micros)) + } + + #[inline] + pub fn from_millis(millis: u64) -> Self { + Self::new(Duration::from_millis(millis)) + } + + #[inline] + pub fn from_secs(secs: u64) -> Self { + Self::new(Duration::from_secs(secs)) + } + + #[inline] + pub fn run(&self) -> Result<(), ()> { + if self.instant.elapsed() < self.duration { + // Sleeps in Redox are only evaluated on PIT ticks (a few ms), which is not + // short enough for a reasonably responsive timeout. However, the clock is + // highly accurate. So, we yield instead of sleep to reduce latency. + //TODO: allow timeout that spins instead of yields? + std::thread::yield_now(); + Ok(()) + } else { + Err(()) + } + } +} \ No newline at end of file diff --git a/drivers/executor/Cargo.toml b/drivers/executor/Cargo.toml new file mode 100644 index 0000000000..0d5b12f070 --- /dev/null +++ b/drivers/executor/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "executor" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Async framework for queue-based HW interfaces" + +[dependencies] +log = "0.4" +redox_event = "0.4.1" +slab = "0.4.9" diff --git a/drivers/executor/src/lib.rs b/drivers/executor/src/lib.rs new file mode 100644 index 0000000000..6b54e07bc1 --- /dev/null +++ b/drivers/executor/src/lib.rs @@ -0,0 +1,396 @@ +use std::cell::{Cell, RefCell}; +use std::collections::{HashMap, VecDeque}; +use std::fmt::Debug; +use std::fs::File; +use std::future::{Future, IntoFuture}; +use std::hash::Hash; +use std::io::{Read, Write}; +use std::marker::PhantomData; +use std::os::fd::AsRawFd; +use std::panic::AssertUnwindSafe; +use std::pin::Pin; +use std::ptr::NonNull; +use std::rc::Rc; +use std::task; + +use event::{EventFlags, RawEventQueue}; +use slab::Slab; + +type EventUserData = usize; + +type FutIdx = usize; + +pub trait Hardware: Sized { + type CmdId: Clone + Copy + Debug + Hash + Eq + PartialEq; + type CqId: Clone + Copy + Debug + Hash + Eq + PartialEq; + type SqId: Clone + Copy + Debug + Hash + Eq + PartialEq; + type Sqe: Debug + Clone + Copy; + type Cqe; + type Iv: Clone + Copy + Debug; + + type GlobalCtxt; + + // TODO: the kernel should also do this automatically before sending EOI messages to the IC + fn mask_vector(ctxt: &Self::GlobalCtxt, iv: Self::Iv); + fn unmask_vector(ctxt: &Self::GlobalCtxt, iv: Self::Iv); + + fn set_sqe_cmdid(sqe: &mut Self::Sqe, id: Self::CmdId); + fn get_cqe_cmdid(cqe: &Self::Cqe) -> Self::CmdId; + + // TODO: support multiple SQs per CQ or vice versa? + fn sq_cq(ctxt: &Self::GlobalCtxt, id: Self::CqId) -> Self::SqId; + + fn current() -> Rc>; + fn vtable() -> &'static task::RawWakerVTable; + + fn try_submit( + ctxt: &Self::GlobalCtxt, + sq_id: Self::SqId, + success: impl FnOnce(Self::CmdId) -> Self::Sqe, + fail: impl FnOnce(), + ) -> Option<(Self::CqId, Self::CmdId)>; + fn poll_cqes(ctxt: &Self::GlobalCtxt, handle: impl FnMut(Self::CqId, Self::Cqe)); +} + +/// Async executor, single IV, thread-per-core architecture +pub struct LocalExecutor { + global_ctxt: Hw::GlobalCtxt, + + queue: RawEventQueue, + vector: Hw::Iv, + irq_handle: File, + intx: bool, + + // TODO: One IV and SQ/CQ per core (where the admin queue can be managed by the main thread). + awaiting_submission: RefCell>>, + awaiting_completion: + RefCell>)>>>, + + external_event: RefCell)>>, + next_user_data: Cell, + + ready_futures: RefCell>, + futures: RefCell + 'static>>>>, + is_polling: Cell, +} + +impl LocalExecutor { + pub fn register_external_event( + &self, + fd: usize, + flags: event::EventFlags, + ) -> ExternalEventSource { + let user_data = self.next_user_data.get(); + self.next_user_data.set(user_data.checked_add(1).unwrap()); + + self.queue + .subscribe(fd, user_data, flags) + .expect("failed to subscribe to event"); + + ExternalEventSource { + flags: event::EventFlags::empty(), + user_data, + _not_send_or_unpin: PhantomData, + } + } + pub fn current() -> Rc { + Hw::current() + } + pub fn poll(&self) -> usize { + assert!(!self.is_polling.replace(true)); + + let mut finished = 0; + + for future_idx in self.ready_futures.borrow_mut().drain(..) { + let waker = waker::(future_idx); + + let mut futures = self.futures.borrow_mut(); + let res = match std::panic::catch_unwind(AssertUnwindSafe(|| { + futures[future_idx] + .as_mut() + .poll(&mut task::Context::from_waker(&waker)) + })) { + Ok(r) => r, + Err(_) => { + log::error!("Task panicked!"); + core::mem::forget(futures.remove(future_idx)); + continue; + } + }; + if res.is_ready() { + drop(futures.remove(future_idx)); + finished += 1; + } + } + self.is_polling.set(false); + + finished + } + pub fn spawn(&self, fut: impl IntoFuture + 'static) { + let idx = self + .futures + .borrow_mut() + .insert(Box::pin(fut.into_future())); + self.ready_futures.borrow_mut().push_back(idx); + } + pub fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture + 'a) -> O { + let retval = Rc::new(RefCell::new(None)); + + let retval2 = Rc::clone(&retval); + let idx = self.futures.borrow_mut().insert({ + let t1: Pin + 'a>> = Box::pin(async move { + *retval2.borrow_mut() = Some(fut.await); + }); + // SAFETY: Apart from the lifetimes, the types are exactly the same. We also know + // block_on simply cannot return without having fully awaited and dropped the future, + // even if that future panics (cf. the catch_unwind invocation). + let t2: Pin + 'static>> = + unsafe { std::mem::transmute(t1) }; + + t2 + }); + + self.ready_futures.borrow_mut().push_front(idx); + + loop { + let finished = self.poll(); + if retval.borrow().is_some() { + break; + } + if finished == 0 { + self.react(); + } + } + + let o = retval.borrow_mut().take().unwrap(); + o + } + fn react(&self) { + let event = self.queue.next_event().expect("failed to get next event"); + + if event.user_data != 0 { + let Some((fut_idx, flags_ptr)) = + self.external_event.borrow_mut().remove(&event.user_data) + else { + // Spurious event + return; + }; + unsafe { + flags_ptr + .as_ptr() + .write(event::EventFlags::from_bits_retain(event.flags)); + } + self.ready_futures.borrow_mut().push_back(fut_idx); + return; + } + + if self.intx { + let mut buf = [0_u8; core::mem::size_of::()]; + if (&self.irq_handle).read(&mut buf).unwrap() != 0 { + (&self.irq_handle).write(&buf).unwrap(); + } + } + + // TODO: The kernel should probably do the masking (when using MSI/MSI-X at least), which + // should happen before EOI messages to the interrupt controller. + Hw::mask_vector(&self.global_ctxt, self.vector); + Hw::poll_cqes(&self.global_ctxt, |cq_id, cqe| { + if let Some((fut_idx, comp_ptr)) = self + .awaiting_completion + .borrow_mut() + .get_mut(&cq_id) + .and_then(|per_cmd| per_cmd.remove(&Hw::get_cqe_cmdid(&cqe))) + { + unsafe { + comp_ptr.as_ptr().write(Some(cqe)); + } + self.ready_futures.borrow_mut().push_back(fut_idx); + + if let Some(submitting) = self + .awaiting_submission + .borrow_mut() + .get_mut(&Hw::sq_cq(&self.global_ctxt, cq_id)) + .and_then(|q| q.pop_front()) + { + self.ready_futures.borrow_mut().push_back(submitting); + } + } + }); + Hw::unmask_vector(&self.global_ctxt, self.vector); + } + pub async fn submit(&self, sq_id: Hw::SqId, cmd: Hw::Sqe) -> Hw::Cqe { + CqeFuture:: { + state: State::::Submitting { sq_id, cmd }, + comp: None, + _not_send: PhantomData, + } + .await + } +} + +struct CqeFuture { + pub state: State, + pub comp: Option, + pub _not_send: PhantomData<*const ()>, +} +enum State { + Submitting { sq_id: Hw::SqId, cmd: Hw::Sqe }, + Completing { cq_id: Hw::CqId, cmd_id: Hw::CmdId }, +} + +fn current_executor_and_idx( + cx: &mut task::Context<'_>, +) -> (Rc>, FutIdx) { + let executor = LocalExecutor::current(); + + let idx = cx.waker().data() as FutIdx; + assert_eq!( + cx.waker().vtable() as *const _, + Hw::vtable(), + "incompatible executor for CqeFuture" + ); + + (executor, idx) +} + +impl Future for CqeFuture { + type Output = Hw::Cqe; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + let this = unsafe { self.get_unchecked_mut() }; + + let (executor, idx) = current_executor_and_idx::(cx); + + match this.state { + State::Submitting { sq_id, mut cmd } => { + let mut awaiting = executor.awaiting_submission.borrow_mut(); + + if let Some((cq_id, cmd_id)) = Hw::try_submit( + &executor.global_ctxt, + sq_id, + |cmd_id| { + Hw::set_sqe_cmdid(&mut cmd, cmd_id); + log::trace!("About to submit {cmd:?}"); + cmd + }, + || { + awaiting.entry(sq_id).or_default().push_back(idx); + }, + ) { + executor + .awaiting_completion + .borrow_mut() + .entry(cq_id) + .or_default() + .insert(cmd_id, (idx, (&mut this.comp).into())); + this.state = State::Completing { cq_id, cmd_id }; + } + task::Poll::Pending + } + State::Completing { cq_id, cmd_id } => match this.comp.take() { + Some(comp) => { + log::trace!("ready!"); + task::Poll::Ready(comp) + } + + // Shouldn't technically be possible + None => { + log::trace!("spurious poll"); + executor + .awaiting_completion + .borrow_mut() + .entry(cq_id) + .or_default() + .insert(cmd_id, (idx, (&mut this.comp).into())); + task::Poll::Pending + } + }, + } + } +} + +unsafe fn vt_clone(idx: *const ()) -> task::RawWaker { + task::RawWaker::new(idx, Hw::vtable()) +} +unsafe fn vt_drop(_idx: *const ()) {} +unsafe fn vt_wake(idx: *const ()) { + Hw::current() + .ready_futures + .borrow_mut() + .push_back(idx as FutIdx); +} + +fn waker(idx: FutIdx) -> task::Waker { + unsafe { task::Waker::from_raw(task::RawWaker::new(idx as *const (), Hw::vtable())) } +} +pub const fn vtable() -> task::RawWakerVTable { + task::RawWakerVTable::new(vt_clone::, vt_wake::, vt_wake::, vt_drop) +} + +pub struct ExternalEventSource { + flags: event::EventFlags, + user_data: EventUserData, + _not_send_or_unpin: PhantomData<(*const (), fn() -> Hw)>, +} +pub struct Event { + flags: event::EventFlags, + _not_send: PhantomData<*const ()>, +} +impl Event { + pub fn flags(&self) -> event::EventFlags { + self.flags + } +} +impl ExternalEventSource { + fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> task::Poll> { + let this = unsafe { self.get_unchecked_mut() }; + + let flags = std::mem::take(&mut this.flags); + + if flags.is_empty() { + let (executor, idx) = current_executor_and_idx::(cx); + executor + .external_event + .borrow_mut() + .insert(this.user_data, (idx, (&mut this.flags).into())); + return task::Poll::Pending; + } + task::Poll::Ready(Some(Event { + flags, + _not_send: PhantomData, + })) + } + pub async fn next(mut self: Pin<&mut Self>) -> Option { + core::future::poll_fn(|cx| self.as_mut().poll_next(cx)).await + } +} +pub fn init_raw( + global_ctxt: Hw::GlobalCtxt, + vector: Hw::Iv, + intx: bool, + irq_handle: File, +) -> LocalExecutor { + let queue = RawEventQueue::new().expect("failed to allocate event queue for local executor"); + + // TODO: Multiple CPUs + queue + .subscribe(irq_handle.as_raw_fd() as usize, 0, EventFlags::READ) + .expect("failed to subscribe to IRQ event"); + + LocalExecutor { + global_ctxt, + + queue, + vector, + intx, + irq_handle, + + awaiting_submission: RefCell::new(HashMap::new()), + awaiting_completion: RefCell::new(HashMap::new()), + external_event: RefCell::new(HashMap::new()), + next_user_data: Cell::new(1), + ready_futures: RefCell::new(VecDeque::new()), + futures: RefCell::new(Slab::with_capacity(16)), + is_polling: Cell::new(false), + } +} diff --git a/drivers/fmt.sh b/drivers/fmt.sh new file mode 100755 index 0000000000..007766e270 --- /dev/null +++ b/drivers/fmt.sh @@ -0,0 +1,10 @@ +#!/usr/bin/bash + +set -eo pipefail + +printf "\e[1;32mFormatting\e[0m $dir\n" +if [[ "$CHECK_ONLY" -eq "1" ]]; then + cargo fmt --all --check +else + cargo fmt --all +fi diff --git a/drivers/graphics/bgad/Cargo.toml b/drivers/graphics/bgad/Cargo.toml new file mode 100644 index 0000000000..fa63aa2069 --- /dev/null +++ b/drivers/graphics/bgad/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bgad" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.47" +log = "0.4" +redox-daemon = "0.1" +redox-scheme = "0.6.2" +redox_syscall = "0.5" + +common = { path = "../../common" } +inputd = { path = "../../inputd" } +pcid = { path = "../../pcid" } +libredox = "0.1.3" diff --git a/drivers/graphics/bgad/config.toml b/drivers/graphics/bgad/config.toml new file mode 100644 index 0000000000..c76de694b1 --- /dev/null +++ b/drivers/graphics/bgad/config.toml @@ -0,0 +1,6 @@ +[[drivers]] +name = "QEMU Graphics Array" +class = 0x03 +vendor = 0x1234 +device = 0x1111 +command = ["bgad"] diff --git a/drivers/graphics/bgad/src/bga.rs b/drivers/graphics/bgad/src/bga.rs new file mode 100644 index 0000000000..5014100c2f --- /dev/null +++ b/drivers/graphics/bgad/src/bga.rs @@ -0,0 +1,51 @@ +const BGA_INDEX_XRES: u16 = 1; +const BGA_INDEX_YRES: u16 = 2; +const BGA_INDEX_BPP: u16 = 3; +const BGA_INDEX_ENABLE: u16 = 4; + +pub struct Bga { + bar: *mut u8, +} + +impl Bga { + pub unsafe fn new(bar: *mut u8) -> Bga { + Bga { bar } + } + + fn bochs_dispi_addr(&mut self, index: u16) -> *mut u16 { + assert!(index <= 0x10); + unsafe { + self.bar + .byte_add(0x500) + .cast::() + .add(usize::from(index)) + } + } + + fn bochs_dispi_read(&mut self, index: u16) -> u16 { + unsafe { self.bochs_dispi_addr(index).read_volatile() } + } + + fn bochs_dispi_write(&mut self, index: u16, data: u16) { + assert!(index <= 0x10); + unsafe { + self.bochs_dispi_addr(index).write_volatile(data); + } + } + + pub fn width(&mut self) -> u16 { + self.bochs_dispi_read(BGA_INDEX_XRES) + } + + pub fn height(&mut self) -> u16 { + self.bochs_dispi_read(BGA_INDEX_YRES) + } + + pub fn set_size(&mut self, width: u16, height: u16) { + self.bochs_dispi_write(BGA_INDEX_ENABLE, 0); + self.bochs_dispi_write(BGA_INDEX_XRES, width); + self.bochs_dispi_write(BGA_INDEX_YRES, height); + self.bochs_dispi_write(BGA_INDEX_BPP, 32); + self.bochs_dispi_write(BGA_INDEX_ENABLE, 0x41); + } +} diff --git a/drivers/graphics/bgad/src/main.rs b/drivers/graphics/bgad/src/main.rs new file mode 100644 index 0000000000..461b1b8b84 --- /dev/null +++ b/drivers/graphics/bgad/src/main.rs @@ -0,0 +1,75 @@ +//! + +use inputd::ProducerHandle; +use pcid_interface::PciFunctionHandle; +use redox_scheme::{RequestKind, SignalBehavior, Socket}; + +use crate::bga::Bga; +use crate::scheme::BgaScheme; + +mod bga; +mod scheme; + +// FIXME add a driver-graphics implementation + +fn main() { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_bga"); + + common::setup_logging( + "graphics", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + log::info!("BGA {}", pci_config.func.display()); + + redox_daemon::Daemon::new(move |daemon| { + let socket = Socket::create("bga").expect("bgad: failed to create bga scheme"); + + let bar = unsafe { pcid_handle.map_bar(2) }.ptr.as_ptr(); + + let mut bga = unsafe { Bga::new(bar) }; + log::debug!("BGA {}x{}", bga.width(), bga.height()); + + let mut scheme = BgaScheme { + bga, + display: ProducerHandle::new().ok(), + }; + + scheme.update_size(); + + libredox::call::setrens(0, 0).expect("bgad: failed to enter null namespace"); + + daemon.ready().expect("bgad: failed to notify parent"); + + loop { + let Some(request) = socket + .next_request(SignalBehavior::Restart) + .expect("bgad: failed to get next scheme request") + else { + // Scheme likely got unmounted + std::process::exit(0); + }; + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut scheme); + + socket + .write_response(response, SignalBehavior::Restart) + .expect("bgad: failed to write next scheme response"); + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + _ => (), + } + } + }) + .expect("bgad: failed to daemonize"); +} diff --git a/drivers/graphics/bgad/src/scheme.rs b/drivers/graphics/bgad/src/scheme.rs new file mode 100644 index 0000000000..5cd214e91e --- /dev/null +++ b/drivers/graphics/bgad/src/scheme.rs @@ -0,0 +1,117 @@ +use inputd::ProducerHandle; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult}; +use std::str; +use syscall::data::Stat; +use syscall::schemev2::NewFdFlags; +use syscall::{Error, Result, EACCES, EINVAL, MODE_CHR}; + +use crate::bga::Bga; + +pub struct BgaScheme { + pub bga: Bga, + pub display: Option, +} + +impl BgaScheme { + pub fn update_size(&mut self) { + if let Some(ref mut display) = self.display { + let _ = display.write_event( + orbclient::ResizeEvent { + width: self.bga.width() as u32, + height: self.bga.height() as u32, + } + .to_event(), + ); + } + } +} + +impl SchemeSync for BgaScheme { + fn open(&mut self, _path: &str, _flags: usize, ctx: &CallerCtx) -> Result { + if ctx.uid == 0 { + Ok(OpenResult::ThisScheme { + number: 0, + flags: NewFdFlags::empty(), + }) + } else { + Err(Error::new(EACCES)) + } + } + + fn read( + &mut self, + _id: usize, + buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let mut i = 0; + let data = format!("{},{}\n", self.bga.width(), self.bga.height()).into_bytes(); + while i < buf.len() && i < data.len() { + buf[i] = data[i]; + i += 1; + } + Ok(i) + } + + fn write( + &mut self, + _id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let string = str::from_utf8(buf).or(Err(Error::new(EINVAL)))?; + let string = string.trim(); + + let mut parts = string.split(','); + + let width = if let Some(part) = parts.next() { + part.parse::().or(Err(Error::new(EINVAL)))? + } else { + self.bga.width() + }; + + let height = if let Some(part) = parts.next() { + part.parse::().or(Err(Error::new(EINVAL)))? + } else { + self.bga.height() + }; + + self.bga.set_size(width, height); + + self.update_size(); + + Ok(buf.len()) + } + + fn fpath(&mut self, _file: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let mut i = 0; + let scheme_path = b"bga"; + while i < buf.len() && i < scheme_path.len() { + buf[i] = scheme_path[i]; + i += 1; + } + Ok(i) + } + + fn fstat(&mut self, _id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { + *stat = Stat { + st_mode: MODE_CHR | 0o666, + ..Default::default() + }; + + Ok(()) + } + + fn fcntl(&mut self, _id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result { + Ok(0) + } +} + +impl BgaScheme { + pub fn on_close(&mut self, _id: usize) {} +} diff --git a/drivers/graphics/console-draw/Cargo.toml b/drivers/graphics/console-draw/Cargo.toml new file mode 100644 index 0000000000..6153d6143d --- /dev/null +++ b/drivers/graphics/console-draw/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "console-draw" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" + +graphics-ipc = { path = "../graphics-ipc" } + +[features] +default = [] diff --git a/drivers/graphics/console-draw/src/lib.rs b/drivers/graphics/console-draw/src/lib.rs new file mode 100644 index 0000000000..20bc77b6e6 --- /dev/null +++ b/drivers/graphics/console-draw/src/lib.rs @@ -0,0 +1,296 @@ +extern crate ransid; + +use std::collections::VecDeque; +use std::convert::{TryFrom, TryInto}; +use std::{cmp, ptr}; + +use graphics_ipc::v1::Damage; +use orbclient::FONT; + +pub struct DisplayMap { + pub offscreen: *mut [u32], + pub width: usize, + pub height: usize, +} + +pub struct TextScreen { + console: ransid::Console, +} + +impl TextScreen { + pub fn new() -> TextScreen { + TextScreen { + // Width and height will be filled in on the next write to the console + console: ransid::Console::new(0, 0), + } + } + + /// Draw a rectangle + fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) { + let start_y = cmp::min(map.height, y); + let end_y = cmp::min(map.height, y + h); + + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; + + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; + + let stride = map.width * 4; + + let offset = y * stride + start_x * 4; + offscreen_ptr += offset; + + let mut rows = end_y - start_y; + while rows > 0 { + for i in 0..len { + unsafe { + *(offscreen_ptr as *mut u32).add(i) = color; + } + } + offscreen_ptr += stride; + rows -= 1; + } + } + + /// Invert a rectangle + fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) { + let start_y = cmp::min(map.height, y); + let end_y = cmp::min(map.height, y + h); + + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; + + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; + + let stride = map.width * 4; + + let offset = y * stride + start_x * 4; + offscreen_ptr += offset; + + let mut rows = end_y - start_y; + while rows > 0 { + let mut row_ptr = offscreen_ptr; + let mut cols = len; + while cols > 0 { + unsafe { + let color = *(row_ptr as *mut u32); + *(row_ptr as *mut u32) = !color; + } + row_ptr += 4; + cols -= 1; + } + offscreen_ptr += stride; + rows -= 1; + } + } + + /// Draw a character + fn char( + map: &mut DisplayMap, + x: usize, + y: usize, + character: char, + color: u32, + _bold: bool, + _italic: bool, + ) { + if x + 8 <= map.width && y + 16 <= map.height { + let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4; + + let font_i = 16 * (character as usize); + if font_i + 16 <= FONT.len() { + for row in 0..16 { + let row_data = FONT[font_i + row]; + for col in 0..8 { + if (row_data >> (7 - col)) & 1 == 1 { + unsafe { + *((dst + col * 4) as *mut u32) = color; + } + } + } + dst += map.width * 4; + } + } + } + } +} + +impl TextScreen { + pub fn write(&mut self, map: &mut DisplayMap, buf: &[u8], input: &mut VecDeque) -> Damage { + let mut min_changed = map.height; + let mut max_changed = 0; + let mut line_changed = |line| { + if line < min_changed { + min_changed = line; + } + if line > max_changed { + max_changed = line; + } + }; + + self.console.resize(map.width / 8, map.height / 16); + if self.console.state.x >= self.console.state.w { + self.console.state.x = self.console.state.w - 1; + } + if self.console.state.y >= self.console.state.h { + self.console.state.y = self.console.state.h - 1; + } + + if self.console.state.cursor + && self.console.state.x < self.console.state.w + && self.console.state.y < self.console.state.h + { + let x = self.console.state.x; + let y = self.console.state.y; + Self::invert(map, x * 8, y * 16, 8, 16); + line_changed(y); + } + + self.console.write(buf, |event| match event { + ransid::Event::Char { + x, + y, + c, + color, + bold, + .. + } => { + Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false); + line_changed(y); + } + ransid::Event::Input { data } => input.extend(data), + ransid::Event::Rect { x, y, w, h, color } => { + Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); + for y2 in y..y + h { + line_changed(y2); + } + } + ransid::Event::ScreenBuffer { .. } => (), + ransid::Event::Move { + from_x, + from_y, + to_x, + to_y, + w, + h, + } => { + let width = map.width; + let pixels = unsafe { &mut *map.offscreen }; + + for raw_y in 0..h { + let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; + + for pixel_y in 0..16 { + { + let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8; + let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8; + let len = w * 8; + + if off_from + len <= pixels.len() && off_to + len <= pixels.len() { + unsafe { + let data_ptr = pixels.as_mut_ptr() as *mut u32; + ptr::copy( + data_ptr.offset(off_from as isize), + data_ptr.offset(off_to as isize), + len, + ); + } + } + } + } + + line_changed(to_y + y); + } + } + ransid::Event::Resize { .. } => (), + ransid::Event::Title { .. } => (), + }); + + if self.console.state.cursor + && self.console.state.x < self.console.state.w + && self.console.state.y < self.console.state.h + { + let x = self.console.state.x; + let y = self.console.state.y; + Self::invert(map, x * 8, y * 16, 8, 16); + line_changed(y); + } + + let width = map.width.try_into().unwrap(); + let damage = Damage { + x: 0, + y: u32::try_from(min_changed).unwrap() * 16, + width, + height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16, + }; + + damage + } + + pub fn resize(&mut self, old_map: &mut DisplayMap, new_map: &mut DisplayMap) { + // FIXME fold row when target is narrower and maybe unfold when it is wider + fn copy_row( + old_map: &mut DisplayMap, + new_map: &mut DisplayMap, + from_row: usize, + to_row: usize, + ) { + for x in 0..cmp::min(old_map.width, new_map.width) { + let old_idx = from_row * old_map.width + x; + let new_idx = to_row * new_map.width + x; + unsafe { + (*new_map.offscreen)[new_idx] = (*old_map.offscreen)[old_idx]; + } + } + } + + if new_map.height >= old_map.height { + for row in 0..old_map.height { + copy_row(old_map, new_map, row, row); + } + } else { + let deleted_rows = (old_map.height - new_map.height).div_ceil(16); + for row in 0..new_map.height { + if row + (deleted_rows + 1) * 16 >= old_map.height { + break; + } + copy_row(old_map, new_map, row + deleted_rows * 16, row); + } + self.console.state.y = self.console.state.y.saturating_sub(deleted_rows); + } + } +} + +pub struct TextBuffer { + pub lines: VecDeque>, + pub lines_max: usize, +} + +impl TextBuffer { + pub fn new(max: usize) -> Self { + let mut lines = VecDeque::new(); + lines.push_back(Vec::new()); + Self { + lines, + lines_max: max, + } + } + pub fn write(&mut self, buf: &[u8]) { + if buf.is_empty() { + return; + } + + for &byte in buf { + self.lines.back_mut().unwrap().push(byte); + + if byte == b'\n' { + self.lines.push_back(Vec::new()); + } + } + + let max_len = self.lines_max; + while self.lines.len() > max_len { + self.lines.pop_front(); + } + } +} diff --git a/drivers/graphics/driver-graphics/Cargo.toml b/drivers/graphics/driver-graphics/Cargo.toml new file mode 100644 index 0000000000..ef5078e59e --- /dev/null +++ b/drivers/graphics/driver-graphics/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "driver-graphics" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4" +redox-scheme = "0.6.2" +redox_syscall = "0.5" +libredox = "0.1.3" + +common = { path = "../../common" } +graphics-ipc = { path = "../graphics-ipc" } +inputd = { path = "../../inputd" } diff --git a/drivers/graphics/driver-graphics/src/lib.rs b/drivers/graphics/driver-graphics/src/lib.rs new file mode 100644 index 0000000000..0fc6995aa0 --- /dev/null +++ b/drivers/graphics/driver-graphics/src/lib.rs @@ -0,0 +1,585 @@ +#![feature(slice_as_array)] + +use std::collections::{BTreeMap, HashMap}; +use std::fs::File; +use std::io::{self, Write}; +use std::mem::transmute; +use std::sync::Arc; + +use graphics_ipc::v1::{CursorDamage, Damage}; +use inputd::{VtEvent, VtEventKind}; +use libredox::Fd; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket}; +use syscall::schemev2::NewFdFlags; +use syscall::{Error, MapFlags, Result, EAGAIN, EBADF, EINVAL, ENOENT, EOPNOTSUPP}; + +pub trait GraphicsAdapter { + type Framebuffer: Framebuffer; + type Cursor: CursorFramebuffer; + + /// The maximum amount of displays that could be attached. + /// + /// This must be constant for the lifetime of the graphics adapter. + fn display_count(&self) -> usize; + fn display_size(&self, display_id: usize) -> (u32, u32); + + fn create_dumb_framebuffer(&mut self, width: u32, height: u32) -> Self::Framebuffer; + fn map_dumb_framebuffer(&mut self, framebuffer: &Self::Framebuffer) -> *mut u8; + + fn update_plane(&mut self, display_id: usize, framebuffer: &Self::Framebuffer, damage: Damage); + + fn supports_hw_cursor(&self) -> bool; + fn create_cursor_framebuffer(&mut self) -> Self::Cursor; + fn map_cursor_framebuffer(&mut self, cursor: &Self::Cursor) -> *mut u8; + fn handle_cursor(&mut self, cursor: &CursorPlane, dirty_fb: bool); +} + +pub trait Framebuffer { + fn width(&self) -> u32; + fn height(&self) -> u32; +} + +pub struct CursorPlane { + pub x: i32, + pub y: i32, + pub hot_x: i32, + pub hot_y: i32, + pub framebuffer: C, +} + +pub trait CursorFramebuffer {} + +pub struct GraphicsScheme { + adapter: T, + + scheme_name: String, + disable_graphical_debug: Option, + socket: Socket, + next_id: usize, + handles: BTreeMap>, + + active_vt: usize, + vts: HashMap>, +} + +struct VtState { + display_fbs: Vec>, + cursor_plane: Option>, +} + +enum Handle { + V1Screen { + vt: usize, + screen: usize, + }, + V2 { + vt: usize, + next_id: usize, + fbs: HashMap>, + }, +} + +impl GraphicsScheme { + pub fn new(adapter: T, scheme_name: String) -> Self { + assert!(scheme_name.starts_with("display")); + let socket = Socket::nonblock(&scheme_name).expect("failed to create graphics scheme"); + + let disable_graphical_debug = Some( + File::open("/scheme/debug/disable-graphical-debug") + .expect("vesad: Failed to open /scheme/debug/disable-graphical-debug"), + ); + + GraphicsScheme { + adapter, + scheme_name, + disable_graphical_debug, + socket, + next_id: 0, + handles: BTreeMap::new(), + active_vt: 0, + vts: HashMap::new(), + } + } + + pub fn event_handle(&self) -> &Fd { + self.socket.inner() + } + + pub fn adapter(&self) -> &T { + &self.adapter + } + + pub fn adapter_mut(&mut self) -> &mut T { + &mut self.adapter + } + + pub fn handle_vt_event(&mut self, vt_event: VtEvent) { + match vt_event.kind { + VtEventKind::Activate => { + log::info!("activate {}", vt_event.vt); + + // Disable the kernel graphical debug writing once switching vt's for the + // first time. This way the kernel graphical debug remains enabled if the + // userspace logging infrastructure doesn't start up because for example a + // kernel panic happened prior to it starting up or logd crashed. + if let Some(mut disable_graphical_debug) = self.disable_graphical_debug.take() { + let _ = disable_graphical_debug.write(&[1]); + } + + self.active_vt = vt_event.vt; + + let vt_state = + Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt_event.vt); + + for (display_id, fb) in vt_state.display_fbs.iter().enumerate() { + Self::update_whole_screen(&mut self.adapter, display_id, fb); + } + + if let Some(cursor_plane) = &vt_state.cursor_plane { + self.adapter.handle_cursor(cursor_plane, true); + } + } + + VtEventKind::Resize => { + log::warn!("driver-graphics: resize is not implemented yet") + } + } + } + + pub fn notify_displays_changed(&mut self) { + // FIXME notify clients + } + + /// Process new scheme requests. + /// + /// This needs to be called each time there is a new event on the scheme + /// file. + pub fn tick(&mut self) -> io::Result<()> { + loop { + let request = match self.socket.next_request(SignalBehavior::Restart) { + Ok(Some(request)) => request, + Ok(None) => { + // Scheme likely got unmounted + std::process::exit(0); + } + Err(err) if err.errno == EAGAIN => break, + Err(err) => panic!("driver-graphics: failed to read display scheme: {err}"), + }; + + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(self); + self.socket + .write_response(response, SignalBehavior::Restart) + .expect("driver-graphics: failed to write response"); + } + RequestKind::OnClose { id } => { + self.on_close(id); + } + _ => (), + } + } + + Ok(()) + } + + fn update_whole_screen(adapter: &mut T, screen: usize, framebuffer: &T::Framebuffer) { + adapter.update_plane( + screen, + framebuffer, + Damage { + x: 0, + y: 0, + width: framebuffer.width(), + height: framebuffer.height(), + }, + ); + } + + fn get_or_create_vt<'a>( + adapter: &mut T, + vts: &'a mut HashMap>, + vt: usize, + ) -> &'a mut VtState { + vts.entry(vt).or_insert_with(|| { + let mut display_fbs = vec![]; + for display_id in 0..adapter.display_count() { + let (width, height) = adapter.display_size(display_id); + display_fbs.push(Arc::new(adapter.create_dumb_framebuffer(width, height))); + } + + let cursor_plane = adapter.supports_hw_cursor().then(|| CursorPlane { + x: 0, + y: 0, + hot_x: 0, + hot_y: 0, + framebuffer: adapter.create_cursor_framebuffer(), + }); + + VtState { + display_fbs, + cursor_plane, + } + }) + } +} + +const MAP_FAKE_OFFSET_MULTIPLIER: usize = 0x10_000_000; + +impl SchemeSync for GraphicsScheme { + fn open(&mut self, path: &str, _flags: usize, _ctx: &CallerCtx) -> Result { + if path.is_empty() { + return Err(Error::new(EINVAL)); + } + + let handle = if path.starts_with("v") { + if !path.starts_with("v2/") { + return Err(Error::new(ENOENT)); + } + let vt = path["v2/".len()..] + .parse::() + .map_err(|_| Error::new(EINVAL))?; + + // Ensure the VT exists such that the rest of the methods can freely access it. + Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt); + + Handle::V2 { + vt, + next_id: 0, + fbs: HashMap::new(), + } + } else { + let mut parts = path.split('/'); + let mut screen = parts.next().unwrap_or("").split('.'); + + let vt = screen.next().unwrap_or("").parse::().unwrap(); + let id = screen.next().unwrap_or("").parse::().unwrap_or(0); + + if id >= self.adapter.display_count() { + return Err(Error::new(EINVAL)); + } + + // Ensure the VT exists such that the rest of the methods can freely access it. + Self::get_or_create_vt(&mut self.adapter, &mut self.vts, vt); + + Handle::V1Screen { vt, screen: id } + }; + self.next_id += 1; + self.handles.insert(self.next_id, handle); + Ok(OpenResult::ThisScheme { + number: self.next_id, + flags: NewFdFlags::empty(), + }) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> syscall::Result { + let path = match self.handles.get(&id).ok_or(Error::new(EBADF))? { + Handle::V1Screen { vt, screen } => { + let framebuffer = &self.vts[vt].display_fbs[*screen]; + format!( + "{}:{vt}.{screen}/{}/{}", + self.scheme_name, + framebuffer.width(), + framebuffer.height() + ) + } + Handle::V2 { + vt, + next_id: _, + fbs: _, + } => format!("/scheme/{}/v2/{vt}", self.scheme_name), + }; + buf[..path.len()].copy_from_slice(path.as_bytes()); + Ok(path.len()) + } + + fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> syscall::Result<()> { + match self.handles.get(&id).ok_or(Error::new(EBADF))? { + Handle::V1Screen { vt, screen } => { + if *vt != self.active_vt { + // This is a protection against background VT's spamming us with flush requests. We will + // flush the framebuffer on the next VT switch anyway + return Ok(()); + } + Self::update_whole_screen( + &mut self.adapter, + *screen, + &self.vts[vt].display_fbs[*screen], + ); + Ok(()) + } + Handle::V2 { .. } => Err(Error::new(EOPNOTSUPP)), + } + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + match self.handles.get(&id).ok_or(Error::new(EBADF))? { + Handle::V1Screen { .. } => { + //Currently read is only used for Orbital to check GPU cursor support + //and only expects a buf to pass a 0 or 1 flag + if self.adapter.supports_hw_cursor() { + buf[0] = 1; + } else { + buf[0] = 0; + } + + Ok(1) + } + Handle::V2 { .. } => Err(Error::new(EOPNOTSUPP)), + } + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + match self.handles.get(&id).ok_or(Error::new(EBADF))? { + Handle::V1Screen { vt, screen } => { + if *vt != self.active_vt { + // This is a protection against background VT's spamming us with flush requests. We will + // flush the framebuffer on the next VT switch anyway + return Ok(buf.len()); + } + + let vt_state = self.vts.get_mut(vt).unwrap(); + + if size_of_val(buf) == std::mem::size_of::() { + let Some(cursor_plane) = &mut vt_state.cursor_plane else { + // Hardware cursor not supported + return Err(Error::new(EINVAL)); + }; + + let cursor_damage = unsafe { *buf.as_ptr().cast::() }; + + cursor_plane.x = cursor_damage.x; + cursor_plane.y = cursor_damage.y; + + if cursor_damage.header == 0 { + self.adapter.handle_cursor(cursor_plane, false); + } else { + cursor_plane.hot_x = cursor_damage.hot_x; + cursor_plane.hot_y = cursor_damage.hot_y; + + let w: i32 = cursor_damage.width; + let h: i32 = cursor_damage.height; + let cursor_image = cursor_damage.cursor_img_bytes; + let cursor_ptr = self + .adapter + .map_cursor_framebuffer(&cursor_plane.framebuffer); + + //Clear previous image from backing storage + unsafe { + core::ptr::write_bytes(cursor_ptr as *mut u8, 0, 64 * 64 * 4); + } + + //Write image to backing storage + for row in 0..h { + let start: usize = (w * row) as usize; + let end: usize = (w * row + w) as usize; + + unsafe { + core::ptr::copy_nonoverlapping( + cursor_image[start..end].as_ptr(), + cursor_ptr.cast::().offset(64 * row as isize), + w as usize, + ); + } + } + + self.adapter.handle_cursor(cursor_plane, true); + } + + return Ok(buf.len()); + } + + assert_eq!(buf.len(), std::mem::size_of::()); + let damage = unsafe { *buf.as_ptr().cast::() }; + + self.adapter + .update_plane(*screen, &vt_state.display_fbs[*screen], damage); + + Ok(buf.len()) + } + Handle::V2 { .. } => Err(Error::new(EOPNOTSUPP)), + } + } + + fn call(&mut self, id: usize, payload: &mut [u8], metadata: &[u64]) -> Result { + use graphics_ipc::v2::ipc; + + match self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::V1Screen { .. } => { + return Err(Error::new(EOPNOTSUPP)); + } + Handle::V2 { vt, next_id, fbs } => match metadata[0] { + ipc::DISPLAY_COUNT => { + if payload.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + let payload = unsafe { + transmute::<&mut [u8; size_of::()], &mut ipc::DisplayCount>( + payload.as_mut_array().unwrap(), + ) + }; + payload.count = self.adapter.display_count(); + Ok(size_of::()) + } + ipc::DISPLAY_SIZE => { + if payload.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + let payload = unsafe { + transmute::<&mut [u8; size_of::()], &mut ipc::DisplaySize>( + payload.as_mut_array().unwrap(), + ) + }; + let display_id = payload.display_id; + if display_id >= self.adapter.display_count() { + return Err(Error::new(EINVAL)); + } + let (width, height) = self.adapter.display_size(display_id); + payload.width = width; + payload.height = height; + Ok(size_of::()) + } + ipc::CREATE_DUMB_FRAMEBUFFER => { + if payload.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + let payload = unsafe { + transmute::< + &mut [u8; size_of::()], + &mut ipc::CreateDumbFramebuffer, + >(payload.as_mut_array().unwrap()) + }; + + let fb = self + .adapter + .create_dumb_framebuffer(payload.width, payload.height); + + *next_id += 1; + fbs.insert(*next_id, Arc::new(fb)); + payload.fb_id = *next_id; + Ok(size_of::()) + } + ipc::DUMB_FRAMEBUFFER_MAP_OFFSET => { + if payload.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + let payload = unsafe { + transmute::< + &mut [u8; size_of::()], + &mut ipc::DumbFramebufferMapOffset, + >(payload.as_mut_array().unwrap()) + }; + + let fb_id = payload.fb_id; + + if !fbs.contains_key(&fb_id) { + return Err(Error::new(EINVAL)); + } + + // FIXME use a better scheme for creating map offsets + assert!( + ((fbs[&fb_id].width() * fbs[&fb_id].height() * 4) as usize) + < MAP_FAKE_OFFSET_MULTIPLIER + ); + + payload.offset = fb_id * MAP_FAKE_OFFSET_MULTIPLIER; + + Ok(size_of::()) + } + ipc::DESTROY_DUMB_FRAMEBUFFER => { + if payload.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + let payload = unsafe { + transmute::< + &mut [u8; size_of::()], + &mut ipc::DestroyDumbFramebuffer, + >(payload.as_mut_array().unwrap()) + }; + + if fbs.remove(&{ payload.fb_id }).is_none() { + return Err(Error::new(ENOENT)); + } + + Ok(size_of::()) + } + ipc::UPDATE_PLANE => { + if payload.len() < size_of::() { + return Err(Error::new(EINVAL)); + } + let payload = unsafe { + transmute::<&mut [u8; size_of::()], &mut ipc::UpdatePlane>( + payload.as_mut_array().unwrap(), + ) + }; + + let display_id = payload.display_id; + if display_id >= self.adapter.display_count() { + return Err(Error::new(EINVAL)); + } + + let Some(framebuffer) = fbs.get(&{ payload.fb_id }) else { + return Err(Error::new(EINVAL)); + }; + + self.vts.get_mut(vt).unwrap().display_fbs[display_id] = framebuffer.clone(); + + if *vt == self.active_vt { + self.adapter + .update_plane(display_id, framebuffer, payload.damage); + } + + Ok(size_of::()) + } + _ => return Err(Error::new(EINVAL)), + }, + } + } + + fn mmap_prep( + &mut self, + id: usize, + offset: u64, + _size: usize, + _flags: MapFlags, + _ctx: &CallerCtx, + ) -> syscall::Result { + // log::trace!("KSMSG MMAP {} {:?} {} {}", id, _flags, _offset, _size); + let (framebuffer, offset) = match self.handles.get(&id).ok_or(Error::new(EINVAL))? { + Handle::V1Screen { vt, screen } => (&self.vts[vt].display_fbs[*screen], offset), + Handle::V2 { + vt: _, + next_id: _, + fbs, + } => ( + fbs.get(&(offset as usize / MAP_FAKE_OFFSET_MULTIPLIER)) + .ok_or(Error::new(EINVAL)) + .unwrap(), + offset & (MAP_FAKE_OFFSET_MULTIPLIER as u64 - 1), + ), + }; + let ptr = T::map_dumb_framebuffer(&mut self.adapter, framebuffer); + Ok(unsafe { ptr.add(offset as usize) } as usize) + } +} + +impl GraphicsScheme { + fn on_close(&mut self, id: usize) { + self.handles.remove(&id); + } +} diff --git a/drivers/graphics/fbbootlogd/Cargo.toml b/drivers/graphics/fbbootlogd/Cargo.toml new file mode 100644 index 0000000000..4652dce682 --- /dev/null +++ b/drivers/graphics/fbbootlogd/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "fbbootlogd" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" +redox_event = "0.4" +redox_syscall = "0.5" +redox-daemon = "0.1" +redox-scheme = "0.6.2" + +console-draw = { path = "../console-draw" } +graphics-ipc = { path = "../graphics-ipc" } +inputd = { path = "../../inputd" } +libredox = "0.1.3" + +[features] +default = [] diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs new file mode 100644 index 0000000000..bc3759c469 --- /dev/null +++ b/drivers/graphics/fbbootlogd/src/main.rs @@ -0,0 +1,126 @@ +//! Fbbootlogd renders the boot log and presents it on VT1. +//! +//! While fbbootlogd is superficially similar to fbcond, the major difference is: +//! +//! * Fbbootlogd doesn't accept input coming from the keyboard. It only allows getting written to. +//! +//! In the future fbbootlogd may also pull from logd as opposed to have logd push logs to it. And it +//! it could display a boot splash like plymouth instead of a boot log when booting in quiet mode. + +use std::io::Write; +use std::os::fd::AsRawFd; + +use event::EventQueue; +use inputd::ConsumerHandleEvent; +use libredox::errno::EAGAIN; +use orbclient::Event; +use redox_scheme::{RequestKind, SignalBehavior, Socket}; + +use crate::scheme::FbbootlogScheme; + +mod scheme; + +fn main() { + redox_daemon::Daemon::new(|daemon| inner(daemon)).expect("failed to create daemon"); +} +fn inner(daemon: redox_daemon::Daemon) -> ! { + let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue"); + + event::user_data! { + enum Source { + Scheme, + Input, + } + } + + let socket = + Socket::nonblock("fbbootlog").expect("fbbootlogd: failed to create fbbootlog scheme"); + + { + // Add ourself as log sink + let mut log_file = std::fs::OpenOptions::new() + .write(true) + .open("/scheme/log/add_sink") + .unwrap(); + log_file.write(b"/scheme/fbbootlog").unwrap(); + } + + event_queue + .subscribe( + socket.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .expect("fbcond: failed to subscribe to scheme events"); + + let mut scheme = FbbootlogScheme::new(); + + event_queue + .subscribe( + scheme.input_handle.event_handle().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) + .expect("fbbootlogd: failed to subscribe to scheme events"); + + // This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics + // driver handoff. In the future inputd may directly pass a handle to the display instead. + //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace"); + + daemon.ready().expect("failed to notify parent"); + + for event in event_queue { + match event.expect("fbbootlogd: failed to get event").user_data { + Source::Scheme => { + loop { + let request = match socket.next_request(SignalBehavior::Restart) { + Ok(Some(request)) => request, + Ok(None) => { + // Scheme likely got unmounted + std::process::exit(0); + } + Err(err) if err.errno == EAGAIN => break, + Err(err) => panic!("fbbootlogd: failed to read display scheme: {err:?}"), + }; + + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut scheme); + + socket + .write_response(response, SignalBehavior::Restart) + .expect("pcid: failed to write next scheme response"); + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + _ => (), + } + } + } + Source::Input => { + let mut events = [Event::new(); 16]; + loop { + match scheme + .input_handle + .read_events(&mut events) + .expect("fbbootlogd: error while reading events") + { + ConsumerHandleEvent::Events(&[]) => break, + ConsumerHandleEvent::Events(events) => { + for event in events { + scheme.handle_input(&event); + } + } + ConsumerHandleEvent::Handoff => { + eprintln!("fbbootlogd: handoff requested"); + scheme.handle_handoff(); + } + } + } + } + } + } + + std::process::exit(0); +} diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs new file mode 100644 index 0000000000..58166f906c --- /dev/null +++ b/drivers/graphics/fbbootlogd/src/scheme.rs @@ -0,0 +1,285 @@ +use std::collections::VecDeque; +use std::{cmp, ptr}; + +use console_draw::TextScreen; +use graphics_ipc::v2::V2GraphicsHandle; +use inputd::ConsumerHandle; +use orbclient::{Event, EventOption}; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult}; +use syscall::schemev2::NewFdFlags; +use syscall::{Error, Result, EINVAL, ENOENT}; + +pub struct DisplayMap { + display_handle: V2GraphicsHandle, + fb: usize, + inner: graphics_ipc::v1::DisplayMap, +} + +pub struct FbbootlogScheme { + pub input_handle: ConsumerHandle, + display_map: Option, + text_screen: console_draw::TextScreen, + text_buffer: console_draw::TextBuffer, + is_scrollback: bool, + scrollback_offset: usize, + shift: bool, +} + +impl FbbootlogScheme { + pub fn new() -> FbbootlogScheme { + let mut scheme = FbbootlogScheme { + input_handle: ConsumerHandle::new_vt().expect("fbbootlogd: Failed to open vt"), + display_map: None, + text_screen: console_draw::TextScreen::new(), + text_buffer: console_draw::TextBuffer::new(1000), + is_scrollback: false, + scrollback_offset: 1000, + shift: false, + }; + + scheme.handle_handoff(); + + scheme + } + + pub fn handle_handoff(&mut self) { + let new_display_handle = match self.input_handle.open_display_v2() { + Ok(display) => V2GraphicsHandle::from_file(display).unwrap(), + Err(err) => { + eprintln!("fbbootlogd: No display present yet: {err}"); + return; + } + }; + + let (width, height) = new_display_handle.display_size(0).unwrap(); + let fb = new_display_handle + .create_dumb_framebuffer(width, height) + .unwrap(); + + match new_display_handle.map_dumb_framebuffer(fb, width, height) { + Ok(display_map) => { + self.display_map = Some(DisplayMap { + display_handle: new_display_handle, + fb, + inner: display_map, + }); + + eprintln!("fbbootlogd: mapped display"); + } + Err(err) => { + eprintln!("fbbootlogd: failed to open display: {}", err); + } + } + } + + pub fn handle_input(&mut self, ev: &Event) { + match ev.to_option() { + EventOption::Key(key_event) => { + if key_event.scancode == 0x2A || key_event.scancode == 0x36 { + self.shift = key_event.pressed; + } else if !key_event.pressed || !self.shift { + return; + } + match key_event.scancode { + 0x48 => { + // Up + if self.scrollback_offset >= 1 { + self.scrollback_offset -= 1; + } + } + 0x49 => { + // Page up + if self.scrollback_offset >= 10 { + self.scrollback_offset -= 10; + } else { + self.scrollback_offset = 0; + } + } + 0x50 => { + // Down + self.scrollback_offset += 1; + } + 0x51 => { + // Page down + self.scrollback_offset += 10; + } + 0x47 => { + // Home + self.scrollback_offset = 0; + } + 0x4F => { + // End + self.scrollback_offset = self.text_buffer.lines_max; + } + _ => return, + } + } + _ => return, + } + self.handle_scrollback_render(); + } + + fn handle_scrollback_render(&mut self) { + let Some(map) = &mut self.display_map else { + return; + }; + let buffer_len = self.text_buffer.lines.len(); + let dmap = &mut console_draw::DisplayMap { + offscreen: map.inner.ptr_mut(), + width: map.inner.width(), + height: map.inner.height(), + }; + // for both extra space on wrapping text and a scrollback indicator + let spare_lines = 3; + self.is_scrollback = true; + self.scrollback_offset = cmp::min( + self.scrollback_offset, + buffer_len - dmap.height / 16 + spare_lines, + ); + let mut i = self.scrollback_offset; + self.text_screen + .write(dmap, b"\x1B[1;1H\x1B[2J", &mut VecDeque::new()); + while i < buffer_len { + let mut damage = + self.text_screen + .write(dmap, &self.text_buffer.lines[i][..], &mut VecDeque::new()); + i += 1; + let yd = (damage.y + damage.height) as usize; + if i == buffer_len || yd + spare_lines * 16 > dmap.height { + // render until end of screen + damage.height = (dmap.height as u32) - damage.y; + map.display_handle.update_plane(0, map.fb, damage).unwrap(); + self.is_scrollback = i < buffer_len; + break; + } else { + map.display_handle.update_plane(0, map.fb, damage).unwrap(); + } + } + } + + fn handle_resize(map: &mut DisplayMap, text_screen: &mut TextScreen) { + let (width, height) = match map.display_handle.display_size(0) { + Ok((width, height)) => (width, height), + Err(err) => { + eprintln!("fbbootlogd: failed to get display size: {}", err); + (map.inner.width() as u32, map.inner.height() as u32) + } + }; + + if width as usize != map.inner.width() || height as usize != map.inner.height() { + match map.display_handle.create_dumb_framebuffer(width, height) { + Ok(fb) => match map.display_handle.map_dumb_framebuffer(fb, width, height) { + Ok(mut new_map) => { + let count = new_map.ptr().len(); + unsafe { + ptr::write_bytes(new_map.ptr_mut() as *mut u32, 0, count); + } + + text_screen.resize( + &mut console_draw::DisplayMap { + offscreen: map.inner.ptr_mut(), + width: map.inner.width(), + height: map.inner.height(), + }, + &mut console_draw::DisplayMap { + offscreen: new_map.ptr_mut(), + width: new_map.width(), + height: new_map.height(), + }, + ); + + let _ = map.display_handle.destroy_dumb_framebuffer(map.fb); + + map.fb = fb; + map.inner = new_map; + + eprintln!("fbbootlogd: mapped display"); + } + Err(err) => { + eprintln!("fbbootlogd: failed to open display: {}", err); + } + }, + Err(err) => { + eprintln!("fbbootlogd: failed to create framebuffer: {}", err); + } + } + } + } +} + +impl SchemeSync for FbbootlogScheme { + fn open(&mut self, path_str: &str, _flags: usize, _ctx: &CallerCtx) -> Result { + if !path_str.is_empty() { + return Err(Error::new(ENOENT)); + } + + Ok(OpenResult::ThisScheme { + number: 0, + flags: NewFdFlags::empty(), + }) + } + + fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let path = b"fbbootlog:"; + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } + + fn fsync(&mut self, _id: usize, _ctx: &CallerCtx) -> Result<()> { + Ok(()) + } + + fn read( + &mut self, + _id: usize, + _buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EINVAL)) + } + + fn write( + &mut self, + _id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + if let Some(map) = &mut self.display_map { + Self::handle_resize(map, &mut self.text_screen); + self.text_buffer.write(buf); + + if !self.is_scrollback { + let damage = self.text_screen.write( + &mut console_draw::DisplayMap { + offscreen: map.inner.ptr_mut(), + width: map.inner.width(), + height: map.inner.height(), + }, + buf, + &mut VecDeque::new(), + ); + + if let Some(map) = &self.display_map { + map.display_handle.update_plane(0, map.fb, damage).unwrap(); + } + } + } + + Ok(buf.len()) + } +} + +impl FbbootlogScheme { + pub fn on_close(&mut self, _id: usize) {} +} diff --git a/drivers/graphics/fbcond/Cargo.toml b/drivers/graphics/fbcond/Cargo.toml new file mode 100644 index 0000000000..435c15b1a7 --- /dev/null +++ b/drivers/graphics/fbcond/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "fbcond" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4" +orbclient = "0.3.27" +ransid = "0.4" +redox_event = "0.4" +redox_syscall = "0.5" +redox-daemon = "0.1" +redox-scheme = "0.4" + +common = { path = "../../common" } +console-draw = { path = "../console-draw" } +graphics-ipc = { path = "../graphics-ipc" } +inputd = { path = "../../inputd" } +libredox = "0.1.3" + +[features] +default = [] diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs new file mode 100644 index 0000000000..9ad75e7f63 --- /dev/null +++ b/drivers/graphics/fbcond/src/display.rs @@ -0,0 +1,115 @@ +use console_draw::TextScreen; +use graphics_ipc::v2::{Damage, V2GraphicsHandle}; +use inputd::ConsumerHandle; +use std::{io, ptr}; + +pub struct Display { + pub input_handle: ConsumerHandle, + pub map: Option, +} + +pub struct DisplayMap { + display_handle: V2GraphicsHandle, + fb: usize, + pub inner: graphics_ipc::v2::DisplayMap, +} + +impl Display { + pub fn open_new_vt() -> io::Result { + let mut display = Self { + input_handle: ConsumerHandle::new_vt()?, + map: None, + }; + + display.reopen_for_handoff(); + + Ok(display) + } + + /// Re-open the display after a handoff. + pub fn reopen_for_handoff(&mut self) { + let display_file = self.input_handle.open_display_v2().unwrap(); + let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap(); + + log::debug!("fbcond: Opened new display"); + + let (width, height) = new_display_handle.display_size(0).unwrap(); + let fb = new_display_handle + .create_dumb_framebuffer(width, height) + .unwrap(); + + match new_display_handle.map_dumb_framebuffer(fb, width, height) { + Ok(map) => { + log::debug!( + "fbcond: Mapped new display with size {}x{}", + map.width(), + map.height() + ); + + self.map = Some(DisplayMap { + display_handle: new_display_handle, + fb, + inner: map, + }); + } + Err(err) => { + log::error!("failed to map display: {}", err); + } + } + } + + pub fn handle_resize(map: &mut DisplayMap, text_screen: &mut TextScreen) { + let (width, height) = match map.display_handle.display_size(0) { + Ok((width, height)) => (width, height), + Err(err) => { + log::error!("fbcond: failed to get display size: {}", err); + (map.inner.width() as u32, map.inner.height() as u32) + } + }; + + if width as usize != map.inner.width() || height as usize != map.inner.height() { + match map.display_handle.create_dumb_framebuffer(width, height) { + Ok(fb) => match map.display_handle.map_dumb_framebuffer(fb, width, height) { + Ok(mut new_map) => { + let count = new_map.ptr().len(); + unsafe { + ptr::write_bytes(new_map.ptr_mut() as *mut u32, 0, count); + } + + text_screen.resize( + &mut console_draw::DisplayMap { + offscreen: map.inner.ptr_mut(), + width: map.inner.width(), + height: map.inner.height(), + }, + &mut console_draw::DisplayMap { + offscreen: new_map.ptr_mut(), + width: new_map.width(), + height: new_map.height(), + }, + ); + + let _ = map.display_handle.destroy_dumb_framebuffer(map.fb); + + map.fb = fb; + map.inner = new_map; + + log::debug!("fbcond: mapped display"); + } + Err(err) => { + log::error!("fbcond: failed to open display: {}", err); + } + }, + Err(err) => { + log::error!("fbcond: failed to create framebuffer: {}", err); + } + } + } + } + + pub fn sync_rect(&mut self, damage: Damage) { + if let Some(map) = &self.map { + map.display_handle.update_plane(0, map.fb, damage).unwrap(); + } + } +} diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs new file mode 100644 index 0000000000..5b9bfd73e8 --- /dev/null +++ b/drivers/graphics/fbcond/src/main.rs @@ -0,0 +1,181 @@ +use event::EventQueue; +use inputd::ConsumerHandleEvent; +use libredox::errno::{EAGAIN, EINTR}; +use orbclient::Event; +use redox_scheme::{CallRequest, RequestKind, Response, SignalBehavior, Socket}; +use std::env; +use syscall::EVENT_READ; + +use crate::scheme::{FbconScheme, VtIndex}; + +mod display; +mod scheme; +mod text; + +fn main() { + let vt_ids = env::args() + .skip(1) + .map(|arg| arg.parse().expect("invalid vt number")) + .collect::>(); + + common::setup_logging( + "graphics", + "fbcond", + "fbcond", + common::output_level(), + common::file_level() + ); + + redox_daemon::Daemon::new(|daemon| inner(daemon, &vt_ids)).expect("failed to create daemon"); +} +fn inner(daemon: redox_daemon::Daemon, vt_ids: &[usize]) -> ! { + let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue"); + + // FIXME listen for resize events from inputd and handle them + + let mut socket = Socket::nonblock("fbcon").expect("fbcond: failed to create fbcon scheme"); + event_queue + .subscribe( + socket.inner().raw(), + VtIndex::SCHEMA_SENTINEL, + event::EventFlags::READ, + ) + .expect("fbcond: failed to subscribe to scheme events"); + + let mut scheme = FbconScheme::new(vt_ids, &mut event_queue); + + // This is not possible for now as fbcond needs to open new displays at runtime for graphics + // driver handoff. In the future inputd may directly pass a handle to the display instead. + //libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace"); + + daemon.ready().expect("failed to notify parent"); + + let mut blocked = Vec::new(); + + // Handle all events that could have happened before registering with the event queue. + handle_event( + &mut socket, + &mut scheme, + &mut blocked, + VtIndex::SCHEMA_SENTINEL, + ); + for vt_i in scheme.vts.keys().copied().collect::>() { + handle_event(&mut socket, &mut scheme, &mut blocked, vt_i); + } + + for event in event_queue { + let event = event.expect("fbcond: failed to read event from event queue"); + handle_event(&mut socket, &mut scheme, &mut blocked, event.user_data); + } + + std::process::exit(0); +} + +fn handle_event( + socket: &mut Socket, + scheme: &mut FbconScheme, + blocked: &mut Vec, + event: VtIndex, +) { + match event { + VtIndex::SCHEMA_SENTINEL => { + loop { + let request = match socket.next_request(SignalBehavior::Restart) { + Ok(Some(request)) => request, + Ok(None) => { + // Scheme likely got unmounted + std::process::exit(0); + } + Err(err) if err.errno == EAGAIN => break, + Err(err) => panic!("vesad: failed to read display scheme: {err}"), + }; + + match request.kind() { + RequestKind::Call(call_request) => { + if let Some(resp) = call_request.handle_scheme_block(scheme) { + socket + .write_response(resp, SignalBehavior::Restart) + .expect("fbcond: failed to write display scheme"); + } else { + blocked.push(call_request); + } + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + RequestKind::Cancellation(cancellation_request) => { + if let Some(i) = blocked + .iter() + .position(|req| req.request().request_id() == cancellation_request.id) + { + let blocked_req = blocked.remove(i); + let resp = Response::new(&blocked_req, Err(syscall::Error::new(EINTR))); + socket + .write_response(resp, SignalBehavior::Restart) + .expect("vesad: failed to write display scheme"); + } + } + _ => {} + } + } + } + vt_i => { + let vt = scheme.vts.get_mut(&vt_i).unwrap(); + + let mut events = [Event::new(); 16]; + loop { + match vt + .display + .input_handle + .read_events(&mut events) + .expect("fbcond: Error while reading events") + { + ConsumerHandleEvent::Events(&[]) => break, + ConsumerHandleEvent::Events(events) => { + for event in events { + vt.input(event) + } + } + ConsumerHandleEvent::Handoff => vt.handle_handoff(), + } + } + } + } + + // If there are blocked readers, try to handle them. + { + let mut i = 0; + while i < blocked.len() { + if let Some(resp) = blocked[i].handle_scheme_block(scheme) { + socket + .write_response(resp, SignalBehavior::Restart) + .expect("vesad: failed to write display scheme"); + blocked.remove(i); + } else { + i += 1; + } + } + } + + for (handle_id, handle) in scheme.handles.iter_mut() { + if !handle.events.contains(EVENT_READ) { + continue; + } + + let can_read = scheme + .vts + .get(&handle.vt_i) + .map_or(false, |console| console.can_read()); + + if can_read { + if !handle.notified_read { + handle.notified_read = true; + socket + .post_fevent(*handle_id, EVENT_READ.bits()) + .expect("fbcond: failed to write display event"); + } + } else { + handle.notified_read = false; + } + } +} diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs new file mode 100644 index 0000000000..76c17ff210 --- /dev/null +++ b/drivers/graphics/fbcond/src/scheme.rs @@ -0,0 +1,170 @@ +use std::collections::BTreeMap; +use std::os::fd::AsRawFd; + +use event::{EventQueue, UserData}; +use redox_scheme::SchemeBlock; +use syscall::{Error, EventFlags, Result, EBADF, ENOENT, O_NONBLOCK}; + +use crate::display::Display; +use crate::text::TextScreen; + +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Debug)] +pub struct VtIndex(usize); + +impl VtIndex { + pub const SCHEMA_SENTINEL: VtIndex = VtIndex(usize::MAX); +} + +impl UserData for VtIndex { + fn into_user_data(self) -> usize { + self.0 + } + + fn from_user_data(user_data: usize) -> Self { + VtIndex(user_data) + } +} + +pub struct Handle { + pub vt_i: VtIndex, + pub flags: usize, + pub events: EventFlags, + pub notified_read: bool, +} + +pub struct FbconScheme { + pub vts: BTreeMap, + next_id: usize, + pub handles: BTreeMap, +} + +impl FbconScheme { + pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue) -> FbconScheme { + let mut vts = BTreeMap::new(); + + for &vt_i in vt_ids { + let display = Display::open_new_vt().expect("Failed to open display for vt"); + event_queue + .subscribe( + display.input_handle.event_handle().as_raw_fd() as usize, + VtIndex(vt_i), + event::EventFlags::READ, + ) + .expect("Failed to subscribe to input events for vt"); + vts.insert(VtIndex(vt_i), TextScreen::new(display)); + } + + FbconScheme { + vts, + next_id: 0, + handles: BTreeMap::new(), + } + } +} + +impl SchemeBlock for FbconScheme { + fn open( + &mut self, + path_str: &str, + flags: usize, + _uid: u32, + _gid: u32, + ) -> Result> { + let vt_i = VtIndex(path_str.parse::().map_err(|_| Error::new(ENOENT))?); + if let Some(_console) = self.vts.get_mut(&vt_i) { + let id = self.next_id; + self.next_id += 1; + + self.handles.insert( + id, + Handle { + vt_i, + flags, + events: EventFlags::empty(), + notified_read: false, + }, + ); + + Ok(Some(id)) + } else { + Err(Error::new(ENOENT)) + } + } + + fn fevent( + &mut self, + id: usize, + flags: syscall::EventFlags, + ) -> Result> { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + handle.notified_read = false; + + handle.events = flags; + Ok(Some(syscall::EventFlags::empty())) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + let path_str = format!("fbcon:{}", handle.vt_i.0); + + let path = path_str.as_bytes(); + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(Some(i)) + } + + fn fsync(&mut self, id: usize) -> Result> { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + return Ok(Some(0)); + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + ) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + if let Some(screen) = self.vts.get_mut(&handle.vt_i) { + if !screen.can_read() && handle.flags & O_NONBLOCK != O_NONBLOCK { + return Ok(None); + } else { + return screen.read(buf).map(Some); + } + } + + Err(Error::new(EBADF)) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + ) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + if let Some(console) = self.vts.get_mut(&handle.vt_i) { + console.write(buf).map(Some) + } else { + Err(Error::new(EBADF)) + } + } +} + +impl FbconScheme { + pub fn on_close(&mut self, id: usize) { + self.handles.remove(&id); + } +} diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs new file mode 100644 index 0000000000..8cbd70b348 --- /dev/null +++ b/drivers/graphics/fbcond/src/text.rs @@ -0,0 +1,142 @@ +use std::collections::VecDeque; + +use orbclient::{Event, EventOption}; +use syscall::error::*; + +use crate::display::Display; + +pub struct TextScreen { + pub display: Display, + inner: console_draw::TextScreen, + ctrl: bool, + input: VecDeque, +} + +impl TextScreen { + pub fn new(display: Display) -> TextScreen { + TextScreen { + display, + inner: console_draw::TextScreen::new(), + ctrl: false, + input: VecDeque::new(), + } + } + + pub fn handle_handoff(&mut self) { + log::info!("fbcond: Performing handoff"); + self.display.reopen_for_handoff(); + } + + pub fn input(&mut self, event: &Event) { + let mut buf = vec![]; + + match event.to_option() { + EventOption::Key(key_event) => { + if key_event.scancode == 0x1D { + self.ctrl = key_event.pressed; + } else if key_event.pressed { + match key_event.scancode { + 0x0E => { + // Backspace + buf.extend_from_slice(b"\x7F"); + } + 0x47 => { + // Home + buf.extend_from_slice(b"\x1B[H"); + } + 0x48 => { + // Up + buf.extend_from_slice(b"\x1B[A"); + } + 0x49 => { + // Page up + buf.extend_from_slice(b"\x1B[5~"); + } + 0x4B => { + // Left + buf.extend_from_slice(b"\x1B[D"); + } + 0x4D => { + // Right + buf.extend_from_slice(b"\x1B[C"); + } + 0x4F => { + // End + buf.extend_from_slice(b"\x1B[F"); + } + 0x50 => { + // Down + buf.extend_from_slice(b"\x1B[B"); + } + 0x51 => { + // Page down + buf.extend_from_slice(b"\x1B[6~"); + } + 0x52 => { + // Insert + buf.extend_from_slice(b"\x1B[2~"); + } + 0x53 => { + // Delete + buf.extend_from_slice(b"\x1B[3~"); + } + _ => { + let c = match key_event.character { + c @ 'A'..='Z' if self.ctrl => ((c as u8 - b'A') + b'\x01') as char, + c @ 'a'..='z' if self.ctrl => ((c as u8 - b'a') + b'\x01') as char, + c => c, + }; + + if c != '\0' { + let mut b = [0; 4]; + buf.extend_from_slice(c.encode_utf8(&mut b).as_bytes()); + } + } + } + } + } + _ => (), //TODO: Mouse in terminal + } + + for &b in buf.iter() { + self.input.push_back(b); + } + } + + pub fn can_read(&self) -> bool { + !self.input.is_empty() + } +} + +impl TextScreen { + pub fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + + while i < buf.len() && !self.input.is_empty() { + buf[i] = self.input.pop_front().unwrap(); + i += 1; + } + + Ok(i) + } + + pub fn write(&mut self, buf: &[u8]) -> Result { + if let Some(map) = &mut self.display.map { + Display::handle_resize(map, &mut self.inner); + + let damage = self.inner.write( + &mut console_draw::DisplayMap { + offscreen: map.inner.ptr_mut(), + width: map.inner.width(), + height: map.inner.height(), + }, + buf, + &mut self.input, + ); + + self.display.sync_rect(damage); + } + + Ok(buf.len()) + } +} diff --git a/drivers/graphics/graphics-ipc/Cargo.toml b/drivers/graphics/graphics-ipc/Cargo.toml new file mode 100644 index 0000000000..7491222b9c --- /dev/null +++ b/drivers/graphics/graphics-ipc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "graphics-ipc" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4" +libredox = "0.1.3" + +common = { path = "../../common" } diff --git a/drivers/graphics/graphics-ipc/src/common.rs b/drivers/graphics/graphics-ipc/src/common.rs new file mode 100644 index 0000000000..81e02640d5 --- /dev/null +++ b/drivers/graphics/graphics-ipc/src/common.rs @@ -0,0 +1,75 @@ +use std::cmp; + +// Keep synced with orbital's SyncRect +// Technically orbital uses i32 rather than u32, but values larger than i32::MAX +// would be a bug anyway. +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct Damage { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +impl Damage { + #[must_use] + pub fn clip(mut self, width: u32, height: u32) -> Self { + // Clip damage + let x2 = self.x + self.width; + self.x = cmp::min(self.x, width); + if x2 > width { + self.width = width - self.x; + } + + let y2 = self.y + self.height; + self.y = cmp::min(self.y, height); + if y2 > height { + self.height = height - self.y; + } + self + } +} + +pub struct DisplayMap { + offscreen: *mut [u32], + width: usize, + height: usize, +} + +impl DisplayMap { + pub(crate) unsafe fn new(offscreen: *mut [u32], width: usize, height: usize) -> Self { + DisplayMap { + offscreen, + width, + height, + } + } + + pub fn ptr(&self) -> *const [u32] { + self.offscreen + } + + pub fn ptr_mut(&mut self) -> *mut [u32] { + self.offscreen + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } +} + +unsafe impl Send for DisplayMap {} +unsafe impl Sync for DisplayMap {} + +impl Drop for DisplayMap { + fn drop(&mut self) { + unsafe { + let _ = libredox::call::munmap(self.offscreen as *mut (), self.offscreen.len()); + } + } +} diff --git a/drivers/graphics/graphics-ipc/src/lib.rs b/drivers/graphics/graphics-ipc/src/lib.rs new file mode 100644 index 0000000000..ed4b73a675 --- /dev/null +++ b/drivers/graphics/graphics-ipc/src/lib.rs @@ -0,0 +1,3 @@ +mod common; +pub mod v1; +pub mod v2; diff --git a/drivers/graphics/graphics-ipc/src/v1.rs b/drivers/graphics/graphics-ipc/src/v1.rs new file mode 100644 index 0000000000..640d117bab --- /dev/null +++ b/drivers/graphics/graphics-ipc/src/v1.rs @@ -0,0 +1,90 @@ +use std::fs::File; +use std::os::unix::io::AsRawFd; +use std::{io, mem, ptr, slice}; + +use libredox::flag; + +pub use crate::common::Damage; +pub use crate::common::DisplayMap; + +/// A graphics handle using the v1 graphics API. +/// +/// The v1 graphics API only allows a single framebuffer for each VT, requires each display to be +/// handled separately and doesn't support page flipping. +/// +/// This API is stable. No breaking changes are allowed to be made without a version bump. +pub struct V1GraphicsHandle { + file: File, +} + +impl V1GraphicsHandle { + pub fn from_file(file: File) -> io::Result { + Ok(V1GraphicsHandle { file }) + } + + pub fn map_display(&self) -> io::Result { + let mut buf: [u8; 4096] = [0; 4096]; + let count = + libredox::call::fpath(self.file.as_raw_fd() as usize, &mut buf).unwrap_or_else(|e| { + panic!("Could not read display path with fpath(): {e}"); + }); + + let url = + String::from_utf8(Vec::from(&buf[..count])).expect("Could not create Utf8 Url String"); + let path = url.split(':').nth(1).expect("Could not get path from url"); + + let mut path_parts = path.split('/').skip(1); + let width = path_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + let height = path_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + + let display_ptr = unsafe { + libredox::call::mmap(libredox::call::MmapArgs { + fd: self.file.as_raw_fd() as usize, + offset: 0, + length: (width * height * 4), + prot: flag::PROT_READ | flag::PROT_WRITE, + flags: flag::MAP_SHARED, + addr: core::ptr::null_mut(), + })? + }; + let offscreen = ptr::slice_from_raw_parts_mut(display_ptr as *mut u32, width * height); + + Ok(unsafe { DisplayMap::new(offscreen, width, height) }) + } + + pub fn sync_full_screen(&self) -> io::Result<()> { + libredox::call::fsync(self.file.as_raw_fd() as usize)?; + Ok(()) + } + + pub fn sync_rect(&self, sync_rect: Damage) -> io::Result<()> { + libredox::call::write(self.file.as_raw_fd() as usize, unsafe { + slice::from_raw_parts( + ptr::addr_of!(sync_rect).cast::(), + mem::size_of::(), + ) + })?; + Ok(()) + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct CursorDamage { + pub header: u32, + pub x: i32, + pub y: i32, + pub hot_x: i32, + pub hot_y: i32, + pub width: i32, + pub height: i32, + pub cursor_img_bytes: [u32; 4096], +} diff --git a/drivers/graphics/graphics-ipc/src/v2.rs b/drivers/graphics/graphics-ipc/src/v2.rs new file mode 100644 index 0000000000..ca23f82e25 --- /dev/null +++ b/drivers/graphics/graphics-ipc/src/v2.rs @@ -0,0 +1,201 @@ +use std::fs::File; +use std::os::unix::io::AsRawFd; +use std::{io, mem, ptr}; + +use libredox::flag; + +pub use crate::common::{Damage, DisplayMap}; + +extern "C" { + fn redox_sys_call_v0( + fd: usize, + payload: *mut u8, + payload_len: usize, + flags: usize, + metadata: *const u64, + metadata_len: usize, + ) -> usize; +} + +unsafe fn sys_call( + fd: &impl AsRawFd, + payload: &mut T, + flags: usize, + metadata: &[u64], +) -> libredox::error::Result { + libredox::error::Error::demux(redox_sys_call_v0( + fd.as_raw_fd() as usize, + payload as *mut T as *mut u8, + mem::size_of::(), + flags, + metadata.as_ptr(), + metadata.len(), + )) +} + +/// A graphics handle using the v2 graphics API. +/// +/// The v2 graphics API allows creating framebuffers on the fly, using them for page flipping and +/// handles all displays using a single fd. +/// +/// This API is not yet stable. Do not depend on it outside of the drivers repo until it has been +/// stabilized. +pub struct V2GraphicsHandle { + file: File, +} + +impl V2GraphicsHandle { + pub fn from_file(file: File) -> io::Result { + Ok(V2GraphicsHandle { file }) + } + + pub fn display_count(&self) -> io::Result { + let mut cmd = ipc::DisplayCount { count: 0 }; + unsafe { + sys_call(&self.file, &mut cmd, 0, &[ipc::DISPLAY_COUNT, 0, 0])?; + } + Ok(cmd.count) + } + + pub fn display_size(&self, id: usize) -> io::Result<(u32, u32)> { + let mut cmd = ipc::DisplaySize { + display_id: id, + width: 0, + height: 0, + }; + unsafe { + sys_call(&self.file, &mut cmd, 0, &[ipc::DISPLAY_SIZE, 0, 0])?; + } + Ok((cmd.width, cmd.height)) + } + + pub fn create_dumb_framebuffer(&self, width: u32, height: u32) -> io::Result { + let mut cmd = ipc::CreateDumbFramebuffer { + width, + height, + + fb_id: 0, + }; + unsafe { + sys_call( + &self.file, + &mut cmd, + 0, + &[ipc::CREATE_DUMB_FRAMEBUFFER, 0, 0], + )?; + } + Ok(cmd.fb_id) + } + + pub fn map_dumb_framebuffer( + &self, + id: usize, + width: u32, + height: u32, + ) -> io::Result { + let mut cmd = ipc::DumbFramebufferMapOffset { + fb_id: id, + offset: 0, + }; + unsafe { + sys_call( + &self.file, + &mut cmd, + 0, + &[ipc::DUMB_FRAMEBUFFER_MAP_OFFSET, 0, 0], + )?; + } + + let display_ptr = unsafe { + libredox::call::mmap(libredox::call::MmapArgs { + fd: self.file.as_raw_fd() as usize, + offset: cmd.offset as u64, + length: (width * height * 4) as usize, + prot: flag::PROT_READ | flag::PROT_WRITE, + flags: flag::MAP_SHARED, + addr: core::ptr::null_mut(), + })? + }; + let offscreen = ptr::slice_from_raw_parts_mut( + display_ptr as *mut u32, + width as usize * height as usize, + ); + + Ok(unsafe { DisplayMap::new(offscreen, width as usize, height as usize) }) + } + + pub fn destroy_dumb_framebuffer(&self, id: usize) -> io::Result { + let mut cmd = ipc::DestroyDumbFramebuffer { fb_id: id }; + unsafe { + sys_call( + &self.file, + &mut cmd, + 0, + &[ipc::DESTROY_DUMB_FRAMEBUFFER, 0, 0], + )?; + } + Ok(cmd.fb_id) + } + + pub fn update_plane(&self, display_id: usize, fb_id: usize, damage: Damage) -> io::Result<()> { + let mut cmd = ipc::UpdatePlane { + display_id, + fb_id, + damage, + }; + unsafe { + sys_call(&self.file, &mut cmd, 0, &[ipc::UPDATE_PLANE, 0, 0])?; + } + Ok(()) + } +} + +pub mod ipc { + use crate::common::Damage; + + pub const DISPLAY_COUNT: u64 = 1; + #[repr(C, packed)] + pub struct DisplayCount { + pub count: usize, + } + + pub const DISPLAY_SIZE: u64 = 2; + #[repr(C, packed)] + pub struct DisplaySize { + pub display_id: usize, + + pub width: u32, + pub height: u32, + } + + pub const CREATE_DUMB_FRAMEBUFFER: u64 = 3; + #[repr(C, packed)] + pub struct CreateDumbFramebuffer { + pub width: u32, + pub height: u32, + + pub fb_id: usize, + } + + pub const DUMB_FRAMEBUFFER_MAP_OFFSET: u64 = 4; + #[repr(C, packed)] + pub struct DumbFramebufferMapOffset { + pub fb_id: usize, + + pub offset: usize, + } + + pub const DESTROY_DUMB_FRAMEBUFFER: u64 = 5; + #[repr(C, packed)] + pub struct DestroyDumbFramebuffer { + pub fb_id: usize, + } + + pub const UPDATE_PLANE: u64 = 6; + #[repr(C, packed)] + pub struct UpdatePlane { + pub display_id: usize, + pub fb_id: usize, + pub damage: Damage, + } +} diff --git a/drivers/graphics/vesad/Cargo.toml b/drivers/graphics/vesad/Cargo.toml new file mode 100644 index 0000000000..fd983213a7 --- /dev/null +++ b/drivers/graphics/vesad/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "vesad" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" +redox_syscall = "0.5" +redox-daemon = "0.1" +redox_event = "0.4.1" + +common = { path = "../../common" } +driver-graphics = { path = "../driver-graphics" } +graphics-ipc = { path = "../graphics-ipc" } +inputd = { path = "../../inputd" } +libredox = "0.1.3" + +[features] +default = [] diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs new file mode 100644 index 0000000000..be52174509 --- /dev/null +++ b/drivers/graphics/vesad/src/main.rs @@ -0,0 +1,131 @@ +extern crate orbclient; +extern crate syscall; + +use driver_graphics::GraphicsScheme; +use event::{user_data, EventQueue}; +use inputd::DisplayHandle; +use std::env; +use std::os::fd::AsRawFd; + +use crate::scheme::{FbAdapter, FrameBuffer}; + +mod scheme; + +fn main() { + if env::var("FRAMEBUFFER_WIDTH").is_err() { + println!("vesad: No boot framebuffer"); + return; + } + + let width = usize::from_str_radix( + &env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"), + 16, + ) + .expect("failed to parse FRAMEBUFFER_WIDTH"); + let height = usize::from_str_radix( + &env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"), + 16, + ) + .expect("failed to parse FRAMEBUFFER_HEIGHT"); + let phys = usize::from_str_radix( + &env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"), + 16, + ) + .expect("failed to parse FRAMEBUFFER_ADDR"); + let stride = usize::from_str_radix( + &env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"), + 16, + ) + .expect("failed to parse FRAMEBUFFER_STRIDE"); + + println!( + "vesad: {}x{} stride {} at 0x{:X}", + width, height, stride, phys + ); + + if phys == 0 { + println!("vesad: Boot framebuffer at address 0"); + return; + } + + let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }]; + + //TODO: ideal maximum number of outputs? + for i in 1..1024 { + match env::var(&format!("FRAMEBUFFER{}", i)) { + Ok(var) => match unsafe { FrameBuffer::parse(&var) } { + Some(fb) => { + println!( + "vesad: framebuffer {}: {}x{} stride {} at 0x{:X}", + i, fb.width, fb.height, fb.stride, fb.phys + ); + framebuffers.push(fb); + } + None => { + eprintln!("vesad: framebuffer {}: failed to parse '{}'", i, var); + } + }, + Err(_err) => break, + }; + } + + redox_daemon::Daemon::new(|daemon| inner(daemon, framebuffers)) + .expect("failed to create daemon"); +} +fn inner(daemon: redox_daemon::Daemon, framebuffers: Vec) -> ! { + let mut inputd_display_handle = DisplayHandle::new_early("vesa").unwrap(); + + let mut scheme = GraphicsScheme::new(FbAdapter { framebuffers }, "display.vesa".to_owned()); + + user_data! { + enum Source { + Input, + Scheme, + } + } + + let event_queue: EventQueue = + EventQueue::new().expect("vesad: failed to create event queue"); + event_queue + .subscribe( + inputd_display_handle.inner().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace"); + + daemon.ready().expect("failed to notify parent"); + + let all = [Source::Input, Source::Scheme]; + for event in all + .into_iter() + .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data)) + { + match event { + Source::Input => { + while let Some(vt_event) = inputd_display_handle + .read_vt_event() + .expect("vesad: failed to read display handle") + { + scheme.handle_vt_event(vt_event); + } + } + Source::Scheme => { + scheme + .tick() + .expect("vesad: failed to handle scheme events"); + } + } + } + + panic!(); +} diff --git a/drivers/graphics/vesad/src/scheme.rs b/drivers/graphics/vesad/src/scheme.rs new file mode 100644 index 0000000000..42e4054e41 --- /dev/null +++ b/drivers/graphics/vesad/src/scheme.rs @@ -0,0 +1,188 @@ +use std::alloc::{self, Layout}; +use std::convert::TryInto; +use std::ptr::{self, NonNull}; + +use driver_graphics::{CursorFramebuffer, CursorPlane, Framebuffer, GraphicsAdapter}; +use graphics_ipc::v1::Damage; +use syscall::PAGE_SIZE; + +pub struct FbAdapter { + pub framebuffers: Vec, +} + +pub enum VesadCursor {} + +impl CursorFramebuffer for VesadCursor {} + +impl GraphicsAdapter for FbAdapter { + type Framebuffer = GraphicScreen; + type Cursor = VesadCursor; + + fn display_count(&self) -> usize { + self.framebuffers.len() + } + + fn display_size(&self, display_id: usize) -> (u32, u32) { + ( + self.framebuffers[display_id].width as u32, + self.framebuffers[display_id].height as u32, + ) + } + + fn create_dumb_framebuffer(&mut self, width: u32, height: u32) -> Self::Framebuffer { + GraphicScreen::new(width as usize, height as usize) + } + + fn map_dumb_framebuffer(&mut self, framebuffer: &Self::Framebuffer) -> *mut u8 { + framebuffer.ptr.as_ptr().cast::() + } + + fn update_plane(&mut self, display_id: usize, framebuffer: &Self::Framebuffer, damage: Damage) { + framebuffer.sync(&mut self.framebuffers[display_id], damage) + } + + fn supports_hw_cursor(&self) -> bool { + false + } + + fn create_cursor_framebuffer(&mut self) -> VesadCursor { + unimplemented!("Vesad does not support this function"); + } + + fn map_cursor_framebuffer(&mut self, _cursor: &Self::Cursor) -> *mut u8 { + unimplemented!("Vesad does not support this function"); + } + + fn handle_cursor(&mut self, _cursor: &CursorPlane, _dirty_fb: bool) { + unimplemented!("Vesad does not support this function"); + } +} + +pub struct FrameBuffer { + pub onscreen: *mut [u32], + pub phys: usize, + pub width: usize, + pub height: usize, + pub stride: usize, +} + +impl FrameBuffer { + pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self { + let size = stride * height; + let virt = common::physmap( + phys, + size * 4, + common::Prot { + read: true, + write: true, + }, + common::MemoryType::WriteCombining, + ) + .expect("vesad: failed to map framebuffer") as *mut u32; + + let onscreen = ptr::slice_from_raw_parts_mut(virt, size); + + Self { + onscreen, + phys, + width, + height, + stride, + } + } + + pub unsafe fn parse(var: &str) -> Option { + fn parse_number(part: &str) -> Option { + let (start, radix) = if part.starts_with("0x") { + (2, 16) + } else { + (0, 10) + }; + match usize::from_str_radix(&part[start..], radix) { + Ok(ok) => Some(ok), + Err(err) => { + eprintln!("vesad: failed to parse '{}': {}", part, err); + None + } + } + } + + let mut parts = var.split(','); + let phys = parse_number(parts.next()?)?; + let width = parse_number(parts.next()?)?; + let height = parse_number(parts.next()?)?; + let stride = parse_number(parts.next()?)?; + Some(Self::new(phys, width, height, stride)) + } +} + +pub struct GraphicScreen { + width: usize, + height: usize, + ptr: NonNull<[u32]>, +} + +impl GraphicScreen { + fn new(width: usize, height: usize) -> GraphicScreen { + let len = width * height; + let layout = Self::layout(len); + let ptr = unsafe { alloc::alloc_zeroed(layout) }; + let ptr = ptr::slice_from_raw_parts_mut(ptr.cast(), len); + let ptr = NonNull::new(ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout)); + + GraphicScreen { width, height, ptr } + } + + #[inline] + fn layout(len: usize) -> Layout { + // optimizes to an integer mul + Layout::array::(len) + .unwrap() + .align_to(PAGE_SIZE) + .unwrap() + } +} + +impl Drop for GraphicScreen { + fn drop(&mut self) { + let layout = Self::layout(self.ptr.len()); + unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), layout) }; + } +} + +impl Framebuffer for GraphicScreen { + fn width(&self) -> u32 { + self.width as u32 + } + + fn height(&self) -> u32 { + self.height as u32 + } +} + +impl GraphicScreen { + fn sync(&self, framebuffer: &mut FrameBuffer, sync_rect: Damage) { + let sync_rect = sync_rect.clip( + self.width.try_into().unwrap(), + self.height.try_into().unwrap(), + ); + + let start_x: usize = sync_rect.x.try_into().unwrap(); + let start_y: usize = sync_rect.y.try_into().unwrap(); + let w: usize = sync_rect.width.try_into().unwrap(); + let h: usize = sync_rect.height.try_into().unwrap(); + + let offscreen_ptr = self.ptr.as_ptr() as *mut u32; + let onscreen_ptr = framebuffer.onscreen as *mut u32; // FIXME use as_mut_ptr once stable + + for row in start_y..start_y + h { + unsafe { + ptr::copy( + offscreen_ptr.add(row * self.width + start_x), + onscreen_ptr.add(row * framebuffer.stride + start_x), + w, + ); + } + } + } +} diff --git a/drivers/graphics/virtio-gpud/Cargo.toml b/drivers/graphics/virtio-gpud/Cargo.toml new file mode 100644 index 0000000000..ae340bc391 --- /dev/null +++ b/drivers/graphics/virtio-gpud/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "virtio-gpud" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +log = "0.4" +static_assertions = "1.1.0" +futures = { version = "0.3.28", features = ["executor"] } +anyhow = "1.0.71" + +common = { path = "../../common" } +driver-graphics = { path = "../driver-graphics" } +graphics-ipc = { path = "../graphics-ipc" } +virtio-core = { path = "../../virtio-core" } +pcid = { path = "../../pcid" } +inputd = { path = "../../inputd" } + +redox-daemon = "0.1" +redox_event = "0.4.1" +redox_syscall = "0.5" +orbclient = "0.3.27" +spin = "0.9.8" +libredox = "0.1.3" diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs new file mode 100644 index 0000000000..ad89ed78e5 --- /dev/null +++ b/drivers/graphics/virtio-gpud/src/main.rs @@ -0,0 +1,560 @@ +//! `virtio-gpu` is a virtio based graphics adapter. It can operate in 2D mode and in 3D mode. +//! +//! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support +//! on the host machine. + +// Notes for the future: +// +// `virtio-gpu` 2D acceleration is just blitting. 3D acceleration has 2 kinds: +// - virgl - OpenGL +// - venus - Vulkan +// +// The Venus driver requires support for the following from the `virtio-gpu` kernel driver: +// - VIRTGPU_PARAM_3D_FEATURES +// - VIRTGPU_PARAM_CAPSET_QUERY_FIX +// - VIRTGPU_PARAM_RESOURCE_BLOB +// - VIRTGPU_PARAM_HOST_VISIBLE +// - VIRTGPU_PARAM_CROSS_DEVICE +// - VIRTGPU_PARAM_CONTEXT_INIT +// +// cc https://docs.mesa3d.org/drivers/venus.html +// cc https://docs.mesa3d.org/drivers/virgl.html + +use std::os::fd::AsRawFd; +use std::sync::atomic::{AtomicU32, Ordering}; + +use event::{user_data, EventQueue}; +use pcid_interface::PciFunctionHandle; + +use virtio_core::utils::VolatileCell; +use virtio_core::MSIX_PRIMARY_VECTOR; + +mod scheme; + +const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0; +const VIRTIO_GPU_MAX_SCANOUTS: usize = 16; + +#[repr(C)] +pub struct GpuConfig { + /// Signals pending events to the driver. + pub events_read: VolatileCell, // read-only + /// Clears pending events in the device (write-to-clear). + pub events_clear: VolatileCell, // write-only + + pub num_scanouts: VolatileCell, + pub num_capsets: VolatileCell, +} + +impl GpuConfig { + #[inline] + pub fn num_scanouts(&self) -> u32 { + self.num_scanouts.get() + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u32)] +pub enum CommandTy { + Undefined = 0, + + // 2D commands + GetDisplayInfo = 0x0100, + ResourceCreate2d, + ResourceUnref, + SetScanout, + ResourceFlush, + TransferToHost2d, + ResourceAttachBacking, + ResourceDetachBacking, + GetCapsetInfo, + GetCapset, + GetEdid, + ResourceAssignUuid, + ResourceCreateBlob, + SetScanoutBlob, + + // 3D commands + CtxCreate = 0x0200, + CtxDestroy, + CtxAttachResource, + CtxDetachResource, + ResourceCreate3d, + TransferToHost3d, + TransferFromHost3d, + Submit3d, + ResourceMapBlob, + ResourceUnmapBlob, + + // cursor commands + UpdateCursor = 0x0300, + MoveCursor, + + // success responses + RespOkNodata = 0x1100, + RespOkDisplayInfo, + RespOkCapsetInfo, + RespOkCapset, + RespOkEdid, + RespOkResourceUuid, + RespOkMapInfo, + + // error responses + RespErrUnspec = 0x1200, + RespErrOutOfMemory, + RespErrInvalidScanoutId, + RespErrInvalidResourceId, + RespErrInvalidContextId, + RespErrInvalidParameter, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 4); + +const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0; +//const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1; + +#[derive(Debug)] +#[repr(C)] +pub struct ControlHeader { + pub ty: CommandTy, + pub flags: u32, + pub fence_id: u64, + pub ctx_id: u32, + pub ring_index: u8, + padding: [u8; 3], +} + +impl ControlHeader { + pub fn with_ty(ty: CommandTy) -> Self { + Self { + ty, + ..Default::default() + } + } +} + +impl Default for ControlHeader { + fn default() -> Self { + Self { + ty: CommandTy::Undefined, + flags: 0, + fence_id: 0, + ctx_id: 0, + ring_index: 0, + padding: [0; 3], + } + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct GpuRect { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +impl GpuRect { + pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self { + Self { + x, + y, + width, + height, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct DisplayInfo { + rect: GpuRect, + pub enabled: u32, + pub flags: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct GetDisplayInfo { + pub header: ControlHeader, + pub display_info: [DisplayInfo; VIRTIO_GPU_MAX_SCANOUTS], +} + +impl Default for GetDisplayInfo { + fn default() -> Self { + Self { + header: ControlHeader { + ty: CommandTy::GetDisplayInfo, + ..Default::default() + }, + + display_info: unsafe { core::mem::zeroed() }, + } + } +} + +static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`. + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[repr(C)] +pub struct ResourceId(u32); + +impl ResourceId { + fn alloc() -> Self { + ResourceId(RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst)) + } +} + +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum ResourceFormat { + Unknown = 0, + + Bgrx = 2, + Xrgb = 4, +} + +#[derive(Debug)] +#[repr(C)] +pub struct ResourceCreate2d { + pub header: ControlHeader, + resource_id: ResourceId, + format: ResourceFormat, + width: u32, + height: u32, +} + +impl ResourceCreate2d { + fn new(resource_id: ResourceId, format: ResourceFormat, width: u32, height: u32) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceCreate2d), + resource_id, + format, + width, + height, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct MemEntry { + pub address: u64, + pub length: u32, + pub padding: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct AttachBacking { + pub header: ControlHeader, + pub resource_id: ResourceId, + pub num_entries: u32, +} + +impl AttachBacking { + pub fn new(resource_id: ResourceId, num_entries: u32) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking), + resource_id, + num_entries, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct DetachBacking { + pub header: ControlHeader, + pub resource_id: ResourceId, + pub padding: u32, +} + +impl DetachBacking { + pub fn new(resource_id: ResourceId) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking), + resource_id, + padding: 0, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct ResourceFlush { + pub header: ControlHeader, + pub rect: GpuRect, + pub resource_id: ResourceId, + pub padding: u32, +} + +impl ResourceFlush { + pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceFlush), + rect, + resource_id, + padding: 0, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct ResourceUnref { + pub header: ControlHeader, + pub resource_id: ResourceId, + pub padding: u32, +} + +impl ResourceUnref { + pub fn new(resource_id: ResourceId) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceUnref), + resource_id, + padding: 0, + } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct SetScanout { + pub header: ControlHeader, + pub rect: GpuRect, + pub scanout_id: u32, + pub resource_id: ResourceId, +} + +impl SetScanout { + pub fn new(scanout_id: u32, resource_id: ResourceId, rect: GpuRect) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::SetScanout), + + rect, + scanout_id, + resource_id, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct XferToHost2d { + pub header: ControlHeader, + pub rect: GpuRect, + pub offset: u64, + pub resource_id: ResourceId, + pub padding: u32, +} + +impl XferToHost2d { + pub fn new(resource_id: ResourceId, rect: GpuRect, offset: u64) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::TransferToHost2d), + rect, + offset, + resource_id, + padding: 0, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct CursorPos { + pub scanout_id: u32, + pub x: i32, + pub y: i32, + _padding: u32, +} + +impl CursorPos { + pub fn new(scanout_id: u32, x: i32, y: i32) -> Self { + Self { + scanout_id, + x, + y, + _padding: 0, + } + } +} + +/* VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR */ +#[derive(Debug)] +#[repr(C)] +pub struct UpdateCursor { + pub header: ControlHeader, + pub pos: CursorPos, + pub resource_id: ResourceId, + pub hot_x: i32, + pub hot_y: i32, + _padding: u32, +} + +impl UpdateCursor { + pub fn update_cursor(x: i32, y: i32, hot_x: i32, hot_y: i32, resource_id: ResourceId) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::UpdateCursor), + pos: CursorPos::new(0, x, y), + resource_id, + hot_x, + hot_y, + _padding: 0, + } + } +} + +pub struct MoveCursor { + pub header: ControlHeader, + pub pos: CursorPos, + pub resource_id: ResourceId, + pub hot_x: i32, + pub hot_y: i32, + _padding: u32, +} + +impl MoveCursor { + pub fn move_cursor(x: i32, y: i32) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::MoveCursor), + pos: CursorPos::new(0, x, y), + resource_id: ResourceId(0), + hot_x: 0, + hot_y: 0, + _padding: 0, + } + } +} + +static DEVICE: spin::Once = spin::Once::new(); + +fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { + let mut pcid_handle = PciFunctionHandle::connect_default(); + + // Double check that we have the right device. + // + // 0x1050 - virtio-gpu + let pci_config = pcid_handle.config(); + + assert_eq!(pci_config.func.full_device_id.device_id, 0x1050); + log::info!("virtio-gpu: initiating startup sequence :^)"); + + let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?; + let config = unsafe { &mut *(device.device_space as *mut GpuConfig) }; + + // Negotiate features. + device.transport.finalize_features(); + + // Queue for sending control commands. + let control_queue = device + .transport + .setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + // Queue for sending cursor updates. + let cursor_queue = device + .transport + .setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + device.transport.setup_config_notify(MSIX_PRIMARY_VECTOR); + + device.transport.run_device(); + deamon.ready().unwrap(); + + let (mut scheme, mut inputd_handle) = futures::executor::block_on(scheme::GpuScheme::new( + config, + control_queue.clone(), + cursor_queue.clone(), + device.transport.clone(), + ))?; + + user_data! { + enum Source { + Input, + Scheme, + Interrupt, + } + } + + let event_queue: EventQueue = + EventQueue::new().expect("virtio-gpud: failed to create event queue"); + event_queue + .subscribe( + inputd_handle.inner().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + device.irq_handle.as_raw_fd() as usize, + Source::Interrupt, + event::EventFlags::READ, + ) + .unwrap(); + + let all = [Source::Input, Source::Scheme, Source::Interrupt]; + for event in all + .into_iter() + .chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data)) + { + match event { + Source::Input => { + while let Some(vt_event) = inputd_handle + .read_vt_event() + .expect("virtio-gpud: failed to read display handle") + { + scheme.handle_vt_event(vt_event); + } + } + Source::Scheme => { + scheme + .tick() + .expect("virtio-gpud: failed to process scheme events"); + } + Source::Interrupt => loop { + let before_gen = device.transport.config_generation(); + + let events = config.events_read.get(); + + if events & VIRTIO_GPU_EVENT_DISPLAY != 0 { + futures::executor::block_on(scheme.adapter_mut().update_displays(config)) + .unwrap(); + scheme.notify_displays_changed(); + config.events_clear.set(VIRTIO_GPU_EVENT_DISPLAY); + } + + let after_gen = device.transport.config_generation(); + if before_gen == after_gen { + break; + } + }, + } + } + + std::process::exit(0); +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + deamon(redox_daemon).unwrap(); + unreachable!(); +} + +pub fn main() { + common::setup_logging( + "graphics", + "pci", + "virtio-gpud", + common::output_level(), + common::file_level(), + ); + redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); +} diff --git a/drivers/graphics/virtio-gpud/src/scheme.rs b/drivers/graphics/virtio-gpud/src/scheme.rs new file mode 100644 index 0000000000..34159dac3f --- /dev/null +++ b/drivers/graphics/virtio-gpud/src/scheme.rs @@ -0,0 +1,423 @@ +use std::sync::Arc; + +use common::{dma::Dma, sgl}; +use driver_graphics::{ + CursorFramebuffer, CursorPlane, Framebuffer, GraphicsAdapter, GraphicsScheme, +}; +use graphics_ipc::v1::Damage; +use inputd::DisplayHandle; + +use syscall::PAGE_SIZE; + +use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; +use virtio_core::transport::{Error, Queue, Transport}; + +use crate::*; + +impl Into for Damage { + fn into(self) -> GpuRect { + GpuRect { + x: self.x, + y: self.y, + width: self.width, + height: self.height, + } + } +} + +pub struct VirtGpuFramebuffer<'a> { + queue: Arc>, + id: ResourceId, + sgl: sgl::Sgl, + width: u32, + height: u32, +} + +impl Framebuffer for VirtGpuFramebuffer<'_> { + fn width(&self) -> u32 { + self.width + } + + fn height(&self) -> u32 { + self.height + } +} + +impl Drop for VirtGpuFramebuffer<'_> { + fn drop(&mut self) { + futures::executor::block_on(async { + let request = Dma::new(ResourceUnref::new(self.id)).unwrap(); + + let header = Dma::new(ControlHeader::default()).unwrap(); + let command = ChainBuilder::new() + .chain(Buffer::new(&request)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.queue.send(command).await; + }); + } +} + +pub struct VirtGpuCursor { + resource_id: ResourceId, + sgl: sgl::Sgl, +} + +impl CursorFramebuffer for VirtGpuCursor {} + +#[derive(Debug, Copy, Clone)] +pub struct Display { + width: u32, + height: u32, + active_resource: Option, +} + +pub struct VirtGpuAdapter<'a> { + control_queue: Arc>, + cursor_queue: Arc>, + transport: Arc, + displays: Vec, +} + +impl VirtGpuAdapter<'_> { + pub async fn update_displays(&mut self, config: &mut GpuConfig) -> Result<(), Error> { + let mut display_info = self.get_display_info().await?; + let raw_displays = &mut display_info.display_info[..config.num_scanouts() as usize]; + + self.displays.resize( + raw_displays.len(), + Display { + width: 0, + height: 0, + active_resource: None, + }, + ); + for (i, info) in raw_displays.iter().enumerate() { + log::info!( + "virtio-gpu: display {i} ({}x{}px)", + info.rect.width, + info.rect.height + ); + + if info.rect.width == 0 || info.rect.height == 0 { + // QEMU gives all displays other than the first a zero width and height, but trying + // to attach a zero sized framebuffer to the display will result an error, so + // default to 640x480px. + self.displays[i].width = 640; + self.displays[i].height = 480; + } else { + self.displays[i].width = info.rect.width; + self.displays[i].height = info.rect.height; + } + } + + Ok(()) + } + + async fn send_request(&self, request: Dma) -> Result, Error> { + let header = Dma::new(ControlHeader::default())?; + let command = ChainBuilder::new() + .chain(Buffer::new(&request)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + Ok(header) + } + + async fn send_request_fenced(&self, request: Dma) -> Result, Error> { + let mut header = Dma::new(ControlHeader::default())?; + header.flags |= VIRTIO_GPU_FLAG_FENCE; + let command = ChainBuilder::new() + .chain(Buffer::new(&request)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + Ok(header) + } + + async fn get_display_info(&self) -> Result, Error> { + let header = Dma::new(ControlHeader::with_ty(CommandTy::GetDisplayInfo))?; + + let response = Dma::new(GetDisplayInfo::default())?; + let command = ChainBuilder::new() + .chain(Buffer::new(&header)) + .chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + assert!(response.header.ty == CommandTy::RespOkDisplayInfo); + + Ok(response) + } + + fn update_cursor(&mut self, cursor: &VirtGpuCursor, x: i32, y: i32, hot_x: i32, hot_y: i32) { + //Transfering cursor resource to host + futures::executor::block_on(async { + let transfer_request = Dma::new(XferToHost2d::new( + cursor.resource_id, + GpuRect { + x: 0, + y: 0, + width: 64, + height: 64, + }, + 0, + )) + .unwrap(); + let header = self.send_request_fenced(transfer_request).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + }); + + //Update the cursor position + let request = Dma::new(UpdateCursor::update_cursor( + x, + y, + hot_x, + hot_y, + cursor.resource_id, + )) + .unwrap(); + futures::executor::block_on(async { + let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); + self.cursor_queue.send(command).await; + }); + } + + fn move_cursor(&mut self, x: i32, y: i32) { + let request = Dma::new(MoveCursor::move_cursor(x, y)).unwrap(); + + futures::executor::block_on(async { + let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); + self.cursor_queue.send(command).await; + }); + } +} + +impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + type Framebuffer = VirtGpuFramebuffer<'a>; + type Cursor = VirtGpuCursor; + + fn display_count(&self) -> usize { + self.displays.len() + } + + fn display_size(&self, display_id: usize) -> (u32, u32) { + ( + self.displays[display_id].width, + self.displays[display_id].height, + ) + } + + fn create_dumb_framebuffer(&mut self, width: u32, height: u32) -> Self::Framebuffer { + futures::executor::block_on(async { + let bpp = 32; + let fb_size = width as usize * height as usize * bpp / 8; + let sgl = sgl::Sgl::new(fb_size).unwrap(); + + unsafe { + core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size); + } + + let res_id = ResourceId::alloc(); + + // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. + let request = Dma::new(ResourceCreate2d::new( + res_id, + ResourceFormat::Bgrx, + width, + height, + )) + .unwrap(); + + let header = self.send_request(request).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + + // Use the allocated framebuffer from the guest ram, and attach it as backing + // storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. + + let mut mem_entries = + unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() }; + for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) { + *entry = MemEntry { + address: chunk.phys as u64, + length: chunk.length.next_multiple_of(PAGE_SIZE) as u32, + padding: 0, + }; + } + + let attach_request = + Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap(); + let header = Dma::new(ControlHeader::default()).unwrap(); + let command = ChainBuilder::new() + .chain(Buffer::new(&attach_request)) + .chain(Buffer::new_unsized(&mem_entries)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + assert_eq!(header.ty, CommandTy::RespOkNodata); + + VirtGpuFramebuffer { + queue: self.control_queue.clone(), + id: res_id, + sgl, + width, + height, + } + }) + } + + fn map_dumb_framebuffer(&mut self, framebuffer: &Self::Framebuffer) -> *mut u8 { + framebuffer.sgl.as_ptr() + } + + fn update_plane(&mut self, display_id: usize, framebuffer: &Self::Framebuffer, damage: Damage) { + futures::executor::block_on(async { + let req = Dma::new(XferToHost2d::new( + framebuffer.id, + GpuRect { + x: 0, + y: 0, + width: framebuffer.width, + height: framebuffer.height, + }, + 0, + )) + .unwrap(); + let header = self.send_request(req).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + + // FIXME once we support resizing we also need to check that the current and target size match + if self.displays[display_id].active_resource != Some(framebuffer.id) { + let scanout_request = Dma::new(SetScanout::new( + display_id as u32, + framebuffer.id, + GpuRect::new(0, 0, framebuffer.width, framebuffer.height), + )) + .unwrap(); + let header = self.send_request(scanout_request).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + self.displays[display_id].active_resource = Some(framebuffer.id); + } + + let flush = ResourceFlush::new( + framebuffer.id, + damage.clip(framebuffer.width, framebuffer.height).into(), + ); + let header = self.send_request(Dma::new(flush).unwrap()).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + }); + } + + fn supports_hw_cursor(&self) -> bool { + true + } + + fn create_cursor_framebuffer(&mut self) -> VirtGpuCursor { + //Creating a new resource for the cursor + let fb_size = 64 * 64 * 4; + let sgl = sgl::Sgl::new(fb_size).unwrap(); + let res_id = ResourceId::alloc(); + + futures::executor::block_on(async { + unsafe { + core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 0, fb_size); + } + + let resource_request = + Dma::new(ResourceCreate2d::new(res_id, ResourceFormat::Bgrx, 64, 64)).unwrap(); + + let header = self.send_request_fenced(resource_request).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + + //Attaching cursor resource as backing storage + let mut mem_entries = + unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() }; + for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) { + *entry = MemEntry { + address: chunk.phys as u64, + length: chunk.length.next_multiple_of(PAGE_SIZE) as u32, + padding: 0, + }; + } + + let attach_request = + Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap(); + let mut header = Dma::new(ControlHeader::default()).unwrap(); + header.flags |= VIRTIO_GPU_FLAG_FENCE; + let command = ChainBuilder::new() + .chain(Buffer::new(&attach_request)) + .chain(Buffer::new_unsized(&mem_entries)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + assert_eq!(header.ty, CommandTy::RespOkNodata); + + //Transfering cursor resource to host + let transfer_request = Dma::new(XferToHost2d::new( + res_id, + GpuRect { + x: 0, + y: 0, + width: 64, + height: 64, + }, + 0, + )) + .unwrap(); + let header = self.send_request_fenced(transfer_request).await.unwrap(); + assert_eq!(header.ty, CommandTy::RespOkNodata); + }); + + VirtGpuCursor { + resource_id: res_id, + sgl, + } + } + + fn map_cursor_framebuffer(&mut self, cursor: &Self::Cursor) -> *mut u8 { + cursor.sgl.as_ptr() + } + + fn handle_cursor(&mut self, cursor: &CursorPlane, dirty_fb: bool) { + if dirty_fb { + self.update_cursor( + &cursor.framebuffer, + cursor.x, + cursor.y, + cursor.hot_x, + cursor.hot_y, + ); + } else { + self.move_cursor(cursor.x, cursor.y); + } + } +} + +pub struct GpuScheme {} + +impl<'a> GpuScheme { + pub async fn new( + config: &mut GpuConfig, + control_queue: Arc>, + cursor_queue: Arc>, + transport: Arc, + ) -> Result<(GraphicsScheme>, DisplayHandle), Error> { + let mut adapter = VirtGpuAdapter { + control_queue, + cursor_queue, + transport, + displays: vec![], + }; + + adapter.update_displays(config).await?; + + let scheme = GraphicsScheme::new(adapter, "display.virtio-gpu".to_owned()); + let handle = DisplayHandle::new("virtio-gpu").unwrap(); + Ok((scheme, handle)) + } +} diff --git a/drivers/hwd/.gitignore b/drivers/hwd/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/hwd/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/hwd/Cargo.toml b/drivers/hwd/Cargo.toml new file mode 100644 index 0000000000..9089880e81 --- /dev/null +++ b/drivers/hwd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "hwd" +version = "0.1.0" +edition = "2018" + +[dependencies] +fdt = "0.1.5" +log = "0.4" +redox-daemon = "0.1" +ron = "0.8.1" + +amlserde = { path = "../amlserde" } +common = { path = "../common" } diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs new file mode 100644 index 0000000000..42170b2221 --- /dev/null +++ b/drivers/hwd/src/backend/acpi.rs @@ -0,0 +1,98 @@ +use amlserde::{AmlSerde, AmlSerdeValue}; +use std::{error::Error, fs, process::Command}; + +use super::Backend; + +pub struct AcpiBackend { + rxsdt: Vec, +} + +impl Backend for AcpiBackend { + fn new() -> Result> { + let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?; + + // Spawn acpid + //TODO: pass rxsdt data to acpid? + Command::new("acpid").spawn()?.wait()?; + + Ok(Self { rxsdt }) + } + + fn probe(&mut self) -> Result<(), Box> { + // Read symbols from acpi scheme + for entry_res in fs::read_dir("/scheme/acpi/symbols")? { + let entry = entry_res?; + if let Some(file_name) = entry.file_name().to_str() { + if file_name.ends_with("_CID") || file_name.ends_with("_HID") { + let ron = fs::read_to_string(entry.path())?; + let AmlSerde { name, value } = ron::from_str(&ron)?; + let id = match value { + AmlSerdeValue::Integer(integer) => { + let vendor = integer & 0xFFFF; + let device = (integer >> 16) & 0xFFFF; + let vendor_rev = ((vendor & 0xFF) << 8) | vendor >> 8; + let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char; + let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char; + let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char; + //TODO: simplify this nibble swap + let device_1 = (device >> 4) & 0xF; + let device_2 = (device >> 0) & 0xF; + let device_3 = (device >> 12) & 0xF; + let device_4 = (device >> 8) & 0xF; + format!( + "{}{}{}{:01X}{:01X}{:01X}{:01X}", + vendor_1, + vendor_2, + vendor_3, + device_1, + device_2, + device_3, + device_4 + ) + } + AmlSerdeValue::String(string) => string, + _ => { + log::warn!("{}: unsupported value {:x?}", name, value); + continue; + } + }; + let what = match id.as_str() { + // https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html + "ACPI0003" => "Power source", + "ACPI0006" => "GPE block", + "ACPI0007" => "Processor", + "ACPI0010" => "Processor control", + // https://uefi.org/sites/default/files/resources/devids%20%285%29.txt + "PNP0000" => "AT interrupt controller", + "PNP0100" => "AT timer", + "PNP0103" => "HPET", + "PNP0200" => "AT DMA controller", + "PNP0303" => "IBM Enhanced (101/102-key, PS/2 mouse support)", + "PNP030B" => "PS/2 keyboard", + "PNP0400" => "Standard LPT printer port", + "PNP0501" => "16550A-compatible COM port", + "PNP0A03" | "PNP0A08" => "PCI bus", + "PNP0A05" => "Generic ACPI bus", + "PNP0A06" => "Generic ACPI Extended-IO bus (EIO bus)", + "PNP0B00" => "AT real-time clock", + "PNP0C01" => "System board", + "PNP0C02" => "Reserved resources", + "PNP0C04" => "Math coprocessor", + "PNP0C09" => "Embedded controller", + "PNP0C0A" => "Battery", + "PNP0C0B" => "Fan", + "PNP0C0C" => "Power button", + "PNP0C0D" => "Lid sensor", + "PNP0C0E" => "Sleep button", + "PNP0C0F" => "PCI interrupt link", + "PNP0C50" => "I2C HID", + "PNP0F13" => "PS/2 port for PS/2-style mouse", + _ => "?", + }; + log::debug!("{}: {} ({})", name, id, what); + } + } + } + Ok(()) + } +} diff --git a/drivers/hwd/src/backend/devicetree.rs b/drivers/hwd/src/backend/devicetree.rs new file mode 100644 index 0000000000..8a91d04e42 --- /dev/null +++ b/drivers/hwd/src/backend/devicetree.rs @@ -0,0 +1,45 @@ +use std::{error::Error, fs}; + +use super::Backend; + +pub struct DeviceTreeBackend { + dtb: Vec, +} + +impl DeviceTreeBackend { + fn dump(node: &fdt::node::FdtNode<'_, '_>, level: usize) { + let mut line = String::new(); + for _ in 0..level { + line.push_str(" "); + } + line.push_str(node.name); + if let Some(compatible) = node.compatible() { + line.push_str(":"); + for id in compatible.all() { + line.push_str(" "); + line.push_str(id); + } + } + log::debug!("{}", line); + for child in node.children() { + Self::dump(&child, level + 1); + } + } +} + +impl Backend for DeviceTreeBackend { + fn new() -> Result> { + let dtb = fs::read("/scheme/kernel.dtb")?; + let dt = fdt::Fdt::new(&dtb).map_err(|err| format!("failed to parse dtb: {}", err))?; + Ok(Self { dtb }) + } + + fn probe(&mut self) -> Result<(), Box> { + let dt = fdt::Fdt::new(&self.dtb).map_err(|err| format!("failed to parse dtb: {}", err))?; + let root = dt + .find_node("/") + .ok_or_else(|| format!("failed to find root node"))?; + Self::dump(&root, 0); + Ok(()) + } +} diff --git a/drivers/hwd/src/backend/legacy.rs b/drivers/hwd/src/backend/legacy.rs new file mode 100644 index 0000000000..23e9c1f24f --- /dev/null +++ b/drivers/hwd/src/backend/legacy.rs @@ -0,0 +1,16 @@ +use std::error::Error; + +use super::Backend; + +pub struct LegacyBackend; + +impl Backend for LegacyBackend { + fn new() -> Result> { + Ok(Self) + } + + fn probe(&mut self) -> Result<(), Box> { + log::info!("TODO: handle driver spawning from legacy backend"); + Ok(()) + } +} diff --git a/drivers/hwd/src/backend/mod.rs b/drivers/hwd/src/backend/mod.rs new file mode 100644 index 0000000000..815b48aa75 --- /dev/null +++ b/drivers/hwd/src/backend/mod.rs @@ -0,0 +1,14 @@ +use std::error::Error; + +mod acpi; +mod devicetree; +mod legacy; + +pub use self::{acpi::AcpiBackend, devicetree::DeviceTreeBackend, legacy::LegacyBackend}; + +pub trait Backend { + fn new() -> Result> + where + Self: Sized; + fn probe(&mut self) -> Result<(), Box>; +} diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs new file mode 100644 index 0000000000..b5dc4db127 --- /dev/null +++ b/drivers/hwd/src/main.rs @@ -0,0 +1,70 @@ +use std::process; + +mod backend; +use self::backend::{AcpiBackend, Backend, DeviceTreeBackend, LegacyBackend}; + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + common::setup_logging( + "misc", + "hwd", + "hwd", + common::output_level(), + common::file_level(), + ); + + // Prefer DTB if available (matches kernel preference) + let mut backend: Box = match DeviceTreeBackend::new() { + Ok(ok) => { + log::info!("using devicetree backend"); + Box::new(ok) + } + Err(err) => { + log::debug!("cannot use devicetree backend: {}", err); + match AcpiBackend::new() { + Ok(ok) => { + log::info!("using ACPI backend"); + Box::new(ok) + } + Err(err) => { + log::debug!("cannot use ACPI backend: {}", err); + + log::info!("using legacy backend"); + Box::new(LegacyBackend) + } + } + } + }; + + //TODO: launch pcid based on backend information? + // Must launch after acpid but before probe calls /scheme/acpi/symbols + match process::Command::new("pcid").spawn() { + Ok(mut child) => match child.wait() { + Ok(status) => if !status.success() { + log::error!("pcid exited with status {}", status); + }, + Err(err) => { + log::error!("failed to wait for pcid: {}", err); + } + }, + Err(err) => { + log::error!("failed to spawn pcid: {}", err); + } + } + + daemon.ready().expect("hwd: failed to notify parent"); + + //TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers + match backend.probe() { + Ok(()) => { + process::exit(0); + } + Err(err) => { + log::error!("failed to probe with error {}", err); + process::exit(1); + } + } +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("hwd: failed to daemonize"); +} \ No newline at end of file diff --git a/drivers/initfs.toml b/drivers/initfs.toml new file mode 100644 index 0000000000..12290f9b38 --- /dev/null +++ b/drivers/initfs.toml @@ -0,0 +1,37 @@ +## Drivers for InitFS ## + +# ahcid +[[drivers]] +name = "AHCI storage" +class = 1 +subclass = 6 +command = ["/scheme/initfs/lib/drivers/ahcid"] + +# ided +[[drivers]] +name = "IDE storage" +class = 1 +subclass = 1 +command = ["/scheme/initfs/lib/drivers/ided"] + +# nvmed +[[drivers]] +name = "NVME storage" +class = 1 +subclass = 8 +command = ["/scheme/initfs/lib/drivers/nvmed"] + +[[drivers]] +name = "virtio-blk" +class = 1 +subclass = 0 +vendor = 0x1AF4 +device = 0x1001 +command = ["/scheme/initfs/lib/drivers/virtio-blkd"] + +[[drivers]] +name = "virtio-gpu" +class = 3 +vendor = 0x1AF4 +device = 0x1050 +command = ["/scheme/initfs/lib/drivers/virtio-gpud"] diff --git a/drivers/input/ps2d/.gitignore b/drivers/input/ps2d/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/input/ps2d/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/input/ps2d/Cargo.toml b/drivers/input/ps2d/Cargo.toml new file mode 100644 index 0000000000..e03d148ff1 --- /dev/null +++ b/drivers/input/ps2d/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ps2d" +version = "0.1.0" +edition = "2018" + +[dependencies] +bitflags = "1" +log = "0.4" +orbclient = "0.3.27" +redox_event = "0.4.1" +redox_syscall = "0.5.10" +redox-daemon = "0.1" +redox-scheme = "0.6.2" +libredox = "0.1.3" + +common = { path = "../../common" } +inputd = { path = "../../inputd" } diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs new file mode 100644 index 0000000000..ae2326062e --- /dev/null +++ b/drivers/input/ps2d/src/controller.rs @@ -0,0 +1,480 @@ +use common::{ + io::{Io, Pio, ReadOnly, WriteOnly}, + timeout::Timeout, +}; +use log::{debug, error, trace}; + +use std::fmt; + +#[derive(Debug)] +pub enum Error { + CommandRetry, + NoMoreTries, + ReadTimeout, + WriteTimeout, +} + +bitflags! { + pub struct StatusFlags: u8 { + const OUTPUT_FULL = 1; + const INPUT_FULL = 1 << 1; + const SYSTEM = 1 << 2; + const COMMAND = 1 << 3; + // Chipset specific + const KEYBOARD_LOCK = 1 << 4; + // Chipset specific + const SECOND_OUTPUT_FULL = 1 << 5; + const TIME_OUT = 1 << 6; + const PARITY = 1 << 7; + } +} + +bitflags! { + pub struct ConfigFlags: u8 { + const FIRST_INTERRUPT = 1 << 0; + const SECOND_INTERRUPT = 1 << 1; + const POST_PASSED = 1 << 2; + // 1 << 3 should be zero + const CONFIG_RESERVED_3 = 1 << 3; + const FIRST_DISABLED = 1 << 4; + const SECOND_DISABLED = 1 << 5; + const FIRST_TRANSLATE = 1 << 6; + // 1 << 7 should be zero + const CONFIG_RESERVED_7 = 1 << 7; + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +#[allow(dead_code)] +enum Command { + ReadConfig = 0x20, + WriteConfig = 0x60, + DisableSecond = 0xA7, + EnableSecond = 0xA8, + TestSecond = 0xA9, + TestController = 0xAA, + TestFirst = 0xAB, + Diagnostic = 0xAC, + DisableFirst = 0xAD, + EnableFirst = 0xAE, + WriteSecond = 0xD4, +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +#[allow(dead_code)] +enum KeyboardCommand { + EnableReporting = 0xF4, + SetDefaultsDisable = 0xF5, + SetDefaults = 0xF6, + Reset = 0xFF, +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +enum KeyboardCommandData { + ScancodeSet = 0xF0, +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +#[allow(dead_code)] +enum MouseCommand { + SetScaling1To1 = 0xE6, + SetScaling2To1 = 0xE7, + StatusRequest = 0xE9, + GetDeviceId = 0xF2, + EnableReporting = 0xF4, + SetDefaultsDisable = 0xF5, + SetDefaults = 0xF6, + Reset = 0xFF, +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +enum MouseCommandData { + SetSampleRate = 0xF3, +} + +// Default timeout in microseconds +const DEFAULT_TIMEOUT: u64 = 50_000; +// Reset timeout in microseconds +const RESET_TIMEOUT: u64 = 500_000; + +pub struct Ps2 { + data: Pio, + status: ReadOnly>, + command: WriteOnly>, +} + +impl Ps2 { + pub fn new() -> Self { + Ps2 { + data: Pio::new(0x60), + status: ReadOnly::new(Pio::new(0x64)), + command: WriteOnly::new(Pio::new(0x64)), + } + } + + fn status(&mut self) -> StatusFlags { + StatusFlags::from_bits_truncate(self.status.read()) + } + + fn wait_read(&mut self, micros: u64) -> Result<(), Error> { + let timeout = Timeout::from_micros(micros); + loop { + if self.status().contains(StatusFlags::OUTPUT_FULL) { + return Ok(()); + } + timeout.run().map_err(|()| Error::ReadTimeout)? + } + } + + fn wait_write(&mut self, micros: u64) -> Result<(), Error> { + let timeout = Timeout::from_micros(micros); + loop { + if !self.status().contains(StatusFlags::INPUT_FULL) { + return Ok(()); + } + timeout.run().map_err(|()| Error::WriteTimeout)? + } + } + + fn command(&mut self, command: Command) -> Result<(), Error> { + self.wait_write(DEFAULT_TIMEOUT)?; + self.command.write(command as u8); + Ok(()) + } + + fn read(&mut self) -> Result { + self.read_timeout(DEFAULT_TIMEOUT) + } + + fn read_timeout(&mut self, micros: u64) -> Result { + self.wait_read(micros)?; + let data = self.data.read(); + Ok(data) + } + + fn write(&mut self, data: u8) -> Result<(), Error> { + self.wait_write(DEFAULT_TIMEOUT)?; + self.data.write(data); + Ok(()) + } + + fn retry Result>( + &mut self, + name: fmt::Arguments, + retries: usize, + f: F, + ) -> Result { + trace!("ps2d: {}", name); + let mut res = Err(Error::NoMoreTries); + for retry in 0..retries { + res = f(self); + match res { + Ok(ok) => { + return Ok(ok); + } + Err(ref err) => { + debug!("ps2d: {}: retry {}/{}: {:?}", name, retry + 1, retries, err); + } + } + } + res + } + + fn config(&mut self) -> Result { + self.retry(format_args!("read config"), 4, |x| { + x.command(Command::ReadConfig)?; + x.read() + }) + .map(ConfigFlags::from_bits_truncate) + } + + fn set_config(&mut self, config: ConfigFlags) -> Result<(), Error> { + self.retry(format_args!("write config"), 4, |x| { + x.command(Command::WriteConfig)?; + x.write(config.bits())?; + Ok(0) + })?; + Ok(()) + } + + fn keyboard_command_inner(&mut self, command: u8) -> Result { + self.write(command)?; + match self.read()? { + 0xFE => Err(Error::CommandRetry), + value => Ok(value), + } + } + + fn keyboard_command(&mut self, command: KeyboardCommand) -> Result { + self.retry(format_args!("keyboard command {:?}", command), 4, |x| { + x.keyboard_command_inner(command as u8) + }) + } + + fn keyboard_command_data( + &mut self, + command: KeyboardCommandData, + data: u8, + ) -> Result { + self.retry( + format_args!("keyboard command {:?} {:#x}", command, data), + 4, + |x| { + let res = x.keyboard_command_inner(command as u8)?; + if res != 0xFA { + //TODO: error? + return Ok(res); + } + x.write(data); + x.read() + }, + ) + } + + fn mouse_command_inner(&mut self, command: u8) -> Result { + self.command(Command::WriteSecond)?; + self.write(command)?; + match self.read()? { + 0xFE => Err(Error::CommandRetry), + value => Ok(value), + } + } + + fn mouse_command(&mut self, command: MouseCommand) -> Result { + self.retry(format_args!("mouse command {:?}", command), 4, |x| { + x.mouse_command_inner(command as u8) + }) + } + + fn mouse_command_data(&mut self, command: MouseCommandData, data: u8) -> Result { + self.retry( + format_args!("mouse command {:?} {:#x}", command, data), + 4, + |x| { + let res = x.mouse_command_inner(command as u8)?; + if res != 0xFA { + //TODO: error? + return Ok(res); + } + x.command(Command::WriteSecond)?; + x.write(data as u8)?; + x.read() + }, + ) + } + + pub fn next(&mut self) -> Option<(bool, u8)> { + let status = self.status(); + if status.contains(StatusFlags::OUTPUT_FULL) { + let data = self.data.read(); + Some((!status.contains(StatusFlags::SECOND_OUTPUT_FULL), data)) + } else { + None + } + } + + pub fn init_keyboard(&mut self) -> Result<(), Error> { + let mut b; + + { + // Enable first device + self.command(Command::EnableFirst)?; + } + + { + // Reset keyboard + b = self.keyboard_command(KeyboardCommand::Reset)?; + if b == 0xFA { + b = self.read().unwrap_or(0); + if b != 0xAA { + error!("ps2d: keyboard failed self test: {:02X}", b); + } + } else { + error!("ps2d: keyboard failed to reset: {:02X}", b); + } + } + + self.retry(format_args!("keyboard defaults"), 4, |x| { + // Set defaults and disable scanning + let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?; + if b != 0xFA { + error!("ps2d: keyboard failed to set defaults: {:02X}", b); + return Err(Error::CommandRetry); + } + + Ok(b) + })?; + + { + // Set scancode set to 2 + let scancode_set = 2; + b = self.keyboard_command_data(KeyboardCommandData::ScancodeSet, scancode_set)?; + if b != 0xFA { + error!( + "ps2d: keyboard failed to set scancode set {}: {:02X}", + scancode_set, b + ); + } + } + + Ok(()) + } + + pub fn init_mouse(&mut self) -> Result { + { + // Enable second device + self.command(Command::EnableSecond)?; + } + + self.retry(format_args!("mouse reset"), 4, |x| { + // Reset mouse + let mut b = x.mouse_command(MouseCommand::Reset)?; + if b == 0xFA { + b = x.read_timeout(RESET_TIMEOUT)?; + if b != 0xAA { + error!("ps2d: mouse failed self test 1: {:02X}", b); + return Err(Error::CommandRetry); + } + + b = x.read_timeout(RESET_TIMEOUT)?; + if b != 0x00 { + error!("ps2d: mouse failed self test 2: {:02X}", b); + return Err(Error::CommandRetry); + } + } else { + error!("ps2d: mouse failed to reset: {:02X}", b); + return Err(Error::CommandRetry); + } + + Ok(b) + })?; + + { + // Enable extra packet on mouse + //TODO: show error return values + if self.mouse_command_data(MouseCommandData::SetSampleRate, 200)? != 0xFA + || self.mouse_command_data(MouseCommandData::SetSampleRate, 100)? != 0xFA + || self.mouse_command_data(MouseCommandData::SetSampleRate, 80)? != 0xFA + { + error!("ps2d: mouse failed to enable extra packet"); + } + } + + let b = self.mouse_command(MouseCommand::GetDeviceId)?; + let mouse_extra = if b == 0xFA { + self.read()? == 3 + } else { + error!("ps2d: mouse failed to get device id: {:02X}", b); + false + }; + + { + // Set sample rate to maximum + let sample_rate = 200; + let b = self.mouse_command_data(MouseCommandData::SetSampleRate, sample_rate)?; + if b != 0xFA { + error!( + "ps2d: mouse failed to set sample rate to {}: {:02X}", + sample_rate, b + ); + } + } + + { + let b = self.mouse_command(MouseCommand::StatusRequest)?; + if b != 0xFA { + error!("ps2d: mouse failed to request status: {:02X}", b); + } else { + let a = self.read()?; + let b = self.read()?; + let c = self.read()?; + + debug!( + "ps2d: mouse status {:#x} resolution {} sample rate {}", + a, b, c + ); + } + } + + Ok(mouse_extra) + } + + pub fn init(&mut self) -> bool { + { + // Disable devices + self.command(Command::DisableFirst) + .expect("ps2d: failed to initialize"); + self.command(Command::DisableSecond) + .expect("ps2d: failed to initialize"); + } + + // Disable clocks, disable interrupts, and disable translate + { + // Since the default config may have interrupts enabled, and the kernel may eat up + // our data in that case, we will write a config without reading the current one + let config = ConfigFlags::POST_PASSED + | ConfigFlags::FIRST_DISABLED + | ConfigFlags::SECOND_DISABLED; + trace!("ps2d: config set {:?}", config); + self.set_config(config).expect("ps2d: failed to initialize"); + } + + { + // Perform the self test + self.command(Command::TestController) + .expect("ps2d: failed to initialize"); + assert_eq!(self.read().expect("ps2d: failed to initialize"), 0x55); + } + + // Initialize keyboard + self.init_keyboard().expect("ps2d: failed to initialize"); + + // Initialize mouse + let (mouse_found, mouse_extra) = match self.init_mouse() { + Ok(ok) => (true, ok), + Err(err) => { + error!("ps2d: failed to initialize mouse: {:?}", err); + (false, false) + } + }; + + { + // Enable keyboard data reporting + // Use inner function to prevent retries + self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) + .expect("ps2d: failed to initialize"); + // Response is ignored since scanning is now on + //TODO: fix by using interrupts? + } + + if mouse_found { + // Enable mouse data reporting + // Use inner function to prevent retries + self.mouse_command_inner(MouseCommand::EnableReporting as u8) + .expect("ps2d: failed to initialize"); + // Response is ignored since scanning is now on + //TODO: fix by using interrupts? + } + + // Enable clocks and interrupts + { + let config = ConfigFlags::POST_PASSED + | ConfigFlags::FIRST_INTERRUPT + | ConfigFlags::FIRST_TRANSLATE + | if mouse_found { + ConfigFlags::SECOND_INTERRUPT + } else { + ConfigFlags::SECOND_DISABLED + }; + trace!("ps2d: config set {:?}", config); + self.set_config(config).expect("ps2d: failed to initialize"); + } + + mouse_extra + } +} diff --git a/drivers/input/ps2d/src/keymap.rs b/drivers/input/ps2d/src/keymap.rs new file mode 100644 index 0000000000..46264606a7 --- /dev/null +++ b/drivers/input/ps2d/src/keymap.rs @@ -0,0 +1,478 @@ +pub mod us { + static US: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '@'], + ['3', '#'], + ['4', '$'], + ['5', '%'], + ['6', '^'], + ['7', '&'], + ['8', '*'], + ['9', '('], + ['0', ')'], + ['-', '_'], + ['=', '+'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['q', 'Q'], + ['w', 'W'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['[', '{'], + [']', '}'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + [';', ':'], + ['\'', '"'], + ['`', '~'], + ['\0', '\0'], + ['\\', '|'], + ['z', 'Z'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + ['m', 'M'], + [',', '<'], + ['.', '>'], + ['/', '?'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = US.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod gb { + static GB: [[char; 2]; 87] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '"'], + ['3', '£'], + ['4', '$'], + ['5', '%'], + ['6', '^'], + ['7', '&'], + ['8', '*'], + ['9', '('], + ['0', ')'], + ['-', '_'], + ['=', '+'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['q', 'Q'], + ['w', 'W'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['[', '{'], + [']', '}'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + [';', ':'], + ['\'', '@'], + ['`', '¬'], + ['\0', '\0'], + ['#', '~'], + ['z', 'Z'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + ['m', 'M'], + [',', '<'], + ['.', '>'], + ['/', '?'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\\', '|'], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = GB.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod dvorak { + static DVORAK: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '@'], + ['3', '#'], + ['4', '$'], + ['5', '%'], + ['6', '^'], + ['7', '&'], + ['8', '*'], + ['9', '('], + ['0', ')'], + ['[', '{'], + [']', '}'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['\'', '"'], + [',', '<'], + ['.', '>'], + ['p', 'P'], + ['y', 'Y'], + ['f', 'F'], + ['g', 'G'], + ['c', 'C'], + ['r', 'R'], + ['l', 'L'], + ['/', '?'], + ['=', '+'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['o', 'O'], + ['e', 'E'], + ['u', 'U'], + ['i', 'I'], + ['d', 'D'], + ['h', 'H'], + ['t', 'T'], + ['n', 'N'], + ['s', 'S'], + ['-', '_'], + ['`', '~'], + ['\0', '\0'], + ['\\', '|'], + [';', ':'], + ['q', 'Q'], + ['j', 'J'], + ['k', 'K'], + ['x', 'X'], + ['b', 'B'], + ['m', 'M'], + ['w', 'W'], + ['v', 'V'], + ['z', 'Z'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = DVORAK.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod azerty { + static AZERTY: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['&', '1'], + ['é', '2'], + ['"', '3'], + ['\'', '4'], + ['(', '5'], + ['|', '6'], + ['è', '7'], + ['_', '8'], + ['ç', '9'], + ['à', '0'], + [')', '°'], + ['=', '+'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['a', 'A'], + ['z', 'Z'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['^', '¨'], + ['$', '£'], + ['\n', '\n'], + ['\0', '\0'], + ['q', 'Q'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + ['m', 'M'], + ['ù', '%'], + ['*', 'µ'], + ['\0', '\0'], + ['ê', 'Ê'], + ['w', 'W'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + [',', '?'], + [';', '.'], + [':', '/'], + ['!', '§'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = AZERTY.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod bepo { + static BEPO: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['"', '1'], + ['«', '2'], + ['»', '3'], + ['(', '4'], + [')', '5'], + ['@', '6'], + ['+', '7'], + ['-', '8'], + ['/', '9'], + ['*', '0'], + ['=', '°'], + ['%', '`'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['b', 'B'], + ['é', 'É'], + ['p', 'P'], + ['o', 'O'], + ['è', 'È'], + ['^', '!'], + ['v', 'V'], + ['d', 'D'], + ['l', 'L'], + ['j', 'J'], + ['z', 'Z'], + ['w', 'W'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['u', 'U'], + ['i', 'I'], + ['e', 'E'], + [',', ';'], + ['c', 'C'], + ['t', 'T'], + ['s', 'S'], + ['r', 'R'], + ['n', 'N'], + ['m', 'M'], + ['ç', 'Ç'], + ['\0', '\0'], + ['ê', 'Ê'], + ['à', 'À'], + ['y', 'Y'], + ['x', 'X'], + ['.', ':'], + ['k', 'K'], + ['\'', '?'], + ['q', 'Q'], + ['g', 'G'], + ['h', 'H'], + ['f', 'F'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = BEPO.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod it { + static IT: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '"'], + ['3', '£'], + ['4', '$'], + ['5', '%'], + ['6', '&'], + ['7', '/'], + ['8', '('], + ['9', ')'], + ['0', '='], + ['?', '\''], + ['ì', '^'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['q', 'Q'], + ['w', 'W'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['è', 'é'], + ['+', '*'], + ['\n', '\n'], + ['\x20', '\x20'], + ['a', 'A'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + ['ò', 'ç'], + ['à', '°'], + ['ù', '§'], + ['\0', '\0'], + ['<', '>'], + ['z', 'Z'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + ['m', 'M'], + [',', ';'], + ['.', ':'], + ['-', '_'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = IT.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs new file mode 100644 index 0000000000..64415dd3d2 --- /dev/null +++ b/drivers/input/ps2d/src/main.rs @@ -0,0 +1,191 @@ +#[macro_use] +extern crate bitflags; +extern crate orbclient; +extern crate syscall; + +use std::fs::OpenOptions; +use std::io::Read; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::AsRawFd; +use std::{env, process}; + +use common::acquire_port_io_rights; +use event::{user_data, EventQueue}; +use inputd::ProducerHandle; +use log::info; +use redox_scheme::{RequestKind, SignalBehavior, Socket}; +use syscall::{EAGAIN, EWOULDBLOCK}; + +use crate::scheme::Ps2Scheme; +use crate::state::Ps2d; + +mod controller; +mod keymap; +mod scheme; +mod state; +mod vm; + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + common::setup_logging( + "input", + "ps2", + "ps2", + common::output_level(), + common::file_level(), + ); + + acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); + + let (mut keymap, mut keymap_name): (fn(u8, bool) -> char, &str) = + match env::args().skip(1).next() { + Some(k) => get_keymap_from_str(&k), + None => (keymap::us::get_char, "us"), + }; + + info!("ps2d: using keymap '{}'", keymap_name); + + let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + + user_data! { + enum Source { + Keyboard, + Mouse, + Scheme, + } + } + + let event_queue: EventQueue = + EventQueue::new().expect("ps2d: failed to create event queue"); + + let mut key_file = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(syscall::O_NONBLOCK as i32) + .open("/scheme/serio/0") + .expect("ps2d: failed to open /scheme/serio/0"); + + event_queue + .subscribe( + key_file.as_raw_fd() as usize, + Source::Keyboard, + event::EventFlags::READ, + ) + .unwrap(); + + let mut mouse_file = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(syscall::O_NONBLOCK as i32) + .open("/scheme/serio/1") + .expect("ps2d: failed to open /scheme/serio/1"); + + event_queue + .subscribe( + mouse_file.as_raw_fd() as usize, + Source::Mouse, + event::EventFlags::READ, + ) + .unwrap(); + + let scheme_file = Socket::nonblock("ps2").expect("ps2d: failed to create ps2 scheme"); + + let mut scheme_handle = Ps2Scheme::new( + keymap_name.to_owned(), + vec!["dvorak", "us", "gb", "azerty", "bepo", "it"], + ); + + event_queue + .subscribe( + scheme_file.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace"); + + daemon + .ready() + .expect("ps2d: failed to mark daemon as ready"); + + let mut ps2d = Ps2d::new(input, keymap); + + let mut data = [0; 256]; + for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { + // There are some gotchas with ps/2 controllers that require this weird + // way of doing things. You read key and mouse data from the same + // place. There is a status register that may show you which the data + // came from, but if it is even implemented it can have a race + // condition causing keyboard data to be read as mouse data. + // + // Due to this, we have a kernel driver doing a small amount of work + // to grab bytes and sort them based on the source + + let (file, keyboard) = match event { + Source::Keyboard => (&mut key_file, true), + Source::Mouse => (&mut mouse_file, false), + Source::Scheme => { + loop { + let request = match scheme_file.next_request(SignalBehavior::Interrupt) { + Ok(Some(request)) => request, + Ok(None) => { + // Scheme likely got unmounted + std::process::exit(0); + } + Err(err) if err.errno == EWOULDBLOCK || err.errno == EAGAIN => break, + Err(err) => panic!("ps2: failed to read scheme: {:?}", err), + }; + + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut scheme_handle); + + scheme_file + .write_response(response, SignalBehavior::Restart) + .expect("ps2: failed to write next scheme response"); + } + RequestKind::OnClose { id: _ } => {} + _ => (), + } + } + + if keymap_name != &scheme_handle.keymap { + (keymap, keymap_name) = get_keymap_from_str(&scheme_handle.keymap); + info!("ps2d: updating to new keymap '{:?}'", keymap_name); + ps2d.update_keymap(keymap); + } + + continue; + } + }; + + loop { + let count = match file.read(&mut data) { + Ok(0) => break, + Ok(count) => count, + Err(_) => break, + }; + for i in 0..count { + ps2d.handle(keyboard, data[i]); + } + } + } + + process::exit(0); +} + +fn get_keymap_from_str(k: &str) -> (fn(u8, bool) -> char, &'static str) { + match k.to_lowercase().as_ref() { + "dvorak" => (keymap::dvorak::get_char, "dvorak"), + "us" => (keymap::us::get_char, "us"), + "gb" => (keymap::gb::get_char, "gb"), + "azerty" => (keymap::azerty::get_char, "azerty"), + "bepo" => (keymap::bepo::get_char, "bepo"), + "it" => (keymap::it::get_char, "it"), + &_ => (keymap::us::get_char, "us"), + } +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("ps2d: failed to create daemon"); +} diff --git a/drivers/input/ps2d/src/scheme.rs b/drivers/input/ps2d/src/scheme.rs new file mode 100644 index 0000000000..058ec776c3 --- /dev/null +++ b/drivers/input/ps2d/src/scheme.rs @@ -0,0 +1,145 @@ +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult}; +use std::convert::TryFrom; +use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; +use syscall::schemev2::NewFdFlags; +use syscall::{ + Error, Result, EACCES, EINVAL, EMFILE, ENOENT, ENOTDIR, MODE_DIR, MODE_FILE, O_WRONLY, +}; + +pub struct Ps2Scheme { + pub keymap: String, + pub keymap_list: String, +} + +impl Ps2Scheme { + pub fn new(keymap: String, keymap_list: Vec<&str>) -> Ps2Scheme { + let scheme = Ps2Scheme { + keymap, + keymap_list: keymap_list.join("\n"), + }; + scheme + } +} + +impl SchemeSync for Ps2Scheme { + fn open(&mut self, path_str: &str, flags: usize, ctx: &CallerCtx) -> Result { + let path = path_str.trim_start_matches('/'); + if flags & O_WRONLY != 0 { + if ctx.uid != 0 || ctx.gid != 0 { + return Err(Error::new(EACCES)); + } else if path != "keymap" { + return Err(Error::new(EINVAL)); + } + } + + match path { + "" => Ok(OpenResult::ThisScheme { + number: 0, + flags: NewFdFlags::empty(), + }), + "keymap" => Ok(OpenResult::ThisScheme { + number: 1, + flags: NewFdFlags::POSITIONED, + }), + "keymap_list" => Ok(OpenResult::ThisScheme { + number: 2, + flags: NewFdFlags::POSITIONED, + }), + _ => Err(Error::new(ENOENT)), + } + } + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + if id != 0 { + return Err(Error::new(ENOTDIR)); + } + let Ok(offset) = usize::try_from(opaque_offset) else { + return Ok(buf); + }; + for (this_idx, name) in ["keymap", "keymap_list"].iter().enumerate().skip(offset) { + buf.entry(DirEntry { + inode: this_idx as u64, + next_opaque_id: this_idx as u64 + 1, + kind: DirentKind::Regular, + name, + })?; + } + Ok(buf) + } + + fn fstat(&mut self, id: usize, stat: &mut syscall::Stat, _ctx: &CallerCtx) -> Result<()> { + stat.st_size = 0; + stat.st_mode = match id { + 0 => 0o555 | MODE_DIR, + 1 => 0o644 | MODE_FILE, + 2 => 0o444 | MODE_FILE, + _ => return Err(Error::new(ENOENT)), + }; + Ok(()) + } + + fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let path = b"/scheme/ps2"; + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } + + fn fsync(&mut self, _id: usize, _ctx: &CallerCtx) -> Result<()> { + Ok(()) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + if offset != 0 { + return Ok(0); + } + let value = match id { + 1 => self.keymap.as_bytes(), + 2 => self.keymap_list.as_bytes(), + _ => { + return Err(Error::new(ENOENT)); + } + }; + + if buf.len() + 2 < value.len() { + return Err(Error::new(EMFILE)); + } + buf[..value.len()].copy_from_slice(value); + buf[value.len()] = b'\n'; + buf[value.len() + 1] = b'\0'; + Ok(value.len() + 2) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + if offset != 0 || id != 1 { + return Ok(0); + } + let new_keymap = String::from_utf8(buf.to_vec()).map_err(|_| Error::new(EINVAL))?; + self.keymap = new_keymap.trim().to_string(); + Ok(buf.len()) + } +} diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs new file mode 100644 index 0000000000..b8126bcb1c --- /dev/null +++ b/drivers/input/ps2d/src/state.rs @@ -0,0 +1,401 @@ +use inputd::ProducerHandle; +use log::{error, warn}; +use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent}; + +use crate::controller::Ps2; +use crate::vm; + +bitflags! { + pub struct MousePacketFlags: u8 { + const LEFT_BUTTON = 1; + const RIGHT_BUTTON = 1 << 1; + const MIDDLE_BUTTON = 1 << 2; + const ALWAYS_ON = 1 << 3; + const X_SIGN = 1 << 4; + const Y_SIGN = 1 << 5; + const X_OVERFLOW = 1 << 6; + const Y_OVERFLOW = 1 << 7; + } +} + +pub struct Ps2d char> { + ps2: Ps2, + vmmouse: bool, + vmmouse_relative: bool, + input: ProducerHandle, + extended: bool, + lshift: bool, + rshift: bool, + mouse_x: i32, + mouse_y: i32, + mouse_left: bool, + mouse_middle: bool, + mouse_right: bool, + packets: [u8; 4], + packet_i: usize, + extra_packet: bool, + //Keymap function + get_char: F, +} + +impl char> Ps2d { + pub fn new(input: ProducerHandle, keymap: F) -> Self { + let mut ps2 = Ps2::new(); + let extra_packet = ps2.init(); + + // FIXME add an option for orbital to disable this when an app captures the mouse. + let vmmouse_relative = false; + let vmmouse = vm::enable(vmmouse_relative); + + Ps2d { + ps2, + vmmouse, + vmmouse_relative, + input, + extended: false, + lshift: false, + rshift: false, + mouse_x: 0, + mouse_y: 0, + mouse_left: false, + mouse_middle: false, + mouse_right: false, + packets: [0; 4], + packet_i: 0, + extra_packet, + get_char: keymap, + } + } + + pub fn update_keymap(&mut self, keymap: F) { + self.get_char = keymap; + } + + pub fn irq(&mut self) { + while let Some((keyboard, data)) = self.ps2.next() { + self.handle(keyboard, data); + } + } + + pub fn handle(&mut self, keyboard: bool, data: u8) { + if keyboard { + if data == 0xE0 { + self.extended = true; + } else { + let (ps2_scancode, pressed) = if data >= 0x80 { + (data - 0x80, false) + } else { + (data, true) + }; + + let scancode = if self.extended { + self.extended = false; + match ps2_scancode { + //TODO: media keys + //TODO: 0x1C => orbclient::K_NUM_ENTER, + 0x1D => orbclient::K_CTRL, //TODO: 0x1D => orbclient::K_RIGHT_CTRL, + 0x20 => 0x80 + 0x20, //TODO: orbclient::K_VOLUME_MUTE, + 0x2E => 0x80 + 0x2E, //TODO: orbclient::K_VOLUME_DOWN, + 0x30 => 0x80 + 0x30, //TODO: orbclient::K_VOLUME_UP, + //TODO: 0x35 => orbclient::K_NUM_SLASH, + 0x38 => orbclient::K_ALT_GR, + 0x47 => orbclient::K_HOME, + 0x48 => orbclient::K_UP, + 0x49 => orbclient::K_PGUP, + 0x4B => orbclient::K_LEFT, + 0x4D => orbclient::K_RIGHT, + 0x4F => orbclient::K_END, + 0x50 => orbclient::K_DOWN, + 0x51 => orbclient::K_PGDN, + //TODO: 0x52 => orbclient::K_INSERT, + 0x53 => orbclient::K_DEL, + 0x5B => 0x5B, //TODO: orbclient::K_LEFT_SUPER, + //TODO: 0x5C => orbclient::K_RIGHT_SUPER, + //TODO: 0x5D => orbclient::K_APP, + //TODO power keys + /* 0x80 to 0xFF used for press/release detection */ + _ => { + if pressed { + warn!("ps2d: unknown extended scancode {:02X}", ps2_scancode); + } + 0 + } + } + } else { + match ps2_scancode { + /* 0x00 unused */ + 0x01 => orbclient::K_ESC, + 0x02 => orbclient::K_1, + 0x03 => orbclient::K_2, + 0x04 => orbclient::K_3, + 0x05 => orbclient::K_4, + 0x06 => orbclient::K_5, + 0x07 => orbclient::K_6, + 0x08 => orbclient::K_7, + 0x09 => orbclient::K_8, + 0x0A => orbclient::K_9, + 0x0B => orbclient::K_0, + 0x0C => orbclient::K_MINUS, + 0x0D => orbclient::K_EQUALS, + 0x0E => orbclient::K_BKSP, + 0x0F => orbclient::K_TAB, + 0x10 => orbclient::K_Q, + 0x11 => orbclient::K_W, + 0x12 => orbclient::K_E, + 0x13 => orbclient::K_R, + 0x14 => orbclient::K_T, + 0x15 => orbclient::K_Y, + 0x16 => orbclient::K_U, + 0x17 => orbclient::K_I, + 0x18 => orbclient::K_O, + 0x19 => orbclient::K_P, + 0x1A => orbclient::K_BRACE_OPEN, + 0x1B => orbclient::K_BRACE_CLOSE, + 0x1C => orbclient::K_ENTER, + 0x1D => orbclient::K_CTRL, + 0x1E => orbclient::K_A, + 0x1F => orbclient::K_S, + 0x20 => orbclient::K_D, + 0x21 => orbclient::K_F, + 0x22 => orbclient::K_G, + 0x23 => orbclient::K_H, + 0x24 => orbclient::K_J, + 0x25 => orbclient::K_K, + 0x26 => orbclient::K_L, + 0x27 => orbclient::K_SEMICOLON, + 0x28 => orbclient::K_QUOTE, + 0x29 => orbclient::K_TICK, + 0x2A => orbclient::K_LEFT_SHIFT, + 0x2B => orbclient::K_BACKSLASH, + 0x2C => orbclient::K_Z, + 0x2D => orbclient::K_X, + 0x2E => orbclient::K_C, + 0x2F => orbclient::K_V, + 0x30 => orbclient::K_B, + 0x31 => orbclient::K_N, + 0x32 => orbclient::K_M, + 0x33 => orbclient::K_COMMA, + 0x34 => orbclient::K_PERIOD, + 0x35 => orbclient::K_SLASH, + 0x36 => orbclient::K_RIGHT_SHIFT, + //TODO: 0x37 => orbclient::K_NUM_ASTERISK, + 0x38 => orbclient::K_ALT, + 0x39 => orbclient::K_SPACE, + 0x3A => orbclient::K_CAPS, + 0x3B => orbclient::K_F1, + 0x3C => orbclient::K_F2, + 0x3D => orbclient::K_F3, + 0x3E => orbclient::K_F4, + 0x3F => orbclient::K_F5, + 0x40 => orbclient::K_F6, + 0x41 => orbclient::K_F7, + 0x42 => orbclient::K_F8, + 0x43 => orbclient::K_F9, + 0x44 => orbclient::K_F10, + //TODO: 0x45 => orbclient::K_NUM_LOCK, + //TODO: 0x46 => orbclient::K_SCROLL_LOCK, + 0x47 => orbclient::K_NUM_7, + 0x48 => orbclient::K_NUM_8, + 0x49 => orbclient::K_NUM_9, + //TODO: 0x4A => orbclient::K_NUM_MINUS, + 0x4B => orbclient::K_NUM_4, + 0x4C => orbclient::K_NUM_5, + 0x4D => orbclient::K_NUM_6, + //TODO: 0x4E => orbclient::K_NUM_PLUS, + 0x4F => orbclient::K_NUM_1, + 0x50 => orbclient::K_NUM_2, + 0x51 => orbclient::K_NUM_3, + 0x52 => orbclient::K_NUM_0, + //TODO: 0x53 => orbclient::K_NUM_PERIOD, + /* 0x54 to 0x56 unused */ + 0x57 => orbclient::K_F11, + 0x58 => orbclient::K_F12, + /* 0x59 to 0x7F unused */ + /* 0x80 to 0xFF used for press/release detection */ + _ => { + if pressed { + warn!("ps2d: unknown scancode {:02X}", ps2_scancode); + } + 0 + } + } + }; + + if scancode == orbclient::K_LEFT_SHIFT { + self.lshift = pressed; + } else if scancode == orbclient::K_RIGHT_SHIFT { + self.rshift = pressed; + } + + if scancode != 0 { + self.input + .write_event( + KeyEvent { + character: (self.get_char)( + ps2_scancode, + self.lshift || self.rshift, + ), + scancode, + pressed, + } + .to_event(), + ) + .expect("ps2d: failed to write key event"); + } + } + } else if self.vmmouse { + for _i in 0..256 { + let (status, _, _, _) = unsafe { vm::cmd(vm::ABSPOINTER_STATUS, 0) }; + //TODO if ((status & VMMOUSE_ERROR) == VMMOUSE_ERROR) + + let queue_length = status & 0xffff; + if queue_length == 0 { + break; + } + + if queue_length % 4 != 0 { + error!("ps2d: queue length not a multiple of 4: {}", queue_length); + break; + } + + let (status, dx, dy, dz) = unsafe { vm::cmd(vm::ABSPOINTER_DATA, 4) }; + + if self.vmmouse_relative { + if dx != 0 || dy != 0 { + self.input + .write_event( + MouseRelativeEvent { + dx: dx as i32, + dy: dy as i32, + } + .to_event(), + ) + .expect("ps2d: failed to write mouse event"); + } + } else { + let x = dx as i32; + let y = dy as i32; + if x != self.mouse_x || y != self.mouse_y { + self.mouse_x = x; + self.mouse_y = y; + self.input + .write_event(MouseEvent { x, y }.to_event()) + .expect("ps2d: failed to write mouse event"); + } + }; + + if dz != 0 { + self.input + .write_event( + ScrollEvent { + x: 0, + y: -(dz as i32), + } + .to_event(), + ) + .expect("ps2d: failed to write scroll event"); + } + + let left = status & vm::LEFT_BUTTON == vm::LEFT_BUTTON; + let middle = status & vm::MIDDLE_BUTTON == vm::MIDDLE_BUTTON; + let right = status & vm::RIGHT_BUTTON == vm::RIGHT_BUTTON; + if left != self.mouse_left + || middle != self.mouse_middle + || right != self.mouse_right + { + self.mouse_left = left; + self.mouse_middle = middle; + self.mouse_right = right; + self.input + .write_event( + ButtonEvent { + left, + middle, + right, + } + .to_event(), + ) + .expect("ps2d: failed to write button event"); + } + } + } else { + self.packets[self.packet_i] = data; + self.packet_i += 1; + + let flags = MousePacketFlags::from_bits_truncate(self.packets[0]); + if !flags.contains(MousePacketFlags::ALWAYS_ON) { + error!("ps2d: mouse misalign {:X}", self.packets[0]); + + self.packets = [0; 4]; + self.packet_i = 0; + } else if self.packet_i >= self.packets.len() + || (!self.extra_packet && self.packet_i >= 3) + { + if !flags.contains(MousePacketFlags::X_OVERFLOW) + && !flags.contains(MousePacketFlags::Y_OVERFLOW) + { + let mut dx = self.packets[1] as i32; + if flags.contains(MousePacketFlags::X_SIGN) { + dx -= 0x100; + } + + let mut dy = -(self.packets[2] as i32); + if flags.contains(MousePacketFlags::Y_SIGN) { + dy += 0x100; + } + + let mut dz = 0; + if self.extra_packet { + let mut scroll = (self.packets[3] & 0xF) as i8; + if scroll & (1 << 3) == 1 << 3 { + scroll -= 16; + } + dz = -scroll as i32; + } + + if dx != 0 || dy != 0 { + self.input + .write_event(MouseRelativeEvent { dx, dy }.to_event()) + .expect("ps2d: failed to write mouse event"); + } + + if dz != 0 { + self.input + .write_event(ScrollEvent { x: 0, y: dz }.to_event()) + .expect("ps2d: failed to write scroll event"); + } + + let left = flags.contains(MousePacketFlags::LEFT_BUTTON); + let middle = flags.contains(MousePacketFlags::MIDDLE_BUTTON); + let right = flags.contains(MousePacketFlags::RIGHT_BUTTON); + if left != self.mouse_left + || middle != self.mouse_middle + || right != self.mouse_right + { + self.mouse_left = left; + self.mouse_middle = middle; + self.mouse_right = right; + self.input + .write_event( + ButtonEvent { + left, + middle, + right, + } + .to_event(), + ) + .expect("ps2d: failed to write button event"); + } + } else { + warn!( + "ps2d: overflow {:X} {:X} {:X} {:X}", + self.packets[0], self.packets[1], self.packets[2], self.packets[3] + ); + } + + self.packets = [0; 4]; + self.packet_i = 0; + } + } + } +} diff --git a/drivers/input/ps2d/src/vm.rs b/drivers/input/ps2d/src/vm.rs new file mode 100644 index 0000000000..8832dc0ac9 --- /dev/null +++ b/drivers/input/ps2d/src/vm.rs @@ -0,0 +1,101 @@ +// This code is informed by the QEMU implementation found here: +// https://github.com/qemu/qemu/blob/master/hw/input/vmmouse.c +// +// As well as the Linux implementation here: +// http://elixir.free-electrons.com/linux/v4.1/source/drivers/input/mouse/vmmouse.c + +use core::arch::asm; + +use log::{error, info, trace}; + +const MAGIC: u32 = 0x564D5868; +const PORT: u16 = 0x5658; + +pub const GETVERSION: u32 = 10; +pub const ABSPOINTER_DATA: u32 = 39; +pub const ABSPOINTER_STATUS: u32 = 40; +pub const ABSPOINTER_COMMAND: u32 = 41; + +pub const CMD_ENABLE: u32 = 0x45414552; +pub const CMD_DISABLE: u32 = 0x000000f5; +pub const CMD_REQUEST_ABSOLUTE: u32 = 0x53424152; +pub const CMD_REQUEST_RELATIVE: u32 = 0x4c455252; + +const VERSION: u32 = 0x3442554a; + +pub const RELATIVE_PACKET: u32 = 0x00010000; + +pub const LEFT_BUTTON: u32 = 0x20; +pub const RIGHT_BUTTON: u32 = 0x10; +pub const MIDDLE_BUTTON: u32 = 0x08; + +pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) { + let a: u32; + let b: u32; + let c: u32; + let d: u32; + + // ebx can't be used as input or output constraint in rust as LLVM reserves it. + // Use xchg to pass it through r9 instead while restoring the original value in + // rbx when leaving the inline asm block. si and di are clobbered too. + #[cfg(not(target_arch = "x86"))] + asm!( + "xchg r9, rbx; in eax, dx; xchg r9, rbx", + inout("eax") MAGIC => a, + inout("r9") arg => b, + inout("ecx") cmd => c, + inout("edx") PORT as u32 => d, + out("rsi") _, + out("rdi") _, + ); + + // On x86 we don't have a spare register, so push ebx to the stack instead. + #[cfg(target_arch = "x86")] + asm!( + "push ebx; mov ebx, edi; in eax, dx; mov edi, ebx; pop ebx", + inout("eax") MAGIC => a, + inout("edi") arg => b, + inout("ecx") cmd => c, + inout("edx") PORT as u32 => d, + ); + + (a, b, c, d) +} + +pub fn enable(relative: bool) -> bool { + trace!("ps2d: Enable vmmouse"); + + unsafe { + let (eax, ebx, _, _) = cmd(GETVERSION, 0); + if ebx != MAGIC || eax == 0xFFFFFFFF { + info!("ps2d: No vmmouse support"); + return false; + } + + let _ = cmd(ABSPOINTER_COMMAND, CMD_ENABLE); + + let (status, _, _, _) = cmd(ABSPOINTER_STATUS, 0); + if (status & 0x0000ffff) == 0 { + info!("ps2d: No vmmouse"); + return false; + } + + let (version, _, _, _) = cmd(ABSPOINTER_DATA, 1); + if version != VERSION { + error!( + "ps2d: Invalid vmmouse version: {} instead of {}", + version, VERSION + ); + let _ = cmd(ABSPOINTER_COMMAND, CMD_DISABLE); + return false; + } + + if relative { + cmd(ABSPOINTER_COMMAND, CMD_REQUEST_RELATIVE); + } else { + cmd(ABSPOINTER_COMMAND, CMD_REQUEST_ABSOLUTE); + } + } + + return true; +} diff --git a/drivers/input/usbhidd/.gitignore b/drivers/input/usbhidd/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/input/usbhidd/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/input/usbhidd/Cargo.toml b/drivers/input/usbhidd/Cargo.toml new file mode 100644 index 0000000000..0a98084810 --- /dev/null +++ b/drivers/input/usbhidd/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "usbhidd" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "2" +log = "0.4" +orbclient = "0.3.47" +redox_syscall = "0.5" +rehid = { git = "https://gitlab.redox-os.org/redox-os/rehid.git" } +xhcid = { path = "../../usb/xhcid" } + +common = { path = "../../common" } +inputd = { path = "../../inputd" } diff --git a/drivers/input/usbhidd/src/keymap.rs b/drivers/input/usbhidd/src/keymap.rs new file mode 100644 index 0000000000..46264606a7 --- /dev/null +++ b/drivers/input/usbhidd/src/keymap.rs @@ -0,0 +1,478 @@ +pub mod us { + static US: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '@'], + ['3', '#'], + ['4', '$'], + ['5', '%'], + ['6', '^'], + ['7', '&'], + ['8', '*'], + ['9', '('], + ['0', ')'], + ['-', '_'], + ['=', '+'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['q', 'Q'], + ['w', 'W'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['[', '{'], + [']', '}'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + [';', ':'], + ['\'', '"'], + ['`', '~'], + ['\0', '\0'], + ['\\', '|'], + ['z', 'Z'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + ['m', 'M'], + [',', '<'], + ['.', '>'], + ['/', '?'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = US.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod gb { + static GB: [[char; 2]; 87] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '"'], + ['3', '£'], + ['4', '$'], + ['5', '%'], + ['6', '^'], + ['7', '&'], + ['8', '*'], + ['9', '('], + ['0', ')'], + ['-', '_'], + ['=', '+'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['q', 'Q'], + ['w', 'W'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['[', '{'], + [']', '}'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + [';', ':'], + ['\'', '@'], + ['`', '¬'], + ['\0', '\0'], + ['#', '~'], + ['z', 'Z'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + ['m', 'M'], + [',', '<'], + ['.', '>'], + ['/', '?'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + ['\\', '|'], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = GB.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod dvorak { + static DVORAK: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '@'], + ['3', '#'], + ['4', '$'], + ['5', '%'], + ['6', '^'], + ['7', '&'], + ['8', '*'], + ['9', '('], + ['0', ')'], + ['[', '{'], + [']', '}'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['\'', '"'], + [',', '<'], + ['.', '>'], + ['p', 'P'], + ['y', 'Y'], + ['f', 'F'], + ['g', 'G'], + ['c', 'C'], + ['r', 'R'], + ['l', 'L'], + ['/', '?'], + ['=', '+'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['o', 'O'], + ['e', 'E'], + ['u', 'U'], + ['i', 'I'], + ['d', 'D'], + ['h', 'H'], + ['t', 'T'], + ['n', 'N'], + ['s', 'S'], + ['-', '_'], + ['`', '~'], + ['\0', '\0'], + ['\\', '|'], + [';', ':'], + ['q', 'Q'], + ['j', 'J'], + ['k', 'K'], + ['x', 'X'], + ['b', 'B'], + ['m', 'M'], + ['w', 'W'], + ['v', 'V'], + ['z', 'Z'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = DVORAK.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod azerty { + static AZERTY: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['&', '1'], + ['é', '2'], + ['"', '3'], + ['\'', '4'], + ['(', '5'], + ['|', '6'], + ['è', '7'], + ['_', '8'], + ['ç', '9'], + ['à', '0'], + [')', '°'], + ['=', '+'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['a', 'A'], + ['z', 'Z'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['^', '¨'], + ['$', '£'], + ['\n', '\n'], + ['\0', '\0'], + ['q', 'Q'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + ['m', 'M'], + ['ù', '%'], + ['*', 'µ'], + ['\0', '\0'], + ['ê', 'Ê'], + ['w', 'W'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + [',', '?'], + [';', '.'], + [':', '/'], + ['!', '§'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = AZERTY.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod bepo { + static BEPO: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['"', '1'], + ['«', '2'], + ['»', '3'], + ['(', '4'], + [')', '5'], + ['@', '6'], + ['+', '7'], + ['-', '8'], + ['/', '9'], + ['*', '0'], + ['=', '°'], + ['%', '`'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['b', 'B'], + ['é', 'É'], + ['p', 'P'], + ['o', 'O'], + ['è', 'È'], + ['^', '!'], + ['v', 'V'], + ['d', 'D'], + ['l', 'L'], + ['j', 'J'], + ['z', 'Z'], + ['w', 'W'], + ['\n', '\n'], + ['\0', '\0'], + ['a', 'A'], + ['u', 'U'], + ['i', 'I'], + ['e', 'E'], + [',', ';'], + ['c', 'C'], + ['t', 'T'], + ['s', 'S'], + ['r', 'R'], + ['n', 'N'], + ['m', 'M'], + ['ç', 'Ç'], + ['\0', '\0'], + ['ê', 'Ê'], + ['à', 'À'], + ['y', 'Y'], + ['x', 'X'], + ['.', ':'], + ['k', 'K'], + ['\'', '?'], + ['q', 'Q'], + ['g', 'G'], + ['h', 'H'], + ['f', 'F'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = BEPO.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} + +pub mod it { + static IT: [[char; 2]; 58] = [ + ['\0', '\0'], + ['\x1B', '\x1B'], + ['1', '!'], + ['2', '"'], + ['3', '£'], + ['4', '$'], + ['5', '%'], + ['6', '&'], + ['7', '/'], + ['8', '('], + ['9', ')'], + ['0', '='], + ['?', '\''], + ['ì', '^'], + ['\x7F', '\x7F'], + ['\t', '\t'], + ['q', 'Q'], + ['w', 'W'], + ['e', 'E'], + ['r', 'R'], + ['t', 'T'], + ['y', 'Y'], + ['u', 'U'], + ['i', 'I'], + ['o', 'O'], + ['p', 'P'], + ['è', 'é'], + ['+', '*'], + ['\n', '\n'], + ['\x20', '\x20'], + ['a', 'A'], + ['s', 'S'], + ['d', 'D'], + ['f', 'F'], + ['g', 'G'], + ['h', 'H'], + ['j', 'J'], + ['k', 'K'], + ['l', 'L'], + ['ò', 'ç'], + ['à', '°'], + ['ù', '§'], + ['\0', '\0'], + ['<', '>'], + ['z', 'Z'], + ['x', 'X'], + ['c', 'C'], + ['v', 'V'], + ['b', 'B'], + ['n', 'N'], + ['m', 'M'], + [',', ';'], + ['.', ':'], + ['-', '_'], + ['\0', '\0'], + ['\0', '\0'], + ['\0', '\0'], + [' ', ' '], + ]; + + pub fn get_char(scancode: u8, shift: bool) -> char { + if let Some(c) = IT.get(scancode as usize) { + if shift { + c[1] + } else { + c[0] + } + } else { + '\0' + } + } +} diff --git a/drivers/input/usbhidd/src/main.rs b/drivers/input/usbhidd/src/main.rs new file mode 100644 index 0000000000..efc763399d --- /dev/null +++ b/drivers/input/usbhidd/src/main.rs @@ -0,0 +1,457 @@ +use std::env; + +use inputd::ProducerHandle; +use orbclient::KeyEvent as OrbKeyEvent; +use rehid::{ + report_desc::{ReportTy, REPORT_DESC_TY}, + report_handler::ReportHandler, + usage_tables::{GenericDesktopUsage, UsagePage}, +}; +use xhcid_interface::{ + ConfigureEndpointsReq, DevDesc, EndpDirection, EndpointTy, PortId, PortReqRecipient, + XhciClientHandle, +}; + +mod keymap; +mod reqs; + +fn send_key_event( + display: &mut ProducerHandle, + usage_page: u16, + usage: u16, + pressed: bool, + shift_opt: Option, +) { + let scancode = match usage_page { + 0x07 => match usage { + 0x04 => orbclient::K_A, + 0x05 => orbclient::K_B, + 0x06 => orbclient::K_C, + 0x07 => orbclient::K_D, + 0x08 => orbclient::K_E, + 0x09 => orbclient::K_F, + 0x0A => orbclient::K_G, + 0x0B => orbclient::K_H, + 0x0C => orbclient::K_I, + 0x0D => orbclient::K_J, + 0x0E => orbclient::K_K, + 0x0F => orbclient::K_L, + 0x10 => orbclient::K_M, + 0x11 => orbclient::K_N, + 0x12 => orbclient::K_O, + 0x13 => orbclient::K_P, + 0x14 => orbclient::K_Q, + 0x15 => orbclient::K_R, + 0x16 => orbclient::K_S, + 0x17 => orbclient::K_T, + 0x18 => orbclient::K_U, + 0x19 => orbclient::K_V, + 0x1A => orbclient::K_W, + 0x1B => orbclient::K_X, + 0x1C => orbclient::K_Y, + 0x1D => orbclient::K_Z, + 0x1E => orbclient::K_1, + 0x1F => orbclient::K_2, + 0x20 => orbclient::K_3, + 0x21 => orbclient::K_4, + 0x22 => orbclient::K_5, + 0x23 => orbclient::K_6, + 0x24 => orbclient::K_7, + 0x25 => orbclient::K_8, + 0x26 => orbclient::K_9, + 0x27 => orbclient::K_0, + 0x28 => orbclient::K_ENTER, + 0x29 => orbclient::K_ESC, + 0x2A => orbclient::K_BKSP, + 0x2B => orbclient::K_TAB, + 0x2C => orbclient::K_SPACE, + 0x2D => orbclient::K_MINUS, + 0x2E => orbclient::K_EQUALS, + 0x2F => orbclient::K_BRACE_OPEN, + 0x30 => orbclient::K_BRACE_CLOSE, + 0x31 => orbclient::K_BACKSLASH, + // 0x32 non-us # and ~ + 0x33 => orbclient::K_SEMICOLON, + 0x34 => orbclient::K_QUOTE, + 0x35 => orbclient::K_TICK, + 0x36 => orbclient::K_COMMA, + 0x37 => orbclient::K_PERIOD, + 0x38 => orbclient::K_SLASH, + 0x39 => orbclient::K_CAPS, + 0x3A => orbclient::K_F1, + 0x3B => orbclient::K_F2, + 0x3C => orbclient::K_F3, + 0x3D => orbclient::K_F4, + 0x3E => orbclient::K_F5, + 0x3F => orbclient::K_F6, + 0x40 => orbclient::K_F7, + 0x41 => orbclient::K_F8, + 0x42 => orbclient::K_F9, + 0x43 => orbclient::K_F10, + 0x44 => orbclient::K_F11, + 0x45 => orbclient::K_F12, + // 0x46 print screen + // 0x47 scroll lock + // 0x48 pause + // 0x49 insert + 0x4A => orbclient::K_HOME, + 0x4B => orbclient::K_PGUP, + 0x4C => orbclient::K_DEL, + 0x4D => orbclient::K_END, + 0x4E => orbclient::K_PGDN, + 0x4F => orbclient::K_RIGHT, + 0x50 => orbclient::K_LEFT, + 0x51 => orbclient::K_DOWN, + 0x52 => orbclient::K_UP, + // 0x53 num lock + // 0x54 num / + // 0x55 num * + // 0x56 num - + // 0x57 num + + // 0x58 num enter + 0x59 => orbclient::K_NUM_1, + 0x5A => orbclient::K_NUM_2, + 0x5B => orbclient::K_NUM_3, + 0x5C => orbclient::K_NUM_4, + 0x5D => orbclient::K_NUM_5, + 0x5E => orbclient::K_NUM_6, + 0x5F => orbclient::K_NUM_7, + 0x60 => orbclient::K_NUM_8, + 0x61 => orbclient::K_NUM_9, + 0x62 => orbclient::K_NUM_0, + // 0x62 num . + // 0x64 non-us \ and | + // 0x64 app + // 0x66 power + // 0x67 num = + // unmapped values + 0xE0 => orbclient::K_CTRL, // TODO: left control + 0xE1 => orbclient::K_LEFT_SHIFT, + 0xE2 => orbclient::K_ALT, + 0xE3 => 0x5B, // left super + 0xE4 => orbclient::K_CTRL, // TODO: right control + 0xE5 => orbclient::K_RIGHT_SHIFT, + 0xE6 => orbclient::K_ALT_GR, + // 0xE7 right super + // reserved values + _ => { + log::warn!("unknown usage_page {:#x} usage {:#x}", usage_page, usage); + return; + } + }, + _ => { + log::warn!("unknown usage_page {:#x}", usage_page); + return; + } + }; + + //TODO: other keymaps + let character = if let Some(shift) = shift_opt { + keymap::us::get_char(scancode, shift) + } else { + '\0' + }; + + let key_event = OrbKeyEvent { + character, + scancode, + pressed, + }; + + match display.write_event(key_event.to_event()) { + Ok(_) => (), + Err(err) => { + log::warn!("failed to send key event to orbital: {}", err); + } + } +} + +fn main() { + let mut args = env::args().skip(1); + + const USAGE: &'static str = "usbhidd "; + + let scheme = args.next().expect(USAGE); + let port = args + .next() + .expect(USAGE) + .parse::() + .expect("Expected port ID"); + let interface_num = args + .next() + .expect(USAGE) + .parse::() + .expect("Expected integer as input of interface"); + + let name = format!("{}_{}_{}_hid", scheme, port, interface_num); + common::setup_logging( + "usb", + "device", + &name, + common::output_level(), + common::file_level(), + ); + + log::info!( + "USB HID driver spawned with scheme `{}`, port {}, interface {}", + scheme, + port, + interface_num + ); + + let handle = XhciClientHandle::new(scheme, port); + let desc: DevDesc = handle + .get_standard_descs() + .expect("Failed to get standard descriptors"); + log::debug!("{:X?}", desc); + + let mut endp_count = 0; + let (conf_desc, (if_desc, endp_desc_opt, hid_desc)) = desc + .config_descs + .iter() + .find_map(|conf_desc| { + let if_desc = conf_desc.interface_descs.iter().find_map(|if_desc| { + if if_desc.number == interface_num { + let endp_desc_opt = if_desc.endpoints.iter().find_map(|endp_desc| { + endp_count += 1; + if endp_desc.ty() == EndpointTy::Interrupt + && endp_desc.direction() == EndpDirection::In + { + Some((endp_count, endp_desc.clone())) + } else { + None + } + }); + let hid_desc = if_desc.hid_descs.iter().find_map(|hid_desc| { + //TODO: should we do any filtering? + Some(hid_desc) + })?; + Some((if_desc.clone(), endp_desc_opt, hid_desc)) + } else { + endp_count += if_desc.endpoints.len(); + None + } + })?; + Some((conf_desc.clone(), if_desc)) + }) + .expect("Failed to find suitable configuration"); + + handle + .configure_endpoints(&ConfigureEndpointsReq { + config_desc: conf_desc.configuration_value, + interface_desc: Some(interface_num), + alternate_setting: Some(if_desc.alternate_setting), + hub_ports: None, + }) + .expect("Failed to configure endpoints"); + + //TODO: do we need to set protocol to report? It fails for mice. + + //TODO: dynamically create good values, fix xhcid so it does not block on each request + // This sets all reports to a duration of 4ms + reqs::set_idle(&handle, 1, 0, interface_num as u16).expect("Failed to set idle"); + + let report_desc_len = hid_desc.desc_len; + assert_eq!(hid_desc.desc_ty, REPORT_DESC_TY); + + let mut report_desc_bytes = vec![0u8; report_desc_len as usize]; + handle + .get_descriptor( + PortReqRecipient::Interface, + REPORT_DESC_TY, + 0, + //TODO: should this be an index into interface_descs? + interface_num as u16, + &mut report_desc_bytes, + ) + .expect("Failed to retrieve report descriptor"); + + let mut handler = + ReportHandler::new(&report_desc_bytes).expect("failed to parse report descriptor"); + + let report_len = match endp_desc_opt { + Some((_endp_num, endp_desc)) => endp_desc.max_packet_size as usize, + None => handler.total_byte_length as usize, + }; + let mut report_buffer = vec![0u8; report_len]; + let report_ty = ReportTy::Input; + let report_id = 0; + + let mut display = ProducerHandle::new().expect("Failed to open input socket"); + let mut endpoint_opt = match endp_desc_opt { + Some((endp_num, _endp_desc)) => match handle.open_endpoint(endp_num as u8) { + Ok(ok) => Some(ok), + Err(err) => { + log::warn!("failed to open endpoint {endp_num}: {err}"); + None + } + }, + None => None, + }; + let mut left_shift = false; + let mut right_shift = false; + let mut last_mouse_pos = (0, 0); + let mut last_buttons = [false, false, false]; + loop { + //TODO: get frequency from device + std::thread::sleep(std::time::Duration::from_millis(10)); + + if let Some(endpoint) = &mut endpoint_opt { + // interrupt transfer + endpoint + .transfer_read(&mut report_buffer) + .expect("failed to get report"); + } else { + // control transfer + reqs::get_report( + &handle, + report_ty, + report_id, + //TODO: should this be an index into interface_descs? + interface_num as u16, + &mut report_buffer, + ) + .expect("failed to get report"); + } + + let mut mouse_pos = last_mouse_pos; + let mut mouse_dx = 0i32; + let mut mouse_dy = 0i32; + let mut scroll_y = 0i32; + let mut buttons = last_buttons; + for event in handler + .handle(&report_buffer) + .expect("failed to parse report") + { + log::debug!("{:X?}", event); + if event.usage_page == UsagePage::GenericDesktop as u16 { + if event.usage == GenericDesktopUsage::X as u16 { + if event.relative { + mouse_dx += event.value as i32; + } else { + mouse_pos.0 = event.value as i32; + } + } else if event.usage == GenericDesktopUsage::Y as u16 { + if event.relative { + mouse_dy += event.value as i32; + } else { + mouse_pos.1 = event.value as i32; + } + } else if event.usage == GenericDesktopUsage::Wheel as u16 { + //TODO: what is X scroll? + if event.relative { + scroll_y += event.value as i32; + } else { + log::warn!("absolute mouse wheel not supported"); + } + } else { + log::info!( + "unsupported generic desktop usage 0x{:X}:0x{:X} value {}", + event.usage_page, + event.usage, + event.value + ); + } + } else if event.usage_page == UsagePage::KeyboardOrKeypad as u16 { + let (pressed, shift_opt) = if event.value != 0 { + (true, Some(left_shift | right_shift)) + } else { + (false, None) + }; + if event.usage == 0xE1 { + left_shift = pressed; + } else if event.usage == 0xE5 { + right_shift = pressed; + } + send_key_event( + &mut display, + event.usage_page, + event.usage, + pressed, + shift_opt, + ); + } else if event.usage_page == UsagePage::Button as u16 { + if event.usage > 0 && event.usage as usize <= buttons.len() { + buttons[event.usage as usize - 1] = event.value != 0; + } else { + log::info!( + "unsupported buttons usage 0x{:X}:0x{:X} value {}", + event.usage_page, + event.usage, + event.value + ); + } + } else if event.usage_page >= 0xFF00 { + // Ignore vendor defined event + } else { + log::info!( + "unsupported usage 0x{:X}:0x{:X} value {}", + event.usage_page, + event.usage, + event.value + ); + } + } + + if mouse_pos != last_mouse_pos { + last_mouse_pos = mouse_pos; + + // ps2d uses 0..=65535 as range, while usb uses 0..=32767. orbital + // expects the former range, so multiply by two here to translate + // the usb coordinates to what orbital expects. + let mouse_event = orbclient::event::MouseEvent { + x: mouse_pos.0 * 2, + y: mouse_pos.1 * 2, + }; + + match display.write_event(mouse_event.to_event()) { + Ok(_) => (), + Err(err) => { + log::warn!("failed to send mouse event to orbital: {}", err); + } + } + } + + if mouse_dx != 0 || mouse_dy != 0 { + let mouse_event = orbclient::event::MouseRelativeEvent { + dx: mouse_dx, + dy: mouse_dy, + }; + + match display.write_event(mouse_event.to_event()) { + Ok(_) => (), + Err(err) => { + log::warn!("failed to send mouse event to orbital: {}", err); + } + } + } + + if scroll_y != 0 { + let scroll_event = orbclient::event::ScrollEvent { x: 0, y: scroll_y }; + + match display.write_event(scroll_event.to_event()) { + Ok(_) => (), + Err(err) => { + log::warn!("failed to send scroll event to orbital: {}", err); + } + } + } + + if buttons != last_buttons { + last_buttons = buttons; + + let button_event = orbclient::event::ButtonEvent { + left: buttons[0], + right: buttons[1], + middle: buttons[2], + }; + + match display.write_event(button_event.to_event()) { + Ok(_) => (), + Err(err) => { + log::warn!("failed to send button event to orbital: {}", err); + } + } + } + } +} diff --git a/drivers/input/usbhidd/src/reqs.rs b/drivers/input/usbhidd/src/reqs.rs new file mode 100644 index 0000000000..c74281dd3d --- /dev/null +++ b/drivers/input/usbhidd/src/reqs.rs @@ -0,0 +1,109 @@ +use std::slice; + +use rehid::report_desc::ReportTy; +use xhcid_interface::{ + DeviceReqData, PortReqRecipient, PortReqTy, XhciClientHandle, XhciClientHandleError, +}; + +const GET_REPORT_REQ: u8 = 0x1; +const SET_REPORT_REQ: u8 = 0x9; +const GET_IDLE_REQ: u8 = 0x2; +const SET_IDLE_REQ: u8 = 0xA; +const GET_PROTOCOL_REQ: u8 = 0x3; +const SET_PROTOCOL_REQ: u8 = 0xB; + +fn concat(hi: u8, lo: u8) -> u16 { + (u16::from(hi) << 8) | u16::from(lo) +} + +pub fn get_report( + handle: &XhciClientHandle, + report_ty: ReportTy, + report_id: u8, + if_num: u16, + buffer: &mut [u8], +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + GET_REPORT_REQ, + concat(report_ty as u8, report_id), + if_num, + DeviceReqData::In(buffer), + ) +} +pub fn set_report( + handle: &XhciClientHandle, + report_ty: ReportTy, + report_id: u8, + if_num: u16, + buffer: &[u8], +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + SET_REPORT_REQ, + concat(report_id, report_ty as u8), + if_num, + DeviceReqData::Out(buffer), + ) +} +pub fn get_idle( + handle: &XhciClientHandle, + report_id: u8, + if_num: u16, +) -> Result { + let mut idle_rate = 0; + let buffer = slice::from_mut(&mut idle_rate); + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + GET_IDLE_REQ, + u16::from(report_id), + if_num, + DeviceReqData::In(buffer), + )?; + Ok(idle_rate) +} +pub fn set_idle( + handle: &XhciClientHandle, + duration: u8, + report_id: u8, + if_num: u16, +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + SET_IDLE_REQ, + concat(duration, report_id), + if_num, + DeviceReqData::NoData, + ) +} +pub fn get_protocol(handle: &XhciClientHandle, if_num: u16) -> Result { + let mut protocol = 0; + let buffer = slice::from_mut(&mut protocol); + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + GET_PROTOCOL_REQ, + 0, + if_num, + DeviceReqData::In(buffer), + )?; + Ok(protocol) +} +pub fn set_protocol( + handle: &XhciClientHandle, + protocol: u8, + if_num: u16, +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + SET_PROTOCOL_REQ, + u16::from(protocol), + if_num, + DeviceReqData::NoData, + ) +} diff --git a/drivers/inputd/Cargo.toml b/drivers/inputd/Cargo.toml new file mode 100644 index 0000000000..20ccff318a --- /dev/null +++ b/drivers/inputd/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "inputd" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +anyhow = "1.0.71" +log = "0.4.19" +redox-daemon = "0.1.2" +redox_syscall = { version = "0.5", features = ["std"] } +orbclient = "0.3.27" +libredox = "0.1.3" + +common = { path = "../common" } +redox-scheme = "0.6.2" diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs new file mode 100644 index 0000000000..e7e7e47fa7 --- /dev/null +++ b/drivers/inputd/src/lib.rs @@ -0,0 +1,198 @@ +use std::fs::{File, OpenOptions}; +use std::io::{self, Read, Write}; +use std::mem::size_of; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd}; +use std::os::unix::fs::OpenOptionsExt; +use std::path::PathBuf; +use std::slice; + +use libredox::flag::{O_CLOEXEC, O_NONBLOCK, O_RDWR}; +use orbclient::Event; +use syscall::ESTALE; + +fn read_to_slice( + file: BorrowedFd, + buf: &mut [T], +) -> Result { + unsafe { + libredox::call::read( + file.as_raw_fd() as usize, + slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len() * size_of::()), + ) + .map(|count| count / size_of::()) + } +} + +unsafe fn any_as_u8_slice(p: &T) -> &[u8] { + slice::from_raw_parts((p as *const T) as *const u8, size_of::()) +} + +unsafe fn any_as_u8_slice_mut(p: &mut T) -> &mut [u8] { + slice::from_raw_parts_mut((p as *mut T) as *mut u8, size_of::()) +} + +pub struct ConsumerHandle(File); + +pub enum ConsumerHandleEvent<'a> { + Events(&'a [Event]), + Handoff, +} + +impl ConsumerHandle { + pub fn new_vt() -> io::Result { + let file = OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK as i32) + .open(format!("/scheme/input/consumer"))?; + Ok(Self(file)) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + + pub fn open_display(&self) -> io::Result { + let mut buffer = [0; 1024]; + let fd = self.0.as_raw_fd(); + let written = libredox::call::fpath(fd as usize, &mut buffer)?; + + assert!(written <= buffer.len()); + + let display_path = std::str::from_utf8(&buffer[..written]) + .expect("init: display path UTF-8 check failed") + .to_owned(); + + let display_file = + libredox::call::open(&display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) + .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) + .unwrap_or_else(|err| { + panic!("failed to open display {}: {}", display_path, err); + }); + + Ok(display_file) + } + + pub fn open_display_v2(&self) -> io::Result { + let mut buffer = [0; 1024]; + let fd = self.0.as_raw_fd(); + let written = libredox::call::fpath(fd as usize, &mut buffer)?; + + assert!(written <= buffer.len()); + + let mut display_path = PathBuf::from( + std::str::from_utf8(&buffer[..written]) + .expect("init: display path UTF-8 check failed") + .to_owned(), + ); + display_path.set_file_name(format!( + "v2/{}", + display_path.file_name().unwrap().to_str().unwrap() + )); + let display_path = display_path.to_str().unwrap(); + + let display_file = + libredox::call::open(&display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) + .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) + .unwrap_or_else(|err| { + panic!("failed to open display {}: {}", display_path, err); + }); + + Ok(display_file) + } + + pub fn read_events<'a>(&self, events: &'a mut [Event]) -> io::Result> { + match read_to_slice(self.0.as_fd(), events) { + Ok(count) => Ok(ConsumerHandleEvent::Events(&events[..count])), + Err(err) if err.errno() == ESTALE => Ok(ConsumerHandleEvent::Handoff), + Err(err) => Err(err.into()), + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct VtActivate { + pub vt: usize, +} + +pub struct DisplayHandle(File); + +impl DisplayHandle { + pub fn new>(device_name: S) -> io::Result { + let path = format!("/scheme/input/handle/display/{}", device_name.into()); + Ok(Self(File::open(path)?)) + } + + pub fn new_early>(device_name: S) -> io::Result { + let path = format!("/scheme/input/handle_early/display/{}", device_name.into()); + Ok(Self(File::open(path)?)) + } + + pub fn read_vt_event(&mut self) -> io::Result> { + let mut event = VtEvent { + kind: VtEventKind::Resize, + vt: usize::MAX, + width: u32::MAX, + height: u32::MAX, + stride: u32::MAX, + }; + + let nread = self.0.read(unsafe { any_as_u8_slice_mut(&mut event) })?; + + if nread == 0 { + Ok(None) + } else { + assert_eq!(nread, size_of::()); + Ok(Some(event)) + } + } + + pub fn inner(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +pub struct ControlHandle(File); + +impl ControlHandle { + pub fn new() -> io::Result { + let path = format!("/scheme/input/control"); + Ok(Self(File::open(path)?)) + } + + pub fn activate_vt(&mut self, vt: usize) -> io::Result { + let cmd = VtActivate { vt }; + self.0.write(unsafe { any_as_u8_slice(&cmd) }) + } +} + +#[derive(Debug)] +#[repr(usize)] +pub enum VtEventKind { + Activate, + Resize, +} + +#[derive(Debug)] +#[repr(C)] +pub struct VtEvent { + pub kind: VtEventKind, + pub vt: usize, + + pub width: u32, + pub height: u32, + pub stride: u32, +} + +pub struct ProducerHandle(File); + +impl ProducerHandle { + pub fn new() -> io::Result { + File::open("/scheme/input/producer").map(ProducerHandle) + } + + pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { + self.0.write(&event)?; + Ok(()) + } +} diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs new file mode 100644 index 0000000000..fa1f963444 --- /dev/null +++ b/drivers/inputd/src/main.rs @@ -0,0 +1,603 @@ +//! `:input` +//! +//! A seperate scheme is required since all of the input from different input devices is required +//! to be combined into a single stream which is later going to be processed by the "consumer" +//! which usually is Orbital. +//! +//! ## Input Device ("producer") +//! Write events to `input:producer`. +//! +//! ## Input Consumer ("consumer") +//! Read events from `input:consumer`. Optionally, set the `EVENT_READ` flag to be notified when +//! events are available. + +use core::mem::size_of; +use std::collections::{BTreeMap, BTreeSet}; +use std::mem::transmute; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use inputd::{VtActivate, VtEvent, VtEventKind}; + +use libredox::errno::ESTALE; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket}; + +use orbclient::{Event, EventOption}; +use syscall::schemev2::NewFdFlags; +use syscall::{Error as SysError, EventFlags, EINVAL}; + +enum Handle { + Producer, + Consumer { + events: EventFlags, + pending: Vec, + /// We return an ESTALE error once to indicate that a handoff to a different graphics driver + /// is necessary. + needs_handoff: bool, + notified: bool, + vt: usize, + }, + Display { + events: EventFlags, + pending: Vec, + notified: bool, + device: String, + /// Control of all VT's gets handed over from earlyfb devices to the first non-earlyfb device. + is_earlyfb: bool, + }, + Control, +} + +struct InputScheme { + handles: BTreeMap, + + next_id: AtomicUsize, + next_vt_id: AtomicUsize, + + display: Option, + vts: BTreeSet, + super_key: bool, + active_vt: Option, + + has_new_events: bool, +} + +impl InputScheme { + fn new() -> Self { + Self { + handles: BTreeMap::new(), + + next_id: AtomicUsize::new(0), + next_vt_id: AtomicUsize::new(1), + + display: None, + vts: BTreeSet::new(), + super_key: false, + active_vt: None, + + has_new_events: false, + } + } + + fn switch_vt(&mut self, new_active: usize) { + if let Some(active_vt) = self.active_vt { + if new_active == active_vt { + return; + } + } + + if !self.vts.contains(&new_active) { + log::warn!("switch to non-existent VT #{new_active} was requested"); + return; + } + + log::debug!( + "switching from VT #{} to VT #{new_active}", + self.active_vt.unwrap_or(0) + ); + + for handle in self.handles.values_mut() { + match handle { + Handle::Display { + pending, + notified, + device, + .. + } => { + if self.display.as_deref() == Some(&*device) { + pending.push(VtEvent { + kind: VtEventKind::Activate, + vt: new_active, + width: 0, + height: 0, + stride: 0, + }); + *notified = false; + } + } + _ => continue, + } + } + + self.active_vt = Some(new_active); + } +} + +impl SchemeSync for InputScheme { + fn open(&mut self, path: &str, _flags: usize, _ctx: &CallerCtx) -> syscall::Result { + let mut path_parts = path.split('/'); + + let command = path_parts.next().ok_or(SysError::new(EINVAL))?; + let fd = self.next_id.fetch_add(1, Ordering::SeqCst); + + let handle_ty = match command { + "producer" => Handle::Producer, + "consumer" => { + let vt = self.next_vt_id.fetch_add(1, Ordering::Relaxed); + self.vts.insert(vt); + + if self.active_vt.is_none() { + self.switch_vt(vt); + } + + Handle::Consumer { + events: EventFlags::empty(), + pending: Vec::new(), + needs_handoff: false, + notified: false, + vt, + } + } + "handle" | "handle_early" => { + let display = path_parts.collect::>().join("."); + + let needs_handoff = match command { + "handle_early" => self.display.is_none(), + "handle" => self.handles.values().all(|handle| { + !matches!( + handle, + Handle::Display { + is_earlyfb: false, + .. + } + ) + }), + _ => unreachable!(), + }; + + if needs_handoff { + self.has_new_events = true; + self.display = Some(display.clone()); + + for handle in self.handles.values_mut() { + match handle { + Handle::Consumer { + needs_handoff, + notified, + .. + } => { + *needs_handoff = true; + *notified = false; + } + _ => continue, + } + } + } + + Handle::Display { + events: EventFlags::empty(), + pending: if let Some(active_vt) = self.active_vt { + vec![VtEvent { + kind: VtEventKind::Activate, + vt: active_vt, + width: 0, + height: 0, + stride: 0, + }] + } else { + vec![] + }, + notified: false, + device: display, + is_earlyfb: command == "handle_early", + } + } + "control" => Handle::Control, + + _ => { + log::error!("invalid path '{path}'"); + return Err(SysError::new(EINVAL)); + } + }; + + log::debug!("{path} channel has been opened"); + + self.handles.insert(fd, handle_ty); + Ok(OpenResult::ThisScheme { + number: fd, + flags: NewFdFlags::empty(), + }) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> syscall::Result { + let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?; + + if let Handle::Consumer { vt, .. } = handle { + let display = self.display.as_ref().ok_or(SysError::new(EINVAL))?; + let vt = format!("/scheme/{}/{vt}", display); + + let size = core::cmp::min(vt.len(), buf.len()); + buf[..size].copy_from_slice(&vt.as_bytes()[..size]); + + Ok(size) + } else { + Err(SysError::new(EINVAL)) + } + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + match handle { + Handle::Consumer { + pending, + needs_handoff, + .. + } => { + if *needs_handoff { + *needs_handoff = false; + // Indicates that handoff to a new graphics driver is necessary. + return Err(SysError::new(ESTALE)); + } + + let copy = core::cmp::min(pending.len(), buf.len()); + + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + + Ok(copy) + } + + Handle::Display { pending, .. } => { + if buf.len() % size_of::() == 0 { + let copy = core::cmp::min(pending.len(), buf.len() / size_of::()); + + for (i, event) in pending.drain(..copy).enumerate() { + buf[i * size_of::()..(i + 1) * size_of::()] + .copy_from_slice(&unsafe { + transmute::()]>(event) + }); + } + Ok(copy * size_of::()) + } else { + log::error!("display tried to read incorrectly sized event"); + return Err(SysError::new(EINVAL)); + } + } + + Handle::Producer => { + log::error!("producer tried to read"); + return Err(SysError::new(EINVAL)); + } + Handle::Control => { + log::error!("control tried to read"); + return Err(SysError::new(EINVAL)); + } + } + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> syscall::Result { + self.has_new_events = true; + + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + match handle { + Handle::Control => { + if buf.len() != size_of::() { + log::error!("control tried to write incorrectly sized command"); + return Err(SysError::new(EINVAL)); + } + + // SAFETY: We have verified the size of the buffer above. + let cmd = unsafe { &*buf.as_ptr().cast::() }; + + self.switch_vt(cmd.vt); + + return Ok(buf.len()); + } + + Handle::Consumer { .. } => { + log::error!("consumer tried to write"); + return Err(SysError::new(EINVAL)); + } + Handle::Display { .. } => { + log::error!("display tried to write"); + return Err(SysError::new(EINVAL)); + } + Handle::Producer => {} + } + + if buf.len() == 1 && buf[0] > 0xf4 { + return Ok(1); + } + + let events = unsafe { + core::slice::from_raw_parts( + buf.as_ptr() as *const Event, + buf.len() / size_of::(), + ) + }; + + for event in events.iter() { + let mut new_active_opt = None; + match event.to_option() { + EventOption::Key(key_event) => match key_event.scancode { + f @ 0x3B..=0x44 if self.super_key => { + // F1 through F10 + new_active_opt = Some((f - 0x3A) as usize); + } + + 0x57 if self.super_key => { + // F11 + new_active_opt = Some(11); + } + + 0x58 if self.super_key => { + // F12 + new_active_opt = Some(12); + } + + 0x5B => { + // Super + self.super_key = key_event.pressed; + } + + _ => (), + }, + + EventOption::Resize(resize_event) => { + for handle in self.handles.values_mut() { + match handle { + Handle::Display { + pending, + notified, + device, + .. + } => { + if self.display.as_ref() == Some(device) { + pending.push(VtEvent { + kind: VtEventKind::Resize, + vt: self.active_vt.unwrap(), + width: resize_event.width, + height: resize_event.height, + + // TODO(andypython): Figure out how to get the stride. + stride: resize_event.width, + }); + *notified = false; + } + } + _ => continue, + } + } + } + + _ => continue, + } + + if let Some(new_active) = new_active_opt { + self.switch_vt(new_active); + } + } + + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + assert!(matches!(handle, Handle::Producer)); + + if let Some(active_vt) = self.active_vt { + for handle in self.handles.values_mut() { + match handle { + Handle::Consumer { + pending, + notified, + vt, + .. + } => { + if *vt != active_vt { + continue; + } + + pending.extend_from_slice(buf); + *notified = false; + } + _ => continue, + } + } + } + + Ok(buf.len()) + } + + fn fevent( + &mut self, + id: usize, + flags: syscall::EventFlags, + _ctx: &CallerCtx, + ) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + match handle { + Handle::Consumer { + ref mut events, + ref mut notified, + .. + } => { + *events = flags; + *notified = false; + Ok(EventFlags::empty()) + } + Handle::Display { + ref mut events, + ref mut notified, + .. + } => { + *events = flags; + *notified = false; + Ok(EventFlags::empty()) + } + Handle::Producer | Handle::Control => { + log::error!("producer or control tried to use an event queue"); + Err(SysError::new(EINVAL)) + } + } + } +} + +impl InputScheme { + fn on_close(&mut self, id: usize) { + let handle = self.handles.remove(&id).unwrap(); + + match handle { + Handle::Consumer { vt, .. } => { + self.vts.remove(&vt); + if self.active_vt == Some(vt) { + if let Some(&new_vt) = self.vts.last() { + self.switch_vt(new_vt); + } else { + self.active_vt = None; + } + } + } + _ => {} + } + } +} + +fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { + // Create the ":input" scheme. + let socket_file = Socket::create("input")?; + let mut scheme = InputScheme::new(); + + deamon.ready().unwrap(); + + loop { + scheme.has_new_events = false; + let Some(request) = socket_file.next_request(SignalBehavior::Restart)? else { + // Scheme likely got unmounted + return Ok(()); + }; + + match request.kind() { + RequestKind::Call(call_request) => { + socket_file.write_response( + call_request.handle_sync(&mut scheme), + SignalBehavior::Restart, + )?; + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + _ => {} + } + + if !scheme.has_new_events { + continue; + } + + for (id, handle) in scheme.handles.iter_mut() { + match handle { + Handle::Consumer { + events, + pending, + needs_handoff, + ref mut notified, + .. + } => { + if (!*needs_handoff && pending.is_empty()) + || *notified + || !events.contains(EventFlags::EVENT_READ) + { + continue; + } + + // Notify the consumer that we have some events to read. Yum yum. + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, + )?; + + *notified = true; + } + Handle::Display { + events, + pending, + ref mut notified, + .. + } => { + if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { + continue; + } + + // Notify the consumer that we have some events to read. Yum yum. + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, + )?; + + *notified = true; + } + _ => {} + } + } + } +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + deamon(redox_daemon).unwrap(); + unreachable!(); +} + +fn main() { + common::setup_logging( + "input", + "inputd", + "inputd", + common::output_level(), + common::file_level(), + ); + + let mut args = std::env::args().skip(1); + + if let Some(val) = args.next() { + match val.as_ref() { + // Activates a VT. + "-A" => { + let vt = args.next().unwrap().parse::().unwrap(); + + let mut handle = + inputd::ControlHandle::new().expect("inputd: failed to open display handle"); + handle + .activate_vt(vt) + .expect("inputd: failed to activate VT"); + } + + _ => panic!("inputd: invalid argument: {}", val), + } + } else { + redox_daemon::Daemon::new(daemon_runner).expect("inputd: failed to daemonize"); + } +} diff --git a/drivers/net/alxd/Cargo.toml b/drivers/net/alxd/Cargo.toml new file mode 100644 index 0000000000..312da94841 --- /dev/null +++ b/drivers/net/alxd/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "alxd" +version = "0.1.0" +edition = "2018" + +[dependencies] +bitflags = "2" +redox_event = "0.4.1" +redox_syscall = "0.5" +redox-daemon = "0.1" + +common = { path = "../../common" } +libredox = "0.1.3" +redox-scheme = "0.8.2" diff --git a/drivers/net/alxd/src/device/mod.rs b/drivers/net/alxd/src/device/mod.rs new file mode 100644 index 0000000000..94a09b8a86 --- /dev/null +++ b/drivers/net/alxd/src/device/mod.rs @@ -0,0 +1,1906 @@ +use std::convert::TryInto; +use std::{ptr, thread, time}; + +use common::io::{Io, Mmio}; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::CallerCtx; +use redox_scheme::OpenResult; +use syscall::error::{Error, Result, EACCES, EINVAL, EIO, EWOULDBLOCK}; +use syscall::flag::{EventFlags, O_NONBLOCK}; +use syscall::schemev2::NewFdFlags; + +use common::dma::Dma; + +use self::regs::*; + +mod regs; + +const ERR_ALOAD: usize = 1; +const ERR_RSTMAC: usize = 2; +const ERR_PARM: usize = 3; +const ERR_MIIBUSY: usize = 4; +const LINK_TIMEOUT: usize = 8; + +const FLAG_HALT: u32 = 0; +const FLAG_TASK_RESET: u32 = 1; +const FLAG_TASK_CHK_LINK: u32 = 2; +const FLAG_TASK_UPDATE_SMB: u32 = 3; + +const HALF_DUPLEX: u8 = 1; +const FULL_DUPLEX: u8 = 2; + +const SPEED_0: u16 = 0; +const SPEED_10: u16 = 10; +const SPEED_100: u16 = 100; +const SPEED_1000: u16 = 1000; + +const FC_RX: u8 = 0x01; +const FC_TX: u8 = 0x02; +const FC_ANEG: u8 = 0x04; + +const CAP_GIGA: u32 = 1 << 0; +const CAP_PTP: u32 = 1 << 1; +const CAP_AZ: u32 = 1 << 2; +const CAP_L0S: u32 = 1 << 3; +const CAP_L1: u32 = 1 << 4; +const CAP_SWOI: u32 = 1 << 5; +const CAP_RSS: u32 = 1 << 6; +const CAP_MSIX: u32 = 1 << 7; +/* support Multi-TX-Q */ +const CAP_MTQ: u32 = 1 << 8; +/* support Multi-RX-Q */ +const CAP_MRQ: u32 = 1 << 9; + +const ISR_MISC: u32 = ISR_PCIE_LNKDOWN | ISR_DMAW | ISR_DMAR | ISR_SMB | ISR_MANU | ISR_TIMER; + +const ISR_FATAL: u32 = ISR_PCIE_LNKDOWN | ISR_DMAW | ISR_DMAR; + +const ISR_ALERT: u32 = ISR_RXF_OV | ISR_TXF_UR | ISR_RFD_UR; + +const ISR_ALL_QUEUES: u32 = ISR_TX_Q0 + | ISR_TX_Q1 + | ISR_TX_Q2 + | ISR_TX_Q3 + | ISR_RX_Q0 + | ISR_RX_Q1 + | ISR_RX_Q2 + | ISR_RX_Q3 + | ISR_RX_Q4 + | ISR_RX_Q5 + | ISR_RX_Q6 + | ISR_RX_Q7; + +const PCI_COMMAND_IO: u16 = 0x1; /* Enable response in I/O space */ +const PCI_COMMAND_MEMORY: u16 = 0x2; /* Enable response in Memory space */ +const PCI_COMMAND_MASTER: u16 = 0x4; /* Enable bus mastering */ +const PCI_COMMAND_SPECIAL: u16 = 0x8; /* Enable response to special cycles */ +const PCI_COMMAND_INVALIDATE: u16 = 0x10; /* Use memory write and invalidate */ +const PCI_COMMAND_VGA_PALETTE: u16 = 0x20; /* Enable palette snooping */ +const PCI_COMMAND_PARITY: u16 = 0x40; /* Enable parity checking */ +const PCI_COMMAND_WAIT: u16 = 0x80; /* Enable address/data stepping */ +const PCI_COMMAND_SERR: u16 = 0x100; /* Enable SERR */ +const PCI_COMMAND_FAST_BACK: u16 = 0x200; /* Enable back-to-back writes */ +const PCI_COMMAND_INTX_DISABLE: u16 = 0x400; /* INTx Emulation Disable */ + +/// MII basic mode control register +const MII_BMCR: u16 = 0x00; +const BMCR_FULLDPLX: u16 = 0x0100; +const BMCR_ANRESTART: u16 = 0x0200; +const BMCR_ANENABLE: u16 = 0x1000; +const BMCR_SPEED100: u16 = 0x2000; +const BMCR_RESET: u16 = 0x8000; + +/// MII basic mode status register +const MII_BMSR: u16 = 0x01; +const BMSR_LSTATUS: u16 = 0x0004; + +/// MII advertisement register +const MII_ADVERTISE: u16 = 0x04; + +/// MII 1000BASE-T control +const MII_CTRL1000: u16 = 0x09; + +const ETH_HLEN: u16 = 14; + +const ADVERTISED_10baseT_Half: u32 = 1 << 0; +const ADVERTISED_10baseT_Full: u32 = 1 << 1; +const ADVERTISED_100baseT_Half: u32 = 1 << 2; +const ADVERTISED_100baseT_Full: u32 = 1 << 3; +const ADVERTISED_1000baseT_Half: u32 = 1 << 4; +const ADVERTISED_1000baseT_Full: u32 = 1 << 5; +const ADVERTISED_Autoneg: u32 = 1 << 6; +const ADVERTISED_Pause: u32 = 1 << 13; +const ADVERTISED_Asym_Pause: u32 = 1 << 14; + +const ADVERTISE_CSMA: u32 = 0x0001; /* Only selector supported */ +const ADVERTISE_10HALF: u32 = 0x0020; /* Try for 10mbps half-duplex */ +const ADVERTISE_1000XFULL: u32 = 0x0020; /* Try for 1000BASE-X full-duplex */ +const ADVERTISE_10FULL: u32 = 0x0040; /* Try for 10mbps full-duplex */ +const ADVERTISE_1000XHALF: u32 = 0x0040; /* Try for 1000BASE-X half-duplex */ +const ADVERTISE_100HALF: u32 = 0x0080; /* Try for 100mbps half-duplex */ +const ADVERTISE_1000XPAUSE: u32 = 0x0080; /* Try for 1000BASE-X pause */ +const ADVERTISE_100FULL: u32 = 0x0100; /* Try for 100mbps full-duplex */ +const ADVERTISE_1000XPSE_ASYM: u32 = 0x0100; /* Try for 1000BASE-X asym pause */ +const ADVERTISE_100BASE4: u32 = 0x0200; /* Try for 100mbps 4k packets */ +const ADVERTISE_PAUSE_CAP: u32 = 0x0400; /* Try for pause */ +const ADVERTISE_PAUSE_ASYM: u32 = 0x0800; /* Try for asymetric pause */ + +const ADVERTISE_1000HALF: u32 = 0x0100; +const ADVERTISE_1000FULL: u32 = 0x0200; + +macro_rules! FIELD_GETX { + ($x:expr, $name:ident) => { + ((($x) >> ${concat($name, _SHIFT)} & ${concat($name, _MASK)})) + }; +} + +macro_rules! FIELDX { + ($name:ident, $v:expr) => { + (((($v) as u32) & ${concat($name, _MASK)}) << ${concat($name, _SHIFT)}) + }; +} + +macro_rules! FIELD_SETS { + ($x:expr, $name:ident, $v:expr) => {{ + ($x) = (($x) & !(${concat($name, _MASK)} << ${concat($name, _SHIFT)})) + | (((($v) as u16) & ${concat($name, _MASK)}) << ${concat($name, _SHIFT)}) + }}; +} + +macro_rules! FIELD_SET32 { + ($x:expr, $name:ident, $v:expr) => {{ + ($x) = (($x) & !(${concat($name, _MASK)} << ${concat($name, _SHIFT)})) + | (((($v) as u32) & ${concat($name, _MASK)}) << ${concat($name, _SHIFT)}) + }}; +} + +fn udelay(micros: u32) { + thread::sleep(time::Duration::new(0, micros * 1000)); +} + +fn ethtool_adv_to_mii_adv_t(ethadv: u32) -> u32 { + let mut result: u32 = 0; + + if (ethadv & ADVERTISED_10baseT_Half > 0) { + result |= ADVERTISE_10HALF; + } + if (ethadv & ADVERTISED_10baseT_Full > 0) { + result |= ADVERTISE_10FULL; + } + if (ethadv & ADVERTISED_100baseT_Half > 0) { + result |= ADVERTISE_100HALF; + } + if (ethadv & ADVERTISED_100baseT_Full > 0) { + result |= ADVERTISE_100FULL; + } + if (ethadv & ADVERTISED_Pause > 0) { + result |= ADVERTISE_PAUSE_CAP; + } + if (ethadv & ADVERTISED_Asym_Pause > 0) { + result |= ADVERTISE_PAUSE_ASYM; + } + + return result; +} + +fn ethtool_adv_to_mii_ctrl1000_t(ethadv: u32) -> u32 { + let mut result: u32 = 0; + + if (ethadv & ADVERTISED_1000baseT_Half > 0) { + result |= ADVERTISE_1000HALF; + } + if (ethadv & ADVERTISED_1000baseT_Full > 0) { + result |= ADVERTISE_1000FULL; + } + + return result; +} + +/// Transmit packet descriptor +#[repr(C, packed)] +struct Tpd { + blen: Mmio, + vlan: Mmio, + flags: Mmio, + addr_low: Mmio, + addr_high: Mmio, +} + +/// Receive free descriptor +#[repr(C, packed)] +struct Rfd { + addr_low: Mmio, + addr_high: Mmio, +} + +/// Receive return descriptor +#[repr(C, packed)] +struct Rrd { + checksum: Mmio, + rfd: Mmio, + rss: Mmio, + vlan: Mmio, + proto: Mmio, + rss_flags: Mmio, + len: Mmio, + flags: Mmio, +} + +pub struct Alx { + base: usize, + + vendor_id: u16, + device_id: u16, + subdev_id: u16, + subven_id: u16, + revision: u8, + + cap: u32, + flag: u32, + + mtu: u16, + imt: u16, + dma_chnl: u8, + ith_tpd: u32, + mc_hash: [u32; 2], + + wrr: [u32; 4], + wrr_ctrl: u32, + + imask: u32, + smb_timer: u32, + link_up: bool, + link_speed: u16, + link_duplex: u8, + + adv_cfg: u32, + flowctrl: u8, + + rx_ctrl: u32, + + lnk_patch: bool, + hib_patch: bool, + is_fpga: bool, + + rfd_buffer: [Dma<[u8; 16384]>; 16], + rfd_ring: Dma<[Rfd; 16]>, + rrd_ring: Dma<[Rrd; 16]>, + tpd_buffer: [Dma<[u8; 16384]>; 16], + tpd_ring: [Dma<[Tpd; 16]>; 4], +} + +fn dma_array() -> Result<[Dma; N]> { + Ok((0..N) + .map(|_| Ok(Dma::zeroed().map(|dma| unsafe { dma.assume_init() })?)) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!())) +} + +impl Alx { + pub unsafe fn new(base: usize) -> Result { + let mut module = Alx { + base, + + vendor_id: 0, + device_id: 0, + subdev_id: 0, + subven_id: 0, + revision: 0, + + cap: 0, + flag: 0, + + mtu: 1500, /*TODO: Get from adapter?*/ + imt: 200, + dma_chnl: 0, + ith_tpd: 5, /* ~ size of tpd_ring / 3 */ + mc_hash: [0; 2], + + wrr: [4; 4], + wrr_ctrl: WRR_PRI_RESTRICT_NONE, + + imask: ISR_MISC, + smb_timer: 400, + link_up: false, + link_speed: 0, + link_duplex: 0, + + adv_cfg: ADVERTISED_Autoneg + | ADVERTISED_10baseT_Half + | ADVERTISED_10baseT_Full + | ADVERTISED_100baseT_Full + | ADVERTISED_100baseT_Half + | ADVERTISED_1000baseT_Full, + flowctrl: FC_ANEG | FC_RX | FC_TX, + + rx_ctrl: MAC_CTRL_WOLSPED_SWEN + | MAC_CTRL_MHASH_ALG_HI5B + | MAC_CTRL_BRD_EN + | MAC_CTRL_PCRCE + | MAC_CTRL_CRCE + | MAC_CTRL_RXFC_EN + | MAC_CTRL_TXFC_EN + | FIELDX!(MAC_CTRL_PRMBLEN, 7), + + lnk_patch: false, + hib_patch: false, + is_fpga: false, + + rfd_buffer: dma_array()?, + rfd_ring: Dma::zeroed()?.assume_init(), + rrd_ring: Dma::zeroed()?.assume_init(), + tpd_buffer: dma_array()?, + tpd_ring: dma_array()?, + }; + + module.init()?; + + Ok(module) + } + + pub fn revid(&self) -> u8 { + self.revision >> PCI_REVID_SHIFT + } + + pub fn with_cr(&self) -> bool { + self.revision & 1 > 0 + } + + unsafe fn handle_intr_misc(&mut self, intr: u32) -> bool { + if (intr & ISR_FATAL > 0) { + println!("intr-fatal: {:X}", intr); + self.flag |= FLAG_TASK_RESET; + self.task(); + return true; + } + + if (intr & ISR_ALERT > 0) { + println!("interrupt alert: {:X}", intr); + } + + if (intr & ISR_SMB > 0) { + self.flag |= FLAG_TASK_UPDATE_SMB; + self.task(); + } + + if (intr & ISR_PHY > 0) { + /* suppress PHY interrupt, because the source + * is from PHY internal. only the internal status + * is cleared, the interrupt status could be cleared. + */ + self.imask &= !ISR_PHY; + let imask = self.imask; + self.reg_write(IMR, imask); + self.flag |= FLAG_TASK_CHK_LINK; + self.task(); + } + + return false; + } + + unsafe fn intr_1(&mut self, mut intr: u32) -> bool { + /* ACK interrupt */ + println!("ACK interrupt: {:X}", intr | ISR_DIS); + self.reg_write(ISR, intr | ISR_DIS); + intr &= self.imask; + + if (self.handle_intr_misc(intr)) { + return true; + } + + if (intr & (ISR_TX_Q0 | ISR_RX_Q0) > 0) { + println!("TX | RX"); + //TODO: napi_schedule(&adpt->qnapi[0]->napi); + /* mask rx/tx interrupt, enable them when napi complete */ + self.imask &= !ISR_ALL_QUEUES; + let imask = self.imask; + self.reg_write(IMR, imask); + } + + self.reg_write(ISR, 0); + + return true; + } + + pub unsafe fn intr_legacy(&mut self) -> bool { + /* read interrupt status */ + let intr = self.reg_read(ISR); + if (intr & ISR_DIS > 0 || intr & self.imask == 0) { + let mask = self.reg_read(IMR); + println!( + "seems a wild interrupt, intr={:X}, imask={:X}, mask={:X}", + intr, self.imask, mask + ); + + return false; + } + + return self.intr_1(intr); + } + + pub fn next_reg_read(&self) -> usize { + /* + let head = unsafe { self.reg_read(RDH) }; + let mut tail = unsafe { self.reg_read(RDT) }; + + tail += 1; + if tail >= self.receive_ring.len() as u32 { + tail = 0; + } + + if tail != head { + let rd = unsafe { &* (self.receive_ring.as_ptr().offset(tail as isize) as *const Rd) }; + if rd.status & RD_DD == RD_DD { + return rd.length as usize; + } + } + + 0 + */ + 0 + } + + unsafe fn reg_read(&self, register: u32) -> u32 { + ptr::read_volatile((self.base + register as usize) as *mut u32) + } + + unsafe fn reg_write(&self, register: u32, data: u32) -> u32 { + ptr::write_volatile((self.base + register as usize) as *mut u32, data); + ptr::read_volatile((self.base + register as usize) as *mut u32) + } + + unsafe fn wait_mdio_idle(&mut self) -> bool { + let mut val: u32; + let mut i: u32 = 0; + + while (i < MDIO_MAX_AC_TO) { + val = self.reg_read(MDIO); + if (val & MDIO_BUSY == 0) { + break; + } + udelay(10); + i += 1; + } + return i != MDIO_MAX_AC_TO; + } + + unsafe fn stop_phy_polling(&mut self) { + if (!self.is_fpga) { + return; + } + + self.reg_write(MDIO, 0); + self.wait_mdio_idle(); + } + + unsafe fn start_phy_polling(&mut self, clk_sel: u16) { + let mut val: u32; + + if (!self.is_fpga) { + return; + } + + val = MDIO_SPRES_PRMBL + | FIELDX!(MDIO_CLK_SEL, clk_sel) + | FIELDX!(MDIO_REG, 1) + | MDIO_START + | MDIO_OP_READ; + self.reg_write(MDIO, val); + self.wait_mdio_idle(); + val |= MDIO_AUTO_POLLING; + val &= !MDIO_START; + self.reg_write(MDIO, val); + udelay(30); + } + + unsafe fn read_phy_core(&mut self, ext: bool, dev: u8, reg: u16, phy_data: &mut u16) -> usize { + let mut val: u32; + let clk_sel: u16; + let err: usize; + + self.stop_phy_polling(); + + *phy_data = 0; + + /* use slow clock when it's in hibernation status */ + clk_sel = if !self.link_up { + MDIO_CLK_SEL_25MD128 + } else { + MDIO_CLK_SEL_25MD4 + }; + + if (ext) { + val = FIELDX!(MDIO_EXTN_DEVAD, dev) | FIELDX!(MDIO_EXTN_REG, reg); + self.reg_write(MDIO_EXTN, val); + + val = MDIO_SPRES_PRMBL + | FIELDX!(MDIO_CLK_SEL, clk_sel) + | MDIO_START + | MDIO_MODE_EXT + | MDIO_OP_READ; + } else { + val = MDIO_SPRES_PRMBL + | FIELDX!(MDIO_CLK_SEL, clk_sel) + | FIELDX!(MDIO_REG, reg) + | MDIO_START + | MDIO_OP_READ; + } + self.reg_write(MDIO, val); + + if (!self.wait_mdio_idle()) { + err = ERR_MIIBUSY; + } else { + val = self.reg_read(MDIO); + *phy_data = FIELD_GETX!(val, MDIO_DATA) as u16; + err = 0; + } + + self.start_phy_polling(clk_sel); + + return err; + } + + unsafe fn write_phy_core(&mut self, ext: bool, dev: u8, reg: u16, phy_data: u16) -> usize { + let mut val: u32; + let clk_sel: u16; + let mut err: usize = 0; + + self.stop_phy_polling(); + + /* use slow clock when it's in hibernation status */ + clk_sel = if !self.link_up { + MDIO_CLK_SEL_25MD128 + } else { + MDIO_CLK_SEL_25MD4 + }; + + if (ext) { + val = FIELDX!(MDIO_EXTN_DEVAD, dev) | FIELDX!(MDIO_EXTN_REG, reg); + self.reg_write(MDIO_EXTN, val); + + val = MDIO_SPRES_PRMBL + | FIELDX!(MDIO_CLK_SEL, clk_sel) + | FIELDX!(MDIO_DATA, phy_data) + | MDIO_START + | MDIO_MODE_EXT; + } else { + val = MDIO_SPRES_PRMBL + | FIELDX!(MDIO_CLK_SEL, clk_sel) + | FIELDX!(MDIO_REG, reg) + | FIELDX!(MDIO_DATA, phy_data) + | MDIO_START; + } + self.reg_write(MDIO, val); + + if !self.wait_mdio_idle() { + err = ERR_MIIBUSY; + } + + self.start_phy_polling(clk_sel); + + return err; + } + + unsafe fn read_phy_reg(&mut self, reg: u16, phy_data: &mut u16) -> usize { + self.read_phy_core(false, 0, reg, phy_data) + } + + unsafe fn write_phy_reg(&mut self, reg: u16, phy_data: u16) -> usize { + self.write_phy_core(false, 0, reg, phy_data) + } + + unsafe fn read_phy_ext(&mut self, dev: u8, reg: u16, data: &mut u16) -> usize { + self.read_phy_core(true, dev, reg, data) + } + + unsafe fn write_phy_ext(&mut self, dev: u8, reg: u16, data: u16) -> usize { + self.write_phy_core(true, dev, reg, data) + } + + unsafe fn read_phy_dbg(&mut self, reg: u16, data: &mut u16) -> usize { + let err = self.write_phy_reg(MII_DBG_ADDR, reg); + if (err > 0) { + return err; + } + + self.read_phy_reg(MII_DBG_DATA, data) + } + + unsafe fn write_phy_dbg(&mut self, reg: u16, data: u16) -> usize { + let err = self.write_phy_reg(MII_DBG_ADDR, reg); + if (err > 0) { + return err; + } + + self.write_phy_reg(MII_DBG_DATA, data) + } + + unsafe fn enable_aspm(&mut self, l0s_en: bool, l1_en: bool) { + let mut pmctrl: u32; + let rev: u8 = self.revid(); + + pmctrl = self.reg_read(PMCTRL); + + FIELD_SET32!(pmctrl, PMCTRL_LCKDET_TIMER, PMCTRL_LCKDET_TIMER_DEF); + pmctrl |= PMCTRL_RCVR_WT_1US | PMCTRL_L1_CLKSW_EN | PMCTRL_L1_SRDSRX_PWD; + FIELD_SET32!(pmctrl, PMCTRL_L1REQ_TO, PMCTRL_L1REG_TO_DEF); + FIELD_SET32!(pmctrl, PMCTRL_L1_TIMER, PMCTRL_L1_TIMER_16US); + pmctrl &= !(PMCTRL_L1_SRDS_EN + | PMCTRL_L1_SRDSPLL_EN + | PMCTRL_L1_BUFSRX_EN + | PMCTRL_SADLY_EN + | PMCTRL_HOTRST_WTEN + | PMCTRL_L0S_EN + | PMCTRL_L1_EN + | PMCTRL_ASPM_FCEN + | PMCTRL_TXL1_AFTER_L0S + | PMCTRL_RXL1_AFTER_L0S); + if ((rev == REV_A0 || rev == REV_A1) && self.with_cr()) { + pmctrl |= PMCTRL_L1_SRDS_EN | PMCTRL_L1_SRDSPLL_EN; + } + + if (l0s_en) { + pmctrl |= (PMCTRL_L0S_EN | PMCTRL_ASPM_FCEN); + } + if (l1_en) { + pmctrl |= (PMCTRL_L1_EN | PMCTRL_ASPM_FCEN); + } + + self.reg_write(PMCTRL, pmctrl); + } + + unsafe fn reset_pcie(&mut self) { + let mut val: u32; + let rev: u8 = self.revid(); + + /* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */ + let mut val16 = ptr::read((self.base + 4) as *const u16); + if (val16 & (PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO) == 0 + || val16 & PCI_COMMAND_INTX_DISABLE > 0) + { + println!("Fix PCI_COMMAND_INTX_DISABLE"); + val16 = (val16 | (PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO)) + & !PCI_COMMAND_INTX_DISABLE; + ptr::write((self.base + 4) as *mut u16, val16); + } + + /* clear WoL setting/status */ + self.reg_read(WOL0); + self.reg_write(WOL0, 0); + + /* deflt val of PDLL D3PLLOFF */ + val = self.reg_read(PDLL_TRNS1); + self.reg_write(PDLL_TRNS1, val & !PDLL_TRNS1_D3PLLOFF_EN); + + /* mask some pcie error bits */ + val = self.reg_read(UE_SVRT); + val &= !(UE_SVRT_DLPROTERR | UE_SVRT_FCPROTERR); + self.reg_write(UE_SVRT, val); + + /* wol 25M & pclk */ + val = self.reg_read(MASTER); + if ((rev == REV_A0 || rev == REV_A1) && self.with_cr()) { + if ((val & MASTER_WAKEN_25M) == 0 || (val & MASTER_PCLKSEL_SRDS) == 0) { + self.reg_write(MASTER, val | MASTER_PCLKSEL_SRDS | MASTER_WAKEN_25M); + } + } else { + if ((val & MASTER_WAKEN_25M) == 0 || (val & MASTER_PCLKSEL_SRDS) != 0) { + self.reg_write(MASTER, (val & !MASTER_PCLKSEL_SRDS) | MASTER_WAKEN_25M); + } + } + + /* ASPM setting */ + let l0s_en = self.cap & CAP_L0S > 0; + let l1_en = self.cap & CAP_L1 > 0; + self.enable_aspm(l0s_en, l1_en); + + udelay(10); + } + + unsafe fn reset_phy(&mut self) { + let mut i: u32; + let mut val: u32; + let mut phy_val: u16 = 0; + + /* (DSP)reset PHY core */ + val = self.reg_read(PHY_CTRL); + val &= !(PHY_CTRL_DSPRST_OUT + | PHY_CTRL_IDDQ + | PHY_CTRL_GATE_25M + | PHY_CTRL_POWER_DOWN + | PHY_CTRL_CLS); + val |= PHY_CTRL_RST_ANALOG; + + if (!self.hib_patch) { + val |= (PHY_CTRL_HIB_PULSE | PHY_CTRL_HIB_EN); + } else { + val &= !(PHY_CTRL_HIB_PULSE | PHY_CTRL_HIB_EN); + } + self.reg_write(PHY_CTRL, val); + udelay(10); + self.reg_write(PHY_CTRL, val | PHY_CTRL_DSPRST_OUT); + + /* delay 800us */ + i = 0; + while (i < PHY_CTRL_DSPRST_TO) { + udelay(10); + i += 1; + } + + if !self.is_fpga { + /* phy power saving & hib */ + if (!self.hib_patch) { + self.write_phy_dbg(MIIDBG_LEGCYPS, LEGCYPS_DEF); + self.write_phy_dbg(MIIDBG_SYSMODCTRL, SYSMODCTRL_IECHOADJ_DEF); + self.write_phy_ext(MIIEXT_PCS, MIIEXT_VDRVBIAS, VDRVBIAS_DEF); + } else { + self.write_phy_dbg(MIIDBG_LEGCYPS, LEGCYPS_DEF & !LEGCYPS_EN); + self.write_phy_dbg(MIIDBG_HIBNEG, HIBNEG_NOHIB); + self.write_phy_dbg(MIIDBG_GREENCFG, GREENCFG_DEF); + } + + /* EEE advertisement */ + if (self.cap & CAP_AZ > 0) { + let eeeadv = if self.cap & CAP_GIGA > 0 { + LOCAL_EEEADV_1000BT | LOCAL_EEEADV_100BT + } else { + LOCAL_EEEADV_100BT + }; + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_LOCAL_EEEADV, eeeadv); + /* half amplify */ + self.write_phy_dbg(MIIDBG_AZ_ANADECT, AZ_ANADECT_DEF); + } else { + val = self.reg_read(LPI_CTRL); + self.reg_write(LPI_CTRL, val & (!LPI_CTRL_EN)); + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_LOCAL_EEEADV, 0); + } + + /* phy power saving */ + self.write_phy_dbg(MIIDBG_TST10BTCFG, TST10BTCFG_DEF); + self.write_phy_dbg(MIIDBG_SRDSYSMOD, SRDSYSMOD_DEF); + self.write_phy_dbg(MIIDBG_TST100BTCFG, TST100BTCFG_DEF); + self.write_phy_dbg(MIIDBG_ANACTRL, ANACTRL_DEF); + self.read_phy_dbg(MIIDBG_GREENCFG2, &mut phy_val); + self.write_phy_dbg(MIIDBG_GREENCFG2, phy_val & (!GREENCFG2_GATE_DFSE_EN)); + /* rtl8139c, 120m issue */ + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_NLP78, MIIEXT_NLP78_120M_DEF); + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_S3DIG10, MIIEXT_S3DIG10_DEF); + + if (self.lnk_patch) { + /* Turn off half amplitude */ + self.read_phy_ext(MIIEXT_PCS, MIIEXT_CLDCTRL3, &mut phy_val); + self.write_phy_ext( + MIIEXT_PCS, + MIIEXT_CLDCTRL3, + phy_val | CLDCTRL3_BP_CABLE1TH_DET_GT, + ); + /* Turn off Green feature */ + self.read_phy_dbg(MIIDBG_GREENCFG2, &mut phy_val); + self.write_phy_dbg(MIIDBG_GREENCFG2, phy_val | GREENCFG2_BP_GREEN); + /* Turn off half Bias */ + self.read_phy_ext(MIIEXT_PCS, MIIEXT_CLDCTRL5, &mut phy_val); + self.write_phy_ext( + MIIEXT_PCS, + MIIEXT_CLDCTRL5, + phy_val | CLDCTRL5_BP_VD_HLFBIAS, + ); + } + } + + /* set phy interrupt mask */ + self.write_phy_reg(MII_IER, IER_LINK_UP | IER_LINK_DOWN); + } + + unsafe fn stop_mac(&mut self) -> usize { + let txq: u32; + let rxq: u32; + let mut val: u32; + let mut i: u32; + + rxq = self.reg_read(RXQ0); + self.reg_write(RXQ0, rxq & (!RXQ0_EN)); + txq = self.reg_read(TXQ0); + self.reg_write(TXQ0, txq & (!TXQ0_EN)); + + udelay(40); + + self.rx_ctrl &= !(MAC_CTRL_RX_EN | MAC_CTRL_TX_EN); + self.reg_write(MAC_CTRL, self.rx_ctrl); + + i = 0; + while i < DMA_MAC_RST_TO { + val = self.reg_read(MAC_STS); + if (val & MAC_STS_IDLE == 0) { + break; + } + udelay(10); + i += 1; + } + + return if (DMA_MAC_RST_TO == i) { + ERR_RSTMAC as usize + } else { + 0 + }; + } + + unsafe fn start_mac(&mut self) { + let mut mac: u32; + let txq: u32; + let rxq: u32; + + rxq = self.reg_read(RXQ0); + self.reg_write(RXQ0, rxq | RXQ0_EN); + txq = self.reg_read(TXQ0); + self.reg_write(TXQ0, txq | TXQ0_EN); + + mac = self.rx_ctrl; + if (self.link_duplex == FULL_DUPLEX) { + mac |= MAC_CTRL_FULLD; + } else { + mac &= !MAC_CTRL_FULLD; + } + FIELD_SET32!( + mac, + MAC_CTRL_SPEED, + if self.link_speed == 1000 { + MAC_CTRL_SPEED_1000 + } else { + MAC_CTRL_SPEED_10_100 + } + ); + mac |= MAC_CTRL_TX_EN | MAC_CTRL_RX_EN; + self.rx_ctrl = mac; + self.reg_write(MAC_CTRL, mac); + } + + unsafe fn reset_osc(&mut self, rev: u8) { + let mut val: u32; + let mut val2: u32; + + /* clear Internal OSC settings, switching OSC by hw itself */ + val = self.reg_read(MISC3); + self.reg_write(MISC3, (val & !MISC3_25M_BY_SW) | MISC3_25M_NOTO_INTNL); + + /* 25M clk from chipset may be unstable 1s after de-assert of + * PERST, driver need re-calibrate before enter Sleep for WoL + */ + val = self.reg_read(MISC); + if (rev >= REV_B0) { + /* restore over current protection def-val, + * this val could be reset by MAC-RST + */ + FIELD_SET32!(val, MISC_PSW_OCP, MISC_PSW_OCP_DEF); + /* a 0->1 change will update the internal val of osc */ + val &= !MISC_INTNLOSC_OPEN; + self.reg_write(MISC, val); + self.reg_write(MISC, val | MISC_INTNLOSC_OPEN); + /* hw will automatically dis OSC after cab. */ + val2 = self.reg_read(MSIC2); + val2 &= !MSIC2_CALB_START; + self.reg_write(MSIC2, val2); + self.reg_write(MSIC2, val2 | MSIC2_CALB_START); + } else { + val &= !MISC_INTNLOSC_OPEN; + /* disable isoloate for A0 */ + if (rev == REV_A0 || rev == REV_A1) { + val &= !MISC_ISO_EN; + } + + self.reg_write(MISC, val | MISC_INTNLOSC_OPEN); + self.reg_write(MISC, val); + } + + udelay(20); + } + + unsafe fn reset_mac(&mut self) -> usize { + let mut val: u32; + let mut pmctrl: u32; + let mut i: u32; + let ret: usize; + let rev: u8; + let a_cr: bool; + + pmctrl = 0; + rev = self.revid(); + a_cr = (rev == REV_A0 || rev == REV_A1) && self.with_cr(); + + /* disable all interrupts, RXQ/TXQ */ + self.reg_write(MSIX_MASK, 0xFFFFFFFF); + self.reg_write(IMR, 0); + self.reg_write(ISR, ISR_DIS); + + ret = self.stop_mac(); + if (ret > 0) { + return ret; + } + + /* mac reset workaroud */ + self.reg_write(RFD_PIDX, 1); + + /* dis l0s/l1 before mac reset */ + if (a_cr) { + pmctrl = self.reg_read(PMCTRL); + if ((pmctrl & (PMCTRL_L1_EN | PMCTRL_L0S_EN)) != 0) { + self.reg_write(PMCTRL, pmctrl & !(PMCTRL_L1_EN | PMCTRL_L0S_EN)); + } + } + + /* reset whole mac safely */ + val = self.reg_read(MASTER); + self.reg_write(MASTER, val | MASTER_DMA_MAC_RST | MASTER_OOB_DIS); + + /* make sure it's real idle */ + udelay(10); + i = 0; + while (i < DMA_MAC_RST_TO) { + val = self.reg_read(RFD_PIDX); + if (val == 0) { + break; + } + udelay(10); + i += 1; + } + while (i < DMA_MAC_RST_TO) { + val = self.reg_read(MASTER); + if ((val & MASTER_DMA_MAC_RST) == 0) { + break; + } + udelay(10); + i += 1; + } + if (i == DMA_MAC_RST_TO) { + return ERR_RSTMAC; + } + udelay(10); + + if (a_cr) { + /* set MASTER_PCLKSEL_SRDS (affect by soft-rst, PERST) */ + self.reg_write(MASTER, val | MASTER_PCLKSEL_SRDS); + /* resoter l0s / l1 */ + if (pmctrl & (PMCTRL_L1_EN | PMCTRL_L0S_EN) > 0) { + self.reg_write(PMCTRL, pmctrl); + } + } + + self.reset_osc(rev); + /* clear Internal OSC settings, switching OSC by hw itself, + * disable isoloate for A version + */ + val = self.reg_read(MISC3); + self.reg_write(MISC3, (val & !MISC3_25M_BY_SW) | MISC3_25M_NOTO_INTNL); + val = self.reg_read(MISC); + val &= !MISC_INTNLOSC_OPEN; + if (rev == REV_A0 || rev == REV_A1) { + val &= !MISC_ISO_EN; + } + self.reg_write(MISC, val); + udelay(20); + + /* driver control speed/duplex, hash-alg */ + self.reg_write(MAC_CTRL, self.rx_ctrl); + + /* clk sw */ + val = self.reg_read(SERDES); + self.reg_write(SERDES, val | SERDES_MACCLK_SLWDWN | SERDES_PHYCLK_SLWDWN); + + /* mac reset cause MDIO ctrl restore non-polling status */ + if (self.is_fpga) { + self.start_phy_polling(MDIO_CLK_SEL_25MD128); + } + + return ret; + } + + unsafe fn ethadv_to_hw_cfg(&self, ethadv_cfg: u32) -> u32 { + let mut cfg: u32 = 0; + + if (ethadv_cfg & ADVERTISED_Autoneg > 0) { + cfg |= DRV_PHY_AUTO; + if (ethadv_cfg & ADVERTISED_10baseT_Half > 0) { + cfg |= DRV_PHY_10; + } + if (ethadv_cfg & ADVERTISED_10baseT_Full > 0) { + cfg |= DRV_PHY_10 | DRV_PHY_DUPLEX; + } + if (ethadv_cfg & ADVERTISED_100baseT_Half > 0) { + cfg |= DRV_PHY_100; + } + if (ethadv_cfg & ADVERTISED_100baseT_Full > 0) { + cfg |= DRV_PHY_100 | DRV_PHY_DUPLEX; + } + if (ethadv_cfg & ADVERTISED_1000baseT_Half > 0) { + cfg |= DRV_PHY_1000; + } + if (ethadv_cfg & ADVERTISED_1000baseT_Full > 0) { + cfg |= DRV_PHY_100 | DRV_PHY_DUPLEX; + } + if (ethadv_cfg & ADVERTISED_Pause > 0) { + cfg |= ADVERTISE_PAUSE_CAP; + } + if (ethadv_cfg & ADVERTISED_Asym_Pause > 0) { + cfg |= ADVERTISE_PAUSE_ASYM; + } + if (self.cap & CAP_AZ > 0) { + cfg |= DRV_PHY_EEE; + } + } else { + match (ethadv_cfg) { + ADVERTISED_10baseT_Half => { + cfg |= DRV_PHY_10; + } + ADVERTISED_100baseT_Half => { + cfg |= DRV_PHY_100; + } + ADVERTISED_10baseT_Full => { + cfg |= DRV_PHY_10 | DRV_PHY_DUPLEX; + } + ADVERTISED_100baseT_Full => { + cfg |= DRV_PHY_100 | DRV_PHY_DUPLEX; + } + _ => (), + } + } + + return cfg; + } + + unsafe fn setup_speed_duplex(&mut self, ethadv: u32, flowctrl: u8) -> usize { + let mut adv: u32; + let mut giga: u16; + let mut cr: u16; + let mut val: u32; + let mut err: usize = 0; + + /* clear flag */ + self.write_phy_reg(MII_DBG_ADDR, 0); + val = self.reg_read(DRV); + FIELD_SET32!(val, DRV_PHY, 0); + + if (ethadv & ADVERTISED_Autoneg > 0) { + adv = ADVERTISE_CSMA; + adv |= ethtool_adv_to_mii_adv_t(ethadv); + + if (flowctrl & FC_ANEG == FC_ANEG) { + if (flowctrl & FC_RX > 0) { + adv |= ADVERTISED_Pause; + if (flowctrl & FC_TX == 0) { + adv |= ADVERTISED_Asym_Pause; + } + } else if (flowctrl & FC_TX > 0) { + adv |= ADVERTISED_Asym_Pause; + } + } + giga = 0; + if (self.cap & CAP_GIGA > 0) { + giga = ethtool_adv_to_mii_ctrl1000_t(ethadv) as u16; + } + + cr = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART; + + if (self.write_phy_reg(MII_ADVERTISE, adv as u16) > 0 + || self.write_phy_reg(MII_CTRL1000, giga) > 0 + || self.write_phy_reg(MII_BMCR, cr) > 0) + { + err = ERR_MIIBUSY; + } + } else { + cr = BMCR_RESET; + if (ethadv == ADVERTISED_100baseT_Half || ethadv == ADVERTISED_100baseT_Full) { + cr |= BMCR_SPEED100; + } + if (ethadv == ADVERTISED_10baseT_Full || ethadv == ADVERTISED_100baseT_Full) { + cr |= BMCR_FULLDPLX; + } + + err = self.write_phy_reg(MII_BMCR, cr); + } + + if (err == 0) { + self.write_phy_reg(MII_DBG_ADDR, PHY_INITED); + /* save config to HW */ + val |= self.ethadv_to_hw_cfg(ethadv); + } + + self.reg_write(DRV, val); + + return err; + } + + unsafe fn get_perm_macaddr(&mut self) -> [u8; 6] { + let mac_low = self.reg_read(STAD0); + let mac_high = self.reg_read(STAD1); + [ + mac_low as u8, + (mac_low >> 8) as u8, + (mac_low >> 16) as u8, + (mac_low >> 24) as u8, + mac_high as u8, + (mac_high >> 8) as u8, + ] + } + + unsafe fn get_phy_link(&mut self, link_up: &mut bool, speed: &mut u16) -> usize { + let mut bmsr: u16 = 0; + let mut giga: u16 = 0; + let mut err: usize; + + self.read_phy_reg(MII_BMSR, &mut bmsr); + err = self.read_phy_reg(MII_BMSR, &mut bmsr); + if (err > 0) { + return err; + } + + if (bmsr & BMSR_LSTATUS == 0) { + *link_up = false; + return err; + } + + *link_up = true; + + /* speed/duplex result is saved in PHY Specific Status Register */ + err = self.read_phy_reg(MII_GIGA_PSSR, &mut giga); + if (err > 0) { + return err; + } + + if (giga & GIGA_PSSR_SPD_DPLX_RESOLVED == 0) { + println!("PHY SPD/DPLX unresolved: {:X}", giga); + err = (-EINVAL) as usize; + } else { + match (giga & GIGA_PSSR_SPEED) { + GIGA_PSSR_1000MBS => *speed = SPEED_1000, + GIGA_PSSR_100MBS => *speed = SPEED_100, + GIGA_PSSR_10MBS => *speed = SPEED_10, + _ => { + println!("PHY SPD/DPLX unresolved: {:X}", giga); + err = (-EINVAL) as usize; + } + } + *speed += if (giga & GIGA_PSSR_DPLX > 0) { + FULL_DUPLEX as u16 + } else { + HALF_DUPLEX as u16 + }; + } + + return err; + } + + fn show_speed(&self, speed: u16) { + let desc = if speed == SPEED_1000 + FULL_DUPLEX as u16 { + "1 Gbps Full" + } else if speed == SPEED_100 + FULL_DUPLEX as u16 { + "100 Mbps Full" + } else if speed == SPEED_100 + HALF_DUPLEX as u16 { + "100 Mbps Half" + } else if speed == SPEED_10 + FULL_DUPLEX as u16 { + "10 Mbps Full" + } else if speed == SPEED_10 + HALF_DUPLEX as u16 { + "10 Mbps Half" + } else { + "Unknown speed" + }; + + println!("NIC Link Up: {}", desc); + } + + unsafe fn configure_basic(&mut self) { + let mut val: u32; + let raw_mtu: u32; + let max_payload: u32; + let val16: u16; + let chip_rev = self.revid(); + + /* mac address */ + //TODO alx_set_macaddr(hw, self.mac_addr); + + /* clk gating */ + self.reg_write(CLK_GATE, CLK_GATE_ALL_A0); + + /* idle timeout to switch clk_125M */ + if (chip_rev >= REV_B0) { + self.reg_write(IDLE_DECISN_TIMER, IDLE_DECISN_TIMER_DEF); + } + + /* stats refresh timeout */ + self.reg_write(SMB_TIMER, self.smb_timer * 500); + + /* intr moduration */ + val = self.reg_read(MASTER); + val = val | MASTER_IRQMOD2_EN | MASTER_IRQMOD1_EN | MASTER_SYSALVTIMER_EN; + self.reg_write(MASTER, val); + self.reg_write(IRQ_MODU_TIMER, FIELDX!(IRQ_MODU_TIMER1, self.imt >> 1)); + /* intr re-trig timeout */ + self.reg_write(INT_RETRIG, INT_RETRIG_TO); + /* tpd threshold to trig int */ + self.reg_write(TINT_TPD_THRSHLD, self.ith_tpd); + self.reg_write(TINT_TIMER, self.imt as u32); + + /* mtu, 8:fcs+vlan */ + raw_mtu = (self.mtu + ETH_HLEN) as u32; + self.reg_write(MTU, raw_mtu + 8); + if (raw_mtu > MTU_JUMBO_TH) { + self.rx_ctrl &= !MAC_CTRL_FAST_PAUSE; + } + + /* txq */ + if ((raw_mtu + 8) < TXQ1_JUMBO_TSO_TH) { + val = (raw_mtu + 8 + 7) >> 3; + } else { + val = TXQ1_JUMBO_TSO_TH >> 3; + } + self.reg_write(TXQ1, val | TXQ1_ERRLGPKT_DROP_EN); + + /* TODO + max_payload = alx_get_readrq(hw) >> 8; + /* + * if BIOS had changed the default dma read max length, + * restore it to default value + */ + if (max_payload < DEV_CTRL_MAXRRS_MIN) + alx_set_readrq(hw, 128 << DEV_CTRL_MAXRRS_MIN); + */ + max_payload = 128 << DEV_CTRL_MAXRRS_MIN; + + val = FIELDX!(TXQ0_TPD_BURSTPREF, TXQ_TPD_BURSTPREF_DEF) + | TXQ0_MODE_ENHANCE + | TXQ0_LSO_8023_EN + | TXQ0_SUPT_IPOPT + | FIELDX!(TXQ0_TXF_BURST_PREF, TXQ_TXF_BURST_PREF_DEF); + self.reg_write(TXQ0, val); + val = FIELDX!(HQTPD_Q1_NUMPREF, TXQ_TPD_BURSTPREF_DEF) + | FIELDX!(HQTPD_Q2_NUMPREF, TXQ_TPD_BURSTPREF_DEF) + | FIELDX!(HQTPD_Q3_NUMPREF, TXQ_TPD_BURSTPREF_DEF) + | HQTPD_BURST_EN; + self.reg_write(HQTPD, val); + + /* rxq, flow control */ + val = self.reg_read(SRAM5); + val = FIELD_GETX!(val, SRAM_RXF_LEN) << 3; + if (val > SRAM_RXF_LEN_8K) { + val16 = (MTU_STD_ALGN >> 3) as u16; + val = (val - RXQ2_RXF_FLOW_CTRL_RSVD) >> 3; + } else { + val16 = (MTU_STD_ALGN >> 3) as u16; + val = (val - MTU_STD_ALGN) >> 3; + } + self.reg_write( + RXQ2, + FIELDX!(RXQ2_RXF_XOFF_THRESH, val16) | FIELDX!(RXQ2_RXF_XON_THRESH, val), + ); + val = FIELDX!(RXQ0_NUM_RFD_PREF, RXQ0_NUM_RFD_PREF_DEF) + | FIELDX!(RXQ0_RSS_MODE, RXQ0_RSS_MODE_DIS) + | FIELDX!(RXQ0_IDT_TBL_SIZE, RXQ0_IDT_TBL_SIZE_DEF) + | RXQ0_RSS_HSTYP_ALL + | RXQ0_RSS_HASH_EN + | RXQ0_IPV6_PARSE_EN; + if (self.cap & CAP_GIGA > 0) { + FIELD_SET32!(val, RXQ0_ASPM_THRESH, RXQ0_ASPM_THRESH_100M); + } + self.reg_write(RXQ0, val); + + /* DMA */ + self.reg_read(DMA); + + val = FIELDX!(DMA_RORDER_MODE, DMA_RORDER_MODE_OUT) + | DMA_RREQ_PRI_DATA + | FIELDX!(DMA_RREQ_BLEN, max_payload) + | FIELDX!(DMA_WDLY_CNT, DMA_WDLY_CNT_DEF) + | FIELDX!(DMA_RDLY_CNT, DMA_RDLY_CNT_DEF) + | FIELDX!(DMA_RCHNL_SEL, self.dma_chnl - 1); + self.reg_write(DMA, val); + + /* multi-tx-q weight */ + if (self.cap & CAP_MTQ > 0) { + val = FIELDX!(WRR_PRI, self.wrr_ctrl) + | FIELDX!(WRR_PRI0, self.wrr[0]) + | FIELDX!(WRR_PRI1, self.wrr[1]) + | FIELDX!(WRR_PRI2, self.wrr[2]) + | FIELDX!(WRR_PRI3, self.wrr[3]); + self.reg_write(WRR, val); + } + } + + unsafe fn set_rx_mode(&mut self) { + /* TODO + struct alx_adapter *adpt = netdev_priv(netdev); + struct alx_hw *hw = &adpt->hw; + struct netdev_hw_addr *ha; + + + /* comoute mc addresses' hash value ,and put it into hash table */ + netdev_for_each_mc_addr(ha, netdev) + alx_add_mc_addr(hw, ha->addr); + */ + + self.reg_write(HASH_TBL0, self.mc_hash[0]); + self.reg_write(HASH_TBL1, self.mc_hash[1]); + + /* check for Promiscuous and All Multicast modes */ + self.rx_ctrl &= !(MAC_CTRL_MULTIALL_EN | MAC_CTRL_PROMISC_EN); + /* TODO + if (netdev->flags & IFF_PROMISC) { + self.rx_ctrl |= MAC_CTRL_PROMISC_EN; + } + if (netdev->flags & IFF_ALLMULTI) { + self.rx_ctrl |= MAC_CTRL_MULTIALL_EN; + } + */ + + self.reg_write(MAC_CTRL, self.rx_ctrl); + } + + unsafe fn set_vlan_mode(&mut self, vlan_rx: bool) { + if (vlan_rx) { + self.rx_ctrl |= MAC_CTRL_VLANSTRIP; + } else { + self.rx_ctrl &= !MAC_CTRL_VLANSTRIP; + } + + self.reg_write(MAC_CTRL, self.rx_ctrl); + } + + unsafe fn configure_rss(&mut self, en: bool) { + let mut ctrl: u32; + + ctrl = self.reg_read(RXQ0); + + if (en) { + unimplemented!(); + /* + for (i = 0; i < sizeof(self.rss_key); i++) { + /* rss key should be saved in chip with + * reversed order. + */ + int j = sizeof(self.rss_key) - i - 1; + + MEM_W8(hw, RSS_KEY0 + j, self.rss_key[i]); + } + + for (i = 0; i < ARRAY_SIZE(self.rss_idt); i++) + self.reg_write(RSS_IDT_TBL0 + i * 4, + self.rss_idt[i]); + + FIELD_SET32(ctrl, RXQ0_RSS_HSTYP, self.rss_hash_type); + FIELD_SET32(ctrl, RXQ0_RSS_MODE, RXQ0_RSS_MODE_MQMI); + FIELD_SET32(ctrl, RXQ0_IDT_TBL_SIZE, self.rss_idt_size); + ctrl |= RXQ0_RSS_HASH_EN; + */ + } else { + ctrl &= !RXQ0_RSS_HASH_EN; + } + + self.reg_write(RXQ0, ctrl); + } + + unsafe fn configure(&mut self) { + self.configure_basic(); + self.configure_rss(false); + self.set_rx_mode(); + self.set_vlan_mode(false); + } + + unsafe fn irq_enable(&mut self) { + self.reg_write(ISR, 0); + let imask = self.imask; + self.reg_write(IMR, imask); + } + + unsafe fn irq_disable(&mut self) { + self.reg_write(ISR, ISR_DIS); + self.reg_write(IMR, 0); + } + + unsafe fn clear_phy_intr(&mut self) -> usize { + let mut isr: u16 = 0; + self.read_phy_reg(MII_ISR, &mut isr) + } + + unsafe fn post_phy_link(&mut self, speed: u16, az_en: bool) { + let mut phy_val: u16 = 0; + let len: u16; + let agc: u16; + let revid: u8 = self.revid(); + let adj_th: bool; + + if (revid != REV_B0 && revid != REV_A1 && revid != REV_A0) { + return; + } + adj_th = if (revid == REV_B0) { true } else { false }; + + /* 1000BT/AZ, wrong cable length */ + if (speed != SPEED_0) { + self.read_phy_ext(MIIEXT_PCS, MIIEXT_CLDCTRL6, &mut phy_val); + len = FIELD_GETX!(phy_val, CLDCTRL6_CAB_LEN); + self.read_phy_dbg(MIIDBG_AGC, &mut phy_val); + agc = FIELD_GETX!(phy_val, AGC_2_VGA); + + if ((speed == SPEED_1000 + && (len > CLDCTRL6_CAB_LEN_SHORT1G || (0 == len && agc > AGC_LONG1G_LIMT))) + || (speed == SPEED_100 + && (len > CLDCTRL6_CAB_LEN_SHORT100M || (0 == len && agc > AGC_LONG100M_LIMT)))) + { + self.write_phy_dbg(MIIDBG_AZ_ANADECT, AZ_ANADECT_LONG); + self.read_phy_ext(MIIEXT_ANEG, MIIEXT_AFE, &mut phy_val); + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_AFE, phy_val | AFE_10BT_100M_TH); + } else { + self.write_phy_dbg(MIIDBG_AZ_ANADECT, AZ_ANADECT_DEF); + self.read_phy_ext(MIIEXT_ANEG, MIIEXT_AFE, &mut phy_val); + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_AFE, phy_val & !AFE_10BT_100M_TH); + } + + /* threashold adjust */ + if (adj_th && self.lnk_patch) { + if (speed == SPEED_100) { + self.write_phy_dbg(MIIDBG_MSE16DB, MSE16DB_UP); + } else if (speed == SPEED_1000) { + /* + * Giga link threshold, raise the tolerance of + * noise 50% + */ + self.read_phy_dbg(MIIDBG_MSE20DB, &mut phy_val); + FIELD_SETS!(phy_val, MSE20DB_TH, MSE20DB_TH_HI); + self.write_phy_dbg(MIIDBG_MSE20DB, phy_val); + } + } + /* phy link-down in 1000BT/AZ mode */ + if (az_en && revid == REV_B0 && speed == SPEED_1000) { + self.write_phy_dbg(MIIDBG_SRDSYSMOD, SRDSYSMOD_DEF & !SRDSYSMOD_DEEMP_EN); + } + } else { + self.read_phy_ext(MIIEXT_ANEG, MIIEXT_AFE, &mut phy_val); + self.write_phy_ext(MIIEXT_ANEG, MIIEXT_AFE, phy_val & !AFE_10BT_100M_TH); + + if (adj_th && self.lnk_patch) { + self.write_phy_dbg(MIIDBG_MSE16DB, MSE16DB_DOWN); + self.read_phy_dbg(MIIDBG_MSE20DB, &mut phy_val); + FIELD_SETS!(phy_val, MSE20DB_TH, MSE20DB_TH_DEF); + self.write_phy_dbg(MIIDBG_MSE20DB, phy_val); + } + if (az_en && revid == REV_B0) { + self.write_phy_dbg(MIIDBG_SRDSYSMOD, SRDSYSMOD_DEF); + } + } + } + + unsafe fn task(&mut self) { + if self.flag & FLAG_HALT > 0 { + return; + } + + //TODO: RESET + if self.flag & FLAG_TASK_RESET > 0 { + self.flag &= !FLAG_TASK_RESET; + println!("reinit"); + self.reinit(); + } + + if self.flag & FLAG_TASK_CHK_LINK > 0 { + self.flag &= !FLAG_TASK_CHK_LINK; + self.check_link(); + } + } + + unsafe fn halt(&mut self) { + self.flag |= FLAG_HALT; + + //alx_netif_stop(adpt); + self.link_up = false; + self.link_speed = SPEED_0; + + self.reset_mac(); + + /* disable l0s/l1 */ + self.enable_aspm(false, false); + self.irq_disable(); + //self.free_all_rings_buf(); + } + + unsafe fn activate(&mut self) { + /* hardware setting lost, restore it */ + self.init_ring_ptrs(); + self.configure(); + + self.flag &= !FLAG_HALT; + /* clear old interrupts */ + self.reg_write(ISR, !ISR_DIS); + + self.irq_enable(); + + self.flag |= FLAG_TASK_CHK_LINK; + self.task(); + } + + unsafe fn reinit(&mut self) { + if self.flag & FLAG_HALT > 0 { + return; + } + + self.halt(); + self.activate(); + } + + unsafe fn init_ring_ptrs(&mut self) { + // Write high addresses + self.reg_write(RX_BASE_ADDR_HI, 0); + self.reg_write(TX_BASE_ADDR_HI, 0); + + // RFD ring + for i in 0..self.rfd_ring.len() { + self.rfd_ring[i] + .addr_low + .write(self.rfd_buffer[i].physical() as u32); + self.rfd_ring[i] + .addr_high + .write(((self.rfd_buffer[i].physical() as u64) >> 32) as u32); + } + self.reg_write(RFD_ADDR_LO, self.rfd_ring.physical() as u32); + self.reg_write(RFD_RING_SZ, self.rfd_ring.len() as u32); + self.reg_write(RFD_BUF_SZ, 16384); + + // RRD ring + self.reg_write(RRD_ADDR_LO, self.rrd_ring.physical() as u32); + self.reg_write(RRD_RING_SZ, self.rrd_ring.len() as u32); + + // TPD ring + self.reg_write(TPD_PRI0_ADDR_LO, self.tpd_ring[0].physical() as u32); + self.reg_write(TPD_PRI1_ADDR_LO, self.tpd_ring[1].physical() as u32); + self.reg_write(TPD_PRI2_ADDR_LO, self.tpd_ring[2].physical() as u32); + self.reg_write(TPD_PRI3_ADDR_LO, self.tpd_ring[3].physical() as u32); + self.reg_write(TPD_RING_SZ, self.tpd_ring[0].len() as u32); + + // Write pointers into chip SRAM + self.reg_write(SRAM9, SRAM_LOAD_PTR); + } + + unsafe fn check_link(&mut self) { + let mut speed: u16 = SPEED_0; + let old_speed: u16; + let mut link_up: bool = false; + let old_link_up: bool; + let mut err: usize; + + if (self.flag & FLAG_HALT > 0) { + return; + } + + macro_rules! goto_out { + () => { + if (err > 0) { + self.flag |= FLAG_TASK_RESET; + self.task(); + } + return; + }; + } + + /* clear PHY internal interrupt status, + * otherwise the Main interrupt status will be asserted + * for ever. + */ + self.clear_phy_intr(); + + err = self.get_phy_link(&mut link_up, &mut speed); + if (err > 0) { + goto_out!(); + } + + /* open interrutp mask */ + self.imask |= ISR_PHY; + let imask = self.imask; + self.reg_write(IMR, imask); + + if (!link_up && !self.link_up) { + goto_out!(); + } + + old_speed = self.link_speed + self.link_duplex as u16; + old_link_up = self.link_up; + + if (link_up) { + /* same speed ? */ + if (old_link_up && old_speed == speed) { + goto_out!(); + } + + self.show_speed(speed); + self.link_duplex = (speed % 10) as u8; + self.link_speed = speed - self.link_duplex as u16; + self.link_up = true; + let link_speed = self.link_speed; + let az_en = self.cap & CAP_AZ > 0; + self.post_phy_link(link_speed, az_en); + let l0s_en = self.cap & CAP_L0S > 0; + let l1_en = self.cap & CAP_L1 > 0; + self.enable_aspm(l0s_en, l1_en); + self.start_mac(); + + /* link kept, just speed changed */ + if (old_link_up) { + goto_out!(); + } + /* link changed from 'down' to 'up' */ + // TODO self.netif_start(); + goto_out!(); + } + + /* link changed from 'up' to 'down' */ + // TODO self.netif_stop(); + self.link_up = false; + self.link_speed = SPEED_0; + println!("NIC Link Down"); + err = self.reset_mac(); + if (err > 0) { + println!("linkdown:reset_mac fail {}", err); + err = (-EIO) as usize; + goto_out!(); + } + self.irq_disable(); + + /* reset-mac cause all settings on HW lost, + * following steps restore all of them and + * refresh whole RX/TX rings + */ + self.init_ring_ptrs(); + + self.configure(); + + let l1_en = self.cap & CAP_L1 > 0; + self.enable_aspm(false, l1_en); + + let cap_az = self.cap & CAP_AZ > 0; + self.post_phy_link(SPEED_0, cap_az); + + self.irq_enable(); + + goto_out!(); + } + + unsafe fn get_phy_info(&mut self) -> bool { + /* + let mut devs1: u16 = 0; + let mut devs2: u16 = 0; + + if (self.read_phy_reg(MII_PHYSID1, &mut self.phy_id[0]) > 0 || + self.read_phy_reg(MII_PHYSID2, &mut self.phy_id[1]) > 0) { + return false; + } + + /* since we haven't PMA/PMD status2 register, we can't + * use mdio45_probe function for prtad and mmds. + * use fixed MMD3 to get mmds. + */ + if (self.read_phy_ext(3, MDIO_DEVS1, &devs1) || + self.read_phy_ext(3, MDIO_DEVS2, &devs2)) { + return false; + } + self.mdio.mmds = devs1 | devs2 << 16; + + return true; + */ + return true; + } + + unsafe fn probe(&mut self) -> Result<()> { + println!(" - Reset PCIE"); + self.reset_pcie(); + + println!(" - Reset PHY"); + self.reset_phy(); + + println!(" - Reset MAC"); + let err = self.reset_mac(); + if err > 0 { + println!(" - MAC reset failed: {}", err); + return Err(Error::new(EIO)); + } + + println!(" - Setup speed duplex"); + let ethadv = self.adv_cfg; + let flowctrl = self.flowctrl; + let err = self.setup_speed_duplex(ethadv, flowctrl); + if err > 0 { + println!(" - PHY speed/duplex failed: {}", err); + return Err(Error::new(EIO)); + } + + let mac = self.get_perm_macaddr(); + println!( + " - MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + if !self.get_phy_info() { + println!(" - Identify PHY failed"); + return Err(Error::new(EIO)); + } + + Ok(()) + } + + unsafe fn free_all_ring_resources(&mut self) { + println!("free_all_ring_resources"); + } + + unsafe fn disable_advanced_intr(&mut self) { + println!("disable_advanced_intr"); + } + + unsafe fn open(&mut self) -> usize { + /* allocate all memory resources */ + self.init_ring_ptrs(); + + /* make hardware ready before allocate interrupt */ + self.configure(); + + self.flag &= !FLAG_HALT; + + /* clear old interrupts */ + self.reg_write(ISR, !ISR_DIS); + + self.irq_enable(); + + self.flag |= FLAG_TASK_CHK_LINK; + self.task(); + return 0; + } + + unsafe fn init(&mut self) -> Result<()> { + { + let pci_id = self.reg_read(0); + self.vendor_id = pci_id as u16; + self.device_id = (pci_id >> 16) as u16; + } + + { + let pci_subid = self.reg_read(0x2C); + self.subven_id = pci_subid as u16; + self.subdev_id = (pci_subid >> 16) as u16; + } + + { + let pci_rev = self.reg_read(8); + self.revision = pci_rev as u8; + } + + { + self.dma_chnl = if self.revid() >= REV_B0 { 4 } else { 2 }; + } + + println!( + " - ID: {:>04X}:{:>04X} SUB: {:>04X}:{:>04X} REV: {:>02X}", + self.vendor_id, self.device_id, self.subven_id, self.subdev_id, self.revision + ); + + self.probe()?; + + let err = self.open(); + if err > 0 { + println!(" - Failed to open: {}", err); + return Err(Error::new(EIO)); + } + + Ok(()) + } +} + +impl SchemeSync for Alx { + fn open(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result { + if ctx.uid == 0 { + Ok(OpenResult::ThisScheme { + number: flags, + flags: NewFdFlags::empty(), + }) + } else { + Err(Error::new(EACCES)) + } + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _flags: u32, + _ctx: &CallerCtx, + ) -> Result { + /* + let head = unsafe { self.reg_read(RDH) }; + let mut tail = unsafe { self.reg_read(RDT) }; + + tail += 1; + if tail >= self.receive_ring.len() as u32 { + tail = 0; + } + + if tail != head { + let rd = unsafe { &mut * (self.receive_ring.as_ptr().offset(tail as isize) as *mut Rd) }; + if rd.status & RD_DD == RD_DD { + rd.status = 0; + + let data = &self.receive_buffer[tail as usize][.. rd.length as usize]; + + let mut i = 0; + while i < buf.len() && i < data.len() { + buf[i] = data[i]; + i += 1; + } + + unsafe { self.reg_write(RDT, tail) }; + + return Ok(i); + } + } + */ + + if id & O_NONBLOCK == O_NONBLOCK { + Ok(0) + } else { + Err(Error::new(EWOULDBLOCK)) + } + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _flags: u32, + _ctx: &CallerCtx, + ) -> Result { + /* + loop { + let head = unsafe { self.reg_read(TDH) }; + let mut tail = unsafe { self.reg_read(TDT) }; + let old_tail = tail; + + tail += 1; + if tail >= self.transmit_ring.len() as u32 { + tail = 0; + } + + if tail != head { + let td = unsafe { &mut * (self.transmit_ring.as_ptr().offset(old_tail as isize) as *mut Td) }; + + td.cso = 0; + td.command = TD_CMD_EOP | TD_CMD_IFCS | TD_CMD_RS; + td.status = 0; + td.css = 0; + td.special = 0; + + td.length = (cmp::min(buf.len(), 0x3FFF)) as u16; + + let mut data = unsafe { slice::from_raw_parts_mut(self.transmit_buffer[old_tail as usize].as_ptr() as *mut u8, td.length as usize) }; + + let mut i = 0; + while i < buf.len() && i < data.len() { + data[i] = buf[i]; + i += 1; + } + + unsafe { self.reg_write(TDT, tail) }; + + while td.status == 0 { + thread::yield_now(); + } + + return Ok(i); + } + } + */ + Ok(0) + } + + fn fevent(&mut self, _id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result { + Ok(EventFlags::empty()) + } + + fn fsync(&mut self, _id: usize, _ctx: &CallerCtx) -> Result<()> { + Ok(()) + } + + fn on_close(&mut self, _id: usize) {} +} diff --git a/drivers/net/alxd/src/device/regs.rs b/drivers/net/alxd/src/device/regs.rs new file mode 100644 index 0000000000..774c1aca48 --- /dev/null +++ b/drivers/net/alxd/src/device/regs.rs @@ -0,0 +1,2275 @@ +/* + * Copyright (c) 2017 Jeremy Soller + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF; + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF; + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* + * Copyright (c) 2012 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF; + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF; + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/**********************************************************************/ +/* following registers are mapped to both pci config and memory space */ +/**********************************************************************/ + +/* pci dev-ids */ +pub const DEV_ID_AR8161: u32 = 0x1091; +pub const DEV_ID_AR8162: u32 = 0x1090; +pub const DEV_ID_AR8171: u32 = 0x10A1; +pub const DEV_ID_AR8172: u32 = 0x10A0; + +/* rev definition, + * bit(0): with xD support + * bit(1): with Card Reader function + * bit(7:2): real revision + */ +pub const PCI_REVID_WTH_CR: u32 = 1 << 1; +pub const PCI_REVID_WTH_XD: u32 = 1 << 0; +pub const PCI_REVID_MASK: u32 = 0x1F; +pub const PCI_REVID_SHIFT: u32 = 3; +pub const REV_A0: u8 = 0; +pub const REV_A1: u8 = 1; +pub const REV_B0: u8 = 2; +pub const REV_C0: u8 = 3; + +pub const PM_CSR: u32 = 0x0044; +pub const PM_CSR_PME_STAT: u32 = 1 << 15; +pub const PM_CSR_DSCAL_MASK: u32 = 0x3; +pub const PM_CSR_DSCAL_SHIFT: u32 = 13; +pub const PM_CSR_DSEL_MASK: u32 = 0xF; +pub const PM_CSR_DSEL_SHIFT: u32 = 9; +pub const PM_CSR_PME_EN: u32 = 1 << 8; +pub const PM_CSR_PWST_MASK: u32 = 0x3; +pub const PM_CSR_PWST_SHIFT: u32 = 0; + +pub const DEV_CAP: u32 = 0x005C; +pub const DEV_CAP_SPLSL_MASK: u32 = 0x3; +pub const DEV_CAP_SPLSL_SHIFT: u32 = 26; +pub const DEV_CAP_SPLV_MASK: u32 = 0xFF; +pub const DEV_CAP_SPLV_SHIFT: u32 = 18; +pub const DEV_CAP_RBER: u32 = 1 << 15; +pub const DEV_CAP_PIPRS: u32 = 1 << 14; +pub const DEV_CAP_AIPRS: u32 = 1 << 13; +pub const DEV_CAP_ABPRS: u32 = 1 << 12; +pub const DEV_CAP_L1ACLAT_MASK: u32 = 0x7; +pub const DEV_CAP_L1ACLAT_SHIFT: u32 = 9; +pub const DEV_CAP_L0SACLAT_MASK: u32 = 0x7; +pub const DEV_CAP_L0SACLAT_SHIFT: u32 = 6; +pub const DEV_CAP_EXTAG: u32 = 1 << 5; +pub const DEV_CAP_PHANTOM: u32 = 1 << 4; +pub const DEV_CAP_MPL_MASK: u32 = 0x7; +pub const DEV_CAP_MPL_SHIFT: u32 = 0; +pub const DEV_CAP_MPL_128: u32 = 1; +pub const DEV_CAP_MPL_256: u32 = 2; +pub const DEV_CAP_MPL_512: u32 = 3; +pub const DEV_CAP_MPL_1024: u32 = 4; +pub const DEV_CAP_MPL_2048: u32 = 5; +pub const DEV_CAP_MPL_4096: u32 = 6; + +pub const DEV_CTRL: u32 = 0x0060; +pub const DEV_CTRL_MAXRRS_MASK: u32 = 0x7; +pub const DEV_CTRL_MAXRRS_SHIFT: u32 = 12; +pub const DEV_CTRL_MAXRRS_MIN: u32 = 2; +pub const DEV_CTRL_NOSNP_EN: u32 = 1 << 11; +pub const DEV_CTRL_AUXPWR_EN: u32 = 1 << 10; +pub const DEV_CTRL_PHANTOM_EN: u32 = 1 << 9; +pub const DEV_CTRL_EXTAG_EN: u32 = 1 << 8; +pub const DEV_CTRL_MPL_MASK: u32 = 0x7; +pub const DEV_CTRL_MPL_SHIFT: u32 = 5; +pub const DEV_CTRL_RELORD_EN: u32 = 1 << 4; +pub const DEV_CTRL_URR_EN: u32 = 1 << 3; +pub const DEV_CTRL_FERR_EN: u32 = 1 << 2; +pub const DEV_CTRL_NFERR_EN: u32 = 1 << 1; +pub const DEV_CTRL_CERR_EN: u32 = 1 << 0; + +pub const DEV_STAT: u32 = 0x0062; +pub const DEV_STAT_XS_PEND: u32 = 1 << 5; +pub const DEV_STAT_AUXPWR: u32 = 1 << 4; +pub const DEV_STAT_UR: u32 = 1 << 3; +pub const DEV_STAT_FERR: u32 = 1 << 2; +pub const DEV_STAT_NFERR: u32 = 1 << 1; +pub const DEV_STAT_CERR: u32 = 1 << 0; + +pub const LNK_CAP: u32 = 0x0064; +pub const LNK_CAP_PRTNUM_MASK: u32 = 0xFF; +pub const LNK_CAP_PRTNUM_SHIFT: u32 = 24; +pub const LNK_CAP_CLK_PM: u32 = 1 << 18; +pub const LNK_CAP_L1EXTLAT_MASK: u32 = 0x7; +pub const LNK_CAP_L1EXTLAT_SHIFT: u32 = 15; +pub const LNK_CAP_L0SEXTLAT_MASK: u32 = 0x7; +pub const LNK_CAP_L0SEXTLAT_SHIFT: u32 = 12; +pub const LNK_CAP_ASPM_SUP_MASK: u32 = 0x3; +pub const LNK_CAP_ASPM_SUP_SHIFT: u32 = 10; +pub const LNK_CAP_ASPM_SUP_L0S: u32 = 1; +pub const LNK_CAP_ASPM_SUP_L0SL1: u32 = 3; +pub const LNK_CAP_MAX_LWH_MASK: u32 = 0x3F; +pub const LNK_CAP_MAX_LWH_SHIFT: u32 = 4; +pub const LNK_CAP_MAX_LSPD_MASK: u32 = 0xF; +pub const LNK_CAP_MAX_LSPD_SHIFT: u32 = 0; + +pub const LNK_CTRL: u32 = 0x0068; +pub const LNK_CTRL_CLK_PM_EN: u32 = 1 << 8; +pub const LNK_CTRL_EXTSYNC: u32 = 1 << 7; +pub const LNK_CTRL_CMNCLK_CFG: u32 = 1 << 6; +pub const LNK_CTRL_RCB_128B: u32 = 1 << 3; +pub const LNK_CTRL_ASPM_MASK: u32 = 0x3; +pub const LNK_CTRL_ASPM_SHIFT: u32 = 0; +pub const LNK_CTRL_ASPM_DIS: u32 = 0; +pub const LNK_CTRL_ASPM_ENL0S: u32 = 1; +pub const LNK_CTRL_ASPM_ENL1: u32 = 2; +pub const LNK_CTRL_ASPM_ENL0SL1: u32 = 3; + +pub const LNK_STAT: u32 = 0x006A; +pub const LNK_STAT_SCLKCFG: u32 = 1 << 12; +pub const LNK_STAT_LNKTRAIN: u32 = 1 << 11; +pub const LNK_STAT_TRNERR: u32 = 1 << 10; +pub const LNK_STAT_LNKSPD_MASK: u32 = 0xF; +pub const LNK_STAT_LNKSPD_SHIFT: u32 = 0; +pub const LNK_STAT_NEGLW_MASK: u32 = 0x3F; +pub const LNK_STAT_NEGLW_SHIFT: u32 = 4; + +pub const MSIX_MASK: u32 = 0x0090; +pub const MSIX_PENDING: u32 = 0x0094; + +pub const UE_SVRT: u32 = 0x010C; +pub const UE_SVRT_UR: u32 = 1 << 20; +pub const UE_SVRT_ECRCERR: u32 = 1 << 19; +pub const UE_SVRT_MTLP: u32 = 1 << 18; +pub const UE_SVRT_RCVOVFL: u32 = 1 << 17; +pub const UE_SVRT_UNEXPCPL: u32 = 1 << 16; +pub const UE_SVRT_CPLABRT: u32 = 1 << 15; +pub const UE_SVRT_CPLTO: u32 = 1 << 14; +pub const UE_SVRT_FCPROTERR: u32 = 1 << 13; +pub const UE_SVRT_PTLP: u32 = 1 << 12; +pub const UE_SVRT_DLPROTERR: u32 = 1 << 4; +pub const UE_SVRT_TRNERR: u32 = 1 << 0; + +/* eeprom & flash load register */ +pub const EFLD: u32 = 0x0204; +pub const EFLD_F_ENDADDR_MASK: u32 = 0x3FF; +pub const EFLD_F_ENDADDR_SHIFT: u32 = 16; +pub const EFLD_F_EXIST: u32 = 1 << 10; +pub const EFLD_E_EXIST: u32 = 1 << 9; +pub const EFLD_EXIST: u32 = 1 << 8; +pub const EFLD_STAT: u32 = 1 << 5; +pub const EFLD_IDLE: u32 = 1 << 4; +pub const EFLD_START: u32 = 1 << 0; + +/* eFuse load register */ +pub const SLD: u32 = 0x0218; +pub const SLD_FREQ_MASK: u32 = 0x3; +pub const SLD_FREQ_SHIFT: u32 = 24; +pub const SLD_FREQ_100K: u32 = 0; +pub const SLD_FREQ_200K: u32 = 1; +pub const SLD_FREQ_300K: u32 = 2; +pub const SLD_FREQ_400K: u32 = 3; +pub const SLD_EXIST: u32 = 1 << 23; +pub const SLD_SLVADDR_MASK: u32 = 0x7F; +pub const SLD_SLVADDR_SHIFT: u32 = 16; +pub const SLD_IDLE: u32 = 1 << 13; +pub const SLD_STAT: u32 = 1 << 12; +pub const SLD_START: u32 = 1 << 11; +pub const SLD_STARTADDR_MASK: u32 = 0xFF; +pub const SLD_STARTADDR_SHIFT: u32 = 0; +pub const SLD_MAX_TO: u32 = 100; + +pub const PCIE_MSIC: u32 = 0x021C; +pub const PCIE_MSIC_MSIX_DIS: u32 = 1 << 22; +pub const PCIE_MSIC_MSI_DIS: u32 = 1 << 21; + +pub const PPHY_MISC1: u32 = 0x1000; +pub const PPHY_MISC1_RCVDET: u32 = 1 << 2; +pub const PPHY_MISC1_NFTS_MASK: u32 = 0xFF; +pub const PPHY_MISC1_NFTS_SHIFT: u32 = 16; +pub const PPHY_MISC1_NFTS_HIPERF: u32 = 0xA0; + +pub const PPHY_MISC2: u32 = 0x1004; +pub const PPHY_MISC2_L0S_TH_MASK: u32 = 0x3; +pub const PPHY_MISC2_L0S_TH_SHIFT: u32 = 18; +pub const PPHY_MISC2_CDR_BW_MASK: u32 = 0x3; +pub const PPHY_MISC2_CDR_BW_SHIFT: u32 = 16; + +pub const PDLL_TRNS1: u32 = 0x1104; +pub const PDLL_TRNS1_D3PLLOFF_EN: u32 = 1 << 11; +pub const PDLL_TRNS1_REGCLK_SEL_NORM: u32 = 1 << 10; +pub const PDLL_TRNS1_REPLY_TO_MASK: u32 = 0x3FF; +pub const PDLL_TRNS1_REPLY_TO_SHIFT: u32 = 0; + +pub const TLEXTN_STATS: u32 = 0x1208; +pub const TLEXTN_STATS_DEVNO_MASK: u32 = 0x1F; +pub const TLEXTN_STATS_DEVNO_SHIFT: u32 = 16; +pub const TLEXTN_STATS_BUSNO_MASK: u32 = 0xFF; +pub const TLEXTN_STATS_BUSNO_SHIFT: u32 = 8; + +pub const EFUSE_CTRL: u32 = 0x12C0; +pub const EFUSE_CTRL_FLAG: u32 = 1 << 31; +pub const EUFSE_CTRL_ACK: u32 = 1 << 30; +pub const EFUSE_CTRL_ADDR_MASK: u32 = 0x3FF; +pub const EFUSE_CTRL_ADDR_SHIFT: u32 = 16; + +pub const EFUSE_DATA: u32 = 0x12C4; + +pub const SPI_OP1: u32 = 0x12C8; +pub const SPI_OP1_RDID_MASK: u32 = 0xFF; +pub const SPI_OP1_RDID_SHIFT: u32 = 24; +pub const SPI_OP1_CE_MASK: u32 = 0xFF; +pub const SPI_OP1_CE_SHIFT: u32 = 16; +pub const SPI_OP1_SE_MASK: u32 = 0xFF; +pub const SPI_OP1_SE_SHIFT: u32 = 8; +pub const SPI_OP1_PRGRM_MASK: u32 = 0xFF; +pub const SPI_OP1_PRGRM_SHIFT: u32 = 0; + +pub const SPI_OP2: u32 = 0x12CC; +pub const SPI_OP2_READ_MASK: u32 = 0xFF; +pub const SPI_OP2_READ_SHIFT: u32 = 24; +pub const SPI_OP2_WRSR_MASK: u32 = 0xFF; +pub const SPI_OP2_WRSR_SHIFT: u32 = 16; +pub const SPI_OP2_RDSR_MASK: u32 = 0xFF; +pub const SPI_OP2_RDSR_SHIFT: u32 = 8; +pub const SPI_OP2_WREN_MASK: u32 = 0xFF; +pub const SPI_OP2_WREN_SHIFT: u32 = 0; + +pub const SPI_OP3: u32 = 0x12E4; +pub const SPI_OP3_WRDI_MASK: u32 = 0xFF; +pub const SPI_OP3_WRDI_SHIFT: u32 = 8; +pub const SPI_OP3_EWSR_MASK: u32 = 0xFF; +pub const SPI_OP3_EWSR_SHIFT: u32 = 0; + +pub const EF_CTRL: u32 = 0x12D0; +pub const EF_CTRL_FSTS_MASK: u32 = 0xFF; +pub const EF_CTRL_FSTS_SHIFT: u32 = 20; +pub const EF_CTRL_CLASS_MASK: u32 = 0x7; +pub const EF_CTRL_CLASS_SHIFT: u32 = 16; +pub const EF_CTRL_CLASS_F_UNKNOWN: u32 = 0; +pub const EF_CTRL_CLASS_F_STD: u32 = 1; +pub const EF_CTRL_CLASS_F_SST: u32 = 2; +pub const EF_CTRL_CLASS_E_UNKNOWN: u32 = 0; +pub const EF_CTRL_CLASS_E_1K: u32 = 1; +pub const EF_CTRL_CLASS_E_4K: u32 = 2; +pub const EF_CTRL_FRET: u32 = 1 << 15; +pub const EF_CTRL_TYP_MASK: u32 = 0x3; +pub const EF_CTRL_TYP_SHIFT: u32 = 12; +pub const EF_CTRL_TYP_NONE: u32 = 0; +pub const EF_CTRL_TYP_F: u32 = 1; +pub const EF_CTRL_TYP_E: u32 = 2; +pub const EF_CTRL_TYP_UNKNOWN: u32 = 3; +pub const EF_CTRL_ONE_CLK: u32 = 1 << 10; +pub const EF_CTRL_ECLK_MASK: u32 = 0x3; +pub const EF_CTRL_ECLK_SHIFT: u32 = 8; +pub const EF_CTRL_ECLK_125K: u32 = 0; +pub const EF_CTRL_ECLK_250K: u32 = 1; +pub const EF_CTRL_ECLK_500K: u32 = 2; +pub const EF_CTRL_ECLK_1M: u32 = 3; +pub const EF_CTRL_FBUSY: u32 = 1 << 7; +pub const EF_CTRL_ACTION: u32 = 1 << 6; +pub const EF_CTRL_AUTO_OP: u32 = 1 << 5; +pub const EF_CTRL_SST_MODE: u32 = 1 << 4; +pub const EF_CTRL_INST_MASK: u32 = 0xF; +pub const EF_CTRL_INST_SHIFT: u32 = 0; +pub const EF_CTRL_INST_NONE: u32 = 0; +pub const EF_CTRL_INST_READ: u32 = 1; +pub const EF_CTRL_INST_RDID: u32 = 2; +pub const EF_CTRL_INST_RDSR: u32 = 3; +pub const EF_CTRL_INST_WREN: u32 = 4; +pub const EF_CTRL_INST_PRGRM: u32 = 5; +pub const EF_CTRL_INST_SE: u32 = 6; +pub const EF_CTRL_INST_CE: u32 = 7; +pub const EF_CTRL_INST_WRSR: u32 = 10; +pub const EF_CTRL_INST_EWSR: u32 = 11; +pub const EF_CTRL_INST_WRDI: u32 = 12; +pub const EF_CTRL_INST_WRITE: u32 = 2; + +pub const EF_ADDR: u32 = 0x12D4; +pub const EF_DATA: u32 = 0x12D8; +pub const SPI_ID: u32 = 0x12DC; + +pub const SPI_CFG_START: u32 = 0x12E0; + +pub const PMCTRL: u32 = 0x12F8; +pub const PMCTRL_HOTRST_WTEN: u32 = 1 << 31; +/* bit30: L0s/L1 controlled by MAC based on throughput(setting in: u32 = 15A0) */ +pub const PMCTRL_ASPM_FCEN: u32 = 1 << 30; +pub const PMCTRL_SADLY_EN: u32 = 1 << 29; +pub const PMCTRL_L0S_BUFSRX_EN: u32 = 1 << 28; +pub const PMCTRL_LCKDET_TIMER_MASK: u32 = 0xF; +pub const PMCTRL_LCKDET_TIMER_SHIFT: u32 = 24; +pub const PMCTRL_LCKDET_TIMER_DEF: u32 = 0xC; +/* bit[23:20] if pm_request_l1 time > @, then enter L0s not L1 */ +pub const PMCTRL_L1REQ_TO_MASK: u32 = 0xF; +pub const PMCTRL_L1REQ_TO_SHIFT: u32 = 20; +pub const PMCTRL_L1REG_TO_DEF: u32 = 0xF; +pub const PMCTRL_TXL1_AFTER_L0S: u32 = 1 << 19; +pub const PMCTRL_L1_TIMER_MASK: u32 = 0x7; +pub const PMCTRL_L1_TIMER_SHIFT: u32 = 16; +pub const PMCTRL_L1_TIMER_DIS: u32 = 0; +pub const PMCTRL_L1_TIMER_2US: u32 = 1; +pub const PMCTRL_L1_TIMER_4US: u32 = 2; +pub const PMCTRL_L1_TIMER_8US: u32 = 3; +pub const PMCTRL_L1_TIMER_16US: u32 = 4; +pub const PMCTRL_L1_TIMER_24US: u32 = 5; +pub const PMCTRL_L1_TIMER_32US: u32 = 6; +pub const PMCTRL_L1_TIMER_63US: u32 = 7; +pub const PMCTRL_RCVR_WT_1US: u32 = 1 << 15; +pub const PMCTRL_PWM_VER_11: u32 = 1 << 14; +/* bit13: enable pcie clk switch in L1 state */ +pub const PMCTRL_L1_CLKSW_EN: u32 = 1 << 13; +pub const PMCTRL_L0S_EN: u32 = 1 << 12; +pub const PMCTRL_RXL1_AFTER_L0S: u32 = 1 << 11; +pub const PMCTRL_L0S_TIMER_MASK: u32 = 0x7; +pub const PMCTRL_L0S_TIMER_SHIFT: u32 = 8; +pub const PMCTRL_L1_BUFSRX_EN: u32 = 1 << 7; +/* bit6: power down serdes RX */ +pub const PMCTRL_L1_SRDSRX_PWD: u32 = 1 << 6; +pub const PMCTRL_L1_SRDSPLL_EN: u32 = 1 << 5; +pub const PMCTRL_L1_SRDS_EN: u32 = 1 << 4; +pub const PMCTRL_L1_EN: u32 = 1 << 3; +pub const PMCTRL_CLKREQ_EN: u32 = 1 << 2; +pub const PMCTRL_RBER_EN: u32 = 1 << 1; +pub const PMCTRL_SPRSDWER_EN: u32 = 1 << 0; + +pub const LTSSM_CTRL: u32 = 0x12FC; +pub const LTSSM_WRO_EN: u32 = 1 << 12; + +/*******************************************************/ +/* following registers are mapped only to memory space */ +/*******************************************************/ + +pub const MASTER: u32 = 0x1400; +pub const MASTER_OTP_FLG: u32 = 1 << 31; +pub const MASTER_DEV_NUM_MASK: u32 = 0x7F; +pub const MASTER_DEV_NUM_SHIFT: u32 = 24; +pub const MASTER_REV_NUM_MASK: u32 = 0xFF; +pub const MASTER_REV_NUM_SHIFT: u32 = 16; +pub const MASTER_DEASSRT: u32 = 1 << 15; +pub const MASTER_RDCLR_INT: u32 = 1 << 14; +pub const MASTER_DMA_RST: u32 = 1 << 13; +/* bit12:: u32 = 1:alwys select pclk from serdes, not sw to: u32 = 25M */ +pub const MASTER_PCLKSEL_SRDS: u32 = 1 << 12; +/* bit11: irq moduration for rx */ +pub const MASTER_IRQMOD2_EN: u32 = 1 << 11; +/* bit10: irq moduration for tx/rx */ +pub const MASTER_IRQMOD1_EN: u32 = 1 << 10; +pub const MASTER_MANU_INT: u32 = 1 << 9; +pub const MASTER_MANUTIMER_EN: u32 = 1 << 8; +pub const MASTER_SYSALVTIMER_EN: u32 = 1 << 7; +pub const MASTER_OOB_DIS: u32 = 1 << 6; +/* bit5: wakeup without pcie clk */ +pub const MASTER_WAKEN_25M: u32 = 1 << 5; +pub const MASTER_BERT_START: u32 = 1 << 4; +pub const MASTER_PCIE_TSTMOD_MASK: u32 = 0x3; +pub const MASTER_PCIE_TSTMOD_SHIFT: u32 = 2; +pub const MASTER_PCIE_RST: u32 = 1 << 1; +/* bit0: MAC & DMA reset */ +pub const MASTER_DMA_MAC_RST: u32 = 1 << 0; +pub const DMA_MAC_RST_TO: u32 = 50; + +pub const MANU_TIMER: u32 = 0x1404; + +pub const IRQ_MODU_TIMER: u32 = 0x1408; +/* hi-16bit is only for RX */ +pub const IRQ_MODU_TIMER2_MASK: u32 = 0xFFFF; +pub const IRQ_MODU_TIMER2_SHIFT: u32 = 16; +pub const IRQ_MODU_TIMER1_MASK: u32 = 0xFFFF; +pub const IRQ_MODU_TIMER1_SHIFT: u32 = 0; + +pub const PHY_CTRL: u32 = 0x140C; +pub const PHY_CTRL_ADDR_MASK: u32 = 0x1F; +pub const PHY_CTRL_ADDR_SHIFT: u32 = 19; +pub const PHY_CTRL_BP_VLTGSW: u32 = 1 << 18; +pub const PHY_CTRL_100AB_EN: u32 = 1 << 17; +pub const PHY_CTRL_10AB_EN: u32 = 1 << 16; +pub const PHY_CTRL_PLL_BYPASS: u32 = 1 << 15; +/* bit14: affect MAC & PHY, go to low power sts */ +pub const PHY_CTRL_POWER_DOWN: u32 = 1 << 14; +/* bit13:: u32 = 1:pll always ON,: u32 = 0:can switch in lpw */ +pub const PHY_CTRL_PLL_ON: u32 = 1 << 13; +pub const PHY_CTRL_RST_ANALOG: u32 = 1 << 12; +pub const PHY_CTRL_HIB_PULSE: u32 = 1 << 11; +pub const PHY_CTRL_HIB_EN: u32 = 1 << 10; +pub const PHY_CTRL_GIGA_DIS: u32 = 1 << 9; +/* bit8: poweron rst */ +pub const PHY_CTRL_IDDQ_DIS: u32 = 1 << 8; +/* bit7: while reboot, it affects bit8 */ +pub const PHY_CTRL_IDDQ: u32 = 1 << 7; +pub const PHY_CTRL_LPW_EXIT: u32 = 1 << 6; +pub const PHY_CTRL_GATE_25M: u32 = 1 << 5; +pub const PHY_CTRL_RVRS_ANEG: u32 = 1 << 4; +pub const PHY_CTRL_ANEG_NOW: u32 = 1 << 3; +pub const PHY_CTRL_LED_MODE: u32 = 1 << 2; +pub const PHY_CTRL_RTL_MODE: u32 = 1 << 1; +/* bit0: out of dsp RST state */ +pub const PHY_CTRL_DSPRST_OUT: u32 = 1 << 0; +pub const PHY_CTRL_DSPRST_TO: u32 = 80; +pub const PHY_CTRL_CLS: u32 = PHY_CTRL_LED_MODE | PHY_CTRL_100AB_EN | PHY_CTRL_PLL_ON; + +pub const MAC_STS: u32 = 0x1410; +pub const MAC_STS_SFORCE_MASK: u32 = 0xF; +pub const MAC_STS_SFORCE_SHIFT: u32 = 14; +pub const MAC_STS_CALIB_DONE: u32 = 1 << 13; +pub const MAC_STS_CALIB_RES_MASK: u32 = 0x1F; +pub const MAC_STS_CALIB_RES_SHIFT: u32 = 8; +pub const MAC_STS_CALIBERR_MASK: u32 = 0xF; +pub const MAC_STS_CALIBERR_SHIFT: u32 = 4; +pub const MAC_STS_TXQ_BUSY: u32 = 1 << 3; +pub const MAC_STS_RXQ_BUSY: u32 = 1 << 2; +pub const MAC_STS_TXMAC_BUSY: u32 = 1 << 1; +pub const MAC_STS_RXMAC_BUSY: u32 = 1 << 0; +pub const MAC_STS_IDLE: u32 = + MAC_STS_TXQ_BUSY | MAC_STS_RXQ_BUSY | MAC_STS_TXMAC_BUSY | MAC_STS_RXMAC_BUSY; + +pub const MDIO: u32 = 0x1414; +pub const MDIO_MODE_EXT: u32 = 1 << 30; +pub const MDIO_POST_READ: u32 = 1 << 29; +pub const MDIO_AUTO_POLLING: u32 = 1 << 28; +pub const MDIO_BUSY: u32 = 1 << 27; +pub const MDIO_CLK_SEL_MASK: u32 = 0x7; +pub const MDIO_CLK_SEL_SHIFT: u32 = 24; +pub const MDIO_CLK_SEL_25MD4: u16 = 0; +pub const MDIO_CLK_SEL_25MD6: u16 = 2; +pub const MDIO_CLK_SEL_25MD8: u16 = 3; +pub const MDIO_CLK_SEL_25MD10: u16 = 4; +pub const MDIO_CLK_SEL_25MD32: u16 = 5; +pub const MDIO_CLK_SEL_25MD64: u16 = 6; +pub const MDIO_CLK_SEL_25MD128: u16 = 7; +pub const MDIO_START: u32 = 1 << 23; +pub const MDIO_SPRES_PRMBL: u32 = 1 << 22; +/* bit21:: u32 = 1:read,0:write */ +pub const MDIO_OP_READ: u32 = 1 << 21; +pub const MDIO_REG_MASK: u32 = 0x1F; +pub const MDIO_REG_SHIFT: u32 = 16; +pub const MDIO_DATA_MASK: u32 = 0xFFFF; +pub const MDIO_DATA_SHIFT: u32 = 0; +pub const MDIO_MAX_AC_TO: u32 = 120; + +pub const MDIO_EXTN: u32 = 0x1448; +pub const MDIO_EXTN_PORTAD_MASK: u32 = 0x1F; +pub const MDIO_EXTN_PORTAD_SHIFT: u32 = 21; +pub const MDIO_EXTN_DEVAD_MASK: u32 = 0x1F; +pub const MDIO_EXTN_DEVAD_SHIFT: u32 = 16; +pub const MDIO_EXTN_REG_MASK: u32 = 0xFFFF; +pub const MDIO_EXTN_REG_SHIFT: u32 = 0; + +pub const PHY_STS: u32 = 0x1418; +pub const PHY_STS_LPW: u32 = 1 << 31; +pub const PHY_STS_LPI: u32 = 1 << 30; +pub const PHY_STS_PWON_STRIP_MASK: u32 = 0xFFF; +pub const PHY_STS_PWON_STRIP_SHIFT: u32 = 16; + +pub const PHY_STS_DUPLEX: u32 = 1 << 3; +pub const PHY_STS_LINKUP: u32 = 1 << 2; +pub const PHY_STS_SPEED_MASK: u32 = 0x3; +pub const PHY_STS_SPEED_SHIFT: u32 = 0; +pub const PHY_STS_SPEED_1000M: u32 = 2; +pub const PHY_STS_SPEED_100M: u32 = 1; +pub const PHY_STS_SPEED_10M: u32 = 0; + +pub const BIST0: u32 = 0x141C; +pub const BIST0_COL_MASK: u32 = 0x3F; +pub const BIST0_COL_SHIFT: u32 = 24; +pub const BIST0_ROW_MASK: u32 = 0xFFF; +pub const BIST0_ROW_SHIFT: u32 = 12; +pub const BIST0_STEP_MASK: u32 = 0xF; +pub const BIST0_STEP_SHIFT: u32 = 8; +pub const BIST0_PATTERN_MASK: u32 = 0x7; +pub const BIST0_PATTERN_SHIFT: u32 = 4; +pub const BIST0_CRIT: u32 = 1 << 3; +pub const BIST0_FIXED: u32 = 1 << 2; +pub const BIST0_FAIL: u32 = 1 << 1; +pub const BIST0_START: u32 = 1 << 0; + +pub const BIST1: u32 = 0x1420; +pub const BIST1_COL_MASK: u32 = 0x3F; +pub const BIST1_COL_SHIFT: u32 = 24; +pub const BIST1_ROW_MASK: u32 = 0xFFF; +pub const BIST1_ROW_SHIFT: u32 = 12; +pub const BIST1_STEP_MASK: u32 = 0xF; +pub const BIST1_STEP_SHIFT: u32 = 8; +pub const BIST1_PATTERN_MASK: u32 = 0x7; +pub const BIST1_PATTERN_SHIFT: u32 = 4; +pub const BIST1_CRIT: u32 = 1 << 3; +pub const BIST1_FIXED: u32 = 1 << 2; +pub const BIST1_FAIL: u32 = 1 << 1; +pub const BIST1_START: u32 = 1 << 0; + +pub const SERDES: u32 = 0x1424; +pub const SERDES_PHYCLK_SLWDWN: u32 = 1 << 18; +pub const SERDES_MACCLK_SLWDWN: u32 = 1 << 17; +pub const SERDES_SELFB_PLL_MASK: u32 = 0x3; +pub const SERDES_SELFB_PLL_SHIFT: u32 = 14; +/* bit13:: u32 = 1:gtx_clk,: u32 = 0:25M */ +pub const SERDES_PHYCLK_SEL_GTX: u32 = 1 << 13; +/* bit12:: u32 = 1:serdes,0:25M */ +pub const SERDES_PCIECLK_SEL_SRDS: u32 = 1 << 12; +pub const SERDES_BUFS_RX_EN: u32 = 1 << 11; +pub const SERDES_PD_RX: u32 = 1 << 10; +pub const SERDES_PLL_EN: u32 = 1 << 9; +pub const SERDES_EN: u32 = 1 << 8; +/* bit6:: u32 = 0:state-machine,1:csr */ +pub const SERDES_SELFB_PLL_SEL_CSR: u32 = 1 << 6; +pub const SERDES_SELFB_PLL_CSR_MASK: u32 = 0x3; +pub const SERDES_SELFB_PLL_CSR_SHIFT: u32 = 4; +/*: u32 = 4-12% OV-CLK */ +pub const SERDES_SELFB_PLL_CSR_4: u32 = 3; +/*: u32 = 0-4% OV-CLK */ +pub const SERDES_SELFB_PLL_CSR_0: u32 = 2; +/*: u32 = 12-18% OV-CLK */ +pub const SERDES_SELFB_PLL_CSR_12: u32 = 1; +/*: u32 = 18-25% OV-CLK */ +pub const SERDES_SELFB_PLL_CSR_18: u32 = 0; +pub const SERDES_VCO_SLOW: u32 = 1 << 3; +pub const SERDES_VCO_FAST: u32 = 1 << 2; +pub const SERDES_LOCKDCT_EN: u32 = 1 << 1; +pub const SERDES_LOCKDCTED: u32 = 1 << 0; + +pub const LED_CTRL: u32 = 0x1428; +pub const LED_CTRL_PATMAP2_MASK: u32 = 0x3; +pub const LED_CTRL_PATMAP2_SHIFT: u32 = 8; +pub const LED_CTRL_PATMAP1_MASK: u32 = 0x3; +pub const LED_CTRL_PATMAP1_SHIFT: u32 = 6; +pub const LED_CTRL_PATMAP0_MASK: u32 = 0x3; +pub const LED_CTRL_PATMAP0_SHIFT: u32 = 4; +pub const LED_CTRL_D3_MODE_MASK: u32 = 0x3; +pub const LED_CTRL_D3_MODE_SHIFT: u32 = 2; +pub const LED_CTRL_D3_MODE_NORMAL: u32 = 0; +pub const LED_CTRL_D3_MODE_WOL_DIS: u32 = 1; +pub const LED_CTRL_D3_MODE_WOL_ANY: u32 = 2; +pub const LED_CTRL_D3_MODE_WOL_EN: u32 = 3; +pub const LED_CTRL_DUTY_CYCL_MASK: u32 = 0x3; +pub const LED_CTRL_DUTY_CYCL_SHIFT: u32 = 0; +/*: u32 = 50% */ +pub const LED_CTRL_DUTY_CYCL_50: u32 = 0; +/*: u32 = 12.5% */ +pub const LED_CTRL_DUTY_CYCL_125: u32 = 1; +/*: u32 = 25% */ +pub const LED_CTRL_DUTY_CYCL_25: u32 = 2; +/*: u32 = 75% */ +pub const LED_CTRL_DUTY_CYCL_75: u32 = 3; + +pub const LED_PATN: u32 = 0x142C; +pub const LED_PATN1_MASK: u32 = 0xFFFF; +pub const LED_PATN1_SHIFT: u32 = 16; +pub const LED_PATN0_MASK: u32 = 0xFFFF; +pub const LED_PATN0_SHIFT: u32 = 0; + +pub const LED_PATN2: u32 = 0x1430; +pub const LED_PATN2_MASK: u32 = 0xFFFF; +pub const LED_PATN2_SHIFT: u32 = 0; + +pub const SYSALV: u32 = 0x1434; +pub const SYSALV_FLAG: u32 = 1 << 0; + +pub const PCIERR_INST: u32 = 0x1438; +pub const PCIERR_INST_TX_RATE_MASK: u32 = 0xF; +pub const PCIERR_INST_TX_RATE_SHIFT: u32 = 4; +pub const PCIERR_INST_RX_RATE_MASK: u32 = 0xF; +pub const PCIERR_INST_RX_RATE_SHIFT: u32 = 0; + +pub const LPI_DECISN_TIMER: u32 = 0x143C; + +pub const LPI_CTRL: u32 = 0x1440; +pub const LPI_CTRL_CHK_DA: u32 = 1 << 31; +pub const LPI_CTRL_ENH_TO_MASK: u32 = 0x1FFF; +pub const LPI_CTRL_ENH_TO_SHIFT: u32 = 12; +pub const LPI_CTRL_ENH_TH_MASK: u32 = 0x1F; +pub const LPI_CTRL_ENH_TH_SHIFT: u32 = 6; +pub const LPI_CTRL_ENH_EN: u32 = 1 << 5; +pub const LPI_CTRL_CHK_RX: u32 = 1 << 4; +pub const LPI_CTRL_CHK_STATE: u32 = 1 << 3; +pub const LPI_CTRL_GMII: u32 = 1 << 2; +pub const LPI_CTRL_TO_PHY: u32 = 1 << 1; +pub const LPI_CTRL_EN: u32 = 1 << 0; + +pub const LPI_WAIT: u32 = 0x1444; +pub const LPI_WAIT_TIMER_MASK: u32 = 0xFFFF; +pub const LPI_WAIT_TIMER_SHIFT: u32 = 0; + +/* heart-beat, for swoi/cifs */ +pub const HRTBT_VLAN: u32 = 0x1450; +pub const HRTBT_VLANID_MASK: u32 = 0xFFFF; +pub const HRRBT_VLANID_SHIFT: u32 = 0; + +pub const HRTBT_CTRL: u32 = 0x1454; +pub const HRTBT_CTRL_EN: u32 = 1 << 31; +pub const HRTBT_CTRL_PERIOD_MASK: u32 = 0x3F; +pub const HRTBT_CTRL_PERIOD_SHIFT: u32 = 25; +pub const HRTBT_CTRL_HASVLAN: u32 = 1 << 24; +pub const HRTBT_CTRL_HDRADDR_MASK: u32 = 0xFFF; +pub const HRTBT_CTRL_HDRADDR_SHIFT: u32 = 12; +pub const HRTBT_CTRL_HDRADDRB0_MASK: u32 = 0x7FF; +pub const HRTBT_CTRL_HDRADDRB0_SHIFT: u32 = 13; +pub const HRTBT_CTRL_PKT_FRAG: u32 = 1 << 12; +pub const HRTBT_CTRL_PKTLEN_MASK: u32 = 0xFFF; +pub const HRTBT_CTRL_PKTLEN_SHIFT: u32 = 0; + +/* for: u32 = B0+, bit[13..] for C0+ */ +pub const HRTBT_EXT_CTRL: u32 = 0x1AD0; +pub const L1F_HRTBT_EXT_CTRL_PERIOD_HIGH_MASK: u32 = 0x3F; +pub const L1F_HRTBT_EXT_CTRL_PERIOD_HIGH_SHIFT: u32 = 24; +pub const L1F_HRTBT_EXT_CTRL_SWOI_STARTUP_PKT_EN: u32 = 1 << 23; +pub const L1F_HRTBT_EXT_CTRL_IOAC_2_FRAGMENTED: u32 = 1 << 22; +pub const L1F_HRTBT_EXT_CTRL_IOAC_1_FRAGMENTED: u32 = 1 << 21; +pub const L1F_HRTBT_EXT_CTRL_IOAC_1_KEEPALIVE_EN: u32 = 1 << 20; +pub const L1F_HRTBT_EXT_CTRL_IOAC_1_HAS_VLAN: u32 = 1 << 19; +pub const L1F_HRTBT_EXT_CTRL_IOAC_1_IS_8023: u32 = 1 << 18; +pub const L1F_HRTBT_EXT_CTRL_IOAC_1_IS_IPV6: u32 = 1 << 17; +pub const L1F_HRTBT_EXT_CTRL_IOAC_2_KEEPALIVE_EN: u32 = 1 << 16; +pub const L1F_HRTBT_EXT_CTRL_IOAC_2_HAS_VLAN: u32 = 1 << 15; +pub const L1F_HRTBT_EXT_CTRL_IOAC_2_IS_8023: u32 = 1 << 14; +pub const L1F_HRTBT_EXT_CTRL_IOAC_2_IS_IPV6: u32 = 1 << 13; +pub const HRTBT_EXT_CTRL_NS_EN: u32 = 1 << 12; +pub const HRTBT_EXT_CTRL_FRAG_LEN_MASK: u32 = 0xFF; +pub const HRTBT_EXT_CTRL_FRAG_LEN_SHIFT: u32 = 4; +pub const HRTBT_EXT_CTRL_IS_8023: u32 = 1 << 3; +pub const HRTBT_EXT_CTRL_IS_IPV6: u32 = 1 << 2; +pub const HRTBT_EXT_CTRL_WAKEUP_EN: u32 = 1 << 1; +pub const HRTBT_EXT_CTRL_ARP_EN: u32 = 1 << 0; + +pub const HRTBT_REM_IPV4_ADDR: u32 = 0x1AD4; +pub const HRTBT_HOST_IPV4_ADDR: u32 = 0x1478; +pub const HRTBT_REM_IPV6_ADDR3: u32 = 0x1AD8; +pub const HRTBT_REM_IPV6_ADDR2: u32 = 0x1ADC; +pub const HRTBT_REM_IPV6_ADDR1: u32 = 0x1AE0; +pub const HRTBT_REM_IPV6_ADDR0: u32 = 0x1AE4; + +/*: u32 = 1B8C ~: u32 = 1B94 for C0+ */ +pub const SWOI_ACER_CTRL: u32 = 0x1B8C; +pub const SWOI_ORIG_ACK_NAK_EN: u32 = 1 << 20; +pub const SWOI_ORIG_ACK_NAK_PKT_LEN_MASK: u32 = 0xFF; +pub const SWOI_ORIG_ACK_NAK_PKT_LEN_SHIFT: u32 = 12; +pub const SWOI_ORIG_ACK_ADDR_MASK: u32 = 0xFFF; +pub const SWOI_ORIG_ACK_ADDR_SHIFT: u32 = 0; + +pub const SWOI_IOAC_CTRL_2: u32 = 0x1B90; +pub const SWOI_IOAC_CTRL_2_SWOI_1_FRAG_LEN_MASK: u32 = 0xFF; +pub const SWOI_IOAC_CTRL_2_SWOI_1_FRAG_LEN_SHIFT: u32 = 24; +pub const SWOI_IOAC_CTRL_2_SWOI_1_PKT_LEN_MASK: u32 = 0xFFF; +pub const SWOI_IOAC_CTRL_2_SWOI_1_PKT_LEN_SHIFT: u32 = 12; +pub const SWOI_IOAC_CTRL_2_SWOI_1_HDR_ADDR_MASK: u32 = 0xFFF; +pub const SWOI_IOAC_CTRL_2_SWOI_1_HDR_ADDR_SHIFT: u32 = 0; + +pub const SWOI_IOAC_CTRL_3: u32 = 0x1B94; +pub const SWOI_IOAC_CTRL_3_SWOI_2_FRAG_LEN_MASK: u32 = 0xFF; +pub const SWOI_IOAC_CTRL_3_SWOI_2_FRAG_LEN_SHIFT: u32 = 24; +pub const SWOI_IOAC_CTRL_3_SWOI_2_PKT_LEN_MASK: u32 = 0xFFF; +pub const SWOI_IOAC_CTRL_3_SWOI_2_PKT_LEN_SHIFT: u32 = 12; +pub const SWOI_IOAC_CTRL_3_SWOI_2_HDR_ADDR_MASK: u32 = 0xFFF; +pub const SWOI_IOAC_CTRL_3_SWOI_2_HDR_ADDR_SHIFT: u32 = 0; + +/*SWOI_HOST_IPV6_ADDR reuse reg1a60-1a6c,: u32 = 1a70-1a7c,: u32 = 1aa0-1aac,: u32 = 1ab0-1abc.*/ +pub const HRTBT_WAKEUP_PORT: u32 = 0x1AE8; +pub const HRTBT_WAKEUP_PORT_SRC_MASK: u32 = 0xFFFF; +pub const HRTBT_WAKEUP_PORT_SRC_SHIFT: u32 = 16; +pub const HRTBT_WAKEUP_PORT_DEST_MASK: u32 = 0xFFFF; +pub const HRTBT_WAKEUP_PORT_DEST_SHIFT: u32 = 0; + +pub const HRTBT_WAKEUP_DATA7: u32 = 0x1AEC; +pub const HRTBT_WAKEUP_DATA6: u32 = 0x1AF0; +pub const HRTBT_WAKEUP_DATA5: u32 = 0x1AF4; +pub const HRTBT_WAKEUP_DATA4: u32 = 0x1AF8; +pub const HRTBT_WAKEUP_DATA3: u32 = 0x1AFC; +pub const HRTBT_WAKEUP_DATA2: u32 = 0x1B80; +pub const HRTBT_WAKEUP_DATA1: u32 = 0x1B84; +pub const HRTBT_WAKEUP_DATA0: u32 = 0x1B88; + +pub const RXPARSE: u32 = 0x1458; +pub const RXPARSE_FLT6_L4_MASK: u32 = 0x3; +pub const RXPARSE_FLT6_L4_SHIFT: u32 = 30; +pub const RXPARSE_FLT6_L3_MASK: u32 = 0x3; +pub const RXPARSE_FLT6_L3_SHIFT: u32 = 28; +pub const RXPARSE_FLT5_L4_MASK: u32 = 0x3; +pub const RXPARSE_FLT5_L4_SHIFT: u32 = 26; +pub const RXPARSE_FLT5_L3_MASK: u32 = 0x3; +pub const RXPARSE_FLT5_L3_SHIFT: u32 = 24; +pub const RXPARSE_FLT4_L4_MASK: u32 = 0x3; +pub const RXPARSE_FLT4_L4_SHIFT: u32 = 22; +pub const RXPARSE_FLT4_L3_MASK: u32 = 0x3; +pub const RXPARSE_FLT4_L3_SHIFT: u32 = 20; +pub const RXPARSE_FLT3_L4_MASK: u32 = 0x3; +pub const RXPARSE_FLT3_L4_SHIFT: u32 = 18; +pub const RXPARSE_FLT3_L3_MASK: u32 = 0x3; +pub const RXPARSE_FLT3_L3_SHIFT: u32 = 16; +pub const RXPARSE_FLT2_L4_MASK: u32 = 0x3; +pub const RXPARSE_FLT2_L4_SHIFT: u32 = 14; +pub const RXPARSE_FLT2_L3_MASK: u32 = 0x3; +pub const RXPARSE_FLT2_L3_SHIFT: u32 = 12; +pub const RXPARSE_FLT1_L4_MASK: u32 = 0x3; +pub const RXPARSE_FLT1_L4_SHIFT: u32 = 10; +pub const RXPARSE_FLT1_L3_MASK: u32 = 0x3; +pub const RXPARSE_FLT1_L3_SHIFT: u32 = 8; +pub const RXPARSE_FLT6_EN: u32 = 1 << 5; +pub const RXPARSE_FLT5_EN: u32 = 1 << 4; +pub const RXPARSE_FLT4_EN: u32 = 1 << 3; +pub const RXPARSE_FLT3_EN: u32 = 1 << 2; +pub const RXPARSE_FLT2_EN: u32 = 1 << 1; +pub const RXPARSE_FLT1_EN: u32 = 1 << 0; +pub const RXPARSE_FLT_L4_UDP: u32 = 0; +pub const RXPARSE_FLT_L4_TCP: u32 = 1; +pub const RXPARSE_FLT_L4_BOTH: u32 = 2; +pub const RXPARSE_FLT_L4_NONE: u32 = 3; +pub const RXPARSE_FLT_L3_IPV6: u32 = 0; +pub const RXPARSE_FLT_L3_IPV4: u32 = 1; +pub const RXPARSE_FLT_L3_BOTH: u32 = 2; + +/* Terodo support */ +pub const TRD_CTRL: u32 = 0x145C; +pub const TRD_CTRL_EN: u32 = 1 << 31; +pub const TRD_CTRL_BUBBLE_WAKE_EN: u32 = 1 << 30; +pub const TRD_CTRL_PREFIX_CMP_HW: u32 = 1 << 28; +pub const TRD_CTRL_RSHDR_ADDR_MASK: u32 = 0xFFF; +pub const TRD_CTRL_RSHDR_ADDR_SHIFT: u32 = 16; +pub const TRD_CTRL_SINTV_MAX_MASK: u32 = 0xFF; +pub const TRD_CTRL_SINTV_MAX_SHIFT: u32 = 8; +pub const TRD_CTRL_SINTV_MIN_MASK: u32 = 0xFF; +pub const TRD_CTRL_SINTV_MIN_SHIFT: u32 = 0; + +pub const TRD_RS: u32 = 0x1460; +pub const TRD_RS_SZ_MASK: u32 = 0xFFF; +pub const TRD_RS_SZ_SHIFT: u32 = 20; +pub const TRD_RS_NONCE_OFS_MASK: u32 = 0xFFF; +pub const TRD_RS_NONCE_OFS_SHIFT: u32 = 8; +pub const TRD_RS_SEQ_OFS_MASK: u32 = 0xFF; +pub const TRD_RS_SEQ_OFS_SHIFT: u32 = 0; + +pub const TRD_SRV_IP4: u32 = 0x1464; + +pub const TRD_CLNT_EXTNL_IP4: u32 = 0x1468; + +pub const TRD_PORT: u32 = 0x146C; +pub const TRD_PORT_CLNT_EXTNL_MASK: u32 = 0xFFFF; +pub const TRD_PORT_CLNT_EXTNL_SHIFT: u32 = 16; +pub const TRD_PORT_SRV_MASK: u32 = 0xFFFF; +pub const TRD_PORT_SRV_SHIFT: u32 = 0; + +pub const TRD_PREFIX: u32 = 0x1470; + +pub const TRD_BUBBLE_DA_IP4: u32 = 0x1478; + +pub const TRD_BUBBLE_DA_PORT: u32 = 0x147C; + +/* for: u32 = B0 */ +pub const IDLE_DECISN_TIMER: u32 = 0x1474; +/*: u32 = 1ms */ +pub const IDLE_DECISN_TIMER_DEF: u32 = 0x400; + +pub const MAC_CTRL: u32 = 0x1480; +pub const MAC_CTRL_FAST_PAUSE: u32 = 1 << 31; +pub const MAC_CTRL_WOLSPED_SWEN: u32 = 1 << 30; +/* bit29:: u32 = 1:legacy(hi5b),: u32 = 0:marvl(lo5b)*/ +pub const MAC_CTRL_MHASH_ALG_HI5B: u32 = 1 << 29; +pub const MAC_CTRL_SPAUSE_EN: u32 = 1 << 28; +pub const MAC_CTRL_DBG_EN: u32 = 1 << 27; +pub const MAC_CTRL_BRD_EN: u32 = 1 << 26; +pub const MAC_CTRL_MULTIALL_EN: u32 = 1 << 25; +pub const MAC_CTRL_RX_XSUM_EN: u32 = 1 << 24; +pub const MAC_CTRL_THUGE: u32 = 1 << 23; +pub const MAC_CTRL_MBOF: u32 = 1 << 22; +pub const MAC_CTRL_SPEED_MASK: u32 = 0x3; +pub const MAC_CTRL_SPEED_SHIFT: u32 = 20; +pub const MAC_CTRL_SPEED_10_100: u32 = 1; +pub const MAC_CTRL_SPEED_1000: u32 = 2; +pub const MAC_CTRL_SIMR: u32 = 1 << 19; +pub const MAC_CTRL_SSTCT: u32 = 1 << 17; +pub const MAC_CTRL_TPAUSE: u32 = 1 << 16; +pub const MAC_CTRL_PROMISC_EN: u32 = 1 << 15; +pub const MAC_CTRL_VLANSTRIP: u32 = 1 << 14; +pub const MAC_CTRL_PRMBLEN_MASK: u32 = 0xF; +pub const MAC_CTRL_PRMBLEN_SHIFT: u32 = 10; +pub const MAC_CTRL_RHUGE_EN: u32 = 1 << 9; +pub const MAC_CTRL_FLCHK: u32 = 1 << 8; +pub const MAC_CTRL_PCRCE: u32 = 1 << 7; +pub const MAC_CTRL_CRCE: u32 = 1 << 6; +pub const MAC_CTRL_FULLD: u32 = 1 << 5; +pub const MAC_CTRL_LPBACK_EN: u32 = 1 << 4; +pub const MAC_CTRL_RXFC_EN: u32 = 1 << 3; +pub const MAC_CTRL_TXFC_EN: u32 = 1 << 2; +pub const MAC_CTRL_RX_EN: u32 = 1 << 1; +pub const MAC_CTRL_TX_EN: u32 = 1 << 0; + +pub const GAP: u32 = 0x1484; +pub const GAP_IPGR2_MASK: u32 = 0x7F; +pub const GAP_IPGR2_SHIFT: u32 = 24; +pub const GAP_IPGR1_MASK: u32 = 0x7F; +pub const GAP_IPGR1_SHIFT: u32 = 16; +pub const GAP_MIN_IFG_MASK: u32 = 0xFF; +pub const GAP_MIN_IFG_SHIFT: u32 = 8; +pub const GAP_IPGT_MASK: u32 = 0x7F; +pub const GAP_IPGT_SHIFT: u32 = 0; + +pub const STAD0: u32 = 0x1488; +pub const STAD1: u32 = 0x148C; + +pub const HASH_TBL0: u32 = 0x1490; +pub const HASH_TBL1: u32 = 0x1494; + +pub const HALFD: u32 = 0x1498; +pub const HALFD_JAM_IPG_MASK: u32 = 0xF; +pub const HALFD_JAM_IPG_SHIFT: u32 = 24; +pub const HALFD_ABEBT_MASK: u32 = 0xF; +pub const HALFD_ABEBT_SHIFT: u32 = 20; +pub const HALFD_ABEBE: u32 = 1 << 19; +pub const HALFD_BPNB: u32 = 1 << 18; +pub const HALFD_NOBO: u32 = 1 << 17; +pub const HALFD_EDXSDFR: u32 = 1 << 16; +pub const HALFD_RETRY_MASK: u32 = 0xF; +pub const HALFD_RETRY_SHIFT: u32 = 12; +pub const HALFD_LCOL_MASK: u32 = 0x3FF; +pub const HALFD_LCOL_SHIFT: u32 = 0; + +pub const MTU: u32 = 0x149C; +pub const MTU_JUMBO_TH: u32 = 1514; +pub const MTU_STD_ALGN: u32 = 1536; +pub const MTU_MIN: u32 = 64; + +pub const SRAM0: u32 = 0x1500; +pub const SRAM_RFD_TAIL_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_RFD_TAIL_ADDR_SHIFT: u32 = 16; +pub const SRAM_RFD_HEAD_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_RFD_HEAD_ADDR_SHIFT: u32 = 0; + +pub const SRAM1: u32 = 0x1510; +pub const SRAM_RFD_LEN_MASK: u32 = 0xFFF; +pub const SRAM_RFD_LEN_SHIFT: u32 = 0; + +pub const SRAM2: u32 = 0x1518; +pub const SRAM_TRD_TAIL_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_TRD_TAIL_ADDR_SHIFT: u32 = 16; +pub const SRMA_TRD_HEAD_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_TRD_HEAD_ADDR_SHIFT: u32 = 0; + +pub const SRAM3: u32 = 0x151C; +pub const SRAM_TRD_LEN_MASK: u32 = 0xFFF; +pub const SRAM_TRD_LEN_SHIFT: u32 = 0; + +pub const SRAM4: u32 = 0x1520; +pub const SRAM_RXF_TAIL_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_RXF_TAIL_ADDR_SHIFT: u32 = 16; +pub const SRAM_RXF_HEAD_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_RXF_HEAD_ADDR_SHIFT: u32 = 0; + +pub const SRAM5: u32 = 0x1524; +pub const SRAM_RXF_LEN_MASK: u32 = 0xFFF; +pub const SRAM_RXF_LEN_SHIFT: u32 = 0; +pub const SRAM_RXF_LEN_8K: u32 = (8 * 1024); + +pub const SRAM6: u32 = 0x1528; +pub const SRAM_TXF_TAIL_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_TXF_TAIL_ADDR_SHIFT: u32 = 16; +pub const SRAM_TXF_HEAD_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_TXF_HEAD_ADDR_SHIFT: u32 = 0; + +pub const SRAM7: u32 = 0x152C; +pub const SRAM_TXF_LEN_MASK: u32 = 0xFFF; +pub const SRAM_TXF_LEN_SHIFT: u32 = 0; + +pub const SRAM8: u32 = 0x1530; +pub const SRAM_PATTERN_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_PATTERN_ADDR_SHIFT: u32 = 16; +pub const SRAM_TSO_ADDR_MASK: u32 = 0xFFF; +pub const SRAM_TSO_ADDR_SHIFT: u32 = 0; + +pub const SRAM9: u32 = 0x1534; +pub const SRAM_LOAD_PTR: u32 = 1 << 0; + +pub const RX_BASE_ADDR_HI: u32 = 0x1540; + +pub const TX_BASE_ADDR_HI: u32 = 0x1544; + +pub const RFD_ADDR_LO: u32 = 0x1550; +pub const RFD_RING_SZ: u32 = 0x1560; +pub const RFD_BUF_SZ: u32 = 0x1564; +pub const RFD_BUF_SZ_MASK: u32 = 0xFFFF; +pub const RFD_BUF_SZ_SHIFT: u32 = 0; + +pub const RRD_ADDR_LO: u32 = 0x1568; +pub const RRD_RING_SZ: u32 = 0x1578; +pub const RRD_RING_SZ_MASK: u32 = 0xFFF; +pub const RRD_RING_SZ_SHIFT: u32 = 0; + +/* pri3: highest, pri0: lowest */ +pub const TPD_PRI3_ADDR_LO: u32 = 0x14E4; +pub const TPD_PRI2_ADDR_LO: u32 = 0x14E0; +pub const TPD_PRI1_ADDR_LO: u32 = 0x157C; +pub const TPD_PRI0_ADDR_LO: u32 = 0x1580; + +/* producer index is: u32 = 16bit */ +pub const TPD_PRI3_PIDX: u32 = 0x1618; +pub const TPD_PRI2_PIDX: u32 = 0x161A; +pub const TPD_PRI1_PIDX: u32 = 0x15F0; +pub const TPD_PRI0_PIDX: u32 = 0x15F2; + +/* consumer index is: u32 = 16bit */ +pub const TPD_PRI3_CIDX: u32 = 0x161C; +pub const TPD_PRI2_CIDX: u32 = 0x161E; +pub const TPD_PRI1_CIDX: u32 = 0x15F4; +pub const TPD_PRI0_CIDX: u32 = 0x15F6; + +pub const TPD_RING_SZ: u32 = 0x1584; +pub const TPD_RING_SZ_MASK: u32 = 0xFFFF; +pub const TPD_RING_SZ_SHIFT: u32 = 0; + +pub const CMB_ADDR_LO: u32 = 0x1588; + +pub const TXQ0: u32 = 0x1590; +pub const TXQ0_TXF_BURST_PREF_MASK: u32 = 0xFFFF; +pub const TXQ0_TXF_BURST_PREF_SHIFT: u32 = 16; +pub const TXQ_TXF_BURST_PREF_DEF: u32 = 0x200; +pub const TXQ0_PEDING_CLR: u32 = 1 << 8; +pub const TXQ0_LSO_8023_EN: u32 = 1 << 7; +pub const TXQ0_MODE_ENHANCE: u32 = 1 << 6; +pub const TXQ0_EN: u32 = 1 << 5; +pub const TXQ0_SUPT_IPOPT: u32 = 1 << 4; +pub const TXQ0_TPD_BURSTPREF_MASK: u32 = 0xF; +pub const TXQ0_TPD_BURSTPREF_SHIFT: u32 = 0; +pub const TXQ_TPD_BURSTPREF_DEF: u32 = 5; + +pub const TXQ1: u32 = 0x1594; +/* bit11: drop large packet, len > (rfd buf) */ +pub const TXQ1_ERRLGPKT_DROP_EN: u32 = 1 << 11; +/* bit[9:0]:: u32 = 8bytes unit */ +pub const TXQ1_JUMBO_TSOTHR_MASK: u32 = 0x7FF; +pub const TXQ1_JUMBO_TSOTHR_SHIFT: u32 = 0; +pub const TXQ1_JUMBO_TSO_TH: u32 = (7 * 1024); + +/* L1 entrance control */ +pub const TXQ2: u32 = 0x1598; +pub const TXQ2_BURST_EN: u32 = 1 << 31; +pub const TXQ2_BURST_HI_WM_MASK: u32 = 0xFFF; +pub const TXQ2_BURST_HI_WM_SHIFT: u32 = 16; +pub const TXQ2_BURST_LO_WM_MASK: u32 = 0xFFF; +pub const TXQ2_BURST_LO_WM_SHIFT: u32 = 0; + +pub const RXQ0: u32 = 0x15A0; +pub const RXQ0_EN: u32 = 1 << 31; +pub const RXQ0_CUT_THRU_EN: u32 = 1 << 30; +pub const RXQ0_RSS_HASH_EN: u32 = 1 << 29; +/* bit28:: u32 = 0:goto Q0,: u32 = 1:as table */ +pub const RXQ0_NON_IP_QTBL: u32 = 1 << 28; +pub const RXQ0_RSS_MODE_MASK: u32 = 0x3; +pub const RXQ0_RSS_MODE_SHIFT: u32 = 26; +pub const RXQ0_RSS_MODE_DIS: u32 = 0; +pub const RXQ0_RSS_MODE_SQSI: u32 = 1; +pub const RXQ0_RSS_MODE_MQSI: u32 = 2; +pub const RXQ0_RSS_MODE_MQMI: u32 = 3; +pub const RXQ0_NUM_RFD_PREF_MASK: u32 = 0x3F; +pub const RXQ0_NUM_RFD_PREF_SHIFT: u32 = 20; +pub const RXQ0_NUM_RFD_PREF_DEF: u32 = 8; +pub const RXQ0_IDT_TBL_SIZE_MASK: u32 = 0x1FF; +pub const RXQ0_IDT_TBL_SIZE_SHIFT: u32 = 8; +pub const RXQ0_IDT_TBL_SIZE_DEF: u32 = 0x100; +pub const RXQ0_IPV6_PARSE_EN: u32 = 1 << 7; +pub const RXQ0_RSS_HSTYP_MASK: u32 = 0xF; +pub const RXQ0_RSS_HSTYP_SHIFT: u32 = 2; +pub const RXQ0_RSS_HSTYP_IPV6_TCP_EN: u32 = 1 << 5; +pub const RXQ0_RSS_HSTYP_IPV6_EN: u32 = 1 << 4; +pub const RXQ0_RSS_HSTYP_IPV4_TCP_EN: u32 = 1 << 3; +pub const RXQ0_RSS_HSTYP_IPV4_EN: u32 = 1 << 2; +pub const RXQ0_RSS_HSTYP_ALL: u32 = RXQ0_RSS_HSTYP_IPV6_TCP_EN + | RXQ0_RSS_HSTYP_IPV4_TCP_EN + | RXQ0_RSS_HSTYP_IPV6_EN + | RXQ0_RSS_HSTYP_IPV4_EN; +pub const RXQ0_ASPM_THRESH_MASK: u32 = 0x3; +pub const RXQ0_ASPM_THRESH_SHIFT: u32 = 0; +pub const RXQ0_ASPM_THRESH_NO: u32 = 0; +pub const RXQ0_ASPM_THRESH_1M: u32 = 1; +pub const RXQ0_ASPM_THRESH_10M: u32 = 2; +pub const RXQ0_ASPM_THRESH_100M: u32 = 3; + +pub const RXQ1: u32 = 0x15A4; +/*: u32 = 32bytes unit */ +pub const RXQ1_JUMBO_LKAH_MASK: u32 = 0xF; +pub const RXQ1_JUMBO_LKAH_SHIFT: u32 = 12; +pub const RXQ1_RFD_PREF_DOWN_MASK: u32 = 0x3F; +pub const RXQ1_RFD_PREF_DOWN_SHIFT: u32 = 6; +pub const RXQ1_RFD_PREF_UP_MASK: u32 = 0x3F; +pub const RXQ1_RFD_PREF_UP_SHIFT: u32 = 0; + +pub const RXQ2: u32 = 0x15A8; +/* XOFF: USED SRAM LOWER THAN IT, THEN NOTIFY THE PEER TO SEND AGAIN */ +pub const RXQ2_RXF_XOFF_THRESH_MASK: u32 = 0xFFF; +pub const RXQ2_RXF_XOFF_THRESH_SHIFT: u32 = 16; +pub const RXQ2_RXF_XON_THRESH_MASK: u32 = 0xFFF; +pub const RXQ2_RXF_XON_THRESH_SHIFT: u32 = 0; +/* Size = tx-packet(1522) + IPG(12) + SOF(8) + 64(Pause) + IPG(12) + SOF(8) + + * rx-packet(1522) + delay-of-link(64) + * =: u32 = 3212. + */ +pub const RXQ2_RXF_FLOW_CTRL_RSVD: u32 = 3212; + +pub const RXQ3: u32 = 0x15AC; +pub const RXQ3_RXD_TIMER_MASK: u32 = 0x7FFF; +pub const RXQ3_RXD_TIMER_SHIFT: u32 = 16; +/*: u32 = 8bytes unit */ +pub const RXQ3_RXD_THRESH_MASK: u32 = 0xFFF; +pub const RXQ3_RXD_THRESH_SHIFT: u32 = 0; + +pub const DMA: u32 = 0x15C0; +pub const DMA_SMB_NOW: u32 = 1 << 31; +pub const DMA_WPEND_CLR: u32 = 1 << 30; +pub const DMA_RPEND_CLR: u32 = 1 << 29; +pub const DMA_WSRAM_RDCTRL: u32 = 1 << 28; +pub const DMA_RCHNL_SEL_MASK: u32 = 0x3; +pub const DMA_RCHNL_SEL_SHIFT: u32 = 26; +pub const DMA_RCHNL_SEL_1: u32 = 0; +pub const DMA_RCHNL_SEL_2: u32 = 1; +pub const DMA_RCHNL_SEL_3: u32 = 2; +pub const DMA_RCHNL_SEL_4: u32 = 3; +pub const DMA_SMB_EN: u32 = 1 << 21; +pub const DMA_WDLY_CNT_MASK: u32 = 0xF; +pub const DMA_WDLY_CNT_SHIFT: u32 = 16; +pub const DMA_WDLY_CNT_DEF: u32 = 4; +pub const DMA_RDLY_CNT_MASK: u32 = 0x1F; +pub const DMA_RDLY_CNT_SHIFT: u32 = 11; +pub const DMA_RDLY_CNT_DEF: u32 = 15; +/* bit10:: u32 = 0:tpd with pri,: u32 = 1: data */ +pub const DMA_RREQ_PRI_DATA: u32 = 1 << 10; +pub const DMA_WREQ_BLEN_MASK: u32 = 0x7; +pub const DMA_WREQ_BLEN_SHIFT: u32 = 7; +pub const DMA_RREQ_BLEN_MASK: u32 = 0x7; +pub const DMA_RREQ_BLEN_SHIFT: u32 = 4; +pub const DMA_PENDING_AUTO_RST: u32 = 1 << 3; +pub const DMA_RORDER_MODE_MASK: u32 = 0x7; +pub const DMA_RORDER_MODE_SHIFT: u32 = 0; +pub const DMA_RORDER_MODE_OUT: u32 = 4; +pub const DMA_RORDER_MODE_ENHANCE: u32 = 2; +pub const DMA_RORDER_MODE_IN: u32 = 1; + +pub const WOL0: u32 = 0x14A0; +pub const WOL0_PT7_MATCH: u32 = 1 << 31; +pub const WOL0_PT6_MATCH: u32 = 1 << 30; +pub const WOL0_PT5_MATCH: u32 = 1 << 29; +pub const WOL0_PT4_MATCH: u32 = 1 << 28; +pub const WOL0_PT3_MATCH: u32 = 1 << 27; +pub const WOL0_PT2_MATCH: u32 = 1 << 26; +pub const WOL0_PT1_MATCH: u32 = 1 << 25; +pub const WOL0_PT0_MATCH: u32 = 1 << 24; +pub const WOL0_PT7_EN: u32 = 1 << 23; +pub const WOL0_PT6_EN: u32 = 1 << 22; +pub const WOL0_PT5_EN: u32 = 1 << 21; +pub const WOL0_PT4_EN: u32 = 1 << 20; +pub const WOL0_PT3_EN: u32 = 1 << 19; +pub const WOL0_PT2_EN: u32 = 1 << 18; +pub const WOL0_PT1_EN: u32 = 1 << 17; +pub const WOL0_PT0_EN: u32 = 1 << 16; +pub const WOL0_IPV4_SYNC_EVT: u32 = 1 << 14; +pub const WOL0_IPV6_SYNC_EVT: u32 = 1 << 13; +pub const WOL0_LINK_EVT: u32 = 1 << 10; +pub const WOL0_MAGIC_EVT: u32 = 1 << 9; +pub const WOL0_PATTERN_EVT: u32 = 1 << 8; +pub const WOL0_SWOI_EVT: u32 = 1 << 7; +pub const WOL0_OOB_EN: u32 = 1 << 6; +pub const WOL0_PME_LINK: u32 = 1 << 5; +pub const WOL0_LINK_EN: u32 = 1 << 4; +pub const WOL0_PME_MAGIC_EN: u32 = 1 << 3; +pub const WOL0_MAGIC_EN: u32 = 1 << 2; +pub const WOL0_PME_PATTERN_EN: u32 = 1 << 1; +pub const WOL0_PATTERN_EN: u32 = 1 << 0; + +pub const WOL1: u32 = 0x14A4; +pub const WOL1_PT3_LEN_MASK: u32 = 0xFF; +pub const WOL1_PT3_LEN_SHIFT: u32 = 24; +pub const WOL1_PT2_LEN_MASK: u32 = 0xFF; +pub const WOL1_PT2_LEN_SHIFT: u32 = 16; +pub const WOL1_PT1_LEN_MASK: u32 = 0xFF; +pub const WOL1_PT1_LEN_SHIFT: u32 = 8; +pub const WOL1_PT0_LEN_MASK: u32 = 0xFF; +pub const WOL1_PT0_LEN_SHIFT: u32 = 0; + +pub const WOL2: u32 = 0x14A8; +pub const WOL2_PT7_LEN_MASK: u32 = 0xFF; +pub const WOL2_PT7_LEN_SHIFT: u32 = 24; +pub const WOL2_PT6_LEN_MASK: u32 = 0xFF; +pub const WOL2_PT6_LEN_SHIFT: u32 = 16; +pub const WOL2_PT5_LEN_MASK: u32 = 0xFF; +pub const WOL2_PT5_LEN_SHIFT: u32 = 8; +pub const WOL2_PT4_LEN_MASK: u32 = 0xFF; +pub const WOL2_PT4_LEN_SHIFT: u32 = 0; + +pub const RFD_PIDX: u32 = 0x15E0; +pub const RFD_PIDX_MASK: u32 = 0xFFF; +pub const RFD_PIDX_SHIFT: u32 = 0; + +pub const RFD_CIDX: u32 = 0x15F8; +pub const RFD_CIDX_MASK: u32 = 0xFFF; +pub const RFD_CIDX_SHIFT: u32 = 0; + +/* MIB */ +pub const MIB_BASE: u32 = 0x1700; +pub const MIB_RX_OK: u32 = (MIB_BASE + 0); +pub const MIB_RX_BC: u32 = (MIB_BASE + 4); +pub const MIB_RX_MC: u32 = (MIB_BASE + 8); +pub const MIB_RX_PAUSE: u32 = (MIB_BASE + 12); +pub const MIB_RX_CTRL: u32 = (MIB_BASE + 16); +pub const MIB_RX_FCS: u32 = (MIB_BASE + 20); +pub const MIB_RX_LENERR: u32 = (MIB_BASE + 24); +pub const MIB_RX_BYTCNT: u32 = (MIB_BASE + 28); +pub const MIB_RX_RUNT: u32 = (MIB_BASE + 32); +pub const MIB_RX_FRAGMENT: u32 = (MIB_BASE + 36); +pub const MIB_RX_64B: u32 = (MIB_BASE + 40); +pub const MIB_RX_127B: u32 = (MIB_BASE + 44); +pub const MIB_RX_255B: u32 = (MIB_BASE + 48); +pub const MIB_RX_511B: u32 = (MIB_BASE + 52); +pub const MIB_RX_1023B: u32 = (MIB_BASE + 56); +pub const MIB_RX_1518B: u32 = (MIB_BASE + 60); +pub const MIB_RX_SZMAX: u32 = (MIB_BASE + 64); +pub const MIB_RX_OVSZ: u32 = (MIB_BASE + 68); +pub const MIB_RXF_OV: u32 = (MIB_BASE + 72); +pub const MIB_RRD_OV: u32 = (MIB_BASE + 76); +pub const MIB_RX_ALIGN: u32 = (MIB_BASE + 80); +pub const MIB_RX_BCCNT: u32 = (MIB_BASE + 84); +pub const MIB_RX_MCCNT: u32 = (MIB_BASE + 88); +pub const MIB_RX_ERRADDR: u32 = (MIB_BASE + 92); +pub const MIB_TX_OK: u32 = (MIB_BASE + 96); +pub const MIB_TX_BC: u32 = (MIB_BASE + 100); +pub const MIB_TX_MC: u32 = (MIB_BASE + 104); +pub const MIB_TX_PAUSE: u32 = (MIB_BASE + 108); +pub const MIB_TX_EXCDEFER: u32 = (MIB_BASE + 112); +pub const MIB_TX_CTRL: u32 = (MIB_BASE + 116); +pub const MIB_TX_DEFER: u32 = (MIB_BASE + 120); +pub const MIB_TX_BYTCNT: u32 = (MIB_BASE + 124); +pub const MIB_TX_64B: u32 = (MIB_BASE + 128); +pub const MIB_TX_127B: u32 = (MIB_BASE + 132); +pub const MIB_TX_255B: u32 = (MIB_BASE + 136); +pub const MIB_TX_511B: u32 = (MIB_BASE + 140); +pub const MIB_TX_1023B: u32 = (MIB_BASE + 144); +pub const MIB_TX_1518B: u32 = (MIB_BASE + 148); +pub const MIB_TX_SZMAX: u32 = (MIB_BASE + 152); +pub const MIB_TX_1COL: u32 = (MIB_BASE + 156); +pub const MIB_TX_2COL: u32 = (MIB_BASE + 160); +pub const MIB_TX_LATCOL: u32 = (MIB_BASE + 164); +pub const MIB_TX_ABRTCOL: u32 = (MIB_BASE + 168); +pub const MIB_TX_UNDRUN: u32 = (MIB_BASE + 172); +pub const MIB_TX_TRDBEOP: u32 = (MIB_BASE + 176); +pub const MIB_TX_LENERR: u32 = (MIB_BASE + 180); +pub const MIB_TX_TRUNC: u32 = (MIB_BASE + 184); +pub const MIB_TX_BCCNT: u32 = (MIB_BASE + 188); +pub const MIB_TX_MCCNT: u32 = (MIB_BASE + 192); +pub const MIB_UPDATE: u32 = (MIB_BASE + 196); + +pub const ISR: u32 = 0x1600; +pub const ISR_DIS: u32 = 1 << 31; +pub const ISR_RX_Q7: u32 = 1 << 30; +pub const ISR_RX_Q6: u32 = 1 << 29; +pub const ISR_RX_Q5: u32 = 1 << 28; +pub const ISR_RX_Q4: u32 = 1 << 27; +pub const ISR_PCIE_LNKDOWN: u32 = 1 << 26; +pub const ISR_PCIE_CERR: u32 = 1 << 25; +pub const ISR_PCIE_NFERR: u32 = 1 << 24; +pub const ISR_PCIE_FERR: u32 = 1 << 23; +pub const ISR_PCIE_UR: u32 = 1 << 22; +pub const ISR_MAC_TX: u32 = 1 << 21; +pub const ISR_MAC_RX: u32 = 1 << 20; +pub const ISR_RX_Q3: u32 = 1 << 19; +pub const ISR_RX_Q2: u32 = 1 << 18; +pub const ISR_RX_Q1: u32 = 1 << 17; +pub const ISR_RX_Q0: u32 = 1 << 16; +pub const ISR_TX_Q0: u32 = 1 << 15; +pub const ISR_TXQ_TO: u32 = 1 << 14; +pub const ISR_PHY_LPW: u32 = 1 << 13; +pub const ISR_PHY: u32 = 1 << 12; +pub const ISR_TX_CREDIT: u32 = 1 << 11; +pub const ISR_DMAW: u32 = 1 << 10; +pub const ISR_DMAR: u32 = 1 << 9; +pub const ISR_TXF_UR: u32 = 1 << 8; +pub const ISR_TX_Q3: u32 = 1 << 7; +pub const ISR_TX_Q2: u32 = 1 << 6; +pub const ISR_TX_Q1: u32 = 1 << 5; +pub const ISR_RFD_UR: u32 = 1 << 4; +pub const ISR_RXF_OV: u32 = 1 << 3; +pub const ISR_MANU: u32 = 1 << 2; +pub const ISR_TIMER: u32 = 1 << 1; +pub const ISR_SMB: u32 = 1 << 0; + +pub const IMR: u32 = 0x1604; + +/* re-send assert msg if SW no response */ +pub const INT_RETRIG: u32 = 0x1608; +pub const INT_RETRIG_TIMER_MASK: u32 = 0xFFFF; +pub const INT_RETRIG_TIMER_SHIFT: u32 = 0; +/*: u32 = 40ms */ +pub const INT_RETRIG_TO: u32 = 20000; + +/* re-send deassert msg if SW no response */ +pub const INT_DEASST_TIMER: u32 = 0x1614; + +/* reg1620 used for sleep status */ +pub const PATTERN_MASK: u32 = 0x1620; +pub const PATTERN_MASK_LEN: u32 = 128; + +pub const FLT1_SRC_IP0: u32 = 0x1A00; +pub const FLT1_SRC_IP1: u32 = 0x1A04; +pub const FLT1_SRC_IP2: u32 = 0x1A08; +pub const FLT1_SRC_IP3: u32 = 0x1A0C; +pub const FLT1_DST_IP0: u32 = 0x1A10; +pub const FLT1_DST_IP1: u32 = 0x1A14; +pub const FLT1_DST_IP2: u32 = 0x1A18; +pub const FLT1_DST_IP3: u32 = 0x1A1C; +pub const FLT1_PORT: u32 = 0x1A20; +pub const FLT1_PORT_DST_MASK: u32 = 0xFFFF; +pub const FLT1_PORT_DST_SHIFT: u32 = 16; +pub const FLT1_PORT_SRC_MASK: u32 = 0xFFFF; +pub const FLT1_PORT_SRC_SHIFT: u32 = 0; + +pub const FLT2_SRC_IP0: u32 = 0x1A24; +pub const FLT2_SRC_IP1: u32 = 0x1A28; +pub const FLT2_SRC_IP2: u32 = 0x1A2C; +pub const FLT2_SRC_IP3: u32 = 0x1A30; +pub const FLT2_DST_IP0: u32 = 0x1A34; +pub const FLT2_DST_IP1: u32 = 0x1A38; +pub const FLT2_DST_IP2: u32 = 0x1A40; +pub const FLT2_DST_IP3: u32 = 0x1A44; +pub const FLT2_PORT: u32 = 0x1A48; +pub const FLT2_PORT_DST_MASK: u32 = 0xFFFF; +pub const FLT2_PORT_DST_SHIFT: u32 = 16; +pub const FLT2_PORT_SRC_MASK: u32 = 0xFFFF; +pub const FLT2_PORT_SRC_SHIFT: u32 = 0; + +pub const FLT3_SRC_IP0: u32 = 0x1A4C; +pub const FLT3_SRC_IP1: u32 = 0x1A50; +pub const FLT3_SRC_IP2: u32 = 0x1A54; +pub const FLT3_SRC_IP3: u32 = 0x1A58; +pub const FLT3_DST_IP0: u32 = 0x1A5C; +pub const FLT3_DST_IP1: u32 = 0x1A60; +pub const FLT3_DST_IP2: u32 = 0x1A64; +pub const FLT3_DST_IP3: u32 = 0x1A68; +pub const FLT3_PORT: u32 = 0x1A6C; +pub const FLT3_PORT_DST_MASK: u32 = 0xFFFF; +pub const FLT3_PORT_DST_SHIFT: u32 = 16; +pub const FLT3_PORT_SRC_MASK: u32 = 0xFFFF; +pub const FLT3_PORT_SRC_SHIFT: u32 = 0; + +pub const FLT4_SRC_IP0: u32 = 0x1A70; +pub const FLT4_SRC_IP1: u32 = 0x1A74; +pub const FLT4_SRC_IP2: u32 = 0x1A78; +pub const FLT4_SRC_IP3: u32 = 0x1A7C; +pub const FLT4_DST_IP0: u32 = 0x1A80; +pub const FLT4_DST_IP1: u32 = 0x1A84; +pub const FLT4_DST_IP2: u32 = 0x1A88; +pub const FLT4_DST_IP3: u32 = 0x1A8C; +pub const FLT4_PORT: u32 = 0x1A90; +pub const FLT4_PORT_DST_MASK: u32 = 0xFFFF; +pub const FLT4_PORT_DST_SHIFT: u32 = 16; +pub const FLT4_PORT_SRC_MASK: u32 = 0xFFFF; +pub const FLT4_PORT_SRC_SHIFT: u32 = 0; + +pub const FLT5_SRC_IP0: u32 = 0x1A94; +pub const FLT5_SRC_IP1: u32 = 0x1A98; +pub const FLT5_SRC_IP2: u32 = 0x1A9C; +pub const FLT5_SRC_IP3: u32 = 0x1AA0; +pub const FLT5_DST_IP0: u32 = 0x1AA4; +pub const FLT5_DST_IP1: u32 = 0x1AA8; +pub const FLT5_DST_IP2: u32 = 0x1AAC; +pub const FLT5_DST_IP3: u32 = 0x1AB0; +pub const FLT5_PORT: u32 = 0x1AB4; +pub const FLT5_PORT_DST_MASK: u32 = 0xFFFF; +pub const FLT5_PORT_DST_SHIFT: u32 = 16; +pub const FLT5_PORT_SRC_MASK: u32 = 0xFFFF; +pub const FLT5_PORT_SRC_SHIFT: u32 = 0; + +pub const FLT6_SRC_IP0: u32 = 0x1AB8; +pub const FLT6_SRC_IP1: u32 = 0x1ABC; +pub const FLT6_SRC_IP2: u32 = 0x1AC0; +pub const FLT6_SRC_IP3: u32 = 0x1AC8; +pub const FLT6_DST_IP0: u32 = 0x1620; +pub const FLT6_DST_IP1: u32 = 0x1624; +pub const FLT6_DST_IP2: u32 = 0x1628; +pub const FLT6_DST_IP3: u32 = 0x162C; +pub const FLT6_PORT: u32 = 0x1630; +pub const FLT6_PORT_DST_MASK: u32 = 0xFFFF; +pub const FLT6_PORT_DST_SHIFT: u32 = 16; +pub const FLT6_PORT_SRC_MASK: u32 = 0xFFFF; +pub const FLT6_PORT_SRC_SHIFT: u32 = 0; + +pub const FLTCTRL: u32 = 0x1634; +pub const FLTCTRL_PSTHR_TIMER_MASK: u32 = 0xFF; +pub const FLTCTRL_PSTHR_TIMER_SHIFT: u32 = 24; +pub const FLTCTRL_CHK_DSTPRT6: u32 = 1 << 23; +pub const FLTCTRL_CHK_SRCPRT6: u32 = 1 << 22; +pub const FLTCTRL_CHK_DSTIP6: u32 = 1 << 21; +pub const FLTCTRL_CHK_SRCIP6: u32 = 1 << 20; +pub const FLTCTRL_CHK_DSTPRT5: u32 = 1 << 19; +pub const FLTCTRL_CHK_SRCPRT5: u32 = 1 << 18; +pub const FLTCTRL_CHK_DSTIP5: u32 = 1 << 17; +pub const FLTCTRL_CHK_SRCIP5: u32 = 1 << 16; +pub const FLTCTRL_CHK_DSTPRT4: u32 = 1 << 15; +pub const FLTCTRL_CHK_SRCPRT4: u32 = 1 << 14; +pub const FLTCTRL_CHK_DSTIP4: u32 = 1 << 13; +pub const FLTCTRL_CHK_SRCIP4: u32 = 1 << 12; +pub const FLTCTRL_CHK_DSTPRT3: u32 = 1 << 11; +pub const FLTCTRL_CHK_SRCPRT3: u32 = 1 << 10; +pub const FLTCTRL_CHK_DSTIP3: u32 = 1 << 9; +pub const FLTCTRL_CHK_SRCIP3: u32 = 1 << 8; +pub const FLTCTRL_CHK_DSTPRT2: u32 = 1 << 7; +pub const FLTCTRL_CHK_SRCPRT2: u32 = 1 << 6; +pub const FLTCTRL_CHK_DSTIP2: u32 = 1 << 5; +pub const FLTCTRL_CHK_SRCIP2: u32 = 1 << 4; +pub const FLTCTRL_CHK_DSTPRT1: u32 = 1 << 3; +pub const FLTCTRL_CHK_SRCPRT1: u32 = 1 << 2; +pub const FLTCTRL_CHK_DSTIP1: u32 = 1 << 1; +pub const FLTCTRL_CHK_SRCIP1: u32 = 1 << 0; + +pub const DROP_ALG1: u32 = 0x1638; +pub const DROP_ALG1_BWCHGVAL_MASK: u32 = 0xFFFFF; +pub const DROP_ALG1_BWCHGVAL_SHIFT: u32 = 12; +/* bit11:: u32 = 0:3.125%,: u32 = 1:6.25% */ +pub const DROP_ALG1_BWCHGSCL_6: u32 = 1 << 11; +pub const DROP_ALG1_ASUR_LWQ_EN: u32 = 1 << 10; +pub const DROP_ALG1_BWCHGVAL_EN: u32 = 1 << 9; +pub const DROP_ALG1_BWCHGSCL_EN: u32 = 1 << 8; +pub const DROP_ALG1_PSTHR_AUTO: u32 = 1 << 7; +pub const DROP_ALG1_MIN_PSTHR_MASK: u32 = 0x3; +pub const DROP_ALG1_MIN_PSTHR_SHIFT: u32 = 5; +pub const DROP_ALG1_MIN_PSTHR_1_16: u32 = 0; +pub const DROP_ALG1_MIN_PSTHR_1_8: u32 = 1; +pub const DROP_ALG1_MIN_PSTHR_1_4: u32 = 2; +pub const DROP_ALG1_MIN_PSTHR_1_2: u32 = 3; +pub const DROP_ALG1_PSCL_MASK: u32 = 0x3; +pub const DROP_ALG1_PSCL_SHIFT: u32 = 3; +pub const DROP_ALG1_PSCL_1_4: u32 = 0; +pub const DROP_ALG1_PSCL_1_8: u32 = 1; +pub const DROP_ALG1_PSCL_1_16: u32 = 2; +pub const DROP_ALG1_PSCL_1_32: u32 = 3; +pub const DROP_ALG1_TIMESLOT_MASK: u32 = 0x7; +pub const DROP_ALG1_TIMESLOT_SHIFT: u32 = 0; +pub const DROP_ALG1_TIMESLOT_4MS: u32 = 0; +pub const DROP_ALG1_TIMESLOT_8MS: u32 = 1; +pub const DROP_ALG1_TIMESLOT_16MS: u32 = 2; +pub const DROP_ALG1_TIMESLOT_32MS: u32 = 3; +pub const DROP_ALG1_TIMESLOT_64MS: u32 = 4; +pub const DROP_ALG1_TIMESLOT_128MS: u32 = 5; +pub const DROP_ALG1_TIMESLOT_256MS: u32 = 6; +pub const DROP_ALG1_TIMESLOT_512MS: u32 = 7; + +pub const DROP_ALG2: u32 = 0x163C; +pub const DROP_ALG2_SMPLTIME_MASK: u32 = 0xF; +pub const DROP_ALG2_SMPLTIME_SHIFT: u32 = 24; +pub const DROP_ALG2_LWQBW_MASK: u32 = 0xFFFFFF; +pub const DROP_ALG2_LWQBW_SHIFT: u32 = 0; + +pub const SMB_TIMER: u32 = 0x15C4; + +pub const TINT_TPD_THRSHLD: u32 = 0x15C8; + +pub const TINT_TIMER: u32 = 0x15CC; + +pub const CLK_GATE: u32 = 0x1814; +/* bit[8:6]: for: u32 = B0+ */ +pub const CLK_GATE_125M_SW_DIS_CR: u32 = 1 << 8; +pub const CLK_GATE_125M_SW_AZ: u32 = 1 << 7; +pub const CLK_GATE_125M_SW_IDLE: u32 = 1 << 6; +pub const CLK_GATE_RXMAC: u32 = 1 << 5; +pub const CLK_GATE_TXMAC: u32 = 1 << 4; +pub const CLK_GATE_RXQ: u32 = 1 << 3; +pub const CLK_GATE_TXQ: u32 = 1 << 2; +pub const CLK_GATE_DMAR: u32 = 1 << 1; +pub const CLK_GATE_DMAW: u32 = 1 << 0; +pub const CLK_GATE_ALL_A0: u32 = + CLK_GATE_RXMAC | CLK_GATE_TXMAC | CLK_GATE_RXQ | CLK_GATE_TXQ | CLK_GATE_DMAR | CLK_GATE_DMAW; +pub const CLK_GATE_ALL_B0: u32 = CLK_GATE_ALL_A0; + +/* PORST affect */ +pub const BTROM_CFG: u32 = 0x1800; + +/* interop between drivers */ +pub const DRV: u32 = 0x1804; +pub const DRV_PHY_AUTO: u32 = 1 << 28; +pub const DRV_PHY_1000: u32 = 1 << 27; +pub const DRV_PHY_100: u32 = 1 << 26; +pub const DRV_PHY_10: u32 = 1 << 25; +pub const DRV_PHY_DUPLEX: u32 = 1 << 24; +/* bit23: adv Pause */ +pub const DRV_PHY_PAUSE: u32 = 1 << 23; +/* bit22: adv Asym Pause */ +pub const DRV_PHY_APAUSE: u32 = 1 << 22; +/* bit21:: u32 = 1:en AZ */ +pub const DRV_PHY_EEE: u32 = 1 << 21; +pub const DRV_PHY_MASK: u32 = 0xFF; +pub const DRV_PHY_SHIFT: u32 = 21; +pub const DRV_PHY_UNKNOWN: u32 = 0; +pub const DRV_DISABLE: u32 = 1 << 18; +pub const DRV_WOLS5_EN: u32 = 1 << 17; +pub const DRV_WOLS5_BIOS_EN: u32 = 1 << 16; +pub const DRV_AZ_EN: u32 = 1 << 12; +pub const DRV_WOLPATTERN_EN: u32 = 1 << 11; +pub const DRV_WOLLINKUP_EN: u32 = 1 << 10; +pub const DRV_WOLMAGIC_EN: u32 = 1 << 9; +pub const DRV_WOLCAP_BIOS_EN: u32 = 1 << 8; +pub const DRV_ASPM_SPD1000LMT_MASK: u32 = 0x3; +pub const DRV_ASPM_SPD1000LMT_SHIFT: u32 = 4; +pub const DRV_ASPM_SPD1000LMT_100M: u32 = 0; +pub const DRV_ASPM_SPD1000LMT_NO: u32 = 1; +pub const DRV_ASPM_SPD1000LMT_1M: u32 = 2; +pub const DRV_ASPM_SPD1000LMT_10M: u32 = 3; +pub const DRV_ASPM_SPD100LMT_MASK: u32 = 0x3; +pub const DRV_ASPM_SPD100LMT_SHIFT: u32 = 2; +pub const DRV_ASPM_SPD100LMT_1M: u32 = 0; +pub const DRV_ASPM_SPD100LMT_10M: u32 = 1; +pub const DRV_ASPM_SPD100LMT_100M: u32 = 2; +pub const DRV_ASPM_SPD100LMT_NO: u32 = 3; +pub const DRV_ASPM_SPD10LMT_MASK: u32 = 0x3; +pub const DRV_ASPM_SPD10LMT_SHIFT: u32 = 0; +pub const DRV_ASPM_SPD10LMT_1M: u32 = 0; +pub const DRV_ASPM_SPD10LMT_10M: u32 = 1; +pub const DRV_ASPM_SPD10LMT_100M: u32 = 2; +pub const DRV_ASPM_SPD10LMT_NO: u32 = 3; + +/* flag of phy inited */ +pub const PHY_INITED: u16 = 0x003F; + +/* PERST affect */ +pub const DRV_ERR1: u32 = 0x1808; +pub const DRV_ERR1_GEN: u32 = 1 << 31; +pub const DRV_ERR1_NOR: u32 = 1 << 30; +pub const DRV_ERR1_TRUNC: u32 = 1 << 29; +pub const DRV_ERR1_RES: u32 = 1 << 28; +pub const DRV_ERR1_INTFATAL: u32 = 1 << 27; +pub const DRV_ERR1_TXQPEND: u32 = 1 << 26; +pub const DRV_ERR1_DMAW: u32 = 1 << 25; +pub const DRV_ERR1_DMAR: u32 = 1 << 24; +pub const DRV_ERR1_PCIELNKDWN: u32 = 1 << 23; +pub const DRV_ERR1_PKTSIZE: u32 = 1 << 22; +pub const DRV_ERR1_FIFOFUL: u32 = 1 << 21; +pub const DRV_ERR1_RFDUR: u32 = 1 << 20; +pub const DRV_ERR1_RRDSI: u32 = 1 << 19; +pub const DRV_ERR1_UPDATE: u32 = 1 << 18; + +pub const DRV_ERR2: u32 = 0x180C; + +pub const DBG_ADDR: u32 = 0x1900; +pub const DBG_DATA: u32 = 0x1904; + +pub const SYNC_IPV4_SA: u32 = 0x1A00; +pub const SYNC_IPV4_DA: u32 = 0x1A04; + +pub const SYNC_V4PORT: u32 = 0x1A08; +pub const SYNC_V4PORT_DST_MASK: u32 = 0xFFFF; +pub const SYNC_V4PORT_DST_SHIFT: u32 = 16; +pub const SYNC_V4PORT_SRC_MASK: u32 = 0xFFFF; +pub const SYNC_V4PORT_SRC_SHIFT: u32 = 0; + +pub const SYNC_IPV6_SA0: u32 = 0x1A0C; +pub const SYNC_IPV6_SA1: u32 = 0x1A10; +pub const SYNC_IPV6_SA2: u32 = 0x1A14; +pub const SYNC_IPV6_SA3: u32 = 0x1A18; +pub const SYNC_IPV6_DA0: u32 = 0x1A1C; +pub const SYNC_IPV6_DA1: u32 = 0x1A20; +pub const SYNC_IPV6_DA2: u32 = 0x1A24; +pub const SYNC_IPV6_DA3: u32 = 0x1A28; + +pub const SYNC_V6PORT: u32 = 0x1A2C; +pub const SYNC_V6PORT_DST_MASK: u32 = 0xFFFF; +pub const SYNC_V6PORT_DST_SHIFT: u32 = 16; +pub const SYNC_V6PORT_SRC_MASK: u32 = 0xFFFF; +pub const SYNC_V6PORT_SRC_SHIFT: u32 = 0; + +pub const ARP_REMOTE_IPV4: u32 = 0x1A30; +pub const ARP_HOST_IPV4: u32 = 0x1A34; +pub const ARP_MAC0: u32 = 0x1A38; +pub const ARP_MAC1: u32 = 0x1A3C; + +pub const FIRST_REMOTE_IPV6_0: u32 = 0x1A40; +pub const FIRST_REMOTE_IPV6_1: u32 = 0x1A44; +pub const FIRST_REMOTE_IPV6_2: u32 = 0x1A48; +pub const FIRST_REMOTE_IPV6_3: u32 = 0x1A4C; + +pub const FIRST_SN_IPV6_0: u32 = 0x1A50; +pub const FIRST_SN_IPV6_1: u32 = 0x1A54; +pub const FIRST_SN_IPV6_2: u32 = 0x1A58; +pub const FIRST_SN_IPV6_3: u32 = 0x1A5C; + +pub const FIRST_TAR_IPV6_1_0: u32 = 0x1A60; +pub const FIRST_TAR_IPV6_1_1: u32 = 0x1A64; +pub const FIRST_TAR_IPV6_1_2: u32 = 0x1A68; +pub const FIRST_TAR_IPV6_1_3: u32 = 0x1A6C; +pub const FIRST_TAR_IPV6_2_0: u32 = 0x1A70; +pub const FIRST_TAR_IPV6_2_1: u32 = 0x1A74; +pub const FIRST_TAR_IPV6_2_2: u32 = 0x1A78; +pub const FIRST_TAR_IPV6_2_3: u32 = 0x1A7C; + +pub const SECOND_REMOTE_IPV6_0: u32 = 0x1A80; +pub const SECOND_REMOTE_IPV6_1: u32 = 0x1A84; +pub const SECOND_REMOTE_IPV6_2: u32 = 0x1A88; +pub const SECOND_REMOTE_IPV6_3: u32 = 0x1A8C; + +pub const SECOND_SN_IPV6_0: u32 = 0x1A90; +pub const SECOND_SN_IPV6_1: u32 = 0x1A94; +pub const SECOND_SN_IPV6_2: u32 = 0x1A98; +pub const SECOND_SN_IPV6_3: u32 = 0x1A9C; + +pub const SECOND_TAR_IPV6_1_0: u32 = 0x1AA0; +pub const SECOND_TAR_IPV6_1_1: u32 = 0x1AA4; +pub const SECOND_TAR_IPV6_1_2: u32 = 0x1AA8; +pub const SECOND_TAR_IPV6_1_3: u32 = 0x1AAC; +pub const SECOND_TAR_IPV6_2_0: u32 = 0x1AB0; +pub const SECOND_TAR_IPV6_2_1: u32 = 0x1AB4; +pub const SECOND_TAR_IPV6_2_2: u32 = 0x1AB8; +pub const SECOND_TAR_IPV6_2_3: u32 = 0x1ABC; + +pub const FIRST_NS_MAC0: u32 = 0x1AC0; +pub const FIRST_NS_MAC1: u32 = 0x1AC4; + +pub const SECOND_NS_MAC0: u32 = 0x1AC8; +pub const SECOND_NS_MAC1: u32 = 0x1ACC; + +pub const PMOFLD: u32 = 0x144C; +/* bit[11:10]: for: u32 = B0+ */ +pub const PMOFLD_ECMA_IGNR_FRG_SSSR: u32 = 1 << 11; +pub const PMOFLD_ARP_CNFLCT_WAKEUP: u32 = 1 << 10; +pub const PMOFLD_MULTI_SOLD: u32 = 1 << 9; +pub const PMOFLD_ICMP_XSUM: u32 = 1 << 8; +pub const PMOFLD_GARP_REPLY: u32 = 1 << 7; +pub const PMOFLD_SYNCV6_ANY: u32 = 1 << 6; +pub const PMOFLD_SYNCV4_ANY: u32 = 1 << 5; +pub const PMOFLD_BY_HW: u32 = 1 << 4; +pub const PMOFLD_NS_EN: u32 = 1 << 3; +pub const PMOFLD_ARP_EN: u32 = 1 << 2; +pub const PMOFLD_SYNCV6_EN: u32 = 1 << 1; +pub const PMOFLD_SYNCV4_EN: u32 = 1 << 0; + +/* reg: u32 = 1830 ~: u32 = 186C for C0+,: u32 = 16 bit map patterns and wake packet detection */ +pub const WOL_CTRL2: u32 = 0x1830; +pub const WOL_CTRL2_DATA_STORE: u32 = 1 << 3; +pub const WOL_CTRL2_PTRN_EVT: u32 = 1 << 2; +pub const WOL_CTRL2_PME_PTRN_EN: u32 = 1 << 1; +pub const WOL_CTRL2_PTRN_EN: u32 = 1 << 0; + +pub const WOL_CTRL3: u32 = 0x1834; +pub const WOL_CTRL3_PTRN_ADDR_MASK: u32 = 0xFFFFF; +pub const WOL_CTRL3_PTRN_ADDR_SHIFT: u32 = 0; + +pub const WOL_CTRL4: u32 = 0x1838; +pub const WOL_CTRL4_PT15_MATCH: u32 = 1 << 31; +pub const WOL_CTRL4_PT14_MATCH: u32 = 1 << 30; +pub const WOL_CTRL4_PT13_MATCH: u32 = 1 << 29; +pub const WOL_CTRL4_PT12_MATCH: u32 = 1 << 28; +pub const WOL_CTRL4_PT11_MATCH: u32 = 1 << 27; +pub const WOL_CTRL4_PT10_MATCH: u32 = 1 << 26; +pub const WOL_CTRL4_PT9_MATCH: u32 = 1 << 25; +pub const WOL_CTRL4_PT8_MATCH: u32 = 1 << 24; +pub const WOL_CTRL4_PT7_MATCH: u32 = 1 << 23; +pub const WOL_CTRL4_PT6_MATCH: u32 = 1 << 22; +pub const WOL_CTRL4_PT5_MATCH: u32 = 1 << 21; +pub const WOL_CTRL4_PT4_MATCH: u32 = 1 << 20; +pub const WOL_CTRL4_PT3_MATCH: u32 = 1 << 19; +pub const WOL_CTRL4_PT2_MATCH: u32 = 1 << 18; +pub const WOL_CTRL4_PT1_MATCH: u32 = 1 << 17; +pub const WOL_CTRL4_PT0_MATCH: u32 = 1 << 16; +pub const WOL_CTRL4_PT15_EN: u32 = 1 << 15; +pub const WOL_CTRL4_PT14_EN: u32 = 1 << 14; +pub const WOL_CTRL4_PT13_EN: u32 = 1 << 13; +pub const WOL_CTRL4_PT12_EN: u32 = 1 << 12; +pub const WOL_CTRL4_PT11_EN: u32 = 1 << 11; +pub const WOL_CTRL4_PT10_EN: u32 = 1 << 10; +pub const WOL_CTRL4_PT9_EN: u32 = 1 << 9; +pub const WOL_CTRL4_PT8_EN: u32 = 1 << 8; +pub const WOL_CTRL4_PT7_EN: u32 = 1 << 7; +pub const WOL_CTRL4_PT6_EN: u32 = 1 << 6; +pub const WOL_CTRL4_PT5_EN: u32 = 1 << 5; +pub const WOL_CTRL4_PT4_EN: u32 = 1 << 4; +pub const WOL_CTRL4_PT3_EN: u32 = 1 << 3; +pub const WOL_CTRL4_PT2_EN: u32 = 1 << 2; +pub const WOL_CTRL4_PT1_EN: u32 = 1 << 1; +pub const WOL_CTRL4_PT0_EN: u32 = 1 << 0; + +pub const WOL_CTRL5: u32 = 0x183C; +pub const WOL_CTRL5_PT3_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT3_LEN_SHIFT: u32 = 24; +pub const WOL_CTRL5_PT2_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT2_LEN_SHIFT: u32 = 16; +pub const WOL_CTRL5_PT1_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT1_LEN_SHIFT: u32 = 8; +pub const WOL_CTRL5_PT0_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT0_LEN_SHIFT: u32 = 0; + +pub const WOL_CTRL6: u32 = 0x1840; +pub const WOL_CTRL5_PT7_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT7_LEN_SHIFT: u32 = 24; +pub const WOL_CTRL5_PT6_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT6_LEN_SHIFT: u32 = 16; +pub const WOL_CTRL5_PT5_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT5_LEN_SHIFT: u32 = 8; +pub const WOL_CTRL5_PT4_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT4_LEN_SHIFT: u32 = 0; + +pub const WOL_CTRL7: u32 = 0x1844; +pub const WOL_CTRL5_PT11_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT11_LEN_SHIFT: u32 = 24; +pub const WOL_CTRL5_PT10_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT10_LEN_SHIFT: u32 = 16; +pub const WOL_CTRL5_PT9_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT9_LEN_SHIFT: u32 = 8; +pub const WOL_CTRL5_PT8_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT8_LEN_SHIFT: u32 = 0; + +pub const WOL_CTRL8: u32 = 0x1848; +pub const WOL_CTRL5_PT15_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT15_LEN_SHIFT: u32 = 24; +pub const WOL_CTRL5_PT14_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT14_LEN_SHIFT: u32 = 16; +pub const WOL_CTRL5_PT13_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT13_LEN_SHIFT: u32 = 8; +pub const WOL_CTRL5_PT12_LEN_MASK: u32 = 0xFF; +pub const WOL_CTRL5_PT12_LEN_SHIFT: u32 = 0; + +pub const ACER_FIXED_PTN0: u32 = 0x1850; +pub const ACER_FIXED_PTN0_MASK: u32 = 0xFFFFFFFF; +pub const ACER_FIXED_PTN0_SHIFT: u32 = 0; + +pub const ACER_FIXED_PTN1: u32 = 0x1854; +pub const ACER_FIXED_PTN1_MASK: u32 = 0xFFFF; +pub const ACER_FIXED_PTN1_SHIFT: u32 = 0; + +pub const ACER_RANDOM_NUM0: u32 = 0x1858; +pub const ACER_RANDOM_NUM0_MASK: u32 = 0xFFFFFFFF; +pub const ACER_RANDOM_NUM0_SHIFT: u32 = 0; + +pub const ACER_RANDOM_NUM1: u32 = 0x185C; +pub const ACER_RANDOM_NUM1_MASK: u32 = 0xFFFFFFFF; +pub const ACER_RANDOM_NUM1_SHIFT: u32 = 0; + +pub const ACER_RANDOM_NUM2: u32 = 0x1860; +pub const ACER_RANDOM_NUM2_MASK: u32 = 0xFFFFFFFF; +pub const ACER_RANDOM_NUM2_SHIFT: u32 = 0; + +pub const ACER_RANDOM_NUM3: u32 = 0x1864; +pub const ACER_RANDOM_NUM3_MASK: u32 = 0xFFFFFFFF; +pub const ACER_RANDOM_NUM3_SHIFT: u32 = 0; + +pub const ACER_MAGIC: u32 = 0x1868; +pub const ACER_MAGIC_EN: u32 = 1 << 31; +pub const ACER_MAGIC_PME_EN: u32 = 1 << 30; +pub const ACER_MAGIC_MATCH: u32 = 1 << 29; +pub const ACER_MAGIC_FF_CHECK: u32 = 1 << 10; +pub const ACER_MAGIC_RAN_LEN_MASK: u32 = 0x1F; +pub const ACER_MAGIC_RAN_LEN_SHIFT: u32 = 5; +pub const ACER_MAGIC_FIX_LEN_MASK: u32 = 0x1F; +pub const ACER_MAGIC_FIX_LEN_SHIFT: u32 = 0; + +pub const ACER_TIMER: u32 = 0x186C; +pub const ACER_TIMER_EN: u32 = 1 << 31; +pub const ACER_TIMER_PME_EN: u32 = 1 << 30; +pub const ACER_TIMER_MATCH: u32 = 1 << 29; +pub const ACER_TIMER_THRES_MASK: u32 = 0x1FFFF; +pub const ACER_TIMER_THRES_SHIFT: u32 = 0; +pub const ACER_TIMER_THRES_DEF: u32 = 1; + +/* RSS definitions */ +pub const RSS_KEY0: u32 = 0x14B0; +pub const RSS_KEY1: u32 = 0x14B4; +pub const RSS_KEY2: u32 = 0x14B8; +pub const RSS_KEY3: u32 = 0x14BC; +pub const RSS_KEY4: u32 = 0x14C0; +pub const RSS_KEY5: u32 = 0x14C4; +pub const RSS_KEY6: u32 = 0x14C8; +pub const RSS_KEY7: u32 = 0x14CC; +pub const RSS_KEY8: u32 = 0x14D0; +pub const RSS_KEY9: u32 = 0x14D4; + +pub const RSS_IDT_TBL0: u32 = 0x1B00; +pub const RSS_IDT_TBL1: u32 = 0x1B04; +pub const RSS_IDT_TBL2: u32 = 0x1B08; +pub const RSS_IDT_TBL3: u32 = 0x1B0C; +pub const RSS_IDT_TBL4: u32 = 0x1B10; +pub const RSS_IDT_TBL5: u32 = 0x1B14; +pub const RSS_IDT_TBL6: u32 = 0x1B18; +pub const RSS_IDT_TBL7: u32 = 0x1B1C; +pub const RSS_IDT_TBL8: u32 = 0x1B20; +pub const RSS_IDT_TBL9: u32 = 0x1B24; +pub const RSS_IDT_TBL10: u32 = 0x1B28; +pub const RSS_IDT_TBL11: u32 = 0x1B2C; +pub const RSS_IDT_TBL12: u32 = 0x1B30; +pub const RSS_IDT_TBL13: u32 = 0x1B34; +pub const RSS_IDT_TBL14: u32 = 0x1B38; +pub const RSS_IDT_TBL15: u32 = 0x1B3C; +pub const RSS_IDT_TBL16: u32 = 0x1B40; +pub const RSS_IDT_TBL17: u32 = 0x1B44; +pub const RSS_IDT_TBL18: u32 = 0x1B48; +pub const RSS_IDT_TBL19: u32 = 0x1B4C; +pub const RSS_IDT_TBL20: u32 = 0x1B50; +pub const RSS_IDT_TBL21: u32 = 0x1B54; +pub const RSS_IDT_TBL22: u32 = 0x1B58; +pub const RSS_IDT_TBL23: u32 = 0x1B5C; +pub const RSS_IDT_TBL24: u32 = 0x1B60; +pub const RSS_IDT_TBL25: u32 = 0x1B64; +pub const RSS_IDT_TBL26: u32 = 0x1B68; +pub const RSS_IDT_TBL27: u32 = 0x1B6C; +pub const RSS_IDT_TBL28: u32 = 0x1B70; +pub const RSS_IDT_TBL29: u32 = 0x1B74; +pub const RSS_IDT_TBL30: u32 = 0x1B78; +pub const RSS_IDT_TBL31: u32 = 0x1B7C; + +pub const RSS_HASH_VAL: u32 = 0x15B0; +pub const RSS_HASH_FLAG: u32 = 0x15B4; + +pub const RSS_BASE_CPU_NUM: u32 = 0x15B8; + +pub const MSI_MAP_TBL1: u32 = 0x15D0; +pub const MSI_MAP_TBL1_ALERT_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_ALERT_SHIFT: u32 = 28; +pub const MSI_MAP_TBL1_TIMER_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_TIMER_SHIFT: u32 = 24; +pub const MSI_MAP_TBL1_TXQ1_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_TXQ1_SHIFT: u32 = 20; +pub const MSI_MAP_TBL1_TXQ0_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_TXQ0_SHIFT: u32 = 16; +pub const MSI_MAP_TBL1_RXQ3_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_RXQ3_SHIFT: u32 = 12; +pub const MSI_MAP_TBL1_RXQ2_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_RXQ2_SHIFT: u32 = 8; +pub const MSI_MAP_TBL1_RXQ1_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_RXQ1_SHIFT: u32 = 4; +pub const MSI_MAP_TBL1_RXQ0_MASK: u32 = 0xF; +pub const MSI_MAP_TBL1_RXQ0_SHIFT: u32 = 0; + +pub const MSI_MAP_TBL2: u32 = 0x15D8; +pub const MSI_MAP_TBL2_PHY_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_PHY_SHIFT: u32 = 28; +pub const MSI_MAP_TBL2_SMB_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_SMB_SHIFT: u32 = 24; +pub const MSI_MAP_TBL2_TXQ3_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_TXQ3_SHIFT: u32 = 20; +pub const MSI_MAP_TBL2_TXQ2_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_TXQ2_SHIFT: u32 = 16; +pub const MSI_MAP_TBL2_RXQ7_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_RXQ7_SHIFT: u32 = 12; +pub const MSI_MAP_TBL2_RXQ6_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_RXQ6_SHIFT: u32 = 8; +pub const MSI_MAP_TBL2_RXQ5_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_RXQ5_SHIFT: u32 = 4; +pub const MSI_MAP_TBL2_RXQ4_MASK: u32 = 0xF; +pub const MSI_MAP_TBL2_RXQ4_SHIFT: u32 = 0; + +pub const MSI_ID_MAP: u32 = 0x15D4; +pub const MSI_ID_MAP_RXQ7: u32 = 1 << 30; +pub const MSI_ID_MAP_RXQ6: u32 = 1 << 29; +pub const MSI_ID_MAP_RXQ5: u32 = 1 << 28; +pub const MSI_ID_MAP_RXQ4: u32 = 1 << 27; +/* bit26:: u32 = 0:common,1:timer */ +pub const MSI_ID_MAP_PCIELNKDW: u32 = 1 << 26; +pub const MSI_ID_MAP_PCIECERR: u32 = 1 << 25; +pub const MSI_ID_MAP_PCIENFERR: u32 = 1 << 24; +pub const MSI_ID_MAP_PCIEFERR: u32 = 1 << 23; +pub const MSI_ID_MAP_PCIEUR: u32 = 1 << 22; +pub const MSI_ID_MAP_MACTX: u32 = 1 << 21; +pub const MSI_ID_MAP_MACRX: u32 = 1 << 20; +pub const MSI_ID_MAP_RXQ3: u32 = 1 << 19; +pub const MSI_ID_MAP_RXQ2: u32 = 1 << 18; +pub const MSI_ID_MAP_RXQ1: u32 = 1 << 17; +pub const MSI_ID_MAP_RXQ0: u32 = 1 << 16; +pub const MSI_ID_MAP_TXQ0: u32 = 1 << 15; +pub const MSI_ID_MAP_TXQTO: u32 = 1 << 14; +pub const MSI_ID_MAP_LPW: u32 = 1 << 13; +pub const MSI_ID_MAP_PHY: u32 = 1 << 12; +pub const MSI_ID_MAP_TXCREDIT: u32 = 1 << 11; +pub const MSI_ID_MAP_DMAW: u32 = 1 << 10; +pub const MSI_ID_MAP_DMAR: u32 = 1 << 9; +pub const MSI_ID_MAP_TXFUR: u32 = 1 << 8; +pub const MSI_ID_MAP_TXQ3: u32 = 1 << 7; +pub const MSI_ID_MAP_TXQ2: u32 = 1 << 6; +pub const MSI_ID_MAP_TXQ1: u32 = 1 << 5; +pub const MSI_ID_MAP_RFDUR: u32 = 1 << 4; +pub const MSI_ID_MAP_RXFOV: u32 = 1 << 3; +pub const MSI_ID_MAP_MANU: u32 = 1 << 2; +pub const MSI_ID_MAP_TIMER: u32 = 1 << 1; +pub const MSI_ID_MAP_SMB: u32 = 1 << 0; + +pub const MSI_RETRANS_TIMER: u32 = 0x1920; +/* bit16:: u32 = 1:line,0:standard */ +pub const MSI_MASK_SEL_LINE: u32 = 1 << 16; +pub const MSI_RETRANS_TM_MASK: u32 = 0xFFFF; +pub const MSI_RETRANS_TM_SHIFT: u32 = 0; + +pub const CR_DMA_CTRL: u32 = 0x1930; +pub const CR_DMA_CTRL_PRI: u32 = 1 << 22; +pub const CR_DMA_CTRL_RRDRXD_JOINT: u32 = 1 << 21; +pub const CR_DMA_CTRL_BWCREDIT_MASK: u32 = 0x3; +pub const CR_DMA_CTRL_BWCREDIT_SHIFT: u32 = 19; +pub const CR_DMA_CTRL_BWCREDIT_2KB: u32 = 0; +pub const CR_DMA_CTRL_BWCREDIT_1KB: u32 = 1; +pub const CR_DMA_CTRL_BWCREDIT_4KB: u32 = 2; +pub const CR_DMA_CTRL_BWCREDIT_8KB: u32 = 3; +pub const CR_DMA_CTRL_BW_EN: u32 = 1 << 18; +pub const CR_DMA_CTRL_BW_RATIO_MASK: u32 = 0x3; +pub const CR_DMA_CTRL_BW_RATIO_1_2: u32 = 0; +pub const CR_DMA_CTRL_BW_RATIO_1_4: u32 = 1; +pub const CR_DMA_CTRL_BW_RATIO_1_8: u32 = 2; +pub const CR_DMA_CTRL_BW_RATIO_2_1: u32 = 3; +pub const CR_DMA_CTRL_SOFT_RST: u32 = 1 << 11; +pub const CR_DMA_CTRL_TXEARLY_EN: u32 = 1 << 10; +pub const CR_DMA_CTRL_RXEARLY_EN: u32 = 1 << 9; +pub const CR_DMA_CTRL_WEARLY_EN: u32 = 1 << 8; +pub const CR_DMA_CTRL_RXTH_MASK: u32 = 0xF; +pub const CR_DMA_CTRL_WTH_MASK: u32 = 0xF; + +pub const EFUSE_BIST: u32 = 0x1934; +pub const EFUSE_BIST_COL_MASK: u32 = 0x3F; +pub const EFUSE_BIST_COL_SHIFT: u32 = 24; +pub const EFUSE_BIST_ROW_MASK: u32 = 0x7F; +pub const EFUSE_BIST_ROW_SHIFT: u32 = 12; +pub const EFUSE_BIST_STEP_MASK: u32 = 0xF; +pub const EFUSE_BIST_STEP_SHIFT: u32 = 8; +pub const EFUSE_BIST_PAT_MASK: u32 = 0x7; +pub const EFUSE_BIST_PAT_SHIFT: u32 = 4; +pub const EFUSE_BIST_CRITICAL: u32 = 1 << 3; +pub const EFUSE_BIST_FIXED: u32 = 1 << 2; +pub const EFUSE_BIST_FAIL: u32 = 1 << 1; +pub const EFUSE_BIST_NOW: u32 = 1 << 0; + +/* CR DMA ctrl */ + +/* TX QoS */ +pub const WRR: u32 = 0x1938; +pub const WRR_PRI_MASK: u32 = 0x3; +pub const WRR_PRI_SHIFT: u32 = 29; +pub const WRR_PRI_RESTRICT_ALL: u32 = 0; +pub const WRR_PRI_RESTRICT_HI: u32 = 1; +pub const WRR_PRI_RESTRICT_HI2: u32 = 2; +pub const WRR_PRI_RESTRICT_NONE: u32 = 3; +pub const WRR_PRI3_MASK: u32 = 0x1F; +pub const WRR_PRI3_SHIFT: u32 = 24; +pub const WRR_PRI2_MASK: u32 = 0x1F; +pub const WRR_PRI2_SHIFT: u32 = 16; +pub const WRR_PRI1_MASK: u32 = 0x1F; +pub const WRR_PRI1_SHIFT: u32 = 8; +pub const WRR_PRI0_MASK: u32 = 0x1F; +pub const WRR_PRI0_SHIFT: u32 = 0; + +pub const HQTPD: u32 = 0x193C; +pub const HQTPD_BURST_EN: u32 = 1 << 31; +pub const HQTPD_Q3_NUMPREF_MASK: u32 = 0xF; +pub const HQTPD_Q3_NUMPREF_SHIFT: u32 = 8; +pub const HQTPD_Q2_NUMPREF_MASK: u32 = 0xF; +pub const HQTPD_Q2_NUMPREF_SHIFT: u32 = 4; +pub const HQTPD_Q1_NUMPREF_MASK: u32 = 0xF; +pub const HQTPD_Q1_NUMPREF_SHIFT: u32 = 0; + +pub const CPUMAP1: u32 = 0x19A0; +pub const CPUMAP1_VCT7_MASK: u32 = 0xF; +pub const CPUMAP1_VCT7_SHIFT: u32 = 28; +pub const CPUMAP1_VCT6_MASK: u32 = 0xF; +pub const CPUMAP1_VCT6_SHIFT: u32 = 24; +pub const CPUMAP1_VCT5_MASK: u32 = 0xF; +pub const CPUMAP1_VCT5_SHIFT: u32 = 20; +pub const CPUMAP1_VCT4_MASK: u32 = 0xF; +pub const CPUMAP1_VCT4_SHIFT: u32 = 16; +pub const CPUMAP1_VCT3_MASK: u32 = 0xF; +pub const CPUMAP1_VCT3_SHIFT: u32 = 12; +pub const CPUMAP1_VCT2_MASK: u32 = 0xF; +pub const CPUMAP1_VCT2_SHIFT: u32 = 8; +pub const CPUMAP1_VCT1_MASK: u32 = 0xF; +pub const CPUMAP1_VCT1_SHIFT: u32 = 4; +pub const CPUMAP1_VCT0_MASK: u32 = 0xF; +pub const CPUMAP1_VCT0_SHIFT: u32 = 0; + +pub const CPUMAP2: u32 = 0x19A4; +pub const CPUMAP2_VCT15_MASK: u32 = 0xF; +pub const CPUMAP2_VCT15_SHIFT: u32 = 28; +pub const CPUMAP2_VCT14_MASK: u32 = 0xF; +pub const CPUMAP2_VCT14_SHIFT: u32 = 24; +pub const CPUMAP2_VCT13_MASK: u32 = 0xF; +pub const CPUMAP2_VCT13_SHIFT: u32 = 20; +pub const CPUMAP2_VCT12_MASK: u32 = 0xF; +pub const CPUMAP2_VCT12_SHIFT: u32 = 16; +pub const CPUMAP2_VCT11_MASK: u32 = 0xF; +pub const CPUMAP2_VCT11_SHIFT: u32 = 12; +pub const CPUMAP2_VCT10_MASK: u32 = 0xF; +pub const CPUMAP2_VCT10_SHIFT: u32 = 8; +pub const CPUMAP2_VCT9_MASK: u32 = 0xF; +pub const CPUMAP2_VCT9_SHIFT: u32 = 4; +pub const CPUMAP2_VCT8_MASK: u32 = 0xF; +pub const CPUMAP2_VCT8_SHIFT: u32 = 0; + +pub const MISC: u32 = 0x19C0; +/* bit31:: u32 = 0:vector,1:cpu */ +pub const MISC_MODU: u32 = 1 << 31; +pub const MISC_OVERCUR: u32 = 1 << 29; +pub const MISC_PSWR_EN: u32 = 1 << 28; +pub const MISC_PSW_CTRL_MASK: u32 = 0xF; +pub const MISC_PSW_CTRL_SHIFT: u32 = 24; +pub const MISC_PSW_OCP_MASK: u32 = 0x7; +pub const MISC_PSW_OCP_SHIFT: u32 = 21; +pub const MISC_PSW_OCP_DEF: u32 = 0x7; +pub const MISC_V18_HIGH: u32 = 1 << 20; +pub const MISC_LPO_CTRL_MASK: u32 = 0xF; +pub const MISC_LPO_CTRL_SHIFT: u32 = 16; +pub const MISC_ISO_EN: u32 = 1 << 12; +pub const MISC_XSTANA_ALWAYS_ON: u32 = 1 << 11; +pub const MISC_SYS25M_SEL_ADAPTIVE: u32 = 1 << 10; +pub const MISC_SPEED_SIM: u32 = 1 << 9; +pub const MISC_S1_LWP_EN: u32 = 1 << 8; +/* bit7: pcie/mac do pwsaving as phy in lpw state */ +pub const MISC_MACLPW: u32 = 1 << 7; +pub const MISC_125M_SW: u32 = 1 << 6; +pub const MISC_INTNLOSC_OFF_EN: u32 = 1 << 5; +/* bit4:: u32 = 0:chipset,1:crystle */ +pub const MISC_EXTN25M_SEL: u32 = 1 << 4; +pub const MISC_INTNLOSC_OPEN: u32 = 1 << 3; +pub const MISC_SMBUS_AT_LED: u32 = 1 << 2; +pub const MISC_PPS_AT_LED_MASK: u32 = 0x3; +pub const MISC_PPS_AT_LED_SHIFT: u32 = 0; +pub const MISC_PPS_AT_LED_ACT: u32 = 1; +pub const MISC_PPS_AT_LED_10_100: u32 = 2; +pub const MISC_PPS_AT_LED_1000: u32 = 3; + +pub const MISC1: u32 = 0x19C4; +pub const MSC1_BLK_CRASPM_REQ: u32 = 1 << 15; + +pub const MSIC2: u32 = 0x19C8; +pub const MSIC2_CALB_START: u32 = 1 << 0; + +pub const MISC3: u32 = 0x19CC; +/* bit1:: u32 = 1:Software control: u32 = 25M */ +pub const MISC3_25M_BY_SW: u32 = 1 << 1; +/* bit0:: u32 = 25M switch to intnl OSC */ +pub const MISC3_25M_NOTO_INTNL: u32 = 1 << 0; + +/* MSIX tbl in memory space */ +pub const MSIX_ENTRY_BASE: u32 = 0x2000; + +/***************************** IO mapping registers ***************************/ +pub const IO_ADDR: u32 = 0x00; +pub const IO_DATA: u32 = 0x04; +/* same as reg1400 */ +pub const IO_MASTER: u32 = 0x08; +/* same as reg1480 */ +pub const IO_MAC_CTRL: u32 = 0x0C; +/* same as reg1600 */ +pub const IO_ISR: u32 = 0x10; +/* same as reg: u32 = 1604 */ +pub const IO_IMR: u32 = 0x14; +/* word, same as reg15F0 */ +pub const IO_TPD_PRI1_PIDX: u32 = 0x18; +/* word, same as reg15F2 */ +pub const IO_TPD_PRI0_PIDX: u32 = 0x1A; +/* word, same as reg15F4 */ +pub const IO_TPD_PRI1_CIDX: u32 = 0x1C; +/* word, same as reg15F6 */ +pub const IO_TPD_PRI0_CIDX: u32 = 0x1E; +/* word, same as reg15E0 */ +pub const IO_RFD_PIDX: u32 = 0x20; +/* word, same as reg15F8 */ +pub const IO_RFD_CIDX: u32 = 0x30; +/* same as reg1414 */ +pub const IO_MDIO: u32 = 0x38; +/* same as reg140C */ +pub const IO_PHY_CTRL: u32 = 0x3C; + +/********************* PHY regs definition ***************************/ + +/* Autoneg Advertisement Register */ +pub const ADVERTISE_SPEED_MASK: u16 = 0x01E0; +pub const ADVERTISE_DEFAULT_CAP: u16 = 0x1DE0; + +/* 1000BASE-T Control Register (0x9) */ +pub const GIGA_CR_1000T_HD_CAPS: u16 = 0x0100; +pub const GIGA_CR_1000T_FD_CAPS: u16 = 0x0200; +pub const GIGA_CR_1000T_REPEATER_DTE: u16 = 0x0400; + +pub const GIGA_CR_1000T_MS_VALUE: u16 = 0x0800; + +pub const GIGA_CR_1000T_MS_ENABLE: u16 = 0x1000; + +pub const GIGA_CR_1000T_TEST_MODE_NORMAL: u16 = 0x0000; +pub const GIGA_CR_1000T_TEST_MODE_1: u16 = 0x2000; +pub const GIGA_CR_1000T_TEST_MODE_2: u16 = 0x4000; +pub const GIGA_CR_1000T_TEST_MODE_3: u16 = 0x6000; +pub const GIGA_CR_1000T_TEST_MODE_4: u16 = 0x8000; +pub const GIGA_CR_1000T_SPEED_MASK: u16 = 0x0300; +pub const GIGA_CR_1000T_DEFAULT_CAP: u16 = 0x0300; + +/* 1000BASE-T Status Register */ +pub const MII_GIGA_SR: u16 = 0x0A; + +/* PHY Specific Status Register */ +pub const MII_GIGA_PSSR: u16 = 0x11; +pub const GIGA_PSSR_FC_RXEN: u16 = 0x0004; +pub const GIGA_PSSR_FC_TXEN: u16 = 0x0008; +pub const GIGA_PSSR_SPD_DPLX_RESOLVED: u16 = 0x0800; +pub const GIGA_PSSR_DPLX: u16 = 0x2000; +pub const GIGA_PSSR_SPEED: u16 = 0xC000; +pub const GIGA_PSSR_10MBS: u16 = 0x0000; +pub const GIGA_PSSR_100MBS: u16 = 0x4000; +pub const GIGA_PSSR_1000MBS: u16 = 0x8000; + +/* PHY Interrupt Enable Register */ +pub const MII_IER: u16 = 0x12; +pub const IER_LINK_UP: u16 = 0x0400; +pub const IER_LINK_DOWN: u16 = 0x0800; + +/* PHY Interrupt Status Register */ +pub const MII_ISR: u16 = 0x13; +pub const ISR_LINK_UP: u16 = 0x0400; +pub const ISR_LINK_DOWN: u16 = 0x0800; + +/* Cable-Detect-Test Control Register */ +pub const MII_CDTC: u16 = 0x16; +/* self clear */ +pub const CDTC_EN: u16 = 1; +pub const CDTC_PAIR_MASK: u16 = 0x3; +pub const CDTC_PAIR_SHIFT: u16 = 8; + +/* Cable-Detect-Test Status Register */ +pub const MII_CDTS: u16 = 0x1C; +pub const CDTS_STATUS_MASK: u16 = 0x3; +pub const CDTS_STATUS_SHIFT: u16 = 8; +pub const CDTS_STATUS_NORMAL: u16 = 0; +pub const CDTS_STATUS_SHORT: u16 = 1; +pub const CDTS_STATUS_OPEN: u16 = 2; +pub const CDTS_STATUS_INVALID: u16 = 3; + +pub const MII_DBG_ADDR: u16 = 0x1D; +pub const MII_DBG_DATA: u16 = 0x1E; + +/***************************** debug port *************************************/ + +pub const MIIDBG_ANACTRL: u16 = 0x00; +pub const ANACTRL_CLK125M_DELAY_EN: u16 = 0x8000; +pub const ANACTRL_VCO_FAST: u16 = 0x4000; +pub const ANACTRL_VCO_SLOW: u16 = 0x2000; +pub const ANACTRL_AFE_MODE_EN: u16 = 0x1000; +pub const ANACTRL_LCKDET_PHY: u16 = 0x0800; +pub const ANACTRL_LCKDET_EN: u16 = 0x0400; +pub const ANACTRL_OEN_125M: u16 = 0x0200; +pub const ANACTRL_HBIAS_EN: u16 = 0x0100; +pub const ANACTRL_HB_EN: u16 = 0x0080; +pub const ANACTRL_SEL_HSP: u16 = 0x0040; +pub const ANACTRL_CLASSA_EN: u16 = 0x0020; +pub const ANACTRL_MANUSWON_SWR_MASK: u16 = 0x3; +pub const ANACTRL_MANUSWON_SWR_SHIFT: u16 = 2; +pub const ANACTRL_MANUSWON_SWR_2V: u16 = 0; +pub const ANACTRL_MANUSWON_SWR_1P9V: u16 = 1; +pub const ANACTRL_MANUSWON_SWR_1P8V: u16 = 2; +pub const ANACTRL_MANUSWON_SWR_1P7V: u16 = 3; +pub const ANACTRL_MANUSWON_BW3_4M: u16 = 0x0002; +pub const ANACTRL_RESTART_CAL: u16 = 0x0001; +pub const ANACTRL_DEF: u16 = 0x02EF; + +pub const MIIDBG_SYSMODCTRL: u16 = 0x04; +pub const SYSMODCTRL_IECHOADJ_PFMH_PHY: u16 = 0x8000; +pub const SYSMODCTRL_IECHOADJ_BIASGEN: u16 = 0x4000; +pub const SYSMODCTRL_IECHOADJ_PFML_PHY: u16 = 0x2000; +pub const SYSMODCTRL_IECHOADJ_PS_MASK: u16 = 0x3; +pub const SYSMODCTRL_IECHOADJ_PS_SHIFT: u16 = 10; +pub const SYSMODCTRL_IECHOADJ_PS_40: u16 = 3; +pub const SYSMODCTRL_IECHOADJ_PS_20: u16 = 2; +pub const SYSMODCTRL_IECHOADJ_PS_0: u16 = 1; +pub const SYSMODCTRL_IECHOADJ_10BT_100MV: u16 = 0x0040; +pub const SYSMODCTRL_IECHOADJ_HLFAP_MASK: u16 = 0x3; +pub const SYSMODCTRL_IECHOADJ_HLFAP_SHIFT: u16 = 4; +pub const SYSMODCTRL_IECHOADJ_VDFULBW: u16 = 0x0008; +pub const SYSMODCTRL_IECHOADJ_VDBIASHLF: u16 = 0x0004; +pub const SYSMODCTRL_IECHOADJ_VDAMPHLF: u16 = 0x0002; +pub const SYSMODCTRL_IECHOADJ_VDLANSW: u16 = 0x0001; +/* en half bias */ +pub const SYSMODCTRL_IECHOADJ_DEF: u16 = 0xBB8B; + +pub const MIIDBG_SRDSYSMOD: u16 = 0x05; +pub const SRDSYSMOD_LCKDET_EN: u16 = 0x2000; +pub const SRDSYSMOD_PLL_EN: u16 = 0x0800; +pub const SRDSYSMOD_SEL_HSP: u16 = 0x0400; +pub const SRDSYSMOD_HLFTXDR: u16 = 0x0200; +pub const SRDSYSMOD_TXCLK_DELAY_EN: u16 = 0x0100; +pub const SRDSYSMOD_TXELECIDLE: u16 = 0x0080; +pub const SRDSYSMOD_DEEMP_EN: u16 = 0x0040; +pub const SRDSYSMOD_MS_PAD: u16 = 0x0004; +pub const SRDSYSMOD_CDR_ADC_VLTG: u16 = 0x0002; +pub const SRDSYSMOD_CDR_DAC_1MA: u16 = 0x0001; +pub const SRDSYSMOD_DEF: u16 = 0x2C46; + +pub const MIIDBG_HIBNEG: u16 = 0x0B; +pub const HIBNEG_PSHIB_EN: u16 = 0x8000; +pub const HIBNEG_WAKE_BOTH: u16 = 0x4000; +pub const HIBNEG_ONOFF_ANACHG_SUDEN: u16 = 0x2000; +pub const HIBNEG_HIB_PULSE: u16 = 0x1000; +pub const HIBNEG_GATE_25M_EN: u16 = 0x0800; +pub const HIBNEG_RST_80U: u16 = 0x0400; +pub const HIBNEG_RST_TIMER_MASK: u16 = 0x3; +pub const HIBNEG_RST_TIMER_SHIFT: u16 = 8; +pub const HIBNEG_GTX_CLK_DELAY_MASK: u16 = 0x3; +pub const HIBNEG_GTX_CLK_DELAY_SHIFT: u16 = 5; +pub const HIBNEG_BYPSS_BRKTIMER: u16 = 0x0010; +pub const HIBNEG_DEF: u16 = 0xBC40; +pub const HIBNEG_NOHIB: u16 = HIBNEG_DEF & !(HIBNEG_PSHIB_EN | HIBNEG_HIB_PULSE); + +pub const MIIDBG_TST10BTCFG: u16 = 0x12; +pub const TST10BTCFG_INTV_TIMER_MASK: u16 = 0x3; +pub const TST10BTCFG_INTV_TIMER_SHIFT: u16 = 14; +pub const TST10BTCFG_TRIGER_TIMER_MASK: u16 = 0x3; +pub const TST10BTCFG_TRIGER_TIMER_SHIFT: u16 = 12; +pub const TST10BTCFG_DIV_MAN_MLT3_EN: u16 = 0x0800; +pub const TST10BTCFG_OFF_DAC_IDLE: u16 = 0x0400; +pub const TST10BTCFG_LPBK_DEEP: u16 = 0x0004; +pub const TST10BTCFG_DEF: u16 = 0x4C04; + +pub const MIIDBG_AZ_ANADECT: u16 = 0x15; +pub const AZ_ANADECT_10BTRX_TH: u16 = 0x8000; +pub const AZ_ANADECT_BOTH_01CHNL: u16 = 0x4000; +pub const AZ_ANADECT_INTV_MASK: u16 = 0x3F; +pub const AZ_ANADECT_INTV_SHIFT: u16 = 8; +pub const AZ_ANADECT_THRESH_MASK: u16 = 0xF; +pub const AZ_ANADECT_THRESH_SHIFT: u16 = 4; +pub const AZ_ANADECT_CHNL_MASK: u16 = 0xF; +pub const AZ_ANADECT_CHNL_SHIFT: u16 = 0; +pub const AZ_ANADECT_DEF: u16 = 0x3220; +pub const AZ_ANADECT_LONG: u16 = 0x3210; + +pub const MIIDBG_MSE16DB: u16 = 0x18; +pub const MSE16DB_UP: u16 = 0x05EA; +pub const MSE16DB_DOWN: u16 = 0x02EA; + +pub const MIIDBG_MSE20DB: u16 = 0x1C; +pub const MSE20DB_TH_MASK: u16 = 0x7F; +pub const MSE20DB_TH_SHIFT: u16 = 2; +pub const MSE20DB_TH_DEF: u16 = 0x2E; +pub const MSE20DB_TH_HI: u16 = 0x54; + +pub const MIIDBG_AGC: u16 = 0x23; +pub const AGC_2_VGA_MASK: u16 = 0x3F; +pub const AGC_2_VGA_SHIFT: u16 = 8; +pub const AGC_LONG1G_LIMT: u16 = 40; +pub const AGC_LONG100M_LIMT: u16 = 44; + +pub const MIIDBG_LEGCYPS: u16 = 0x29; +pub const LEGCYPS_EN: u16 = 0x8000; +pub const LEGCYPS_DAC_AMP1000_MASK: u16 = 0x7; +pub const LEGCYPS_DAC_AMP1000_SHIFT: u16 = 12; +pub const LEGCYPS_DAC_AMP100_MASK: u16 = 0x7; +pub const LEGCYPS_DAC_AMP100_SHIFT: u16 = 9; +pub const LEGCYPS_DAC_AMP10_MASK: u16 = 0x7; +pub const LEGCYPS_DAC_AMP10_SHIFT: u16 = 6; +pub const LEGCYPS_UNPLUG_TIMER_MASK: u16 = 0x7; +pub const LEGCYPS_UNPLUG_TIMER_SHIFT: u16 = 3; +pub const LEGCYPS_UNPLUG_DECT_EN: u16 = 0x0004; +pub const LEGCYPS_ECNC_PS_EN: u16 = 0x0001; +pub const LEGCYPS_DEF: u16 = 0x129D; + +pub const MIIDBG_TST100BTCFG: u16 = 0x36; +pub const TST100BTCFG_NORMAL_BW_EN: u16 = 0x8000; +pub const TST100BTCFG_BADLNK_BYPASS: u16 = 0x4000; +pub const TST100BTCFG_SHORTCABL_TH_MASK: u16 = 0x3F; +pub const TST100BTCFG_SHORTCABL_TH_SHIFT: u16 = 8; +pub const TST100BTCFG_LITCH_EN: u16 = 0x0080; +pub const TST100BTCFG_VLT_SW: u16 = 0x0040; +pub const TST100BTCFG_LONGCABL_TH_MASK: u16 = 0x3F; +pub const TST100BTCFG_LONGCABL_TH_SHIFT: u16 = 0; +pub const TST100BTCFG_DEF: u16 = 0xE12C; + +pub const MIIDBG_GREENCFG: u16 = 0x3B; +pub const GREENCFG_MSTPS_MSETH2_MASK: u16 = 0xFF; +pub const GREENCFG_MSTPS_MSETH2_SHIFT: u16 = 8; +pub const GREENCFG_MSTPS_MSETH1_MASK: u16 = 0xFF; +pub const GREENCFG_MSTPS_MSETH1_SHIFT: u16 = 0; +pub const GREENCFG_DEF: u16 = 0x7078; + +pub const MIIDBG_GREENCFG2: u16 = 0x3D; +pub const GREENCFG2_BP_GREEN: u16 = 0x8000; +pub const GREENCFG2_GATE_DFSE_EN: u16 = 0x0080; + +/***************************** extension **************************************/ + +/******* dev 3 *********/ +pub const MIIEXT_PCS: u8 = 3; + +pub const MIIEXT_CLDCTRL3: u16 = 0x8003; +pub const CLDCTRL3_BP_CABLE1TH_DET_GT: u16 = 0x8000; +pub const CLDCTRL3_AZ_DISAMP: u16 = 0x1000; + +pub const MIIEXT_CLDCTRL5: u16 = 0x8005; +pub const CLDCTRL5_BP_VD_HLFBIAS: u16 = 0x4000; + +pub const MIIEXT_CLDCTRL6: u16 = 0x8006; +pub const CLDCTRL6_CAB_LEN_MASK: u16 = 0xFF; +pub const CLDCTRL6_CAB_LEN_SHIFT: u16 = 0; +pub const CLDCTRL6_CAB_LEN_SHORT1G: u16 = 116; +pub const CLDCTRL6_CAB_LEN_SHORT100M: u16 = 152; + +pub const MIIEXT_CLDCTRL7: u16 = 0x8007; +pub const CLDCTRL7_VDHLF_BIAS_TH_MASK: u16 = 0x7F; +pub const CLDCTRL7_VDHLF_BIAS_TH_SHIFT: u16 = 9; +pub const CLDCTRL7_AFE_AZ_MASK: u16 = 0x1F; +pub const CLDCTRL7_AFE_AZ_SHIFT: u16 = 4; +pub const CLDCTRL7_SIDE_PEAK_TH_MASK: u16 = 0xF; +pub const CLDCTRL7_SIDE_PEAK_TH_SHIFT: u16 = 0; +pub const CLDCTRL7_DEF: u16 = 0x6BF6; + +pub const MIIEXT_AZCTRL: u16 = 0x8008; +pub const AZCTRL_SHORT_TH_MASK: u16 = 0xFF; +pub const AZCTRL_SHORT_TH_SHIFT: u16 = 8; +pub const AZCTRL_LONG_TH_MASK: u16 = 0xFF; +pub const AZCTRL_LONG_TH_SHIFT: u16 = 0; +pub const AZCTRL_DEF: u16 = 0x1629; + +pub const MIIEXT_AZCTRL2: u16 = 0x8009; +pub const AZCTRL2_WAKETRNING_MASK: u16 = 0xFF; +pub const AZCTRL2_WAKETRNING_SHIFT: u16 = 8; +pub const AZCTRL2_QUIET_TIMER_MASK: u16 = 0x3; +pub const AZCTRL2_QUIET_TIMER_SHIFT: u16 = 6; +pub const AZCTRL2_PHAS_JMP2: u16 = 0x0010; +pub const AZCTRL2_CLKTRCV_125MD16: u16 = 0x0008; +pub const AZCTRL2_GATE1000_EN: u16 = 0x0004; +pub const AZCTRL2_AVRG_FREQ: u16 = 0x0002; +pub const AZCTRL2_PHAS_JMP4: u16 = 0x0001; +pub const AZCTRL2_DEF: u16 = 0x32C0; + +pub const MIIEXT_AZCTRL6: u16 = 0x800D; + +pub const MIIEXT_VDRVBIAS: u16 = 0x8062; +pub const VDRVBIAS_SEL_MASK: u16 = 0x3; +pub const VDRVBIAS_SEL_SHIFT: u16 = 0; +pub const VDRVBIAS_DEF: u16 = 0x3; + +/********* dev 7 **********/ +pub const MIIEXT_ANEG: u8 = 7; + +pub const MIIEXT_LOCAL_EEEADV: u16 = 0x3C; +pub const LOCAL_EEEADV_1000BT: u16 = 0x0004; +pub const LOCAL_EEEADV_100BT: u16 = 0x0002; + +pub const MIIEXT_REMOTE_EEEADV: u16 = 0x3D; +pub const REMOTE_EEEADV_1000BT: u16 = 0x0004; +pub const REMOTE_EEEADV_100BT: u16 = 0x0002; + +pub const MIIEXT_EEE_ANEG: u16 = 0x8000; +pub const EEE_ANEG_1000M: u16 = 0x0004; +pub const EEE_ANEG_100M: u16 = 0x0002; + +pub const MIIEXT_AFE: u16 = 0x801A; +pub const AFE_10BT_100M_TH: u16 = 0x0040; + +pub const MIIEXT_S3DIG10: u16 = 0x8023; +/* bit0:: u16 = 1:bypass: u16 = 10BT rx fifo,: u16 = 0:riginal: u16 = 10BT rx */ +pub const MIIEXT_S3DIG10_SL: u16 = 0x0001; +pub const MIIEXT_S3DIG10_DEF: u16 = 0; + +pub const MIIEXT_NLP34: u16 = 0x8025; +/* for: u16 = 160m */ +pub const MIIEXT_NLP34_DEF: u16 = 0x1010; + +pub const MIIEXT_NLP56: u16 = 0x8026; +/* for: u16 = 160m */ +pub const MIIEXT_NLP56_DEF: u16 = 0x1010; + +pub const MIIEXT_NLP78: u16 = 0x8027; +/* for: u16 = 160m */ +pub const MIIEXT_NLP78_160M_DEF: u16 = 0x8D05; +pub const MIIEXT_NLP78_120M_DEF: u16 = 0x8A05; diff --git a/drivers/net/alxd/src/main.rs b/drivers/net/alxd/src/main.rs new file mode 100644 index 0000000000..37351c8ccd --- /dev/null +++ b/drivers/net/alxd/src/main.rs @@ -0,0 +1,141 @@ +#![feature(macro_metavar_expr_concat)] +#![allow(dead_code)] +#![allow(non_upper_case_globals)] +#![allow(unused_parens)] + +extern crate event; +extern crate syscall; + +use std::fs::File; +use std::io::{Read, Write}; +use std::os::fd::AsRawFd; +use std::os::unix::io::{FromRawFd, RawFd}; +use std::{env, iter}; + +use event::{user_data, EventQueue}; +use libredox::flag; +use redox_scheme::wrappers::ReadinessBased; +use redox_scheme::Socket; +use std::cell::RefCell; +use syscall::error::EWOULDBLOCK; + +pub mod device; + +fn main() { + let mut args = env::args().skip(1); + + let mut name = args.next().expect("alxd: no name provided"); + name.push_str("_alx"); + + let bar_str = args.next().expect("alxd: no address provided"); + let bar = usize::from_str_radix(&bar_str, 16).expect("alxd: failed to parse address"); + + let irq_str = args.next().expect("alxd: no irq provided"); + let irq = irq_str.parse::().expect("alxd: failed to parse irq"); + + println!(" + ALX {} on: {:X}, IRQ: {}\n", name, bar, irq); + + // Daemonize + redox_daemon::Daemon::new(move |daemon| { + let socket = Socket::nonblock("network").expect("alxd: failed to create socket"); + let mut readiness_based = ReadinessBased::new(&socket, 16); + + daemon.ready().expect("alxd: failed to signal readiness"); + + let mut irq_file = + File::open(format!("/scheme/irq/{}", irq)).expect("alxd: failed to open IRQ file"); + + let address = unsafe { + common::physmap( + bar, + 128 * 1024, + common::Prot::RW, + common::MemoryType::Uncacheable, + ) + .expect("alxd: failed to map address") as usize + }; + { + let device = RefCell::new(unsafe { + device::Alx::new(address).expect("alxd: failed to allocate device") + }); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = + EventQueue::::new().expect("alxd: failed to create event queue"); + event_queue + .subscribe( + irq_file.as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + socket.inner().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("alxd: failed to enter null namespace"); + + for event in iter::once(Source::Scheme) + .chain(event_queue.map(|e| e.expect("alxd: failed to get next event").user_data)) + { + match event { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.read(&mut irq).unwrap(); + if !unsafe { device.borrow_mut().intr_legacy() } { + continue; + } + irq_file.write(&mut irq).unwrap(); + + readiness_based + .poll_all_requests(|| device.borrow_mut()) + .expect("ihdad: failed to poll requests"); + + /* TODO: Currently a no-op + let next_read = device.next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + + Source::Scheme => { + if !readiness_based + .read_requests() + .expect("alxd: failed to read from socket") + { + break; + } + readiness_based.process_requests(|| device.borrow_mut()); + if !readiness_based + .write_responses() + .expect("alxd: failed to write to socket") + { + break; + } + + // TODO + /* + let next_read = device.next_read(); + if next_read > 0 { + return Ok(Some(next_read)); + } + */ + } + } + } + } + std::process::exit(0); + }) + .expect("alxd: failed to daemonize"); +} diff --git a/drivers/net/driver-network/Cargo.toml b/drivers/net/driver-network/Cargo.toml new file mode 100644 index 0000000000..8220537c1b --- /dev/null +++ b/drivers/net/driver-network/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "driver-network" +version = "0.1.0" +edition = "2021" + +[dependencies] +libredox = "0.1.3" +redox-daemon = "0.1" +redox-scheme = "0.4" +redox_syscall = { version = "0.5", features = ["std"] } diff --git a/drivers/net/driver-network/src/lib.rs b/drivers/net/driver-network/src/lib.rs new file mode 100644 index 0000000000..eadaaa0289 --- /dev/null +++ b/drivers/net/driver-network/src/lib.rs @@ -0,0 +1,290 @@ +use std::collections::BTreeMap; +use std::{cmp, io}; + +use libredox::flag::O_NONBLOCK; +use libredox::Fd; +use redox_scheme::{ + CallRequest, CallerCtx, OpenResult, RequestKind, Response, SchemeBlock, SignalBehavior, Socket, +}; +use syscall::schemev2::NewFdFlags; +use syscall::{ + Error, EventFlags, Result, Stat, EACCES, EAGAIN, EBADF, EINTR, EINVAL, EWOULDBLOCK, MODE_FILE, +}; + +pub trait NetworkAdapter { + /// The [MAC address](https://en.wikipedia.org/wiki/MAC_address) of this + /// network adapter. + fn mac_address(&mut self) -> [u8; 6]; + + /// The amount of network packets that can be read without blocking. + fn available_for_read(&mut self) -> usize; + + /// Attempt to read a network packet without blocking. + /// + /// Returns `Ok(None)` when there is no pending network packet. + fn read_packet(&mut self, buf: &mut [u8]) -> Result>; + + /// Write a single network packet. + // FIXME support back pressure on writes by returning EWOULDBLOCK or not + // returning from the write syscall until there is room. + fn write_packet(&mut self, buf: &[u8]) -> Result; +} + +pub struct NetworkScheme { + adapter: T, + scheme_name: String, + socket: Socket, + next_id: usize, + handles: BTreeMap, + blocked: Vec, +} + +enum Handle { + Data, + Mac, +} + +impl NetworkScheme { + pub fn new( + adapter_fn: impl FnOnce() -> T, + daemon: redox_daemon::Daemon, + scheme_name: String, + ) -> Self { + assert!(scheme_name.starts_with("network")); + let socket = Socket::nonblock(&scheme_name).expect("failed to create network scheme"); + daemon.ready().expect("failed to mark daemon as ready"); + let adapter = adapter_fn(); + NetworkScheme { + adapter, + scheme_name, + socket, + next_id: 0, + handles: BTreeMap::new(), + blocked: vec![], + } + } + + pub fn event_handle(&self) -> &Fd { + self.socket.inner() + } + + pub fn adapter(&self) -> &T { + &self.adapter + } + + pub fn adapter_mut(&mut self) -> &mut T { + &mut self.adapter + } + + /// Process pending and new requests. + /// + /// This needs to be called each time there is a new event on the scheme + /// file and each time a new network packet has been received by the + /// driver. + // FIXME maybe split into one method for events on the scheme fd and one + // to call when an irq is received to indicate that blocked requests can + // be processed. + pub fn tick(&mut self) -> io::Result<()> { + // Handle any blocked requests + let mut i = 0; + while i < self.blocked.len() { + if let Some(resp) = self.blocked[i].handle_scheme_block(self) { + self.socket + .write_response(resp, SignalBehavior::Restart) + .expect("driver-network: failed to write scheme"); + self.blocked.remove(i); + } else { + i += 1; + } + } + + // Handle new scheme requests + loop { + let request = match self.socket.next_request(SignalBehavior::Restart) { + Ok(Some(request)) => request, + Ok(None) => { + // Scheme likely got unmounted + std::process::exit(0); + } + Err(err) if err.errno == EAGAIN => break, + Err(err) => return Err(err.into()), + }; + + match request.kind() { + RequestKind::Call(call_request) => { + if let Some(resp) = call_request.handle_scheme_block(self) { + self.socket.write_response(resp, SignalBehavior::Restart)?; + } else { + self.blocked.push(call_request); + } + } + RequestKind::OnClose { id } => { + self.on_close(id); + } + RequestKind::Cancellation(cancellation_request) => { + if let Some(i) = self + .blocked + .iter() + .position(|req| req.request().request_id() == cancellation_request.id) + { + let blocked_req = self.blocked.remove(i); + let resp = Response::new(&blocked_req, Err(syscall::Error::new(EINTR))); + self.socket.write_response(resp, SignalBehavior::Restart)?; + } + } + _ => {} + } + } + + // Notify readers about incoming events + let available_for_read = self.adapter.available_for_read(); + if available_for_read > 0 { + for &handle_id in self.handles.keys() { + self.socket + .post_fevent(handle_id, syscall::flag::EVENT_READ.bits())?; + } + return Ok(()); + } + + Ok(()) + } +} + +impl SchemeBlock for NetworkScheme { + fn xopen( + &mut self, + path: &str, + _flags: usize, + caller_ctx: &CallerCtx, + ) -> Result> { + if caller_ctx.uid != 0 { + return Err(Error::new(EACCES)); + } + + let (handle, flags) = match path { + "" => (Handle::Data, NewFdFlags::empty()), + "mac" => (Handle::Mac, NewFdFlags::POSITIONED), + _ => return Err(Error::new(EINVAL)), + }; + + self.next_id += 1; + self.handles.insert(self.next_id, handle); + Ok(Some(OpenResult::ThisScheme { + number: self.next_id, + flags, + })) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + fcntl_flags: u32, + ) -> Result> { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + match *handle { + Handle::Data => {} + Handle::Mac => { + let data = &self.adapter.mac_address()[offset as usize..]; + let i = cmp::min(buf.len(), data.len()); + buf[..i].copy_from_slice(&data[..i]); + return Ok(Some(i)); + } + }; + + match self.adapter.read_packet(buf)? { + Some(count) => Ok(Some(count)), + None => { + if fcntl_flags & O_NONBLOCK as u32 != 0 { + Err(Error::new(EWOULDBLOCK)) + } else { + Ok(None) + } + } + } + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + ) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + match handle { + Handle::Data => {} + Handle::Mac { .. } => return Err(Error::new(EINVAL)), + } + + Ok(Some(self.adapter.write_packet(buf)?)) + } + + fn fevent(&mut self, id: usize, _flags: EventFlags) -> Result> { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + Ok(Some(EventFlags::empty())) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + let mut i = 0; + + let scheme_name = self.scheme_name.as_bytes(); + let mut j = 0; + while i < buf.len() && j < scheme_name.len() { + buf[i] = scheme_name[j]; + i += 1; + j += 1; + } + + if i < buf.len() { + buf[i] = b':'; + i += 1; + } + + let path = match handle { + Handle::Data { .. } => &b""[..], + Handle::Mac { .. } => &b"mac"[..], + }; + + j = 0; + while i < buf.len() && j < path.len() { + buf[i] = path[j]; + i += 1; + j += 1; + } + + Ok(Some(i)) + } + + fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + match handle { + Handle::Data { .. } => { + stat.st_mode = MODE_FILE | 0o700; + } + Handle::Mac { .. } => { + stat.st_mode = MODE_FILE | 0o400; + stat.st_size = 6; + } + } + + Ok(Some(0)) + } + + fn fsync(&mut self, id: usize) -> Result> { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + Ok(Some(0)) + } +} + +impl NetworkScheme { + fn on_close(&mut self, id: usize) { + self.handles.remove(&id); + } +} diff --git a/drivers/net/e1000d/Cargo.toml b/drivers/net/e1000d/Cargo.toml new file mode 100644 index 0000000000..62653d7b36 --- /dev/null +++ b/drivers/net/e1000d/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "e1000d" +version = "0.1.0" +edition = "2018" + +[dependencies] +bitflags = "2" +log = "0.4" +redox-daemon = "0.1.2" +libredox = "0.1.3" +redox_event = "0.4.1" +redox_syscall = "0.5" + +common = { path = "../../common" } +driver-network = { path = "../driver-network" } +pcid = { path = "../../pcid" } diff --git a/drivers/net/e1000d/config.toml b/drivers/net/e1000d/config.toml new file mode 100644 index 0000000000..4862da27dc --- /dev/null +++ b/drivers/net/e1000d/config.toml @@ -0,0 +1,5 @@ +[[drivers]] +name = "E1000 NIC" +class = 0x02 +ids = { 0x8086 = [0x1004, 0x100e, 0x100f, 0x109a, 0x1503] } +command = ["e1000d"] diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs new file mode 100644 index 0000000000..4c518f30fc --- /dev/null +++ b/drivers/net/e1000d/src/device.rs @@ -0,0 +1,368 @@ +use std::convert::TryInto; +use std::{cmp, mem, ptr, slice, thread, time}; + +use driver_network::NetworkAdapter; + +use syscall::error::Result; + +use common::dma::Dma; + +const CTRL: u32 = 0x00; +const CTRL_LRST: u32 = 1 << 3; +const CTRL_ASDE: u32 = 1 << 5; +const CTRL_SLU: u32 = 1 << 6; +const CTRL_ILOS: u32 = 1 << 7; +const CTRL_RST: u32 = 1 << 26; +const CTRL_VME: u32 = 1 << 30; +const CTRL_PHY_RST: u32 = 1 << 31; + +const STATUS: u32 = 0x08; + +const FCAL: u32 = 0x28; +const FCAH: u32 = 0x2C; +const FCT: u32 = 0x30; +const FCTTV: u32 = 0x170; + +const ICR: u32 = 0xC0; + +const IMS: u32 = 0xD0; +const IMS_TXDW: u32 = 1; +const IMS_TXQE: u32 = 1 << 1; +const IMS_LSC: u32 = 1 << 2; +const IMS_RXSEQ: u32 = 1 << 3; +const IMS_RXDMT: u32 = 1 << 4; +const IMS_RX: u32 = 1 << 6; +const IMS_RXT: u32 = 1 << 7; + +const RCTL: u32 = 0x100; +const RCTL_EN: u32 = 1 << 1; +const RCTL_UPE: u32 = 1 << 3; +const RCTL_MPE: u32 = 1 << 4; +const RCTL_LPE: u32 = 1 << 5; +const RCTL_LBM: u32 = 1 << 6 | 1 << 7; +const RCTL_BAM: u32 = 1 << 15; +const RCTL_BSIZE1: u32 = 1 << 16; +const RCTL_BSIZE2: u32 = 1 << 17; +const RCTL_BSEX: u32 = 1 << 25; +const RCTL_SECRC: u32 = 1 << 26; + +const RDBAL: u32 = 0x2800; +const RDBAH: u32 = 0x2804; +const RDLEN: u32 = 0x2808; +const RDH: u32 = 0x2810; +const RDT: u32 = 0x2818; + +const RAL0: u32 = 0x5400; +const RAH0: u32 = 0x5404; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +struct Rd { + buffer: u64, + length: u16, + checksum: u16, + status: u8, + error: u8, + special: u16, +} +const RD_DD: u8 = 1; +const RD_EOP: u8 = 1 << 1; + +const TCTL: u32 = 0x400; +const TCTL_EN: u32 = 1 << 1; +const TCTL_PSP: u32 = 1 << 3; + +const TDBAL: u32 = 0x3800; +const TDBAH: u32 = 0x3804; +const TDLEN: u32 = 0x3808; +const TDH: u32 = 0x3810; +const TDT: u32 = 0x3818; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +struct Td { + buffer: u64, + length: u16, + cso: u8, + command: u8, + status: u8, + css: u8, + special: u16, +} +const TD_CMD_EOP: u8 = 1; +const TD_CMD_IFCS: u8 = 1 << 1; +const TD_CMD_RS: u8 = 1 << 3; +const TD_DD: u8 = 1; + +pub struct Intel8254x { + base: usize, + mac_address: [u8; 6], + receive_buffer: [Dma<[u8; 16384]>; 16], + receive_ring: Dma<[Rd; 16]>, + receive_index: usize, + transmit_buffer: [Dma<[u8; 16384]>; 16], + transmit_ring: Dma<[Td; 16]>, + transmit_ring_free: usize, + transmit_index: usize, + transmit_clean_index: usize, +} + +#[derive(Copy, Clone)] +pub enum Handle { + Data { flags: usize }, + Mac { offset: usize }, +} + +fn wrap_ring(index: usize, ring_size: usize) -> usize { + (index + 1) & (ring_size - 1) +} + +impl NetworkAdapter for Intel8254x { + fn mac_address(&mut self) -> [u8; 6] { + self.mac_address + } + + fn available_for_read(&mut self) -> usize { + let desc = unsafe { &*(self.receive_ring.as_ptr().add(self.receive_index) as *const Rd) }; + + if desc.status & RD_DD == RD_DD { + return desc.length as usize; + } + + 0 + } + + fn read_packet(&mut self, buf: &mut [u8]) -> Result> { + let desc = unsafe { &mut *(self.receive_ring.as_ptr().add(self.receive_index) as *mut Rd) }; + + if desc.status & RD_DD == RD_DD { + desc.status = 0; + + let data = &self.receive_buffer[self.receive_index][..desc.length as usize]; + + let i = cmp::min(buf.len(), data.len()); + buf[..i].copy_from_slice(&data[..i]); + + unsafe { self.write_reg(RDT, self.receive_index as u32) }; + self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len()); + + return Ok(Some(i)); + } + + Ok(None) + } + + fn write_packet(&mut self, buf: &[u8]) -> Result { + if self.transmit_ring_free == 0 { + loop { + let desc = unsafe { + &*(self.transmit_ring.as_ptr().add(self.transmit_clean_index) as *const Td) + }; + + if desc.status != 0 { + self.transmit_clean_index = + wrap_ring(self.transmit_clean_index, self.transmit_ring.len()); + self.transmit_ring_free += 1; + } else if self.transmit_ring_free > 0 { + break; + } + + if self.transmit_ring_free >= self.transmit_ring.len() { + break; + } + } + } + + let desc = + unsafe { &mut *(self.transmit_ring.as_ptr().add(self.transmit_index) as *mut Td) }; + + let data = unsafe { + slice::from_raw_parts_mut( + self.transmit_buffer[self.transmit_index].as_ptr() as *mut u8, + cmp::min(buf.len(), self.transmit_buffer[self.transmit_index].len()) as usize, + ) + }; + + let i = cmp::min(buf.len(), data.len()); + data[..i].copy_from_slice(&buf[..i]); + + desc.cso = 0; + desc.command = TD_CMD_EOP | TD_CMD_IFCS | TD_CMD_RS; + desc.status = 0; + desc.css = 0; + desc.special = 0; + + desc.length = (cmp::min( + buf.len(), + self.transmit_buffer[self.transmit_index].len() - 1, + )) as u16; + + self.transmit_index = wrap_ring(self.transmit_index, self.transmit_ring.len()); + self.transmit_ring_free -= 1; + + unsafe { self.write_reg(TDT, self.transmit_index as u32) }; + + Ok(i) + } +} + +fn dma_array() -> Result<[Dma; N]> { + Ok((0..N) + .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!())) +} +impl Intel8254x { + pub unsafe fn new(base: usize) -> Result { + #[rustfmt::skip] + let mut module = Intel8254x { + base, + mac_address: [0; 6], + receive_buffer: dma_array()?, + receive_ring: Dma::zeroed()?.assume_init(), + transmit_buffer: dma_array()?, + receive_index: 0, + transmit_ring: Dma::zeroed()?.assume_init(), + transmit_ring_free: 16, + transmit_index: 0, + transmit_clean_index: 0, + }; + + module.init(); + + Ok(module) + } + + pub unsafe fn irq(&self) -> bool { + let icr = self.read_reg(ICR); + icr != 0 + } + + pub unsafe fn read_reg(&self, register: u32) -> u32 { + ptr::read_volatile((self.base + register as usize) as *mut u32) + } + + pub unsafe fn write_reg(&self, register: u32, data: u32) -> u32 { + ptr::write_volatile((self.base + register as usize) as *mut u32, data); + ptr::read_volatile((self.base + register as usize) as *mut u32) + } + + pub unsafe fn flag(&self, register: u32, flag: u32, value: bool) { + if value { + self.write_reg(register, self.read_reg(register) | flag); + } else { + self.write_reg(register, self.read_reg(register) & !flag); + } + } + + pub unsafe fn init(&mut self) { + self.flag(CTRL, CTRL_RST, true); + while self.read_reg(CTRL) & CTRL_RST == CTRL_RST { + log::trace!("Waiting for reset: {:X}", self.read_reg(CTRL)); + } + + // Enable auto negotiate, link, clear reset, do not Invert Loss-Of Signal + self.flag(CTRL, CTRL_ASDE | CTRL_SLU, true); + self.flag(CTRL, CTRL_LRST | CTRL_PHY_RST | CTRL_ILOS, false); + + // No flow control + self.write_reg(FCAH, 0); + self.write_reg(FCAL, 0); + self.write_reg(FCT, 0); + self.write_reg(FCTTV, 0); + + // Do not use VLANs + self.flag(CTRL, CTRL_VME, false); + + // TODO: Clear statistical counters + + let mac_low = self.read_reg(RAL0); + let mac_high = self.read_reg(RAH0); + let mac = [ + mac_low as u8, + (mac_low >> 8) as u8, + (mac_low >> 16) as u8, + (mac_low >> 24) as u8, + mac_high as u8, + (mac_high >> 8) as u8, + ]; + log::debug!( + "MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + self.mac_address = mac; + + // + // MTA => 0; + // + + // Receive Buffer + for i in 0..self.receive_ring.len() { + self.receive_ring[i].buffer = self.receive_buffer[i].physical() as u64; + } + + self.write_reg(RDBAH, ((self.receive_ring.physical() as u64) >> 32) as u32); + self.write_reg(RDBAL, self.receive_ring.physical() as u32); + self.write_reg( + RDLEN, + (self.receive_ring.len() * mem::size_of::()) as u32, + ); + self.write_reg(RDH, 0); + self.write_reg(RDT, self.receive_ring.len() as u32 - 1); + + // Transmit Buffer + for i in 0..self.transmit_ring.len() { + self.transmit_ring[i].buffer = self.transmit_buffer[i].physical() as u64; + } + + self.write_reg(TDBAH, ((self.transmit_ring.physical() as u64) >> 32) as u32); + self.write_reg(TDBAL, self.transmit_ring.physical() as u32); + self.write_reg( + TDLEN, + (self.transmit_ring.len() * mem::size_of::()) as u32, + ); + self.write_reg(TDH, 0); + self.write_reg(TDT, 0); + + self.write_reg(IMS, IMS_RXT | IMS_RX | IMS_RXDMT | IMS_RXSEQ); // | IMS_LSC | IMS_TXQE | IMS_TXDW + + self.flag(RCTL, RCTL_EN, true); + self.flag(RCTL, RCTL_UPE, true); + // self.flag(RCTL, RCTL_MPE, true); + self.flag(RCTL, RCTL_LPE, true); + self.flag(RCTL, RCTL_LBM, false); + // RCTL.RDMTS = Minimum threshold size ??? + // RCTL.MO = Multicast offset + self.flag(RCTL, RCTL_BAM, true); + self.flag(RCTL, RCTL_BSIZE1, true); + self.flag(RCTL, RCTL_BSIZE2, false); + self.flag(RCTL, RCTL_BSEX, true); + self.flag(RCTL, RCTL_SECRC, true); + + self.flag(TCTL, TCTL_EN, true); + self.flag(TCTL, TCTL_PSP, true); + // TCTL.CT = Collision threshold + // TCTL.COLD = Collision distance + // TIPG Packet Gap + // TODO ... + + log::debug!("Waiting for link up: {:X}", self.read_reg(STATUS)); + while self.read_reg(STATUS) & 2 != 2 { + thread::sleep(time::Duration::from_millis(100)); + } + log::debug!( + "Link is up with speed {}", + match (self.read_reg(STATUS) >> 6) & 0b11 { + 0b00 => "10 Mb/s", + 0b01 => "100 Mb/s", + _ => "1000 Mb/s", + } + ); + } +} diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs new file mode 100644 index 0000000000..b7601d17fd --- /dev/null +++ b/drivers/net/e1000d/src/main.rs @@ -0,0 +1,91 @@ +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; + +use driver_network::NetworkScheme; +use event::{user_data, EventQueue}; +use pcid_interface::PciFunctionHandle; + +pub mod device; + +fn main() { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_e1000"); + + common::setup_logging( + "net", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + let irq = pci_config + .func + .legacy_interrupt_line + .expect("e1000d: no legacy interrupts supported"); + + log::info!("E1000 {}", pci_config.func.display()); + + redox_daemon::Daemon::new(move |daemon| { + let mut irq_file = irq.irq_handle("e1000d"); + + let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; + + let mut scheme = NetworkScheme::new( + move || unsafe { + device::Intel8254x::new(address).expect("e1000d: failed to allocate device") + }, + daemon, + format!("network.{name}"), + ); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = + EventQueue::::new().expect("e1000d: failed to create event queue"); + + event_queue + .subscribe( + irq_file.as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .expect("e1000d: failed to subscribe to IRQ fd"); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .expect("e1000d: failed to subscribe to scheme fd"); + + libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); + + scheme.tick().unwrap(); + + for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { + match event.user_data { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.read(&mut irq).unwrap(); + if unsafe { scheme.adapter().irq() } { + irq_file.write(&mut irq).unwrap(); + + scheme.tick().expect("e1000d: failed to handle IRQ") + } + } + Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"), + } + } + unreachable!() + }) + .expect("e1000d: failed to create daemon"); +} diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml new file mode 100644 index 0000000000..cce0badf9f --- /dev/null +++ b/drivers/net/ixgbed/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ixgbed" +version = "1.0.0" +edition = "2021" + +[dependencies] +bitflags = "2" +libredox = "0.1.3" +redox_event = "0.4.1" +redox_syscall = "0.5" +redox-daemon = "0.1" + +common = { path = "../../common" } +driver-network = { path = "../driver-network" } +pcid = { path = "../../pcid" } diff --git a/drivers/net/ixgbed/LICENSE b/drivers/net/ixgbed/LICENSE new file mode 100644 index 0000000000..0ad25db4bd --- /dev/null +++ b/drivers/net/ixgbed/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/drivers/net/ixgbed/README.md b/drivers/net/ixgbed/README.md new file mode 100644 index 0000000000..abcd4a2f83 --- /dev/null +++ b/drivers/net/ixgbed/README.md @@ -0,0 +1,37 @@ +# ixgbed (a.k.a. ixy.rs on Redox) + +ixgbed is the Redox port of [ixy.rs](https://github.com/ixy-languages/ixy.rs), a Rust rewrite of the [ixy](https://github.com/emmericp/ixy) userspace network driver. +It is designed to be readable, idiomatic Rust code. +It supports Intel 82599 10GbE NICs (`ixgbe` family). + +## Features + +* first 10 Gbit/s network driver on Redox +* transmitting 250 times faster than e1000 / rtl8168 driver +* MSI-X interrupts (not supported by Redox yet) +* less than 1000 lines of code for the driver +* documented code + +## Build instructions + +See the [Redox README](https://gitlab.redox-os.org/redox-os/redox/blob/master/README.md) for build instructions. + +To run ixgbed on Redox (in case the driver is not shipped with Redox anymore) + +* clone this project into `cookbook/recipes/drivers/source/` +* create an entry for ixgbed in `cookbook/recipes/drivers/source/Cargo.toml` +* check if your ixgbe device is included in `config.toml` +* touch `filesystem.toml` in Redox's root directory, build Redox and run it + +## Usage + +To test the driver's transmit and forwarding capabilities, have a look at [rheinfall](https://github.com/ackxolotl/rheinfall), a simple packet generator / forwarder application. + +## Docs + +ixgbed contains documentation that can be created and viewed by running + +``` +cargo doc --open +``` + diff --git a/drivers/net/ixgbed/config.toml b/drivers/net/ixgbed/config.toml new file mode 100644 index 0000000000..a10fba5a8f --- /dev/null +++ b/drivers/net/ixgbed/config.toml @@ -0,0 +1,5 @@ +[[drivers]] +name = "Intel 10G NIC" +class = 0x02 +ids = { 0x8086 = [0x10F7, 0x1514, 0x1517, 0x151C, 0x10F9, 0x10FB, 0x152a, 0x1529, 0x1507, 0x154D, 0x1557, 0x10FC, 0x10F8, 0x154F, 0x1528, 0x154A, 0x1558, 0x1560, 0x1563, 0x15D1, 0x15AA, 0x15AB, 0x15AC, 0x15AD, 0x15AE, 0x15B0, 0x15C2, 0x15C3, 0x15C4, 0x15C6, 0x15C7, 0x15C8, 0x15CE, 0x15E4, 0x15E5, 0x10ED, 0x1515, 0x1565, 0x15A8, 0x15C5] } +command = ["ixgbed"] diff --git a/drivers/net/ixgbed/src/device.rs b/drivers/net/ixgbed/src/device.rs new file mode 100644 index 0000000000..0d59b46d34 --- /dev/null +++ b/drivers/net/ixgbed/src/device.rs @@ -0,0 +1,579 @@ +use std::convert::TryInto; +use std::time::{Duration, Instant}; +use std::{cmp, mem, ptr, slice, thread}; + +use driver_network::NetworkAdapter; +use syscall::error::Result; + +use common::dma::Dma; + +use crate::ixgbe::*; + +pub struct Intel8259x { + base: usize, + size: usize, + receive_buffer: [Dma<[u8; 16384]>; 32], + receive_ring: Dma<[ixgbe_adv_rx_desc; 32]>, + receive_index: usize, + transmit_buffer: [Dma<[u8; 16384]>; 32], + transmit_ring: Dma<[ixgbe_adv_tx_desc; 32]>, + transmit_ring_free: usize, + transmit_index: usize, + transmit_clean_index: usize, + mac_address: [u8; 6], +} + +fn wrap_ring(index: usize, ring_size: usize) -> usize { + (index + 1) & (ring_size - 1) +} + +impl NetworkAdapter for Intel8259x { + fn mac_address(&mut self) -> [u8; 6] { + self.mac_address + } + + fn available_for_read(&mut self) -> usize { + self.next_read() + } + + fn read_packet(&mut self, buf: &mut [u8]) -> Result> { + let desc = unsafe { + &mut *(self.receive_ring.as_ptr().add(self.receive_index) as *mut ixgbe_adv_rx_desc) + }; + + let status = unsafe { desc.wb.upper.status_error }; + + if (status & IXGBE_RXDADV_STAT_DD) != 0 { + if (status & IXGBE_RXDADV_STAT_EOP) == 0 { + panic!("increase buffer size or decrease MTU") + } + + let data = unsafe { + &self.receive_buffer[self.receive_index][..desc.wb.upper.length as usize] + }; + + let i = cmp::min(buf.len(), data.len()); + buf[..i].copy_from_slice(&data[..i]); + + desc.read.pkt_addr = self.receive_buffer[self.receive_index].physical() as u64; + desc.read.hdr_addr = 0; + + self.write_reg(IXGBE_RDT(0), self.receive_index as u32); + self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len()); + + return Ok(Some(i)); + } + + Ok(None) + } + + fn write_packet(&mut self, buf: &[u8]) -> Result { + if self.transmit_ring_free == 0 { + loop { + let desc = unsafe { + &*(self.transmit_ring.as_ptr().add(self.transmit_clean_index) + as *const ixgbe_adv_tx_desc) + }; + + if (unsafe { desc.wb.status } & IXGBE_ADVTXD_STAT_DD) != 0 { + self.transmit_clean_index = + wrap_ring(self.transmit_clean_index, self.transmit_ring.len()); + self.transmit_ring_free += 1; + } else if self.transmit_ring_free > 0 { + break; + } + + if self.transmit_ring_free >= self.transmit_ring.len() { + break; + } + } + } + + let desc = unsafe { + &mut *(self.transmit_ring.as_ptr().add(self.transmit_index) as *mut ixgbe_adv_tx_desc) + }; + + let data = unsafe { + slice::from_raw_parts_mut( + self.transmit_buffer[self.transmit_index].as_ptr() as *mut u8, + cmp::min(buf.len(), self.transmit_buffer[self.transmit_index].len()) as usize, + ) + }; + + let i = cmp::min(buf.len(), data.len()); + data[..i].copy_from_slice(&buf[..i]); + + desc.read.cmd_type_len = IXGBE_ADVTXD_DCMD_EOP + | IXGBE_ADVTXD_DCMD_RS + | IXGBE_ADVTXD_DCMD_IFCS + | IXGBE_ADVTXD_DCMD_DEXT + | IXGBE_ADVTXD_DTYP_DATA + | buf.len() as u32; + + desc.read.olinfo_status = (buf.len() as u32) << IXGBE_ADVTXD_PAYLEN_SHIFT; + + self.transmit_index = wrap_ring(self.transmit_index, self.transmit_ring.len()); + self.transmit_ring_free -= 1; + + self.write_reg(IXGBE_TDT(0), self.transmit_index as u32); + + Ok(i) + } +} + +impl Intel8259x { + /// Returns an initialized `Intel8259x` on success. + pub fn new(base: usize, size: usize) -> Result { + #[rustfmt::skip] + let mut module = Intel8259x { + base, + size, + receive_buffer: (0..32) + .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()), + receive_ring: unsafe { Dma::zeroed()?.assume_init() }, + transmit_buffer: (0..32) + .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()), + receive_index: 0, + transmit_ring: unsafe { Dma::zeroed()?.assume_init() }, + transmit_ring_free: 32, + transmit_index: 0, + transmit_clean_index: 0, + mac_address: [0; 6], + }; + + module.init(); + + Ok(module) + } + + pub fn irq(&self) -> bool { + let icr = self.read_reg(IXGBE_EICR); + icr != 0 + } + + pub fn next_read(&self) -> usize { + let desc = unsafe { + &*(self.receive_ring.as_ptr().add(self.receive_index) as *const ixgbe_adv_rx_desc) + }; + + let status = unsafe { desc.wb.upper.status_error }; + + if (status & IXGBE_RXDADV_STAT_DD) != 0 { + if (status & IXGBE_RXDADV_STAT_EOP) == 0 { + panic!("increase buffer size or decrease MTU") + } + + return unsafe { desc.wb.upper.length as usize }; + } + + 0 + } + + /// Returns the mac address of this device. + pub fn get_mac_addr(&self) -> [u8; 6] { + let low = self.read_reg(IXGBE_RAL(0)); + let high = self.read_reg(IXGBE_RAH(0)); + + [ + (low & 0xff) as u8, + (low >> 8 & 0xff) as u8, + (low >> 16 & 0xff) as u8, + (low >> 24) as u8, + (high & 0xff) as u8, + (high >> 8 & 0xff) as u8, + ] + } + + /// Sets the mac address of this device. + #[allow(dead_code)] + pub fn set_mac_addr(&mut self, mac: [u8; 6]) { + let low: u32 = u32::from(mac[0]) + + (u32::from(mac[1]) << 8) + + (u32::from(mac[2]) << 16) + + (u32::from(mac[3]) << 24); + let high: u32 = u32::from(mac[4]) + (u32::from(mac[5]) << 8); + + self.write_reg(IXGBE_RAL(0), low); + self.write_reg(IXGBE_RAH(0), high); + + self.mac_address = mac; + } + + /// Returns the register at `self.base` + `register`. + /// + /// # Panics + /// + /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. + fn read_reg(&self, register: u32) -> u32 { + assert!( + register as usize <= self.size - 4 as usize, + "MMIO access out of bounds" + ); + + unsafe { ptr::read_volatile((self.base + register as usize) as *mut u32) } + } + + /// Sets the register at `self.base` + `register`. + /// + /// # Panics + /// + /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. + fn write_reg(&self, register: u32, data: u32) -> u32 { + assert!( + register as usize <= self.size - 4 as usize, + "MMIO access out of bounds" + ); + + unsafe { + ptr::write_volatile((self.base + register as usize) as *mut u32, data); + ptr::read_volatile((self.base + register as usize) as *mut u32) + } + } + + fn write_flag(&self, register: u32, flags: u32) { + self.write_reg(register, self.read_reg(register) | flags); + } + + fn clear_flag(&self, register: u32, flags: u32) { + self.write_reg(register, self.read_reg(register) & !flags); + } + + fn wait_clear_reg(&self, register: u32, value: u32) { + loop { + let current = self.read_reg(register); + if (current & value) == 0 { + break; + } + thread::sleep(Duration::from_millis(100)); + } + } + + fn wait_write_reg(&self, register: u32, value: u32) { + loop { + let current = self.read_reg(register); + if (current & value) == value { + break; + } + thread::sleep(Duration::from_millis(100)); + } + } + + /// Resets and initializes an ixgbe device. + fn init(&mut self) { + // section 4.6.3.1 - disable all interrupts + self.write_reg(IXGBE_EIMC, 0x7fff_ffff); + + // section 4.6.3.2 + self.write_reg(IXGBE_CTRL, IXGBE_CTRL_RST_MASK); + self.wait_clear_reg(IXGBE_CTRL, IXGBE_CTRL_RST_MASK); + thread::sleep(Duration::from_millis(10)); + + // section 4.6.3.1 - disable interrupts again after reset + self.write_reg(IXGBE_EIMC, 0x7fff_ffff); + + let mac = self.get_mac_addr(); + + println!( + " - MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + self.mac_address = mac; + + // section 4.6.3 - wait for EEPROM auto read completion + self.wait_write_reg(IXGBE_EEC, IXGBE_EEC_ARD); + + // section 4.6.3 - wait for dma initialization done + self.wait_write_reg( + IXGBE_RDRXCTL, + IXGBE_RDRXCTL_DMAIDONE | IXGBE_RDRXCTL_RESERVED_BITS, + ); + + // section 4.6.4 - initialize link (auto negotiation) + self.init_link(); + + // section 4.6.5 - statistical counters + // reset-on-read registers, just read them once + self.reset_stats(); + + // section 4.6.7 - init rx + self.init_rx(); + + // section 4.6.8 - init tx + self.init_tx(); + + // start a single receive queue/ring + self.start_rx_queue(0); + + // start a single transmit queue/ring + self.start_tx_queue(0); + + // section 4.6.3.9 - enable interrupts + self.enable_msix_interrupt(0); + + // wait some time for the link to come up + self.wait_for_link(); + } + + /// Resets the stats of this device. + fn reset_stats(&self) { + self.read_reg(IXGBE_GPRC); + self.read_reg(IXGBE_GPTC); + self.read_reg(IXGBE_GORCL); + self.read_reg(IXGBE_GORCH); + self.read_reg(IXGBE_GOTCL); + self.read_reg(IXGBE_GOTCH); + } + + // sections 4.6.7 + /// Initializes the rx queues of this device. + fn init_rx(&mut self) { + // disable rx while re-configuring it + self.clear_flag(IXGBE_RXCTRL, IXGBE_RXCTRL_RXEN); + + // section 4.6.11.3.4 - allocate all queues and traffic to PB0 + self.write_reg(IXGBE_RXPBSIZE(0), IXGBE_RXPBSIZE_128KB); + for i in 1..8 { + self.write_reg(IXGBE_RXPBSIZE(i), 0); + } + + // enable CRC offloading + self.write_flag(IXGBE_HLREG0, IXGBE_HLREG0_RXCRCSTRP); + self.write_flag(IXGBE_RDRXCTL, IXGBE_RDRXCTL_CRCSTRIP); + + // accept broadcast packets + self.write_flag(IXGBE_FCTRL, IXGBE_FCTRL_BAM); + + // configure a single receive queue/ring + let i: u32 = 0; + + // enable advanced rx descriptors + self.write_reg( + IXGBE_SRRCTL(i), + (self.read_reg(IXGBE_SRRCTL(i)) & !IXGBE_SRRCTL_DESCTYPE_MASK) + | IXGBE_SRRCTL_DESCTYPE_ADV_ONEBUF, + ); + // let nic drop packets if no rx descriptor is available instead of buffering them + self.write_flag(IXGBE_SRRCTL(i), IXGBE_SRRCTL_DROP_EN); + + self.write_reg(IXGBE_RDBAL(i), self.receive_ring.physical() as u32); + + self.write_reg( + IXGBE_RDBAH(i), + ((self.receive_ring.physical() as u64) >> 32) as u32, + ); + self.write_reg( + IXGBE_RDLEN(i), + (self.receive_ring.len() * mem::size_of::()) as u32, + ); + + // set ring to empty at start + self.write_reg(IXGBE_RDH(i), 0); + self.write_reg(IXGBE_RDT(i), 0); + + // last sentence of section 4.6.7 - set some magic bits + self.write_flag(IXGBE_CTRL_EXT, IXGBE_CTRL_EXT_NS_DIS); + + // probably a broken feature, this flag is initialized with 1 but has to be set to 0 + self.clear_flag(IXGBE_DCA_RXCTRL(i), 1 << 12); + + // enable promisc mode by default to make testing easier + // this has to be done when the rxctrl.rxen bit is not set + self.set_promisc(true); + + // start rx + self.write_flag(IXGBE_RXCTRL, IXGBE_RXCTRL_RXEN); + } + + // section 4.6.8 + /// Initializes the tx queues of this device. + fn init_tx(&mut self) { + // crc offload and small packet padding + self.write_flag(IXGBE_HLREG0, IXGBE_HLREG0_TXCRCEN | IXGBE_HLREG0_TXPADEN); + + // section 4.6.11.3.4 - set default buffer size allocations + self.write_reg(IXGBE_TXPBSIZE(0), IXGBE_TXPBSIZE_40KB); + for i in 1..8 { + self.write_reg(IXGBE_TXPBSIZE(i), 0); + } + + // required when not using DCB/VTd + self.write_reg(IXGBE_DTXMXSZRQ, 0xfff); + self.clear_flag(IXGBE_RTTDCS, IXGBE_RTTDCS_ARBDIS); + + // configure a single transmit queue/ring + let i: u32 = 0; + + // section 7.1.9 - setup descriptor ring + + self.write_reg(IXGBE_TDBAL(i), self.transmit_ring.physical() as u32); + self.write_reg( + IXGBE_TDBAH(i), + ((self.transmit_ring.physical() as u64) >> 32) as u32, + ); + self.write_reg( + IXGBE_TDLEN(i), + (self.transmit_ring.len() * mem::size_of::()) as u32, + ); + + // descriptor writeback magic values, important to get good performance and low PCIe overhead + // see 7.2.3.4.1 and 7.2.3.5 for an explanation of these values and how to find good ones + // we just use the defaults from DPDK here, but this is a potentially interesting point for optimizations + let mut txdctl = self.read_reg(IXGBE_TXDCTL(i)); + // there are no defines for this in ixgbe.rs for some reason + // pthresh: 6:0, hthresh: 14:8, wthresh: 22:16 + txdctl &= !(0x3F | (0x3F << 8) | (0x3F << 16)); + txdctl |= 36 | (8 << 8) | (4 << 16); + + self.write_reg(IXGBE_TXDCTL(i), txdctl); + + // final step: enable DMA + self.write_reg(IXGBE_DMATXCTL, IXGBE_DMATXCTL_TE); + } + + /// Sets the rx queues` descriptors and enables the queues. + /// + /// # Panics + /// Panics if length of `self.receive_ring` is not a power of 2. + fn start_rx_queue(&mut self, queue_id: u16) { + if self.receive_ring.len() & (self.receive_ring.len() - 1) != 0 { + panic!("number of receive queue entries must be a power of 2"); + } + + for i in 0..self.receive_ring.len() { + self.receive_ring[i].read.pkt_addr = self.receive_buffer[i].physical() as u64; + self.receive_ring[i].read.hdr_addr = 0; + } + + // enable queue and wait if necessary + self.write_flag(IXGBE_RXDCTL(u32::from(queue_id)), IXGBE_RXDCTL_ENABLE); + self.wait_write_reg(IXGBE_RXDCTL(u32::from(queue_id)), IXGBE_RXDCTL_ENABLE); + + // rx queue starts out full + self.write_reg(IXGBE_RDH(u32::from(queue_id)), 0); + + // was set to 0 before in the init function + self.write_reg( + IXGBE_RDT(u32::from(queue_id)), + (self.receive_ring.len() - 1) as u32, + ); + } + + /// Enables the tx queues. + /// + /// # Panics + /// Panics if length of `self.transmit_ring` is not a power of 2. + fn start_tx_queue(&mut self, queue_id: u16) { + if self.transmit_ring.len() & (self.transmit_ring.len() - 1) != 0 { + panic!("number of receive queue entries must be a power of 2"); + } + + for i in 0..self.transmit_ring.len() { + self.transmit_ring[i].read.buffer_addr = self.transmit_buffer[i].physical() as u64; + } + + // tx queue starts out empty + self.write_reg(IXGBE_TDH(u32::from(queue_id)), 0); + self.write_reg(IXGBE_TDT(u32::from(queue_id)), 0); + + // enable queue and wait if necessary + self.write_flag(IXGBE_TXDCTL(u32::from(queue_id)), IXGBE_TXDCTL_ENABLE); + self.wait_write_reg(IXGBE_TXDCTL(u32::from(queue_id)), IXGBE_TXDCTL_ENABLE); + } + + // see section 4.6.4 + /// Initializes the link of this device. + fn init_link(&self) { + // link auto-configuration register should already be set correctly, we're resetting it anyway + self.write_reg( + IXGBE_AUTOC, + (self.read_reg(IXGBE_AUTOC) & !IXGBE_AUTOC_LMS_MASK) | IXGBE_AUTOC_LMS_10G_SERIAL, + ); + self.write_reg( + IXGBE_AUTOC, + (self.read_reg(IXGBE_AUTOC) & !IXGBE_AUTOC_10G_PMA_PMD_MASK) | IXGBE_AUTOC_10G_XAUI, + ); + // negotiate link + self.write_flag(IXGBE_AUTOC, IXGBE_AUTOC_AN_RESTART); + // datasheet wants us to wait for the link here, but we can continue and wait afterwards + } + + /// Waits for the link to come up. + fn wait_for_link(&self) { + println!(" - waiting for link"); + let time = Instant::now(); + let mut speed = self.get_link_speed(); + while speed == 0 && time.elapsed().as_secs() < 10 { + thread::sleep(Duration::from_millis(100)); + speed = self.get_link_speed(); + } + println!(" - link speed is {} Mbit/s", self.get_link_speed()); + } + + /// Enables or disables promisc mode of this device. + fn set_promisc(&self, enabled: bool) { + if enabled { + self.write_flag(IXGBE_FCTRL, IXGBE_FCTRL_MPE | IXGBE_FCTRL_UPE); + } else { + self.clear_flag(IXGBE_FCTRL, IXGBE_FCTRL_MPE | IXGBE_FCTRL_UPE); + } + } + + /// Set the IVAR registers, mapping interrupt causes to vectors. + fn set_ivar(&mut self, direction: i8, queue_id: u16, mut msix_vector: u8) { + let index = ((16 * (queue_id & 1)) as i16 + i16::from(8 * direction)) as u32; + + msix_vector |= IXGBE_IVAR_ALLOC_VAL as u8; + + let mut ivar = self.read_reg(IXGBE_IVAR(u32::from(queue_id >> 1))); + ivar &= !(0xFF << index); + ivar |= u32::from(msix_vector << index); + + self.write_reg(IXGBE_IVAR(u32::from(queue_id >> 1)), ivar); + } + + /// Enable MSI-X interrupt for a queue. + fn enable_msix_interrupt(&mut self, queue_id: u16) { + // Step 1: The software driver associates between interrupt causes and MSI-X vectors and the + //throttling timers EITR[n] by programming the IVAR[n] and IVAR_MISC registers. + self.set_ivar(0, queue_id, queue_id as u8); + + // Step 2: Program SRRCTL[n].RDMTS (per receive queue) if software uses the receive + // descriptor minimum threshold interrupt + + // Step 3: The EIAC[n] registers should be set to auto clear for transmit and receive interrupt + // causes (for best performance). The EIAC bits that control the other and TCP timer + // interrupt causes should be set to 0b (no auto clear). + self.write_reg(IXGBE_EIAC, IXGBE_EICR_RTX_QUEUE); + + // Step 4: Set the auto mask in the EIAM register according to the preferred mode of operation. + + // Step 5: Set the interrupt throttling in EITR[n] and GPIE according to the preferred mode of operation. + + // Step 6: Software enables the required interrupt causes by setting the EIMS register + let mut mask: u32 = self.read_reg(IXGBE_EIMS); + mask |= 1 << queue_id; + + self.write_reg(IXGBE_EIMS, mask); + } + + /// Returns the link speed of this device. + fn get_link_speed(&self) -> u16 { + let speed = self.read_reg(IXGBE_LINKS); + if (speed & IXGBE_LINKS_UP) == 0 { + return 0; + } + match speed & IXGBE_LINKS_SPEED_82599 { + IXGBE_LINKS_SPEED_100_82599 => 100, + IXGBE_LINKS_SPEED_1G_82599 => 1000, + IXGBE_LINKS_SPEED_10G_82599 => 10000, + _ => 0, + } + } +} diff --git a/drivers/net/ixgbed/src/ixgbe.rs b/drivers/net/ixgbed/src/ixgbe.rs new file mode 100644 index 0000000000..8d77959444 --- /dev/null +++ b/drivers/net/ixgbed/src/ixgbe.rs @@ -0,0 +1,315 @@ +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(clippy::unreadable_literal)] + +pub const IXGBE_EIMC: u32 = 0x00888; + +pub const IXGBE_CTRL: u32 = 0x00000; +pub const IXGBE_CTRL_LNK_RST: u32 = 0x00000008; /* Link Reset. Resets everything. */ +pub const IXGBE_CTRL_RST: u32 = 0x04000000; /* Reset (SW) */ +pub const IXGBE_CTRL_RST_MASK: u32 = IXGBE_CTRL_LNK_RST | IXGBE_CTRL_RST; + +pub const IXGBE_EEC: u32 = 0x10010; +pub const IXGBE_EEC_ARD: u32 = 0x00000200; /* EEPROM Auto Read Done */ + +pub const IXGBE_RDRXCTL: u32 = 0x02F00; +pub const IXGBE_RDRXCTL_RESERVED_BITS: u32 = 1 << 25 | 1 << 26; +pub const IXGBE_RDRXCTL_DMAIDONE: u32 = 0x00000008; /* DMA init cycle done */ + +pub const IXGBE_AUTOC: u32 = 0x042A0; +pub const IXGBE_AUTOC_LMS_SHIFT: u32 = 13; +pub const IXGBE_AUTOC_LMS_MASK: u32 = 0x7 << IXGBE_AUTOC_LMS_SHIFT; +pub const IXGBE_AUTOC_LMS_10G_SERIAL: u32 = 0x3 << IXGBE_AUTOC_LMS_SHIFT; +pub const IXGBE_AUTOC_10G_PMA_PMD_MASK: u32 = 0x00000180; +pub const IXGBE_AUTOC_10G_PMA_PMD_SHIFT: u32 = 7; +pub const IXGBE_AUTOC_10G_XAUI: u32 = 0x0 << IXGBE_AUTOC_10G_PMA_PMD_SHIFT; +pub const IXGBE_AUTOC_AN_RESTART: u32 = 0x00001000; + +pub const IXGBE_GPRC: u32 = 0x04074; +pub const IXGBE_GPTC: u32 = 0x04080; +pub const IXGBE_GORCL: u32 = 0x04088; +pub const IXGBE_GORCH: u32 = 0x0408C; +pub const IXGBE_GOTCL: u32 = 0x04090; +pub const IXGBE_GOTCH: u32 = 0x04094; + +pub const IXGBE_RXCTRL: u32 = 0x03000; +pub const IXGBE_RXCTRL_RXEN: u32 = 0x00000001; /* Enable Receiver */ + +pub fn IXGBE_RXPBSIZE(i: u32) -> u32 { + 0x03C00 + (i * 4) +} + +pub const IXGBE_RXPBSIZE_128KB: u32 = 0x00020000; /* 128KB Packet Buffer */ +pub const IXGBE_HLREG0: u32 = 0x04240; +pub const IXGBE_HLREG0_RXCRCSTRP: u32 = 0x00000002; /* bit 1 */ +pub const IXGBE_RDRXCTL_CRCSTRIP: u32 = 0x00000002; /* CRC Strip */ + +pub const IXGBE_FCTRL: u32 = 0x05080; +pub const IXGBE_FCTRL_BAM: u32 = 0x00000400; /* Broadcast Accept Mode */ + +pub fn IXGBE_SRRCTL(i: u32) -> u32 { + if i <= 15 { + 0x02100 + (i * 4) + } else if i < 64 { + 0x01014 + (i * 0x40) + } else { + 0x0D014 + ((i - 64) * 0x40) + } +} + +pub const IXGBE_SRRCTL_DESCTYPE_MASK: u32 = 0x0E000000; +pub const IXGBE_SRRCTL_DESCTYPE_ADV_ONEBUF: u32 = 0x02000000; +pub const IXGBE_SRRCTL_DROP_EN: u32 = 0x10000000; + +pub fn IXGBE_RDBAL(i: u32) -> u32 { + if i < 64 { + 0x01000 + (i * 0x40) + } else { + 0x0D000 + ((i - 64) * 0x40) + } +} +pub fn IXGBE_RDBAH(i: u32) -> u32 { + if i < 64 { + 0x01004 + (i * 0x40) + } else { + 0x0D004 + ((i - 64) * 0x40) + } +} +pub fn IXGBE_RDLEN(i: u32) -> u32 { + if i < 64 { + 0x01008 + (i * 0x40) + } else { + 0x0D008 + ((i - 64) * 0x40) + } +} +pub fn IXGBE_RDH(i: u32) -> u32 { + if i < 64 { + 0x01010 + (i * 0x40) + } else { + 0x0D010 + ((i - 64) * 0x40) + } +} +pub fn IXGBE_RDT(i: u32) -> u32 { + if i < 64 { + 0x01018 + (i * 0x40) + } else { + 0x0D018 + ((i - 64) * 0x40) + } +} + +pub const IXGBE_CTRL_EXT: u32 = 0x00018; +pub const IXGBE_CTRL_EXT_NS_DIS: u32 = 0x00010000; /* No Snoop disable */ + +pub fn IXGBE_DCA_RXCTRL(i: u32) -> u32 { + if i <= 15 { + 0x02200 + (i * 4) + } else if i < 64 { + 0x0100C + (i * 0x40) + } else { + 0x0D00C + ((i - 64) * 0x40) + } +} + +pub const IXGBE_HLREG0_TXCRCEN: u32 = 0x00000001; /* bit 0 */ +pub const IXGBE_HLREG0_TXPADEN: u32 = 0x00000400; /* bit 10 */ + +pub fn IXGBE_TXPBSIZE(i: u32) -> u32 { + 0x0CC00 + (i * 4) +} /* 8 of these */ + +pub const IXGBE_TXPBSIZE_40KB: u32 = 0x0000A000; /* 40KB Packet Buffer */ +pub const IXGBE_DTXMXSZRQ: u32 = 0x08100; +pub const IXGBE_RTTDCS: u32 = 0x04900; +pub const IXGBE_RTTDCS_ARBDIS: u32 = 0x00000040; /* DCB arbiter disable */ + +pub fn IXGBE_TDBAL(i: u32) -> u32 { + 0x06000 + (i * 0x40) +} /* 32 of them (0-31)*/ +pub fn IXGBE_TDBAH(i: u32) -> u32 { + 0x06004 + (i * 0x40) +} +pub fn IXGBE_TDLEN(i: u32) -> u32 { + 0x06008 + (i * 0x40) +} +pub fn IXGBE_TXDCTL(i: u32) -> u32 { + 0x06028 + (i * 0x40) +} + +pub const IXGBE_DMATXCTL: u32 = 0x04A80; +pub const IXGBE_DMATXCTL_TE: u32 = 0x1; /* Transmit Enable */ + +pub fn IXGBE_RXDCTL(i: u32) -> u32 { + if i < 64 { + 0x01028 + (i * 0x40) + } else { + 0x0D028 + ((i - 64) * 0x40) + } +} +pub const IXGBE_RXDCTL_ENABLE: u32 = 0x02000000; /* Ena specific Rx Queue */ +pub const IXGBE_TXDCTL_ENABLE: u32 = 0x02000000; /* Ena specific Tx Queue */ + +pub fn IXGBE_TDH(i: u32) -> u32 { + 0x06010 + (i * 0x40) +} +pub fn IXGBE_TDT(i: u32) -> u32 { + 0x06018 + (i * 0x40) +} + +pub const IXGBE_FCTRL_MPE: u32 = 0x00000100; /* Multicast Promiscuous Ena*/ +pub const IXGBE_FCTRL_UPE: u32 = 0x00000200; /* Unicast Promiscuous Ena */ + +pub const IXGBE_LINKS: u32 = 0x042A4; +pub const IXGBE_LINKS_UP: u32 = 0x40000000; +pub const IXGBE_LINKS_SPEED_82599: u32 = 0x30000000; +pub const IXGBE_LINKS_SPEED_100_82599: u32 = 0x10000000; +pub const IXGBE_LINKS_SPEED_1G_82599: u32 = 0x20000000; +pub const IXGBE_LINKS_SPEED_10G_82599: u32 = 0x30000000; + +pub fn IXGBE_RAL(i: u32) -> u32 { + if i <= 15 { + 0x05400 + (i * 8) + } else { + 0x0A200 + (i * 8) + } +} + +pub fn IXGBE_RAH(i: u32) -> u32 { + if i <= 15 { + 0x05404 + (i * 8) + } else { + 0x0A204 + (i * 8) + } +} + +pub const IXGBE_RXD_STAT_DD: u32 = 0x01; /* Descriptor Done */ +pub const IXGBE_RXD_STAT_EOP: u32 = 0x02; /* End of Packet */ +pub const IXGBE_RXDADV_STAT_DD: u32 = IXGBE_RXD_STAT_DD; /* Done */ +pub const IXGBE_RXDADV_STAT_EOP: u32 = IXGBE_RXD_STAT_EOP; /* End of Packet */ + +pub const IXGBE_ADVTXD_PAYLEN_SHIFT: u32 = 14; /* Adv desc PAYLEN shift */ +pub const IXGBE_TXD_CMD_EOP: u32 = 0x01000000; /* End of Packet */ +pub const IXGBE_ADVTXD_DCMD_EOP: u32 = IXGBE_TXD_CMD_EOP; /* End of Packet */ +pub const IXGBE_TXD_CMD_RS: u32 = 0x08000000; /* Report Status */ +pub const IXGBE_ADVTXD_DCMD_RS: u32 = IXGBE_TXD_CMD_RS; /* Report Status */ +pub const IXGBE_TXD_CMD_IFCS: u32 = 0x02000000; /* Insert FCS (Ethernet CRC) */ +pub const IXGBE_ADVTXD_DCMD_IFCS: u32 = IXGBE_TXD_CMD_IFCS; /* Insert FCS */ +pub const IXGBE_TXD_CMD_DEXT: u32 = 0x20000000; /* Desc extension (0 = legacy) */ +pub const IXGBE_ADVTXD_DTYP_DATA: u32 = 0x00300000; /* Adv Data Descriptor */ +pub const IXGBE_ADVTXD_DCMD_DEXT: u32 = IXGBE_TXD_CMD_DEXT; /* Desc ext 1=Adv */ +pub const IXGBE_TXD_STAT_DD: u32 = 0x00000001; /* Descriptor Done */ +pub const IXGBE_ADVTXD_STAT_DD: u32 = IXGBE_TXD_STAT_DD; /* Descriptor Done */ + +/* Interrupt Registers */ +pub const IXGBE_EICR: u32 = 0x00800; +pub const IXGBE_EIAC: u32 = 0x00810; +pub const IXGBE_EIMS: u32 = 0x00880; +pub const IXGBE_IVAR_ALLOC_VAL: u32 = 0x80; /* Interrupt Allocation valid */ +pub const IXGBE_EICR_RTX_QUEUE: u32 = 0x0000FFFF; /* RTx Queue Interrupt */ + +pub fn IXGBE_IVAR(i: u32) -> u32 { + 0x00900 + (i * 4) +} /* 24 at 0x900-0x960 */ + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_rx_desc_read { + pub pkt_addr: u64, + /* Packet buffer address */ + pub hdr_addr: u64, + /* Header buffer address */ +} + +/* Receive Descriptor - Advanced */ +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_rx_desc_wb_lower_lo_dword_hs_rss { + pub pkt_info: u16, + /* RSS, Pkt type */ + pub hdr_info: u16, + /* Splithdr, hdrlen */ +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub union ixgbe_adv_rx_desc_wb_lower_lo_dword { + pub data: u32, + pub hs_rss: ixgbe_adv_rx_desc_wb_lower_lo_dword_hs_rss, +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_rx_desc_wb_lower_hi_dword_csum_ip { + pub ip_id: u16, + /* IP id */ + pub csum: u16, + /* Packet Checksum */ +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub union ixgbe_adv_rx_desc_wb_lower_hi_dword { + pub rss: u32, + /* RSS Hash */ + pub csum_ip: ixgbe_adv_rx_desc_wb_lower_hi_dword_csum_ip, +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_rx_desc_wb_lower { + pub lo_dword: ixgbe_adv_rx_desc_wb_lower_lo_dword, + pub hi_dword: ixgbe_adv_rx_desc_wb_lower_hi_dword, +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_rx_desc_wb_upper { + pub status_error: u32, + /* ext status/error */ + pub length: u16, + /* Packet length */ + pub vlan: u16, + /* VLAN tag */ +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_rx_desc_wb { + pub lower: ixgbe_adv_rx_desc_wb_lower, + pub upper: ixgbe_adv_rx_desc_wb_upper, +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub union ixgbe_adv_rx_desc { + pub read: ixgbe_adv_rx_desc_read, + pub wb: ixgbe_adv_rx_desc_wb, /* writeback */ + _union_align: [u64; 2], +} + +/* Transmit Descriptor - Advanced */ +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_tx_desc_read { + pub buffer_addr: u64, + /* Address of descriptor's data buf */ + pub cmd_type_len: u32, + pub olinfo_status: u32, +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct ixgbe_adv_tx_desc_wb { + pub rsvd: u64, + /* Reserved */ + pub nxtseq_seed: u32, + pub status: u32, +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub union ixgbe_adv_tx_desc { + pub read: ixgbe_adv_tx_desc_read, + pub wb: ixgbe_adv_tx_desc_wb, + _union_align: [u64; 2], +} diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs new file mode 100644 index 0000000000..c1aa533572 --- /dev/null +++ b/drivers/net/ixgbed/src/main.rs @@ -0,0 +1,89 @@ +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; + +use driver_network::NetworkScheme; +use event::{user_data, EventQueue}; +use pcid_interface::PciFunctionHandle; + +pub mod device; +#[rustfmt::skip] +mod ixgbe; + +fn main() { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_ixgbe"); + + let irq = pci_config + .func + .legacy_interrupt_line + .expect("ixgbed: no legacy interrupts supported"); + + println!(" + IXGBE {}", pci_config.func.display()); + + redox_daemon::Daemon::new(move |daemon| { + let mut irq_file = irq.irq_handle("ixgbed"); + + let mapped_bar = unsafe { pcid_handle.map_bar(0) }; + let address = mapped_bar.ptr.as_ptr(); + let size = mapped_bar.bar_size; + + let mut scheme = NetworkScheme::new( + move || { + device::Intel8259x::new(address as usize, size) + .expect("ixgbed: failed to allocate device") + }, + daemon, + format!("network.{name}"), + ); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = + EventQueue::::new().expect("ixgbed: Could not create event queue."); + event_queue + .subscribe( + irq_file.as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("ixgbed: failed to enter null namespace"); + + scheme.tick().unwrap(); + + for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) { + match event.user_data { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.read(&mut irq).unwrap(); + if scheme.adapter().irq() { + irq_file.write(&mut irq).unwrap(); + + scheme.tick().unwrap(); + } + } + Source::Scheme => { + scheme.tick().unwrap(); + } + } + } + unreachable!() + }) + .expect("ixgbed: failed to create daemon"); +} diff --git a/drivers/net/rtl8139d/Cargo.toml b/drivers/net/rtl8139d/Cargo.toml new file mode 100644 index 0000000000..cd6ed71f17 --- /dev/null +++ b/drivers/net/rtl8139d/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rtl8139d" +version = "0.1.0" +edition = "2018" + +[dependencies] +bitflags = "2" +libredox = "0.1.3" +log = "0.4" +redox_event = "0.4.1" +redox_syscall = "0.5" +redox-daemon = "0.1" + +common = { path = "../../common" } +driver-network = { path = "../driver-network" } +pcid = { path = "../../pcid" } diff --git a/drivers/net/rtl8139d/config.toml b/drivers/net/rtl8139d/config.toml new file mode 100644 index 0000000000..05c5322479 --- /dev/null +++ b/drivers/net/rtl8139d/config.toml @@ -0,0 +1,5 @@ +[[drivers]] +name = "RTL8139 NIC" +class = 0x02 +ids = { 0x10ec = [0x8139] } +command = ["rtl8139d"] diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs new file mode 100644 index 0000000000..37167ee2fb --- /dev/null +++ b/drivers/net/rtl8139d/src/device.rs @@ -0,0 +1,309 @@ +use std::convert::TryInto; +use std::mem; + +use driver_network::NetworkAdapter; +use syscall::error::{Error, Result, EIO, EMSGSIZE}; + +use common::dma::Dma; +use common::io::{Io, Mmio, ReadOnly}; +use common::timeout::Timeout; + +const RX_BUFFER_SIZE: usize = 64 * 1024; + +const RXSTS_ROK: u16 = 1 << 0; + +const TSD_TOK: u32 = 1 << 15; +const TSD_OWN: u32 = 1 << 13; +const TSD_SIZE_MASK: u32 = 0x1FFF; + +const CR_RST: u8 = 1 << 4; +const CR_RE: u8 = 1 << 3; +const CR_TE: u8 = 1 << 2; +const CR_BUFE: u8 = 1 << 0; + +const IMR_TOK: u16 = 1 << 2; +const IMR_ROK: u16 = 1 << 0; + +const RCR_RBLEN_8K: u32 = 0b00 << 11; +const RCR_RBLEN_16K: u32 = 0b01 << 11; +const RCR_RBLEN_32K: u32 = 0b10 << 11; +const RCR_RBLEN_64K: u32 = 0b11 << 11; +const RCR_RBLEN_MASK: u32 = 0b11 << 11; +const RCR_AER: u32 = 1 << 5; +const RCR_AR: u32 = 1 << 4; +const RCR_AB: u32 = 1 << 3; +const RCR_AM: u32 = 1 << 2; +const RCR_APM: u32 = 1 << 1; +const RCR_AAP: u32 = 1 << 0; + +#[repr(C, packed)] +struct Regs { + mac: [Mmio; 2], + mar: [Mmio; 2], + tsd: [Mmio; 4], + tsad: [Mmio; 4], + rbstart: Mmio, + erbcr: ReadOnly>, + ersr: ReadOnly>, + cr: Mmio, + capr: Mmio, + cbr: ReadOnly>, + imr: Mmio, + isr: Mmio, + tcr: Mmio, + rcr: Mmio, + tctr: Mmio, + mpc: Mmio, + cr_9346: Mmio, + config0: Mmio, + config1: Mmio, + rsvd_53: ReadOnly>, + timer_int: Mmio, + msr: Mmio, + config2: Mmio, + config3: Mmio, + rsvd_5b: ReadOnly>, + mulint: Mmio, + rerid: ReadOnly>, + rsvd_5f: ReadOnly>, + tsts: ReadOnly>, + _todo: [ReadOnly>; 158], +} + +impl Regs { + unsafe fn from_base(base: usize) -> &'static mut Self { + assert_eq!(mem::size_of::(), 256); + + let regs = &mut *(base as *mut Regs); + + assert_eq!(®s.mac[0] as *const _ as usize - base, 0x00); + assert_eq!(®s.mac[1] as *const _ as usize - base, 0x04); + assert_eq!(®s.mar[0] as *const _ as usize - base, 0x08); + assert_eq!(®s.mar[1] as *const _ as usize - base, 0x0C); + assert_eq!(®s.tsd[0] as *const _ as usize - base, 0x10); + assert_eq!(®s.tsd[1] as *const _ as usize - base, 0x14); + assert_eq!(®s.tsd[2] as *const _ as usize - base, 0x18); + assert_eq!(®s.tsd[3] as *const _ as usize - base, 0x1C); + assert_eq!(®s.tsad[0] as *const _ as usize - base, 0x20); + assert_eq!(®s.tsad[1] as *const _ as usize - base, 0x24); + assert_eq!(®s.tsad[2] as *const _ as usize - base, 0x28); + assert_eq!(®s.tsad[3] as *const _ as usize - base, 0x2C); + assert_eq!(®s.rbstart as *const _ as usize - base, 0x30); + assert_eq!(®s.erbcr as *const _ as usize - base, 0x34); + assert_eq!(®s.ersr as *const _ as usize - base, 0x36); + assert_eq!(®s.cr as *const _ as usize - base, 0x37); + assert_eq!(®s.capr as *const _ as usize - base, 0x38); + assert_eq!(®s.cbr as *const _ as usize - base, 0x3A); + assert_eq!(®s.imr as *const _ as usize - base, 0x3C); + assert_eq!(®s.isr as *const _ as usize - base, 0x3E); + assert_eq!(®s.tcr as *const _ as usize - base, 0x40); + assert_eq!(®s.rcr as *const _ as usize - base, 0x44); + assert_eq!(®s.tctr as *const _ as usize - base, 0x48); + assert_eq!(®s.mpc as *const _ as usize - base, 0x4C); + assert_eq!(®s.cr_9346 as *const _ as usize - base, 0x50); + assert_eq!(®s.config0 as *const _ as usize - base, 0x51); + assert_eq!(®s.config1 as *const _ as usize - base, 0x52); + assert_eq!(®s.rsvd_53 as *const _ as usize - base, 0x53); + assert_eq!(®s.timer_int as *const _ as usize - base, 0x54); + assert_eq!(®s.msr as *const _ as usize - base, 0x58); + assert_eq!(®s.config2 as *const _ as usize - base, 0x59); + assert_eq!(®s.config3 as *const _ as usize - base, 0x5A); + assert_eq!(®s.rsvd_5b as *const _ as usize - base, 0x5B); + assert_eq!(®s.mulint as *const _ as usize - base, 0x5C); + assert_eq!(®s.rerid as *const _ as usize - base, 0x5E); + assert_eq!(®s.rsvd_5f as *const _ as usize - base, 0x5F); + assert_eq!(®s.tsts as *const _ as usize - base, 0x60); + + regs + } +} + +pub struct Rtl8139 { + regs: &'static mut Regs, + receive_buffer: Dma<[Mmio; RX_BUFFER_SIZE + 16]>, + receive_i: usize, + transmit_buffer: [Dma<[Mmio; 1792]>; 4], + transmit_i: usize, + mac_address: [u8; 6], +} + +impl NetworkAdapter for Rtl8139 { + fn mac_address(&mut self) -> [u8; 6] { + self.mac_address + } + + fn available_for_read(&mut self) -> usize { + self.next_read() + } + + fn read_packet(&mut self, buf: &mut [u8]) -> Result> { + if !self.regs.cr.readf(CR_BUFE) { + let rxsts = (self.rx(0) as u16) | (self.rx(1) as u16) << 8; + + let size_with_crc = (self.rx(2) as usize) | (self.rx(3) as usize) << 8; + + let res = if (rxsts & RXSTS_ROK) == RXSTS_ROK { + let mut i = 0; + while i < buf.len() && i < size_with_crc.saturating_sub(4) { + buf[i] = self.rx(4 + i as u16); + i += 1; + } + Ok(Some(i)) + } else { + //TODO: better error types + log::error!("invalid receive status 0x{:X}", rxsts); + Err(Error::new(EIO)) + }; + + self.receive_i = + (self.receive_i + 4 + size_with_crc).next_multiple_of(4) % RX_BUFFER_SIZE; + let capr = self.receive_i.wrapping_sub(16) as u16; + self.regs.capr.write(capr); + + res + } else { + Ok(None) + } + } + + fn write_packet(&mut self, buf: &[u8]) -> Result { + loop { + if self.transmit_i >= 4 { + self.transmit_i = 0; + } + + if self.regs.tsd[self.transmit_i].readf(TSD_OWN) { + let data = &mut self.transmit_buffer[self.transmit_i]; + + if buf.len() > data.len() { + return Err(Error::new(EMSGSIZE)); + } + + let mut i = 0; + while i < buf.len() && i < data.len() { + data[i].write(buf[i]); + i += 1; + } + + self.regs.tsad[self.transmit_i].write(data.physical() as u32); + assert_eq!(i as u32, i as u32 & TSD_SIZE_MASK); + self.regs.tsd[self.transmit_i].write(i as u32 & TSD_SIZE_MASK); + + //TODO: wait for TSD_TOK or error + + self.transmit_i += 1; + + return Ok(i); + } + + std::hint::spin_loop(); + } + } +} + +impl Rtl8139 { + pub unsafe fn new(base: usize) -> Result { + let regs = Regs::from_base(base); + + let mut module = Rtl8139 { + regs, + //TODO: limit to 32-bit + receive_buffer: Dma::zeroed().map(|dma| dma.assume_init())?, + receive_i: 0, + //TODO: limit to 32-bit + transmit_buffer: (0..4) + .map(|_| Ok(Dma::zeroed()?.assume_init())) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()), + transmit_i: 0, + mac_address: [0; 6], + }; + + module.init()?; + + Ok(module) + } + + pub unsafe fn irq(&mut self) -> bool { + // Read and then clear the ISR + let isr = self.regs.isr.read(); + self.regs.isr.write(isr); + let imr = self.regs.imr.read(); + (isr & imr) != 0 + } + + fn rx(&self, offset: u16) -> u8 { + let index = (self.receive_i + offset as usize) % RX_BUFFER_SIZE; + self.receive_buffer[index].read() + } + + pub fn next_read(&self) -> usize { + if !self.regs.cr.readf(CR_BUFE) { + let rxsts = (self.rx(0) as u16) | (self.rx(1) as u16) << 8; + + let size_with_crc = (self.rx(2) as usize) | (self.rx(3) as usize) << 8; + + if (rxsts & RXSTS_ROK) == RXSTS_ROK { + size_with_crc.saturating_sub(4) + } else { + 0 + } + } else { + 0 + } + } + + pub unsafe fn init(&mut self) -> Result<()> { + let mac_low = self.regs.mac[0].read(); + let mac_high = self.regs.mac[1].read(); + let mac = [ + mac_low as u8, + (mac_low >> 8) as u8, + (mac_low >> 16) as u8, + (mac_low >> 24) as u8, + mac_high as u8, + (mac_high >> 8) as u8, + ]; + log::debug!( + "MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + self.mac_address = mac; + + // Reset - this will disable tx and rx, reinitialize FIFOs, and set the system buffer pointer to the initial value + { + log::debug!("Reset"); + let timeout = Timeout::from_secs(1); + self.regs.cr.writef(CR_RST, true); + while self.regs.cr.readf(CR_RST) { + timeout.run().map_err(|()| Error::new(EIO))?; + } + } + + // Set up rx buffer + log::debug!("Receive buffer"); + self.regs + .rbstart + .write(self.receive_buffer.physical() as u32); + + log::debug!("Interrupt mask"); + self.regs.imr.write(IMR_TOK | IMR_ROK); + + log::debug!("Receive configuration"); + self.regs + .rcr + .write(RCR_RBLEN_64K | RCR_AB | RCR_AM | RCR_APM | RCR_AAP); + + log::debug!("Enable RX and TX"); + self.regs.cr.writef(CR_RE | CR_TE, true); + + log::debug!("Complete!"); + Ok(()) + } +} diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs new file mode 100644 index 0000000000..2f3533978e --- /dev/null +++ b/drivers/net/rtl8139d/src/main.rs @@ -0,0 +1,117 @@ +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; + +use driver_network::NetworkScheme; +use event::{user_data, EventQueue}; +use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; +use pcid_interface::PciFunctionHandle; + +pub mod device; + +use std::ops::{Add, Div, Rem}; +pub fn div_round_up(a: T, b: T) -> T +where + T: Add + Div + Rem + PartialEq + From + Copy, +{ + if a % b != T::from(0u8) { + a / b + T::from(1u8) + } else { + a / b + } +} + +fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { + let config = pcid_handle.config(); + + // RTL8139 uses BAR2, RTL8169 uses BAR1, search in that order + for &barnum in &[2, 1] { + match config.func.bars[usize::from(barnum)] { + pcid_interface::PciBar::Memory32 { .. } | pcid_interface::PciBar::Memory64 { .. } => unsafe { + return pcid_handle.map_bar(barnum).ptr.as_ptr(); + }, + other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), + } + } + panic!("rtl8139d: failed to find BAR"); +} + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let mut pcid_handle = PciFunctionHandle::connect_default(); + + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_rtl8139"); + + common::setup_logging( + "net", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + log::info!(" + RTL8139 {}", pci_config.func.display()); + + let bar = map_bar(&mut pcid_handle); + + let mut irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8139d"); + + let mut scheme = NetworkScheme::new( + move || unsafe { + device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device") + }, + daemon, + format!("network.{name}"), + ); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = EventQueue::::new().expect("rtl8139d: Could not create event queue."); + event_queue + .subscribe( + irq_file.irq_handle().as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("rtl8139d: failed to enter null namespace"); + + scheme.tick().unwrap(); + + for event in event_queue.map(|e| e.expect("rtl8139d: failed to get next event")) { + match event.user_data { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.irq_handle().read(&mut irq).unwrap(); + //TODO: This may be causing spurious interrupts + if unsafe { scheme.adapter_mut().irq() } { + irq_file.irq_handle().write(&mut irq).unwrap(); + + scheme.tick().unwrap(); + } + } + Source::Scheme => { + scheme.tick().unwrap(); + } + } + } + unreachable!() +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("rtl8139d: failed to create daemon"); +} diff --git a/drivers/net/rtl8168d/Cargo.toml b/drivers/net/rtl8168d/Cargo.toml new file mode 100644 index 0000000000..a8aca0c7c2 --- /dev/null +++ b/drivers/net/rtl8168d/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rtl8168d" +version = "0.1.0" +edition = "2018" + +[dependencies] +bitflags = "2" +libredox = "0.1.3" +log = "0.4" +redox_event = "0.4.1" +redox_syscall = "0.5" +redox-daemon = "0.1" + +common = { path = "../../common" } +driver-network = { path = "../driver-network" } +pcid = { path = "../../pcid" } diff --git a/drivers/net/rtl8168d/config.toml b/drivers/net/rtl8168d/config.toml new file mode 100644 index 0000000000..ee98e345f3 --- /dev/null +++ b/drivers/net/rtl8168d/config.toml @@ -0,0 +1,5 @@ +[[drivers]] +name = "RTL8168 NIC" +class = 0x02 +ids = { 0x10ec = [0x8168, 0x8169] } +command = ["rtl8168d"] diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs new file mode 100644 index 0000000000..ce0675877a --- /dev/null +++ b/drivers/net/rtl8168d/src/device.rs @@ -0,0 +1,345 @@ +use std::convert::TryInto; +use std::mem; + +use common::dma::Dma; +use common::io::{Io, Mmio, ReadOnly}; +use common::timeout::Timeout; +use driver_network::NetworkAdapter; +use syscall::error::{Error, Result, EIO, EMSGSIZE}; + +#[repr(C, packed)] +struct Regs { + mac: [Mmio; 2], + _mar: [Mmio; 2], + _dtccr: [Mmio; 2], + _rsv0: [Mmio; 2], + tnpds: [Mmio; 2], + thpds: [Mmio; 2], + _rsv1: [Mmio; 7], + cmd: Mmio, + tppoll: Mmio, + _rsv2: [Mmio; 3], + imr: Mmio, + isr: Mmio, + tcr: Mmio, + rcr: Mmio, + _tctr: Mmio, + _rsv3: Mmio, + cmd_9346: Mmio, + _config: [Mmio; 6], + _rsv4: Mmio, + timer_int: Mmio, + _rsv5: Mmio, + _phys_ar: Mmio, + _rsv6: [Mmio; 2], + phys_sts: ReadOnly>, + _rsv7: [Mmio; 23], + _wakeup: [Mmio; 16], + _crc: [Mmio; 5], + _rsv8: [Mmio; 12], + rms: Mmio, + _rsv9: Mmio, + _c_plus_cr: Mmio, + _rsv10: Mmio, + rdsar: [Mmio; 2], + mtps: Mmio, + _rsv11: [Mmio; 19], +} + +const OWN: u32 = 1 << 31; +const EOR: u32 = 1 << 30; +const FS: u32 = 1 << 29; +const LS: u32 = 1 << 28; + +#[repr(C, packed)] +struct Rd { + ctrl: Mmio, + _vlan: Mmio, + buffer_low: Mmio, + buffer_high: Mmio, +} + +#[repr(C, packed)] +struct Td { + ctrl: Mmio, + _vlan: Mmio, + buffer_low: Mmio, + buffer_high: Mmio, +} + +pub struct Rtl8168 { + regs: &'static mut Regs, + receive_buffer: [Dma<[Mmio; 0x1FF8]>; 64], + receive_ring: Dma<[Rd; 64]>, + receive_i: usize, + transmit_buffer: [Dma<[Mmio; 7552]>; 16], + transmit_ring: Dma<[Td; 16]>, + transmit_i: usize, + transmit_buffer_h: [Dma<[Mmio; 7552]>; 1], + transmit_ring_h: Dma<[Td; 1]>, + mac_address: [u8; 6], +} + +impl NetworkAdapter for Rtl8168 { + fn mac_address(&mut self) -> [u8; 6] { + self.mac_address + } + + fn available_for_read(&mut self) -> usize { + self.next_read() + } + + fn read_packet(&mut self, buf: &mut [u8]) -> Result> { + if self.receive_i >= self.receive_ring.len() { + self.receive_i = 0; + } + + let rd = &mut self.receive_ring[self.receive_i]; + if !rd.ctrl.readf(OWN) { + let rd_len = rd.ctrl.read() & 0x3FFF; + + let data = &self.receive_buffer[self.receive_i]; + + let mut i = 0; + while i < buf.len() && i < rd_len as usize { + buf[i] = data[i].read(); + i += 1; + } + + let eor = rd.ctrl.read() & EOR; + rd.ctrl.write(OWN | eor | data.len() as u32); + + self.receive_i += 1; + + Ok(Some(i)) + } else { + Ok(None) + } + } + + fn write_packet(&mut self, buf: &[u8]) -> Result { + loop { + if self.transmit_i >= self.transmit_ring.len() { + self.transmit_i = 0; + } + + let td = &mut self.transmit_ring[self.transmit_i]; + if !td.ctrl.readf(OWN) { + let data = &mut self.transmit_buffer[self.transmit_i]; + + if buf.len() > data.len() { + return Err(Error::new(EMSGSIZE)); + } + + let mut i = 0; + while i < buf.len() && i < data.len() { + data[i].write(buf[i]); + i += 1; + } + + let eor = td.ctrl.read() & EOR; + td.ctrl.write(OWN | eor | FS | LS | i as u32); + + self.regs.tppoll.writef(1 << 6, true); //Notify of normal priority packet + + while self.regs.tppoll.readf(1 << 6) { + std::hint::spin_loop(); + } + + self.transmit_i += 1; + + return Ok(i); + } + + std::hint::spin_loop(); + } + } +} + +impl Rtl8168 { + pub unsafe fn new(base: usize) -> Result { + assert_eq!(mem::size_of::(), 256); + + let regs = &mut *(base as *mut Regs); + assert_eq!(®s.tnpds as *const _ as usize - base, 0x20); + assert_eq!(®s.cmd as *const _ as usize - base, 0x37); + assert_eq!(®s.tcr as *const _ as usize - base, 0x40); + assert_eq!(®s.rcr as *const _ as usize - base, 0x44); + assert_eq!(®s.cmd_9346 as *const _ as usize - base, 0x50); + assert_eq!(®s.phys_sts as *const _ as usize - base, 0x6C); + assert_eq!(®s.rms as *const _ as usize - base, 0xDA); + assert_eq!(®s.rdsar as *const _ as usize - base, 0xE4); + assert_eq!(®s.mtps as *const _ as usize - base, 0xEC); + + let mut module = Rtl8168 { + regs, + receive_buffer: (0..64) + .map(|_| Ok(Dma::zeroed()?.assume_init())) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()), + + receive_ring: Dma::zeroed()?.assume_init(), + receive_i: 0, + transmit_buffer: (0..16) + .map(|_| Ok(Dma::zeroed()?.assume_init())) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()), + transmit_ring: Dma::zeroed()?.assume_init(), + transmit_i: 0, + transmit_buffer_h: [Dma::zeroed()?.assume_init()], + transmit_ring_h: Dma::zeroed()?.assume_init(), + mac_address: [0; 6], + }; + + module.init(); + + Ok(module) + } + + pub unsafe fn irq(&mut self) -> bool { + // Read and then clear the ISR + let isr = self.regs.isr.read(); + self.regs.isr.write(isr); + let imr = self.regs.imr.read(); + (isr & imr) != 0 + } + + pub fn next_read(&self) -> usize { + let mut receive_i = self.receive_i; + if receive_i >= self.receive_ring.len() { + receive_i = 0; + } + + let rd = &self.receive_ring[receive_i]; + if !rd.ctrl.readf(OWN) { + (rd.ctrl.read() & 0x3FFF) as usize + } else { + 0 + } + } + + pub unsafe fn init(&mut self) -> Result<()> { + let mac_low = self.regs.mac[0].read(); + let mac_high = self.regs.mac[1].read(); + let mac = [ + mac_low as u8, + (mac_low >> 8) as u8, + (mac_low >> 16) as u8, + (mac_low >> 24) as u8, + mac_high as u8, + (mac_high >> 8) as u8, + ]; + log::debug!( + "MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + self.mac_address = mac; + + // Reset - this will disable tx and rx, reinitialize FIFOs, and set the system buffer pointer to the initial value + { + log::debug!("Reset"); + let timeout = Timeout::from_secs(1); + self.regs.cmd.writef(1 << 4, true); + while self.regs.cmd.readf(1 << 4) { + timeout.run().map_err(|()| Error::new(EIO))?; + } + } + + // Set up rx buffers + log::debug!("Receive buffers"); + for i in 0..self.receive_ring.len() { + let rd = &mut self.receive_ring[i]; + let data = &mut self.receive_buffer[i]; + rd.buffer_low.write(data.physical() as u32); + rd.buffer_high.write((data.physical() as u64 >> 32) as u32); + rd.ctrl.write(OWN | data.len() as u32); + } + if let Some(rd) = self.receive_ring.last_mut() { + rd.ctrl.writef(EOR, true); + } + + // Set up normal priority tx buffers + log::debug!("Transmit buffers (normal priority)"); + for i in 0..self.transmit_ring.len() { + self.transmit_ring[i] + .buffer_low + .write(self.transmit_buffer[i].physical() as u32); + self.transmit_ring[i] + .buffer_high + .write((self.transmit_buffer[i].physical() as u64 >> 32) as u32); + } + if let Some(td) = self.transmit_ring.last_mut() { + td.ctrl.writef(EOR, true); + } + + // Set up high priority tx buffers + log::debug!("Transmit buffers (high priority)"); + for i in 0..self.transmit_ring_h.len() { + self.transmit_ring_h[i] + .buffer_low + .write(self.transmit_buffer_h[i].physical() as u32); + self.transmit_ring_h[i] + .buffer_high + .write((self.transmit_buffer_h[i].physical() as u64 >> 32) as u32); + } + if let Some(td) = self.transmit_ring_h.last_mut() { + td.ctrl.writef(EOR, true); + } + + log::debug!("Set config"); + // Unlock config + self.regs.cmd_9346.write(1 << 7 | 1 << 6); + + // Enable rx (bit 3) and tx (bit 2) + self.regs.cmd.writef(1 << 3 | 1 << 2, true); + + // Max RX packet size + self.regs.rms.write(0x1FF8); + + // Max TX packet size + self.regs.mtps.write(0x3B); + + // Set tx low priority buffer address + self.regs.tnpds[0].write(self.transmit_ring.physical() as u32); + self.regs.tnpds[1].write(((self.transmit_ring.physical() as u64) >> 32) as u32); + + // Set tx high priority buffer address + self.regs.thpds[0].write(self.transmit_ring_h.physical() as u32); + self.regs.thpds[1].write(((self.transmit_ring_h.physical() as u64) >> 32) as u32); + + // Set rx buffer address + self.regs.rdsar[0].write(self.receive_ring.physical() as u32); + self.regs.rdsar[1].write(((self.receive_ring.physical() as u64) >> 32) as u32); + + // Disable timer interrupt + self.regs.timer_int.write(0); + + //Clear ISR + let isr = self.regs.isr.read(); + self.regs.isr.write(isr); + + // Interrupt on tx error (bit 3), tx ok (bit 2), rx error(bit 1), and rx ok (bit 0) + self.regs.imr.write( + 1 << 15 | 1 << 14 | 1 << 7 | 1 << 6 | 1 << 5 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 1 | 1, + ); + + // Set TX config + self.regs.tcr.write(0b11 << 24 | 0b111 << 8); + + // Set RX config - Accept broadcast (bit 3), multicast (bit 2), and unicast (bit 1) + self.regs.rcr.write(0xE70E); + + // Lock config + self.regs.cmd_9346.write(0); + + log::debug!("Complete!"); + Ok(()) + } +} diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs new file mode 100644 index 0000000000..f89c9d9825 --- /dev/null +++ b/drivers/net/rtl8168d/src/main.rs @@ -0,0 +1,117 @@ +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; + +use driver_network::NetworkScheme; +use event::{user_data, EventQueue}; +use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; +use pcid_interface::PciFunctionHandle; + +pub mod device; + +use std::ops::{Add, Div, Rem}; +pub fn div_round_up(a: T, b: T) -> T +where + T: Add + Div + Rem + PartialEq + From + Copy, +{ + if a % b != T::from(0u8) { + a / b + T::from(1u8) + } else { + a / b + } +} + +fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { + let config = pcid_handle.config(); + + // RTL8168 uses BAR2, RTL8169 uses BAR1, search in that order + for &barnum in &[2, 1] { + match config.func.bars[usize::from(barnum)] { + pcid_interface::PciBar::Memory32 { .. } | pcid_interface::PciBar::Memory64 { .. } => unsafe { + return pcid_handle.map_bar(barnum).ptr.as_ptr(); + }, + other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), + } + } + panic!("rtl8168d: failed to find BAR"); +} + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let mut pcid_handle = PciFunctionHandle::connect_default(); + + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_rtl8168"); + + common::setup_logging( + "net", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + log::info!("RTL8168 {}", pci_config.func.display()); + + let bar = map_bar(&mut pcid_handle); + + let mut irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "rtl8168d"); + + let mut scheme = NetworkScheme::new( + move || unsafe { + device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device") + }, + daemon, + format!("network.{name}"), + ); + + user_data! { + enum Source { + Irq, + Scheme, + } + } + + let event_queue = EventQueue::::new().expect("rtl8168d: Could not create event queue."); + event_queue + .subscribe( + irq_file.irq_handle().as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace"); + + scheme.tick().unwrap(); + + for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) { + match event.user_data { + Source::Irq => { + let mut irq = [0; 8]; + irq_file.irq_handle().read(&mut irq).unwrap(); + //TODO: This may be causing spurious interrupts + if unsafe { scheme.adapter_mut().irq() } { + irq_file.irq_handle().write(&mut irq).unwrap(); + + scheme.tick().unwrap(); + } + } + Source::Scheme => { + scheme.tick().unwrap(); + } + } + } + unreachable!() +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("rtl8168d: failed to create daemon"); +} diff --git a/drivers/net/virtio-netd/Cargo.toml b/drivers/net/virtio-netd/Cargo.toml new file mode 100644 index 0000000000..3becfd7897 --- /dev/null +++ b/drivers/net/virtio-netd/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "virtio-netd" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4" +static_assertions = "1.1.0" +futures = { version = "0.3.28", features = ["executor"] } + +virtio-core = { path = "../../virtio-core" } +pcid = { path = "../../pcid" } +common = { path = "../../common" } +driver-network = { path = "../driver-network" } + +redox-daemon = "0.1" +redox_syscall = "0.5" +libredox = "0.1.3" diff --git a/drivers/net/virtio-netd/config.toml b/drivers/net/virtio-netd/config.toml new file mode 100644 index 0000000000..ebedb9e40c --- /dev/null +++ b/drivers/net/virtio-netd/config.toml @@ -0,0 +1,6 @@ +[[drivers]] +name = "virtio-net" +class = 0x02 +vendor = 0x1AF4 +device = 0x1000 +command = ["virtio-netd"] diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs new file mode 100644 index 0000000000..3ee723a5de --- /dev/null +++ b/drivers/net/virtio-netd/src/main.rs @@ -0,0 +1,136 @@ +mod scheme; + +use std::fs::File; +use std::io::{Read, Write}; +use std::mem; + +use driver_network::NetworkScheme; +use pcid_interface::PciFunctionHandle; + +use scheme::VirtioNet; + +pub const VIRTIO_NET_F_MAC: u32 = 5; + +#[derive(Debug)] +#[repr(C)] +pub struct VirtHeader { + pub flags: u8, + pub gso_type: u8, + pub hdr_len: u16, + pub gso_size: u16, + pub csum_start: u16, + pub csum_offset: u16, + pub num_buffers: u16, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 12); + +const MAX_BUFFER_LEN: usize = 65535; + +fn deamon(daemon: redox_daemon::Daemon) -> Result<(), Box> { + let mut pcid_handle = PciFunctionHandle::connect_default(); + + // Double check that we have the right device. + // + // 0x1000 - virtio-net + let pci_config = pcid_handle.config(); + + assert_eq!(pci_config.func.full_device_id.device_id, 0x1000); + log::info!("virtio-net: initiating startup sequence :^)"); + + let device = virtio_core::probe_device(&mut pcid_handle)?; + let device_space = device.device_space; + + // Negotiate device features: + let mac_address = if device.transport.check_device_feature(VIRTIO_NET_F_MAC) { + let mac = unsafe { + [ + core::ptr::read_volatile(device_space.add(0)), + core::ptr::read_volatile(device_space.add(1)), + core::ptr::read_volatile(device_space.add(2)), + core::ptr::read_volatile(device_space.add(3)), + core::ptr::read_volatile(device_space.add(4)), + core::ptr::read_volatile(device_space.add(5)), + ] + }; + + log::info!( + "virtio-net: device MAC is {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5] + ); + + device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); + mac + } else { + unimplemented!() + }; + + device.transport.finalize_features(); + + // Allocate the recieve and transmit queues: + // + // > Empty buffers are placed in one virtqueue for receiving + // > packets, and outgoing packets are enqueued into another + // > for transmission in that order. + // + // TODO(andypython): Should we use the same IRQ vector for both? + let rx_queue = device + .transport + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + let tx_queue = device + .transport + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + device.transport.run_device(); + + let mut name = pci_config.func.name(); + name.push_str("_virtio_net"); + + let device = VirtioNet::new(mac_address, rx_queue, tx_queue); + let mut scheme = NetworkScheme::new( + move || { + //TODO: do device init in this function to prevent hangs + device + }, + daemon, + format!("network.{name}"), + ); + + let mut event_queue = File::open("/scheme/event")?; + event_queue.write(&syscall::Event { + id: scheme.event_handle().raw(), + flags: syscall::EVENT_READ, + data: 0, + })?; + + libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace"); + + scheme.tick()?; + + loop { + event_queue.read(&mut [0; mem::size_of::()])?; // Wait for event + scheme.tick()?; + } +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + deamon(redox_daemon).unwrap(); + unreachable!(); +} + +pub fn main() { + common::setup_logging( + "net", + "pci", + "virtio-netd", + common::output_level(), + common::file_level(), + ); + redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); +} diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs new file mode 100644 index 0000000000..59b3b93e20 --- /dev/null +++ b/drivers/net/virtio-netd/src/scheme.rs @@ -0,0 +1,118 @@ +use std::sync::Arc; + +use driver_network::NetworkAdapter; + +use common::dma::Dma; + +use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; +use virtio_core::transport::Queue; + +use crate::{VirtHeader, MAX_BUFFER_LEN}; + +pub struct VirtioNet<'a> { + mac_address: [u8; 6], + + /// Reciever Queue. + rx: Arc>, + rx_buffers: Vec>, + + /// Transmiter Queue. + tx: Arc>, + + recv_head: u16, +} + +impl<'a> VirtioNet<'a> { + pub fn new(mac_address: [u8; 6], rx: Arc>, tx: Arc>) -> Self { + // Populate all of the `rx_queue` with buffers to maximize performence. + let mut rx_buffers = vec![]; + for i in 0..(rx.descriptor_len() as usize) { + rx_buffers.push(unsafe { + Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) + .unwrap() + .assume_init() + }); + + let chain = ChainBuilder::new() + .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + let _ = rx.send(chain); + } + + Self { + mac_address, + + rx, + rx_buffers, + tx, + + recv_head: 0, + } + } + + /// Returns the number of bytes read. Returns `0` if the operation would block. + fn try_recv(&mut self, target: &mut [u8]) -> usize { + let header_size = core::mem::size_of::(); + + if self.recv_head == self.rx.used.head_index() { + // The read would block. + return 0; + } + + let idx = self.rx.used.head_index() as usize; + let element = self.rx.used.get_element_at(idx - 1); + + let descriptor_idx = element.table_index.get(); + let payload_size = element.written.get() as usize - header_size; + + // XXX: The header and packet are added as one output descriptor to the transmit queue, + // and the device is notified of the new entry (see 5.1.5 Device Initialization). + let buffer = &self.rx_buffers[descriptor_idx as usize]; + // TODO: Check the header. + let _header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) }; + let packet = &buffer[header_size..(header_size + payload_size)]; + + // Copy the packet into the buffer. + target[..payload_size].copy_from_slice(&packet); + + self.recv_head = self.rx.used.head_index(); + payload_size + } +} + +impl<'a> NetworkAdapter for VirtioNet<'a> { + fn mac_address(&mut self) -> [u8; 6] { + self.mac_address + } + + fn available_for_read(&mut self) -> usize { + (self.rx.used.head_index() - self.recv_head).into() + } + + fn read_packet(&mut self, buf: &mut [u8]) -> syscall::Result> { + let bytes = self.try_recv(buf); + + if bytes != 0 { + // We read some bytes. + Ok(Some(bytes)) + } else { + Ok(None) + } + } + + fn write_packet(&mut self, buffer: &[u8]) -> syscall::Result { + let header = unsafe { Dma::::zeroed()?.assume_init() }; + + let mut payload = unsafe { Dma::<[u8]>::zeroed_slice(buffer.len())?.assume_init() }; + payload.copy_from_slice(buffer); + + let chain = ChainBuilder::new() + .chain(Buffer::new(&header)) + .chain(Buffer::new_unsized(&payload)) + .build(); + + futures::executor::block_on(self.tx.send(chain)); + Ok(buffer.len()) + } +} diff --git a/drivers/pcid-spawner/Cargo.toml b/drivers/pcid-spawner/Cargo.toml new file mode 100644 index 0000000000..77dd21063f --- /dev/null +++ b/drivers/pcid-spawner/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "pcid-spawner" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2021" +license = "MIT" + +[dependencies] +anyhow = "1" +log = "0.4" +pico-args = "0.5" +redox_syscall = "0.5.9" +serde = { version = "1", features = ["derive"] } +toml = "0.5" + +common = { path = "../common" } +pcid = { path = "../pcid" } diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs new file mode 100644 index 0000000000..97cc3cece0 --- /dev/null +++ b/drivers/pcid-spawner/src/main.rs @@ -0,0 +1,102 @@ +use std::fs; +use std::process::Command; + +use anyhow::{anyhow, Context, Result}; + +use pcid_interface::config::Config; +use pcid_interface::PciFunctionHandle; + +fn main() -> Result<()> { + let mut args = pico_args::Arguments::from_env(); + let config_path = args + .free_from_str::() + .expect("failed to parse --config argument"); + + common::setup_logging( + "bus", + "pci", + "pci-spawner.log", + common::output_level(), + common::file_level(), + ); + + let config_data = if fs::metadata(&config_path)?.is_file() { + fs::read_to_string(&config_path)? + } else { + let mut config_data = String::new(); + for path in fs::read_dir(&config_path)? { + if let Ok(tmp) = fs::read_to_string(path.unwrap().path()) { + config_data.push_str(&tmp); + } + } + config_data + }; + let config: Config = toml::from_str(&config_data)?; + + for entry in fs::read_dir("/scheme/pci")? { + let entry = entry.context("failed to get entry")?; + let device_path = entry.path(); + log::trace!("ENTRY: {}", device_path.to_string_lossy()); + + let mut handle = match PciFunctionHandle::connect_by_path(&device_path) { + Ok(handle) => handle, + Err(err) => { + // Either the device is gone or it is already in-use by a driver. + log::debug!( + "pcid-spawner: {} already in use: {err}", + device_path.display(), + ); + continue; + } + }; + + let full_device_id = handle.config().func.full_device_id; + + log::debug!( + "pcid-spawner enumerated: PCI {} {}", + handle.config().func.addr, + full_device_id.display() + ); + + let Some(driver) = config + .drivers + .iter() + .find(|driver| driver.match_function(&full_device_id)) + else { + log::debug!("no driver for {}, continuing", handle.config().func.addr); + continue; + }; + + let mut args = driver.command.iter(); + + let program = args + .next() + .ok_or_else(|| anyhow!("driver configuration entry did not have any command!"))?; + let program = if program.starts_with('/') { + program.to_owned() + } else { + "/usr/lib/drivers/".to_owned() + program + }; + + let mut command = Command::new(program); + command.args(args); + + log::info!("pcid-spawner: spawn {:?}", command); + + handle.enable_device(); + + let channel_fd = handle.into_inner_fd(); + command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); + + match command.status() { + Ok(status) if !status.success() => { + log::error!("pcid-spawner: driver {command:?} failed with {status}"); + } + Ok(_) => {} + Err(err) => log::error!("pcid-spawner: failed to execute {command:?}: {err}"), + } + syscall::close(channel_fd as usize).unwrap(); + } + + Ok(()) +} diff --git a/drivers/pcid/.gitignore b/drivers/pcid/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/pcid/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/pcid/Cargo.toml b/drivers/pcid/Cargo.toml new file mode 100644 index 0000000000..45261f838b --- /dev/null +++ b/drivers/pcid/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pcid" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "pcid" +path = "src/main.rs" + +[lib] +name = "pcid_interface" +path = "src/lib.rs" + +[dependencies] +bincode = "1.2" +fdt = "0.1.5" +libc = "0.2" +log = "0.4" +pci_types = "0.10" +pico-args = { version = "0.5", features = ["combined-flags"] } +plain = "0.2" +redox-daemon = "0.1" +redox-scheme = "0.6.2" +redox_syscall = "0.5.9" +serde = { version = "1", features = ["derive"] } + +common = { path = "../common" } +libredox = "0.1.3" diff --git a/drivers/pcid/src/cfg_access/fallback.rs b/drivers/pcid/src/cfg_access/fallback.rs new file mode 100644 index 0000000000..671d17f735 --- /dev/null +++ b/drivers/pcid/src/cfg_access/fallback.rs @@ -0,0 +1,96 @@ +use std::cell::Cell; +use std::convert::TryFrom; +use std::sync::Mutex; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use common::io::{Io as _, Pio}; + +use log::info; +use pci_types::{ConfigRegionAccess, PciAddress}; + +pub(crate) struct Pci { + lock: Mutex<()>, +} + +impl Pci { + pub(crate) fn new() -> Self { + Self { + lock: Mutex::new(()), + } + } + + fn set_iopl() { + // The IO privilege level is per-thread, so we need to do the initialization on every thread. + thread_local! { + static IOPL_ONCE: Cell = Cell::new(false); + } + + IOPL_ONCE.with(|iopl_once| { + if !iopl_once.replace(true) { + // make sure that pcid is not granted io port permission unless pcie memory-mapped + // configuration space is not available. + info!( + "PCI: couldn't find or access PCIe extended configuration, \ + and thus falling back to PCI 3.0 io ports" + ); + common::acquire_port_io_rights().expect("pcid: failed to get IO port rights"); + } + }); + } + + fn address(address: PciAddress, offset: u8) -> u32 { + assert_eq!( + address.segment(), + 0, + "usage of multiple segments requires PCIe extended configuration" + ); + + assert_eq!(offset & 0xFC, offset, "pci offset is not aligned"); + + 0x80000000 + | (u32::from(address.bus()) << 16) + | (u32::from(address.device()) << 11) + | (u32::from(address.function()) << 8) + | u32::from(offset) + } +} +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl ConfigRegionAccess for Pci { + unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 { + let _guard = self.lock.lock().unwrap(); + + Self::set_iopl(); + + let offset = + u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); + let address = Self::address(address, offset); + + Pio::::new(0xCF8).write(address); + Pio::::new(0xCFC).read() + } + + unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) { + let _guard = self.lock.lock().unwrap(); + + Self::set_iopl(); + + let offset = + u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); + let address = Self::address(address, offset); + + Pio::::new(0xCF8).write(address); + Pio::::new(0xCFC).write(value); + } +} +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +impl ConfigRegionAccess for Pci { + unsafe fn read(&self, addr: PciAddress, offset: u16) -> u32 { + let _guard = self.lock.lock().unwrap(); + todo!("Pci::CfgAccess::read on this architecture") + } + + unsafe fn write(&self, addr: PciAddress, offset: u16, value: u32) { + let _guard = self.lock.lock().unwrap(); + todo!("Pci::CfgAccess::write on this architecture") + } +} diff --git a/drivers/pcid/src/cfg_access/mod.rs b/drivers/pcid/src/cfg_access/mod.rs new file mode 100644 index 0000000000..c25524485a --- /dev/null +++ b/drivers/pcid/src/cfg_access/mod.rs @@ -0,0 +1,372 @@ +use std::sync::Mutex; +use std::{fs, io, mem}; + +use common::{MemoryType, PhysBorrowed, Prot}; +use fdt::Fdt; +use pci_types::{ConfigRegionAccess, PciAddress}; + +use fallback::Pci; + +mod fallback; + +pub struct InterruptMap { + pub addr: [u32; 3], + pub interrupt: u32, + pub parent_phandle: u32, + pub parent_interrupt: [u32; 3], + pub parent_interrupt_cells: usize, +} + +// https://elinux.org/Device_Tree_Usage has a lot of useful information +fn locate_ecam_dtb( + f: impl FnOnce(PcieAllocs<'_>, Vec, [u32; 4]) -> io::Result, +) -> io::Result { + let dtb = fs::read("/scheme/kernel.dtb")?; + let dt = Fdt::new(&dtb).map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("invalid device tree: {err:?}"), + ) + })?; + + let node = dt + .find_compatible(&["pci-host-ecam-generic"]) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::NotFound, + "couldn't find pci-host-ecam-generic node in device tree", + ) + })?; + + let address = node.reg().unwrap().next().unwrap().starting_address as u64; + + let bus_range = node.property("bus-range").unwrap(); + assert_eq!(bus_range.value.len(), 8); + let start_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[0..4]).unwrap()); + let end_bus = u32::from_be_bytes(<[u8; 4]>::try_from(&bus_range.value[4..8]).unwrap()); + + // address-cells == 3, size-cells == 2, interrupt-cells == 1 + let mut interrupt_map_data = node + .property("interrupt-map") + .unwrap() + .value + .chunks_exact(4) + .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap())); + let mut interrupt_map = Vec::::new(); + while let Ok([addr1, addr2, addr3, int1, phandle]) = interrupt_map_data.next_chunk::<5>() { + let parent = dt.find_phandle(phandle).unwrap(); + let parent_address_cells = u32::from_be_bytes( + parent.property("#address-cells").unwrap().value[..4] + .try_into() + .unwrap(), + ); + match parent_address_cells { + 0 => {} + 1 => { + assert_eq!(interrupt_map_data.next().unwrap(), 0); + } + 2 => { + assert_eq!(interrupt_map_data.next_chunk::<2>().unwrap(), [0, 0]); + } + 3 => { + assert_eq!(interrupt_map_data.next_chunk::<3>().unwrap(), [0, 0, 0]); + } + _ => break, + }; + let parent_interrupt_cells = parent.interrupt_cells().unwrap(); + let parent_interrupt = match parent_interrupt_cells { + 1 if let Some(a) = interrupt_map_data.next() => [a, 0, 0], + 2 if let Ok([a, b]) = interrupt_map_data.next_chunk::<2>() => [a, b, 0], + 3 if let Ok([a, b, c]) = interrupt_map_data.next_chunk::<3>() => [a, b, c], + _ => break, + }; + interrupt_map.push(InterruptMap { + addr: [addr1, addr2, addr3], + interrupt: int1, + parent_phandle: phandle, + parent_interrupt, + parent_interrupt_cells, + }); + } + + let interrupt_map_mask = if let Some(interrupt_mask_node) = node.property("interrupt-map-mask") + { + let mut cells = interrupt_mask_node + .value + .chunks_exact(4) + .map(|x| u32::from_be_bytes(<[u8; 4]>::try_from(x).unwrap())); + cells.next_chunk::<4>().unwrap().to_owned() + } else { + [u32::MAX, u32::MAX, u32::MAX, u32::MAX] + }; + + f( + PcieAllocs(&[PcieAlloc { + base_addr: address, + seg_group_num: 0, + start_bus: start_bus.try_into().unwrap(), + end_bus: end_bus.try_into().unwrap(), + _rsvd: [0; 4], + }]), + interrupt_map, + interrupt_map_mask, + ) +} + +pub const MCFG_NAME: [u8; 4] = *b"MCFG"; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct Mcfg { + // base sdt fields + name: [u8; 4], + length: u32, + revision: u8, + checksum: u8, + oem_id: [u8; 6], + oem_table_id: [u8; 8], + oem_revision: u32, + creator_id: [u8; 4], + creator_revision: u32, + _rsvd: [u8; 8], +} +unsafe impl plain::Plain for Mcfg {} + +/// The "Memory Mapped Enhanced Configuration Space Base Address Allocation Structure" (yes, it's +/// called that). +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct PcieAlloc { + pub base_addr: u64, + pub seg_group_num: u16, + pub start_bus: u8, + pub end_bus: u8, + _rsvd: [u8; 4], +} +unsafe impl plain::Plain for PcieAlloc {} + +#[derive(Debug)] +struct PcieAllocs<'a>(&'a [PcieAlloc]); + +impl Mcfg { + fn with( + f: impl FnOnce(PcieAllocs<'_>, Vec, [u32; 4]) -> io::Result, + ) -> io::Result { + let table_dir = fs::read_dir("/scheme/acpi/tables")?; + + // TODO: validate/print MCFG? + + for table_direntry in table_dir { + let table_path = table_direntry?.path(); + + // Every directory entry has to have a filename unless + // the filesystem (or in this case acpid) misbehaves. + // If it misbehaves we have worse problems than pcid + // crashing. `as_encoded_bytes()` returns some superset + // of ASCII, so directly comparing it with an ASCII name + // is fine. + let table_filename = table_path.file_name().unwrap().as_encoded_bytes(); + if table_filename.get(0..4) == Some(&MCFG_NAME) { + let bytes = fs::read(table_path)?.into_boxed_slice(); + match Mcfg::parse(&*bytes) { + Some((_mcfg, allocs)) => { + log::debug!("MCFG ALLOCS {:?}", allocs.0); + return f(allocs, Vec::new(), [u32::MAX, u32::MAX, u32::MAX, u32::MAX]); + } + None => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "couldn't find mcfg table", + )); + } + } + } + } + + Err(io::Error::new( + io::ErrorKind::NotFound, + "couldn't find mcfg table", + )) + } + + fn parse<'a>(bytes: &'a [u8]) -> Option<(&'a Mcfg, PcieAllocs<'a>)> { + if bytes.len() < mem::size_of::() { + return None; + } + let (header_bytes, allocs_bytes) = bytes.split_at(mem::size_of::()); + + let mcfg = + plain::from_bytes::(header_bytes).expect("packed -> align 1, checked size"); + if mcfg.length as usize != bytes.len() { + log::warn!("MCFG {mcfg:?} length mismatch, expected {}", bytes.len()); + return None; + } + // TODO: Allow invalid bytes not divisible by PcieAlloc? + + let allocs_len = + allocs_bytes.len() / mem::size_of::() * mem::size_of::(); + + let allocs = plain::slice_from_bytes::(&allocs_bytes[..allocs_len]) + .expect("packed -> align 1, checked size"); + Some((mcfg, PcieAllocs(allocs))) + } +} + +pub struct Pcie { + lock: Mutex<()>, + allocs: Vec, + pub interrupt_map: Vec, + pub interrupt_map_mask: [u32; 4], + fallback: Pci, +} +struct Alloc { + seg: u16, + start_bus: u8, + end_bus: u8, + mem: PhysBorrowed, +} + +unsafe impl Send for Pcie {} +unsafe impl Sync for Pcie {} + +const BYTES_PER_BUS: usize = 1 << 20; + +impl Pcie { + pub fn new() -> Self { + match Mcfg::with(Self::from_allocs) { + Ok(pcie) => pcie, + Err(acpi_error) => match locate_ecam_dtb(Self::from_allocs) { + Ok(pcie) => pcie, + Err(fdt_error) => { + log::warn!( + "Couldn't retrieve PCIe info, perhaps the kernel is not compiled with \ + acpi or device tree support? Using the PCI 3.0 configuration space \ + instead. ACPI error: {:?} FDT error: {:?}", + acpi_error, + fdt_error + ); + Self { + lock: Mutex::new(()), + allocs: Vec::new(), + fallback: Pci::new(), + interrupt_map: Vec::new(), + interrupt_map_mask: [u32::MAX, u32::MAX, u32::MAX, u32::MAX], + } + } + }, + } + } + + fn from_allocs( + allocs: PcieAllocs<'_>, + interrupt_map: Vec, + interrupt_map_mask: [u32; 4], + ) -> Result { + let mut allocs = allocs + .0 + .iter() + .filter_map(|desc| { + Some(Alloc { + seg: desc.seg_group_num, + start_bus: desc.start_bus, + end_bus: desc.end_bus, + mem: PhysBorrowed::map( + desc.base_addr.try_into().ok()?, + BYTES_PER_BUS + * (usize::from(desc.end_bus) - usize::from(desc.start_bus) + 1), + Prot::RW, + MemoryType::Uncacheable, + ) + .inspect_err(|err| { + log::error!( + "failed to map seg {} bus {}..={}: {}", + { desc.seg_group_num }, + { desc.start_bus }, + { desc.end_bus }, + err + ) + }) + .ok()?, + }) + }) + .collect::>(); + + allocs.sort_by_key(|alloc| (alloc.seg, alloc.start_bus)); + + Ok(Self { + lock: Mutex::new(()), + allocs, + interrupt_map, + interrupt_map_mask, + fallback: Pci::new(), + }) + } + + fn bus_addr(&self, seg: u16, bus: u8) -> Option<*mut u32> { + let alloc = match self + .allocs + .binary_search_by_key(&(seg, bus), |alloc| (alloc.seg, alloc.start_bus)) + { + Ok(present_idx) => &self.allocs[present_idx], + Err(0) => return None, + Err(above_idx) => { + let below_alloc = &self.allocs[above_idx - 1]; + if bus > below_alloc.end_bus { + return None; + } + below_alloc + } + }; + let bus_off = bus - alloc.start_bus; + Some(unsafe { + alloc + .mem + .as_ptr() + .cast::() + .add(usize::from(bus_off) * BYTES_PER_BUS) + .cast::() + }) + } + + fn bus_addr_offset_in_dwords(address: PciAddress, offset: u16) -> usize { + assert_eq!(offset & 0xFFFC, offset, "pcie offset not dword-aligned"); + assert_eq!(offset & 0x0FFF, offset, "pcie offset larger than 4095"); + + (((address.device() as usize) << 15) + | ((address.function() as usize) << 12) + | (offset as usize)) + >> 2 + } + // TODO: A safer interface, using e.g. a VolatileCell or Volatile<'a>. The PhysBorrowed wrapper + // can possibly deref to or provide a Volatile. + fn mmio_addr(&self, address: PciAddress, offset: u16) -> Option<*mut u32> { + assert_eq!( + address.segment(), + 0, + "multiple segments not yet implemented" + ); + + let bus_addr = self.bus_addr(address.segment(), address.bus())?; + Some(unsafe { bus_addr.add(Self::bus_addr_offset_in_dwords(address, offset)) }) + } +} + +impl ConfigRegionAccess for Pcie { + unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 { + let _guard = self.lock.lock().unwrap(); + + match self.mmio_addr(address, offset) { + Some(addr) => addr.read_volatile(), + None => self.fallback.read(address, offset), + } + } + + unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) { + let _guard = self.lock.lock().unwrap(); + + match self.mmio_addr(address, offset) { + Some(addr) => addr.write_volatile(value), + None => self.fallback.write(address, offset, value), + } + } +} diff --git a/drivers/pcid/src/driver_handler.rs b/drivers/pcid/src/driver_handler.rs new file mode 100644 index 0000000000..f70a7f6da3 --- /dev/null +++ b/drivers/pcid/src/driver_handler.rs @@ -0,0 +1,284 @@ +use pci_types::capability::{MultipleMessageSupport, PciCapability}; +use pci_types::{ConfigRegionAccess, EndpointHeader}; +use pcid_interface::PciFunction; + +use crate::cfg_access::Pcie; + +pub struct DriverHandler<'a> { + func: PciFunction, + endpoint_header: &'a mut EndpointHeader, + capabilities: &'a mut [PciCapability], + + pcie: &'a Pcie, +} + +impl<'a> DriverHandler<'a> { + pub fn new( + func: PciFunction, + endpoint_header: &'a mut EndpointHeader, + capabilities: &'a mut [PciCapability], + pcie: &'a Pcie, + ) -> Self { + DriverHandler { + func, + endpoint_header, + capabilities, + pcie, + } + } + + pub fn respond( + &mut self, + request: pcid_interface::PcidClientRequest, + ) -> pcid_interface::PcidClientResponse { + use pcid_interface::*; + + #[forbid(non_exhaustive_omitted_patterns)] + match request { + PcidClientRequest::EnableDevice => { + self.func.legacy_interrupt_line = crate::enable_function( + &self.pcie, + &mut self.endpoint_header, + &mut self.capabilities, + ); + + PcidClientResponse::EnabledDevice + } + PcidClientRequest::RequestVendorCapabilities => PcidClientResponse::VendorCapabilities( + self.capabilities + .iter() + .filter_map(|capability| match capability { + PciCapability::Vendor(addr) => unsafe { + Some(VendorSpecificCapability::parse(*addr, self.pcie)) + }, + _ => None, + }) + .collect::>(), + ), + PcidClientRequest::RequestConfig => { + PcidClientResponse::Config(SubdriverArguments { func: self.func }) + } + PcidClientRequest::RequestFeatures => PcidClientResponse::AllFeatures( + self.capabilities + .iter() + .filter_map(|capability| match capability { + PciCapability::Msi(_) => Some(PciFeature::Msi), + PciCapability::MsiX(_) => Some(PciFeature::MsiX), + _ => None, + }) + .collect(), + ), + PcidClientRequest::EnableFeature(feature) => { + match feature { + PciFeature::Msi => { + if let Some(msix_capability) = + self.capabilities + .iter_mut() + .find_map(|capability| match capability { + PciCapability::MsiX(cap) => Some(cap), + _ => None, + }) + { + // If MSI-X is supported disable it before enabling MSI as they can't be + // active at the same time. + msix_capability.set_enabled(false, self.pcie); + } + + let capability = match self.capabilities.iter_mut().find_map(|capability| { + match capability { + PciCapability::Msi(cap) => Some(cap), + _ => None, + } + }) { + Some(capability) => capability, + None => { + return PcidClientResponse::Error( + PcidServerResponseError::NonexistentFeature(feature), + ) + } + }; + capability.set_enabled(true, self.pcie); + PcidClientResponse::FeatureEnabled(feature) + } + PciFeature::MsiX => { + if let Some(msi_capability) = + self.capabilities + .iter_mut() + .find_map(|capability| match capability { + PciCapability::Msi(cap) => Some(cap), + _ => None, + }) + { + // If MSI is supported disable it before enabling MSI-X as they can't be + // active at the same time. + msi_capability.set_enabled(false, self.pcie); + } + + let capability = match self.capabilities.iter_mut().find_map(|capability| { + match capability { + PciCapability::MsiX(cap) => Some(cap), + _ => None, + } + }) { + Some(capability) => capability, + None => { + return PcidClientResponse::Error( + PcidServerResponseError::NonexistentFeature(feature), + ) + } + }; + capability.set_enabled(true, self.pcie); + PcidClientResponse::FeatureEnabled(feature) + } + } + } + PcidClientRequest::FeatureInfo(feature) => PcidClientResponse::FeatureInfo( + feature, + match feature { + PciFeature::Msi => { + if let Some(info) = + self.capabilities + .iter() + .find_map(|capability| match capability { + PciCapability::Msi(cap) => Some(cap), + _ => None, + }) + { + PciFeatureInfo::Msi(msi::MsiInfo { + log2_multiple_message_capable: info.multiple_message_capable() + as u8, + is_64bit: info.is_64bit(), + has_per_vector_masking: info.has_per_vector_masking(), + }) + } else { + return PcidClientResponse::Error( + PcidServerResponseError::NonexistentFeature(feature), + ); + } + } + PciFeature::MsiX => { + if let Some(info) = + self.capabilities + .iter() + .find_map(|capability| match capability { + PciCapability::MsiX(cap) => Some(cap), + _ => None, + }) + { + PciFeatureInfo::MsiX(msi::MsixInfo { + table_bar: info.table_bar(), + table_offset: info.table_offset(), + table_size: info.table_size(), + pba_bar: info.pba_bar(), + pba_offset: info.pba_offset(), + }) + } else { + return PcidClientResponse::Error( + PcidServerResponseError::NonexistentFeature(feature), + ); + } + } + }, + ), + PcidClientRequest::SetFeatureInfo(info_to_set) => match info_to_set { + SetFeatureInfo::Msi(info_to_set) => { + if let Some(info) = + self.capabilities + .iter_mut() + .find_map(|capability| match capability { + PciCapability::Msi(cap) => Some(cap), + _ => None, + }) + { + if let Some(mme) = info_to_set.multi_message_enable { + if (info.multiple_message_capable() as u8) < mme { + return PcidClientResponse::Error( + PcidServerResponseError::InvalidBitPattern, + ); + } + info.set_multiple_message_enable( + match mme { + 0 => MultipleMessageSupport::Int1, + 1 => MultipleMessageSupport::Int2, + 2 => MultipleMessageSupport::Int4, + 3 => MultipleMessageSupport::Int8, + 4 => MultipleMessageSupport::Int16, + 5 => MultipleMessageSupport::Int32, + _ => { + return PcidClientResponse::Error( + PcidServerResponseError::InvalidBitPattern, + ) + } + }, + self.pcie, + ); + } + if let Some(message_addr_and_data) = info_to_set.message_address_and_data { + let message_addr = message_addr_and_data.addr; + if message_addr & 0b11 != 0 { + return PcidClientResponse::Error( + PcidServerResponseError::InvalidBitPattern, + ); + } + if message_addr_and_data.data + & ((1 << info.multiple_message_enable(self.pcie) as u8) - 1) + != 0 + { + return PcidClientResponse::Error( + PcidServerResponseError::InvalidBitPattern, + ); + } + info.set_message_info( + message_addr, + message_addr_and_data + .data + .try_into() + .expect("pcid: MSI message data too big"), + self.pcie, + ); + } + if let Some(mask_bits) = info_to_set.mask_bits { + info.set_message_mask(mask_bits, self.pcie); + } + PcidClientResponse::SetFeatureInfo(PciFeature::Msi) + } else { + return PcidClientResponse::Error( + PcidServerResponseError::NonexistentFeature(PciFeature::Msi), + ); + } + } + SetFeatureInfo::MsiX { function_mask } => { + if let Some(info) = + self.capabilities + .iter_mut() + .find_map(|capability| match capability { + PciCapability::MsiX(cap) => Some(cap), + _ => None, + }) + { + if let Some(mask) = function_mask { + info.set_function_mask(mask, self.pcie); + } + PcidClientResponse::SetFeatureInfo(PciFeature::MsiX) + } else { + return PcidClientResponse::Error( + PcidServerResponseError::NonexistentFeature(PciFeature::MsiX), + ); + } + } + _ => unreachable!(), + }, + PcidClientRequest::ReadConfig(offset) => { + let value = unsafe { self.pcie.read(self.func.addr, offset) }; + return PcidClientResponse::ReadConfig(value); + } + PcidClientRequest::WriteConfig(offset, value) => { + unsafe { + self.pcie.write(self.func.addr, offset, value); + } + return PcidClientResponse::WriteConfig; + } + _ => unreachable!(), + } + } +} diff --git a/drivers/pcid/src/driver_interface/bar.rs b/drivers/pcid/src/driver_interface/bar.rs new file mode 100644 index 0000000000..b2c1d35bbd --- /dev/null +++ b/drivers/pcid/src/driver_interface/bar.rs @@ -0,0 +1,55 @@ +use std::convert::TryInto; + +use serde::{Deserialize, Serialize}; + +// This type is used instead of [pci_types::Bar] in the driver interface as the +// latter can't be serialized and is missing the convenience functions of [PciBar]. +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum PciBar { + None, + Memory32 { addr: u32, size: u32 }, + Memory64 { addr: u64, size: u64 }, + Port(u16), +} + +impl PciBar { + pub fn display(&self) -> String { + match self { + PciBar::None => format!(""), + PciBar::Memory32 { addr, .. } => format!("{addr:08X}"), + PciBar::Memory64 { addr, .. } => format!("{addr:016X}"), + PciBar::Port(port) => format!("P{port:04X}"), + } + } + + pub fn is_none(&self) -> bool { + match self { + &PciBar::None => true, + _ => false, + } + } + + pub fn expect_port(&self) -> u16 { + match *self { + PciBar::Port(port) => port, + PciBar::Memory32 { .. } | PciBar::Memory64 { .. } => { + panic!("expected port BAR, found memory BAR"); + } + PciBar::None => panic!("expected BAR to exist"), + } + } + + pub fn expect_mem(&self) -> (usize, usize) { + match *self { + PciBar::Memory32 { addr, size } => (addr as usize, size as usize), + PciBar::Memory64 { addr, size } => ( + addr.try_into() + .expect("conversion from 64bit BAR to usize failed"), + size.try_into() + .expect("conversion from 64bit BAR size to usize failed"), + ), + PciBar::Port(_) => panic!("expected memory BAR, found port BAR"), + PciBar::None => panic!("expected BAR to exist"), + } + } +} diff --git a/drivers/pcid/src/driver_interface/cap.rs b/drivers/pcid/src/driver_interface/cap.rs new file mode 100644 index 0000000000..19521608f8 --- /dev/null +++ b/drivers/pcid/src/driver_interface/cap.rs @@ -0,0 +1,38 @@ +use pci_types::capability::PciCapabilityAddress; +use pci_types::ConfigRegionAccess; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct VendorSpecificCapability { + pub data: Vec, +} + +impl VendorSpecificCapability { + pub unsafe fn parse(addr: PciCapabilityAddress, access: &dyn ConfigRegionAccess) -> Self { + let dword = access.read(addr.address, addr.offset); + let length = ((dword >> 16) & 0xFF) as u16; + // let next = (dword >> 8) & 0xFF; + // log::trace!( + // "Vendor specific offset: {:#02x} next: {next:#02x} cap len: {length:#02x}", + // addr.offset + // ); + let data = if length > 0 { + assert!( + length > 3 && length % 4 == 0, + "invalid range length: {}", + length + ); + let mut raw_data = { + (addr.offset..addr.offset + length) + .step_by(4) + .flat_map(|offset| access.read(addr.address, offset).to_le_bytes()) + .collect::>() + }; + raw_data.drain(3..).collect() + } else { + log::warn!("Vendor specific capability is invalid"); + Vec::new() + }; + VendorSpecificCapability { data } + } +} diff --git a/drivers/pcid/src/driver_interface/config.rs b/drivers/pcid/src/driver_interface/config.rs new file mode 100644 index 0000000000..e148b26ce6 --- /dev/null +++ b/drivers/pcid/src/driver_interface/config.rs @@ -0,0 +1,88 @@ +use std::collections::BTreeMap; +use std::ops::Range; + +use serde::Deserialize; + +use crate::driver_interface::FullDeviceId; + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct Config { + pub drivers: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct DriverConfig { + pub name: Option, + pub class: Option, + pub subclass: Option, + pub interface: Option, + pub ids: Option>>, + pub vendor: Option, + pub device: Option, + pub device_id_range: Option>, + pub command: Vec, +} + +impl DriverConfig { + pub fn match_function(&self, id: &FullDeviceId) -> bool { + if let Some(class) = self.class { + if class != id.class { + return false; + } + } + + if let Some(subclass) = self.subclass { + if subclass != id.subclass { + return false; + } + } + + if let Some(interface) = self.interface { + if interface != id.interface { + return false; + } + } + + if let Some(ref ids) = self.ids { + let mut device_found = false; + for (vendor, devices) in ids { + let vendor_without_prefix = vendor.trim_start_matches("0x"); + let vendor = i64::from_str_radix(vendor_without_prefix, 16).unwrap() as u16; + + if vendor != id.vendor_id { + continue; + } + + for device in devices { + if *device == id.device_id { + device_found = true; + break; + } + } + } + if !device_found { + return false; + } + } else { + if let Some(vendor) = self.vendor { + if vendor != id.vendor_id { + return false; + } + } + + if let Some(device) = self.device { + if device != id.device_id { + return false; + } + } + } + + if let Some(ref device_id_range) = self.device_id_range { + if id.device_id < device_id_range.start || device_id_range.end <= id.device_id { + return false; + } + } + + true + } +} diff --git a/drivers/pcid/src/driver_interface/id.rs b/drivers/pcid/src/driver_interface/id.rs new file mode 100644 index 0000000000..7b4ec84444 --- /dev/null +++ b/drivers/pcid/src/driver_interface/id.rs @@ -0,0 +1,48 @@ +use pci_types::device_type::DeviceType; +use serde::{Deserialize, Serialize}; + +/// All identifying information of a PCI function. +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub struct FullDeviceId { + pub vendor_id: u16, + pub device_id: u16, + pub class: u8, + pub subclass: u8, + pub interface: u8, + pub revision: u8, +} + +impl FullDeviceId { + pub fn display(&self) -> String { + let mut string = format!( + "{:>04X}:{:>04X} {:>02X}.{:>02X}.{:>02X}.{:>02X} {:?}", + self.vendor_id, + self.device_id, + self.class, + self.subclass, + self.interface, + self.revision, + self.class, + ); + let device_type = DeviceType::from((self.class, self.subclass)); + match device_type { + DeviceType::LegacyVgaCompatible => string.push_str(" VGA CTL"), + DeviceType::IdeController => string.push_str(" IDE"), + DeviceType::SataController => match self.interface { + 0 => string.push_str(" SATA VND"), + 1 => string.push_str(" SATA AHCI"), + _ => (), + }, + DeviceType::UsbController => match self.interface { + 0x00 => string.push_str(" UHCI"), + 0x10 => string.push_str(" OHCI"), + 0x20 => string.push_str(" EHCI"), + 0x30 => string.push_str(" XHCI"), + _ => (), + }, + DeviceType::NvmeController => string.push_str(" NVME"), + _ => (), + } + string + } +} diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs new file mode 100644 index 0000000000..28ca077a92 --- /dev/null +++ b/drivers/pcid/src/driver_interface/irq_helpers.rs @@ -0,0 +1,334 @@ +//! IRQ helpers. +//! +//! This module allows easy handling of the `/scheme/irq` scheme, and allocating interrupt vectors +//! for use by INTx#, MSI, or MSI-X. + +use std::convert::TryFrom; +use std::fs::{self, File}; +use std::io::{self, prelude::*}; +use std::num::NonZeroU8; + +use crate::driver_interface::msi::{MsiAddrAndData, MsixTableEntry}; + +/// Read the local APIC ID of the bootstrap processor. +pub fn read_bsp_apic_id() -> io::Result { + let mut buffer = [0u8; 8]; + + let mut file = File::open("/scheme/irq/bsp")?; + let bytes_read = file.read(&mut buffer)?; + + (if bytes_read == 8 { + usize::try_from(u64::from_le_bytes(buffer)) + } else if bytes_read == 4 { + usize::try_from(u32::from_le_bytes([ + buffer[0], buffer[1], buffer[2], buffer[3], + ])) + } else { + panic!( + "`/scheme/irq` scheme responded with {} bytes, expected {}", + bytes_read, + std::mem::size_of::() + ); + }) + .or(Err(io::Error::new( + io::ErrorKind::InvalidData, + "bad BSP int size", + ))) +} + +// TODO: Perhaps read the MADT instead? +/// Obtains an interator over all of the visible CPU ids, for use in IRQ allocation and MSI +/// capability structs or MSI-X tables. +pub fn cpu_ids() -> io::Result> + 'static> { + Ok( + fs::read_dir("/scheme/irq")?.filter_map(|entry| -> Option> { + match entry { + Ok(e) => { + let path = e.path(); + let file_name = path.file_name()?.to_str()?; + // the file name should be in the format `cpu-` + if !file_name.starts_with("cpu-") { + return None; + } + u8::from_str_radix(&file_name[4..], 16) + .map(usize::from) + .map(Ok) + .ok() + } + Err(e) => Some(Err(e)), + } + }), + ) +} + +/// Allocate multiple interrupt vectors, from the IDT of the specified processor, returning the +/// start vector and the IRQ handles. +/// +/// The alignment is a requirement for the allocation range. For example, with an alignment of 8, +/// only ranges that begin with a multiple of eight are accepted. The IRQ handles returned will +/// always correspond to the subsequent IRQ numbers beginning the first value in the return tuple. +/// +/// This function is not actually guaranteed to allocate all of the IRQs specified in `count`, +/// since another process might already have requested one vector in the range. The caller must +/// check that the returned vector have the same length as `count`. In the future this function may +/// perhaps lock the entire directory to prevent this from happening, or maybe find the smallest free +/// range with the minimum alignment, to allow other drivers to obtain their necessary IRQs. +/// +/// Note that this count/alignment restriction is only mandatory for MSI; MSI-X allows for +/// individually allocated vectors that might be spread out, even on multiple CPUs. Thus, multiple +/// invocations with alignment 1 and count 1 are totally acceptable, although allocating in bulk +/// minimizes the initialization overhead. +pub fn allocate_aligned_interrupt_vectors( + cpu_id: usize, + alignment: NonZeroU8, + count: u8, +) -> io::Result)>> { + let cpu_id = u8::try_from(cpu_id).expect("usize cpu ids not implemented yet"); + if count == 0 { + return Ok(None); + } + + let available_irqs = fs::read_dir(format!("/scheme/irq/cpu-{:02x}", cpu_id))?; + let mut available_irq_numbers = available_irqs.filter_map(|entry| -> Option> { + let entry = match entry { + Ok(e) => e, + Err(err) => return Some(Err(err)), + }; + + let path = entry.path(); + + let file_name = match path.file_name() { + Some(f) => f, + None => return None, + }; + + let path_str = match file_name.to_str() { + Some(s) => s, + None => return None, + }; + + match path_str.parse::() { + Ok(p) => Some(Ok(p)), + Err(_) => None, + } + }); + + // TODO: fcntl F_SETLK on `/scheme/irq/`? + + let mut handles = Vec::with_capacity(usize::from(count)); + + let mut index = 0; + let mut first = None; + + while let Some(number) = available_irq_numbers.next() { + let number = number?; + + // Skip until a suitable alignment is found. + if number % u8::from(alignment) != 0 { + continue; + } + let first = *first.get_or_insert(number); + let irq_number = first + index; + + // From the point where the range is aligned, we can start to advance until `count` IRQs + // have been allocated. + if index >= count { + break; + } + + // if found, reserve the irq + let irq_handle = + match File::create(format!("/scheme/irq/cpu-{:02x}/{}", cpu_id, irq_number)) { + Ok(handle) => handle, + + // return early if the entire range couldn't be allocated + Err(err) if err.kind() == io::ErrorKind::NotFound => break, + + Err(err) => return Err(err), + }; + handles.push(irq_handle); + index += 1; + } + if handles.is_empty() { + return Ok(None); + } + let first = match first { + Some(f) => f, + None => return Ok(None), + }; + + Ok(Some((first + 32, handles))) +} + +/// Allocate at most `count` interrupt vectors, which can start at any offset. Unless MSI is used +/// and an entire aligned range of vectors is needed, this function should be used. +pub fn allocate_interrupt_vectors(cpu_id: usize, count: u8) -> io::Result)>> { + allocate_aligned_interrupt_vectors(cpu_id, NonZeroU8::new(1).unwrap(), count) +} + +/// Allocate a single interrupt vector, returning both the vector number (starting from 32 up to +/// 254), and its IRQ handle which is then reserved. Returns Ok(None) if allocation fails due to +/// no available IRQs. +pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result> { + let (base, mut files) = match allocate_interrupt_vectors(cpu_id, 1) { + Ok(Some((base, files))) => (base, files), + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + assert_eq!(files.len(), 1); + Ok(Some((base, files.pop().unwrap()))) +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) { + use crate::driver_interface::msi::x86 as x86_msix; + + // FIXME for cpu_id >255 we need to use the IOMMU to use IRQ remapping + let lapic_id = u8::try_from(cpu_id).expect("CPU id couldn't fit inside u8"); + let rh = false; + let dm = false; + let addr = x86_msix::message_address(lapic_id, rh, dm); + + let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id) + .expect("failed to allocate interrupt vector") + .expect("no interrupt vectors left"); + let msg_data = x86_msix::message_data_edge_triggered(x86_msix::DeliveryMode::Fixed, vector); + + ( + MsiAddrAndData { + addr, + data: msg_data, + }, + interrupt_handle, + ) +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub fn allocate_first_msi_interrupt_on_bsp( + pcid_handle: &mut crate::driver_interface::PciFunctionHandle, +) -> File { + use crate::driver_interface::{MsiSetFeatureInfo, PciFeature, SetFeatureInfo}; + + // TODO: Allow allocation of up to 32 vectors. + + let destination_id = read_bsp_apic_id().expect("failed to read BSP apic id"); + let (msg_addr_and_data, interrupt_handle) = + allocate_single_interrupt_vector_for_msi(destination_id); + + let set_feature_info = MsiSetFeatureInfo { + multi_message_enable: Some(0), + message_address_and_data: Some(msg_addr_and_data), + mask_bits: None, + }; + pcid_handle.set_feature_info(SetFeatureInfo::Msi(set_feature_info)); + + pcid_handle.enable_feature(PciFeature::Msi); + log::debug!("Enabled MSI"); + + interrupt_handle +} + +pub struct InterruptVector { + irq_handle: File, + vector: u16, + kind: InterruptVectorKind, +} + +enum InterruptVectorKind { + Legacy, + Msi, + MsiX { table_entry: *mut MsixTableEntry }, +} + +impl InterruptVector { + pub fn irq_handle(&self) -> &File { + &self.irq_handle + } + + pub fn vector(&self) -> u16 { + self.vector + } + + pub fn set_masked_if_fast(&mut self, masked: bool) -> bool { + match self.kind { + InterruptVectorKind::Legacy | InterruptVectorKind::Msi => false, + InterruptVectorKind::MsiX { table_entry } => { + unsafe { (*table_entry).set_masked(masked) }; + true + } + } + } +} + +/// Get the most optimal supported interrupt mechanism: either (in the order of preference): +/// MSI-X, MSI, and INTx# pin. Returns both runtime interrupt structures (MSI/MSI-X capability +/// structures), and the handles to the interrupts. +// FIXME allow allocating multiple interrupt vectors +// FIXME move MSI-X IRQ allocation to pcid +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub fn pci_allocate_interrupt_vector( + pcid_handle: &mut crate::driver_interface::PciFunctionHandle, + driver: &str, +) -> InterruptVector { + let features = pcid_handle.fetch_all_features(); + + let has_msi = features.iter().any(|feature| feature.is_msi()); + let has_msix = features.iter().any(|feature| feature.is_msix()); + + if has_msix { + let msix_info = match pcid_handle.feature_info(super::PciFeature::MsiX) { + super::PciFeatureInfo::MsiX(msix) => msix, + _ => unreachable!(), + }; + let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; + + pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX); + + let entry = info.table_entry_pointer(0); + + let bsp_cpu_id = read_bsp_apic_id() + .unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}")); + let (msg_addr_and_data, irq_handle) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id); + entry.write_addr_and_data(msg_addr_and_data); + entry.unmask(); + + InterruptVector { + irq_handle, + vector: 0, + kind: InterruptVectorKind::MsiX { table_entry: entry }, + } + } else if has_msi { + InterruptVector { + irq_handle: allocate_first_msi_interrupt_on_bsp(pcid_handle), + vector: 0, + kind: InterruptVectorKind::Msi, + } + } else if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line { + // INTx# pin based interrupts. + InterruptVector { + irq_handle: irq.irq_handle(driver), + vector: 0, + kind: InterruptVectorKind::Legacy, + } + } else { + panic!("{driver}: no interrupts supported at all") + } +} + +// FIXME support MSI on non-x86 systems +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +pub fn pci_allocate_interrupt_vector( + pcid_handle: &mut crate::driver_interface::PciFunctionHandle, + driver: &str, +) -> InterruptVector { + if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line { + // INTx# pin based interrupts. + InterruptVector { + irq_handle: irq.irq_handle(driver), + vector: 0, + kind: InterruptVectorKind::Legacy, + } + } else { + panic!("{driver}: no interrupts supported at all") + } +} diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs new file mode 100644 index 0000000000..d1fa8d733d --- /dev/null +++ b/drivers/pcid/src/driver_interface/mod.rs @@ -0,0 +1,473 @@ +use std::fs::File; +use std::io::prelude::*; +use std::os::fd::{FromRawFd, IntoRawFd, RawFd}; +use std::path::Path; +use std::ptr::NonNull; +use std::{env, io}; +use std::{fmt, process}; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +pub use bar::PciBar; +pub use cap::VendorSpecificCapability; +pub use id::FullDeviceId; +pub use pci_types::PciAddress; + +mod bar; +pub mod cap; +pub mod config; +mod id; +pub mod irq_helpers; +pub mod msi; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct LegacyInterruptLine { + #[doc(hidden)] + pub irq: u8, + pub phandled: Option<(u32, [u32; 3], usize)>, +} + +impl LegacyInterruptLine { + /// Get an IRQ handle for this interrupt line. + pub fn irq_handle(self, driver: &str) -> File { + if let Some((phandle, addr, cells)) = self.phandled { + let path = match cells { + 1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]), + 2 => format!("/scheme/irq/phandle-{}/{},{}", phandle, addr[0], addr[1]), + 3 => format!( + "/scheme/irq/phandle-{}/{},{},{}", + phandle, addr[0], addr[1], addr[2] + ), + _ => panic!( + "unexpected number of IRQ description cells for phandle {phandle}: {cells}" + ), + }; + File::create(path) + .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) + } else { + File::open(format!("/scheme/irq/{}", self.irq)) + .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) + } + } +} + +impl fmt::Display for LegacyInterruptLine { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some((phandle, addr, cells)) = self.phandled { + match cells { + 1 => write!(f, "(phandle {}, {:?})", phandle, addr[0]), + 2 => write!(f, "(phandle {}, {:?},{:?})", phandle, addr[0], addr[1]), + 3 => write!(f, "(phandle {}, {:?})", phandle, addr), + _ => panic!( + "unexpected number of IRQ description cells for phandle {phandle}: {cells}" + ), + } + } else { + write!(f, "{}", self.irq) + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "PciAddress")] +struct PciAddressDef { + #[serde(getter = "PciAddress::segment")] + segment: u16, + #[serde(getter = "PciAddress::bus")] + bus: u8, + #[serde(getter = "PciAddress::device")] + device: u8, + #[serde(getter = "PciAddress::function")] + function: u8, +} + +impl From for PciAddress { + fn from(value: PciAddressDef) -> Self { + PciAddress::new(value.segment, value.bus, value.device, value.function) + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct PciFunction { + /// Address of the PCI function. + #[serde(with = "PciAddressDef")] + pub addr: PciAddress, + + /// PCI Base Address Registers + pub bars: [PciBar; 6], + + /// Legacy IRQ line: It's the responsibility of pcid to make sure that it be mapped in either + /// the I/O APIC or the 8259 PIC, so that the subdriver can map the interrupt vector directly. + /// The vector to map is always this field, plus 32. + /// If INTx# interrupts aren't supported at all this is `None`. + pub legacy_interrupt_line: Option, + + /// All identifying information of the PCI function. + pub full_device_id: FullDeviceId, +} +impl PciFunction { + pub fn name(&self) -> String { + // FIXME stop replacing : with - once it is a valid character in scheme names + format!("pci-{}", self.addr).replace(':', "-") + } + + pub fn display(&self) -> String { + let mut string = self.name(); + let mut first = true; + for (i, bar) in self.bars.iter().enumerate() { + if !bar.is_none() { + if first { + first = false; + string.push_str(" on:"); + } + string.push_str(&format!(" {i}={}", bar.display())); + } + } + if let Some(irq) = self.legacy_interrupt_line { + string.push_str(&format!(" IRQ: {irq}")); + } + string + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SubdriverArguments { + pub func: PciFunction, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum FeatureStatus { + Enabled, + Disabled, +} + +impl FeatureStatus { + pub fn enabled(enabled: bool) -> Self { + if enabled { + Self::Enabled + } else { + Self::Disabled + } + } + pub fn is_enabled(&self) -> bool { + if let &Self::Enabled = self { + true + } else { + false + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum PciFeature { + Msi, + MsiX, +} +impl PciFeature { + pub fn is_msi(self) -> bool { + if let Self::Msi = self { + true + } else { + false + } + } + pub fn is_msix(self) -> bool { + if let Self::MsiX = self { + true + } else { + false + } + } +} +#[derive(Debug, Serialize, Deserialize)] +pub enum PciFeatureInfo { + Msi(msi::MsiInfo), + MsiX(msi::MsixInfo), +} + +// TODO: Remove these "features" and just go strait to the actual thing. + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct MsiSetFeatureInfo { + /// The Multi Message Enable field of the Message Control in the MSI Capability Structure, + /// is the log2 of the interrupt vectors, minus one. Can only be 0b000..=0b101. + pub multi_message_enable: Option, + + /// The system-specific message address and data. + /// + /// The message address contains things like the CPU that will be targeted, at least on + /// x86_64. The message data contains the actual interrupt vector (lower 8 bits) and + /// the kind of interrupt, at least on x86_64. + pub message_address_and_data: Option, + + /// A bitmap of the vectors that are masked. This field is not guaranteed (and not likely, + /// at least according to the feature flags I got from QEMU), to exist. + pub mask_bits: Option, +} + +/// Some flags that might be set simultaneously, but separately. +#[derive(Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum SetFeatureInfo { + Msi(MsiSetFeatureInfo), + + MsiX { + /// Masks the entire function, and all of its vectors. + function_mask: Option, + }, +} + +#[derive(Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum PcidClientRequest { + EnableDevice, + RequestConfig, + RequestFeatures, + RequestVendorCapabilities, + EnableFeature(PciFeature), + FeatureInfo(PciFeature), + SetFeatureInfo(SetFeatureInfo), + ReadConfig(u16), + WriteConfig(u16, u32), +} + +#[derive(Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum PcidServerResponseError { + NonexistentFeature(PciFeature), + InvalidBitPattern, +} + +#[derive(Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum PcidClientResponse { + EnabledDevice, + Config(SubdriverArguments), + AllFeatures(Vec), + VendorCapabilities(Vec), + FeatureEnabled(PciFeature), + FeatureStatus(PciFeature, FeatureStatus), + Error(PcidServerResponseError), + FeatureInfo(PciFeature, PciFeatureInfo), + SetFeatureInfo(PciFeature), + ReadConfig(u32), + WriteConfig, +} + +pub struct MappedBar { + pub ptr: NonNull, + pub bar_size: usize, +} + +/// A handle from a `pcid` client (e.g. `ahcid`) to `pcid`. +pub struct PciFunctionHandle { + channel: File, + config: SubdriverArguments, + mapped_bars: [Option; 6], +} + +fn send(w: &mut File, message: &T) { + let mut data = Vec::new(); + bincode::serialize_into(&mut data, message).expect("couldn't serialize pcid message"); + match w.write(&data) { + Ok(len) => assert_eq!(len, data.len()), + Err(err) => { + log::error!("writing pcid request failed: {err}"); + process::exit(1); + } + } +} +fn recv(r: &mut File) -> T { + let mut length_bytes = [0u8; 8]; + if let Err(err) = r.read_exact(&mut length_bytes) { + log::error!("reading pcid response length failed: {err}"); + process::exit(1); + } + let length = u64::from_le_bytes(length_bytes); + if length > 0x100_000 { + panic!("pcid_interface: buffer too large"); + } + let mut data = vec![0u8; length as usize]; + if let Err(err) = r.read_exact(&mut data) { + log::error!("reading pcid response failed: {err}"); + process::exit(1); + } + + bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") +} + +impl PciFunctionHandle { + pub fn connect_default() -> Self { + let channel_fd = match env::var("PCID_CLIENT_CHANNEL") { + Ok(channel_fd) => channel_fd, + Err(err) => { + log::error!("PCID_CLIENT_CHANNEL invalid: {err}"); + process::exit(1); + } + }; + let channel_fd = match channel_fd.parse::() { + Ok(channel_fd) => channel_fd, + Err(err) => { + log::error!("PCID_CLIENT_CHANNEL invalid: {err}"); + process::exit(1); + } + }; + Self::connect_common(channel_fd) + } + + pub fn connect_by_path(device_path: &Path) -> io::Result { + let channel_fd = syscall::open( + device_path.join("channel").to_str().unwrap(), + syscall::O_RDWR, + ) + .map_err(|err| io::Error::other(format!("failed to open pcid channel: {}", err)))?; + Ok(Self::connect_common(channel_fd as RawFd)) + } + + fn connect_common(channel_fd: i32) -> PciFunctionHandle { + let mut channel = unsafe { File::from_raw_fd(channel_fd) }; + + send(&mut channel, &PcidClientRequest::RequestConfig); + let config = match recv(&mut channel) { + PcidClientResponse::Config(a) => a, + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + }; + + Self { + channel, + config, + mapped_bars: [const { None }; 6], + } + } + + pub fn into_inner_fd(self) -> RawFd { + self.channel.into_raw_fd() + } + + fn send(&mut self, req: &PcidClientRequest) { + send(&mut self.channel, req) + } + fn recv(&mut self) -> PcidClientResponse { + recv(&mut self.channel) + } + + pub fn config(&self) -> SubdriverArguments { + self.config.clone() + } + + pub fn enable_device(&mut self) { + self.send(&PcidClientRequest::EnableDevice); + match self.recv() { + PcidClientResponse::EnabledDevice => {} + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + + pub fn get_vendor_capabilities(&mut self) -> Vec { + self.send(&PcidClientRequest::RequestVendorCapabilities); + match self.recv() { + PcidClientResponse::VendorCapabilities(a) => a, + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + + // FIXME turn into struct with bool fields + pub fn fetch_all_features(&mut self) -> Vec { + self.send(&PcidClientRequest::RequestFeatures); + match self.recv() { + PcidClientResponse::AllFeatures(a) => a, + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + pub fn enable_feature(&mut self, feature: PciFeature) { + self.send(&PcidClientRequest::EnableFeature(feature)); + match self.recv() { + PcidClientResponse::FeatureEnabled(feat) if feat == feature => {} + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + pub fn feature_info(&mut self, feature: PciFeature) -> PciFeatureInfo { + self.send(&PcidClientRequest::FeatureInfo(feature)); + match self.recv() { + PcidClientResponse::FeatureInfo(feat, info) if feat == feature => info, + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + pub fn set_feature_info(&mut self, info: SetFeatureInfo) { + self.send(&PcidClientRequest::SetFeatureInfo(info)); + match self.recv() { + PcidClientResponse::SetFeatureInfo(_) => {} + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + pub unsafe fn read_config(&mut self, offset: u16) -> u32 { + self.send(&PcidClientRequest::ReadConfig(offset)); + match self.recv() { + PcidClientResponse::ReadConfig(value) => value, + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + pub unsafe fn write_config(&mut self, offset: u16, value: u32) { + self.send(&PcidClientRequest::WriteConfig(offset, value)); + match self.recv() { + PcidClientResponse::WriteConfig => {} + other => { + log::error!("received wrong pcid response: {other:?}"); + process::exit(1); + } + } + } + pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar { + let mapped_bar = &mut self.mapped_bars[bir as usize]; + if let Some(mapped_bar) = mapped_bar { + mapped_bar + } else { + let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem(); + + let ptr = match unsafe { + common::physmap( + bar, + bar_size, + common::Prot::RW, + // FIXME once the kernel supports this use write-through for prefetchable BAR + common::MemoryType::Uncacheable, + ) + } { + Ok(ptr) => ptr, + Err(err) => { + log::error!("failed to map BAR at {bar:016X}: {err}"); + process::exit(1); + } + }; + + mapped_bar.insert(MappedBar { + ptr: NonNull::new(ptr.cast::()).expect("Mapping a BAR resulted in a nullptr"), + bar_size, + }) + } + } +} diff --git a/drivers/pcid/src/driver_interface/msi.rs b/drivers/pcid/src/driver_interface/msi.rs new file mode 100644 index 0000000000..0ca68ec556 --- /dev/null +++ b/drivers/pcid/src/driver_interface/msi.rs @@ -0,0 +1,257 @@ +use std::fmt; +use std::ptr::NonNull; + +use crate::driver_interface::PciBar; +use crate::PciFunctionHandle; + +use common::io::{Io, Mmio}; +use serde::{Deserialize, Serialize}; + +/// The address and data to use for MSI and MSI-X. +/// +/// For MSI using this only works when you need a single interrupt vector. +/// For MSI-X you can have a single [MsiEntry] for each interrupt vector. +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct MsiAddrAndData { + pub addr: u64, + pub data: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MsiInfo { + pub log2_multiple_message_capable: u8, + pub is_64bit: bool, + pub has_per_vector_masking: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct MsixInfo { + pub table_bar: u8, + pub table_offset: u32, + pub table_size: u16, + pub pba_bar: u8, + pub pba_offset: u32, +} + +impl MsixInfo { + pub unsafe fn map_and_mask_all(self, pcid_handle: &mut PciFunctionHandle) -> MappedMsixRegs { + self.validate(pcid_handle.config().func.bars); + + let virt_table_base = unsafe { + pcid_handle + .map_bar(self.table_bar) + .ptr + .as_ptr() + .byte_add(self.table_offset as usize) + }; + + let mut info = MappedMsixRegs { + virt_table_base: NonNull::new(virt_table_base.cast::()).unwrap(), + info: self, + }; + + // Mask all interrupts in case some earlier driver/os already unmasked them (according to + // the PCI Local Bus spec 3.0, they are masked after system reset). + for i in 0..info.info.table_size { + info.table_entry_pointer(i.into()).mask(); + } + + info + } + + fn validate(&self, bars: [PciBar; 6]) { + if self.table_bar > 5 { + panic!( + "MSI-X Table BIR contained a reserved enum value: {}", + self.table_bar + ); + } + if self.pba_bar > 5 { + panic!( + "MSI-X PBA BIR contained a reserved enum value: {}", + self.pba_bar + ); + } + + let table_size = self.table_size; + let table_offset = self.table_offset as usize; + let table_min_length = table_size * 16; + + let pba_offset = self.pba_offset as usize; + let pba_min_length = table_size.div_ceil(8); + + let (_, table_bar_size) = bars[self.table_bar as usize].expect_mem(); + let (_, pba_bar_size) = bars[self.pba_bar as usize].expect_mem(); + + // Ensure that the table and PBA are within the BAR. + + if !(0..table_bar_size as u64).contains(&(table_offset as u64 + table_min_length as u64)) { + panic!( + "Table {:#x}:{:#x} outside of BAR with length {:#x}", + table_offset, + table_offset + table_min_length as usize, + table_bar_size + ); + } + + if !(0..pba_bar_size as u64).contains(&(pba_offset as u64 + pba_min_length as u64)) { + panic!( + "PBA {:#x}:{:#x} outside of BAR with length {:#x}", + pba_offset, + pba_offset + pba_min_length as usize, + pba_bar_size + ); + } + } +} + +pub struct MappedMsixRegs { + pub virt_table_base: NonNull, + pub info: MsixInfo, +} +impl MappedMsixRegs { + pub unsafe fn table_entry_pointer_unchecked(&mut self, k: usize) -> &mut MsixTableEntry { + &mut *self.virt_table_base.as_ptr().add(k) + } + + pub fn table_entry_pointer(&mut self, k: usize) -> &mut MsixTableEntry { + assert!(k < self.info.table_size as usize); + unsafe { self.table_entry_pointer_unchecked(k) } + } +} + +#[repr(C, packed)] +pub struct MsixTableEntry { + pub addr_lo: Mmio, + pub addr_hi: Mmio, + pub msg_data: Mmio, + pub vec_ctl: Mmio, +} + +const _: () = { + assert!(size_of::() == 16); +}; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub mod x86 { + #[repr(u8)] + pub enum TriggerMode { + Edge = 0, + Level = 1, + } + + #[repr(u8)] + pub enum LevelTriggerMode { + Deassert = 0, + Assert = 1, + } + + #[repr(u8)] + pub enum DeliveryMode { + Fixed = 0b000, + LowestPriority = 0b001, + Smi = 0b010, + // 0b011 is reserved + Nmi = 0b100, + Init = 0b101, + // 0b110 is reserved + ExtInit = 0b111, + } + + // TODO: should the reserved field be preserved? + pub const fn message_address( + destination_id: u8, + redirect_hint: bool, + dest_mode_logical: bool, + ) -> u64 { + 0x0000_0000_FEE0_0000u64 + | ((destination_id as u64) << 12) + | ((redirect_hint as u64) << 3) + | ((dest_mode_logical as u64) << 2) + } + pub const fn message_data( + trigger_mode: TriggerMode, + level_trigger_mode: LevelTriggerMode, + delivery_mode: DeliveryMode, + vector: u8, + ) -> u32 { + ((trigger_mode as u32) << 15) + | ((level_trigger_mode as u32) << 14) + | ((delivery_mode as u32) << 8) + | vector as u32 + } + pub const fn message_data_level_triggered( + level_trigger_mode: LevelTriggerMode, + delivery_mode: DeliveryMode, + vector: u8, + ) -> u32 { + message_data( + TriggerMode::Level, + level_trigger_mode, + delivery_mode, + vector, + ) + } + pub const fn message_data_edge_triggered(delivery_mode: DeliveryMode, vector: u8) -> u32 { + message_data( + TriggerMode::Edge, + LevelTriggerMode::Deassert, + delivery_mode, + vector, + ) + } +} + +impl MsixTableEntry { + pub fn addr_lo(&self) -> u32 { + self.addr_lo.read() + } + pub fn addr_hi(&self) -> u32 { + self.addr_hi.read() + } + pub fn set_addr_lo(&mut self, value: u32) { + self.addr_lo.write(value); + } + pub fn set_addr_hi(&mut self, value: u32) { + self.addr_hi.write(value); + } + pub fn msg_data(&self) -> u32 { + self.msg_data.read() + } + pub fn vec_ctl(&self) -> u32 { + self.vec_ctl.read() + } + pub fn set_msg_data(&mut self, value: u32) { + self.msg_data.write(value); + } + pub fn addr(&self) -> u64 { + u64::from(self.addr_lo()) | (u64::from(self.addr_hi()) << 32) + } + pub const VEC_CTL_MASK_BIT: u32 = 1; + + pub fn set_masked(&mut self, masked: bool) { + self.vec_ctl.writef(Self::VEC_CTL_MASK_BIT, masked) + } + pub fn mask(&mut self) { + self.set_masked(true); + } + pub fn unmask(&mut self) { + self.set_masked(false); + } + + pub fn write_addr_and_data(&mut self, entry: MsiAddrAndData) { + self.set_addr_lo(entry.addr as u32); + self.set_addr_hi((entry.addr >> 32) as u32); + self.set_msg_data(entry.data); + } +} + +impl fmt::Debug for MsixTableEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MsixTableEntry") + .field("addr", &self.addr()) + .field("msg_data", &self.msg_data()) + .field("vec_ctl", &self.vec_ctl()) + .finish() + } +} diff --git a/drivers/pcid/src/lib.rs b/drivers/pcid/src/lib.rs new file mode 100644 index 0000000000..312a886cf5 --- /dev/null +++ b/drivers/pcid/src/lib.rs @@ -0,0 +1,4 @@ +//! Interface to `pcid`. + +mod driver_interface; +pub use driver_interface::*; diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs new file mode 100644 index 0000000000..238ca8c7d7 --- /dev/null +++ b/drivers/pcid/src/main.rs @@ -0,0 +1,313 @@ +#![feature(iter_next_chunk)] +#![feature(if_let_guard)] + +use std::collections::BTreeMap; + +use log::{debug, info, trace, warn}; +use pci_types::capability::PciCapability; +use pci_types::{ + Bar as TyBar, CommandRegister, EndpointHeader, HeaderType, PciAddress, + PciHeader as TyPciHeader, PciPciBridgeHeader, +}; +use redox_scheme::{RequestKind, SignalBehavior}; + +use crate::cfg_access::Pcie; +use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction}; + +mod cfg_access; +mod driver_handler; +mod scheme; + +pub struct Func { + inner: PciFunction, + + capabilities: Vec, + endpoint_header: EndpointHeader, + enabled: bool, +} + +fn handle_parsed_header( + pcie: &Pcie, + tree: &mut BTreeMap, + endpoint_header: EndpointHeader, + full_device_id: FullDeviceId, +) { + let mut bars = [PciBar::None; 6]; + let mut skip = false; + for i in 0..6 { + if skip { + skip = false; + continue; + } + match endpoint_header.bar(i, pcie) { + Some(TyBar::Io { port }) => bars[i as usize] = PciBar::Port(port.try_into().unwrap()), + Some(TyBar::Memory32 { + address, + size, + prefetchable: _, + }) => { + bars[i as usize] = PciBar::Memory32 { + addr: address, + size, + } + } + Some(TyBar::Memory64 { + address, + size, + prefetchable: _, + }) => { + bars[i as usize] = PciBar::Memory64 { + addr: address, + size, + }; + skip = true; // Each 64bit memory BAR occupies two slots + } + None => bars[i as usize] = PciBar::None, + } + } + + let mut string = String::new(); + for (i, bar) in bars.iter().enumerate() { + if !bar.is_none() { + string.push_str(&format!(" {i}={}", bar.display())); + } + } + + if !string.is_empty() { + debug!(" BAR{}", string); + } + + let capabilities = if endpoint_header.status(pcie).has_capability_list() { + endpoint_header.capabilities(pcie).collect::>() + } else { + Vec::new() + }; + debug!( + "PCI DEVICE CAPABILITIES for {}: {:?}", + endpoint_header.header().address(), + capabilities + ); + + let func = Func { + inner: pcid_interface::PciFunction { + bars, + addr: endpoint_header.header().address(), + legacy_interrupt_line: None, // Will be filled in when enabling the device + full_device_id: full_device_id.clone(), + }, + + capabilities, + endpoint_header, + enabled: false, + }; + + tree.insert(func.inner.addr, func); +} + +fn enable_function( + pcie: &Pcie, + endpoint_header: &mut EndpointHeader, + capabilities: &mut [PciCapability], +) -> Option { + // Enable bus mastering, memory space, and I/O space + endpoint_header.update_command(pcie, |cmd| { + cmd | CommandRegister::BUS_MASTER_ENABLE + | CommandRegister::MEMORY_ENABLE + | CommandRegister::IO_ENABLE + }); + + // Disable MSI and MSI-X in case a previous driver instance enabled them. + for capability in capabilities { + match capability { + PciCapability::Msi(capability) => { + capability.set_enabled(false, pcie); + } + PciCapability::MsiX(capability) => { + capability.set_enabled(false, pcie); + } + _ => {} + } + } + + // Set IRQ line to 9 if not set + let mut irq = 0xFF; + let mut interrupt_pin = 0xFF; + + endpoint_header.update_interrupt(pcie, |(pin, mut line)| { + if line == 0xFF { + line = 9; + } + irq = line; + interrupt_pin = pin; + (pin, line) + }); + + let legacy_interrupt_enabled = match interrupt_pin { + 0 => false, + 1 | 2 | 3 | 4 => true, + + other => { + warn!("pcid: invalid interrupt pin: {}", other); + false + } + }; + + if legacy_interrupt_enabled { + let pci_address = endpoint_header.header().address(); + let dt_address = ((pci_address.bus() as u32) << 16) + | ((pci_address.device() as u32) << 11) + | ((pci_address.function() as u32) << 8); + let addr = [ + dt_address & pcie.interrupt_map_mask[0], + 0u32, + 0u32, + interrupt_pin as u32 & pcie.interrupt_map_mask[3], + ]; + let mapping = pcie + .interrupt_map + .iter() + .find(|x| x.addr == addr[0..3] && x.interrupt == addr[3]); + let phandled = if let Some(mapping) = mapping { + Some(( + mapping.parent_phandle, + mapping.parent_interrupt, + mapping.parent_interrupt_cells, + )) + } else { + None + }; + if mapping.is_some() { + debug!("found mapping: addr={:?} => {:?}", addr, phandled); + } + + Some(LegacyInterruptLine { irq, phandled }) + } else { + None + } +} + +fn main() { + let mut args = pico_args::Arguments::from_env(); + let verbosity = (0..).find(|_| !args.contains("-v")).unwrap_or(0); + let log_level = match verbosity { + 0 => log::LevelFilter::Info, + 1 => log::LevelFilter::Debug, + _ => log::LevelFilter::Trace, + }; + + common::setup_logging("bus", "pci", "pcid", log_level, log::LevelFilter::Info); + + redox_daemon::Daemon::new(move |daemon| main_inner(daemon)).unwrap(); +} + +fn main_inner(daemon: redox_daemon::Daemon) -> ! { + let pcie = Pcie::new(); + let mut tree = BTreeMap::new(); + + info!("PCI SG-BS:DV.F VEND:DEVI CL.SC.IN.RV"); + + // FIXME Use full ACPI for enumerating the host bridges. MCFG only describes the first + // host bridge, while multi-processor systems likely have a host bridge for each CPU. + // See also https://www.kernel.org/doc/html/latest/PCI/acpi-info.html + // Bus 0x80 is scanned for compatibility with newer (Arrow Lake) Intel CPUs where PCH devices + // are there. This workaround may not be required if we had ACPI bus enumeration. + let mut bus_nums = vec![0, 0x80]; + let mut bus_i = 0; + while bus_i < bus_nums.len() { + let bus_num = bus_nums[bus_i]; + bus_i += 1; + + for dev_num in 0..32 { + scan_device(&mut tree, &pcie, &mut bus_nums, bus_num, dev_num); + } + } + debug!("Enumeration complete, now starting pci scheme"); + + let mut scheme = scheme::PciScheme::new(pcie, tree); + let socket = redox_scheme::Socket::create("pci").expect("failed to open pci scheme socket"); + + let _ = daemon.ready(); + + loop { + let Some(request) = socket + .next_request(SignalBehavior::Restart) + .expect("pcid: failed to get next scheme request") + else { + break; + }; + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut scheme); + + socket + .write_response(response, SignalBehavior::Restart) + .expect("pcid: failed to write next scheme response"); + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + _ => (), + } + } + + println!("pcid: exit"); + std::process::exit(0); +} + +fn scan_device( + tree: &mut BTreeMap, + pcie: &Pcie, + bus_nums: &mut Vec, + bus_num: u8, + dev_num: u8, +) { + for func_num in 0..8 { + let header = TyPciHeader::new(PciAddress::new(0, bus_num, dev_num, func_num)); + + let (vendor_id, device_id) = header.id(pcie); + if vendor_id == 0xffff && device_id == 0xffff { + if func_num == 0 { + trace!("PCI {:>02X}:{:>02X}: no dev", bus_num, dev_num); + return; + } + + continue; + } + + let (revision, class, subclass, interface) = header.revision_and_class(pcie); + let full_device_id = FullDeviceId { + vendor_id, + device_id, + class, + subclass, + interface, + revision, + }; + + info!("PCI {} {}", header.address(), full_device_id.display()); + + let has_multiple_functions = header.has_multiple_functions(pcie); + + match header.header_type(pcie) { + HeaderType::Endpoint => { + handle_parsed_header( + pcie, + tree, + EndpointHeader::from_header(header, pcie).unwrap(), + full_device_id, + ); + } + HeaderType::PciPciBridge => { + let bridge_header = PciPciBridgeHeader::from_header(header, pcie).unwrap(); + bus_nums.push(bridge_header.secondary_bus_number(pcie)); + } + ty => { + warn!("pcid: unknown header type: {ty:?}"); + } + } + + if func_num == 0 && !has_multiple_functions { + return; + } + } +} diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs new file mode 100644 index 0000000000..2c6fb65d19 --- /dev/null +++ b/drivers/pcid/src/scheme.rs @@ -0,0 +1,394 @@ +use std::collections::{BTreeMap, VecDeque}; + +use pci_types::{ConfigRegionAccess, PciAddress}; +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult}; +use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; +use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; +use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; +use syscall::schemev2::NewFdFlags; +use syscall::ENOLCK; + +use crate::cfg_access::Pcie; + +pub struct PciScheme { + handles: BTreeMap, + next_id: usize, + pcie: Pcie, + tree: BTreeMap, +} +enum Handle { + TopLevel { entries: Vec }, + Access, + Device, + Channel { addr: PciAddress, st: ChannelState }, +} +struct HandleWrapper { + inner: Handle, + stat: bool, +} +impl Handle { + fn is_file(&self) -> bool { + matches!(self, Self::Access | Self::Channel { .. }) + } + fn is_dir(&self) -> bool { + !self.is_file() + } + // TODO: capability rather than root + fn requires_root(&self) -> bool { + matches!(self, Self::Access | Self::Channel { .. }) + } +} + +enum ChannelState { + AwaitingData, + AwaitingResponseRead(VecDeque), +} + +const DEVICE_CONTENTS: &[&str] = &["channel"]; + +impl SchemeSync for PciScheme { + fn open(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result { + log::trace!("OPEN `{}` flags {}", path, flags); + + // TODO: Check flags are correct + let expects_dir = path.ends_with('/') || flags & O_DIRECTORY != 0; + + let path = path.trim_matches('/'); + + let handle = if path.is_empty() { + Handle::TopLevel { + entries: self + .tree + .iter() + // FIXME remove replacement of : once the old scheme format is no longer supported. + .map(|(addr, _)| format!("{}", addr).replace(':', "--")) + .collect::>(), + } + } else if path == "access" { + Handle::Access + } else { + let idx = path.find('/').unwrap_or(path.len()); + let (addr_str, after) = path.split_at(idx); + let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?; + + self.parse_after_pci_addr(addr, after)? + }; + + let stat = flags & O_STAT != 0; + if expects_dir && handle.is_file() && !stat { + return Err(Error::new(ENOTDIR)); + } + if !expects_dir && handle.is_dir() && !stat { + return Err(Error::new(EISDIR)); + } + if ctx.uid != 0 && handle.requires_root() && !stat { + return Err(Error::new(EACCES)); + } + + let id = self.next_id; + self.next_id += 1; + + self.handles.insert( + id, + HandleWrapper { + inner: handle, + stat, + }, + ); + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::POSITIONED, + }) + } + fn fstat(&mut self, id: usize, stat: &mut syscall::Stat, _ctx: &CallerCtx) -> Result<()> { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + let (len, mode) = match handle.inner { + Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), + Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), + Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), + }; + stat.st_size = len as u64; + stat.st_mode = mode; + Ok(()) + } + fn read( + &mut self, + id: usize, + buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + match handle.inner { + Handle::TopLevel { .. } => Err(Error::new(EISDIR)), + Handle::Device => Err(Error::new(EISDIR)), + Handle::Channel { + addr: _, + ref mut st, + } => Self::read_channel(st, buf), + _ => Err(Error::new(EBADF)) + } + } + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + let Ok(offset) = usize::try_from(opaque_offset) else { + return Ok(buf); + }; + + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + let entries = match handle.inner { + Handle::TopLevel { ref entries } => { + for (i, dent_name) in entries.iter().enumerate().skip(offset) { + buf.entry(DirEntry { + inode: 0, + name: dent_name, + kind: DirentKind::Unspecified, + next_opaque_id: i as u64 + 1, + })?; + } + return Ok(buf); + } + Handle::Device => DEVICE_CONTENTS, + Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), + }; + + for (i, dent_name) in entries.iter().enumerate().skip(offset) { + buf.entry(DirEntry { + inode: 0, + name: dent_name, + kind: DirentKind::Unspecified, + next_opaque_id: i as u64 + 1, + })?; + } + Ok(buf) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + match handle.inner { + Handle::Channel { addr, ref mut st } => { + Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) + } + + _ => Err(Error::new(EBADF)), + } + } + + fn call(&mut self, id: usize, payload: &mut [u8], metadata: &[u64]) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + match handle.inner { + Handle::Access => { + let payload_len = u16::try_from(payload.len()).map_err(|_| Error::new(EINVAL))?; + let write = match metadata.get(0) { + Some(1) => false, + Some(2) => true, + _ => return Err(Error::new(EINVAL)), + }; + let (addr, offset) = match metadata.get(1) { + Some(value) => { + // Segment: u16, at 28 bits + // Bus: u8, 8 bits, 256 total, at 20 bits + // Device: u8, 5 bits, 32 total, at 15 bits + // Function: u8, 3 bits, 8 total, at 12 bits + // Offset: u16, 12 bits, 4096 total, at 0 bits + ( + PciAddress::new( + ((value >> 28) & 0xFFFF) as u16, + ((value >> 20) & 0xFF) as u8, + ((value >> 15) & 0x1F) as u8, + ((value >> 12) & 0x7) as u8, + ), + (value & 0xFFF) as u16 + ) + } + None => return Err(Error::new(EINVAL)), + }; + // This handle must allow less than 4 byte access, but the + // lower level only works with 4 byte reads and writes + let unaligned = offset % 4; + let start = offset - unaligned; + let end = offset + payload_len; + let mut i = 0; + while start + i < end { + let mut bytes = unsafe { self.pcie.read(addr, start + i) }.to_le_bytes(); + for j in 0..bytes.len() { + if let Some(payload_i) = i.checked_sub(unaligned) { + if let Some(payload_b) = payload.get_mut(usize::from(payload_i)) { + if write { + bytes[j] = *payload_b; + } else { + *payload_b = bytes[j] + } + } + } + i += 1; + } + if write { + let value = u32::from_le_bytes(bytes); + unsafe { self.pcie.write(addr, start + i, value); } + } + } + Ok(payload.len()) + } + + _ => Err(Error::new(EBADF)), + } + } +} + +impl PciScheme { + pub fn on_close(&mut self, id: usize) { + match self.handles.remove(&id) { + Some(HandleWrapper { + inner: Handle::Channel { addr, .. }, + .. + }) => { + log::trace!("TODO: Support disabling device (called on {})", addr); + if let Some(func) = self.tree.get_mut(&addr) { + func.enabled = false; + } + } + _ => {} + } + } +} + +impl PciScheme { + pub fn new(pcie: Pcie, tree: BTreeMap) -> Self { + Self { + handles: BTreeMap::new(), + next_id: 0, + pcie, + tree, + } + } + fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result { + if after.chars().next().map_or(false, |c| c != '/') { + return Err(Error::new(ENOENT)); + } + let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?; + + Ok(if after.is_empty() { + Handle::Device + } else { + let path = &after[1..]; + + match path { + "channel" => { + if func.enabled { + return Err(Error::new(ENOLCK)); + } + func.inner.legacy_interrupt_line = crate::enable_function( + &self.pcie, + &mut func.endpoint_header, + &mut func.capabilities, + ); + func.enabled = true; + Handle::Channel { + addr, + st: ChannelState::AwaitingData, + } + } + _ => return Err(Error::new(ENOENT)), + } + }) + } + + fn read_channel(state: &mut ChannelState, buf: &mut [u8]) -> Result { + match *state { + ChannelState::AwaitingResponseRead(ref mut queue) => { + let byte_count = std::cmp::min(queue.len(), buf.len()); + // XXX: Why can't VecDeque support dequeueing into slices? + for (idx, byte) in queue.drain(..byte_count).enumerate() { + buf[idx] = byte; + } + if queue.is_empty() { + *state = ChannelState::AwaitingData; + } + Ok(byte_count) + } + ChannelState::AwaitingData => Err(Error::new(EINVAL)), + } + } + fn write_channel( + pci_state: &Pcie, + tree: &mut BTreeMap, + addr: PciAddress, + state: &mut ChannelState, + buf: &[u8], + ) -> Result { + match *state { + ChannelState::AwaitingResponseRead(_) => return Err(Error::new(EINVAL)), + ChannelState::AwaitingData => { + let func = tree.get_mut(&addr).unwrap(); + + let request = bincode::deserialize_from(buf).map_err(|_| Error::new(EINVAL))?; + let response = crate::driver_handler::DriverHandler::new( + func.inner.clone(), + &mut func.endpoint_header, + &mut func.capabilities, + &*pci_state, + ) + .respond(request); + + let mut output_bytes = vec![0_u8; 8]; + bincode::serialize_into(&mut output_bytes, &response) + .map_err(|_| Error::new(EIO))?; + let len = output_bytes.len() - 8; + output_bytes[..8].copy_from_slice(&u64::to_le_bytes(len as u64)); + *state = ChannelState::AwaitingResponseRead(output_bytes.into()); + + Ok(buf.len()) + } + } + } +} + +fn parse_pci_addr(addr: &str) -> Option { + let (segment, rest) = addr.split_once('-')?; + let segment = u16::from_str_radix(segment, 16).ok()?; + + // FIXME use : instead of -- as separator once the old scheme format is no longer supported. + let (bus, rest) = rest.split_once("--")?; + let bus = u8::from_str_radix(bus, 16).ok()?; + + let (device, function) = rest.split_once('.')?; + let device = u8::from_str_radix(device, 16).ok()?; + let function = u8::from_str_radix(function, 16).ok()?; + + Some(PciAddress::new(segment, bus, device, function)) +} diff --git a/drivers/rtcd/Cargo.toml b/drivers/rtcd/Cargo.toml new file mode 100644 index 0000000000..a6b9587872 --- /dev/null +++ b/drivers/rtcd/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rtcd" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2018" +license = "MIT" + + +[dependencies] +anyhow = "1" + +common = { path = "../common" } diff --git a/drivers/rtcd/src/main.rs b/drivers/rtcd/src/main.rs new file mode 100644 index 0000000000..3e913780ab --- /dev/null +++ b/drivers/rtcd/src/main.rs @@ -0,0 +1,26 @@ +use anyhow::{Context, Result}; + +// TODO: Do not use target architecture to distinguish these. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod x86; + +/// The rtc driver runs only once, being perhaps the first of all processes that init starts (since +/// early logging benefits from knowing the time, even though this can be adjusted later once the +/// time is known). The sole job of `rtcd` is to read from the hardware real-time clock, and then +/// write the offset to the kernel. + +fn main() -> Result<()> { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + common::acquire_port_io_rights().context("failed to set iopl")?; + + let time_s = self::x86::get_time(); + let time_ns = u128::from(time_s) * 1_000_000_000; + + std::fs::write("/scheme/sys/update_time_offset", &time_ns.to_ne_bytes()) + .context("failed to write to time offset")?; + } + // TODO: aarch64 is currently handled in the kernel + + Ok(()) +} diff --git a/drivers/rtcd/src/x86.rs b/drivers/rtcd/src/x86.rs new file mode 100644 index 0000000000..ea2aebcbbf --- /dev/null +++ b/drivers/rtcd/src/x86.rs @@ -0,0 +1,147 @@ +// TODO: Get RTC information from acpid. +use common::io::{Io, Pio}; + +pub fn get_time() -> u64 { + Rtc::new().time() +} + +fn cvt_bcd(value: usize) -> usize { + (value & 0xF) + ((value / 16) * 10) +} + +/// RTC +pub struct Rtc { + addr: Pio, + data: Pio, + nmi: bool, +} + +impl Rtc { + /// Create new empty RTC + pub fn new() -> Self { + Rtc { + addr: Pio::::new(0x70), + data: Pio::::new(0x71), + nmi: false, + } + } + + /// Read + unsafe fn read(&mut self, reg: u8) -> u8 { + if self.nmi { + self.addr.write(reg & 0x7F); + } else { + self.addr.write(reg | 0x80); + } + self.data.read() + } + + /// Write + #[allow(dead_code)] + unsafe fn write(&mut self, reg: u8, value: u8) { + if self.nmi { + self.addr.write(reg & 0x7F); + } else { + self.addr.write(reg | 0x80); + } + self.data.write(value); + } + + /// Wait for an update, can take one second if full is specified! + unsafe fn wait(&mut self, full: bool) { + if full { + while self.read(0xA) & 0x80 != 0x80 {} + } + while self.read(0xA) & 0x80 == 0x80 {} + } + + /// Get time without waiting + pub unsafe fn time_no_wait(&mut self) -> u64 { + /*let century_register = if let Some(ref fadt) = acpi::ACPI_TABLE.lock().fadt { + Some(fadt.century) + } else { + None + };*/ + + let mut second = self.read(0) as usize; + let mut minute = self.read(2) as usize; + let mut hour = self.read(4) as usize; + let mut day = self.read(7) as usize; + let mut month = self.read(8) as usize; + let mut year = self.read(9) as usize; + let mut century = /* TODO: Fix invalid value from VirtualBox + if let Some(century_reg) = century_register { + self.read(century_reg) as usize + } else */ { + 20 + }; + let register_b = self.read(0xB); + + if register_b & 4 != 4 { + second = cvt_bcd(second); + minute = cvt_bcd(minute); + hour = cvt_bcd(hour & 0x7F) | (hour & 0x80); + day = cvt_bcd(day); + month = cvt_bcd(month); + year = cvt_bcd(year); + century = /* TODO: Fix invalid value from VirtualBox + if century_register.is_some() { + cvt_bcd(century) + } else */ { + century + }; + } + + if register_b & 2 != 2 || hour & 0x80 == 0x80 { + hour = ((hour & 0x7F) + 12) % 24; + } + + year += century * 100; + + // Unix time from clock + let mut secs: u64 = (year as u64 - 1970) * 31_536_000; + + let mut leap_days = (year as u64 - 1972) / 4 + 1; + if year % 4 == 0 && month <= 2 { + leap_days -= 1; + } + secs += leap_days * 86_400; + + match month { + 2 => secs += 2_678_400, + 3 => secs += 5_097_600, + 4 => secs += 7_776_000, + 5 => secs += 10_368_000, + 6 => secs += 13_046_400, + 7 => secs += 15_638_400, + 8 => secs += 18_316_800, + 9 => secs += 20_995_200, + 10 => secs += 23_587_200, + 11 => secs += 26_265_600, + 12 => secs += 28_857_600, + _ => (), + } + + secs += (day as u64 - 1) * 86_400; + secs += hour as u64 * 3600; + secs += minute as u64 * 60; + secs += second as u64; + + secs + } + + /// Get time + pub fn time(&mut self) -> u64 { + loop { + unsafe { + self.wait(false); + let time = self.time_no_wait(); + self.wait(false); + let next_time = self.time_no_wait(); + if time == next_time { + return time; + } + } + } + } +} diff --git a/drivers/rust-toolchain.toml b/drivers/rust-toolchain.toml new file mode 100644 index 0000000000..42f22f6190 --- /dev/null +++ b/drivers/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2025-10-03" +components = ["rust-src"] diff --git a/drivers/storage/ahcid/.gitignore b/drivers/storage/ahcid/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/storage/ahcid/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/storage/ahcid/Cargo.toml b/drivers/storage/ahcid/Cargo.toml new file mode 100644 index 0000000000..7b1a06accf --- /dev/null +++ b/drivers/storage/ahcid/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ahcid" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "1.2" +byteorder = "1.2" +log = "0.4" +redox-daemon = "0.1" +redox_syscall = { version = "0.5", features = ["std"] } + +common = { path = "../../common" } +driver-block = { path = "../driver-block" } +pcid = { path = "../../pcid" } +libredox = "0.1.3" +redox_event = "0.4" diff --git a/drivers/storage/ahcid/src/ahci/disk_ata.rs b/drivers/storage/ahcid/src/ahci/disk_ata.rs new file mode 100644 index 0000000000..4f83c51dcf --- /dev/null +++ b/drivers/storage/ahcid/src/ahci/disk_ata.rs @@ -0,0 +1,185 @@ +use std::convert::TryInto; +use std::ptr; + +use syscall::error::Result; + +use common::dma::Dma; + +use super::hba::{HbaCmdHeader, HbaCmdTable, HbaPort}; +use super::Disk; + +enum BufferKind<'a> { + Read(&'a mut [u8]), + Write(&'a [u8]), +} + +struct Request { + address: usize, + total_sectors: usize, + sector: usize, + running_opt: Option<(u32, usize)>, +} + +pub struct DiskATA { + id: usize, + port: &'static mut HbaPort, + size: u64, + request_opt: Option, + clb: Dma<[HbaCmdHeader; 32]>, + ctbas: [Dma; 32], + _fb: Dma<[u8; 256]>, + buf: Dma<[u8; 256 * 512]>, +} + +impl DiskATA { + pub fn new(id: usize, port: &'static mut HbaPort) -> Result { + let mut clb = unsafe { Dma::zeroed()?.assume_init() }; + + let mut ctbas: [_; 32] = (0..32) + .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()); + + let mut fb = unsafe { Dma::zeroed()?.assume_init() }; + let buf = unsafe { Dma::zeroed()?.assume_init() }; + + port.init(&mut clb, &mut ctbas, &mut fb)?; + + let size = unsafe { port.identify(&mut clb, &mut ctbas).unwrap_or(0) }; + + Ok(DiskATA { + id: id, + port: port, + size: size, + request_opt: None, + clb: clb, + ctbas, + _fb: fb, + buf: buf, + }) + } + + fn request(&mut self, block: u64, mut buffer_kind: BufferKind) -> Result> { + let (write, address, total_sectors) = match buffer_kind { + BufferKind::Read(ref buffer) => (false, buffer.as_ptr() as usize, buffer.len() / 512), + BufferKind::Write(ref buffer) => (true, buffer.as_ptr() as usize, buffer.len() / 512), + }; + + loop { + let mut request = match self.request_opt.take() { + Some(request) => { + if address == request.address && total_sectors == request.total_sectors { + // Keep servicing current request + request + } else { + // Have to wait for another request to finish + self.request_opt = Some(request); + return Ok(None); + } + } + None => { + // Create new request + Request { + address, + total_sectors, + sector: 0, + running_opt: None, + } + } + }; + + // Finish a previously running request + if let Some(running) = request.running_opt.take() { + if self.port.ata_running(running.0) { + // Continue waiting for request + request.running_opt = Some(running); + self.request_opt = Some(request); + return Ok(None); + } + + self.port.ata_stop(running.0)?; + + if let BufferKind::Read(ref mut buffer) = buffer_kind { + unsafe { + ptr::copy( + self.buf.as_ptr(), + buffer.as_mut_ptr().add(request.sector * 512), + running.1 * 512, + ); + } + } + + request.sector += running.1; + } + + if request.sector < request.total_sectors { + // Start a new request + let sectors = if request.total_sectors - request.sector >= 255 { + 255 + } else { + request.total_sectors - request.sector + }; + + if let BufferKind::Write(ref buffer) = buffer_kind { + unsafe { + ptr::copy( + buffer.as_ptr().add(request.sector * 512), + self.buf.as_mut_ptr(), + sectors * 512, + ); + } + } + + if let Some(slot) = self.port.ata_dma( + block + request.sector as u64, + sectors, + write, + &mut self.clb, + &mut self.ctbas, + &mut self.buf, + )? { + request.running_opt = Some((slot, sectors)); + } + + self.request_opt = Some(request); + + // TODO: support async internally + return Ok(None); + } else { + // Done + return Ok(Some(request.sector * 512)); + } + } + } +} + +impl Disk for DiskATA { + fn block_size(&self) -> u32 { + 512 + } + + fn size(&self) -> u64 { + self.size + } + + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result { + //TODO: FIGURE OUT WHY INTERRUPTS CAUSE HANGS + loop { + match self.request(block, BufferKind::Read(buffer))? { + Some(count) => return Ok(count), + None => std::thread::yield_now(), + } + } + } + + async fn write(&mut self, block: u64, buffer: &[u8]) -> Result { + //TODO: FIGURE OUT WHY INTERRUPTS CAUSE HANGS + loop { + match self.request(block, BufferKind::Write(buffer))? { + Some(count) => return Ok(count), + None => std::thread::yield_now(), + } + } + } +} diff --git a/drivers/storage/ahcid/src/ahci/disk_atapi.rs b/drivers/storage/ahcid/src/ahci/disk_atapi.rs new file mode 100644 index 0000000000..a0e75c0964 --- /dev/null +++ b/drivers/storage/ahcid/src/ahci/disk_atapi.rs @@ -0,0 +1,148 @@ +#![allow(dead_code)] + +use std::convert::TryInto; +use std::ptr; + +use byteorder::{BigEndian, ByteOrder}; + +use syscall::error::{Error, Result, EBADF}; + +use common::dma::Dma; + +use super::hba::{HbaCmdHeader, HbaCmdTable, HbaPort}; +use super::Disk; + +const SCSI_READ_CAPACITY: u8 = 0x25; +const SCSI_READ10: u8 = 0x28; + +pub struct DiskATAPI { + id: usize, + port: &'static mut HbaPort, + size: u64, + clb: Dma<[HbaCmdHeader; 32]>, + ctbas: [Dma; 32], + _fb: Dma<[u8; 256]>, + // Just using the same buffer size as DiskATA + // Although the sector size is different (and varies) + buf: Dma<[u8; 256 * 512]>, + blk_count: u32, + blk_size: u32, +} + +impl DiskATAPI { + pub fn new(id: usize, port: &'static mut HbaPort) -> Result { + let mut clb = unsafe { Dma::zeroed()?.assume_init() }; + + let mut ctbas: [_; 32] = (0..32) + .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) + .collect::>>()? + .try_into() + .unwrap_or_else(|_| unreachable!()); + + let mut fb = unsafe { Dma::zeroed()?.assume_init() }; + let mut buf = unsafe { Dma::zeroed()?.assume_init() }; + + port.init(&mut clb, &mut ctbas, &mut fb)?; + + let size = unsafe { port.identify_packet(&mut clb, &mut ctbas).unwrap_or(0) }; + + let mut cmd = [0; 16]; + cmd[0] = SCSI_READ_CAPACITY; + port.atapi_dma(&cmd, 8, &mut clb, &mut ctbas, &mut buf)?; + + // Instead of a count, contains number of last LBA, so add 1 + let blk_count = BigEndian::read_u32(&buf[0..4]) + 1; + let blk_size = BigEndian::read_u32(&buf[4..8]); + + Ok(DiskATAPI { + id, + port, + size, + clb, + ctbas, + _fb: fb, + buf, + blk_count, + blk_size, + }) + } +} + +impl Disk for DiskATAPI { + fn block_size(&self) -> u32 { + self.blk_size + } + + fn size(&self) -> u64 { + u64::from(self.blk_count) * u64::from(self.blk_size) + } + + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result { + // TODO: Handle audio CDs, which use special READ CD command + + let blk_len = self.blk_size; + let sectors = buffer.len() as u32 / blk_len; + + fn read10_cmd(block: u32, count: u16) -> [u8; 16] { + let mut cmd = [0; 16]; + cmd[0] = SCSI_READ10; + BigEndian::write_u32(&mut cmd[2..6], block as u32); + BigEndian::write_u16(&mut cmd[7..9], count as u16); + cmd + } + + let mut sector = 0; + let buf_len = (256 * 512) / blk_len; + let buf_size = buf_len * blk_len; + while sectors - sector >= buf_len { + let cmd = read10_cmd(block as u32 + sector, buf_len as u16); + self.port.atapi_dma( + &cmd, + buf_size, + &mut self.clb, + &mut self.ctbas, + &mut self.buf, + )?; + + unsafe { + ptr::copy( + self.buf.as_ptr(), + buffer + .as_mut_ptr() + .offset(sector as isize * blk_len as isize), + buf_size as usize, + ); + } + + sector += blk_len; + } + if sector < sectors { + let cmd = read10_cmd(block as u32 + sector, (sectors - sector) as u16); + self.port.atapi_dma( + &cmd, + buf_size, + &mut self.clb, + &mut self.ctbas, + &mut self.buf, + )?; + + unsafe { + ptr::copy( + self.buf.as_ptr(), + buffer + .as_mut_ptr() + .offset(sector as isize * blk_len as isize), + ((sectors - sector) * blk_len) as usize, + ); + } + + sector += sectors - sector; + } + + Ok((sector * blk_len) as usize) + } + + async fn write(&mut self, _block: u64, _buffer: &[u8]) -> Result { + Err(Error::new(EBADF)) // TODO: Implement writing + } +} diff --git a/drivers/storage/ahcid/src/ahci/fis.rs b/drivers/storage/ahcid/src/ahci/fis.rs new file mode 100644 index 0000000000..56dd45b814 --- /dev/null +++ b/drivers/storage/ahcid/src/ahci/fis.rs @@ -0,0 +1,157 @@ +use common::io::Mmio; + +#[repr(u8)] +pub enum FisType { + /// Register FIS - host to device + RegH2D = 0x27, + /// Register FIS - device to host + RegD2H = 0x34, + /// DMA activate FIS - device to host + DmaAct = 0x39, + /// DMA setup FIS - bidirectional + DmaSetup = 0x41, + /// Data FIS - bidirectional + Data = 0x46, + /// BIST activate FIS - bidirectional + Bist = 0x58, + /// PIO setup FIS - device to host + PioSetup = 0x5F, + /// Set device bits FIS - device to host + DevBits = 0xA1, +} + +#[repr(C, packed)] +pub struct FisRegH2D { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_REG_H2D + + pub pm: Mmio, // Port multiplier, 1: Command, 0: Control + + pub command: Mmio, // Command register + pub featurel: Mmio, // Feature register, 7:0 + + // DWORD 1 + pub lba0: Mmio, // LBA low register, 7:0 + pub lba1: Mmio, // LBA mid register, 15:8 + pub lba2: Mmio, // LBA high register, 23:16 + pub device: Mmio, // Device register + + // DWORD 2 + pub lba3: Mmio, // LBA register, 31:24 + pub lba4: Mmio, // LBA register, 39:32 + pub lba5: Mmio, // LBA register, 47:40 + pub featureh: Mmio, // Feature register, 15:8 + + // DWORD 3 + pub countl: Mmio, // Count register, 7:0 + pub counth: Mmio, // Count register, 15:8 + pub icc: Mmio, // Isochronous command completion + pub control: Mmio, // Control register + + // DWORD 4 + pub rsv1: [Mmio; 4], // Reserved +} + +#[repr(C, packed)] +pub struct FisRegD2H { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_REG_D2H + + pub pm: Mmio, // Port multiplier, Interrupt bit: 2 + + pub status: Mmio, // Status register + pub error: Mmio, // Error register + + // DWORD 1 + pub lba0: Mmio, // LBA low register, 7:0 + pub lba1: Mmio, // LBA mid register, 15:8 + pub lba2: Mmio, // LBA high register, 23:16 + pub device: Mmio, // Device register + + // DWORD 2 + pub lba3: Mmio, // LBA register, 31:24 + pub lba4: Mmio, // LBA register, 39:32 + pub lba5: Mmio, // LBA register, 47:40 + pub rsv2: Mmio, // Reserved + + // DWORD 3 + pub countl: Mmio, // Count register, 7:0 + pub counth: Mmio, // Count register, 15:8 + pub rsv3: [Mmio; 2], // Reserved + + // DWORD 4 + pub rsv4: [Mmio; 4], // Reserved +} + +#[repr(C, packed)] +pub struct FisData { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_DATA + + pub pm: Mmio, // Port multiplier + + pub rsv1: [Mmio; 2], // Reserved + + // DWORD 1 ~ N + pub data: [Mmio; 252], // Payload +} + +#[repr(C, packed)] +pub struct FisPioSetup { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_PIO_SETUP + + pub pm: Mmio, // Port multiplier, direction: 4 - device to host, interrupt: 2 + + pub status: Mmio, // Status register + pub error: Mmio, // Error register + + // DWORD 1 + pub lba0: Mmio, // LBA low register, 7:0 + pub lba1: Mmio, // LBA mid register, 15:8 + pub lba2: Mmio, // LBA high register, 23:16 + pub device: Mmio, // Device register + + // DWORD 2 + pub lba3: Mmio, // LBA register, 31:24 + pub lba4: Mmio, // LBA register, 39:32 + pub lba5: Mmio, // LBA register, 47:40 + pub rsv2: Mmio, // Reserved + + // DWORD 3 + pub countl: Mmio, // Count register, 7:0 + pub counth: Mmio, // Count register, 15:8 + pub rsv3: Mmio, // Reserved + pub e_status: Mmio, // New value of status register + + // DWORD 4 + pub tc: Mmio, // Transfer count + pub rsv4: [Mmio; 2], // Reserved +} + +#[repr(C, packed)] +pub struct FisDmaSetup { + // DWORD 0 + pub fis_type: Mmio, // FIS_TYPE_DMA_SETUP + + pub pm: Mmio, // Port multiplier, direction: 4 - device to host, interrupt: 2, auto-activate: 1 + + pub rsv1: [Mmio; 2], // Reserved + + // DWORD 1&2 + /* DMA Buffer Identifier. Used to Identify DMA buffer in host memory. SATA Spec says host specific and not in Spec. Trying AHCI spec might work. */ + pub dma_buffer_id_low: Mmio, + pub dma_buffer_id_high: Mmio, + + // DWORD 3 + pub rsv3: Mmio, // More reserved + + // DWORD 4 + pub dma_buffer_offset: Mmio, // Byte offset into buffer. First 2 bits must be 0 + + // DWORD 5 + pub transfer_count: Mmio, // Number of bytes to transfer. Bit 0 must be 0 + + // DWORD 6 + pub rsv6: Mmio, // Reserved +} diff --git a/drivers/storage/ahcid/src/ahci/hba.rs b/drivers/storage/ahcid/src/ahci/hba.rs new file mode 100644 index 0000000000..bea8792c80 --- /dev/null +++ b/drivers/storage/ahcid/src/ahci/hba.rs @@ -0,0 +1,549 @@ +use log::{debug, error, info, trace}; +use std::mem::size_of; +use std::ops::DerefMut; +use std::time::Duration; +use std::{ptr, u32}; + +use common::dma::Dma; +use common::io::{Io, Mmio}; +use common::timeout::Timeout; +use syscall::error::{Error, Result, EIO}; + +use super::fis::{FisRegH2D, FisType}; + +const ATA_CMD_READ_DMA_EXT: u8 = 0x25; +const ATA_CMD_WRITE_DMA_EXT: u8 = 0x35; +const ATA_CMD_IDENTIFY: u8 = 0xEC; +const ATA_CMD_IDENTIFY_PACKET: u8 = 0xA1; +const ATA_CMD_PACKET: u8 = 0xA0; +const ATA_DEV_BUSY: u8 = 0x80; +const ATA_DEV_DRQ: u8 = 0x08; + +const HBA_PORT_CMD_CR: u32 = 1 << 15; +const HBA_PORT_CMD_FR: u32 = 1 << 14; +const HBA_PORT_CMD_FRE: u32 = 1 << 4; +const HBA_PORT_CMD_ST: u32 = 1; +const HBA_PORT_IS_ERR: u32 = 1 << 30 | 1 << 29 | 1 << 28 | 1 << 27; +const HBA_SSTS_PRESENT: u32 = 0x3; +const HBA_SIG_ATA: u32 = 0x00000101; +const HBA_SIG_ATAPI: u32 = 0xEB140101; +const HBA_SIG_PM: u32 = 0x96690101; +const HBA_SIG_SEMB: u32 = 0xC33C0101; + +const TIMEOUT: Duration = Duration::new(5, 0); + +#[derive(Debug)] +pub enum HbaPortType { + None, + Unknown(u32), + SATA, + SATAPI, + PM, + SEMB, +} + +#[repr(C, packed)] +pub struct HbaPort { + pub clb: [Mmio; 2], // 0x00, command list base address, 1K-byte aligned + pub fb: [Mmio; 2], // 0x08, FIS base address, 256-byte aligned + pub is: Mmio, // 0x10, interrupt status + pub ie: Mmio, // 0x14, interrupt enable + pub cmd: Mmio, // 0x18, command and status + pub _rsv0: Mmio, // 0x1C, Reserved + pub tfd: Mmio, // 0x20, task file data + pub sig: Mmio, // 0x24, signature + pub ssts: Mmio, // 0x28, SATA status (SCR0:SStatus) + pub sctl: Mmio, // 0x2C, SATA control (SCR2:SControl) + pub serr: Mmio, // 0x30, SATA error (SCR1:SError) + pub sact: Mmio, // 0x34, SATA active (SCR3:SActive) + pub ci: Mmio, // 0x38, command issue + pub sntf: Mmio, // 0x3C, SATA notification (SCR4:SNotification) + pub fbs: Mmio, // 0x40, FIS-based switch control + pub _rsv1: [Mmio; 11], // 0x44 ~ 0x6F, Reserved + pub vendor: [Mmio; 4], // 0x70 ~ 0x7F, vendor specific +} + +impl HbaPort { + pub fn probe(&self) -> HbaPortType { + if self.ssts.readf(HBA_SSTS_PRESENT) { + let sig = self.sig.read(); + match sig { + HBA_SIG_ATA => HbaPortType::SATA, + HBA_SIG_ATAPI => HbaPortType::SATAPI, + HBA_SIG_PM => HbaPortType::PM, + HBA_SIG_SEMB => HbaPortType::SEMB, + _ => HbaPortType::Unknown(sig), + } + } else { + HbaPortType::None + } + } + + pub fn start(&mut self) -> Result<()> { + let timeout = Timeout::new(TIMEOUT); + while self.cmd.readf(HBA_PORT_CMD_CR) { + timeout.run().map_err(|()| { + log::error!("HBA start timed out"); + Error::new(EIO) + })?; + } + + self.cmd.writef(HBA_PORT_CMD_FRE | HBA_PORT_CMD_ST, true); + Ok(()) + } + + pub fn stop(&mut self) -> Result<()> { + self.cmd.writef(HBA_PORT_CMD_ST, false); + + let timeout = Timeout::new(TIMEOUT); + while self.cmd.readf(HBA_PORT_CMD_FR | HBA_PORT_CMD_CR) { + timeout.run().map_err(|()| { + log::error!("HBA stop timed out"); + Error::new(EIO) + })?; + } + + self.cmd.writef(HBA_PORT_CMD_FRE, false); + Ok(()) + } + + pub fn slot(&self) -> Option { + let slots = self.sact.read() | self.ci.read(); + for i in 0..32 { + if slots & 1 << i == 0 { + return Some(i); + } + } + None + } + + pub fn init( + &mut self, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + fb: &mut Dma<[u8; 256]>, + ) -> Result<()> { + self.stop()?; + + for i in 0..32 { + let cmdheader = &mut clb[i]; + cmdheader.ctba_low.write(ctbas[i].physical() as u32); + cmdheader + .ctba_high + .write((ctbas[i].physical() as u64 >> 32) as u32); + cmdheader.prdtl.write(0); + } + + self.clb[0].write(clb.physical() as u32); + self.clb[1].write(((clb.physical() as u64) >> 32) as u32); + self.fb[0].write(fb.physical() as u32); + self.fb[1].write(((fb.physical() as u64) >> 32) as u32); + let is = self.is.read(); + self.is.write(is); + self.ie.write(0b10111); + let serr = self.serr.read(); + self.serr.write(serr); + + // Disable power management + let sctl = self.sctl.read(); + self.sctl.write(sctl | 7 << 8); + + // Power on and spin up device + self.cmd.writef(1 << 2 | 1 << 1, true); + + debug!("AHCI init {:X}", self.cmd.read()); + Ok(()) + } + + pub unsafe fn identify( + &mut self, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + ) -> Result { + self.identify_inner(ATA_CMD_IDENTIFY, clb, ctbas) + } + + pub unsafe fn identify_packet( + &mut self, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + ) -> Result { + self.identify_inner(ATA_CMD_IDENTIFY_PACKET, clb, ctbas) + } + + // Shared between identify() and identify_packet() + unsafe fn identify_inner( + &mut self, + cmd: u8, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + ) -> Result { + let dest: Dma<[u16; 256]> = Dma::new([0; 256]).unwrap(); + + let slot = self + .ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| { + cmdheader.prdtl.write(1); + + let prdt_entry = &mut prdt_entries[0]; + prdt_entry.dba_low.write(dest.physical() as u32); + prdt_entry + .dba_high + .write((dest.physical() as u64 >> 32) as u32); + prdt_entry.dbc.write(512 | 1); + + cmdfis.pm.write(1 << 7); + cmdfis.command.write(cmd); + cmdfis.device.write(0); + cmdfis.countl.write(1); + cmdfis.counth.write(0); + })? + .ok_or(Error::new(EIO))?; + + self.ata_stop(slot)?; + + let mut serial = String::new(); + for word in 10..20 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + serial.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + serial.push(b); + } + } + + let mut firmware = String::new(); + for word in 23..27 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + firmware.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + firmware.push(b); + } + } + + let mut model = String::new(); + for word in 27..47 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + model.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + model.push(b); + } + } + + let mut sectors = (dest[100] as u64) + | ((dest[101] as u64) << 16) + | ((dest[102] as u64) << 32) + | ((dest[103] as u64) << 48); + + let lba_bits = if sectors == 0 { + sectors = (dest[60] as u64) | ((dest[61] as u64) << 16); + 28 + } else { + 48 + }; + + info!( + "Serial: {} Firmware: {} Model: {} {}-bit LBA Size: {} MB", + serial.trim(), + firmware.trim(), + model.trim(), + lba_bits, + sectors / 2048 + ); + + Ok(sectors * 512) + } + + pub fn ata_dma( + &mut self, + block: u64, + sectors: usize, + write: bool, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + buf: &mut Dma<[u8; 256 * 512]>, + ) -> Result> { + trace!( + "AHCI {:X} DMA BLOCK: {:X} SECTORS: {} WRITE: {}", + (self as *mut HbaPort) as usize, + block, + sectors, + write + ); + + assert!(sectors > 0 && sectors < 256); + + self.ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| { + if write { + let cfl = cmdheader.cfl.read(); + cmdheader.cfl.write(cfl | 1 << 7 | 1 << 6) + } + + cmdheader.prdtl.write(1); + + let prdt_entry = &mut prdt_entries[0]; + prdt_entry.dba_low.write(buf.physical() as u32); + prdt_entry + .dba_high + .write((buf.physical() as u64 >> 32) as u32); + prdt_entry.dbc.write(((sectors * 512) as u32) | 1); + + cmdfis.pm.write(1 << 7); + if write { + cmdfis.command.write(ATA_CMD_WRITE_DMA_EXT); + } else { + cmdfis.command.write(ATA_CMD_READ_DMA_EXT); + } + + cmdfis.lba0.write(block as u8); + cmdfis.lba1.write((block >> 8) as u8); + cmdfis.lba2.write((block >> 16) as u8); + + cmdfis.device.write(1 << 6); + + cmdfis.lba3.write((block >> 24) as u8); + cmdfis.lba4.write((block >> 32) as u8); + cmdfis.lba5.write((block >> 40) as u8); + + cmdfis.countl.write(sectors as u8); + cmdfis.counth.write((sectors >> 8) as u8); + }) + } + + /// Send ATAPI packet + pub fn atapi_dma( + &mut self, + cmd: &[u8; 16], + size: u32, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + buf: &mut Dma<[u8; 256 * 512]>, + ) -> Result<()> { + let slot = self + .ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, acmd| { + let cfl = cmdheader.cfl.read(); + cmdheader.cfl.write(cfl | 1 << 5); + + cmdheader.prdtl.write(1); + + let prdt_entry = &mut prdt_entries[0]; + prdt_entry.dba_low.write(buf.physical() as u32); + prdt_entry + .dba_high + .write((buf.physical() as u64 >> 32) as u32); + prdt_entry.dbc.write(size - 1); + + cmdfis.pm.write(1 << 7); + cmdfis.command.write(ATA_CMD_PACKET); + cmdfis.device.write(0); + cmdfis.lba1.write(0); + cmdfis.lba2.write(0); + cmdfis.featurel.write(1); + cmdfis.featureh.write(0); + + unsafe { ptr::write_volatile(acmd.as_mut_ptr() as *mut [u8; 16], *cmd) }; + })? + .ok_or(Error::new(EIO))?; + self.ata_stop(slot) + } + + pub fn ata_start( + &mut self, + clb: &mut Dma<[HbaCmdHeader; 32]>, + ctbas: &mut [Dma; 32], + callback: F, + ) -> Result> + where + F: FnOnce( + &mut HbaCmdHeader, + &mut FisRegH2D, + &mut [HbaPrdtEntry; PRDT_ENTRIES], + &mut [Mmio; 16], + ), + { + //TODO: Should probably remove + self.is.write(u32::MAX); + + let Some(slot) = self.slot() else { + return Ok(None); + }; + + { + let cmdheader = &mut clb[slot as usize]; + cmdheader + .cfl + .write((size_of::() / size_of::()) as u8); + + let cmdtbl = &mut ctbas[slot as usize]; + unsafe { + ptr::write_bytes( + cmdtbl.deref_mut() as *mut HbaCmdTable as *mut u8, + 0, + size_of::(), + ); + } + + let cmdfis = unsafe { &mut *(cmdtbl.cfis.as_mut_ptr() as *mut FisRegH2D) }; + cmdfis.fis_type.write(FisType::RegH2D as u8); + + let prdt_entry = unsafe { &mut *(&mut cmdtbl.prdt_entry as *mut _) }; + let acmd = unsafe { &mut *(&mut cmdtbl.acmd as *mut _) }; + + callback(cmdheader, cmdfis, prdt_entry, acmd) + } + + let timeout = Timeout::new(TIMEOUT); + while self.tfd.readf((ATA_DEV_BUSY | ATA_DEV_DRQ) as u32) { + timeout.run().map_err(|()| { + log::error!("HBA ata_start timeout"); + Error::new(EIO) + })?; + } + + self.ci.writef(1 << slot, true); + + //TODO: Should probably remove + self.start()?; + + Ok(Some(slot)) + } + + pub fn ata_running(&self, slot: u32) -> bool { + (self.ci.readf(1 << slot) || self.tfd.readf(0x80)) && self.is.read() & HBA_PORT_IS_ERR == 0 + } + + pub fn ata_stop(&mut self, slot: u32) -> Result<()> { + let timeout = Timeout::new(TIMEOUT); + while self.ata_running(slot) { + timeout.run().map_err(|()| { + log::error!("HBA ata_stop timeout"); + Error::new(EIO) + })?; + } + + self.stop()?; + + if self.is.read() & HBA_PORT_IS_ERR != 0 { + let (is, ie, cmd, tfd, ssts, sctl, serr, sact, ci, sntf, fbs) = ( + self.is.read(), + self.ie.read(), + self.cmd.read(), + self.tfd.read(), + self.ssts.read(), + self.sctl.read(), + self.serr.read(), + self.sact.read(), + self.ci.read(), + self.sntf.read(), + self.fbs.read(), + ); + + error!("IS {:X} IE {:X} CMD {:X} TFD {:X}", is, ie, cmd, tfd); + error!( + "SSTS {:X} SCTL {:X} SERR {:X} SACT {:X}", + ssts, sctl, serr, sact + ); + error!("CI {:X} SNTF {:X} FBS {:X}", ci, sntf, fbs); + + self.is.write(u32::MAX); + Err(Error::new(EIO)) + } else { + Ok(()) + } + } +} + +#[repr(C, packed)] +pub struct HbaMem { + pub cap: Mmio, // 0x00, Host capability + pub ghc: Mmio, // 0x04, Global host control + pub is: Mmio, // 0x08, Interrupt status + pub pi: Mmio, // 0x0C, Port implemented + pub vs: Mmio, // 0x10, Version + pub ccc_ctl: Mmio, // 0x14, Command completion coalescing control + pub ccc_pts: Mmio, // 0x18, Command completion coalescing ports + pub em_loc: Mmio, // 0x1C, Enclosure management location + pub em_ctl: Mmio, // 0x20, Enclosure management control + pub cap2: Mmio, // 0x24, Host capabilities extended + pub bohc: Mmio, // 0x28, BIOS/OS handoff control and status + pub _rsv: [Mmio; 116], // 0x2C - 0x9F, Reserved + pub vendor: [Mmio; 96], // 0xA0 - 0xFF, Vendor specific registers + pub ports: [HbaPort; 32], // 0x100 - 0x10FF, Port control registers +} + +impl HbaMem { + pub fn init(&mut self) { + /* + self.ghc.writef(1, true); + while self.ghc.readf(1) { + pause(); + } + */ + self.ghc.write(1 << 31 | 1 << 1); + + debug!( + "AHCI CAP {:X} GHC {:X} IS {:X} PI {:X} VS {:X} CAP2 {:X} BOHC {:X}", + self.cap.read(), + self.ghc.read(), + self.is.read(), + self.pi.read(), + self.vs.read(), + self.cap2.read(), + self.bohc.read() + ); + } +} + +#[repr(C, packed)] +pub struct HbaPrdtEntry { + dba_low: Mmio, // Data base address (low + dba_high: Mmio, // Data base address (high) + _rsv0: Mmio, // Reserved + dbc: Mmio, // Byte count, 4M max, interrupt = 1 +} + +#[repr(C, packed)] +pub struct HbaCmdTable { + // 0x00 + cfis: [Mmio; 64], // Command FIS + + // 0x40 + acmd: [Mmio; 16], // ATAPI command, 12 or 16 bytes + + // 0x50 + _rsv: [Mmio; 48], // Reserved + + // 0x80 + prdt_entry: [HbaPrdtEntry; PRDT_ENTRIES], // Physical region descriptor table entries, 0 ~ 65535 +} +const CMD_TBL_SIZE: usize = 256 * 4096; +const PRDT_ENTRIES: usize = (CMD_TBL_SIZE - 128) / size_of::(); + +#[repr(C, packed)] +pub struct HbaCmdHeader { + // DW0 + cfl: Mmio, /* Command FIS length in DWORDS, 2 ~ 16, atapi: 4, write - host to device: 2, prefetchable: 1 */ + _pm: Mmio, // Reset - 0x80, bist: 0x40, clear busy on ok: 0x20, port multiplier + + prdtl: Mmio, // Physical region descriptor table length in entries + + // DW1 + _prdbc: Mmio, // Physical region descriptor byte count transferred + + // DW2, 3 + ctba_low: Mmio, // Command table descriptor base address (low) + ctba_high: Mmio, // Command table descriptor base address (high) + + // DW4 - 7 + _rsv1: [Mmio; 4], // Reserved +} diff --git a/drivers/storage/ahcid/src/ahci/mod.rs b/drivers/storage/ahcid/src/ahci/mod.rs new file mode 100644 index 0000000000..4d8cc8c040 --- /dev/null +++ b/drivers/storage/ahcid/src/ahci/mod.rs @@ -0,0 +1,79 @@ +use common::io::Io; +use driver_block::Disk; +use log::{error, info}; + +use self::disk_ata::DiskATA; +use self::disk_atapi::DiskATAPI; +use self::hba::{HbaMem, HbaPortType}; + +pub mod disk_ata; +pub mod disk_atapi; +pub mod fis; +pub mod hba; + +pub enum AnyDisk { + Ata(DiskATA), + Atapi(DiskATAPI), +} +impl Disk for AnyDisk { + fn block_size(&self) -> u32 { + match self { + Self::Ata(a) => a.block_size(), + Self::Atapi(a) => a.block_size(), + } + } + fn size(&self) -> u64 { + match self { + Self::Ata(a) => a.size(), + Self::Atapi(a) => a.size(), + } + } + async fn read(&mut self, base: u64, buffer: &mut [u8]) -> syscall::Result { + match self { + Self::Ata(a) => a.read(base, buffer).await, + Self::Atapi(a) => a.read(base, buffer).await, + } + } + async fn write(&mut self, base: u64, buffer: &[u8]) -> syscall::Result { + match self { + Self::Ata(a) => a.write(base, buffer).await, + Self::Atapi(a) => a.write(base, buffer).await, + } + } +} + +pub fn disks(base: usize, name: &str) -> (&'static mut HbaMem, Vec) { + let hba_mem = unsafe { &mut *(base as *mut HbaMem) }; + hba_mem.init(); + let pi = hba_mem.pi.read(); + let disks: Vec = (0..hba_mem.ports.len()) + .filter(|&i| pi & 1 << i as i32 == 1 << i as i32) + .filter_map(|i| { + let port = unsafe { &mut *hba_mem.ports.as_mut_ptr().add(i) }; + let port_type = port.probe(); + info!("{}-{}: {:?}", name, i, port_type); + + let disk: Option = match port_type { + HbaPortType::SATA => match DiskATA::new(i, port) { + Ok(disk) => Some(AnyDisk::Ata(disk)), + Err(err) => { + error!("{}: {}", i, err); + None + } + }, + HbaPortType::SATAPI => match DiskATAPI::new(i, port) { + Ok(disk) => Some(AnyDisk::Atapi(disk)), + Err(err) => { + error!("{}: {}", i, err); + None + } + }, + _ => None, + }; + + disk + }) + .collect(); + + (hba_mem, disks) +} diff --git a/drivers/storage/ahcid/src/main.rs b/drivers/storage/ahcid/src/main.rs new file mode 100644 index 0000000000..159a726cb8 --- /dev/null +++ b/drivers/storage/ahcid/src/main.rs @@ -0,0 +1,110 @@ +#![cfg_attr(target_arch = "aarch64", feature(stdsimd))] // Required for yield instruction + +use std::io::{Read, Write}; +use std::os::fd::AsRawFd; +use std::usize; + +use common::io::Io; +use driver_block::{DiskScheme, ExecutorTrait, FuturesExecutor}; +use event::{EventFlags, RawEventQueue}; +use pcid_interface::PciFunctionHandle; + +use log::{error, info}; + +pub mod ahci; + +fn main() { + redox_daemon::Daemon::new(daemon).expect("ahcid: failed to daemonize"); +} + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_ahci"); + + let irq = pci_config + .func + .legacy_interrupt_line + .expect("ahcid: no legacy interrupts supported"); + + common::setup_logging( + "disk", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + info!("AHCI {}", pci_config.func.display()); + + let address = unsafe { pcid_handle.map_bar(5) }.ptr.as_ptr() as usize; + { + let (hba_mem, disks) = ahci::disks(address as usize, &name); + + let scheme_name = format!("disk.{}", name); + let mut scheme = DiskScheme::new( + Some(daemon), + scheme_name, + disks + .into_iter() + .enumerate() + .map(|(i, disk)| (i as u32, disk)) + .collect(), + &FuturesExecutor, + ); + + let mut irq_file = irq.irq_handle("ahcid"); + let irq_fd = irq_file.as_raw_fd() as usize; + + let event_queue = RawEventQueue::new().expect("ahcid: failed to create event queue"); + + libredox::call::setrens(0, 0).expect("ahcid: failed to enter null namespace"); + + event_queue + .subscribe(scheme.event_handle().raw(), 1, EventFlags::READ) + .expect("ahcid: failed to event scheme socket"); + event_queue + .subscribe(irq_fd, 1, EventFlags::READ) + .expect("ahcid: failed to event irq scheme"); + + for event in event_queue { + let event = event.unwrap(); + if event.fd == scheme.event_handle().raw() { + FuturesExecutor.block_on(scheme.tick()).unwrap(); + } else if event.fd == irq_fd { + let mut irq = [0; 8]; + if irq_file + .read(&mut irq) + .expect("ahcid: failed to read irq file") + >= irq.len() + { + let is = hba_mem.is.read(); + if is > 0 { + let pi = hba_mem.pi.read(); + let pi_is = pi & is; + for i in 0..hba_mem.ports.len() { + if pi_is & 1 << i > 0 { + let port = &mut hba_mem.ports[i]; + let is = port.is.read(); + port.is.write(is); + } + } + hba_mem.is.write(is); + + irq_file + .write(&irq) + .expect("ahcid: failed to write irq file"); + + FuturesExecutor.block_on(scheme.tick()).unwrap(); + } + } + } else { + error!("Unknown event {}", event.fd); + } + } + } + + std::process::exit(0); +} diff --git a/drivers/storage/bcm2835-sdhcid/Cargo.toml b/drivers/storage/bcm2835-sdhcid/Cargo.toml new file mode 100644 index 0000000000..ceabb4a0b6 --- /dev/null +++ b/drivers/storage/bcm2835-sdhcid/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bcm2835-sdhcid" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +fdt = { git = "https://github.com/repnop/fdt.git" } +common = { path = "../../common" } +driver-block = { path = "../driver-block" } + +redox-daemon = "0.1" +libredox = "0.1.3" +redox_syscall = { version = "0.5", features = ["std"] } +redox_event = "0.4" diff --git a/drivers/storage/bcm2835-sdhcid/src/main.rs b/drivers/storage/bcm2835-sdhcid/src/main.rs new file mode 100644 index 0000000000..c6b67acf97 --- /dev/null +++ b/drivers/storage/bcm2835-sdhcid/src/main.rs @@ -0,0 +1,128 @@ +use std::process; + +use driver_block::{DiskScheme, ExecutorTrait, TrivialExecutor}; +use event::{EventFlags, RawEventQueue}; +use fdt::Fdt; + +mod sd; + +#[cfg(target_os = "redox")] +fn get_dtb() -> Vec { + std::fs::read("kernel.dtb:").unwrap() +} + +#[cfg(target_os = "linux")] +fn get_dtb() -> Vec { + use std::env; + if let Some(arg1) = env::args().nth(1) { + std::fs::read(arg1).unwrap() + } else { + Vec::new() + } +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("mmc:failed to daemonize"); +} + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let dtb_data = get_dtb(); + println!("read from OS, len = {}", dtb_data.len()); + if dtb_data.len() == 0 { + process::exit(0); + } + + let fdt = Fdt::new(&dtb_data).unwrap(); + println!("DTB model = {}", fdt.root().model()); + let with = ["brcm,bcm2835-sdhci"]; + let compat_node = fdt.find_compatible(&with).unwrap(); + let reg = compat_node.reg().unwrap().next().unwrap(); + let reg_size = reg.size.unwrap(); + let mut reg_addr = reg.starting_address as usize; + println!( + "DeviceMemory start = 0x{:08x}, size = 0x{:08x}", + reg_addr, reg_size + ); + if let Some(mut ranges) = fdt.find_node("/soc").and_then(|f| f.ranges()) { + let range = ranges + .find(|f| f.child_bus_address <= reg_addr && reg_addr - f.child_bus_address < f.size) + .expect("Couldn't find device range in /soc/@ranges"); + reg_addr = range.parent_bus_address + (reg_addr - range.child_bus_address); + println!( + "DeviceMemory remapped onto CPU address space: start = 0x{:08x}, size = 0x{:08x}", + reg_addr, reg_size + ); + } + + let addr = unsafe { + common::physmap( + reg_addr, + reg_size, + common::Prot::RW, + common::MemoryType::DeviceMemory, + ) + .expect("bcm2835-sdhcid: failed to map address") as usize + }; + println!( + "ioremap 0x{:08x} to 0x{:08x} 2222", + reg.starting_address as usize, addr + ); + let mut sdhci = sd::SdHostCtrl::new(addr); + unsafe { + sdhci.init(); + /* + let mut buf1 = [0u32; 512]; + sdhci.sd_readblock(1, &mut buf1, 1); + println!("readblock {:?}", buf1); + buf1[0] = 0xdead_0000; + buf1[1] = 0xdead_0000; + buf1[2] = 0x0000_dead; + buf1[3] = 0x0000_dead; + sdhci.sd_writeblock(1, &buf1, 1); + sdhci.sd_readblock(1, &mut buf1, 1); + println!("readblock {:?}", buf1); + */ + /* + let mut buf1 = [0u8; 512]; + sdhci.read(1, &mut buf1); + println!("readblock {:?}", buf1); + buf1[0] = 0xde; + buf1[1] = 0xad; + buf1[2] = 0xde; + buf1[3] = 0xad; + sdhci.write(1, &buf1); + sdhci.read(1, &mut buf1); + println!("readblock {:?}", buf1); + */ + } + + let mut disks = Vec::new(); + disks.push(sdhci); + let mut scheme = DiskScheme::new( + Some(daemon), + "disk.mmc".to_string(), + disks + .into_iter() + .enumerate() + .map(|(i, disk)| (i as u32, disk)) + .collect(), + &TrivialExecutor, // TODO: real executor + ); + + let event_queue = RawEventQueue::new().expect("mmcd: failed to open event file"); + event_queue + .subscribe(scheme.event_handle().raw(), 0, EventFlags::READ) + .expect("mmcd: failed to event disk scheme"); + + libredox::call::setrens(0, 0).expect("mmcd: failed to enter null namespace"); + + for event in event_queue { + let event = event.unwrap(); + if event.fd == scheme.event_handle().raw() { + TrivialExecutor.block_on(scheme.tick()).unwrap(); + } else { + println!("Unknown event {}", event.fd); + } + } + process::exit(0); +} diff --git a/drivers/storage/bcm2835-sdhcid/src/sd/mod.rs b/drivers/storage/bcm2835-sdhcid/src/sd/mod.rs new file mode 100644 index 0000000000..dcbcb3e09f --- /dev/null +++ b/drivers/storage/bcm2835-sdhcid/src/sd/mod.rs @@ -0,0 +1,779 @@ +use common::io::{Io, Mmio}; +use driver_block::Disk; +use std::{sync::RwLock, thread, time::Duration}; +use syscall::{Error, Result, EINVAL}; + +#[cfg(target_arch = "aarch64")] +#[inline(always)] +pub(crate) unsafe fn wait_cycles(mut n: usize) { + use core::arch::asm; + + while n > 0 { + asm!("nop"); + n -= 1; + } +} + +#[cfg(target_arch = "aarch64")] +#[inline(always)] +pub(crate) unsafe fn wait_msec(mut n: usize) { + use core::arch::asm; + + let mut f: usize; + let mut t: usize; + let mut r: usize; + + asm!("mrs {0}, cntfrq_el0", out(reg) f); + asm!("mrs {0}, cntpct_el0", out(reg) t); + + t += ((f / 1000) * n) / 1000; + + loop { + asm!("mrs {0}, cntpct_el0", out(reg) r); + if r >= t { + break; + } + } +} + +#[cfg(target_arch = "x86_64")] +#[inline(always)] +pub(crate) unsafe fn wait_msec(n: usize) { + thread::sleep(Duration::from_millis(n as u64)); +} + +//cmd Flags +const CMD_NEED_APP: u32 = 0x8000_0000; +const CMD_RSPNS_48: u32 = 0x0002_0000; +const CMD_ERRORS_MASK: u32 = 0xfff9_c004; +const CMD_RCA_MASK: u32 = 0xffff_0000; + +//CMD +const CMD_GO_IDLE: u32 = 0x0000_0000; +const CMD_ALL_SEND_CID: u32 = 0x0201_0000; +const CMD_SEND_CSD: u32 = 0x0901_0000; +const CMD_SEND_REL_ADDR: u32 = 0x0302_0000; +const CMD_CARD_SELECT: u32 = 0x0703_0000; +const CMD_SEND_IF_COND: u32 = 0x0802_0000; +const CMD_STOP_TRANS: u32 = 0x0c03_0000; +const CMD_READ_SINGLE: u32 = 0x1122_0010; +const CMD_READ_MULTI: u32 = 0x1222_0032; +const CMD_SET_BLOCKCNT: u32 = 0x1702_0000; +const CMD_WRITE_SINGLE: u32 = 0x1822_0000; +const CMD_WRITE_MULTI: u32 = 0x1922_0022; + +const CMD_APP_CMD: u32 = 0x3700_0000; +const CMD_SET_BUS_WIDTH: u32 = 0x0602_0000 | CMD_NEED_APP; +const CMD_SEND_OP_COND: u32 = 0x2902_0000 | CMD_NEED_APP; +const CMD_SEND_SCR: u32 = 0x3322_0010 | CMD_NEED_APP; + +//STATUS register settings +const SR_READ_AVAILABLE: u32 = 0x0000_0800; +const SR_WRITE_AVAILABLE: u32 = 0x0000_0400; +const SR_DAT_INHIBIT: u32 = 0x0000_0002; +const SR_CMD_INHIBIT: u32 = 0x0000_0001; +const SR_APP_CMD: u32 = 0x0000_0020; + +//CONTROL register settings + +const C0_SPI_MODE_EN: u32 = 0x0010_0000; +const C0_HCTL_HS_EN: u32 = 0x0000_0004; +const C0_HCTL_DWITDH: u32 = 0x0000_0002; + +const C1_SRST_DATA: u32 = 0x0400_0000; +const C1_SRST_CMD: u32 = 0x0200_0000; +const C1_SRST_HC: u32 = 0x0100_0000; +const C1_TOUNIT_DIS: u32 = 0x000f_0000; +const C1_TOUNIT_MAX: u32 = 0x000e_0000; +const C1_CLK_GENSEL: u32 = 0x0000_0020; +const C1_CLK_EN: u32 = 0x0000_0004; +const C1_CLK_STABLE: u32 = 0x0000_0002; +const C1_CLK_INTLEN: u32 = 0x0000_0001; + +//INTERRUPT register settings +const INT_DATA_TIMEOUT: u32 = 0x0010_0000; +const INT_CMD_TIMEOUT: u32 = 0x0001_0000; +const INT_READ_RDY: u32 = 0x0000_0020; +const INT_WRITE_RDY: u32 = 0x0000_0010; +const INT_DATA_DONE: u32 = 0x0000_0002; +const INT_CMD_DONE: u32 = 0x0000_0001; +const INT_ERROR_MASK: u32 = 0x017e_8000; + +const HOST_SPEC_VERSION_OFFSET: u32 = 16; +const HOST_SPEC_VERSION_MASK: u32 = 0x00ff_0000; +const HOST_SPEC_V3: u32 = 2; +const HOST_SPEC_V2: u32 = 1; +const HOST_SPEC_V1: u32 = 0; + +const ACMD41_VOLTAGE: u32 = 0x00ff_8000; +const ACMD41_CMD_COMPLETE: u32 = 0x8000_0000; +const ACMD41_CMD_CCS: u32 = 0x4000_0000; +const ACMD41_ARG_HC: u32 = 0x51ff_8000; + +const SCR_SD_BUS_WIDTH_4: u32 = 0x0000_0400; +const SCR_SUPP_SET_BLKCNT: u32 = 0x0200_0000; +//added by bztsrc driver +const SCR_SUPP_CCS: u32 = 0x0000_0001; + +#[repr(C, packed)] +pub struct SdHostCtrlRegs { + //LSB + + //ACMD23 Argument + _arg2: Mmio, + + //Block Size and Count + blksizecnt: Mmio, + + //Argument + arg1: Mmio, + + //Command and Transfer Mode + cmdtm: Mmio, + + //Response bit 0-127 + resp0: Mmio, + resp1: Mmio, + resp2: Mmio, + resp3: Mmio, + + //Data + data: Mmio, + + //Status + status: Mmio, + + //Host Configuration bits + control0: Mmio, + + //Host Configuration bits + control1: Mmio, + + //Interrupt Flags + interrupt: Mmio, + + //Interrupt Flag Enable + irpt_mask: Mmio, + + //Interrupt Generation Enable + irpt_en: Mmio, + + //Host Configuration bits + _control2: Mmio, + + _rsvd: [Mmio; 47], + + //Slot Interrupt Status and Version + slotisr_ver: Mmio, +} + +//TODO: refactor, sd/sdhci/bcmh2835-sdhci three different modules. +pub struct SdHostCtrl { + regs: RwLock<&'static mut SdHostCtrlRegs>, + host_spec_ver: u32, + cid: [u32; 4], + csd: [u32; 4], + rca: u32, //relative card address + scr: [u32; 2], + ocr: u32, + size: u64, +} + +impl SdHostCtrl { + pub fn new(address: usize) -> Self { + SdHostCtrl { + regs: RwLock::new(unsafe { &mut *(address as *mut SdHostCtrlRegs) }), + host_spec_ver: 0, + cid: [0; 4], + csd: [0; 4], + rca: 0, + scr: [0; 2], + ocr: 0, + size: 0, + } + } + + pub unsafe fn init(&mut self) { + let regs = self.regs.get_mut().unwrap(); + + let mut reg_val = regs.slotisr_ver.read(); + self.host_spec_ver = (reg_val & HOST_SPEC_VERSION_MASK) >> HOST_SPEC_VERSION_OFFSET; + + regs.control0.write(0x0); + reg_val = regs.control1.read(); + regs.control1.write(reg_val | C1_SRST_HC); + let mut cnt = 1000; + while (cnt >= 0) && ((regs.control1.read() & C1_SRST_HC) == C1_SRST_HC) { + cnt -= 1; + wait_msec(10); + } + + if cnt < 0 { + println!("ERROR: failed to reset EMMC"); + return; + } + println!("EMMC: reset OK"); + reg_val = regs.control1.read(); + regs.control1.write(reg_val | C1_CLK_INTLEN | C1_TOUNIT_MAX); + + wait_msec(10); + + { + if let Err(_) = self.set_clock(40_0000) { + println!("ERROR: failed to set clock {}", 40_0000); + return; + } + } + + let regs = self.regs.get_mut().unwrap(); + regs.irpt_en.write(0xffff_ffff); + regs.irpt_mask.write(0xffff_ffff); + + if let Err(_) = self.sd_cmd(CMD_GO_IDLE, 0) { + println!("failed to go idle"); + return; + } + + if let Err(_) = self.sd_cmd(CMD_SEND_IF_COND, 0x0000_01aa) { + println!("failed to send if cond"); + return; + } + + cnt = 6; + reg_val = 0; + + while ((reg_val & ACMD41_CMD_COMPLETE) == 0) && cnt > 0 { + wait_msec(10); + cnt -= 1; + + if let Ok(val) = self.sd_cmd(CMD_SEND_OP_COND, ACMD41_ARG_HC) { + reg_val = val; + self.ocr = reg_val; + print!("EMMC: CMD_SEND_OP_COND returned 0x{:08x} = ", reg_val); + + if (reg_val & ACMD41_CMD_COMPLETE) != 0 { + print!("COMPLETE "); + } + if (reg_val & ACMD41_VOLTAGE) != 0 { + print!("VOLTAGE "); + } + if (reg_val & ACMD41_CMD_CCS) != 0 { + print!("CCS "); + } + print!("\n"); + } else { + println!("ERROR: EMMC ACMD41 returned error"); + return; + } + } + + if (reg_val & ACMD41_CMD_COMPLETE) == 0 || cnt <= 0 { + println!("ACMD41 TIMEOUT"); + return; + } + + if (reg_val & ACMD41_VOLTAGE) == 0 { + println!("ACMD41 VOLTAGE NOT FOUND!"); + return; + } + + let ccs = if (reg_val & ACMD41_CMD_CCS) != 0 { + SCR_SUPP_CCS + } else { + 0 + }; + + if let Err(_) = self.sd_cmd(CMD_ALL_SEND_CID, 0) { + println!("CMD_ALL_SEND_CID ERROR, IGNORE!"); + } + + let sd_rca = self.sd_cmd(CMD_SEND_REL_ADDR, 0x0).unwrap(); + println!("CMD_SEND_REL_ADDR = 0x{:08x}", sd_rca); + self.rca = sd_rca; + + if let Err(_) = self.sd_cmd(CMD_SEND_CSD, sd_rca) { + println!("failed to get csd"); + return; + } + + let (csize, cmult) = if (self.ocr & ACMD41_CMD_CCS) != 0 { + let csize = (self.csd[1] & 0x3f) << 16 | (self.csd[2] & 0xffff_0000) >> 16; + let cmult = 8; + (csize as u64, cmult as u64) + } else { + let csize = (self.csd[1] & 0x3ff) << 2 | (self.csd[2] & 0xc000_0000) >> 30; + let cmult = (self.csd[2] & 0x0003_8000) >> 15; + (csize as u64, cmult as u64) + }; + self.size = ((csize + 1) << (cmult + 2)) * 512; + println!("mmc size = 0x{:08x}", self.size); + + if let Err(_) = self.set_clock(2500_0000) { + println!("failed to set clock 2500_0000 Hz"); + return; + } + + if let Err(_) = self.sd_cmd(CMD_CARD_SELECT, sd_rca) { + println!("failed to CMD_CARD_SELECT 0x{:08x}", sd_rca); + return; + } + + if let Err(_) = self.sd_status(SR_DAT_INHIBIT) { + println!("SR_DAT_INHIBIT return"); + return; + } + + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write(1 << 16 | 8); + + if let Err(_) = self.sd_cmd(CMD_SEND_SCR, 0) { + println!("failed to CMD_SEND_SCR"); + return; + } + + if let Err(_) = self.sd_int(INT_READ_RDY) { + println!("failed to INT_READ_RDY"); + return; + } + + cnt = 10000; + let mut i = 0; + let regs = self.regs.get_mut().unwrap(); + while i < 2 && cnt > 0 { + reg_val = regs.status.read(); + cnt -= 1; + if (reg_val & SR_READ_AVAILABLE) != 0 { + self.scr[i] = regs.data.read(); + i += 1; + } else { + wait_msec(10); + cnt -= 1; + } + } + if i != 2 { + println!("SD TIMEOUT FOR SCR[; 2]"); + return; + } + + if (self.scr[0] & SCR_SD_BUS_WIDTH_4) != 0 { + if let Err(_) = self.sd_cmd(CMD_SET_BUS_WIDTH, sd_rca | 2) { + println!("failed to set bus width, {}", sd_rca | 2); + return; + } + let regs = self.regs.get_mut().unwrap(); + regs.control0.write(C0_HCTL_DWITDH); + } + + print!("EMMC: supports "); + + if (self.scr[0] & SCR_SUPP_SET_BLKCNT) != 0 { + print!("SET_BLKCNT "); + } + + if ccs != 0 { + print!("CCS "); + } + + print!("\n"); + + self.scr[0] &= !SCR_SUPP_CCS; + self.scr[0] |= ccs; + } + + pub unsafe fn set_clock(&mut self, freq: u32) -> Result<()> { + let regs = self.regs.get_mut().unwrap(); + + let mut reg_val = regs.status.read() & (SR_CMD_INHIBIT | SR_DAT_INHIBIT); + let mut cnt = 10_0000; + while (cnt > 0) && reg_val != 0 { + wait_msec(1); + cnt -= 1; + reg_val = regs.status.read() & (SR_CMD_INHIBIT | SR_DAT_INHIBIT); + } + + if cnt <= 0 { + println!("ERROR: TIMEOUT WAITING FOR INHIBIT FLAG"); + return Err(Error::new(EINVAL)); + } + + reg_val = regs.control1.read(); + reg_val &= !C1_CLK_EN; + regs.control1.write(reg_val); + wait_msec(10); + + let c = 4166_6666 / freq; + let mut x: u32 = c - 1; + let mut s: u32 = 32; + + if x == 0 { + s = 0; + } else { + if (x & 0xffff_0000) == 0 { + x <<= 16; + s -= 16; + } + if (x & 0xff00_0000) == 0 { + x <<= 8; + s -= 8; + } + if (x & 0xf000_0000) == 0 { + x <<= 4; + s -= 4; + } + if (x & 0xc000_0000) == 0 { + x <<= 2; + s -= 2; + } + if (x & 0x8000_0000) == 0 { + x <<= 1; + s -= 1; + } + if s > 0 { + s -= 1; + } + if s > 7 { + s = 7; + } + } + let mut d; + if self.host_spec_ver > HOST_SPEC_V2 { + d = c; + } else { + d = 1 << s; + } + + if d <= 2 { + d = 2; + s = 0; + } + println!("sd clk divisor: 0x{:08x}, shift: 0x{:08x}", d, s); + + let mut h = 0; + if self.host_spec_ver > HOST_SPEC_V2 { + h = (d & 0x300) >> 2; + } + + d = ((d & 0xff) << 8) | h; + reg_val = regs.control1.read() & 0xffff_003f; + regs.control1.write(reg_val | d); + wait_msec(10); + reg_val = regs.control1.read(); + regs.control1.write(reg_val | C1_CLK_EN); + wait_msec(10); + + reg_val = regs.control1.read() & C1_CLK_STABLE; + cnt = 10000; + while cnt > 0 && reg_val == 0 { + wait_msec(10); + cnt -= 1; + reg_val = regs.control1.read() & C1_CLK_STABLE; + } + + if cnt <= 0 { + println!("ERROR: failed to get stable clock"); + return Err(Error::new(EINVAL)); + } + + Ok(()) + } + + pub unsafe fn sd_cmd(&mut self, mut code: u32, arg: u32) -> Result { + if (code & CMD_NEED_APP) != 0 { + let pre_cmd = CMD_APP_CMD | if self.rca != 0 { CMD_RSPNS_48 } else { 0 }; + match self.sd_cmd(pre_cmd, self.rca) { + Err(_) => { + println!("ERROR: failed to send SD APP command"); + return Err(Error::new(EINVAL)); + } + Ok(_) => { + code &= !CMD_NEED_APP; + } + } + } + + if let Err(_) = self.sd_status(SR_CMD_INHIBIT) { + println!("ERROR: Emmc busy"); + return Err(Error::new(EINVAL)); + } + + //println!("EMMC: Sending command 0x{:08x}, arg 0x{:08x}", code, arg); + + let regs = self.regs.get_mut().unwrap(); + let mut reg_val = regs.interrupt.read(); + regs.interrupt.write(reg_val); + regs.arg1.write(arg); + regs.cmdtm.write(code); + + if code == CMD_SEND_OP_COND { + wait_msec(1000); + } else if code == CMD_SEND_IF_COND || code == CMD_APP_CMD { + wait_msec(200); + } + + if let Err(_) = self.sd_int(INT_CMD_DONE) { + println!("ERROR: failed to send EMMC command"); + return Err(Error::new(EINVAL)); + } + + let regs = self.regs.get_mut().unwrap(); + reg_val = regs.resp0.read(); + + if code == CMD_GO_IDLE || code == CMD_APP_CMD { + return Ok(0); + } else if code == (CMD_APP_CMD | CMD_RSPNS_48) { + return Ok(reg_val & SR_APP_CMD); + } else if code == CMD_SEND_OP_COND { + return Ok(reg_val); + } else if code == CMD_SEND_IF_COND { + if reg_val == arg { + return Ok(0); + } else { + return Err(Error::new(EINVAL)); + } + } else if code == CMD_ALL_SEND_CID { + self.cid[0] = reg_val; + self.cid[1] = regs.resp1.read(); + self.cid[2] = regs.resp2.read(); + self.cid[3] = regs.resp3.read(); + + //FIXME: wrong implement, see CMD_SEND_CSD for detail + return Ok(reg_val); + } else if code == CMD_SEND_CSD { + let tmp0 = reg_val; + let tmp1 = regs.resp1.read(); + let tmp2 = regs.resp2.read(); + let tmp3 = regs.resp3.read(); + + self.csd[0] = tmp3 << 8 | tmp2 >> 24; + self.csd[1] = tmp2 << 8 | tmp1 >> 24; + self.csd[2] = tmp1 << 8 | tmp0 >> 24; + self.csd[3] = tmp0 << 8; + + //FIXME: support variable length of result. + return Ok(reg_val); + } else if code == CMD_SEND_REL_ADDR { + let mut err = reg_val & 0x1fff; + err |= (reg_val & 0x2000) << 6; + err |= (reg_val & 0x4000) << 8; + err |= (reg_val & 0x8000) << 8; + err &= CMD_ERRORS_MASK; + + if err != 0 { + return Err(Error::new(EINVAL)); + } else { + return Ok(reg_val & CMD_RCA_MASK); + } + } else { + return Ok(reg_val & CMD_ERRORS_MASK); + } + } + + pub unsafe fn sd_status(&mut self, mask: u32) -> Result<()> { + let regs = self.regs.get_mut().unwrap(); + let mut cnt = 500000; + + let mut reg_val = regs.status.read() & mask; + let mut reg_val1 = regs.interrupt.read() & INT_ERROR_MASK; + + while cnt > 0 && reg_val != 0 && reg_val1 == 0 { + wait_msec(1); + cnt -= 1; + reg_val = regs.status.read() & mask; + reg_val1 = regs.interrupt.read() & INT_ERROR_MASK; + } + reg_val1 = regs.interrupt.read() & INT_ERROR_MASK; + + if cnt <= 0 || reg_val1 != 0 { + return Err(Error::new(EINVAL)); + } else { + return Ok(()); + } + } + pub unsafe fn sd_int(&mut self, mask: u32) -> Result<()> { + let regs = self.regs.get_mut().unwrap(); + let mut cnt = 100_0000; + let m = mask | INT_ERROR_MASK; + + let mut reg_val = regs.interrupt.read() & m; + + while cnt > 0 && reg_val == 0 { + wait_msec(1); + cnt -= 1; + reg_val = regs.interrupt.read() & m; + } + reg_val = regs.interrupt.read(); + let err = reg_val & (INT_CMD_TIMEOUT | INT_DATA_TIMEOUT | INT_ERROR_MASK); + + if cnt <= 0 || err != 0 { + regs.interrupt.write(reg_val); + return Err(Error::new(EINVAL)); + } else { + regs.interrupt.write(mask); + return Ok(()); + } + } + + pub unsafe fn sd_readblock(&mut self, lba: u32, buf: &mut [u32], num: u32) -> Result { + let num = if num < 1 { 1 } else { num }; + + //println!("sd_readblock lba 0x{:x}, num 0x{:x}", lba, num); + + if let Err(_) = self.sd_status(SR_DAT_INHIBIT) { + println!("SR_DAT_INHIBIT TIMEOUT"); + return Err(Error::new(EINVAL)); + } + + if (self.scr[0] & SCR_SUPP_CCS) != 0 { + if num > 1 && ((self.scr[0] & SCR_SUPP_SET_BLKCNT) != 0) { + if let Err(_) = self.sd_cmd(CMD_SET_BLOCKCNT, num) { + println!("CMD_SET_BLOCKCNT ERROR"); + return Err(Error::new(EINVAL)); + } + } + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write((num) << 16 | 512); + if num == 1 { + self.sd_cmd(CMD_READ_SINGLE, lba).unwrap(); + } else { + self.sd_cmd(CMD_READ_MULTI, lba).unwrap(); + } + } else { + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write(1 << 16 | 512); + } + + let mut cnt = 0; + while cnt < num { + if (self.scr[0] & SCR_SUPP_CCS) == 0 { + self.sd_cmd(CMD_READ_SINGLE, (lba + cnt) * 512).unwrap(); + } + + if let Err(_) = self.sd_int(INT_READ_RDY) { + println!("ERROR: Timeout waiting for ready to read"); + return Err(Error::new(EINVAL)); + } + + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write(1 << 16 | 512); + for d in 0..128 { + buf[(128 * cnt + d) as usize] = regs.data.read(); + } + cnt += 1; + } + + if num > 1 && (self.scr[0] & SCR_SUPP_SET_BLKCNT) == 0 && (self.scr[0] & SCR_SUPP_CCS) != 0 + { + self.sd_cmd(CMD_STOP_TRANS, 0).unwrap(); + } + Ok((num * 512) as usize) + } + + pub unsafe fn sd_writeblock(&mut self, lba: u32, buf: &[u32], num: u32) -> Result { + let num = if num < 1 { 1 } else { num }; + + //println!("sd_writelock lba 0x{:x}, num 0x{:x}", lba, num); + + if let Err(_) = self.sd_status(SR_DAT_INHIBIT | SR_WRITE_AVAILABLE) { + println!("SR_DAT_INHIBIT TIMEOUT"); + return Err(Error::new(EINVAL)); + } + + if (self.scr[0] & SCR_SUPP_CCS) != 0 { + if num > 1 && ((self.scr[0] & SCR_SUPP_SET_BLKCNT) != 0) { + if let Err(_) = self.sd_cmd(CMD_SET_BLOCKCNT, num) { + println!("CMD_SET_BLOCKCNT ERROR"); + return Err(Error::new(EINVAL)); + } + } + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write((num) << 16 | 512); + if num == 1 { + self.sd_cmd(CMD_WRITE_SINGLE, lba).unwrap(); + } else { + self.sd_cmd(CMD_WRITE_MULTI, lba).unwrap(); + } + } else { + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write(1 << 16 | 512); + } + + let mut cnt = 0; + while cnt < num { + if (self.scr[0] & SCR_SUPP_CCS) == 0 { + self.sd_cmd(CMD_WRITE_SINGLE, (lba + cnt) * 512).unwrap(); + } + + if let Err(_) = self.sd_int(INT_WRITE_RDY) { + println!("ERROR: Timeout waiting for ready to write"); + return Err(Error::new(EINVAL)); + } + + let regs = self.regs.get_mut().unwrap(); + regs.blksizecnt.write(1 << 16 | 512); + for d in 0..128 { + regs.data.write(buf[(128 * cnt + d) as usize]); + } + cnt += 1; + } + + if let Err(_) = self.sd_int(INT_DATA_DONE) { + println!("ERROR: Timeout waiting for data done"); + return Err(Error::new(EINVAL)); + } + + if num > 1 && (self.scr[0] & SCR_SUPP_SET_BLKCNT) == 0 && (self.scr[0] & SCR_SUPP_CCS) != 0 + { + self.sd_cmd(CMD_STOP_TRANS, 0).unwrap(); + } + Ok((num * 512) as usize) + } +} + +impl Disk for SdHostCtrl { + fn block_size(&self) -> u32 { + 512 + } + + fn size(&self) -> u64 { + //assert 512MiB + self.size + } + + // TODO: real async? + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result { + if (buffer.len() % 512) != 0 { + println!("buffer.len {} should be aligned to {}", buffer.len(), 512); + return Err(Error::new(EINVAL)); + } + let u32_len = buffer.len() / core::mem::size_of::(); + let num = buffer.len() / 512; + let u8_ptr = buffer.as_mut_ptr(); + let ret = unsafe { + let u32_buffer = core::slice::from_raw_parts_mut(u8_ptr as *mut u32, u32_len); + self.sd_readblock(block as u32, u32_buffer, num as u32) + }; + match ret { + Ok(cnt) => Ok(cnt), + Err(err) => Err(err), + } + } + + // TODO: real async? + async fn write(&mut self, block: u64, buffer: &[u8]) -> Result { + if (buffer.len() % 512) != 0 { + println!("buffer.len {} should be aligned to {}", buffer.len(), 512); + return Err(Error::new(EINVAL)); + } + let u32_len = buffer.len() / core::mem::size_of::(); + let num = buffer.len() / 512; + let u8_ptr = buffer.as_ptr(); + let ret = unsafe { + let u32_buffer = core::slice::from_raw_parts(u8_ptr as *const u32, u32_len); + self.sd_writeblock(block as u32, u32_buffer, num as u32) + }; + match ret { + Ok(cnt) => Ok(cnt), + Err(err) => Err(err), + } + } +} diff --git a/drivers/storage/driver-block/Cargo.toml b/drivers/storage/driver-block/Cargo.toml new file mode 100644 index 0000000000..7a0b2a4029 --- /dev/null +++ b/drivers/storage/driver-block/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "driver-block" +version = "0.1.0" +edition = "2021" + +[dependencies] +executor = { path = "../../executor" } +partitionlib = { path = "../partitionlib" } + +libredox = "0.1.3" +log = "0.4" + +# TODO: migrate virtio to our executor +futures = { version = "0.3.28", features = ["executor"] } + +redox-daemon = "0.1" +redox_syscall = { version = "0.5", features = ["std"] } +redox-scheme = "0.6.2" diff --git a/drivers/storage/driver-block/src/lib.rs b/drivers/storage/driver-block/src/lib.rs new file mode 100644 index 0000000000..1c28cae0aa --- /dev/null +++ b/drivers/storage/driver-block/src/lib.rs @@ -0,0 +1,654 @@ +use std::cmp; +use std::future::{Future, IntoFuture}; +use std::io::{self, Read, Seek, SeekFrom}; + +use std::collections::BTreeMap; +use std::convert::TryFrom; +use std::fmt::Write; +use std::str; +use std::task::Poll; + +use executor::LocalExecutor; +use libredox::Fd; +use partitionlib::{LogicalBlockSize, PartitionTable}; +use redox_scheme::scheme::SchemeAsync; +use redox_scheme::{CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket}; +use syscall::dirent::DirentBuf; +use syscall::schemev2::NewFdFlags; +use syscall::{ + Error, Result, Stat, EACCES, EAGAIN, EBADF, EINTR, EINVAL, EISDIR, ENOENT, ENOLCK, EOPNOTSUPP, + EOVERFLOW, EWOULDBLOCK, MODE_DIR, MODE_FILE, O_DIRECTORY, O_STAT, +}; + +/// Split the read operation into a series of block reads. +/// `read_fn` will be called with a block number to be read, and a buffer to be filled. +/// `read_fn` must return a full block of data. +/// Result will be the number of bytes read. +fn block_read( + offset: u64, + blksize: u32, + buf: &mut [u8], + mut read_fn: impl FnMut(u64, &mut [u8]) -> io::Result<()>, +) -> io::Result { + // TODO: Yield sometimes, perhaps after a few blocks or something. + + if buf.len() == 0 { + return Ok(0); + } + let to_copy = usize::try_from( + offset.saturating_add(u64::try_from(buf.len()).expect("buf.len() larger than u64")) + - offset, + ) + .expect("bytes to copy larger than usize"); + let mut curr_buf = &mut buf[..to_copy]; + let mut curr_offset = offset; + let blk_size = usize::try_from(blksize).expect("blksize larger than usize"); + let mut total_read = 0; + + let mut block_bytes = [0u8; 4096]; + let block_bytes = &mut block_bytes[..blk_size]; + + while curr_buf.len() > 0 { + // TODO: Async/await? I mean, shouldn't AHCI be async? + + let blk_offset = + usize::try_from(curr_offset % u64::from(blksize)).expect("usize smaller than blksize"); + let to_copy = cmp::min(curr_buf.len(), blk_size - blk_offset); + assert!(blk_offset + to_copy <= blk_size); + + read_fn(curr_offset / u64::from(blksize), block_bytes)?; + + let src_buf = &block_bytes[blk_offset..]; + + curr_buf[..to_copy].copy_from_slice(&src_buf[..to_copy]); + curr_buf = &mut curr_buf[to_copy..]; + curr_offset += u64::try_from(to_copy).expect("bytes to copy larger than u64"); + total_read += to_copy; + } + Ok(total_read) +} + +pub trait Disk { + fn block_size(&self) -> u32; + fn size(&self) -> u64; + + // These operate on a whole multiple of the block size + // FIXME maybe only operate on a single block worth of data? + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result; + async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result; +} + +impl Disk for Box { + fn block_size(&self) -> u32 { + (**self).block_size() + } + + fn size(&self) -> u64 { + (**self).size() + } + + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { + (**self).read(block, buffer).await + } + + async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result { + (**self).write(block, buffer).await + } +} + +pub struct DiskWrapper { + pub disk: T, + pub pt: Option, +} + +impl DiskWrapper { + pub fn pt(disk: &mut T, executor: &impl ExecutorTrait) -> Option { + let bs = match disk.block_size() { + 512 => LogicalBlockSize::Lb512, + 4096 => LogicalBlockSize::Lb4096, + _ => return None, + }; + struct Device<'a, D: Disk, E: ExecutorTrait> { + disk: &'a mut D, + executor: &'a E, + offset: u64, + } + + impl<'a, D: Disk, E: ExecutorTrait> Seek for Device<'a, D, E> { + fn seek(&mut self, from: SeekFrom) -> io::Result { + let size = i64::try_from(self.disk.size()).or(Err(io::Error::new( + io::ErrorKind::Other, + "Disk larger than 2^63 - 1 bytes", + )))?; + + self.offset = match from { + SeekFrom::Start(new_pos) => cmp::min(self.disk.size(), new_pos), + SeekFrom::Current(new_pos) => { + cmp::max(0, cmp::min(size, self.offset as i64 + new_pos)) as u64 + } + SeekFrom::End(new_pos) => cmp::max(0, cmp::min(size + new_pos, size)) as u64, + }; + + Ok(self.offset) + } + } + // TODO: Perhaps this impl should be used in the rest of the scheme. + impl<'a, D: Disk, E: ExecutorTrait> Read for Device<'a, D, E> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let blksize = self.disk.block_size(); + let size_in_blocks = self.disk.size() / u64::from(blksize); + + let disk = &mut self.disk; + + let read_block = |block: u64, block_bytes: &mut [u8]| { + if block >= size_in_blocks { + return Err(io::Error::from_raw_os_error(syscall::EOVERFLOW)); + } + + let bytes = self.executor.block_on(disk.read(block, block_bytes))?; + assert_eq!(bytes, block_bytes.len()); + Ok(()) + }; + let bytes_read = block_read(self.offset, blksize, buf, read_block)?; + + self.offset += bytes_read as u64; + Ok(bytes_read) + } + } + + partitionlib::get_partitions( + &mut Device { + disk, + offset: 0, + executor, + }, + bs, + ) + .ok() + .flatten() + } + + pub fn new(mut disk: T, executor: &impl ExecutorTrait) -> Self { + Self { + pt: Self::pt(&mut disk, executor), + disk, + } + } + + pub fn disk(&self) -> &T { + &self.disk + } + + pub fn disk_mut(&mut self) -> &mut T { + &mut self.disk + } + + pub fn block_size(&self) -> u32 { + self.disk.block_size() + } + + pub fn size(&self) -> u64 { + self.disk.size() + } + + pub async fn read( + &mut self, + part_num: Option, + block: u64, + buf: &mut [u8], + ) -> syscall::Result { + if buf.len() as u64 % u64::from(self.disk.block_size()) != 0 { + return Err(Error::new(EINVAL)); + } + + if let Some(part_num) = part_num { + let part = self + .pt + .as_ref() + .ok_or(syscall::Error::new(EBADF))? + .partitions + .get(part_num) + .ok_or(syscall::Error::new(EBADF))?; + + if block >= part.size { + return Err(syscall::Error::new(EOVERFLOW)); + } + + let abs_block = part.start_lba + block; + + self.disk.read(abs_block, buf).await + } else { + self.disk.read(block, buf).await + } + } + + pub async fn write( + &mut self, + part_num: Option, + block: u64, + buf: &[u8], + ) -> syscall::Result { + if buf.len() as u64 % u64::from(self.disk.block_size()) != 0 { + return Err(Error::new(EINVAL)); + } + + if let Some(part_num) = part_num { + let part = self + .pt + .as_ref() + .ok_or(syscall::Error::new(EBADF))? + .partitions + .get(part_num) + .ok_or(syscall::Error::new(EBADF))?; + + if block >= part.size { + return Err(syscall::Error::new(EOVERFLOW)); + } + + let abs_block = part.start_lba + block; + + self.disk.write(abs_block, buf).await + } else { + self.disk.write(block, buf).await + } + } +} + +enum Handle { + List(Vec), // entries + Disk(u32), // disk num + Partition(u32, u32), // disk num, part num +} + +pub struct DiskScheme { + scheme_name: String, + socket: Socket, + disks: BTreeMap>, + handles: BTreeMap, + next_id: usize, +} + +pub trait ExecutorTrait { + fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture + 'a) -> O; +} +impl ExecutorTrait for LocalExecutor { + fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture + 'a) -> O { + LocalExecutor::block_on(self, fut) + } +} +#[deprecated = "use custom executor"] +pub struct FuturesExecutor; + +#[allow(deprecated)] +impl ExecutorTrait for FuturesExecutor { + fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture + 'a) -> O { + futures::executor::block_on(fut.into_future()) + } +} +pub struct TrivialExecutor; +impl ExecutorTrait for TrivialExecutor { + fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture + 'a) -> O { + let mut fut = std::pin::pin!(fut.into_future()); + let mut cx = std::task::Context::from_waker(std::task::Waker::noop()); + loop { + match fut.as_mut().poll(&mut cx) { + Poll::Ready(v) => return v, + Poll::Pending => { + log::warn!("TrivialExecutor: future wasn't trivial"); + continue; + } + } + } + } +} + +impl DiskScheme { + pub fn new( + daemon: Option, + scheme_name: String, + disks: BTreeMap, + executor: &impl ExecutorTrait, + ) -> Self { + assert!(scheme_name.starts_with("disk")); + let socket = Socket::nonblock(&scheme_name).expect("failed to create disk scheme"); + + if let Some(daemon) = daemon { + daemon.ready().expect("failed to signal readiness"); + } + + Self { + scheme_name, + socket, + disks: disks + .into_iter() + .map(|(k, disk)| (k, DiskWrapper::new(disk, executor))) + .collect(), + next_id: 0, + handles: BTreeMap::new(), + } + } + + pub fn event_handle(&self) -> &Fd { + self.socket.inner() + } + + /// Process pending and new requests. + /// + /// This needs to be called each time there is a new event on the scheme. + pub async fn tick(&mut self) -> io::Result<()> { + // Handle new scheme requests + loop { + let request = match self.socket.next_request(SignalBehavior::Interrupt) { + Ok(Some(request)) => request, + Ok(None) => { + // Scheme likely got unmounted + // TODO: return this to caller instead + std::process::exit(0); + } + Err(error) if error.errno == EWOULDBLOCK || error.errno == EAGAIN => break, + Err(err) if err.errno == EINTR => continue, + Err(err) => return Err(err.into()), + }; + + let response = match request.kind() { + RequestKind::Call(call_request) => { + // TODO: Spawn a separate task for each scheme call. This would however require the + // use of a smarter buffer pool (or direct IO, or a buffer per fd) in order to do + // parallel IO. It might also require async-aware locks so that a close() is + // correctly ordered wrt IO on the same fd. + call_request.handle_async(self).await + } + RequestKind::SendFd(sendfd_request) => Response::err(EOPNOTSUPP, sendfd_request), + RequestKind::Cancellation(_cancellation_request) => { + // FIXME implement cancellation + continue; + } + RequestKind::MsyncMsg | RequestKind::MunmapMsg | RequestKind::MmapMsg => { + unreachable!() + } + RequestKind::OnClose { id } => { + self.on_close(id); + continue; + } + }; + self.socket + .write_response(response, SignalBehavior::Restart)?; + } + + Ok(()) + } + + // Checks if any conflicting handles already exist + fn check_locks(&self, disk_i: u32, part_i_opt: Option) -> Result<()> { + for (_, handle) in self.handles.iter() { + match handle { + Handle::Disk(i) => { + if disk_i == *i { + return Err(Error::new(ENOLCK)); + } + } + Handle::Partition(i, p) => { + if disk_i == *i { + match part_i_opt { + Some(part_i) => { + if part_i == *p { + return Err(Error::new(ENOLCK)); + } + } + None => { + return Err(Error::new(ENOLCK)); + } + } + } + } + _ => (), + } + } + Ok(()) + } +} + +impl SchemeAsync for DiskScheme { + async fn open(&mut self, path_str: &str, flags: usize, ctx: &CallerCtx) -> Result { + if ctx.uid != 0 { + return Err(Error::new(EACCES)); + } + let path_str = path_str.trim_matches('/'); + + let handle = if path_str.is_empty() { + if flags & O_DIRECTORY == O_DIRECTORY || flags & O_STAT == O_STAT { + let mut list = String::new(); + + for (nsid, disk) in self.disks.iter() { + write!(list, "{}\n", nsid).unwrap(); + + if disk.pt.is_none() { + continue; + } + for part_num in 0..disk.pt.as_ref().unwrap().partitions.len() { + write!(list, "{}p{}\n", nsid, part_num).unwrap(); + } + } + + Handle::List(list.into_bytes()) + } else { + return Err(Error::new(EISDIR)); + } + } else if let Some(p_pos) = path_str.chars().position(|c| c == 'p') { + let nsid_str = &path_str[..p_pos]; + + if p_pos + 1 >= path_str.len() { + return Err(Error::new(ENOENT)); + } + let part_num_str = &path_str[p_pos + 1..]; + + let nsid = nsid_str.parse::().or(Err(Error::new(ENOENT)))?; + let part_num = part_num_str.parse::().or(Err(Error::new(ENOENT)))?; + + if let Some(disk) = self.disks.get(&nsid) { + if disk + .pt + .as_ref() + .ok_or(Error::new(ENOENT))? + .partitions + .get(part_num as usize) + .is_some() + { + self.check_locks(nsid, Some(part_num))?; + + Handle::Partition(nsid, part_num) + } else { + return Err(Error::new(ENOENT)); + } + } else { + return Err(Error::new(ENOENT)); + } + } else { + let nsid = path_str.parse::().or(Err(Error::new(ENOENT)))?; + + if self.disks.contains_key(&nsid) { + self.check_locks(nsid, None)?; + Handle::Disk(nsid) + } else { + return Err(Error::new(ENOENT)); + } + }; + let id = self.next_id; + self.next_id += 1; + self.handles.insert(id, handle); + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::POSITIONED, + }) + } + async fn getdents<'buf>( + &mut self, + _id: usize, + _buf: DirentBuf<&'buf mut [u8]>, + _opaque_offset: u64, + ) -> Result> { + // TODO + Err(Error::new(EOPNOTSUPP)) + } + + async fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { + match *self.handles.get(&id).ok_or(Error::new(EBADF))? { + Handle::List(ref data) => { + stat.st_mode = MODE_DIR; + stat.st_size = data.len() as u64; + Ok(()) + } + Handle::Disk(number) => { + let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?; + stat.st_mode = MODE_FILE; + stat.st_blocks = disk.disk().size() / u64::from(disk.block_size()); + stat.st_blksize = disk.block_size(); + stat.st_size = disk.size(); + Ok(()) + } + Handle::Partition(disk_num, part_num) => { + let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?; + let part = disk + .pt + .as_ref() + .ok_or(Error::new(EBADF))? + .partitions + .get(part_num as usize) + .ok_or(Error::new(EBADF))?; + stat.st_mode = MODE_FILE; + stat.st_size = part.size * u64::from(disk.block_size()); + stat.st_blocks = part.size; + stat.st_blksize = disk.block_size(); + Ok(()) + } + } + } + + async fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + let mut i = 0; + + let scheme_name = self.scheme_name.as_bytes(); + let mut j = 0; + // TODO: copy_from_slice + while i < buf.len() && j < scheme_name.len() { + buf[i] = scheme_name[j]; + i += 1; + j += 1; + } + + if i < buf.len() { + buf[i] = b':'; + i += 1; + } + + match *handle { + Handle::List(_) => (), + Handle::Disk(number) => { + let number_str = format!("{}", number); + let number_bytes = number_str.as_bytes(); + j = 0; + while i < buf.len() && j < number_bytes.len() { + buf[i] = number_bytes[j]; + i += 1; + j += 1; + } + } + Handle::Partition(disk_num, part_num) => { + let number_str = format!("{}p{}", disk_num, part_num); + let number_bytes = number_str.as_bytes(); + j = 0; + while i < buf.len() && j < number_bytes.len() { + buf[i] = number_bytes[j]; + i += 1; + j += 1; + } + } + } + + Ok(i) + } + + async fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::List(ref handle) => { + let src = usize::try_from(offset) + .ok() + .and_then(|o| handle.get(o..)) + .unwrap_or(&[]); + let count = core::cmp::min(src.len(), buf.len()); + buf[..count].copy_from_slice(&src[..count]); + Ok(count) + } + Handle::Disk(number) => { + let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?; + let block = offset / u64::from(disk.block_size()); + disk.read(None, block, buf).await + } + Handle::Partition(disk_num, part_num) => { + let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?; + let block = offset / u64::from(disk.block_size()); + disk.read(Some(part_num as usize), block, buf).await + } + } + } + + async fn write( + &mut self, + id: usize, + buf: &[u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::List(_) => Err(Error::new(EBADF)), + Handle::Disk(number) => { + let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?; + let block = offset / u64::from(disk.block_size()); + disk.write(None, block, buf).await + } + Handle::Partition(disk_num, part_num) => { + let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?; + let block = offset / u64::from(disk.block_size()); + disk.write(Some(part_num as usize), block, buf).await + } + } + } + + async fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result { + Ok(match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::List(ref handle) => handle.len() as u64, + Handle::Disk(number) => { + let disk = self.disks.get_mut(&number).ok_or(Error::new(EBADF))?; + disk.size() + } + Handle::Partition(disk_num, part_num) => { + let disk = self.disks.get_mut(&disk_num).ok_or(Error::new(EBADF))?; + let part = disk + .pt + .as_ref() + .ok_or(Error::new(EBADF))? + .partitions + .get(part_num as usize) + .ok_or(Error::new(EBADF))?; + + part.size * u64::from(disk.block_size()) + } + }) + } +} + +impl DiskScheme { + pub fn on_close(&mut self, id: usize) { + let _ = self.handles.remove(&id); + } +} diff --git a/drivers/storage/ided/.gitignore b/drivers/storage/ided/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/storage/ided/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/storage/ided/Cargo.toml b/drivers/storage/ided/Cargo.toml new file mode 100644 index 0000000000..9f6ee66745 --- /dev/null +++ b/drivers/storage/ided/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ided" +version = "0.1.0" +edition = "2021" + +[dependencies] +common = { path = "../../common" } +driver-block = { path = "../driver-block" } +libredox = "0.1.3" +log = "0.4" +pcid = { path = "../../pcid" } +redox-daemon = "0.1" +redox_syscall = { version = "0.5", features = ["std"] } +redox_event = "0.4" diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs new file mode 100644 index 0000000000..0a14bcc99a --- /dev/null +++ b/drivers/storage/ided/src/ide.rs @@ -0,0 +1,469 @@ +use std::{ + convert::TryInto, + sync::{Arc, Mutex}, + thread, + time::{Duration, Instant}, +}; + +use driver_block::Disk; +use syscall::error::{Error, Result, EIO}; + +use common::dma::Dma; +use common::io::{Io, Pio, ReadOnly, WriteOnly}; + +static TIMEOUT: Duration = Duration::new(1, 0); + +#[repr(u8)] +pub enum AtaCommand { + ReadPio = 0x20, + ReadPioExt = 0x24, + ReadDma = 0xC8, + ReadDmaExt = 0x25, + WritePio = 0x30, + WritePioExt = 0x34, + WriteDma = 0xCA, + WriteDmaExt = 0x35, + CacheFlush = 0xE7, + CacheFlushExt = 0xEA, + Packet = 0xA0, + IdentifyPacket = 0xA1, + Identify = 0xEC, +} + +#[repr(C, packed)] +struct PrdtEntry { + phys: u32, + size: u16, + flags: u16, +} + +pub struct Channel { + pub data8: Pio, + pub data32: Pio, + pub error: ReadOnly>, + pub features: WriteOnly>, + pub sector_count: Pio, + pub lba_0: Pio, + pub lba_1: Pio, + pub lba_2: Pio, + pub device_select: Pio, + pub status: ReadOnly>, + pub command: WriteOnly>, + pub alt_status: ReadOnly>, + pub control: WriteOnly>, + pub busmaster_command: Pio, + pub busmaster_status: Pio, + pub busmaster_prdt: Pio, + prdt: Dma<[PrdtEntry; 128]>, + buf: Dma<[u8; 128 * 512]>, +} + +impl Channel { + pub fn new(base: u16, control_base: u16, busmaster_base: u16) -> Result { + Ok(Self { + data8: Pio::new(base + 0), + data32: Pio::new(base + 0), + error: ReadOnly::new(Pio::new(base + 1)), + features: WriteOnly::new(Pio::new(base + 1)), + sector_count: Pio::new(base + 2), + lba_0: Pio::new(base + 3), + lba_1: Pio::new(base + 4), + lba_2: Pio::new(base + 5), + device_select: Pio::new(base + 6), + status: ReadOnly::new(Pio::new(base + 7)), + command: WriteOnly::new(Pio::new(base + 7)), + alt_status: ReadOnly::new(Pio::new(control_base)), + control: WriteOnly::new(Pio::new(control_base)), + busmaster_command: Pio::new(busmaster_base), + busmaster_status: Pio::new(busmaster_base + 2), + busmaster_prdt: Pio::new(busmaster_base + 4), + prdt: unsafe { + Dma::zeroed( + //TODO: PhysBox::new_in_32bit_space(4096)? + )? + .assume_init() + }, + buf: unsafe { + Dma::zeroed( + //TODO: PhysBox::new_in_32bit_space(16 * 4096)? + )? + .assume_init() + }, + }) + } + + pub fn primary_compat(busmaster_base: u16) -> Result { + Self::new(0x1F0, 0x3F6, busmaster_base) + } + + pub fn secondary_compat(busmaster_base: u16) -> Result { + Self::new(0x170, 0x376, busmaster_base) + } + + fn check_status(&mut self) -> Result { + let status = self.status.read(); + + if status & 0x01 != 0 { + log::error!("IDE error: {:#x}", self.error.read()); + return Err(Error::new(EIO)); + } + + if status & 0x20 != 0 { + log::error!("IDE device write fault"); + return Err(Error::new(EIO)); + } + + Ok(status) + } + + fn polling(&mut self, read: bool, line: u32) -> Result<()> { + /* + #define ATA_SR_BSY 0x80 // Busy + #define ATA_SR_DRDY 0x40 // Drive ready + #define ATA_SR_DF 0x20 // Drive write fault + #define ATA_SR_DSC 0x10 // Drive seek complete + #define ATA_SR_DRQ 0x08 // Data request ready + #define ATA_SR_CORR 0x04 // Corrected data + #define ATA_SR_IDX 0x02 // Index + #define ATA_SR_ERR 0x01 // Error + */ + + for _ in 0..4 { + // Doing this 4 times creates a 400ns delay + self.alt_status.read(); + } + + let start = Instant::now(); + loop { + let status = self.check_status()?; + if status & 0x80 == 0 { + if read && status & 0x08 == 0 { + log::error!("IDE read data not ready"); + return Err(Error::new(EIO)); + } + break; + } + if start.elapsed() >= TIMEOUT { + log::error!( + "line {} polling {} timeout with status 0x{:02X}", + line, + if read { "read" } else { "write" }, + status + ); + return Err(Error::new(EIO)); + } + thread::yield_now(); + } + + Ok(()) + } +} + +pub struct AtaDisk { + pub chan: Arc>, + pub chan_i: usize, + pub dev: u8, + pub size: u64, + pub dma: bool, + pub lba_48: bool, +} + +impl Disk for AtaDisk { + fn block_size(&self) -> u32 { + 512 + } + + fn size(&self) -> u64 { + self.size + } + + // NOTE: not async + async fn read(&mut self, start_block: u64, buffer: &mut [u8]) -> Result { + let mut count = 0; + for chunk in buffer.chunks_mut(65536) { + let block = start_block + (count as u64) / 512; + + //TODO: support other LBA modes + assert!(block < 0x1_0000_0000_0000); + + let sectors = (chunk.len() + 511) / 512; + assert!(sectors <= 128); + + log::trace!( + "IDE read chan {} dev {} block {:#x} count {:#x}", + self.chan_i, + self.dev, + block, + sectors + ); + + let mut chan = self.chan.lock().unwrap(); + + if self.dma { + // Stop bus master + chan.busmaster_command.writef(1, false); + // Make PRDT EOT match chunk size + for i in 0..sectors { + chan.prdt[i] = PrdtEntry { + phys: (chan.buf.physical() + i * 512).try_into().unwrap(), + size: 512, + flags: if i + 1 == sectors { + 1 << 15 // End of table + } else { + 0 + }, + }; + } + // Set PRDT + let prdt = chan.prdt.physical(); + chan.busmaster_prdt.write(prdt.try_into().unwrap()); + // Set to read + chan.busmaster_command.writef(1 << 3, true); + // Clear interrupt and error bits + chan.busmaster_status.write(0b110); + } + + // Select drive + //TODO: upper part of LBA 28 + chan.device_select.write(0xE0 | (self.dev << 4)); + + if self.lba_48 { + // Set high sector count and LBA + chan.control.writef(0x80, true); + chan.sector_count.write((sectors >> 8) as u8); + chan.lba_0.write((block >> 24) as u8); + chan.lba_1.write((block >> 32) as u8); + chan.lba_2.write((block >> 40) as u8); + chan.control.writef(0x80, false); + } + + // Set low sector count and LBA + chan.sector_count.write(sectors as u8); + chan.lba_0.write(block as u8); + chan.lba_1.write((block >> 8) as u8); + chan.lba_2.write((block >> 16) as u8); + + // Send command + chan.command.write(if self.dma { + if self.lba_48 { + AtaCommand::ReadDmaExt as u8 + } else { + AtaCommand::ReadDma as u8 + } + } else { + if self.lba_48 { + AtaCommand::ReadPioExt as u8 + } else { + AtaCommand::ReadPio as u8 + } + }); + + // Read data + if self.dma { + // Start bus master + chan.busmaster_command.writef(1, true); + + // Wait for transaction to finish + chan.polling(false, line!())?; + + // Wait for bus master to finish + let start = Instant::now(); + let error = loop { + let status = chan.busmaster_status.read(); + if status & 1 << 1 != 0 { + // Break with error status + break true; + } + if status & 1 == 0 { + // Break when not busy and no error + break false; + } + if start.elapsed() >= TIMEOUT { + log::error!("busmaster read timeout with status 0x{:02X}", status); + return Err(Error::new(EIO)); + } + thread::yield_now(); + }; + + // Stop bus master + chan.busmaster_command.writef(1, false); + + // Clear bus master error and interrupt + chan.busmaster_status.write(0b110); + + if error { + log::error!("IDE bus master error"); + return Err(Error::new(EIO)); + } + + // Read buffer + chunk.copy_from_slice(&chan.buf[..chunk.len()]); + } else { + for sector in 0..sectors { + chan.polling(true, line!())?; + + for i in 0..128 { + let data = chan.data32.read(); + chunk[sector * 512 + i * 4 + 0] = (data >> 0) as u8; + chunk[sector * 512 + i * 4 + 1] = (data >> 8) as u8; + chunk[sector * 512 + i * 4 + 2] = (data >> 16) as u8; + chunk[sector * 512 + i * 4 + 3] = (data >> 24) as u8; + } + } + } + + count += chunk.len(); + } + + Ok(count) + } + + // NOTE: not async + async fn write(&mut self, start_block: u64, buffer: &[u8]) -> Result { + let mut count = 0; + for chunk in buffer.chunks(65536) { + let block = start_block + (count as u64) / 512; + + //TODO: support other LBA modes + assert!(block < 0x1_0000_0000_0000); + + let sectors = (chunk.len() + 511) / 512; + assert!(sectors <= 128); + + log::trace!( + "IDE write chan {} dev {} block {:#x} count {:#x}", + self.chan_i, + self.dev, + block, + sectors + ); + + let mut chan = self.chan.lock().unwrap(); + + if self.dma { + // Stop bus master + chan.busmaster_command.writef(1, false); + // Make PRDT EOT match chunk size + for i in 0..sectors { + chan.prdt[i] = PrdtEntry { + phys: (chan.buf.physical() + i * 512).try_into().unwrap(), + size: 512, + flags: if i + 1 == sectors { + 1 << 15 // End of table + } else { + 0 + }, + }; + } + // Set PRDT + let prdt = chan.prdt.physical(); + chan.busmaster_prdt.write(prdt.try_into().unwrap()); + // Set to write + chan.busmaster_command.writef(1 << 3, false); + // Clear interrupt and error bits + chan.busmaster_status.write(0b110); + + // Write buffer + chan.buf[..chunk.len()].copy_from_slice(chunk); + } + + // Select drive + //TODO: upper part of LBA 28 + chan.device_select.write(0xE0 | (self.dev << 4)); + + if self.lba_48 { + // Set high sector count and LBA + chan.control.writef(0x80, true); + chan.sector_count.write((sectors >> 8) as u8); + chan.lba_0.write((block >> 24) as u8); + chan.lba_1.write((block >> 32) as u8); + chan.lba_2.write((block >> 40) as u8); + chan.control.writef(0x80, false); + } + + // Set low sector count and LBA + chan.sector_count.write(sectors as u8); + chan.lba_0.write(block as u8); + chan.lba_1.write((block >> 8) as u8); + chan.lba_2.write((block >> 16) as u8); + + // Send command + chan.command.write(if self.dma { + if self.lba_48 { + AtaCommand::WriteDmaExt as u8 + } else { + AtaCommand::WriteDma as u8 + } + } else { + if self.lba_48 { + AtaCommand::WritePioExt as u8 + } else { + AtaCommand::WritePio as u8 + } + }); + + // Write data + if self.dma { + // Start bus master + chan.busmaster_command.writef(1, true); + + // Wait for transaction to finish + chan.polling(false, line!())?; + + // Wait for bus master to finish + let start = Instant::now(); + let error = loop { + let status = chan.busmaster_status.read(); + if status & 1 << 1 != 0 { + // Break with error status + break true; + } + if status & 1 == 0 { + // Break when not busy and no error + break false; + } + if start.elapsed() >= TIMEOUT { + log::error!("busmaster write timeout with status 0x{:02X}", status); + return Err(Error::new(EIO)); + } + thread::yield_now(); + }; + + // Stop bus master + chan.busmaster_command.writef(1, false); + + // Clear bus master error and interrupt + chan.busmaster_status.write(0b110); + + if error { + log::error!("IDE bus master error"); + return Err(Error::new(EIO)); + } + } else { + for sector in 0..sectors { + chan.polling(false, line!())?; + + for i in 0..128 { + chan.data32.write( + ((chunk[sector * 512 + i * 4 + 0] as u32) << 0) + | ((chunk[sector * 512 + i * 4 + 1] as u32) << 8) + | ((chunk[sector * 512 + i * 4 + 2] as u32) << 16) + | ((chunk[sector * 512 + i * 4 + 3] as u32) << 24), + ); + } + } + } + + chan.command.write(if self.lba_48 { + AtaCommand::CacheFlushExt as u8 + } else { + AtaCommand::CacheFlush as u8 + }); + chan.polling(false, line!())?; + + count += chunk.len(); + } + + Ok(count) + } +} diff --git a/drivers/storage/ided/src/main.rs b/drivers/storage/ided/src/main.rs new file mode 100644 index 0000000000..8015baae9e --- /dev/null +++ b/drivers/storage/ided/src/main.rs @@ -0,0 +1,298 @@ +use common::io::Io as _; +use driver_block::{Disk, DiskScheme, ExecutorTrait, FuturesExecutor}; +use event::{EventFlags, RawEventQueue}; +use libredox::flag; +use log::{error, info}; +use pcid_interface::PciFunctionHandle; +use std::{ + fs::File, + io::{Read, Write}, + os::unix::io::{FromRawFd, RawFd}, + sync::{Arc, Mutex}, + thread::{self, sleep}, + time::Duration, +}; + +use crate::ide::{AtaCommand, AtaDisk, Channel}; + +pub mod ide; + +fn main() { + redox_daemon::Daemon::new(daemon).expect("ided: failed to daemonize"); +} + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let pcid_handle = PciFunctionHandle::connect_default(); + + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_ide"); + + common::setup_logging( + "disk", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + info!("IDE PCI CONFIG: {:?}", pci_config); + + // Get controller DMA capable + let dma = pci_config.func.full_device_id.interface & 0x80 != 0; + + let busmaster_base = pci_config.func.bars[4].expect_port(); + let (primary, primary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { + panic!("TODO: IDE primary channel is PCI native"); + } else { + (Channel::primary_compat(busmaster_base).unwrap(), 14) + }; + let (secondary, secondary_irq) = if pci_config.func.full_device_id.interface & 1 != 0 { + panic!("TODO: IDE secondary channel is PCI native"); + } else { + (Channel::secondary_compat(busmaster_base + 8).unwrap(), 15) + }; + + common::acquire_port_io_rights().expect("ided: failed to get I/O privilege"); + + //TODO: move this to ide.rs? + let chans = vec![ + Arc::new(Mutex::new(primary)), + Arc::new(Mutex::new(secondary)), + ]; + enum AnyDisk { + Ata(AtaDisk), + } + impl Disk for AnyDisk { + fn block_size(&self) -> u32 { + let AnyDisk::Ata(a) = self; + a.block_size() + } + fn size(&self) -> u64 { + let AnyDisk::Ata(a) = self; + a.size() + } + async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result { + let AnyDisk::Ata(a) = self; + a.write(block, buffer).await + } + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { + let AnyDisk::Ata(a) = self; + a.read(block, buffer).await + } + } + let mut disks: Vec = Vec::new(); + for (chan_i, chan_lock) in chans.iter().enumerate() { + let mut chan = chan_lock.lock().unwrap(); + + println!(" - channel {}", chan_i); + + // Disable IRQs + chan.control.write(2); + + for dev in 0..=1 { + println!(" - device {}", dev); + + // Select device + chan.device_select.write(0xA0 | (dev << 4)); + sleep(Duration::from_millis(1)); + + // ATA identify command + chan.command.write(AtaCommand::Identify as u8); + sleep(Duration::from_millis(1)); + + // Check if device exists + if chan.status.read() == 0 { + println!(" not found"); + continue; + } + + // Poll for status + let error = loop { + let status = chan.status.read(); + if status & 1 != 0 { + // Error + break true; + } + if status & 0x80 == 0 && status & 0x08 != 0 { + // Not busy and data ready + break false; + } + thread::yield_now(); + }; + + //TODO: probe ATAPI + if error { + println!(" error"); + continue; + } + + // Read and print identity + { + let mut dest = [0u16; 256]; + for chunk in dest.chunks_mut(2) { + let data = chan.data32.read(); + chunk[0] = data as u16; + chunk[1] = (data >> 16) as u16; + } + + let mut serial = String::new(); + for word in 10..20 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + serial.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + serial.push(b); + } + } + + let mut firmware = String::new(); + for word in 23..27 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + firmware.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + firmware.push(b); + } + } + + let mut model = String::new(); + for word in 27..47 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + model.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + model.push(b); + } + } + + let mut sectors = (dest[100] as u64) + | ((dest[101] as u64) << 16) + | ((dest[102] as u64) << 32) + | ((dest[103] as u64) << 48); + + let lba_bits = if sectors == 0 { + sectors = (dest[60] as u64) | ((dest[61] as u64) << 16); + 28 + } else { + 48 + }; + + println!(" Serial: {}", serial.trim()); + println!(" Firmware: {}", firmware.trim()); + println!(" Model: {}", model.trim()); + println!(" Size: {} MB", sectors / 2048); + println!(" DMA: {}", dma); + println!(" {}-bit LBA", lba_bits); + + disks.push(AnyDisk::Ata(AtaDisk { + chan: chan_lock.clone(), + chan_i, + dev, + size: sectors * 512, + dma, + lba_48: lba_bits == 48, + })); + } + } + } + + let scheme_name = format!("disk.{}", name); + let mut scheme = DiskScheme::new( + Some(daemon), + scheme_name, + disks + .into_iter() + .enumerate() + .map(|(i, disk)| (i as u32, disk)) + .collect(), + // TODO: Should ided just use TrivialExecutor or would it be valuable to actually use a + // real executor? + &FuturesExecutor, + ); + + let primary_irq_fd = libredox::call::open( + &format!("/scheme/irq/{}", primary_irq), + flag::O_RDWR | flag::O_NONBLOCK, + 0, + ) + .expect("ided: failed to open irq file"); + let mut primary_irq_file = unsafe { File::from_raw_fd(primary_irq_fd as RawFd) }; + + let secondary_irq_fd = libredox::call::open( + &format!("/scheme/irq/{}", secondary_irq), + flag::O_RDWR | flag::O_NONBLOCK, + 0, + ) + .expect("ided: failed to open irq file"); + let mut secondary_irq_file = unsafe { File::from_raw_fd(secondary_irq_fd as RawFd) }; + + let event_queue = RawEventQueue::new().expect("ided: failed to open event file"); + + libredox::call::setrens(0, 0).expect("ided: failed to enter null namespace"); + + event_queue + .subscribe(scheme.event_handle().raw(), 0, EventFlags::READ) + .expect("ided: failed to event disk scheme"); + + event_queue + .subscribe(primary_irq_fd, 0, EventFlags::READ) + .expect("ided: failed to event irq scheme"); + + event_queue + .subscribe(secondary_irq_fd, 0, EventFlags::READ) + .expect("ided: failed to event irq scheme"); + + for event in event_queue { + let event = event.unwrap(); + if event.fd == scheme.event_handle().raw() { + FuturesExecutor.block_on(scheme.tick()).unwrap(); + } else if event.fd == primary_irq_fd { + let mut irq = [0; 8]; + if primary_irq_file + .read(&mut irq) + .expect("ided: failed to read irq file") + >= irq.len() + { + let _chan = chans[0].lock().unwrap(); + //TODO: check chan for irq + + primary_irq_file + .write(&irq) + .expect("ided: failed to write irq file"); + + FuturesExecutor.block_on(scheme.tick()).unwrap(); + } + } else if event.fd == secondary_irq_fd { + let mut irq = [0; 8]; + if secondary_irq_file + .read(&mut irq) + .expect("ided: failed to read irq file") + >= irq.len() + { + let _chan = chans[1].lock().unwrap(); + //TODO: check chan for irq + + secondary_irq_file + .write(&irq) + .expect("ided: failed to write irq file"); + + FuturesExecutor.block_on(scheme.tick()).unwrap(); + } + } else { + error!("Unknown event {}", event.fd); + } + } + + std::process::exit(0); +} diff --git a/drivers/storage/lived/Cargo.toml b/drivers/storage/lived/Cargo.toml new file mode 100644 index 0000000000..034ba80d0a --- /dev/null +++ b/drivers/storage/lived/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lived" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +version = "0.1.0" +edition = "2021" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +libredox = "0.1.3" +redox-daemon = "0.1" +redox_syscall = { version = "0.5", features = ["std"] } +redox_event = "0.4" +driver-block = { path = "../driver-block" } diff --git a/drivers/storage/lived/src/main.rs b/drivers/storage/lived/src/main.rs new file mode 100644 index 0000000000..57003daedc --- /dev/null +++ b/drivers/storage/lived/src/main.rs @@ -0,0 +1,174 @@ +//! Disk scheme replacement when making live disk + +#![feature(int_roundings)] + +use std::collections::{BTreeMap, HashMap}; +use std::fs::File; + +use std::os::fd::AsRawFd; + +use driver_block::{Disk, DiskScheme}; +use driver_block::{ExecutorTrait, TrivialExecutor}; +use libredox::call::MmapArgs; +use libredox::flag; + +use syscall::error::*; +use syscall::PAGE_SIZE; + +use anyhow::{anyhow, Context}; + +struct LiveDisk { + original: &'static [u8], + //TODO: drop overlay blocks if they match the original + overlay: HashMap>, +} + +impl LiveDisk { + fn new(phys: usize, size: usize) -> anyhow::Result { + let start = phys.div_floor(PAGE_SIZE) * PAGE_SIZE; + let end = phys + .checked_add(size) + .context("phys + size overflow")? + .next_multiple_of(PAGE_SIZE); + let size = end - start; + + let original = unsafe { + let file = File::open("/scheme/memory/physical")?; + let base = libredox::call::mmap(MmapArgs { + fd: file.as_raw_fd() as usize, + addr: core::ptr::null_mut(), + offset: start as u64, + length: size, + prot: flag::PROT_READ, + flags: flag::MAP_SHARED, + }) + .map_err(|err| anyhow!("failed to mmap livedisk: {}", err))?; + + std::slice::from_raw_parts_mut(base as *mut u8, size) + }; + + Ok(LiveDisk { + original, + overlay: HashMap::new(), + }) + } +} + +impl Disk for LiveDisk { + fn block_size(&self) -> u32 { + PAGE_SIZE as u32 + } + + fn size(&self) -> u64 { + self.original.len() as u64 + } + + async fn read(&mut self, mut block: u64, buffer: &mut [u8]) -> syscall::Result { + let mut offset = (block as usize) * PAGE_SIZE; + if offset + buffer.len() > self.original.len() { + return Err(syscall::Error::new(EINVAL)); + } + for chunk in buffer.chunks_mut(PAGE_SIZE) { + match self.overlay.get(&block) { + Some(overlay) => { + chunk.copy_from_slice(&overlay[..chunk.len()]); + } + None => { + chunk.copy_from_slice(&self.original[offset..offset + chunk.len()]); + } + } + block += 1; + offset += PAGE_SIZE; + } + Ok(buffer.len()) + } + + async fn write(&mut self, mut block: u64, buffer: &[u8]) -> syscall::Result { + let mut offset = (block as usize) * PAGE_SIZE; + if offset + buffer.len() > self.original.len() { + return Err(syscall::Error::new(EINVAL)); + } + for chunk in buffer.chunks(PAGE_SIZE) { + self.overlay.entry(block).or_insert_with(|| { + let offset = (block as usize) * PAGE_SIZE; + self.original[offset..offset + PAGE_SIZE] + .to_vec() + .into_boxed_slice() + })[..chunk.len()] + .copy_from_slice(chunk); + block += 1; + offset += PAGE_SIZE; + } + Ok(buffer.len()) + } +} + +fn main() -> anyhow::Result<()> { + let mut phys = 0; + let mut size = 0; + + // TODO: handle error + for line in std::fs::read_to_string("/scheme/sys/env") + .context("failed to read env")? + .lines() + { + let mut parts = line.splitn(2, '='); + let name = parts.next().unwrap_or(""); + let value = parts.next().unwrap_or(""); + + if name == "DISK_LIVE_ADDR" { + phys = usize::from_str_radix(value, 16).unwrap_or(0); + } + + if name == "DISK_LIVE_SIZE" { + size = usize::from_str_radix(value, 16).unwrap_or(0); + } + } + + if phys == 0 || size == 0 { + // No live disk data, no need to say anything or exit with error + std::process::exit(0); + } + + redox_daemon::Daemon::new(move |daemon| { + let event_queue = event::EventQueue::new().unwrap(); + + event::user_data! { + enum Event { + Scheme, + } + }; + + let mut scheme = DiskScheme::new( + Some(daemon), + "disk.live".to_owned(), + BTreeMap::from([( + 0, + LiveDisk::new(phys, size).unwrap_or_else(|err| { + eprintln!("failed to initialize livedisk scheme: {}", err); + std::process::exit(1) + }), + )]), + &TrivialExecutor, + ); + + libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + + event_queue + .subscribe( + scheme.event_handle().raw(), + Event::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + for event in event_queue { + match event.unwrap().user_data { + Event::Scheme => TrivialExecutor.block_on(scheme.tick()).unwrap(), + } + } + + std::process::exit(0); + }) + .map_err(|err| anyhow!("failed to start daemon: {}", err))?; +} diff --git a/drivers/storage/nvmed/.gitignore b/drivers/storage/nvmed/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/storage/nvmed/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/storage/nvmed/Cargo.toml b/drivers/storage/nvmed/Cargo.toml new file mode 100644 index 0000000000..1d00992081 --- /dev/null +++ b/drivers/storage/nvmed/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "nvmed" +version = "0.1.0" +edition = "2021" + +[dependencies] +arrayvec = "0.7" +bitflags = "2" +futures = "0.3" +libredox = "0.1.3" +log = "0.4" +parking_lot = "0.12.1" +redox-daemon = "0.1" +redox_event = "0.4.1" +redox_syscall = { version = "0.5", features = ["std"] } +smallvec = "1" + +executor = { path = "../../executor" } +common = { path = "../../common" } +driver-block = { path = "../driver-block" } +partitionlib = { path = "../partitionlib" } +pcid = { path = "../../pcid" } + +[features] +default = [] diff --git a/drivers/storage/nvmed/src/main.rs b/drivers/storage/nvmed/src/main.rs new file mode 100644 index 0000000000..4437ddde3c --- /dev/null +++ b/drivers/storage/nvmed/src/main.rs @@ -0,0 +1,154 @@ +use std::cell::RefCell; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::os::fd::AsRawFd; +use std::rc::Rc; +use std::sync::Arc; +use std::usize; + +use driver_block::{Disk, DiskScheme}; +use pcid_interface::{irq_helpers, PciFunctionHandle}; + +use crate::nvme::NvmeNamespace; + +use self::nvme::Nvme; + +mod nvme; + +struct NvmeDisk { + nvme: Arc, + ns: NvmeNamespace, +} + +impl Disk for NvmeDisk { + fn block_size(&self) -> u32 { + self.ns.block_size.try_into().unwrap() + } + + fn size(&self) -> u64 { + self.ns.blocks * self.ns.block_size + } + + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { + self.nvme.namespace_read(&self.ns, block, buffer).await + } + + async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result { + self.nvme.namespace_write(&self.ns, block, buffer).await + } +} + +fn time_arm(time_handle: &mut File, secs: i64) -> io::Result<()> { + let mut time_buf = [0_u8; core::mem::size_of::()]; + if time_handle.read(&mut time_buf)? < time_buf.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "time read too small", + )); + } + + match libredox::data::timespec_from_mut_bytes(&mut time_buf) { + time => { + time.tv_sec += secs; + } + } + time_handle.write(&time_buf)?; + Ok(()) +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("nvmed: failed to daemonize"); +} +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let scheme_name = format!("disk.{}-nvme", pci_config.func.name()); + + common::setup_logging( + "disk", + "pci", + &scheme_name, + common::output_level(), + common::file_level(), + ); + + log::debug!("NVME PCI CONFIG: {:?}", pci_config); + + let address = unsafe { pcid_handle.map_bar(0).ptr }; + + let interrupt_vector = irq_helpers::pci_allocate_interrupt_vector(&mut pcid_handle, "nvmed"); + let iv = interrupt_vector.vector(); + let irq_handle = interrupt_vector.irq_handle().try_clone().unwrap(); + + let mut nvme = Nvme::new(address.as_ptr() as usize, interrupt_vector, pcid_handle) + .expect("nvmed: failed to allocate driver data"); + + unsafe { nvme.init().expect("nvmed: failed to init") } + log::debug!("Finished base initialization"); + let nvme = Arc::new(nvme); + + let executor = nvme::executor::init(Arc::clone(&nvme), iv, false /* FIXME */, irq_handle); + + let mut time_handle = File::open(&format!("/scheme/time/{}", libredox::flag::CLOCK_MONOTONIC)) + .expect("failed to open time handle"); + + let mut time_events = Box::pin( + executor.register_external_event(time_handle.as_raw_fd() as usize, event::EventFlags::READ), + ); + + // Try to init namespaces for 5 seconds + time_arm(&mut time_handle, 5).expect("failed to arm timer"); + let namespaces = executor.block_on(async { + let namespaces_future = nvme.init_with_queues(); + let time_future = time_events.as_mut().next(); + futures::pin_mut!(namespaces_future); + futures::pin_mut!(time_future); + match futures::future::select(namespaces_future, time_future).await { + futures::future::Either::Left((namespaces, _)) => namespaces, + futures::future::Either::Right(_) => panic!("timeout on init"), + } + }); + log::debug!("Initialized!"); + + let scheme = Rc::new(RefCell::new(DiskScheme::new( + Some(daemon), + scheme_name, + namespaces + .into_iter() + .map(|(k, ns)| { + ( + k, + NvmeDisk { + nvme: nvme.clone(), + ns, + }, + ) + }) + .collect(), + &*executor, + ))); + + let mut scheme_events = Box::pin(executor.register_external_event( + scheme.borrow().event_handle().raw(), + event::EventFlags::READ, + )); + + libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + + log::debug!("Starting to listen for scheme events"); + + executor.block_on(async { + loop { + log::trace!("new event iteration"); + if let Err(err) = scheme.borrow_mut().tick().await { + log::error!("scheme error: {err}"); + } + let _ = scheme_events.as_mut().next().await; + } + }); + + //TODO: destroy NVMe stuff + + std::process::exit(0); +} diff --git a/drivers/storage/nvmed/src/nvme/cmd.rs b/drivers/storage/nvmed/src/nvme/cmd.rs new file mode 100644 index 0000000000..b3567d9923 --- /dev/null +++ b/drivers/storage/nvmed/src/nvme/cmd.rs @@ -0,0 +1,162 @@ +use super::NvmeCmd; + +impl NvmeCmd { + pub fn create_io_completion_queue( + cid: u16, + qid: u16, + ptr: usize, + size: u16, + iv: Option, + ) -> Self { + const DW11_PHYSICALLY_CONTIGUOUS_BIT: u32 = 0x0000_0001; + const DW11_ENABLE_INTERRUPTS_BIT: u32 = 0x0000_0002; + const DW11_INTERRUPT_VECTOR_SHIFT: u8 = 16; + + Self { + opcode: 5, + flags: 0, + cid, + nsid: 0, + _rsvd: 0, + mptr: 0, + dptr: [ptr as u64, 0], + cdw10: ((size as u32) << 16) | (qid as u32), + + cdw11: DW11_PHYSICALLY_CONTIGUOUS_BIT + | if let Some(iv) = iv { + // enable interrupts if a vector is present + DW11_ENABLE_INTERRUPTS_BIT | (u32::from(iv) << DW11_INTERRUPT_VECTOR_SHIFT) + } else { + 0 + }, + + cdw12: 0, + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } + + pub fn create_io_submission_queue( + cid: u16, + qid: u16, + ptr: usize, + size: u16, + cqid: u16, + ) -> Self { + Self { + opcode: 1, + flags: 0, + cid, + nsid: 0, + _rsvd: 0, + mptr: 0, + dptr: [ptr as u64, 0], + cdw10: ((size as u32) << 16) | (qid as u32), + cdw11: ((cqid as u32) << 16) | 1, /* Physically Contiguous */ + //TODO: QPRIO + cdw12: 0, //TODO: NVMSETID + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } + + pub fn identify_namespace(cid: u16, ptr: usize, nsid: u32) -> Self { + Self { + opcode: 6, + flags: 0, + cid, + nsid, + _rsvd: 0, + mptr: 0, + dptr: [ptr as u64, 0], + cdw10: 0, + cdw11: 0, + cdw12: 0, + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } + + pub fn identify_controller(cid: u16, ptr: usize) -> Self { + Self { + opcode: 6, + flags: 0, + cid, + nsid: 0, + _rsvd: 0, + mptr: 0, + dptr: [ptr as u64, 0], + cdw10: 1, + cdw11: 0, + cdw12: 0, + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } + + pub fn identify_namespace_list(cid: u16, ptr: usize, base: u32) -> Self { + Self { + opcode: 6, + flags: 0, + cid, + nsid: base, + _rsvd: 0, + mptr: 0, + dptr: [ptr as u64, 0], + cdw10: 2, + cdw11: 0, + cdw12: 0, + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } + pub fn get_features(cid: u16, ptr: usize, fid: u8) -> Self { + Self { + opcode: 0xA, + dptr: [ptr as u64, 0], + cdw10: u32::from(fid), // TODO: SEL + ..Default::default() + } + } + + pub fn io_read(cid: u16, nsid: u32, lba: u64, blocks_1: u16, ptr0: u64, ptr1: u64) -> Self { + Self { + opcode: 2, + flags: 0, + cid, + nsid, + _rsvd: 0, + mptr: 0, + dptr: [ptr0, ptr1], + cdw10: lba as u32, + cdw11: (lba >> 32) as u32, + cdw12: blocks_1 as u32, + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } + + pub fn io_write(cid: u16, nsid: u32, lba: u64, blocks_1: u16, ptr0: u64, ptr1: u64) -> Self { + Self { + opcode: 1, + flags: 0, + cid, + nsid, + _rsvd: 0, + mptr: 0, + dptr: [ptr0, ptr1], + cdw10: lba as u32, + cdw11: (lba >> 32) as u32, + cdw12: blocks_1 as u32, + cdw13: 0, + cdw14: 0, + cdw15: 0, + } + } +} diff --git a/drivers/storage/nvmed/src/nvme/executor.rs b/drivers/storage/nvmed/src/nvme/executor.rs new file mode 100644 index 0000000000..6242fa98cb --- /dev/null +++ b/drivers/storage/nvmed/src/nvme/executor.rs @@ -0,0 +1,82 @@ +use std::cell::RefCell; +use std::fs::File; +use std::rc::Rc; +use std::sync::Arc; + +use executor::{Hardware, LocalExecutor}; + +use super::{CmdId, CqId, Nvme, NvmeCmd, NvmeComp, SqId}; + +pub struct NvmeHw; + +impl Hardware for NvmeHw { + type Iv = u16; + type Sqe = NvmeCmd; + type Cqe = NvmeComp; + type CmdId = CmdId; + type CqId = CqId; + type SqId = SqId; + type GlobalCtxt = Arc; + + fn mask_vector(ctxt: &Arc, iv: Self::Iv) { + ctxt.set_vector_masked(iv, true) + } + fn unmask_vector(ctxt: &Arc, iv: Self::Iv) { + ctxt.set_vector_masked(iv, false) + } + fn set_sqe_cmdid(sqe: &mut NvmeCmd, id: CmdId) { + sqe.cid = id; + } + fn get_cqe_cmdid(cqe: &Self::Cqe) -> Self::CmdId { + cqe.cid + } + fn vtable() -> &'static std::task::RawWakerVTable { + &VTABLE + } + fn current() -> std::rc::Rc> { + THE_EXECUTOR.with(|exec| Rc::clone(exec.borrow().as_ref().unwrap())) + } + fn try_submit( + nvme: &Arc, + sq_id: Self::SqId, + success: impl FnOnce(Self::CmdId) -> Self::Sqe, + fail: impl FnOnce(), + ) -> Option<(Self::CqId, Self::CmdId)> { + let ctxt = nvme.cur_thread_ctxt(); + let ctxt = ctxt.lock(); + + nvme.try_submit_raw(&*ctxt, sq_id, success, fail) + } + fn poll_cqes(nvme: &Arc, mut handle: impl FnMut(Self::CqId, Self::Cqe)) { + let ctxt = nvme.cur_thread_ctxt(); + let ctxt = ctxt.lock(); + + for (sq_cq_id, (sq, cq)) in ctxt.queues.borrow_mut().iter_mut() { + while let Some((new_head, cqe)) = cq.complete() { + unsafe { + nvme.completion_queue_head(*sq_cq_id, new_head); + } + sq.head = cqe.sq_head; + log::trace!("new head {new_head} cqe {cqe:?}"); + handle(*sq_cq_id, cqe); + } + } + } + fn sq_cq(_ctxt: &Arc, id: Self::CqId) -> Self::SqId { + id + } +} + +static VTABLE: std::task::RawWakerVTable = executor::vtable::(); + +thread_local! { + static THE_EXECUTOR: RefCell>>> = RefCell::new(None); +} + +pub type NvmeExecutor = LocalExecutor; + +pub fn init(nvme: Arc, iv: u16, intx: bool, irq_handle: File) -> Rc> { + let this = Rc::new(executor::init_raw(nvme, iv, intx, irq_handle)); + THE_EXECUTOR.with(|exec| *exec.borrow_mut() = Some(Rc::clone(&this))); + this +} diff --git a/drivers/storage/nvmed/src/nvme/identify.rs b/drivers/storage/nvmed/src/nvme/identify.rs new file mode 100644 index 0000000000..05e5b9b2b6 --- /dev/null +++ b/drivers/storage/nvmed/src/nvme/identify.rs @@ -0,0 +1,228 @@ +use super::{Nvme, NvmeCmd, NvmeNamespace}; + +use common::dma::Dma; + +/// See NVME spec section 5.15.2.2. +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct IdentifyControllerData { + /// PCI vendor ID, always the same as in the PCI function header. + pub vid: u16, + /// PCI subsystem vendor ID. + pub ssvid: u16, + /// ASCII + pub serial_no: [u8; 20], + /// ASCII + pub model_no: [u8; 48], + /// ASCII + pub firmware_rev: [u8; 8], + // TODO: Lots of fields + pub _4k_pad: [u8; 4096 - 72], +} + +/// See NVME spec section 5.15.2.1. +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct IdentifyNamespaceData { + pub nsze: u64, + pub ncap: u64, + pub nuse: u64, + + pub nsfeat: u8, + pub nlbaf: u8, + pub flbas: u8, + pub mc: u8, + + pub dpc: u8, + pub dps: u8, + pub nmic: u8, + pub rescap: u8, + // 32 + pub fpi: u8, + pub dlfeat: u8, + pub nawun: u16, + + pub nawupf: u16, + pub nacwu: u16, + // 40 + pub nabsn: u16, + pub nabo: u16, + + pub nabspf: u16, + pub noiob: u16, + // 48 + pub nvmcap: u128, + // 64 + pub npwg: u16, + pub npwa: u16, + pub npdg: u16, + pub npda: u16, + // 72 + pub nows: u16, + pub _rsvd1: [u8; 18], + // 92 + pub anagrpid: u32, + pub _rsvd2: [u8; 3], + pub nsattr: u8, + + // 100 + pub nvmsetid: u16, + pub endgid: u16, + pub nguid: [u8; 16], + pub eui64: u64, + + pub lba_format_support: [LbaFormat; 16], + pub _rsvd3: [u8; 192], + pub vendor_specific: [u8; 3712], +} + +impl IdentifyNamespaceData { + pub fn size_in_blocks(&self) -> u64 { + self.nsze + } + pub fn capacity_in_blocks(&self) -> u64 { + self.ncap + } + /// Guaranteed to be within 0..=15 + pub fn formatted_lba_size_idx(&self) -> usize { + (self.flbas & 0xF) as usize + } + pub fn formatted_lba_size(&self) -> &LbaFormat { + &self.lba_format_support[self.formatted_lba_size_idx()] + } + pub fn has_metadata_after_data(&self) -> bool { + (self.flbas & (1 << 4)) != 0 + } +} + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct LbaFormat(pub u32); + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum RelativePerformance { + Best = 0b00, + Better, + Good, + Degraded, +} +impl Ord for RelativePerformance { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // higher performance is better, hence reversed + Ord::cmp(&(*self as u8), &(*other as u8)).reverse() + } +} +impl PartialOrd for RelativePerformance { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} + +impl LbaFormat { + pub fn relative_performance(&self) -> RelativePerformance { + match ((self.0 >> 24) & 0b11) { + 0b00 => RelativePerformance::Best, + 0b01 => RelativePerformance::Better, + 0b10 => RelativePerformance::Good, + 0b11 => RelativePerformance::Degraded, + _ => unreachable!(), + } + } + pub fn is_available(&self) -> bool { + self.log_lba_data_size() != 0 + } + pub fn log_lba_data_size(&self) -> u8 { + ((self.0 >> 16) & 0xFF) as u8 + } + pub fn lba_data_size(&self) -> Option { + if self.log_lba_data_size() < 9 { + return None; + } + if self.log_lba_data_size() >= 32 { + return None; + } + Some(1u64 << self.log_lba_data_size()) + } + pub fn metadata_size(&self) -> u16 { + (self.0 & 0xFFFF) as u16 + } +} + +impl Nvme { + /// Returns the serial number, model, and firmware, in that order. + pub async fn identify_controller(&self) { + // TODO: Use same buffer + let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; + + // println!(" - Attempting to identify controller"); + let comp = self + .submit_and_complete_admin_command(|cid| { + NvmeCmd::identify_controller(cid, data.physical()) + }) + .await; + log::trace!("Completion: {:?}", comp); + + // println!(" - Dumping identify controller"); + + let model_cow = String::from_utf8_lossy(&data.model_no); + let serial_cow = String::from_utf8_lossy(&data.serial_no); + let fw_cow = String::from_utf8_lossy(&data.firmware_rev); + + let model = model_cow.trim(); + let serial = serial_cow.trim(); + let firmware = fw_cow.trim(); + + log::info!( + " - Model: {} Serial: {} Firmware: {}", + model, + serial, + firmware, + ); + } + pub async fn identify_namespace_list(&self, base: u32) -> Vec { + // TODO: Use buffer + let data: Dma<[u32; 1024]> = unsafe { Dma::zeroed().unwrap().assume_init() }; + + // println!(" - Attempting to retrieve namespace ID list"); + let comp = self + .submit_and_complete_admin_command(|cid| { + NvmeCmd::identify_namespace_list(cid, data.physical(), base) + }) + .await; + + log::trace!("Completion2: {:?}", comp); + + // println!(" - Dumping namespace ID list"); + data.iter().copied().take_while(|&nsid| nsid != 0).collect() + } + pub async fn identify_namespace(&self, nsid: u32) -> NvmeNamespace { + //TODO: Use buffer + let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; + + log::debug!("Attempting to identify namespace {nsid}"); + let comp = self + .submit_and_complete_admin_command(|cid| { + NvmeCmd::identify_namespace(cid, data.physical(), nsid) + }) + .await; + + log::debug!("Dumping identify namespace"); + + let size = data.size_in_blocks(); + let capacity = data.capacity_in_blocks(); + log::info!("NSID: {} Size: {} Capacity: {}", nsid, size, capacity); + + let block_size = data + .formatted_lba_size() + .lba_data_size() + .expect("nvmed: error: size outside 512-2^64 range"); + log::debug!("NVME block size: {}", block_size); + + NvmeNamespace { + id: nsid, + blocks: size, + block_size, + } + } +} diff --git a/drivers/storage/nvmed/src/nvme/mod.rs b/drivers/storage/nvmed/src/nvme/mod.rs new file mode 100644 index 0000000000..682ee93329 --- /dev/null +++ b/drivers/storage/nvmed/src/nvme/mod.rs @@ -0,0 +1,541 @@ +use std::cell::RefCell; +use std::collections::{BTreeMap, HashMap}; +use std::convert::TryFrom; +use std::iter; +use std::sync::atomic::AtomicU16; +use std::sync::Arc; + +use parking_lot::{Mutex, ReentrantMutex, RwLock}; +use pcid_interface::irq_helpers::InterruptVector; + +use common::io::{Io, Mmio}; +use common::timeout::Timeout; +use syscall::error::{Error, Result, EIO}; + +use common::dma::Dma; + +pub mod cmd; +pub mod executor; +pub mod identify; +pub mod queues; + +use self::executor::NvmeExecutor; +pub use self::queues::{NvmeCmd, NvmeCmdQueue, NvmeComp, NvmeCompQueue}; + +use pcid_interface::PciFunctionHandle; + +#[repr(C, packed)] +pub struct NvmeRegs { + /// Controller Capabilities + cap_low: Mmio, + cap_high: Mmio, + /// Version + vs: Mmio, + /// Interrupt mask set + intms: Mmio, + /// Interrupt mask clear + intmc: Mmio, + /// Controller configuration + cc: Mmio, + /// Reserved + _rsvd: Mmio, + /// Controller status + csts: Mmio, + /// NVM subsystem reset + nssr: Mmio, + /// Admin queue attributes + aqa: Mmio, + /// Admin submission queue base address + asq_low: Mmio, + asq_high: Mmio, + /// Admin completion queue base address + acq_low: Mmio, + acq_high: Mmio, + /// Controller memory buffer location + cmbloc: Mmio, + /// Controller memory buffer size + cmbsz: Mmio, +} + +#[derive(Copy, Clone, Debug)] +pub struct NvmeNamespace { + pub id: u32, + pub blocks: u64, + pub block_size: u64, +} + +pub type CqId = u16; +pub type SqId = u16; +pub type CmdId = u16; +pub type AtomicCqId = AtomicU16; +pub type AtomicSqId = AtomicU16; +pub type AtomicCmdId = AtomicU16; +pub type Iv = u16; + +pub struct Nvme { + interrupt_vector: Mutex, + pcid_interface: Mutex, + regs: RwLock<&'static mut NvmeRegs>, + + sq_ivs: RwLock>, + cq_ivs: RwLock>, + + // maps interrupt vectors with the completion queues they have + thread_ctxts: RwLock>>>, + + next_sqid: AtomicSqId, + next_cqid: AtomicCqId, +} + +pub struct ThreadCtxt { + buffer: RefCell>, // 2MB of buffer + buffer_prp: RefCell>, // 4KB of PRP for the buffer + + // Yes, technically NVME allows multiple submission queues to be mapped to the same completion + // queue, but we don't use that feature. + queues: RefCell>, +} + +unsafe impl Send for Nvme {} +unsafe impl Sync for Nvme {} + +/// How to handle full submission queues. +pub enum FullSqHandling { + /// Return an error immediately prior to posting the command. + ErrorDirectly, + + /// Tell the executor that we want to be notified when a command on the same submission queue + /// has been completed. + Wait, +} + +impl Nvme { + pub fn new( + address: usize, + interrupt_vector: InterruptVector, + pcid_interface: PciFunctionHandle, + ) -> Result { + Ok(Nvme { + regs: RwLock::new(unsafe { &mut *(address as *mut NvmeRegs) }), + thread_ctxts: RwLock::new( + iter::once(( + 0_u16, + Arc::new(ReentrantMutex::new(ThreadCtxt { + buffer: RefCell::new(unsafe { Dma::zeroed()?.assume_init() }), + buffer_prp: RefCell::new(unsafe { Dma::zeroed()?.assume_init() }), + + queues: RefCell::new( + iter::once((0, (NvmeCmdQueue::new()?, NvmeCompQueue::new()?))) + .collect(), + ), + })), + )) + .collect(), + ), + + cq_ivs: RwLock::new(iter::once((0, 0)).collect()), + sq_ivs: RwLock::new(iter::once((0, 0)).collect()), + + interrupt_vector: Mutex::new(interrupt_vector), + pcid_interface: Mutex::new(pcid_interface), + + // TODO + next_sqid: AtomicSqId::new(2), + next_cqid: AtomicCqId::new(2), + }) + } + /// Write to a doorbell register. + /// + /// # Locking + /// Locks `regs`. + unsafe fn doorbell_write(&self, index: usize, value: u32) { + use std::ops::DerefMut; + + let mut regs_guard = self.regs.write(); + let regs: &mut NvmeRegs = regs_guard.deref_mut(); + + let dstrd = (regs.cap_high.read() & 0b1111) as usize; + let addr = (regs as *mut NvmeRegs as usize) + 0x1000 + index * (4 << dstrd); + (&mut *(addr as *mut Mmio)).write(value); + } + fn cur_thread_ctxt(&self) -> Arc> { + // TODO: multi-threading + Arc::clone(self.thread_ctxts.read().get(&0).unwrap()) + } + + pub unsafe fn submission_queue_tail(&self, qid: u16, tail: u16) { + self.doorbell_write(2 * (qid as usize), u32::from(tail)); + } + + pub unsafe fn completion_queue_head(&self, qid: u16, head: u16) { + self.doorbell_write(2 * (qid as usize) + 1, u32::from(head)); + } + + pub unsafe fn init(&mut self) -> Result<()> { + let thread_ctxts = self.thread_ctxts.get_mut(); + { + let regs = self.regs.read(); + log::debug!("CAP_LOW: {:X}", regs.cap_low.read()); + log::debug!("CAP_HIGH: {:X}", regs.cap_high.read()); + log::debug!("VS: {:X}", regs.vs.read()); + log::debug!("CC: {:X}", regs.cc.read()); + log::debug!("CSTS: {:X}", regs.csts.read()); + } + + log::debug!("Disabling controller."); + self.regs.get_mut().cc.writef(1, false); + + { + log::trace!("Waiting for not ready."); + let timeout = Timeout::from_secs(1); + loop { + let csts = self.regs.get_mut().csts.read(); + log::trace!("CSTS: {:X}", csts); + if csts & 1 == 1 { + timeout.run().map_err(|()| { + log::error!("failed to wait for not ready"); + Error::new(EIO) + })?; + } else { + break; + } + } + } + + if !self.interrupt_vector.get_mut().set_masked_if_fast(false) { + self.regs.get_mut().intms.write(0xFFFF_FFFF); + self.regs.get_mut().intmc.write(0x0000_0001); + } + + for (qid, iv) in self.cq_ivs.get_mut().iter_mut() { + let ctxt = thread_ctxts.get(&0).unwrap().lock(); + let queues = ctxt.queues.borrow(); + + let &(ref cq, ref sq) = queues.get(qid).unwrap(); + log::debug!( + "iv {iv} [cq {qid}: {:X}, {}] [sq {qid}: {:X}, {}]", + cq.data.physical(), + cq.data.len(), + sq.data.physical(), + sq.data.len() + ); + } + + { + let main_ctxt = thread_ctxts.get(&0).unwrap().lock(); + + for (i, prp) in main_ctxt.buffer_prp.borrow_mut().iter_mut().enumerate() { + *prp = (main_ctxt.buffer.borrow_mut().physical() + i * 4096) as u64; + } + + let regs = self.regs.get_mut(); + + let mut queues = main_ctxt.queues.borrow_mut(); + let (asq, acq) = queues.get_mut(&0).unwrap(); + regs.aqa + .write(((acq.data.len() as u32 - 1) << 16) | (asq.data.len() as u32 - 1)); + regs.asq_low.write(asq.data.physical() as u32); + regs.asq_high + .write((asq.data.physical() as u64 >> 32) as u32); + regs.acq_low.write(acq.data.physical() as u32); + regs.acq_high + .write((acq.data.physical() as u64 >> 32) as u32); + + // Set IOCQES, IOSQES, AMS, MPS, and CSS + let mut cc = regs.cc.read(); + cc &= 0xFF00000F; + cc |= (4 << 20) | (6 << 16); + regs.cc.write(cc); + } + + log::debug!("Enabling controller."); + self.regs.get_mut().cc.writef(1, true); + + { + log::debug!("Waiting for ready"); + let timeout = Timeout::from_secs(1); + loop { + let csts = self.regs.get_mut().csts.read(); + log::debug!("CSTS: {:X}", csts); + if csts & 1 == 0 { + timeout.run().map_err(|()| { + log::error!("failed to wait for ready"); + Error::new(EIO) + })?; + } else { + break; + } + } + } + + Ok(()) + } + + pub fn set_vector_masked(&self, vector: u16, masked: bool) { + let mut interrupt_vector_guard = (&self).interrupt_vector.lock(); + + if !interrupt_vector_guard.set_masked_if_fast(masked) { + let mut to_mask = 0x0000_0000; + let mut to_clear = 0x0000_0000; + + let vector = vector as u8; + + if masked { + assert_ne!( + to_clear & (1 << vector), + (1 << vector), + "nvmed: internal error: cannot both mask and set" + ); + to_mask |= 1 << vector; + } else { + assert_ne!( + to_mask & (1 << vector), + (1 << vector), + "nvmed: internal error: cannot both mask and set" + ); + to_clear |= 1 << vector; + } + + if to_mask != 0 { + (&self).regs.write().intms.write(to_mask); + } + if to_clear != 0 { + (&self).regs.write().intmc.write(to_clear); + } + } + } + + pub async fn submit_and_complete_command( + &self, + sq_id: SqId, + cmd_init: impl FnOnce(CmdId) -> NvmeCmd, + ) -> NvmeComp { + NvmeExecutor::current().submit(sq_id, cmd_init(0)).await + } + + pub async fn submit_and_complete_admin_command( + &self, + cmd_init: impl FnOnce(CmdId) -> NvmeCmd, + ) -> NvmeComp { + self.submit_and_complete_command(0, cmd_init).await + } + pub fn try_submit_raw( + &self, + ctxt: &ThreadCtxt, + sq_id: SqId, + cmd_init: impl FnOnce(CmdId) -> NvmeCmd, + fail: impl FnOnce(), + ) -> Option<(CqId, CmdId)> { + match ctxt.queues.borrow_mut().get_mut(&sq_id).unwrap() { + (sq, _cq) => { + if sq.is_full() { + fail(); + return None; + } + let cmd_id = sq.tail; + let tail = sq.submit_unchecked(cmd_init(cmd_id)); + + // TODO: Submit in bulk + unsafe { + self.submission_queue_tail(sq_id, tail); + } + Some((sq_id, cmd_id)) + } + } + } + + pub async fn create_io_completion_queue( + &self, + io_cq_id: CqId, + vector: Option, + ) -> NvmeCompQueue { + let queue = NvmeCompQueue::new().expect("nvmed: failed to allocate I/O completion queue"); + + let len = u16::try_from(queue.data.len()) + .expect("nvmed: internal error: I/O CQ longer than 2^16 entries"); + let raw_len = len + .checked_sub(1) + .expect("nvmed: internal error: CQID 0 for I/O CQ"); + + let comp = self + .submit_and_complete_admin_command(|cid| { + NvmeCmd::create_io_completion_queue( + cid, + io_cq_id, + queue.data.physical(), + raw_len, + vector, + ) + }) + .await; + + /*match comp.status.specific { + 1 => panic!("invalid queue identifier"), + 2 => panic!("invalid queue size"), + 8 => panic!("invalid interrupt vector"), + _ => (), + }*/ + + queue + } + pub async fn create_io_submission_queue(&self, io_sq_id: SqId, io_cq_id: CqId) -> NvmeCmdQueue { + let q = NvmeCmdQueue::new().expect("failed to create submission queue"); + + let len = u16::try_from(q.data.len()) + .expect("nvmed: internal error: I/O SQ longer than 2^16 entries"); + let raw_len = len + .checked_sub(1) + .expect("nvmed: internal error: SQID 0 for I/O SQ"); + + let comp = self + .submit_and_complete_admin_command(|cid| { + NvmeCmd::create_io_submission_queue( + cid, + io_sq_id, + q.data.physical(), + raw_len, + io_cq_id, + ) + }) + .await; + /*match comp.status.specific { + 0 => panic!("completion queue invalid"), + 1 => panic!("invalid queue identifier"), + 2 => panic!("invalid queue size"), + _ => (), + }*/ + + q + } + + pub async fn init_with_queues(&self) -> BTreeMap { + log::trace!("preinit"); + + self.identify_controller().await; + + let nsids = self.identify_namespace_list(0).await; + + log::debug!("first commands"); + + let mut namespaces = BTreeMap::new(); + + for nsid in nsids.iter().copied() { + namespaces.insert(nsid, self.identify_namespace(nsid).await); + } + + // TODO: Multiple queues + let cq = self.create_io_completion_queue(1, Some(0)).await; + log::trace!("created compq"); + let sq = self.create_io_submission_queue(1, 1).await; + log::trace!("created subq"); + self.thread_ctxts + .read() + .get(&0) + .unwrap() + .lock() + .queues + .borrow_mut() + .insert(1, (sq, cq)); + self.sq_ivs.write().insert(1, 0); + self.cq_ivs.write().insert(1, 0); + + namespaces + } + + async fn namespace_rw( + &self, + ctxt: &ThreadCtxt, + namespace: &NvmeNamespace, + lba: u64, + blocks_1: u16, + write: bool, + ) -> Result<()> { + let block_size = namespace.block_size; + + let prp = ctxt.buffer_prp.borrow_mut(); + let bytes = ((blocks_1 as u64) + 1) * block_size; + let (ptr0, ptr1) = if bytes <= 4096 { + (prp[0], 0) + } else if bytes <= 8192 { + (prp[0], prp[1]) + } else { + (prp[0], (prp.physical() + 8) as u64) + }; + + let mut cmd = NvmeCmd::default(); + let comp = self + .submit_and_complete_command(1, |cid| { + cmd = if write { + NvmeCmd::io_write(cid, namespace.id, lba, blocks_1, ptr0, ptr1) + } else { + NvmeCmd::io_read(cid, namespace.id, lba, blocks_1, ptr0, ptr1) + }; + cmd.clone() + }) + .await; + + let status = comp.status >> 1; + if status == 0 { + Ok(()) + } else { + log::error!("command {:#x?} failed with status {:#x}", cmd, status); + Err(Error::new(EIO)) + } + } + + pub async fn namespace_read( + &self, + namespace: &NvmeNamespace, + mut lba: u64, + buf: &mut [u8], + ) -> Result { + let ctxt = self.cur_thread_ctxt(); + let ctxt = ctxt.lock(); + + let block_size = namespace.block_size as usize; + + for chunk in buf.chunks_mut(/* TODO: buf len */ 8192) { + let blocks = (chunk.len() + block_size - 1) / block_size; + + assert!(blocks > 0); + assert!(blocks <= 0x1_0000); + + self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, false) + .await?; + + chunk.copy_from_slice(&ctxt.buffer.borrow()[..chunk.len()]); + + lba += blocks as u64; + } + + Ok(buf.len()) + } + + pub async fn namespace_write( + &self, + namespace: &NvmeNamespace, + mut lba: u64, + buf: &[u8], + ) -> Result { + let ctxt = self.cur_thread_ctxt(); + let ctxt = ctxt.lock(); + + let block_size = namespace.block_size as usize; + + for chunk in buf.chunks(/* TODO: buf len */ 8192) { + let blocks = (chunk.len() + block_size - 1) / block_size; + + assert!(blocks > 0); + assert!(blocks <= 0x1_0000); + + ctxt.buffer.borrow_mut()[..chunk.len()].copy_from_slice(chunk); + + self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, true) + .await?; + + lba += blocks as u64; + } + + Ok(buf.len()) + } +} diff --git a/drivers/storage/nvmed/src/nvme/queues.rs b/drivers/storage/nvmed/src/nvme/queues.rs new file mode 100644 index 0000000000..a3712aeb74 --- /dev/null +++ b/drivers/storage/nvmed/src/nvme/queues.rs @@ -0,0 +1,151 @@ +use std::cell::UnsafeCell; +use std::ptr; +use syscall::Result; + +use common::dma::Dma; + +/// A submission queue entry. +#[derive(Clone, Copy, Debug, Default)] +#[repr(C, packed)] +pub struct NvmeCmd { + /// Opcode + pub opcode: u8, + /// Flags + pub flags: u8, + /// Command ID + pub cid: u16, + /// Namespace identifier + pub nsid: u32, + /// Reserved + pub _rsvd: u64, + /// Metadata pointer + pub mptr: u64, + /// Data pointer + pub dptr: [u64; 2], + /// Command dword 10 + pub cdw10: u32, + /// Command dword 11 + pub cdw11: u32, + /// Command dword 12 + pub cdw12: u32, + /// Command dword 13 + pub cdw13: u32, + /// Command dword 14 + pub cdw14: u32, + /// Command dword 15 + pub cdw15: u32, +} + +/// A completion queue entry. +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct NvmeComp { + pub command_specific: u32, + pub _rsvd: u32, + pub sq_head: u16, + pub sq_id: u16, + pub cid: u16, + pub status: u16, +} + +/// Completion queue +pub struct NvmeCompQueue { + pub data: Dma<[UnsafeCell]>, + pub head: u16, + pub phase: bool, +} + +impl NvmeCompQueue { + pub fn new() -> Result { + Ok(Self { + data: unsafe { Dma::zeroed_slice(256)?.assume_init() }, + head: 0, + phase: true, + }) + } + + /// Get a new completion queue entry, or return None if no entry is available yet. + pub(crate) fn complete(&mut self) -> Option<(u16, NvmeComp)> { + let entry = unsafe { ptr::read_volatile(self.data[usize::from(self.head)].get()) }; + + if ((entry.status & 1) == 1) == self.phase { + self.head = (self.head + 1) % (self.data.len() as u16); + if self.head == 0 { + self.phase = !self.phase; + } + Some((self.head, entry)) + } else { + None + } + } + + /// Get a new CQ entry, busy waiting until an entry appears. + pub fn complete_spin(&mut self) -> (u16, NvmeComp) { + log::debug!("Waiting for new CQ entry"); + loop { + if let Some(some) = self.complete() { + return some; + } else { + unsafe { + std::hint::spin_loop(); + } + } + } + } +} + +/// Submission queue +pub struct NvmeCmdQueue { + pub data: Dma<[UnsafeCell]>, + pub tail: u16, + pub head: u16, +} + +impl NvmeCmdQueue { + pub fn new() -> Result { + Ok(Self { + data: unsafe { Dma::zeroed_slice(64)?.assume_init() }, + tail: 0, + head: 0, + }) + } + + pub fn is_empty(&self) -> bool { + self.head == self.tail + } + pub fn is_full(&self) -> bool { + self.head == self.tail + 1 + } + + /// Add a new submission command entry to the queue. The caller must ensure that the queue have free + /// entries; this can be checked using `is_full`. + pub fn submit_unchecked(&mut self, entry: NvmeCmd) -> u16 { + unsafe { ptr::write_volatile(self.data[usize::from(self.tail)].get(), entry) } + self.tail = (self.tail + 1) % (self.data.len() as u16); + self.tail + } +} + +#[derive(Debug)] +pub enum Status { + GenericCmdStatus(u8), + CommandSpecificStatus(u8), + IntegrityError(u8), + PathRelatedStatus(u8), + Rsvd(u8), + Vendor(u8), +} +impl Status { + pub fn parse(raw: u16) -> Self { + let code = (raw >> 1) as u8; + match (raw >> 9) & 0b111 { + 0 => Self::GenericCmdStatus(code), + 1 => Self::CommandSpecificStatus(code), + 2 => Self::IntegrityError(code), + 3 => Self::PathRelatedStatus(code), + 4..=6 => Self::Rsvd(code), + 7 => Self::Vendor(code), + _ => unreachable!(), + } + } +} diff --git a/drivers/storage/partitionlib/Cargo.toml b/drivers/storage/partitionlib/Cargo.toml new file mode 100644 index 0000000000..8e5e3fad44 --- /dev/null +++ b/drivers/storage/partitionlib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "partitionlib" +version = "0.1.0" +authors = ["Deepak Sirone "] +edition = "2021" +license = "MIT" + +[dependencies] +gpt = { version = "3.0.1" } +scroll = { version = "0.10", features = ["derive"] } +uuid = { version = "1.0", features = ["v4"] } diff --git a/drivers/storage/partitionlib/resources/disk.img b/drivers/storage/partitionlib/resources/disk.img new file mode 100644 index 0000000000..76913628c2 Binary files /dev/null and b/drivers/storage/partitionlib/resources/disk.img differ diff --git a/drivers/storage/partitionlib/resources/disk_mbr.img b/drivers/storage/partitionlib/resources/disk_mbr.img new file mode 100644 index 0000000000..429d41ca6b Binary files /dev/null and b/drivers/storage/partitionlib/resources/disk_mbr.img differ diff --git a/drivers/storage/partitionlib/src/lib.rs b/drivers/storage/partitionlib/src/lib.rs new file mode 100644 index 0000000000..be369ce64e --- /dev/null +++ b/drivers/storage/partitionlib/src/lib.rs @@ -0,0 +1,3 @@ +mod mbr; +mod partition; +pub use self::partition::*; diff --git a/drivers/storage/partitionlib/src/mbr.rs b/drivers/storage/partitionlib/src/mbr.rs new file mode 100644 index 0000000000..67ae5a6dad --- /dev/null +++ b/drivers/storage/partitionlib/src/mbr.rs @@ -0,0 +1,57 @@ +use scroll::{Pread, Pwrite}; +use std::io::{self, Read, Seek}; + +#[derive(Clone, Copy, Debug, Pread, Pwrite)] +pub(crate) struct Entry { + pub(crate) drive_attrs: u8, + pub(crate) start_head: u8, + pub(crate) start_cs: u16, + pub(crate) sys_id: u8, + pub(crate) end_head: u8, + pub(crate) end_cs: u16, + pub(crate) rel_sector: u32, + pub(crate) len: u32, +} + +#[derive(Pread, Pwrite)] +pub(crate) struct Header { + pub(crate) bootstrap: [u8; 446], + pub(crate) first_entry: Entry, + pub(crate) second_entry: Entry, + pub(crate) third_entry: Entry, + pub(crate) fourth_entry: Entry, + pub(crate) last_signature: u16, // 0xAA55 +} + +pub(crate) fn read_header(device: &mut D) -> io::Result> { + device.seek(io::SeekFrom::Start(0))?; + + let mut bytes = [0u8; 512]; + device.read_exact(&mut bytes)?; + + let header: Header = bytes.pread_with(0, scroll::LE).unwrap(); + + if header.last_signature != 0xAA55 { + return Ok(None); + } + + Ok(Some(header)) +} + +impl Header { + pub(crate) fn partitions(&self) -> impl Iterator { + [ + self.first_entry, + self.second_entry, + self.third_entry, + self.fourth_entry, + ] + .into_iter() + .filter(Entry::is_valid) + } +} +impl Entry { + fn is_valid(&self) -> bool { + (self.drive_attrs == 0 || self.drive_attrs == 0x80) && self.len != 0 + } +} diff --git a/drivers/storage/partitionlib/src/partition.rs b/drivers/storage/partitionlib/src/partition.rs new file mode 100644 index 0000000000..949171511b --- /dev/null +++ b/drivers/storage/partitionlib/src/partition.rs @@ -0,0 +1,84 @@ +pub use gpt::disk::LogicalBlockSize; +use std::io::{self, Read, Seek}; +use uuid::Uuid; + +/// A union of the MBR and GPT partition entry +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Partition { + /// The starting logical block number + pub start_lba: u64, + /// The size of the partition in sectors + pub size: u64, + pub flags: Option, + pub name: Option, + pub uuid: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PartitionTableKind { + Mbr, + Gpt, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PartitionTable { + pub partitions: Vec, + pub kind: PartitionTableKind, +} + +fn get_gpt_partitions( + device: &mut D, + sector_size: LogicalBlockSize, +) -> io::Result { + let header = gpt::header::read_header_from_arbitrary_device(device, sector_size)?; + Ok(PartitionTable { + partitions: gpt::partition::file_read_partitions(device, &header, sector_size).map( + |btree| { + btree + .into_iter() + .map(|(_, part)| Partition { + flags: Some(part.flags), + size: part.last_lba - part.first_lba + 1, + name: Some(part.name.clone()), + uuid: Some(part.part_guid), + start_lba: part.first_lba, + }) + .collect() + }, + )?, + kind: PartitionTableKind::Gpt, + }) +} +fn get_mbr_partitions(device: &mut D) -> io::Result> { + let Some(header) = crate::mbr::read_header(device)? else { + return Ok(None); + }; + Ok(Some(PartitionTable { + kind: PartitionTableKind::Mbr, + partitions: header + .partitions() + .map(|partition: crate::mbr::Entry| Partition { + name: None, + uuid: None, // TODO: Some kind of one-way conversion should be possible + flags: None, // TODO + size: partition.len.into(), + start_lba: partition.rel_sector.into(), + }) + .collect(), + })) +} +pub fn get_partitions( + device: &mut D, + sector_size: LogicalBlockSize, +) -> io::Result> { + get_gpt_partitions(device, sector_size) + .map(Some) + .or_else(|_| get_mbr_partitions(device)) +} + +impl Partition { + pub fn to_offset(&self, sector_size: LogicalBlockSize) -> u64 { + let blksize: u64 = sector_size.into(); + self.start_lba * blksize + } +} diff --git a/drivers/storage/partitionlib/tests/test.rs b/drivers/storage/partitionlib/tests/test.rs new file mode 100644 index 0000000000..c4ef93220e --- /dev/null +++ b/drivers/storage/partitionlib/tests/test.rs @@ -0,0 +1,45 @@ +use std::fs::File; + +use partitionlib::{ + get_partitions, LogicalBlockSize, Partition, PartitionTable, PartitionTableKind, +}; + +fn get_partitions_from_file(path: &str) -> PartitionTable { + let mut file = File::open(path).unwrap(); + get_partitions(&mut file, LogicalBlockSize::Lb512) + .unwrap() + .unwrap() +} + +// NOTE: The following tests rely on outside resource files being correct. +#[test] +fn gpt() { + let table = get_partitions_from_file("./resources/disk.img"); + assert_eq!(table.kind, PartitionTableKind::Gpt); + assert_eq!( + &table.partitions, + &[Partition { + flags: Some(0), + name: Some("bug".to_owned()), + uuid: Some(uuid::Uuid::parse_str("b665fba9-74d5-4069-a6b9-5ba3a164fdfe").unwrap()), // Microsoft basic data + size: 957, + start_lba: 34, + }] + ); +} + +#[test] +fn mbr() { + let table = get_partitions_from_file("./resources/disk_mbr.img"); + assert_eq!(table.kind, PartitionTableKind::Mbr); + assert_eq!( + &table.partitions, + &[Partition { + flags: None, + name: None, + uuid: None, + size: 3, + start_lba: 1, + }] + ); +} diff --git a/drivers/storage/usbscsid/.gitignore b/drivers/storage/usbscsid/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/storage/usbscsid/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/storage/usbscsid/Cargo.toml b/drivers/storage/usbscsid/Cargo.toml new file mode 100644 index 0000000000..d7b148ee47 --- /dev/null +++ b/drivers/storage/usbscsid/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "usbscsid" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2021" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64 = "0.11" # Only for debugging +libredox = "0.1.3" +plain = "0.2" +driver-block = { path = "../driver-block" } +redox-daemon = "0.1" +redox_event = "0.4" +redox_syscall = { version = "0.5", features = ["std"] } +thiserror = "1" +xhcid = { path = "../../usb/xhcid" } diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs new file mode 100644 index 0000000000..bc6d993a5e --- /dev/null +++ b/drivers/storage/usbscsid/src/main.rs @@ -0,0 +1,168 @@ +use std::collections::BTreeMap; +use std::env; + +use driver_block::{Disk, DiskScheme, ExecutorTrait}; +use syscall::{Error, EIO}; +use xhcid_interface::{ConfigureEndpointsReq, PortId, XhciClientHandle}; + +pub mod protocol; +pub mod scsi; + +use crate::protocol::Protocol; +use crate::scsi::Scsi; + +fn main() { + let mut args = env::args().skip(1); + + const USAGE: &'static str = "usbscsid "; + + let scheme = args.next().expect(USAGE); + let port = args + .next() + .expect(USAGE) + .parse::() + .expect("Expected port ID"); + let protocol = args + .next() + .expect(USAGE) + .parse::() + .expect("protocol has to be a number 0-255"); + + println!( + "USB SCSI driver spawned with scheme `{}`, port {}, protocol {}", + scheme, port, protocol + ); + + redox_daemon::Daemon::new(move |d| daemon(d, scheme, port, protocol)) + .expect("usbscsid: failed to daemonize"); +} +fn daemon(daemon: redox_daemon::Daemon, scheme: String, port: PortId, protocol: u8) -> ! { + let disk_scheme_name = format!("disk.usb-{scheme}+{port}-scsi"); + + // TODO: Use eventfds. + let handle = XhciClientHandle::new(scheme.to_owned(), port); + + // FIXME should this wait notifying readiness until the disk scheme is created? + daemon.ready().expect("usbscsid: failed to signal rediness"); + + let desc = handle + .get_standard_descs() + .expect("Failed to get standard descriptors"); + + // TODO: Perhaps the drivers should just be given the config, interface, and alternate setting + // from xhcid. + let (conf_desc, configuration_value, (if_desc, interface_num, alternate_setting)) = desc + .config_descs + .iter() + .find_map(|config_desc| { + let interface_desc = config_desc.interface_descs.iter().find_map(|if_desc| { + if if_desc.class == 8 && if_desc.sub_class == 6 && if_desc.protocol == 0x50 { + Some((if_desc.clone(), if_desc.number, if_desc.alternate_setting)) + } else { + None + } + })?; + Some(( + config_desc.clone(), + config_desc.configuration_value, + interface_desc, + )) + }) + .expect("Failed to find suitable configuration"); + + handle + .configure_endpoints(&ConfigureEndpointsReq { + config_desc: configuration_value, + interface_desc: Some(interface_num), + alternate_setting: Some(alternate_setting), + hub_ports: None, + }) + .expect("Failed to configure endpoints"); + + let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) + .expect("Failed to setup protocol"); + + // TODO: Let all of the USB drivers fork or be managed externally, and xhcid won't have to keep + // track of all the drivers. + let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI"); + println!("SCSI initialized"); + let mut buffer = [0u8; 512]; + scsi.read(&mut *protocol, 0, &mut buffer).unwrap(); + println!("DISK CONTENT: {}", base64::encode(&buffer[..])); + + let event_queue = event::EventQueue::new().unwrap(); + + event::user_data! { + enum Event { + Scheme, + } + }; + + let mut scheme = DiskScheme::new( + None, + disk_scheme_name, + BTreeMap::from([( + 0, + UsbDisk { + scsi: &mut scsi, + protocol: &mut *protocol, + }, + )]), + &driver_block::FuturesExecutor, + ); + + //libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + + event_queue + .subscribe( + scheme.event_handle().raw(), + Event::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + for event in event_queue { + match event.unwrap().user_data { + Event::Scheme => driver_block::FuturesExecutor + .block_on(scheme.tick()) + .unwrap(), + } + } + + std::process::exit(0); +} + +struct UsbDisk<'a> { + scsi: &'a mut Scsi, + protocol: &'a mut dyn Protocol, +} + +impl Disk for UsbDisk<'_> { + fn block_size(&self) -> u32 { + self.scsi.block_size + } + + fn size(&self) -> u64 { + self.scsi.get_disk_size() + } + + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { + match self.scsi.read(self.protocol, block, buffer) { + Ok(bytes_read) => Ok(bytes_read as usize), + Err(err) => { + eprintln!("usbscsid: READ IO ERROR: {err}"); + Err(Error::new(EIO)) + } + } + } + + async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result { + match self.scsi.write(self.protocol, block, buffer) { + Ok(bytes_written) => Ok(bytes_written as usize), + Err(err) => { + eprintln!("usbscsid: WRITE IO ERROR: {err}"); + Err(Error::new(EIO)) + } + } + } +} diff --git a/drivers/storage/usbscsid/src/protocol/bot.rs b/drivers/storage/usbscsid/src/protocol/bot.rs new file mode 100644 index 0000000000..b751d51ad6 --- /dev/null +++ b/drivers/storage/usbscsid/src/protocol/bot.rs @@ -0,0 +1,363 @@ +use std::num::NonZeroU32; +use std::slice; + +use xhcid_interface::{ + ConfDesc, DeviceReqData, EndpBinaryDirection, EndpDirection, EndpointStatus, IfDesc, Invalid, + PortReqRecipient, PortReqTy, PortTransferStatus, PortTransferStatusKind, XhciClientHandle, + XhciClientHandleError, XhciEndpHandle, +}; + +use super::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind}; + +pub const CBW_SIGNATURE: u32 = 0x43425355; + +/// 0 means host to dev, 1 means dev to host +pub const CBW_FLAGS_DIRECTION_BIT: u8 = 1 << CBW_FLAGS_DIRECTION_SHIFT; +pub const CBW_FLAGS_DIRECTION_SHIFT: u8 = 7; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct CommandBlockWrapper { + pub signature: u32, + pub tag: u32, + pub data_transfer_len: u32, + pub flags: u8, // upper nibble reserved + pub lun: u8, // bits 7:5 reserved + pub cb_len: u8, + pub command_block: [u8; 16], +} +impl CommandBlockWrapper { + pub fn new( + tag: u32, + data_transfer_len: u32, + direction: EndpBinaryDirection, + lun: u8, + cb: &[u8], + ) -> Result { + let mut command_block = [0u8; 16]; + if cb.len() > 16 { + return Err(ProtocolError::TooLargeCommandBlock(cb.len())); + } + + command_block[..cb.len()].copy_from_slice(&cb); + Ok(Self { + signature: CBW_SIGNATURE, + tag, + data_transfer_len, + flags: match direction { + EndpBinaryDirection::Out => 0, + EndpBinaryDirection::In => 1, + } << CBW_FLAGS_DIRECTION_SHIFT, + lun, + cb_len: cb.len() as u8, + command_block, + }) + } +} +unsafe impl plain::Plain for CommandBlockWrapper {} + +pub const CSW_SIGNATURE: u32 = 0x53425355; + +#[repr(u8)] +pub enum CswStatus { + Passed = 0, + Failed = 1, + PhaseError = 2, + // the rest are reserved +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct CommandStatusWrapper { + pub signature: u32, + pub tag: u32, + pub data_residue: u32, + pub status: u8, +} +unsafe impl plain::Plain for CommandStatusWrapper {} + +impl CommandStatusWrapper { + pub fn is_valid(&self) -> bool { + self.signature == CSW_SIGNATURE + } +} + +pub struct BulkOnlyTransport<'a> { + handle: &'a XhciClientHandle, + bulk_in: XhciEndpHandle, + bulk_out: XhciEndpHandle, + bulk_in_num: u8, + bulk_out_num: u8, + max_lun: u8, + current_tag: u32, + interface_num: u8, +} + +pub const FEATURE_ENDPOINT_HALT: u16 = 0; + +impl<'a> BulkOnlyTransport<'a> { + pub fn init( + handle: &'a XhciClientHandle, + config_desc: &ConfDesc, + if_desc: &IfDesc, + ) -> Result { + let endpoints = &if_desc.endpoints; + + let bulk_in_num = (endpoints + .iter() + .position(|endpoint| endpoint.direction() == EndpDirection::In) + .unwrap() + + 1) as u8; + let bulk_out_num = (endpoints + .iter() + .position(|endpoint| endpoint.direction() == EndpDirection::Out) + .unwrap() + + 1) as u8; + + let max_lun = get_max_lun(handle, 0)?; + println!("BOT_MAX_LUN {}", max_lun); + + Ok(Self { + bulk_in: handle.open_endpoint(bulk_in_num)?, + bulk_out: handle.open_endpoint(bulk_out_num)?, + bulk_in_num, + bulk_out_num, + handle, + max_lun, + current_tag: 0, + interface_num: if_desc.number, + }) + } + fn clear_stall_in(&mut self) -> Result<(), XhciClientHandleError> { + if self.bulk_in.status()? == EndpointStatus::Halted { + self.bulk_in.reset(false)?; + self.handle.clear_feature( + PortReqRecipient::Endpoint, + u16::from(self.bulk_in_num), + FEATURE_ENDPOINT_HALT, + )?; + } + Ok(()) + } + fn clear_stall_out(&mut self) -> Result<(), XhciClientHandleError> { + if self.bulk_out.status()? == EndpointStatus::Halted { + self.bulk_out.reset(false)?; + self.handle.clear_feature( + PortReqRecipient::Endpoint, + u16::from(self.bulk_out_num), + FEATURE_ENDPOINT_HALT, + )?; + } + Ok(()) + } + fn reset_recovery(&mut self) -> Result<(), ProtocolError> { + bulk_only_mass_storage_reset(self.handle, self.interface_num.into())?; + self.clear_stall_in()?; + self.clear_stall_out()?; + + if self.bulk_in.status()? == EndpointStatus::Halted + || self.bulk_out.status()? == EndpointStatus::Halted + { + return Err(ProtocolError::RecoveryFailed); + } + Ok(()) + } + fn read_csw_raw( + &mut self, + csw_buffer: &mut [u8; 13], + already: bool, + ) -> Result<(), ProtocolError> { + match self.bulk_in.transfer_read(&mut csw_buffer[..])? { + PortTransferStatus { + kind: PortTransferStatusKind::Stalled, + .. + } => { + if already { + self.reset_recovery()?; + } + println!("bulk in endpoint stalled when reading CSW"); + self.clear_stall_in()?; + self.read_csw_raw(csw_buffer, true)?; + } + PortTransferStatus { + kind: PortTransferStatusKind::ShortPacket, + bytes_transferred, + } if bytes_transferred != 13 => { + panic!( + "received a short packet when reading CSW ({} != 13)", + bytes_transferred + ) + } + _ => (), + } + Ok(()) + } + fn read_csw(&mut self, csw_buffer: &mut [u8; 13]) -> Result<(), ProtocolError> { + self.read_csw_raw(csw_buffer, false) + } +} + +impl<'a> Protocol for BulkOnlyTransport<'a> { + fn send_command( + &mut self, + cb: &[u8], + data: DeviceReqData, + ) -> Result { + self.current_tag += 1; + let tag = self.current_tag; + + let mut cbw_bytes = [0u8; 31]; + let cbw = plain::from_mut_bytes::(&mut cbw_bytes).unwrap(); + *cbw = CommandBlockWrapper::new(tag, data.len() as u32, data.direction().into(), 0, cb)?; + let cbw = *cbw; + + match self.bulk_out.transfer_write(&cbw_bytes)? { + PortTransferStatus { + kind: PortTransferStatusKind::Stalled, + .. + } => { + // TODO: Error handling + panic!("bulk out endpoint stalled when sending CBW {:?}", cbw); + //self.clear_stall_out()?; + //dbg!(self.bulk_in.status()?, self.bulk_out.status()?); + } + PortTransferStatus { + bytes_transferred, .. + } if bytes_transferred != 31 => { + panic!( + "received short packet when sending CBW ({} != 31)", + bytes_transferred + ); + } + _ => (), + } + + let early_residue: Option = match data { + DeviceReqData::In(buffer) => match self.bulk_in.transfer_read(buffer)? { + PortTransferStatus { + kind, + bytes_transferred, + } => match kind { + PortTransferStatusKind::Success => None, + PortTransferStatusKind::ShortPacket => { + println!( + "received short packet (len {}) when transferring data", + bytes_transferred + ); + NonZeroU32::new(bytes_transferred) + } + PortTransferStatusKind::Stalled => { + panic!("bulk in endpoint stalled when reading data"); + //self.clear_stall_in()?; + } + PortTransferStatusKind::Unknown => { + return Err(ProtocolError::XhciError( + XhciClientHandleError::InvalidResponse(Invalid( + "unknown transfer status", + )), + )); + } + }, + }, + DeviceReqData::Out(buffer) => match self.bulk_out.transfer_write(buffer)? { + PortTransferStatus { + kind, + bytes_transferred, + } => match kind { + PortTransferStatusKind::Success => None, + PortTransferStatusKind::ShortPacket => { + println!( + "received short packet (len {}) when transferring data", + bytes_transferred + ); + NonZeroU32::new(bytes_transferred) + } + PortTransferStatusKind::Stalled => { + panic!("bulk out endpoint stalled when reading data"); + //self.clear_stall_out()?; + } + PortTransferStatusKind::Unknown => { + return Err(ProtocolError::XhciError( + XhciClientHandleError::InvalidResponse(Invalid( + "unknown transfer status", + )), + )); + } + }, + }, + DeviceReqData::NoData => None, + }; + + let mut csw_buffer = [0u8; 13]; + self.read_csw(&mut csw_buffer)?; + let csw = plain::from_bytes::(&csw_buffer).unwrap(); + + let residue = early_residue.or(NonZeroU32::new(csw.data_residue)); + + if csw.status == CswStatus::Failed as u8 { + println!("CSW indicated failure (CSW {:?}, CBW {:?})", csw, cbw); + } + + if !csw.is_valid() || csw.tag != cbw.tag { + println!("Invald CSW {:?} (for CBW {:?})", csw, cbw); + self.reset_recovery()?; + if self.bulk_in.status()? == EndpointStatus::Halted + || self.bulk_out.status()? == EndpointStatus::Halted + { + return Err(ProtocolError::ProtocolError( + "Reset Recovery didn't reset endpoints", + )); + } + return Err(ProtocolError::ProtocolError( + "CSW invalid, but a recover was successful", + )); + } + + /*if self.bulk_in.status()? == EndpointStatus::Halted + || self.bulk_out.status()? == EndpointStatus::Halted + { + println!("Trying to recover from stall"); + dbg!(self.bulk_in.status()?, self.bulk_out.status()?); + }*/ + + Ok(SendCommandStatus { + kind: if csw.status == CswStatus::Passed as u8 { + SendCommandStatusKind::Success + } else if csw.status == CswStatus::Failed as u8 { + SendCommandStatusKind::Failed + } else { + return Err(ProtocolError::ProtocolError( + "bulk-only transport phase error, or other", + )); + }, + residue, + }) + } +} + +pub fn bulk_only_mass_storage_reset( + handle: &XhciClientHandle, + if_num: u16, +) -> Result<(), XhciClientHandleError> { + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + 0xFF, + 0, + if_num, + DeviceReqData::NoData, + ) +} +pub fn get_max_lun(handle: &XhciClientHandle, if_num: u16) -> Result { + let mut lun = 0u8; + let buffer = slice::from_mut(&mut lun); + handle.device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + 0xFE, + 0, + if_num, + DeviceReqData::In(buffer), + )?; + Ok(lun) +} diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs new file mode 100644 index 0000000000..a580765f1d --- /dev/null +++ b/drivers/storage/usbscsid/src/protocol/mod.rs @@ -0,0 +1,81 @@ +use std::io; +use std::num::NonZeroU32; + +use thiserror::Error; +use xhcid_interface::{ + ConfDesc, DevDesc, DeviceReqData, IfDesc, XhciClientHandle, XhciClientHandleError, +}; + +#[derive(Debug, Error)] +pub enum ProtocolError { + #[error("Too large command block ({0} > 16)")] + TooLargeCommandBlock(usize), + + #[error("xhcid connection error: {0}")] + XhciError(#[from] XhciClientHandleError), + + #[error("i/o error")] + IoError(#[from] io::Error), + + #[error("attempted recovery failed")] + RecoveryFailed, + + #[error("protocol error")] + ProtocolError(&'static str), +} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct SendCommandStatus { + pub residue: Option, + pub kind: SendCommandStatusKind, +} + +impl SendCommandStatus { + pub fn bytes_transferred(&self, transfer_len: u32) -> u32 { + transfer_len - self.residue.map(u32::from).unwrap_or(0) + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SendCommandStatusKind { + Success, + Failed, +} + +impl Default for SendCommandStatusKind { + fn default() -> Self { + Self::Success + } +} + +pub trait Protocol { + fn send_command( + &mut self, + command: &[u8], + data: DeviceReqData, + ) -> Result; +} + +/// Bulk-only transport +pub mod bot; + +mod uas { + // TODO +} + +use bot::BulkOnlyTransport; + +pub fn setup<'a>( + handle: &'a XhciClientHandle, + protocol: u8, + dev_desc: &DevDesc, + conf_desc: &ConfDesc, + if_desc: &IfDesc, +) -> Option> { + match protocol { + 0x50 => Some(Box::new( + BulkOnlyTransport::init(handle, conf_desc, if_desc).unwrap(), + )), + _ => None, + } +} diff --git a/drivers/storage/usbscsid/src/scsi/cmds.rs b/drivers/storage/usbscsid/src/scsi/cmds.rs new file mode 100644 index 0000000000..ab02525e93 --- /dev/null +++ b/drivers/storage/usbscsid/src/scsi/cmds.rs @@ -0,0 +1,559 @@ +use super::opcodes::Opcode; +use std::convert::TryInto; +use std::{fmt, mem, slice}; + +#[repr(C, packed)] +pub struct Inquiry { + pub opcode: u8, + /// bits 7:2 are reserved, bit 1 (CMDDT) is obsolete, bit 0 is EVPD + pub evpd: u8, + pub page_code: u8, + /// big endian + pub alloc_len: u16, + pub control: u8, +} +unsafe impl plain::Plain for Inquiry {} + +impl Inquiry { + pub const fn new(evpd: bool, page_code: u8, alloc_len: u16, control: u8) -> Self { + Self { + opcode: Opcode::Inquiry as u8, + evpd: evpd as u8, + page_code, + alloc_len: u16::to_be(alloc_len), + control, + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct StandardInquiryData { + /// Peripheral device type (bits 4:0), and peripheral device qualifier (bits 7:5). + pub a: u8, + /// Removable media bit (bit 7, bits 6:0 are reserved). + pub rmb: u8, + /// Version of the SCSI command set. + pub version: u8, + pub b: u8, + pub additional_len: u8, + pub c: u8, + pub d: u8, + pub e: u8, + pub t10_vendor_info: u64, + pub product_ident: [u8; 16], + pub product_rev_label: u32, + pub driver_serial_no: [u8; 8], + pub vendor_uniq: [u8; 12], + _rsvd1: [u8; 2], + pub version_descs: [u16; 8], + _rsvd2: [u8; 22], +} +unsafe impl plain::Plain for StandardInquiryData {} +impl StandardInquiryData { + pub const fn periph_dev_ty(&self) -> u8 { + self.a & 0x1F + } + pub const fn periph_dev_qual(&self) -> u8 { + (self.a & 0xE0) >> 5 + } + pub const fn version(&self) -> u8 { + self.version + } +} + +#[repr(u8)] +pub enum PeriphDeviceType { + DirectAccess, + SeqAccess, + // there are more +} +#[repr(u8)] +pub enum InquiryVersion { + NoConformance, + Spc, + Spc2, + Spc3, + Spc4, + Spc5, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct RequestSense { + pub opcode: u8, + pub desc: u8, // bits 7:1 reserved + _rsvd: u16, + pub alloc_len: u8, + pub control: u8, +} +unsafe impl plain::Plain for RequestSense {} + +impl RequestSense { + pub const MINIMAL_ALLOC_LEN: u8 = 252; + + pub const fn new(desc: bool, alloc_len: u8, control: u8) -> Self { + Self { + opcode: Opcode::RequestSense as u8, + desc: desc as u8, + _rsvd: 0, + alloc_len, + control, + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct FixedFormatSenseData { + pub a: u8, + _obsolete: u8, + pub b: u8, + pub info: u32, + pub add_sense_len: u8, + pub command_specific_info: u32, + pub add_sense_code: u8, + pub add_sense_code_qual: u8, + pub field_replacable_unit_code: u8, + pub sense_key_specific: [u8; 3], // big endian + pub add_sense_bytes: [u8; 0], +} +unsafe impl plain::Plain for FixedFormatSenseData {} + +impl FixedFormatSenseData { + pub const fn additional_len(&self) -> u16 { + self.add_sense_len as u16 + 7 + } + pub unsafe fn add_sense_bytes(&self) -> &[u8] { + slice::from_raw_parts( + &self.add_sense_len as *const u8, + self.add_sense_len as usize - 18, + ) + } + pub fn sense_key(&self) -> SenseKey { + let sense_key_raw = self.b & 0b1111; + // Safe because all possible values (0-15) are used by the enum. + unsafe { mem::transmute(sense_key_raw) } + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum SenseKey { + NoSense = 0x00, + RecoveredError = 0x01, + NotReady = 0x02, + MediumError = 0x03, + HardwareError = 0x04, + IllegalRequest = 0x05, + UnitAttention = 0x06, + DataProtect = 0x07, + BlankCheck = 0x08, + VendorSpecific = 0x09, + CopyAborted = 0x0A, + AbortedCommand = 0x0B, + Reserved = 0x0C, + VolumeOverflow = 0x0D, + Miscompare = 0x0E, + Completed = 0x0F, +} +impl Default for SenseKey { + fn default() -> Self { + Self::NoSense + } +} + +pub const ADD_SENSE_CODE05_INVAL_CDB_FIELD: u8 = 0x24; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct Read16 { + pub opcode: u8, + pub a: u8, + pub lba: u64, + pub transfer_len: u32, + pub b: u8, + pub control: u8, +} +unsafe impl plain::Plain for Read16 {} + +impl Read16 { + pub const fn new(lba: u64, transfer_len: u32, control: u8) -> Self { + // TODO: RDPROTECT, DPO, FUA, RARC + // TODO: DLD + // TODO: Group number + Self { + opcode: Opcode::Read16 as u8, + a: 0, + lba: u64::to_be(lba), + transfer_len: u32::to_be(transfer_len), + b: 0, + control, + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct Write16 { + pub opcode: u8, + pub a: u8, + pub lba: u64, // big endian + pub transfer_len: u32, + pub b: u8, + pub control: u8, +} +unsafe impl plain::Plain for Write16 {} + +impl Write16 { + pub const fn new(lba: u64, transfer_len: u32, control: u8) -> Self { + Self { + // TODO + opcode: Opcode::Write16 as u8, + a: 0, + lba, + transfer_len, + b: 0, + control, + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct ModeSense6 { + pub opcode: u8, + pub a: u8, + pub b: u8, + pub subpage_code: u8, + pub alloc_len: u8, + pub control: u8, +} +unsafe impl plain::Plain for ModeSense6 {} + +impl ModeSense6 { + pub const fn new( + dbd: bool, + page_code: u8, + pc: u8, + subpage_code: u8, + alloc_len: u8, + control: u8, + ) -> Self { + Self { + opcode: Opcode::ModeSense6 as u8, + a: (dbd as u8) << 3, + b: page_code | (pc << 6), + subpage_code, + alloc_len, + control, + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct ModeSense10 { + pub opcode: u8, + pub a: u8, + pub b: u8, + pub subpage_code: u8, + pub _rsvd: [u8; 3], + pub alloc_len: u16, + pub control: u8, +} +unsafe impl plain::Plain for ModeSense10 {} + +impl ModeSense10 { + pub const fn new( + dbd: bool, + llbaa: bool, + page_code: u8, + pc: ModePageControl, + subpage_code: u8, + alloc_len: u16, + control: u8, + ) -> Self { + Self { + opcode: Opcode::ModeSense10 as u8, + a: ((dbd as u8) << 3) | ((llbaa as u8) << 4), + b: page_code | ((pc as u8) << 6), + subpage_code, + _rsvd: [0u8; 3], + alloc_len: u16::from_be(alloc_len), + control, + } + } + pub const fn get_block_desc(alloc_len: u16, control: u8) -> Self { + Self::new( + false, + true, + 0x3F, + ModePageControl::CurrentValues, + 0x00, + alloc_len, + control, + ) + } +} + +#[repr(u8)] +pub enum ModePageControl { + CurrentValues, + ChangeableChanges, + DefaultValues, + SavedValue, +} + +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub struct ShortLbaModeParamBlkDesc { + pub block_count: u32, + _rsvd: u8, + pub logical_block_len: [u8; 3], +} +unsafe impl plain::Plain for ShortLbaModeParamBlkDesc {} + +impl ShortLbaModeParamBlkDesc { + pub const fn block_count(&self) -> u32 { + u32::from_be(self.block_count) + } + pub const fn logical_block_len(&self) -> u32 { + u24_be_to_u32(self.logical_block_len) + } +} +impl fmt::Debug for ShortLbaModeParamBlkDesc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ShortLbaModeParamBlkDesc") + .field("block_count", &self.block_count()) + .field("logical_block_len", &self.logical_block_len()) + .finish() + } +} + +const fn u24_be_to_u32(u24: [u8; 3]) -> u32 { + ((u24[0] as u32) << 16) | ((u24[1] as u32) << 8) | (u24[2] as u32) +} + +/// From SPC-3, when LONGLBA is not set, and the peripheral device type of the INQUIRY data indicates that the device is not a direct access device. Otherwise, `ShortLbaModeParamBlkDesc` is used instead. +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub struct GeneralModeParamBlkDesc { + pub density_code: u8, + pub block_count: [u8; 3], + _rsvd: u8, + pub block_length: [u8; 3], +} +unsafe impl plain::Plain for GeneralModeParamBlkDesc {} + +impl GeneralModeParamBlkDesc { + pub fn block_count(&self) -> u32 { + u24_be_to_u32(self.block_count) + } + pub fn logical_block_len(&self) -> u32 { + u24_be_to_u32(self.block_length) + } +} + +impl fmt::Debug for GeneralModeParamBlkDesc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("GeneralModeParamBlkDesc") + .field("density_code", &self.density_code) + .field("block_count", &u24_be_to_u32(self.block_count)) + .field("block_length", &u24_be_to_u32(self.block_length)) + .finish() + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct LongLbaModeParamBlkDesc { + pub block_count: u64, + _rsvd: u32, + pub logical_block_len: u32, +} +unsafe impl plain::Plain for LongLbaModeParamBlkDesc {} + +impl LongLbaModeParamBlkDesc { + pub const fn block_count(&self) -> u64 { + u64::from_be(self.block_count) + } + pub const fn logical_block_len(&self) -> u32 { + u32::from_be(self.logical_block_len) + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct ModeParamHeader6 { + pub mode_data_len: u8, + pub medium_ty: u8, + pub a: u8, + pub block_desc_len: u8, +} +unsafe impl plain::Plain for ModeParamHeader6 {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct ModeParamHeader10 { + pub mode_data_len: u16, + pub medium_ty: u8, + pub a: u8, + pub b: u8, + _rsvd: u8, + pub block_desc_len: u16, +} +unsafe impl plain::Plain for ModeParamHeader10 {} +impl ModeParamHeader10 { + pub const fn mode_data_len(&self) -> u16 { + u16::from_be(self.mode_data_len) + } + pub const fn block_desc_len(&self) -> u16 { + u16::from_be(self.block_desc_len) + } + pub const fn longlba(&self) -> bool { + (self.b & 0x01) != 0 + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct ReadCapacity10 { + pub opcode: u8, + _rsvd1: u8, + obsolete_lba: u32, + _rsvd2: [u8; 3], + pub control: u8, +} +unsafe impl plain::Plain for ReadCapacity10 {} + +impl ReadCapacity10 { + pub const fn new(control: u8) -> Self { + Self { + opcode: Opcode::ReadCapacity10 as u8, + _rsvd1: 0, + obsolete_lba: 0, + _rsvd2: [0; 3], + control, + } + } +} +// TODO: ReadCapacity16 + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct ReadCapacity10ParamData { + pub max_lba: u32, + pub block_len: u32, +} +unsafe impl plain::Plain for ReadCapacity10ParamData {} + +impl ReadCapacity10ParamData { + pub const fn block_count(&self) -> u32 { + u32::from_be(self.max_lba) + } + pub const fn logical_block_len(&self) -> u32 { + u32::from_be(self.block_len) + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct RwErrorRecoveryPage { + pub a: u8, + pub page_length: u8, + pub b: u8, + pub read_retry_count: u8, + _obsolete: [u8; 3], + _rsvd: u8, + pub recovery_time_limit: u16, +} +unsafe impl plain::Plain for RwErrorRecoveryPage {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct CachingModePage { + pub a: u8, + pub page_length: u8, + // TODO: more +} +unsafe impl plain::Plain for CachingModePage {} + +pub(crate) struct ModePageIterRaw<'a> { + buffer: &'a [u8], +} + +impl<'a> Iterator for ModePageIterRaw<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if self.buffer.len() < 2 { + return None; + } + + let a = self.buffer[0]; + let page_len = if a & (1 << 6) == 0 { + // item is page_0 mode + self.buffer[1] as usize + 1 + } else { + // item is sub_page mode + u16::from_be_bytes((&self.buffer[2..3]).try_into().ok()?) as usize + 3 + }; + if self.buffer.len() < page_len { + return None; + } + let buffer = &self.buffer[..page_len]; + + self.buffer = if page_len == self.buffer.len() { + &[] + } else { + &self.buffer[page_len..] + }; + + Some(buffer) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum AnyModePage<'a> { + RwErrorRecovery(&'a RwErrorRecoveryPage), + Caching(&'a CachingModePage), +} + +struct ModePageIter<'a> { + raw: ModePageIterRaw<'a>, +} + +impl<'a> Iterator for ModePageIter<'a> { + type Item = AnyModePage<'a>; + + fn next(&mut self) -> Option { + let next_buf = self.raw.next()?; + let a = next_buf[0]; + + let page_code = a & 0x1F; + let spf = a & (1 << 6) != 0; + + if !spf { + if page_code == 0x01 { + Some(AnyModePage::RwErrorRecovery( + plain::from_bytes(next_buf).ok()?, + )) + } else if page_code == 0x08 { + Some(AnyModePage::Caching(plain::from_bytes(next_buf).ok()?)) + } else { + println!("Unimplemented sub_page {}", base64::encode(next_buf)); + None + } + } else { + println!("Unimplemented page_0 {}", base64::encode(next_buf)); + None + } + } +} + +pub fn mode_page_iter(buffer: &[u8]) -> impl Iterator> { + ModePageIter { + raw: ModePageIterRaw { buffer }, + } +} diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs new file mode 100644 index 0000000000..790abea68c --- /dev/null +++ b/drivers/storage/usbscsid/src/scsi/mod.rs @@ -0,0 +1,339 @@ +use std::convert::TryFrom; +use std::mem; + +pub mod cmds; +pub mod opcodes; + +use thiserror::Error; +use xhcid_interface::DeviceReqData; + +use crate::protocol::{Protocol, ProtocolError, SendCommandStatus, SendCommandStatusKind}; +use cmds::StandardInquiryData; + +pub struct Scsi { + command_buffer: [u8; 16], + inquiry_buffer: [u8; 259], + data_buffer: Vec, + pub block_size: u32, + pub block_count: u64, +} + +const INQUIRY_CMD_LEN: u8 = 6; +const REPORT_SUPP_OPCODES_CMD_LEN: u8 = 12; +const REQUEST_SENSE_CMD_LEN: u8 = 6; +const MIN_INQUIRY_ALLOC_LEN: u16 = 5; +const MIN_REPORT_SUPP_OPCODES_ALLOC_LEN: u32 = 4; + +type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum ScsiError { + // TODO: Add some kind of context here, since it's very useful indeed to be able to see which + // command returned the protocol error. + #[error("protocol error when sending command: {0}")] + ProtocolError(#[from] ProtocolError), + + #[error("overflow")] + Overflow(&'static str), +} + +impl Scsi { + pub fn new(protocol: &mut dyn Protocol) -> Result { + assert_eq!(std::mem::size_of::(), 96); + + let mut this = Self { + command_buffer: [0u8; 16], + // separate buffer since the inquiry data is most likely going to be used in the + // future. + inquiry_buffer: [0u8; 259], // additional_len = 255 max + data_buffer: Vec::new(), + block_size: 0, + block_count: 0, + }; + + // Get the max length that the device supports, of the Standard Inquiry Data. + let max_inquiry_len = this.get_inquiry_alloc_len(protocol)?; + // Get the Standard Inquiry Data. + this.get_standard_inquiry_data(protocol, max_inquiry_len)?; + + let version = this.res_standard_inquiry_data().version(); + println!("Inquiry version: {}", version); + + let (block_size, block_count) = { + let (_, blkdescs, mode_page_iter) = this.get_mode_sense10(protocol)?; + + for page in mode_page_iter { + println!("PAGE: {:?}", page); + } + + // TODO: Can there be multiple disks at all? + if let Some(only_blkdesc) = blkdescs.get(0) { + println!("Found block desc: {:?}", only_blkdesc); + (only_blkdesc.block_size(), only_blkdesc.block_count()) + } else { + println!("read_capacity10"); + let r = this.read_capacity(protocol)?; + println!("read_capacity10 result: {:?}", r); + (r.logical_block_len(), r.block_count().into()) + } + }; + + this.block_size = block_size; + this.block_count = block_count; + + Ok(this) + } + pub fn get_inquiry_alloc_len(&mut self, protocol: &mut dyn Protocol) -> Result { + self.get_standard_inquiry_data(protocol, MIN_INQUIRY_ALLOC_LEN)?; + let standard_inquiry_data = self.res_standard_inquiry_data(); + Ok(4 + u16::from(standard_inquiry_data.additional_len)) + } + pub fn get_standard_inquiry_data( + &mut self, + protocol: &mut dyn Protocol, + max_inquiry_len: u16, + ) -> Result<()> { + let inquiry = self.cmd_inquiry(); + *inquiry = cmds::Inquiry::new(false, 0, max_inquiry_len, 0); + + protocol.send_command( + &self.command_buffer[..INQUIRY_CMD_LEN as usize], + DeviceReqData::In(&mut self.inquiry_buffer[..max_inquiry_len as usize]), + )?; + Ok(()) + } + pub fn get_ff_sense(&mut self, protocol: &mut dyn Protocol, alloc_len: u8) -> Result<()> { + let request_sense = self.cmd_request_sense(); + *request_sense = cmds::RequestSense::new(false, alloc_len, 0); + self.data_buffer.resize(alloc_len.into(), 0); + protocol.send_command( + &self.command_buffer[..REQUEST_SENSE_CMD_LEN as usize], + DeviceReqData::In(&mut self.data_buffer[..alloc_len as usize]), + )?; + Ok(()) + } + pub fn read_capacity( + &mut self, + protocol: &mut dyn Protocol, + ) -> Result<&cmds::ReadCapacity10ParamData> { + // The spec explicitly states that the allocation length is 8 bytes. + let read_capacity10 = self.cmd_read_capacity10(); + *read_capacity10 = cmds::ReadCapacity10::new(0); + self.data_buffer.resize(10usize, 0u8); + protocol.send_command( + &self.command_buffer[..10], + DeviceReqData::In(&mut self.data_buffer[..8]), + )?; + Ok(self.res_read_capacity10()) + } + pub fn get_mode_sense10( + &mut self, + protocol: &mut dyn Protocol, + ) -> Result<( + &cmds::ModeParamHeader10, + BlkDescSlice<'_>, + impl Iterator>, + )> { + let initial_alloc_len = mem::size_of::() as u16; // covers both mode_data_len and blk_desc_len. + let mode_sense10 = self.cmd_mode_sense10(); + *mode_sense10 = cmds::ModeSense10::get_block_desc(initial_alloc_len, 0); + self.data_buffer + .resize(mem::size_of::(), 0); + if let SendCommandStatus { + kind: SendCommandStatusKind::Failed, + .. + } = protocol.send_command( + &self.command_buffer[..10], + DeviceReqData::In(&mut self.data_buffer[..initial_alloc_len as usize]), + )? { + self.get_ff_sense(protocol, 252)?; + panic!("{:?}", self.res_ff_sense_data()); + } + + let optimal_alloc_len = self.res_mode_param_header10().mode_data_len() + 2; // the length of the mode data field itself + + let mode_sense10 = self.cmd_mode_sense10(); + *mode_sense10 = cmds::ModeSense10::get_block_desc(optimal_alloc_len, 0); + self.data_buffer.resize(optimal_alloc_len as usize, 0); + protocol.send_command( + &self.command_buffer[..10], + DeviceReqData::In(&mut self.data_buffer[..optimal_alloc_len as usize]), + )?; + Ok(( + self.res_mode_param_header10(), + self.res_blkdesc_mode10(), + self.res_mode_pages10(), + )) + } + + pub fn cmd_inquiry(&mut self) -> &mut cmds::Inquiry { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn cmd_mode_sense6(&mut self) -> &mut cmds::ModeSense6 { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn cmd_mode_sense10(&mut self) -> &mut cmds::ModeSense10 { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn cmd_request_sense(&mut self) -> &mut cmds::RequestSense { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn cmd_read_capacity10(&mut self) -> &mut cmds::ReadCapacity10 { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn cmd_read16(&mut self) -> &mut cmds::Read16 { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn cmd_write16(&mut self) -> &mut cmds::Write16 { + plain::from_mut_bytes(&mut self.command_buffer).unwrap() + } + pub fn res_standard_inquiry_data(&self) -> &StandardInquiryData { + plain::from_bytes(&self.inquiry_buffer).unwrap() + } + pub fn res_ff_sense_data(&self) -> &cmds::FixedFormatSenseData { + plain::from_bytes(&self.data_buffer).unwrap() + } + pub fn res_mode_param_header6(&self) -> &cmds::ModeParamHeader6 { + plain::from_bytes(&self.data_buffer).unwrap() + } + pub fn res_mode_param_header10(&self) -> &cmds::ModeParamHeader10 { + plain::from_bytes(&self.data_buffer).unwrap() + } + pub fn res_blkdesc_mode6(&self) -> &[cmds::ShortLbaModeParamBlkDesc] { + let header = self.res_mode_param_header6(); + let descs_start = mem::size_of::(); + plain::slice_from_bytes( + &self.data_buffer[descs_start..descs_start + usize::from(header.block_desc_len)], + ) + .unwrap() + } + pub fn res_blkdesc_mode10(&self) -> BlkDescSlice<'_> { + let header = self.res_mode_param_header10(); + let descs_start = mem::size_of::(); + if header.longlba() { + BlkDescSlice::Long( + plain::slice_from_bytes( + &self.data_buffer + [descs_start..descs_start + usize::from(header.block_desc_len())], + ) + .unwrap(), + ) + } else if self.res_standard_inquiry_data().periph_dev_ty() + != cmds::PeriphDeviceType::DirectAccess as u8 + && self.res_standard_inquiry_data().version() == cmds::InquiryVersion::Spc3 as u8 + { + BlkDescSlice::General( + plain::slice_from_bytes( + &self.data_buffer + [descs_start..descs_start + usize::from(header.block_desc_len())], + ) + .unwrap(), + ) + } else { + BlkDescSlice::Short( + plain::slice_from_bytes( + &self.data_buffer + [descs_start..descs_start + usize::from(header.block_desc_len())], + ) + .unwrap(), + ) + } + } + + pub fn res_mode_pages10(&self) -> impl Iterator> { + let header = self.res_mode_param_header10(); + let descs_start = mem::size_of::(); + let buffer = &self.data_buffer[descs_start + header.block_desc_len() as usize..]; + cmds::mode_page_iter(buffer) + } + pub fn res_read_capacity10(&self) -> &cmds::ReadCapacity10ParamData { + plain::from_bytes(&self.data_buffer).unwrap() + } + pub fn get_disk_size(&self) -> u64 { + self.block_count * u64::from(self.block_size) + } + pub fn read( + &mut self, + protocol: &mut dyn Protocol, + lba: u64, + buffer: &mut [u8], + ) -> Result { + let blocks_to_read = buffer.len() as u64 / u64::from(self.block_size); + let bytes_to_read = blocks_to_read as usize * self.block_size as usize; + let transfer_len = u32::try_from(blocks_to_read).or(Err(ScsiError::Overflow( + "number of blocks to read couldn't fit inside a u32", + )))?; + { + let read = self.cmd_read16(); + *read = cmds::Read16::new(lba, transfer_len, 0); + } + // TODO: Use the to-be-written TransferReadStream instead of relying on everything being + // able to fit within a single buffer. + self.data_buffer.resize(bytes_to_read, 0u8); + let status = protocol.send_command( + &self.command_buffer[..16], + DeviceReqData::In(&mut self.data_buffer[..bytes_to_read]), + )?; + buffer[..bytes_to_read].copy_from_slice(&self.data_buffer[..bytes_to_read]); + Ok(status.bytes_transferred(bytes_to_read as u32)) + } + pub fn write(&mut self, protocol: &mut dyn Protocol, lba: u64, buffer: &[u8]) -> Result { + let blocks_to_write = buffer.len() as u64 / u64::from(self.block_size); + let bytes_to_write = blocks_to_write as usize * self.block_size as usize; + let transfer_len = u32::try_from(blocks_to_write).or(Err(ScsiError::Overflow( + "number of blocks to write couldn't fit inside a u32", + )))?; + { + let read = self.cmd_write16(); + *read = cmds::Write16::new(lba, transfer_len, 0); + } + // TODO: Use the to-be-written TransferReadStream instead of relying on everything being + // able to fit within a single buffer. + self.data_buffer.resize(bytes_to_write, 0u8); + self.data_buffer[..bytes_to_write].copy_from_slice(&buffer[..bytes_to_write]); + let status = protocol.send_command( + &self.command_buffer[..16], + DeviceReqData::Out(&buffer[..bytes_to_write]), + )?; + Ok(status.bytes_transferred(bytes_to_write as u32)) + } +} +#[derive(Debug)] +pub enum BlkDescSlice<'a> { + Short(&'a [cmds::ShortLbaModeParamBlkDesc]), + General(&'a [cmds::GeneralModeParamBlkDesc]), + Long(&'a [cmds::LongLbaModeParamBlkDesc]), +} + +#[derive(Debug)] +pub enum BlkDesc<'a> { + Short(&'a cmds::ShortLbaModeParamBlkDesc), + General(&'a cmds::GeneralModeParamBlkDesc), + Long(&'a cmds::LongLbaModeParamBlkDesc), +} +impl<'a> BlkDesc<'a> { + fn block_size(&self) -> u32 { + match self { + Self::Short(s) => s.logical_block_len(), + Self::General(g) => g.logical_block_len(), + Self::Long(l) => l.logical_block_len(), + } + } + fn block_count(&self) -> u64 { + match self { + Self::Short(s) => s.block_count().into(), + Self::General(g) => g.block_count().into(), + Self::Long(l) => l.block_count(), + } + } +} + +impl<'a> BlkDescSlice<'a> { + fn get(&self, idx: usize) -> Option> { + match self { + Self::Short(s) => s.get(idx).map(BlkDesc::Short), + Self::Long(l) => l.get(idx).map(BlkDesc::Long), + Self::General(g) => g.get(idx).map(BlkDesc::General), + } + } +} diff --git a/drivers/storage/usbscsid/src/scsi/opcodes.rs b/drivers/storage/usbscsid/src/scsi/opcodes.rs new file mode 100644 index 0000000000..d146238297 --- /dev/null +++ b/drivers/storage/usbscsid/src/scsi/opcodes.rs @@ -0,0 +1,112 @@ +#[repr(u8)] +pub enum Opcode { + TestUnitReady = 0x00, + /// obsolete + RezeroUnit = 0x01, + RequestSense = 0x03, + FormatUnit = 0x04, + ReassignBlocks = 0x07, + /// obsolete + Read6 = 0x08, + /// obsolete + Write6 = 0x0A, + /// obsolete + Seek = 0x0B, + Inquiry = 0x12, + ModeSelect6 = 0x15, + /// obsolete + Reserve6 = 0x16, + /// obsolete + Release6 = 0x17, + ModeSense6 = 0x1A, + StartStopUnit = 0x1B, + RecvDiagnosticRes = 0x1C, + SendDiagnostic = 0x1D, + ReadCapacity10 = 0x25, + Read10 = 0x28, + Write10 = 0x2A, + /// obsolete + SeekExt = 0x2B, + WriteAndVerify10 = 0x2E, + Verify10 = 0x2F, + SyncCache10 = 0x35, + ReadDefectData10 = 0x37, + WriteBuf10 = 0x3B, + ReadBuf10 = 0x3C, + /// obsolete + ReadLong10 = 0x3E, + WriteLong10 = 0x3F, + /// obsolete + ChangeDef = 0x40, + WriteSame10 = 0x41, + Unmap = 0x42, + Sanitize = 0x48, + LogSelect = 0x4C, + LogSense = 0x4D, + ModeSelect10 = 0x55, + /// obsolete + Reserve10 = 0x56, + /// obsolete + Release10 = 0x57, + ModeSense10 = 0x5A, + PersistentResvIn = 0x5E, + PersistentResvOut = 0x5F, + ServiceAction7F = 0x7F, + Read16 = 0x88, + Write16 = 0x8A, + WriteAndVerify16 = 0x8E, + Verify16 = 0x8F, + SyncCache16 = 0x91, + WriteSame16 = 0x93, + WriteStream16 = 0x9A, + ReadBuf16 = 0x9B, + WriteAtomic16 = 0x9C, + ServiceAction9E = 0x9E, + ServiceAction9F, + ReportLuns = 0xA0, + SecurityProtoIn = 0xA2, + ServiceActionA3 = 0xA3, + ServiceActionA4 = 0xA4, + Read12 = 0xA8, + Write12 = 0xAA, + WriteAndVerify12 = 0xAE, + Verify12 = 0xAF, + SecurityProtoOut = 0xB5, + ReadDefectData12 = 0xB7, +} + +#[repr(u8)] +pub enum ServiceAction7F { + Read32 = 0x09, + Verify32 = 0x0A, + Write32 = 0x0B, + WriteAndVerify32 = 0x0C, + WriteSame32 = 0x0D, + WriteAtomic32 = 0x18, +} + +#[repr(u8)] +pub enum ServiceAction9E { + ReadCapacity16 = 0x10, + ReadLong16 = 0x11, + GetLbaStatus = 0x12, + StreamControl = 0x14, + BackgroundControl = 0x15, + GetStreamStatus = 0x16, +} +#[repr(u8)] +pub enum ServiceAction9F { + WriteLong16 = 0x11, +} +#[repr(u8)] +pub enum ServiceActionA3 { + ReportIdentInfo = 0x05, + ReportSuppOpcodes = 0x0C, + ReportSuppTaskManFuncs = 0x0D, + ReportTimestamp = 0x0F, +} +#[repr(u8)] +pub enum ServiceActionA4 { + SetIdentInfo = 0x06, + SetTimestamp = 0x0F, +} diff --git a/drivers/storage/virtio-blkd/Cargo.toml b/drivers/storage/virtio-blkd/Cargo.toml new file mode 100644 index 0000000000..34cc370cae --- /dev/null +++ b/drivers/storage/virtio-blkd/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "virtio-blkd" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +anyhow = "1.0.71" +log = "0.4" +thiserror = "1.0.40" +static_assertions = "1.1.0" +futures = { version = "0.3.28", features = ["executor"] } +spin = "*" + +redox-daemon = "0.1" +redox_event = "0.4" +redox_syscall = { version = "0.5", features = ["std"] } + +common = { path = "../../common" } +driver-block = { path = "../driver-block" } +pcid = { path = "../../pcid" } +virtio-core = { path = "../../virtio-core" } +libredox = "0.1.3" diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs new file mode 100644 index 0000000000..82818a7584 --- /dev/null +++ b/drivers/storage/virtio-blkd/src/main.rs @@ -0,0 +1,183 @@ +#![deny(trivial_numeric_casts, unused_allocation)] + +use std::collections::BTreeMap; +use std::sync::{Arc, Weak}; + +use driver_block::DiskScheme; +use static_assertions::const_assert_eq; + +use pcid_interface::*; +use virtio_core::spec::*; + +use virtio_core::transport::Transport; +use virtio_core::utils::VolatileCell; + +mod scheme; + +use thiserror::Error; + +use crate::scheme::VirtioDisk; + +#[derive(Debug, Error)] +pub enum Error { + #[error("capability {0:?} not found")] + InCapable(CfgType), + #[error("failed to map memory")] + Physmap, + #[error("failed to allocate an interrupt vector")] + ExhaustedInt, + #[error("syscall error")] + SyscallError(syscall::Error), +} + +pub fn main() -> anyhow::Result<()> { + common::setup_logging( + "disk", + "pci", + "virtio-blkd", + common::output_level(), + common::file_level(), + ); + redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); +} + +#[repr(C)] +pub struct BlockGeometry { + pub cylinders: VolatileCell, + pub heads: VolatileCell, + pub sectors: VolatileCell, +} + +#[repr(u8)] +pub enum DeviceConfigTy { + Capacity = 0, + SizeMax = 0x8, + SeqMax = 0xc, + Geometry = 0x10, + BlkSize = 0x14, +} + +pub struct BlockDeviceConfig(Weak); + +impl BlockDeviceConfig { + #[inline] + fn new(tranport: &Arc) -> Self { + Self(Arc::downgrade(&tranport)) + } + + pub fn load_config(&self, ty: DeviceConfigTy) -> T + where + T: Sized + TryFrom, + >::Error: std::fmt::Debug, + { + let transport = self.0.upgrade().unwrap(); + + let size = core::mem::size_of::() + .try_into() + .expect("load_config: invalid size"); + + let value = transport.load_config(ty as u8, size); + T::try_from(value).unwrap() + } + + /// Returns the capacity of the block device in bytes. + #[inline] + pub fn capacity(&self) -> u64 { + self.load_config(DeviceConfigTy::Capacity) + } + + #[inline] + pub fn block_size(&self) -> u32 { + self.load_config(DeviceConfigTy::BlkSize) + } +} + +#[repr(u32)] +pub enum BlockRequestTy { + In = 0, + Out = 1, +} + +const_assert_eq!(core::mem::size_of::(), 4); + +#[repr(C)] +pub struct BlockVirtRequest { + pub ty: BlockRequestTy, + pub reserved: u32, + pub sector: u64, +} + +const_assert_eq!(core::mem::size_of::(), 16); + +fn daemon(daemon: redox_daemon::Daemon) -> anyhow::Result<()> { + let mut pcid_handle = PciFunctionHandle::connect_default(); + + // Double check that we have the right device. + // + // 0x1001 - virtio-blk + let pci_config = pcid_handle.config(); + + assert_eq!(pci_config.func.full_device_id.device_id, 0x1001); + log::info!("virtio-blk: initiating startup sequence :^)"); + + let device = virtio_core::probe_device(&mut pcid_handle)?; + device.transport.finalize_features(); + + let queue = device + .transport + .setup_queue(virtio_core::MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + let device_space = BlockDeviceConfig::new(&device.transport); + + // At this point the device is alive! + device.transport.run_device(); + + log::info!( + "virtio-blk: disk size: {} sectors and block size of {} bytes", + device_space.capacity(), + device_space.block_size() + ); + + let mut name = pci_config.func.name(); + name.push_str("_virtio_blk"); + + let scheme_name = format!("disk.{}", name); + + let event_queue = event::EventQueue::new().unwrap(); + + event::user_data! { + enum Event { + Scheme, + } + }; + + let mut scheme = DiskScheme::new( + Some(daemon), + scheme_name, + BTreeMap::from([(0, VirtioDisk::new(queue, device_space))]), + &driver_block::FuturesExecutor, + ); + + libredox::call::setrens(0, 0).expect("nvmed: failed to enter null namespace"); + + event_queue + .subscribe( + scheme.event_handle().raw(), + Event::Scheme, + event::EventFlags::READ, + ) + .unwrap(); + + for event in event_queue { + match event.unwrap().user_data { + Event::Scheme => futures::executor::block_on(scheme.tick()).unwrap(), + } + } + + Ok(()) +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + daemon(redox_daemon).unwrap(); + unreachable!(); +} diff --git a/drivers/storage/virtio-blkd/src/scheme.rs b/drivers/storage/virtio-blkd/src/scheme.rs new file mode 100644 index 0000000000..ec4ecf732d --- /dev/null +++ b/drivers/storage/virtio-blkd/src/scheme.rs @@ -0,0 +1,103 @@ +use std::sync::Arc; + +use common::dma::Dma; +use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; +use virtio_core::transport::Queue; + +use crate::BlockDeviceConfig; +use crate::BlockRequestTy; +use crate::BlockVirtRequest; + +trait BlkExtension { + async fn read(&self, block: u64, target: &mut [u8]) -> usize; + async fn write(&self, block: u64, target: &[u8]) -> usize; +} + +impl BlkExtension for Queue<'_> { + async fn read(&self, block: u64, target: &mut [u8]) -> usize { + let req = Dma::new(BlockVirtRequest { + ty: BlockRequestTy::In, + reserved: 0, + sector: block, + }) + .unwrap(); + + let result = unsafe { + Dma::<[u8]>::zeroed_slice(target.len()) + .unwrap() + .assume_init() + }; + let status = Dma::new(u8::MAX).unwrap(); + + let chain = ChainBuilder::new() + .chain(Buffer::new(&req)) + .chain(Buffer::new_unsized(&result).flags(DescriptorFlags::WRITE_ONLY)) + .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + // XXX: Subtract 1 because the of status byte. + let written = self.send(chain).await as usize - 1; + assert_eq!(*status, 0); + + target[..written].copy_from_slice(&result); + written + } + + async fn write(&self, block: u64, target: &[u8]) -> usize { + let req = Dma::new(BlockVirtRequest { + ty: BlockRequestTy::Out, + reserved: 0, + sector: block, + }) + .unwrap(); + + let mut result = unsafe { + Dma::<[u8]>::zeroed_slice(target.len()) + .unwrap() + .assume_init() + }; + result.copy_from_slice(target.as_ref()); + + let status = Dma::new(u8::MAX).unwrap(); + + let chain = ChainBuilder::new() + .chain(Buffer::new(&req)) + .chain(Buffer::new_sized(&result, target.len())) + .chain(Buffer::new(&status).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.send(chain).await as usize; + assert_eq!(*status, 0); + + target.len() + } +} + +pub(crate) struct VirtioDisk<'a> { + queue: Arc>, + cfg: BlockDeviceConfig, +} + +impl<'a> VirtioDisk<'a> { + pub(crate) fn new(queue: Arc>, cfg: BlockDeviceConfig) -> Self { + Self { queue, cfg } + } +} + +impl driver_block::Disk for VirtioDisk<'_> { + fn block_size(&self) -> u32 { + self.cfg.block_size() + } + + fn size(&self) -> u64 { + self.cfg.capacity() * u64::from(self.cfg.block_size()) + } + + async fn read(&mut self, block: u64, buffer: &mut [u8]) -> syscall::Result { + Ok(self.queue.read(block, buffer).await) + } + + async fn write(&mut self, block: u64, buffer: &[u8]) -> syscall::Result { + Ok(self.queue.write(block, buffer).await) + } +} diff --git a/drivers/usb/usbctl/.gitignore b/drivers/usb/usbctl/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/usb/usbctl/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/usb/usbctl/Cargo.toml b/drivers/usb/usbctl/Cargo.toml new file mode 100644 index 0000000000..ee18af204b --- /dev/null +++ b/drivers/usb/usbctl/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "usbctl" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "2.33" +xhcid = { path = "../xhcid" } diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs new file mode 100644 index 0000000000..517cde041a --- /dev/null +++ b/drivers/usb/usbctl/src/main.rs @@ -0,0 +1,56 @@ +use clap::{App, Arg}; +use xhcid_interface::{PortId, XhciClientHandle}; + +fn main() { + let matches = App::new("usbctl") + .arg( + Arg::with_name("SCHEME") + .takes_value(true) + .required(true) + .long("scheme") + .short("s"), + ) + .subcommand( + App::new("port") + .arg(Arg::with_name("PORT").takes_value(true).required(true)) + .subcommand(App::new("status")) + .subcommand( + App::new("endpoint") + .arg( + Arg::with_name("ENDPOINT_NUM") + .takes_value(true) + .required(true), + ) + .subcommand(App::new("status")), + ), + ) + .get_matches(); + + let scheme = matches.value_of("SCHEME").expect("no scheme"); + + if let Some(port_scmd_matches) = matches.subcommand_matches("port") { + let port = port_scmd_matches + .value_of("PORT") + .expect("invalid utf-8 for PORT argument") + .parse::() + .expect("expected PORT ID"); + + let handle = XhciClientHandle::new(scheme.to_owned(), port); + + if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") { + let state = handle.port_state().expect("Failed to get port state"); + println!("{}", state.as_str()); + } else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") { + let endp_num = endp_scmd_matches + .value_of("ENDPOINT_NUM") + .expect("no valid ENDPOINT_NUM") + .parse::() + .expect("expected ENDPOINT_NUM to be an 8-bit integer"); + let mut endp_handle = handle + .open_endpoint(endp_num) + .expect("Failed to open endpoint"); + let state = endp_handle.status().expect("Failed to get endpoint state"); + println!("{}", state.as_str()); + } + } +} diff --git a/drivers/usb/usbhubd/.gitignore b/drivers/usb/usbhubd/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/usb/usbhubd/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/usb/usbhubd/Cargo.toml b/drivers/usb/usbhubd/Cargo.toml new file mode 100644 index 0000000000..8534ebb2a1 --- /dev/null +++ b/drivers/usb/usbhubd/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "usbhubd" +version = "0.1.0" +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4" +redox_syscall = "0.5" +xhcid = { path = "../xhcid" } + +common = { path = "../../common" } diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs new file mode 100644 index 0000000000..d98706f838 --- /dev/null +++ b/drivers/usb/usbhubd/src/main.rs @@ -0,0 +1,246 @@ +use std::{env, thread, time}; + +use xhcid_interface::{ + plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy, + XhciClientHandle, +}; + +fn main() { + let mut args = env::args().skip(1); + + const USAGE: &'static str = "usbhubd "; + + let scheme = args.next().expect(USAGE); + let port_id = args + .next() + .expect(USAGE) + .parse::() + .expect("Expected port ID"); + let interface_num = args + .next() + .expect(USAGE) + .parse::() + .expect("Expected integer as input of interface"); + + log::info!( + "USB HUB driver spawned with scheme `{}`, port {}, interface {}", + scheme, + port_id, + interface_num + ); + + let name = format!("{}_{}_{}_hub", scheme, port_id, interface_num); + common::setup_logging( + "usb", + "device", + &name, + log::LevelFilter::Warn, + common::file_level(), + ); + + let handle = XhciClientHandle::new(scheme.clone(), port_id); + let desc: DevDesc = handle + .get_standard_descs() + .expect("Failed to get standard descriptors"); + + let (conf_desc, if_desc) = desc + .config_descs + .iter() + .find_map(|conf_desc| { + let if_desc = conf_desc.interface_descs.iter().find_map(|if_desc| { + if if_desc.number == interface_num { + Some(if_desc.clone()) + } else { + None + } + })?; + Some((conf_desc.clone(), if_desc)) + }) + .expect("Failed to find suitable configuration"); + + // Read hub descriptor + let (ports, usb_3) = if desc.major_version() >= 3 { + // USB 3.0 hubs + let mut hub_desc = usb::HubDescriptorV3::default(); + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Device, + usb::SetupReq::GetDescriptor as u8, + u16::from(usb::HubDescriptorV3::DESCRIPTOR_KIND) << 8, + 0, + DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), + ) + .expect("Failed to read hub descriptor"); + (hub_desc.ports, true) + } else { + // USB 2.0 and earlier hubs + let mut hub_desc = usb::HubDescriptorV2::default(); + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Device, + usb::SetupReq::GetDescriptor as u8, + u16::from(usb::HubDescriptorV2::DESCRIPTOR_KIND) << 8, + 0, + DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut hub_desc) }), + ) + .expect("Failed to read hub descriptor"); + (hub_desc.ports, false) + }; + + // Configure as hub device + handle + .configure_endpoints(&ConfigureEndpointsReq { + config_desc: conf_desc.configuration_value, + interface_desc: None, //TODO: stalls on USB 3 hub: Some(interface_num), + alternate_setting: None, //TODO: stalls on USB 3 hub: Some(if_desc.alternate_setting), + hub_ports: Some(ports), + }) + .expect("Failed to configure endpoints after reading hub descriptor"); + + if usb_3 { + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Device, + 0x0c, // SET_HUB_DEPTH + port_id.hub_depth().into(), + 0, + DeviceReqData::NoData, + ) + .expect("Failed to set hub depth"); + } + + // Initialize states + struct PortState { + port_id: PortId, + port_sts: usb::HubPortStatus, + handle: XhciClientHandle, + attached: bool, + } + + impl PortState { + pub fn ensure_attached(&mut self, attached: bool) { + if attached == self.attached { + return; + } + + if attached { + self.handle.attach().expect("Failed to attach"); + } else { + self.handle.detach().expect("Failed to detach"); + } + + self.attached = attached; + } + } + + let mut states = Vec::new(); + for port in 1..=ports { + let child_port_id = port_id.child(port).expect("Cannot get child port ID"); + states.push(PortState { + port_id: child_port_id, + port_sts: if usb_3 { + usb::HubPortStatus::V3(usb::HubPortStatusV3::default()) + } else { + usb::HubPortStatus::V2(usb::HubPortStatusV2::default()) + }, + handle: XhciClientHandle::new(scheme.clone(), child_port_id), + attached: false, + }); + } + + //TODO: use change flags? + loop { + for port in 1..=ports { + let port_idx: usize = port.checked_sub(1).unwrap().into(); + let state = states.get_mut(port_idx).unwrap(); + + let port_sts = if usb_3 { + let mut port_sts = usb::HubPortStatusV3::default(); + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::GetStatus as u8, + 0, + port as u16, + DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), + ) + .expect("Failed to retrieve port status"); + usb::HubPortStatus::V3(port_sts) + } else { + let mut port_sts = usb::HubPortStatusV2::default(); + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::GetStatus as u8, + 0, + port as u16, + DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }), + ) + .expect("Failed to retrieve port status"); + usb::HubPortStatus::V2(port_sts) + }; + if state.port_sts != port_sts { + state.port_sts = port_sts; + log::info!("port {} status {:X?}", port, port_sts); + } + + // Ensure port is powered on + if !port_sts.is_powered() { + log::info!("power on port {port}"); + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::SetFeature as u8, + usb::HubPortFeature::PortPower as u16, + port as u16, + DeviceReqData::NoData, + ) + .expect("Failed to set port power"); + state.ensure_attached(false); + continue; + } + + // Ignore disconnected port + if !port_sts.is_connected() { + state.ensure_attached(false); + continue; + } + + // Ignore port in reset + if port_sts.is_resetting() { + state.ensure_attached(false); + continue; + } + + // Ensure port is enabled + if !port_sts.is_enabled() { + log::info!("reset port {port}"); + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Other, + usb::SetupReq::SetFeature as u8, + usb::HubPortFeature::PortReset as u16, + port as u16, + DeviceReqData::NoData, + ) + .expect("Failed to set port enable"); + state.ensure_attached(false); + continue; + } + + state.ensure_attached(true); + } + + //TODO: use interrupts or poll faster? + thread::sleep(time::Duration::new(1, 0)); + } + + //TODO: read interrupt port for changes +} diff --git a/drivers/usb/xhcid/.gitignore b/drivers/usb/xhcid/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/drivers/usb/xhcid/.gitignore @@ -0,0 +1 @@ +/target diff --git a/drivers/usb/xhcid/Cargo.toml b/drivers/usb/xhcid/Cargo.toml new file mode 100644 index 0000000000..e59d63cd61 --- /dev/null +++ b/drivers/usb/xhcid/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "xhcid" +version = "0.1.0" +edition = "2018" + +[[bin]] +name = "xhcid" +path = "src/main.rs" + +[lib] +name = "xhcid_interface" +path = "src/lib.rs" + +[dependencies] +bitflags = "1" +chashmap = "2.2.2" +crossbeam-channel = "0.4" +futures = "0.3" +plain = "0.2" +lazy_static = "1.4" +log = "0.4" +redox-daemon = "0.1" +redox_event = "0.4.1" +redox-scheme = "0.6.2" +redox_syscall = "0.5" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +smallvec = { version = "1", features = ["serde"] } +thiserror = "1" +toml = "0.5" + +common = { path = "../../common" } +pcid = { path = "../../pcid" } +libredox = "0.1.3" +regex = "1.10.6" diff --git a/drivers/usb/xhcid/config.toml b/drivers/usb/xhcid/config.toml new file mode 100644 index 0000000000..80e781c52a --- /dev/null +++ b/drivers/usb/xhcid/config.toml @@ -0,0 +1,7 @@ +[[drivers]] +name = "XHCI" +class = 0x0C +subclass = 0x03 +interface = 0x30 +command = ["xhcid"] +use_channel = true diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml new file mode 100644 index 0000000000..470ec06321 --- /dev/null +++ b/drivers/usb/xhcid/drivers.toml @@ -0,0 +1,17 @@ +[[drivers]] +name = "SCSI over USB" +class = 8 # Mass Storage class +subclass = 6 # SCSI transparent command set +command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] + +[[drivers]] +name = "USB HUB" +class = 9 # HUB class +subclass = -1 +command = ["usbhubd", "$SCHEME", "$PORT", "$IF_NUM"] + +[[drivers]] +name = "USB HID" +class = 3 # HID class +subclass = -1 +command = ["usbhidd", "$SCHEME", "$PORT", "$IF_NUM"] diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs new file mode 100644 index 0000000000..3268aa3de9 --- /dev/null +++ b/drivers/usb/xhcid/src/driver_interface.rs @@ -0,0 +1,866 @@ +pub extern crate serde; +pub extern crate smallvec; + +use std::convert::TryFrom; +use std::fs::{File, OpenOptions}; +use std::io::prelude::*; +use std::num::NonZeroU8; +use std::{fmt, io, result, str}; + +use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; +use syscall::{Error, Result, EINVAL}; +use thiserror::Error; + +pub use crate::usb::{EndpointTy, ENDP_ATTR_TY_MASK}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ConfigureEndpointsReq { + /// Index into the configuration descriptors of the device descriptor. + pub config_desc: u8, + pub interface_desc: Option, + pub alternate_setting: Option, + pub hub_ports: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DevDesc { + pub kind: u8, + pub usb: u16, + pub class: u8, + pub sub_class: u8, + pub protocol: u8, + pub packet_size: u8, + pub vendor: u16, + pub product: u16, + pub release: u16, + pub manufacturer_str: Option, + pub product_str: Option, + pub serial_str: Option, + pub config_descs: SmallVec<[ConfDesc; 1]>, +} + +impl DevDesc { + pub fn major_version(&self) -> u8 { + ((self.usb & 0xFF00) >> 8) as u8 + } + pub fn minor_version(&self) -> u8 { + self.usb as u8 + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ConfDesc { + pub kind: u8, + pub configuration_value: u8, + pub configuration: Option, + pub attributes: u8, + pub max_power: u8, + pub interface_descs: SmallVec<[IfDesc; 1]>, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct EndpDesc { + pub kind: u8, + pub address: u8, + pub attributes: u8, + pub max_packet_size: u16, + pub interval: u8, + pub ssc: Option, + pub sspc: Option, +} +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum EndpDirection { + Out, + In, + Bidirectional, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum EndpBinaryDirection { + Out, + In, +} + +impl From for EndpBinaryDirection { + fn from(d: PortReqDirection) -> Self { + match d { + PortReqDirection::DeviceToHost => Self::In, + PortReqDirection::HostToDevice => Self::Out, + } + } +} + +impl From for EndpDirection { + fn from(b: EndpBinaryDirection) -> Self { + match b { + EndpBinaryDirection::In => Self::In, + EndpBinaryDirection::Out => Self::Out, + } + } +} + +impl From for EndpDirection { + fn from(d: PortReqDirection) -> Self { + match d { + PortReqDirection::HostToDevice => Self::Out, + PortReqDirection::DeviceToHost => Self::In, + } + } +} + +impl EndpDesc { + pub fn ty(self) -> EndpointTy { + match self.attributes & ENDP_ATTR_TY_MASK { + 0 => EndpointTy::Ctrl, + 1 => EndpointTy::Isoch, + 2 => EndpointTy::Bulk, + 3 => EndpointTy::Interrupt, + _ => unreachable!(), + } + } + pub fn is_control(&self) -> bool { + self.ty() == EndpointTy::Ctrl + } + pub fn is_interrupt(&self) -> bool { + self.ty() == EndpointTy::Interrupt + } + pub fn is_bulk(&self) -> bool { + self.ty() == EndpointTy::Bulk + } + pub fn is_isoch(&self) -> bool { + self.ty() == EndpointTy::Isoch + } + pub fn direction(&self) -> EndpDirection { + if self.is_control() { + return EndpDirection::Bidirectional; + } + if self.address & 0x80 != 0 { + EndpDirection::In + } else { + EndpDirection::Out + } + } + pub fn xhci_ep_type(&self) -> Result { + Ok(match self.direction() { + EndpDirection::Out if self.is_isoch() => 1, + EndpDirection::Out if self.is_bulk() => 2, + EndpDirection::Out if self.is_interrupt() => 3, + EndpDirection::Bidirectional if self.is_control() => 4, + EndpDirection::In if self.is_isoch() => 5, + EndpDirection::In if self.is_bulk() => 6, + EndpDirection::In if self.is_interrupt() => 7, + _ => return Err(Error::new(EINVAL)), + }) + } + pub fn is_superspeed(&self) -> bool { + self.ssc.is_some() + } + pub fn is_superspeedplus(&self) -> bool { + self.sspc.is_some() + } + fn interrupt_usage_bits(&self) -> u8 { + assert!(self.is_interrupt()); + (self.attributes & 0x20) >> 4 + } + pub fn is_periodic(&self) -> bool { + #[repr(u8)] + enum InterruptUsageBits { + Periodic, + Notification, + Rsvd2, + Rsvd3, + } + + if self.is_interrupt() { + self.interrupt_usage_bits() == InterruptUsageBits::Periodic as u8 + } else { + self.is_isoch() + } + } + pub fn log_max_streams(&self) -> Option { + self.ssc + .as_ref() + .map(|ssc| { + if self.is_bulk() { + let raw = ssc.attributes & 0x1F; + NonZeroU8::new(raw) + } else { + None + } + }) + .flatten() + } + pub fn isoch_mult(&self, lec: bool) -> u8 { + if !lec && self.is_isoch() { + if self.is_superspeedplus() { + return 0; + } + self.ssc + .as_ref() + .map(|ssc| ssc.attributes & 0x3) + .unwrap_or(0) + } else { + 0 + } + } + pub fn max_burst(&self) -> u8 { + self.ssc.map(|ssc| ssc.max_burst).unwrap_or(0) + } + pub fn has_ssp_companion(&self) -> bool { + self.ssc + .map(|ssc| ssc.attributes & (1 << 7) != 0) + .unwrap_or(false) + } +} +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IfDesc { + pub kind: u8, + pub number: u8, + pub alternate_setting: u8, + pub class: u8, + pub sub_class: u8, + pub protocol: u8, + pub interface_str: Option, + pub endpoints: SmallVec<[EndpDesc; 4]>, + pub hid_descs: SmallVec<[HidDesc; 1]>, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct SuperSpeedCmp { + pub kind: u8, + pub max_burst: u8, + pub attributes: u8, + pub bytes_per_interval: u16, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct SuperSpeedPlusIsochCmp { + pub kind: u8, + pub bytes_per_interval: u32, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct HidDesc { + pub kind: u8, + pub hid_spec_release: u16, + pub country: u8, + pub desc_count: u8, + pub desc_ty: u8, + pub desc_len: u16, + pub optional_desc_ty: u8, + pub optional_desc_len: u16, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct PortReq { + pub direction: PortReqDirection, + pub req_type: PortReqTy, + pub req_recipient: PortReqRecipient, + pub request: u8, + pub value: u16, + pub index: u16, + pub length: u16, + pub transfers_data: bool, +} +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum PortReqDirection { + HostToDevice, + DeviceToHost, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum PortReqTy { + Class, + Vendor, + Standard, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum PortReqRecipient { + Device, + Interface, + Endpoint, + Other, + VendorSpecific, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct PortId { + pub root_hub_port_num: u8, + pub route_string: u32, +} + +impl PortId { + pub fn root_hub_port_index(&self) -> usize { + self.root_hub_port_num.checked_sub(1).unwrap().into() + } + + pub fn hub_depth(&self) -> u8 { + let mut hub_depth = 0; + let mut route_string = self.route_string; + while route_string != 0 { + route_string >>= 4; + hub_depth += 1; + } + hub_depth + } + + pub fn child(&self, value: u8) -> Result { + let depth = self.hub_depth(); + if depth >= 5 { + return Err(format!("too many route string components")); + } + if value & 0xF0 != 0 { + return Err(format!( + "value {:?} is too large for route string component", + value + )); + } + Ok(Self { + root_hub_port_num: self.root_hub_port_num, + route_string: self.route_string | u32::from(value) << (depth * 4), + }) + } + + pub fn parent(&self) -> Option<(Self, u8)> { + let depth = self.hub_depth(); + let parent_depth = depth.checked_sub(1)?; + let parent_shift = parent_depth * 4; + let parent_mask = 0xF << parent_shift; + Some(( + Self { + root_hub_port_num: self.root_hub_port_num, + route_string: self.route_string & !parent_mask, + }, + u8::try_from((self.route_string & parent_mask) >> parent_shift).unwrap(), + )) + } +} + +impl fmt::Display for PortId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.root_hub_port_num)?; + // USB 3.1 Revision 1.1 Specification Section 8.9 Route String Field + // The Route String is a 20-bit field in downstream directed packets that the hub uses to route + // each packet to the designated downstream port. It is composed of a concatenation of the + // downstream port numbers (4 bits per hub) for each hub traversed to reach a device. + let mut route_string = self.route_string; + while route_string != 0 { + write!(f, ".{}", route_string & 0xF)?; + route_string >>= 4; + } + Ok(()) + } +} + +impl str::FromStr for PortId { + type Err = String; + + fn from_str(s: &str) -> Result { + let mut root_hub_port_num = 0; + let mut route_string = 0; + for (i, part) in s.split('.').enumerate() { + let value: u8 = part + .parse() + .map_err(|e| format!("failed to parse {:?}: {}", part, e))?; + + // Neither root hub port number nor route string support 0 components + // to identify downstream ports + if value == 0 { + return Err(format!("zero is not a valid port ID component")); + } + + // Parse root hub port number + if i == 0 { + root_hub_port_num = value; + continue; + } + + // Parse route string component + let depth = i - 1; + if depth >= 5 { + return Err(format!("too many route string components")); + } + if value & 0xF0 != 0 { + return Err(format!( + "value {:?} is too large for route string component", + value + )); + } + route_string |= u32::from(value) << (depth * 4); + } + Ok(Self { + root_hub_port_num, + route_string, + }) + } +} + +#[derive(Debug)] +pub struct XhciClientHandle { + scheme: String, + port: PortId, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum PortState { + EnabledOrDisabled, + Default, + Addressed, + Configured, +} +impl PortState { + pub fn as_str(&self) -> &'static str { + match self { + Self::EnabledOrDisabled => "enabled_or_disabled", + Self::Default => "default", + Self::Addressed => "addressed", + Self::Configured => "configured", + } + } +} +#[derive(Debug, Error)] +#[error("invalid input")] +pub struct Invalid(pub &'static str); + +impl str::FromStr for PortState { + type Err = Invalid; + + fn from_str(s: &str) -> result::Result { + Ok(match s { + "enabled_or_disabled" | "enabled/disabled" => Self::EnabledOrDisabled, + "default" => Self::Default, + "addressed" => Self::Addressed, + "configured" => Self::Configured, + _ => return Err(Invalid("read reserved port state")), + }) + } +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum EndpointStatus { + Disabled, + Enabled, + Halted, + Stopped, + Error, +} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct PortTransferStatus { + pub kind: PortTransferStatusKind, + pub bytes_transferred: u32, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum PortTransferStatusKind { + Success, + ShortPacket, + Stalled, + Unknown, +} +impl Default for PortTransferStatusKind { + fn default() -> Self { + Self::Success + } +} + +impl EndpointStatus { + pub fn as_str(&self) -> &'static str { + match self { + Self::Disabled => "disabled", + Self::Enabled => "enabled", + Self::Halted => "halted", + Self::Stopped => "stopped", + Self::Error => "error", + } + } +} + +impl str::FromStr for EndpointStatus { + type Err = Invalid; + + fn from_str(s: &str) -> result::Result { + Ok(match s { + "disabled" => Self::Disabled, + "enabled" => Self::Enabled, + "halted" => Self::Halted, + "stopped" => Self::Stopped, + "error" => Self::Error, + _ => return Err(Invalid("read reserved endpoint state")), + }) + } +} + +pub enum DeviceReqData<'a> { + In(&'a mut [u8]), + Out(&'a [u8]), + NoData, +} +impl DeviceReqData<'_> { + pub fn len(&self) -> usize { + match self { + Self::In(buf) => buf.len(), + Self::Out(buf) => buf.len(), + Self::NoData => 0, + } + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn map_buf T>(&self, f: F) -> Option { + match self { + Self::In(sbuf) => Some(f(sbuf)), + Self::Out(dbuf) => Some(f(dbuf)), + _ => None, + } + } + pub fn direction(&self) -> PortReqDirection { + match self { + DeviceReqData::Out(_) => PortReqDirection::HostToDevice, + DeviceReqData::NoData => PortReqDirection::HostToDevice, + DeviceReqData::In(_) => PortReqDirection::DeviceToHost, + } + } +} + +impl XhciClientHandle { + pub fn new(scheme: String, port: PortId) -> Self { + Self { scheme, port } + } + + pub fn attach(&self) -> result::Result<(), XhciClientHandleError> { + let path = format!("/scheme/{}/port{}/attach", self.scheme, self.port); + let mut file = OpenOptions::new().read(false).write(true).open(path)?; + let _bytes_written = file.write(&[])?; + Ok(()) + } + pub fn detach(&self) -> result::Result<(), XhciClientHandleError> { + let path = format!("/scheme/{}/port{}/detach", self.scheme, self.port); + let mut file = OpenOptions::new().read(false).write(true).open(path)?; + let _bytes_written = file.write(&[])?; + Ok(()) + } + pub fn get_standard_descs(&self) -> result::Result { + let path = format!("/scheme/{}/port{}/descriptors", self.scheme, self.port); + let json = std::fs::read(path)?; + Ok(serde_json::from_slice(&json)?) + } + pub fn configure_endpoints( + &self, + req: &ConfigureEndpointsReq, + ) -> result::Result<(), XhciClientHandleError> { + let path = format!("/scheme/{}/port{}/configure", self.scheme, self.port); + let json = serde_json::to_vec(req)?; + let mut file = OpenOptions::new().read(false).write(true).open(path)?; + let json_bytes_written = file.write(&json)?; + if json_bytes_written != json.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid( + "configure_endpoints didn't read as many bytes as were requested", + ))); + } + Ok(()) + } + pub fn port_state(&self) -> result::Result { + let path = format!("/scheme/{}/port{}/state", self.scheme, self.port); + let string = std::fs::read_to_string(path)?; + Ok(string.parse()?) + } + pub fn open_endpoint_ctl(&self, num: u8) -> result::Result { + let path = format!( + "/scheme/{}/port{}/endpoints/{}/ctl", + self.scheme, self.port, num + ); + Ok(File::open(path)?) + } + pub fn open_endpoint_data(&self, num: u8) -> result::Result { + let path = format!( + "/scheme/{}/port{}/endpoints/{}/data", + self.scheme, self.port, num + ); + Ok(File::open(path)?) + } + pub fn open_endpoint(&self, num: u8) -> result::Result { + Ok(XhciEndpHandle { + ctl: self.open_endpoint_ctl(num)?, + data: self.open_endpoint_data(num)?, + }) + } + pub fn device_request<'a>( + &self, + req_type: PortReqTy, + req_recipient: PortReqRecipient, + request: u8, + value: u16, + index: u16, + data: DeviceReqData<'a>, + ) -> result::Result<(), XhciClientHandleError> { + let length = u16::try_from(data.len()) + .or(Err(XhciClientHandleError::TransferBufTooLarge(data.len())))?; + + let req = PortReq { + direction: data.direction(), + req_type, + req_recipient, + request, + value, + index, + length, + transfers_data: !matches!(data, DeviceReqData::NoData), + }; + let json = serde_json::to_vec(&req)?; + + let path = format!("/scheme/{}/port{}/request", self.scheme, self.port); + let mut file = File::open(path)?; + + let json_bytes_written = file.write(&json)?; + if json_bytes_written != json.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid( + "device_request didn't return the same number of bytes as were written", + ))); + } + + match data { + DeviceReqData::In(buf) => { + let bytes_read = file.read(buf)?; + + if bytes_read != buf.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid( + "device_request didn't transfer (host2dev) all bytes", + ))); + } + } + DeviceReqData::Out(buf) => { + let bytes_read = file.write(&buf)?; + + if bytes_read != buf.len() { + return Err(XhciClientHandleError::InvalidResponse(Invalid( + "device_request didn't transfer (dev2host) all bytes", + ))); + } + } + DeviceReqData::NoData => (), + } + Ok(()) + } + pub fn get_descriptor( + &self, + recipient: PortReqRecipient, + ty: u8, + idx: u8, + windex: u16, + buffer: &mut [u8], + ) -> result::Result<(), XhciClientHandleError> { + self.device_request( + PortReqTy::Standard, + recipient, + 0x06, + (u16::from(ty) << 8) | u16::from(idx), + windex, + DeviceReqData::In(buffer), + ) + } + pub fn clear_feature( + &self, + recipient: PortReqRecipient, + index: u16, + feature_sel: u16, + ) -> result::Result<(), XhciClientHandleError> { + self.device_request( + PortReqTy::Standard, + recipient, + 0x01, + feature_sel, + index, + DeviceReqData::NoData, + ) + } +} + +#[derive(Debug)] +pub struct XhciEndpHandle { + data: File, + ctl: File, +} + +/// The direction of a transfer. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum XhciEndpCtlDirection { + /// Host to device + Out, + /// Device to host + In, + /// No data, and hence no I/O on the Data interface file at all. + NoData, +} + +/// A request to an endpoint Ctl interface file. Currently serialized with JSON. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum XhciEndpCtlReq { + // TODO: Reduce the number of direction enums from 5 to perhaps 2. + // TODO: Allow to send multiple buffers in one transfer. + /// Tells xhcid that a buffer is about to be sent from the Data interface file, to the + /// endpoint. + Transfer { + /// The direction of the transfer. If the direction is `XhciEndpCtlDirection::NoData`, no + /// bytes will be transferred, and therefore no reads or writes shall be done to the Data + /// driver interface file. + direction: XhciEndpCtlDirection, + + /// The number of bytes to be read or written. This field must be set to zero if the + /// direction is `XhciEndpCtlDirection::NoData`. When all bytes have been read or written, + /// the transfer will be considered complete by xhcid, and a non-pending status will be + /// returned. + count: u32, + }, + // TODO: Allow clients to specify what to reset. + /// Tells xhcid that the endpoint is going to be reset. + Reset { + /// Only issue the Reset Endpoint and Set TR Dequeue Pointer commands, and let the client + /// itself send a potential ClearFeature(ENDPOINT_HALT). + no_clear_feature: bool, + }, + + /// Tells xhcid that the endpoint status is going to be retrieved from the Ctl interface file. + Status, +} +/// A response from an endpoint Ctl interface file. Currently serialized with JSON. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub enum XhciEndpCtlRes { + /// Xhcid responded with the current state of an endpoint. + Status(EndpointStatus), + + /// Xhci sent the result of a transfer. + TransferResult(PortTransferStatus), + + /// Xhcid is waiting for data to be sent or received on the Data interface file. + Pending, + + /// No Ctl request is currently being processed by xhcid. + Idle, +} + +impl XhciEndpHandle { + fn ctl_req(&mut self, ctl_req: &XhciEndpCtlReq) -> result::Result<(), XhciClientHandleError> { + let ctl_buffer = serde_json::to_vec(ctl_req)?; + + let ctl_bytes_written = self.ctl.write(&ctl_buffer)?; + if ctl_bytes_written != ctl_buffer.len() { + return Err(Invalid("xhcid didn't process all of the ctl bytes").into()); + } + + Ok(()) + } + fn ctl_res(&mut self) -> result::Result { + // a response must never exceed 256 bytes + let mut ctl_buffer = [0u8; 256]; + let ctl_bytes_read = self.ctl.read(&mut ctl_buffer)?; + + let ctl_res = serde_json::from_slice(&ctl_buffer[..ctl_bytes_read as usize])?; + Ok(ctl_res) + } + pub fn reset(&mut self, no_clear_feature: bool) -> result::Result<(), XhciClientHandleError> { + self.ctl_req(&XhciEndpCtlReq::Reset { no_clear_feature }) + } + pub fn status(&mut self) -> result::Result { + self.ctl_req(&XhciEndpCtlReq::Status)?; + match self.ctl_res()? { + XhciEndpCtlRes::Status(s) => Ok(s), + _ => Err(Invalid("expected status response").into()), + } + } + fn generic_transfer io::Result>( + &mut self, + direction: XhciEndpCtlDirection, + f: F, + expected_len: u32, + ) -> result::Result { + let req = XhciEndpCtlReq::Transfer { + direction, + count: expected_len, + }; + self.ctl_req(&req)?; + + let bytes_read = f(&mut self.data)?; + let res = self.ctl_res()?; + + match res { + XhciEndpCtlRes::TransferResult(PortTransferStatus { + kind: PortTransferStatusKind::Success, + .. + }) if bytes_read != expected_len as usize => { + Err(Invalid("no short packet, but fewer bytes were read/written").into()) + } + XhciEndpCtlRes::TransferResult(r) => Ok(r), + _ => Err(Invalid("expected transfer result").into()), + } + } + pub fn transfer_write( + &mut self, + buf: &[u8], + ) -> result::Result { + self.generic_transfer( + XhciEndpCtlDirection::Out, + |data| data.write(buf), + buf.len() as u32, + ) + } + pub fn transfer_read( + &mut self, + buf: &mut [u8], + ) -> result::Result { + let len = buf.len() as u32; + self.generic_transfer(XhciEndpCtlDirection::In, |data| data.read(buf), len) + } + pub fn transfer_nodata(&mut self) -> result::Result { + self.generic_transfer(XhciEndpCtlDirection::NoData, |_| Ok(0), 0) + } + fn transfer_stream(&mut self, total_len: u32) -> TransferStream<'_> { + TransferStream { + bytes_to_transfer: total_len, + bytes_transferred: 0, + bytes_per_transfer: 32768, // TODO + endp_handle: self, + } + } + pub fn transfer_write_stream(&mut self, total_len: u32) -> TransferWriteStream<'_> { + TransferWriteStream { + inner: self.transfer_stream(total_len), + } + } + pub fn transfer_read_stream(&mut self, total_len: u32) -> TransferReadStream<'_> { + TransferReadStream { + inner: self.transfer_stream(total_len), + } + } +} + +pub struct TransferWriteStream<'a> { + inner: TransferStream<'a>, +} +pub struct TransferReadStream<'a> { + inner: TransferStream<'a>, +} +struct TransferStream<'a> { + bytes_to_transfer: u32, + bytes_transferred: u32, + bytes_per_transfer: u32, + endp_handle: &'a mut XhciEndpHandle, +} + +#[derive(Debug, Error)] +pub enum XhciClientHandleError { + #[error("i/o error: {0}")] + IoError(#[from] io::Error), + + #[error("serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + + #[error("invalid response")] + InvalidResponse(#[from] Invalid), + + #[error("transfer buffer too large ({0} > 65536)")] + TransferBufTooLarge(usize), + + #[error("unexpected short packet of size {0}")] + UnexpectedShortPacket(usize), +} diff --git a/drivers/usb/xhcid/src/lib.rs b/drivers/usb/xhcid/src/lib.rs new file mode 100644 index 0000000000..771958a18d --- /dev/null +++ b/drivers/usb/xhcid/src/lib.rs @@ -0,0 +1,30 @@ +//! The eXtensible Host Controller Interface (XHCI) Daemon Interface +//! +//! This crate implements the driver interface for interacting with the Redox xhcid daemon from +//! another userspace process. +//! +//! XHCI is a standard for the USB Host Controller interface specified by Intel that provides a +//! common register interface for systems to use to interact with the Universal Serial Bus (USB) +//! subsystem. +//! +//! USB consists of three types of devices: The Host Controller/Root Hub, USB Hubs, and Endpoints. +//! Endpoints represent actual devices connected to the USB fabric. USB Hubs are intermediaries +//! between the Host Controller and the endpoints that report when devices have been connected/disconnected. +//! The Host Controller provides the interface to the USB subsystem that software running on the +//! system's CPU can interact with. It's a tree-like structure, which the Host Controller enumerating +//! and addressing all the hubs and endpoints in the tree. Data then flows through the fabric +//! using the USB protocol (2.0 or 3.2) as packets. Hubs have multiple ports that endpoints can +//! connect to, and they notify the Host Controller/Root Hub when devices are hot plugged or removed. +//! +//! This documentation will refer directly to the relevant standards, which are as follows: +//! +//! - XHCI - [eXtensible Host Controller Interface for Universal Serial Bus (xHCI) Requirements Specification](https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf) +//! - USB2 - [Universal Serial Bus Specification](https://www.usb.org/document-library/usb-20-specification) +//! - USB32 - [Universal Serial Bus 3.2 Specification Revision 1.1](https://usb.org/document-library/usb-32-revision-11-june-2022) +//! +pub extern crate plain; + +mod driver_interface; +pub mod usb; + +pub use driver_interface::*; diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs new file mode 100644 index 0000000000..debf867c6a --- /dev/null +++ b/drivers/usb/xhcid/src/main.rs @@ -0,0 +1,197 @@ +//! The eXtensible Host Controller Interface (XHCI) Daemon +//! +//! This crate provides the executable xhcid daemon that implements the driver for interacting with +//! a PCIe XHCI device +//! +//! XHCI is a standard for the USB Host Controller interface specified by Intel that provides a +//! common register interface for systems to use to interact with the Universal Serial Bus (USB) +//! subsystem. +//! +//! USB consists of three types of devices: The Host Controller/Root Hub, USB Hubs, and Endpoints. +//! Endpoints represent actual devices connected to the USB fabric. USB Hubs are intermediaries +//! between the Host Controller and the endpoints that report when devices have been connected/disconnected. +//! The Host Controller provides the interface to the USB subsystem that software running on the +//! system's CPU can interact with. It's a tree-like structure, which the Host Controller enumerating +//! and addressing all the hubs and endpoints in the tree. Data then flows through the fabric +//! using the USB protocol (2.0 or 3.2) as packets. Hubs have multiple ports that endpoints can +//! connect to, and they notify the Host Controller/Root Hub when devices are hot plugged or removed. +//! +//! This documentation will refer directly to the relevant standards, which are as follows: +//! +//! - XHCI - [eXtensible Host Controller Interface for Universal Serial Bus (xHCI) Requirements Specification](https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf) +//! - USB2 - [Universal Serial Bus Specification](https://www.usb.org/document-library/usb-20-specification) +//! - USB32 - [Universal Serial Bus 3.2 Specification Revision 1.1](https://usb.org/document-library/usb-32-revision-11-june-2022) +//! +#![allow(warnings)] + +#[macro_use] +extern crate bitflags; + +use std::fs::File; +use std::sync::Arc; + +use pcid_interface::irq_helpers::read_bsp_apic_id; +#[cfg(target_arch = "x86_64")] +use pcid_interface::irq_helpers::{ + allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi, +}; +use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle}; + +use redox_scheme::{RequestKind, SignalBehavior, Socket}; + +use crate::xhci::{InterruptMethod, Xhci}; + +// Declare as pub so that no warnings appear due to parts of the interface code not being used by +// the driver. Since there's also a dedicated crate for the driver interface, those warnings don't +// mean anything. +pub mod driver_interface; + +mod usb; +mod xhci; + +#[cfg(target_arch = "x86_64")] +fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, InterruptMethod) { + let pci_config = pcid_handle.config(); + + let all_pci_features = pcid_handle.fetch_all_features(); + log::debug!("XHCI PCI FEATURES: {:?}", all_pci_features); + + let has_msi = all_pci_features.iter().any(|feature| feature.is_msi()); + let has_msix = all_pci_features.iter().any(|feature| feature.is_msix()); + + if has_msix { + let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { + PciFeatureInfo::Msi(_) => panic!(), + PciFeatureInfo::MsiX(s) => s, + }; + let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; + + // Allocate one msi vector. + + let method = { + // primary interrupter + let k = 0; + + let table_entry_pointer = info.table_entry_pointer(k); + + let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id"); + let (msg_addr_and_data, interrupt_handle) = + allocate_single_interrupt_vector_for_msi(destination_id); + table_entry_pointer.write_addr_and_data(msg_addr_and_data); + table_entry_pointer.unmask(); + + (Some(interrupt_handle), InterruptMethod::Msi) + }; + + pcid_handle.enable_feature(PciFeature::MsiX); + log::debug!("Enabled MSI-X"); + + method + } else if has_msi { + let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle); + (Some(interrupt_handle), InterruptMethod::Msi) + } else if let Some(irq) = pci_config.func.legacy_interrupt_line { + log::debug!("Legacy IRQ {}", irq); + + // legacy INTx# interrupt pins. + (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx) + } else { + // no interrupts at all + (None, InterruptMethod::Polling) + } +} + +//TODO: MSI on non-x86_64? +#[cfg(not(target_arch = "x86_64"))] +fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option, InterruptMethod) { + let pci_config = pcid_handle.config(); + + if let Some(irq) = pci_config.func.legacy_interrupt_line { + // legacy INTx# interrupt pins. + (Some(irq.irq_handle("xhcid")), InterruptMethod::Intx) + } else { + // no interrupts at all + (None, InterruptMethod::Polling) + } +} + +fn main() { + redox_daemon::Daemon::new(daemon).expect("xhcid: failed to daemonize"); +} + +//TODO: cleanup CSZ support +fn daemon_with_context_size( + daemon: redox_daemon::Daemon, + mut pcid_handle: PciFunctionHandle, +) -> ! { + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_xhci"); + + common::setup_logging( + "usb", + "host", + &name, + log::LevelFilter::Warn, + common::file_level(), + ); + + log::debug!("XHCI PCI CONFIG: {:?}", pci_config); + + let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; + + let (irq_file, interrupt_method) = (None, InterruptMethod::Polling); //get_int_method(&mut pcid_handle); + //TODO: Fix interrupts. + + log::info!("XHCI {}", pci_config.func.display()); + + let scheme_name = format!("usb.{}", name); + let socket = Socket::create(scheme_name.clone()).expect("xhcid: failed to create usb scheme"); + + daemon.ready().expect("xhcid: failed to notify parent"); + + let hci = Arc::new( + Xhci::::new(scheme_name, address, interrupt_method, pcid_handle) + .expect("xhcid: failed to allocate device"), + ); + + xhci::start_irq_reactor(&hci, irq_file); + xhci::start_device_enumerator(&hci); + + hci.poll(); + + loop { + let Some(request) = socket + .next_request(SignalBehavior::Restart) + .expect("xhcid: failed to read scheme") + else { + // Scheme likely got unmounted + std::process::exit(0); + }; + + match request.kind() { + RequestKind::Call(call_request) => { + let resp = call_request.handle_sync(&mut &*hci); + socket + .write_response(resp, SignalBehavior::Restart) + .expect("xhcid: failed to write scheme"); + } + RequestKind::OnClose { id } => { + hci.on_close(id); + } + _ => {} + } + } +} + +fn daemon(daemon: redox_daemon::Daemon) -> ! { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; + let cap = unsafe { &mut *(address as *mut xhci::CapabilityRegs) }; + if cap.csz() { + daemon_with_context_size::<{ xhci::CONTEXT_64 }>(daemon, pcid_handle) + } else { + daemon_with_context_size::<{ xhci::CONTEXT_32 }>(daemon, pcid_handle) + } +} diff --git a/drivers/usb/xhcid/src/usb/bos.rs b/drivers/usb/xhcid/src/usb/bos.rs new file mode 100644 index 0000000000..f6a095712d --- /dev/null +++ b/drivers/usb/xhcid/src/usb/bos.rs @@ -0,0 +1,182 @@ +use std::slice; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct BosDescriptor { + pub len: u8, + pub kind: u8, + pub total_len: u16, + pub cap_count: u8, +} + +unsafe impl plain::Plain for BosDescriptor {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct BosDevDescriptorBase { + pub len: u8, + pub kind: u8, + pub cap_ty: u8, +} + +unsafe impl plain::Plain for BosDevDescriptorBase {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct BosSuperSpeedDesc { + pub len: u8, + pub kind: u8, + pub cap_ty: u8, + + pub attrs: u8, + pub speed_supp: u16, + pub func_supp: u8, + pub u1_dev_exit_lat: u8, + pub u2_dev_exit_lat: u16, +} +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct BosSuperSpeedPlusDesc { + pub len: u8, + pub kind: u8, + pub cap_ty: u8, + pub _rsvd0: u8, + pub attrs: u32, + pub func_supp: u32, + pub _rsvd1: u16, +} + +unsafe impl plain::Plain for BosSuperSpeedPlusDesc {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct BosUsb2ExtDesc { + pub len: u8, + pub kind: u8, + pub cap_ty: u8, + + pub attrs: u32, +} + +unsafe impl plain::Plain for BosUsb2ExtDesc {} + +#[repr(u8)] +pub enum DeviceCapability { + Usb2Ext = 0x02, + SuperSpeed, + SuperSpeedPlus = 0x0A, +} + +unsafe impl plain::Plain for BosSuperSpeedDesc {} + +impl BosSuperSpeedPlusDesc { + pub fn ssac(&self) -> u8 { + (self.attrs & 0x0000_000F) as u8 + } + pub fn sublink_speed_attr(&self) -> &[u32] { + unsafe { + slice::from_raw_parts( + (self as *const Self).add(1) as *const u32, + self.ssac() as usize + 1, + ) + } + } +} + +pub struct BosDevDescIter<'a> { + bytes: &'a [u8], +} +impl<'a> BosDevDescIter<'a> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { bytes } + } +} +impl<'a> From<&'a [u8]> for BosDevDescIter<'a> { + fn from(slice: &'a [u8]) -> Self { + Self::new(slice) + } +} +impl<'a> Iterator for BosDevDescIter<'a> { + type Item = (BosDevDescriptorBase, &'a [u8]); + + fn next(&mut self) -> Option { + if let Some(desc) = plain::from_bytes::(self.bytes).ok() { + if desc.len as usize > self.bytes.len() { + return None; + }; + let bytes_ret = &self.bytes[..desc.len as usize]; + self.bytes = &self.bytes[desc.len as usize..]; + Some((*desc, bytes_ret)) + } else { + return None; + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum BosAnyDevDesc { + Usb2Ext(BosUsb2ExtDesc), + SuperSpeed(BosSuperSpeedDesc), + SuperSpeedPlus(BosSuperSpeedPlusDesc), + Unknown, +} + +impl BosAnyDevDesc { + pub fn is_superspeed(&self) -> bool { + match self { + Self::SuperSpeed(_) => true, + _ => false, + } + } + pub fn is_superspeedplus(&self) -> bool { + match self { + Self::SuperSpeedPlus(_) => true, + _ => false, + } + } +} + +pub struct BosAnyDevDescIter<'a> { + inner: BosDevDescIter<'a>, +} +impl<'a> From> for BosAnyDevDescIter<'a> { + fn from(ll: BosDevDescIter<'a>) -> Self { + Self { inner: ll } + } +} +impl<'a> From<&'a [u8]> for BosAnyDevDescIter<'a> { + fn from(slice: &'a [u8]) -> Self { + Self::from(BosDevDescIter::from(slice)) + } +} +impl<'a> Iterator for BosAnyDevDescIter<'a> { + type Item = BosAnyDevDesc; + + fn next(&mut self) -> Option { + let (base, slice) = self.inner.next()?; + + if base.cap_ty == DeviceCapability::Usb2Ext as u8 { + Some(BosAnyDevDesc::Usb2Ext(*plain::from_bytes(slice).ok()?)) + } else if base.cap_ty == DeviceCapability::SuperSpeed as u8 { + Some(BosAnyDevDesc::SuperSpeed(*plain::from_bytes(slice).ok()?)) + } else if base.cap_ty == DeviceCapability::SuperSpeedPlus as u8 { + Some(BosAnyDevDesc::SuperSpeedPlus( + *plain::from_bytes(slice).ok()?, + )) + } else if base.cap_ty == 0 { + // TODO + return None; + } else { + log::warn!("unknown USB device capability of type: {:#x}", base.cap_ty); + Some(BosAnyDevDesc::Unknown) + } + } +} + +pub fn bos_capability_descs<'a>( + desc: BosDescriptor, + data: &'a [u8], +) -> impl Iterator + 'a { + BosAnyDevDescIter::from(&data[..desc.total_len as usize - std::mem::size_of_val(&desc)]) + .take(desc.cap_count as usize) +} diff --git a/drivers/usb/xhcid/src/usb/config.rs b/drivers/usb/xhcid/src/usb/config.rs new file mode 100644 index 0000000000..5d4a23bca7 --- /dev/null +++ b/drivers/usb/xhcid/src/usb/config.rs @@ -0,0 +1,27 @@ +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct ConfigDescriptor { + pub length: u8, + pub kind: u8, + pub total_length: u16, + pub interfaces: u8, + pub configuration_value: u8, + pub configuration_str: u8, + pub attributes: u8, + pub max_power: u8, +} + +unsafe impl plain::Plain for ConfigDescriptor {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct OtherSpeedConfig { + pub length: u8, + pub kind: u8, + pub total_length: u16, + pub interfaces: u8, + pub configuration_value: u8, + pub configuration_str: u8, + pub attributes: u8, + pub max_power: u8, +} diff --git a/drivers/usb/xhcid/src/usb/device.rs b/drivers/usb/xhcid/src/usb/device.rs new file mode 100644 index 0000000000..accac1c316 --- /dev/null +++ b/drivers/usb/xhcid/src/usb/device.rs @@ -0,0 +1,191 @@ +//! Implements the "Device" USB Descriptor. +//! +//! This descriptor is described in USB32 section 9.6.1 + +/// A USB Device Descriptor. +/// +/// This is common to all USB standards, and "provides information that applies globally to the +/// device and all the device's configurations" (USB32 9.6.1) +/// +/// A given device will only have one device descriptor. +/// +/// USB32 Table 9-11 describes the USB packet offsets of the fields described by this structure. +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct DeviceDescriptor { + /// The length of this descriptor in bytes. + /// The bLength field in USB32 Table 9-11 + pub length: u8, + /// The descriptor type. See [DescriptorKind] + /// The bDescriptorType field in USB32 Table 9-11. + pub kind: u8, + /// The USB standard version in binary-coded decimal. + /// + /// USB 2.1 would be encoded as 210H, 3.2 would be 320H. + /// The bcdUSB field in USB32 Table 9-11 + pub usb: u16, + /// The USB Class Code. + /// + /// bDeviceClass in USB32 Table 9-11. + /// + /// These are values assigned by USB-IF that describes the type of device connected via USB. + /// + /// A value of FF indicates a vendor-specific class. A value of 0 indicates that all the + /// interfaces in a configuration will provide their own class information. + pub class: u8, + /// The USB Sub Device Class Code. + /// + /// bDeviceSubClass in USB32 Table 9-11 + /// + /// These specify subclasses of a device class specified by the 'class' field. + pub sub_class: u8, + /// The USB Protocol code. + /// + /// bDeviceProtocol in USB32 Table 9-11 + /// + /// This qualified by the class and sub_class fields, and specifies the application-layer protocol + /// (the protocol encapsulated by USB) of this device. + pub protocol: u8, + /// The maximum packet size for endpoint 0. + /// + /// bMaxPacketSize0 in USB32 Table 9-11 + pub packet_size: u8, + /// The USB Vendor ID + /// + /// idVendor in USB32 Table 9-11 + pub vendor: u16, + /// The USB Product ID + /// + /// idProduct in USB32 Table 9-11 + pub product: u16, + /// The device release number in binary-coded decimal. + /// + /// bcdDevice in USB32 Table 9-11 + pub release: u16, + /// Index of the String Descriptor describing the device manufacturer + /// + /// iManufacturer in USB32 Table 9-11 + pub manufacturer_str: u8, + /// Index of the String Descriptor describing the product + /// + /// iProduct in Table 9-11 + pub product_str: u8, + /// Index of the string descriptor describing the device's serial number + /// + /// iSerialNumber in USB32 Table 9-11 + pub serial_str: u8, + /// The number of possible configurations (Configuration Descriptors) for this device. + /// + /// bNumConfigurations in USB32 Table 9-11 + pub configurations: u8, +} + +unsafe impl plain::Plain for DeviceDescriptor {} + +impl DeviceDescriptor { + /// Gets the USB Minor Version + pub fn minor_usb_vers(&self) -> u8 { + (self.usb & 0xFF) as u8 + } + /// Gets the USB Major Version + pub fn major_usb_vers(&self) -> u8 { + ((self.usb >> 8) & 0xFF) as u8 + } +} + +/// The 8-byte version of the Device Descriptor +/// +/// This is a subset of the full Device Descriptor. When the system is first performing device +/// enumeration, it will request only the first eight bytes of the DeviceDescriptor from each +/// device as this contains the crucial information, and then it will request the full descriptor +/// at a later point. +/// +/// See [DeviceDescriptor] +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct DeviceDescriptor8Byte { + /// See [DeviceDescriptor] + pub length: u8, + /// See [DeviceDescriptor] + pub kind: u8, + /// See [DeviceDescriptor] + pub usb: u16, + /// See [DeviceDescriptor] + pub class: u8, + /// See [DeviceDescriptor] + pub sub_class: u8, + /// See [DeviceDescriptor] + pub protocol: u8, + /// See [DeviceDescriptor] + pub packet_size: u8, +} + +unsafe impl plain::Plain for DeviceDescriptor8Byte {} + +impl DeviceDescriptor8Byte { + /// Gets the USB Minor Version + pub fn minor_usb_vers(&self) -> u8 { + (self.usb & 0xFF) as u8 + } + + /// Gets the USB Major Version + pub fn major_usb_vers(&self) -> u8 { + ((self.usb >> 8) & 0xFF) as u8 + } +} + +/// A Device Qualifier Descriptor +/// +/// This is a descriptor specific to the USB2 standard, and was deprecated in USB3. USB2 devices +/// will still provide this value. +/// +/// A Device Qualifier is sent by a high-speed capable USB2 device to describe information in its +/// descriptor that would change if it was operating at the other speed. If it was at low speed, +/// the qualifier would describe the device at high speed. If it was at high speed, the qualifier +/// would describe the device at low speed. +/// +/// See USB2 section 9.6.2 +/// +/// The packet offsets are described in USB2 Table 9-9 +#[repr(C, packed)] +pub struct DeviceQualifier { + /// The size of the descriptor. + /// + /// bLength in USB2 Table 9-9 + pub length: u8, + /// The Device Descriptor Type (see [xhci_interface::usb::DescriptorKind]) + /// + /// bDescriptorType in USB2 Table 9-9 + pub kind: u8, + /// The USB specification version number in binary-coded decimal + /// + /// bDeviceClass in USB2 Table 9-9 + pub usb: u16, + /// The USB Device Class Code + /// + /// bDeviceClass in USB2 Table 9-9 + pub class: u8, + /// The USB Device Sub Class Code + /// + /// bDeviceSubClass in USB2 Table 9-9 + pub sub_class: u8, + /// The USB Device Protocol Code + /// + /// bDeviceProtocol in USB2 Table 9-9 + pub protocol: u8, + /// The maximum packet size for the other speed\ + /// + /// bMaxPacketSize0 in USB2 Table9-9 + pub pkgsz_other_speed: u8, + /// The number of device configurations for the other speed + /// + /// bNumConfiguration in USB2 Table 9-9 + pub num_other_speed_cfgs: u8, + /// Reserved for future use by the USB2 standard + /// + /// (DeviceQualifier was dropped in USB3, so it was never used!) + /// bReserved in USB2 Table 9-9 + pub _rsvd: u8, +} + +unsafe impl plain::Plain for DeviceQualifier {} diff --git a/drivers/usb/xhcid/src/usb/endpoint.rs b/drivers/usb/xhcid/src/usb/endpoint.rs new file mode 100644 index 0000000000..e0f3510a7d --- /dev/null +++ b/drivers/usb/xhcid/src/usb/endpoint.rs @@ -0,0 +1,86 @@ +use plain::Plain; + +/// The descriptor for a USB Endpoint. +/// +/// Each endpoint for a particular interface has its own descriptor. The information in this +/// structure is used by the host to determine the bandwidth requirements of the endpoint. +/// +/// This is returned automatically when you send a request for a ConfigurationDescriptor, +/// and cannot be requested individually. +/// +/// See USB32 9.6.6 +/// +/// The offsets for the fields in the packet are described in USB32 Table 9-26 +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct EndpointDescriptor { + pub length: u8, + pub kind: u8, + pub address: u8, + pub attributes: u8, + pub max_packet_size: u16, + pub interval: u8, +} + +/// Mask that is ANDed to the [EndpointDescriptor].attributes buffer to get the endpoint type. +pub const ENDP_ATTR_TY_MASK: u8 = 0x3; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum EndpointTy { + Ctrl = 0, + Isoch = 1, + Bulk = 2, + Interrupt = 3, +} + +impl EndpointDescriptor { + fn ty(self) -> EndpointTy { + match self.attributes & ENDP_ATTR_TY_MASK { + 0 => EndpointTy::Ctrl, + 1 => EndpointTy::Isoch, + 2 => EndpointTy::Bulk, + 3 => EndpointTy::Interrupt, + _ => unreachable!(), + } + } +} + +unsafe impl Plain for EndpointDescriptor {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct SuperSpeedCompanionDescriptor { + pub length: u8, + pub kind: u8, + pub max_burst: u8, + pub attributes: u8, + pub bytes_per_interval: u16, +} +unsafe impl Plain for SuperSpeedCompanionDescriptor {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct SuperSpeedPlusIsochCmpDescriptor { + pub length: u8, + pub kind: u8, + pub reserved: u16, + pub bytes_per_interval: u32, +} +unsafe impl Plain for SuperSpeedPlusIsochCmpDescriptor {} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct HidDescriptor { + pub length: u8, + pub kind: u8, + pub hid_spec_release: u16, + pub country_code: u8, + pub num_descriptors: u8, + pub report_desc_ty: u8, + pub report_desc_len: u16, + pub optional_desc_ty: u8, + pub optional_desc_len: u16, +} + +unsafe impl Plain for HidDescriptor {} diff --git a/drivers/usb/xhcid/src/usb/hub.rs b/drivers/usb/xhcid/src/usb/hub.rs new file mode 100644 index 0000000000..fbdb860624 --- /dev/null +++ b/drivers/usb/xhcid/src/usb/hub.rs @@ -0,0 +1,187 @@ +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct HubDescriptorV2 { + pub length: u8, + pub kind: u8, + pub ports: u8, + pub characteristics: u16, + pub power_on_good: u8, + pub current: u8, + /*TODO: USB 2 and 3 disagree on the descriptor, so some fields are disabled + // device_removable: bitmap of ports, maximum of 256 bits (32 bytes) + // power_control_mask: bitmap of ports, maximum of 256 bits (32 bytes) + bitmaps: [u8; 64], + */ +} + +unsafe impl plain::Plain for HubDescriptorV2 {} + +impl HubDescriptorV2 { + pub const DESCRIPTOR_KIND: u8 = 0x29; +} + +impl Default for HubDescriptorV2 { + fn default() -> Self { + Self { + length: 0, + kind: 0, + ports: 0, + characteristics: 0, + power_on_good: 0, + current: 0, + /* + bitmaps: [0; 64], + */ + } + } +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct HubDescriptorV3 { + pub length: u8, + pub kind: u8, + pub ports: u8, + pub characteristics: u16, + pub power_on_good: u8, + pub current: u8, + pub decode_latency: u8, + pub delay: u16, + /*TODO: USB 2 and 3 disagree on the descriptor, so some fields are disabled + // device_removable: bitmap of ports, maximum of 256 bits (32 bytes) + // power_control_mask: bitmap of ports, maximum of 256 bits (32 bytes) + bitmaps: [u8; 64], + */ +} + +unsafe impl plain::Plain for HubDescriptorV3 {} + +impl HubDescriptorV3 { + pub const DESCRIPTOR_KIND: u8 = 0x2A; +} + +impl Default for HubDescriptorV3 { + fn default() -> Self { + Self { + length: 0, + kind: 0, + ports: 0, + characteristics: 0, + power_on_good: 0, + current: 0, + decode_latency: 0, + delay: 0, + /* + bitmaps: [0; 64], + */ + } + } +} + +// This only includes matching features from both USB 2.0 and 3.0 specs +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum HubPortFeature { + PortConnection = 0, + PortOverCurrent = 3, + PortReset = 4, + PortLinkState = 5, + PortPower = 8, + CPortConnection = 16, + CPortOverCurrent = 19, + CPortReset = 20, +} + +bitflags::bitflags! { + #[derive(Default)] + #[repr(transparent)] + pub struct HubPortStatusV2: u32 { + const CONNECTION = 1 << 0; + const ENABLE = 1 << 1; + const SUSPEND = 1 << 2; + const OVER_CURRENT = 1 << 3; + const RESET = 1 << 4; + // bits 5-7 reserved + const POWER = 1 << 8; + const LOW_SPEED = 1 << 9; + const HIGH_SPEED = 1 << 10; + const TEST = 1 << 11; + const INDICATOR = 1 << 12; + // bits 13-15 reserved + const CONNECTION_CHANGED = 1 << 16; + const ENABLE_CHANGED = 1 << 17; + const SUSPEND_CHANGED = 1 << 18; + const OVER_CURRENT_CHANGED = 1 << 19; + const RESET_CHANGED = 1 << 20; + // bits 21 - 31 reserved + } +} + +unsafe impl plain::Plain for HubPortStatusV2 {} + +bitflags::bitflags! { + #[derive(Default)] + #[repr(transparent)] + pub struct HubPortStatusV3: u32 { + const CONNECTION = 1 << 0; + const ENABLE = 1 << 1; + // bit 2 reserved + const OVER_CURRENT = 1 << 3; + const RESET = 1 << 4; + const LINK_STATE_0 = 1 << 5; + const LINK_STATE_1 = 1 << 6; + const LINK_STATE_2 = 1 << 7; + const LINK_STATE_3 = 1 << 8; + const POWER = 1 << 9; + const SPEED_0 = 1 << 10; + const SPEED_1 = 1 << 11; + const SPEED_2 = 1 << 12; + // bits 13 - 15 reserved + const CONNECTION_CHANGED = 1 << 16; + // bits 17-18 + const OVER_CURRENT_CHANGED = 1 << 19; + const RESET_CHANGED = 1 << 20; + const BH_RESET_CHANGED = 1 << 21; + const LINK_STATE_CHANGED = 1 << 22; + const CONFIG_ERROR = 1 << 23; + // bits 24 - 31 reserved + } +} + +unsafe impl plain::Plain for HubPortStatusV3 {} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HubPortStatus { + V2(HubPortStatusV2), + V3(HubPortStatusV3), +} + +impl HubPortStatus { + pub fn is_powered(&self) -> bool { + match self { + Self::V2(x) => x.contains(HubPortStatusV2::POWER), + Self::V3(x) => x.contains(HubPortStatusV3::POWER), + } + } + + pub fn is_connected(&self) -> bool { + match self { + Self::V2(x) => x.contains(HubPortStatusV2::CONNECTION), + Self::V3(x) => x.contains(HubPortStatusV3::CONNECTION), + } + } + + pub fn is_resetting(&self) -> bool { + match self { + Self::V2(x) => x.contains(HubPortStatusV2::RESET), + Self::V3(x) => x.contains(HubPortStatusV3::RESET), + } + } + + pub fn is_enabled(&self) -> bool { + match self { + Self::V2(x) => x.contains(HubPortStatusV2::ENABLE), + Self::V3(x) => x.contains(HubPortStatusV3::ENABLE), + } + } +} diff --git a/drivers/usb/xhcid/src/usb/interface.rs b/drivers/usb/xhcid/src/usb/interface.rs new file mode 100644 index 0000000000..4b60e06cf0 --- /dev/null +++ b/drivers/usb/xhcid/src/usb/interface.rs @@ -0,0 +1,18 @@ +use plain::Plain; + +/// +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct InterfaceDescriptor { + pub length: u8, + pub kind: u8, + pub number: u8, + pub alternate_setting: u8, + pub endpoints: u8, + pub class: u8, + pub sub_class: u8, + pub protocol: u8, + pub interface_str: u8, +} + +unsafe impl Plain for InterfaceDescriptor {} diff --git a/drivers/usb/xhcid/src/usb/mod.rs b/drivers/usb/xhcid/src/usb/mod.rs new file mode 100644 index 0000000000..d0d659882a --- /dev/null +++ b/drivers/usb/xhcid/src/usb/mod.rs @@ -0,0 +1,63 @@ +//! The Universal Serial Bus (USB) Module +//! +//! The implementations in this module are common to all USB interfaces (though individual elements +//! may be specific to only 2.0 or 3.2), and are used by specialized driver components like [xhci] +//! to implement the driver interface. +//! +//! The [Universal Serial Bus Specification](https://www.usb.org/document-library/usb-20-specification) and the [Universal Serial Bus 3.2 Specification](https://usb.org/document-library/usb-32-revision-11-june-2022) are +//! the documents that inform this implementation. +//! +//! See the crate-level documentation for the acronyms used to refer to specific documents. +pub use self::bos::{bos_capability_descs, BosAnyDevDesc, BosDescriptor, BosSuperSpeedDesc}; +pub use self::config::ConfigDescriptor; +pub use self::device::{DeviceDescriptor, DeviceDescriptor8Byte}; +pub use self::endpoint::{ + EndpointDescriptor, EndpointTy, HidDescriptor, SuperSpeedCompanionDescriptor, + SuperSpeedPlusIsochCmpDescriptor, ENDP_ATTR_TY_MASK, +}; +pub use self::hub::*; +pub use self::interface::InterfaceDescriptor; +pub use self::setup::{Setup, SetupReq}; + +/// Enumerates the list of descriptor kinds that can be reported by a USB device to report its +/// attributes to the system. (See USB32 Sections 9.5 and 9.6) +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum DescriptorKind { + /// No Descriptor TODO: Determine why this state exists, and what it does in the code. + None = 0, + /// A Device Descriptor. See [DeviceDescriptor] + Device = 1, + /// A Configuration Descriptor. See [ConfigDescriptor] + Configuration = 2, + /// A String Descriptor. See (USB32 Section 9.6.9). + String = 3, + /// An Interface Descriptor. See [InterfaceDescriptor] + Interface = 4, + /// An Endpoint Descriptor. See [EndpointDescriptor] + Endpoint = 5, + /// A Device Qualifier. USB2-specific. See [DeviceQualifier] + DeviceQualifier = 6, + /// The "Other Speed Configuration" descriptor. USB2-specific. See (USB2 9.6.4] + OtherSpeedConfiguration = 7, + /// TODO: Determine the standard that specifies this + InterfacePower = 8, + /// TODO: Determine the standard that specifies this (Possibly USB-C?) + OnTheGo = 9, + /// A Binary Device Object Store Descriptor. See [BosDescriptor] + BinaryObjectStorage = 15, + /// TODO: Track down the HID standard for references + Hid = 33, + /// A USB Hub Device Descriptor. See [HubDescriptor] + Hub = 41, + /// A Super Speed Endpoint Companion Descriptor. See [SuperSpeedCompanionDescriptor] + SuperSpeedCompanion = 48, +} + +pub(crate) mod bos; +pub(crate) mod config; +pub(crate) mod device; +pub(crate) mod endpoint; +pub(crate) mod hub; +pub(crate) mod interface; +pub(crate) mod setup; diff --git a/drivers/usb/xhcid/src/usb/setup.rs b/drivers/usb/xhcid/src/usb/setup.rs new file mode 100644 index 0000000000..dc315ac502 --- /dev/null +++ b/drivers/usb/xhcid/src/usb/setup.rs @@ -0,0 +1,209 @@ +use super::DescriptorKind; +use crate::driver_interface::*; + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +pub struct Setup { + pub kind: u8, + pub request: u8, + pub value: u16, + pub index: u16, + pub length: u16, +} + +#[repr(u8)] +pub enum ReqDirection { + HostToDevice = 0, + DeviceToHost = 1, +} +impl From for ReqDirection { + fn from(d: PortReqDirection) -> Self { + match d { + PortReqDirection::DeviceToHost => Self::DeviceToHost, + PortReqDirection::HostToDevice => Self::HostToDevice, + } + } +} + +#[repr(u8)] +pub enum ReqType { + /// Standard device requests, such as SET_ADDRESS and SET_CONFIGURATION. These aren't directly + /// accessible using the API, but are sent from xhcid when required. + Standard = 0, + + /// Class specific requests that are directly accessible from the API. + Class = 1, + + /// Vendor specific requests that are accessible using the API. + Vendor = 2, + + /// Reserved + Reserved = 3, +} +impl From for ReqType { + fn from(d: PortReqTy) -> Self { + match d { + PortReqTy::Standard => Self::Standard, + PortReqTy::Class => Self::Class, + PortReqTy::Vendor => Self::Vendor, + } + } +} + +#[repr(u8)] +pub enum ReqRecipient { + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3, + // 4..=30 are reserved + VendorSpecific = 31, +} +impl From for ReqRecipient { + fn from(d: PortReqRecipient) -> Self { + match d { + PortReqRecipient::Device => Self::Device, + PortReqRecipient::Interface => Self::Interface, + PortReqRecipient::Endpoint => Self::Endpoint, + PortReqRecipient::Other => Self::Other, + PortReqRecipient::VendorSpecific => Self::VendorSpecific, + } + } +} + +#[repr(u8)] +pub enum SetupReq { + GetStatus = 0x00, + ClearFeature = 0x01, + SetFeature = 0x03, + SetAddress = 0x05, + GetDescriptor = 0x06, + SetDescriptor = 0x07, + GetConfiguration = 0x08, + SetConfiguration = 0x09, + GetInterface = 0x0A, + SetInterface = 0x0B, + SynchFrame = 0x0C, +} + +pub const USB_SETUP_DIR_BIT: u8 = 1 << 7; +pub const USB_SETUP_DIR_SHIFT: u8 = 7; +pub const USB_SETUP_REQ_TY_MASK: u8 = 0x60; +pub const USB_SETUP_REQ_TY_SHIFT: u8 = 5; +pub const USB_SETUP_RECIPIENT_MASK: u8 = 0x1F; +pub const USB_SETUP_RECIPIENT_SHIFT: u8 = 0; + +impl Setup { + pub fn direction(&self) -> ReqDirection { + if self.kind & USB_SETUP_DIR_BIT == 0 { + ReqDirection::HostToDevice + } else { + ReqDirection::DeviceToHost + } + } + pub const fn req_ty(&self) -> u8 { + (self.kind & USB_SETUP_REQ_TY_MASK) >> USB_SETUP_REQ_TY_SHIFT + } + + pub const fn req_recipient(&self) -> u8 { + (self.kind & USB_SETUP_RECIPIENT_MASK) >> USB_SETUP_RECIPIENT_SHIFT + } + pub fn is_allowed_from_api(&self) -> bool { + self.req_ty() == ReqType::Class as u8 || self.req_ty() == ReqType::Vendor as u8 + } + + pub const fn get_status() -> Self { + Self { + kind: 0b1000_0000, + request: 0x00, + value: 0, + index: 0, + length: 2, + } + } + + pub const fn clear_feature(feature: u16) -> Self { + Self { + kind: 0b0000_0000, + request: 0x01, + value: feature, + index: 0, + length: 0, + } + } + + pub const fn set_feature(feature: u16) -> Self { + Self { + kind: 0b0000_0000, + request: 0x03, + value: feature, + index: 0, + length: 0, + } + } + + pub const fn set_address(address: u16) -> Self { + Self { + kind: 0b0000_0000, + request: 0x05, + value: address, + index: 0, + length: 0, + } + } + + pub const fn get_descriptor( + kind: DescriptorKind, + index: u8, + language: u16, + length: u16, + ) -> Self { + Self { + kind: 0b1000_0000, + request: 0x06, + value: ((kind as u16) << 8) | (index as u16), + index: language, + length: length, + } + } + + pub const fn set_descriptor(kind: u8, index: u8, language: u16, length: u16) -> Self { + Self { + kind: 0b0000_0000, + request: 0x07, + value: ((kind as u16) << 8) | (index as u16), + index: language, + length: length, + } + } + + pub const fn get_configuration() -> Self { + Self { + kind: 0b1000_0000, + request: 0x08, + value: 0, + index: 0, + length: 1, + } + } + + pub const fn set_configuration(value: u8) -> Self { + Self { + kind: 0b0000_0000, + request: 0x09, + value: value as u16, + index: 0, + length: 0, + } + } + + pub const fn set_interface(interface: u8, alternate_setting: u8) -> Self { + Self { + kind: 0b0000_0001, + request: 0x0B, + value: alternate_setting as u16, + index: interface as u16, + length: 0, + } + } +} diff --git a/drivers/usb/xhcid/src/xhci/capability.rs b/drivers/usb/xhcid/src/xhci/capability.rs new file mode 100644 index 0000000000..2ad4ad1aa0 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/capability.rs @@ -0,0 +1,225 @@ +use common::io::{Io, Mmio}; + +/// Represents the memory-mapped Capability Registers of the XHCI +/// +/// These are read-only registers that specify the capabilities +/// of the host controller implementation. +/// +/// They are used by the driver to determine what subsystems to +/// configure during initialization. +/// +/// See XHCI Section 5.3. Table 5-9 describes the offsets of the registers +/// in memory. +#[repr(C, packed)] +pub struct CapabilityRegs { + /// The length of the Capability Registers data structure in XHCI memory. + /// + /// While only the registers in this structure are defined by the XHCI standard, + /// the standard defines an arbitrary amount of space following those registers that + /// are reserved for the standard. As such, you need to know the offset to the operational + /// registers, which immediately follow. + /// + /// CAPLENGTH in XHC Table 5-9. See XHC 5.3.1 + pub len: Mmio, + /// Reserved byte + /// + /// Rsvd in XHC Table 5-9 + _rsvd: Mmio, + /// The XHCI interface version number in Binary-Encoded Decimal. + /// + /// This specifies the version of the XHCI specification that is supported by this controller. + /// HCIVERSION in XHC Table 5-9 + pub hci_ver: Mmio, + /// The HCI Structural Parameters 1 Register. + /// + /// -Bits 0 - 7 describe the number of device slots supported by this controller + /// -Bits 8 - 18 describe the number of interrupters supported by this controller + /// -Bits 19-23 are reserved + /// -Bits 24-31 specify the maximum number of ports supported by this controller. + /// + /// HCPARAMS1 in XHC Table 5-9. See 5.3.3 + pub hcs_params1: Mmio, + /// The HCI Structural Parameters 2 Register. + /// + /// - Bits 0-3 describe the Isochronus Scheduling Threshold (IST) + /// - Bits 4-7 describe the Event Ring Segment Table Max (ERST Max). The maximum number of event + /// ring segment table entries is 2^(ERST Max) + /// - Bits 8-20 are reserved + /// - Bits 25-21 describe the high order five bits of the maximum number of scratchpad buffers + /// - Bit 26 is the Scratchpad Restore Buffer (SPR). (See XHC 4.23.2) + /// - Bits 26-31 describe the low order five bits of the maximum number of scratchpad buffers + /// + /// HCPARAMS2 in XHC Table 5-9. See 5.3.4 + pub hcs_params2: Mmio, + /// The HCI Structural Parameters 3 Register. + /// + /// - Bits 0-7 describes the worst-case U1 Device Exit Latency. Values are in microseconds, from 00h to 0Ah. 0B-FFh are reserved + /// - Bits 8-15 are reserved + /// - Bits 16-31 describe the worst-case U2 Device Exit Latency. Values are in microseconds, from 0000h to 07FFh. 0800-FFFFh are reserved + /// + /// HCPARAMS3 in XHC Table 5-9. See XHC 5.3.5 + pub hcs_params3: Mmio, + /// The HCI Capability Parameters 1 Register. + /// + /// This register defines optional capabilities supported by the xHCI + /// + /// - Bit 0 is the 64-bit Address Capability Flag (AC64). 0 = 32-bit pointers, 1 = 64-bit pointers. + /// - Bit 1 is the Bandwidth Negotation Capability Flag (BNC) + /// - Bit 2 is the Context Size Flag (CSZ). 0 = 32-byte, 1 = 64-byte Context Data Structures + /// - Bit 3 is the Port Power Control Flag (PPC). Indicates whether the implementation supports port power control. + /// - Bit 4 is the Port Indicators Flag (PIND). Indicates whether the XHC root hub supports port indicator control + /// - Bit 5 is the Light Host Controller Reset Capability Flag (LHRC). Indicates whether the implementation supports a light reset + /// - Bit 6 is the Latency Tolerance Messaging Capability Flag (LTC). Indicates whether the implementation supports Latency Tolerance Messaging + /// - Bit 7 is the no Secondary SID Support Flag (NSS). Indicates whether secondary stream ids is supported. 1 = NO, 0 = YES + /// - Bit 8 is the Parse All Event Data Flag (PAE). (See XHC Table 5-13) + /// - Bit 9 is the Stopped - Short Packet Capability Flag (SPC). (See XHC 4.6.9) + /// - Bit 10 is the Stopped EDTLA Capability Flag (SEC). (See XHC 4.6.9, 4.12, and 6.4.4.1) + /// - Bit 11 is the Contiguous Frame ID Capability Flag (CFC). (See XHC 4.11.2.5) + /// - Bits 12-15 are the Maximum Primary Stream Array Size (MaxPSASize). Identifies the maximum size of PSA that the implementation supports. + /// - Bits 16-31 The xHCI Extended Capabilities Pointer (xECP). Points to an extended capabilities list. (See XHC Table 5-13 to see how to process this value) + /// + /// HCCPARAMS1 in XHC Table 5-9. See XHC 5.3.6 + pub hcc_params1: Mmio, + /// The Doorbell Offset Register + /// + /// This register defines the offset of the Doorbell Array base address from the Base. + /// + /// Bits 0-1 are reserved. + /// Bits 2-31 contain the offset. + /// + /// DBOFF in XHC Table 5-9. See XHC 5.3.7 + pub db_offset: Mmio, + /// The Runtime Register Space Offset + /// + /// The offset of the xHCI Runtime Registers from the Base. + /// + /// - Bits 0-4 are reserved. + /// - Bits 5-31 contain the offset. + /// + /// RTSOFF in XHC Table 5-9. See XHC 5.3.8 + pub rts_offset: Mmio, + /// The HC Capability Parameters 2 Register + /// + /// This register defines optional capabilities supported by the xHCI + /// + /// - Bit 0 is the UC3 Entry Capability Flag (U3C). See XHC 4.15.1 + /// - Bit 1 is the Configure Endpoint Command Max Latency Too Large Capability Flag (CMC). See XHC 4.23.5.2 and 5.4.1 + /// - Bit 2 is the Force Save Context Capability (FCS). See XHC 4.23.2 and 5.4.1 + /// - Bit 3 is the Compliance Transition Capability (CTC). See XHC 4.19.2.4.1 + /// - Bit 4 is the Large ESIT Payload Capability (LEC). See XHC 6.2.3.8 + /// - Bit 5 is the Configuration Information Capability (CIC). See XHC 6.2.5.1 + /// - Bit 6 is the Extended TBC Capability (ETC). See XHC 4.11.2.3 + /// - Bit 7 is the Extended TBC TRB Status Capability (ETC_TSC). See XHC 4.11.2.3 + /// - Bit 8 is the Get/Set Extended Property Capability (GSC). See Sections XHC 4.6.17 and 4.6.18 + /// - Bits 10-31 are reserved. + pub hcc_params2: Mmio, + //TODO: VTIOSOFF register for I/O virtualization +} + +/// The mask to use to get the AC64 bit from HCCPARAMS1. See [CapabilityRegs] +pub const HCC_PARAMS1_AC64_BIT: u32 = 1 << HCC_PARAMS1_AC64_SHIFT; +/// The shift to use to get the AC64 bit from HCCParams1. See [CapabilityRegs] +pub const HCC_PARAMS1_AC64_SHIFT: u8 = 0; +/// The mask to use to get the CSZ bit from HCCPARAMS1. See [CapabilityRegs] +pub const HCC_PARAMS1_CSZ_BIT: u32 = 1 << HCC_PARAMS1_CSZ_SHIFT; +/// The shift to use to get the CSZ bit from HCCParams1. See [CapabilityRegs] +pub const HCC_PARAMS1_CSZ_SHIFT: u8 = 2; +/// The Mask to use to get the MAXPSASIZE value from HCCParams1. See [CapabilityRegs] +pub const HCC_PARAMS1_MAXPSASIZE_MASK: u32 = 0xF000; // 15:12 +/// The shift to use to get the MAXPSASIZE value from HCCParams1. See [CapabilityRegs] +pub const HCC_PARAMS1_MAXPSASIZE_SHIFT: u8 = 12; +/// The mask to use to get the XECP value from HCCParams1. See [CapabilityRegs] +pub const HCC_PARAMS1_XECP_MASK: u32 = 0xFFFF_0000; +/// The shift to use to get the XECP value from HCCParams1. See [CapabilityRegs] +pub const HCC_PARAMS1_XECP_SHIFT: u8 = 16; + +/// The mask to use to get the LEC bit from HCCParams2. See [CapabilityRegs] +pub const HCC_PARAMS2_LEC_BIT: u32 = 1 << 4; +/// The mask to use to get the CIC bit from HCCParams2. See [CapabilityRegs] +pub const HCC_PARAMS2_CIC_BIT: u32 = 1 << 5; +/// The mask to use to get MAXPORTS from HCSParams1. See [CapabilityRegs] +pub const HCS_PARAMS1_MAX_PORTS_MASK: u32 = 0xFF00_0000; +/// The shift to use to get MAXPORTS from HCSParams1. See [CapabilityRegs] +pub const HCS_PARAMS1_MAX_PORTS_SHIFT: u8 = 24; +/// The shift to use to get MAXSLOTS from HCSParams1. See [CapabilityRegs] +pub const HCS_PARAMS1_MAX_SLOTS_MASK: u32 = 0x0000_00FF; +/// The shift to use to get MAXSLOTS from HCSParams1. See [CapabilityRegs] +pub const HCS_PARAMS1_MAX_SLOTS_SHIFT: u8 = 0; +/// The mask to use to get MAXSCRATPADBUFS_LO from HCSParams2. See [CapabilityRegs] +pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_MASK: u32 = 0xF800_0000; +/// The shift to use to get MAXSCRATCHPADBUFS_LO from HCSParams2. See [CapabilityRegs] +pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_SHIFT: u8 = 27; +/// The mask to use to get the SPR bit from HCSParams2. See [CapabilityRegs] +pub const HCS_PARAMS2_SPR_BIT: u32 = 1 << HCS_PARAMS2_SPR_SHIFT; +/// The shift to use to get the SPR bit from HCSParams2. See [CapabilityRegs] +pub const HCS_PARAMS2_SPR_SHIFT: u8 = 26; +/// The mask to use to get MAXSCRATCHPADBUFS_HI from HCSParams2. See [CapabilityRegs] +pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_MASK: u32 = 0x03E0_0000; +/// The shift to use to get MAXSCRATCHPADBUFS_HI from HCSParams2. See [CapabilityRegs] + +pub const HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_SHIFT: u8 = 21; + +impl CapabilityRegs { + /// Gets the ACS64 bit from HCCParams1. + pub fn ac64(&self) -> bool { + self.hcc_params1.readf(HCC_PARAMS1_AC64_BIT) + } + + /// Gets the context size (CSZ) bit from HCCParams1. + pub fn csz(&self) -> bool { + self.hcc_params1.readf(HCC_PARAMS1_CSZ_BIT) + } + + /// Gets the LEC bit from HCCParams2. + pub fn lec(&self) -> bool { + self.hcc_params2.readf(HCC_PARAMS2_LEC_BIT) + } + /// Gets the CIC bit from HCCParams2. + pub fn cic(&self) -> bool { + self.hcc_params2.readf(HCC_PARAMS2_CIC_BIT) + } + + /// Gets the Max PSA Size from HCCParams1 + pub fn max_psa_size(&self) -> u8 { + ((self.hcc_params1.read() & HCC_PARAMS1_MAXPSASIZE_MASK) >> HCC_PARAMS1_MAXPSASIZE_SHIFT) + as u8 + } + + /// Gets the maximum number of ports from HCCParams1 + pub fn max_ports(&self) -> u8 { + ((self.hcs_params1.read() & HCS_PARAMS1_MAX_PORTS_MASK) >> HCS_PARAMS1_MAX_PORTS_SHIFT) + as u8 + } + + /// Gets the maximum number of ports from HCCParams 2 + pub fn max_slots(&self) -> u8 { + (self.hcs_params1.read() & HCS_PARAMS1_MAX_SLOTS_MASK) as u8 + } + + /// Gets the extended capability pointer from HCCParams1 in DWORDs. + pub fn ext_caps_ptr_in_dwords(&self) -> u16 { + ((self.hcc_params1.read() & HCC_PARAMS1_XECP_MASK) >> HCC_PARAMS1_XECP_SHIFT) as u16 + } + + /// Gets the lower five bits from the Max Scratchpad Buffer Lo Register in HCSParams2 + pub fn max_scratchpad_bufs_lo(&self) -> u8 { + ((self.hcs_params2.read() & HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_MASK) + >> HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_LO_SHIFT) as u8 + } + + /// Gets the SPR register from HCSParams2 + pub fn spr(&self) -> bool { + self.hcs_params2.readf(HCS_PARAMS2_SPR_BIT) + } + + /// Gets the higher five bits from the Max Scratchpad Buffer Hi Register in HCSParams2 + pub fn max_scratchpad_bufs_hi(&self) -> u8 { + ((self.hcs_params2.read() & HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_MASK) + >> HCS_PARAMS2_MAX_SCRATCHPAD_BUFS_HI_SHIFT) as u8 + } + + /// Gets the maximum number of scratchpad buffers supported by this implementation. + pub fn max_scratchpad_bufs(&self) -> u16 { + u16::from(self.max_scratchpad_bufs_lo()) | (u16::from(self.max_scratchpad_bufs_hi()) << 5) + } +} diff --git a/drivers/usb/xhcid/src/xhci/context.rs b/drivers/usb/xhcid/src/xhci/context.rs new file mode 100644 index 0000000000..b8f2f45a95 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/context.rs @@ -0,0 +1,228 @@ +use std::collections::BTreeMap; + +use common::io::{Io, Mmio}; +use log::debug; +use syscall::error::Result; +use syscall::PAGE_SIZE; + +use common::dma::Dma; + +use super::ring::Ring; +use super::Xhci; + +pub const CONTEXT_32: usize = 0; +pub const CONTEXT_64: usize = 1; + +#[repr(C, packed)] +struct Rsvd64([[Mmio; 8]; N]); + +#[repr(C, packed)] +pub struct SlotContext { + pub a: Mmio, + pub b: Mmio, + pub c: Mmio, + pub d: Mmio, + _rsvd: [Mmio; 4], + _rsvd64: Rsvd64, +} + +pub const SLOT_CONTEXT_STATE_MASK: u32 = 0xF800_0000; +pub const SLOT_CONTEXT_STATE_SHIFT: u8 = 27; + +#[repr(u8)] +pub enum SlotState { + EnabledOrDisabled = 0, + Default = 1, + Addressed = 2, + Configured = 3, +} + +#[repr(C, packed)] +pub struct EndpointContext { + pub a: Mmio, + pub b: Mmio, + pub trl: Mmio, + pub trh: Mmio, + pub c: Mmio, + _rsvd: [Mmio; 3], + _rsvd64: Rsvd64, +} + +pub const ENDPOINT_CONTEXT_STATUS_MASK: u32 = 0x7; + +#[repr(C, packed)] +pub struct DeviceContext { + pub slot: SlotContext, + pub endpoints: [EndpointContext; 31], +} + +#[repr(C, packed)] +pub struct InputContext { + pub drop_context: Mmio, + pub add_context: Mmio, + _rsvd: [Mmio; 5], + pub control: Mmio, + _rsvd64: Rsvd64, + pub device: DeviceContext, +} +impl InputContext { + pub fn dump_control(&self) { + debug!( + "INPUT CONTEXT: {} {} [{} {} {} {} {}] {}", + self.drop_context.read(), + self.add_context.read(), + self._rsvd[0].read(), + self._rsvd[1].read(), + self._rsvd[2].read(), + self._rsvd[3].read(), + self._rsvd[4].read(), + self.control.read() + ); + } +} + +pub struct DeviceContextList { + pub dcbaa: Dma<[u64; 256]>, + pub contexts: Box<[Dma>]>, +} + +impl DeviceContextList { + pub fn new(ac64: bool, max_slots: u8) -> Result { + let mut dcbaa = unsafe { Xhci::::alloc_dma_zeroed_raw::<[u64; 256]>(ac64)? }; + let mut contexts = vec![]; + + // Create device context buffers for each slot + for i in 0..max_slots as usize { + let context: Dma> = unsafe { Xhci::::alloc_dma_zeroed_raw(ac64) }?; + dcbaa[i] = context.physical() as u64; + contexts.push(context); + } + + Ok(DeviceContextList { + dcbaa, + contexts: contexts.into_boxed_slice(), + }) + } + + pub fn dcbaap(&self) -> u64 { + self.dcbaa.physical() as u64 + } +} + +#[repr(C, packed)] +pub struct StreamContext { + trl: Mmio, + trh: Mmio, + edtla: Mmio, + rsvd: Mmio, +} + +unsafe impl plain::Plain for StreamContext {} + +#[repr(u8)] +pub enum StreamContextType { + SecondaryRing, + PrimaryRing, + PrimarySsa8, + PrimarySsa16, + PrimarySsa32, + PrimarySsa64, + PrimarySsa128, + PrimarySsa256, +} + +pub struct StreamContextArray { + pub contexts: Dma<[StreamContext]>, + pub rings: BTreeMap, +} + +impl StreamContextArray { + pub fn new(ac64: bool, count: usize) -> Result { + unsafe { + Ok(Self { + contexts: Xhci::::alloc_dma_zeroed_unsized_raw(ac64, count)?, + rings: BTreeMap::new(), + }) + } + } + pub fn add_ring( + &mut self, + ac64: bool, + stream_id: u16, + link: bool, + ) -> Result<()> { + // NOTE: stream_id 0 is reserved + assert_ne!(stream_id, 0); + + let ring = Ring::new::(ac64, 16, link)?; + let pointer = ring.register(); + let sct = StreamContextType::PrimaryRing; + + assert_eq!(pointer & (!0xE), pointer); + { + let context = &mut self.contexts[stream_id as usize]; + context.trl.write((pointer as u32) | ((sct as u32) << 1)); + context.trh.write((pointer >> 32) as u32); + // TODO: stopped edtla + } + self.rings.insert(stream_id, ring); + Ok(()) + } + pub fn register(&self) -> u64 { + self.contexts.physical() as u64 + } +} + +#[repr(C, packed)] +pub struct ScratchpadBufferEntry { + pub value_low: Mmio, + pub value_high: Mmio, +} +impl ScratchpadBufferEntry { + pub fn set_addr(&mut self, addr: u64) { + self.value_low.write(addr as u32); + self.value_high.write((addr >> 32) as u32); + } +} + +pub struct ScratchpadBufferArray { + pub entries: Dma<[ScratchpadBufferEntry]>, + pub pages: Vec>, +} +impl ScratchpadBufferArray { + pub fn new(ac64: bool, entries: u16) -> Result { + let mut entries = + unsafe { Xhci::::alloc_dma_zeroed_unsized_raw(ac64, entries as usize)? }; + + let pages = entries + .iter_mut() + .map( + |entry: &mut ScratchpadBufferEntry| -> Result<_, syscall::Error> { + let dma = unsafe { Dma::<[u8; PAGE_SIZE]>::zeroed()?.assume_init() }; + assert_eq!(dma.physical() % PAGE_SIZE, 0); + entry.set_addr(dma.physical() as u64); + Ok(dma) + }, + ) + .collect::, _>>()?; + + Ok(Self { entries, pages }) + } + pub fn register(&self) -> usize { + self.entries.physical() + } +} + +#[cfg(test)] +mod test { + use super::*; + use core::mem; + + #[test] + fn context_size() { + assert_eq!(mem::size_of::>(), 32); + assert_eq!(mem::size_of::>(), 64); + assert_eq!(mem::size_of::>(), 32); + assert_eq!(mem::size_of::>(), 64); + } +} diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs new file mode 100644 index 0000000000..a84c2ada59 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs @@ -0,0 +1,141 @@ +use crate::xhci::port::PortFlags; +use crate::xhci::{PortId, Xhci}; +use common::io::Io; +use crossbeam_channel; +use log::{debug, info, warn}; +use std::sync::Arc; +use std::time::Duration; +use syscall::EAGAIN; + +pub struct DeviceEnumerationRequest { + pub port_id: PortId, +} + +pub struct DeviceEnumerator { + hci: Arc>, + request_queue: crossbeam_channel::Receiver, +} + +impl DeviceEnumerator { + pub fn new(hci: Arc>) -> Self { + let request_queue = hci.device_enumerator_receiver.clone(); + DeviceEnumerator { hci, request_queue } + } + + pub fn run(&mut self) { + loop { + debug!("Start Device Enumerator Loop"); + let request = match self.request_queue.recv() { + Ok(req) => req, + Err(err) => { + panic!("Failed to received an enumeration request! error: {}", err) + } + }; + + let port_id = request.port_id; + let port_array_index = port_id.root_hub_port_index(); + + debug!("Device Enumerator request for port {}", port_id); + + let (len, flags) = { + let ports = self.hci.ports.lock().unwrap(); + + let len = ports.len(); + + if port_array_index >= len { + warn!( + "Received out of bounds Device Enumeration request for port {}", + port_id + ); + continue; + } + + (len, ports[port_array_index].flags()) + }; + + if flags.contains(PortFlags::CCS) { + debug!( + "Received Device Connect Port Status Change Event with port flags {:?}", + flags + ); + //If the port isn't enabled (i.e. it's a USB2 port), we need to reset it if it isn't resetting already + //A USB3 port won't generate a Connect Status Change until it's already enabled, so this check + //will always be skipped for USB3 ports + if !flags.contains(PortFlags::PED) { + let disabled_state = flags.contains(PortFlags::PP) + && flags.contains(PortFlags::CCS) + && !flags.contains(PortFlags::PED) + && !flags.contains(PortFlags::PR); + + if !disabled_state { + panic!( + "Port {} isn't in the disabled state! Current flags: {:?}", + port_id, flags + ); + } else { + debug!("Port {} has entered the disabled state.", port_id); + } + + //THIS LOCKS THE PORTS. DO NOT LOCK PORTS BEFORE THIS POINT + info!("Received a device connect on port {}, but it's not enabled. Resetting the port.", port_id); + let _ = self.hci.reset_port(port_id); + + let mut ports = self.hci.ports.lock().unwrap(); + let port = &mut ports[port_array_index]; + + port.clear_prc(); + + std::thread::sleep(Duration::from_millis(16)); //Some controllers need some extra time to make the transition. + + let flags = port.flags(); + + let enabled_state = flags.contains(PortFlags::PP) + && flags.contains(PortFlags::CCS) + && flags.contains(PortFlags::PED) + && !flags.contains(PortFlags::PR); + + if !enabled_state { + warn!( + "Port {} isn't in the enabled state! Current flags: {:?}", + port_id, flags + ); + } else { + debug!( + "Port {} is in the enabled state. Proceeding with enumeration", + port_id + ); + } + } + + let result = futures::executor::block_on(self.hci.attach_device(port_id)); + match result { + Ok(_) => { + info!("Device on port {} was attached", port_id); + } + Err(err) => { + if err.errno == EAGAIN { + debug!("Received a device connect notification for an already connected device. Ignoring...") + } else { + warn!("processing of device attach request failed! Error: {}", err); + } + } + } + } else { + debug!( + "Device Enumerator received Detach request on port {} which is in state {}", + port_id, + self.hci.get_pls(port_id) + ); + let result = futures::executor::block_on(self.hci.detach_device(port_id)); + match result { + Ok(_) => { + info!("Device on port {} was detached", port_id); + } + Err(err) => { + warn!("processing of device attach request failed! Error: {}", err); + } + } + } + } + } +} diff --git a/drivers/usb/xhcid/src/xhci/doorbell.rs b/drivers/usb/xhcid/src/xhci/doorbell.rs new file mode 100644 index 0000000000..f65db206aa --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/doorbell.rs @@ -0,0 +1,14 @@ +use common::io::{Io, Mmio}; + +#[repr(C, packed)] +pub struct Doorbell(Mmio); + +impl Doorbell { + pub fn read(&self) -> u32 { + self.0.read() + } + + pub fn write(&mut self, data: u32) { + self.0.write(data); + } +} diff --git a/drivers/usb/xhcid/src/xhci/event.rs b/drivers/usb/xhcid/src/xhci/event.rs new file mode 100644 index 0000000000..83af1209af --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/event.rs @@ -0,0 +1,52 @@ +use common::io::{Io, Mmio}; +use syscall::error::Result; + +use common::dma::Dma; + +use super::ring::Ring; +use super::trb::Trb; +use super::Xhci; + +#[repr(C, packed)] +pub struct EventRingSte { + pub address_low: Mmio, + pub address_high: Mmio, + pub size: Mmio, + _rsvd: Mmio, + _rsvd2: Mmio, +} + +// TODO: Use atomic operations, and perhaps an occasional lock for reallocating. +pub struct EventRing { + pub ste: Dma<[EventRingSte]>, + pub ring: Ring, +} + +impl EventRing { + pub fn new(ac64: bool) -> Result { + let mut ring = EventRing { + ste: unsafe { Xhci::::alloc_dma_zeroed_unsized_raw(ac64, 1)? }, + ring: Ring::new::(ac64, 256, false)?, + }; + + ring.ste[0] + .address_low + .write(ring.ring.trbs.physical() as u32); + ring.ste[0] + .address_high + .write((ring.ring.trbs.physical() as u64 >> 32) as u32); + ring.ste[0].size.write(ring.ring.trbs.len() as u16); + + Ok(ring) + } + + pub fn next(&mut self) -> &mut Trb { + self.ring.next().0 + } + pub fn erdp(&self) -> u64 { + self.ring.register() & 0xFFFF_FFFF_FFFF_FFF0 + } + pub fn erstba(&self) -> u64 { + self.ste.physical() as u64 + } +} diff --git a/drivers/usb/xhcid/src/xhci/extended.rs b/drivers/usb/xhcid/src/xhci/extended.rs new file mode 100644 index 0000000000..00ab6f2f1b --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/extended.rs @@ -0,0 +1,287 @@ +use common::io::{Io, Mmio}; +use std::ops::Range; +use std::ptr::NonNull; +use std::{fmt, mem, ptr, slice}; + +pub struct ExtendedCapabilitiesIter { + base: *const u8, +} +impl ExtendedCapabilitiesIter { + pub unsafe fn new(base: *const u8) -> Self { + Self { base } + } +} +impl Iterator for ExtendedCapabilitiesIter { + type Item = (NonNull, u8); // pointer, capability id + + fn next(&mut self) -> Option { + unsafe { + let current = NonNull::new(self.base as *mut _)?; + + let reg = current.cast::>().as_ref().read(); + let capability_id = (reg & 0xFF) as u8; + let next_rel_in_dwords = ((reg & 0xFF00) >> 8) as u8; + + let next_rel = u16::from(next_rel_in_dwords) << 2; + + self.base = if next_rel != 0 { + self.base.offset(next_rel as isize) + } else { + ptr::null() + }; + + Some((current, capability_id)) + } + } +} + +#[repr(u8)] +pub enum CapabilityId { + // bit 0 is reserved + UsbLegacySupport = 1, + SupportedProtocol, + ExtendedPowerManagement, + IoVirtualization, + MessageInterrupt, + LocalMem, + // bits 7-9 are reserved + UsbDebugCapability = 10, + // bits 11-16 are reserved + ExtendedMessageInterrupt = 17, + // bits 18-191 are reserved + // bits 192-255 are vendor-defined +} + +#[repr(C, packed)] +pub struct SupportedProtoCap { + a: Mmio, + b: Mmio, + c: Mmio, + d: Mmio, + protocol_speeds: [u8; 0], +} + +#[repr(C, packed)] +pub struct ProtocolSpeed { + a: Mmio, +} + +pub const PROTO_SPEED_PSIV_MASK: u32 = 0x0000_000F; +pub const PROTO_SPEED_PSIV_SHIFT: u8 = 0; + +pub const PROTO_SPEED_PSIE_MASK: u32 = 0x0000_0030; +pub const PROTO_SPEED_PSIE_SHIFT: u8 = 4; + +pub const PROTO_SPEED_PLT_MASK: u32 = 0x0000_00C0; +pub const PROTO_SPEED_PLT_SHIFT: u8 = 6; + +pub const PROTO_SPEED_PFD_BIT: u32 = 1 << PROTO_SPEED_PFD_SHIFT; +pub const PROTO_SPEED_PFD_SHIFT: u8 = 8; + +pub const PROTO_SPEED_LP_MASK: u32 = 0x0000_C000; +pub const PROTO_SPEED_LP_SHIFT: u8 = 14; + +pub const PROTO_SPEED_PSIM_MASK: u32 = 0xFFFF_0000; +pub const PROTO_SPEED_PSIM_SHIFT: u8 = 16; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Psie { + Bps, + Kbps, + Mbps, + Gbps, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Plt { + Symmetric, + Reserved, + AsymmetricRx, + AsymmetricTx, +} +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Lp { + SuperSpeed, + SuperSpeedPlus, + Rsvd2, + Rsvd3, +} + +impl ProtocolSpeed { + pub const fn from_raw(raw: u32) -> Self { + Self { a: Mmio::new(raw) } + } + pub fn is_lowspeed(&self) -> bool { + self.psim() == 1500 && self.psie() == Psie::Kbps && !self.pfd() + } + pub fn is_fullspeed(&self) -> bool { + self.psim() == 12 && self.psie() == Psie::Mbps && !self.pfd() + } + pub fn is_highspeed(&self) -> bool { + self.psim() == 480 && self.psie() == Psie::Mbps && !self.pfd() + } + pub fn is_superspeed_gen1x1(&self) -> bool { + self.psim() == 5 && self.psie() == Psie::Gbps && self.pfd() && self.lp() == Lp::SuperSpeed + } + pub fn is_superspeedplus_gen2x1(&self) -> bool { + self.psim() == 10 + && self.psie() == Psie::Gbps + && self.pfd() + && self.lp() == Lp::SuperSpeedPlus + } + pub fn is_superspeedplus_gen1x2(&self) -> bool { + self.psim() == 10 + && self.psie() == Psie::Gbps + && self.pfd() + && self.lp() == Lp::SuperSpeedPlus + } + pub fn is_superspeedplus_gen2x2(&self) -> bool { + self.psim() == 20 + && self.psie() == Psie::Gbps + && self.pfd() + && self.lp() == Lp::SuperSpeedPlus + } + pub fn is_superspeed_gen_x(&self) -> bool { + self.is_superspeed_gen1x1() + || self.is_superspeedplus_gen2x1() + || self.is_superspeedplus_gen1x2() + || self.is_superspeedplus_gen2x2() + } + /// Protocol speed ID value + pub fn psiv(&self) -> u8 { + ((self.a.read() & PROTO_SPEED_PSIV_MASK) >> PROTO_SPEED_PSIV_SHIFT) as u8 + } + pub fn psie_raw(&self) -> u8 { + ((self.a.read() & PROTO_SPEED_PSIE_MASK) >> PROTO_SPEED_PSIE_SHIFT) as u8 + } + /// Protocol speed ID exponent + pub fn psie(&self) -> Psie { + // safe because psie_raw can only return values in 0..=3 + unsafe { mem::transmute(self.psie_raw()) } + } + pub fn plt_raw(&self) -> u8 { + ((self.a.read() & PROTO_SPEED_PLT_MASK) >> PROTO_SPEED_PLT_SHIFT) as u8 + } + /// PSI type + pub fn plt(&self) -> Plt { + // safe because plt_raw can only return values in 0..=3 + unsafe { mem::transmute(self.plt_raw()) } + } + /// PSI Full-duplex + pub fn pfd(&self) -> bool { + self.a.readf(PROTO_SPEED_PFD_BIT) + } + pub fn lp_raw(&self) -> u8 { + ((self.a.read() & PROTO_SPEED_LP_MASK) >> PROTO_SPEED_LP_SHIFT) as u8 + } + /// Link protocol + pub fn lp(&self) -> Lp { + // safe because lp_raw can only return values in 0..=3 + unsafe { mem::transmute(self.lp_raw()) } + } + /// Protocol speed ID mantissa + pub fn psim(&self) -> u16 { + ((self.a.read() & PROTO_SPEED_PSIM_MASK) >> PROTO_SPEED_PSIM_SHIFT) as u16 + } +} + +impl fmt::Debug for ProtocolSpeed { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ProtocolSpeed") + .field("psiv", &self.psiv()) + .field("psie", &self.psie()) + .field("plt", &self.plt()) + .field("pfd", &self.pfd()) + .field("lp", &self.lp()) + .field("psim", &self.psim()) + .finish() + } +} + +pub const SUPP_PROTO_CAP_REV_MIN_MASK: u32 = 0x00FF_0000; +pub const SUPP_PROTO_CAP_REV_MIN_SHIFT: u8 = 16; + +pub const SUPP_PROTO_CAP_REV_MAJ_MASK: u32 = 0xFF00_0000; +pub const SUPP_PROTO_CAP_REV_MAJ_SHIFT: u8 = 24; + +pub const SUPP_PROTO_CAP_COMPAT_PORT_OFF_MASK: u32 = 0x0000_00FF; +pub const SUPP_PROTO_CAP_COMPAT_PORT_OFF_SHIFT: u8 = 0; + +pub const SUPP_PROTO_CAP_COMPAT_PORT_CNT_MASK: u32 = 0x0000_FF00; +pub const SUPP_PROTO_CAP_COMPAT_PORT_CNT_SHIFT: u8 = 8; + +pub const SUPP_PROTO_CAP_PROTO_DEF_MASK: u32 = 0x0FFF_0000; +pub const SUPP_PROTO_CAP_PROTO_DEF_SHIFT: u8 = 16; + +pub const SUPP_PROTO_CAP_PSIC_MASK: u32 = 0xF000_0000; +pub const SUPP_PROTO_CAP_PSIC_SHIFT: u8 = 28; + +pub const SUPP_PROTO_CAP_PORT_SLOT_TYPE_MASK: u32 = 0x0000_001F; +pub const SUPP_PROTO_CAP_PORT_SLOT_TYPE_SHIFT: u8 = 0; + +impl SupportedProtoCap { + pub unsafe fn protocol_speeds(&self) -> &[ProtocolSpeed] { + slice::from_raw_parts( + &self.protocol_speeds as *const u8 as *const _, + self.psic() as usize, + ) + } + pub unsafe fn protocol_speeds_mut(&mut self) -> &mut [ProtocolSpeed] { + // XXX: Variance really is annoying sometimes. + slice::from_raw_parts_mut( + &self.protocol_speeds as *const u8 as *mut u8 as *mut _, + self.psic() as usize, + ) + } + pub fn rev_minor(&self) -> u8 { + ((self.a.read() & SUPP_PROTO_CAP_REV_MIN_MASK) >> SUPP_PROTO_CAP_REV_MIN_SHIFT) as u8 + } + pub fn rev_major(&self) -> u8 { + ((self.a.read() & SUPP_PROTO_CAP_REV_MAJ_MASK) >> SUPP_PROTO_CAP_REV_MAJ_SHIFT) as u8 + } + pub fn name_string(&self) -> [u8; 4] { + // TODO: Little endian, right? + u32::to_le_bytes(self.b.read()) + } + pub fn compat_port_offset(&self) -> u8 { + ((self.c.read() & SUPP_PROTO_CAP_COMPAT_PORT_OFF_MASK) + >> SUPP_PROTO_CAP_COMPAT_PORT_OFF_SHIFT) as u8 + } + pub fn compat_port_count(&self) -> u8 { + ((self.c.read() & SUPP_PROTO_CAP_COMPAT_PORT_CNT_MASK) + >> SUPP_PROTO_CAP_COMPAT_PORT_CNT_SHIFT) as u8 + } + pub fn compat_port_range(&self) -> Range { + self.compat_port_offset()..self.compat_port_offset() + self.compat_port_count() + } + + pub fn proto_defined(&self) -> u16 { + ((self.c.read() & SUPP_PROTO_CAP_PROTO_DEF_MASK) >> SUPP_PROTO_CAP_PROTO_DEF_SHIFT) as u16 + } + pub fn psic(&self) -> u8 { + ((self.c.read() & SUPP_PROTO_CAP_PSIC_MASK) >> SUPP_PROTO_CAP_PSIC_SHIFT) as u8 + } + pub fn proto_slot_ty(&self) -> u8 { + ((self.d.read() & SUPP_PROTO_CAP_PORT_SLOT_TYPE_MASK) + >> SUPP_PROTO_CAP_PORT_SLOT_TYPE_SHIFT) as u8 + } +} +impl fmt::Debug for SupportedProtoCap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SupportedProtoCap") + .field("rev_minor", &self.rev_minor()) + .field("rev_major", &self.rev_major()) + .field("name_string", &String::from_utf8_lossy(&self.name_string())) + .field("compat_port_offset", &self.compat_port_count()) + .field("compat_port_count", &self.compat_port_offset()) + .field("proto_defined", &self.proto_defined()) + .field("psic", &self.psic()) + .field("proto_slot_ty", &self.proto_slot_ty()) + .field("proto_speeds", unsafe { + &self.protocol_speeds().to_owned() + }) + .finish() + } +} diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs new file mode 100644 index 0000000000..ac492d5bc1 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs @@ -0,0 +1,743 @@ +use std::fs::File; +use std::future::Future; +use std::io::prelude::*; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task; + +use std::os::unix::io::AsRawFd; + +use crossbeam_channel::{Receiver, Sender}; +use log::{debug, error, info, trace, warn}; + +use super::doorbell::Doorbell; +use super::event::EventRing; +use super::ring::Ring; +use super::trb::{Trb, TrbCompletionCode, TrbType}; +use super::{PortId, Xhci}; +use crate::xhci::device_enumerator::DeviceEnumerationRequest; +use crate::xhci::port::PortFlags; +use common::io::Io as _; +use event::RawEventQueue; + +/// Short-term states (as in, they are removed when the waker is consumed, but probably pushed back +/// by the future unless it completed). +#[derive(Debug)] +pub struct State { + waker: task::Waker, + kind: StateKind, + message: Arc>>, + is_isoch_or_vf: bool, +} + +impl State { + fn finish(self, message: Option) { + *self.message.lock().unwrap() = message; + trace!("Waking up future with waker: {:?}", self.waker); + self.waker.wake(); + } +} + +#[derive(Debug)] +pub struct NextEventTrb { + pub event_trb: Trb, + pub src_trb: Option, +} + +// TODO: Perhaps all of the transfer rings used by the xHC should be stored linearly, and then +// indexed using this struct instead. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct RingId { + pub port: PortId, + pub endpoint_num: u8, + pub stream_id: u16, +} +impl RingId { + pub const fn default_control_pipe(port: PortId) -> Self { + Self { + port, + endpoint_num: 0, + stream_id: 0, + } + } +} + +/// The state specific to a TRB-type. Since some of the event TDs may asynchronously appear, for +/// example the Command Completion Event and the Transfer Event TDs, they have to be +/// distinguishable. Luckily, the xHC also gives us the actual (physical) pointer to the source +/// TRB, from the command ring, unless the event TD has one the completion codes Ring Underrun, +/// Ring Overrun, or VF Event Ring Full Error. When these errors are encountered, it simply +/// indicates that the commands causing the errors continue to be pending, and thus no information +/// is lost. +#[derive(Clone, Copy, Debug)] +pub enum StateKind { + CommandCompletion { + phys_ptr: u64, + }, + Transfer { + first_phys_ptr: u64, + last_phys_ptr: u64, + ring_id: RingId, + }, + Other(TrbType), +} + +impl StateKind { + pub fn trb_type(&self) -> TrbType { + match self { + &Self::CommandCompletion { .. } => TrbType::CommandCompletion, + &Self::Transfer { .. } => TrbType::Transfer, + &Self::Other(ty) => ty, + } + } +} + +pub struct IrqReactor { + hci: Arc>, + irq_file: Option, + irq_receiver: Receiver, + device_enumerator_sender: Sender, + states: Vec, + // TODO: Since the IRQ reactor is the only part of this driver that gets event TRBs, perhaps + // the event ring should be owned here? +} + +pub type NewPendingTrb = State; + +impl IrqReactor { + pub fn new(hci: Arc>, irq_file: Option) -> Self { + let device_enumerator_sender = hci.device_enumerator_sender.clone(); + let irq_receiver = hci.irq_reactor_receiver.clone(); + + Self { + hci, + irq_file, + irq_receiver, + device_enumerator_sender, + states: Vec::new(), + } + } + // TODO: Configure the amount of time wait when no more work can be done (for IRQ-less polling). + fn pause(&self) { + std::thread::sleep(std::time::Duration::from_millis(2)); + } + fn run_polling(mut self) -> ! { + debug!("Running IRQ reactor in polling mode."); + let hci_clone = Arc::clone(&self.hci); + + let mut event_trb_index = { + hci_clone + .primary_event_ring + .lock() + .unwrap() + .ring + .next_index() + }; + + 'trb_loop: loop { + self.pause(); + + let mut event_ring = hci_clone.primary_event_ring.lock().unwrap(); + + let event_trb = &mut event_ring.ring.trbs[event_trb_index]; + + if event_trb.completion_code() == TrbCompletionCode::Invalid as u8 { + continue 'trb_loop; + } + + trace!( + "Found event TRB at index {} with type {} and cycle bit {}: {:?}", + event_trb_index, + event_trb.trb_type(), + event_trb.cycle() as u8, + event_trb + ); + + if self.check_event_ring_full(event_trb.clone()) { + info!("Had to resize event TRB, retrying..."); + continue 'trb_loop; + } + + trace!("Handling requests"); + self.handle_requests(); + trace!("Requests handled"); + + match event_trb.trb_type() { + _ if event_trb.trb_type() == TrbType::PortStatusChange as u8 => { + trace!("Received a port status change!"); + self.handle_port_status_change(event_trb.clone()) + } //TODO Handle the other unprompted events + _ => { + self.acknowledge(event_trb.clone()); + } + } + + event_trb.reserved(false); + + self.update_erdp(&*event_ring); + hci_clone.event_handler_finished(); + + event_trb_index = event_ring.ring.next_index(); + } + } + + fn mask_interrupts(&mut self) { + let mut run = self.hci.run.lock().unwrap(); + + debug!("Masking interrupts!"); + + if !run.ints[0].iman.readf(1 << 1) { + warn!("Attempted to mask interrupts when they were already disabled!") + } + + run.ints[0].iman.writef(1 << 1, false); + } + + fn unmask_interrupts(&mut self) { + let mut run = self.hci.run.lock().unwrap(); + + debug!("unmasking interrupts!"); + if run.ints[0].iman.readf(1 << 1) { + warn!("Attempted to unmask interrupts when they were already enabled!") + } + + run.ints[0].iman.writef(1 << 1, true); + } + + fn run_with_irq_file(mut self) -> ! { + debug!("Running IRQ reactor with IRQ file and event queue"); + + let hci_clone = Arc::clone(&self.hci); + let event_queue = + RawEventQueue::new().expect("xhcid irq_reactor: failed to create IRQ event queue"); + let irq_fd = self.irq_file.as_ref().unwrap().as_raw_fd(); + event_queue + .subscribe(irq_fd as usize, 0, event::EventFlags::READ) + .unwrap(); + + trace!("IRQ Reactor has created its event queue."); + let mut event_trb_index = { + hci_clone + .primary_event_ring + .lock() + .unwrap() + .ring + .next_index() + }; + + trace!("IRQ reactor has grabbed the next index in the event ring."); + 'trb_loop: loop { + let _event = event_queue.next_event().unwrap(); + trace!("IRQ event queue notified"); + let mut buffer = [0u8; 8]; + + let _ = self + .irq_file + .as_mut() + .unwrap() + .read(&mut buffer) + .expect("Failed to read from irq scheme"); + + if !self.hci.received_irq() { + // continue only when an IRQ to this device was received + trace!("no interrupt pending"); + continue 'trb_loop; + } + + self.mask_interrupts(); + + trace!("IRQ reactor received an IRQ"); + + let _ = self.irq_file.as_mut().unwrap().write(&buffer); + + // TODO: More event rings, probably even with different IRQs. + + let mut event_ring = hci_clone.primary_event_ring.lock().unwrap(); + + let mut count = 0; + + loop { + trace!("count: {}", count); + let event_trb = &mut event_ring.ring.trbs[event_trb_index]; + + if event_trb.completion_code() == TrbCompletionCode::Invalid as u8 { + if count == 0 { + warn!("xhci: Received interrupt, but no event was found in the event ring. Ignoring interrupt.") + } + //hci_clone.event_handler_finished(); + self.unmask_interrupts(); + continue 'trb_loop; + } else { + count += 1 + } + + info!( + "Found event TRB at index {} with type {} and cycle bit {}: {:?}", + event_trb_index, + event_trb.trb_type(), + event_trb.cycle() as u8, + event_trb + ); + + if self.check_event_ring_full(event_trb.clone()) { + info!("Had to resize event TRB, retrying..."); + //hci_clone.event_handler_finished(); + if self.hci.interrupt_is_pending(0) { + warn!("After incrementing the dequeue pointer, the interrupt bit is still pending.") + } else { + debug!("The interrupt bit is no longer pending."); + } + self.unmask_interrupts(); + continue 'trb_loop; + } + self.handle_requests(); + + match event_trb.trb_type() { + _ if event_trb.trb_type() == TrbType::PortStatusChange as u8 => { + trace!("Received a port status change!"); + self.handle_port_status_change(event_trb.clone()) + } //TODO Handle the other unprompted events + _ => { + trace!("Received a non-status trb"); + self.acknowledge(event_trb.clone()); + } + } + + event_trb.reserved(false); + + self.update_erdp(&*event_ring); + self.hci.event_handler_finished(); + + event_trb_index = event_ring.ring.next_index(); + } + } + } + + /// Handles device attach/detach events as indicated by a PortStatusChange + fn handle_port_status_change(&mut self, trb: Trb) { + if let Some(root_hub_port_num) = trb.port_status_change_port_id() { + let port_id = PortId { + root_hub_port_num, + route_string: 0, + }; + trace!("Received Port Status Change Request on port {}", port_id); + self.device_enumerator_sender + .send(DeviceEnumerationRequest { port_id }) + .expect( + format!( + "Failed to transmit device numeration request on port {}", + port_id + ) + .as_str(), + ); + { + let mut ports = self.hci.ports.lock().unwrap(); + let root_port_index = port_id.root_hub_port_index(); + if root_port_index >= ports.len() { + warn!( + "Received out of bounds transmit device numeration request on root index {} at port {} [port len was: {}]", + root_port_index, port_id, ports.len() + ); + return; + } + + let port = &mut ports[root_port_index]; + port.clear_csc(); + } + } else { + warn!( + "Received a TRB of type {}, which was unexpected", + trb.trb_type() + ) + } + } + + fn update_erdp(&self, event_ring: &EventRing) { + let dequeue_pointer_and_dcs = event_ring.erdp(); + let dequeue_pointer = dequeue_pointer_and_dcs & 0xFFFF_FFFF_FFFF_FFFE; + assert_eq!( + dequeue_pointer & 0xFFFF_FFFF_FFFF_FFF0, + dequeue_pointer, + "unaligned ERDP received from primary event ring" + ); + + trace!("Updated ERDP to {:#0x}", dequeue_pointer); + + self.hci.run.lock().unwrap().ints[0] + .erdp_low + .write(dequeue_pointer as u32); + self.hci.run.lock().unwrap().ints[0] + .erdp_high + .write((dequeue_pointer >> 32) as u32); + } + fn handle_requests(&mut self) { + self.states.extend( + self.irq_receiver + .try_iter() + .inspect(|req| trace!("Received request: {:X?}", req)), + ); + } + fn acknowledge(&mut self, trb: Trb) { + //TODO: handle TRBs without an attached state + + trace!("ACK TRB {:X?}", trb); + + let mut index = 0; + while index < self.states.len() { + trace!("ACK STATE {}: {:X?}", index, self.states[index].kind); + + match self.states[index].kind { + StateKind::CommandCompletion { phys_ptr } + if trb.trb_type() == TrbType::CommandCompletion as u8 => + { + if trb.completion_trb_pointer() == Some(phys_ptr) { + trace!("Found matching command completion future"); + let state = self.states.remove(index); + + // Before waking, it's crucial that the command TRB that generated this event + // is fetched before removing this event TRB from the queue. + let command_trb = match self + .hci + .cmd + .lock() + .unwrap() + .phys_addr_to_entry_mut(self.hci.cap.ac64(), phys_ptr) + { + Some(command_trb) => { + let t = command_trb.clone(); + command_trb.reserved(false); + t + } + None => { + warn!("The xHC supplied a pointer to a command TRB that was outside the known command ring bounds. Ignoring event TRB {:?}.", trb); + continue; + } + }; + + // TODO: Validate the command TRB. + state.finish(Some(NextEventTrb { + src_trb: Some(command_trb.clone()), + event_trb: trb.clone(), + })); + + return; + } else if trb.completion_trb_pointer().is_none() { + warn!("Command TRB somehow resulted in an error that only can be caused by transfer TRBs. Ignoring event TRB: {:?}.", trb); + } + } + + StateKind::Transfer { + first_phys_ptr, + last_phys_ptr, + ring_id, + } => { + // Check if the TRB matches the transfer + if trb.trb_type() == TrbType::Transfer as u8 { + match trb.transfer_event_trb_pointer() { + Some(phys_ptr) => { + let matches = if first_phys_ptr <= last_phys_ptr { + phys_ptr >= first_phys_ptr && phys_ptr <= last_phys_ptr + } else { + // Handle ring buffer wrap + phys_ptr >= first_phys_ptr || phys_ptr <= last_phys_ptr + }; + if matches { + let src_trb = self.hci.get_transfer_trb(phys_ptr, ring_id); + // Give the source transfer TRB together with the event TRB, to the future. + let state = self.states.remove(index); + state.finish(Some(NextEventTrb { + src_trb: src_trb, + event_trb: trb.clone(), + })); + return; + } + } + None => { + // Ring Overrun, Ring Underrun, or Virtual Function Event Ring Full. + // + // These errors are caused when either an isoch transfer that shall write data, doesn't + // have any data since the ring is empty, or if an isoch receive is impossible due to a + // full ring. The Virtual Function Event Ring Full is only for Virtual Machine + // Managers, and since this isn't implemented yet, they are irrelevant. + // + // The best solution here is to differentiate between isoch transfers (and + // virtual function event rings when virtualization gets implemented), with + // regular commands and transfers, and send the error TRB to all of them, or + // possibly an error code wrapped in a Result. + self.acknowledge_failed_transfer_trbs(trb); + return; + } + } + } + + // Also check if the transfer is on a dead ring + if self.hci.with_ring(ring_id, |_ring| ()).is_none() { + log::debug!("State {} is a dead transfer", index); + let state = self.states.remove(index); + state.finish(Some(NextEventTrb { + src_trb: None, + //TODO: don't send this TRB as it may not be related + event_trb: trb.clone(), + })); + continue; + } + } + + StateKind::Other(trb_type) if trb_type as u8 == trb.trb_type() => { + let state = self.states.remove(index); + state.finish(None); + return; + } + + _ => (), + } + + index += 1; + } + warn!( + "Lost event TRB type {}, completion code: {}: {:X?}", + trb.trb_type(), + trb.completion_code(), + trb + ); + } + fn acknowledge_failed_transfer_trbs(&mut self, trb: Trb) { + let mut index = 0; + + loop { + if !self.states[index].is_isoch_or_vf { + index += 1; + if index >= self.states.len() { + break; + } + continue; + } + let state = self.states.remove(index); + state.finish(Some(NextEventTrb { + event_trb: trb.clone(), + src_trb: None, + })); + } + } + /// Checks if an event TRB is a Host Controller Event, with the completion code Event Ring + /// Full. If so, it grows the event ring. The return value is whether the event ring was full, + /// and then grown. + fn check_event_ring_full(&mut self, event_trb: Trb) -> bool { + let had_event_ring_full_error = event_trb.trb_type() == TrbType::HostController as u8 + && event_trb.completion_code() == TrbCompletionCode::EventRingFull as u8; + + if had_event_ring_full_error { + self.grow_event_ring(); + } + had_event_ring_full_error + } + /// Grows the event ring + fn grow_event_ring(&mut self) { + // TODO + error!("TODO: grow event ring"); + } + + pub fn run(self) -> ! { + if self.irq_file.is_some() { + self.run_with_irq_file(); + } else { + self.run_polling(); + } + } +} + +struct FutureState { + message: Arc>>, + is_isoch_or_vf: bool, + state_kind: StateKind, +} + +pub struct EventDoorbell { + dbs: Arc>, + index: usize, + data: u32, +} + +impl EventDoorbell { + pub fn new(hci: &Xhci, index: usize, data: u32) -> Self { + Self { + //TODO: simplify this logic, maybe just use a raw pointer? + dbs: hci.dbs.clone(), + index, + data, + } + } + + pub fn ring(self) { + trace!("Ring doorbell {} with data {}", self.index, self.data); + self.dbs.lock().unwrap()[self.index].write(self.data); + trace!("Doorbell was rung."); + } +} + +enum EventTrbFuture { + Pending { + state: FutureState, + sender: Sender, + doorbell_opt: Option, + }, + Finished, +} + +impl Future for EventTrbFuture { + type Output = NextEventTrb; + + fn poll(self: Pin<&mut Self>, context: &mut task::Context) -> task::Poll { + let this = self.get_mut(); + trace!("Start poll!"); + let message = match this { + &mut Self::Pending { + ref state, + ref sender, + ref mut doorbell_opt, + } => match state.message.lock().unwrap().take() { + Some(message) => message, + + None => { + // Register state with IRQ reactor + trace!("Send state {:X?}", state.state_kind); + sender + .send(State { + message: Arc::clone(&state.message), + is_isoch_or_vf: state.is_isoch_or_vf, + kind: state.state_kind, + waker: context.waker().clone(), + }) + .expect("IRQ reactor thread unexpectedly stopped"); + + // Doorbell must be rung after sending state + if let Some(doorbell) = doorbell_opt.take() { + doorbell.ring(); + } + return task::Poll::Pending; + } + }, + &mut Self::Finished => panic!("Polling finished EventTrbFuture again."), + }; + trace!("finished!"); + *this = Self::Finished; + task::Poll::Ready(message) + } +} + +impl Xhci { + pub fn get_transfer_trb(&self, paddr: u64, id: RingId) -> Option { + self.with_ring(id, |ring| ring.phys_addr_to_entry(self.cap.ac64(), paddr)) + .flatten() + } + pub fn with_ring T>(&self, id: RingId, function: F) -> Option { + use super::RingOrStreams; + + let slot_state = self.port_states.get(&id.port)?; + let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?; + + let ring_ref = match endpoint_state.transfer { + RingOrStreams::Ring(ref ring) => ring, + RingOrStreams::Streams(ref ctx_arr) => ctx_arr.rings.get(&id.stream_id)?, + }; + + Some(function(ring_ref)) + } + pub fn with_ring_mut T>( + &self, + id: RingId, + function: F, + ) -> Option { + use super::RingOrStreams; + + let mut slot_state = self.port_states.get_mut(&id.port)?; + let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?; + + let ring_ref = match endpoint_state.transfer { + RingOrStreams::Ring(ref mut ring) => ring, + RingOrStreams::Streams(ref mut ctx_arr) => ctx_arr.rings.get_mut(&id.stream_id)?, + }; + + Some(function(ring_ref)) + } + pub fn next_transfer_event_trb( + &self, + ring_id: RingId, + ring: &Ring, + first_trb: &Trb, + last_trb: &Trb, + doorbell: EventDoorbell, + ) -> impl Future + Send + Sync + 'static { + if !last_trb.is_transfer_trb() { + panic!("Invalid TRB type given to next_transfer_event_trb(): {} (TRB {:?}. Expected transfer TRB.", last_trb.trb_type(), last_trb) + } + + let is_isoch_or_vf = last_trb.trb_type() == TrbType::Isoch as u8; + let first_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), first_trb); + let last_phys_ptr = ring.trb_phys_ptr(self.cap.ac64(), last_trb); + EventTrbFuture::Pending { + state: FutureState { + is_isoch_or_vf, + state_kind: StateKind::Transfer { + ring_id, + first_phys_ptr, + last_phys_ptr, + }, + message: Arc::new(Mutex::new(None)), + }, + sender: self.irq_reactor_sender.clone(), + doorbell_opt: Some(doorbell), + } + } + pub fn next_command_completion_event_trb( + &self, + command_ring: &Ring, + trb: &Trb, + doorbell: EventDoorbell, + ) -> impl Future + Send + Sync + 'static { + trace!( + "Sending command at phys_ptr {:X}", + command_ring.trb_phys_ptr(self.cap.ac64(), trb) + ); + if !trb.is_command_trb() { + panic!("Invalid TRB type given to next_command_completion_event_trb(): {} (TRB {:?}. Expected command TRB.", trb.trb_type(), trb) + } + EventTrbFuture::Pending { + state: FutureState { + // This is only possible for transfers if they are isochronous, or for Force Event TRBs (virtualization). + is_isoch_or_vf: false, + state_kind: StateKind::CommandCompletion { + phys_ptr: command_ring.trb_phys_ptr(self.cap.ac64(), trb), + }, + message: Arc::new(Mutex::new(None)), + }, + sender: self.irq_reactor_sender.clone(), + doorbell_opt: Some(doorbell), + } + } + pub fn next_misc_event_trb( + &self, + trb_type: TrbType, + ) -> impl Future + Send + Sync + 'static { + let valid_trb_types = [ + TrbType::PortStatusChange as u8, + TrbType::BandwidthRequest as u8, + TrbType::Doorbell as u8, + TrbType::HostController as u8, + TrbType::DeviceNotification as u8, + TrbType::MfindexWrap as u8, + ]; + if !valid_trb_types.contains(&(trb_type as u8)) { + panic!("Invalid TRB type given to next_misc_event_trb(): {:?}. Only event TRB types that are neither transfer events or command completion events can be used.", trb_type) + } + EventTrbFuture::Pending { + state: FutureState { + is_isoch_or_vf: false, + state_kind: StateKind::Other(trb_type), + message: Arc::new(Mutex::new(None)), + }, + sender: self.irq_reactor_sender.clone(), + doorbell_opt: None, + } + } +} diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs new file mode 100644 index 0000000000..5881b55994 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -0,0 +1,1490 @@ +//! The eXtensible Host Controller Interface (XHCI) Module +//! +//! This module implements the XHCI functionality of Redox's USB driver daemon. +//! +//! XHCI is a standard for the USB Host Controller interface specified by Intel that provides a +//! common register interface for systems to use to interact with the Universal Serial Bus (USB) +//! subsystem. +//! +//! The standard can be found [here](https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf). +//! The standard is referenced frequently throughout this documentation. The acronyms used for specific +//! documents are specified in the crate-level documentation. +use std::collections::BTreeMap; +use std::convert::TryFrom; +use std::fs::File; +use std::sync::atomic::AtomicUsize; +use std::sync::{Arc, Mutex}; + +use std::{mem, process, slice, thread}; +use syscall::error::{Error, Result, EBADF, EBADMSG, EIO, ENOENT}; +use syscall::{EAGAIN, PAGE_SIZE}; + +use chashmap::CHashMap; +use common::{dma::Dma, io::Io, timeout::Timeout}; +use crossbeam_channel::{Receiver, Sender}; +use log::{debug, error, info, trace, warn}; +use serde::Deserialize; + +use crate::usb; + +use pcid_interface::PciFunctionHandle; + +mod capability; +mod context; +mod device_enumerator; +mod doorbell; +mod event; +mod extended; +pub mod irq_reactor; +mod operational; +mod port; +mod ring; +mod runtime; +pub mod scheme; +mod trb; + +pub use self::capability::CapabilityRegs; +use self::context::{ + DeviceContextList, InputContext, ScratchpadBufferArray, StreamContextArray, + SLOT_CONTEXT_STATE_MASK, SLOT_CONTEXT_STATE_SHIFT, +}; +pub use self::context::{CONTEXT_32, CONTEXT_64}; +use self::doorbell::Doorbell; +use self::event::EventRing; +use self::extended::{CapabilityId, ExtendedCapabilitiesIter, ProtocolSpeed, SupportedProtoCap}; +use self::irq_reactor::{EventDoorbell, IrqReactor, NewPendingTrb, RingId}; +use self::operational::*; +use self::port::Port; +use self::ring::Ring; +use self::runtime::RuntimeRegs; +use self::trb::{TransferKind, Trb, TrbCompletionCode}; + +use self::scheme::EndpIfState; + +pub use crate::driver_interface::PortId; +use crate::driver_interface::*; + +/// Specifies the configurable interrupt mechanism used by the xhci subsystem for registering +/// device state change notifications. +pub enum InterruptMethod { + /// No interrupts whatsoever; the driver will instead rely on polling event rings. + Polling, + + /// Legacy PCI INTx# interrupt pin. + Intx, + + /// (Extended) Message signaled interrupts. + Msi, +} + +impl Xhci { + /// Gets descriptors, before the port state is initiated. + async fn get_desc_raw( + &self, + port: PortId, + slot: u8, + kind: usb::DescriptorKind, + value: u8, + index: u16, + desc: &mut Dma, + ) -> Result<()> { + if self.interrupt_is_pending(0) { + debug!("EHB is already set!"); + self.force_clear_interrupt(0); + } + let len = mem::size_of::(); + log::debug!( + "get_desc_raw port {} slot {} kind {:?} value {} index {} len {}", + port, + slot, + kind, + value, + index, + len + ); + + let future = { + let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(ENOENT))?; + let ring = port_state + .endpoint_states + .get_mut(&0) + .ok_or(Error::new(EIO))? + .ring() + .expect("no ring for the default control pipe"); + + let first_index = ring.next_index(); + let (cmd, cycle) = (&mut ring.trbs[first_index], ring.cycle); + cmd.setup( + usb::Setup::get_descriptor(kind, value, index, len as u16), + TransferKind::In, + cycle, + ); + + let (cmd, cycle) = ring.next(); + cmd.data(desc.physical(), len as u16, true, cycle); + + let last_index = ring.next_index(); + let (cmd, cycle) = (&mut ring.trbs[last_index], ring.cycle); + + let interrupter = 0; + // When the data stage is in, the status stage must be out + let input = false; + let ioc = true; + let ch = false; + let ent = false; + cmd.status(interrupter, input, ioc, ch, ent, cycle); + + self.next_transfer_event_trb( + RingId::default_control_pipe(port), + &ring, + &ring.trbs[first_index], + &ring.trbs[last_index], + EventDoorbell::new(self, usize::from(slot), Self::def_control_endp_doorbell()), + ) + }; + + debug!("Waiting for the next transfer event TRB..."); + let trbs = future.await; + let event_trb = trbs.event_trb; + let status_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + trace!("Handling the transfer event TRB!"); + self::scheme::handle_transfer_event_trb("GET_DESC", &event_trb, &status_trb)?; + + //self.event_handler_finished(); + Ok(()) + } + + async fn fetch_dev_desc_8_byte( + &self, + port: PortId, + slot: u8, + ) -> Result { + let mut desc = unsafe { self.alloc_dma_zeroed::()? }; + self.get_desc_raw(port, slot, usb::DescriptorKind::Device, 0, 0, &mut desc) + .await?; + Ok(*desc) + } + + async fn fetch_dev_desc(&self, port: PortId, slot: u8) -> Result { + let mut desc = unsafe { self.alloc_dma_zeroed::()? }; + self.get_desc_raw(port, slot, usb::DescriptorKind::Device, 0, 0, &mut desc) + .await?; + Ok(*desc) + } + + async fn fetch_config_desc( + &self, + port: PortId, + slot: u8, + config: u8, + ) -> Result<(usb::ConfigDescriptor, [u8; 4087])> { + let mut desc = unsafe { self.alloc_dma_zeroed::<(usb::ConfigDescriptor, [u8; 4087])>()? }; + self.get_desc_raw( + port, + slot, + usb::DescriptorKind::Configuration, + config, + 0, + &mut desc, + ) + .await?; + Ok(*desc) + } + + async fn fetch_bos_desc( + &self, + port: PortId, + slot: u8, + ) -> Result<(usb::BosDescriptor, [u8; 4087])> { + let mut desc = unsafe { self.alloc_dma_zeroed::<(usb::BosDescriptor, [u8; 4087])>()? }; + self.get_desc_raw( + port, + slot, + usb::DescriptorKind::BinaryObjectStorage, + 0, + 0, + &mut desc, + ) + .await?; + Ok(*desc) + } + + async fn fetch_lang_ids_desc(&self, port: PortId, slot: u8) -> Result> { + let mut sdesc = unsafe { self.alloc_dma_zeroed::<(u8, u8, [u16; 127])>()? }; + self.get_desc_raw(port, slot, usb::DescriptorKind::String, 0, 0, &mut sdesc) + .await?; + + let len = sdesc.0 as usize; + if len > 2 { + Ok(sdesc.2[..(len - 2) / 2].to_vec()) + } else { + Ok(Vec::new()) + } + } + + async fn fetch_string_desc( + &self, + port: PortId, + slot: u8, + value: u8, + lang_id: u16, + ) -> Result { + let mut sdesc = unsafe { self.alloc_dma_zeroed::<(u8, u8, [u16; 127])>()? }; + self.get_desc_raw( + port, + slot, + usb::DescriptorKind::String, + value, + lang_id, + &mut sdesc, + ) + .await?; + + let len = sdesc.0 as usize; + if len > 2 { + Ok(String::from_utf16(&sdesc.2[..(len - 2) / 2]).unwrap_or(String::new())) + } else { + Ok(String::new()) + } + } +} + +/// The eXtensible Host Controller Interface (XHCI) data structure +pub struct Xhci { + // immutable + /// The Host Controller Interface Capability Registers. These read-only registers specify the + /// limits and capabilities of the host controller implementation (See XHCI section 5.3) + cap: &'static CapabilityRegs, + //page_size: usize, + + // XXX: It would be really useful to be able to mutably access individual elements of a slice, + // without having to wrap every element in a lock (which wouldn't work since they're packed). + /// The Host Controller Interface Operational Registers. These registers provide the software + /// interface to configure and monitor the state of the XHCI (See XHCI section 5.4) + op: Mutex<&'static mut OperationalRegs>, + ports: Mutex<&'static mut [Port]>, + /// The Host Controller Interface Doorbell Registers. There is one register per device slot, + /// and these registers are used by system software to notify the XHC that it has work to perform + /// for a specific device slot. (See XHCI sections 4.7 and 5.6) + dbs: Arc>, + /// The Host Controller Interface Runtime Registers. These handle interrupt and event processing, + /// and provide time-sensitive information such as the current microframe. (See XHCI section 5.5) + run: Mutex<&'static mut RuntimeRegs>, + cmd: Mutex, + primary_event_ring: Mutex, + + // immutable + dev_ctx: DeviceContextList, + scratchpad_buf_arr: Option, + + // used for the extended capabilities, and so far none of them are mutated, and thus no lock. + base: *const u8, + + handles: CHashMap, + next_handle: AtomicUsize, + port_states: CHashMap>, + drivers: CHashMap>, + scheme_name: String, + + interrupt_method: InterruptMethod, + pcid_handle: Mutex, + + irq_reactor: Mutex>>, + + irq_reactor_sender: Sender, + + // not used, but still stored so that the thread, when created, can get the channel without the + // channel being in a mutex. + irq_reactor_receiver: Receiver, + device_enumerator: Mutex>>, + device_enumerator_sender: Sender, + device_enumerator_receiver: Receiver, +} + +unsafe impl Send for Xhci {} +unsafe impl Sync for Xhci {} + +struct PortState { + slot: u8, + protocol_speed: &'static ProtocolSpeed, + cfg_idx: Option, + input_context: Mutex>>, + dev_desc: Option, + endpoint_states: BTreeMap, +} + +impl PortState { + //TODO: fetch using endpoint number instead + fn get_endp_desc(&self, endp_idx: u8) -> Option<&EndpDesc> { + let cfg_idx = self.cfg_idx?; + let config_desc = self + .dev_desc + .as_ref()? + .config_descs + .iter() + .find(|desc| desc.configuration_value == cfg_idx)?; + let mut endp_count = 0; + for if_desc in config_desc.interface_descs.iter() { + for endp_desc in if_desc.endpoints.iter() { + if endp_idx == endp_count { + return Some(endp_desc); + } + endp_count += 1; + } + } + None + } +} + +pub(crate) enum RingOrStreams { + Ring(Ring), + Streams(StreamContextArray), +} + +pub(crate) struct EndpointState { + pub transfer: RingOrStreams, + pub driver_if_state: EndpIfState, +} +impl EndpointState { + fn ring(&mut self) -> Option<&mut Ring> { + match self.transfer { + RingOrStreams::Ring(ref mut ring) => Some(ring), + _ => None, + } + } +} + +impl Xhci { + pub fn new( + scheme_name: String, + address: usize, + interrupt_method: InterruptMethod, + pcid_handle: PciFunctionHandle, + ) -> Result { + //Locate the capability registers from the mapped PCI Bar + let cap = unsafe { &mut *(address as *mut CapabilityRegs) }; + debug!("CAP REGS BASE {:X}", address); + + //let page_size = ... + + //The operational registers appear immediately after the capability registers. + let op_base = address + cap.len.read() as usize; + let op = unsafe { &mut *(op_base as *mut OperationalRegs) }; + debug!("OP REGS BASE {:X}", op_base); + + //Reset the XHCI device + let (max_slots, max_ports) = { + { + debug!("Waiting for xHC becoming ready."); + let timeout = Timeout::from_secs(1); + while op.usb_sts.readf(USB_STS_CNR) { + timeout.run().map_err(|()| { + log::error!("timeout on USB_STS_CNR"); + Error::new(EIO) + })?; + } + } + + debug!("Stopping the xHC"); + // Set run/stop to 0 + op.usb_cmd.writef(USB_CMD_RS, false); + + { + debug!("Waiting for the xHC to stop."); + let timeout = Timeout::from_secs(1); + while !op.usb_sts.readf(USB_STS_HCH) { + timeout.run().map_err(|()| { + log::error!("timeout on USB_STS_HCH"); + Error::new(EIO) + })?; + } + } + + { + debug!("Resetting the xHC."); + op.usb_cmd.writef(USB_CMD_HCRST, true); + let timeout = Timeout::from_secs(1); + while op.usb_cmd.readf(USB_CMD_HCRST) { + timeout.run().map_err(|()| { + log::error!("timeout on USB_CMD_HCRST"); + Error::new(EIO) + })?; + } + } + + debug!("Reading max slots."); + + let max_slots = cap.max_slots(); + let max_ports = cap.max_ports(); + + info!("xHC max slots: {}, max ports: {}", max_slots, max_ports); + (max_slots, max_ports) + }; + + //Get the address of the port register table + let port_base = op_base + 0x400; + let ports = + unsafe { slice::from_raw_parts_mut(port_base as *mut Port, max_ports as usize) }; + debug!("PORT BASE {:X}", port_base); + + //Get the address of the dorbell register table + let db_base = address + cap.db_offset.read() as usize; + let dbs = unsafe { slice::from_raw_parts_mut(db_base as *mut Doorbell, 256) }; + debug!("DOORBELL REGS BASE {:X}", db_base); + + let run_base = address + cap.rts_offset.read() as usize; + let run = unsafe { &mut *(run_base as *mut RuntimeRegs) }; + debug!("RUNTIME REGS BASE {:X}", run_base); + + // Create the command ring with 4096 / 16 (TRB size) entries, so that it uses all of the + // DMA allocation (which is at least a 4k page). + let entries_per_page = PAGE_SIZE / mem::size_of::(); + let cmd = Ring::new::(cap.ac64(), entries_per_page, true)?; + + let (irq_reactor_sender, irq_reactor_receiver) = crossbeam_channel::unbounded(); + + let (device_enumerator_sender, device_enumerator_receiver) = crossbeam_channel::unbounded(); + + let mut xhci = Self { + base: address as *const u8, + + cap, + //page_size, + op: Mutex::new(op), + ports: Mutex::new(ports), + dbs: Arc::new(Mutex::new(dbs)), + run: Mutex::new(run), + + dev_ctx: DeviceContextList::new(cap.ac64(), max_slots)?, + scratchpad_buf_arr: None, // initialized in init() + + cmd: Mutex::new(cmd), + primary_event_ring: Mutex::new(EventRing::new::(cap.ac64())?), + handles: CHashMap::new(), + next_handle: AtomicUsize::new(0), + port_states: CHashMap::new(), + drivers: CHashMap::new(), + scheme_name, + + interrupt_method, + pcid_handle: Mutex::new(pcid_handle), + + irq_reactor: Mutex::new(None), + irq_reactor_sender, + irq_reactor_receiver, + device_enumerator: Mutex::new(None), + device_enumerator_sender, + device_enumerator_receiver, + }; + + xhci.init(max_slots)?; + + Ok(xhci) + } + + pub fn init(&mut self, max_slots: u8) -> Result<()> { + // Set run/stop to 0 + debug!("Stopping xHC."); + self.op.get_mut().unwrap().usb_cmd.writef(USB_CMD_RS, false); + + // Warm reset + { + debug!("Reset xHC"); + let timeout = Timeout::from_secs(1); + self.op + .get_mut() + .unwrap() + .usb_cmd + .writef(USB_CMD_HCRST, true); + while self.op.get_mut().unwrap().usb_cmd.readf(USB_CMD_HCRST) { + timeout.run().map_err(|()| { + log::error!("timeout on USB_CMD_HCRST"); + Error::new(EIO) + })?; + } + } + + // Set enabled slots + debug!("Setting enabled slots to {}.", max_slots); + self.op.get_mut().unwrap().config.write(max_slots as u32); + debug!( + "Enabled Slots: {}", + self.op.get_mut().unwrap().config.read() & 0xFF + ); + + // Set device context address array pointer + let dcbaap = self.dev_ctx.dcbaap(); + debug!("Writing DCBAAP: {:X}", dcbaap); + self.op.get_mut().unwrap().dcbaap_low.write(dcbaap as u32); + self.op + .get_mut() + .unwrap() + .dcbaap_high + .write((dcbaap as u64 >> 32) as u32); + + // Set command ring control register + let crcr = self.cmd.get_mut().unwrap().register(); + assert_eq!(crcr & 0xFFFF_FFFF_FFFF_FFC1, crcr, "unaligned CRCR"); + debug!("Writing CRCR: {:X}", crcr); + self.op.get_mut().unwrap().crcr_low.write(crcr as u32); + self.op + .get_mut() + .unwrap() + .crcr_high + .write((crcr as u64 >> 32) as u32); + + // Set event ring segment table registers + debug!( + "Interrupter 0: {:p}", + self.run.get_mut().unwrap().ints.as_ptr() + ); + { + let int = &mut self.run.get_mut().unwrap().ints[0]; + + let erstz = 1; + debug!("Writing ERSTZ: {}", erstz); + int.erstsz.write(erstz); + + let erdp = self.primary_event_ring.get_mut().unwrap().erdp(); + debug!("Writing ERDP: {:X}", erdp); + int.erdp_low.write(erdp as u32 | (1 << 3)); + int.erdp_high.write((erdp as u64 >> 32) as u32); + + let erstba = self.primary_event_ring.get_mut().unwrap().erstba(); + debug!("Writing ERSTBA: {:X}", erstba); + int.erstba_low.write(erstba as u32); + int.erstba_high.write((erstba as u64 >> 32) as u32); + + debug!("Writing IMODC and IMODI: {} and {}", 0, 0); + int.imod.write(0); + + debug!("Enabling Primary Interrupter."); + int.iman.writef(1 << 1 | 1, true); + } + self.op + .get_mut() + .unwrap() + .usb_cmd + .writef(USB_CMD_INTE, true); + + // Setup the scratchpad buffers that are required for the xHC to function. + self.setup_scratchpads()?; + + // Set run/stop to 1 + debug!("Starting xHC."); + self.op.get_mut().unwrap().usb_cmd.writef(USB_CMD_RS, true); + + { + debug!("Waiting for start request to complete."); + let timeout = Timeout::from_secs(1); + while self.op.get_mut().unwrap().usb_sts.readf(USB_STS_HCH) { + timeout.run().map_err(|()| { + log::error!("timeout on USB_STS_HCH"); + Error::new(EIO) + })?; + } + } + + // Ring command doorbell + debug!("Ringing command doorbell."); + self.dbs.lock().unwrap()[0].write(0); + + info!("XHCI initialized."); + + self.op.get_mut().unwrap().set_cie(self.cap.cic()); + + self.print_port_capabilities(); + + Ok(()) + } + + pub fn get_pls(&self, port_id: PortId) -> u8 { + let mut ports = self.ports.lock().unwrap(); + let port = ports.get_mut(port_id.root_hub_port_index()).unwrap(); + port.state() + } + + pub fn poll(&self) { + debug!("Polling Initial Devices!"); + + let len = self.ports.lock().unwrap().len(); + + for root_hub_port_num in 1..=(len as u8) { + let port_id = PortId { + root_hub_port_num, + route_string: 0, + }; + + //Get the CCS and CSC flags + let (ccs, csc, flags) = { + let mut ports = self.ports.lock().unwrap(); + let port = &mut ports[port_id.root_hub_port_index()]; + let flags = port.flags(); + let ccs = flags.contains(PortFlags::CCS); + let csc = flags.contains(PortFlags::CSC); + + (ccs, csc, flags) + }; + + debug!("Port {} has flags {:?}", port_id, flags); + + match (ccs, csc) { + (false, false) => { // Nothing is connected, and there was no port status change + //Do nothing + } + _ => { + //Either something is connected, or nothing is connected and a port status change was asserted. + self.device_enumerator_sender + .send(DeviceEnumerationRequest { port_id }) + .expect("Failed to generate the port enumeration request!"); + } + } + } + } + + pub fn print_port_capabilities(&self) { + let len; + { + let mut ports = self.ports.lock().unwrap(); + len = ports.len(); + } + + for root_hub_port_num in 1..=(len as u8) { + let port_id = PortId { + root_hub_port_num, + route_string: 0, + }; + + let state = self.get_pls(port_id); + let mut flags; + { + let mut ports = self.ports.lock().unwrap(); + + flags = ports[port_id.root_hub_port_index()].flags(); + } + + match self.supported_protocol(port_id) { + None => { + warn!("No detected supported protocol for port {}", port_id); + } + Some(protocol) => { + info!( + "Port {} is a USB {}.{} port with slot type {} and in current state {}: {:?}", + port_id, + protocol.rev_major(), + protocol.rev_minor(), + protocol.proto_slot_ty(), + state, + flags + ); + } + }; + } + } + pub fn reset_port(&self, port_id: PortId) -> Result<()> { + debug!("XHCI Port {} reset", port_id); + + //TODO handle the second unwrap + let mut ports = self.ports.lock().unwrap(); + let port = ports.get_mut(port_id.root_hub_port_index()).unwrap(); + let instant = std::time::Instant::now(); + + debug!("Port {} Link State: {}", port_id, port.state()); + + { + port.set_pr(); + debug!( + "Flags after setting port {} reset: {:?}", + port_id, + port.flags() + ); + let timeout = Timeout::from_secs(1); + while !port.flags().contains(port::PortFlags::PRC) { + timeout.run().map_err(|()| { + log::error!("timeout on port {} PRC", port_id); + Error::new(EIO) + })?; + } + } + Ok(()) + } + + pub fn setup_scratchpads(&mut self) -> Result<()> { + let buf_count = self.cap.max_scratchpad_bufs(); + + if buf_count == 0 { + return Ok(()); + } + let scratchpad_buf_arr = ScratchpadBufferArray::new::(self.cap.ac64(), buf_count)?; + self.dev_ctx.dcbaa[0] = scratchpad_buf_arr.register() as u64; + debug!( + "Setting up {} scratchpads, at {:#0x}", + buf_count, + scratchpad_buf_arr.register() + ); + self.scratchpad_buf_arr = Some(scratchpad_buf_arr); + + Ok(()) + } + + pub fn force_clear_interrupt(&self, index: usize) { + { + // If ERDP EHB bit is set, clear it before sending command + //TODO: find out why this bit is set earlier! + let mut run = self.run.lock().unwrap(); + let mut int = &mut run.ints[index]; + + if int.erdp_low.readf(1 << 3) { + int.erdp_low.writef(1 << 3, true); + } else { + warn!("Attempted to clear the interrupt bit when no interrupt was pending"); + } + } + } + + pub fn interrupt_is_pending(&self, index: usize) -> bool { + let mut run = self.run.lock().unwrap(); + let mut int = &mut run.ints[index]; + int.erdp_low.readf(1 << 3) + } + + pub async fn enable_port_slot(&self, slot_ty: u8) -> Result { + assert_eq!(slot_ty & 0x1F, slot_ty); + + let (event_trb, command_trb) = self + .execute_command(|cmd, cycle| cmd.enable_slot(slot_ty, cycle)) + .await; + + trace!("Slot is enabled!"); + self::scheme::handle_event_trb("ENABLE_SLOT", &event_trb, &command_trb)?; + //self.event_handler_finished(); + + Ok(event_trb.event_slot()) + } + pub async fn disable_port_slot(&self, slot: u8) -> Result<()> { + trace!("Disable slot {}", slot); + let (event_trb, command_trb) = self + .execute_command(|cmd, cycle| cmd.disable_slot(slot, cycle)) + .await; + + self::scheme::handle_event_trb("DISABLE_SLOT", &event_trb, &command_trb)?; + //self.event_handler_finished(); + + Ok(()) + } + + pub fn slot_state(&self, slot: usize) -> u8 { + ((self.dev_ctx.contexts[slot].slot.d.read() & SLOT_CONTEXT_STATE_MASK) + >> SLOT_CONTEXT_STATE_SHIFT) as u8 + } + pub unsafe fn alloc_dma_zeroed_raw(_ac64: bool) -> Result> { + // TODO: ac64 + Ok(Dma::zeroed()?.assume_init()) + } + pub unsafe fn alloc_dma_zeroed(&self) -> Result> { + Self::alloc_dma_zeroed_raw(self.cap.ac64()) + } + pub unsafe fn alloc_dma_zeroed_unsized_raw(_ac64: bool, count: usize) -> Result> { + // TODO: ac64 + Ok(Dma::zeroed_slice(count)?.assume_init()) + } + pub unsafe fn alloc_dma_zeroed_unsized(&self, count: usize) -> Result> { + Self::alloc_dma_zeroed_unsized_raw(self.cap.ac64(), count) + } + + pub async fn attach_device(&self, port_id: PortId) -> syscall::Result<()> { + if self.port_states.contains_key(&port_id) { + debug!("Already contains port {}", port_id); + return Err(syscall::Error::new(EAGAIN)); + } + + let (data, state, speed, flags) = { + let port = &self.ports.lock().unwrap()[port_id.root_hub_port_index()]; + (port.read(), port.state(), port.speed(), port.flags()) + }; + + info!( + "XHCI Port {}: {:X}, State {}, Speed {}, Flags {:?}", + port_id, data, state, speed, flags + ); + + if flags.contains(port::PortFlags::CCS) { + let slot_ty = match self.supported_protocol(port_id) { + Some(protocol) => protocol.proto_slot_ty(), + None => { + warn!("Failed to find supported protocol information for port"); + 0 + } + }; + + debug!("Slot type: {}", slot_ty); + debug!("Enabling slot."); + let slot = match self.enable_port_slot(slot_ty).await { + Ok(ok) => ok, + Err(err) => { + error!("Failed to enable slot for port {}: {}", port_id, err); + return Err(err); + } + }; + + debug!("Enabled port {}, which the xHC mapped to {}", port_id, slot); + + //TODO: get correct speed for child devices + let protocol_speed = self + .lookup_psiv(port_id, speed) + .expect("Failed to retrieve speed ID"); + + let mut input = unsafe { self.alloc_dma_zeroed::>()? }; + + info!("Attempting to address the device"); + let mut ring = match self + .address_device(&mut input, port_id, slot_ty, slot, protocol_speed, speed) + .await + { + Ok(device_ring) => device_ring, + Err(err) => { + error!("Failed to address device for port {}: `{}`", port_id, err); + return Err(err); + } + }; + + debug!("Addressed device"); + + // TODO: Should the descriptors be cached in PortState, or refetched? + + let mut port_state = PortState { + slot, + protocol_speed, + input_context: Mutex::new(input), + dev_desc: None, + cfg_idx: None, + endpoint_states: std::iter::once(( + 0, + EndpointState { + transfer: RingOrStreams::Ring(ring), + driver_if_state: EndpIfState::Init, + }, + )) + .collect::>(), + }; + self.port_states.insert(port_id, port_state); + debug!("Got port states!"); + + // Ensure correct packet size is used + let dev_desc_8_byte = self.fetch_dev_desc_8_byte(port_id, slot).await?; + { + let mut port_state = self.port_states.get_mut(&port_id).unwrap(); + + let mut input = port_state.input_context.lock().unwrap(); + + self.update_max_packet_size(&mut *input, slot, dev_desc_8_byte) + .await?; + } + + debug!("Got the 8 byte dev descriptor: {:X?}", dev_desc_8_byte); + + let dev_desc = self.get_desc(port_id, slot).await?; + debug!("Got the full device descriptor!"); + self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); + + debug!("Got the port states again!"); + { + let mut port_state = self.port_states.get_mut(&port_id).unwrap(); + + let mut input = port_state.input_context.lock().unwrap(); + debug!("Got the input context!"); + let dev_desc = port_state.dev_desc.as_ref().unwrap(); + + self.update_default_control_pipe(&mut *input, slot, dev_desc) + .await?; + } + + debug!("Updated the default control pipe"); + + match self.spawn_drivers(port_id) { + Ok(()) => (), + Err(err) => { + error!("Failed to spawn driver for port {}: `{}`", port_id, err) + } + } + } else { + warn!("Attempted to attach a device that didnt have CCS=1"); + } + + Ok(()) + } + + pub async fn detach_device(&self, port_id: PortId) -> Result<()> { + if let Some(children) = self.drivers.remove(&port_id) { + for mut child in children { + info!("killing driver process {} for port {}", child.id(), port_id); + match child.kill() { + Ok(()) => { + info!("killed driver process {} for port {}", child.id(), port_id); + match child.try_wait() { + Ok(status_opt) => match status_opt { + Some(status) => { + debug!( + "driver process {} for port {} exited with status {}", + child.id(), + port_id, + status + ); + } + None => { + //TODO: kill harder + warn!( + "driver process {} for port {} still running", + child.id(), + port_id + ); + } + }, + Err(err) => { + warn!( + "failed to wait for the driver process {} for port {}: {}", + child.id(), + port_id, + err + ); + } + } + } + Err(err) => { + warn!( + "failed to kill the driver process {} for port {}: {}", + child.id(), + port_id, + err + ); + } + } + } + } + + if let Some(state) = self.port_states.remove(&port_id) { + debug!("disabling port slot {} for port {}", state.slot, port_id); + let result = self.disable_port_slot(state.slot).await; + debug!( + "disabled port slot {} for port {} with result: {:?}", + state.slot, port_id, result + ); + + result + } else { + debug!( + "Attempted to detach from port {}, which wasn't previously attached.", + port_id + ); + Ok(()) + } + } + + pub async fn update_max_packet_size( + &self, + input_context: &mut Dma>, + slot_id: u8, + dev_desc: usb::DeviceDescriptor8Byte, + ) -> Result<()> { + let new_max_packet_size = if dev_desc.major_usb_vers() <= 2 { + // For USB 2.0 and below, packet_size is in bytes + u32::from(dev_desc.packet_size) + } else { + // For later USB versions, packet_size is the shift + 1u32 << dev_desc.packet_size + }; + let mut b = input_context.device.endpoints[0].b.read(); + b &= 0x0000_FFFF; + b |= (new_max_packet_size) << 16; + input_context.device.endpoints[0].b.write(b); + + let (event_trb, command_trb) = self + .execute_command(|trb, cycle| { + trb.evaluate_context(slot_id, input_context.physical(), false, cycle) + }) + .await; + + self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; + //self.event_handler_finished(); + + Ok(()) + } + + pub async fn update_default_control_pipe( + &self, + input_context: &mut Dma>, + slot_id: u8, + dev_desc: &DevDesc, + ) -> Result<()> { + debug!("Updating default control pipe!"); + input_context.add_context.write(1 << 1); + input_context.drop_context.write(0); + + let new_max_packet_size = if dev_desc.major_version() <= 2 { + // For USB 2.0 and below, packet_size is in bytes + u32::from(dev_desc.packet_size) + } else { + // For later USB versions, packet_size is the shift + 1u32 << dev_desc.packet_size + }; + let mut b = input_context.device.endpoints[0].b.read(); + b &= 0x0000_FFFF; + b |= (new_max_packet_size) << 16; + input_context.device.endpoints[0].b.write(b); + + let (event_trb, command_trb) = self + .execute_command(|trb, cycle| { + trb.evaluate_context(slot_id, input_context.physical(), false, cycle) + }) + .await; + debug!("Completed the command to update the default control pipe"); + + self::scheme::handle_event_trb("EVALUATE_CONTEXT", &event_trb, &command_trb)?; + //self.event_handler_finished(); + + Ok(()) + } + + pub async fn address_device( + &self, + input_context: &mut Dma>, + port: PortId, + slot_ty: u8, + slot: u8, + protocol_speed: &ProtocolSpeed, + speed: u8, + ) -> Result { + // Collect MTT, parent port number, parent slot ID + let mut mtt = false; + let mut parent_hub_slot_id = 0u8; + let mut parent_port_num = 0u8; + if let Some((parent_port, port_num)) = port.parent() { + match self.port_states.get(&parent_port) { + Some(parent_state) => { + // parent info must be supplied if: + let mut needs_parent_info = false; + // 1. the device is low or full speed and connected through a high speed hub + //TODO: determine device speed (speed is not accurate as it comes from the port) + // 2. the device is superspeed and connected through a higher rank hub + //TODO: determine device speed (speed is not accurate as it comes from the port) + // For now, this is just set to true to force things to work + needs_parent_info = true; + if needs_parent_info { + parent_hub_slot_id = parent_state.slot; + parent_port_num = port_num; + } + info!( + "port {} parent_hub_slot_id {} parent_port_num {}", + port, parent_hub_slot_id, parent_port_num + ); + } + None => { + warn!("port {} missing parent port {} state", port, parent_port); + } + } + } + + let mut ring = Ring::new::(self.cap.ac64(), 16, true)?; + + { + input_context.add_context.write(1 << 1 | 1); // Enable the slot (zeroth bit) and the control endpoint (first bit). + + let route_string = port.route_string; + let context_entries = 1u8; + let hub = false; + + assert_eq!(route_string & 0x000F_FFFF, route_string); + input_context.device.slot.a.write( + route_string + | (u32::from(speed) << 20) + | (u32::from(mtt) << 25) + | (u32::from(hub) << 26) + | (u32::from(context_entries) << 27), + ); + + let max_exit_latency = 0u16; + let root_hub_port_num = port.root_hub_port_num; + let number_of_ports = 0u8; + input_context.device.slot.b.write( + u32::from(max_exit_latency) + | (u32::from(root_hub_port_num) << 16) + | (u32::from(number_of_ports) << 24), + ); + + // TODO + let ttt = 0u8; + let interrupter = 0u8; + + assert_eq!(ttt & 0b11, ttt); + input_context.device.slot.c.write( + u32::from(parent_hub_slot_id) + | (u32::from(parent_port_num) << 8) + | (u32::from(ttt) << 16) + | (u32::from(interrupter) << 22), + ); + + let max_error_count = 3u8; // recommended value according to the XHCI spec + let ep_ty = 4u8; // control endpoint, bidirectional + let max_packet_size: u32 = + if protocol_speed.is_lowspeed() || protocol_speed.is_fullspeed() { + 8 + } else if protocol_speed.is_highspeed() { + 64 + } else { + 512 + }; + let host_initiate_disable = false; // only applies to streams + let max_burst_size = 0u8; // TODO + + assert_eq!(max_error_count & 0b11, max_error_count); + input_context.device.endpoints[0].b.write( + (u32::from(max_error_count) << 1) + | (u32::from(ep_ty) << 3) + | (u32::from(host_initiate_disable) << 7) + | (u32::from(max_burst_size) << 8) + | (u32::from(max_packet_size) << 16), + ); + + let dequeue_cycle_state = true; + let tr = ring.register(); + input_context.device.endpoints[0] + .trh + .write((tr >> 32) as u32); + input_context.device.endpoints[0] + .trl + .write((tr as u32) | u32::from(dequeue_cycle_state)); + + // The default control pipe can always use 8 bytes + let avg_trb_len = 8u8; + input_context.device.endpoints[0] + .c + .write(u32::from(avg_trb_len)); + } + + let input_context_physical = input_context.physical(); + + let (event_trb, _) = self + .execute_command(|trb, cycle| { + trb.address_device(slot, input_context_physical, false, cycle) + }) + .await; + + if event_trb.completion_code() != TrbCompletionCode::Success as u8 { + error!( + "Failed to address device at slot {} (port {}), completion code 0x{:X}", + slot, + port, + event_trb.completion_code() + ); + //self.event_handler_finished(); + return Err(Error::new(EIO)); + } + //self.event_handler_finished(); + + Ok(ring) + } + + fn uses_msi_interrupts(&self) -> bool { + matches!(self.interrupt_method, InterruptMethod::Msi) + } + + /// Checks whether an IRQ has been received from *this* device, in case of an interrupt. Always + /// true when using MSI/MSI-X. + pub fn received_irq(&self) -> bool { + let mut runtime_regs = self.run.lock().unwrap(); + + if self.uses_msi_interrupts() { + // Since using MSI and MSI-X implies having no IRQ sharing whatsoever, the IP bit + // doesn't have to be touched. + trace!( + "Successfully received MSI/MSI-X interrupt, IP={}, EHB={}", + runtime_regs.ints[0].iman.readf(1), + runtime_regs.ints[0].erdp_low.readf(1 << 3) + ); + true + } else if runtime_regs.ints[0].iman.readf(1) { + trace!( + "Successfully received INTx# interrupt, IP={}, EHB={}", + runtime_regs.ints[0].iman.readf(1), + runtime_regs.ints[0].erdp_low.readf(1 << 3) + ); + // If MSI and/or MSI-X are not used, the interrupt might have to be shared, and thus there is + // a special register to specify whether the IRQ actually came from the xHC. + runtime_regs.ints[0].iman.writef(1, true); + + // The interrupt came from the xHC. + true + } else { + // The interrupt came from a different device. + false + } + } + fn spawn_drivers(&self, port: PortId) -> Result<()> { + // TODO: There should probably be a way to select alternate interfaces, and not just the + // first one. + // TODO: Now that there are some good error crates, I don't think errno.h error codes are + // suitable here. + + let ps = self.port_states.get(&port).unwrap(); + trace!("Spawning driver on port: {}", port); + + //TODO: support choosing config? + let config_desc = &ps + .dev_desc + .as_ref() + .ok_or_else(|| { + log::warn!("Missing device descriptor"); + Error::new(EBADF) + })? + .config_descs + .first() + .ok_or_else(|| { + log::warn!("Missing config descriptor"); + Error::new(EBADF) + })?; + + trace!("Got config and device descriptors on port {}", port); + let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG; + + for ifdesc in config_desc.interface_descs.iter() { + //TODO: support alternate settings + // This is difficult because the device driver must know which alternate + // to use, but if alternates can have different classes, then a different + // device driver may be required for each alternate. For now, we will use + // only the default alternate setting (0) + if ifdesc.alternate_setting != 0 { + warn!( + "ignoring port {} iface {} alternate {} class {}.{} proto {}", + port, + ifdesc.number, + ifdesc.alternate_setting, + ifdesc.class, + ifdesc.sub_class, + ifdesc.protocol + ); + continue; + } + + if let Some(driver) = drivers_usercfg.drivers.iter().find(|driver| { + driver.class == ifdesc.class + && driver + .subclass() + .map(|subclass| subclass == ifdesc.sub_class) + .unwrap_or(true) + }) { + info!( + "Loading subdriver \"{}\" for port {} iface {} alternate {} class {}.{} proto {}", + driver.name, + port, + ifdesc.number, + ifdesc.alternate_setting, + ifdesc.class, + ifdesc.sub_class, + ifdesc.protocol, + ); + let (command, args) = driver.command.split_first().ok_or(Error::new(EBADMSG))?; + + let command = if command.starts_with('/') { + command.to_owned() + } else { + "/usr/lib/drivers/".to_owned() + command + }; + let process = process::Command::new(command) + .args( + args.into_iter() + .map(|arg| { + arg.replace("$SCHEME", &self.scheme_name) + .replace("$PORT", &format!("{}", port)) + .replace("$IF_NUM", &format!("{}", ifdesc.number)) + .replace("$IF_PROTO", &format!("{}", ifdesc.protocol)) + }) + .collect::>(), + ) + .stdin(process::Stdio::null()) + .spawn() + .or(Err(Error::new(ENOENT)))?; + self.drivers.alter(port, |children_opt| { + let mut children = children_opt.unwrap_or_else(|| Vec::new()); + children.push(process); + Some(children) + }); + } else { + warn!( + "No driver for port {} iface {} alternate {} class {}.{} proto {}", + port, + ifdesc.number, + ifdesc.alternate_setting, + ifdesc.class, + ifdesc.sub_class, + ifdesc.protocol + ); + } + } + + Ok(()) + } + pub fn capabilities_iter(&self) -> ExtendedCapabilitiesIter { + unsafe { + ExtendedCapabilitiesIter::new( + (self.base as *mut u8).offset((self.cap.ext_caps_ptr_in_dwords() << 2) as isize), + ) + } + } + pub fn supported_protocols_iter(&self) -> impl Iterator { + self.capabilities_iter() + .filter_map(|(pointer, cap_num)| unsafe { + if cap_num == CapabilityId::SupportedProtocol as u8 { + Some(&*pointer.cast::().as_ptr()) + } else { + None + } + }) + } + pub fn supported_protocol(&self, port: PortId) -> Option<&'static SupportedProtoCap> { + self.supported_protocols_iter().find(|supp_proto| { + supp_proto + .compat_port_range() + .contains(&port.root_hub_port_num) + }) + } + pub fn supported_protocol_speeds( + &self, + port: PortId, + ) -> impl Iterator { + use extended::*; + const DEFAULT_SUPP_PROTO_SPEEDS: [ProtocolSpeed; 7] = [ + // Full-speed + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (false as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Mbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 12 << PROTO_SPEED_PSIM_SHIFT + | 1 << PROTO_SPEED_PSIV_SHIFT, + ), + // Low-speed + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (false as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Kbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 1500 << PROTO_SPEED_PSIM_SHIFT + | 2 << PROTO_SPEED_PSIV_SHIFT, + ), + // High-speed + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (false as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Mbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 480 << PROTO_SPEED_PSIM_SHIFT + | 3 << PROTO_SPEED_PSIV_SHIFT, + ), + // SuperSpeed Gen1 x1 + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (true as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Gbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 5 << PROTO_SPEED_PSIM_SHIFT + | (Lp::SuperSpeed as u32) << PROTO_SPEED_LP_SHIFT + | 4 << PROTO_SPEED_PSIV_SHIFT, + ), + // SuperSpeedPlus Gen2 x1 + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (true as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Gbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 10 << PROTO_SPEED_PSIM_SHIFT + | (Lp::SuperSpeedPlus as u32) << PROTO_SPEED_LP_SHIFT + | 5 << PROTO_SPEED_PSIV_SHIFT, + ), + // SuperSpeedPlus Gen1 x2 + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (true as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Gbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 10 << PROTO_SPEED_PSIM_SHIFT + | (Lp::SuperSpeedPlus as u32) << PROTO_SPEED_LP_SHIFT + | 6 << PROTO_SPEED_PSIV_SHIFT, + ), + // SuperSpeedPlus Gen2 x2 + ProtocolSpeed::from_raw( + (Plt::Symmetric as u32) << PROTO_SPEED_PLT_SHIFT + | (true as u32) << PROTO_SPEED_PFD_SHIFT + | (Psie::Gbps as u32) << PROTO_SPEED_PSIE_SHIFT + | 20 << PROTO_SPEED_PSIM_SHIFT + | (Lp::SuperSpeedPlus as u32) << PROTO_SPEED_LP_SHIFT + | 7 << PROTO_SPEED_PSIV_SHIFT, + ), + ]; + + match self.supported_protocol(port) { + Some(supp_proto) => { + if supp_proto.psic() != 0 { + unsafe { supp_proto.protocol_speeds().iter() } + } else { + DEFAULT_SUPP_PROTO_SPEEDS.iter() + } + } + None => { + log::warn!( + "falling back to default supported protocol speeds for port {}", + port + ); + DEFAULT_SUPP_PROTO_SPEEDS.iter() + } + } + } + pub fn lookup_psiv(&self, port: PortId, psiv: u8) -> Option<&'static ProtocolSpeed> { + self.supported_protocol_speeds(port) + .find(|speed| speed.psiv() == psiv) + } +} +pub fn start_irq_reactor(hci: &Arc>, irq_file: Option) { + let hci_clone = Arc::clone(&hci); + + debug!("About to start IRQ reactor"); + + *hci.irq_reactor.lock().unwrap() = Some(thread::spawn(move || { + debug!("Started IRQ reactor thread"); + IrqReactor::new(hci_clone, irq_file).run() + })); +} + +pub fn start_device_enumerator(hci: &Arc>) { + let hci_clone = Arc::clone(&hci); + + debug!("About to start Device Enumerator"); + + *hci.device_enumerator.lock().unwrap() = Some(thread::spawn(move || { + debug!("Started Device Enumerator"); + DeviceEnumerator::new(hci_clone).run(); + })); +} + +#[derive(Deserialize)] +struct DriverConfig { + name: String, + class: u8, + subclass: i16, // The subclass may be meaningless for some drivers, hence negative values (and values above 255) mean "undefined". + command: Vec, +} +impl DriverConfig { + fn subclass(&self) -> Option { + u8::try_from(self.subclass).ok() + } +} +#[derive(Deserialize)] +struct DriversConfig { + drivers: Vec, +} + +use crate::xhci::device_enumerator::{DeviceEnumerationRequest, DeviceEnumerator}; +use crate::xhci::port::PortFlags; +use lazy_static::lazy_static; + +lazy_static! { + static ref DRIVERS_CONFIG: DriversConfig = { + // TODO: Load this at runtime. + const TOML: &'static [u8] = include_bytes!("../../drivers.toml"); + + toml::from_slice::(TOML).expect("Failed to parse internally embedded config file") + }; +} diff --git a/drivers/usb/xhcid/src/xhci/operational.rs b/drivers/usb/xhcid/src/xhci/operational.rs new file mode 100644 index 0000000000..12be477296 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/operational.rs @@ -0,0 +1,101 @@ +use common::io::{Io, Mmio}; + +/// The XHCI Operational Registers +/// +/// These registers specify the operational state of the XHCI device, and are used to receive status +/// messages and transmit commands. These registers are offset from the XHCI base address by the +/// "length" field of the [CapabilityRegs] +/// +/// See XHCI section 5.4. Table 5-18 describes the offset of these registers in memory. +#[repr(C, packed)] +pub struct OperationalRegs { + /// The USB Command Register (USBCMD) + /// + /// Describes the command to be executed by the XHCI. Writes to this register case a command + /// to be executed. + /// + /// - Bit 0 is the Run/Stop bit (R/S). Writing a value of 1 stops the xHC from executing the schedule, 1 resumes. Latency is ~16ms at worst. (See XHCI Table 5-20) + /// - Bit 1 is the Host Controller Reset Bit (HCRST). Used by software to reset the host controller (See XHCI Table 5-20) + /// - Bit 2 is the Interrupter Enable Bit (INTE). Enables interrupting the host system. + /// - Bit 3 is the Host System Error Enable Bit (HSEE). Enables out-of-band error signalling to the host. + /// - Bits 4-6 are reserved. + /// - Bit 7 is the Light Host Controller Reset Bit (LHCRST). Resets the driver without affecting the state of the ports. Affected by [CapabilityRegs] + /// - Bit 8 is the Controller Save State Bit (CSS). See XHCI Table 5-20 + /// - Bit 9 is the Controller Restore State Bit (CRS). See XHCI Table 5-20 + /// - Bit 10 is the Enable Wrap Event Bit (EWE). See XHCI Table 5-20 + /// - Bit 11 is the Enable U3 MFINDEX Stop Bit (EU3S). See XHCI Table 5-20 + /// - Bit 12 is reserved. + /// - Bit 13 is the CEM Enable Bit (CME). See XHCI Table 5-20 + /// - Bit 14 is the Extended TBC Enable Bit (ETE). See XHCI Table 5-20 + /// - Bit 15 is the Extended TBC TRB Status Enable Bit (TSC_En). See XHCI Table 5-20 + /// - Bit 16 is the VTIO Enable Bit (VTIOE). Controls the enable state of the VTIO capability. + /// - Bits 17-31 are reserved. + /// + pub usb_cmd: Mmio, + /// The USB Status Register (USBSTS) + /// + /// This register indicates pending interrupts and various states of the host controller. + /// + /// Software sets a bit to '0' in this register by writing a 1 to it. + /// + /// + pub usb_sts: Mmio, + /// The PAGESIZE Register (PAGESIZE) + /// + /// + pub page_size: Mmio, + /// Reserved bits (RsvdZ) + _rsvd: [Mmio; 2], + /// The Device Notification Control Register (DNCTRL) + /// + /// + pub dn_ctrl: Mmio, + /// The Command Ring Control Register Lower 32 bits (CRCR) + /// + /// + pub crcr_low: Mmio, + /// The Command Ring Control Register Upper 32 bits (CRCR) + /// + /// + pub crcr_high: Mmio, + /// Reserved bits (RsvdZ) + _rsvd2: [Mmio; 4], + /// Device Context Base Address Array Pointer Lower 32 bits (DCBAAP) + /// + /// + pub dcbaap_low: Mmio, + /// Device Context Base Address Array Pointer Upper 32 bits (DCBAAP) + /// + /// + pub dcbaap_high: Mmio, + /// The Configure Register (CONFIG) + /// + /// + pub config: Mmio, + // The standard has another set of reserved bits from 3C-3FFh here + // The standard has 400-13FFh has a Port Register Set here (likely defined in port.rs). +} + +// Run/stop +pub const USB_CMD_RS: u32 = 1 << 0; +/// Host controller reset +pub const USB_CMD_HCRST: u32 = 1 << 1; +// Interrupter enable +pub const USB_CMD_INTE: u32 = 1 << 2; + +/// Host controller halted +pub const USB_STS_HCH: u32 = 1 << 0; +/// Host controller not ready +pub const USB_STS_CNR: u32 = 1 << 11; + +/// The mask to get the CIE bit from the Config register. See [OperationalRegs] +pub const OP_CONFIG_CIE_BIT: u32 = 1 << 9; + +impl OperationalRegs { + pub fn cie(&self) -> bool { + self.config.readf(OP_CONFIG_CIE_BIT) + } + pub fn set_cie(&mut self, value: bool) { + self.config.writef(OP_CONFIG_CIE_BIT, value) + } +} diff --git a/drivers/usb/xhcid/src/xhci/port.rs b/drivers/usb/xhcid/src/xhci/port.rs new file mode 100644 index 0000000000..7c0f570d37 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/port.rs @@ -0,0 +1,113 @@ +use common::io::{Io, Mmio}; + +// RO - read-only +// ROS - read-only sticky +// RW - read/write +// RWS - read/write sticky +// RW1CS - read/write-1-to-clear sticky +// RW1S - read/write-1-to-set +// Sticky register values may preserve values through chip hardware reset + +bitflags! { + pub struct PortFlags: u32 { + const CCS = 1 << 0; // ROS + const PED = 1 << 1; // RW1CS + const RSVD_2 = 1 << 2; // RsvdZ + const OCA = 1 << 3; // RO + const PR = 1 << 4; // RW1S + const PLS_0 = 1 << 5; // RWS + const PLS_1 = 1 << 6; // RWS + const PLS_2 = 1 << 7; // RWS + const PLS_3 = 1 << 8; // RWS + const PP = 1 << 9; // RWS + const SPEED_0 = 1 << 10; // ROS + const SPEED_1 = 1 << 11; // ROS + const SPEED_2 = 1 << 12; // ROS + const SPEED_3 = 1 << 13; // ROS + const PIC_AMB = 1 << 14; // RWS + const PIC_GRN = 1 << 15; // RWS + const LWS = 1 << 16; // RW + const CSC = 1 << 17; // RW1CS + const PEC = 1 << 18; // RW1CS + const WRC = 1 << 19; // RW1CS + const OCC = 1 << 20; // RW1CS + const PRC = 1 << 21; // RW1CS + const PLC = 1 << 22; // RW1CS + const CEC = 1 << 23; // RW1CS + const CAS = 1 << 24; // RO + const WCE = 1 << 25; // RWS + const WDE = 1 << 26; // RWS + const WOE = 1 << 27; // RWS + const RSVD_28 = 1 << 28; // RsvdZ + const RSVD_29 = 1 << 29; // RsvdZ + const DR = 1 << 30; // RO + const WPR = 1 << 31; // RW1S + } +} + +#[repr(C, packed)] +pub struct Port { + // This has write one to clear fields, do not expose it, handle writes carefully! + portsc: Mmio, + pub portpmsc: Mmio, + pub portli: Mmio, + pub porthlpmc: Mmio, +} + +impl Port { + pub fn read(&self) -> u32 { + self.portsc.read() + } + + pub fn clear_csc(&mut self) { + self.portsc + .write((self.flags_preserved() | PortFlags::CSC).bits()); + } + + pub fn clear_prc(&mut self) { + self.portsc + .write((self.flags_preserved() | PortFlags::PRC).bits()); + } + + pub fn set_pr(&mut self) { + self.portsc + .write((self.flags_preserved() | PortFlags::PR).bits()); + } + + pub fn state(&self) -> u8 { + ((self.read() & (0b1111 << 5)) >> 5) as u8 + } + + pub fn speed(&self) -> u8 { + ((self.read() & (0b1111 << 10)) >> 10) as u8 + } + + pub fn flags(&self) -> PortFlags { + PortFlags::from_bits_truncate(self.read()) + } + + // Read only preserved flags + pub fn flags_preserved(&self) -> PortFlags { + // RO(S) and RW(S) bits should be preserved + // RW1S and RW1CS bits should not + let preserved = PortFlags::CCS + | PortFlags::OCA + | PortFlags::PLS_0 + | PortFlags::PLS_1 + | PortFlags::PLS_2 + | PortFlags::PLS_3 + | PortFlags::PP + | PortFlags::SPEED_0 + | PortFlags::SPEED_1 + | PortFlags::SPEED_2 + | PortFlags::SPEED_3 + | PortFlags::PIC_AMB + | PortFlags::PIC_GRN + | PortFlags::WCE + | PortFlags::WDE + | PortFlags::WOE + | PortFlags::DR; + + self.flags() & preserved + } +} diff --git a/drivers/usb/xhcid/src/xhci/ring.rs b/drivers/usb/xhcid/src/xhci/ring.rs new file mode 100644 index 0000000000..8e187ebea4 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/ring.rs @@ -0,0 +1,164 @@ +use std::mem; + +use syscall::error::Result; + +use common::dma::Dma; + +use super::trb::Trb; +use super::Xhci; + +pub struct Ring { + pub link: bool, + pub trbs: Dma<[Trb]>, + pub i: usize, + pub cycle: bool, +} + +impl Ring { + pub fn new(ac64: bool, length: usize, link: bool) -> Result { + Ok(Ring { + link, + trbs: unsafe { Xhci::::alloc_dma_zeroed_unsized_raw(ac64, length)? }, + i: 0, + cycle: link, + }) + } + + pub fn register(&self) -> u64 { + let base = self.trbs.physical() as *const Trb; + let addr = unsafe { base.offset(self.i as isize) }; + addr as u64 | self.cycle as u64 + } + + pub fn next_index(&mut self) -> usize { + let mut i; + loop { + i = self.i; + self.i += 1; + if self.i >= self.trbs.len() { + self.i = 0; + + if self.link { + let address = self.trbs.physical(); + self.trbs[i].link(address, true, self.cycle); + self.cycle = !self.cycle; + } else { + break; + } + } else { + break; + } + } + i + } + + pub fn next(&mut self) -> (&mut Trb, bool) { + let i = self.next_index(); + (&mut self.trbs[i], self.cycle) + } + /// Endless iterator that iterates through the ring items, over and over again. The iterator + /// doesn't enqueue or dequeue anything. + pub fn iter(&self) -> impl Iterator + '_ { + Iter { + ring: self, + i: self.i, + } + } + /// Takes a physical address and returns the index into this ring, that the index represents. + /// Returns `None` if the address is outside the bounds of this ring. + /// + /// # Panics + /// Panics if paddr is not a multiple of 16 bytes, i.e. the size of a TRB. + pub fn phys_addr_to_index(&self, ac64: bool, paddr: u64) -> Option { + let base = (self.trbs.physical() as u64) + & if ac64 { + 0xFFFF_FFFF_FFFF_FFFF + } else { + 0xFFFF_FFFF + }; + let offset = paddr.checked_sub(base)? as usize; + + assert_eq!( + offset % mem::size_of::(), + 0, + "unaligned TRB physical address" + ); + + let index = offset / mem::size_of::(); + + if index > self.trbs.len() { + return None; + } + + Some(index) + } + pub fn phys_addr_to_entry_ref(&self, ac64: bool, paddr: u64) -> Option<&Trb> { + Some(&self.trbs[self.phys_addr_to_index(ac64, paddr)?]) + } + pub fn phys_addr_to_entry_mut(&mut self, ac64: bool, paddr: u64) -> Option<&mut Trb> { + let index = self.phys_addr_to_index(ac64, paddr)?; + Some(&mut self.trbs[index]) + } + pub fn phys_addr_to_entry(&self, ac64: bool, paddr: u64) -> Option { + Some(self.trbs[self.phys_addr_to_index(ac64, paddr)?].clone()) + } + pub(crate) fn start_virt_addr(&self) -> *const Trb { + self.trbs.as_ptr() + } + pub(crate) fn end_virt_addr(&self) -> *const Trb { + unsafe { self.start_virt_addr().offset(self.trbs.len() as isize) } + } + pub fn trb_phys_ptr(&self, ac64: bool, trb: &Trb) -> u64 { + let trb_virt_pointer = trb as *const Trb; + let trbs_base_virt_pointer = self.trbs.as_ptr(); + + if (trb_virt_pointer as usize) < (trbs_base_virt_pointer as usize) + || (trb_virt_pointer as usize) + > (trbs_base_virt_pointer as usize) + self.trbs.len() * mem::size_of::() + { + panic!("Gave a TRB outside of the ring, when retrieving its physical address in that ring. TRB: {:?} (at address {:p})", trb, trb); + } + let trb_offset_from_base = trb_virt_pointer as u64 - trbs_base_virt_pointer as u64; + + let trbs_base_phys_ptr = (self.trbs.physical() as u64) + & if ac64 { + 0xFFFF_FFFF_FFFF_FFFF + } else { + 0xFFFF_FFFF + }; + let trb_phys_ptr = trbs_base_phys_ptr + trb_offset_from_base; + trb_phys_ptr + } + /* + /// Endless mutable iterator that iterates through the ring items, over and over again. The + /// iterator doesn't enqueue or dequeue anything, but the trbs are mutably borrowed. + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + IterMut { ring: self, i: self.i } + }*/ +} +struct Iter<'ring> { + ring: &'ring Ring, + i: usize, +} +impl<'ring> Iterator for Iter<'ring> { + type Item = &'ring Trb; + + fn next(&mut self) -> Option { + let i = self.i; + self.i = (self.i + 1) % self.ring.trbs.len(); + Some(&self.ring.trbs[i]) + } +} +/*struct IterMut<'ring> { + ring: &'ring mut Ring, + i: usize, +} +impl<'ring> Iterator for IterMut<'ring> { + type Item = &'ring mut Trb; + + fn next(&mut self) -> Option { + let i = self.i; + self.i = (self.i + 1) % self.ring.trbs.len(); + Some(&mut self.ring.trbs[i]) + } +}*/ diff --git a/drivers/usb/xhcid/src/xhci/runtime.rs b/drivers/usb/xhcid/src/xhci/runtime.rs new file mode 100644 index 0000000000..55d54d4472 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/runtime.rs @@ -0,0 +1,20 @@ +use common::io::Mmio; + +#[repr(C, packed)] +pub struct Interrupter { + pub iman: Mmio, + pub imod: Mmio, + pub erstsz: Mmio, + _rsvd: Mmio, + pub erstba_low: Mmio, + pub erstba_high: Mmio, + pub erdp_low: Mmio, + pub erdp_high: Mmio, +} + +#[repr(C, packed)] +pub struct RuntimeRegs { + pub mfindex: Mmio, + _rsvd: [Mmio; 7], + pub ints: [Interrupter; 1024], +} diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs new file mode 100644 index 0000000000..1b92fa51aa --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -0,0 +1,2811 @@ +//! Provides the File Descriptor Scheme Interface to the XHCI. +//! +//! This file implements the basic unix file operations that are used to interface with the XHCI +//! driver. While an external program could interact with the driver using this interface, a +//! higher-level abstraction can be found in driver_interface.rs. It is recommended that you use +//! the functions in that module to interact with the driver. +//! +//! The XHCI driver has the following set of schemes: +//! +//! port +//! port/configure +//! port/request +//! port/endpoints +//! port/descriptors +//! port/state +//! port/endpoints/ +//! port/endpoints//ctl +//! port/endpoints//data +use std::convert::TryFrom; +use std::io::prelude::*; +use std::ops::Deref; +use std::sync::atomic; +use std::{cmp, fmt, io, mem, str}; + +use common::dma::Dma; +use futures::executor::block_on; +use log::{debug, error, info, trace, warn}; +use redox_scheme::scheme::SchemeSync; +use smallvec::SmallVec; + +use common::io::Io; +use redox_scheme::{CallerCtx, OpenResult}; +use syscall::schemev2::NewFdFlags; +use syscall::{ + Error, Result, Stat, EACCES, EBADF, EBADFD, EBADMSG, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, + ENOTDIR, EPROTO, ESPIPE, MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_RDWR, O_STAT, O_WRONLY, + SEEK_CUR, SEEK_END, SEEK_SET, +}; + +use super::{port, usb}; +use super::{EndpointState, PortId, Xhci}; + +use super::context::{ + SlotState, StreamContextArray, StreamContextType, CONTEXT_32, CONTEXT_64, + SLOT_CONTEXT_STATE_MASK, SLOT_CONTEXT_STATE_SHIFT, +}; +use super::extended::ProtocolSpeed; +use super::irq_reactor::{EventDoorbell, RingId}; +use super::ring::Ring; +use super::trb::{TransferKind, Trb, TrbCompletionCode, TrbType}; +use super::usb::endpoint::EndpointTy; + +use crate::driver_interface::*; +use regex::Regex; + +lazy_static! { + static ref REGEX_PORT_CONFIGURE: Regex = Regex::new(r"^port([\d\.]+)/configure$") + .expect("Failed to create the regex for the port/configure scheme."); + static ref REGEX_PORT_ATTACH: Regex = Regex::new(r"^port([\d\.]+)/attach$") + .expect("Failed to create the regex for the port/attach scheme."); + static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") + .expect("Failed to create the regex for the port/detach scheme."); + static ref REGEX_PORT_DESCRIPTORS: Regex = Regex::new(r"^port([\d\.]+)/descriptors$") + .expect("Failed to create the regex for the port/descriptors"); + static ref REGEX_PORT_STATE: Regex = Regex::new(r"^port([\d\.]+)/state$") + .expect("Failed to create the regex for the port/state scheme"); + static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$") + .expect("Failed to create the regex for the port/request scheme"); + static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$") + .expect("Failed to create the regex for the port/endpoints scheme"); + static ref REGEX_PORT_SPECIFIC_ENDPOINT: Regex = + Regex::new(r"^port([\d\.]+)/endpoints/(\d{1,3})$") + .expect("Failed to create the regex for the port/endpoints/ scheme"); + static ref REGEX_PORT_SUB_ENDPOINT: Regex = Regex::new( + r"port([\d\.]+)/endpoints/(\d{1,3})/(ctl|data)$" + ) + .expect("Failed to create the regex for the port/endpoints// scheme"); + static ref REGEX_TOP_LEVEL: Regex = + Regex::new(r"^$").expect("Failed to create the regex for the top-level scheme"); +} + +pub enum ControlFlow { + Continue, + Break, +} + +#[derive(Clone, Copy, Debug)] +pub enum EndpIfState { + Init, + WaitingForDataPipe { + direction: XhciEndpCtlDirection, + bytes_transferred: u32, + bytes_to_transfer: u32, + }, + WaitingForStatus, + WaitingForTransferResult(PortTransferStatus), +} + +/// Subdirs of an endpoint +#[derive(Debug)] +pub enum EndpointHandleTy { + /// portX/endpoints/Y/data. Allows clients to read and write data associated with ctl requests. + Data, + + /// portX/endpoints/Y/status + Ctl, + + /// portX/endpoints/Y/ + Root(Vec), // content +} + +#[derive(Clone, Copy, Debug)] +pub enum PortTransferState { + /// Ready to read or write to do another transfer + Ready, + + /// Transfer has completed, and the status has to be read. + WaitingForStatusReq(PortTransferStatus), +} + +pub enum PortReqState { + Init, + WaitingForDeviceBytes(Dma<[u8]>, usb::Setup), // buffer, setup params + WaitingForHostBytes(Dma<[u8]>, usb::Setup), // buffer, setup params + TmpSetup(usb::Setup), + Tmp, +} + +/// The Handle to a specific scheme that is returned by an open() operation. +/// +/// Contains some information about the data requested via the handle. +#[derive(Debug)] +pub enum Handle { + TopLevel(Vec), // contents (ports) + Port(PortId, Vec), // port, contents + PortDesc(PortId, Vec), // port, contents + PortState(PortId), // port + PortReq(PortId, PortReqState), // port, state + Endpoints(PortId, Vec), // port, contents + Endpoint(PortId, u8, EndpointHandleTy), // port, endpoint, state + ConfigureEndpoints(PortId), // port + AttachDevice(PortId), // port + DetachDevice(PortId), // port +} + +/// The type of handle. +/// +/// This is used by fstat() to determine whether to return a: +/// - MODE_DIR +/// - MODE_FILE +/// - MODE_CHR +pub(crate) enum HandleType { + Directory, + File, + Character, +} + +/// Parameters to a handle that were extracted from a scheme. +/// +/// This structure is used to easily convert a scheme filesystem path to +/// the parameters that we care about when constructing a handle. +#[derive(Debug)] +enum SchemeParameters { + /// The scheme references the top-level XHCI driver endpoint + TopLevel, + /// /port + Port(PortId), // port number + /// /port/descriptors + PortDesc(PortId), // port number + /// /port/state + PortState(PortId), // port number + /// /port/request + PortReq(PortId), // port number + /// /port/endpoints + Endpoints(PortId), // port number + /// /port/endpoints//(data|ctl) + /// + /// This can also represent + /// /port/endpoints/ + Endpoint(PortId, u8, String), // port number, endpoint number, handle type + /// /port/configure + ConfigureEndpoints(PortId), // port number + /// /port/attach + AttachDevice(PortId), // port number + /// /port/detach + DetachDevice(PortId), // port number +} + +impl Handle { + /// Converts a handle back into the scheme that generated it. + /// + /// This is useful for implementing fpath, as the input parameters for our existing schemes + /// are generally static for the lifetime of the driver and can easily be retrieved. + /// + /// # Returns + /// - A [String] containing the scheme path that the handle is associated with. + pub(crate) fn to_scheme(&self) -> String { + match self { + Handle::TopLevel(_) => String::from(""), + Handle::Port(port_num, _) => { + format!("port{}", port_num) + } + Handle::PortDesc(port_num, _) => { + format!("port{}/descriptors", port_num) + } + Handle::PortState(port_num) => { + format!("port{}/state", port_num) + } + Handle::PortReq(port_num, _) => { + format!("port{}/request", port_num) + } + Handle::Endpoints(port_num, _) => { + format!("port{}/endpoints", port_num) + } + Handle::Endpoint(port_num, endpoint_num, handle_type) => match handle_type { + EndpointHandleTy::Data => { + format!("port{}/endpoints/{}/data", port_num, endpoint_num) + } + EndpointHandleTy::Ctl => { + format!("port{}/endpoints/{}/ctl", port_num, endpoint_num) + } + EndpointHandleTy::Root(_) => { + format!("port{}/endpoints/{}", port_num, endpoint_num) + } + }, + Handle::ConfigureEndpoints(port_num) => { + format!("port{}/configure", port_num) + } + Handle::AttachDevice(port_num) => { + format!("port{}/attach", port_num) + } + Handle::DetachDevice(port_num) => { + format!("port{}/detach", port_num) + } + } + } + + /// Gets the access mode for this handle + /// + /// Handles can be a file, a directory, or a character interface. The mode that we use is + /// entirely dependent upon the functionality of the scheme endpoint, so this returns the value + /// that should be associated with that endpoint. + /// + /// # Returns + /// - [HandleType] - The access mode associated with the handle. + pub(crate) fn get_handle_type(&self) -> HandleType { + match self { + &Handle::TopLevel(_) => HandleType::Directory, + &Handle::Port(_, _) => HandleType::Directory, + &Handle::Endpoints(_, _) => HandleType::Directory, + &Handle::PortDesc(_, _) => HandleType::File, + &Handle::PortReq(_, PortReqState::WaitingForDeviceBytes(_, _)) => HandleType::Character, + &Handle::PortReq(_, PortReqState::WaitingForHostBytes(_, _)) => HandleType::Character, + &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), + &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), + &Handle::PortState(_) => HandleType::Character, + &Handle::PortReq(_, _) => HandleType::Character, + &Handle::ConfigureEndpoints(_) => HandleType::Character, + &Handle::AttachDevice(_) => HandleType::Character, + &Handle::DetachDevice(_) => HandleType::Character, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => HandleType::Character, + EndpointHandleTy::Ctl => HandleType::Character, + EndpointHandleTy::Root(_) => HandleType::Directory, + }, + } + } + + /// Gets the length of the file buffer as returned by fstat in Stat.st_size + /// + /// As some of these endpoints did not return a length in the origin code, this + /// provides an Option + /// + /// # Returns + /// Either the size of the buffer, or [Option::None] if the buffer does not exist. + pub(crate) fn get_buf_len(&self) -> Option { + match self { + &Handle::TopLevel(ref buf) => Some(buf.len()), + &Handle::Port(_, ref buf) => Some(buf.len()), + &Handle::Endpoints(_, ref buf) => Some(buf.len()), + &Handle::PortDesc(_, ref buf) => Some(buf.len()), + &Handle::PortReq(_, PortReqState::WaitingForDeviceBytes(ref buf, _)) => Some(buf.len()), + &Handle::PortReq(_, PortReqState::WaitingForHostBytes(ref buf, _)) => Some(buf.len()), + &Handle::PortReq(_, PortReqState::Tmp) => None, + &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, + &Handle::PortState(_) => None, + &Handle::PortReq(_, _) => None, + &Handle::ConfigureEndpoints(_) => None, + &Handle::AttachDevice(_) => None, + &Handle::DetachDevice(_) => None, + &Handle::Endpoint(_, _, ref st) => match st { + EndpointHandleTy::Data => None, + EndpointHandleTy::Ctl => None, + EndpointHandleTy::Root(ref buf) => Some(buf.len()), + }, + } + } +} + +impl SchemeParameters { + /// This function gets a partially populated handle from a scheme string. + /// + /// This function is intended to be used by the driver's 'open' filesystem + /// hook to determine if the given string value represents a valid scheme + /// + /// # Arguments + /// 'scheme: &[str]' - A scheme in string format. + /// + /// # Returns + /// A [Result] containing: + /// - A [SchemeParameters] object representing the scheme that was passed, populated with the input parameters + /// - [ENOENT] if the passed scheme path is not valid for this driver. + /// + /// # Notes + /// ENOENT is returned so that it can easily be forwarded to the caller of open(). It cleans + /// up the function considerably to be able to use the ? syntax. + pub fn from_scheme(scheme: &str) -> Result { + fn get_string_from_regex( + rgx: &Regex, + scheme: &str, + capture_idx: usize, + ) -> syscall::Result { + if let Some(capture_list) = rgx.captures(scheme) { + if let Some(value) = capture_list.get(capture_idx + 1) { + return Ok(value.as_str().to_string()); + } + } + + Err(Error::new(ENOENT)) + }; + + fn get_port_id_from_regex( + rgx: &Regex, + scheme: &str, + capture_idx: usize, + ) -> syscall::Result { + if let Some(capture_list) = rgx.captures(scheme) { + if let Some(value) = capture_list.get(capture_idx + 1) { + if let Ok(port_id) = value.as_str().parse::() { + return Ok(port_id); + } + } + } + + Err(Error::new(ENOENT)) + }; + + fn get_u8_from_regex(rgx: &Regex, scheme: &str, capture_idx: usize) -> syscall::Result { + if let Some(capture_list) = rgx.captures(scheme) { + if let Some(value) = capture_list.get(capture_idx + 1) { + if let Ok(integer) = value.as_str().parse::() { + return Ok(integer); + } + } + } + + Err(Error::new(ENOENT)) + }; + + //We don't implement From::<&path::Path> because we don't want to make this a part of + //the public interface. This function does not guarantee that the handle is VALID, only + //that the scheme is valid. open() will validate the contents of the enumeration instance, + //and store it if it's valid. + + //Generate the regular expressions for all of our valid schemes. + + //Check if we have a match and either return a partially initialized scheme, OR ENOENT + if REGEX_PORT_CONFIGURE.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_CONFIGURE, scheme, 0)?; + + Ok(Self::ConfigureEndpoints(port_num)) + } else if REGEX_PORT_ATTACH.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_ATTACH, scheme, 0)?; + + Ok(Self::AttachDevice(port_num)) + } else if REGEX_PORT_DETACH.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; + + Ok(Self::DetachDevice(port_num)) + } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; + + Ok(Self::PortDesc(port_num)) + } else if REGEX_PORT_STATE.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; + + Ok(Self::PortState(port_num)) + } else if REGEX_PORT_REQUEST.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; + + Ok(Self::PortReq(port_num)) + } else if REGEX_PORT_ENDPOINTS.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_ENDPOINTS, scheme, 0)?; + + Ok(Self::Endpoints(port_num)) + } else if REGEX_PORT_SPECIFIC_ENDPOINT.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_SPECIFIC_ENDPOINT, scheme, 0)?; + let endpoint_num = get_u8_from_regex(®EX_PORT_SPECIFIC_ENDPOINT, scheme, 1)?; + + Ok(Self::Endpoint(port_num, endpoint_num, String::from("root"))) + } else if REGEX_PORT_SUB_ENDPOINT.is_match(scheme) { + let port_num = get_port_id_from_regex(®EX_PORT_SUB_ENDPOINT, scheme, 0)?; + let endpoint_num = get_u8_from_regex(®EX_PORT_SUB_ENDPOINT, scheme, 1)?; + let handle_type = get_string_from_regex(®EX_PORT_SUB_ENDPOINT, scheme, 2)?; + + Ok(Self::Endpoint(port_num, endpoint_num, handle_type)) + } else if REGEX_TOP_LEVEL.is_match(scheme) { + Ok(Self::TopLevel) + } else { + Err(Error::new(ENOENT)) + } + } +} + +#[derive(Clone, Copy)] +struct DmaSliceDbg<'a, T>(&'a Dma<[T]>); + +impl<'a, T> fmt::Debug for DmaSliceDbg<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let DmaSliceDbg(dma) = self; + + f.debug_struct("Dma") + .field("phys_ptr", &(dma.physical() as *const u8)) + .field("virt_ptr", &(dma.deref().as_ptr() as *const u8)) + .field("length", &(dma.len() * mem::size_of::())) + .finish() + } +} + +impl fmt::Debug for PortReqState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Init => f.debug_struct("PortReqState::Init").finish(), + Self::WaitingForDeviceBytes(ref dma, setup) => f + .debug_tuple("PortReqState::WaitingForDeviceBytes") + .field(&DmaSliceDbg(dma)) + .field(&setup) + .finish(), + Self::WaitingForHostBytes(ref dma, setup) => f + .debug_tuple("PortReqState::WaitingForHostBytes") + .field(&DmaSliceDbg(dma)) + .field(&setup) + .finish(), + Self::TmpSetup(setup) => f + .debug_tuple("PortReqState::TmpSetup") + .field(&setup) + .finish(), + Self::Tmp => f.debug_struct("PortReqState::Init").finish(), + } + } +} + +// TODO: Even though the driver interface descriptors are originally intended for JSON, they should suffice... for +// now. + +impl From for EndpDesc { + fn from(d: usb::EndpointDescriptor) -> Self { + Self { + kind: d.kind, + address: d.address, + attributes: d.attributes, + interval: d.interval, + max_packet_size: d.max_packet_size, + ssc: None, + sspc: None, + } + } +} + +impl From for HidDesc { + fn from(d: usb::HidDescriptor) -> Self { + Self { + kind: d.kind, + hid_spec_release: d.hid_spec_release, + country: d.country_code, + desc_count: d.num_descriptors, + desc_ty: d.report_desc_ty, + desc_len: d.report_desc_len, + optional_desc_ty: d.optional_desc_ty, + optional_desc_len: d.optional_desc_len, + } + } +} + +impl From for SuperSpeedCmp { + fn from(d: usb::SuperSpeedCompanionDescriptor) -> Self { + Self { + kind: d.kind, + attributes: d.attributes, + bytes_per_interval: d.bytes_per_interval, + max_burst: d.max_burst, + } + } +} +impl From for SuperSpeedPlusIsochCmp { + fn from(r: usb::SuperSpeedPlusIsochCmpDescriptor) -> Self { + Self { + kind: r.kind, + bytes_per_interval: r.bytes_per_interval, + } + } +} + +/// Any descriptor that can be stored in the config desc "data" area. +#[derive(Debug)] +pub enum AnyDescriptor { + // These are the ones that I have found, but there are more. + Device(usb::DeviceDescriptor), + Config(usb::ConfigDescriptor), + Interface(usb::InterfaceDescriptor), + Endpoint(usb::EndpointDescriptor), + Hid(usb::HidDescriptor), + SuperSpeedCompanion(usb::SuperSpeedCompanionDescriptor), + SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor), +} + +impl AnyDescriptor { + fn parse(bytes: &[u8]) -> Option<(Self, usize)> { + if bytes.len() < 2 { + return None; + } + + let len = bytes[0]; + let kind = bytes[1]; + + if bytes.len() < len.into() { + return None; + } + + Some(( + match kind { + 1 => Self::Device(*plain::from_bytes(bytes).ok()?), + 2 => Self::Config(*plain::from_bytes(bytes).ok()?), + 4 => Self::Interface(*plain::from_bytes(bytes).ok()?), + 5 => Self::Endpoint(*plain::from_bytes(bytes).ok()?), + 33 => Self::Hid(*plain::from_bytes(bytes).ok()?), + 48 => Self::SuperSpeedCompanion(*plain::from_bytes(bytes).ok()?), + 49 => Self::SuperSpeedPlusCompanion(*plain::from_bytes(bytes).ok()?), + _ => { + //panic!("Descriptor unknown {}: bytes {:#0x?}", kind, bytes); + return None; + } + }, + len.into(), + )) + } +} + +impl Xhci { + async fn new_if_desc( + &self, + port_id: PortId, + slot: u8, + desc: usb::InterfaceDescriptor, + endps: impl IntoIterator, + hid_descs: impl IntoIterator, + lang_id: u16, + ) -> Result { + Ok(IfDesc { + alternate_setting: desc.alternate_setting, + class: desc.class, + interface_str: if desc.interface_str > 0 { + Some( + self.fetch_string_desc(port_id, slot, desc.interface_str, lang_id) + .await?, + ) + } else { + None + }, + kind: desc.kind, + number: desc.number, + protocol: desc.protocol, + sub_class: desc.sub_class, + endpoints: endps.into_iter().collect(), + hid_descs: hid_descs.into_iter().collect(), + }) + } + /// Pushes a command TRB to the command ring, rings the doorbell, and then awaits its Command + /// Completion Event. + /// + /// # Locking + /// This function will lock `Xhci::cmd` and `Xhci::dbs`. + pub async fn execute_command(&self, f: F) -> (Trb, Trb) { + //TODO: find out why this bit is set earlier! + if self.interrupt_is_pending(0) { + debug!("The EHB bit is already set!"); + //self.force_clear_interrupt(0); + } + + let next_event = { + let mut command_ring = self.cmd.lock().unwrap(); + let (cmd_index, cycle) = (command_ring.next_index(), command_ring.cycle); + + debug!("Sending command with cycle bit {}", cycle as u8); + + { + let command_trb = &mut command_ring.trbs[cmd_index]; + f(command_trb, cycle); + } + + // get the future here before awaiting, to destroy the lock before deadlock + let command_trb = &command_ring.trbs[cmd_index]; + self.next_command_completion_event_trb( + &*command_ring, + command_trb, + EventDoorbell::new(self, 0, 0), + ) + }; + + let trbs = next_event.await; + let event_trb = trbs.event_trb; + let command_trb = trbs.src_trb.expect("Command completion event TRBs shall always have a valid pointer to a valid source command TRB"); + + assert_eq!( + event_trb.trb_type(), + TrbType::CommandCompletion as u8, + "The IRQ reactor (or the xHC) gave an invalid event TRB" + ); + + (event_trb, command_trb) + } + pub async fn execute_control_transfer( + &self, + port_num: PortId, + setup: usb::Setup, + tk: TransferKind, + name: &str, + mut d: D, + ) -> Result + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { + let future = { + let mut port_state = self.port_state_mut(port_num)?; + let slot = port_state.slot; + + let mut endpoint_state = port_state + .endpoint_states + .get_mut(&0) + .ok_or(Error::new(EIO))?; + + let ring = endpoint_state.ring().ok_or(Error::new(EIO))?; + + let first_index = ring.next_index(); + let (cmd, cycle) = (&mut ring.trbs[first_index], ring.cycle); + cmd.setup(setup, tk, cycle); + + if tk != TransferKind::NoData { + loop { + let (trb, cycle) = ring.next(); + match d(trb, cycle) { + ControlFlow::Break => break, + ControlFlow::Continue => continue, + } + } + } + + let last_index = ring.next_index(); + let (cmd, cycle) = (&mut ring.trbs[last_index], ring.cycle); + + let interrupter = 0; + // When the data stage is in, the status stage must be out + let input = tk != TransferKind::In; + let ioc = true; + let ch = false; + let ent = false; + cmd.status(interrupter, input, ioc, ch, ent, cycle); + + self.next_transfer_event_trb( + RingId::default_control_pipe(port_num), + ring, + &ring.trbs[first_index], + &ring.trbs[last_index], + EventDoorbell::new(self, usize::from(slot), Self::def_control_endp_doorbell()), + ) + }; + + let trbs = future.await; + let event_trb = trbs.event_trb; + let status_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + + handle_transfer_event_trb("CONTROL_TRANSFER", &event_trb, &status_trb)?; + + //self.event_handler_finished(); + + Ok(event_trb) + } + /// NOTE: There has to be AT LEAST one successful invocation of `d`, that actually updates the + /// TRB (it could be a NO-OP in the worst case). + /// The function is also required to set the Interrupt on Completion flag, or this function + /// will never complete. + pub async fn execute_transfer( + &self, + port_num: PortId, + endp_num: u8, + stream_id: u16, + name: &str, + mut d: D, + ) -> Result + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let mut port_state = self.port_state_mut(port_num)?; + + let slot = port_state.slot; + + let (doorbell_data_stream, doorbell_data_no_stream) = { + let endp_desc = port_state + .get_endp_desc(endp_idx) + .ok_or(Error::new(EBADFD))?; + + //TODO: clean this up + ( + Self::endp_doorbell(endp_num, endp_desc, stream_id), + Self::endp_doorbell(endp_num, endp_desc, 0), + ) + }; + + let endp_state = port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADF))?; + + let (has_streams, ring) = match endp_state { + EndpointState { + transfer: super::RingOrStreams::Ring(ref mut ring), + .. + } => (false, ring), + EndpointState { + transfer: super::RingOrStreams::Streams(stream_ctx_array), + .. + } => ( + true, + stream_ctx_array + .rings + .get_mut(&1) + .ok_or(Error::new(EBADF))?, + ), + }; + + let future = loop { + let last_index = ring.next_index(); + let (trb, cycle) = (&mut ring.trbs[last_index], ring.cycle); + + match d(trb, cycle) { + ControlFlow::Break => { + break self.next_transfer_event_trb( + super::irq_reactor::RingId { + port: port_num, + endpoint_num: endp_num, + stream_id, + }, + ring, + //TODO: find first TRB + &ring.trbs[last_index], + &ring.trbs[last_index], + EventDoorbell::new( + self, + usize::from(slot), + if has_streams { + doorbell_data_stream + } else { + doorbell_data_no_stream + }, + ), + ); + } + ControlFlow::Continue => continue, + } + }; + + drop(port_state); + + let trbs = future.await; + let event_trb = trbs.event_trb; + let transfer_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + + handle_transfer_event_trb("EXECUTE_TRANSFER", &event_trb, &transfer_trb)?; + + // FIXME: EDTLA if event data was set + if event_trb.completion_code() != TrbCompletionCode::ShortPacket as u8 + && event_trb.transfer_length() != 0 + { + error!("Event trb didn't yield a short packet, but some bytes were not transferred"); + return Err(Error::new(EIO)); + } + + // TODO: Handle event data + trace!("EVENT DATA: {:?}", event_trb.event_data()); + + Ok(event_trb) + } + async fn device_req_no_data(&self, port: PortId, req: usb::Setup) -> Result<()> { + trace!("DEVICE_REQ_NO_DATA port {}, req: {:?}", port, req); + + self.execute_control_transfer( + port, + req, + TransferKind::NoData, + "DEVICE_REQ_NO_DATA", + |_, _| ControlFlow::Break, + ) + .await?; + Ok(()) + } + + async fn set_configuration(&self, port: PortId, config: u8) -> Result<()> { + debug!("Setting configuration value {} to port {}", config, port); + self.device_req_no_data(port, usb::Setup::set_configuration(config)) + .await + } + + async fn set_interface( + &self, + port: PortId, + interface_num: u8, + alternate_setting: u8, + ) -> Result<()> { + debug!( + "Setting interface value {} (alternate setting {}) to port {}", + interface_num, alternate_setting, port + ); + self.device_req_no_data( + port, + usb::Setup::set_interface(interface_num, alternate_setting), + ) + .await + } + + async fn reset_endpoint(&self, port_num: PortId, endp_num: u8, tsp: bool) -> Result<()> { + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + + let endp_desc = port_state + .get_endp_desc(endp_idx) + .ok_or(Error::new(EBADFD))?; + let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc); + + let slot = self + .port_states + .get(&port_num) + .ok_or(Error::new(EBADF))? + .slot; + + let (event_trb, command_trb) = self + .execute_command(|trb, cycle| { + trb.reset_endpoint(slot, endp_num_xhc, tsp, cycle); + }) + .await; + //self.event_handler_finished(); + + handle_event_trb("RESET_ENDPOINT", &event_trb, &command_trb) + } + + fn endp_ctx_interval(speed_id: &ProtocolSpeed, endp_desc: &EndpDesc) -> u8 { + /// Logarithmic (base 2) 125 µs periods per millisecond. + const MILLISEC_PERIODS: u8 = 3; + + // TODO: Also check the Speed ID for superspeed(plus). + if (speed_id.is_lowspeed() || speed_id.is_fullspeed()) && endp_desc.is_interrupt() { + // The interval field has values 1-255, ranging from 1 ms to 255 ms. + // TODO: This is correct, right? + let last_power_of_two = 8 - endp_desc.interval.leading_zeros() as u8; + last_power_of_two - 1 + MILLISEC_PERIODS + } else if speed_id.is_fullspeed() && endp_desc.is_isoch() { + // bInterval has values 1-16, ranging from 1 ms to 32,768 ms. + endp_desc.interval - 1 + MILLISEC_PERIODS + } else if (speed_id.is_fullspeed() + || endp_desc.is_superspeed() + || endp_desc.is_superspeedplus()) + && (endp_desc.is_interrupt() || endp_desc.is_isoch()) + { + // bInterval has values 1-16, but ranging from 125 µs to 4096 ms. + endp_desc.interval - 1 + } else { + // This includes superspeed(plus) control and bulk endpoints in particular. + 0 + } + } + fn endp_ctx_max_burst( + speed_id: &ProtocolSpeed, + dev_desc: &DevDesc, + endp_desc: &EndpDesc, + ) -> u8 { + if speed_id.is_highspeed() && (endp_desc.is_interrupt() || endp_desc.is_isoch()) { + assert_eq!(dev_desc.major_version(), 2); + ((endp_desc.max_packet_size & 0x0C00) >> 11) as u8 + } else if endp_desc.is_superspeed() { + endp_desc.max_burst() + } else { + 0 + } + } + fn endp_ctx_max_packet_size(endp_desc: &EndpDesc) -> u16 { + // TODO: Control endpoint? Encoding? + endp_desc.max_packet_size & 0x07FF + } + fn endp_ctx_max_esit_payload( + speed_id: &ProtocolSpeed, + dev_desc: &DevDesc, + endp_desc: &EndpDesc, + max_packet_size: u16, + max_burst_size: u8, + ) -> u32 { + const KIB: u32 = 1024; + + if dev_desc.major_version() == 2 && endp_desc.is_periodic() { + u32::from(max_packet_size) * (u32::from(max_burst_size) + 1) + } else if endp_desc.has_ssp_companion() { + endp_desc.sspc.as_ref().unwrap().bytes_per_interval + } else if endp_desc.ssc.is_some() { + u32::from(endp_desc.ssc.as_ref().unwrap().bytes_per_interval) + } else if speed_id.is_fullspeed() && endp_desc.is_interrupt() { + 64 + } else if speed_id.is_fullspeed() && endp_desc.is_isoch() { + 1 * KIB + } else if (speed_id.is_highspeed() && (endp_desc.is_interrupt() || endp_desc.is_isoch())) + || endp_desc.is_superspeed() && endp_desc.is_interrupt() + { + 3 * KIB + } else if endp_desc.is_superspeed() && endp_desc.is_isoch() { + 48 * KIB + } else { + // TODO: Is "maximum allowed" ESIT payload, the same as "maximum" ESIT payload. + 0 + } + } + + fn port_state( + &self, + port: PortId, + ) -> Result>> { + self.port_states.get(&port).ok_or(Error::new(EBADF)) + } + fn port_state_mut( + &self, + port: PortId, + ) -> Result>> { + self.port_states.get_mut(&port).ok_or(Error::new(EBADF)) + } + + async fn configure_endpoints_once( + &self, + port: PortId, + req: &ConfigureEndpointsReq, + ) -> Result<()> { + let (endp_desc_count, new_context_entries, configuration_value) = { + let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; + + port_state.cfg_idx = Some(req.config_desc); + + let config_desc = port_state + .dev_desc + .as_ref() + .unwrap() + .config_descs + .iter() + .find(|desc| desc.configuration_value == req.config_desc) + .ok_or(Error::new(EBADFD))?; + + //TODO: USE ENDPOINTS FROM ALL INTERFACES + let mut endp_desc_count = 0; + let mut new_context_entries = 1; + for if_desc in config_desc.interface_descs.iter() { + for endpoint in if_desc.endpoints.iter() { + endp_desc_count += 1; + let entry = Self::endp_num_to_dci(endp_desc_count, endpoint); + if entry > new_context_entries { + new_context_entries = entry; + } + } + } + new_context_entries += 1; + + if endp_desc_count >= 31 { + warn!("endpoints length {} >= 31", endp_desc_count); + return Err(Error::new(EIO)); + } + + ( + endp_desc_count, + new_context_entries, + config_desc.configuration_value, + ) + }; + let lec = self.cap.lec(); + let log_max_psa_size = self.cap.max_psa_size(); + + let port_speed_id = self.ports.lock().unwrap()[port.root_hub_port_index()].speed(); + let speed_id: &ProtocolSpeed = self.lookup_psiv(port, port_speed_id).ok_or_else(|| { + warn!("no speed_id"); + Error::new(EIO) + })?; + + { + let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; + let mut input_context = port_state.input_context.lock().unwrap(); + + // Configure the slot context as well, which holds the last index of the endp descs. + input_context.add_context.write(1); + input_context.drop_context.write(0); + + const CONTEXT_ENTRIES_MASK: u32 = 0xF800_0000; + const CONTEXT_ENTRIES_SHIFT: u8 = 27; + + const HUB_PORTS_MASK: u32 = 0xFF00_0000; + const HUB_PORTS_SHIFT: u8 = 24; + + let mut current_slot_a = input_context.device.slot.a.read(); + let mut current_slot_b = input_context.device.slot.b.read(); + + // Set context entries + current_slot_a &= !CONTEXT_ENTRIES_MASK; + current_slot_a |= + (u32::from(new_context_entries) << CONTEXT_ENTRIES_SHIFT) & CONTEXT_ENTRIES_MASK; + + // Set hub data + current_slot_a &= !(1 << 26); + current_slot_b &= !HUB_PORTS_MASK; + if let Some(hub_ports) = req.hub_ports { + current_slot_a |= 1 << 26; + current_slot_b |= (u32::from(hub_ports) << HUB_PORTS_SHIFT) & HUB_PORTS_MASK; + } + + input_context.device.slot.a.write(current_slot_a); + input_context.device.slot.b.write(current_slot_b); + + let control = if self.op.lock().unwrap().cie() { + (u32::from(req.alternate_setting.unwrap_or(0)) << 16) + | (u32::from(req.interface_desc.unwrap_or(0)) << 8) + | u32::from(configuration_value) + } else { + 0 + }; + input_context.control.write(control); + } + + for endp_idx in 0..endp_desc_count as u8 { + let endp_num = endp_idx + 1; + + let mut port_state = self.port_states.get_mut(&port).ok_or(Error::new(EBADFD))?; + let dev_desc = port_state.dev_desc.as_ref().unwrap(); + let endp_desc = port_state.get_endp_desc(endp_idx).ok_or_else(|| { + warn!("failed to find endpoint {}", endp_idx); + Error::new(EIO) + })?; + + let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc); + + let usb_log_max_streams = endp_desc.log_max_streams(); + + // TODO: Secondary streams. + let primary_streams = if let Some(log_max_streams) = usb_log_max_streams { + // TODO: Can streams-capable be configured to not use streams? + if log_max_psa_size != 0 { + cmp::min(u8::from(log_max_streams), log_max_psa_size + 1) - 1 + } else { + 0 + } + } else { + 0 + }; + let linear_stream_array = if primary_streams != 0 { true } else { false }; + + // TODO: Interval related fields + // TODO: Max ESIT payload size. + + let mult = endp_desc.isoch_mult(lec); + + let max_packet_size = Self::endp_ctx_max_packet_size(endp_desc); + let max_burst_size = Self::endp_ctx_max_burst(speed_id, dev_desc, endp_desc); + + let max_esit_payload = Self::endp_ctx_max_esit_payload( + speed_id, + dev_desc, + endp_desc, + max_packet_size, + max_burst_size, + ); + let max_esit_payload_lo = max_esit_payload as u16; + let max_esit_payload_hi = ((max_esit_payload & 0x00FF_0000) >> 16) as u8; + + let interval = Self::endp_ctx_interval(speed_id, endp_desc); + + let max_error_count = 3; + let ep_ty = endp_desc.xhci_ep_type()?; + let host_initiate_disable = false; + + // TODO: Maybe this value is out of scope for xhcid, because the actual usb device + // driver probably knows better. The spec says that the initial value should be 8 bytes + // for control, 1KiB for interrupt and 3KiB for bulk and isoch. + let avg_trb_len: u16 = match endp_desc.ty() { + EndpointTy::Ctrl => { + warn!("trying to use control endpoint"); + return Err(Error::new(EIO)); // only endpoint zero is of type control, and is configured separately with the address device command. + } + EndpointTy::Bulk | EndpointTy::Isoch => 3072, // 3 KiB + EndpointTy::Interrupt => 1024, // 1 KiB + }; + + assert_eq!(ep_ty & 0x7, ep_ty); + assert_eq!(mult & 0x3, mult); + assert_eq!(max_error_count & 0x3, max_error_count); + assert_ne!(ep_ty, 0); // 0 means invalid. + + let ring_ptr = if usb_log_max_streams.is_some() { + let mut array = + StreamContextArray::new::(self.cap.ac64(), 1 << (primary_streams + 1))?; + + // TODO: Use as many stream rings as needed. + array.add_ring::(self.cap.ac64(), 1, true)?; + let array_ptr = array.register(); + + assert_eq!( + array_ptr & 0xFFFF_FFFF_FFFF_FF81, + array_ptr, + "stream ctx ptr not aligned to 16 bytes" + ); + port_state.endpoint_states.insert( + endp_num, + EndpointState { + transfer: super::RingOrStreams::Streams(array), + driver_if_state: EndpIfState::Init, + }, + ); + + array_ptr + } else { + let ring = Ring::new::(self.cap.ac64(), 16, true)?; + let ring_ptr = ring.register(); + + assert_eq!( + ring_ptr & 0xFFFF_FFFF_FFFF_FF81, + ring_ptr, + "ring pointer not aligned to 16 bytes" + ); + port_state.endpoint_states.insert( + endp_num, + EndpointState { + transfer: super::RingOrStreams::Ring(ring), + driver_if_state: EndpIfState::Init, + }, + ); + ring_ptr + }; + assert_eq!(primary_streams & 0x1F, primary_streams); + + let mut input_context = port_state.input_context.lock().unwrap(); + input_context.add_context.writef(1 << endp_num_xhc, true); + + let endp_i = endp_num_xhc as usize - 1; + input_context.device.endpoints[endp_i].a.write( + u32::from(mult) << 8 + | u32::from(primary_streams) << 10 + | u32::from(linear_stream_array) << 15 + | u32::from(interval) << 16 + | u32::from(max_esit_payload_hi) << 24, + ); + input_context.device.endpoints[endp_i].b.write( + max_error_count << 1 + | u32::from(ep_ty) << 3 + | u32::from(host_initiate_disable) << 7 + | u32::from(max_burst_size) << 8 + | u32::from(max_packet_size) << 16, + ); + + input_context.device.endpoints[endp_i] + .trl + .write(ring_ptr as u32); + input_context.device.endpoints[endp_i] + .trh + .write((ring_ptr >> 32) as u32); + + input_context.device.endpoints[endp_i] + .c + .write(u32::from(avg_trb_len) | (u32::from(max_esit_payload_lo) << 16)); + + log::info!("initialized endpoint {}", endp_num); + } + + { + let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; + let slot = port_state.slot; + let input_context_physical = port_state.input_context.lock().unwrap().physical(); + + let (event_trb, command_trb) = self + .execute_command(|trb, cycle| { + trb.configure_endpoint(slot, input_context_physical, cycle) + }) + .await; + + //self.event_handler_finished(); + + handle_event_trb("CONFIGURE_ENDPOINT", &event_trb, &command_trb)?; + } + + // Tell the device about this configuration. + self.set_configuration(port, configuration_value).await?; + + Ok(()) + } + + async fn configure_endpoints(&self, port: PortId, json_buf: &[u8]) -> Result<()> { + let mut req: ConfigureEndpointsReq = + serde_json::from_slice(json_buf).or(Err(Error::new(EBADMSG)))?; + + info!( + "Running configure endpoints command, at port {}, request: {:?}", + port, req + ); + + if req.interface_desc.is_some() != req.alternate_setting.is_some() { + return Err(Error::new(EBADMSG)); + } + + let already_configured = { + let port_state = self.port_states.get(&port).ok_or(Error::new(EBADFD))?; + port_state.cfg_idx == Some(req.config_desc) + }; + + if !already_configured { + self.configure_endpoints_once(port, &req).await?; + } + + if let Some(interface_num) = req.interface_desc { + if let Some(alternate_setting) = req.alternate_setting { + self.set_interface(port, interface_num, alternate_setting) + .await?; + } + } + + Ok(()) + } + async fn transfer_read( + &self, + port_num: PortId, + endp_idx: u8, + buf: &mut [u8], + ) -> Result<(u8, u32)> { + if buf.is_empty() { + return Err(Error::new(EINVAL)); + } + let dma_buffer = unsafe { self.alloc_dma_zeroed_unsized(buf.len())? }; + + let (completion_code, bytes_transferred, dma_buffer) = self + .transfer( + port_num, + endp_idx, + Some(dma_buffer), + PortReqDirection::DeviceToHost, + ) + .await?; + + buf.copy_from_slice(&*dma_buffer.as_ref().unwrap()); + Ok((completion_code, bytes_transferred)) + } + async fn transfer_write( + &self, + port_num: PortId, + endp_idx: u8, + sbuf: &[u8], + ) -> Result<(u8, u32)> { + if sbuf.is_empty() { + return Err(Error::new(EINVAL)); + } + let mut dma_buffer = unsafe { self.alloc_dma_zeroed_unsized(sbuf.len()) }?; + dma_buffer.copy_from_slice(sbuf); + + trace!( + "TRANSFER_WRITE port {} ep {}, buffer at {:p}, size {}, dma buffer {:?}", + port_num, + endp_idx + 1, + sbuf.as_ptr(), + sbuf.len(), + DmaSliceDbg(&dma_buffer) + ); + + let (completion_code, bytes_transferred, _) = self + .transfer( + port_num, + endp_idx, + Some(dma_buffer), + PortReqDirection::HostToDevice, + ) + .await?; + Ok((completion_code, bytes_transferred)) + } + pub const fn def_control_endp_doorbell() -> u32 { + 1 + } + // TODO: Wrap DCIs and driver-level endp_num into distinct types, due to the high chance of + // mixing the two up. + fn endp_num_to_dci(endp_num: u8, desc: &EndpDesc) -> u8 { + if endp_num == 0 { + unreachable!("EndpDesc cannot be obtained from the default control endpoint") + } + + if desc.is_control() || desc.direction() == EndpDirection::In { + endp_num * 2 + 1 + } else if desc.direction() == EndpDirection::Out { + endp_num * 2 + } else { + unreachable!() + } + } + fn endp_doorbell(endp_num: u8, desc: &EndpDesc, stream_id: u16) -> u32 { + let db_target = Self::endp_num_to_dci(endp_num, desc); + let db_task_id: u16 = stream_id; + + (u32::from(db_task_id) << 16) | u32::from(db_target) + } + // TODO: Rename DeviceReqData to something more general. + async fn transfer( + &self, + port_num: PortId, + endp_idx: u8, + dma_buf: Option>, + direction: PortReqDirection, + ) -> Result<(u8, u32, Option>)> { + // TODO: Check that only readable enpoints are read, etc. + let endp_num = endp_idx + 1; + + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADFD))?; + + let endp_desc: &EndpDesc = port_state + .get_endp_desc(endp_idx) + .ok_or(Error::new(EBADFD))?; + + let direction = endp_desc.direction(); + + if endp_desc.is_isoch() { + return Err(Error::new(ENOSYS)); + } + + if EndpDirection::from(direction) != endp_desc.direction() { + return Err(Error::new(EBADF)); + } + + let max_packet_size = endp_desc.max_packet_size; + let max_transfer_size = 65536u32; + + let (buffer, idt, estimated_td_size) = { + let (buffer, idt) = if dma_buf.as_ref().map(|buf| buf.len()).unwrap_or(0) <= 8 + && max_packet_size >= 8 + && direction != EndpDirection::In + { + dma_buf + .as_ref() + .map(|sbuf| { + let mut bytes = [0u8; 8]; + bytes[..sbuf.len()].copy_from_slice(&sbuf); + (u64::from_le_bytes(bytes), true) + }) + .unwrap_or((0, false)) + } else { + ( + dma_buf.as_ref().map(|dma| dma.physical()).unwrap_or(0) as u64, + false, + ) + }; + let estimated_td_size = cmp::min( + u8::try_from( + div_round_up( + dma_buf.as_ref().map(|buf| buf.len()).unwrap_or(0), + max_transfer_size as usize, + ) * mem::size_of::(), + ) + .ok() + .unwrap_or(0x1F), + 0x1F, + ); // one trb per td + (buffer, idt, estimated_td_size) + }; + + let stream_id = 1u16; + + let mut bytes_left = dma_buf.as_ref().map(|buf| buf.len()).unwrap_or(0); + + drop(port_state); + + let event = self + .execute_transfer( + port_num, + endp_num, + stream_id, + "CUSTOM_TRANSFER", + |trb, cycle| { + let len = cmp::min(bytes_left, max_transfer_size as usize) as u32; + + // set the interrupt on completion (IOC) flag for the last trb. + let ioc = bytes_left <= max_transfer_size as usize; + let chain = !ioc; + + let interrupter = 0; + let ent = false; + let isp = true; + let bei = false; + trb.normal( + buffer, + len, + cycle, + estimated_td_size, + interrupter, + ent, + isp, + chain, + ioc, + idt, + bei, + ); + + bytes_left -= len as usize; + + if bytes_left != 0 { + ControlFlow::Continue + } else { + ControlFlow::Break + } + }, + ) + .await?; + //self.event_handler_finished(); + + let bytes_transferred = dma_buf + .as_ref() + .map(|buf| buf.len() as u32 - event.transfer_length()) + .unwrap_or(0); + + Ok((event.completion_code(), bytes_transferred, dma_buf)) + } + pub async fn get_desc(&self, port_id: PortId, slot: u8) -> Result { + let ports = self.ports.lock().unwrap(); + let port = ports + .get(port_id.root_hub_port_index()) + .ok_or(Error::new(ENOENT))?; + if !port.flags().contains(port::PortFlags::CCS) { + return Err(Error::new(ENOENT)); + } + + let raw_dd = self.fetch_dev_desc(port_id, slot).await?; + log::debug!("port {} slot {} desc {:X?}", port_id, slot, raw_dd); + + // Only fetch language IDs if we need to. Some devices will fail to return this descriptor + //TODO: also check configurations and interfaces for defined strings? + let lang_id = + if raw_dd.manufacturer_str > 0 || raw_dd.product_str > 0 || raw_dd.serial_str > 0 { + let lang_ids = self.fetch_lang_ids_desc(port_id, slot).await?; + // Prefer US English, but fall back to first language ID, or zero + let en_us_id = 0x409; + if lang_ids.contains(&en_us_id) { + en_us_id + } else { + match lang_ids.first() { + Some(some) => *some, + None => 0, + } + } + } else { + 0 + }; + log::debug!("port {} using language ID 0x{:04x}", port_id, lang_id); + + let (manufacturer_str, product_str, serial_str) = ( + if raw_dd.manufacturer_str > 0 { + Some( + self.fetch_string_desc(port_id, slot, raw_dd.manufacturer_str, lang_id) + .await?, + ) + } else { + None + }, + if raw_dd.product_str > 0 { + Some( + self.fetch_string_desc(port_id, slot, raw_dd.product_str, lang_id) + .await?, + ) + } else { + None + }, + if raw_dd.serial_str > 0 { + Some( + self.fetch_string_desc(port_id, slot, raw_dd.serial_str, lang_id) + .await?, + ) + } else { + None + }, + ); + log::debug!( + "manufacturer {:?} product {:?} serial {:?}", + manufacturer_str, + product_str, + serial_str + ); + + //TODO let (bos_desc, bos_data) = self.fetch_bos_desc(port_id, slot).await?; + + let supports_superspeed = false; + //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeed()); + let supports_superspeedplus = false; + //TODO usb::bos_capability_descs(bos_desc, &bos_data).any(|desc| desc.is_superspeedplus()); + + let mut config_descs = SmallVec::new(); + + for index in 0..raw_dd.configurations { + debug!("Fetching the config descriptor at index {}", index); + let (desc, data) = self.fetch_config_desc(port_id, slot, index).await?; + log::debug!( + "port {} slot {} config {} desc {:X?}", + port_id, + slot, + index, + desc + ); + + let extra_length = desc.total_length as usize - mem::size_of_val(&desc); + let data = &data[..extra_length]; + + let mut i = 0; + let mut descriptors = Vec::new(); + + while let Some((descriptor, len)) = AnyDescriptor::parse(&data[i..]) { + descriptors.push(descriptor); + i += len; + } + + let mut interface_descs = SmallVec::new(); + let mut iter = descriptors.into_iter().peekable(); + + while let Some(item) = iter.next() { + if let AnyDescriptor::Interface(idesc) = item { + let mut endpoints = SmallVec::<[EndpDesc; 4]>::new(); + let mut hid_descs = SmallVec::<[HidDesc; 1]>::new(); + + while endpoints.len() < idesc.endpoints as usize { + let next = match iter.next() { + Some(AnyDescriptor::Endpoint(n)) => n, + Some(AnyDescriptor::Hid(h)) if idesc.class == 3 => { + hid_descs.push(h.into()); + continue; + } + Some(unexpected) => { + log::warn!("expected endpoint, got {:X?}", unexpected); + break; + } + None => break, + }; + let mut endp = EndpDesc::from(next); + + loop { + match iter.peek() { + Some(AnyDescriptor::SuperSpeedCompanion(n)) => { + endp.ssc = Some(SuperSpeedCmp::from(n.clone())); + iter.next().unwrap(); + } + Some(AnyDescriptor::SuperSpeedPlusCompanion(n)) => { + endp.sspc = Some(SuperSpeedPlusIsochCmp::from(n.clone())); + iter.next().unwrap(); + } + _ => break, + } + } + + endpoints.push(endp); + } + + interface_descs.push( + self.new_if_desc(port_id, slot, idesc, endpoints, hid_descs, lang_id) + .await?, + ); + } else { + log::warn!("expected interface, got {:?}", item); + // TODO + //break; + } + } + + config_descs.push(ConfDesc { + kind: desc.kind, + configuration: if desc.configuration_str > 0 { + Some( + self.fetch_string_desc(port_id, slot, desc.configuration_str, lang_id) + .await?, + ) + } else { + None + }, + configuration_value: desc.configuration_value, + attributes: desc.attributes, + max_power: desc.max_power, + interface_descs, + }); + } + + Ok(DevDesc { + kind: raw_dd.kind, + usb: raw_dd.usb, + class: raw_dd.class, + sub_class: raw_dd.sub_class, + protocol: raw_dd.protocol, + packet_size: raw_dd.packet_size, + vendor: raw_dd.vendor, + product: raw_dd.product, + release: raw_dd.release, + manufacturer_str, + product_str, + serial_str, + config_descs, + }) + } + fn port_desc_json(&self, port_id: PortId) -> Result> { + let dev_desc = &self + .port_states + .get(&port_id) + .ok_or(Error::new(ENOENT))? + .dev_desc; + serde_json::to_vec(dev_desc).or(Err(Error::new(EIO))) + } + fn write_dyn_string(string: &[u8], buf: &mut [u8], offset: usize) -> usize { + let max_bytes_to_read = cmp::min(string.len(), buf.len()); + let bytes_to_read = cmp::max(offset, max_bytes_to_read) - offset; + buf[..bytes_to_read].copy_from_slice(&string[..bytes_to_read]); + + bytes_to_read + } + async fn port_req_transfer( + &self, + port_num: PortId, + data_buffer: Option<&mut Dma<[u8]>>, + setup: usb::Setup, + transfer_kind: TransferKind, + ) -> Result<()> { + self.execute_control_transfer( + port_num, + setup, + transfer_kind, + "CUSTOM_DEVICE_REQ", + |trb, cycle| { + trb.data( + data_buffer.as_ref().map(|dma| dma.physical()).unwrap_or(0), + setup.length, + transfer_kind == TransferKind::In, + cycle, + ); + ControlFlow::Break + }, + ) + .await?; + Ok(()) + } + fn port_req_init_st(&self, port_num: PortId, req: &PortReq) -> Result { + use usb::setup::*; + + let direction = ReqDirection::from(req.direction); + let ty = ReqType::from(req.req_type) as u8; + let recipient = ReqRecipient::from(req.req_recipient) as u8; + + let transfer_kind = match direction { + _ if !req.transfers_data => TransferKind::NoData, + ReqDirection::DeviceToHost => TransferKind::In, + ReqDirection::HostToDevice => TransferKind::Out, + }; + + let setup = Setup { + kind: ((direction as u8) << USB_SETUP_DIR_SHIFT) + | (ty << USB_SETUP_REQ_TY_SHIFT) + | (recipient << USB_SETUP_RECIPIENT_SHIFT), + request: req.request, + value: req.value, + index: req.index, + length: req.length, + }; + // TODO: Reuse buffers, or something. + // TODO: Validate the size. + // TODO: Sizes above 65536, *perhaps*. + let data_buffer_opt = if req.transfers_data { + let data_buffer = unsafe { self.alloc_dma_zeroed_unsized(req.length as usize)? }; + assert_eq!(data_buffer.len(), req.length as usize); + Some(data_buffer) + } else { + None + }; + + Ok(match transfer_kind { + TransferKind::In => PortReqState::WaitingForDeviceBytes( + data_buffer_opt.ok_or(Error::new(EINVAL))?, + setup, + ), + TransferKind::Out => { + PortReqState::WaitingForHostBytes(data_buffer_opt.ok_or(Error::new(EINVAL))?, setup) + } + TransferKind::NoData => PortReqState::TmpSetup(setup), + _ => unreachable!(), + }) + // FIXME: Make sure there aren't any other PortReq handles, perhaps by storing the state in + // PortState? + } + async fn handle_port_req_write( + &self, + fd: usize, + port_num: PortId, + mut st: PortReqState, + buf: &[u8], + ) -> Result { + let bytes_written = match st { + PortReqState::Init => { + let req = serde_json::from_slice::(buf).or(Err(Error::new(EBADMSG)))?; + + st = self.port_req_init_st(port_num, &req)?; + + if let PortReqState::TmpSetup(setup) = st { + // No need for any additional reads or writes, before completing. + self.port_req_transfer(port_num, None, setup, TransferKind::NoData) + .await?; + st = PortReqState::Init; + } + + buf.len() + } + PortReqState::WaitingForHostBytes(mut dma_buffer, setup) => { + if buf.len() != dma_buffer.len() { + return Err(Error::new(EINVAL)); + } + dma_buffer.copy_from_slice(buf); + + self.port_req_transfer(port_num, Some(&mut dma_buffer), setup, TransferKind::Out) + .await?; + st = PortReqState::Init; + + buf.len() + } + PortReqState::WaitingForDeviceBytes(_, _) => return Err(Error::new(EBADF)), + PortReqState::Tmp | PortReqState::TmpSetup(_) => unreachable!(), + }; + let mut guard = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; + match &mut *guard { + Handle::PortReq(_, ref mut state) => *state = st, + _ => unreachable!(), + } + Ok(bytes_written) + } + async fn handle_port_req_read( + &self, + fd: usize, + port_num: PortId, + mut st: PortReqState, + buf: &mut [u8], + ) -> Result { + let bytes_read = match st { + PortReqState::WaitingForDeviceBytes(mut dma_buffer, setup) => { + if buf.len() != dma_buffer.len() { + return Err(Error::new(EINVAL)); + } + self.port_req_transfer(port_num, Some(&mut dma_buffer), setup, TransferKind::In) + .await?; + buf.copy_from_slice(&dma_buffer); + + st = PortReqState::Init; + + buf.len() + } + PortReqState::Init | PortReqState::WaitingForHostBytes(_, _) => { + return Err(Error::new(EBADF)) + } + PortReqState::Tmp | PortReqState::TmpSetup(_) => unreachable!(), + }; + + let mut guard = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; + match &mut *guard { + Handle::PortReq(_, ref mut state) => *state = st, + _ => unreachable!(), + } + Ok(bytes_read) + } + + /// Implements open() for the root level scheme + /// + /// # Arguments + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either + /// + /// - Handle::TopLevel - The file was opened. + /// - EISDIR - This is a directory endpoint, but neither O_DIRECTORY nor O_STAT were passed. + /// + fn open_handle_top_level(&self, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 || flags & O_STAT != 0 { + let mut contents = Vec::new(); + + let ports_guard = self.ports.lock().unwrap(); + + for (index, _) in ports_guard + .iter() + .enumerate() + .filter(|(_, port)| port.flags().contains(port::PortFlags::CCS)) + { + write!(contents, "port{}\n", index).unwrap(); + } + + Ok(Handle::TopLevel(contents)) + } else { + Err(Error::new(EISDIR)) + } + } + + /// implements open() for /port/descriptors + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::PortDesc] - The handle was opened successfully + /// - [ENOTDIR] - Directory-specific flags were passed to open(), but this endpoint is not a directory. + fn open_handle_port_descriptors(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + let contents = self.port_desc_json(port_num)?; + Ok(Handle::PortDesc(port_num, contents)) + } + + /// implements open() for /port + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + fn open_handle_port(&self, port_num: PortId, flags: usize) -> Result { + // The != here is unintuitive. You would assume that you could do + // flags & O_DIRECTORY || flags & O_STAT, but rust doesn't allow + // you to cast integers to booleans. + if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { + let mut contents = Vec::new(); + + write!(contents, "descriptors\nendpoints\n").unwrap(); + + if self.slot_state( + self.port_states + .get(&port_num) + .ok_or(Error::new(ENOENT))? + .slot as usize, + ) != SlotState::Configured as u8 + { + write!(contents, "configure\n").unwrap(); + } + + Ok(Handle::Port(port_num, contents)) + } else { + Err(Error::new(EISDIR)) + } + } + + /// implements open() for /port/state + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [ENOTDIR] - open() was called on this scheme endpoint, but directory-specific flags were passed to open + fn open_handle_port_state(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + Ok(Handle::PortState(port_num)) + } + + /// implements open() for /port/endpoints + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + fn open_handle_port_endpoints(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY == 0 && flags & O_STAT == 0 { + return Err(Error::new(EISDIR)); + }; + let mut contents = Vec::new(); + let ps = self.port_states.get(&port_num).ok_or(Error::new(ENOENT))?; + + /*for (ep_num, _) in self.dev_ctx.contexts[ps.slot as usize].endpoints.iter().enumerate().filter(|(_, ep)| ep.a.read() & 0b111 == 1) { + write!(contents, "{}\n", ep_num).unwrap(); + }*/ + + for ep_num in ps.endpoint_states.keys() { + write!(contents, "{}\n", ep_num).unwrap(); + } + + Ok(Handle::Endpoints(port_num, contents)) + } + + /// implements open() for /port/endpoints/ + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'endpoint_num: [u8]' - The endpoint number to access + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + /// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num + fn open_handle_endpoint_root( + &self, + port_num: PortId, + endpoint_num: u8, + flags: usize, + ) -> Result { + if flags & O_DIRECTORY == 0 && flags & O_STAT == 0 { + return Err(Error::new(EISDIR)); + } + + let port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(ENOENT))?; + + /*if self.dev_ctx.contexts[port_state.slot as usize].endpoints.get(endpoint_num as usize).ok_or(Error::new(ENOENT))?.a.read() & 0b111 != 1 { + return Err(Error::new(ENXIO)); // TODO: Find a proper error code for "endpoint not initialized". + }*/ + + if !port_state.endpoint_states.contains_key(&endpoint_num) { + return Err(Error::new(ENOENT)); + } + let contents = "ctl\ndata\n".as_bytes().to_owned(); + + Ok(Handle::Endpoint( + port_num, + endpoint_num, + EndpointHandleTy::Root(contents), + )) + } + + /// implements open() for /port/endpoints//data and /port/endpoints//ctl + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'endpoint_num: [u8]' - The endpoint number to access + /// - 'handle_type: [String]' - The type of the handle + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + /// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num, or no endpoint with the given endpoint_num + fn open_handle_single_endpoint( + &self, + port_num: PortId, + endpoint_num: u8, + handle_type: String, + flags: usize, + ) -> Result { + match handle_type.as_str() { + "root" => self.open_handle_endpoint_root(port_num, endpoint_num, flags), + "ctl" | "data" => { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(EISDIR)); + } + + let port_state = self.port_states.get(&port_num).ok_or(Error::new(ENOENT))?; + + if port_state.endpoint_states.get(&endpoint_num).is_none() { + return Err(Error::new(ENOENT)); + } + + let st = match handle_type.as_str() { + "ctl" => EndpointHandleTy::Ctl, + "data" => EndpointHandleTy::Data, + _ => return Err(Error::new(ENOENT)), + }; + Ok(Handle::Endpoint(port_num, endpoint_num, st)) + } + _ => panic!( + "Scheme parser returned an invalid string '{}' for the endpoint handle type", + handle_type + ), + } + } + + /// implements open() for /port/configure + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'endpoint_num: [u8]' - The endpoint number to access + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + /// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num, or no endpoint with the given endpoint_num + fn open_handle_configure_endpoints(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { + return Err(Error::new(EACCES)); + } + + Ok(Handle::ConfigureEndpoints(port_num)) + } + + /// implements open() for /port/attach + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + /// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num, or no endpoint with the given endpoint_num + fn open_handle_attach_device(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { + return Err(Error::new(EACCES)); + } + + Ok(Handle::AttachDevice(port_num)) + } + + /// implements open() for /port/detach + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [EISDIR] - open() was called on this scheme endpoint, but no directory-specific flags were passed to open + /// - [ENOENT] - The scheme is valid, but there is no port associated with the given port_num, or no endpoint with the given endpoint_num + fn open_handle_detach_device(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + if flags & O_RDWR != O_WRONLY && flags & O_STAT == 0 { + return Err(Error::new(EACCES)); + } + + Ok(Handle::DetachDevice(port_num)) + } + + /// implements open() for /port/request + /// + /// # Arguments + /// - 'port_num: [PortId]' - The port number specified in the scheme path + /// - 'flags: [usize]' - The flags parameter passed to open() + /// + /// # Returns + /// This function returns a [Result] containing either: + /// + /// - [Handle::Port] - The handle was opened successfully + /// - [ENOTDIR] - open() was called on this scheme endpoint, but directory-specific flags were passed to open + fn open_handle_port_request(&self, port_num: PortId, flags: usize) -> Result { + if flags & O_DIRECTORY != 0 && flags & O_STAT == 0 { + return Err(Error::new(ENOTDIR)); + } + + Ok(Handle::PortReq(port_num, PortReqState::Init)) + } +} + +impl SchemeSync for &Xhci { + fn open(&mut self, path_str: &str, flags: usize, ctx: &CallerCtx) -> Result { + if ctx.uid != 0 { + return Err(Error::new(EACCES)); + } + + //Parse the scheme, determine if it's in the valid format, return an error if not. + //This doesn't guarantee that the parameters themselves are valid (i.e. bounded correctly) + //only that the scheme itself was parseable. + let scheme_parameters = SchemeParameters::from_scheme(path_str)?; + + //Once we have our scheme parsed into parameters, we can match on those parameters to + //find the correct routine to open a handle + let handle = match scheme_parameters { + SchemeParameters::TopLevel => self.open_handle_top_level(flags)?, + SchemeParameters::Port(port_number) => self.open_handle_port(port_number, flags)?, + SchemeParameters::PortDesc(port_number) => { + self.open_handle_port_descriptors(port_number, flags)? + } + SchemeParameters::PortState(port_number) => { + self.open_handle_port_state(port_number, flags)? + } + SchemeParameters::PortReq(port_number) => { + self.open_handle_port_request(port_number, flags)? + } + SchemeParameters::Endpoints(port_number) => { + self.open_handle_port_endpoints(port_number, flags)? + } + SchemeParameters::Endpoint(port_number, endpoint_number, handle_type) => { + self.open_handle_single_endpoint(port_number, endpoint_number, handle_type, flags)? + } + SchemeParameters::ConfigureEndpoints(port_number) => { + self.open_handle_configure_endpoints(port_number, flags)? + } + SchemeParameters::AttachDevice(port_number) => { + self.open_handle_attach_device(port_number, flags)? + } + SchemeParameters::DetachDevice(port_number) => { + self.open_handle_detach_device(port_number, flags)? + } + }; + + let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); + + trace!("OPENED {} to FD={}, handle: {:?}", path_str, fd, handle); + + self.handles.insert(fd, handle); + + Ok(OpenResult::ThisScheme { + number: fd, + flags: NewFdFlags::POSITIONED, + }) + } + + fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { + let guard = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + stat.st_mode = match (&*guard).get_handle_type() { + HandleType::Directory => MODE_DIR, + HandleType::File => MODE_FILE, + HandleType::Character => MODE_CHR, + }; + + stat.st_size = match (&*guard).get_buf_len() { + None => stat.st_size, + Some(size) => size as u64, + }; + + //If we have a handle to the configure scheme, we need to mark it as write only. + match &*guard { + Handle::ConfigureEndpoints(_) | Handle::AttachDevice(_) | Handle::DetachDevice(_) => { + stat.st_mode = stat.st_mode | 0o200; + } + _ => {} + } + + Ok(()) + } + + fn fpath(&mut self, fd: usize, buffer: &mut [u8], _ctx: &CallerCtx) -> Result { + let mut cursor = io::Cursor::new(buffer); + + let guard = self.handles.get(&fd).ok_or(Error::new(EBADF))?; + let scheme = (&*guard).to_scheme(); + + write!(cursor, "{}", scheme.as_str()).expect( + format!( + "Failed to convert the file descriptor with value {} to the associated file path", + fd + ) + .as_str(), + ); + + let src_len = usize::try_from(cursor.seek(io::SeekFrom::End(0)).unwrap()).unwrap(); + Ok(src_len) + } + + fn read( + &mut self, + fd: usize, + buf: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let offset = offset as usize; + let mut guard = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; + trace!( + "READ fd={}, handle={:?}, buf=(addr {:p}, length {})", + fd, + guard, + buf.as_ptr(), + buf.len() + ); + match &mut *guard { + Handle::TopLevel(ref src_buf) + | Handle::Port(_, ref src_buf) + | Handle::PortDesc(_, ref src_buf) + | Handle::Endpoints(_, ref src_buf) + | Handle::Endpoint(_, _, EndpointHandleTy::Root(ref src_buf)) => { + let max_bytes_to_read = cmp::min(src_buf.len(), buf.len()); + let bytes_to_read = cmp::max(max_bytes_to_read, offset) - offset; + + buf[..bytes_to_read].copy_from_slice(&src_buf[..bytes_to_read]); + + Ok(bytes_to_read) + } + Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), + Handle::AttachDevice(_) => Err(Error::new(EBADF)), + Handle::DetachDevice(_) => Err(Error::new(EBADF)), + + &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { + EndpointHandleTy::Ctl => self.on_read_endp_ctl(port_num, endp_num, buf), + EndpointHandleTy::Data => block_on(self.on_read_endp_data(port_num, endp_num, buf)), + EndpointHandleTy::Root(_) => Err(Error::new(EBADF)), + }, + &mut Handle::PortState(port_num) => { + let ps = self.port_states.get(&port_num).ok_or(Error::new(EBADF))?; + let ctx = self + .dev_ctx + .contexts + .get(ps.slot as usize) + .ok_or(Error::new(EBADF))?; + let state = ((ctx.slot.d.read() & SLOT_CONTEXT_STATE_MASK) + >> SLOT_CONTEXT_STATE_SHIFT) as u8; + + let string = match state { + 0 => Some(PortState::EnabledOrDisabled), + 1 => Some(PortState::Default), + 2 => Some(PortState::Addressed), + 3 => Some(PortState::Configured), + _ => None, + } + .as_ref() + .map(PortState::as_str) + .unwrap_or("unknown") + .as_bytes(); + + Ok(Xhci::::write_dyn_string(string, buf, offset)) + } + &mut Handle::PortReq(port_num, ref mut st) => { + let state = std::mem::replace(st, PortReqState::Tmp); + drop(guard); // release the lock + block_on(self.handle_port_req_read(fd, port_num, state, buf)) + } + } + } + fn write( + &mut self, + fd: usize, + buf: &[u8], + _offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let mut guard = self.handles.get_mut(&fd).ok_or(Error::new(EBADF))?; + trace!( + "WRITE fd={}, handle={:?}, buf=(addr {:p}, length {})", + fd, + guard, + buf.as_ptr(), + buf.len() + ); + + match &mut *guard { + &mut Handle::ConfigureEndpoints(port_num) => { + block_on(self.configure_endpoints(port_num, buf))?; + Ok(buf.len()) + } + &mut Handle::AttachDevice(port_num) => { + //TODO: accept some arguments in buffer? + block_on(self.attach_device(port_num))?; + Ok(buf.len()) + } + &mut Handle::DetachDevice(port_num) => { + //TODO: accept some arguments in buffer? + block_on(self.detach_device(port_num))?; + Ok(buf.len()) + } + &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { + EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), + EndpointHandleTy::Data => { + block_on(self.on_write_endp_data(port_num, endp_num, buf)) + } + EndpointHandleTy::Root(_) => return Err(Error::new(EBADF)), + }, + &mut Handle::PortReq(port_num, ref mut st) => { + let state = std::mem::replace(st, PortReqState::Tmp); + drop(guard); // release the lock + block_on(self.handle_port_req_write(fd, port_num, state, buf)) + } + // TODO: Introduce PortReqState::Waiting, which this write call changes to + // PortReqState::ReadyToWrite when all bytes are written. + _ => Err(Error::new(EBADF)), + } + } +} +impl Xhci { + pub fn on_close(&self, fd: usize) { + self.handles.remove(&fd); + } + + pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + + let slot = port_state.slot; + + let endp_desc = port_state + .dev_desc + .as_ref() + .unwrap() + .config_descs + .get(0) + .ok_or(Error::new(EIO))? + .interface_descs + .get(0) + .ok_or(Error::new(EIO))? + .endpoints + .get(endp_num as usize - 1) + .ok_or(Error::new(EBADFD))?; + + let endp_num_xhc = if endp_num != 0 { + Self::endp_num_to_dci(endp_num, endp_desc) + } else { + 1 + }; + + let raw = self + .dev_ctx + .contexts + .get(slot as usize) + .ok_or(Error::new(EBADFD))? + .endpoints[endp_num_xhc as usize - 1] + .a + .read() + & super::context::ENDPOINT_CONTEXT_STATUS_MASK; + + Ok(match raw { + 0 => EndpointStatus::Disabled, + 1 => EndpointStatus::Enabled, + 2 => EndpointStatus::Halted, + 3 => EndpointStatus::Stopped, + 4 => EndpointStatus::Error, + _ => return Err(Error::new(EIO)), + }) + } + pub async fn on_req_reset_device( + &self, + port_num: PortId, + endp_num: u8, + clear_feature: bool, + ) -> Result<()> { + if self.get_endp_status(port_num, endp_num)? != EndpointStatus::Halted { + return Err(Error::new(EPROTO)); + } + // Change the endpoint state from anything, but most likely HALTED (otherwise resetting + // would be quite meaningless), to stopped. + self.reset_endpoint(port_num, endp_num, false).await?; + self.restart_endpoint(port_num, endp_num).await?; + + if clear_feature { + self.device_req_no_data( + port_num, + usb::Setup { + kind: 0b0000_0010, // endpoint recipient + request: 0x01, // CLEAR_FEATURE + value: 0x00, // ENDPOINT_HALT + index: 0, // TODO: interface num + length: 0, + }, + ) + .await?; + } + Ok(()) + } + pub async fn restart_endpoint(&self, port_num: PortId, endp_num: u8) -> Result<()> { + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADFD))?; + let slot = port_state.slot; + + let mut endpoint_state = port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADFD))?; + + let (has_streams, ring) = match &mut endpoint_state.transfer { + &mut super::RingOrStreams::Ring(ref mut ring) => (false, ring), + &mut super::RingOrStreams::Streams(ref mut arr) => { + (true, arr.rings.get_mut(&1).ok_or(Error::new(EBADFD))?) + } + }; + + let (cmd, cycle) = ring.next(); + cmd.transfer_no_op(0, false, false, false, cycle); + + let deque_ptr_and_cycle = ring.register(); + + let endp_desc = port_state + .dev_desc + .as_ref() + .unwrap() + .config_descs + .get(0) + .ok_or(Error::new(EIO))? + .interface_descs + .get(0) + .ok_or(Error::new(EIO))? + .endpoints + .get(endp_num as usize - 1) + .ok_or(Error::new(EBADFD))?; + + let doorbell = if endp_num != 0 { + let stream_id = 1u16; + + Self::endp_doorbell(endp_num, endp_desc, if has_streams { stream_id } else { 0 }) + } else { + Self::def_control_endp_doorbell() + }; + + self.dbs.lock().unwrap()[slot as usize].write(doorbell); + + self.set_tr_deque_ptr(port_num, endp_num, deque_ptr_and_cycle) + .await?; + + Ok(()) + } + pub fn endp_direction(&self, port_num: PortId, endp_num: u8) -> Result { + Ok(self + .port_states + .get(&port_num) + .ok_or(Error::new(EIO))? + .dev_desc + .as_ref() + .unwrap() + .config_descs + .first() + .ok_or(Error::new(EIO))? + .interface_descs + .first() + .ok_or(Error::new(EIO))? + .endpoints + .get(endp_num as usize) + .ok_or(Error::new(EIO))? + .direction()) + } + pub fn slot(&self, port_num: PortId) -> Result { + Ok(self.port_states.get(&port_num).ok_or(Error::new(EIO))?.slot) + } + pub async fn set_tr_deque_ptr( + &self, + port_num: PortId, + endp_num: u8, + deque_ptr_and_cycle: u64, + ) -> Result<()> { + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + let slot = port_state.slot; + + let endp_desc = port_state + .get_endp_desc(endp_idx) + .ok_or(Error::new(EBADFD))?; + let endp_num_xhc = Self::endp_num_to_dci(endp_num, endp_desc); + + let (event_trb, command_trb) = self + .execute_command(|trb, cycle| { + trb.set_tr_deque_ptr( + deque_ptr_and_cycle, + cycle, + StreamContextType::PrimaryRing, + 1, + endp_num_xhc, + slot, + ) + }) + .await; + //self.event_handler_finished(); + + handle_event_trb("SET_TR_DEQUEUE_PTR", &event_trb, &command_trb) + } + pub async fn on_write_endp_ctl( + &self, + port_num: PortId, + endp_num: u8, + buf: &[u8], + ) -> Result { + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADF))?; + + let ep_if_state = &mut port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADF))? + .driver_if_state; + + let req = serde_json::from_slice::(buf).or(Err(Error::new(EBADMSG)))?; + match req { + XhciEndpCtlReq::Status => match ep_if_state { + state @ EndpIfState::Init => *state = EndpIfState::WaitingForStatus, + other => { + return Err(Error::new(EBADF)); + } + }, + XhciEndpCtlReq::Reset { no_clear_feature } => match ep_if_state { + EndpIfState::Init => { + self.on_req_reset_device(port_num, endp_num, !no_clear_feature) + .await? + } + other => { + return Err(Error::new(EBADF)); + } + }, + XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state { + state @ EndpIfState::Init => { + if direction == XhciEndpCtlDirection::NoData { + // Yield the result directly because no bytes have to be sent or received + // beforehand. + let (completion_code, bytes_transferred, _) = self + .transfer(port_num, endp_num - 1, None, PortReqDirection::DeviceToHost) + .await?; + if bytes_transferred > 0 { + return Err(Error::new(EIO)); + } + let result = Self::transfer_result(completion_code, 0); + + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADF))?; + let new_state = &mut port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADF))? + .driver_if_state; + *new_state = EndpIfState::WaitingForTransferResult(result) + } else { + *state = EndpIfState::WaitingForDataPipe { + direction, + bytes_to_transfer: count, + bytes_transferred: 0, + }; + } + } + other => { + return Err(Error::new(EBADF)); + } + }, + other => { + return Err(Error::new(EBADF)); + } + } + Ok(buf.len()) + } + fn transfer_result(completion_code: u8, bytes_transferred: u32) -> PortTransferStatus { + let kind = if completion_code == TrbCompletionCode::Success as u8 { + PortTransferStatusKind::Success + } else if completion_code == TrbCompletionCode::ShortPacket as u8 { + PortTransferStatusKind::ShortPacket + } else if completion_code == TrbCompletionCode::Stall as u8 { + PortTransferStatusKind::Stalled + } else { + PortTransferStatusKind::Unknown + }; + PortTransferStatus { + kind, + bytes_transferred, + } + } + pub async fn on_write_endp_data( + &self, + port_num: PortId, + endp_num: u8, + buf: &[u8], + ) -> Result { + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADFD))?; + let mut endpoint_state = port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADFD))?; + + let ep_if_state = &mut endpoint_state.driver_if_state; + + match ep_if_state { + &mut EndpIfState::WaitingForDataPipe { + direction: XhciEndpCtlDirection::Out, + bytes_to_transfer: total_bytes_to_transfer, + bytes_transferred, + } => { + if buf.len() > total_bytes_to_transfer as usize - bytes_transferred as usize { + return Err(Error::new(EINVAL)); + } + drop(port_state); + let (completion_code, some_bytes_transferred) = + self.transfer_write(port_num, endp_num - 1, buf).await?; + let result = Self::transfer_result(completion_code, some_bytes_transferred); + + // To avoid having to read from the Ctl interface file, the client should stop + // invoking further data transfer calls if any single transfer returns fewer bytes + // than requested. + + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADFD))?; + let mut endpoint_state = port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADFD))?; + let ep_if_state = &mut endpoint_state.driver_if_state; + + if let &mut EndpIfState::WaitingForDataPipe { + direction: XhciEndpCtlDirection::Out, + bytes_to_transfer, + ref mut bytes_transferred, + } = ep_if_state + { + if *bytes_transferred + some_bytes_transferred == bytes_to_transfer + || completion_code != TrbCompletionCode::Success as u8 + { + *ep_if_state = EndpIfState::WaitingForTransferResult(result); + } else { + *bytes_transferred += some_bytes_transferred; + } + } else { + unreachable!() + } + Ok(some_bytes_transferred as usize) + } + _ => return Err(Error::new(EBADF)), + } + } + pub fn on_read_endp_ctl( + &self, + port_num: PortId, + endp_num: u8, + buf: &mut [u8], + ) -> Result { + let port_state = &mut self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADF))?; + + let ep_if_state = &mut port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADF))? + .driver_if_state; + + let res: XhciEndpCtlRes = match ep_if_state { + &mut EndpIfState::Init => XhciEndpCtlRes::Idle, + + state @ &mut EndpIfState::WaitingForStatus => { + *state = EndpIfState::Init; + XhciEndpCtlRes::Status(self.get_endp_status(port_num, endp_num)?) + } + &mut EndpIfState::WaitingForDataPipe { .. } => XhciEndpCtlRes::Pending, + &mut EndpIfState::WaitingForTransferResult(status) => { + *ep_if_state = EndpIfState::Init; + XhciEndpCtlRes::TransferResult(status) + } + }; + + let mut cursor = io::Cursor::new(buf); + serde_json::to_writer(&mut cursor, &res).or(Err(Error::new(EIO)))?; + Ok(cursor.seek(io::SeekFrom::Current(0)).unwrap() as usize) + } + pub async fn on_read_endp_data( + &self, + port_num: PortId, + endp_num: u8, + buf: &mut [u8], + ) -> Result { + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADF))?; + + let mut ep_if_state = &mut port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADF))? + .driver_if_state; + + match ep_if_state { + &mut EndpIfState::WaitingForDataPipe { + direction: XhciEndpCtlDirection::In, + bytes_transferred, + bytes_to_transfer: total_bytes_to_transfer, + } => { + if buf.len() > total_bytes_to_transfer as usize - bytes_transferred as usize { + return Err(Error::new(EINVAL)); + } + + drop(port_state); + let (completion_code, some_bytes_transferred) = + self.transfer_read(port_num, endp_num - 1, buf).await?; + + // Just as with on_write_endp_data, a client issuing multiple reads must always + // stop reading if one read returns fewer bytes than expected. + + let result = Self::transfer_result(completion_code, some_bytes_transferred); + + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADF))?; + + let mut ep_state = port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADF))?; + + let ep_if_state = &mut ep_state.driver_if_state; + + if let &mut EndpIfState::WaitingForDataPipe { + direction: XhciEndpCtlDirection::In, + bytes_to_transfer, + ref mut bytes_transferred, + } = ep_if_state + { + if *bytes_transferred + some_bytes_transferred == bytes_to_transfer + || completion_code != TrbCompletionCode::Success as u8 + { + *ep_if_state = EndpIfState::WaitingForTransferResult(result); + } else { + *bytes_transferred += some_bytes_transferred; + } + } else { + unreachable!() + } + Ok(some_bytes_transferred as usize) + } + _ => return Err(Error::new(EBADF)), + } + } + /// Notifies the xHC that the current event handler has finished, so that new interrupts can be + /// sent. This is required after each invocation of `Self::execute_command`. + /// + /// # Locking + /// This function locks `Xhci::run`. + pub fn event_handler_finished(&self) { + trace!("Event handler finished"); + // write 1 to EHB to clear it + self.run.lock().unwrap().ints[0] + .erdp_low + .writef(1 << 3, true); + } +} +pub fn handle_event_trb(name: &str, event_trb: &Trb, command_trb: &Trb) -> Result<()> { + if event_trb.completion_code() == TrbCompletionCode::Success as u8 { + Ok(()) + } else { + error!( + "{} command (TRB {:?}) failed with event trb {:?}", + name, command_trb, event_trb + ); + Err(Error::new(EIO)) + } +} +pub fn handle_transfer_event_trb(name: &str, event_trb: &Trb, transfer_trb: &Trb) -> Result<()> { + if event_trb.completion_code() == TrbCompletionCode::Success as u8 + || event_trb.completion_code() == TrbCompletionCode::ShortPacket as u8 + { + Ok(()) + } else { + error!( + "{} transfer {:?} failed with event {:?}", + name, transfer_trb, event_trb + ); + Err(Error::new(EIO)) + } +} +use lazy_static::lazy_static; +use std::ops::{Add, Div, Rem}; + +pub fn div_round_up(a: T, b: T) -> T +where + T: Add + Div + Rem + PartialEq + From + Copy, +{ + if a % b != T::from(0u8) { + a / b + T::from(1u8) + } else { + a / b + } +} diff --git a/drivers/usb/xhcid/src/xhci/trb.rs b/drivers/usb/xhcid/src/xhci/trb.rs new file mode 100644 index 0000000000..e0e5dc79fe --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/trb.rs @@ -0,0 +1,510 @@ +use super::context::StreamContextType; +use crate::usb; +use common::io::{Io, Mmio}; +use log::trace; +use std::{fmt, mem}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum TrbType { + Reserved, + /* Transfer */ + Normal, + SetupStage, + DataStage, + StatusStage, + Isoch, + Link, + EventData, + NoOp, + /* Command */ + EnableSlot, + DisableSlot, + AddressDevice, + ConfigureEndpoint, + EvaluateContext, + ResetEndpoint, + StopEndpoint, + SetTrDequeuePointer, + ResetDevice, + ForceEvent, + NegotiateBandwidth, + SetLatencyToleranceValue, + GetPortBandwidth, + ForceHeader, + NoOpCmd, + /* Reserved */ + GetExtendedProperty, + SetExtendedProperty, + Rsv26, + Rsv27, + Rsv28, + Rsv29, + Rsv30, + Rsv31, + /* Events */ + Transfer, + CommandCompletion, + PortStatusChange, + BandwidthRequest, + Doorbell, + HostController, + DeviceNotification, + MfindexWrap, + /* Reserved from 40 to 47, vendor devined from 48 to 63 */ +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum TrbCompletionCode { + Invalid = 0x00, + Success = 0x01, + DataBuffer = 0x02, + BabbleDetected = 0x03, + UsbTransaction = 0x04, + Trb = 0x05, + Stall = 0x06, + Resource = 0x07, + Bandwidth = 0x08, + NoSlotsAvailable = 0x09, + InvalidStreamType = 0x0A, + SlotNotEnabled = 0x0B, + EndpointNotEnabled = 0x0C, + ShortPacket = 0x0D, + RingUnderrun = 0x0E, + RingOverrun = 0x0F, + VfEventRingFull = 0x10, + Parameter = 0x11, + BandwidthOverrun = 0x12, + ContextState = 0x13, + NoPingResponse = 0x14, + EventRingFull = 0x15, + IncompatibleDevice = 0x16, + MissedService = 0x17, + CommandRingStopped = 0x18, + CommandAborted = 0x19, + Stopped = 0x1A, + StoppedLengthInvalid = 0x1B, + StoppedShortPacket = 0x1C, + MaxExitLatencyTooLarge = 0x1D, + Rsv30 = 0x1E, + IsochBuffer = 0x1F, + EventLost = 0x20, + Undefined = 0x21, + InvalidStreamId = 0x22, + SecondaryBandwidth = 0x23, + SplitTransaction = 0x24, + /* Values from 37 to 191 are reserved */ + /* 192 to 223 are vendor defined errors */ + /* 224 to 255 are vendor defined information */ +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TransferKind { + NoData, + Reserved, + Out, + In, +} + +#[repr(C, packed)] +pub struct Trb { + pub data_low: Mmio, + pub data_high: Mmio, + pub status: Mmio, + pub control: Mmio, +} +impl Clone for Trb { + fn clone(&self) -> Self { + Self { + data_low: Mmio::new(self.data_low.read()), + data_high: Mmio::new(self.data_high.read()), + status: Mmio::new(self.status.read()), + control: Mmio::new(self.control.read()), + } + } +} + +pub const TRB_STATUS_COMPLETION_CODE_SHIFT: u8 = 24; +pub const TRB_STATUS_COMPLETION_CODE_MASK: u32 = 0xFF00_0000; + +pub const TRB_STATUS_COMPLETION_PARAM_SHIFT: u8 = 0; +pub const TRB_STATUS_COMPLETION_PARAM_MASK: u32 = 0x00FF_FFFF; + +pub const TRB_STATUS_TRANSFER_LENGTH_SHIFT: u8 = 0; +pub const TRB_STATUS_TRANSFER_LENGTH_MASK: u32 = 0x00FF_FFFF; + +pub const TRB_CONTROL_TRB_TYPE_SHIFT: u8 = 10; +pub const TRB_CONTROL_TRB_TYPE_MASK: u32 = 0x0000_FC00; + +pub const TRB_CONTROL_EVENT_DATA_SHIFT: u8 = 2; +pub const TRB_CONTROL_EVENT_DATA_BIT: u32 = 1 << TRB_CONTROL_EVENT_DATA_SHIFT; + +pub const TRB_CONTROL_ENDPOINT_ID_MASK: u32 = 0x001F_0000; +pub const TRB_CONTROL_ENDPOINT_ID_SHIFT: u8 = 16; + +impl Trb { + pub fn set(&mut self, data: u64, status: u32, control: u32) { + self.data_low.write(data as u32); + self.data_high.write((data >> 32) as u32); + self.status.write(status); + self.control.write(control); + } + + pub fn reserved(&mut self, cycle: bool) { + self.set(0, 0, ((TrbType::Reserved as u32) << 10) | (cycle as u32)); + } + + pub fn read_data(&self) -> u64 { + (self.data_low.read() as u64) | ((self.data_high.read() as u64) << 32) + } + + pub fn completion_code(&self) -> u8 { + (self.status.read() >> TRB_STATUS_COMPLETION_CODE_SHIFT) as u8 + } + pub fn completion_param(&self) -> u32 { + self.status.read() & TRB_STATUS_COMPLETION_PARAM_MASK + } + fn has_completion_trb_pointer(&self) -> bool { + if self.completion_code() == TrbCompletionCode::RingUnderrun as u8 + || self.completion_code() == TrbCompletionCode::RingOverrun as u8 + { + false + } else if self.completion_code() == TrbCompletionCode::VfEventRingFull as u8 { + false + } else { + true + } + } + pub fn completion_trb_pointer(&self) -> Option { + debug_assert_eq!(self.trb_type(), TrbType::CommandCompletion as u8); + + if self.has_completion_trb_pointer() { + Some(self.read_data()) + } else { + None + } + } + pub fn transfer_event_trb_pointer(&self) -> Option { + debug_assert_eq!(self.trb_type(), TrbType::Transfer as u8); + + if self.has_completion_trb_pointer() { + Some(self.read_data()) + } else { + None + } + } + + pub fn port_status_change_port_id(&self) -> Option { + debug_assert_eq!(self.trb_type(), TrbType::PortStatusChange as u8); + + if self.has_completion_trb_pointer() { + let data = self.read_data(); + Some(((data >> 24) & 0xFF) as u8) + } else { + None + } + } + + pub fn event_slot(&self) -> u8 { + (self.control.read() >> 24) as u8 + } + /// Returns the number of bytes that should have been transmitten, but weren't. + pub fn transfer_length(&self) -> u32 { + self.status.read() & TRB_STATUS_TRANSFER_LENGTH_MASK + } + pub fn event_data_bit(&self) -> bool { + self.control.readf(TRB_CONTROL_EVENT_DATA_BIT) + } + pub fn event_data(&self) -> Option { + if self.event_data_bit() { + Some(self.read_data()) + } else { + None + } + } + pub fn endpoint_id(&self) -> u8 { + ((self.control.read() & TRB_CONTROL_ENDPOINT_ID_MASK) >> TRB_CONTROL_ENDPOINT_ID_SHIFT) + as u8 + } + pub fn trb_type(&self) -> u8 { + ((self.control.read() & TRB_CONTROL_TRB_TYPE_MASK) >> TRB_CONTROL_TRB_TYPE_SHIFT) as u8 + } + + pub fn link(&mut self, address: usize, toggle: bool, cycle: bool) { + self.set( + address as u64, + 0, + ((TrbType::Link as u32) << 10) | ((toggle as u32) << 1) | (cycle as u32), + ); + } + + pub fn no_op_cmd(&mut self, cycle: bool) { + self.set(0, 0, ((TrbType::NoOpCmd as u32) << 10) | (cycle as u32)); + } + + pub fn enable_slot(&mut self, slot_type: u8, cycle: bool) { + trace!("Enabling slot with type {}", slot_type); + self.set( + 0, + 0, + (((slot_type as u32) & 0x1F) << 16) + | ((TrbType::EnableSlot as u32) << 10) + | (cycle as u32), + ); + } + pub fn disable_slot(&mut self, slot: u8, cycle: bool) { + self.set( + 0, + 0, + (u32::from(slot) << 24) | ((TrbType::DisableSlot as u32) << 10) | u32::from(cycle), + ); + } + + pub fn address_device(&mut self, slot_id: u8, input_ctx_ptr: usize, bsr: bool, cycle: bool) { + assert_eq!( + (input_ctx_ptr as u64) & 0xFFFF_FFFF_FFFF_FFF0, + input_ctx_ptr as u64, + "unaligned input context ptr" + ); + self.set( + input_ctx_ptr as u64, + 0, + (u32::from(slot_id) << 24) + | ((TrbType::AddressDevice as u32) << 10) + | (u32::from(bsr) << 9) + | u32::from(cycle), + ); + } + // Synchronizes the input context endpoints with the device context endpoints, I think. + pub fn configure_endpoint(&mut self, slot_id: u8, input_ctx_ptr: usize, cycle: bool) { + assert_eq!( + (input_ctx_ptr as u64) & 0xFFFF_FFFF_FFFF_FFF0, + input_ctx_ptr as u64, + "unaligned input context ptr" + ); + + self.set( + input_ctx_ptr as u64, + 0, + (u32::from(slot_id) << 24) + | ((TrbType::ConfigureEndpoint as u32) << 10) + | u32::from(cycle), + ); + } + pub fn evaluate_context(&mut self, slot_id: u8, input_ctx_ptr: usize, bsr: bool, cycle: bool) { + assert_eq!( + (input_ctx_ptr as u64) & 0xFFFF_FFFF_FFFF_FFF0, + input_ctx_ptr as u64, + "unaligned input context ptr" + ); + self.set( + input_ctx_ptr as u64, + 0, + (u32::from(slot_id) << 24) + | ((TrbType::EvaluateContext as u32) << 10) + | (u32::from(bsr) << 9) + | u32::from(cycle), + ); + } + pub fn reset_endpoint(&mut self, slot_id: u8, endp_num_xhc: u8, tsp: bool, cycle: bool) { + assert_eq!(endp_num_xhc & 0x1F, endp_num_xhc); + self.set( + 0, + 0, + (u32::from(slot_id) << 24) + | (u32::from(endp_num_xhc) << 16) + | ((TrbType::ResetEndpoint as u32) << 10) + | (u32::from(tsp) << 9) + | u32::from(cycle), + ); + } + /// The deque_ptr has to contain the DCS bit (bit 0). + pub fn set_tr_deque_ptr( + &mut self, + deque_ptr: u64, + cycle: bool, + sct: StreamContextType, + stream_id: u16, + endp_num_xhc: u8, + slot: u8, + ) { + assert_eq!(deque_ptr & 0xFFFF_FFFF_FFFF_FFF1, deque_ptr); + assert_eq!(endp_num_xhc & 0x1F, endp_num_xhc); + + self.set( + deque_ptr | ((sct as u64) << 1), + u32::from(stream_id) << 16, + (u32::from(slot) << 24) + | (u32::from(endp_num_xhc) << 16) + | ((TrbType::SetTrDequeuePointer as u32) << 10) + | u32::from(cycle), + ) + } + pub fn stop_endpoint(&mut self, slot_id: u8, endp_num_xhc: u8, suspend: bool, cycle: bool) { + assert_eq!(endp_num_xhc & 0x1F, endp_num_xhc); + self.set( + 0, + 0, + (u32::from(slot_id) << 24) + | (u32::from(suspend) << 23) + | (u32::from(endp_num_xhc) << 16) + | ((TrbType::StopEndpoint as u32) << 10) + | u32::from(cycle), + ); + } + pub fn reset_device(&mut self, slot_id: u8, cycle: bool) { + self.set( + 0, + 0, + (u32::from(slot_id) << 24) | ((TrbType::ResetDevice as u32) << 10) | u32::from(cycle), + ); + } + + pub fn transfer_no_op(&mut self, interrupter: u8, ent: bool, ch: bool, ioc: bool, cycle: bool) { + self.set( + 0, + u32::from(interrupter) << 22, + ((TrbType::NoOp as u32) << 10) + | (u32::from(ioc) << 5) + | (u32::from(ch) << 4) + | (u32::from(ent) << 1) + | u32::from(cycle), + ); + } + + pub fn setup(&mut self, setup: usb::Setup, transfer: TransferKind, cycle: bool) { + self.set( + unsafe { mem::transmute(setup) }, + 8, + ((transfer as u32) << 16) + | ((TrbType::SetupStage as u32) << 10) + | (1 << 6) + | (cycle as u32), + ); + } + + pub fn data(&mut self, buffer: usize, length: u16, input: bool, cycle: bool) { + self.set( + buffer as u64, + length as u32, + ((input as u32) << 16) | ((TrbType::DataStage as u32) << 10) | (cycle as u32), + ); + } + + pub fn cycle(&self) -> bool { + self.control.readf(0x01) + } + + pub fn status( + &mut self, + interrupter: u16, + input: bool, + ioc: bool, + ch: bool, + ent: bool, + cycle: bool, + ) { + self.set( + 0, + u32::from(interrupter) << 22, + (u32::from(input) << 16) + | ((TrbType::StatusStage as u32) << 10) + | (u32::from(ioc) << 5) + | (u32::from(ch) << 4) + | (u32::from(ent) << 1) + | (cycle as u32), + ); + } + pub fn normal( + &mut self, + buffer: u64, + len: u32, + cycle: bool, + estimated_td_size: u8, + interrupter: u8, + ent: bool, + isp: bool, + chain: bool, + ioc: bool, + idt: bool, + bei: bool, + ) { + assert_eq!(estimated_td_size & 0x1F, estimated_td_size); + // NOTE: The interrupter target and no snoop flags have been omitted. + self.set( + buffer, + len | (u32::from(estimated_td_size) << 17) | (u32::from(interrupter) << 22), + u32::from(cycle) + | (u32::from(ent) << 1) + | (u32::from(isp) << 2) + | (u32::from(chain) << 4) + | (u32::from(ioc) << 5) + | (u32::from(idt) << 6) + | (u32::from(bei) << 9) + | ((TrbType::Normal as u32) << 10), + ) + } + pub fn is_command_trb(&self) -> bool { + let valid_trb_types = [ + TrbType::NoOpCmd as u8, + TrbType::EnableSlot as u8, + TrbType::DisableSlot as u8, + TrbType::AddressDevice as u8, + TrbType::ConfigureEndpoint as u8, + TrbType::EvaluateContext as u8, + TrbType::ResetEndpoint as u8, + TrbType::StopEndpoint as u8, + TrbType::SetTrDequeuePointer as u8, + TrbType::ResetDevice as u8, + TrbType::ForceEvent as u8, + TrbType::NegotiateBandwidth as u8, + TrbType::SetLatencyToleranceValue as u8, + TrbType::GetPortBandwidth as u8, + TrbType::ForceHeader as u8, + TrbType::GetExtendedProperty as u8, + TrbType::SetExtendedProperty as u8, + ]; + valid_trb_types.contains(&self.trb_type()) + } + pub fn is_transfer_trb(&self) -> bool { + // XXX: Unfortunately, the only way to use match statements with integer constants, is to + // precast them into valid enum values, which either requires a derive macro such as + // num_traits's #[derive(FromPrimitive)], or manually writing the reverse match statement + // first. + let valid_trb_types = [ + TrbType::Normal as u8, + TrbType::SetupStage as u8, + TrbType::DataStage as u8, + TrbType::StatusStage as u8, + TrbType::Isoch as u8, + TrbType::NoOp as u8, + ]; + valid_trb_types.contains(&self.trb_type()) + } +} + +impl fmt::Debug for Trb { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Trb {{ data: {:>016X}, status: {:>08X}, control: {:>08X} }}", + self.read_data(), + self.status.read(), + self.control.read() + ) + } +} + +impl fmt::Display for Trb { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "({:>016X}, {:>08X}, {:>08X})", + self.read_data(), + self.status.read(), + self.control.read() + ) + } +} diff --git a/drivers/vboxd/Cargo.toml b/drivers/vboxd/Cargo.toml new file mode 100644 index 0000000000..5c595e5ea1 --- /dev/null +++ b/drivers/vboxd/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "vboxd" +version = "0.1.0" +edition = "2018" + +[dependencies] +libredox = "0.1.3" +orbclient = "0.3.47" +redox_event = "0.4.1" +redox_syscall = "0.5" +redox-daemon = "0.1" + +common = { path = "../common" } +pcid = { path = "../pcid" } diff --git a/drivers/vboxd/config.toml b/drivers/vboxd/config.toml new file mode 100644 index 0000000000..1216625579 --- /dev/null +++ b/drivers/vboxd/config.toml @@ -0,0 +1,6 @@ +[[drivers]] +name = "VirtualBox Guest Device" +class = 0x08 +vendor = 0x80EE +device = 0xCAFE +command = ["vboxd"] diff --git a/drivers/vboxd/src/bga.rs b/drivers/vboxd/src/bga.rs new file mode 100644 index 0000000000..264c9c568c --- /dev/null +++ b/drivers/vboxd/src/bga.rs @@ -0,0 +1,46 @@ +use common::io::{Io, Pio}; + +const BGA_INDEX_XRES: u16 = 1; +const BGA_INDEX_YRES: u16 = 2; +const BGA_INDEX_BPP: u16 = 3; +const BGA_INDEX_ENABLE: u16 = 4; + +pub struct Bga { + index: Pio, + data: Pio, +} + +impl Bga { + pub fn new() -> Bga { + Bga { + index: Pio::new(0x1CE), + data: Pio::new(0x1CF), + } + } + + fn read(&mut self, index: u16) -> u16 { + self.index.write(index); + self.data.read() + } + + fn write(&mut self, index: u16, data: u16) { + self.index.write(index); + self.data.write(data); + } + + pub fn width(&mut self) -> u16 { + self.read(BGA_INDEX_XRES) + } + + pub fn height(&mut self) -> u16 { + self.read(BGA_INDEX_YRES) + } + + pub fn set_size(&mut self, width: u16, height: u16) { + self.write(BGA_INDEX_ENABLE, 0); + self.write(BGA_INDEX_XRES, width); + self.write(BGA_INDEX_YRES, height); + self.write(BGA_INDEX_BPP, 32); + self.write(BGA_INDEX_ENABLE, 0x41); + } +} diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs new file mode 100644 index 0000000000..cac69b6d0d --- /dev/null +++ b/drivers/vboxd/src/main.rs @@ -0,0 +1,335 @@ +//#![deny(warnings)] + +use event::{user_data, EventQueue}; +use std::fs::File; +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; +use std::{iter, mem}; + +use common::io::{Io, Mmio, Pio}; +use pcid_interface::PciFunctionHandle; + +use common::dma::Dma; + +use crate::bga::Bga; + +mod bga; + +const VBOX_REQUEST_HEADER_VERSION: u32 = 0x10001; +const VBOX_VMMDEV_VERSION: u32 = 0x00010003; + +const VBOX_EVENT_DISPLAY: u32 = 1 << 2; +const VBOX_EVENT_MOUSE: u32 = 1 << 9; + +/// VBox VMMDevMemory +#[repr(C, packed)] +struct VboxVmmDev { + size: Mmio, + version: Mmio, + host_events: Mmio, + guest_events: Mmio, +} + +/// VBox Guest packet header +#[repr(C, packed)] +struct VboxHeader { + /// Size of the entire packet (including this header) + size: Mmio, + /// Version; always VBOX_REQUEST_HEADER_VERSION + version: Mmio, + /// Request type + request: Mmio, + /// Return code + result: Mmio, + _reserved1: Mmio, + _reserved2: Mmio, +} + +/// VBox Get Mouse +#[repr(C, packed)] +struct VboxGetMouse { + header: VboxHeader, + features: Mmio, + x: Mmio, + y: Mmio, +} + +impl VboxGetMouse { + fn request() -> u32 { + 1 + } + + fn new() -> syscall::Result> { + let mut packet = unsafe { Dma::::zeroed()?.assume_init() }; + + packet.header.size.write(mem::size_of::() as u32); + packet.header.version.write(VBOX_REQUEST_HEADER_VERSION); + packet.header.request.write(Self::request()); + + Ok(packet) + } +} + +/// VBox Set Mouse +#[repr(C, packed)] +struct VboxSetMouse { + header: VboxHeader, + features: Mmio, + x: Mmio, + y: Mmio, +} + +impl VboxSetMouse { + fn request() -> u32 { + 2 + } + + fn new() -> syscall::Result> { + let mut packet = unsafe { Dma::::zeroed()?.assume_init() }; + + packet.header.size.write(mem::size_of::() as u32); + packet.header.version.write(VBOX_REQUEST_HEADER_VERSION); + packet.header.request.write(Self::request()); + + Ok(packet) + } +} + +/// VBox Acknowledge Events packet +#[repr(C, packed)] +struct VboxAckEvents { + header: VboxHeader, + events: Mmio, +} + +impl VboxAckEvents { + fn request() -> u32 { + 41 + } + + fn new() -> syscall::Result> { + let mut packet = unsafe { Dma::::zeroed()?.assume_init() }; + + packet.header.size.write(mem::size_of::() as u32); + packet.header.version.write(VBOX_REQUEST_HEADER_VERSION); + packet.header.request.write(Self::request()); + + Ok(packet) + } +} + +/// VBox Guest Capabilities packet +#[repr(C, packed)] +struct VboxGuestCaps { + header: VboxHeader, + caps: Mmio, +} + +impl VboxGuestCaps { + fn request() -> u32 { + 55 + } + + fn new() -> syscall::Result> { + let mut packet = unsafe { Dma::::zeroed()?.assume_init() }; + + packet.header.size.write(mem::size_of::() as u32); + packet.header.version.write(VBOX_REQUEST_HEADER_VERSION); + packet.header.request.write(Self::request()); + + Ok(packet) + } +} + +/* VBox GetDisplayChange packet */ +struct VboxDisplayChange { + header: VboxHeader, + xres: Mmio, + yres: Mmio, + bpp: Mmio, + eventack: Mmio, +} + +impl VboxDisplayChange { + fn request() -> u32 { + 51 + } + + fn new() -> syscall::Result> { + let mut packet = unsafe { Dma::::zeroed()?.assume_init() }; + + packet.header.size.write(mem::size_of::() as u32); + packet.header.version.write(VBOX_REQUEST_HEADER_VERSION); + packet.header.request.write(Self::request()); + + Ok(packet) + } +} + +/// VBox Guest Info packet (legacy) +#[repr(C, packed)] +struct VboxGuestInfo { + header: VboxHeader, + version: Mmio, + ostype: Mmio, +} + +impl VboxGuestInfo { + fn request() -> u32 { + 50 + } + + fn new() -> syscall::Result> { + let mut packet = unsafe { Dma::::zeroed()?.assume_init() }; + + packet.header.size.write(mem::size_of::() as u32); + packet.header.version.write(VBOX_REQUEST_HEADER_VERSION); + packet.header.request.write(Self::request()); + + Ok(packet) + } +} + +fn main() { + let mut pcid_handle = PciFunctionHandle::connect_default(); + let pci_config = pcid_handle.config(); + + let mut name = pci_config.func.name(); + name.push_str("_vbox"); + + let bar0 = pci_config.func.bars[0].expect_port(); + + let irq = pci_config + .func + .legacy_interrupt_line + .expect("vboxd: no legacy interrupts supported"); + + println!(" + VirtualBox {}", pci_config.func.display()); + + // Daemonize + redox_daemon::Daemon::new(move |daemon| { + common::acquire_port_io_rights().expect("vboxd: failed to get I/O permission"); + + let mut width = 0; + let mut height = 0; + let mut display_opt = File::open("inputd:producer").ok(); + if let Some(ref display) = display_opt { + let mut buf: [u8; 4096] = [0; 4096]; + if let Ok(count) = libredox::call::fpath(display.as_raw_fd() as usize, &mut buf) { + let path = unsafe { String::from_utf8_unchecked(Vec::from(&buf[..count])) }; + let res = path.split(":").nth(1).unwrap_or(""); + width = res + .split("/") + .nth(1) + .unwrap_or("") + .parse::() + .unwrap_or(0); + height = res + .split("/") + .nth(2) + .unwrap_or("") + .parse::() + .unwrap_or(0); + } + } + + let mut irq_file = irq.irq_handle("vboxd"); + + let mut port = Pio::::new(bar0 as u16); + let address = unsafe { pcid_handle.map_bar(1) }.ptr.as_ptr(); + { + let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) }; + + let mut guest_info = VboxGuestInfo::new().expect("vboxd: failed to map GuestInfo"); + guest_info.version.write(VBOX_VMMDEV_VERSION); + guest_info.ostype.write(0x100); + port.write(guest_info.physical() as u32); + + let mut guest_caps = VboxGuestCaps::new().expect("vboxd: failed to map GuestCaps"); + guest_caps.caps.write(1 << 2); + port.write(guest_caps.physical() as u32); + + let mut set_mouse = VboxSetMouse::new().expect("vboxd: failed to map SetMouse"); + set_mouse.features.write(1 << 4 | 1); + port.write(set_mouse.physical() as u32); + + vmmdev + .guest_events + .write(VBOX_EVENT_DISPLAY | VBOX_EVENT_MOUSE); + + user_data! { + enum Source { + Irq, + } + } + + let event_queue = + EventQueue::::new().expect("vboxd: Could not create event queue."); + event_queue + .subscribe( + irq_file.as_raw_fd() as usize, + Source::Irq, + event::EventFlags::READ, + ) + .unwrap(); + + daemon.ready().expect("failed to signal readiness"); + + libredox::call::setrens(0, 0).expect("vboxd: failed to enter null namespace"); + + let mut bga = Bga::new(); + let get_mouse = VboxGetMouse::new().expect("vboxd: failed to map GetMouse"); + let display_change = + VboxDisplayChange::new().expect("vboxd: failed to map DisplayChange"); + let ack_events = VboxAckEvents::new().expect("vboxd: failed to map AckEvents"); + + for Source::Irq in iter::once(Source::Irq) + .chain(event_queue.map(|e| e.expect("vboxd: failed to get next event").user_data)) + { + let mut irq = [0; 8]; + if irq_file.read(&mut irq).unwrap() >= irq.len() { + let host_events = vmmdev.host_events.read(); + if host_events != 0 { + port.write(ack_events.physical() as u32); + irq_file.write(&irq).unwrap(); + + if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY { + port.write(display_change.physical() as u32); + if let Some(ref mut display) = display_opt { + let new_width = display_change.xres.read(); + let new_height = display_change.yres.read(); + if width != new_width || height != new_height { + width = new_width; + height = new_height; + println!("Display {}, {}", width, height); + bga.set_size(width as u16, height as u16); + let _ = display.write( + &orbclient::ResizeEvent { width, height }.to_event(), + ); + } + } + } + + if host_events & VBOX_EVENT_MOUSE == VBOX_EVENT_MOUSE { + port.write(get_mouse.physical() as u32); + if let Some(ref mut display) = display_opt { + let x = get_mouse.x.read() * width / 0x10000; + let y = get_mouse.y.read() * height / 0x10000; + let _ = display.write( + &orbclient::MouseEvent { + x: x as i32, + y: y as i32, + } + .to_event(), + ); + } + } + } + } + } + } + + std::process::exit(0); + }) + .expect("vboxd: failed to daemonize"); +} diff --git a/drivers/virtio-core/Cargo.toml b/drivers/virtio-core/Cargo.toml new file mode 100644 index 0000000000..4a5630b53a --- /dev/null +++ b/drivers/virtio-core/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "virtio-core" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +static_assertions = "1.1.0" +bitflags = "2.3.2" +redox_syscall = "0.5" +libredox = "0.1.3" +log = "0.4" +thiserror = "1.0.40" +futures = { version = "0.3.28", features = ["executor"] } +crossbeam-queue = "0.3.8" + +redox_event = "0.4.1" + +common = { path = "../common" } +pcid = { path = "../pcid" } diff --git a/drivers/virtio-core/src/arch/aarch64.rs b/drivers/virtio-core/src/arch/aarch64.rs new file mode 100644 index 0000000000..4801a1f25d --- /dev/null +++ b/drivers/virtio-core/src/arch/aarch64.rs @@ -0,0 +1,9 @@ +use std::fs::File; + +use pcid_interface::*; + +use crate::{transport::Error, Device}; + +pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { + unimplemented!("virtio_core: aarch64 enable_msix") +} diff --git a/drivers/virtio-core/src/arch/riscv64.rs b/drivers/virtio-core/src/arch/riscv64.rs new file mode 100644 index 0000000000..2551479f52 --- /dev/null +++ b/drivers/virtio-core/src/arch/riscv64.rs @@ -0,0 +1,9 @@ +use std::fs::File; + +use pcid_interface::*; + +use crate::{transport::Error, Device}; + +pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { + unimplemented!("virtio_core: enable_msix") +} diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs new file mode 100644 index 0000000000..751f268e07 --- /dev/null +++ b/drivers/virtio-core/src/arch/x86.rs @@ -0,0 +1,37 @@ +use crate::transport::Error; + +use pcid_interface::irq_helpers::{allocate_single_interrupt_vector_for_msi, read_bsp_apic_id}; +use std::fs::File; + +use crate::MSIX_PRIMARY_VECTOR; + +use pcid_interface::*; + +pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { + // Extended message signaled interrupts. + let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { + PciFeatureInfo::MsiX(capability) => capability, + _ => unreachable!(), + }; + let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; + + // Allocate the primary MSI vector. + // FIXME allow the driver to register multiple MSI-X vectors + // FIXME move this MSI-X registering code into pcid_interface or pcid itself + let interrupt_handle = { + let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize); + + let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed"); + let (msg_addr_and_data, interrupt_handle) = + allocate_single_interrupt_vector_for_msi(destination_id); + table_entry_pointer.write_addr_and_data(msg_addr_and_data); + table_entry_pointer.unmask(); + + interrupt_handle + }; + + pcid_handle.enable_feature(PciFeature::MsiX); + + log::info!("virtio: using MSI-X (interrupt_handle={interrupt_handle:?})"); + Ok(interrupt_handle) +} diff --git a/drivers/virtio-core/src/lib.rs b/drivers/virtio-core/src/lib.rs new file mode 100644 index 0000000000..2557d0b78e --- /dev/null +++ b/drivers/virtio-core/src/lib.rs @@ -0,0 +1,19 @@ +pub mod spec; +pub mod transport; +pub mod utils; + +mod probe; + +#[cfg(target_arch = "aarch64")] +#[path = "arch/aarch64.rs"] +mod arch; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[path = "arch/x86.rs"] +mod arch; + +#[cfg(target_arch = "riscv64")] +#[path = "arch/riscv64.rs"] +mod arch; + +pub use probe::{probe_device, reinit, Device, MSIX_PRIMARY_VECTOR}; diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs new file mode 100644 index 0000000000..5762912f8e --- /dev/null +++ b/drivers/virtio-core/src/probe.rs @@ -0,0 +1,158 @@ +use std::fs::File; +use std::sync::Arc; + +use pcid_interface::*; + +use crate::spec::*; +use crate::transport::{Error, StandardTransport, Transport}; +use crate::utils::align_down; + +pub struct Device { + pub transport: Arc, + pub device_space: *const u8, + pub irq_handle: File, +} + +// FIXME(andypython): `device_space` should not be `Send` nor `Sync`. Take +// it out of `Device`. +unsafe impl Send for Device {} +unsafe impl Sync for Device {} + +pub const MSIX_PRIMARY_VECTOR: u16 = 0; + +/// VirtIO Device Probe +/// +/// ## Device State +/// After this function, the device will have been successfully reseted and is ready for use. +/// +/// The caller is required to do the following: +/// * Negotiate the device and driver supported features (finialize via [`StandardTransport::finalize_features`]) +/// * Create the device specific virtio queues (via [`StandardTransport::setup_queue`]). This is *required* to be done +/// before starting the device. +/// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device +/// is alive. +/// +/// ## Panics +/// This function panics if the device is not a virtio device. +pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result { + let pci_config = pcid_handle.config(); + + assert_eq!( + pci_config.func.full_device_id.vendor_id, 6900, + "virtio_core::probe_device: not a virtio device" + ); + + let mut common_addr = None; + let mut notify_addr = None; + let mut device_addr = None; + + for raw_capability in pcid_handle.get_vendor_capabilities() { + // SAFETY: We have verified that the length of the data is correct. + let capability = unsafe { &*(raw_capability.data.as_ptr() as *const PciCapability) }; + + match capability.cfg_type { + CfgType::Common | CfgType::Notify | CfgType::Device => {} + _ => continue, + } + + let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem(); + + let address = unsafe { + let addr = addr + capability.offset as usize; + + // XXX: physmap() requires the address to be page aligned. + let aligned_addr = align_down(addr); + let offset = addr - aligned_addr; + + let size = offset + capability.length as usize; + + let addr = common::physmap( + aligned_addr, + size, + common::Prot::RW, + common::MemoryType::Uncacheable, + )? as usize; + + addr + offset + }; + + match capability.cfg_type { + CfgType::Common => { + debug_assert!(common_addr.is_none()); + common_addr = Some(address); + } + + CfgType::Notify => { + debug_assert!(notify_addr.is_none()); + + // SAFETY: The capability type is `Notify`, so its safe to access + // the `notify_multiplier` field. + let multiplier = unsafe { + (&*(raw_capability.data.as_ptr() as *const PciCapability + as *const PciCapabilityNotify)) + .notify_off_multiplier() + }; + notify_addr = Some((address, multiplier)); + } + + CfgType::Device => { + debug_assert!(device_addr.is_none()); + device_addr = Some(address); + } + + _ => unreachable!(), + } + } + + let common_addr = common_addr.expect("virtio common capability missing"); + let device_addr = device_addr.expect("virtio device capability missing"); + let (notify_addr, notify_multiplier) = notify_addr.expect("virtio notify capability missing"); + + // FIXME this is explicitly allowed by the virtio specification to happen + assert!( + notify_multiplier != 0, + "virtio-core::device_probe: device uses the same Queue Notify addresses for all queues" + ); + + let common = unsafe { &mut *(common_addr as *mut CommonCfg) }; + let device_space = unsafe { &mut *(device_addr as *mut u8) }; + + let transport = StandardTransport::new( + common, + notify_addr as *const u8, + notify_multiplier, + device_space, + ); + + // Setup interrupts. + let all_pci_features = pcid_handle.fetch_all_features(); + let has_msix = all_pci_features.iter().any(|feature| feature.is_msix()); + + // According to the virtio specification, the device REQUIRED to support MSI-X. + assert!(has_msix, "virtio: device does not support MSI-X"); + let irq_handle = crate::arch::enable_msix(pcid_handle)?; + + log::info!("virtio: using standard PCI transport"); + + let device = Device { + transport, + device_space, + irq_handle, + }; + + device.transport.reset(); + reinit(&device)?; + + Ok(device) +} + +pub fn reinit(device: &Device) -> Result<(), Error> { + // XXX: According to the virtio specification v1.2, setting the ACKNOWLEDGE and DRIVER bits + // in `device_status` is required to be done in two steps. + device + .transport + .insert_status(DeviceStatusFlags::ACKNOWLEDGE); + + device.transport.insert_status(DeviceStatusFlags::DRIVER); + Ok(()) +} diff --git a/drivers/virtio-core/src/spec/mod.rs b/drivers/virtio-core/src/spec/mod.rs new file mode 100644 index 0000000000..b78931d2cb --- /dev/null +++ b/drivers/virtio-core/src/spec/mod.rs @@ -0,0 +1,56 @@ +//! https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html +//! +//! This file contains comments copied from the VirtIO specification which are +//! licensed under the following conditions: +//! +//! Copyright © OASIS Open 2022. All Rights Reserved. +//! +//! All capitalized terms in the following text have the meanings assigned to them +//! in the OASIS Intellectual Property Rights Policy (the "OASIS IPR Policy"). The +//! full Policy may be found at the OASIS website. +//! +//! This document and translations of it may be copied and furnished to others, +//! and derivative works that comment on or otherwise explain it or assist in its +//! implementation may be prepared, copied, published, and distributed, in whole +//! or in part, without restriction of any kind, provided that the above copyright +//! notice and this section are included on all such copies and derivative works. +//! However, this document itself may not be modified in any way, including by +//! removing the copyright notice or references to OASIS, except as needed for the +//! purpose of developing any document or deliverable produced by an OASIS Technical +//! Committee (in which case the rules applicable to copyrights, as set forth in the +//! OASIS IPR Policy, must be followed) or as required to translate it into languages +//! other than English. + +bitflags::bitflags! { + /// [2.1 Device Status Field](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-110001) + #[derive(Debug, Copy, Clone, PartialEq)] + #[repr(transparent)] + pub struct DeviceStatusFlags: u8 { + /// Indicates that the guest OS has found the device and recognized it as a + /// valid device. + const ACKNOWLEDGE = 1; + /// Indicates that the guest OS knows how to drive the device. + const DRIVER = 2; + /// Indicates that something went wrong in the guest and it has given up on + /// the device. + const FAILED = 128; + /// Indicates that the driver has acknowledged all the features it understands + /// and feature negotiation is complete. + const FEATURES_OK = 8; + /// Indicates that the driver is set up and ready to drive the device. + const DRIVER_OK = 4; + /// Indicates that the device has experienced an error from which it can’t recover. + const DEVICE_NEEDS_RESET = 64; + } +} + +mod split_virtqueue; +pub use split_virtqueue::*; + +// FIXME add [2.8 Packed Virtqueues](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-720008) + +mod transport_pci; +pub use transport_pci::*; + +mod reserved_features; +pub use reserved_features::*; diff --git a/drivers/virtio-core/src/spec/reserved_features.rs b/drivers/virtio-core/src/spec/reserved_features.rs new file mode 100644 index 0000000000..9f88676767 --- /dev/null +++ b/drivers/virtio-core/src/spec/reserved_features.rs @@ -0,0 +1,100 @@ +//! [6 Reserved Feature Bits](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-6600006) +//! +//! This file contains comments copied from the VirtIO specification which are +//! licensed under the following conditions: +//! +//! Copyright © OASIS Open 2022. All Rights Reserved. +//! +//! All capitalized terms in the following text have the meanings assigned to them +//! in the OASIS Intellectual Property Rights Policy (the "OASIS IPR Policy"). The +//! full Policy may be found at the OASIS website. +//! +//! This document and translations of it may be copied and furnished to others, +//! and derivative works that comment on or otherwise explain it or assist in its +//! implementation may be prepared, copied, published, and distributed, in whole +//! or in part, without restriction of any kind, provided that the above copyright +//! notice and this section are included on all such copies and derivative works. +//! However, this document itself may not be modified in any way, including by +//! removing the copyright notice or references to OASIS, except as needed for the +//! purpose of developing any document or deliverable produced by an OASIS Technical +//! Committee (in which case the rules applicable to copyrights, as set forth in the +//! OASIS IPR Policy, must be followed) or as required to translate it into languages +//! other than English. + +/// Negotiating this feature indicates that the driver can use descriptors +/// with the VIRTQ_DESC_F_INDIRECT flag set as described in 2.7.5.3 Indirect +/// Descriptors and 2.8.7 Indirect Flag: Scatter-Gather Support. +pub const VIRTIO_F_INDIRECT_DESC: u32 = 28; + +/// This feature enables the used_event and the avail_event fields as +/// described in 2.7.7, 2.7.8 and 2.8.10. +pub const VIRTIO_F_EVENT_IDX: u32 = 29; + +/// This indicates compliance with this specification, giving a simple way +/// to detect legacy devices or drivers. +pub const VIRTIO_F_VERSION_1: u32 = 32; + +/// This feature indicates that the device can be used on a platform where device +/// access to data in memory is limited and/or translated. E.g. this is the case +/// if the device can be located behind an IOMMU that translates bus addresses +/// from the device into physical addresses in memory, if the device can be limited +/// to only access certain memory addresses or if special commands such as a cache +/// flush can be needed to synchronise data in memory with the device. Whether +/// accesses are actually limited or translated is described by platform-specific +/// means. If this feature bit is set to 0, then the device has same access to +/// memory addresses supplied to it as the driver has. In particular, the device +/// will always use physical addresses matching addresses used by the driver +/// (typically meaning physical addresses used by the CPU) and not translated +/// further, and can access any address supplied to it by the driver. When clear, +/// this overrides any platform-specific description of whether device access is +/// limited or translated in any way, e.g. whether an IOMMU may be present. +pub const VIRTIO_F_ACCESS_PLATFORM: u32 = 33; + +/// This feature indicates support for the packed virtqueue layout as described +/// in 2.8 Packed Virtqueues. +pub const VIRTIO_F_RING_PACKED: u32 = 34; + +/// This feature indicates that all buffers are used by the device in the same order +/// in which they have been made available. +pub const VIRTIO_F_IN_ORDER: u32 = 35; + +/// This feature indicates that memory accesses by the driver and the device are +/// ordered in a way described by the platform. +/// If this feature bit is negotiated, the ordering in effect for any memory +/// accesses by the driver that need to be ordered in a specific way with respect +/// to accesses by the device is the one suitable for devices described by the +/// platform. This implies that the driver needs to use memory barriers suitable +/// for devices described by the platform; e.g. for the PCI transport in the case +/// of hardware PCI devices. +/// +/// If this feature bit is not negotiated, then the device and driver are assumed +/// to be implemented in software, that is they can be assumed to run on identical +/// CPUs in an SMP configuration. Thus a weaker form of memory barriers is sufficient +/// to yield better performance. +pub const VIRTIO_F_ORDER_PLATFORM: u32 = 36; + +/// This feature indicates that the device supports Single Root I/O Virtualization. +/// Currently only PCI devices support this feature. +pub const VIRTIO_F_SR_IOV: u32 = 37; + +/// This feature indicates that the driver passes extra data (besides identifying +/// the virtqueue) in its device notifications. See 2.9 Driver Notifications. +pub const VIRTIO_F_NOTIFICATION_DATA: u32 = 38; + +/// This feature indicates that the driver uses the data provided by the device as +/// a virtqueue identifier in available buffer notifications. As mentioned in section +/// 2.9, when the driver is required to send an available buffer notification to the +/// device, it sends the virtqueue number to be notified. The method of delivering +/// notifications is transport specific. With the PCI transport, the device can +/// optionally provide a per-virtqueue value for the driver to use in driver +/// notifications, instead of the virtqueue number. Some devices may benefit from this +/// flexibility by providing, for example, an internal virtqueue identifier, or an +/// internal offset related to the virtqueue number. +/// +/// This feature indicates the availability of such value. The definition of the data +/// to be provided in driver notification and the delivery method is transport +/// specific. For more details about driver notifications over PCI see 4.1.5.2. +pub const VIRTIO_F_NOTIF_CONFIG_DATA: u32 = 39; + +/// This feature indicates that the driver can reset a queue individually. See 2.6.1. +pub const VIRTIO_F_RING_RESET: u32 = 40; diff --git a/drivers/virtio-core/src/spec/split_virtqueue.rs b/drivers/virtio-core/src/spec/split_virtqueue.rs new file mode 100644 index 0000000000..b96367111f --- /dev/null +++ b/drivers/virtio-core/src/spec/split_virtqueue.rs @@ -0,0 +1,205 @@ +//! [2.7 Split Virtqueues](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-350007) +//! +//! This file contains comments copied from the VirtIO specification which are +//! licensed under the following conditions: +//! +//! Copyright © OASIS Open 2022. All Rights Reserved. +//! +//! All capitalized terms in the following text have the meanings assigned to them +//! in the OASIS Intellectual Property Rights Policy (the "OASIS IPR Policy"). The +//! full Policy may be found at the OASIS website. +//! +//! This document and translations of it may be copied and furnished to others, +//! and derivative works that comment on or otherwise explain it or assist in its +//! implementation may be prepared, copied, published, and distributed, in whole +//! or in part, without restriction of any kind, provided that the above copyright +//! notice and this section are included on all such copies and derivative works. +//! However, this document itself may not be modified in any way, including by +//! removing the copyright notice or references to OASIS, except as needed for the +//! purpose of developing any document or deliverable produced by an OASIS Technical +//! Committee (in which case the rules applicable to copyrights, as set forth in the +//! OASIS IPR Policy, must be followed) or as required to translate it into languages +//! other than English. + +use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, Ordering}; + +use crate::utils::{IncompleteArrayField, VolatileCell}; +use static_assertions::const_assert_eq; + +/// [2.7.5 The Virtqueue Descriptor table](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-430005) +#[repr(C, align(16))] +pub struct Descriptor { + /// Address (guest-physical). + address: AtomicU64, + /// Size of the descriptor. + size: AtomicU32, + flags: AtomicU16, + /// Next field if flags & NEXT + next: AtomicU16, +} + +const_assert_eq!(core::mem::size_of::(), 16); + +bitflags::bitflags! { + #[derive(Debug, Copy, Clone)] + #[repr(transparent)] + pub struct DescriptorFlags: u16 { + /// This marks a buffer as continuing via the next field. + const NEXT = 1 << 0; + /// This marks a buffer as device write-only (otherwise device read-only). + const WRITE_ONLY = 1 << 1; + /// This means the buffer contains a list of buffer descriptors. + const INDIRECT = 1 << 2; + } +} + +impl Descriptor { + pub fn set_addr(&self, addr: u64) { + self.address.store(addr, Ordering::SeqCst) + } + + pub fn set_size(&self, size: u32) { + self.size.store(size, Ordering::SeqCst) + } + + pub fn set_next(&self, next: Option) { + self.next.store(next.unwrap_or_default(), Ordering::SeqCst) + } + + pub fn set_flags(&self, flags: DescriptorFlags) { + self.flags.store(flags.bits(), Ordering::SeqCst) + } + + pub fn next(&self) -> u16 { + self.next.load(Ordering::SeqCst) + } + + pub fn flags(&self) -> DescriptorFlags { + DescriptorFlags::from_bits_truncate(self.flags.load(Ordering::SeqCst)) + } +} + +// ======== Available Ring ======== +// +// XXX: The driver uses the available ring to offer buffers to the +// device. Each ring entry refers to the head of a descriptor +// chain. + +/// [2.7.6 The Virtqueue Available Ring](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-490006) +#[repr(C, align(2))] +pub struct AvailableRing { + pub flags: VolatileCell, + pub head_index: AtomicU16, + pub elements: IncompleteArrayField, +} + +const_assert_eq!(core::mem::size_of::(), 4); + +#[repr(C)] +pub struct AvailableRingElement { + pub table_index: AtomicU16, +} + +impl AvailableRingElement { + pub fn set_table_index(&self, index: u16) { + self.table_index.store(index, Ordering::SeqCst) + } +} + +const_assert_eq!(core::mem::size_of::(), 2); + +#[repr(C)] +pub struct AvailableRingExtra { + pub avail_event: VolatileCell, // Only if `VIRTIO_F_EVENT_IDX` +} + +const_assert_eq!(core::mem::size_of::(), 2); + +// ======== Used Ring ======== + +/// [2.7.8 The Virtqueue Used Ring](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-540008) +#[repr(C, align(4))] +pub struct UsedRing { + pub flags: VolatileCell, + pub head_index: VolatileCell, + pub elements: IncompleteArrayField, +} + +const_assert_eq!(core::mem::size_of::(), 4); + +#[repr(C)] +pub struct UsedRingElement { + pub table_index: VolatileCell, + pub written: VolatileCell, +} + +const_assert_eq!(core::mem::size_of::(), 8); + +#[repr(C)] +pub struct UsedRingExtra { + pub event_index: VolatileCell, +} + +// ======== Utils ======== +pub struct Buffer { + pub(crate) buffer: usize, + pub(crate) size: usize, + pub(crate) flags: DescriptorFlags, +} + +impl Buffer { + pub fn new(val: &common::dma::Dma) -> Self { + Self { + buffer: val.physical(), + size: core::mem::size_of::(), + flags: DescriptorFlags::empty(), + } + } + + pub fn new_unsized(val: &common::dma::Dma<[T]>) -> Self { + Self { + buffer: val.physical(), + size: core::mem::size_of::() * val.len(), + flags: DescriptorFlags::empty(), + } + } + + pub fn new_sized(val: &common::dma::Dma<[T]>, size: usize) -> Self { + Self { + buffer: val.physical(), + size, + flags: DescriptorFlags::empty(), + } + } + + pub fn flags(mut self, flags: DescriptorFlags) -> Self { + self.flags = flags; + self + } +} + +/// XXX: The [`DescriptorFlags::NEXT`] flag is set automatically. +pub struct ChainBuilder { + buffers: Vec, +} + +impl ChainBuilder { + pub fn new() -> Self { + Self { + buffers: Vec::new(), + } + } + + pub fn chain(mut self, mut buffer: Buffer) -> Self { + buffer.flags |= DescriptorFlags::NEXT; + self.buffers.push(buffer); + self + } + + pub fn build(mut self) -> Vec { + let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain"); + last_buffer.flags.remove(DescriptorFlags::NEXT); + + self.buffers + } +} diff --git a/drivers/virtio-core/src/spec/transport_pci.rs b/drivers/virtio-core/src/spec/transport_pci.rs new file mode 100644 index 0000000000..c6cb4a8af4 --- /dev/null +++ b/drivers/virtio-core/src/spec/transport_pci.rs @@ -0,0 +1,176 @@ +//! [4.1 Virtio Over PCI Bus](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-1150001) +//! +//! This file contains comments copied from the VirtIO specification which are +//! licensed under the following conditions: +//! +//! Copyright © OASIS Open 2022. All Rights Reserved. +//! +//! All capitalized terms in the following text have the meanings assigned to them +//! in the OASIS Intellectual Property Rights Policy (the "OASIS IPR Policy"). The +//! full Policy may be found at the OASIS website. +//! +//! This document and translations of it may be copied and furnished to others, +//! and derivative works that comment on or otherwise explain it or assist in its +//! implementation may be prepared, copied, published, and distributed, in whole +//! or in part, without restriction of any kind, provided that the above copyright +//! notice and this section are included on all such copies and derivative works. +//! However, this document itself may not be modified in any way, including by +//! removing the copyright notice or references to OASIS, except as needed for the +//! purpose of developing any document or deliverable produced by an OASIS Technical +//! Committee (in which case the rules applicable to copyrights, as set forth in the +//! OASIS IPR Policy, must be followed) or as required to translate it into languages +//! other than English. + +use super::DeviceStatusFlags; +use crate::utils::VolatileCell; +use static_assertions::const_assert_eq; + +/// [4.1.4 Virtio Structure PCI Capabilities](https://docs.oasis-open.org/virtio/virtio/v1.2/cs01/virtio-v1.2-cs01.html#x1-1240004) +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct PciCapability { + /// Identifies the structure. + pub cfg_type: CfgType, + /// Where to find it. + pub bar: u8, + /// Multiple capabilities of the same type. + pub id: u8, + /// Pad to a full dword. + pub padding: [u8; 2], + /// Offset within the bar. + pub offset: u32, + /// Length of the structure, in bytes. + pub length: u32, +} + +// The size of `PciCapability` is 13 bytes since the generic +// PCI fields are *not* included. +const_assert_eq!(core::mem::size_of::(), 13); + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum CfgType { + /// Common Configuration. + Common = 1, + /// Notifications. + Notify = 2, + /// ISR Status. + Isr = 3, + /// Device specific configuration. + Device = 4, + /// PCI configuration access. + PciConfig = 5, + /// Shared memory region. + SharedMemory = 8, + /// Vendor-specific data. + Vendor = 9, +} + +const_assert_eq!(core::mem::size_of::(), 1); + +#[derive(Debug)] +#[repr(C)] +pub struct CommonCfg { + // About the whole device. + /// The driver uses this to select which feature bits device_feature shows. + /// Value 0x0 selects Feature Bits 0 to 31, 0x1 selects Feature Bits 32 to 63, etc. + /// read-write + pub device_feature_select: VolatileCell, + /// The device uses this to report which feature bits it is offering to the driver: + /// the driver writes to device_feature_select to select which feature bits are presented. + /// read-only for driver + pub device_feature: VolatileCell, + /// The driver uses this to select which feature bits driver_feature shows. + /// Value 0x0 selects Feature Bits 0 to 31, 0x1 selects Feature Bits 32 to 63, etc. + /// read-write + pub driver_feature_select: VolatileCell, + /// The driver writes this to accept feature bits offered by the device. + /// Driver Feature Bits selected by driver_feature_select. + /// read-write + pub driver_feature: VolatileCell, + /// The driver sets the Configuration Vector for MSI-X. + /// read-write + pub config_msix_vector: VolatileCell, + /// The device specifies the maximum number of virtqueues supported here. + /// read-only for driver + pub num_queues: VolatileCell, + /// The driver writes the device status here (see 2.1). + /// Writing 0 into this field resets the device. + /// read-write + pub device_status: VolatileCell, + /// Configuration atomicity value. The device changes this every time the + /// configuration noticeably changes. + /// read-only for driver + pub config_generation: VolatileCell, + + // About a specific virtqueue. + /// Queue Select. The driver selects which virtqueue the following fields refer to. + /// read-write + pub queue_select: VolatileCell, + /// Queue Size. On reset, specifies the maximum queue size supported by the device. + /// This can be modified by the driver to reduce memory requirements. + /// A 0 means the queue is unavailable. + /// read-write + pub queue_size: VolatileCell, + /// The driver uses this to specify the queue vector for MSI-X. + /// read-write + pub queue_msix_vector: VolatileCell, + /// The driver uses this to selectively prevent the device from executing + /// requests from this virtqueue. 1 - enabled; 0 - disabled. + /// read-write + pub queue_enable: VolatileCell, + /// The driver reads this to calculate the offset from start of Notification + /// structure at which this virtqueue is located. Note: this is not an offset + /// in bytes. See 4.1.4.4 below. + /// read-only for driver + pub queue_notify_off: VolatileCell, + /// The driver writes the physical address of Descriptor Area here. + /// See section 2.6. + /// read-write + pub queue_desc: VolatileCell, + /// The driver writes the physical address of Driver Area here. + /// See section 2.6. + /// read-write + pub queue_driver: VolatileCell, + /// The driver writes the physical address of Device Area here. + /// See section 2.6. + /// read-write + pub queue_device: VolatileCell, + /// This field exists only if VIRTIO_F_NOTIF_CONFIG_DATA has been negotiated. + /// The driver will use this value to put it in the ’virtqueue number’ field + /// in the available buffer notification structure. See section 4.1.5.2. Note: + /// This field provides the device with flexibility to determine how virtqueues + /// will be referred to in available buffer notifications. In a trivial case the + /// device can set queue_notify_data=vqn. Some devices may benefit from providing + /// another value, for example an internal virtqueue identifier, or an internal + /// offset related to the virtqueue number. + /// read-only for driver + pub queue_notify_data: VolatileCell, + /// The driver uses this to selectively reset the queue. This field exists + /// only if VIRTIO_F_RING_RESET has been negotiated. (see 2.6.1). + /// read-write + pub queue_reset: VolatileCell, +} + +//TODO: why does this fail on x86? +#[cfg(not(target_arch = "x86"))] +const_assert_eq!(core::mem::size_of::(), 64); + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct PciCapabilityNotify { + pub cap: PciCapability, + /// Multiplier for queue_notify_off. + notify_off_multiplier: u32, +} + +impl PciCapabilityNotify { + pub fn notify_off_multiplier(&self) -> u32 { + self.notify_off_multiplier + } +} + +const_assert_eq!(core::mem::size_of::(), 17); + +/// Vector value used to disable MSI for queue +pub const VIRTIO_MSI_NO_VECTOR: u16 = 0xffff; diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs new file mode 100644 index 0000000000..72376fe003 --- /dev/null +++ b/drivers/virtio-core/src/transport.rs @@ -0,0 +1,696 @@ +use crate::spec::*; +use crate::utils::align; + +use common::dma::Dma; +use event::RawEventQueue; + +use core::mem::size_of; +use core::sync::atomic::{AtomicU16, Ordering}; + +use std::fs::File; +use std::future::Future; +use std::os::fd::AsRawFd; +use std::sync::{Arc, Mutex, Weak}; +use std::task::{Poll, Waker}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("syscall failed")] + SyscallError(#[from] libredox::error::Error), + #[error("the device is incapable of {0:?}")] + InCapable(CfgType), +} + +/// Returns the queue part sizes in bytes. +/// +/// ## Reference +/// Section 2.7 Split Virtqueues of the specfication v1.2 describes the alignment +/// and size of the queue parts. +/// +/// ## Panics +/// If `queue_size` is not a power of two or is zero. +pub const fn queue_part_sizes(queue_size: usize) -> (usize, usize, usize) { + assert!(queue_size.is_power_of_two() && queue_size != 0); + + const DESCRIPTOR_ALIGN: usize = 16; + const AVAILABLE_ALIGN: usize = 2; + const USED_ALIGN: usize = 4; + + let queue_size = queue_size as usize; + let desc = size_of::() * queue_size; + + // `avail_header`: Size of the available ring header and the footer. + let avail_header = size_of::() + size_of::(); + let avail = avail_header + size_of::() * queue_size; + + // `used_header`: Size of the used ring header and the footer. + let used_header = size_of::() + size_of::(); + let used = used_header + size_of::() * queue_size; + + ( + align(desc, DESCRIPTOR_ALIGN).next_multiple_of(syscall::PAGE_SIZE), + align(avail, AVAILABLE_ALIGN).next_multiple_of(syscall::PAGE_SIZE), + align(used, USED_ALIGN).next_multiple_of(syscall::PAGE_SIZE), + ) +} + +pub fn spawn_irq_thread(irq_handle: &File, queue: &Arc>) { + let irq_fd = irq_handle.as_raw_fd(); + let queue_copy = queue.clone(); + + std::thread::spawn(move || { + let event_queue = RawEventQueue::new().unwrap(); + + event_queue + .subscribe(irq_fd as usize, 0, event::EventFlags::READ) + .unwrap(); + + for event in event_queue.map(Result::unwrap) { + // Wake up the tasks waiting on the queue. + for (_, task) in queue_copy.waker.lock().unwrap().iter() { + task.wake_by_ref(); + } + } + }); +} + +pub trait NotifyBell { + fn ring(&self, queue_index: u16); +} + +pub struct PendingRequest<'a> { + queue: Arc>, + first_descriptor: u32, +} + +impl<'a> Future for PendingRequest<'a> { + type Output = u32; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + // XXX: Register the waker before checking the queue to avoid the race condition + // where you lose a notification. + self.queue + .waker + .lock() + .unwrap() + .insert(self.first_descriptor, cx.waker().clone()); + + let used_head = self.queue.used.head_index(); + + if used_head == self.queue.used_head.load(Ordering::SeqCst) { + // No new requests have been completed. + return Poll::Pending; + } + + let used_element = self.queue.used.get_element_at((used_head - 1) as usize); + let written = used_element.written.get(); + + let mut table_index = used_element.table_index.get(); + + if table_index == self.first_descriptor { + // The request has been completed; recycle the descriptors used. + while self.queue.descriptor[table_index as usize] + .flags() + .contains(DescriptorFlags::NEXT) + { + let next_index = self.queue.descriptor[table_index as usize].next(); + self.queue.descriptor_stack.push(table_index as u16); + table_index = next_index.into(); + } + + // Push the last descriptor. + self.queue.descriptor_stack.push(table_index as u16); + self.queue + .waker + .lock() + .unwrap() + .remove(&self.first_descriptor); + + self.queue.used_head.store(used_head, Ordering::SeqCst); + return Poll::Ready(written); + } else { + return Poll::Pending; + } + } +} + +pub struct Queue<'a> { + pub queue_index: u16, + pub waker: Mutex>, + pub used: Used<'a>, + pub descriptor: Dma<[Descriptor]>, + pub available: Available<'a>, + pub used_head: AtomicU16, + vector: u16, + + notification_bell: Box, + descriptor_stack: crossbeam_queue::SegQueue, + sref: Weak, +} + +impl<'a> Queue<'a> { + pub fn new( + descriptor: Dma<[Descriptor]>, + available: Available<'a>, + used: Used<'a>, + + notification_bell: N, + queue_index: u16, + vector: u16, + ) -> Arc + where + N: NotifyBell + 'static, + { + let descriptor_stack = crossbeam_queue::SegQueue::new(); + (0..descriptor.len() as u16).for_each(|i| descriptor_stack.push(i)); + + Arc::new_cyclic(|sref| Self { + notification_bell: Box::new(notification_bell), + available, + descriptor, + used, + waker: Mutex::new(std::collections::HashMap::new()), + queue_index, + descriptor_stack, + used_head: AtomicU16::new(0), + sref: sref.clone(), + vector, + }) + } + + fn reinit(&self) { + self.used_head.store(0, Ordering::SeqCst); + self.available.set_head_idx(0); + + // Drain all of the available descriptors. + while let Some(_) = self.descriptor_stack.pop() {} + + // Refill the descriptor stack. + (0..self.descriptor.len() as u16).for_each(|i| self.descriptor_stack.push(i)); + } + + #[must_use = "The function returns a future that must be awaited to ensure the sent request is completed."] + pub fn send(&self, chain: Vec) -> PendingRequest<'a> { + let mut first_descriptor: Option = None; + let mut last_descriptor: Option = None; + + for buffer in chain.iter() { + let descriptor = self.descriptor_stack.pop().unwrap() as usize; + + if first_descriptor.is_none() { + first_descriptor = Some(descriptor); + } + + self.descriptor[descriptor].set_addr(buffer.buffer as u64); + self.descriptor[descriptor].set_flags(buffer.flags); + self.descriptor[descriptor].set_size(buffer.size as u32); + + if let Some(index) = last_descriptor { + self.descriptor[index].set_next(Some(descriptor as u16)); + } + + last_descriptor = Some(descriptor); + } + + let last_descriptor = last_descriptor.unwrap(); + let first_descriptor = first_descriptor.unwrap(); + + self.descriptor[last_descriptor].set_next(None); + + let index = self.available.head_index() as usize; + + self.available + .get_element_at(index) + .set_table_index(first_descriptor as u16); + + self.available.set_head_idx(index as u16 + 1); + self.notification_bell.ring(self.queue_index); + + PendingRequest { + queue: self.sref.upgrade().unwrap(), + first_descriptor: first_descriptor as u32, + } + } + + /// Returns the number of descriptors in the descriptor table of this queue. + pub fn descriptor_len(&self) -> usize { + self.descriptor.len() + } +} + +unsafe impl Sync for Queue<'_> {} +unsafe impl Send for Queue<'_> {} + +pub struct Available<'a> { + mem: Mem<'a>, + queue_size: usize, +} +pub struct Borrowed<'a> { + phys: usize, + virt: usize, + size: usize, + _unused: &'a (), +} +pub enum Mem<'a> { + Owned(Dma<[u8]>), + Borrowed(Borrowed<'a>), +} +impl Borrowed<'_> { + pub unsafe fn new(phys: usize, virt: usize, size: usize) -> Self { + Self { + phys, + virt, + size, + _unused: &(), + } + } +} +impl<'a> Mem<'a> { + pub fn as_ptr(&self) -> *const T { + match *self { + Self::Owned(ref dma) => dma.as_ptr().cast(), + Self::Borrowed(Borrowed { + phys: _, + virt, + size: _, + _unused, + }) => virt as *const T, + } + } + pub fn as_mut_ptr(&mut self) -> *mut T { + match *self { + Self::Owned(ref mut dma) => dma.as_mut_ptr().cast(), + Self::Borrowed(Borrowed { + phys: _, + virt, + size: _, + _unused, + }) => virt as *mut T, + } + } + pub fn physical(&self) -> usize { + match self { + Self::Owned(dma) => dma.physical(), + Self::Borrowed(borrowed) => borrowed.phys, + } + } +} + +impl<'a> Available<'a> { + pub fn ring(&self) -> &AvailableRing { + unsafe { &*self.mem.as_ptr() } + } + pub fn ring_mut(&mut self) -> &mut AvailableRing { + unsafe { &mut *self.mem.as_mut_ptr() } + } + pub fn new(queue_size: usize) -> Result { + let (_, _, size) = queue_part_sizes(queue_size); + let mem = unsafe { + Dma::zeroed_slice(size) + .map_err(Error::SyscallError)? + .assume_init() + }; + + unsafe { Self::from_raw(Mem::Owned(mem), queue_size) } + } + + /// `addr` is the physical address of the ring. + pub unsafe fn from_raw(mem: Mem<'a>, queue_size: usize) -> Result { + let ring = Self { mem, queue_size }; + + for i in 0..queue_size { + // Setting them to `u16::MAX` helps with debugging since qemu reports them + // as illegal values. + ring.get_element_at(i) + .table_index + .store(u16::MAX, Ordering::SeqCst); + } + + Ok(ring) + } + + /// ## Panics + /// This function panics if the index is out of bounds. + pub fn get_element_at(&self, index: usize) -> &AvailableRingElement { + // SAFETY: We have exclusive access to the elements and the number of elements + // is correct; same as the queue size. + unsafe { + self.ring() + .elements + .as_slice(self.queue_size) + .get(index % self.queue_size) + .expect("virtio-core::available: index out of bounds") + } + } + + pub fn head_index(&self) -> u16 { + self.ring().head_index.load(Ordering::SeqCst) + } + + pub fn set_head_idx(&self, index: u16) { + self.ring().head_index.store(index, Ordering::SeqCst); + } + + pub fn phys_addr(&self) -> usize { + self.mem.physical() + } +} + +impl<'a> Drop for Available<'a> { + fn drop(&mut self) { + log::warn!( + "virtio-core: dropping 'available' ring at {:#x}", + self.phys_addr() + ); + } +} + +pub struct Used<'a> { + mem: Mem<'a>, + queue_size: usize, + _unused: &'a (), +} + +impl<'a> Used<'a> { + fn ring(&self) -> &UsedRing { + unsafe { &*self.mem.as_ptr() } + } + fn ring_mut(&mut self) -> &mut UsedRing { + unsafe { &mut *self.mem.as_mut_ptr() } + } + + pub fn new(queue_size: usize) -> Result { + let (_, _, size) = queue_part_sizes(queue_size); + let mem = unsafe { + Dma::zeroed_slice(size) + .map_err(Error::SyscallError)? + .assume_init() + }; + + unsafe { Self::from_raw(Mem::Owned(mem), queue_size) } + } + + /// `addr` is the physical address of the ring. + pub unsafe fn from_raw(mem: Mem<'a>, queue_size: usize) -> Result { + let mut ring = Self { + mem, + queue_size, + _unused: &(), + }; + + for i in 0..queue_size { + // Setting them to `u32::MAX` helps with debugging since qemu reports them + // as illegal values. + ring.get_mut_element_at(i).table_index.set(u32::MAX); + } + + Ok(ring) + } + + /// ## Panics + /// This function panics if the index is out of bounds. + pub fn get_element_at(&self, index: usize) -> &UsedRingElement { + // SAFETY: We have exclusive access to the elements and the number of elements + // is correct; same as the queue size. + unsafe { + self.ring() + .elements + .as_slice(self.queue_size) + .get(index % self.queue_size) + .expect("virtio-core::used: index out of bounds") + } + } + + /// ## Panics + /// This function panics if the index is out of bounds. + pub fn get_mut_element_at(&mut self, index: usize) -> &mut UsedRingElement { + // SAFETY: We have exclusive access to the elements and the number of elements + // is correct; same as the queue size. + let queue_size = self.queue_size; + unsafe { + self.ring_mut() + .elements + .as_mut_slice(queue_size) + .get_mut(index % 256) + .expect("virtio-core::used: index out of bounds") + } + } + + pub fn flags(&self) -> u16 { + self.ring().flags.get() + } + + pub fn head_index(&self) -> u16 { + self.ring().head_index.get() + } + + pub fn phys_addr(&self) -> usize { + self.mem.physical() + } +} + +impl Drop for Used<'_> { + fn drop(&mut self) { + log::warn!( + "virtio-core: dropping 'used' ring at {:#x}", + self.phys_addr() + ); + } +} + +pub trait Transport: Sync + Send { + /// `size` specifies the size of the read in bytes. + /// + /// ## Panics + /// This function panics if the provided `size` is more then `size_of::()`. + fn load_config(&self, offset: u8, size: u8) -> u64; + + /// Resets the device. + fn reset(&self); + + /// Returns whether the device supports the specified feature. + fn check_device_feature(&self, feature: u32) -> bool; + + /// Acknowledges the specified feature. + /// + /// **Note**: [`Transport::check_device_feature`] must be used to check whether + /// the device supports the feature before acknowledging it. + fn ack_driver_feature(&self, feature: u32); + + /// Finalizes the acknowledged features by setting the `FEATURES_OK` bit in the + /// device status flags. + fn finalize_features(&self); + + /// Runs the device. + /// + /// At this point, all of the queues must be created and the features must be + /// finalized. + /// + /// ## Panics + /// This function panics if the device is already running. + fn run_device(&self) { + self.insert_status(DeviceStatusFlags::DRIVER_OK); + } + + /// Request to be notified on configuration changes on the given MSI-X vector. + fn setup_config_notify(&self, vector: u16); + + /// Each time the device configuration changes this number will be updated. + fn config_generation(&self) -> u32; + + /// Creates a new queue. + /// + /// ## Panics + /// This function panics if the device is running. + fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result>, Error>; + + // TODO(andypython): Should this function be unsafe? + fn reinit_queue(&self, queue: Arc); + fn insert_status(&self, status: DeviceStatusFlags); +} + +struct StandardBell<'a>(&'a mut AtomicU16); + +impl NotifyBell for StandardBell<'_> { + #[inline] + fn ring(&self, queue_index: u16) { + self.0.store(queue_index, Ordering::SeqCst); + } +} + +pub struct StandardTransport<'a> { + pub(crate) common: Mutex<&'a mut CommonCfg>, + notify: *const u8, + notify_mul: u32, + device_space: *const u8, + + queue_index: AtomicU16, +} + +impl<'a> StandardTransport<'a> { + pub fn new( + common: &'a mut CommonCfg, + notify: *const u8, + notify_mul: u32, + device_space: *const u8, + ) -> Arc { + Arc::new(Self { + common: Mutex::new(common), + notify, + notify_mul, + + queue_index: AtomicU16::new(0), + device_space, + }) + } +} + +impl Transport for StandardTransport<'_> { + fn load_config(&self, offset: u8, size: u8) -> u64 { + unsafe { + let ptr = self.device_space.add(offset as usize); + let size = size as usize; + + if size == size_of::() { + ptr.cast::().read() as u64 + } else if size == size_of::() { + ptr.cast::().read() as u64 + } else if size == size_of::() { + ptr.cast::().read() as u64 + } else if size == size_of::() { + ptr.cast::().read() as u64 + } else { + unreachable!() + } + } + } + + fn reset(&self) { + let mut common = self.common.lock().unwrap(); + + common.device_status.set(DeviceStatusFlags::empty()); + // Upon reset, the device must initialize device status to 0. + assert_eq!(common.device_status.get(), DeviceStatusFlags::empty()); + } + + fn check_device_feature(&self, feature: u32) -> bool { + let mut common = self.common.lock().unwrap(); + + common.device_feature_select.set(feature >> 5); + (common.device_feature.get() & (1 << (feature & 31))) != 0 + } + + fn ack_driver_feature(&self, feature: u32) { + let mut common = self.common.lock().unwrap(); + + common.driver_feature_select.set(feature >> 5); + + let current = common.driver_feature.get(); + common.driver_feature.set(current | (1 << (feature & 31))); + } + + fn finalize_features(&self) { + // Check VirtIO version 1 compliance. + assert!(self.check_device_feature(VIRTIO_F_VERSION_1)); + self.ack_driver_feature(VIRTIO_F_VERSION_1); + + let mut common = self.common.lock().unwrap(); + + let status = common.device_status.get(); + common + .device_status + .set(status | DeviceStatusFlags::FEATURES_OK); + + // Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise, + // the device does not support our subset of features and the device is unusable. + let confirm = common.device_status.get(); + assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK); + } + + fn setup_config_notify(&self, vector: u16) { + self.common.lock().unwrap().config_msix_vector.set(vector); + } + + fn config_generation(&self) -> u32 { + u32::from(self.common.lock().unwrap().config_generation.get()) + } + + fn setup_queue(&self, vector: u16, irq_handle: &File) -> Result>, Error> { + let mut common = self.common.lock().unwrap(); + + let queue_index = self.queue_index.fetch_add(1, Ordering::SeqCst); + common.queue_select.set(queue_index); + + let queue_size = common.queue_size.get() as usize; + let queue_notify_idx = common.queue_notify_off.get(); + + // Allocate memory for the queue structues. + let descriptor = unsafe { + Dma::<[Descriptor]>::zeroed_slice(queue_size) + .map_err(Error::SyscallError)? + .assume_init() + }; + + let avail = Available::new(queue_size)?; + let used = Used::new(queue_size)?; + + common.queue_desc.set(descriptor.physical() as u64); + common.queue_driver.set(avail.phys_addr() as u64); + common.queue_device.set(used.phys_addr() as u64); + + // Set the MSI-X vector. + common.queue_msix_vector.set(vector); + assert!(common.queue_msix_vector.get() == vector); + + // Enable the queue. + common.queue_enable.set(1); + + let notification_bell = unsafe { + let offset = self.notify_mul * queue_notify_idx as u32; + &mut *(self.notify.add(offset as usize) as *mut AtomicU16) + }; + + log::info!("virtio-core: enabled queue #{queue_index} (size={queue_size})"); + + let queue = Queue::new( + descriptor, + avail, + used, + StandardBell(notification_bell), + queue_index, + vector, + ); + + spawn_irq_thread(irq_handle, &queue); + Ok(queue) + } + + fn insert_status(&self, status: DeviceStatusFlags) { + let mut common = self.common.lock().unwrap(); + let old = common.device_status.get(); + + common.device_status.set(old | status); + } + + /// Re-initializes a queue; usually done after a device reset. + fn reinit_queue(&self, queue: Arc) { + let mut common = self.common.lock().unwrap(); + queue.reinit(); + + common.queue_select.set(queue.queue_index); + + common.queue_desc.set(queue.descriptor.physical() as u64); + common.queue_driver.set(queue.available.phys_addr() as u64); + common.queue_device.set(queue.used.phys_addr() as u64); + + // Set the MSI-X vector. + common.queue_msix_vector.set(queue.vector); + assert!(common.queue_msix_vector.get() == queue.vector); + + // Enable the queue. + common.queue_enable.set(1); + } +} + +unsafe impl Send for StandardTransport<'_> {} +unsafe impl Sync for StandardTransport<'_> {} diff --git a/drivers/virtio-core/src/utils.rs b/drivers/virtio-core/src/utils.rs new file mode 100644 index 0000000000..76d8ff7eb7 --- /dev/null +++ b/drivers/virtio-core/src/utils.rs @@ -0,0 +1,80 @@ +use core::cell::UnsafeCell; +use core::fmt::Debug; +use core::marker::PhantomData; + +#[repr(C)] +pub struct VolatileCell { + value: UnsafeCell, +} + +impl VolatileCell { + #[inline] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + /// Returns a copy of the contained value. + #[inline] + pub fn get(&self) -> T { + unsafe { core::ptr::read_volatile(self.value.get()) } + } + + /// Sets the contained value. + #[inline] + pub fn set(&mut self, value: T) { + unsafe { core::ptr::write_volatile(self.value.get(), value) } + } +} + +impl Debug for VolatileCell +where + T: Debug + Copy, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VolatileCell") + .field("value", &self.get()) + .finish() + } +} + +unsafe impl Sync for VolatileCell {} + +#[repr(C)] +pub struct IncompleteArrayField(PhantomData, [T; 0]); + +impl IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + IncompleteArrayField(PhantomData, []) + } + + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + core::slice::from_raw_parts(self.as_ptr(), len) + } + + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + core::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } + + #[inline] + pub unsafe fn as_ptr(&self) -> *const T { + self as *const _ as *const T + } + + #[inline] + pub unsafe fn as_mut_ptr(&mut self) -> *mut T { + self as *mut _ as *mut T + } +} + +pub const fn align(val: usize, align: usize) -> usize { + (val + align) & !align +} + +pub const fn align_down(addr: usize) -> usize { + addr & !(syscall::PAGE_SIZE - 1) +}