diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..24db350 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Auto detect text files and perform normalization +* text=auto + +*.rs text diff=rust +*.toml text diff=toml +Cargo.lock text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d92d5d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +DriverCertificate.cer +.cargo-make-loadscripts/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9ba8cf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a423238 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,155 @@ +# Contributing to windows-drivers-rs + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit . + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +* [Code of Conduct](#coc) +* [Issues and Bugs](#issue) +* [Feature Requests](#feature) +* [Submission Guidelines](#submit) +* [Getting Started with windows-drivers-rs Development](#development) + +## Code of Conduct + +Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +## Found an Issue? + +If you find a bug in the source code or a mistake in the documentation, you can help us by +[submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can +[submit a Pull Request](#submit-pr) with a fix. + +## Want a Feature? + +You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub +Repository. If you would like to *implement* a new feature, please submit an issue with +a proposal for your work first, to be sure that we can use it. + +* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). + +## Submission Guidelines + +### Submitting an Issue + +Before you submit an issue, search the archive, maybe your question was already answered. + +If your issue appears to be a bug, and hasn't been reported, open a new issue. +Help us to maximize the effort we can spend fixing issues and adding new +features, by not reporting duplicate issues. Providing the following information will increase the +chances of your issue being dealt with quickly: + +* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps +* **Version** - what version is affected (e.g. 0.1.2) +* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you +* **Browsers and Operating System** - is this a problem with all browsers? +* **Reproduce the Error** - provide a live example or a unambiguous set of steps +* **Related Issues** - has a similar issue been reported before? +* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be + causing the problem (line of code or commit) + +You can file new issues by providing the above information at the corresponding repository's issues link: ]. + +### Submitting a Pull Request (PR) + +Before you submit your Pull Request (PR) consider the following guidelines: + +* Search the repository () for an open or closed PR + that relates to your submission. You don't want to duplicate effort. + +* Make your changes in a new git fork: + +* Commit your changes using a descriptive commit message +* Push your fork to GitHub: +* In GitHub, create a pull request +* If we suggest changes then: + * Make the required updates. + * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): + + ```shell + git rebase master -i + git push -f + ``` + +That's it! Thank you for your contribution! + +## Getting Started with windows-drivers-rs Development + +### Development Requirements + +The following tools should be installed as a part of the `windows-drivers-rs` developer workflow: + +* `cargo-expand`: `cargo install --locked cargo-expand` +* `cargo-audit`: `cargo install --locked cargo-audit` +* `cargo-udeps`: `cargo install --locked cargo-udeps` +* `taplo-cli`: `cargo install --locked taplo-cli` + +**Note on arm64:** ARM64 support for ring is [not released yet](https://github.com/briansmith/ring/issues/1167), so TLS features must be disabled until arm64 is officially supported by ring (probably in 0.17.0 release) + +### Generating Documentation + +* To compile and open documentation: `cargo doc --locked --open` + * To include nightly features: `cargo +nightly doc --locked --open --features nightly` + +### Policy on using Nightly/Unstable Features + +#### In `lib` and `bin` targets + +The crates in this repository are designed to work with `stable` rust. Some of the crates expose a `nightly` feature that adds additional functionality that requires unstable rust features in the `nightly` toolchains. + +#### In `test` targets and unit tests + +`test` targets and unit tests in other targets will automatically enable nightly features when a nightly toolchain is detected. This is done via the `nightly_toolchain` `cfg` value. This allows us to take advantage of unstable features (ex. [`assert_matches`](https://doc.rust-lang.org/std/assert_matches/macro.assert_matches.html)) in tests. + +### Build and Test + +To **only build** the workspace: `cargo build` + +To **both** build and package the samples in the workspace: `cargo make --cwd .\crates\` + +### Quality + +To maintain the quality of code, tests and tools are required to pass before contributions are accepted. This is a suggested list of things that should be run before contributions will be accepted: + +**_Functional Correctness:_** + +* `cargo test --locked --workspace --exclude sample-*` + * To test `nightly` features: `cargo +nightly test --locked --workspace --exclude sample-* --features nightly` + +**_Static Analysis and Linting:_** + +* `cargo clippy --locked --all-targets -- -D warnings` + * To lint `nightly` features: `cargo +nightly clippy --locked --all-targets --features nightly -- -D warnings` + +**_Formatting:_** + +* Check for consistent `.rs` file formatting: `cargo +nightly fmt --all -- --check` + * Running `cargo +nightly fmt --all` resolves these formatting inconsistencies usually + * `+nightly` is required to use some `nightly` configuration features in [the `rustfmt.toml` config](./rustfmt.toml) +* Check for consistent `.toml` file formatting: `taplo fmt --check --diff` + * Running `taplo fmt` resolves these formatting inconsistencies usually + +**_Dependency Analysis:_** + +* Scan for security advisories in dependent crates: `cargo audit --deny warnings` +* Scan for unused dependencies: `cargo +nightly udeps --locked --all-targets` + * `cargo udeps` requires `nightly` to function + +**_Rust Documentation Build Test_** + +* `cargo doc --locked` + * To build docs for `nightly` features: `cargo +nightly doc --locked --features nightly` + +### A Note on Code-Style + +Any bindings generated to C code maintains their original names, including their original style conventions(ex. PascalCase for functions). These bindings should all reside in `wdk-sys` and are marked as `unsafe` since all ffi is inherently `unsafe`. `wdk-sys` also retains manual implementations of wdk code (ex. because `bindgen` fails to resolve some macros). These should also maintain their original names and style. + +Any Rust wrappers written around the bindings should follow [Rust style and naming conventions](https://rust-lang.github.io/api-guidelines/naming.html) per RFC-430. Any wrappers around the FFI bindings should also be written to guarantee safety. Refer to [this](https://doc.rust-lang.org/nomicon/ffi.html#creating-a-safe-interface) for more information on writing safe rust wrappers to ffi code. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..328646b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,655 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "echo-2" +version = "0.1.0" +dependencies = [ + "paste", + "wdk", + "wdk-alloc", + "wdk-build", + "wdk-panic", + "wdk-sys", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "2.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wdk" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfcf77e36f4a923621bbfb1f71c8726b3e5f095ce2f3024a67944977e6bd2bc" +dependencies = [ + "wdk-build", + "wdk-sys", +] + +[[package]] +name = "wdk-alloc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a691cf90d7f95f00d579d23f5e1f26c5c7b1117ed916e9b4aff3d576cb8278" +dependencies = [ + "lazy_static", + "wdk-sys", +] + +[[package]] +name = "wdk-build" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d9809e05d5da7ab48d128cd8d14cc411993f9a167a4beda39d4be8c1d7acae" +dependencies = [ + "bindgen", + "rustversion", + "serde", + "serde_json", + "thiserror", + "windows", +] + +[[package]] +name = "wdk-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "454c8d83d6eddb8a888ed8ae317294798fa9530501b09182f6a352e6ba4b3b12" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wdk-panic" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0361d0d5c0ecdecae24b0e4ebc1373f6e2ed641fe72fb06ac2175715b939bf83" + +[[package]] +name = "wdk-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f106b3502b4eada628dadc18c3a7371bdc47074f6cfffc3c45b1373ffcf653" +dependencies = [ + "bindgen", + "lazy_static", + "rustversion", + "thiserror", + "tracing-subscriber", + "wdk-build", + "wdk-macros", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[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" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c0a15d6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +members = ["general/echo/kmdf/driver/*"] +resolver = "2" + +[workspace.package] +edition = "2021" +publish = false + +[profile.dev] +panic = "abort" +lto = true + +[profile.release] +panic = "abort" +lto = true + +[workspace.dependencies] +wdk = "0.1.0" +wdk-alloc = "0.1.0" +wdk-build = "0.1.0" +wdk-panic = "0.1.0" +wdk-sys = "0.1.0" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..7965606 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + 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 \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..6694102 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,14 @@ +extend = ".cargo-make-loadscripts/rust-driver-makefile.toml" + +[env] +CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true + +[config] +load_script = """ +pwsh.exe -Command "\ +if ($env:CARGO_MAKE_CRATE_IS_WORKSPACE) { return };\ +$cargoMakeURI = 'https://raw.githubusercontent.com/microsoft/windows-drivers-rs/main/rust-driver-makefile.toml';\ +New-Item -ItemType Directory .cargo-make-loadscripts -Force;\ +Invoke-RestMethod -Method GET -Uri $CargoMakeURI -OutFile $env:CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY/.cargo-make-loadscripts/rust-driver-makefile.toml\ +" +""" diff --git a/README.md b/README.md new file mode 100644 index 0000000..7155246 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# Rust Driver Samples + +This is a Rust port of the driver samples from the original [Windows Driver Samples on Github.](https://github.com/microsoft/Windows-driver-samples) + +The repository provides examples and best practices for Windows driver development in Rust using crates from [windows-drivers-rs.](https://github.com/microsoft/windows-drivers-rs) + +## Getting Started + +### Pre-requisites + +#### Required +* Set up [EWDK Build Environment.](https://learn.microsoft.com/en-us/windows-hardware/drivers/develop/using-the-enterprise-wdk) +* Install [Clang.](https://clang.llvm.org/get_started.html) +* Install [Rust.](https://www.rust-lang.org/tools/install) + +#### Rust Setup +Run the following commands after setting up Rust. +* `rustup toolchain add nightly-msvc` +* `cargo install cargo-make --no-default-features --features tls-native` + +__Note on arm64: ARM64 support for ring is [not released yet](https://github.com/briansmith/ring/issues/1167), so TLS features must be disabled until arm64 is officially supported by ring (probably in 0.17.0 release)__ + +##### Optional + +These are not-required, but may make it easier to work in a rust environment: + +* `cargo install cargo-expand` +* `cargo install cargo-edit` +* `cargo install cargo-workspaces` + +## Documentation + +Use `cargo doc --document-private-items --open` to compile and open documentation + +## Build and Test + +### Build + +From an EWDK development command prompt, run: + +`cargo make` + +If build is successful, this will stamp the INF and create a CAT file placed with driver binary and INF in `Package` folder. + +### Install + +1. Copy the following to the DUT (Device Under Test: the computer you want to test the driver on): + 1. `.\target\..configuration..\package` + 2. `Path\To\Driver\DriverCertificate.cer` + 3. The version of `devgen.exe` from the WDK Developer Tools that matches the archtecture of your DUT + * Ex. `C:\Program Files\Windows Kits\10\Tools\10.0.22621.0\x64\devgen.exe` +2. Install the Certificate on the DUT: + 1. Double click the certificate + 2. Click Install Certificate + 3. Select a Store Location __(Either Store Location is Fine)__ -> Next + 4. Place all certificates in the following Store -> Browse -> Trusted Root Certification Authorities -> Ok -> Next + 5. Finish +3. Install the driver: + * In the package directory, run: `pnputil.exe /add-driver echo_2.inf /install` +4. Create a software device: + * In the directory that `devgen.exe` was copied to, run: `devgen.exe /add /hardwareid "root\ECHO_2"` + +### Test + +* To capture prints: + * Start [DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview) + 1. Enable `Capture Kernel` + 2. Enable `Enable Verbose Kernel Output` + * Alternatively, you can see prints in an active Windbg session. + 1. Attach WinDBG + 2. `ed nt!Kd_DEFAULT_Mask 0xFFFFFFFF` + +### Usage + +The echo driver can be tested by using the [echo executable](https://github.com/microsoft/Windows-driver-samples/tree/main/general/echo/kmdf/exe) +- Echoapp.exe --- Send single write and read request synchronously + +- Echoapp.exe -Async --- Send 100 reads and writes asynchronously + +Exit the app anytime by pressing Ctrl-C + +## Windows driver development + +### Windows Driver Kit (WDK) + +Take a look at the compilation of the new and changed driver-related content for Windows 11. Areas of improvement include camera, print, display, Near Field Communication (NFC), WLAN, Bluetooth, and more. + +[Find out what's new in the WDK](https://docs.microsoft.com/windows-hardware/drivers/what-s-new-in-driver-development) + +### Windows Driver Frameworks + +The Windows Driver Frameworks (WDF) are a set of libraries that make it simple to write high-quality device drivers. + +[WDF driver development guide](https://docs.microsoft.com/windows-hardware/drivers/wdf/) + +### Samples + +Use the samples in this repo to guide your Windows driver development. Whether you're just getting started or porting an older driver to the newest version of Windows, code samples are valuable guides on how to write drivers. + +For information about important changes that need to be made to the WDK sample drivers before releasing device drivers based on the sample code, see the following topic: + +[From Sample Code to Production Driver - What to Change in the Samples](https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/from-sample-code-to-production-driver) + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow +[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b3c89ef --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). + + diff --git a/general/echo/kmdf/driver/DriverSync/Cargo.toml b/general/echo/kmdf/driver/DriverSync/Cargo.toml new file mode 100644 index 0000000..480afcd --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "echo-2" +version = "0.1.0" +license = "MIT OR Apache-2.0" +edition.workspace = true +publish.workspace = true + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wdk.workspace = true +wdk-alloc.workspace = true +wdk-panic.workspace = true +wdk-sys.workspace = true +paste = "1.0.14" + +[build-dependencies] +wdk-build.workspace = true + +[features] +default = [] +nightly = ["wdk/nightly", "wdk-sys/nightly"] diff --git a/general/echo/kmdf/driver/DriverSync/build.rs b/general/echo/kmdf/driver/DriverSync/build.rs new file mode 100644 index 0000000..ebbc7e2 --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/build.rs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +fn main() -> Result<(), wdk_build::ConfigError> { + wdk_build::Config::from_env_auto()?.configure_binary_build(); + Ok(()) +} diff --git a/general/echo/kmdf/driver/DriverSync/echo_2.inx b/general/echo/kmdf/driver/DriverSync/echo_2.inx new file mode 100644 index 0000000..7b9daaa --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/echo_2.inx @@ -0,0 +1,66 @@ +;=================================================================== +; Copyright (c)2023, Microsoft Corporation +; +;Module Name: +; ECHO_2.INF +;=================================================================== + +[Version] +Signature = "$WINDOWS NT$" +Class = Sample +ClassGuid = {78A1C341-4539-11d3-B88D-00C04FAD5171} +Provider = %ProviderString% +PnpLockDown = 1 + +[DestinationDirs] +DefaultDestDir = 13 + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +echo_2.sys = 1,, + +; ================= Class section ===================== + +[ClassInstall32] +Addreg=SampleClassReg + +[SampleClassReg] +HKR,,,0,%ClassName% +HKR,,Icon,,-5 + +; ================= Install section ================= + +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...16299 + +[Standard.NT$ARCH$.10.0...16299] +%ECHO.DeviceDesc%=ECHO_Device, root\ECHO_2 + +[ECHO_Device.NT$ARCH$] +CopyFiles=Drivers_Dir + +[Drivers_Dir] +echo_2.sys + +; ================= Service installation ================= +[ECHO_Device.NT$ARCH$.Services] +AddService = ECHO_2, %SPSVCINST_ASSOCSERVICE%, ECHO_Service_Inst + +[ECHO_Service_Inst] +DisplayName = %ECHO.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %13%\echo_2.sys + +; ================= Strings ================= +[Strings] +SPSVCINST_ASSOCSERVICE = 0x00000002 +ProviderString = "TODO-Set-Provider" +StdMfg = "(Standard system devices)" +DiskId1 = "WDF Sample ECHO Installation Disk #1 (DriverSync)" +ECHO.DeviceDesc = "Sample WDF ECHO Driver (DriverSync)" +ECHO.SVCDESC = "Sample WDF ECHO Service (DriverSync)" +ClassName = "Sample Device" diff --git a/general/echo/kmdf/driver/DriverSync/src/device.rs b/general/echo/kmdf/driver/DriverSync/src/device.rs new file mode 100644 index 0000000..934e2f5 --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/src/device.rs @@ -0,0 +1,202 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +use wdk::{nt_success, paged_code, println}; +use wdk_sys::{NTSTATUS, WDFDEVICE_INIT, *}; + +use crate::{ + queue::echo_queue_initialize, + queue_get_context, + wdf_object_context::*, + DeviceContext, + GUID_DEVINTERFACE_ECHO, + WDF_REQUEST_CONTEXT_TYPE_INFO, + *, +}; + +/// Worker routine called to create a device and its software resources. +/// +/// # Arguments: +/// +/// * `device_init` - Pointer to an opaque init structure. Memory for this +/// structure will be freed by the framework when the WdfDeviceCreate +/// succeeds. So don't access the structure after that point. +/// +/// # Return value: +/// +/// * `NTSTATUS` +#[link_section = "PAGE"] +pub(crate) fn echo_device_create(mut device_init: &mut WDFDEVICE_INIT) -> NTSTATUS { + paged_code!(); + + // Register pnp/power callbacks so that we can start and stop the timer as the + // device gets started and stopped. + let mut pnp_power_callbacks = WDF_PNPPOWER_EVENT_CALLBACKS { + Size: core::mem::size_of::() as ULONG, + EvtDeviceSelfManagedIoInit: Some(echo_evt_device_self_managed_io_start), + EvtDeviceSelfManagedIoSuspend: Some(echo_evt_device_self_managed_io_suspend), + // Function used for both Init and Restart Callbacks + EvtDeviceSelfManagedIoRestart: Some(echo_evt_device_self_managed_io_start), + ..WDF_PNPPOWER_EVENT_CALLBACKS::default() + }; + + // Register the PnP and power callbacks. Power policy related callbacks will be + // registered later in SotwareInit. + let [()] = [unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDeviceInitSetPnpPowerEventCallbacks, + device_init, + &mut pnp_power_callbacks + ) + }]; + + let mut attributes = WDF_OBJECT_ATTRIBUTES { + Size: core::mem::size_of::() as ULONG, + ExecutionLevel: _WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent, + SynchronizationScope: _WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent, + ContextTypeInfo: wdf_get_context_type_info!(RequestContext), + ..WDF_OBJECT_ATTRIBUTES::default() + }; + + let [()] = [unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDeviceInitSetRequestAttributes, + device_init, + &mut attributes + ) + }]; + + let mut attributes = WDF_OBJECT_ATTRIBUTES { + Size: core::mem::size_of::() as ULONG, + ExecutionLevel: _WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent, + SynchronizationScope: _WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent, + ContextTypeInfo: wdf_get_context_type_info!(DeviceContext), + ..WDF_OBJECT_ATTRIBUTES::default() + }; + + let mut device = WDF_NO_HANDLE as WDFDEVICE; + let mut nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDeviceCreate, + (core::ptr::addr_of_mut!(device_init)) as *mut *mut WDFDEVICE_INIT, + &mut attributes, + &mut device, + ) + }; + + if nt_success(nt_status) { + // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an + // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the + // device.h header file. This function will do the type checking and return + // the device context. If you pass a wrong object handle + // it will return NULL and assert if run under framework verifier mode. + let device_context: *mut DeviceContext = + unsafe { wdf_object_get_device_context(device as WDFOBJECT) }; + unsafe { (*device_context).private_device_data = 0 }; + + // Create a device interface so that application can find and talk + // to us. + nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDeviceCreateDeviceInterface, + device, + &GUID_DEVINTERFACE_ECHO, + core::ptr::null_mut(), + ) + }; + + if nt_success(nt_status) { + // Initialize the I/O Package and any Queues + nt_status = unsafe { echo_queue_initialize(device) }; + } + } + nt_status +} + +/// This event is called by the Framework when the device is started +/// or restarted after a suspend operation. +/// +/// This function is not marked pageable because this function is in the +/// device power up path. When a function is marked pagable and the code +/// section is paged out, it will generate a page fault which could impact +/// the fast resume behavior because the client driver will have to wait +/// until the system drivers can service this page fault. +/// +/// # Arguments: +/// +/// * `device` - Handle to a framework device object. +/// +/// # Return value: +/// +/// * `NTSTATUS` - Failures will result in the device stack being torn down. +extern "C" fn echo_evt_device_self_managed_io_start(device: WDFDEVICE) -> NTSTATUS { + // Restart the queue and the periodic timer. We stopped them before going + // into low power state. + let queue: WDFQUEUE; + + println!("--> EchoEvtDeviceSelfManagedIoInit"); + + unsafe { + queue = macros::call_unsafe_wdf_function_binding!(WdfDeviceGetDefaultQueue, device); + }; + + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + + // Restart the queue and the periodic timer. We stopped them before going + // into low power state. + let [()] = [unsafe { macros::call_unsafe_wdf_function_binding!(WdfIoQueueStart, queue) }]; + + let due_time: i64 = -(100) * (10000); + + let _ = unsafe { (*queue_context).timer.start(due_time) }; + + println!("<-- EchoEvtDeviceSelfManagedIoInit"); + + STATUS_SUCCESS +} + +/// This event is called by the Framework when the device is stopped +/// for resource rebalance or suspended when the system is entering +/// Sx state. +/// +/// # Arguments: +/// +/// * `device` - Handle to a framework device object. +/// +/// # Return value: +/// +/// * `NTSTATUS` - The driver is not allowed to fail this function. If it does, +/// the device stack will be torn down. +#[link_section = "PAGE"] +unsafe extern "C" fn echo_evt_device_self_managed_io_suspend(device: WDFDEVICE) -> NTSTATUS { + paged_code!(); + + println!("--> EchoEvtDeviceSelfManagedIoSuspend"); + + // Before we stop the timer we should make sure there are no outstanding + // i/o. We need to do that because framework cannot suspend the device + // if there are requests owned by the driver. There are two ways to solve + // this issue: 1) We can wait for the outstanding I/O to be complete by the + // periodic timer 2) Register EvtIoStop callback on the queue and acknowledge + // the request to inform the framework that it's okay to suspend the device + // with outstanding I/O. In this sample we will use the 1st approach + // because it's pretty easy to do. We will restart the queue when the + // device is restarted. + let queue = + unsafe { macros::call_unsafe_wdf_function_binding!(WdfDeviceGetDefaultQueue, device) }; + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + + unsafe { + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfIoQueueStopSynchronously, + queue + )]; + // Stop the watchdog timer and wait for DPC to run to completion if it's already + // fired. + let _ = (*queue_context).timer.stop(true); + }; + + println!("<-- EchoEvtDeviceSelfManagedIoSuspend"); + + STATUS_SUCCESS +} diff --git a/general/echo/kmdf/driver/DriverSync/src/driver.rs b/general/echo/kmdf/driver/DriverSync/src/driver.rs new file mode 100644 index 0000000..1958be7 --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/src/driver.rs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +use wdk::{nt_success, paged_code, println}; +use wdk_sys::{macros, ntddk::KeGetCurrentIrql, NTSTATUS, WDFDRIVER, *}; + +use crate::device; + +extern crate alloc; + +use alloc::{slice, string::String}; + +/// DriverEntry initializes the driver and is the first routine called by the +/// system after the driver is loaded. DriverEntry specifies the other entry +/// points in the function driver, such as EvtDevice and DriverUnload. +/// +/// # Arguments +/// +/// * `driver` - represents the instance of the function driver that is loaded +/// into memory. DriverEntry must initialize members of DriverObject before it +/// returns to the caller. DriverObject is allocated by the system before the +/// driver is loaded, and it is released by the system after the system +/// unloads the function driver from memory. +/// * `registry_path` - represents the driver specific path in the Registry. The +/// function driver can use the path to store driver related data between +/// reboots. The path does not store hardware instance specific data. +/// +/// # Return value: +/// +/// * `STATUS_SUCCESS` - if successful, +/// * `STATUS_UNSUCCESSFUL` - otherwise. +#[link_section = "INIT"] +#[export_name = "DriverEntry"] // WDF expects a symbol with the name DriverEntry +extern "system" fn driver_entry( + driver: &mut DRIVER_OBJECT, + registry_path: PCUNICODE_STRING, +) -> NTSTATUS { + let mut driver_config = WDF_DRIVER_CONFIG { + Size: core::mem::size_of::() as ULONG, + EvtDriverDeviceAdd: Some(echo_evt_device_add), + ..WDF_DRIVER_CONFIG::default() + }; + let driver_handle_output = WDF_NO_HANDLE as *mut WDFDRIVER; + + let nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfDriverCreate, + driver as PDRIVER_OBJECT, + registry_path, + WDF_NO_OBJECT_ATTRIBUTES, + &mut driver_config, + driver_handle_output, + ) + }; + + if !nt_success(nt_status) { + println!("Error: WdfDriverCreate failed {nt_status:#010X}"); + return nt_status; + } + + echo_print_driver_version(); + + nt_status +} + +/// EvtDeviceAdd is called by the framework in response to AddDevice +/// call from the PnP manager. We create and initialize a device object to +/// represent a new instance of the device. +/// +/// # Arguments: +/// +/// * `_driver` - Handle to a framework driver object created in DriverEntry +/// * `device_init` - Pointer to a framework-allocated WDFDEVICE_INIT structure. +/// +/// # Return value: +/// +/// * `NTSTATUS` +#[link_section = "PAGE"] +extern "C" fn echo_evt_device_add(_driver: WDFDRIVER, device_init: PWDFDEVICE_INIT) -> NTSTATUS { + paged_code!(); + + println!("Enter EchoEvtDeviceAdd"); + + let device_init = + // SAFETY: WDF should always be providing a pointer that is properly aligned, dereferencable per https://doc.rust-lang.org/std/ptr/index.html#safety, and initialized. For the lifetime of the resulting reference, the pointed-to memory is never accessed through any other pointer. + unsafe { + device_init + .as_mut() + .expect("WDF should never provide a null pointer for device_init") + }; + device::echo_device_create(device_init) +} + +/// This routine shows how to retrieve framework version string and +/// also how to find out to which version of framework library the +/// client driver is bound to. +/// +/// # Arguments: +/// +/// # Return value: +/// +/// * `NTSTATUS` +#[link_section = "INIT"] +fn echo_print_driver_version() -> NTSTATUS { + // 1) Retreive version string and print that in the debugger. + // + let mut string: WDFSTRING = core::ptr::null_mut(); + let mut us: UNICODE_STRING = UNICODE_STRING::default(); + let mut nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfStringCreate, + core::ptr::null_mut(), + WDF_NO_OBJECT_ATTRIBUTES, + &mut string + ) + }; + if !nt_success(nt_status) { + println!("Error: WdfStringCreate failed {nt_status:#010X}"); + return nt_status; + } + + // driver = unsafe{macros::call_unsafe_wdf_function_binding!(WdfGetDriver)}; + let driver = unsafe { (*wdk_sys::WdfDriverGlobals).Driver }; + nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!(WdfDriverRetrieveVersionString, driver, string) + }; + if !nt_success(nt_status) { + // No need to worry about delete the string object because + // by default it's parented to the driver and it will be + // deleted when the driverobject is deleted when the DriverEntry + // returns a failure status. + // + println!("Error: WdfDriverRetrieveVersionString failed {nt_status:#010X}"); + return nt_status; + } + + let [_] = [unsafe { + macros::call_unsafe_wdf_function_binding!(WdfStringGetUnicodeString, string, &mut us) + }]; + let driver_version = String::from_utf16_lossy(unsafe { + slice::from_raw_parts( + us.Buffer, + us.Length as usize / core::mem::size_of_val(&(*us.Buffer)), + ) + }); + println!("Echo Sample {driver_version}"); + + let [_] = [unsafe { + macros::call_unsafe_wdf_function_binding!(WdfObjectDelete, string as WDFOBJECT) + }]; + // string = core::ptr::null_mut(); + + // 2) Find out to which version of framework this driver is bound to. + // + let mut ver = WDF_DRIVER_VERSION_AVAILABLE_PARAMS { + Size: core::mem::size_of::() as ULONG, + MajorVersion: 1, + MinorVersion: 0, + }; + + if unsafe { + macros::call_unsafe_wdf_function_binding!(WdfDriverIsVersionAvailable, driver, &mut ver) + } > 0 + { + println!("Yes, framework version is 1.0"); + } else { + println!("No, framework version is not 1.0"); + } + + STATUS_SUCCESS +} diff --git a/general/echo/kmdf/driver/DriverSync/src/lib.rs b/general/echo/kmdf/driver/DriverSync/src/lib.rs new file mode 100644 index 0000000..1f7734c --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/src/lib.rs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +//! # Abstract +//! +//! This driver demonstrates use of a default I/O Queue, its +//! request start events, cancellation event, and a synchronized DPC. +//! +//! To demonstrate asynchronous operation, the I/O requests are not completed +//! immediately, but stored in the drivers private data structure, and a +//! timer DPC will complete it next time the DPC runs. +//! +//! During the time the request is waiting for the DPC to run, it is +//! made cancellable by the call WdfRequestMarkCancelable. This +//! allows the test program to cancel the request and exit instantly. +//! +//! This rather complicated set of events is designed to demonstrate +//! the driver frameworks synchronization of access to a device driver +//! data structure, and a pointer which can be a proxy for device hardware +//! registers or resources. +//! +//! This common data structure, or resource is accessed by new request +//! events arriving, the DPC that completes it, and cancel processing. +//! +//! Notice the lack of specific lock/unlock operations. +//! +//! Even though this example utilizes a serial queue, a parallel queue +//! would not need any additional explicit synchronization, just a +//! strategy for managing multiple requests outstanding. + +#![no_std] +#![cfg_attr(feature = "nightly", feature(hint_must_use))] +#![deny(warnings)] +#![deny(clippy::all)] +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::cargo)] +#![allow(clippy::missing_safety_doc)] + +mod device; +mod driver; +mod queue; + +#[cfg(not(test))] +extern crate wdk_panic; + +use wdk::wdf; +#[cfg(not(test))] +use wdk_alloc::WDKAllocator; +use wdk_sys::{ntddk::KeGetCurrentIrql, *}; +mod wdf_object_context; +use core::sync::atomic::AtomicI32; + +use wdf_object_context::{wdf_declare_context_type, wdf_declare_context_type_with_name}; + +#[cfg(not(test))] +#[global_allocator] +static GLOBAL_ALLOCATOR: WDKAllocator = WDKAllocator; + +// {CDC35B6E-0BE4-4936-BF5F-5537380A7C1A} +const GUID_DEVINTERFACE_ECHO: GUID = GUID { + Data1: 0xCDC3_5B6Eu32, + Data2: 0x0BE4u16, + Data3: 0x4936u16, + Data4: [ + 0xBFu8, 0x5Fu8, 0x55u8, 0x37u8, 0x38u8, 0x0Au8, 0x7Cu8, 0x1Au8, + ], +}; + +// Declare queue context. +// +// ====== CONTEXT SETUP ========// + +// The device context performs the same job as +// a WDM device extension in the driver frameworks +pub struct DeviceContext { + private_device_data: ULONG, // just a placeholder +} +wdf_declare_context_type!(DeviceContext); + +pub struct QueueContext { + buffer: PVOID, + length: usize, + timer: wdf::Timer, + current_request: WDFREQUEST, + current_status: NTSTATUS, + spin_lock: wdf::SpinLock, +} +wdf_declare_context_type_with_name!(QueueContext, queue_get_context); + +pub struct RequestContext { + cancel_completion_ownership_count: AtomicI32, +} +wdf_declare_context_type_with_name!(RequestContext, request_get_context); diff --git a/general/echo/kmdf/driver/DriverSync/src/queue.rs b/general/echo/kmdf/driver/DriverSync/src/queue.rs new file mode 100644 index 0000000..9969370 --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/src/queue.rs @@ -0,0 +1,714 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +use core::sync::atomic::Ordering; + +use wdk::{nt_success, paged_code, println, wdf}; +use wdk_sys::{ntddk::*, *}; + +use crate::{queue_get_context, wdf_object_context::*, *}; + +/// Set max write length for testing +const MAX_WRITE_LENGTH: usize = 1024 * 40; + +/// Set timer period in ms +const TIMER_PERIOD: u32 = 1000 * 10; + +/// This routine will interlock increment a value only if the current value +/// is greater then the floor value. +/// +/// The volatile keyword on the Target pointer is absolutely required, otherwise +/// the compiler might rearrange pointer dereferences and that cannot happen. +/// +/// # Arguments: +/// +/// * `target` - the value that will be pontetially incrmented +/// * `floor` - the value in which the Target value must be greater then if it +/// is to be incremented +/// +/// # Return value: +/// +/// The current value of Target. To detect failure, the return value will be +/// <= Floor + 1. It is +1 because we cannot increment from the Floor value +/// itself, so Floor+1 cannot be a successful return value. +fn echo_interlocked_increment_floor(target: &AtomicI32, floor: i32) -> i32 { + let mut current_value = target.load(Ordering::SeqCst); + loop { + if current_value <= floor { + return current_value; + } + + // currentValue will be the value that used to be Target if the exchange + // was made or its current value if the exchange was not made. + // + match target.compare_exchange( + current_value, + current_value + 1, + Ordering::SeqCst, + Ordering::SeqCst, + ) { + // If oldValue == currentValue, then no one updated Target in between + // the deref at the top and the InterlockecCompareExchange afterward + // and we have successfully incremented the value and can exit the loop. + Ok(_) => break, + Err(v) => current_value = v, + } + } + + current_value + 1 +} + +/// Increment the value only if it is currently > 0. +/// +/// # Arguments: +/// +/// * `target` - the value to be incremented +/// +/// # Return value: +/// +/// Upon success, a value > 0. Upon failure, a value <= 0. +fn echo_interlocked_increment_gtzero(target: &AtomicI32) -> i32 { + echo_interlocked_increment_floor(target, 0) +} + +/// The I/O dispatch callbacks for the frameworks device object +/// are configured in this function. +/// +/// A single default I/O Queue is configured for serial request +/// processing, and a driver context memory allocation is created +/// to hold our structure QUEUE_CONTEXT. +/// +/// This memory may be used by the driver automatically synchronized +/// by the Queue's presentation lock. +/// +/// The lifetime of this memory is tied to the lifetime of the I/O +/// Queue object, and we register an optional destructor callback +/// to release any private allocations, and/or resources. +/// +/// # Arguments: +/// +/// * `device` - Handle to a framework device object. +/// +/// # Return value: +/// +/// * `NTSTATUS` +#[link_section = "PAGE"] +pub unsafe fn echo_queue_initialize(device: WDFDEVICE) -> NTSTATUS { + let mut queue = WDF_NO_HANDLE as WDFQUEUE; + + paged_code!(); + + // Configure a default queue so that requests that are not + // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto + // other queues get dispatched here. + let mut queue_config = WDF_IO_QUEUE_CONFIG { + Size: core::mem::size_of::() as ULONG, + PowerManaged: _WDF_TRI_STATE::WdfUseDefault, + DefaultQueue: true as u8, + DispatchType: _WDF_IO_QUEUE_DISPATCH_TYPE::WdfIoQueueDispatchSequential, + EvtIoRead: Some(echo_evt_io_read), + EvtIoWrite: Some(echo_evt_io_write), + ..WDF_IO_QUEUE_CONFIG::default() + }; + + // Fill in a callback for destroy, and our QUEUE_CONTEXT size + let mut attributes = WDF_OBJECT_ATTRIBUTES { + Size: core::mem::size_of::() as ULONG, + ExecutionLevel: _WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent, + SynchronizationScope: _WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent, + ContextTypeInfo: wdf_get_context_type_info!(QueueContext), + EvtDestroyCallback: Some(echo_evt_io_queue_context_destroy), + ..WDF_OBJECT_ATTRIBUTES::default() + }; + + // Create queue. + let nt_status = unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfIoQueueCreate, + device, + &mut queue_config, + &mut attributes, + &mut queue + ) + }; + + if !nt_success(nt_status) { + println!("WdfIoQueueCreate failed {nt_status:#010X}"); + return nt_status; + } + + // Get our Driver Context memory from the returned Queue handle + let queue_context: *mut QueueContext = unsafe { queue_get_context(queue as WDFOBJECT) }; + unsafe { + (*queue_context).buffer = core::ptr::null_mut(); + (*queue_context).current_request = core::ptr::null_mut(); + (*queue_context).current_status = STATUS_INVALID_DEVICE_REQUEST; + } + + // Create the SpinLock. + let mut attributes = WDF_OBJECT_ATTRIBUTES { + Size: core::mem::size_of::() as ULONG, + ExecutionLevel: _WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent, + SynchronizationScope: _WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent, + ParentObject: queue as WDFOBJECT, + ..WDF_OBJECT_ATTRIBUTES::default() + }; + + match wdf::SpinLock::create(&mut attributes) { + Err(status) => { + println!("SpinLock create failed {nt_status:#010X}"); + return status; + } + Ok(spin_lock) => unsafe { (*queue_context).spin_lock = spin_lock }, + }; + + // Create the Queue timer + // + // By not setting the synchronization scope and using the default at + // WdfIoQueueCreate, we are explicitly *not* serializing against the queue's + // lock. Instead, we will do that on our own. + let mut timer_config = WDF_TIMER_CONFIG { + Size: core::mem::size_of::() as ULONG, + EvtTimerFunc: Some(echo_evt_timer_func), + Period: TIMER_PERIOD, + AutomaticSerialization: true as u8, + TolerableDelay: 0, + ..WDF_TIMER_CONFIG::default() + }; + + match wdf::Timer::create(&mut timer_config, &mut attributes) { + Err(status) => { + println!("Timer create failed {nt_status:#010X}"); + return status; + } + Ok(wdftimer) => unsafe { (*queue_context).timer = wdftimer }, + }; + + STATUS_SUCCESS +} + +/// This is called when the Queue that our driver context memory +/// is associated with is destroyed. +/// +/// # Arguments: +/// +/// * `object` - Queue object to be freed. +/// +/// # Return value: +/// +/// * `VOID` +extern "C" fn echo_evt_io_queue_context_destroy(object: WDFOBJECT) { + let queue_context = unsafe { queue_get_context(object) }; + // Release any resources pointed to in the queue context. + // + // The body of the queue context will be released after + // this callback handler returns + + // If Queue context has an I/O buffer, release it + unsafe { + if !(*queue_context).buffer.is_null() { + ExFreePool((*queue_context).buffer); + (*queue_context).buffer = core::ptr::null_mut(); + } + } +} + +/// Decrements the cancel ownership count for the request. When the count +/// reaches zero ownership has been acquired. +/// +/// # Arguments: +/// +/// * `request_context` - the context which holds the count. +/// +/// # Return value: +/// +/// * TRUE if the caller can complete the request, FALSE otherwise +fn echo_decrement_request_cancel_ownership_count(request_context: *mut RequestContext) -> bool { + let result = unsafe { + (*request_context) + .cancel_completion_ownership_count + .fetch_sub(1, Ordering::SeqCst) + }; + + result - 1 == 0 +} + +/// Attempts to increment the request ownership count so that it cannot be +/// completed until the count has been decremented +/// +/// # Arguments: +/// +/// * `request_context` - the context which holds the count. +/// +/// # Return value: +/// +/// * TRUE if the count was incremented, FALSE otherwise +fn echo_increment_request_cancel_ownership_count(request_context: *mut RequestContext) -> bool { + // See comments in echo_interlocked_increment_floor as to why <= 1 is failure + // + (unsafe { + echo_interlocked_increment_gtzero(&(*request_context).cancel_completion_ownership_count) + }) > 1 +} + +/// Called when an I/O request is cancelled after the driver has marked +/// the request cancellable. This callback is not automatically synchronized +/// with the I/O callbacks since we have chosen not to use frameworks Device +/// or Queue level locking. +/// +/// # Arguments: +/// +/// * `request` - Request being cancelled. +/// +/// # Return value: +/// +/// * `VOID` +extern "C" fn echo_evt_request_cancel(request: WDFREQUEST) { + let queue = unsafe { macros::call_unsafe_wdf_function_binding!(WdfRequestGetIoQueue, request) }; + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + let request_context = unsafe { request_get_context(request as WDFOBJECT) }; + + println!("echo_evt_request_cancel called on Request {:?}", request); + + // This book keeping is synchronized by the common + // Queue presentation lock which we are now acquiring + unsafe { (*queue_context).spin_lock.acquire() }; + + let complete_request: bool = echo_decrement_request_cancel_ownership_count(request_context); + + if complete_request { + unsafe { + (*queue_context).current_request = core::ptr::null_mut(); + } + } else { + unsafe { + (*queue_context).current_status = STATUS_CANCELLED; + } + } + + unsafe { (*queue_context).spin_lock.release() }; + + // Complete the request outside of holding any locks + if complete_request { + let [()] = [unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfRequestCompleteWithInformation, + request, + STATUS_CANCELLED, + 0 + ) + }]; + } +} + +/// Setup the request, intialize its context and mark it as cancelable. +/// +/// # Arguments: +/// +/// * `request` - Request being set up. +/// * `queue` - Queue associated with the request +/// +/// # Return value: +/// +/// * `VOID` +fn echo_set_current_request(request: WDFREQUEST, queue: WDFQUEUE) { + let status: NTSTATUS; + let request_context = unsafe { request_get_context(request as WDFOBJECT) }; + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + + // Set the ownership count to one. When a caller wants to claim ownership, + // they will interlock decrement the count. When the count reaches zero, + // ownership has been acquired and the caller may complete the request. + unsafe { + (*request_context).cancel_completion_ownership_count = AtomicI32::new(1); + } + + // Defer the completion to another thread from the timer dpc + unsafe { (*queue_context).spin_lock.acquire() }; + unsafe { + (*queue_context).current_request = request; + (*queue_context).current_status = STATUS_SUCCESS; + } + + // Set the cancel routine under the lock, otherwise if we set it outside + // of the lock, the timer could run and attempt to mark the request + // uncancelable before we can mark it cancelable on this thread. Use + // WdfRequestMarkCancelableEx here to prevent to deadlock with ourselves + // (cancel routine tries to acquire the queue object lock). + unsafe { + status = macros::call_unsafe_wdf_function_binding!( + WdfRequestMarkCancelableEx, + request, + Some(echo_evt_request_cancel) + ); + if !nt_success(status) { + (*queue_context).current_request = core::ptr::null_mut(); + } + } + + unsafe { (*queue_context).spin_lock.release() }; + + unsafe { + // Complete the request with an error when unable to mark it cancelable. + if !nt_success(status) { + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestCompleteWithInformation, + request, + status, + 0 + )]; + } + } +} + +/// This event is called when the framework receives IRP_MJ_READ request. +/// It will copy the content from the queue-context buffer to the request +/// buffer. If the driver hasn't received any write request earlier, the read +/// returns zero. +/// +/// # Arguments: +/// +/// * `queue` - Handle to the framework queue object that is associated with the +/// I/O request. +/// * `request` - Handle to a framework request object. +/// * `length` - number of bytes to be read. The default property of the queue +/// is to not dispatch zero lenght read & write requests to the driver and +/// complete is with status success. So we will never get a zero length +/// request. +/// +/// # Return value: +/// +/// * `VOID` +extern "C" fn echo_evt_io_read(queue: WDFQUEUE, request: WDFREQUEST, mut length: usize) { + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + let mut memory = WDF_NO_HANDLE as WDFMEMORY; + let mut nt_status: NTSTATUS; + + println!( + "echo_evt_io_read called! queue {:?}, request {:?}, length {:?}", + queue, request, length + ); + + // No data to read + unsafe { + if (*queue_context).buffer.is_null() { + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestCompleteWithInformation, + request, + STATUS_SUCCESS, + 0, + )]; + return; + } + } + + // Read what we have + unsafe { + if (*queue_context).length < length { + length = (*queue_context).length; + } + } + + // Get the request memory + unsafe { + nt_status = macros::call_unsafe_wdf_function_binding!( + WdfRequestRetrieveOutputMemory, + request, + &mut memory + ); + + if !nt_success(nt_status) { + println!("echo_evt_io_read Could not get request memory buffer {nt_status:#010X}"); + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestCompleteWithInformation, + request, + nt_status, + 0 + )]; + return; + } + } + + // Copy the memory out + unsafe { + nt_status = macros::call_unsafe_wdf_function_binding!( + WdfMemoryCopyFromBuffer, + memory, + 0, + (*queue_context).buffer, + length + ); + + if !nt_success(nt_status) { + println!("echo_evt_io_read: WdfMemoryCopyFromBuffer failed {nt_status:#010X}"); + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestComplete, + request, + nt_status + )]; + return; + } + } + + // Set transfer information + let [()] = unsafe { + [macros::call_unsafe_wdf_function_binding!( + WdfRequestSetInformation, + request, + length as u64 + )] + }; + + // Mark the request is cancelable. This must be the last thing we do because + // the cancel routine can run immediately after we set it. This means that + // CurrentRequest and CurrentStatus must be initialized before we mark the + // request cancelable. + echo_set_current_request(request, queue); +} + +/// This event is invoked when the framework receives IRP_MJ_WRITE request. +/// This routine allocates memory buffer, copies the data from the request to +/// it, and stores the buffer pointer in the queue-context with the length +/// variable representing the buffers length. The actual completion of the +/// request is defered to the periodic timer dpc. +/// +/// # Arguments: +/// +/// * `queue` - Handle to the framework queue object that is associated with the +/// I/O request. +/// * `request` - Handle to a framework request object. +/// * `length` - number of bytes to be read. The default property of the queue +/// is to not dispatch zero lenght read & write requests to the driver and +/// complete is with status success. So we will never get a zero length +/// request. +/// +/// # Return value: +/// +/// * `VOID` +extern "C" fn echo_evt_io_write(queue: WDFQUEUE, request: WDFREQUEST, length: usize) { + let mut memory = WDF_NO_HANDLE as WDFMEMORY; + let mut status: NTSTATUS; + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + + println!( + "echo_evt_io_write called! queue {:?}, request {:?}, length {:?}", + queue, request, length + ); + + if length > MAX_WRITE_LENGTH { + let [()] = [unsafe { + println!( + "echo_evt_io_write Buffer Length to big {:?}, Max is {:?}", + length, MAX_WRITE_LENGTH + ); + macros::call_unsafe_wdf_function_binding!( + WdfRequestCompleteWithInformation, + request, + STATUS_BUFFER_OVERFLOW, + 0 + ) + }]; + } + + // Get the memory buffer + unsafe { + status = macros::call_unsafe_wdf_function_binding!( + WdfRequestRetrieveInputMemory, + request, + &mut memory + ); + if !nt_success(status) { + println!("echo_evt_io_write Could not get request memory buffer {status:#010X}"); + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestComplete, + request, + status + )]; + return; + } + } + + // Release previous buffer if set + unsafe { + if !(*queue_context).buffer.is_null() { + ExFreePool((*queue_context).buffer); + (*queue_context).buffer = core::ptr::null_mut(); + (*queue_context).length = 0; + } + + // FIXME: Memory Tag + (*queue_context).buffer = + ExAllocatePool2(POOL_FLAG_NON_PAGED, length as SIZE_T, 's' as u32); + if (*queue_context).buffer.is_null() { + println!( + "echo_evt_io_write Could not allocate {:?} byte buffer", + length + ); + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestComplete, + request, + STATUS_INSUFFICIENT_RESOURCES + )]; + return; + } + } + + // Copy the memory in + unsafe { + status = macros::call_unsafe_wdf_function_binding!( + WdfMemoryCopyToBuffer, + memory, + 0, + (*queue_context).buffer, + length + ); + + if !nt_success(status) { + println!("echo_evt_io_write WdfMemoryCopyToBuffer failed {status:#010X}"); + ExFreePool((*queue_context).buffer); + (*queue_context).buffer = core::ptr::null_mut(); + (*queue_context).length = 0; + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestComplete, + request, + status + )]; + return; + } + + (*queue_context).length = length; + } + + // Set transfer information + unsafe { + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestSetInformation, + request, + length as u64 + )]; + } + + // Mark the request is cancelable. This must be the last thing we do because + // the cancel routine can run immediately after we set it. This means that + // CurrentRequest and CurrentStatus must be initialized before we mark the + // request cancelable. + echo_set_current_request(request, queue); +} + +/// This is the TimerDPC the driver sets up to complete requests. +/// This function is registered when the WDFTIMER object is created. +/// +/// This function does *NOT* automatically synchronize with the I/O Queue +/// callbacks and cancel routine, we must do it ourself in the routine. +/// +/// # Arguments: +/// +/// * `timer` - Handle to a framework Timer object. +/// +/// # Return value: +/// +/// * `VOID` +unsafe extern "C" fn echo_evt_timer_func(timer: WDFTIMER) { + // Default to failure. status is initialized so that the compiler does not + // think we are using an uninitialized value when completing the request. + let mut status; + let mut cancel = false; + let complete_request; + let queue: WDFQUEUE; + let request: WDFREQUEST; + let mut request_context: *mut RequestContext = core::ptr::null_mut(); + unsafe { + queue = + macros::call_unsafe_wdf_function_binding!(WdfTimerGetParentObject, timer,) as WDFQUEUE; + } + let queue_context = unsafe { queue_get_context(queue as WDFOBJECT) }; + + // We must synchronize with the cancel routine which will be taking the + // request out of the context under this lock. + unsafe { (*queue_context).spin_lock.acquire() }; + unsafe { + request = (*queue_context).current_request; + } + if !request.is_null() { + request_context = unsafe { request_get_context(request as WDFOBJECT) }; + if echo_increment_request_cancel_ownership_count(request_context) { + cancel = true; + } else { + // What has happened is that the cancel routine has executed and + // has already claimed cancel ownership of the request, but has not + // yet acquired the object lock and cleared the CurrentRequest field + // in queueContext. In this case, do nothing and let the cancel + // routine run to completion and complete the request. + } + } + + unsafe { (*queue_context).spin_lock.release() }; + + // If we could not claim cancel ownership, we are done. + if !cancel { + return; + } + + // The request handle and requestContext are valid until we release + // the cancel ownership count we already acquired. + unsafe { + status = macros::call_unsafe_wdf_function_binding!(WdfRequestUnmarkCancelable, request,); + if status != STATUS_CANCELLED { + println!( + "CustomTimerDPC successfully cleared cancel routine on request {:?}, status {:?}", + request, status + ); + + // Since we successfully removed the cancel routine (and we are not + // currently racing with it), there is no need to use an interlocked + // decrement to lower the cancel ownership count. + + // 2 is the initial count we set when we initialized + // CancelCompletionOwnershipCount plus the call to + // EchoIncrementRequestCancelOwnershipCount() + (*request_context) + .cancel_completion_ownership_count + .fetch_sub(2, Ordering::SeqCst); + complete_request = true; + } else { + complete_request = echo_decrement_request_cancel_ownership_count(request_context); + + if complete_request { + println!( + "CustomTimerDPC Request {:?} is STATUS_CANCELLED, but claimed completion \ + ownership", + request + ); + } else { + println!( + "CustomTimerDPC Request {:?} is STATUS_CANCELLED, not completing", + request + ); + } + } + } + + if complete_request { + println!( + "CustomTimerDPC Completing request {:?}, status {:?}", + request, status + ); + + // Clear the current request out of the queue context and complete + // the request. + unsafe { (*queue_context).spin_lock.acquire() }; + unsafe { + (*queue_context).current_request = core::ptr::null_mut(); + status = (*queue_context).current_status; + } + unsafe { (*queue_context).spin_lock.release() }; + + unsafe { + let [()] = [macros::call_unsafe_wdf_function_binding!( + WdfRequestComplete, + request, + status + )]; + } + } +} diff --git a/general/echo/kmdf/driver/DriverSync/src/wdf_object_context.rs b/general/echo/kmdf/driver/DriverSync/src/wdf_object_context.rs new file mode 100644 index 0000000..ba91e54 --- /dev/null +++ b/general/echo/kmdf/driver/DriverSync/src/wdf_object_context.rs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// License: MIT OR Apache-2.0 + +use wdk_sys::{PCWDF_OBJECT_CONTEXT_TYPE_INFO, WDF_OBJECT_CONTEXT_TYPE_INFO}; + +#[repr(transparent)] +pub struct WDFObjectContextTypeInfo(WDF_OBJECT_CONTEXT_TYPE_INFO); +unsafe impl Sync for WDFObjectContextTypeInfo {} + +impl WDFObjectContextTypeInfo { + pub const fn new(inner: WDF_OBJECT_CONTEXT_TYPE_INFO) -> Self { + Self(inner) + } + + pub const fn get_unique_type(&self) -> PCWDF_OBJECT_CONTEXT_TYPE_INFO { + let inner = (self as *const Self).cast::(); + // SAFETY: This dereference is sound since the underlying + // WDF_OBJECT_CONTEXT_TYPE_INFO is guaranteed to have the same memory + // layout as WDFObjectContextTypeInfo since WDFObjectContextTypeInfo is + // declared as repr(transparent) + unsafe { *inner }.UniqueType + } +} + +macro_rules! wdf_get_context_type_info { + ($context_type:ident) => { + paste::paste! { + [].get_unique_type() + } + }; +} + +pub(crate) use wdf_get_context_type_info; + +macro_rules! wdf_declare_context_type_with_name { + ($context_type:ident , $casting_function:ident) => { + paste::paste! { + type [] = *mut $context_type; + + #[link_section = ".data"] + pub static []: crate::wdf_object_context::WDFObjectContextTypeInfo = crate::wdf_object_context::WDFObjectContextTypeInfo::new(WDF_OBJECT_CONTEXT_TYPE_INFO { + Size: core::mem::size_of::() as ULONG, + ContextName: concat!(stringify!($context_type),'\0').as_bytes().as_ptr().cast(), + ContextSize: core::mem::size_of::<$context_type>(), + UniqueType: core::ptr::addr_of!([]) as *const WDF_OBJECT_CONTEXT_TYPE_INFO, + EvtDriverGetUniqueContextType: None, + }); + + pub unsafe fn $casting_function(handle: WDFOBJECT) -> [] { + unsafe { + macros::call_unsafe_wdf_function_binding!( + WdfObjectGetTypedContextWorker, + handle, + crate::wdf_object_context::wdf_get_context_type_info!($context_type), + ).cast() + } + } + } + }; +} + +pub(crate) use wdf_declare_context_type_with_name; + +macro_rules! wdf_declare_context_type { + ($context_type:ident) => { + paste::paste! { + crate::wdf_object_context::wdf_declare_context_type_with_name!($context_type, []); + } + }; +} + +pub(crate) use wdf_declare_context_type; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..c6c2391 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,43 @@ +# https://rust-lang.github.io/rustfmt/ outlines all configuration options + +# Required to enable unstable features: https://github.com/rust-lang/rustfmt/issues/3387 +unstable_features = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3384 +condense_wildcard_suffixes = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3348 +format_code_in_doc_comments = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3354 +format_macro_matchers = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3353 +format_strings = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/5081 +hex_literal_case = "Upper" + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3361 +imports_layout = "HorizontalVertical" + +# Unstable: https://github.com/rust-lang/rustfmt/issues/4991 +imports_granularity = "Crate" + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3350 +normalize_comments = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3351 +normalize_doc_attributes = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/5083 +group_imports = "StdExternalCrate" + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3363 +reorder_impl_items = true + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3383 +version = "Two" + +# Unstable: https://github.com/rust-lang/rustfmt/issues/3347 +wrap_comments = true diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 0000000..38c0e60 --- /dev/null +++ b/taplo.toml @@ -0,0 +1,5 @@ +exclude = ["target/**"] + +[formatting] +# enable crlf endings since core.eol is CRLF by default on Windows +crlf = true