nusb-0.1.13/.cargo_vcs_info.json0000644000000001360000000000100120650ustar { "git": { "sha1": "a64dfbad2ecd1cd5037f25a8d6c4c9c0edeb03b0" }, "path_in_vcs": "" }nusb-0.1.13/.github/workflows/rust.yml000064400000000000000000000020751046102023000157760ustar 00000000000000name: Rust on: - push - pull_request env: CARGO_TERM_COLOR: always jobs: format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Cargo Fmt run: cargo fmt --all -- --check build: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] rust: ['stable', '1.74'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} override: true - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose build_android: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: targets: 'aarch64-linux-android, armv7-linux-androideabi' - name: build run: | cargo build --target aarch64-linux-android --all-features cargo build --target armv7-linux-androideabi --all-features nusb-0.1.13/.gitignore000064400000000000000000000000231046102023000126400ustar 00000000000000/target Cargo.lock nusb-0.1.13/Cargo.lock0000644000000300060000000000100100370ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "instant" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] [[package]] name = "io-kit-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" dependencies = [ "core-foundation-sys", "mach2", ] [[package]] name = "is-terminal" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", "windows-sys 0.59.0", ] [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "mach2" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nusb" version = "0.1.13" dependencies = [ "atomic-waker", "core-foundation", "core-foundation-sys", "env_logger", "futures-core", "futures-lite", "io-kit-sys", "libc", "log", "once_cell", "rustix", "slab", "windows-sys 0.48.0", ] [[package]] name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "waker-fn" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[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_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[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_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" nusb-0.1.13/Cargo.toml0000644000000053760000000000100100760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.74" name = "nusb" version = "0.1.13" authors = ["Kevin Mehall "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Cross-platform low-level access to USB devices in pure Rust" readme = "README.md" keywords = [ "usb", "hardware", ] categories = ["hardware-support"] license = "Apache-2.0 OR MIT" repository = "https://github.com/kevinmehall/nusb" [lib] name = "nusb" path = "src/lib.rs" [[example]] name = "blocking" path = "examples/blocking.rs" [[example]] name = "bulk" path = "examples/bulk.rs" [[example]] name = "control" path = "examples/control.rs" [[example]] name = "descriptors" path = "examples/descriptors.rs" [[example]] name = "detach" path = "examples/detach.rs" [[example]] name = "detach_claim" path = "examples/detach_claim.rs" [[example]] name = "hotplug" path = "examples/hotplug.rs" [[example]] name = "list" path = "examples/list.rs" [[example]] name = "string_descriptors" path = "examples/string_descriptors.rs" [dependencies.atomic-waker] version = "1.1.2" [dependencies.futures-core] version = "0.3.29" [dependencies.log] version = "0.4.20" [dependencies.once_cell] version = "1.18.0" [dependencies.slab] version = "0.4.9" [dev-dependencies.env_logger] version = "0.10.0" [dev-dependencies.futures-lite] version = "1.13.0" [target.'cfg(any(target_os="linux", target_os="android"))'.dependencies.libc] version = "0.2.155" [target.'cfg(any(target_os="linux", target_os="android"))'.dependencies.rustix] version = "0.38.17" features = [ "fs", "event", "net", ] [target.'cfg(target_os="macos")'.dependencies.core-foundation] version = "0.9.3" [target.'cfg(target_os="macos")'.dependencies.core-foundation-sys] version = "0.8.4" [target.'cfg(target_os="macos")'.dependencies.io-kit-sys] version = "0.4.0" [target.'cfg(target_os="windows")'.dependencies.windows-sys] version = "0.48.0" features = [ "Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Com", ] [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(fuzzing)"] nusb-0.1.13/Cargo.toml.orig000064400000000000000000000023051046102023000135440ustar 00000000000000[package] name = "nusb" version = "0.1.13" description = "Cross-platform low-level access to USB devices in pure Rust" categories = ["hardware-support"] keywords = ["usb", "hardware"] authors = ["Kevin Mehall "] edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://github.com/kevinmehall/nusb" rust-version = "1.74" [dependencies] atomic-waker = "1.1.2" futures-core = "0.3.29" log = "0.4.20" once_cell = "1.18.0" slab = "0.4.9" [dev-dependencies] env_logger = "0.10.0" futures-lite = "1.13.0" [target.'cfg(any(target_os="linux", target_os="android"))'.dependencies] rustix = { version = "0.38.17", features = ["fs", "event", "net"] } libc = "0.2.155" [target.'cfg(target_os="windows")'.dependencies] windows-sys = { version = "0.48.0", features = ["Win32_Devices_Usb", "Win32_Devices_DeviceAndDriverInstallation", "Win32_Foundation", "Win32_Devices_Properties", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Registry", "Win32_System_Com"] } [target.'cfg(target_os="macos")'.dependencies] core-foundation = "0.9.3" core-foundation-sys = "0.8.4" io-kit-sys = "0.4.0" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } nusb-0.1.13/LICENSE-APACHE000064400000000000000000000251371046102023000126110ustar 00000000000000 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. nusb-0.1.13/LICENSE-MIT000064400000000000000000000017771046102023000123250ustar 00000000000000Permission 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. nusb-0.1.13/README.md000064400000000000000000000021721046102023000121360ustar 00000000000000nusb ---- A new pure-Rust library for cross-platform low-level access to USB devices. [Documentation](https://docs.rs/nusb) ### Compared to [rusb](https://docs.rs/rusb/latest/rusb/) and [libusb](https://libusb.info/) * Pure Rust, no dependency on libusb or any other C library. * Async-first, while not requiring an async runtime like `tokio` or `async-std`. Still easily supports blocking with `futures_lite::block_on`. * No context object. You just open a device. There is a global event loop thread that is started when opening the first device. * Thinner layer over OS APIs, with less internal state. ### Current status * Support for Linux, Windows, macOS, and Android * Device listing and descriptor parsing * Transfers on control, bulk and interrupt endpoints * Device connect / disconnect events on all platforms * Used by [probe-rs](https://github.com/probe-rs/probe-rs), [packetry](https://packetry.readthedocs.io/en/latest/), [postcard-rpc](https://github.com/jamesmunns/postcard-rpc) and others, but still relatively new. Please test with your device and report issues. ### License MIT or Apache 2.0, at your option nusb-0.1.13/examples/blocking.rs000064400000000000000000000044151046102023000146350ustar 00000000000000use std::time::Duration; use nusb::transfer::{Control, ControlType, Recipient}; fn main() { env_logger::init(); let di = nusb::list_devices() .unwrap() .find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23) .expect("device should be connected"); println!("Device info: {di:?}"); let device = di.open().unwrap(); // Linux can make control transfers without claiming an interface #[cfg(any(target_os = "linux", target_os = "macos"))] { let result = device.control_out_blocking( Control { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, }, &[1, 2, 3, 4], Duration::from_secs(1), ); println!("{result:?}"); let mut buf = [0; 64]; let len = device .control_in_blocking( Control { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, }, &mut buf, Duration::from_secs(1), ) .unwrap(); println!("{result:?}, {data:?}", data = &buf[..len]); } // but we also provide an API on the `Interface` to support Windows let interface = device.claim_interface(0).unwrap(); let result = interface.control_out_blocking( Control { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, }, &[1, 2, 3, 4, 5], Duration::from_secs(1), ); println!("{result:?}"); let mut buf = [0; 64]; let len = interface .control_in_blocking( Control { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, }, &mut buf, Duration::from_secs(1), ) .unwrap(); println!("{data:?}", data = &buf[..len]); } nusb-0.1.13/examples/bulk.rs000064400000000000000000000015101046102023000137730ustar 00000000000000use futures_lite::future::block_on; use nusb::transfer::RequestBuffer; fn main() { env_logger::init(); let di = nusb::list_devices() .unwrap() .find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23) .expect("device should be connected"); println!("Device info: {di:?}"); let device = di.open().unwrap(); let interface = device.claim_interface(0).unwrap(); block_on(interface.bulk_out(0x02, Vec::from([1, 2, 3, 4, 5]))) .into_result() .unwrap(); let mut queue = interface.bulk_in_queue(0x81); loop { while queue.pending() < 8 { queue.submit(RequestBuffer::new(256)); } let result = block_on(queue.next_complete()); println!("{result:?}"); if result.status.is_err() { break; } } } nusb-0.1.13/examples/control.rs000064400000000000000000000034311046102023000145220ustar 00000000000000use futures_lite::future::block_on; use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient}; fn main() { env_logger::init(); let di = nusb::list_devices() .unwrap() .find(|d| d.vendor_id() == 0x59e3 && d.product_id() == 0x0a23) .expect("device should be connected"); println!("Device info: {di:?}"); let device = di.open().unwrap(); // Linux can make control transfers without claiming an interface #[cfg(any(target_os = "linux", target_os = "macos"))] { let result = block_on(device.control_out(ControlOut { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, data: &[1, 2, 3, 4], })); println!("{result:?}"); let result = block_on(device.control_in(ControlIn { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, length: 256, })); println!("{result:?}"); } // but we also provide an API on the `Interface` to support Windows let interface = device.claim_interface(0).unwrap(); let result = block_on(interface.control_out(ControlOut { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, data: &[1, 2, 3, 4], })); println!("{result:?}"); let result = block_on(interface.control_in(ControlIn { control_type: ControlType::Vendor, recipient: Recipient::Device, request: 0x81, value: 0x9999, index: 0x9999, length: 256, })); println!("{result:?}"); } nusb-0.1.13/examples/descriptors.rs000064400000000000000000000016571046102023000154130ustar 00000000000000use nusb::DeviceInfo; fn main() { env_logger::init(); for dev in nusb::list_devices().unwrap() { inspect_device(dev); } } fn inspect_device(dev: DeviceInfo) { println!( "Device {:03}.{:03} ({:04x}:{:04x}) {} {}", dev.bus_number(), dev.device_address(), dev.vendor_id(), dev.product_id(), dev.manufacturer_string().unwrap_or(""), dev.product_string().unwrap_or("") ); let dev = match dev.open() { Ok(dev) => dev, Err(e) => { println!("Failed to open device: {}", e); return; } }; match dev.active_configuration() { Ok(config) => println!("Active configuration is {}", config.configuration_value()), Err(e) => println!("Unknown active configuration: {e}"), } for config in dev.configurations() { println!("{config:#?}"); } println!(""); println!(""); } nusb-0.1.13/examples/detach.rs000064400000000000000000000007441046102023000142760ustar 00000000000000//! Detach the kernel driver for an FTDI device and then reattach it. use std::{thread::sleep, time::Duration}; fn main() { env_logger::init(); let di = nusb::list_devices() .unwrap() .find(|d| d.vendor_id() == 0x0403 && d.product_id() == 0x6001) .expect("device should be connected"); let device = di.open().unwrap(); device.detach_kernel_driver(0).unwrap(); sleep(Duration::from_secs(10)); device.attach_kernel_driver(0).unwrap(); } nusb-0.1.13/examples/detach_claim.rs000064400000000000000000000007771046102023000154510ustar 00000000000000//! Detach the kernel driver for an FTDI device, claim the USB interface, and //! then reattach it. use std::{thread::sleep, time::Duration}; fn main() { env_logger::init(); let di = nusb::list_devices() .unwrap() .find(|d| d.vendor_id() == 0x0403 && d.product_id() == 0x6010) .expect("device should be connected"); let device = di.open().unwrap(); let interface = device.detach_and_claim_interface(0).unwrap(); sleep(Duration::from_secs(1)); drop(interface); } nusb-0.1.13/examples/hotplug.rs000064400000000000000000000002551046102023000145250ustar 00000000000000use futures_lite::stream; fn main() { env_logger::init(); for event in stream::block_on(nusb::watch_devices().unwrap()) { println!("{:#?}", event); } } nusb-0.1.13/examples/list.rs000064400000000000000000000001731046102023000140150ustar 00000000000000fn main() { env_logger::init(); for dev in nusb::list_devices().unwrap() { println!("{:#?}", dev); } } nusb-0.1.13/examples/string_descriptors.rs000064400000000000000000000036201046102023000167710ustar 00000000000000use std::time::Duration; use nusb::{descriptors::language_id::US_ENGLISH, DeviceInfo}; fn main() { env_logger::init(); for dev in nusb::list_devices().unwrap() { inspect_device(dev); } } fn inspect_device(dev: DeviceInfo) { println!( "Device {:03}.{:03} ({:04x}:{:04x}) {} {}", dev.bus_number(), dev.device_address(), dev.vendor_id(), dev.product_id(), dev.manufacturer_string().unwrap_or(""), dev.product_string().unwrap_or("") ); let dev = match dev.open() { Ok(dev) => dev, Err(e) => { println!("Failed to open device: {}", e); return; } }; let timeout = Duration::from_millis(100); let dev_descriptor = dev.get_descriptor(0x01, 0, 0, timeout).unwrap(); if dev_descriptor.len() < 18 || dev_descriptor[0] as usize > dev_descriptor.len() || dev_descriptor[1] != 0x01 { println!(" Invalid device descriptor: {dev_descriptor:?}"); return; } let languages: Vec = dev .get_string_descriptor_supported_languages(timeout) .map(|i| i.collect()) .unwrap_or_default(); println!(" Languages: {languages:02x?}"); let language = languages.first().copied().unwrap_or(US_ENGLISH); let i_manufacturer = dev_descriptor[14]; if i_manufacturer != 0 { let s = dev.get_string_descriptor(i_manufacturer, language, timeout); println!(" Manufacturer({i_manufacturer}): {s:?}"); } let i_product = dev_descriptor[15]; if i_product != 0 { let s = dev.get_string_descriptor(i_product, language, timeout); println!(" Product({i_product}): {s:?}"); } let i_serial = dev_descriptor[16]; if i_serial != 0 { let s = dev.get_string_descriptor(i_serial, language, timeout); println!(" Serial({i_serial}): {s:?}"); } println!(""); } nusb-0.1.13/src/descriptors.rs000064400000000000000000000740041046102023000143600ustar 00000000000000//! Utilities for parsing USB descriptors. //! //! Descriptors are blocks of data that describe the functionality of a USB device. use std::{ collections::BTreeMap, fmt::{Debug, Display}, io::ErrorKind, iter, ops::Deref, }; use log::warn; use crate::{ transfer::{Direction, EndpointType}, Error, }; #[allow(dead_code)] pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01; pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18; pub(crate) const DESCRIPTOR_TYPE_CONFIGURATION: u8 = 0x02; pub(crate) const DESCRIPTOR_LEN_CONFIGURATION: u8 = 9; pub(crate) const DESCRIPTOR_TYPE_INTERFACE: u8 = 0x04; pub(crate) const DESCRIPTOR_LEN_INTERFACE: u8 = 9; pub(crate) const DESCRIPTOR_TYPE_ENDPOINT: u8 = 0x05; pub(crate) const DESCRIPTOR_LEN_ENDPOINT: u8 = 7; pub(crate) const DESCRIPTOR_TYPE_STRING: u8 = 0x03; /// USB defined language IDs for string descriptors. /// /// In practice, different language IDs are not used, /// and device string descriptors are only provided /// with [`language_id::US_ENGLISH`]. pub mod language_id { /// US English pub const US_ENGLISH: u16 = 0x0409; } /// A raw USB descriptor. /// /// Wraps a byte slice to provide access to the bytes of a descriptor by implementing `Deref` to `[u8]`, /// while also exposing the descriptor length and type. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Descriptor<'a>(&'a [u8]); impl<'a> Descriptor<'a> { /// Create a `Descriptor` from a buffer. /// /// Returns `None` if /// * the slice length is not at least 2. /// * the `bLength` field (first byte) is greater than the slice length. pub fn new(buf: &[u8]) -> Option { if buf.len() >= 2 && buf.len() >= buf[0] as usize { Some(Descriptor(buf)) } else { None } } /// Get the length field of the descriptor. #[doc(alias = "bLength")] pub fn descriptor_len(&self) -> usize { self.0[0] as usize } /// Get the type field of the descriptor. #[doc(alias = "bDescriptorType")] pub fn descriptor_type(&self) -> u8 { self.0[1] } } impl<'a> Deref for Descriptor<'a> { type Target = [u8]; fn deref(&self) -> &[u8] { self.0 } } /// An iterator over a sequence of USB descriptors. #[derive(Clone)] pub struct Descriptors<'a>(&'a [u8]); impl<'a> Descriptors<'a> { /// Get the concatenated bytes of the remaining descriptors. pub fn as_bytes(&self) -> &'a [u8] { self.0 } fn split_first(&self) -> Option<(&'a [u8], &'a [u8])> { if self.0.len() < 2 { return None; } if self.0[0] < 2 { warn!( "descriptor with bLength {} can't point to next descriptor", self.0[0] ); return None; } if self.0[0] as usize > self.0.len() { warn!( "descriptor with bLength {} exceeds remaining buffer length {}", self.0[0], self.0.len() ); return None; } Some(self.0.split_at(self.0[0] as usize)) } fn split_by_type(mut self, descriptor_type: u8, min_len: u8) -> impl Iterator { iter::from_fn(move || { loop { let (_, next) = self.split_first()?; if self.0[1] == descriptor_type { if self.0[0] >= min_len { break; } else { warn!("ignoring descriptor of type {} and length {} because the minimum length is {}", self.0[1], self.0[0], min_len); } } self.0 = next; } let mut end = self.0[0] as usize; while self.0.len() >= end + 2 && self.0[end] > 2 && self.0[end + 1] != descriptor_type && self.0.len() >= end + self.0[end] as usize { end += self.0[end] as usize; } let (r, next) = self.0.split_at(end); self.0 = next; Some(r) }) } } impl<'a> Iterator for Descriptors<'a> { type Item = Descriptor<'a>; fn next(&mut self) -> Option { if let Some((cur, next)) = self.split_first() { self.0 = next; Some(Descriptor(cur)) } else { None } } } macro_rules! descriptor_fields { (impl<'a> $tname:ident<'a> { $( $(#[$attr:meta])* $vis:vis fn $name:ident at $pos:literal -> $ty:ty; )* }) => { impl<'a> $tname<'a> { $( $(#[$attr])* #[inline] $vis fn $name(&self) -> $ty { <$ty>::from_le_bytes(self.0[$pos..$pos + std::mem::size_of::<$ty>()].try_into().unwrap()) } )* } } } pub(crate) fn validate_config_descriptor(buf: &[u8]) -> Option { if buf.len() < DESCRIPTOR_LEN_CONFIGURATION as usize { if buf.len() != 0 { warn!( "config descriptor buffer is {} bytes, need {}", buf.len(), DESCRIPTOR_LEN_CONFIGURATION ); } return None; } if buf[0] < DESCRIPTOR_LEN_CONFIGURATION { warn!("invalid config descriptor bLength"); return None; } if buf[1] != DESCRIPTOR_TYPE_CONFIGURATION { warn!( "config bDescriptorType is {}, not a configuration descriptor", buf[0] ); return None; } let total_len = u16::from_le_bytes(buf[2..4].try_into().unwrap()) as usize; if total_len < buf[0] as usize || total_len > buf.len() { warn!( "invalid config descriptor wTotalLen of {total_len} (buffer size is {bufsize})", bufsize = buf.len() ); return None; } Some(total_len) } /// Information about a USB configuration with access to all associated interfaces, endpoints, and other descriptors. #[derive(Clone)] pub struct Configuration<'a>(&'a [u8]); impl<'a> Configuration<'a> { /// Create a `Configuration` from a buffer containing a series of descriptors. /// /// You normally obtain a `Configuration` from a [`Device`][crate::Device], but this allows creating /// one from your own descriptor bytes for tests. /// /// ### Panics /// * when the buffer is too short for a configuration descriptor /// * when the bLength and wTotalLength fields are longer than the buffer /// * when the first descriptor is not a configuration descriptor pub fn new(buf: &[u8]) -> Configuration { assert!(buf.len() >= DESCRIPTOR_LEN_CONFIGURATION as usize); assert!(buf[0] as usize >= DESCRIPTOR_LEN_CONFIGURATION as usize); assert!(buf[1] == DESCRIPTOR_TYPE_CONFIGURATION); assert!(buf.len() == u16::from_le_bytes(buf[2..4].try_into().unwrap()) as usize); Configuration(buf) } /// Get the configuration descriptor followed by all trailing interface and other descriptors. pub fn descriptors(&self) -> Descriptors<'a> { Descriptors(self.0) } /// Iterate all interfaces and alternate settings settings of this configuration. pub fn interface_alt_settings(&self) -> impl Iterator> { self.descriptors() .split_by_type(DESCRIPTOR_TYPE_INTERFACE, DESCRIPTOR_LEN_INTERFACE) .map(InterfaceAltSetting) } /// Iterate the interfaces of this configuration, grouping together alternate settings of the same interface. pub fn interfaces(&self) -> impl Iterator> { let mut interfaces = BTreeMap::new(); for intf in self.interface_alt_settings() { interfaces .entry(intf.interface_number()) .or_insert_with(Vec::new) .push(intf); } interfaces .into_iter() .map(|(intf_number, interfaces)| InterfaceGroup { intf_number, interfaces, }) } } descriptor_fields! { impl<'a> Configuration<'a> { /// `bNumInterfaces` descriptor field: Number of interfaces. #[doc(alias = "bNumInterfaces")] pub fn num_interfaces at 4 -> u8; /// `bConfigurationValue` descriptor field: Identifier for the configuration. /// /// Pass this value to /// [`Device::set_configuration`][crate::Device::set_configuration] to /// select this configuration. #[doc(alias = "bConfigurationValue")] pub fn configuration_value at 5 -> u8; fn string_index_raw at 6 -> u8; /// `bmAttributes` descriptor field: Bitmap of configuration attributes. #[doc(alias = "bmAttributes")] pub fn attributes at 7 -> u8; /// `bMaxPower` descriptor field: Maximum power, in units of **2** milliamps. #[doc(alias = "bMaxPower")] pub fn max_power at 8 -> u8; } } impl<'a> Configuration<'a> { /// Index of the string descriptor describing this configuration. #[doc(alias = "iConfiguration")] pub fn string_index(&self) -> Option { Some(self.string_index_raw()).filter(|&i| i != 0) } } struct DebugEntries(F); impl Debug for DebugEntries where F: Fn() -> I, I: Iterator, I::Item: Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.0()).finish() } } impl<'a> Debug for Configuration<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Configuration") .field("configuration_value", &self.configuration_value()) .field("num_interfaces", &self.num_interfaces()) .field("attributes", &self.attributes()) .field("max_power", &self.max_power()) .field("string_index", &self.string_index()) .field( "interface_alt_settings", &DebugEntries(|| self.interface_alt_settings()), ) .finish() } } /// Interface descriptors for alternate settings, grouped by the interface number. #[derive(Clone)] pub struct InterfaceGroup<'a> { intf_number: u8, interfaces: Vec>, } impl<'a> InterfaceGroup<'a> { /// `bInterfaceNumber` descriptor field: Identifier for the interface. /// /// Pass this to [`Device::claim_interface`][crate::Device::claim_interface] to work with the interface. #[doc(alias = "bInterfaceNumber")] pub fn interface_number(&self) -> u8 { self.intf_number } /// Iterator over alternate settings of the interface. pub fn alt_settings(&self) -> impl Iterator { self.interfaces.iter().cloned() } } /// Information about a USB interface alternate setting, with access to associated endpoints and other descriptors. /// /// An interface descriptor represents a single alternate setting of /// an interface. Multiple interface descriptors with the same [`interface_number`][Self::interface_number] /// but different [`alternate_setting`][Self::alternate_setting] values represent different alternate settings. #[derive(Clone)] pub struct InterfaceAltSetting<'a>(&'a [u8]); impl<'a> InterfaceAltSetting<'a> { /// Get the interface descriptor followed by all trailing endpoint and other /// descriptors up to the next interface descriptor. pub fn descriptors(&self) -> Descriptors { Descriptors(self.0) } /// Get the endpoints of this interface. pub fn endpoints(&self) -> impl Iterator { self.descriptors() .split_by_type(DESCRIPTOR_TYPE_ENDPOINT, DESCRIPTOR_LEN_ENDPOINT) .map(Endpoint) } } descriptor_fields! { impl<'a> InterfaceAltSetting<'a> { /// `bInterfaceNumber` descriptor field: Identifier for the interface. /// /// Pass this to [`Device::claim_interface`][crate::Device::claim_interface] to work with the interface. #[doc(alias="bInterfaceNumber")] pub fn interface_number at 2 -> u8; /// `bAlternateSetting` descriptor field: Identifier for this alternate setting. /// /// Pass this to [`Interface::set_alt_setting`][crate::Interface::set_alt_setting] to use this alternate setting. #[doc(alias="bAlternateSetting")] pub fn alternate_setting at 3 -> u8; /// `bNumEndpoints` descriptor field: Number of endpoints in this alternate setting. #[doc(alias="bNumEndpoints")] pub fn num_endpoints at 4 -> u8; /// `bInterfaceClass` descriptor field: Standard interface class. #[doc(alias="bInterfaceClass")] pub fn class at 5 -> u8; /// `bInterfaceSubClass` descriptor field: Standard interface subclass. #[doc(alias="bInterfaceSubClass")] pub fn subclass at 6 -> u8; /// `bInterfaceProtocol` descriptor field: Standard interface protocol. #[doc(alias="bInterfaceProtocol")] pub fn protocol at 7 -> u8; fn string_index_raw at 8 -> u8; } } impl<'a> InterfaceAltSetting<'a> { /// Index of the string descriptor describing this interface or alternate setting. #[doc(alias = "iInterface")] pub fn string_index(&self) -> Option { Some(self.string_index_raw()).filter(|&i| i != 0) } } impl<'a> Debug for InterfaceAltSetting<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InterfaceAltSetting") .field("interface_number", &self.interface_number()) .field("alternate_setting", &self.alternate_setting()) .field("num_endpoints", &self.num_endpoints()) .field("class", &self.class()) .field("subclass", &self.subclass()) .field("protocol", &self.protocol()) .field("string_index", &self.string_index()) .field("endpoints", &DebugEntries(|| self.endpoints())) .finish() } } /// Information about a USB endpoint, with access to any associated descriptors. pub struct Endpoint<'a>(&'a [u8]); impl<'a> Endpoint<'a> { /// Get the endpoint descriptor followed by all trailing descriptors up to the next endpoint or interface descriptor. pub fn descriptors(&self) -> impl Iterator { Descriptors(self.0) } /// Get the endpoint's direction. pub fn direction(&self) -> Direction { match self.address() & 0x80 { 0 => Direction::Out, _ => Direction::In, } } /// Get the endpoint's transfer type. pub fn transfer_type(&self) -> EndpointType { match self.attributes() & 0x03 { 0 => EndpointType::Control, 1 => EndpointType::Isochronous, 2 => EndpointType::Bulk, 3 => EndpointType::Interrupt, _ => unreachable!(), } } /// Get the maximum packet size in bytes. pub fn max_packet_size(&self) -> usize { (self.max_packet_size_raw() & ((1 << 11) - 1)) as usize } /// For isochronous endpoints at high speed, get the number of packets per microframe (1, 2, or 3). pub fn packets_per_microframe(&self) -> u8 { ((self.max_packet_size_raw() >> 11) & 0b11) as u8 + 1 } } descriptor_fields! { impl<'a> Endpoint<'a> { /// Get the `bEndpointAddress` descriptor field: Endpoint address. #[doc(alias = "bEndpointAddress")] pub fn address at 2 -> u8; /// Get the raw value of the `bmAttributes` descriptor field. /// /// See [`transfer_type``][Self::transfer_type] for the transfer type field. #[doc(alias = "bmAttributes")] pub fn attributes at 3 -> u8; /// Get the raw value of the `wMaxPacketSize` descriptor field. /// /// See [`max_macket_size`][Self::max_packet_size] and [`packets_per_microframe`][Self::packets_per_microframe] /// for the parsed subfields. #[doc(alias = "wMaxPacketSize")] pub fn max_packet_size_raw at 4 -> u16; /// Get the `bInterval` field: Polling interval in frames or microframes. #[doc(alias = "bInterval")] pub fn interval at 6 -> u8; } } impl<'a> Debug for Endpoint<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Endpoint") .field("address", &format_args!("0x{:02X}", self.address())) .field("direction", &self.direction()) .field("transfer_type", &self.transfer_type()) .field("max_packet_size", &self.max_packet_size()) .field("packets_per_microframe", &self.packets_per_microframe()) .field("interval", &self.interval()) .finish() } } /// Error from [`crate::Device::active_configuration`] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ActiveConfigurationError { pub(crate) configuration_value: u8, } impl Display for ActiveConfigurationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.configuration_value == 0 { write!(f, "device is not configured") } else { write!( f, "no descriptor found for active configuration {}", self.configuration_value ) } } } impl std::error::Error for ActiveConfigurationError {} impl From for Error { fn from(value: ActiveConfigurationError) -> Self { Error::new(ErrorKind::Other, value) } } /// Split a chain of concatenated configuration descriptors by `wTotalLength` pub(crate) fn parse_concatenated_config_descriptors(mut buf: &[u8]) -> impl Iterator { iter::from_fn(move || { let total_len = validate_config_descriptor(buf)?; let descriptors = &buf[..total_len]; buf = &buf[total_len..]; Some(descriptors) }) } pub(crate) fn validate_string_descriptor(data: &[u8]) -> bool { data.len() >= 2 && data[0] as usize == data.len() && data[1] == DESCRIPTOR_TYPE_STRING } pub(crate) fn decode_string_descriptor(data: &[u8]) -> Result { if !validate_string_descriptor(data) { return Err(()); } Ok(char::decode_utf16( data[2..] .chunks_exact(2) .map(|c| u16::from_le_bytes(c.try_into().unwrap())), ) .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) .collect::()) } /// Make public when fuzzing #[cfg(fuzzing)] pub fn fuzz_parse_concatenated_config_descriptors(buf: &[u8]) -> impl Iterator { parse_concatenated_config_descriptors(buf) } #[cfg(test)] mod test_concatenated { use super::parse_concatenated_config_descriptors; #[test] fn test_empty() { assert_eq!( parse_concatenated_config_descriptors(&[]).collect::>(), Vec::<&[u8]>::new() ); } #[test] fn test_short() { assert_eq!( parse_concatenated_config_descriptors(&[0]).collect::>(), Vec::<&[u8]>::new() ); } #[test] fn test_invalid_total_len() { assert_eq!( parse_concatenated_config_descriptors(&[9, 2, 0, 0, 0, 0, 0, 0, 0]) .collect::>(), Vec::<&[u8]>::new() ); } #[test] fn test_one_config() { assert_eq!( parse_concatenated_config_descriptors(&[9, 2, 9, 0, 0, 0, 0, 0, 0]) .collect::>(), vec![&[9, 2, 9, 0, 0, 0, 0, 0, 0]] ); assert_eq!( parse_concatenated_config_descriptors(&[9, 2, 13, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0]) .collect::>(), vec![&[9, 2, 13, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0]] ); } #[test] fn test_two_configs() { assert_eq!( parse_concatenated_config_descriptors(&[ 9, 2, 13, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 9, 2, 9, 0, 0, 0, 0, 0, 0 ]) .collect::>(), vec![ [9, 2, 13, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0].as_slice(), [9, 2, 9, 0, 0, 0, 0, 0, 0].as_slice() ] ); } } #[test] fn test_empty_config() { let c = Configuration(&[9, 2, 9, 0, 0, 1, 0, 0, 250]); assert_eq!(c.num_interfaces(), 0); assert_eq!(c.configuration_value(), 1); assert_eq!(c.string_index(), None); assert_eq!(c.interfaces().count(), 0); } #[test] fn test_malformed() { let c = Configuration(&[9, 2, 0, 0, 0, 1, 0, 0, 2, 5, 250, 0, 0, 0]); assert!(c.interfaces().next().is_none()); } #[test] #[rustfmt::skip] fn test_linux_root_hub() { let c = Configuration(&[ 0x09, 0x02, 0x19, 0x00, 0x01, 0x01, 0x00, 0xe0, 0x00, 0x09, 0x04, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0x00, 0x07, 0x05, 0x81, 0x03, 0x04, 0x00, 0x0c ]); assert_eq!(c.num_interfaces(), 1); assert_eq!(c.configuration_value(), 1); assert_eq!(c.max_power(), 0); assert_eq!(c.interfaces().count(), 1); let interface = c.interfaces().next().unwrap(); assert_eq!(interface.interface_number(), 0); let mut alts = interface.alt_settings(); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 0); assert_eq!(alt.alternate_setting(), 0); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 9); assert_eq!(alt.subclass(), 0); assert_eq!(alt.protocol(), 0); assert_eq!(alt.endpoints().count(), 1); let endpoint = alt.endpoints().next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Interrupt); assert_eq!(endpoint.max_packet_size(), 4); assert_eq!(endpoint.interval(), 12); assert!(alts.next().is_none()); } #[test] #[rustfmt::skip] fn test_dell_webcam() { let c = Configuration(&[ 0x09, 0x02, 0xa3, 0x02, 0x02, 0x01, 0x00, 0x80, 0xfa, // unknown (skipped) 0x28, 0xff, 0x42, 0x49, 0x53, 0x54, 0x00, 0x01, 0x06, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x10, 0xd0, 0x07, 0xd2, 0x11, 0xf4, 0x01, 0xd3, 0x12, 0xf4, 0x01, 0xd4, 0x13, 0xf4, 0x01, 0xd5, 0x14, 0xd0, 0x07, 0xd6, 0x15, 0xf4, 0x01, // interface association 0x08, 0x0b, 0x00, 0x02, 0x0e, 0x03, 0x00, 0x05, // interface 0x09, 0x04, 0x00, 0x00, 0x01, 0x0e, 0x01, 0x00, 0x05, // VideoControl 0x0d, 0x24, 0x01, 0x00, 0x01, 0x67, 0x00, 0xc0, 0xe1, 0xe4, 0x00, 0x01, 0x01, // VideoControl 0x09, 0x24, 0x03, 0x05, 0x01, 0x01, 0x00, 0x04, 0x00, // VideoControl 0x1a, 0x24, 0x06, 0x03, 0x70, 0x33, 0xf0, 0x28, 0x11, 0x63, 0x2e, 0x4a, 0xba, 0x2c, 0x68, 0x90, 0xeb, 0x33, 0x40, 0x16, 0x08, 0x01, 0x02, 0x01, 0x9f, 0x00, // VideoControl 0x1a, 0x24, 0x06, 0x04, 0xc3, 0x85, 0xb8, 0x0f, 0xc2, 0x68, 0x47, 0x45, 0x90, 0xf7, 0x8f, 0x47, 0x57, 0x9d, 0x95, 0xfc, 0x08, 0x01, 0x03, 0x01, 0x0f, 0x00, // VideoControl 0x12, 0x24, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0e, 0x00, 0x20, // VideoControl 0x0b, 0x24, 0x05, 0x02, 0x01, 0x00, 0x00, 0x02, 0x7f, 0x17, 0x00, // Endpoint 0x07, 0x05, 0x83, 0x03, 0x10, 0x00, 0x06, // Class-specific endpoint info 0x05, 0x25, 0x03, 0x80, 0x00, // Interface 0x09, 0x04, 0x01, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, // Video Streaming 0x0f, 0x24, 0x01, 0x02, 0x85, 0x01, 0x81, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // Video streaming 0x0b, 0x24, 0x06, 0x01, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // Video streaming (12 more omitted) 0x06, 0x24, 0x0d, 0x01, 0x01, 0x04, // Interface 0x09, 0x04, 0x01, 0x01, 0x01, 0x0e, 0x02, 0x00, 0x00, // Endpoint 0x07, 0x05, 0x81, 0x05, 0x80, 0x00, 0x01, // Interface 0x09, 0x04, 0x01, 0x02, 0x01, 0x0e, 0x02, 0x00, 0x00, // Endpoint 0x07, 0x05, 0x81, 0x05, 0x00, 0x01, 0x01, // Interface 0x09, 0x04, 0x01, 0x03, 0x01, 0x0e, 0x02, 0x00, 0x00, // Endpoint 0x07, 0x05, 0x81, 0x05, 0x20, 0x03, 0x01, // Interface 0x09, 0x04, 0x01, 0x04, 0x01, 0x0e, 0x02, 0x00, 0x00, // Endpoint 0x07, 0x05, 0x81, 0x05, 0x20, 0x0b, 0x01, // Interface 0x09, 0x04, 0x01, 0x05, 0x01, 0x0e, 0x02, 0x00, 0x00, // Endpoint 0x07, 0x05, 0x81, 0x05, 0x20, 0x13, 0x01, // Interface 0x09, 0x04, 0x01, 0x06, 0x01, 0x0e, 0x02, 0x00, 0x00, // Endpoint 0x07, 0x05, 0x81, 0x05, 0x00, 0x14, 0x01 ]); assert_eq!(c.configuration_value(), 1); assert_eq!(c.num_interfaces(), 2); assert_eq!(c.max_power(), 250); let mut interfaces = c.interfaces(); let interface = interfaces.next().unwrap(); assert_eq!(interface.interface_number(), 0); let mut alts = interface.alt_settings(); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 0); assert_eq!(alt.alternate_setting(), 0); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 1); assert_eq!(alt.protocol(), 0); let mut descriptors = alt.descriptors(); assert_eq!(descriptors.next().unwrap().descriptor_type(), DESCRIPTOR_TYPE_INTERFACE); for _ in 0..6 { assert_eq!(descriptors.next().unwrap().descriptor_type(), 0x24); } assert_eq!(descriptors.next().unwrap().descriptor_type(), DESCRIPTOR_TYPE_ENDPOINT); assert_eq!(descriptors.next().unwrap().descriptor_type(), 0x25); assert!(descriptors.next().is_none()); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x83); assert_eq!(endpoint.transfer_type(), EndpointType::Interrupt); assert_eq!(endpoint.max_packet_size(), 16); assert_eq!(endpoint.descriptors().nth(1).unwrap().descriptor_type(), 0x25); assert!(endpoints.next().is_none()); assert!(alts.next().is_none()); let interface = interfaces.next().unwrap(); assert_eq!(interface.interface_number(), 1); let mut alts = interface.alt_settings(); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 0); assert_eq!(alt.num_endpoints(), 0); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); assert!(endpoints.next().is_none()); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 1); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Isochronous); assert_eq!(endpoint.max_packet_size(), 128); assert!(endpoints.next().is_none()); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 2); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Isochronous); assert_eq!(endpoint.max_packet_size(), 256); assert_eq!(endpoint.packets_per_microframe(), 1); assert!(endpoints.next().is_none()); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 3); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Isochronous); assert_eq!(endpoint.max_packet_size(), 800); assert_eq!(endpoint.packets_per_microframe(), 1); assert!(endpoints.next().is_none()); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 4); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Isochronous); assert_eq!(endpoint.max_packet_size(), 800); assert_eq!(endpoint.packets_per_microframe(), 2); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 5); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Isochronous); assert_eq!(endpoint.max_packet_size(), 800); assert_eq!(endpoint.packets_per_microframe(), 3); let alt = alts.next().unwrap(); assert_eq!(alt.interface_number(), 1); assert_eq!(alt.alternate_setting(), 6); assert_eq!(alt.num_endpoints(), 1); assert_eq!(alt.class(), 14); assert_eq!(alt.subclass(), 2); assert_eq!(alt.protocol(), 0); let mut endpoints = alt.endpoints(); let endpoint = endpoints.next().unwrap(); assert_eq!(endpoint.address(), 0x81); assert_eq!(endpoint.transfer_type(), EndpointType::Isochronous); assert_eq!(endpoint.max_packet_size(), 1024); assert_eq!(endpoint.packets_per_microframe(), 3); assert!(endpoints.next().is_none()); assert!(alts.next().is_none()); assert!(interfaces.next().is_none()); } nusb-0.1.13/src/device.rs000064400000000000000000000550161046102023000132600ustar 00000000000000use std::{io::ErrorKind, sync::Arc, time::Duration}; use log::error; use crate::{ descriptors::{ decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError, Configuration, InterfaceAltSetting, DESCRIPTOR_TYPE_STRING, }, platform, transfer::{ Control, ControlIn, ControlOut, EndpointType, Queue, RequestBuffer, TransferError, TransferFuture, }, DeviceInfo, Error, }; /// An opened USB device. /// /// Obtain a `Device` by calling [`DeviceInfo::open`]: /// /// ```no_run /// use nusb; /// let device_info = nusb::list_devices().unwrap() /// .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB) /// .expect("device not connected"); /// /// let device = device_info.open().expect("failed to open device"); /// let interface = device.claim_interface(0); /// ``` /// /// This type is reference-counted with an [`Arc`] internally, and can be cloned cheaply for /// use in multiple places in your program. The device is closed when all clones and all /// associated [`Interface`]s are dropped. /// /// Use [`.claim_interface(i)`][`Device::claim_interface`] to open an interface to submit /// transfers. #[derive(Clone)] pub struct Device { backend: Arc, } impl Device { pub(crate) fn open(d: &DeviceInfo) -> Result { let backend = platform::Device::from_device_info(d)?; Ok(Device { backend }) } /// Wraps a device that is already open. #[cfg(any(target_os = "android", target_os = "linux"))] pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result { Ok(Device { backend: platform::Device::from_fd(fd)?, }) } /// Open an interface of the device and claim it for exclusive use. pub fn claim_interface(&self, interface: u8) -> Result { let backend = self.backend.claim_interface(interface)?; Ok(Interface { backend }) } /// Detach kernel drivers and open an interface of the device and claim it for exclusive use. /// /// ### Platform notes /// This function can only detach kernel drivers on Linux. Calling on other platforms has /// the same effect as [`claim_interface`][`Device::claim_interface`]. pub fn detach_and_claim_interface(&self, interface: u8) -> Result { let backend = self.backend.detach_and_claim_interface(interface)?; Ok(Interface { backend }) } /// Detach kernel drivers for the specified interface. /// /// ### Platform notes /// This function can only detach kernel drivers on Linux. Calling on other platforms has /// no effect. pub fn detach_kernel_driver(&self, interface: u8) -> Result<(), Error> { #[cfg(target_os = "linux")] self.backend.detach_kernel_driver(interface)?; let _ = interface; Ok(()) } /// Attach kernel drivers for the specified interface. /// /// ### Platform notes /// This function can only attach kernel drivers on Linux. Calling on other platforms has /// no effect. pub fn attach_kernel_driver(&self, interface: u8) -> Result<(), Error> { #[cfg(target_os = "linux")] self.backend.attach_kernel_driver(interface)?; let _ = interface; Ok(()) } /// Get information about the active configuration. /// /// This returns cached data and does not perform IO. However, it can fail if the /// device is unconfigured, or if it can't find a configuration descriptor for /// the configuration reported as active by the OS. pub fn active_configuration(&self) -> Result { let active = self.backend.active_configuration_value(); self.configurations() .find(|c| c.configuration_value() == active) .ok_or_else(|| ActiveConfigurationError { configuration_value: active, }) } /// Get an iterator returning information about each configuration of the device. /// /// This returns cached data and does not perform IO. pub fn configurations(&self) -> impl Iterator { self.backend .configuration_descriptors() .map(Configuration::new) } /// Set the device configuration. /// /// The argument is the desired configuration's `bConfigurationValue` /// descriptor field from [`Configuration::configuration_value`] or `0` to /// unconfigure the device. /// /// ### Platform-specific notes /// * Not supported on Windows pub fn set_configuration(&self, configuration: u8) -> Result<(), Error> { self.backend.set_configuration(configuration) } /// Request a descriptor from the device. /// /// The `language_id` should be `0` unless you are requesting a string descriptor. /// /// ### Platform-specific details /// /// * On Windows, the timeout argument is ignored, and an OS-defined timeout is used. /// * On Windows, this does not wake suspended devices. Reading their /// descriptors will return an error. pub fn get_descriptor( &self, desc_type: u8, desc_index: u8, language_id: u16, timeout: Duration, ) -> Result, Error> { #[cfg(target_os = "windows")] { let _ = timeout; self.backend .get_descriptor(desc_type, desc_index, language_id) } #[cfg(not(target_os = "windows"))] { const STANDARD_REQUEST_GET_DESCRIPTOR: u8 = 0x06; use crate::transfer::{ControlType, Recipient}; let mut buf = vec![0; 4096]; let len = self.control_in_blocking( Control { control_type: ControlType::Standard, recipient: Recipient::Device, request: STANDARD_REQUEST_GET_DESCRIPTOR, value: ((desc_type as u16) << 8) | desc_index as u16, index: language_id, }, &mut buf, timeout, )?; buf.truncate(len); Ok(buf) } } /// Request the list of supported languages for string descriptors. /// /// ### Platform-specific details /// /// See notes on [`get_descriptor`][`Self::get_descriptor`]. pub fn get_string_descriptor_supported_languages( &self, timeout: Duration, ) -> Result, Error> { let data = self.get_descriptor(DESCRIPTOR_TYPE_STRING, 0, 0, timeout)?; if !validate_string_descriptor(&data) { error!("String descriptor language list read {data:?}, not a valid string descriptor"); return Err(Error::new( ErrorKind::InvalidData, "string descriptor data was invalid", )); } //TODO: Use array_chunks once stable let mut iter = data.into_iter().skip(2); Ok(std::iter::from_fn(move || { Some(u16::from_le_bytes([iter.next()?, iter.next()?])) })) } /// Request a string descriptor from the device. /// /// Almost all devices support only the language ID [`US_ENGLISH`][`crate::descriptors::language_id::US_ENGLISH`]. /// /// Unpaired UTF-16 surrogates will be replaced with `�`, like [`String::from_utf16_lossy`]. /// /// ### Platform-specific details /// /// See notes on [`get_descriptor`][`Self::get_descriptor`]. pub fn get_string_descriptor( &self, desc_index: u8, language_id: u16, timeout: Duration, ) -> Result { if desc_index == 0 { return Err(Error::new( ErrorKind::InvalidInput, "string descriptor index 0 is reserved for the language table", )); } let data = self.get_descriptor(DESCRIPTOR_TYPE_STRING, desc_index, language_id, timeout)?; decode_string_descriptor(&data) .map_err(|_| Error::new(ErrorKind::InvalidData, "string descriptor data was invalid")) } /// Reset the device, forcing it to re-enumerate. /// /// This `Device` will no longer be usable, and you should drop it and call /// [`super::list_devices`] to find and re-open it again. /// /// ### Platform-specific notes /// * Not supported on Windows pub fn reset(&self) -> Result<(), Error> { self.backend.reset() } /// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint. /// /// ### Platform-specific notes /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. /// * On Linux, this takes a device-wide lock, so if you have multiple threads, you /// are better off using the async methods. #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { self.backend.control_in_blocking(control, data, timeout) } /// Synchronously perform a single **OUT (host-to-device)** transfer on the default **control** endpoint. /// /// ### Platform-specific notes /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. /// * On Linux, this takes a device-wide lock, so if you have multiple threads, you /// are better off using the async methods. #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { self.backend.control_out_blocking(control, data, timeout) } /// Asynchronously submit a single **IN (device-to-host)** transfer on the default **control** endpoint. /// /// ### Example /// /// ```no_run /// use futures_lite::future::block_on; /// use nusb::transfer::{ ControlIn, ControlType, Recipient }; /// # fn main() -> Result<(), std::io::Error> { /// # let di = nusb::list_devices().unwrap().next().unwrap(); /// # let device = di.open().unwrap(); /// /// let data: Vec = block_on(device.control_in(ControlIn { /// control_type: ControlType::Vendor, /// recipient: Recipient::Device, /// request: 0x30, /// value: 0x0, /// index: 0x0, /// length: 64, /// })).into_result()?; /// # Ok(()) } /// ``` /// /// ### Platform-specific notes /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_in(&self, data: ControlIn) -> TransferFuture { let mut t = self.backend.make_control_transfer(); t.submit::(data); TransferFuture::new(t) } /// Submit a single **OUT (host-to-device)** transfer on the default **control** endpoint. /// /// ### Example /// /// ```no_run /// use futures_lite::future::block_on; /// use nusb::transfer::{ ControlOut, ControlType, Recipient }; /// # fn main() -> Result<(), std::io::Error> { /// # let di = nusb::list_devices().unwrap().next().unwrap(); /// # let device = di.open().unwrap(); /// /// block_on(device.control_out(ControlOut { /// control_type: ControlType::Vendor, /// recipient: Recipient::Device, /// request: 0x32, /// value: 0x0, /// index: 0x0, /// data: &[0x01, 0x02, 0x03, 0x04], /// })).into_result()?; /// # Ok(()) } /// ``` /// /// ### Platform-specific notes /// /// * Not supported on Windows. You must [claim an interface][`Device::claim_interface`] /// and use the interface handle to submit transfers. #[cfg(any(target_os = "linux", target_os = "macos", target_os = "android"))] pub fn control_out(&self, data: ControlOut) -> TransferFuture { let mut t = self.backend.make_control_transfer(); t.submit::(data); TransferFuture::new(t) } } /// An opened interface of a USB device. /// /// Obtain an `Interface` with the [`Device::claim_interface`] method. /// /// This type is reference-counted with an [`Arc`] internally, and can be cloned cheaply for /// use in multiple places in your program. The interface is released when all clones, and all /// associated [`TransferFuture`]s and [`Queue`]s are dropped. #[derive(Clone)] pub struct Interface { backend: Arc, } impl Interface { /// Select the alternate setting of this interface. /// /// An alternate setting is a mode of the interface that makes particular endpoints available /// and may enable or disable functionality of the device. The OS resets the device to the default /// alternate setting when the interface is released or the program exits. pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> { self.backend.set_alt_setting(alt_setting) } /// Synchronously perform a single **IN (device-to-host)** transfer on the default **control** endpoint. /// /// ### Platform-specific notes /// /// * On Linux, this takes a device-wide lock, so if you have multiple /// threads, you are better off using the async methods. /// * On Windows, if the `recipient` is `Interface`, the WinUSB driver sends /// the interface number in the least significant byte of `index`, /// overriding any value passed. A warning is logged if the passed `index` /// least significant byte differs from the interface number, and this may /// become an error in the future. pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { self.backend.control_in_blocking(control, data, timeout) } /// Synchronously perform a single **OUT (host-to-device)** transfer on the default **control** endpoint. /// /// ### Platform-specific notes /// /// * On Linux, this takes a device-wide lock, so if you have multiple /// threads, you are better off using the async methods. /// * On Windows, if the `recipient` is `Interface`, the WinUSB driver sends /// the interface number in the least significant byte of `index`, /// overriding any value passed. A warning is logged if the passed `index` /// least significant byte differs from the interface number, and this may /// become an error in the future. pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { self.backend.control_out_blocking(control, data, timeout) } /// Submit a single **IN (device-to-host)** transfer on the default **control** endpoint. /// /// ### Example /// /// ```no_run /// use futures_lite::future::block_on; /// use nusb::transfer::{ ControlIn, ControlType, Recipient }; /// # fn main() -> Result<(), std::io::Error> { /// # let di = nusb::list_devices().unwrap().next().unwrap(); /// # let device = di.open().unwrap(); /// # let interface = device.claim_interface(0).unwrap(); /// /// let data: Vec = block_on(interface.control_in(ControlIn { /// control_type: ControlType::Vendor, /// recipient: Recipient::Device, /// request: 0x30, /// value: 0x0, /// index: 0x0, /// length: 64, /// })).into_result()?; /// # Ok(()) } /// ``` /// /// ### Platform-specific notes /// * On Windows, if the `recipient` is `Interface`, the WinUSB driver sends /// the interface number in the least significant byte of `index`, /// overriding any value passed. A warning is logged if the passed `index` /// least significant byte differs from the interface number, and this may /// become an error in the future. pub fn control_in(&self, data: ControlIn) -> TransferFuture { let mut t = self.backend.make_transfer(0, EndpointType::Control); t.submit::(data); TransferFuture::new(t) } /// Submit a single **OUT (host-to-device)** transfer on the default **control** endpoint. /// /// ### Example /// /// ```no_run /// use futures_lite::future::block_on; /// use nusb::transfer::{ ControlOut, ControlType, Recipient }; /// # fn main() -> Result<(), std::io::Error> { /// # let di = nusb::list_devices().unwrap().next().unwrap(); /// # let device = di.open().unwrap(); /// # let interface = device.claim_interface(0).unwrap(); /// /// block_on(interface.control_out(ControlOut { /// control_type: ControlType::Vendor, /// recipient: Recipient::Device, /// request: 0x32, /// value: 0x0, /// index: 0x0, /// data: &[0x01, 0x02, 0x03, 0x04], /// })).into_result()?; /// # Ok(()) } /// ``` /// /// ### Platform-specific notes /// * On Windows, if the `recipient` is `Interface`, the WinUSB driver sends /// the interface number in the least significant byte of `index`, /// overriding any value passed. A warning is logged if the passed `index` /// least significant byte differs from the interface number, and this may /// become an error in the future. pub fn control_out(&self, data: ControlOut) -> TransferFuture { let mut t = self.backend.make_transfer(0, EndpointType::Control); t.submit::(data); TransferFuture::new(t) } /// Submit a single **IN (device-to-host)** transfer on the specified **bulk** endpoint. /// /// * The requested length must be a multiple of the endpoint's maximum packet size /// * An IN endpoint address must have the top (`0x80`) bit set. pub fn bulk_in(&self, endpoint: u8, buf: RequestBuffer) -> TransferFuture { let mut t = self.backend.make_transfer(endpoint, EndpointType::Bulk); t.submit(buf); TransferFuture::new(t) } /// Submit a single **OUT (host-to-device)** transfer on the specified **bulk** endpoint. /// /// * An OUT endpoint address must have the top (`0x80`) bit clear. pub fn bulk_out(&self, endpoint: u8, buf: Vec) -> TransferFuture> { let mut t = self.backend.make_transfer(endpoint, EndpointType::Bulk); t.submit(buf); TransferFuture::new(t) } /// Create a queue for managing multiple **IN (device-to-host)** transfers on a **bulk** endpoint. /// /// * An IN endpoint address must have the top (`0x80`) bit set. pub fn bulk_in_queue(&self, endpoint: u8) -> Queue { Queue::new(self.backend.clone(), endpoint, EndpointType::Bulk) } /// Create a queue for managing multiple **OUT (host-to-device)** transfers on a **bulk** endpoint. /// /// * An OUT endpoint address must have the top (`0x80`) bit clear. pub fn bulk_out_queue(&self, endpoint: u8) -> Queue> { Queue::new(self.backend.clone(), endpoint, EndpointType::Bulk) } /// Submit a single **IN (device-to-host)** transfer on the specified **interrupt** endpoint. /// /// * The requested length must be a multiple of the endpoint's maximum packet size /// * An IN endpoint address must have the top (`0x80`) bit set. pub fn interrupt_in(&self, endpoint: u8, buf: RequestBuffer) -> TransferFuture { let mut t = self .backend .make_transfer(endpoint, EndpointType::Interrupt); t.submit(buf); TransferFuture::new(t) } /// Submit a single **OUT (host-to-device)** transfer on the specified **interrupt** endpoint. /// /// * An OUT endpoint address must have the top (`0x80`) bit clear. pub fn interrupt_out(&self, endpoint: u8, buf: Vec) -> TransferFuture> { let mut t = self .backend .make_transfer(endpoint, EndpointType::Interrupt); t.submit(buf); TransferFuture::new(t) } /// Create a queue for managing multiple **IN (device-to-host)** transfers on an **interrupt** endpoint. /// /// * An IN endpoint address must have the top (`0x80`) bit set. pub fn interrupt_in_queue(&self, endpoint: u8) -> Queue { Queue::new(self.backend.clone(), endpoint, EndpointType::Interrupt) } /// Create a queue for managing multiple **OUT (device-to-host)** transfers on an **interrupt** endpoint. /// /// * An OUT endpoint address must have the top (`0x80`) bit clear. pub fn interrupt_out_queue(&self, endpoint: u8) -> Queue> { Queue::new(self.backend.clone(), endpoint, EndpointType::Interrupt) } /// Clear a bulk or interrupt endpoint's halt / stall condition. /// /// Sends a `CLEAR_FEATURE` `ENDPOINT_HALT` control transfer to tell the /// device to reset the endpoint's data toggle and clear the halt / stall /// condition, and resets the host-side data toggle. /// /// Use this after receiving [`TransferError::Stall`] to clear the error and /// resume use of the endpoint. /// /// This should not be called when transfers are pending on the endpoint. pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> { self.backend.clear_halt(endpoint) } /// Get the interface number. pub fn interface_number(&self) -> u8 { self.backend.interface_number } /// Get the interface descriptors for the alternate settings of this interface. /// /// This returns cached data and does not perform IO. pub fn descriptors(&self) -> impl Iterator { let active = self.backend.device.active_configuration_value(); let configuration = self .backend .device .configuration_descriptors() .map(Configuration::new) .find(|c| c.configuration_value() == active); configuration .into_iter() .flat_map(|i| i.interface_alt_settings()) .filter(|g| g.interface_number() == self.backend.interface_number) } } #[test] fn assert_send_sync() { fn require_send_sync() {} require_send_sync::(); require_send_sync::(); } nusb-0.1.13/src/enumeration.rs000064400000000000000000000265611046102023000143520ustar 00000000000000#[cfg(target_os = "windows")] use std::ffi::{OsStr, OsString}; #[cfg(any(target_os = "linux", target_os = "android"))] use crate::platform::SysfsPath; use crate::{Device, Error}; /// Opaque device identifier #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct DeviceId(pub(crate) crate::platform::DeviceId); /// Information about a device that can be obtained without opening it. /// /// Found in the results of [`crate::list_devices`]. /// /// ### Platform-specific notes /// /// * Some fields are platform-specific /// * Linux: `sysfs_path` /// * Windows: `instance_id`, `parent_instance_id`, `port_number`, `driver` /// * macOS: `registry_id`, `location_id` #[derive(Clone)] pub struct DeviceInfo { #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) path: SysfsPath, #[cfg(target_os = "windows")] pub(crate) instance_id: OsString, #[cfg(target_os = "windows")] pub(crate) parent_instance_id: OsString, #[cfg(target_os = "windows")] pub(crate) port_number: u32, #[cfg(target_os = "windows")] pub(crate) devinst: crate::platform::DevInst, #[cfg(target_os = "windows")] pub(crate) driver: Option, #[cfg(target_os = "macos")] pub(crate) registry_id: u64, #[cfg(target_os = "macos")] pub(crate) location_id: u32, pub(crate) bus_number: u8, pub(crate) device_address: u8, pub(crate) vendor_id: u16, pub(crate) product_id: u16, pub(crate) device_version: u16, pub(crate) class: u8, pub(crate) subclass: u8, pub(crate) protocol: u8, pub(crate) speed: Option, pub(crate) manufacturer_string: Option, pub(crate) product_string: Option, pub(crate) serial_number: Option, pub(crate) interfaces: Vec, } impl DeviceInfo { /// Opaque identifier for the device. pub fn id(&self) -> DeviceId { #[cfg(target_os = "windows")] { DeviceId(self.devinst) } #[cfg(any(target_os = "linux", target_os = "android"))] { DeviceId(crate::platform::DeviceId { bus: self.bus_number, addr: self.device_address, }) } #[cfg(target_os = "macos")] { DeviceId(self.registry_id) } } /// *(Linux-only)* Sysfs path for the device. #[doc(hidden)] #[deprecated = "use `sysfs_path()` instead"] #[cfg(target_os = "linux")] pub fn path(&self) -> &SysfsPath { &self.path } /// *(Linux-only)* Sysfs path for the device. #[cfg(any(target_os = "linux", target_os = "android"))] pub fn sysfs_path(&self) -> &std::path::Path { &self.path.0 } /// *(Windows-only)* Instance ID path of this device #[cfg(target_os = "windows")] pub fn instance_id(&self) -> &OsStr { &self.instance_id } /// *(Windows-only)* Instance ID path of the parent hub #[cfg(target_os = "windows")] pub fn parent_instance_id(&self) -> &OsStr { &self.parent_instance_id } /// *(Windows-only)* Port number #[cfg(target_os = "windows")] pub fn port_number(&self) -> u32 { self.port_number } /// *(Windows-only)* Driver associated with the device as a whole #[cfg(target_os = "windows")] pub fn driver(&self) -> Option<&str> { self.driver.as_deref() } /// *(macOS-only)* IOKit Location ID #[cfg(target_os = "macos")] pub fn location_id(&self) -> u32 { self.location_id } /// *(macOS-only)* IOKit [Registry Entry ID](https://developer.apple.com/documentation/iokit/1514719-ioregistryentrygetregistryentryi?language=objc) #[cfg(target_os = "macos")] pub fn registry_entry_id(&self) -> u64 { self.registry_id } /// Number identifying the bus / host controller where the device is connected. pub fn bus_number(&self) -> u8 { self.bus_number } /// Number identifying the device within the bus. pub fn device_address(&self) -> u8 { self.device_address } /// The 16-bit number identifying the device's vendor, from the `idVendor` device descriptor field. #[doc(alias = "idVendor")] pub fn vendor_id(&self) -> u16 { self.vendor_id } /// The 16-bit number identifying the product, from the `idProduct` device descriptor field. #[doc(alias = "idProduct")] pub fn product_id(&self) -> u16 { self.product_id } /// The device version, normally encoded as BCD, from the `bcdDevice` device descriptor field. #[doc(alias = "bcdDevice")] pub fn device_version(&self) -> u16 { self.device_version } /// Code identifying the standard device class, from the `bDeviceClass` device descriptor field. /// /// `0x00`: specified at the interface level.\ /// `0xFF`: vendor-defined. #[doc(alias = "bDeviceClass")] pub fn class(&self) -> u8 { self.class } /// Standard subclass, from the `bDeviceSubClass` device descriptor field. #[doc(alias = "bDeviceSubClass")] pub fn subclass(&self) -> u8 { self.subclass } /// Standard protocol, from the `bDeviceProtocol` device descriptor field. #[doc(alias = "bDeviceProtocol")] pub fn protocol(&self) -> u8 { self.protocol } /// Connection speed pub fn speed(&self) -> Option { self.speed } /// Manufacturer string, if available without device IO. /// /// ### Platform-specific notes /// * Windows: Windows does not cache the manufacturer string, and /// this will return `None` regardless of whether a descriptor exists. #[doc(alias = "iManufacturer")] pub fn manufacturer_string(&self) -> Option<&str> { self.manufacturer_string.as_deref() } /// Product string, if available without device IO. #[doc(alias = "iProduct")] pub fn product_string(&self) -> Option<&str> { self.product_string.as_deref() } /// Serial number string, if available without device IO. #[doc(alias = "iSerial")] pub fn serial_number(&self) -> Option<&str> { self.serial_number.as_deref() } /// Iterator over the device's interfaces. /// /// This returns summary information about the interfaces in the device's /// active configuration for the purposes of matching devices prior to /// opening them. /// /// Additional information about interfaces can be found in the /// configuration descriptor after opening the device by calling /// [`Device::active_configuration`]. /// /// ### Platform-specific notes: /// * Windows: this is only available for composite devices bound to the /// `usbccgp` driver, and will be empty if the entire device is bound to /// a specific driver. /// * Windows: When interfaces are grouped by an interface /// association descriptor, this returns details from the interface /// association descriptor and does not include each of the associated /// interfaces. pub fn interfaces(&self) -> impl Iterator { self.interfaces.iter() } /// Open the device pub fn open(&self) -> Result { Device::open(self) } } // Not derived so that we can format some fields in hex impl std::fmt::Debug for DeviceInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("DeviceInfo"); s.field("bus_number", &self.bus_number) .field("device_address", &self.device_address) .field("vendor_id", &format_args!("0x{:04X}", self.vendor_id)) .field("product_id", &format_args!("0x{:04X}", self.product_id)) .field( "device_version", &format_args!("0x{:04X}", self.device_version), ) .field("class", &format_args!("0x{:02X}", self.class)) .field("subclass", &format_args!("0x{:02X}", self.subclass)) .field("protocol", &format_args!("0x{:02X}", self.protocol)) .field("speed", &self.speed) .field("manufacturer_string", &self.manufacturer_string) .field("product_string", &self.product_string) .field("serial_number", &self.serial_number); #[cfg(any(target_os = "linux", target_os = "android"))] { s.field("sysfs_path", &self.path); } #[cfg(target_os = "windows")] { s.field("instance_id", &self.instance_id); s.field("parent_instance_id", &self.parent_instance_id); s.field("port_number", &self.port_number); s.field("driver", &self.driver); } #[cfg(target_os = "macos")] { s.field("location_id", &format_args!("0x{:08X}", self.location_id)); s.field( "registry_entry_id", &format_args!("0x{:08X}", self.registry_id), ); } s.field("interfaces", &self.interfaces); s.finish() } } /// USB connection speed #[derive(Copy, Clone, Eq, PartialOrd, Ord, PartialEq, Hash, Debug)] #[non_exhaustive] pub enum Speed { /// Low speed (1.5 Mbit) Low, /// Full speed (12 Mbit) Full, /// High speed (480 Mbit) High, /// Super speed (5000 Mbit) Super, /// Super speed (10000 Mbit) SuperPlus, } impl Speed { #[allow(dead_code)] // not used on all platforms pub(crate) fn from_str(s: &str) -> Option { match s { "low" | "1.5" => Some(Speed::Low), "full" | "12" => Some(Speed::Full), "high" | "480" => Some(Speed::High), "super" | "5000" => Some(Speed::Super), "super+" | "10000" => Some(Speed::SuperPlus), _ => None, } } } /// Summary information about a device's interface, available before opening a device. #[derive(Clone)] pub struct InterfaceInfo { pub(crate) interface_number: u8, pub(crate) class: u8, pub(crate) subclass: u8, pub(crate) protocol: u8, pub(crate) interface_string: Option, } impl InterfaceInfo { /// Identifier for the interface from the `bInterfaceNumber` descriptor field. pub fn interface_number(&self) -> u8 { self.interface_number } /// Code identifying the standard interface class, from the `bInterfaceClass` interface descriptor field. pub fn class(&self) -> u8 { self.class } /// Standard subclass, from the `bInterfaceSubClass` interface descriptor field. pub fn subclass(&self) -> u8 { self.subclass } /// Standard protocol, from the `bInterfaceProtocol` interface descriptor field. pub fn protocol(&self) -> u8 { self.protocol } /// Interface string descriptor value as cached by the OS. pub fn interface_string(&self) -> Option<&str> { self.interface_string.as_deref() } } // Not derived so that we can format some fields in hex impl std::fmt::Debug for InterfaceInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InterfaceInfo") .field("interface_number", &self.interface_number) .field("class", &format_args!("0x{:02X}", self.class)) .field("subclass", &format_args!("0x{:02X}", self.subclass)) .field("protocol", &format_args!("0x{:02X}", self.protocol)) .field("interface_string", &self.interface_string) .finish() } } nusb-0.1.13/src/hotplug.rs000064400000000000000000000020521046102023000134730ustar 00000000000000//! Types for receiving notifications when USB devices are connected or //! disconnected from the system. //! //! See [`super::watch_devices`] for a usage example. use futures_core::Stream; use crate::{DeviceId, DeviceInfo}; /// Stream of device connection / disconnection events. /// /// Call [`super::watch_devices`] to begin watching device /// events and create a `HotplugWatch`. pub struct HotplugWatch(pub(crate) crate::platform::HotplugWatch); impl Stream for HotplugWatch { type Item = HotplugEvent; fn poll_next( mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { self.0.poll_next(cx).map(Some) } } /// Event returned from the [`HotplugWatch`] stream. #[derive(Debug)] pub enum HotplugEvent { /// A device has been connected. Connected(DeviceInfo), /// A device has been disconnected. Disconnected(DeviceId), } #[test] fn assert_send_sync() { fn require_send_sync() {} require_send_sync::(); } nusb-0.1.13/src/lib.rs000064400000000000000000000166501046102023000125700ustar 00000000000000#![warn(missing_docs)] //! A new library for cross-platform low-level access to USB devices. //! //! `nusb` is comparable to the C library [libusb] and its Rust bindings [rusb], //! but written in pure Rust. It's built on and exposes async APIs by default, //! but can be made blocking using [`futures_lite::future::block_on`][block_on] //! or similar. //! //! [libusb]: https://libusb.info //! [rusb]: https://docs.rs/rusb/ //! [block_on]: https://docs.rs/futures-lite/latest/futures_lite/future/fn.block_on.html //! //! Use `nusb` to write user-space drivers in Rust for non-standard USB devices //! or those without kernel support. For devices implementing a standard USB //! class such as Mass Storage, CDC (Serial), HID, Audio, or Video, this is //! probably not the library you're looking for -- use something built on the //! existing kernel driver instead. (On some platforms you could detach or //! replace the kernel driver and program the device from user-space using this //! library, but you'd have to re-implement the class functionality yourself.) //! //! ## USB and usage overview //! //! When a USB device connects, the OS queries the device descriptor containing //! basic device information such as the vendor and product ID (VID / PID) and //! string descriptors like the manufacturer, product, and serial number //! strings. [`list_devices`] returns an iterator listing connected USB devices, //! which can be filtered by these fields to identify and select the desired //! device. //! //! Call [`device_info.open()`](`DeviceInfo::open`) to open a selected device. //! Additional information about the device can be queried with //! [`device.active_configuration()`](`Device::active_configuration`). //! //! USB devices consist of one or more interfaces exposing a group of //! functionality. A device with multiple interfaces is known as a composite //! device. To open an interface, call [`Device::claim_interface`]. Only one //! program (or kernel driver) may claim an interface at a time. //! //! Use the resulting [`Interface`] to transfer data on the device's control, //! bulk or interrupt endpoints. Transfers are async by default, and can be //! awaited as individual [`Future`][`transfer::TransferFuture`]s, or use a //! [`Queue`][`transfer::Queue`] to manage streams of data. //! //! *For more details on how USB works, [USB in a //! Nutshell](https://beyondlogic.org/usbnutshell/usb1.shtml) is a good //! overview.* //! //! ## Logging //! //! `nusb` uses the [`log`](https://docs.rs/log) crate to log debug and error //! information. //! //! When [submitting a bug report][gh-issues], please include the logs: use a //! `log` backend like [`env_logger`](https://docs.rs/env_logger) and configure //! it to enable log output for this crate (for `env_logger` set environment //! variable `RUST_LOG=nusb=debug`.) //! //! [gh-issues]: https://github.com/kevinmehall/nusb/issues //! //! ## Platform support //! //! ### Linux //! //! `nusb` is built on the kernel's [usbfs] API. //! //! A user must have write access on the `/dev/bus/usb/XXX/YYY` nodes to //! successfully open a device. Use [udev rules] to configure these permissions. //! //! For a single-user system used for development, it may be desirable to give //! your user account access to all USB devices by placing the following in //! `/etc/udev/rules.d/70-plugdev-usb.rules`: //! //! ```not_rust //! SUBSYSTEM=="usb", MODE="0660", GROUP="plugdev" //! ``` //! //! This grants access on all USB devices to the `plugdev` group, which your //! user may be a member of by default on Debian/Ubuntu-based distros. If you //! are developing an app for others to install, you should scope the //! permissions more narrowly using the `ATTRS{idVendor}=="ZZZZ", //! ATTRS{idProduct}=="ZZZZ"` filters to only apply to your device. //! //! [usbfs]: //! https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#the-usb-character-device-nodes //! [udev rules]: https://www.reactivated.net/writing_udev_rules.html //! //! ### Windows //! //! `nusb` uses [WinUSB] on Windows. //! //! On Windows, devices are associated with a particular driver, which persists //! across connections and reboots. Composite devices appear as multiple devices //! in the Windows device model, and each interface can be associated with a //! separate driver. //! //! To use `nusb`, your device or interface must be associated with the `WinUSB` //! driver. If you control the device firmware, the recommended way is to use a //! [WCID] descriptor to tell Windows to install the WinUSB driver automatically //! when the device is first connected. Alternatively [Zadig] (GUI) or [libwdi] //! (CLI / C library) can be used to manually install the WinUSB driver for a //! device. //! //! [SetupAPI]: //! https://learn.microsoft.com/en-us/windows-hardware/drivers/install/setupapi //! [WinUSB]: https://learn.microsoft.com/en-us/windows/win32/api/winusb/ //! [WCID]: https://github.com/pbatard/libwdi/wiki/WCID-Devices //! [Zadig]:https://zadig.akeo.ie/ //! [libwdi]: https://github.com/pbatard/libwdi //! //! ### macOS //! //! `nusb` uses IOKit on macOS. //! //! Users have access to USB devices by default, with no permission configuration needed. //! Devices with a kernel driver are not accessible. use std::io; mod platform; pub mod descriptors; mod enumeration; pub use enumeration::{DeviceId, DeviceInfo, InterfaceInfo, Speed}; mod device; pub use device::{Device, Interface}; pub mod transfer; pub mod hotplug; /// OS error returned from operations other than transfers. pub type Error = io::Error; /// Get an iterator listing the connected devices. /// /// ### Example /// /// ```no_run /// use nusb; /// let device = nusb::list_devices().unwrap() /// .find(|dev| dev.vendor_id() == 0xAAAA && dev.product_id() == 0xBBBB) /// .expect("device not connected"); /// ``` /// /// ### Platform-specific notes /// * On Windows, hubs are not included in the list pub fn list_devices() -> Result, Error> { platform::list_devices() } /// Get a [`Stream`][`futures_core::Stream`] that yields an /// [event][`hotplug::HotplugEvent`] when a USB device is connected or /// disconnected from the system. /// /// Events will be returned for devices connected or disconnected beginning at /// the time this function is called. To maintain a list of connected devices, /// call [`list_devices`] after creating the watch with this function to avoid /// potentially missing a newly-attached device: /// /// ## Example /// /// ```no_run /// use std::collections::HashMap; /// use nusb::{DeviceInfo, DeviceId, hotplug::HotplugEvent}; /// let watch = nusb::watch_devices().unwrap(); /// let mut devices: HashMap = nusb::list_devices().unwrap() /// .map(|d| (d.id(), d)).collect(); /// for event in futures_lite::stream::block_on(watch) { /// match event { /// HotplugEvent::Connected(d) => { /// devices.insert(d.id(), d); /// } /// HotplugEvent::Disconnected(id) => { /// devices.remove(&id); /// } /// } /// } /// ``` /// /// ### Platform-specific notes: /// /// * On Windows, the interfaces of a composite device might not be ready /// when the `Connected` event is emitted. If you are immediately opening the device /// and claiming an interface when receiving a `Connected` event, /// you should retry after a short delay if opening or claiming fails. pub fn watch_devices() -> Result { Ok(hotplug::HotplugWatch(platform::HotplugWatch::new()?)) } nusb-0.1.13/src/platform/linux_usbfs/device.rs000064400000000000000000000357521046102023000174520ustar 00000000000000use std::io::{ErrorKind, Seek}; use std::{ffi::c_void, time::Duration}; use std::{ fs::File, io::Read, mem::ManuallyDrop, path::PathBuf, sync::{ atomic::{AtomicU8, Ordering}, Arc, }, }; use log::{debug, error, warn}; use rustix::event::epoll; use rustix::fd::AsFd; use rustix::{ fd::{AsRawFd, FromRawFd, OwnedFd}, fs::{Mode, OFlags}, io::Errno, }; use super::{ errno_to_transfer_error, events, usbfs::{self, Urb}, SysfsPath, }; use crate::platform::linux_usbfs::events::Watch; use crate::{ descriptors::{parse_concatenated_config_descriptors, Configuration, DESCRIPTOR_LEN_DEVICE}, transfer::{ notify_completion, Control, ControlType, Direction, EndpointType, Recipient, TransferError, TransferHandle, }, DeviceInfo, Error, }; pub(crate) struct LinuxDevice { fd: OwnedFd, events_id: usize, /// Read from the fd, consists of device descriptor followed by configuration descriptors descriptors: Vec, sysfs: Option, active_config: AtomicU8, } impl LinuxDevice { pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { let busnum = d.bus_number(); let devnum = d.device_address(); let active_config = d.path.read_attr("bConfigurationValue")?; let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}")); debug!("Opening usbfs device {}", path.display()); let fd = rustix::fs::open(path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())?; let inner = Self::create_inner(fd, Some(d.path.clone()), Some(active_config)); if inner.is_ok() { debug!("Opened device bus={busnum} addr={devnum}",); } inner } pub(crate) fn from_fd(fd: OwnedFd) -> Result, Error> { debug!("Wrapping fd {} as usbfs device", fd.as_raw_fd()); Self::create_inner(fd, None, None) } pub(crate) fn create_inner( fd: OwnedFd, sysfs: Option, active_config: Option, ) -> Result, Error> { let descriptors = { let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd.as_raw_fd())) }; // NOTE: Seek required on android file.seek(std::io::SeekFrom::Start(0))?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; buf }; let active_config = if let Some(active_config) = active_config { active_config } else { Self::get_config(&descriptors, &fd)? }; // because there's no Arc::try_new_cyclic let mut events_err = None; let arc = Arc::new_cyclic(|weak| { let res = events::register( fd.as_fd(), Watch::Device(weak.clone()), epoll::EventFlags::OUT, ); let events_id = *res.as_ref().unwrap_or(&usize::MAX); events_err = res.err(); if events_err.is_none() { debug!("Opened device fd={} with id {}", fd.as_raw_fd(), events_id,); } LinuxDevice { fd, events_id, descriptors, sysfs, active_config: AtomicU8::new(active_config), } }); if let Some(err) = events_err { error!("Failed to initialize event loop: {err}"); Err(err) } else { Ok(arc) } } pub(crate) fn handle_events(&self) { debug!("Handling events for device {}", self.events_id); match usbfs::reap_urb_ndelay(&self.fd) { Ok(urb_ptr) => { let user_data = { let urb = unsafe { &*urb_ptr }; debug!( "URB {:?} for ep {:x} completed, status={} actual_length={}", urb_ptr, urb.endpoint, urb.status, urb.actual_length ); urb.usercontext }; // SAFETY: pointer came from submit via kernel an we're now done with it unsafe { notify_completion::(user_data) } } Err(Errno::AGAIN) => {} Err(Errno::NODEV) => { debug!("Device {} disconnected", self.events_id); // epoll returns events continuously on a disconnected device, and REAPURB // only returns ENODEV after all events are received, so unregister to // keep the event thread from spinning because we won't receive further events. // The drop impl will try to unregister again, but that's ok. events::unregister_fd(self.fd.as_fd()); } Err(e) => { error!("Unexpected error {e} from REAPURBNDELAY"); } } } pub(crate) fn configuration_descriptors(&self) -> impl Iterator { parse_concatenated_config_descriptors(&self.descriptors[DESCRIPTOR_LEN_DEVICE as usize..]) } pub(crate) fn active_configuration_value(&self) -> u8 { if let Some(sysfs) = self.sysfs.as_ref() { match sysfs.read_attr("bConfigurationValue") { Ok(v) => { self.active_config.store(v, Ordering::SeqCst); return v; } Err(e) => { error!("Failed to read sysfs bConfigurationValue: {e}, using cached value"); } } } self.active_config.load(Ordering::SeqCst) } pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> { usbfs::set_configuration(&self.fd, configuration)?; self.active_config.store(configuration, Ordering::SeqCst); Ok(()) } pub(crate) fn reset(&self) -> Result<(), Error> { usbfs::reset(&self.fd)?; Ok(()) } /// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction` unsafe fn control_blocking( &self, direction: Direction, control: Control, data: *mut u8, len: usize, timeout: Duration, ) -> Result { let r = usbfs::control( &self.fd, usbfs::CtrlTransfer { bRequestType: control.request_type(direction), bRequest: control.request, wValue: control.value, wIndex: control.index, wLength: len.try_into().expect("length must fit in u16"), timeout: timeout .as_millis() .try_into() .expect("timeout must fit in u32 ms"), data: data as *mut c_void, }, ); r.map_err(errno_to_transfer_error) } pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { unsafe { self.control_blocking( Direction::In, control, data.as_mut_ptr(), data.len(), timeout, ) } } pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { unsafe { self.control_blocking( Direction::Out, control, data.as_ptr() as *mut u8, data.len(), timeout, ) } } pub(crate) fn make_control_transfer(self: &Arc) -> TransferHandle { TransferHandle::new(super::TransferData::new( self.clone(), None, 0, EndpointType::Control, )) } pub(crate) fn claim_interface( self: &Arc, interface_number: u8, ) -> Result, Error> { usbfs::claim_interface(&self.fd, interface_number)?; debug!( "Claimed interface {interface_number} on device id {dev}", dev = self.events_id ); Ok(Arc::new(LinuxInterface { device: self.clone(), interface_number, reattach: false, })) } pub(crate) fn detach_and_claim_interface( self: &Arc, interface_number: u8, ) -> Result, Error> { usbfs::detach_and_claim_interface(&self.fd, interface_number)?; debug!( "Detached and claimed interface {interface_number} on device id {dev}", dev = self.events_id ); Ok(Arc::new(LinuxInterface { device: self.clone(), interface_number, reattach: true, })) } pub(crate) fn detach_kernel_driver( self: &Arc, interface_number: u8, ) -> Result<(), Error> { usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) } pub(crate) fn attach_kernel_driver( self: &Arc, interface_number: u8, ) -> Result<(), Error> { usbfs::attach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) } pub(crate) unsafe fn submit_urb(&self, urb: *mut Urb) { let ep = unsafe { (*urb).endpoint }; if let Err(e) = usbfs::submit_urb(&self.fd, urb) { // SAFETY: Transfer was not submitted. We still own the transfer // and can write to the URB and complete it in place of the handler. unsafe { let user_data = { let u = &mut *urb; debug!("Failed to submit URB {urb:?} on ep {ep:x}: {e} {u:?}"); u.actual_length = 0; u.status = e.raw_os_error(); u.usercontext }; notify_completion::(user_data) } } else { debug!("Submitted URB {urb:?} on ep {ep:x}"); } } pub(crate) unsafe fn cancel_urb(&self, urb: *mut Urb) { unsafe { if let Err(e) = usbfs::discard_urb(&self.fd, urb) { debug!("Failed to cancel URB {urb:?}: {e}"); } } } fn get_config(descriptors: &[u8], fd: &OwnedFd) -> Result { const REQUEST_GET_CONFIGURATION: u8 = 0x08; let mut dst = [0u8; 1]; let control = Control { control_type: ControlType::Standard, recipient: Recipient::Device, request: REQUEST_GET_CONFIGURATION, value: 0, index: 0, }; let r = usbfs::control( &fd, usbfs::CtrlTransfer { bRequestType: control.request_type(Direction::In), bRequest: control.request, wValue: control.value, wIndex: control.index, wLength: dst.len() as u16, timeout: Duration::from_millis(50) .as_millis() .try_into() .expect("timeout must fit in u32 ms"), data: &mut dst[0] as *mut u8 as *mut c_void, }, ); match r { Ok(n) => { if n == dst.len() { let active_config = dst[0]; debug!("Obtained active configuration for fd {} from GET_CONFIGURATION request: {active_config}", fd.as_raw_fd()); return Ok(active_config); } else { warn!("GET_CONFIGURATION request returned incorrect length: {n}, expected 1",); } } Err(e) => { warn!( "GET_CONFIGURATION request failed: {:?}", errno_to_transfer_error(e) ); } } if descriptors.len() < DESCRIPTOR_LEN_DEVICE as usize { warn!( "Descriptors for device fd {} too short to use fallback configuration", fd.as_raw_fd() ); return Err(ErrorKind::Other.into()); } // Assume the current configuration is the first one // See: https://github.com/libusb/libusb/blob/467b6a8896daea3d104958bf0887312c5d14d150/libusb/os/linux_usbfs.c#L865 let mut descriptors = parse_concatenated_config_descriptors(&descriptors[DESCRIPTOR_LEN_DEVICE as usize..]) .map(Configuration::new); if let Some(config) = descriptors.next() { return Ok(config.configuration_value()); } error!( "No available configurations for device fd {}", fd.as_raw_fd() ); return Err(ErrorKind::Other.into()); } } impl Drop for LinuxDevice { fn drop(&mut self) { debug!("Closing device {}", self.events_id); events::unregister(self.fd.as_fd(), self.events_id) } } pub(crate) struct LinuxInterface { pub(crate) interface_number: u8, pub(crate) device: Arc, pub(crate) reattach: bool, } impl LinuxInterface { pub(crate) fn make_transfer( self: &Arc, endpoint: u8, ep_type: EndpointType, ) -> TransferHandle { TransferHandle::new(super::TransferData::new( self.device.clone(), Some(self.clone()), endpoint, ep_type, )) } pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { self.device.control_in_blocking(control, data, timeout) } pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { self.device.control_out_blocking(control, data, timeout) } pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> { debug!( "Set interface {} alt setting to {alt_setting}", self.interface_number ); Ok(usbfs::set_interface( &self.device.fd, self.interface_number, alt_setting, )?) } pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> { debug!("Clear halt, endpoint {endpoint:02x}"); Ok(usbfs::clear_halt(&self.device.fd, endpoint)?) } } impl Drop for LinuxInterface { fn drop(&mut self) { let res = usbfs::release_interface(&self.device.fd, self.interface_number); debug!( "Released interface {} on device {}: {res:?}", self.interface_number, self.device.events_id ); if res.is_ok() && self.reattach { let res = usbfs::attach_kernel_driver(&self.device.fd, self.interface_number); debug!( "Reattached kernel drivers for interface {} on device {}: {res:?}", self.interface_number, self.device.events_id ); } } } nusb-0.1.13/src/platform/linux_usbfs/enumeration.rs000064400000000000000000000100501046102023000205210ustar 00000000000000use std::fs; use std::io; use std::num::ParseIntError; use std::path::PathBuf; use std::str::FromStr; use log::debug; use crate::enumeration::InterfaceInfo; use crate::DeviceInfo; use crate::Error; use crate::Speed; #[derive(Debug, Clone)] pub struct SysfsPath(pub(crate) PathBuf); impl SysfsPath { pub(crate) fn read_attr(&self, attr: &str) -> Result where T: FromStr, T::Err: std::error::Error + Send + Sync + 'static, { let attr_path = self.0.join(attr); let read_res = fs::read_to_string(&attr_path); debug!("sysfs read {attr_path:?}: {read_res:?}"); read_res? .trim() .parse() .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } fn read_attr_hex(&self, attr: &str) -> Result { let s = self.read_attr::(attr)?; T::from_hex_str(&s) .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid hex str")) } fn children(&self) -> impl Iterator { fs::read_dir(&self.0) .ok() .into_iter() .flat_map(|x| x) .filter_map(|f| f.ok()) .filter(|f| f.file_type().ok().is_some_and(|t| t.is_dir())) .map(|f| SysfsPath(f.path())) } } trait FromHexStr: Sized { fn from_hex_str(s: &str) -> Result; } impl FromHexStr for u8 { fn from_hex_str(s: &str) -> Result { u8::from_str_radix(s, 16) } } impl FromHexStr for u16 { fn from_hex_str(s: &str) -> Result { u16::from_str_radix(s, 16) } } const SYSFS_PREFIX: &'static str = "/sys/bus/usb/devices/"; pub fn list_devices() -> Result, Error> { Ok(fs::read_dir(SYSFS_PREFIX)?.flat_map(|entry| { let res = probe_device(SysfsPath(entry.ok()?.path())); if let Err(x) = &res { debug!("failed to probe, skipping: {x}") } res.ok() })) } pub fn probe_device(path: SysfsPath) -> Result { debug!("probe device {path:?}"); Ok(DeviceInfo { bus_number: path.read_attr("busnum")?, device_address: path.read_attr("devnum")?, vendor_id: path.read_attr_hex("idVendor")?, product_id: path.read_attr_hex("idProduct")?, device_version: path.read_attr_hex("bcdDevice")?, class: path.read_attr_hex("bDeviceClass")?, subclass: path.read_attr_hex("bDeviceSubClass")?, protocol: path.read_attr_hex("bDeviceProtocol")?, speed: path .read_attr::("speed") .ok() .as_deref() .and_then(Speed::from_str), manufacturer_string: path.read_attr("manufacturer").ok(), product_string: path.read_attr("product").ok(), serial_number: path.read_attr("serial").ok(), interfaces: { let mut interfaces: Vec<_> = path .children() .filter(|i| { // Skip subdirectories like `power` that aren't interfaces // (they would be skipped when missing required properties, // but might as well not open them) i.0.file_name() .unwrap_or_default() .as_encoded_bytes() .contains(&b':') }) .flat_map(|i| { Some(InterfaceInfo { interface_number: i.read_attr_hex("bInterfaceNumber").ok()?, class: i.read_attr_hex("bInterfaceClass").ok()?, subclass: i.read_attr_hex("bInterfaceSubClass").ok()?, protocol: i.read_attr_hex("bInterfaceProtocol").ok()?, interface_string: i.read_attr("interface").ok(), }) }) .collect(); interfaces.sort_unstable_by_key(|i| i.interface_number); interfaces }, path, }) } nusb-0.1.13/src/platform/linux_usbfs/events.rs000064400000000000000000000070621046102023000175100ustar 00000000000000use atomic_waker::AtomicWaker; /// Epoll based event loop for Linux. /// /// Launches a thread when opening the first device that polls /// for events on usbfs devices and arbitrary file descriptors /// (used for udev hotplug). /// /// ### Why not share an event loop with `tokio` or `async-io`? /// /// This event loop will call USBFS_REAP_URB on the event thread and /// dispatch to the transfer's waker directly. Since all USB transfers /// on a device use the same file descriptor, putting USB-specific /// dispatch in the event loop avoids additonal synchronization. use once_cell::sync::OnceCell; use rustix::{ event::epoll::{self, EventData, EventFlags}, fd::{AsFd, BorrowedFd, OwnedFd}, io::retry_on_intr, }; use slab::Slab; use std::{ io, sync::{Arc, Mutex, Weak}, task::Waker, thread, }; use crate::Error; use super::Device; static EPOLL_FD: OnceCell = OnceCell::new(); static WATCHES: Mutex> = Mutex::new(Slab::new()); pub(super) enum Watch { Device(Weak), Fd(Arc), } pub(super) fn register(fd: BorrowedFd, watch: Watch, flags: EventFlags) -> Result { let mut start_thread = false; let epoll_fd = EPOLL_FD.get_or_try_init(|| { start_thread = true; epoll::create(epoll::CreateFlags::CLOEXEC) })?; let id = { let mut watches = WATCHES.lock().unwrap(); watches.insert(watch) }; if start_thread { thread::spawn(event_loop); } let data = EventData::new_u64(id as u64); epoll::add(epoll_fd, fd, data, flags)?; Ok(id) } pub(super) fn unregister_fd(fd: BorrowedFd) { let epoll_fd = EPOLL_FD.get().unwrap(); epoll::delete(epoll_fd, fd).ok(); } pub(super) fn unregister(fd: BorrowedFd, events_id: usize) { let epoll_fd = EPOLL_FD.get().unwrap(); epoll::delete(epoll_fd, fd).ok(); WATCHES.lock().unwrap().remove(events_id); } fn event_loop() { let epoll_fd = EPOLL_FD.get().unwrap(); let mut event_list = epoll::EventVec::with_capacity(4); loop { retry_on_intr(|| epoll::wait(epoll_fd, &mut event_list, -1)).unwrap(); for event in &event_list { let key = event.data.u64() as usize; let lock = WATCHES.lock().unwrap(); let Some(watch) = lock.get(key) else { continue }; match watch { Watch::Device(w) => { if let Some(device) = w.upgrade() { drop(lock); device.handle_events(); // `device` gets dropped here. if it was the last reference, the LinuxDevice will be dropped. // That will unregister its fd, so it's important that WATCHES is unlocked here, or we'd deadlock. } } Watch::Fd(waker) => waker.wake(), } } } } pub(crate) struct Async { pub(crate) inner: T, waker: Arc, id: usize, } impl Async { pub fn new(inner: T) -> Result { let waker = Arc::new(AtomicWaker::new()); let id = register(inner.as_fd(), Watch::Fd(waker.clone()), EventFlags::empty())?; Ok(Async { inner, id, waker }) } pub fn register(&self, waker: &Waker) -> Result<(), io::Error> { self.waker.register(waker); let epoll_fd = EPOLL_FD.get().unwrap(); epoll::modify( epoll_fd, self.inner.as_fd(), EventData::new_u64(self.id as u64), EventFlags::ONESHOT | EventFlags::IN, )?; Ok(()) } } nusb-0.1.13/src/platform/linux_usbfs/hotplug.rs000064400000000000000000000127531046102023000176710ustar 00000000000000use libc::{sockaddr, sockaddr_nl, socklen_t, AF_NETLINK, MSG_DONTWAIT}; use log::{debug, error, warn}; use rustix::{ fd::{AsFd, AsRawFd, OwnedFd}, net::{netlink, socket_with, AddressFamily, SocketFlags, SocketType}, }; use std::{ io::ErrorKind, mem, os::{raw::c_void, unix::prelude::BorrowedFd}, path::Path, task::Poll, }; use crate::{hotplug::HotplugEvent, Error}; use super::{enumeration::probe_device, events::Async, SysfsPath}; const UDEV_MAGIC: &[u8; 12] = b"libudev\0\xfe\xed\xca\xfe"; const UDEV_MULTICAST_GROUP: u32 = 1 << 1; pub(crate) struct LinuxHotplugWatch { fd: Async, } impl LinuxHotplugWatch { pub(crate) fn new() -> Result { let fd = socket_with( AddressFamily::NETLINK, SocketType::RAW, SocketFlags::CLOEXEC, Some(netlink::KOBJECT_UEVENT), )?; unsafe { // rustix doesn't support netlink yet (pending https://github.com/bytecodealliance/rustix/pull/1004) // so use libc for now. let mut addr: sockaddr_nl = mem::zeroed(); addr.nl_family = AF_NETLINK as u16; addr.nl_groups = UDEV_MULTICAST_GROUP; let r = libc::bind( fd.as_raw_fd(), &addr as *const sockaddr_nl as *const sockaddr, mem::size_of_val(&addr) as socklen_t, ); if r != 0 { return Err(Error::last_os_error()); } } Ok(LinuxHotplugWatch { fd: Async::new(fd)?, }) } pub(crate) fn poll_next(&mut self, cx: &mut std::task::Context<'_>) -> Poll { if let Some(event) = try_receive_event(self.fd.inner.as_fd()) { return Poll::Ready(event); } if let Err(e) = self.fd.register(cx.waker()) { log::error!("failed to register udev socket with epoll: {e}"); } Poll::Pending } } fn try_receive_event(fd: BorrowedFd) -> Option { let mut buf = [0; 8192]; let received = unsafe { let mut addr: sockaddr_nl = mem::zeroed(); let mut addrlen: socklen_t = mem::size_of_val(&addr) as socklen_t; let r = libc::recvfrom( fd.as_raw_fd(), buf.as_mut_ptr() as *mut c_void, buf.len(), MSG_DONTWAIT, &mut addr as *mut sockaddr_nl as *mut sockaddr, &mut addrlen, ); if r >= 0 { Ok((r as usize, addr.nl_groups)) } else { Err(Error::last_os_error()) } }; match received { // udev messages will normally be sent to a multicast group, which only // root can send to. Reject unicast messages that may be from anywhere. Ok((size, groups)) if groups == UDEV_MULTICAST_GROUP => parse_packet(&buf[..size]), Ok((_, src)) => { warn!("udev netlink socket received message from {src:?}"); None } Err(e) if e.kind() == ErrorKind::WouldBlock => None, Err(e) => { error!("udev netlink socket recvfrom failed with {e}"); None } } } fn parse_packet(buf: &[u8]) -> Option { if buf.len() < 24 { error!("packet too short: {buf:x?}"); return None; } if !buf.starts_with(UDEV_MAGIC) { error!("packet does not start with expected header: {buf:x?}"); return None; } let properties_off = u32::from_ne_bytes(buf[16..20].try_into().unwrap()) as usize; let properties_len = u32::from_ne_bytes(buf[20..24].try_into().unwrap()) as usize; let Some(properties_buf) = buf.get(properties_off..properties_off + properties_len) else { error!("properties offset={properties_off} length={properties_len} exceeds buffer length {len}", len = buf.len()); return None; }; let mut is_add = None; let mut busnum = None; let mut devnum = None; let mut devpath = None; for (k, v) in parse_properties(properties_buf) { debug!("uevent property {k} = {v}"); match k { "SUBSYSTEM" if v != "usb" => return None, "DEVTYPE" if v != "usb_device" => return None, "ACTION" => { is_add = Some(match v { "add" => true, "remove" => false, _ => return None, }); } "BUSNUM" => { busnum = v.parse::().ok(); } "DEVNUM" => { devnum = v.parse::().ok(); } "DEVPATH" => { devpath = Some(v); } _ => {} } } let is_add = is_add?; let busnum = busnum?; let devnum = devnum?; let devpath = devpath?; if is_add { let path = Path::new("/sys/").join(devpath.trim_start_matches('/')); match probe_device(SysfsPath(path.clone())) { Ok(d) => Some(HotplugEvent::Connected(d)), Err(e) => { error!("Failed to probe device {path:?}: {e}"); None } } } else { Some(HotplugEvent::Disconnected(crate::DeviceId( super::DeviceId { bus: busnum, addr: devnum, }, ))) } } /// Split nul-separated key=value pairs fn parse_properties(buf: &[u8]) -> impl Iterator + '_ { buf.split(|b| b == &0) .filter_map(|entry| std::str::from_utf8(entry).ok()?.split_once('=')) } nusb-0.1.13/src/platform/linux_usbfs/mod.rs000064400000000000000000000017001046102023000167540ustar 00000000000000mod transfer; use rustix::io::Errno; pub(crate) use transfer::TransferData; mod usbfs; mod enumeration; mod events; pub use enumeration::{list_devices, SysfsPath}; mod device; pub(crate) use device::LinuxDevice as Device; pub(crate) use device::LinuxInterface as Interface; mod hotplug; pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch; use crate::transfer::TransferError; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct DeviceId { pub(crate) bus: u8, pub(crate) addr: u8, } fn errno_to_transfer_error(e: Errno) -> TransferError { match e { Errno::NODEV | Errno::SHUTDOWN => TransferError::Disconnected, Errno::PIPE => TransferError::Stall, Errno::NOENT | Errno::CONNRESET | Errno::TIMEDOUT => TransferError::Cancelled, Errno::PROTO | Errno::ILSEQ | Errno::OVERFLOW | Errno::COMM | Errno::TIME => { TransferError::Fault } _ => TransferError::Unknown, } } nusb-0.1.13/src/platform/linux_usbfs/transfer.rs000064400000000000000000000163471046102023000200360ustar 00000000000000use std::{ ffi::c_void, mem::{self, ManuallyDrop}, ptr::null_mut, sync::Arc, }; use rustix::io::Errno; use crate::transfer::{ Completion, ControlIn, ControlOut, EndpointType, PlatformSubmit, PlatformTransfer, RequestBuffer, ResponseBuffer, TransferError, SETUP_PACKET_SIZE, }; use super::{ errno_to_transfer_error, usbfs::{ Urb, USBDEVFS_URB_TYPE_BULK, USBDEVFS_URB_TYPE_CONTROL, USBDEVFS_URB_TYPE_INTERRUPT, USBDEVFS_URB_TYPE_ISO, }, }; /// Linux-specific transfer state. /// /// This logically contains a `Vec` with urb.buffer and capacity. /// It also owns the `urb` allocation itself, which is stored out-of-line /// to avoid violating noalias when submitting the transfer while holding /// `&mut TransferData`. pub struct TransferData { urb: *mut Urb, capacity: usize, device: Arc, /// Not directly used, exists just to keep the interface from being released /// while active. _interface: Option>, } unsafe impl Send for TransferData {} impl TransferData { pub(super) fn new( device: Arc, interface: Option>, endpoint: u8, ep_type: EndpointType, ) -> TransferData { let ep_type = match ep_type { EndpointType::Control => USBDEVFS_URB_TYPE_CONTROL, EndpointType::Interrupt => USBDEVFS_URB_TYPE_INTERRUPT, EndpointType::Bulk => USBDEVFS_URB_TYPE_BULK, EndpointType::Isochronous => USBDEVFS_URB_TYPE_ISO, }; TransferData { urb: Box::into_raw(Box::new(Urb { ep_type, endpoint, status: 0, flags: 0, buffer: null_mut(), buffer_length: 0, actual_length: 0, start_frame: 0, number_of_packets_or_stream_id: 0, error_count: 0, signr: 0, usercontext: null_mut(), })), capacity: 0, device, _interface: interface, } } fn urb_mut(&mut self) -> &mut Urb { // SAFETY: if we have `&mut`, the transfer is not pending unsafe { &mut *self.urb } } fn fill(&mut self, v: Vec, len: usize, user_data: *mut c_void) { let mut v = ManuallyDrop::new(v); let urb = self.urb_mut(); urb.buffer = v.as_mut_ptr(); urb.buffer_length = len.try_into().expect("buffer size should fit in i32"); urb.usercontext = user_data; urb.actual_length = 0; self.capacity = v.capacity(); } /// SAFETY: requires that the transfer has completed and `length` bytes are initialized unsafe fn take_buf(&mut self, length: usize) -> Vec { let urb = self.urb_mut(); assert!(!urb.buffer.is_null()); let ptr = mem::replace(&mut urb.buffer, null_mut()); let capacity = mem::replace(&mut self.capacity, 0); assert!(length <= capacity); Vec::from_raw_parts(ptr, length, capacity) } } impl Drop for TransferData { fn drop(&mut self) { unsafe { if !self.urb_mut().buffer.is_null() { drop(Vec::from_raw_parts(self.urb_mut().buffer, 0, self.capacity)); } drop(Box::from_raw(self.urb)); } } } impl PlatformTransfer for TransferData { fn cancel(&self) { unsafe { self.device.cancel_urb(self.urb); } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: Vec, user_data: *mut c_void) { let ep = self.urb_mut().endpoint; assert!(ep & 0x80 == 0); let len = data.len(); self.fill(data, len, user_data); // SAFETY: we just properly filled the buffer and it is not already pending unsafe { self.device.submit_urb(self.urb) } } unsafe fn take_completed(&mut self) -> Completion { let status = urb_status(self.urb_mut()); let len = self.urb_mut().actual_length as usize; // SAFETY: self is completed (precondition) let data = ResponseBuffer::from_vec(self.take_buf(0), len); Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: RequestBuffer, user_data: *mut c_void) { let ep = self.urb_mut().endpoint; let ty = self.urb_mut().ep_type; assert!(ep & 0x80 == 0x80); assert!(ty == USBDEVFS_URB_TYPE_BULK || ty == USBDEVFS_URB_TYPE_INTERRUPT); let (data, len) = data.into_vec(); self.fill(data, len, user_data); // SAFETY: we just properly filled the buffer and it is not already pending unsafe { self.device.submit_urb(self.urb) } } unsafe fn take_completed(&mut self) -> Completion> { let status = urb_status(self.urb_mut()); let len = self.urb_mut().actual_length as usize; // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. let data = unsafe { self.take_buf(len) }; Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: ControlIn, user_data: *mut c_void) { let buf_len = SETUP_PACKET_SIZE + data.length as usize; let mut buf = Vec::with_capacity(buf_len); buf.extend_from_slice(&data.setup_packet()); self.fill(buf, buf_len, user_data); // SAFETY: we just properly filled the buffer and it is not already pending unsafe { self.device.submit_urb(self.urb) } } unsafe fn take_completed(&mut self) -> Completion> { let status = urb_status(self.urb_mut()); let len = self.urb_mut().actual_length as usize; // SAFETY: transfer is completed (precondition) and `actual_length` // bytes were initialized with setup buf in front let mut data = unsafe { self.take_buf(SETUP_PACKET_SIZE + len) }; data.splice(0..SETUP_PACKET_SIZE, []); Completion { data, status } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: ControlOut, user_data: *mut c_void) { let buf_len = SETUP_PACKET_SIZE + data.data.len(); let mut buf = Vec::with_capacity(buf_len); buf.extend_from_slice( &data .setup_packet() .expect("data length should fit in setup packet's u16"), ); buf.extend_from_slice(data.data); self.fill(buf, buf_len, user_data); // SAFETY: we just properly filled the buffer and it is not already pending unsafe { self.device.submit_urb(self.urb) } } unsafe fn take_completed(&mut self) -> Completion { let status = urb_status(self.urb_mut()); let len = self.urb_mut().actual_length as usize; let data = ResponseBuffer::from_vec(self.take_buf(0), len); Completion { data, status } } } fn urb_status(urb: &Urb) -> Result<(), TransferError> { if urb.status == 0 { return Ok(()); } // It's sometimes positive, sometimes negative, but rustix panics if negative. Err(errno_to_transfer_error(Errno::from_raw_os_error( urb.status.abs(), ))) } nusb-0.1.13/src/platform/linux_usbfs/usbfs.rs000064400000000000000000000202361046102023000173240ustar 00000000000000//! Wrappers for the [usbfs] character device ioctls, translated from the //! [C structures and ioctl definitions][uapi]. //! //! [usbfs]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#the-usb-character-device-nodes //! [uapi]: https://github.com/torvalds/linux/blob/master/tools/include/uapi/linux/usbdevice_fs.h #![allow(dead_code)] use std::{ ffi::{c_int, c_uchar, c_uint, c_void}, marker::PhantomData, }; use rustix::{ fd::AsFd, io, ioctl::{self, CompileTimeOpcode, Ioctl, IoctlOutput}, }; pub fn set_configuration(fd: Fd, configuration: u8) -> io::Result<()> { unsafe { let ctl = ioctl::Setter::, c_uint>::new(configuration.into()); ioctl::ioctl(fd, ctl) } } pub fn claim_interface(fd: Fd, interface: u8) -> io::Result<()> { unsafe { let ctl = ioctl::Setter::, c_uint>::new(interface.into()); ioctl::ioctl(fd, ctl) } } pub fn release_interface(fd: Fd, interface: u8) -> io::Result<()> { unsafe { let ctl = ioctl::Setter::, c_uint>::new(interface.into()); ioctl::ioctl(fd, ctl) } } #[repr(C)] struct DetachAndClaim { interface: c_uint, flags: c_uint, driver: [c_uchar; 255 + 1], } pub fn detach_and_claim_interface(fd: Fd, interface: u8) -> io::Result<()> { const USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER: c_uint = 0x02; unsafe { let mut dc = DetachAndClaim { interface: interface.into(), flags: USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER, driver: [0; 256], }; dc.driver[0..6].copy_from_slice(b"usbfs\0"); let ctl = ioctl::Setter::::new(dc); ioctl::ioctl(&fd, ctl) } } #[repr(C)] struct UsbFsIoctl { interface: c_uint, ioctl_code: c_uint, data: *mut c_void, } /// Opcodes used in ioctl with the usb device fs. /// /// Taken from https://github.com/torvalds/linux/blob/e9680017b2dc8686a908ea1b51941a91b6da9f1d/include/uapi/linux/usbdevice_fs.h#L187 // We repeat the USBDEVFS_ prefix to help keep the same names as what linux uses. // This makes the code more searchable. // TODO: Move the rest of the opcodes into here. #[allow(non_camel_case_types)] mod opcodes { use super::*; pub type USBDEVFS_IOCTL = ioctl::ReadWriteOpcode; pub type USBDEVFS_DISCONNECT_CLAIM = ioctl::ReadOpcode; /// These opcodes are nested inside a [`USBDEVFS_IOCTL`] operation. pub mod nested { use super::*; pub type USBDEVFS_DISCONNECT = ioctl::NoneOpcode; pub type USBDEVFS_CONNECT = ioctl::NoneOpcode; } } pub fn detach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { let command = UsbFsIoctl { interface: interface.into(), // NOTE: Cast needed since on android this type is i32 vs u32 on linux ioctl_code: opcodes::nested::USBDEVFS_DISCONNECT::OPCODE.raw() as _, data: std::ptr::null_mut(), }; unsafe { let ctl = ioctl::Setter::::new(command); ioctl::ioctl(fd, ctl) } } pub fn attach_kernel_driver(fd: Fd, interface: u8) -> io::Result<()> { let command = UsbFsIoctl { interface: interface.into(), ioctl_code: opcodes::nested::USBDEVFS_CONNECT::OPCODE.raw() as _, data: std::ptr::null_mut(), }; unsafe { let ctl = ioctl::Setter::::new(command); ioctl::ioctl(fd, ctl) } } #[repr(C)] struct SetAltSetting { interface: c_int, alt_setting: c_int, } pub fn set_interface(fd: Fd, interface: u8, alt_setting: u8) -> io::Result<()> { unsafe { let ctl = ioctl::Setter::, SetAltSetting>::new( SetAltSetting { interface: interface.into(), alt_setting: alt_setting.into(), }, ); ioctl::ioctl(fd, ctl) } } pub struct PassPtr { input: *mut Input, _opcode: PhantomData, } impl PassPtr { /// Create a new pointer setter-style `ioctl` object. /// /// # Safety /// /// - `Opcode` must provide a valid opcode. /// - For this opcode, `Input` must be the type that the kernel expects to /// get. #[inline] pub unsafe fn new(input: *mut Input) -> Self { Self { input, _opcode: PhantomData, } } } unsafe impl Ioctl for PassPtr { type Output = (); const IS_MUTATING: bool = false; const OPCODE: rustix::ioctl::Opcode = Opcode::OPCODE; fn as_ptr(&mut self) -> *mut c_void { self.input as *mut c_void } unsafe fn output_from_ptr(_: IoctlOutput, _: *mut c_void) -> rustix::io::Result { Ok(()) } } pub unsafe fn submit_urb(fd: Fd, urb: *mut Urb) -> io::Result<()> { unsafe { let ctl = PassPtr::, Urb>::new(urb); ioctl::ioctl(fd, ctl) } } pub fn reap_urb_ndelay(fd: Fd) -> io::Result<*mut Urb> { unsafe { let ctl = ioctl::Getter::, *mut Urb>::new(); ioctl::ioctl(fd, ctl) } } pub unsafe fn discard_urb(fd: Fd, urb: *mut Urb) -> io::Result<()> { unsafe { let ctl = PassPtr::, Urb>::new(urb); ioctl::ioctl(fd, ctl) } } pub fn reset(fd: Fd) -> io::Result<()> { unsafe { let ctl = ioctl::NoArg::>::new(); ioctl::ioctl(fd, ctl) } } const USBDEVFS_URB_SHORT_NOT_OK: c_uint = 0x01; const USBDEVFS_URB_ISO_ASAP: c_uint = 0x02; const USBDEVFS_URB_BULK_CONTINUATION: c_uint = 0x04; const USBDEVFS_URB_ZERO_PACKET: c_uint = 0x40; const USBDEVFS_URB_NO_INTERRUPT: c_uint = 0x80; pub const USBDEVFS_URB_TYPE_ISO: c_uchar = 0; pub const USBDEVFS_URB_TYPE_INTERRUPT: c_uchar = 1; pub const USBDEVFS_URB_TYPE_CONTROL: c_uchar = 2; pub const USBDEVFS_URB_TYPE_BULK: c_uchar = 3; #[repr(C)] #[derive(Debug)] pub struct Urb { pub ep_type: c_uchar, pub endpoint: c_uchar, pub status: c_int, pub flags: c_uint, pub buffer: *mut u8, pub buffer_length: c_int, pub actual_length: c_int, pub start_frame: c_int, pub number_of_packets_or_stream_id: c_uint, // a union in C pub error_count: c_int, pub signr: c_uint, pub usercontext: *mut c_void, // + variable size array of iso_packet_desc } pub struct Transfer { input: Input, _opcode: PhantomData, } impl Transfer { #[inline] pub unsafe fn new(input: Input) -> Self { Self { input, _opcode: PhantomData, } } } unsafe impl Ioctl for Transfer { type Output = usize; const IS_MUTATING: bool = true; const OPCODE: rustix::ioctl::Opcode = Opcode::OPCODE; fn as_ptr(&mut self) -> *mut c_void { &mut self.input as *mut Input as *mut c_void } unsafe fn output_from_ptr(r: IoctlOutput, _: *mut c_void) -> io::Result { Ok(r as usize) } } #[repr(C)] #[allow(non_snake_case)] pub struct CtrlTransfer { pub bRequestType: u8, pub bRequest: u8, pub wValue: u16, pub wIndex: u16, pub wLength: u16, pub timeout: u32, /* in milliseconds */ pub data: *mut c_void, } pub fn control(fd: Fd, transfer: CtrlTransfer) -> io::Result { unsafe { let ctl = Transfer::, CtrlTransfer>::new(transfer); ioctl::ioctl(fd, ctl) } } pub fn clear_halt(fd: Fd, endpoint: u8) -> io::Result<()> { unsafe { let ctl = ioctl::Setter::, c_uint>::new(endpoint.into()); ioctl::ioctl(fd, ctl) } } nusb-0.1.13/src/platform/macos_iokit/device.rs000064400000000000000000000255451046102023000174110ustar 00000000000000use std::{ collections::BTreeMap, ffi::c_void, io::ErrorKind, sync::{ atomic::{AtomicU8, AtomicUsize, Ordering}, Arc, Mutex, }, time::Duration, }; use log::{debug, error}; use crate::{ platform::macos_iokit::events::add_event_source, transfer::{Control, Direction, EndpointType, TransferError, TransferHandle}, DeviceInfo, Error, }; use super::{ enumeration::service_by_registry_id, events::EventRegistration, iokit::{call_iokit_function, check_iokit_return}, iokit_c::IOUSBDevRequestTO, iokit_usb::{EndpointInfo, IoKitDevice, IoKitInterface}, status_to_transfer_result, }; pub(crate) struct MacDevice { _event_registration: EventRegistration, pub(super) device: IoKitDevice, active_config: AtomicU8, is_open_exclusive: Mutex, claimed_interfaces: AtomicUsize, } // `get_configuration` does IO, so avoid it in the common case that: // * the device has a single configuration // * the device has at least one interface, indicating that it is configured fn guess_active_config(dev: &IoKitDevice) -> Option { if dev.get_number_of_configurations().unwrap_or(0) != 1 { return None; } let mut intf = dev.create_interface_iterator().ok()?; intf.next()?; let config_desc = dev.get_configuration_descriptor(0).ok()?; config_desc.get(5).copied() // get bConfigurationValue from descriptor } impl MacDevice { pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { log::debug!("Opening device from registry id {}", d.registry_id); let service = service_by_registry_id(d.registry_id)?; let device = IoKitDevice::new(service)?; let _event_registration = add_event_source(device.create_async_event_source()?); let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } { io_kit_sys::ret::kIOReturnSuccess => true, err => { // Most methods don't require USBDeviceOpen() so this can be ignored // to allow different processes to open different interfaces. log::debug!("Could not open device for exclusive access: {err:x}"); false } }; let active_config = if let Some(active_config) = guess_active_config(&device) { log::debug!("Active config from single descriptor is {}", active_config); active_config } else { let res = device.get_configuration(); log::debug!("Active config from request is {:?}", res); res.unwrap_or(0) }; Ok(Arc::new(MacDevice { _event_registration, device, active_config: AtomicU8::new(active_config), is_open_exclusive: Mutex::new(opened), claimed_interfaces: AtomicUsize::new(0), })) } pub(crate) fn active_configuration_value(&self) -> u8 { self.active_config.load(Ordering::SeqCst) } pub(crate) fn configuration_descriptors(&self) -> impl Iterator { let num_configs = self.device.get_number_of_configurations().unwrap_or(0); (0..num_configs).flat_map(|i| self.device.get_configuration_descriptor(i).ok()) } fn require_open_exclusive(&self) -> Result<(), Error> { let mut state = self.is_open_exclusive.lock().unwrap(); if *state == false { unsafe { check_iokit_return(call_iokit_function!(self.device.raw, USBDeviceOpen()))? }; *state = true; } if self.claimed_interfaces.load(Ordering::Relaxed) != 0 { return Err(Error::new( ErrorKind::Other, "cannot perform this operation while interfaces are claimed", )); } Ok(()) } pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), Error> { self.require_open_exclusive()?; unsafe { check_iokit_return(call_iokit_function!( self.device.raw, SetConfiguration(configuration) ))? } log::debug!("Set configuration {configuration}"); self.active_config.store(configuration, Ordering::SeqCst); Ok(()) } pub(crate) fn reset(&self) -> Result<(), Error> { self.require_open_exclusive()?; unsafe { check_iokit_return(call_iokit_function!( self.device.raw, USBDeviceReEnumerate(0) )) } } /// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction` unsafe fn control_blocking( &self, direction: Direction, control: Control, data: *mut u8, len: usize, timeout: Duration, ) -> Result { let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32; let mut req = IOUSBDevRequestTO { bmRequestType: control.request_type(direction), bRequest: control.request, wValue: control.value, wIndex: control.index, wLength: len.try_into().expect("length must fit in u16"), pData: data.cast::(), wLenDone: 0, noDataTimeout: timeout_ms, completionTimeout: timeout_ms, }; let r = unsafe { call_iokit_function!(self.device.raw, DeviceRequestTO(&mut req)) }; status_to_transfer_result(r).map(|()| req.wLenDone as usize) } pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { unsafe { self.control_blocking( Direction::In, control, data.as_mut_ptr(), data.len(), timeout, ) } } pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { unsafe { self.control_blocking( Direction::Out, control, data.as_ptr() as *mut u8, data.len(), timeout, ) } } pub(crate) fn make_control_transfer(self: &Arc) -> TransferHandle { TransferHandle::new(super::TransferData::new_control(self.clone())) } pub(crate) fn claim_interface( self: &Arc, interface_number: u8, ) -> Result, Error> { let intf_service = self .device .create_interface_iterator()? .nth(interface_number as usize) .ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?; let mut interface = IoKitInterface::new(intf_service)?; let _event_registration = add_event_source(interface.create_async_event_source()?); interface.open()?; let endpoints = interface.endpoints()?; debug!("Found endpoints: {endpoints:?}"); self.claimed_interfaces.fetch_add(1, Ordering::Acquire); Ok(Arc::new(MacInterface { device: self.clone(), interface_number, interface, endpoints: Mutex::new(endpoints), _event_registration, })) } pub(crate) fn detach_and_claim_interface( self: &Arc, interface: u8, ) -> Result, Error> { self.claim_interface(interface) } } impl Drop for MacDevice { fn drop(&mut self) { if *self.is_open_exclusive.get_mut().unwrap() { match unsafe { call_iokit_function!(self.device.raw, USBDeviceClose()) } { io_kit_sys::ret::kIOReturnSuccess => {} err => log::debug!("Failed to close device: {err:x}"), }; } } } pub(crate) struct MacInterface { pub(crate) interface_number: u8, _event_registration: EventRegistration, pub(crate) interface: IoKitInterface, pub(crate) device: Arc, /// Map from address to a structure that contains the `pipe_ref` used by iokit pub(crate) endpoints: Mutex>, } impl MacInterface { pub(crate) fn make_transfer( self: &Arc, endpoint: u8, ep_type: EndpointType, ) -> TransferHandle { if ep_type == EndpointType::Control { assert!(endpoint == 0); TransferHandle::new(super::TransferData::new_control(self.device.clone())) } else { let endpoints = self.endpoints.lock().unwrap(); // This function can't fail, so if the endpoint is not found, use an invalid // pipe_ref that will fail when submitting the transfer. let pipe_ref = endpoints.get(&endpoint).map(|e| e.pipe_ref).unwrap_or(0); TransferHandle::new(super::TransferData::new( self.device.clone(), self.clone(), endpoint, pipe_ref, )) } } pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { self.device.control_in_blocking(control, data, timeout) } pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { self.device.control_out_blocking(control, data, timeout) } pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> { debug!( "Set interface {} alt setting to {alt_setting}", self.interface_number ); let mut endpoints = self.endpoints.lock().unwrap(); unsafe { check_iokit_return(call_iokit_function!( self.interface.raw, SetAlternateInterface(alt_setting) ))?; } *endpoints = self.interface.endpoints()?; debug!("Found endpoints: {endpoints:?}"); Ok(()) } pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> { debug!("Clear halt, endpoint {endpoint:02x}"); let pipe_ref = { let endpoints = self.endpoints.lock().unwrap(); let ep = endpoints .get(&endpoint) .ok_or_else(|| Error::new(ErrorKind::NotFound, "Endpoint not found"))?; ep.pipe_ref }; unsafe { check_iokit_return(call_iokit_function!( self.interface.raw, ClearPipeStallBothEnds(pipe_ref) )) } } } impl Drop for MacInterface { fn drop(&mut self) { if let Err(err) = self.interface.close() { error!("Failed to close interface: {err}") } self.device .claimed_interfaces .fetch_sub(1, Ordering::Release); } } nusb-0.1.13/src/platform/macos_iokit/enumeration.rs000064400000000000000000000140061046102023000204660ustar 00000000000000use std::io::ErrorKind; use core_foundation::{ base::{CFType, TCFType}, number::CFNumber, string::CFString, ConcreteCFType, }; use io_kit_sys::{ kIOMasterPortDefault, kIORegistryIterateParents, kIORegistryIterateRecursively, keys::kIOServicePlane, ret::kIOReturnSuccess, usb::lib::kIOUSBDeviceClassName, IORegistryEntryGetChildIterator, IORegistryEntryGetRegistryEntryID, IORegistryEntrySearchCFProperty, IOServiceGetMatchingServices, IOServiceMatching, }; use log::debug; use crate::{DeviceInfo, Error, InterfaceInfo, Speed}; use super::iokit::{IoService, IoServiceIterator}; fn usb_service_iter() -> Result { unsafe { let dictionary = IOServiceMatching(kIOUSBDeviceClassName); if dictionary.is_null() { return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed")); } let mut iterator = 0; let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator); if r != kIOReturnSuccess { return Err(Error::from_raw_os_error(r)); } Ok(IoServiceIterator::new(iterator)) } } pub fn list_devices() -> Result, Error> { Ok(usb_service_iter()?.filter_map(probe_device)) } pub(crate) fn service_by_registry_id(registry_id: u64) -> Result { usb_service_iter()? .find(|dev| get_registry_id(dev) == Some(registry_id)) .ok_or(Error::new(ErrorKind::NotFound, "not found by registry id")) } pub(crate) fn probe_device(device: IoService) -> Option { let registry_id = get_registry_id(&device)?; log::debug!("Probing device {registry_id:08x}"); // Can run `ioreg -p IOUSB -l` to see all properties Some(DeviceInfo { registry_id, location_id: get_integer_property(&device, "locationID")? as u32, bus_number: 0, // TODO: does this exist on macOS? device_address: get_integer_property(&device, "USB Address")? as u8, vendor_id: get_integer_property(&device, "idVendor")? as u16, product_id: get_integer_property(&device, "idProduct")? as u16, device_version: get_integer_property(&device, "bcdDevice")? as u16, class: get_integer_property(&device, "bDeviceClass")? as u8, subclass: get_integer_property(&device, "bDeviceSubClass")? as u8, protocol: get_integer_property(&device, "bDeviceProtocol")? as u8, speed: get_integer_property(&device, "Device Speed").and_then(map_speed), manufacturer_string: get_string_property(&device, "kUSBVendorString") .or_else(|| get_string_property(&device, "USB Vendor Name")), product_string: get_string_property(&device, "kUSBProductString") .or_else(|| get_string_property(&device, "USB Product Name")), serial_number: get_string_property(&device, "kUSBSerialNumberString") .or_else(|| get_string_property(&device, "USB Serial Number")), interfaces: get_children(&device).map_or(Vec::new(), |iter| { iter.flat_map(|child| { Some(InterfaceInfo { interface_number: get_integer_property(&child, "bInterfaceNumber")? as u8, class: get_integer_property(&child, "bInterfaceClass")? as u8, subclass: get_integer_property(&child, "bInterfaceSubClass")? as u8, protocol: get_integer_property(&child, "bInterfaceProtocol")? as u8, interface_string: get_string_property(&child, "kUSBString") .or_else(|| get_string_property(&child, "USB Interface Name")), }) }) .collect() }), }) } pub(crate) fn get_registry_id(device: &IoService) -> Option { unsafe { let mut out = 0; let r = IORegistryEntryGetRegistryEntryID(device.get(), &mut out); if r == kIOReturnSuccess { Some(out) } else { // not sure this can actually fail. debug!("IORegistryEntryGetRegistryEntryID failed with {r}"); None } } } fn get_property(device: &IoService, property: &'static str) -> Option { unsafe { let cf_property = CFString::from_static_string(property); let raw = IORegistryEntrySearchCFProperty( device.get(), kIOServicePlane as *mut i8, cf_property.as_CFTypeRef() as *const _, std::ptr::null(), kIORegistryIterateRecursively | kIORegistryIterateParents, ); if raw.is_null() { debug!("Device does not have property `{property}`"); return None; } let res = CFType::wrap_under_create_rule(raw).downcast_into(); if res.is_none() { debug!("Failed to convert device property `{property}`"); } res } } fn get_string_property(device: &IoService, property: &'static str) -> Option { get_property::(device, property).map(|s| s.to_string()) } fn get_integer_property(device: &IoService, property: &'static str) -> Option { let n = get_property::(device, property)?; n.to_i64().or_else(|| { debug!("failed to convert {property} value {n:?} to i64"); None }) } fn get_children(device: &IoService) -> Result { unsafe { let mut iterator = 0; let r = IORegistryEntryGetChildIterator(device.get(), kIOServicePlane as *mut _, &mut iterator); if r != kIOReturnSuccess { debug!("IORegistryEntryGetChildIterator failed: {r}"); return Err(Error::from_raw_os_error(r)); } Ok(IoServiceIterator::new(iterator)) } } fn map_speed(speed: i64) -> Option { // https://developer.apple.com/documentation/iokit/1425357-usbdevicespeed match speed { 0 => Some(Speed::Low), 1 => Some(Speed::Full), 2 => Some(Speed::High), 3 => Some(Speed::Super), 4 | 5 => Some(Speed::SuperPlus), _ => None, } } nusb-0.1.13/src/platform/macos_iokit/events.rs000064400000000000000000000046161046102023000174520ustar 00000000000000use std::{ ops::Deref, sync::{mpsc, Mutex}, thread, }; use core_foundation::runloop::{CFRunLoop, CFRunLoopSource}; use core_foundation_sys::runloop::kCFRunLoopCommonModes; use log::debug; // Pending release of https://github.com/servo/core-foundation-rs/pull/610 struct SendCFRunLoop(CFRunLoop); unsafe impl Send for SendCFRunLoop {} impl Deref for SendCFRunLoop { type Target = CFRunLoop; fn deref(&self) -> &Self::Target { &self.0 } } struct SendCFRunLoopSource(CFRunLoopSource); unsafe impl Send for SendCFRunLoopSource {} unsafe impl Sync for SendCFRunLoopSource {} struct EventLoop { runloop: Option, count: usize, } static EVENT_LOOP: Mutex = Mutex::new(EventLoop { runloop: None, count: 0, }); pub(crate) fn add_event_source(source: CFRunLoopSource) -> EventRegistration { let mut event_loop = EVENT_LOOP.lock().unwrap(); if let Some(runloop) = event_loop.runloop.as_ref() { if runloop.contains_source(&source, unsafe { kCFRunLoopCommonModes }) { panic!("source already registered"); } runloop.add_source(&source, unsafe { kCFRunLoopCommonModes }); event_loop.count += 1; } else { let (tx, rx) = mpsc::channel(); let source = SendCFRunLoopSource(source.clone()); debug!("starting event loop thread"); thread::spawn(move || { let runloop = CFRunLoop::get_current(); let source = source; runloop.add_source(&source.0, unsafe { kCFRunLoopCommonModes }); tx.send(SendCFRunLoop(runloop)).unwrap(); CFRunLoop::run_current(); debug!("event loop thread exited"); }); event_loop.runloop = Some(rx.recv().expect("failed to start run loop thread")); event_loop.count = 1; } EventRegistration(SendCFRunLoopSource(source)) } pub(crate) struct EventRegistration(SendCFRunLoopSource); impl Drop for EventRegistration { fn drop(&mut self) { let mut event_loop = EVENT_LOOP.lock().unwrap(); event_loop.count -= 1; let runloop = event_loop .runloop .as_ref() .expect("runloop should exist while events are registered"); runloop.remove_source(&self.0 .0, unsafe { kCFRunLoopCommonModes }); if event_loop.count == 0 { runloop.stop(); event_loop.runloop.take(); } } } nusb-0.1.13/src/platform/macos_iokit/hotplug.rs000064400000000000000000000125331046102023000176250ustar 00000000000000use std::{ ffi::{c_char, c_void}, io::ErrorKind, mem::ManuallyDrop, sync::Mutex, task::{Context, Poll, Waker}, }; use atomic_waker::AtomicWaker; use core_foundation::{base::TCFType, dictionary::CFDictionary, runloop::CFRunLoopSource}; use io_kit_sys::{ kIOMasterPortDefault, keys::{kIOFirstMatchNotification, kIOTerminatedNotification}, ret::kIOReturnSuccess, types::io_iterator_t, usb::lib::kIOUSBDeviceClassName, IONotificationPort, IONotificationPortCreate, IONotificationPortDestroy, IONotificationPortGetRunLoopSource, IOServiceAddMatchingNotification, IOServiceMatching, }; use log::debug; use slab::Slab; use crate::{hotplug::HotplugEvent, DeviceId, Error}; use super::{ enumeration::{get_registry_id, probe_device}, events::{add_event_source, EventRegistration}, iokit::IoServiceIterator, }; // Wakers are owned by a global slab to avoid race conditions when freeing them static WAKERS: Mutex> = Mutex::new(Slab::new()); /// An AtomicWaker registered with `WAKERS` struct SlabWaker(usize); impl SlabWaker { fn new() -> SlabWaker { SlabWaker(WAKERS.lock().unwrap().insert(AtomicWaker::new())) } fn register(&self, w: &Waker) { WAKERS.lock().unwrap()[self.0].register(w); } } impl Drop for SlabWaker { fn drop(&mut self) { WAKERS.lock().unwrap().remove(self.0); } } pub(crate) struct MacHotplugWatch { waker_id: SlabWaker, terminated_iter: IoServiceIterator, matched_iter: IoServiceIterator, _registration: EventRegistration, _notification_port: NotificationPort, } struct NotificationPort(*mut IONotificationPort); impl NotificationPort { fn new() -> NotificationPort { unsafe { NotificationPort(IONotificationPortCreate(kIOMasterPortDefault)) } } } impl Drop for NotificationPort { fn drop(&mut self) { unsafe { IONotificationPortDestroy(self.0) } } } unsafe impl Send for NotificationPort {} impl MacHotplugWatch { pub(crate) fn new() -> Result { let waker_id = SlabWaker::new(); let dictionary = unsafe { let d = IOServiceMatching(kIOUSBDeviceClassName); if d.is_null() { return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed")); } CFDictionary::wrap_under_create_rule(d) }; let notification_port = NotificationPort::new(); let terminated_iter = register_notification( ¬ification_port, &dictionary, &waker_id, kIOTerminatedNotification, )?; let matched_iter = register_notification( ¬ification_port, &dictionary, &waker_id, kIOFirstMatchNotification, )?; let source = unsafe { CFRunLoopSource::wrap_under_get_rule(IONotificationPortGetRunLoopSource( notification_port.0, )) }; let registration = add_event_source(source); Ok(MacHotplugWatch { waker_id, terminated_iter, matched_iter, _registration: registration, _notification_port: notification_port, }) } pub fn poll_next(&mut self, cx: &mut Context) -> Poll { self.waker_id.register(cx.waker()); while let Some(s) = self.matched_iter.next() { if let Some(dev) = probe_device(s) { return Poll::Ready(HotplugEvent::Connected(dev)); } else { debug!("failed to probe connected device"); } } if let Some(s) = self.terminated_iter.next() { if let Some(registry_id) = get_registry_id(&s) { debug!("device {registry_id} disconnected"); let id = DeviceId(registry_id); return Poll::Ready(HotplugEvent::Disconnected(id)); } else { debug!("failed to get registry ID for disconnected device") } } Poll::Pending } } // Safety: Structurally Send and only method is &mut self, so Sync // doesn't have any additional requirements. unsafe impl Sync for MacHotplugWatch {} fn register_notification( port: &NotificationPort, dictionary: &CFDictionary, waker: &SlabWaker, event: *const i8, ) -> Result { assert!(event == kIOFirstMatchNotification || event == kIOTerminatedNotification); unsafe { let mut iter = 0; let r = IOServiceAddMatchingNotification( port.0, event as *mut c_char, ManuallyDrop::new(dictionary.clone()).as_concrete_TypeRef(), callback, waker.0 as *mut c_void, &mut iter, ); if r != kIOReturnSuccess { return Err(Error::new( ErrorKind::Other, "Failed to register notification", )); } let mut iter = IoServiceIterator::new(iter); // Drain events for already-connected devices and to arm the notification for future events while let Some(_) = iter.next() {} Ok(iter) } } unsafe extern "C" fn callback(refcon: *mut c_void, _iterator: io_iterator_t) { debug!("hotplug event callback"); let id = refcon as usize; if let Some(waker) = WAKERS.lock().unwrap().get(id) { waker.wake() } } nusb-0.1.13/src/platform/macos_iokit/iokit.rs000064400000000000000000000074471046102023000172720ustar 00000000000000//! Utilities for using IOKit APIs. //! //! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) //! licensed under MIT OR Apache-2.0. use core_foundation_sys::uuid::CFUUIDBytes; use io_kit_sys::{ret::IOReturn, IOIteratorNext, IOObjectRelease}; use std::io::ErrorKind; use crate::Error; use super::iokit_c::{self, CFUUIDGetUUIDBytes, IOCFPlugInInterface}; pub(crate) struct IoObject(u32); impl IoObject { // Safety: `handle` must be an IOObject handle. Ownership is transferred. pub unsafe fn new(handle: u32) -> IoObject { IoObject(handle) } pub fn get(&self) -> u32 { self.0 } } impl Drop for IoObject { fn drop(&mut self) { unsafe { IOObjectRelease(self.0); } } } pub(crate) struct IoService(IoObject); impl IoService { // Safety: `handle` must be an IOService handle. Ownership is transferred. pub unsafe fn new(handle: u32) -> IoService { IoService(IoObject(handle)) } pub fn get(&self) -> u32 { self.0 .0 } } pub(crate) struct IoServiceIterator(IoObject); impl IoServiceIterator { // Safety: `handle` must be an IoIterator of IoService. Ownership is transferred. pub unsafe fn new(handle: u32) -> IoServiceIterator { IoServiceIterator(IoObject::new(handle)) } } impl Iterator for IoServiceIterator { type Item = IoService; fn next(&mut self) -> Option { unsafe { let handle = IOIteratorNext(self.0.get()); if handle != 0 { Some(IoService::new(handle)) } else { None } } } } /// Helper for calling IOKit function pointers. macro_rules! call_iokit_function { ($ptr:expr, $function:ident($($args:expr),*)) => {{ use std::ffi::c_void; let func = (**$ptr).$function.expect("function pointer from IOKit was null"); func($ptr as *mut c_void, $($args),*) }}; } pub(crate) use call_iokit_function; /// Wrapper around a **IOCFPluginInterface that automatically releases it. #[derive(Debug)] pub(crate) struct PluginInterface { interface: *mut *mut IOCFPlugInInterface, } impl PluginInterface { pub(crate) fn new(interface: *mut *mut IOCFPlugInInterface) -> Self { Self { interface } } /// Fetches the inner pointer for passing to IOKit functions. pub(crate) fn get(&self) -> *mut *mut IOCFPlugInInterface { self.interface } } impl Drop for PluginInterface { fn drop(&mut self) { unsafe { call_iokit_function!(self.interface, Release()); } } } /// Alias that select the "version 500" (IOKit 5.0.0) version of UsbDevice, which means /// that we support macOS versions back to 10.7.3, which is currently every version that Rust /// supports. Use this instead of touching the iokit_c structure; this may be bumped to /// (compatible) newer versions of the struct as Rust's support changes. pub(crate) type UsbDevice = iokit_c::IOUSBDeviceStruct500; pub(crate) type UsbInterface = iokit_c::IOUSBInterfaceStruct500; pub(crate) fn usb_device_type_id() -> CFUUIDBytes { unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBDeviceInterfaceID500()) } } pub(crate) fn usb_interface_type_id() -> CFUUIDBytes { unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBInterfaceInterfaceID500()) } } pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), Error> { #[allow(non_upper_case_globals)] #[deny(unreachable_patterns)] match r { io_kit_sys::ret::kIOReturnSuccess => Ok(()), io_kit_sys::ret::kIOReturnExclusiveAccess => Err(Error::new( ErrorKind::Other, "could not be opened for exclusive access", )), io_kit_sys::ret::kIOReturnNotFound => Err(Error::new(ErrorKind::NotFound, "not found")), _ => Err(Error::from_raw_os_error(r)), } } nusb-0.1.13/src/platform/macos_iokit/iokit_c.rs000064400000000000000000001055451046102023000175720ustar 00000000000000//! FFI types we're including here, as they're missing from io-kit-sys. //! You may not want to stare too closely at this; it's tweaked bindgen output. //! //! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) //! licensed under MIT OR Apache-2.0. #![allow( non_camel_case_types, non_snake_case, dead_code, non_upper_case_globals )] use std::ffi::{c_int, c_void}; use core_foundation_sys::{ base::{kCFAllocatorSystemDefault, mach_port_t, SInt32}, dictionary::CFDictionaryRef, mach_port::CFAllocatorRef, runloop::CFRunLoopSourceRef, uuid::{CFUUIDBytes, CFUUIDRef}, }; use io_kit_sys::{ ret::IOReturn, types::{io_iterator_t, io_service_t, IOByteCount}, IOAsyncCallback1, }; // // Constants. // const SYS_IOKIT: c_int = ((0x38) & 0x3f) << 26; const SUB_IOKIT_USB: c_int = ((1) & 0xfff) << 14; pub(crate) const kIOUSBUnknownPipeErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x61; // 0xe0004061 Pipe ref not recognized pub(crate) const kIOUSBTooManyPipesErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x60; // 0xe0004060 Too many pipes pub(crate) const kIOUSBNoAsyncPortErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x5f; // 0xe000405f no async port pub(crate) const kIOUSBNotEnoughPipesErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x5e; // 0xe000405e not enough pipes in interface pub(crate) const kIOUSBNotEnoughPowerErr: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x5d; // 0xe000405d not enough power for selected configuration pub(crate) const kIOUSBEndpointNotFound: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x57; // 0xe0004057 Endpoint Not found pub(crate) const kIOUSBConfigNotFound: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x56; // 0xe0004056 Configuration Not found pub(crate) const kIOUSBPortWasSuspended: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x52; // 0xe0004052 The transaction was returned because the port was suspended pub(crate) const kIOUSBPipeStalled: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4f; // 0xe000404f Pipe has stalled, error needs to be cleared pub(crate) const kIOUSBInterfaceNotFound: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4e; // 0xe000404e Interface ref not recognized pub(crate) const kIOUSBLowLatencyBufferNotPreviouslyAllocated: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4d; // 0xe000404d Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first pub(crate) const kIOUSBLowLatencyFrameListNotPreviouslyAllocated: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4c; // 0xe000404c Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first pub(crate) const kIOUSBHighSpeedSplitError: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4b; // 0xe000404b Error to hub on high speed bus trying to do split transaction pub(crate) const kIOUSBSyncRequestOnWLThread: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x4a; // 0xe000404a A synchronous USB request was made on the workloop thread (from a callback?). Only async requests are permitted in that case pub(crate) const kIOUSBDeviceNotHighSpeed: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x49; // 0xe0004049 Name is deprecated, see below pub(crate) const kIOUSBDeviceTransferredToCompanion: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x49; // 0xe0004049 The device has been tranferred to another controller for enumeration pub(crate) const kIOUSBClearPipeStallNotRecursive: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x48; // 0xe0004048 IOUSBPipe::ClearPipeStall should not be called recursively pub(crate) const kIOUSBDevicePortWasNotSuspended: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x47; // 0xe0004047 Port was not suspended pub(crate) const kIOUSBEndpointCountExceeded: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x46; // 0xe0004046 The endpoint was not created because the controller cannot support more endpoints pub(crate) const kIOUSBDeviceCountExceeded: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x45; // 0xe0004045 The device cannot be enumerated because the controller cannot support more devices pub(crate) const kIOUSBStreamsNotSupported: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x44; // 0xe0004044 The request cannot be completed because the XHCI controller does not support streams pub(crate) const kIOUSBInvalidSSEndpoint: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x43; // 0xe0004043 An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) pub(crate) const kIOUSBTooManyTransactionsPending: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x42; // 0xe0004042 The transaction cannot be submitted because it would exceed the allowed number of pending transactions pub(crate) const kIOUSBTransactionReturned: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x50; pub(crate) const kIOUSBTransactionTimeout: c_int = SYS_IOKIT | SUB_IOKIT_USB | 0x51; pub(crate) const kIOUSBFindInterfaceDontCare: UInt16 = 0xFFFF; // // // Type aliases. // pub(crate) type REFIID = CFUUIDBytes; pub(crate) type LPVOID = *mut c_void; pub(crate) type HRESULT = SInt32; pub(crate) type UInt8 = ::std::os::raw::c_uchar; pub(crate) type UInt16 = ::std::os::raw::c_ushort; pub(crate) type UInt32 = ::std::os::raw::c_uint; pub(crate) type UInt64 = ::std::os::raw::c_ulonglong; pub(crate) type ULONG = ::std::os::raw::c_ulong; pub(crate) type kern_return_t = ::std::os::raw::c_int; pub(crate) type USBDeviceAddress = UInt16; pub(crate) type AbsoluteTime = UnsignedWide; pub(crate) type Boolean = std::os::raw::c_uchar; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NumVersion { pub nonRelRev: UInt8, pub stage: UInt8, pub minorAndBugRev: UInt8, pub majorRev: UInt8, } #[repr(C, packed(2))] #[derive(Debug, Copy, Clone)] pub struct UnsignedWide { pub lo: UInt32, pub hi: UInt32, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBDevRequest { pub bmRequestType: UInt8, pub bRequest: UInt8, pub wValue: UInt16, pub wIndex: UInt16, pub wLength: UInt16, pub pData: *mut ::std::os::raw::c_void, pub wLenDone: UInt32, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBFindInterfaceRequest { pub bInterfaceClass: UInt16, pub bInterfaceSubClass: UInt16, pub bInterfaceProtocol: UInt16, pub bAlternateSetting: UInt16, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBDevRequestTO { pub bmRequestType: UInt8, pub bRequest: UInt8, pub wValue: UInt16, pub wIndex: UInt16, pub wLength: UInt16, pub pData: *mut ::std::os::raw::c_void, pub wLenDone: UInt32, pub noDataTimeout: UInt32, pub completionTimeout: UInt32, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBIsocFrame { pub frStatus: IOReturn, pub frReqCount: UInt16, pub frActCount: UInt16, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBLowLatencyIsocFrame { pub frStatus: IOReturn, pub frReqCount: UInt16, pub frActCount: UInt16, pub frTimeStamp: AbsoluteTime, } #[repr(C, packed)] #[derive(Debug, Copy, Clone)] pub struct IOUSBDescriptorHeader { pub bLength: u8, pub bDescriptorType: u8, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOCFPlugInInterfaceStruct { pub _reserved: *mut ::std::os::raw::c_void, pub QueryInterface: ::std::option::Option< unsafe extern "C" fn( thisPointer: *mut ::std::os::raw::c_void, iid: REFIID, ppv: *mut LPVOID, ) -> HRESULT, >, pub AddRef: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, >, pub Release: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, >, pub version: UInt16, pub revision: UInt16, pub Probe: ::std::option::Option< unsafe extern "C" fn( thisPointer: *mut ::std::os::raw::c_void, propertyTable: CFDictionaryRef, service: io_service_t, order: *mut SInt32, ) -> IOReturn, >, pub Start: ::std::option::Option< unsafe extern "C" fn( thisPointer: *mut ::std::os::raw::c_void, propertyTable: CFDictionaryRef, service: io_service_t, ) -> IOReturn, >, pub Stop: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> IOReturn, >, } pub type IOCFPlugInInterface = IOCFPlugInInterfaceStruct; extern "C" { pub fn CFUUIDGetUUIDBytes(uuid: CFUUIDRef) -> CFUUIDBytes; pub fn IOCreatePlugInInterfaceForService( service: io_service_t, pluginType: CFUUIDRef, interfaceType: CFUUIDRef, theInterface: *mut *mut *mut IOCFPlugInInterface, theScore: *mut SInt32, ) -> kern_return_t; pub fn CFUUIDGetConstantUUIDWithBytes( alloc: CFAllocatorRef, byte0: UInt8, byte1: UInt8, byte2: UInt8, byte3: UInt8, byte4: UInt8, byte5: UInt8, byte6: UInt8, byte7: UInt8, byte8: UInt8, byte9: UInt8, byte10: UInt8, byte11: UInt8, byte12: UInt8, byte13: UInt8, byte14: UInt8, byte15: UInt8, ) -> CFUUIDRef; } pub fn kIOUsbDeviceUserClientTypeID() -> CFUUIDRef { unsafe { CFUUIDGetConstantUUIDWithBytes( std::ptr::null(), 0x9d, 0xc7, 0xb7, 0x80, 0x9e, 0xc0, 0x11, 0xD4, 0xa5, 0x4f, 0x00, 0x0a, 0x27, 0x05, 0x28, 0x61, ) } } pub fn kIOUsbInterfaceUserClientTypeID() -> CFUUIDRef { unsafe { CFUUIDGetConstantUUIDWithBytes( std::ptr::null(), 0x2d, 0x97, 0x86, 0xc6, 0x9e, 0xf3, 0x11, 0xD4, 0xad, 0x51, 0x00, 0x0a, 0x27, 0x05, 0x28, 0x61, ) } } pub fn kIOCFPlugInInterfaceID() -> CFUUIDRef { unsafe { CFUUIDGetConstantUUIDWithBytes( std::ptr::null(), 0xC2, 0x44, 0xE8, 0x58, 0x10, 0x9C, 0x11, 0xD4, 0x91, 0xD4, 0x00, 0x50, 0xE4, 0xC6, 0x42, 0x6F, ) } } pub fn kIOUSBDeviceInterfaceID500() -> CFUUIDRef { unsafe { CFUUIDGetConstantUUIDWithBytes( kCFAllocatorSystemDefault, 0xA3, 0x3C, 0xF0, 0x47, 0x4B, 0x5B, 0x48, 0xE2, 0xB5, 0x7D, 0x02, 0x07, 0xFC, 0xEA, 0xE1, 0x3B, ) } } pub fn kIOUSBInterfaceInterfaceID500() -> CFUUIDRef { unsafe { CFUUIDGetConstantUUIDWithBytes( kCFAllocatorSystemDefault, 0x6C, 0x0D, 0x38, 0xC3, 0xB0, 0x93, 0x4E, 0xA7, 0x80, 0x9B, 0x09, 0xFB, 0x5D, 0xDD, 0xAC, 0x16, ) } } #[repr(C, packed)] #[derive(Debug, Copy, Clone)] pub struct IOUSBConfigurationDescriptor { pub bLength: u8, pub bDescriptorType: u8, pub wTotalLength: u16, pub bNumInterfaces: u8, pub bConfigurationValue: u8, pub iConfiguration: u8, pub bmAttributes: u8, pub MaxPower: u8, } pub type IOUSBConfigurationDescriptorPtr = *mut IOUSBConfigurationDescriptor; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBDeviceStruct500 { pub _reserved: *mut ::std::os::raw::c_void, pub QueryInterface: ::std::option::Option< unsafe extern "C" fn( thisPointer: *mut ::std::os::raw::c_void, iid: REFIID, ppv: *mut LPVOID, ) -> HRESULT, >, pub AddRef: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, >, pub Release: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, >, pub CreateDeviceAsyncEventSource: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, source: *mut CFRunLoopSourceRef, ) -> IOReturn, >, pub GetDeviceAsyncEventSource: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> CFRunLoopSourceRef, >, pub CreateDeviceAsyncPort: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, port: *mut mach_port_t, ) -> IOReturn, >, pub GetDeviceAsyncPort: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> mach_port_t, >, pub USBDeviceOpen: ::std::option::Option IOReturn>, pub USBDeviceClose: ::std::option::Option IOReturn>, pub GetDeviceClass: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, devClass: *mut UInt8) -> IOReturn, >, pub GetDeviceSubClass: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devSubClass: *mut UInt8, ) -> IOReturn, >, pub GetDeviceProtocol: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devProtocol: *mut UInt8, ) -> IOReturn, >, pub GetDeviceVendor: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devVendor: *mut UInt16, ) -> IOReturn, >, pub GetDeviceProduct: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devProduct: *mut UInt16, ) -> IOReturn, >, pub GetDeviceReleaseNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devRelNum: *mut UInt16, ) -> IOReturn, >, pub GetDeviceAddress: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, addr: *mut USBDeviceAddress, ) -> IOReturn, >, pub GetDeviceBusPowerAvailable: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, powerAvailable: *mut UInt32, ) -> IOReturn, >, pub GetDeviceSpeed: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, devSpeed: *mut UInt8) -> IOReturn, >, pub GetNumberOfConfigurations: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, numConfig: *mut UInt8) -> IOReturn, >, pub GetLocationID: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, locationID: *mut UInt32, ) -> IOReturn, >, pub GetConfigurationDescriptorPtr: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, configIndex: UInt8, desc: *mut IOUSBConfigurationDescriptorPtr, ) -> IOReturn, >, pub GetConfiguration: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, configNum: *mut UInt8) -> IOReturn, >, pub SetConfiguration: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, configNum: UInt8) -> IOReturn, >, pub GetBusFrameNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, frame: *mut UInt64, atTime: *mut AbsoluteTime, ) -> IOReturn, >, pub ResetDevice: ::std::option::Option IOReturn>, pub DeviceRequest: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, req: *mut IOUSBDevRequest, ) -> IOReturn, >, pub DeviceRequestAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, req: *mut IOUSBDevRequest, callback: IOAsyncCallback1, refCon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub CreateInterfaceIterator: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, req: *mut IOUSBFindInterfaceRequest, iter: *mut io_iterator_t, ) -> IOReturn, >, pub USBDeviceOpenSeize: ::std::option::Option IOReturn>, pub DeviceRequestTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, req: *mut IOUSBDevRequestTO, ) -> IOReturn, >, pub DeviceRequestAsyncTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, req: *mut IOUSBDevRequestTO, callback: IOAsyncCallback1, refCon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub USBDeviceSuspend: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, suspend: Boolean) -> IOReturn, >, pub USBDeviceAbortPipeZero: ::std::option::Option IOReturn>, pub USBGetManufacturerStringIndex: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, msi: *mut UInt8) -> IOReturn, >, pub USBGetProductStringIndex: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, psi: *mut UInt8) -> IOReturn, >, pub USBGetSerialNumberStringIndex: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, snsi: *mut UInt8) -> IOReturn, >, pub USBDeviceReEnumerate: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, options: UInt32) -> IOReturn, >, pub GetBusMicroFrameNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, microFrame: *mut UInt64, atTime: *mut AbsoluteTime, ) -> IOReturn, >, pub GetIOUSBLibVersion: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, ioUSBLibVersion: *mut NumVersion, usbFamilyVersion: *mut NumVersion, ) -> IOReturn, >, pub GetBusFrameNumberWithTime: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, frame: *mut UInt64, atTime: *mut AbsoluteTime, ) -> IOReturn, >, pub GetUSBDeviceInformation: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, info: *mut UInt32) -> IOReturn, >, pub RequestExtraPower: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, type_: UInt32, requestedPower: UInt32, powerAvailable: *mut UInt32, ) -> IOReturn, >, pub ReturnExtraPower: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, type_: UInt32, powerReturned: UInt32, ) -> IOReturn, >, pub GetExtraPowerAllocated: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, type_: UInt32, powerAllocated: *mut UInt32, ) -> IOReturn, >, pub GetBandwidthAvailableForDevice: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, bandwidth: *mut UInt32, ) -> IOReturn, >, } pub type IOUSBDeviceInterface500 = IOUSBDeviceStruct500; // Tweak: these are just function pointers to thread-safe functions, // so add send and sync to the C-type. (Calling these from multiple threads // may cause odd behavior on the USB bus, though, so we'll still want to wrap the // device in Mutex somewhere up from here.) unsafe impl Send for IOUSBDeviceInterface500 {} unsafe impl Sync for IOUSBDeviceInterface500 {} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct IOUSBInterfaceStruct500 { pub _reserved: *mut ::std::os::raw::c_void, pub QueryInterface: ::std::option::Option< unsafe extern "C" fn( thisPointer: *mut ::std::os::raw::c_void, iid: REFIID, ppv: *mut LPVOID, ) -> HRESULT, >, pub AddRef: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, >, pub Release: ::std::option::Option< unsafe extern "C" fn(thisPointer: *mut ::std::os::raw::c_void) -> ULONG, >, pub CreateInterfaceAsyncEventSource: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, source: *mut CFRunLoopSourceRef, ) -> IOReturn, >, pub GetInterfaceAsyncEventSource: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> CFRunLoopSourceRef, >, pub CreateInterfaceAsyncPort: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, port: *mut mach_port_t, ) -> IOReturn, >, pub GetInterfaceAsyncPort: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void) -> mach_port_t, >, pub USBInterfaceOpen: ::std::option::Option IOReturn>, pub USBInterfaceClose: ::std::option::Option IOReturn>, pub GetInterfaceClass: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, intfClass: *mut UInt8) -> IOReturn, >, pub GetInterfaceSubClass: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, intfSubClass: *mut UInt8, ) -> IOReturn, >, pub GetInterfaceProtocol: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, intfProtocol: *mut UInt8, ) -> IOReturn, >, pub GetDeviceVendor: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devVendor: *mut UInt16, ) -> IOReturn, >, pub GetDeviceProduct: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devProduct: *mut UInt16, ) -> IOReturn, >, pub GetDeviceReleaseNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, devRelNum: *mut UInt16, ) -> IOReturn, >, pub GetConfigurationValue: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, configVal: *mut UInt8) -> IOReturn, >, pub GetInterfaceNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, intfNumber: *mut UInt8, ) -> IOReturn, >, pub GetAlternateSetting: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, intfAltSetting: *mut UInt8, ) -> IOReturn, >, pub GetNumEndpoints: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, intfNumEndpoints: *mut UInt8, ) -> IOReturn, >, pub GetLocationID: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, locationID: *mut UInt32, ) -> IOReturn, >, pub GetDevice: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, device: *mut io_service_t, ) -> IOReturn, >, pub SetAlternateInterface: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, alternateSetting: UInt8, ) -> IOReturn, >, pub GetBusFrameNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, frame: *mut UInt64, atTime: *mut AbsoluteTime, ) -> IOReturn, >, pub ControlRequest: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, req: *mut IOUSBDevRequest, ) -> IOReturn, >, pub ControlRequestAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, req: *mut IOUSBDevRequest, callback: IOAsyncCallback1, refCon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub GetPipeProperties: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, direction: *mut UInt8, number: *mut UInt8, transferType: *mut UInt8, maxPacketSize: *mut UInt16, interval: *mut UInt8, ) -> IOReturn, >, pub GetPipeStatus: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, >, pub AbortPipe: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, >, pub ResetPipe: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, >, pub ClearPipeStall: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, >, pub ReadPipe: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: *mut UInt32, ) -> IOReturn, >, pub WritePipe: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: UInt32, ) -> IOReturn, >, pub ReadPipeAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: UInt32, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub WritePipeAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: UInt32, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub ReadIsochPipeAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, frameStart: UInt64, numFrames: UInt32, frameList: *mut IOUSBIsocFrame, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub WriteIsochPipeAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, frameStart: UInt64, numFrames: UInt32, frameList: *mut IOUSBIsocFrame, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub ControlRequestTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, req: *mut IOUSBDevRequestTO, ) -> IOReturn, >, pub ControlRequestAsyncTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, req: *mut IOUSBDevRequestTO, callback: IOAsyncCallback1, refCon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub ReadPipeTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: *mut UInt32, noDataTimeout: UInt32, completionTimeout: UInt32, ) -> IOReturn, >, pub WritePipeTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: UInt32, noDataTimeout: UInt32, completionTimeout: UInt32, ) -> IOReturn, >, pub ReadPipeAsyncTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: UInt32, noDataTimeout: UInt32, completionTimeout: UInt32, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub WritePipeAsyncTO: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, size: UInt32, noDataTimeout: UInt32, completionTimeout: UInt32, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub USBInterfaceGetStringIndex: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, si: *mut UInt8) -> IOReturn, >, pub USBInterfaceOpenSeize: ::std::option::Option IOReturn>, pub ClearPipeStallBothEnds: ::std::option::Option< unsafe extern "C" fn(self_: *mut ::std::os::raw::c_void, pipeRef: UInt8) -> IOReturn, >, pub SetPipePolicy: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, maxPacketSize: UInt16, maxInterval: UInt8, ) -> IOReturn, >, pub GetBandwidthAvailable: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, bandwidth: *mut UInt32, ) -> IOReturn, >, pub GetEndpointProperties: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, alternateSetting: UInt8, endpointNumber: UInt8, direction: UInt8, transferType: *mut UInt8, maxPacketSize: *mut UInt16, interval: *mut UInt8, ) -> IOReturn, >, pub LowLatencyReadIsochPipeAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, frameStart: UInt64, numFrames: UInt32, updateFrequency: UInt32, frameList: *mut IOUSBLowLatencyIsocFrame, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub LowLatencyWriteIsochPipeAsync: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, buf: *mut ::std::os::raw::c_void, frameStart: UInt64, numFrames: UInt32, updateFrequency: UInt32, frameList: *mut IOUSBLowLatencyIsocFrame, callback: IOAsyncCallback1, refcon: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub LowLatencyCreateBuffer: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, buffer: *mut *mut ::std::os::raw::c_void, size: IOByteCount, bufferType: UInt32, ) -> IOReturn, >, pub LowLatencyDestroyBuffer: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, buffer: *mut ::std::os::raw::c_void, ) -> IOReturn, >, pub GetBusMicroFrameNumber: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, microFrame: *mut UInt64, atTime: *mut AbsoluteTime, ) -> IOReturn, >, pub GetFrameListTime: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, microsecondsInFrame: *mut UInt32, ) -> IOReturn, >, pub GetIOUSBLibVersion: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, ioUSBLibVersion: *mut NumVersion, usbFamilyVersion: *mut NumVersion, ) -> IOReturn, >, pub FindNextAssociatedDescriptor: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, currentDescriptor: *const ::std::os::raw::c_void, descriptorType: UInt8, ) -> *mut IOUSBDescriptorHeader, >, pub FindNextAltInterface: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, current: *const ::std::os::raw::c_void, request: *mut IOUSBFindInterfaceRequest, ) -> *mut IOUSBDescriptorHeader, >, pub GetBusFrameNumberWithTime: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, frame: *mut UInt64, atTime: *mut AbsoluteTime, ) -> IOReturn, >, pub GetPipePropertiesV2: ::std::option::Option< unsafe extern "C" fn( self_: *mut ::std::os::raw::c_void, pipeRef: UInt8, direction: *mut UInt8, number: *mut UInt8, transferType: *mut UInt8, maxPacketSize: *mut UInt16, interval: *mut UInt8, maxBurst: *mut UInt8, mult: *mut UInt8, bytesPerInterval: *mut UInt16, ) -> IOReturn, >, } // Tweak: these are just function pointers to thread-safe functions, // so add send and sync to the C-type. (Calling these from multiple threads // may cause odd behavior on the USB bus, though, so we'll still want to wrap the // device in Mutex somewhere up from here.) unsafe impl Send for IOUSBInterfaceStruct500 {} unsafe impl Sync for IOUSBInterfaceStruct500 {} nusb-0.1.13/src/platform/macos_iokit/iokit_usb.rs000064400000000000000000000277551046102023000201470ustar 00000000000000//! Wrappers for IOKit USB device and interface //! //! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) //! licensed under MIT OR Apache-2.0. use std::{collections::BTreeMap, io::ErrorKind, ptr, slice, time::Duration}; use core_foundation::{base::TCFType, runloop::CFRunLoopSource}; use core_foundation_sys::runloop::CFRunLoopSourceRef; use io_kit_sys::{ ret::{kIOReturnNoResources, kIOReturnSuccess}, types::io_iterator_t, }; use log::error; use crate::{ platform::macos_iokit::{ iokit::usb_interface_type_id, iokit_c::kIOUsbInterfaceUserClientTypeID, }, Error, }; use super::{ iokit::{ self, call_iokit_function, check_iokit_return, usb_device_type_id, IoService, IoServiceIterator, PluginInterface, }, iokit_c::{ kIOCFPlugInInterfaceID, kIOUSBFindInterfaceDontCare, kIOUsbDeviceUserClientTypeID, IOCFPlugInInterface, IOCreatePlugInInterfaceForService, IOUSBConfigurationDescriptor, IOUSBFindInterfaceRequest, }, }; /// Wrapper around an IOKit UsbDevice pub(crate) struct IoKitDevice { pub(crate) raw: *mut *mut iokit::UsbDevice, } impl IoKitDevice { /// Get the raw USB device associated with the service. pub(crate) fn new(service: IoService) -> Result { unsafe { // According to the libusb maintainers, this will sometimes spuriously // return `kIOReturnNoResources` for reasons Apple won't explain, usually // when a device is freshly plugged in. We'll allow this a few retries, // accordingly. // // [This behavior actually makes sense to me -- when the device is first plugged // in, it exists to IOKit, but hasn't been enumerated, yet. Accordingly, the device // interface doesn't actually yet exist for us to grab, and/or doesn't yet have the // right permissions for us to grab it. MacOS needs to see if a kernel driver binds // to it; as its security model won't allow the userland to grab a device that the // kernel owns.] // // If the kIOReturnNoResources persists, it's typically an indication that // macOS is preventing us from touching the relevant device due to its security // model. This happens when the device has a kernel-mode driver bound to the // whole device -- the kernel owns it, and it's unwilling to give it to us. for _ in 0..5 { let mut _score: i32 = 0; let mut raw_device_plugin: *mut *mut IOCFPlugInInterface = std::ptr::null_mut(); let rc = IOCreatePlugInInterfaceForService( service.get(), kIOUsbDeviceUserClientTypeID(), kIOCFPlugInInterfaceID(), &mut raw_device_plugin, &mut _score, ); if rc == kIOReturnNoResources { std::thread::sleep(Duration::from_millis(1)); continue; } if rc != kIOReturnSuccess { return Err(Error::from_raw_os_error(rc)); } if raw_device_plugin.is_null() { error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); return Err(Error::new( ErrorKind::Other, "Could not create PlugInInterface", )); } let device_plugin = PluginInterface::new(raw_device_plugin); let mut raw_device: *mut *mut iokit::UsbDevice = std::ptr::null_mut(); call_iokit_function!( device_plugin.get(), QueryInterface( usb_device_type_id(), &mut raw_device as *mut *mut *mut _ as *mut *mut c_void ) ); // macOS claims that call will never fail, and will always produce a valid pointer. // We don't trust it, so we're going to panic if it's lied to us. if raw_device.is_null() { panic!( "query_interface returned a null pointer, which Apple says is impossible" ); } return Ok(IoKitDevice { raw: raw_device }); } return Err(Error::new( ErrorKind::NotFound, "Couldn't create device after retries", )); } } pub(crate) fn create_async_event_source(&self) -> Result { unsafe { let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); check_iokit_return(call_iokit_function!( self.raw, CreateDeviceAsyncEventSource(&mut raw_source) ))?; Ok(CFRunLoopSource::wrap_under_create_rule(raw_source)) } } /// Returns an IOKit iterator that can be used to iterate over all interfaces on this device. pub(crate) fn create_interface_iterator(&self) -> Result { unsafe { let mut iterator: io_iterator_t = 0; let mut dont_care = IOUSBFindInterfaceRequest { bInterfaceClass: kIOUSBFindInterfaceDontCare, bInterfaceSubClass: kIOUSBFindInterfaceDontCare, bInterfaceProtocol: kIOUSBFindInterfaceDontCare, bAlternateSetting: kIOUSBFindInterfaceDontCare, }; check_iokit_return(call_iokit_function!( self.raw, CreateInterfaceIterator(&mut dont_care, &mut iterator) ))?; Ok(IoServiceIterator::new(iterator)) } } pub(crate) fn get_number_of_configurations(&self) -> Result { unsafe { let mut num = 0; check_iokit_return(call_iokit_function!( self.raw, GetNumberOfConfigurations(&mut num) ))?; Ok(num) } } pub(crate) fn get_configuration_descriptor(&self, index: u8) -> Result<&[u8], Error> { unsafe { let mut ptr: *mut IOUSBConfigurationDescriptor = ptr::null_mut(); check_iokit_return(call_iokit_function!( self.raw, GetConfigurationDescriptorPtr(index, &mut ptr) ))?; let len = u16::to_le((*ptr).wTotalLength) as usize; Ok(slice::from_raw_parts(ptr as *const u8, len)) } } pub(crate) fn get_configuration(&self) -> Result { unsafe { let mut val = 0; check_iokit_return(call_iokit_function!(self.raw, GetConfiguration(&mut val)))?; Ok(val) } } } impl Drop for IoKitDevice { fn drop(&mut self) { unsafe { call_iokit_function!(self.raw, Release()); } } } unsafe impl Send for IoKitDevice {} unsafe impl Sync for IoKitDevice {} #[derive(Debug)] #[allow(dead_code)] pub(crate) struct EndpointInfo { pub(crate) pipe_ref: u8, pub(crate) direction: u8, pub(crate) number: u8, pub(crate) transfer_type: u8, pub(crate) max_packet_size: u16, pub(crate) interval: u8, pub(crate) max_burst: u8, pub(crate) mult: u8, pub(crate) bytes_per_interval: u16, } impl EndpointInfo { pub(crate) fn address(&self) -> u8 { if self.direction == 0 { self.number } else { self.number | 0x80 } } } /// Wrapper around an IOKit UsbInterface pub(crate) struct IoKitInterface { pub(crate) raw: *mut *mut iokit::UsbInterface, } impl IoKitInterface { pub(crate) fn new(service: IoService) -> Result { unsafe { let mut _score: i32 = 0; let mut raw_interface_plugin: *mut *mut IOCFPlugInInterface = std::ptr::null_mut(); let rc = IOCreatePlugInInterfaceForService( service.get(), kIOUsbInterfaceUserClientTypeID(), kIOCFPlugInInterfaceID(), &mut raw_interface_plugin, &mut _score, ); if rc != kIOReturnSuccess { return Err(Error::from_raw_os_error(rc)); } if raw_interface_plugin.is_null() { error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); return Err(Error::new( ErrorKind::Other, "Could not create PlugInInterface", )); } let interface_plugin = PluginInterface::new(raw_interface_plugin); let mut raw: *mut *mut iokit::UsbInterface = std::ptr::null_mut(); call_iokit_function!( interface_plugin.get(), QueryInterface( usb_interface_type_id(), &mut raw as *mut *mut *mut _ as *mut *mut c_void ) ); // macOS claims that call will never fail, and will always produce a valid pointer. // We don't trust it, so we're going to panic if it's lied to us. if raw.is_null() { panic!("query_interface returned a null pointer, which Apple says is impossible"); } return Ok(IoKitInterface { raw }); } } pub(crate) fn open(&mut self) -> Result<(), Error> { unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceOpen())) } } pub(crate) fn close(&mut self) -> Result<(), Error> { unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceClose())) } } pub(crate) fn create_async_event_source(&self) -> Result { unsafe { let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); check_iokit_return(call_iokit_function!( self.raw, CreateInterfaceAsyncEventSource(&mut raw_source) ))?; Ok(CFRunLoopSource::wrap_under_create_rule(raw_source)) } } pub(crate) fn endpoints(&self) -> Result, Error> { unsafe { let mut endpoints = BTreeMap::new(); let mut count = 0; check_iokit_return(call_iokit_function!(self.raw, GetNumEndpoints(&mut count)))?; // Pipe references are 1-indexed for pipe_ref in 1..=count { let mut direction: u8 = 0; let mut number: u8 = 0; let mut transfer_type: u8 = 0; let mut max_packet_size: u16 = 0; let mut interval: u8 = 0; let mut max_burst: u8 = 0; let mut mult: u8 = 0; let mut bytes_per_interval: u16 = 0; check_iokit_return(call_iokit_function!( self.raw, GetPipePropertiesV2( pipe_ref, &mut direction, &mut number, &mut transfer_type, &mut max_packet_size, &mut interval, &mut max_burst, &mut mult, &mut bytes_per_interval ) ))?; let endpoint = EndpointInfo { pipe_ref, direction, number, transfer_type, max_packet_size, interval, max_burst, mult, bytes_per_interval, }; endpoints.insert(endpoint.address(), endpoint); } Ok(endpoints) } } } impl Drop for IoKitInterface { fn drop(&mut self) { unsafe { call_iokit_function!(self.raw, Release()); } } } unsafe impl Send for IoKitInterface {} unsafe impl Sync for IoKitInterface {} nusb-0.1.13/src/platform/macos_iokit/mod.rs000064400000000000000000000017471046102023000167270ustar 00000000000000mod transfer; use io_kit_sys::ret::IOReturn; pub(crate) use transfer::TransferData; mod enumeration; mod events; pub use enumeration::list_devices; mod device; pub(crate) use device::MacDevice as Device; pub(crate) use device::MacInterface as Interface; mod hotplug; pub(crate) use hotplug::MacHotplugWatch as HotplugWatch; use crate::transfer::TransferError; mod iokit; mod iokit_c; mod iokit_usb; /// Device ID is the registry entry ID pub type DeviceId = u64; fn status_to_transfer_result(status: IOReturn) -> Result<(), TransferError> { #[allow(non_upper_case_globals)] #[deny(unreachable_patterns)] match status { io_kit_sys::ret::kIOReturnSuccess | io_kit_sys::ret::kIOReturnUnderrun => Ok(()), io_kit_sys::ret::kIOReturnNoDevice => Err(TransferError::Disconnected), io_kit_sys::ret::kIOReturnAborted => Err(TransferError::Cancelled), iokit_c::kIOUSBPipeStalled => Err(TransferError::Stall), _ => Err(TransferError::Unknown), } } nusb-0.1.13/src/platform/macos_iokit/transfer.rs000064400000000000000000000247351046102023000177760ustar 00000000000000use std::{ ffi::c_void, mem::{self, ManuallyDrop}, ptr::null_mut, sync::Arc, }; use io_kit_sys::ret::{kIOReturnSuccess, IOReturn}; use log::{debug, error}; use crate::{ platform::macos_iokit::iokit_c::IOUSBDevRequest, transfer::{ notify_completion, Completion, ControlIn, ControlOut, PlatformSubmit, PlatformTransfer, RequestBuffer, ResponseBuffer, TransferError, }, }; use super::{iokit::call_iokit_function, status_to_transfer_result}; extern "C" fn transfer_callback(refcon: *mut c_void, result: IOReturn, len: *mut c_void) { debug!( "Completion callback for transfer {refcon:?}, status={result:x}, len={len}", len = len as usize ); unsafe { let callback_data = { let inner = &mut *(refcon as *mut TransferDataInner); inner.actual_len = len as usize; inner.status = result; inner.callback_data }; notify_completion::(callback_data) } } pub struct TransferData { endpoint_addr: u8, pipe_ref: u8, buf: *mut u8, capacity: usize, inner: *mut TransferDataInner, device: Arc, interface: Option>, } impl Drop for TransferData { fn drop(&mut self) { if !self.buf.is_null() { unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity)) } } unsafe { drop(Box::from_raw(self.inner)) } } } /// Bring the data accessed on the transfer callback out-of-line /// so that we can have a reference to it while the callback may /// write to other fields concurrently. This could be included /// in `TransferData`` with the proposed [`UnsafePinned`](https://github.com/rust-lang/rfcs/pull/3467) pub struct TransferDataInner { actual_len: usize, callback_data: *mut c_void, status: IOReturn, } impl TransferData { pub(super) fn new( device: Arc, interface: Arc, endpoint_addr: u8, pipe_ref: u8, ) -> TransferData { TransferData { endpoint_addr, pipe_ref, buf: null_mut(), capacity: 0, inner: Box::into_raw(Box::new(TransferDataInner { actual_len: 0, callback_data: null_mut(), status: kIOReturnSuccess, })), device, interface: Some(interface), } } pub(super) fn new_control(device: Arc) -> TransferData { TransferData { endpoint_addr: 0, pipe_ref: 0, buf: null_mut(), capacity: 0, inner: Box::into_raw(Box::new(TransferDataInner { actual_len: 0, callback_data: null_mut(), status: kIOReturnSuccess, })), device, interface: None, } } /// SAFETY: Requires that the transfer is not active unsafe fn fill(&mut self, buf: Vec, callback_data: *mut c_void) { let mut buf = ManuallyDrop::new(buf); self.buf = buf.as_mut_ptr(); self.capacity = buf.capacity(); let inner = &mut *self.inner; inner.actual_len = 0; inner.status = kIOReturnSuccess; inner.callback_data = callback_data; } /// SAFETY: requires that the transfer has completed and `length` bytes are initialized unsafe fn take_buf(&mut self, length: usize) -> Vec { assert!(!self.buf.is_null()); let ptr = mem::replace(&mut self.buf, null_mut()); let capacity = mem::replace(&mut self.capacity, 0); assert!(length <= capacity); Vec::from_raw_parts(ptr, length, capacity) } /// SAFETY: requires that the transfer is not active, but is fully prepared (as it is when submitting the transfer fails) unsafe fn check_submit_result(&mut self, res: IOReturn) { if res != kIOReturnSuccess { error!( "Failed to submit transfer on endpoint {ep}: {res:x}", ep = self.endpoint_addr ); let callback_data = { let inner = &mut *self.inner; inner.status = res; inner.callback_data }; // Complete the transfer in the place of the callback notify_completion::(callback_data) } } /// SAFETY: requires that the transfer is in a completed state unsafe fn take_status(&mut self) -> (Result<(), TransferError>, usize) { let inner = unsafe { &*self.inner }; (status_to_transfer_result(inner.status), inner.actual_len) } } unsafe impl Send for TransferData {} impl PlatformTransfer for TransferData { fn cancel(&self) { if let Some(intf) = self.interface.as_ref() { let r = unsafe { call_iokit_function!(intf.interface.raw, AbortPipe(self.pipe_ref)) }; debug!( "Cancelled all transfers on endpoint {ep:02x}. status={r:x}", ep = self.endpoint_addr ); } else { assert!(self.pipe_ref == 0); let r = unsafe { call_iokit_function!(self.device.device.raw, USBDeviceAbortPipeZero()) }; debug!("Cancelled all transfers on control pipe. status={r:x}"); } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: Vec, callback_data: *mut std::ffi::c_void) { assert!(self.endpoint_addr & 0x80 == 0); let len = data.len(); self.fill(data, callback_data); // SAFETY: we just properly filled the buffer and it is not already pending let res = call_iokit_function!( self.interface.as_ref().unwrap().interface.raw, WritePipeAsync( self.pipe_ref, self.buf as *mut c_void, u32::try_from(len).expect("request too large"), transfer_callback, self.inner as *mut c_void ) ); debug!( "Submitted OUT transfer {inner:?} on endpoint {ep:02x}", inner = self.inner, ep = self.endpoint_addr ); self.check_submit_result(res); } unsafe fn take_completed(&mut self) -> crate::transfer::Completion { let (status, actual_len) = self.take_status(); // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. let data = ResponseBuffer::from_vec(unsafe { self.take_buf(0) }, actual_len); Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: RequestBuffer, callback_data: *mut std::ffi::c_void) { assert!(self.endpoint_addr & 0x80 == 0x80); let (data, len) = data.into_vec(); self.fill(data, callback_data); // SAFETY: we just properly filled the buffer and it is not already pending let res = call_iokit_function!( self.interface.as_ref().unwrap().interface.raw, ReadPipeAsync( self.pipe_ref, self.buf as *mut c_void, u32::try_from(len).expect("request too large"), transfer_callback, self.inner as *mut c_void ) ); debug!( "Submitted IN transfer {inner:?} on endpoint {ep:02x}", inner = self.inner, ep = self.endpoint_addr ); self.check_submit_result(res); } unsafe fn take_completed(&mut self) -> crate::transfer::Completion> { let (status, actual_len) = self.take_status(); // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. let data = unsafe { self.take_buf(actual_len) }; Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: ControlIn, callback_data: *mut std::ffi::c_void) { assert!(self.pipe_ref == 0); let buf = Vec::with_capacity(data.length as usize); self.fill(buf, callback_data); let mut req = IOUSBDevRequest { bmRequestType: data.request_type(), bRequest: data.request, wValue: data.value, wIndex: data.index, wLength: data.length, pData: self.buf as *mut c_void, wLenDone: 0, }; // SAFETY: we just properly filled the buffer and it is not already pending let res = call_iokit_function!( self.device.device.raw, DeviceRequestAsync(&mut req, transfer_callback, self.inner as *mut c_void) ); debug!( "Submitted Control IN transfer {inner:?}", inner = self.inner ); self.check_submit_result(res); } unsafe fn take_completed(&mut self) -> crate::transfer::Completion> { let (status, actual_len) = self.take_status(); // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. let data = unsafe { self.take_buf(actual_len) }; Completion { data, status } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: ControlOut<'_>, callback_data: *mut std::ffi::c_void) { assert!(self.pipe_ref == 0); let buf = data.data.to_vec(); let len = buf.len(); self.fill(buf, callback_data); let mut req = IOUSBDevRequest { bmRequestType: data.request_type(), bRequest: data.request, wValue: data.value, wIndex: data.index, wLength: u16::try_from(len).expect("request too long"), pData: self.buf as *mut c_void, wLenDone: 0, }; // SAFETY: we just properly filled the buffer and it is not already pending let res = call_iokit_function!( self.device.device.raw, DeviceRequestAsync(&mut req, transfer_callback, self.inner as *mut c_void) ); debug!( "Submitted Control OUT transfer {inner:?}", inner = self.inner ); self.check_submit_result(res); } unsafe fn take_completed(&mut self) -> crate::transfer::Completion { let (status, actual_len) = self.take_status(); // SAFETY: self is completed (precondition) and `actual_length` bytes were initialized. let data = ResponseBuffer::from_vec(unsafe { self.take_buf(0) }, actual_len); Completion { data, status } } } nusb-0.1.13/src/platform/mod.rs000064400000000000000000000005521046102023000144170ustar 00000000000000#[cfg(any(target_os = "linux", target_os = "android"))] mod linux_usbfs; #[cfg(any(target_os = "linux", target_os = "android"))] pub use linux_usbfs::*; #[cfg(target_os = "windows")] mod windows_winusb; #[cfg(target_os = "windows")] pub use windows_winusb::*; #[cfg(target_os = "macos")] mod macos_iokit; #[cfg(target_os = "macos")] pub use macos_iokit::*; nusb-0.1.13/src/platform/windows_winusb/cfgmgr32.rs000064400000000000000000000167241046102023000203430ustar 00000000000000use std::{ffi::OsString, iter, mem, ptr}; use log::debug; use windows_sys::{ core::GUID, Win32::{ Devices::{ DeviceAndDriverInstallation::{ CM_Get_Child, CM_Get_DevNode_PropertyW, CM_Get_Device_Interface_ListW, CM_Get_Device_Interface_List_SizeW, CM_Get_Device_Interface_PropertyW, CM_Get_Parent, CM_Get_Sibling, CM_Locate_DevNodeW, CM_Open_DevNode_Key, RegDisposition_OpenExisting, CM_GET_DEVICE_INTERFACE_LIST_PRESENT, CM_LOCATE_DEVNODE_PHANTOM, CM_REGISTRY_HARDWARE, CR_BUFFER_SMALL, CR_SUCCESS, }, Properties::{ DEVPKEY_Device_InstanceId, DEVPROPKEY, DEVPROPTYPE, DEVPROP_TYPE_STRING, DEVPROP_TYPE_STRING_LIST, DEVPROP_TYPE_UINT32, }, }, Foundation::INVALID_HANDLE_VALUE, System::Registry::KEY_READ, }, }; use super::{ registry::RegKey, util::{NulSepList, NulSepListIter, WCStr, WCString}, }; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct DevInst(u32); impl DevInst { pub fn from_instance_id(id: &WCStr) -> Option { let mut devinst = 0; let c = unsafe { CM_Locate_DevNodeW(&mut devinst, id.as_ptr(), CM_LOCATE_DEVNODE_PHANTOM) }; if c == CR_SUCCESS { Some(DevInst(devinst)) } else { None } } pub fn get_property(&self, pkey: DEVPROPKEY) -> Option { let mut property_type: DEVPROPTYPE = 0; let mut buffer: T::Buffer = T::empty_buffer(); let mut size: u32 = mem::size_of_val(&buffer) as u32; let r = unsafe { CM_Get_DevNode_PropertyW( self.0, &pkey, &mut property_type, &mut buffer as *mut _ as *mut u8, &mut size, 0, ) }; if r == CR_SUCCESS && property_type == T::PROPTYPE { Some(T::from_buffer(&buffer)) } else { None } } pub fn instance_id(&self) -> WCString { self.get_property(DEVPKEY_Device_InstanceId) .expect("device should always have instance ID") } pub fn parent(&self) -> Option { let mut out = 0; let cr = unsafe { CM_Get_Parent(&mut out, self.0, 0) }; if cr == CR_SUCCESS { Some(DevInst(out)) } else { None } } pub fn first_child(&self) -> Option { let mut out = 0; let cr = unsafe { CM_Get_Child(&mut out, self.0, 0) }; if cr == CR_SUCCESS { Some(DevInst(out)) } else { None } } pub fn next_sibling(&self) -> Option { let mut out = 0; let cr = unsafe { CM_Get_Sibling(&mut out, self.0, 0) }; if cr == CR_SUCCESS { Some(DevInst(out)) } else { None } } pub fn children(&self) -> impl Iterator { let mut node = self.first_child(); iter::from_fn(move || { if let Some(n) = node { node = n.next_sibling(); Some(n) } else { None } }) } pub fn registry_key(&self) -> Option { let mut hkey = INVALID_HANDLE_VALUE; let cr = unsafe { CM_Open_DevNode_Key( self.0, KEY_READ, 0, RegDisposition_OpenExisting, &mut hkey, CM_REGISTRY_HARDWARE, ) }; if cr == CR_SUCCESS && hkey != INVALID_HANDLE_VALUE { Some(unsafe { RegKey::new(hkey) }) } else { None } } /// Get interfaces of this device with the specified interface class. /// /// Note: these are Windows device interfaces (paths to open a device /// handle), not to be confused with USB interfaces. pub fn interfaces(&self, interface: GUID) -> NulSepList { let id = self.instance_id(); list_interfaces(interface, Some(&id)) } } pub trait PropertyType { const PROPTYPE: DEVPROPTYPE; type Buffer; fn empty_buffer() -> Self::Buffer; fn from_buffer(b: &Self::Buffer) -> Self; } impl PropertyType for u32 { const PROPTYPE: DEVPROPTYPE = DEVPROP_TYPE_UINT32; type Buffer = u32; fn empty_buffer() -> u32 { 0 } fn from_buffer(b: &Self::Buffer) -> Self { *b } } impl PropertyType for WCString { const PROPTYPE: DEVPROPTYPE = DEVPROP_TYPE_STRING; type Buffer = [u16; 1024]; fn empty_buffer() -> Self::Buffer { [0; 1024] } fn from_buffer(b: &Self::Buffer) -> Self { WCStr::from_slice_until_nul(b).to_owned() } } impl PropertyType for Vec { const PROPTYPE: DEVPROPTYPE = DEVPROP_TYPE_STRING_LIST; type Buffer = [u16; 1024]; fn empty_buffer() -> Self::Buffer { [0; 1024] } fn from_buffer(b: &Self::Buffer) -> Self { NulSepListIter(b).map(|s| s.to_owned()).collect() } } impl PropertyType for OsString { const PROPTYPE: DEVPROPTYPE = DEVPROP_TYPE_STRING; type Buffer = [u16; 1024]; fn empty_buffer() -> Self::Buffer { [0; 1024] } fn from_buffer(b: &Self::Buffer) -> Self { WCStr::from_slice_until_nul(b).into() } } impl PropertyType for Vec { const PROPTYPE: DEVPROPTYPE = DEVPROP_TYPE_STRING_LIST; type Buffer = [u16; 1024]; fn empty_buffer() -> Self::Buffer { [0; 1024] } fn from_buffer(b: &Self::Buffer) -> Self { NulSepListIter(b).map(|s| s.into()).collect() } } pub fn list_interfaces(interface: GUID, instance_id: Option<&WCStr>) -> NulSepList { let flags = CM_GET_DEVICE_INTERFACE_LIST_PRESENT; let mut buf: Vec = Vec::new(); loop { let mut len = 0; let cr = unsafe { CM_Get_Device_Interface_List_SizeW( &mut len, &interface, instance_id.map_or(ptr::null(), |x| x.as_ptr()), flags, ) }; if cr != CR_SUCCESS { buf.clear(); debug!("CM_Get_Device_Interface_List_SizeW failed, status {cr}"); break; } buf.resize(len as usize, 0); let cr = unsafe { CM_Get_Device_Interface_ListW( &interface, instance_id.map_or(ptr::null(), |x| x.as_ptr()), buf.as_mut_ptr(), buf.len() as u32, flags, ) }; if cr == CR_SUCCESS { break; } else if cr == CR_BUFFER_SMALL { continue; } else { buf.clear(); debug!("CM_Get_Device_Interface_ListW failed, status {cr}"); break; } } NulSepList(buf) } pub fn get_device_interface_property( interface: &WCStr, pkey: DEVPROPKEY, ) -> Option { let mut property_type: DEVPROPTYPE = 0; let mut buffer: T::Buffer = T::empty_buffer(); let mut size: u32 = mem::size_of_val(&buffer) as u32; let r = unsafe { CM_Get_Device_Interface_PropertyW( interface.as_ptr(), &pkey, &mut property_type, &mut buffer as *mut _ as *mut u8, &mut size, 0, ) }; if r == CR_SUCCESS && property_type == T::PROPTYPE { Some(T::from_buffer(&buffer)) } else { None } } nusb-0.1.13/src/platform/windows_winusb/device.rs000064400000000000000000000215641046102023000201660ustar 00000000000000use std::{ ffi::c_void, io::{self, ErrorKind}, mem::size_of_val, os::windows::prelude::OwnedHandle, ptr::null_mut, sync::Arc, time::Duration, }; use log::{debug, error, warn}; use windows_sys::Win32::{ Devices::Usb::{ WinUsb_ControlTransfer, WinUsb_Free, WinUsb_Initialize, WinUsb_ResetPipe, WinUsb_SetCurrentAlternateSetting, WinUsb_SetPipePolicy, PIPE_TRANSFER_TIMEOUT, WINUSB_INTERFACE_HANDLE, WINUSB_SETUP_PACKET, }, Foundation::{GetLastError, FALSE, TRUE}, }; use crate::{ descriptors::{validate_config_descriptor, DESCRIPTOR_TYPE_CONFIGURATION}, transfer::{Control, Direction, EndpointType, Recipient, TransferError, TransferHandle}, DeviceInfo, Error, }; use super::{ enumeration::find_device_interface_path, hub::HubPort, util::{create_file, raw_handle}, DevInst, }; pub(crate) struct WindowsDevice { config_descriptors: Vec>, active_config: u8, devinst: DevInst, } impl WindowsDevice { pub(crate) fn from_device_info(d: &DeviceInfo) -> Result, Error> { debug!("Creating device for {:?}", d.instance_id); // Look up the device again in case the DeviceInfo is stale. In // particular, don't trust its `port_number` because another device // might now be connected to that port, and we'd get its descriptors // instead. let hub_port = HubPort::by_child_devinst(d.devinst)?; let connection_info = hub_port.get_info()?; let num_configurations = connection_info.device_desc.bNumConfigurations; let config_descriptors = (0..num_configurations) .flat_map(|i| { let res = hub_port.get_descriptor(DESCRIPTOR_TYPE_CONFIGURATION, i, 0); match res { Ok(v) => validate_config_descriptor(&v[..]).map(|_| v), Err(e) => { error!("Failed to read config descriptor {}: {}", i, e); None } } }) .collect(); Ok(Arc::new(WindowsDevice { config_descriptors, active_config: connection_info.active_config, devinst: d.devinst, })) } pub(crate) fn active_configuration_value(&self) -> u8 { self.active_config } pub(crate) fn configuration_descriptors(&self) -> impl Iterator { self.config_descriptors.iter().map(|d| &d[..]) } pub(crate) fn set_configuration(&self, _configuration: u8) -> Result<(), Error> { Err(io::Error::new( ErrorKind::Unsupported, "set_configuration not supported by WinUSB", )) } pub(crate) fn get_descriptor( &self, desc_type: u8, desc_index: u8, language_id: u16, ) -> Result, Error> { HubPort::by_child_devinst(self.devinst)?.get_descriptor(desc_type, desc_index, language_id) } pub(crate) fn reset(&self) -> Result<(), Error> { Err(io::Error::new( ErrorKind::Unsupported, "reset not supported by WinUSB", )) } pub(crate) fn claim_interface( self: &Arc, interface_number: u8, ) -> Result, Error> { let path = find_device_interface_path(self.devinst, interface_number)?; log::debug!( "Claiming device {:?} interface {interface_number} with interface path `{path}`", self.devinst ); let handle = create_file(&path)?; super::events::register(&handle)?; let winusb_handle = unsafe { let mut h = 0; if WinUsb_Initialize(raw_handle(&handle), &mut h) == FALSE { error!("WinUsb_Initialize failed: {:?}", io::Error::last_os_error()); return Err(io::Error::last_os_error()); } h }; Ok(Arc::new(WindowsInterface { handle, device: self.clone(), interface_number, winusb_handle, })) } pub(crate) fn detach_and_claim_interface( self: &Arc, interface: u8, ) -> Result, Error> { self.claim_interface(interface) } } pub(crate) struct WindowsInterface { pub(crate) handle: OwnedHandle, pub(crate) device: Arc, pub(crate) interface_number: u8, pub(crate) winusb_handle: WINUSB_INTERFACE_HANDLE, } impl WindowsInterface { pub(crate) fn make_transfer( self: &Arc, endpoint: u8, ep_type: EndpointType, ) -> TransferHandle { TransferHandle::new(super::TransferData::new(self.clone(), endpoint, ep_type)) } /// SAFETY: `data` must be valid for `len` bytes to read or write, depending on `Direction` unsafe fn control_blocking( &self, direction: Direction, control: Control, data: *mut u8, len: usize, timeout: Duration, ) -> Result { debug!("Blocking control {direction:?}, {len} bytes"); if control.recipient == Recipient::Interface && control.index as u8 != self.interface_number { warn!("WinUSB sends interface number instead of passed `index` when performing a control transfer with `Recipient::Interface`"); } let timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32; let r = WinUsb_SetPipePolicy( self.winusb_handle, 0, PIPE_TRANSFER_TIMEOUT, size_of_val(&timeout_ms) as u32, &timeout_ms as *const u32 as *const c_void, ); if r != TRUE { error!( "WinUsb_SetPipePolicy PIPE_TRANSFER_TIMEOUT failed: {}", io::Error::last_os_error() ); } let pkt = WINUSB_SETUP_PACKET { RequestType: control.request_type(direction), Request: control.request, Value: control.value, Index: control.index, Length: len.try_into().expect("request size too large"), }; let mut actual_len = 0; let r = WinUsb_ControlTransfer( self.winusb_handle, pkt, data, len.try_into().expect("request size too large"), &mut actual_len, null_mut(), ); if r == TRUE { Ok(actual_len as usize) } else { error!( "WinUsb_ControlTransfer failed: {}", io::Error::last_os_error() ); Err(super::transfer::map_error(GetLastError())) } } pub fn control_in_blocking( &self, control: Control, data: &mut [u8], timeout: Duration, ) -> Result { unsafe { self.control_blocking( Direction::In, control, data.as_mut_ptr(), data.len(), timeout, ) } } pub fn control_out_blocking( &self, control: Control, data: &[u8], timeout: Duration, ) -> Result { // When passed a pointer to read-only memory (e.g. a constant slice), // WinUSB fails with "Invalid access to memory location. (os error 998)". // I assume the kernel is checking the pointer for write access // regardless of the transfer direction. Copy the data to the stack to ensure // we give it a pointer to writable memory. let mut buf = [0; 4096]; let Some(buf) = buf.get_mut(..data.len()) else { error!( "Control transfer length {} exceeds limit of 4096", data.len() ); return Err(TransferError::Unknown); }; buf.copy_from_slice(data); unsafe { self.control_blocking( Direction::Out, control, buf.as_mut_ptr(), buf.len(), timeout, ) } } pub fn set_alt_setting(&self, alt_setting: u8) -> Result<(), Error> { unsafe { let r = WinUsb_SetCurrentAlternateSetting(self.winusb_handle, alt_setting.into()); if r == TRUE { Ok(()) } else { Err(io::Error::last_os_error()) } } } pub fn clear_halt(&self, endpoint: u8) -> Result<(), Error> { debug!("Clear halt, endpoint {endpoint:02x}"); unsafe { let r = WinUsb_ResetPipe(self.winusb_handle, endpoint); if r == TRUE { Ok(()) } else { Err(io::Error::last_os_error()) } } } } impl Drop for WindowsInterface { fn drop(&mut self) { unsafe { WinUsb_Free(self.winusb_handle); } } } nusb-0.1.13/src/platform/windows_winusb/enumeration.rs000064400000000000000000000212471046102023000212530ustar 00000000000000use std::{ ffi::{OsStr, OsString}, io::ErrorKind, }; use log::debug; use windows_sys::Win32::Devices::{ Properties::{ DEVPKEY_Device_Address, DEVPKEY_Device_BusNumber, DEVPKEY_Device_BusReportedDeviceDesc, DEVPKEY_Device_CompatibleIds, DEVPKEY_Device_HardwareIds, DEVPKEY_Device_InstanceId, DEVPKEY_Device_Parent, DEVPKEY_Device_Service, }, Usb::GUID_DEVINTERFACE_USB_DEVICE, }; use crate::{ descriptors::{decode_string_descriptor, language_id::US_ENGLISH, DESCRIPTOR_TYPE_STRING}, DeviceInfo, Error, InterfaceInfo, }; use super::{ cfgmgr32::{self, get_device_interface_property, DevInst}, hub::HubPort, util::WCString, }; pub fn list_devices() -> Result, Error> { let devs: Vec = cfgmgr32::list_interfaces(GUID_DEVINTERFACE_USB_DEVICE, None) .iter() .flat_map(|i| get_device_interface_property::(i, DEVPKEY_Device_InstanceId)) .flat_map(|d| DevInst::from_instance_id(&d)) .flat_map(probe_device) .collect(); Ok(devs.into_iter()) } pub fn probe_device(devinst: DevInst) -> Option { let instance_id = devinst.get_property::(DEVPKEY_Device_InstanceId)?; debug!("Probing device {instance_id:?}"); let parent_instance_id = devinst.get_property::(DEVPKEY_Device_Parent)?; let bus_number = devinst.get_property::(DEVPKEY_Device_BusNumber)?; let port_number = devinst.get_property::(DEVPKEY_Device_Address)?; let hub_port = HubPort::by_child_devinst(devinst).ok()?; let info = hub_port.get_info().ok()?; let product_string = devinst .get_property::(DEVPKEY_Device_BusReportedDeviceDesc) .and_then(|s| s.into_string().ok()); let serial_number = if info.device_desc.iSerialNumber != 0 { // Experimentally confirmed, the string descriptor is cached and this does // not perform IO. However, the language ID list is not cached, so we // have to assume 0x0409 (which will be right 99% of the time). hub_port .get_descriptor( DESCRIPTOR_TYPE_STRING, info.device_desc.iSerialNumber, US_ENGLISH, ) .ok() .and_then(|data| decode_string_descriptor(&data).ok()) } else { None }; let driver = devinst .get_property::(DEVPKEY_Device_Service) .and_then(|s| s.into_string().ok()) .unwrap_or_default(); let mut interfaces = if driver.eq_ignore_ascii_case("usbccgp") { devinst .children() .flat_map(|intf| { let interface_number = get_interface_number(intf)?; let (class, subclass, protocol) = intf .get_property::>(DEVPKEY_Device_CompatibleIds)? .iter() .find_map(|s| parse_compatible_id(s))?; let interface_string = intf .get_property::(DEVPKEY_Device_BusReportedDeviceDesc) .and_then(|s| s.into_string().ok()); Some(InterfaceInfo { interface_number, class, subclass, protocol, interface_string, }) }) .collect() } else { Vec::new() }; interfaces.sort_unstable_by_key(|i| i.interface_number); Some(DeviceInfo { instance_id, parent_instance_id, devinst, port_number, driver: Some(driver).filter(|s| !s.is_empty()), bus_number: bus_number as u8, device_address: info.address, vendor_id: info.device_desc.idVendor, product_id: info.device_desc.idProduct, device_version: info.device_desc.bcdDevice, class: info.device_desc.bDeviceClass, subclass: info.device_desc.bDeviceSubClass, protocol: info.device_desc.bDeviceProtocol, speed: info.speed, manufacturer_string: None, product_string, serial_number, interfaces, }) } /// Find the path to open for an interface of a device /// /// If the whole device is bound to WinUSB, it can be opened directly. For a /// composite device, USB interfaces are represented by child device nodes. pub(crate) fn find_device_interface_path(dev: DevInst, intf: u8) -> Result { let driver = dev .get_property::(DEVPKEY_Device_Service) .and_then(|s| s.into_string().ok()) .unwrap_or_default(); if driver.eq_ignore_ascii_case("usbccgp") { let child = dev .children() .find(|i| get_interface_number(*i) == Some(intf)) .ok_or_else(|| Error::new(ErrorKind::NotFound, "Interface not found"))?; let Some(driver) = child.get_property::(DEVPKEY_Device_Service) else { return Err(Error::new( ErrorKind::Unsupported, "Could not determine driver for interface", )); }; if !driver.eq_ignore_ascii_case("winusb") { return Err(Error::new( ErrorKind::Unsupported, format!("Interface driver is {driver:?}, not WinUSB"), )); } let reg_key = child.registry_key().unwrap(); let guid = match reg_key.query_value_guid("DeviceInterfaceGUIDs") { Ok(s) => s, Err(e) => match reg_key.query_value_guid("DeviceInterfaceGUID") { Ok(s) => s, Err(f) => { if e.kind() == f.kind() { debug!("Failed to get DeviceInterfaceGUID or DeviceInterfaceGUIDs from registry: {e}"); } else { debug!("Failed to get DeviceInterfaceGUID or DeviceInterfaceGUIDs from registry: {e}, {f}"); } return Err(Error::new( ErrorKind::Unsupported, "Could not find DeviceInterfaceGUIDs in registry. WinUSB driver may not be correctly installed for this interface." )); } }, }; let paths = child.interfaces(guid); let Some(path) = paths.iter().next() else { return Err(Error::new( ErrorKind::Other, "Failed to find device path for WinUSB interface", )); }; Ok(path.to_owned()) } else if driver.eq_ignore_ascii_case("winusb") { let paths = dev.interfaces(GUID_DEVINTERFACE_USB_DEVICE); let Some(path) = paths.iter().next() else { return Err(Error::new( ErrorKind::Other, "Failed to find device path for WinUSB device", )); }; Ok(path.to_owned()) } else { return Err(Error::new( ErrorKind::Unsupported, format!("Device driver is {driver:?}, not WinUSB"), )); } } fn get_interface_number(intf_dev: DevInst) -> Option { let hw_ids = intf_dev.get_property::>(DEVPKEY_Device_HardwareIds); hw_ids .as_deref() .unwrap_or_default() .iter() .find_map(|id| parse_hardware_id(id)) .or_else(|| { debug!("Failed to parse interface number in hardware IDs: {hw_ids:?}"); None }) } /// Parse interface number from a Hardware ID value fn parse_hardware_id(s: &OsStr) -> Option { let s = s.to_str()?; let s = s.rsplit_once("&MI_")?.1; u8::from_str_radix(s.get(0..2)?, 16).ok() } #[test] fn test_parse_hardware_id() { assert_eq!(parse_hardware_id(OsStr::new("")), None); assert_eq!( parse_hardware_id(OsStr::new("USB\\VID_1234&PID_5678&MI_0A")), Some(10) ); assert_eq!( parse_hardware_id(OsStr::new("USB\\VID_9999&PID_AAAA&REV_0101&MI_01")), Some(1) ); } /// Parse class, subclass, protocol from a Compatible ID value fn parse_compatible_id(s: &OsStr) -> Option<(u8, u8, u8)> { let s = s.to_str()?; let s = s.strip_prefix("USB\\Class_")?; let class = u8::from_str_radix(s.get(0..2)?, 16).ok()?; let s = s.get(2..)?.strip_prefix("&SubClass_")?; let subclass = u8::from_str_radix(s.get(0..2)?, 16).ok()?; let s = s.get(2..)?.strip_prefix("&Prot_")?; let protocol = u8::from_str_radix(s.get(0..2)?, 16).ok()?; Some((class, subclass, protocol)) } #[test] fn test_parse_compatible_id() { assert_eq!(parse_compatible_id(OsStr::new("")), None); assert_eq!(parse_compatible_id(OsStr::new("USB\\Class_03")), None); assert_eq!( parse_compatible_id(OsStr::new("USB\\Class_03&SubClass_11&Prot_22")), Some((3, 17, 34)) ); } nusb-0.1.13/src/platform/windows_winusb/events.rs000064400000000000000000000056221046102023000202300ustar 00000000000000use log::error; use once_cell::sync::OnceCell; use std::{ os::windows::{ io::HandleOrNull, prelude::{OwnedHandle, RawHandle}, }, thread, }; use windows_sys::Win32::{ Foundation::{GetLastError, FALSE, INVALID_HANDLE_VALUE}, System::IO::{CreateIoCompletionPort, GetQueuedCompletionStatusEx, OVERLAPPED_ENTRY}, }; use crate::Error; use super::util::raw_handle; struct IoCompletionPort(OwnedHandle); impl IoCompletionPort { fn new() -> Result { unsafe { let port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); match HandleOrNull::from_raw_handle(port as RawHandle).try_into() { Ok(handle) => Ok(IoCompletionPort(handle)), Err(_) => { let err = GetLastError(); error!("CreateIoCompletionPort (create) failed: {err:?}"); Err(std::io::Error::from_raw_os_error(err as i32)) } } } } fn register(&self, handle: &OwnedHandle) -> Result<(), Error> { unsafe { let r = CreateIoCompletionPort(raw_handle(handle), raw_handle(&self.0), 0, 0); if r == 0 { let err = std::io::Error::last_os_error(); error!("CreateIoCompletionPort (register) failed: {err:?}"); Err(err) } else { Ok(()) } } } fn wait(&self, events: &mut Vec) -> Result<(), Error> { unsafe { let mut event_count = 0; let r = GetQueuedCompletionStatusEx( raw_handle(&self.0), events.as_mut_ptr(), events .capacity() .try_into() .expect("events capacity should fit in u32"), &mut event_count, u32::MAX, 0, ); if r == FALSE { let err = std::io::Error::last_os_error(); error!("GetQueuedCompletionStatusEx failed: {err:?}"); Err(err) } else { events.set_len(event_count as usize); Ok(()) } } } } static IOCP_HANDLE: OnceCell = OnceCell::new(); pub(super) fn register(usb_fd: &OwnedHandle) -> Result<(), Error> { let mut start_thread = false; let iocp = IOCP_HANDLE.get_or_try_init(|| { start_thread = true; IoCompletionPort::new() })?; if start_thread { thread::spawn(event_loop); } iocp.register(usb_fd) } fn event_loop() { let iocp = IOCP_HANDLE.get().unwrap(); let mut event_list = Vec::with_capacity(8); loop { event_list.clear(); iocp.wait(&mut event_list).unwrap(); for event in &event_list { super::transfer::handle_event(event.lpOverlapped); } } } nusb-0.1.13/src/platform/windows_winusb/hotplug.rs000064400000000000000000000121121046102023000203760ustar 00000000000000use std::{ collections::VecDeque, ffi::c_void, io::ErrorKind, mem::size_of, ptr::addr_of, sync::Mutex, task::{Context, Poll}, }; use atomic_waker::AtomicWaker; use log::{debug, error}; use windows_sys::Win32::{ Devices::{ DeviceAndDriverInstallation::{ CM_Register_Notification, CM_Unregister_Notification, CM_NOTIFY_ACTION, CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL, CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL, CM_NOTIFY_EVENT_DATA, CM_NOTIFY_FILTER, CM_NOTIFY_FILTER_0, CM_NOTIFY_FILTER_0_2, CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE, CR_SUCCESS, HCMNOTIFICATION, }, Properties::DEVPKEY_Device_InstanceId, Usb::GUID_DEVINTERFACE_USB_DEVICE, }, Foundation::ERROR_SUCCESS, }; use crate::{ hotplug::HotplugEvent, platform::windows_winusb::{cfgmgr32::get_device_interface_property, util::WCString}, DeviceId, Error, }; use super::{enumeration::probe_device, util::WCStr}; use super::DevInst; pub(crate) struct WindowsHotplugWatch { inner: *mut HotplugInner, registration: HCMNOTIFICATION, } struct HotplugInner { waker: AtomicWaker, events: Mutex>, } #[derive(Debug)] enum Action { Connect, Disconnect, } impl WindowsHotplugWatch { pub fn new() -> Result { let inner = Box::into_raw(Box::new(HotplugInner { events: Mutex::new(VecDeque::new()), waker: AtomicWaker::new(), })); let mut registration = 0; let filter = CM_NOTIFY_FILTER { cbSize: size_of::() as u32, Flags: 0, FilterType: CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE, Reserved: 0, u: CM_NOTIFY_FILTER_0 { DeviceInterface: CM_NOTIFY_FILTER_0_2 { ClassGuid: GUID_DEVINTERFACE_USB_DEVICE, }, }, }; let cr = unsafe { CM_Register_Notification( &filter, inner as *mut c_void, Some(hotplug_callback), &mut registration, ) }; if cr != CR_SUCCESS { error!("CM_Register_Notification failed: {cr}"); return Err(Error::new( ErrorKind::Other, "Failed to initialize hotplug notifications", )); } Ok(WindowsHotplugWatch { inner, registration, }) } fn inner(&self) -> &HotplugInner { unsafe { &*self.inner } } pub fn poll_next(&mut self, cx: &mut Context) -> Poll { self.inner().waker.register(cx.waker()); let event = self.inner().events.lock().unwrap().pop_front(); match event { Some((Action::Connect, devinst)) => { if let Some(dev) = probe_device(devinst) { return Poll::Ready(HotplugEvent::Connected(dev)); }; } Some((Action::Disconnect, devinst)) => { return Poll::Ready(HotplugEvent::Disconnected(DeviceId(devinst))); } None => {} } Poll::Pending } } // Safety: Effectively a Box, which is Send+Sync; // `registration` is accessed only in `Drop` and `CM_Unregister_Notification` // docs mention using a threadpool to call the function. unsafe impl Send for WindowsHotplugWatch {} unsafe impl Sync for WindowsHotplugWatch {} impl Drop for WindowsHotplugWatch { fn drop(&mut self) { unsafe { // According to [1], `CM_Unregister_Notification` waits for // callbacks to finish, so it should be safe to drop `inner` // immediately afterward without races. // [1]: https://learn.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_unregister_notification CM_Unregister_Notification(self.registration); drop(Box::from_raw(self.inner)); } } } unsafe extern "system" fn hotplug_callback( _hnotify: HCMNOTIFICATION, context: *const ::core::ffi::c_void, action: CM_NOTIFY_ACTION, eventdata: *const CM_NOTIFY_EVENT_DATA, _eventdatasize: u32, ) -> u32 { let inner = unsafe { &*(context as *const HotplugInner) }; let action = match action { CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL => Action::Connect, CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL => Action::Disconnect, _ => { debug!("Hotplug callback: unknown action {action}"); return ERROR_SUCCESS; } }; let device_interface = unsafe { WCStr::from_ptr(addr_of!((*eventdata).u.DeviceInterface.SymbolicLink[0])) }; let device_instance = get_device_interface_property::(device_interface, DEVPKEY_Device_InstanceId) .unwrap(); let devinst = DevInst::from_instance_id(&device_instance).unwrap(); debug!("Hotplug callback: action={action:?}, instance={device_instance}"); inner.events.lock().unwrap().push_back((action, devinst)); inner.waker.wake(); return ERROR_SUCCESS; } nusb-0.1.13/src/platform/windows_winusb/hub.rs000064400000000000000000000227141046102023000175030ustar 00000000000000use std::{ alloc::{self, Layout}, ffi::c_void, io::ErrorKind, mem, os::windows::prelude::OwnedHandle, ptr::{addr_of, null_mut}, slice, }; use log::debug; use windows_sys::Win32::{ Devices::{ Properties::DEVPKEY_Device_Address, Usb::{ UsbFullSpeed, UsbHighSpeed, UsbLowSpeed, GUID_DEVINTERFACE_USB_HUB, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, USB_DESCRIPTOR_REQUEST, USB_DESCRIPTOR_REQUEST_0, USB_DEVICE_DESCRIPTOR, USB_DEVICE_SPEED, USB_NODE_CONNECTION_INFORMATION_EX, USB_NODE_CONNECTION_INFORMATION_EX_V2, }, }, Foundation::{GetLastError, ERROR_GEN_FAILURE, TRUE}, System::IO::DeviceIoControl, }; // flags for USB_NODE_CONNECTION_INFORMATION_EX_V2.SupportedUsbProtocols const USB110: u32 = 0x01; const USB200: u32 = 0x02; const USB300: u32 = 0x04; // USB_NODE_CONNECTION_INFORMATION_EX_V2_FLAGS const DEVICE_IS_OPERATING_AT_SUPER_SPEED_OR_HIGHER: u32 = 0x01; const DEVICE_IS_SUPER_SPEED_CAPABLE_OR_HIGHER: u32 = 0x02; const DEVICE_IS_OPERATING_AT_SUPER_SPEED_PLUS_OR_HIGHER: u32 = 0x04; const DEVICE_IS_SUPER_SPEED_PLUS_CAPABLE_OR_HIGHER: u32 = 0x08; use crate::{descriptors::DESCRIPTOR_TYPE_DEVICE, Error, Speed}; use super::{ cfgmgr32::DevInst, util::{create_file, raw_handle}, }; /// Safe wrapper around hub ioctls used to get descriptors for child devices. pub struct HubHandle(OwnedHandle); impl HubHandle { pub fn by_devinst(devinst: DevInst) -> Option { let paths = devinst.interfaces(GUID_DEVINTERFACE_USB_HUB); let Some(path) = paths.iter().next() else { debug!("Failed to find hub interface"); return None; }; match create_file(path) { Ok(f) => Some(HubHandle(f)), Err(e) => { debug!("Failed to open hub: {e}"); None } } } pub fn get_node_connection_info( &self, port_number: u32, ) -> Result { unsafe { let mut info: USB_NODE_CONNECTION_INFORMATION_EX = mem::zeroed(); info.ConnectionIndex = port_number; let mut bytes_returned: u32 = 0; let r = DeviceIoControl( raw_handle(&self.0), IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, &info as *const _ as *const c_void, mem::size_of_val(&info) as u32, &mut info as *mut _ as *mut c_void, mem::size_of_val(&info) as u32, &mut bytes_returned, null_mut(), ); if r == TRUE { if info.DeviceDescriptor.bDescriptorType != DESCRIPTOR_TYPE_DEVICE { // When the device is disconnected during this call, Windows is observed to // sometimes return an all-zero device descriptor. return Err(Error::new( std::io::ErrorKind::Other, "IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX returned an invalid device descriptor", )); } Ok(info) } else { let err = Error::last_os_error(); debug!("Hub DeviceIoControl failed: {err:?}"); Err(err) } } } pub fn get_node_connection_info_v2( &self, port_number: u32, ) -> Result { unsafe { let mut info: USB_NODE_CONNECTION_INFORMATION_EX_V2 = mem::zeroed(); info.ConnectionIndex = port_number; info.Length = mem::size_of_val(&info) as u32; info.SupportedUsbProtocols.ul = USB110 | USB200 | USB300; let mut bytes_returned: u32 = 0; let r = DeviceIoControl( raw_handle(&self.0), IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2, &info as *const _ as *const c_void, mem::size_of_val(&info) as u32, &mut info as *mut _ as *mut c_void, mem::size_of_val(&info) as u32, &mut bytes_returned, null_mut(), ); if r == TRUE { Ok(info) } else { let err = Error::last_os_error(); debug!("Hub DeviceIoControl failed: {err:?}"); Err(err) } } } pub fn get_descriptor( &self, port_number: u32, descriptor_type: u8, descriptor_index: u8, language_id: u16, ) -> Result, Error> { // Experimentally determined on Windows 10 19045.3803 that this fails // with ERROR_INVALID_PARAMETER for non-cached descriptors when // requesting length greater than 4095. let length = 4095; unsafe { let layout = Layout::from_size_align( mem::size_of::() + length, mem::align_of::(), ) .unwrap(); let req = alloc::alloc(layout).cast::(); req.write(USB_DESCRIPTOR_REQUEST { ConnectionIndex: port_number, SetupPacket: USB_DESCRIPTOR_REQUEST_0 { bmRequest: 0x80, bRequest: 0x06, wValue: ((descriptor_type as u16) << 8) | descriptor_index as u16, wIndex: language_id, wLength: length as u16, }, Data: [0], }); let mut bytes_returned: u32 = 0; let r = DeviceIoControl( raw_handle(&self.0), IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, req as *const c_void, layout.size() as u32, req as *mut c_void, layout.size() as u32, &mut bytes_returned, null_mut(), ); let res = if r == TRUE { let start = addr_of!((*req).Data[0]); let end = (req as *mut u8).offset(bytes_returned as isize); let len = end.offset_from(start) as usize; let vec = slice::from_raw_parts(start, len).to_owned(); Ok(vec) } else { let err = GetLastError(); debug!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}"); Err(match err { ERROR_GEN_FAILURE => Error::new( ErrorKind::Other, "Descriptor request failed. Device might be suspended.", ), _ => Error::from_raw_os_error(err as i32), }) }; alloc::dealloc(req as *mut _, layout); res } } } pub struct HubPort { hub_handle: HubHandle, port_number: u32, } pub struct HubDeviceInfo { pub device_desc: USB_DEVICE_DESCRIPTOR, pub speed: Option, pub address: u8, pub active_config: u8, } impl HubPort { pub fn by_child_devinst(devinst: DevInst) -> Result { let parent_hub = devinst .parent() .ok_or_else(|| Error::new(ErrorKind::Other, "failed to find parent hub"))?; let hub_handle = HubHandle::by_devinst(parent_hub) .ok_or_else(|| Error::new(ErrorKind::Other, "failed to open parent hub"))?; let Some(port_number) = devinst.get_property::(DEVPKEY_Device_Address) else { return Err(Error::new( ErrorKind::NotConnected, "Could not find hub port number", )); }; Ok(HubPort { hub_handle, port_number, }) } pub fn get_info(&self) -> Result { #![allow(non_upper_case_globals)] let info = self.hub_handle.get_node_connection_info(self.port_number)?; let info_v2 = self .hub_handle .get_node_connection_info_v2(self.port_number)?; const SUPER_PLUS: u32 = DEVICE_IS_OPERATING_AT_SUPER_SPEED_PLUS_OR_HIGHER | DEVICE_IS_SUPER_SPEED_PLUS_CAPABLE_OR_HIGHER; const SUPER: u32 = DEVICE_IS_OPERATING_AT_SUPER_SPEED_OR_HIGHER | DEVICE_IS_SUPER_SPEED_CAPABLE_OR_HIGHER; let v2_flags = unsafe { info_v2.Flags.ul }; let speed = match info.Speed as USB_DEVICE_SPEED { _ if v2_flags & SUPER_PLUS == SUPER_PLUS => Some(Speed::SuperPlus), _ if v2_flags & SUPER == SUPER => Some(Speed::Super), UsbHighSpeed => Some(Speed::High), UsbFullSpeed => Some(Speed::Full), UsbLowSpeed => Some(Speed::Low), _ => None, }; Ok(HubDeviceInfo { device_desc: info.DeviceDescriptor, address: info.DeviceAddress as u8, active_config: info.CurrentConfigurationValue, speed, }) } pub fn get_descriptor( &self, descriptor_type: u8, descriptor_index: u8, language_id: u16, ) -> Result, Error> { self.hub_handle.get_descriptor( self.port_number, descriptor_type, descriptor_index, language_id, ) } } nusb-0.1.13/src/platform/windows_winusb/mod.rs000064400000000000000000000006521046102023000175010ustar 00000000000000mod enumeration; pub use enumeration::list_devices; mod events; mod device; pub(crate) use device::WindowsDevice as Device; pub(crate) use device::WindowsInterface as Interface; mod transfer; pub(crate) use transfer::TransferData; mod cfgmgr32; mod hub; mod registry; pub(crate) use cfgmgr32::DevInst; pub(crate) use DevInst as DeviceId; mod hotplug; mod util; pub(crate) use hotplug::WindowsHotplugWatch as HotplugWatch; nusb-0.1.13/src/platform/windows_winusb/registry.rs000064400000000000000000000042011046102023000205640ustar 00000000000000use std::{ alloc::{self, Layout}, ffi::OsStr, io::ErrorKind, mem, ptr::{null, null_mut}, }; use windows_sys::{ core::GUID, Win32::{ Foundation::{ERROR_SUCCESS, S_OK}, System::{ Com::IIDFromString, Registry::{RegCloseKey, RegQueryValueExW, HKEY, REG_MULTI_SZ, REG_SZ}, }, }, }; use crate::Error; use super::util::WCString; pub struct RegKey(HKEY); impl RegKey { pub unsafe fn new(k: HKEY) -> RegKey { RegKey(k) } pub fn query_value_guid(&self, value_name: &str) -> Result { unsafe { let value_name: WCString = OsStr::new(value_name).into(); let mut ty = 0; let mut size = 0; // get size let r = RegQueryValueExW( self.0, value_name.as_ptr(), null_mut(), &mut ty, null_mut(), &mut size, ); if r != ERROR_SUCCESS { return Err(Error::from_raw_os_error(r as i32)); } if ty != REG_MULTI_SZ && ty != REG_SZ { return Err(Error::new( ErrorKind::InvalidInput, "registry value type not string", )); } let layout = Layout::from_size_align(size as usize, mem::align_of::()).unwrap(); let buf = alloc::alloc(layout); let r = RegQueryValueExW(self.0, value_name.as_ptr(), null(), &mut ty, buf, &mut size); if r != ERROR_SUCCESS { alloc::dealloc(buf, layout); return Err(Error::from_raw_os_error(r as i32)); } let mut guid = GUID::from_u128(0); let r = IIDFromString(buf as *mut u16, &mut guid); alloc::dealloc(buf, layout); if r == S_OK { Ok(guid) } else { Err(Error::new(ErrorKind::InvalidData, "invalid UUID")) } } } } impl Drop for RegKey { fn drop(&mut self) { unsafe { RegCloseKey(self.0); } } } nusb-0.1.13/src/platform/windows_winusb/transfer.rs000064400000000000000000000254421046102023000205520ustar 00000000000000use std::{ ffi::c_void, io, mem::{self, ManuallyDrop}, ptr::{addr_of_mut, null_mut}, sync::Arc, }; use log::{debug, error, warn}; use windows_sys::Win32::{ Devices::Usb::{ WinUsb_ControlTransfer, WinUsb_GetOverlappedResult, WinUsb_ReadPipe, WinUsb_WritePipe, WINUSB_SETUP_PACKET, }, Foundation::{ GetLastError, ERROR_DEVICE_NOT_CONNECTED, ERROR_FILE_NOT_FOUND, ERROR_GEN_FAILURE, ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_SUCH_DEVICE, ERROR_OPERATION_ABORTED, ERROR_REQUEST_ABORTED, ERROR_SEM_TIMEOUT, ERROR_TIMEOUT, FALSE, TRUE, WIN32_ERROR, }, System::IO::{CancelIoEx, OVERLAPPED}, }; use crate::transfer::{ notify_completion, Completion, ControlIn, ControlOut, EndpointType, PlatformSubmit, PlatformTransfer, Recipient, RequestBuffer, ResponseBuffer, TransferError, }; use super::util::raw_handle; #[repr(C)] pub(crate) struct EventNotify { // first member of repr(C) struct; can cast pointer between types overlapped: OVERLAPPED, ptr: *mut c_void, } pub struct TransferData { interface: Arc, event: *mut EventNotify, buf: *mut u8, capacity: usize, endpoint: u8, ep_type: EndpointType, submit_error: Option, } unsafe impl Send for TransferData {} impl TransferData { pub(crate) fn new( interface: std::sync::Arc, endpoint: u8, ep_type: EndpointType, ) -> TransferData { TransferData { interface, event: Box::into_raw(Box::new(unsafe { mem::zeroed() })), buf: null_mut(), capacity: 0, endpoint, ep_type, submit_error: None, } } /// SAFETY: requires that the transfer has completed and `length` bytes are initialized unsafe fn take_buf(&mut self, length: usize) -> Vec { let v = Vec::from_raw_parts(self.buf, length, self.capacity); self.buf = null_mut(); self.capacity = 0; v } /// SAFETY: user_data must be the callback pointer passed to `submit` unsafe fn post_submit(&mut self, r: i32, func: &str, user_data: *mut c_void) { if r == TRUE { error!("{func} completed synchronously") } let err = GetLastError(); if err != ERROR_IO_PENDING { self.submit_error = Some(err); error!("{func} failed: {}", io::Error::from_raw_os_error(err as _)); // Safety: Transfer was not submitted, so we still own it // and must complete it in place of the event thread. notify_completion::(user_data); } else { self.submit_error = None; } } /// SAFETY: transfer must be completed unsafe fn get_status(&mut self) -> (usize, Result<(), TransferError>) { if let Some(err) = self.submit_error { debug!( "Transfer {:?} on endpoint {:02x} failed on submit: {}", self.event, self.endpoint, err ); return (0, Err(map_error(err))); } let mut actual_len = 0; let r = WinUsb_GetOverlappedResult( self.interface.winusb_handle, self.event as *mut OVERLAPPED, &mut actual_len, FALSE, ); let status = if r != 0 { debug!( "Transfer {:?} on endpoint {:02x} complete: {} bytes transferred", self.event, self.endpoint, actual_len ); Ok(()) } else { let err = GetLastError(); debug!( "Transfer {:?} on endpoint {:02x} failed: {}, {} bytes transferred", self.event, self.endpoint, err, actual_len ); Err(map_error(err)) }; (actual_len as usize, status) } } impl Drop for TransferData { fn drop(&mut self) { if !self.buf.is_null() { unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity)) } } unsafe { drop(Box::from_raw(self.event)) } } } impl PlatformTransfer for TransferData { fn cancel(&self) { debug!("Cancelling transfer {:?}", self.event); unsafe { let r = CancelIoEx( raw_handle(&self.interface.handle), self.event as *mut OVERLAPPED, ); if r == 0 { let err = GetLastError(); if err != ERROR_NOT_FOUND { error!( "CancelIoEx failed: {}", io::Error::from_raw_os_error(err as i32) ); } } } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: Vec, user_data: *mut c_void) { addr_of_mut!((*self.event).ptr).write(user_data); let mut data = ManuallyDrop::new(data); self.buf = data.as_mut_ptr(); self.capacity = data.capacity(); let len = data.len(); debug!( "Submit transfer {:?} on endpoint {:02X} for {} bytes OUT", self.event, self.endpoint, len ); let r = WinUsb_WritePipe( self.interface.winusb_handle, self.endpoint, self.buf, len.try_into().expect("transfer size should fit in u32"), null_mut(), self.event as *mut OVERLAPPED, ); self.post_submit(r, "WinUsb_WritePipe", user_data); } unsafe fn take_completed(&mut self) -> Completion { let (actual_len, status) = self.get_status(); let data = ResponseBuffer::from_vec(self.take_buf(0), actual_len); Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: RequestBuffer, user_data: *mut c_void) { addr_of_mut!((*self.event).ptr).write(user_data); let (buf, request_len) = data.into_vec(); let mut buf = ManuallyDrop::new(buf); self.buf = buf.as_mut_ptr(); self.capacity = buf.capacity(); debug!( "Submit transfer {:?} on endpoint {:02X} for {} bytes IN", self.event, self.endpoint, request_len ); let r = WinUsb_ReadPipe( self.interface.winusb_handle, self.endpoint, self.buf, request_len .try_into() .expect("transfer size should fit in u32"), null_mut(), self.event as *mut OVERLAPPED, ); self.post_submit(r, "WinUsb_ReadPipe", user_data); } unsafe fn take_completed(&mut self) -> Completion> { let (actual_len, status) = self.get_status(); let data = self.take_buf(actual_len); Completion { data, status } } } impl PlatformSubmit for TransferData { unsafe fn submit(&mut self, data: ControlIn, user_data: *mut c_void) { assert_eq!(self.endpoint, 0); assert_eq!(self.ep_type, EndpointType::Control); if data.recipient == Recipient::Interface && data.index as u8 != self.interface.interface_number { warn!("WinUSB sends interface number instead of passed `index` when performing a control transfer with `Recipient::Interface`"); } addr_of_mut!((*self.event).ptr).write(user_data); let mut buf = ManuallyDrop::new(Vec::with_capacity(data.length as usize)); self.buf = buf.as_mut_ptr(); self.capacity = buf.capacity(); debug!( "Submit transfer {:?} on endpoint {:02X} for {} bytes ControlIN", self.event, self.endpoint, data.length ); let pkt = WINUSB_SETUP_PACKET { RequestType: data.request_type(), Request: data.request, Value: data.value, Index: data.index, Length: data.length, }; let r = WinUsb_ControlTransfer( self.interface.winusb_handle, pkt, self.buf, data.length as u32, null_mut(), self.event as *mut OVERLAPPED, ); self.post_submit(r, "WinUsb_ControlTransfer", user_data); } unsafe fn take_completed(&mut self) -> Completion> { let (actual_len, status) = self.get_status(); let data = self.take_buf(actual_len); Completion { data, status } } } impl PlatformSubmit> for TransferData { unsafe fn submit(&mut self, data: ControlOut, user_data: *mut c_void) { assert_eq!(self.endpoint, 0); assert_eq!(self.ep_type, EndpointType::Control); if data.recipient == Recipient::Interface && data.index as u8 != self.interface.interface_number { warn!("WinUSB sends interface number instead of passed `index` when performing a control transfer with `Recipient::Interface`"); } addr_of_mut!((*self.event).ptr).write(user_data); let mut buf = ManuallyDrop::new(data.data.to_vec()); self.buf = buf.as_mut_ptr(); self.capacity = buf.capacity(); let len: u16 = buf .len() .try_into() .expect("transfer size should fit in u16"); debug!( "Submit transfer {:?} on endpoint {:02X} for {} bytes ControlOUT", self.event, self.endpoint, len ); let pkt = WINUSB_SETUP_PACKET { RequestType: data.request_type(), Request: data.request, Value: data.value, Index: data.index, Length: len as u16, }; let r = WinUsb_ControlTransfer( self.interface.winusb_handle, pkt, self.buf, len as u32, null_mut(), self.event as *mut OVERLAPPED, ); self.post_submit(r, "WinUsb_ControlTransfer", user_data); } unsafe fn take_completed(&mut self) -> Completion { let (actual_len, status) = self.get_status(); let data = ResponseBuffer::from_vec(self.take_buf(0), actual_len); Completion { data, status } } } pub(super) fn handle_event(completion: *mut OVERLAPPED) { let completion = completion as *mut EventNotify; debug!("Handling completion for transfer {completion:?}"); unsafe { let p = addr_of_mut!((*completion).ptr).read(); notify_completion::(p) } } pub(crate) fn map_error(err: WIN32_ERROR) -> TransferError { match err { ERROR_GEN_FAILURE => TransferError::Stall, ERROR_REQUEST_ABORTED | ERROR_TIMEOUT | ERROR_SEM_TIMEOUT | ERROR_OPERATION_ABORTED => { TransferError::Cancelled } ERROR_FILE_NOT_FOUND | ERROR_DEVICE_NOT_CONNECTED | ERROR_NO_SUCH_DEVICE => { TransferError::Disconnected } _ => TransferError::Unknown, } } nusb-0.1.13/src/platform/windows_winusb/util.rs000064400000000000000000000101641046102023000176760ustar 00000000000000use std::{ borrow::Borrow, ffi::{OsStr, OsString}, fmt::{Display, Write}, io, ops::Deref, os::windows::prelude::{ AsHandle, AsRawHandle, HandleOrInvalid, OsStrExt, OsStringExt, OwnedHandle, RawHandle, }, ptr::null, slice, }; use windows_sys::Win32::{ Foundation::{GENERIC_READ, GENERIC_WRITE, HANDLE}, Storage::FileSystem::{ CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }, }; /// Wrapper around `CreateFile` pub fn create_file(path: &WCStr) -> Result { unsafe { let r = CreateFileW( path.as_ptr(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, null(), OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0, ); HandleOrInvalid::from_raw_handle(r as RawHandle) .try_into() .map_err(|_| io::Error::last_os_error()) } } pub fn raw_handle(h: impl AsHandle) -> HANDLE { h.as_handle().as_raw_handle() as HANDLE } /// A utf-16 owned null-terminated string #[repr(transparent)] pub struct WCString(Vec); impl From<&OsStr> for WCString { fn from(s: &OsStr) -> Self { WCString(s.encode_wide().chain(Some(0)).collect()) } } impl Borrow for WCString { fn borrow(&self) -> &WCStr { &self } } impl Deref for WCString { type Target = WCStr; fn deref(&self) -> &Self::Target { unsafe { WCStr::from_slice_unchecked(&self.0) } } } impl From for OsString { fn from(s: WCString) -> Self { (&*s).into() } } impl Display for WCString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.deref().fmt(f) } } /// A utf-16 borrowed null-terminated string #[repr(transparent)] pub struct WCStr([u16]); impl WCStr { pub unsafe fn from_ptr<'a>(ptr: *const u16) -> &'a WCStr { let mut len = 0; while ptr.add(len).read() != 0 { len += 1; } Self::from_slice_unchecked(slice::from_raw_parts(ptr, len + 1)) } unsafe fn from_slice_unchecked(s: &[u16]) -> &WCStr { debug_assert_eq!( s.last().copied(), Some(0), "string should be null-terminated" ); let p: *const [u16] = s; unsafe { &*(p as *const WCStr) } } pub fn from_slice_until_nul(s: &[u16]) -> &WCStr { let nul = s .iter() .copied() .position(|x| x == 0) .expect("string should be null-terminated"); unsafe { Self::from_slice_unchecked(&s[..nul + 1]) } } pub fn as_slice(&self) -> &[u16] { &self.0 } pub fn as_slice_without_nul(&self) -> &[u16] { &self.0[..self.0.len() - 1] } pub fn as_ptr(&self) -> *const u16 { self.0.as_ptr() } } impl ToOwned for WCStr { type Owned = WCString; fn to_owned(&self) -> Self::Owned { WCString(self.0.to_owned()) } } impl From<&WCStr> for OsString { fn from(s: &WCStr) -> Self { debug_assert_eq!( s.0.last().copied(), Some(0), "string should be null-terminated" ); OsString::from_wide(&s.0[..s.0.len() - 1]) } } impl Display for WCStr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = self.as_slice_without_nul(); for c in char::decode_utf16(s.iter().copied()) { f.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER))?; } Ok(()) } } pub struct NulSepList(pub Vec); impl NulSepList { pub fn iter(&self) -> NulSepListIter { NulSepListIter(&self.0) } } pub struct NulSepListIter<'a>(pub &'a [u16]); impl<'a> Iterator for NulSepListIter<'a> { type Item = &'a WCStr; fn next(&mut self) -> Option { if let Some(next_nul) = self.0.iter().copied().position(|x| x == 0) { let (i, next) = self.0.split_at(next_nul + 1); self.0 = next; Some(unsafe { WCStr::from_slice_unchecked(i) }) } else { None } } } nusb-0.1.13/src/transfer/buffer.rs000064400000000000000000000075121046102023000151140ustar 00000000000000use std::fmt::Debug; use std::mem::ManuallyDrop; use super::TransferRequest; /// A buffer for requesting an IN transfer. /// /// A `RequestBuffer` is passed when submitting an `IN` transfer to define the /// requested length and provide a buffer to receive data into. The buffer is /// returned in the [`Completion`][`crate::transfer::Completion`] as a `Vec` /// with the data read from the endpoint. The `Vec`'s allocation can turned back /// into a `RequestBuffer` to re-use it for another transfer. /// /// You can think of a `RequestBuffer` as a `Vec` with uninitialized contents. pub struct RequestBuffer { pub(crate) buf: *mut u8, pub(crate) capacity: usize, pub(crate) requested: usize, } impl RequestBuffer { /// Create a `RequestBuffer` of the specified size. pub fn new(len: usize) -> RequestBuffer { let mut v = ManuallyDrop::new(Vec::with_capacity(len)); RequestBuffer { buf: v.as_mut_ptr(), capacity: v.capacity(), requested: len, } } pub(crate) fn into_vec(self) -> (Vec, usize) { let s = ManuallyDrop::new(self); let v = unsafe { Vec::from_raw_parts(s.buf, 0, s.capacity) }; (v, s.requested) } /// Create a `RequestBuffer` by re-using the allocation of a `Vec`. pub fn reuse(v: Vec, len: usize) -> RequestBuffer { let mut v = ManuallyDrop::new(v); v.clear(); v.reserve_exact(len); RequestBuffer { buf: v.as_mut_ptr(), capacity: v.capacity(), requested: len, } } } unsafe impl Send for RequestBuffer {} unsafe impl Sync for RequestBuffer {} impl Drop for RequestBuffer { fn drop(&mut self) { unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity)) } } } impl Debug for RequestBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RequestBuffer") .field("requested", &self.requested) .finish_non_exhaustive() } } impl TransferRequest for RequestBuffer { type Response = Vec; } /// Returned buffer and actual length for a completed OUT transfer. /// /// When an `OUT` transfer completes, a `ResponseBuffer` is returned in the /// `Completion`. The [`actual_length`][`ResponseBuffer::actual_length`] tells /// you how many bytes were successfully sent, which may be useful in the case /// of a partially-completed transfer. /// /// The `ResponseBuffer` can be turned into an empty `Vec` to re-use the allocation /// for another transfer, or dropped to free the memory. pub struct ResponseBuffer { pub(crate) buf: *mut u8, pub(crate) capacity: usize, pub(crate) transferred: usize, } impl ResponseBuffer { pub(crate) fn from_vec(v: Vec, transferred: usize) -> ResponseBuffer { let mut v = ManuallyDrop::new(v); ResponseBuffer { buf: v.as_mut_ptr(), capacity: v.capacity(), transferred, } } /// Get the number of bytes successfully transferred. pub fn actual_length(&self) -> usize { self.transferred } /// Extract the buffer as an empty `Vec` to re-use in another transfer. pub fn reuse(self) -> Vec { let s = ManuallyDrop::new(self); unsafe { Vec::from_raw_parts(s.buf, 0, s.capacity) } } } impl Debug for ResponseBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ResponseBuffer") .field("transferred", &self.transferred) .finish_non_exhaustive() } } unsafe impl Send for ResponseBuffer {} unsafe impl Sync for ResponseBuffer {} impl Drop for ResponseBuffer { fn drop(&mut self) { unsafe { drop(Vec::from_raw_parts(self.buf, 0, self.capacity)) } } } impl TransferRequest for Vec { type Response = ResponseBuffer; } nusb-0.1.13/src/transfer/control.rs000064400000000000000000000127421046102023000153240ustar 00000000000000use super::{ResponseBuffer, TransferRequest}; /// Transfer direction #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(u8)] pub enum Direction { /// Host to device Out = 0, /// Device to host In = 1, } /// Specification defining the request. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(u8)] pub enum ControlType { /// Request defined by the USB standard. Standard = 0, /// Request defined by the standard USB class specification. Class = 1, /// Non-standard request. Vendor = 2, } /// Entity targeted by the request. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(u8)] pub enum Recipient { /// Request made to device as a whole. Device = 0, /// Request made to specific interface. Interface = 1, /// Request made to specific endpoint. Endpoint = 2, /// Other request. Other = 3, } /// SETUP packet without direction or buffers pub struct Control { /// Request type used for the `bmRequestType` field sent in the SETUP packet. #[doc(alias = "bmRequestType")] pub control_type: ControlType, /// Recipient used for the `bmRequestType` field sent in the SETUP packet. #[doc(alias = "bmRequestType")] pub recipient: Recipient, /// `bRequest` field sent in the SETUP packet. #[doc(alias = "bRequest")] pub request: u8, /// `wValue` field sent in the SETUP packet. #[doc(alias = "wValue")] pub value: u16, /// `wIndex` field sent in the SETUP packet. /// /// For [`Recipient::Interface`] this is the interface number. For [`Recipient::Endpoint`] this is the endpoint number. #[doc(alias = "wIndex")] pub index: u16, } impl Control { pub(crate) fn request_type(&self, direction: Direction) -> u8 { request_type(direction, self.control_type, self.recipient) } } /// SETUP packet and associated data to make an **OUT** request on a control endpoint. pub struct ControlOut<'a> { /// Request type used for the `bmRequestType` field sent in the SETUP packet. #[doc(alias = "bmRequestType")] pub control_type: ControlType, /// Recipient used for the `bmRequestType` field sent in the SETUP packet. #[doc(alias = "bmRequestType")] pub recipient: Recipient, /// `bRequest` field sent in the SETUP packet. #[doc(alias = "bRequest")] pub request: u8, /// `wValue` field sent in the SETUP packet. #[doc(alias = "wValue")] pub value: u16, /// `wIndex` field sent in the SETUP packet. /// /// For [`Recipient::Interface`] this is the interface number. For [`Recipient::Endpoint`] this is the endpoint number. #[doc(alias = "wIndex")] pub index: u16, /// Data to be sent in the data stage. #[doc(alias = "wLength")] pub data: &'a [u8], } impl<'a> ControlOut<'a> { #[allow(unused)] pub(crate) fn setup_packet(&self) -> Result<[u8; SETUP_PACKET_SIZE], ()> { Ok(pack_setup( Direction::Out, self.control_type, self.recipient, self.request, self.value, self.index, self.data.len().try_into().map_err(|_| ())?, )) } #[allow(unused)] pub(crate) fn request_type(&self) -> u8 { request_type(Direction::Out, self.control_type, self.recipient) } } impl TransferRequest for ControlOut<'_> { type Response = ResponseBuffer; } /// SETUP packet to make an **IN** request on a control endpoint. pub struct ControlIn { /// Request type used for the `bmRequestType` field sent in the SETUP packet. #[doc(alias = "bmRequestType")] pub control_type: ControlType, /// Recipient used for the `bmRequestType` field sent in the SETUP packet. #[doc(alias = "bmRequestType")] pub recipient: Recipient, /// `bRequest` field sent in the SETUP packet. #[doc(alias = "bRequest")] pub request: u8, /// `wValue` field sent in the SETUP packet. #[doc(alias = "wValue")] pub value: u16, /// `wIndex` field sent in the SETUP packet. /// /// For [`Recipient::Interface`] this is the interface number. For [`Recipient::Endpoint`] this is the endpoint number. #[doc(alias = "wIndex")] pub index: u16, /// Number of bytes to be read in the data stage. #[doc(alias = "wLength")] pub length: u16, } impl ControlIn { #[allow(unused)] pub(crate) fn setup_packet(&self) -> [u8; SETUP_PACKET_SIZE] { pack_setup( Direction::In, self.control_type, self.recipient, self.request, self.value, self.index, self.length, ) } #[allow(unused)] pub(crate) fn request_type(&self) -> u8 { request_type(Direction::In, self.control_type, self.recipient) } } pub(crate) const SETUP_PACKET_SIZE: usize = 8; fn pack_setup( direction: Direction, control_type: ControlType, recipient: Recipient, request: u8, value: u16, index: u16, length: u16, ) -> [u8; SETUP_PACKET_SIZE] { let bmrequesttype = request_type(direction, control_type, recipient); [ bmrequesttype, request, (value & 0xFF) as u8, (value >> 8) as u8, (index & 0xFF) as u8, (index >> 8) as u8, (length & 0xFF) as u8, (length >> 8) as u8, ] } fn request_type(direction: Direction, control_type: ControlType, recipient: Recipient) -> u8 { ((direction as u8) << 7) | ((control_type as u8) << 5) | (recipient as u8) } impl TransferRequest for ControlIn { type Response = Vec; } nusb-0.1.13/src/transfer/internal.rs000064400000000000000000000147071046102023000154630ustar 00000000000000use std::{ cell::UnsafeCell, ffi::c_void, ptr::NonNull, sync::{ atomic::{AtomicU8, Ordering}, Arc, }, task::{Context, Poll}, }; use atomic_waker::AtomicWaker; use super::Completion; pub trait PlatformTransfer: Send { /// Request cancellation of a transfer that may or may not currently be /// pending. fn cancel(&self); } pub trait TransferRequest { type Response; } pub trait PlatformSubmit: PlatformTransfer { /// Fill the transfer with the data from `data` and submit it to the kernel. /// Arrange for `notify_completion(transfer)` to be called once the transfer /// has completed. /// /// SAFETY(caller): transfer is in an idle state unsafe fn submit(&mut self, data: D, transfer: *mut c_void); /// SAFETY(caller): `transfer` is in a completed state unsafe fn take_completed(&mut self) -> Completion; } struct TransferInner { /// Platform-specific data. /// /// In an `UnsafeCell` because we provide `&mut` when the /// state guarantees us exclusive access platform_data: UnsafeCell

, /// One of the `STATE_*` constants below, used to synchronize /// the state. state: AtomicU8, /// Waker that is notified when transfer completes. waker: Arc, } /// Handle to a transfer. /// /// Cancels the transfer and arranges for memory to be freed /// when dropped. pub(crate) struct TransferHandle { ptr: NonNull>, } unsafe impl Send for TransferHandle

{} unsafe impl Sync for TransferHandle

{} /// The transfer has not been submitted. The buffer is not valid. const STATE_IDLE: u8 = 0; /// The transfer has been or is about to be submitted to the kernel and /// completion has not yet been handled. The buffer points to valid memory but /// cannot necessarily be accessed by userspace. There is a future or queue /// waiting for it completion. const STATE_PENDING: u8 = 1; /// Like PENDING, but there is no one waiting for completion. The completion /// handler will drop the buffer and transfer. const STATE_ABANDONED: u8 = 2; /// The transfer completion has been handled on the event loop thread. The /// buffer is valid and may be accessed by the `TransferHandle`. const STATE_COMPLETED: u8 = 3; impl TransferHandle

{ /// Create a new transfer and get a handle. pub(crate) fn new(inner: P) -> TransferHandle

{ let b = Box::new(TransferInner { platform_data: UnsafeCell::new(inner), state: AtomicU8::new(STATE_IDLE), waker: Arc::new(AtomicWaker::new()), }); TransferHandle { ptr: Box::leak(b).into(), } } fn inner(&self) -> &TransferInner

{ // SAFETY: while `TransferHandle` is alive, its `TransferInner` is alive // (it may be shared by `notify_completion` on the event thread, so can't be &mut) unsafe { self.ptr.as_ref() } } fn platform_data(&self) -> &P { // SAFETY: while `TransferHandle` is alive, the only mutable access to `platform_data` // is via this `TransferHandle`. unsafe { &*self.inner().platform_data.get() } } pub(crate) fn submit(&mut self, data: D) where D: TransferRequest, P: PlatformSubmit, { let inner = self.inner(); // It's the syscall that submits the transfer that actually performs the // release ordering. let prev = self.inner().state.swap(STATE_PENDING, Ordering::Relaxed); assert_eq!(prev, STATE_IDLE, "Transfer should be idle when submitted"); // SAFETY: while `TransferHandle` is alive, the only mutable access to `platform_data` // is via this `TransferHandle`. Verified that it is idle. unsafe { let p = &mut *inner.platform_data.get(); p.submit(data, self.ptr.as_ptr() as *mut c_void); } } pub(crate) fn cancel(&mut self) { self.platform_data().cancel(); } fn poll_completion_generic(&mut self, cx: &Context) -> Poll<&mut P> { let inner = self.inner(); inner.waker.register(cx.waker()); match inner.state.load(Ordering::Acquire) { STATE_PENDING => Poll::Pending, STATE_COMPLETED => { // Relaxed because this doesn't synchronize with anything, // just marks that we no longer need to drop the buffer inner.state.store(STATE_IDLE, Ordering::Relaxed); // SAFETY: while `TransferHandle` is alive, the only mutable access to `platform_data` // is via this `TransferHandle`. Poll::Ready(unsafe { &mut *inner.platform_data.get() }) } s => panic!("Polling transfer in unexpected state {s}"), } } pub fn poll_completion( &mut self, cx: &Context, ) -> Poll> where D: TransferRequest, P: PlatformSubmit, { // SAFETY: `poll_completion_generic` checks that it is completed self.poll_completion_generic(cx) .map(|u| unsafe { u.take_completed() }) } } impl Drop for TransferHandle

{ fn drop(&mut self) { match self.inner().state.swap(STATE_ABANDONED, Ordering::Acquire) { STATE_PENDING => { self.cancel(); /* handler responsible for dropping */ } STATE_IDLE | STATE_COMPLETED => { // SAFETY: state means there is no concurrent access unsafe { drop(Box::from_raw(self.ptr.as_ptr())) } } s => panic!("Dropping transfer in unexpected state {s}"), } } } /// Notify that a transfer has completed. /// /// SAFETY: `transfer` must be a pointer previously passed to `submit`, and /// the caller / kernel must no longer dereference it or its buffer. pub(crate) unsafe fn notify_completion(transfer: *mut c_void) { unsafe { let transfer = transfer as *mut TransferInner

; let waker = (*transfer).waker.clone(); match (*transfer).state.swap(STATE_COMPLETED, Ordering::Release) { STATE_PENDING => waker.wake(), STATE_ABANDONED => { drop(Box::from_raw(transfer)); } s => panic!("Completing transfer in unexpected state {s}"), } } } nusb-0.1.13/src/transfer/mod.rs000064400000000000000000000125241046102023000144210ustar 00000000000000//! Transfer-related types. //! //! Use the methods on an [`Interface`][`super::Interface`] to make individual //! transfers or obtain a [`Queue`] to manage multiple transfers. use std::{ fmt::Display, future::Future, io, marker::PhantomData, task::{Context, Poll}, }; use crate::platform; mod queue; pub use queue::Queue; mod buffer; pub use buffer::{RequestBuffer, ResponseBuffer}; mod control; #[allow(unused)] pub(crate) use control::SETUP_PACKET_SIZE; pub use control::{Control, ControlIn, ControlOut, ControlType, Direction, Recipient}; mod internal; pub(crate) use internal::{ notify_completion, PlatformSubmit, PlatformTransfer, TransferHandle, TransferRequest, }; /// Endpoint type. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[allow(dead_code)] pub enum EndpointType { /// Control endpoint. Control = 0, /// Isochronous endpoint. Isochronous = 1, /// Bulk endpoint. Bulk = 2, /// Interrupt endpoint. Interrupt = 3, } /// Transfer error. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TransferError { /// Transfer was cancelled. Cancelled, /// Endpoint in a STALL condition. /// /// This is used by the device to signal that an error occurred. For bulk /// and interrupt endpoints, the stall condition can be cleared with /// [`Interface::clear_halt`][crate::Interface::clear_halt]. For control /// requests, the stall is automatically cleared when another request is /// submitted. Stall, /// Device disconnected. Disconnected, /// Hardware issue or protocol violation. Fault, /// Unknown or OS-specific error. Unknown, } impl Display for TransferError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TransferError::Cancelled => write!(f, "transfer was cancelled"), TransferError::Stall => write!(f, "endpoint STALL condition"), TransferError::Disconnected => write!(f, "device disconnected"), TransferError::Fault => write!(f, "hardware fault or protocol violation"), TransferError::Unknown => write!(f, "unknown error"), } } } impl std::error::Error for TransferError {} impl From for io::Error { fn from(value: TransferError) -> Self { match value { TransferError::Cancelled => io::Error::new(io::ErrorKind::Interrupted, value), TransferError::Stall => io::Error::new(io::ErrorKind::ConnectionReset, value), TransferError::Disconnected => io::Error::new(io::ErrorKind::ConnectionAborted, value), TransferError::Fault => io::Error::new(io::ErrorKind::Other, value), TransferError::Unknown => io::Error::new(io::ErrorKind::Other, value), } } } /// Status and data returned on transfer completion. /// /// A transfer can return partial data even in the case of failure or /// cancellation, thus this is a struct containing both `data` and `status` /// rather than a `Result`. Use [`into_result`][`Completion::into_result`] to /// ignore a partial transfer and get a `Result`. #[derive(Debug, Clone)] #[must_use] pub struct Completion { /// Returned data or buffer to re-use. pub data: T, /// Indicates successful completion or error. pub status: Result<(), TransferError>, } impl Completion { /// Ignore any partial completion, turning `self` into a `Result` containing /// either the completed buffer for a successful transfer or a /// `TransferError`. pub fn into_result(self) -> Result { self.status.map(|()| self.data) } } impl TryFrom>> for Vec { type Error = TransferError; fn try_from(c: Completion>) -> Result { c.into_result() } } impl TryFrom> for ResponseBuffer { type Error = TransferError; fn try_from(c: Completion) -> Result { c.into_result() } } /// [`Future`] used to await the completion of a transfer. /// /// Use the methods on [`Interface`][super::Interface] to /// submit an individual transfer and obtain a `TransferFuture`. /// /// The transfer is cancelled on drop. The buffer and /// any partially-completed data are destroyed. This means /// that `TransferFuture` is not [cancel-safe] and cannot be used /// in `select!{}`, When racing a `TransferFuture` with a timeout /// you cannot tell whether data may have been partially transferred on timeout. /// Use the [`Queue`] interface if these matter for your application. /// /// [cancel-safe]: https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety pub struct TransferFuture { transfer: TransferHandle, ty: PhantomData, } impl TransferFuture { pub(crate) fn new(transfer: TransferHandle) -> TransferFuture { TransferFuture { transfer, ty: PhantomData, } } } impl Future for TransferFuture where platform::TransferData: PlatformSubmit, D::Response: Unpin, { type Output = Completion; fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.as_mut().transfer.poll_completion::(cx) } } nusb-0.1.13/src/transfer/queue.rs000064400000000000000000000211331046102023000147620ustar 00000000000000use std::{ collections::VecDeque, future::{poll_fn, Future}, marker::PhantomData, sync::Arc, task::{Context, Poll}, }; use crate::{platform, Error}; use super::{Completion, EndpointType, PlatformSubmit, TransferHandle, TransferRequest}; /// Manages a stream of transfers on an endpoint. /// /// A `Queue` optimizes a common pattern when streaming data to or from a USB /// endpoint: To maximize throughput and minimize latency, the host controller /// needs to attempt a transfer in every possible frame. That requires always /// having a transfer request pending with the kernel by submitting multiple /// transfer requests and re-submitting them as they complete. /// /// Use the methods on [`Interface`][`crate::Interface`] to obtain a `Queue`. /// /// When the `Queue` is dropped, all pending transfers are cancelled. /// /// ### Why use a `Queue` instead of submitting multiple transfers individually with the methods on [`Interface`][`crate::Interface`]? /// /// * Individual transfers give you individual `Future`s, which you then have /// to keep track of and poll using something like `FuturesUnordered`. /// * A `Queue` provides better cancellation semantics than `Future`'s /// cancel-on-drop. /// * After dropping a [`TransferFuture`][super::TransferFuture], you lose /// the ability to get the status of the cancelled transfer and see if it /// may have been partially or fully completed. /// * When cancelling multiple transfers, it's important to do so in reverse /// order so that subsequent pending transfers can't end up executing. /// When managing a collection of `TransferFuture`s it's tricky to /// guarantee drop order, while `Queue` always cancels its contained /// transfers in reverse order. /// * The `TransferFuture` methods on `Interface` are not [cancel-safe], /// meaning they cannot be used in `select!{}` or similar patterns, /// because dropping the Future has side effects and can lose data. The /// Future returned from [`Queue::next_complete`] is cancel-safe because /// it merely waits for completion, while the `Queue` owns the pending /// transfers. /// * A queue caches the internal transfer data structures of the last /// completed transfer, meaning that if you re-use the data buffer there is /// no memory allocation involved in continued streaming. /// /// [cancel-safe]: https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety /// ### Example (read from an endpoint) /// /// ```no_run /// use futures_lite::future::block_on; /// use nusb::transfer::RequestBuffer; /// # let di = nusb::list_devices().unwrap().next().unwrap(); /// # let device = di.open().unwrap(); /// # let interface = device.claim_interface(0).unwrap(); /// # fn handle_data(_: &[u8]) {} /// let mut queue = interface.bulk_in_queue(0x81); /// /// let n_transfers = 8; /// let transfer_size = 256; /// /// while queue.pending() < n_transfers { /// queue.submit(RequestBuffer::new(transfer_size)); /// } /// /// loop { /// let completion = block_on(queue.next_complete()); /// handle_data(&completion.data); // your function /// /// if completion.status.is_err() { /// break; /// } /// /// queue.submit(RequestBuffer::reuse(completion.data, transfer_size)) /// } /// ``` /// /// ### Example (write to an endpoint) /// ```no_run /// use std::mem; /// use futures_lite::future::block_on; /// # let di = nusb::list_devices().unwrap().next().unwrap(); /// # let device = di.open().unwrap(); /// # let interface = device.claim_interface(0).unwrap(); /// # fn fill_data(_: &mut Vec) {} /// # fn data_confirmed_sent(_: usize) {} /// let mut queue = interface.bulk_out_queue(0x02); /// /// let n_transfers = 8; /// /// let mut next_buf = Vec::new(); /// /// loop { /// while queue.pending() < n_transfers { /// let mut buf = mem::replace(&mut next_buf, Vec::new()); /// fill_data(&mut buf); // your function /// queue.submit(buf); /// } /// /// let completion = block_on(queue.next_complete()); /// data_confirmed_sent(completion.data.actual_length()); // your function /// next_buf = completion.data.reuse(); /// if completion.status.is_err() { /// break; /// } /// } /// ``` pub struct Queue { interface: Arc, endpoint: u8, endpoint_type: EndpointType, /// A queue of pending transfers, expected to complete in order pending: VecDeque>, /// An idle transfer that recently completed for re-use. cached: Option>, bufs: PhantomData, } impl Queue where R: TransferRequest + Send + Sync, platform::TransferData: PlatformSubmit, { pub(crate) fn new( interface: Arc, endpoint: u8, endpoint_type: EndpointType, ) -> Queue { Queue { interface, endpoint, endpoint_type, pending: VecDeque::new(), cached: None, bufs: PhantomData, } } /// Submit a new transfer on the endpoint. /// /// For an `IN` endpoint, pass a [`RequestBuffer`][`super::RequestBuffer`].\ /// For an `OUT` endpoint, pass a [`Vec`]. pub fn submit(&mut self, data: R) { let mut transfer = self.cached.take().unwrap_or_else(|| { self.interface .make_transfer(self.endpoint, self.endpoint_type) }); transfer.submit(data); self.pending.push_back(transfer); } /// Return a `Future` that waits for the next pending transfer to complete, and yields its /// buffer and status. /// /// For an `IN` endpoint, the completion contains a [`Vec`].\ /// For an `OUT` endpoint, the completion contains a [`ResponseBuffer`][`super::ResponseBuffer`]. /// /// This future is cancel-safe: it can be cancelled and re-created without /// side effects, enabling its use in `select!{}` or similar. /// /// Panics if there are no transfers pending. pub fn next_complete<'a>( &'a mut self, ) -> impl Future> + Unpin + Send + Sync + 'a { poll_fn(|cx| self.poll_next(cx)) } /// Get the next pending transfer if one has completed, or register the /// current task for wakeup when the next transfer completes. /// /// For an `IN` endpoint, the completion contains a [`Vec`].\ /// For an `OUT` endpoint, the completion contains a /// [`ResponseBuffer`][`super::ResponseBuffer`]. /// /// Panics if there are no transfers pending. pub fn poll_next(&mut self, cx: &mut Context) -> Poll> { let res = self .pending .front_mut() .expect("queue should have pending transfers when calling next_complete") .poll_completion::(cx); if res.is_ready() { self.cached = self.pending.pop_front(); } res } /// Get the number of transfers that have been submitted with `submit` that /// have not yet been returned from `next_complete`. pub fn pending(&self) -> usize { self.pending.len() } /// Request cancellation of all pending transfers. /// /// The transfers will still be returned from subsequent calls to /// `next_complete` so you can tell which were completed, /// partially-completed, or cancelled. pub fn cancel_all(&mut self) { // Cancel transfers in reverse order to ensure subsequent transfers // can't complete out of order while we're going through them. for transfer in self.pending.iter_mut().rev() { transfer.cancel(); } } /// Clear the endpoint's halt / stall condition. /// /// Sends a `CLEAR_FEATURE` `ENDPOINT_HALT` control transfer to tell the /// device to reset the endpoint's data toggle and clear the halt / stall /// condition, and resets the host-side data toggle. /// /// Use this after receiving /// [`TransferError::Stall`][crate::transfer::TransferError::Stall] to clear /// the error and resume use of the endpoint. /// /// This should not be called when transfers are pending on the endpoint. pub fn clear_halt(&mut self) -> Result<(), Error> { self.interface.clear_halt(self.endpoint) } } impl Drop for Queue { fn drop(&mut self) { // Cancel transfers in reverse order to ensure subsequent transfers // can't complete out of order while we're going through them. self.pending.drain(..).rev().for_each(drop) } }