rockusb-0.2.0/.cargo_vcs_info.json0000644000000001450000000000100125030ustar { "git": { "sha1": "ba976b07dc936962f3d0b960b683f0c148ad6876" }, "path_in_vcs": "rockusb" }rockusb-0.2.0/Cargo.lock0000644000000763710000000000100104740ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" [[package]] name = "async-compression" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "103db485efc3e41214fe4fda9f3dbeae2eb9082f48fd236e6095627a9422066e" dependencies = [ "flate2", "futures-core", "futures-io", "memchr", "pin-project-lite", ] [[package]] name = "async-trait" version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", "syn", ] [[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 = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bmap-parser" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d9f48369233f8a3e882072f8e20407fa4b182a899e33516efcc67b0faa70024" dependencies = [ "async-trait", "digest", "flate2", "futures 0.3.31", "quick-xml", "serde", "sha2", "strum", "thiserror", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" dependencies = [ "byteorder", "iovec", ] [[package]] name = "bytes" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap-num" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[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 = "cpufeatures" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "flate2" version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "futures" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures 0.1.31", "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", "tokio-io", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] [[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 = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" dependencies = [ "libc", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "libc" version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libusb1-sys" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[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 = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "nbd" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbe5c84546ed331d4e5b8a2960d166dda49732b07f00a0ac27084783724dc000" dependencies = [ "byteorder", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_enum" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "nusb" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d8beeee5c0ad012e1eaca540f5610d09a3af58ec435537d511b67e0be36ea66" dependencies = [ "atomic-waker", "core-foundation", "core-foundation-sys", "io-kit-sys", "log", "once_cell", "rustix", "slab", "windows-sys 0.48.0", ] [[package]] name = "object" version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] [[package]] name = "rockfile" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ff09b2bb83cbea829c1903c8356af61d75919ed2ccd05c6ff9ffc9c21bb84bd" dependencies = [ "bytes 1.7.2", ] [[package]] name = "rockusb" version = "0.2.0" dependencies = [ "anyhow", "async-compression", "bmap-parser", "bytes 1.7.2", "clap", "clap-num", "crc", "fastrand", "flate2", "futures 0.3.31", "nbd", "num_enum", "nusb", "rockfile", "rusb", "thiserror", "tokio", "tokio-util", ] [[package]] name = "rusb" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" dependencies = [ "libc", "libusb1-sys", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustversion" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", "sha2-asm", ] [[package]] name = "sha2-asm" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b845214d6175804686b2bd482bcffe96651bb2d1200742b712003504a2dac1ab" dependencies = [ "cc", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn", ] [[package]] name = "syn" version = "2.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e185e337f816bc8da115b8afcb3324006ccc82eeaddf35113888d3bd8e44ac" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio" version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes 1.7.2", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-io" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.31", "log", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-util" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes 1.7.2", "futures-core", "futures-io", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 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" [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] rockusb-0.2.0/Cargo.toml0000644000000045110000000000100105020ustar # 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" name = "rockusb" version = "0.2.0" authors = ["Sjoerd Simons "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Rockchip usb protocol host implementation" homepage = "https://github.com/collabora/rockchiprs" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/collabora/rockchiprs" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [lib] name = "rockusb" path = "src/lib.rs" [[example]] name = "rockusb" path = "examples/rockusb.rs" required-features = ["libusb"] [[example]] name = "rockusb-nusb" path = "examples/rockusb-nusb.rs" required-features = ["nusb"] [dependencies.bytes] version = "1.4.0" [dependencies.crc] version = "3.0.1" [dependencies.fastrand] version = "2" [dependencies.futures] version = "0.3.31" optional = true [dependencies.num_enum] version = "0.7" [dependencies.nusb] version = "0.1.10" optional = true [dependencies.rusb] version = "0.9.4" optional = true [dependencies.thiserror] version = "1.0.38" [dev-dependencies.anyhow] version = "1.0.69" [dev-dependencies.async-compression] version = "0.4.5" features = [ "gzip", "futures-io", ] [dev-dependencies.bmap-parser] version = "0.2.0" [dev-dependencies.clap] version = "4.2" features = ["derive"] [dev-dependencies.clap-num] version = "1.0" [dev-dependencies.flate2] version = "1.0.25" [dev-dependencies.futures] version = "0.3.31" features = [ "compat", "io-compat", ] [dev-dependencies.nbd] version = "0.3" [dev-dependencies.rockfile] version = "0.1.2" [dev-dependencies.rusb] version = "0.9.1" [dev-dependencies.tokio] version = "1.40.0" features = ["full"] [dev-dependencies.tokio-util] version = "0.7.12" features = ["compat"] [features] libusb = ["dep:rusb"] nusb = [ "dep:nusb", "dep:futures", ] rockusb-0.2.0/Cargo.toml.orig000064400000000000000000000026211046102023000141630ustar 00000000000000[package] name = "rockusb" version = "0.2.0" edition = "2021" authors = ["Sjoerd Simons "] license = "MIT OR Apache-2.0" description = "Rockchip usb protocol host implementation" homepage = "https://github.com/collabora/rockchiprs" repository = "https://github.com/collabora/rockchiprs" readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] libusb = ["dep:rusb"] nusb = ["dep:nusb", "dep:futures"] [dependencies] bytes = "1.4.0" crc = "3.0.1" fastrand = "2" num_enum = "0.7" thiserror = "1.0.38" rusb = { version = "0.9.4", optional = true } nusb = { version = "0.1.10", optional = true } futures = { version = "0.3.31", optional = true } [dev-dependencies] anyhow = "1.0.69" bmap-parser = "0.2.0" clap = { version = "4.2", features = ["derive"] } clap-num = "1.0" flate2 = "1.0.25" nbd = "0.3" rockfile = { path = "../rockfile", version = "0.1.2" } rusb = "0.9.1" tokio = { version = "1.40.0", features = ["full"] } futures = { version = "0.3.31", features = ["compat", "io-compat"]} tokio-util = { version = "0.7.12", features = ["compat"] } async-compression = { version = "0.4.5", features = ["gzip", "futures-io"] } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [[example]] name="rockusb" required-features = ["libusb"] [[example]] name="rockusb-nusb" required-features = ["nusb"] rockusb-0.2.0/README.md000064400000000000000000000017541046102023000125610ustar 00000000000000# Rockchip usb protocol host implementation Rockchip bootroms and early loaders implement an USB protocol to help loader early firmware, flashing persistant storage etc. This crate contains a sans-io implementation of that protocol as well as an optional implementations of IO using libusb or nusb. Printing chip info using libusb backend: ```rust,no_run # fn main() -> anyhow::Result<()> { let devices = rockusb::libusb::Devices::new()?; let mut transport = devices.iter().next() .ok_or_else(|| anyhow::anyhow!("No Device found"))??; println!("Chip Info: {:0x?}", transport.chip_info()?); Ok(()) # } ``` Printing chip info using nusb backend: ```rust,no_run # #[tokio::main] # async fn main() -> anyhow::Result<()> { let mut devices = rockusb::nusb::devices()?; let info = devices.next() .ok_or_else(|| anyhow::anyhow!("No Device found"))?; let mut transport = rockusb::nusb::Transport::from_usb_device_info(info)?; println!("Chip Info: {:0x?}", transport.chip_info().await?); Ok(()) # } ``` rockusb-0.2.0/examples/rockusb-nusb.rs000064400000000000000000000247611046102023000161060ustar 00000000000000use std::{ ffi::OsStr, io::SeekFrom, path::{Path, PathBuf}, thread::sleep, time::Duration, }; use anyhow::{anyhow, Result}; use async_compression::futures::bufread::GzipDecoder; use bmap_parser::Bmap; use clap::{Parser, ValueEnum}; use clap_num::maybe_hex; use futures::io::{BufReader, BufWriter}; use rockfile::boot::{ RkBootEntry, RkBootEntryBytes, RkBootHeader, RkBootHeaderBytes, RkBootHeaderEntry, }; use rockusb::nusb::Transport; use rockusb::protocol::ResetOpcode; use tokio::{ fs::File, io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, }; use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; async fn read_flash_info(mut transport: Transport) -> Result<()> { let info = transport.flash_info().await?; println!("Raw Flash Info: {:0x?}", info); println!( "Flash size: {} MB ({} sectors)", info.sectors() / 2048, info.sectors() ); Ok(()) } async fn reset_device(mut transport: Transport, opcode: ResetOpcode) -> Result<()> { transport.reset_device(opcode).await?; Ok(()) } async fn read_chip_info(mut transport: Transport) -> Result<()> { println!("Chip Info: {:0x?}", transport.chip_info().await?); Ok(()) } async fn read_lba(mut transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut data = vec![0; length as usize * 512]; transport.read_lba(offset, &mut data).await?; let mut file = tokio::fs::OpenOptions::new() .create(true) .write(true) .truncate(true) .open(path) .await?; file.write_all(&data).await?; Ok(()) } async fn write_lba(mut transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut data = vec![0; length as usize * 512]; let mut file = File::open(path).await?; file.read_exact(&mut data).await?; transport.write_lba(offset, &data).await?; Ok(()) } async fn read_file(transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut file = tokio::fs::File::create(path).await?; let mut io = transport.into_io().await?.compat(); io.seek(SeekFrom::Start(offset as u64 * 512)).await?; tokio::io::copy(&mut io.take(length as u64 * 512), &mut file).await?; Ok(()) } async fn write_file(transport: Transport, offset: u32, path: &Path) -> Result<()> { let mut file = tokio::fs::File::open(path).await?; let mut io = transport.into_io().await?.compat(); io.seek(SeekFrom::Start(offset as u64 * 512)).await?; tokio::io::copy(&mut file, &mut io).await?; Ok(()) } fn find_bmap(img: &Path) -> Option { fn append(path: PathBuf) -> PathBuf { let mut p = path.into_os_string(); p.push(".bmap"); p.into() } let mut bmap = img.to_path_buf(); loop { bmap = append(bmap); if bmap.exists() { return Some(bmap); } // Drop .bmap bmap.set_extension(""); bmap.extension()?; // Drop existing orignal extension part bmap.set_extension(""); } } async fn write_bmap(transport: Transport, path: &Path) -> Result<()> { let bmap_path = find_bmap(path).ok_or_else(|| anyhow!("Failed to find bmap"))?; println!("Using bmap file: {}", path.display()); let mut bmap_file = File::open(bmap_path).await?; let mut xml = String::new(); bmap_file.read_to_string(&mut xml).await?; let bmap = Bmap::from_xml(&xml)?; // HACK to minimize small writes let mut writer = BufWriter::with_capacity(16 * 1024 * 1024, transport.into_io().await?); let file = File::open(path).await?; let mut file = BufReader::with_capacity(16 * 1024 * 1024, file.compat()); match path.extension().and_then(OsStr::to_str) { Some("gz") => { let gz = GzipDecoder::new(file); let mut gz = bmap_parser::AsyncDiscarder::new(gz); bmap_parser::copy_async(&mut gz, &mut writer, &bmap).await?; } _ => { bmap_parser::copy_async(&mut file, &mut writer, &bmap).await?; } } Ok(()) } async fn download_entry( header: RkBootHeaderEntry, code: u16, file: &mut File, transport: &mut Transport, ) -> Result<()> { for i in 0..header.count { let mut entry: RkBootEntryBytes = [0; 57]; file.seek(SeekFrom::Start( header.offset as u64 + (header.size * i) as u64, )) .await?; file.read_exact(&mut entry).await?; let entry = RkBootEntry::from_bytes(&entry); println!("{} Name: {}", i, String::from_utf16(entry.name.as_slice())?); let mut data = vec![0; entry.data_size as usize]; file.seek(SeekFrom::Start(entry.data_offset as u64)).await?; file.read_exact(&mut data).await?; transport.write_maskrom_area(code, &data).await?; println!("Done!... waiting {}ms", entry.data_delay); if entry.data_delay > 0 { sleep(Duration::from_millis(entry.data_delay as u64)); } } Ok(()) } async fn download_boot(mut transport: Transport, path: &Path) -> Result<()> { let mut file = File::open(path).await?; let mut header: RkBootHeaderBytes = [0; 102]; file.read_exact(&mut header).await?; let header = RkBootHeader::from_bytes(&header).ok_or_else(|| anyhow!("Failed to parse header"))?; download_entry(header.entry_471, 0x471, &mut file, &mut transport).await?; download_entry(header.entry_472, 0x472, &mut file, &mut transport).await?; Ok(()) } #[derive(Debug, clap::Parser)] enum Command { List, DownloadBoot { path: PathBuf, }, Read { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, ReadFile { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, Write { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, WriteFile { #[clap(value_parser=maybe_hex::)] offset: u32, path: PathBuf, }, WriteBmap { path: PathBuf, }, ChipInfo, FlashId, FlashInfo, ResetDevice { #[clap(value_enum, default_value_t=ArgResetOpcode::Reset)] opcode: ArgResetOpcode, }, } #[derive(ValueEnum, Clone, Debug)] pub enum ArgResetOpcode { /// Reset Reset, /// Reset to USB mass-storage device class MSC, /// Powers the SOC off PowerOff, /// Reset to maskrom mode Maskrom, /// Disconnect from USB Disconnect, } impl From for ResetOpcode { fn from(arg: ArgResetOpcode) -> ResetOpcode { match arg { ArgResetOpcode::Reset => ResetOpcode::Reset, ArgResetOpcode::MSC => ResetOpcode::MSC, ArgResetOpcode::PowerOff => ResetOpcode::PowerOff, ArgResetOpcode::Maskrom => ResetOpcode::Maskrom, ArgResetOpcode::Disconnect => ResetOpcode::Disconnect, } } } #[derive(Debug, Clone)] struct DeviceArg { bus_number: u8, address: u8, } fn parse_device(device: &str) -> Result { let mut parts = device.split(':'); let bus_number = parts .next() .ok_or_else(|| anyhow!("No bus number: use :
"))? .parse() .map_err(|_| anyhow!("Bus should be a number"))?; let address = parts .next() .ok_or_else(|| anyhow!("No address: use :
"))? .parse() .map_err(|_| anyhow!("Address should be a numbrer"))?; if parts.next().is_some() { return Err(anyhow!("Too many parts")); } Ok(DeviceArg { bus_number, address, }) } #[derive(clap::Parser)] struct Opts { #[arg(short, long, value_parser = parse_device)] /// Device type specified as :
device: Option, #[command(subcommand)] command: Command, } fn list_available_devices() -> Result<()> { let devices = rockusb::nusb::devices()?; println!("Available rockchip devices:"); for d in devices { println!( "* Bus {} Device {} ID {}:{}", d.bus_number(), d.device_address(), d.vendor_id(), d.product_id() ); } Ok(()) } #[tokio::main] async fn main() -> Result<()> { let opt = Opts::parse(); // Commands that don't talk a device if matches!(opt.command, Command::List) { return list_available_devices(); } let mut devices = rockusb::nusb::devices()?; let info = if let Some(dev) = opt.device { devices .find(|d| d.bus_number() == dev.bus_number && d.device_address() == dev.address) .ok_or_else(|| anyhow!("Specified device not found"))? } else { let mut devices: Vec<_> = devices.collect(); match devices.len() { 0 => Err(anyhow!("No devices found")), 1 => Ok(devices.pop().unwrap()), _ => { drop(devices); let _ = list_available_devices(); println!(); Err(anyhow!( "Please select a specific device using the -d option" )) } }? }; let mut transport = Transport::from_usb_device_info(info)?; match opt.command { Command::List => unreachable!(), Command::DownloadBoot { path } => download_boot(transport, &path).await, Command::Read { offset, length, path, } => read_lba(transport, offset, length, &path).await, Command::ReadFile { offset, length, path, } => read_file(transport, offset, length, &path).await, Command::Write { offset, length, path, } => write_lba(transport, offset, length, &path).await, Command::WriteFile { offset, path } => write_file(transport, offset, &path).await, Command::WriteBmap { path } => write_bmap(transport, &path).await, Command::ChipInfo => read_chip_info(transport).await, Command::FlashId => { let id = transport.flash_id().await?; println!("Flash id: {}", id.to_str()); println!("raw: {:?}", id); Ok(()) } Command::FlashInfo => read_flash_info(transport).await, Command::ResetDevice { opcode } => reset_device(transport, opcode.into()).await, } } rockusb-0.2.0/examples/rockusb.rs000064400000000000000000000250661046102023000151400ustar 00000000000000use std::{ ffi::OsStr, fs::File, io::{BufWriter, Read, Seek, SeekFrom, Write}, net::TcpListener, path::{Path, PathBuf}, thread::sleep, time::Duration, }; use anyhow::{anyhow, Result}; use bmap_parser::Bmap; use clap::{Parser, ValueEnum}; use clap_num::maybe_hex; use flate2::read::GzDecoder; use rockfile::boot::{ RkBootEntry, RkBootEntryBytes, RkBootHeader, RkBootHeaderBytes, RkBootHeaderEntry, }; use rockusb::libusb::{DeviceUnavalable, Transport}; use rockusb::protocol::ResetOpcode; fn read_flash_info(mut transport: Transport) -> Result<()> { let info = transport.flash_info()?; println!("Raw Flash Info: {:0x?}", info); println!( "Flash size: {} MB ({} sectors)", info.sectors() / 2048, info.sectors() ); Ok(()) } fn reset_device(mut transport: Transport, opcode: ResetOpcode) -> Result<()> { transport.reset_device(opcode)?; Ok(()) } fn read_chip_info(mut transport: Transport) -> Result<()> { println!("Chip Info: {:0x?}", transport.chip_info()?); Ok(()) } fn read_lba(mut transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut data = vec![0; length as usize * 512]; transport.read_lba(offset, &mut data)?; let mut file = std::fs::OpenOptions::new() .create(true) .write(true) .truncate(true) .open(path)?; file.write_all(&data)?; Ok(()) } fn write_lba(mut transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut data = vec![0; length as usize * 512]; let mut file = File::open(path)?; file.read_exact(&mut data)?; transport.write_lba(offset, &data)?; Ok(()) } fn write_file(mut transport: Transport, offset: u32, path: &Path) -> Result<()> { let mut file = File::open(path)?; let mut io = transport.io()?; io.seek(SeekFrom::Start(offset as u64 * 512))?; std::io::copy(&mut file, &mut io)?; Ok(()) } fn find_bmap(img: &Path) -> Option { fn append(path: PathBuf) -> PathBuf { let mut p = path.into_os_string(); p.push(".bmap"); p.into() } let mut bmap = img.to_path_buf(); loop { bmap = append(bmap); if bmap.exists() { return Some(bmap); } // Drop .bmap bmap.set_extension(""); bmap.extension()?; // Drop existing orignal extension part bmap.set_extension(""); } } fn write_bmap(transport: Transport, path: &Path) -> Result<()> { let bmap_path = find_bmap(path).ok_or_else(|| anyhow!("Failed to find bmap"))?; println!("Using bmap file: {}", path.display()); let mut bmap_file = File::open(bmap_path)?; let mut xml = String::new(); bmap_file.read_to_string(&mut xml)?; let bmap = Bmap::from_xml(&xml)?; // HACK to minimize small writes let mut writer = BufWriter::with_capacity(16 * 1024 * 1024, transport.into_io()?); let mut file = File::open(path)?; match path.extension().and_then(OsStr::to_str) { Some("gz") => { let gz = GzDecoder::new(file); let mut gz = bmap_parser::Discarder::new(gz); bmap_parser::copy(&mut gz, &mut writer, &bmap)?; } _ => { bmap_parser::copy(&mut file, &mut writer, &bmap)?; } } Ok(()) } fn download_entry( header: RkBootHeaderEntry, code: u16, file: &mut File, transport: &mut Transport, ) -> Result<()> { for i in 0..header.count { let mut entry: RkBootEntryBytes = [0; 57]; file.seek(SeekFrom::Start( header.offset as u64 + (header.size * i) as u64, ))?; file.read_exact(&mut entry)?; let entry = RkBootEntry::from_bytes(&entry); println!("{} Name: {}", i, String::from_utf16(entry.name.as_slice())?); let mut data = vec![0; entry.data_size as usize]; file.seek(SeekFrom::Start(entry.data_offset as u64))?; file.read_exact(&mut data)?; transport.write_maskrom_area(code, &data)?; println!("Done!... waiting {}ms", entry.data_delay); if entry.data_delay > 0 { sleep(Duration::from_millis(entry.data_delay as u64)); } } Ok(()) } fn download_boot(mut transport: Transport, path: &Path) -> Result<()> { let mut file = File::open(path)?; let mut header: RkBootHeaderBytes = [0; 102]; file.read_exact(&mut header)?; let header = RkBootHeader::from_bytes(&header).ok_or_else(|| anyhow!("Failed to parse header"))?; download_entry(header.entry_471, 0x471, &mut file, &mut transport)?; download_entry(header.entry_472, 0x472, &mut file, &mut transport)?; Ok(()) } fn run_nbd(transport: Transport) -> Result<()> { let listener = TcpListener::bind("127.0.0.1:10809").unwrap(); println!( "Listening for nbd connection on: {:?}", listener.local_addr()? ); let mut stream = listener .incoming() .next() .transpose()? .ok_or_else(|| anyhow!("Connection failure"))?; // Stop listening for new connections drop(listener); let io = transport.into_io()?; println!("Connection!"); nbd::server::handshake(&mut stream, |_s| { Ok(nbd::Export { size: io.size(), readonly: false, resizeable: false, rotational: false, send_trim: false, send_flush: true, data: (), }) })?; println!("Shook hands!"); nbd::server::transmission(&mut stream, io)?; println!("nbd client disconnected"); Ok(()) } #[derive(Debug, clap::Parser)] enum Command { List, DownloadBoot { path: PathBuf, }, Read { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, Write { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, WriteFile { #[clap(value_parser=maybe_hex::)] offset: u32, path: PathBuf, }, WriteBmap { path: PathBuf, }, ChipInfo, FlashId, FlashInfo, ResetDevice { #[clap(value_enum, default_value_t=ArgResetOpcode::Reset)] opcode: ArgResetOpcode, }, // Run/expose device as a network block device Nbd, } #[derive(ValueEnum, Clone, Debug)] pub enum ArgResetOpcode { /// Reset Reset, /// Reset to USB mass-storage device class MSC, /// Powers the SOC off PowerOff, /// Reset to maskrom mode Maskrom, /// Disconnect from USB Disconnect, } impl From for ResetOpcode { fn from(arg: ArgResetOpcode) -> ResetOpcode { match arg { ArgResetOpcode::Reset => ResetOpcode::Reset, ArgResetOpcode::MSC => ResetOpcode::MSC, ArgResetOpcode::PowerOff => ResetOpcode::PowerOff, ArgResetOpcode::Maskrom => ResetOpcode::Maskrom, ArgResetOpcode::Disconnect => ResetOpcode::Disconnect, } } } #[derive(Debug, Clone)] struct DeviceArg { bus_number: u8, address: u8, } fn parse_device(device: &str) -> Result { let mut parts = device.split(':'); let bus_number = parts .next() .ok_or_else(|| anyhow!("No bus number: use :
"))? .parse() .map_err(|_| anyhow!("Bus should be a number"))?; let address = parts .next() .ok_or_else(|| anyhow!("No address: use :
"))? .parse() .map_err(|_| anyhow!("Address should be a numbrer"))?; if parts.next().is_some() { return Err(anyhow!("Too many parts")); } Ok(DeviceArg { bus_number, address, }) } #[derive(clap::Parser)] struct Opts { #[arg(short, long, value_parser = parse_device)] /// Device type specified as :
device: Option, #[command(subcommand)] command: Command, } fn list_available_devices() -> Result<()> { let devices = rockusb::libusb::Devices::new()?; println!("Available rockchip devices"); for d in devices.iter() { match d { Ok(mut d) => println!("* {:?}", d.handle().device()), Err(DeviceUnavalable { device, error }) => { println!("* {:?} - Unavailable: {}", device, error) } } } Ok(()) } fn main() -> Result<()> { let opt = Opts::parse(); // Commands that don't talk a device if matches!(opt.command, Command::List) { return list_available_devices(); } let devices = rockusb::libusb::Devices::new()?; let mut transport = if let Some(dev) = opt.device { devices .iter() .find(|d| match d { Ok(device) => { device.bus_number() == dev.bus_number && device.address() == dev.address } Err(DeviceUnavalable { device, .. }) => { device.bus_number() == dev.bus_number && device.address() == dev.address } }) .ok_or_else(|| anyhow!("Specified device not found"))? } else { let mut devices: Vec<_> = devices.iter().collect(); match devices.len() { 0 => Err(anyhow!("No devices found")), 1 => Ok(devices.pop().unwrap()), _ => { drop(devices); let _ = list_available_devices(); println!(); Err(anyhow!( "Please select a specific device using the -d option" )) } }? }?; match opt.command { Command::List => unreachable!(), Command::DownloadBoot { path } => download_boot(transport, &path), Command::Read { offset, length, path, } => read_lba(transport, offset, length, &path), Command::Write { offset, length, path, } => write_lba(transport, offset, length, &path), Command::WriteFile { offset, path } => write_file(transport, offset, &path), Command::WriteBmap { path } => write_bmap(transport, &path), Command::ChipInfo => read_chip_info(transport), Command::FlashId => { let id = transport.flash_id()?; println!("Flash id: {}", id.to_str()); println!("raw: {:?}", id); Ok(()) } Command::FlashInfo => read_flash_info(transport), Command::ResetDevice { opcode } => reset_device(transport, opcode.into()), Command::Nbd => run_nbd(transport), } } rockusb-0.2.0/src/lib.rs000064400000000000000000000010701046102023000131740ustar 00000000000000#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] /// libusb transport implementation #[cfg(feature = "libusb")] pub mod libusb; /// nusb transport implementation #[cfg(feature = "nusb")] pub mod nusb; /// sans-io protocol implementations /// /// This module contains all protocol logic; Each operation implements the [operation::OperationSteps] /// trait which gives a transport a series of [operation::UsbStep] to execute to complete an /// operation. pub mod operation; /// low-level usb protocol data structures pub mod protocol; rockusb-0.2.0/src/libusb.rs000064400000000000000000000353261046102023000137210ustar 00000000000000use std::{ borrow::BorrowMut, io::{Read, Seek, SeekFrom, Write}, time::Duration, }; use crate::{ operation::{OperationSteps, UsbStep}, protocol::{ChipInfo, FlashId, FlashInfo, ResetOpcode, SECTOR_SIZE}, }; use rusb::{DeviceHandle, GlobalContext}; use thiserror::Error; /// Error indicate a device is not available #[derive(Debug, Clone, Eq, PartialEq, Error)] #[error("Device is not available: {device:?} {error}")] pub struct DeviceUnavalable { pub device: rusb::Device, #[source] pub error: rusb::Error, } #[derive(Debug, Clone, Eq, PartialEq, Error)] pub enum Error { #[error("Usb error: {0}")] UsbError(#[from] rusb::Error), #[error("Operation error: {0}")] OperationError(#[from] crate::operation::UsbOperationError), } type Result = std::result::Result; /// Rockchip devices pub struct Devices { devices: rusb::DeviceList, } impl Devices { pub fn new() -> Result { let devices = rusb::DeviceList::new()?; Ok(Self { devices }) } /// Create an Iterator over found Rockchip device pub fn iter(&self) -> DevicesIter { let iter = self.devices.iter(); DevicesIter { iter } } } /// Iterator over found Rockchip device pub struct DevicesIter<'a> { iter: rusb::Devices<'a, GlobalContext>, } impl Iterator for DevicesIter<'_> { type Item = std::result::Result; fn next(&mut self) -> Option { for device in self.iter.by_ref() { let desc = match device.device_descriptor() { Ok(desc) => desc, _ => continue, }; if desc.vendor_id() != 0x2207 { continue; } let handle = match device.open() { Ok(handle) => handle, Err(error) => return Some(Err(DeviceUnavalable { device, error })), }; return Some(Transport::from_usb_device(handle)); } None } } /// libusb based Transport for rockusb operation pub struct Transport { handle: DeviceHandle, ep_in: u8, ep_out: u8, } impl Transport { fn new( handle: DeviceHandle, interface: u8, ep_in: u8, ep_out: u8, ) -> std::result::Result { handle .claim_interface(interface) .map_err(|error| DeviceUnavalable { device: handle.device(), error, })?; Ok(Self { handle, ep_in, ep_out, }) } /// Create a new transport from an exist device handle pub fn from_usb_device( handle: rusb::DeviceHandle, ) -> std::result::Result { let device = handle.device(); let desc = device .device_descriptor() .map_err(|error| DeviceUnavalable { device: device.clone(), error, })?; for c in 0..desc.num_configurations() { let config = device .config_descriptor(c) .map_err(|error| DeviceUnavalable { device: device.clone(), error, })?; for i in config.interfaces() { for i_desc in i.descriptors() { let output = i_desc.endpoint_descriptors().find(|e| { e.direction() == rusb::Direction::Out && e.transfer_type() == rusb::TransferType::Bulk }); let input = i_desc.endpoint_descriptors().find(|e| { e.direction() == rusb::Direction::In && e.transfer_type() == rusb::TransferType::Bulk }); if let (Some(input), Some(output)) = (input, output) { return Transport::new( handle, i_desc.setting_number(), input.address(), output.address(), ); } } } } Err(DeviceUnavalable { device, error: rusb::Error::NotFound, }) } /// Create an IO object which implements [Read], [Write] and /// [Seek] pub fn io(&mut self) -> Result> { TransportIO::new(self) } /// Convert into an IO object which implements [Read], [Write] and /// [Seek] pub fn into_io(self) -> Result> { TransportIO::new(self) } /// Get a reference to the underlying device handle pub fn handle(&mut self) -> &mut DeviceHandle { &mut self.handle } /// Get the bus number of the current device pub fn bus_number(&self) -> u8 { self.handle.device().bus_number() } /// Get the bus address of the current device pub fn address(&self) -> u8 { self.handle.device().address() } fn handle_operation(&mut self, mut operation: O) -> Result where O: OperationSteps, { loop { let step = operation.step(); match step { UsbStep::WriteBulk { data } => { let _written = self.handle .write_bulk(self.ep_out, data, Duration::from_secs(5))?; } UsbStep::ReadBulk { data } => { let _read = self .handle .read_bulk(self.ep_in, data, Duration::from_secs(5))?; } UsbStep::Finished(r) => break r.map_err(|e| e.into()), UsbStep::WriteControl { request_type, request, value, index, data, } => { self.handle.write_control( request_type, request, value, index, data, Duration::from_secs(5), )?; } } } } /// retrieve SoC flash identifier pub fn flash_id(&mut self) -> Result { self.handle_operation(crate::operation::flash_id()) } /// retrieve SoC flash info pub fn flash_info(&mut self) -> Result { self.handle_operation(crate::operation::flash_info()) } /// retrieve SoC chip info pub fn chip_info(&mut self) -> Result { self.handle_operation(crate::operation::chip_info()) } /// read from the flash /// /// start_sector with [SECTOR_SIZE] sectors. the data to be read /// must be a multiple of [SECTOR_SIZE] bytes pub fn read_lba(&mut self, start_sector: u32, read: &mut [u8]) -> Result { self.handle_operation(crate::operation::read_lba(start_sector, read)) .map(|t| t.into()) } /// Create operation to read an lba from the flash /// /// start_sector based on [SECTOR_SIZE] sectors. the data to be /// written must be a multiple of [SECTOR_SIZE] bytes pub fn write_lba(&mut self, start_sector: u32, write: &[u8]) -> Result { self.handle_operation(crate::operation::write_lba(start_sector, write)) .map(|t| t.into()) } /// Write a specific area while in maskrom mode; typically 0x471 or 0x472 data as retrieved from a /// rockchip boot file pub fn write_maskrom_area(&mut self, area: u16, data: &[u8]) -> Result<()> { self.handle_operation(crate::operation::write_area(area, data)) } /// Reset the device pub fn reset_device(&mut self, opcode: ResetOpcode) -> Result<()> { self.handle_operation(crate::operation::reset_device(opcode)) } } /// IO object which implements [Read], [Write] and [Seek] pub struct TransportIO { transport: T, size: u64, // Read/Write offset in bytes offset: u64, buffer: [u8; 512], // Whether or not the buffer is dirty state: BufferState, } impl TransportIO where T: BorrowMut, { const MAXIO_SIZE: u64 = 128 * crate::protocol::SECTOR_SIZE; /// Create a new IO object around a given transport pub fn new(mut transport: T) -> Result { let info = transport.borrow_mut().flash_info()?; Ok(Self { transport, size: info.size(), offset: 0, buffer: [0u8; 512], state: BufferState::Invalid, }) } /// Get a reference to the inner transport pub fn inner(&mut self) -> &mut Transport { self.transport.borrow_mut() } /// Convert into the inner transport pub fn into_inner(self) -> T { self.transport } /// Size of the flash in bytes pub fn size(&self) -> u64 { self.size } fn current_sector(&self) -> u64 { self.offset / SECTOR_SIZE } // Want to start an i/o operation with a given maximum length fn pre_io(&mut self, len: u64) -> std::result::Result { if self.offset >= self.size { return Ok(IOOperation::Eof); } // Offset inside the current sector let sector_offset = self.offset % SECTOR_SIZE; // bytes left from current position to end of current sector let sector_remaining = SECTOR_SIZE - sector_offset; // If the I/O operation is starting at a sector edge and encompasses at least one sector // then direct I/O can be done if sector_offset == 0 && len >= SECTOR_SIZE { // At most read the amount of bytes left let left = self.size - self.offset; let io_len = len.min(left) / SECTOR_SIZE * SECTOR_SIZE; Ok(IOOperation::Direct { len: io_len.min(Self::MAXIO_SIZE) as usize, }) } else { if self.state == BufferState::Invalid { let sector = self.current_sector() as u32; self.transport .borrow_mut() .read_lba(sector, &mut self.buffer) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; self.state = BufferState::Valid; } Ok(IOOperation::Buffered { offset: sector_offset as usize, len: len.min(sector_remaining) as usize, }) } } fn post_io(&mut self, len: u64) -> std::result::Result { // Offset inside the current sector let sector_offset = self.offset % SECTOR_SIZE; // bytes left from current position to end of current sector let sector_remaining = SECTOR_SIZE - sector_offset; // If going over the sector edge flush the current buffer and invalidate it if len >= sector_remaining { self.flush_buffer()?; self.state = BufferState::Invalid; } self.offset += len; Ok(len as usize) } fn flush_buffer(&mut self) -> std::io::Result<()> { if self.state == BufferState::Dirty { let sector = self.current_sector() as u32; self.transport .borrow_mut() .write_lba(sector, &self.buffer) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; self.state = BufferState::Valid; } Ok(()) } fn do_read(&mut self, buf: &mut [u8]) -> std::io::Result { let sector = self.current_sector() as u32; self.transport .borrow_mut() .read_lba(sector, buf) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; Ok(buf.len()) } fn do_write(&mut self, buf: &[u8]) -> std::io::Result { let sector = self.current_sector() as u32; self.transport .borrow_mut() .write_lba(sector, buf) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; Ok(buf.len()) } } enum IOOperation { Direct { len: usize }, Buffered { offset: usize, len: usize }, Eof, } #[derive(Clone, Copy, Eq, PartialEq)] enum BufferState { // Buffer content doesn't match current offset Invalid, // Buffer content matches offset and device-side Valid, // Buffer content matches offset and has outstanding data Dirty, } impl Write for TransportIO where T: BorrowMut, { fn write(&mut self, buf: &[u8]) -> std::io::Result { let r = match self.pre_io(buf.len() as u64)? { IOOperation::Direct { len } => self.do_write(&buf[..len])?, IOOperation::Buffered { offset, len } => { self.buffer[offset..offset + len].copy_from_slice(&buf[0..len]); self.state = BufferState::Dirty; len } IOOperation::Eof => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "Trying to write past end of area", )) } }; self.post_io(r as u64) } fn flush(&mut self) -> std::io::Result<()> { self.flush_buffer() } } impl Read for TransportIO where T: BorrowMut, { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let r = match self.pre_io(buf.len() as u64)? { IOOperation::Direct { len } => self.do_read(&mut buf[..len])?, IOOperation::Buffered { offset, len } => { buf[0..len].copy_from_slice(&self.buffer[offset..offset + len]); len } IOOperation::Eof => 0, }; self.post_io(r as u64) } } impl Seek for TransportIO where T: BorrowMut, { fn seek(&mut self, pos: SeekFrom) -> std::io::Result { self.offset = match pos { SeekFrom::Start(offset) => self.size.min(offset), SeekFrom::End(offset) => { if offset > 0 { self.size } else { let offset = offset.unsigned_abs(); self.size.saturating_sub(offset) } } SeekFrom::Current(offset) => { if offset > 0 { let offset = offset as u64; self.offset.saturating_add(offset).min(self.size) } else { let offset = offset.unsigned_abs(); self.offset.saturating_sub(offset) } } }; Ok(self.offset) } } rockusb-0.2.0/src/nusb.rs000064400000000000000000000471771046102023000134170ustar 00000000000000use std::io::SeekFrom; use std::{borrow::BorrowMut, task::Poll}; use crate::{ operation::{OperationSteps, UsbStep}, protocol::{ChipInfo, FlashId, FlashInfo, ResetOpcode, SECTOR_SIZE}, }; use futures::{future::BoxFuture, ready}; use futures::{AsyncRead, AsyncSeek, AsyncWrite}; use nusb::{ transfer::{ControlOut, ControlType, Recipient, RequestBuffer}, DeviceInfo, }; use thiserror::Error; /// Error indicate a device is not available #[derive(Debug, Error)] #[error("Device is not available: {error}")] pub struct DeviceUnavalable { #[from] pub error: nusb::Error, } #[derive(Debug, Error)] pub enum Error { #[error("Usb error: {0}")] UsbError(#[from] nusb::Error), #[error("Usb transfer error: {0}")] UsbTransferError(#[from] nusb::transfer::TransferError), #[error("Operation error: {0}")] OperationError(#[from] crate::operation::UsbOperationError), } type Result = std::result::Result; /// List rockchip devices pub fn devices() -> std::result::Result, nusb::Error> { Ok(nusb::list_devices()?.filter(|d| d.vendor_id() == 0x2207)) } /// nusb based Transport for rockusb operation pub struct Transport { interface: nusb::Interface, ep_in: u8, ep_out: u8, } impl Transport { fn new( device: nusb::Device, interface: u8, ep_in: u8, ep_out: u8, ) -> std::result::Result { let interface = device.claim_interface(interface)?; Ok(Self { interface, ep_in, ep_out, }) } /// Create a new transport from a device info pub fn from_usb_device_info( info: nusb::DeviceInfo, ) -> std::result::Result { let device = info.open()?; Self::from_usb_device(device) } /// Create a new transport from an existing device pub fn from_usb_device(device: nusb::Device) -> std::result::Result { for config in device.clone().configurations() { for interface in config.interface_alt_settings() { let output = interface.endpoints().find(|e| { e.direction() == nusb::transfer::Direction::Out && e.transfer_type() == nusb::transfer::EndpointType::Bulk }); let input = interface.endpoints().find(|e| { e.direction() == nusb::transfer::Direction::In && e.transfer_type() == nusb::transfer::EndpointType::Bulk }); if let (Some(input), Some(output)) = (input, output) { return Transport::new( device, interface.interface_number(), input.address(), output.address(), ); } } } Err(DeviceUnavalable { error: nusb::Error::new(std::io::ErrorKind::NotFound, "Device not found"), }) } /// Convert into an IO object which implements [AsyncRead], /// [AsyncWrite] and [AsyncSeek] pub async fn into_io(self) -> Result { TransportIO::new(self).await } async fn handle_operation(&mut self, mut operation: O) -> Result where O: OperationSteps, { loop { let step = operation.step(); match step { UsbStep::WriteBulk { data } => { let _written = self .interface .bulk_out(self.ep_out, data.to_vec()) .await .into_result()?; } UsbStep::ReadBulk { data } => { let req = RequestBuffer::new(data.len()); let read = self .interface .bulk_in(self.ep_in, req) .await .into_result()?; data.copy_from_slice(&read); } UsbStep::WriteControl { request_type, request, value, index, data, } => { let (control_type, recipient) = ( match request_type >> 5 & 0x03 { 0 => ControlType::Standard, 1 => ControlType::Class, 2 => ControlType::Vendor, _ => ControlType::Standard, }, match request_type & 0x1f { 0 => Recipient::Device, 1 => Recipient::Interface, 2 => Recipient::Endpoint, 3 => Recipient::Other, _ => Recipient::Device, }, ); let data = ControlOut { control_type, recipient, request, value, index, data, }; self.interface.control_out(data).await.into_result()?; } UsbStep::Finished(r) => break r.map_err(|e| e.into()), } } } /// retrieve SoC flash identifier pub async fn flash_id(&mut self) -> Result { self.handle_operation(crate::operation::flash_id()).await } /// retrieve SoC flash info pub async fn flash_info(&mut self) -> Result { self.handle_operation(crate::operation::flash_info()).await } /// retrieve SoC chip info pub async fn chip_info(&mut self) -> Result { self.handle_operation(crate::operation::chip_info()).await } /// read from the flash /// /// start_sector with [SECTOR_SIZE] sectors. the data to be read /// must be a multiple of [SECTOR_SIZE] bytes pub async fn read_lba(&mut self, start_sector: u32, read: &mut [u8]) -> Result { self.handle_operation(crate::operation::read_lba(start_sector, read)) .await .map(|t| t.into()) } /// Create operation to read an lba from the flash /// /// start_sector based on [SECTOR_SIZE] sectors. the data to be /// written must be a multiple of [SECTOR_SIZE] bytes pub async fn write_lba(&mut self, start_sector: u32, write: &[u8]) -> Result { self.handle_operation(crate::operation::write_lba(start_sector, write)) .await .map(|t| t.into()) } /// Write a specific area while in maskrom mode; typically 0x471 or 0x472 data as retrieved from a /// rockchip boot file pub async fn write_maskrom_area(&mut self, area: u16, data: &[u8]) -> Result<()> { self.handle_operation(crate::operation::write_area(area, data)) .await } /// Reset the device pub async fn reset_device(&mut self, opcode: ResetOpcode) -> Result<()> { self.handle_operation(crate::operation::reset_device(opcode)) .await } } type ReadResult = std::io::Result<(Vec, usize)>; enum IoState { Idle(Option), Read(BoxFuture<'static, (TransportIOInner, ReadResult)>), Write(BoxFuture<'static, (TransportIOInner, std::io::Result)>), Flush(BoxFuture<'static, (TransportIOInner, std::io::Result<()>)>), } struct TransportIOInner { transport: Transport, // Read/Write offset in bytes offset: u64, buffer: Box<[u8; 512]>, size: u64, // Whether or not the buffer is dirty state: BufferState, } /// IO object which implements [AsyncRead], [AsyncWrite] and [AsyncSeek] pub struct TransportIO { // io execution state io_state: IoState, size: u64, } impl TransportIO { /// Create a new IO object around a given transport pub async fn new(mut transport: Transport) -> Result { let info = transport.borrow_mut().flash_info().await?; let size = info.size(); let inner = TransportIOInner { transport, offset: 0, buffer: Box::new([0u8; 512]), size, state: BufferState::Invalid, }; Ok(Self { size, io_state: IoState::Idle(Some(inner)), }) } // Convert into the inner transport // // Panics if the the TransportIO is currently executing I/O operations pub fn into_inner(self) -> Transport { let inner = match self.io_state { IoState::Idle(Some(i)) => i, _ => panic!("TransportIO is currently executing I/O operations"), }; inner.transport } // Size of the flash in bytes pub fn size(&self) -> u64 { self.size } } impl TransportIOInner { const MAXIO_SIZE: u64 = 128 * crate::protocol::SECTOR_SIZE; fn current_sector(&self) -> u64 { self.offset / SECTOR_SIZE } // Want to start an i/o operation with a given maximum length async fn pre_io(&mut self, len: u64) -> std::result::Result { if self.offset >= self.size { return Ok(IOOperation::Eof); } // Offset inside the current sector let sector_offset = self.offset % SECTOR_SIZE; // bytes left from current position to end of current sector let sector_remaining = SECTOR_SIZE - sector_offset; // If the I/O operation is starting at a sector edge and encompasses at least one sector // then direct I/O can be done if sector_offset == 0 && len >= SECTOR_SIZE { // At most read the amount of bytes left let left = self.size - self.offset; let io_len = len.min(left) / SECTOR_SIZE * SECTOR_SIZE; Ok(IOOperation::Direct { len: io_len.min(Self::MAXIO_SIZE) as usize, }) } else { if self.state == BufferState::Invalid { let sector = self.current_sector() as u32; self.transport .borrow_mut() .read_lba(sector, self.buffer.as_mut()) .await .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; self.state = BufferState::Valid; } Ok(IOOperation::Buffered { offset: sector_offset as usize, len: len.min(sector_remaining) as usize, }) } } async fn post_io(&mut self, len: u64) -> std::result::Result { // Offset inside the current sector let sector_offset = self.offset % SECTOR_SIZE; // bytes left from current position to end of current sector let sector_remaining = SECTOR_SIZE - sector_offset; // If going over the sector edge flush the current buffer and invalidate it if len >= sector_remaining { self.flush_buffer().await?; self.state = BufferState::Invalid; } self.offset += len; Ok(len as usize) } async fn flush_buffer(&mut self) -> std::io::Result<()> { if self.state == BufferState::Dirty { let sector = self.current_sector() as u32; self.transport .borrow_mut() .write_lba(sector, self.buffer.as_mut()) .await .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; self.state = BufferState::Valid; } Ok(()) } async fn do_read(&mut self, buf: &mut [u8]) -> std::io::Result { let sector = self.current_sector() as u32; self.transport .borrow_mut() .read_lba(sector, buf) .await .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; Ok(buf.len()) } async fn do_write(&mut self, buf: &[u8]) -> std::io::Result { let sector = self.current_sector() as u32; self.transport .borrow_mut() .write_lba(sector, buf) .await .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; Ok(buf.len()) } } enum IOOperation { Direct { len: usize }, Buffered { offset: usize, len: usize }, Eof, } #[derive(Clone, Copy, Eq, PartialEq)] enum BufferState { // Buffer content doesn't match current offset Invalid, // Buffer content matches offset and device-side Valid, // Buffer content matches offset and has outstanding data Dirty, } impl AsyncWrite for TransportIO { fn poll_write( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &[u8], ) -> std::task::Poll> { let me = self.get_mut(); loop { match me.io_state { IoState::Idle(ref mut inner) => { let mut inner = inner.take().unwrap(); let buf = Vec::from(&buf[0..buf.len().min(TransportIOInner::MAXIO_SIZE as usize)]); me.io_state = IoState::Write(Box::pin(async move { let io = match inner.pre_io(buf.len() as u64).await { Ok(io) => io, Err(e) => return (inner, Err(e)), }; let r = match io { IOOperation::Direct { len } => { match inner.do_write(&buf[..len]).await { Ok(r) => r, Err(e) => return (inner, Err(e)), } } IOOperation::Buffered { offset, len } => { inner.buffer[offset..offset + len].copy_from_slice(&buf[0..len]); inner.state = BufferState::Dirty; len } IOOperation::Eof => { return ( inner, Err(std::io::Error::new( std::io::ErrorKind::Other, "Trying to write past end of area", )), ) } }; let r = inner.post_io(r as u64).await; (inner, r) })) } IoState::Write(ref mut f) => { let (inner, r) = ready!(f.as_mut().poll(cx)); me.io_state = IoState::Idle(Some(inner)); return Poll::Ready(r); } _ => { return Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::BrokenPipe, "Invalid transport state", ))) } } } } fn poll_flush( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { let me = self.get_mut(); loop { match me.io_state { IoState::Idle(ref mut inner) => { let mut inner = inner.take().unwrap(); me.io_state = IoState::Flush(Box::pin(async move { let r = inner.flush_buffer().await; (inner, r) })) } IoState::Flush(ref mut f) => { let (inner, r) = ready!(f.as_mut().poll(cx)); me.io_state = IoState::Idle(Some(inner)); return Poll::Ready(r); } _ => { return Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::BrokenPipe, "Invalid transport state", ))) } } } } fn poll_close( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { self.poll_flush(cx) } } impl AsyncRead for TransportIO { fn poll_read( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &mut [u8], ) -> std::task::Poll> { let me = self.get_mut(); if let IoState::Idle(ref mut inner) = me.io_state { let mut inner = inner.take().unwrap(); let mut buf = vec![0; buf.len()]; me.io_state = IoState::Read(Box::pin(async move { let io = match inner.pre_io(buf.len() as u64).await { Ok(io) => io, Err(e) => return (inner, Err(e)), }; let r = match io { IOOperation::Direct { len } => match inner.do_read(&mut buf[..len]).await { Ok(r) => r, Err(e) => return (inner, Err(e)), }, IOOperation::Buffered { offset, len } => { buf[0..len].copy_from_slice(&inner.buffer[offset..offset + len]); len } IOOperation::Eof => 0, }; let r = inner.post_io(r as u64).await.map(|r| (buf, r)); (inner, r) })) } match me.io_state { IoState::Read(ref mut f) => { let (inner, r) = ready!(f.as_mut().poll(cx)); me.io_state = IoState::Idle(Some(inner)); let r = match r { Ok((read_buf, r)) => { buf[..r].copy_from_slice(&read_buf[..r]); Ok(r) } Err(e) => Err(e), }; Poll::Ready(r) } _ => Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::BrokenPipe, "Invalid transport state", ))), } } } impl AsyncSeek for TransportIO { fn poll_seek( self: std::pin::Pin<&mut Self>, _cx: &mut std::task::Context<'_>, pos: SeekFrom, ) -> std::task::Poll> { let me = self.get_mut(); match me.io_state { IoState::Idle(Some(ref mut inner)) => { inner.offset = match pos { SeekFrom::Start(offset) => inner.size.min(offset), SeekFrom::End(offset) => { if offset > 0 { inner.size } else { let offset = offset.unsigned_abs(); inner.size.saturating_sub(offset) } } SeekFrom::Current(offset) => { if offset > 0 { let offset = offset as u64; inner.offset.saturating_add(offset).min(inner.size) } else { let offset = offset.unsigned_abs(); inner.offset.saturating_sub(offset) } } }; Poll::Ready(Ok(inner.offset)) } _ => Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::BrokenPipe, "Invalid transport state", ))), } } } rockusb-0.2.0/src/operation.rs000064400000000000000000000350531046102023000144360ustar 00000000000000use std::marker::PhantomData; use crate::protocol::{ self, ChipInfo, CommandBlock, CommandStatus, CommandStatusParseError, Direction, FlashId, FlashInfo, ResetOpcode, }; use thiserror::Error; /// Errors for usb operations #[derive(Debug, Clone, Eq, PartialEq, Error)] pub enum UsbOperationError { #[error("Tag mismatch between command and status")] TagMismatch, #[error("Incorrect status Signature receveived: {0:?}")] InvalidStatusSignature([u8; 4]), #[error("Invalid status status: {0}")] InvalidStatusStatus(u8), #[error("Invalid status data length")] InvalidStatusLength, #[error("Failed to parse reply")] ReplyParseFailure, #[error("Device indicated operation failed")] FailedStatus, } impl From for UsbOperationError { fn from(e: CommandStatusParseError) -> Self { match e { CommandStatusParseError::InvalidSignature(s) => { UsbOperationError::InvalidStatusSignature(s) } CommandStatusParseError::InvalidLength(_) => UsbOperationError::InvalidStatusLength, CommandStatusParseError::InvalidStatus(s) => UsbOperationError::InvalidStatusStatus(s), } } } /// Step to take by the transport implementation #[derive(Debug, Eq, PartialEq)] pub enum UsbStep<'a, T> { /// Write USB data using a control transfer WriteControl { request_type: u8, request: u8, value: u16, index: u16, data: &'a [u8], }, /// Write USB data using a bulk transfer WriteBulk { data: &'a [u8] }, /// Read USB data using a bulk transfer ReadBulk { data: &'a mut [u8] }, /// Operation is finished with a given result or failure Finished(Result), } /// steps to take to finish an operation pub trait OperationSteps { /// Next step to execute by a transport fn step(&mut self) -> UsbStep; } enum MaskRomSteps { Writing(crc::Digest<'static, u16>), Dummy, Done, } /// Operations that can be executed when the SoC is in MaskRom mode pub struct MaskRomOperation<'a> { written: usize, block: [u8; 4096], data: &'a [u8], area: u16, steps: MaskRomSteps, } const CRC: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_3740); impl<'a> MaskRomOperation<'a> { fn new(area: u16, data: &'a [u8]) -> Self { Self { written: 0, block: [0; 4096], data, area, steps: MaskRomSteps::Writing(CRC.digest()), } } } impl OperationSteps<()> for MaskRomOperation<'_> { fn step(&mut self) -> UsbStep<()> { let mut current = MaskRomSteps::Done; std::mem::swap(&mut self.steps, &mut current); match current { MaskRomSteps::Writing(mut crc) => { let chunksize = 4096.min(self.data.len() - self.written); self.block[..chunksize] .copy_from_slice(&self.data[self.written..self.written + chunksize]); self.written += chunksize; let chunk = match chunksize { 4096 => { crc.update(&self.block); self.steps = MaskRomSteps::Writing(crc); &self.block[..] } 4095 => { // Add extra 0 to avoid splitting crc over two blocks self.block[4095] = 0; crc.update(&self.block); self.steps = MaskRomSteps::Writing(crc); &self.block[..] } mut end => { crc.update(&self.block[0..end]); let crc = crc.finalize(); self.block[end] = (crc >> 8) as u8; self.block[end + 1] = (crc & 0xff) as u8; end += 2; if end == 4096 { self.steps = MaskRomSteps::Dummy; } else { self.steps = MaskRomSteps::Done; } &self.block[0..end] } }; UsbStep::WriteControl { request_type: 0x40, request: 0xc, value: 0, index: self.area, data: chunk, } } MaskRomSteps::Dummy => { self.steps = MaskRomSteps::Done; self.block[0] = 0; UsbStep::WriteControl { request_type: 0x40, request: 0xc, value: 0, index: self.area, data: &self.block[0..1], } } MaskRomSteps::Done => UsbStep::Finished(Ok(())), } } } /// Write a specific area; typically 0x471 or 0x472 data as retrieved from a rockchip boot file pub fn write_area(area: u16, data: &[u8]) -> MaskRomOperation { MaskRomOperation::new(area, data) } trait FromOperation { fn from_operation(io: &[u8], status: &CommandStatus) -> Result where Self: Sized; } enum Operation { CommandBlock, IO, CommandStatus, Finish, } enum IOBytes<'a> { // Biggest transfer that's not a data read/write Inband([u8; 16]), Read(&'a mut [u8]), Write(&'a [u8]), } /// Operation to execute using the "full" USB protocol pub struct UsbOperation<'a, T> { command: CommandBlock, command_bytes: [u8; 31], data: IOBytes<'a>, next: Operation, _result: PhantomData, } impl<'a, T> UsbOperation<'a, T> { fn new(command: CommandBlock) -> Self { Self { command, command_bytes: [0u8; protocol::COMMAND_BLOCK_BYTES], data: IOBytes::Inband([0u8; 16]), next: Operation::CommandBlock, _result: PhantomData, } } fn new_write(command: CommandBlock, data: &'a [u8]) -> Self { Self { command, command_bytes: [0u8; protocol::COMMAND_BLOCK_BYTES], data: IOBytes::Write(data), next: Operation::CommandBlock, _result: PhantomData, } } fn new_read(command: CommandBlock, data: &'a mut [u8]) -> Self { Self { command, command_bytes: [0u8; protocol::COMMAND_BLOCK_BYTES], data: IOBytes::Read(data), next: Operation::CommandBlock, _result: PhantomData, } } fn io_data_mut(&mut self) -> &mut [u8] { match &mut self.data { IOBytes::Inband(ref mut data) => data, IOBytes::Read(ref mut data) => data, IOBytes::Write(_) => unreachable!(), } } fn io_data(&mut self) -> &[u8] { match self.data { IOBytes::Inband(ref data) => data, IOBytes::Read(ref data) => data, IOBytes::Write(data) => data, } } } impl OperationSteps for UsbOperation<'_, T> where T: FromOperation, T: std::fmt::Debug, { fn step(&mut self) -> UsbStep { let mut next = Operation::CommandBlock; std::mem::swap(&mut self.next, &mut next); match next { Operation::CommandBlock => { let len = self.command.to_bytes(&mut self.command_bytes); // If there is no transfer to be made (e.g. a command) just skip // sending data. if self.command.transfer_length() == 0 { self.next = Operation::CommandStatus; } else { self.next = Operation::IO; } UsbStep::WriteBulk { data: &self.command_bytes[..len], } } Operation::IO => { self.next = Operation::CommandStatus; let len = self.command.transfer_length() as usize; match self.command.direction() { Direction::Out => UsbStep::WriteBulk { data: &self.io_data()[..len], }, Direction::In => UsbStep::ReadBulk { data: &mut self.io_data_mut()[..len], }, } } Operation::CommandStatus => { self.next = Operation::Finish; UsbStep::ReadBulk { data: &mut self.command_bytes[..protocol::COMMAND_STATUS_BYTES], } } Operation::Finish => { let r = CommandStatus::from_bytes(&self.command_bytes) .map_err(UsbOperationError::from) .and_then(|csw| { if csw.status == protocol::Status::FAILED { Err(UsbOperationError::FailedStatus) } else if csw.tag == self.command.tag() { let transfer = self.command.transfer_length() as usize; T::from_operation(&self.io_data()[..transfer], &csw) } else { Err(UsbOperationError::TagMismatch) } }); UsbStep::Finished(r) } } } } impl FromOperation for ChipInfo { fn from_operation(io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { let data = io .try_into() .map_err(|_e| UsbOperationError::ReplyParseFailure)?; Ok(ChipInfo::from_bytes(data)) } } /// Create operation to retrieve SoC Chip information pub fn chip_info() -> UsbOperation<'static, ChipInfo> { UsbOperation::new(CommandBlock::chip_info()) } impl FromOperation for FlashId { fn from_operation(io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { let data = io .try_into() .map_err(|_e| UsbOperationError::ReplyParseFailure)?; Ok(FlashId::from_bytes(data)) } } /// Create operation to retrieve SoC flash identifier pub fn flash_id() -> UsbOperation<'static, FlashId> { UsbOperation::new(CommandBlock::flash_id()) } impl FromOperation for FlashInfo { fn from_operation(io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { let data = io .try_into() .map_err(|_e| UsbOperationError::ReplyParseFailure)?; Ok(FlashInfo::from_bytes(data)) } } /// Create operation to retrieve SoC flash information pub fn flash_info() -> UsbOperation<'static, FlashInfo> { UsbOperation::new(CommandBlock::flash_info()) } impl FromOperation for () { fn from_operation(_io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { Ok(()) } } /// Create operation to reset the SoC pub fn reset_device(opcode: ResetOpcode) -> UsbOperation<'static, ()> { UsbOperation::new(CommandBlock::reset_device(opcode)) } /// Bytes transferred #[derive(Debug, Clone, Copy)] pub struct Transferred(u32); impl FromOperation for Transferred { fn from_operation(io: &[u8], status: &CommandStatus) -> Result where Self: Sized, { let totransfer = io.len() as u32; if status.residue > totransfer { Err(UsbOperationError::ReplyParseFailure) } else { Ok(Transferred(totransfer - status.residue)) } } } impl From for u32 { fn from(t: Transferred) -> Self { t.0 } } /// Create operation to read an lba from the flash /// /// start_sector with [protocol::SECTOR_SIZE] sectors. the data to be read must be a multiple of /// [protocol::SECTOR_SIZE] bytes pub fn read_lba(start_sector: u32, read: &mut [u8]) -> UsbOperation<'_, Transferred> { assert_eq!(read.len() % 512, 0, "Not a multiple of 512: {}", read.len()); UsbOperation::new_read( CommandBlock::read_lba(start_sector, (read.len() / 512) as u16), read, ) } /// Create operation to read an lba from the flash /// /// start_sector with [protocol::SECTOR_SIZE] sectors. the data to be written must be a multiple of /// [protocol::SECTOR_SIZE] bytes pub fn write_lba(start_sector: u32, write: &[u8]) -> UsbOperation<'_, Transferred> { assert_eq!( write.len() % 512, 0, "Not a multiple of 512: {}", write.len() ); UsbOperation::new_write( CommandBlock::write_lba(start_sector, (write.len() / 512) as u16), write, ) } #[cfg(test)] mod test { use super::*; #[test] fn chip_info_operation() { let mut o = chip_info(); let cb = CommandBlock::chip_info(); let mut cb_bytes = [0u8; protocol::COMMAND_BLOCK_BYTES]; cb.to_bytes(&mut cb_bytes); // Write the command block let tag = match o.step() { UsbStep::WriteBulk { data } if data.len() == protocol::COMMAND_BLOCK_BYTES => { [data[4], data[5], data[6], data[7]] } o => panic!("Unexpected step: {:?}", o), }; // Reading the chip info match o.step() { UsbStep::ReadBulk { data } if data.len() as u32 == cb.transfer_length() => { data.fill(0); /* 3588 */ data[0] = 0x38; data[1] = 0x38; data[2] = 0x35; data[3] = 0x33; } o => panic!("Unexpected step: {:?}", o), } // reading status match o.step() { UsbStep::ReadBulk { data } if data.len() == protocol::COMMAND_STATUS_BYTES => { data.fill(0); /* signature */ data[0] = b'U'; data[1] = b'S'; data[2] = b'B'; data[3] = b'S'; /* tag */ data[4] = tag[0]; data[5] = tag[1]; data[6] = tag[2]; data[7] = tag[3]; // status good data[12] = 0; } o => panic!("Unexpected step: {:?}", o), } match o.step() { UsbStep::Finished(Ok(info)) => { assert_eq!( info.inner(), [ 0x38u8, 0x38, 0x35, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ] ) } o => panic!("Unexpected step: {:?}", o), } } } rockusb-0.2.0/src/protocol.rs000064400000000000000000000237211046102023000142760ustar 00000000000000use std::borrow::Cow; use bytes::{Buf, BufMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; pub const SECTOR_SIZE: u64 = 512; #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] pub enum Direction { In = 0x80, Out = 0x0, } #[non_exhaustive] #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] enum CommandCode { TestUnitReady = 0, ReadFlashId = 0x01, TestBadBlock = 0x03, ReadSector = 0x04, WriteSector = 0x05, EraseNormal = 0x06, EraseForce = 0x0B, ReadLBA = 0x14, WriteLBA = 0x15, EraseSystemDisk = 0x16, ReadSDram = 0x17, WriteSDram = 0x18, ExecuteSDram = 0x19, ReadFlashInfo = 0x1A, ReadChipInfo = 0x1B, SetResetFlag = 0x1E, WriteEFuse = 0x1F, ReadEFuse = 0x20, ReadSPIFlash = 0x21, WriteSPIFlash = 0x22, WriteNewEfuse = 0x23, ReadNewEfuse = 0x24, EraseLBA = 0x25, ReadCapability = 0xAA, DeviceReset = 0xFF, } #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] pub enum ResetOpcode { /// Reset Reset = 0, /// Reset to USB mass-storage device class MSC, /// Powers the SOC off PowerOff, /// Reset to maskrom mode Maskrom, /// Disconnect from USB Disconnect, } #[derive(Debug, thiserror::Error, Clone)] pub enum CommandStatusParseError { #[error("Invalid signature: {0:x?}")] InvalidSignature([u8; 4]), #[error("Invalid length: {0}")] InvalidLength(usize), #[error("Invalid status: {0}")] InvalidStatus(u8), } #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] pub enum Status { SUCCESS = 0, FAILED = 1, } pub const COMMAND_STATUS_BYTES: usize = 13; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommandStatus { pub tag: u32, pub residue: u32, pub status: Status, } impl CommandStatus { pub fn to_bytes(&self, bytes: &mut [u8]) -> usize { let mut bytes = &mut bytes[..]; bytes.put_slice(b"USBS"); bytes.put_u32(self.tag); bytes.put_u32_le(self.residue); bytes.put_u8(self.status.into()); COMMAND_STATUS_BYTES } pub fn from_bytes(mut bytes: &[u8]) -> Result { if bytes.len() < COMMAND_STATUS_BYTES { return Err(CommandStatusParseError::InvalidLength(bytes.len())); } let mut magic = [0u8; 4]; bytes.copy_to_slice(&mut magic); if &magic != b"USBS" { return Err(CommandStatusParseError::InvalidSignature(magic)); } let tag = bytes.get_u32(); let residue = bytes.get_u32_le(); let status = Status::try_from(bytes.get_u8()) .map_err(|e| CommandStatusParseError::InvalidStatus(e.number))?; Ok(CommandStatus { tag, residue, status, }) } } #[derive(Debug, Clone, Copy)] pub struct ChipInfo([u8; 16]); impl ChipInfo { pub fn from_bytes(data: [u8; 16]) -> Self { ChipInfo(data) } pub fn inner(&self) -> &[u8] { &self.0 } } #[derive(Debug, Clone, Copy)] pub struct FlashId([u8; 5]); impl FlashId { pub fn from_bytes(data: [u8; 5]) -> Self { FlashId(data) } pub fn to_str(&self) -> Cow<'_, str> { String::from_utf8_lossy(&self.0) } } #[derive(Debug, Clone, Copy)] pub struct FlashInfo([u8; 11]); impl FlashInfo { pub fn from_bytes(data: [u8; 11]) -> Self { FlashInfo(data) } /// flash size in 512 byte sectors pub fn sectors(&self) -> u32 { self.0.as_slice().get_u32_le() } /// flash size in bytes pub fn size(&self) -> u64 { u64::from(self.sectors()) * SECTOR_SIZE } /// Block size in 512 bytes sectors pub fn block_size_sectors(&self) -> u16 { (&self.0[4..]).get_u16_le() } pub fn inner(&self) -> &[u8] { &self.0 } } #[derive(Debug, thiserror::Error, Clone)] pub enum CommandBlockParseError { #[error("Invalid Command block signature: {0:x?}")] InvalidSignature([u8; 4]), #[error("Unknown Command code : {0:x}")] UnknownCommandCode(u8), #[error("Unknown flags: {0:x}")] UnknownFlags(u8), #[error("Invalid command block length: {0}")] InvalidLength(usize), } /// Total size of a CBW command pub const COMMAND_BLOCK_BYTES: usize = 31; /// This structure represents a CBW command block according the USB Mass /// Storage class specification. It carries a SCSI command inside the 'CBWCB' /// bytes that is referred to in the code as 'command data block'. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommandBlock { tag: u32, transfer_length: u32, flags: Direction, lun: u8, // Length of command data block cdb_length: u8, // Command data block fields cd_code: CommandCode, cd_opcode: u8, cd_address: u32, cd_length: u16, } impl CommandBlock { pub fn flash_id() -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 5, flags: Direction::In, lun: 0, cdb_length: 0x6, cd_code: CommandCode::ReadFlashId, cd_opcode: 0, cd_address: 0, cd_length: 0x0, } } pub fn flash_info() -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 11, flags: Direction::In, lun: 0, cdb_length: 0x6, cd_code: CommandCode::ReadFlashInfo, cd_opcode: 0, cd_address: 0, cd_length: 0x0, } } pub fn chip_info() -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 16, flags: Direction::In, lun: 0, cdb_length: 0x6, cd_code: CommandCode::ReadChipInfo, cd_opcode: 0, cd_address: 0, cd_length: 0x0, } } pub fn read_lba(start_sector: u32, sectors: u16) -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: u32::from(sectors) * SECTOR_SIZE as u32, flags: Direction::In, lun: 0, cdb_length: 0xa, cd_code: CommandCode::ReadLBA, cd_opcode: 0, cd_address: start_sector, cd_length: sectors, } } pub fn write_lba(start_sector: u32, sectors: u16) -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: u32::from(sectors) * SECTOR_SIZE as u32, flags: Direction::Out, lun: 0, cdb_length: 0xa, cd_code: CommandCode::WriteLBA, cd_opcode: 0, cd_address: start_sector, cd_length: sectors, } } pub fn reset_device(opcode: ResetOpcode) -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 0, flags: Direction::Out, lun: 0, cdb_length: 0x6, cd_code: CommandCode::DeviceReset, cd_opcode: opcode.into(), cd_address: 0, cd_length: 0x0, } } pub fn tag(&self) -> u32 { self.tag } pub fn direction(&self) -> Direction { self.flags } pub fn transfer_length(&self) -> u32 { self.transfer_length } pub fn to_bytes(&self, mut bytes: &mut [u8]) -> usize { bytes.put_slice(b"USBC"); bytes.put_u32(self.tag); bytes.put_u32_le(self.transfer_length); bytes.put_u8(self.flags.into()); bytes.put_u8(self.lun); bytes.put_u8(self.cdb_length); bytes.put_u8(self.cd_code.into()); bytes.put_u8(self.cd_opcode); bytes.put_u32(self.cd_address); bytes.put_u8(0); bytes.put_u16(self.cd_length); COMMAND_BLOCK_BYTES } pub fn from_bytes(mut bytes: &[u8]) -> Result { if bytes.len() < COMMAND_BLOCK_BYTES { return Err(CommandBlockParseError::InvalidLength(bytes.len())); } let mut magic = [0u8; 4]; bytes.copy_to_slice(&mut magic); if &magic != b"USBC" { return Err(CommandBlockParseError::InvalidSignature(magic)); } let tag = bytes.get_u32(); let transfer_length = bytes.get_u32_le(); let flags = Direction::try_from(bytes.get_u8()) .map_err(|e| CommandBlockParseError::UnknownFlags(e.number))?; let lun = bytes.get_u8(); let cdb_length = bytes.get_u8(); let cd_code = CommandCode::try_from(bytes.get_u8()) .map_err(|e| CommandBlockParseError::UnknownCommandCode(e.number))?; let cd_opcode = bytes.get_u8(); let cd_address = bytes.get_u32(); bytes.advance(1); let cd_length = bytes.get_u16(); Ok(CommandBlock { tag, transfer_length, flags, lun, cdb_length, cd_code, cd_opcode, cd_address, cd_length, }) } } #[cfg(test)] mod test { use super::*; #[test] fn csw() { let c = CommandStatus { tag: 0x11223344, residue: 0x55667788, status: Status::SUCCESS, }; let mut b = [0u8; 13]; c.to_bytes(&mut b); let c2 = CommandStatus::from_bytes(&b).unwrap(); assert_eq!(c, c2); } #[test] fn cbw() { let c = CommandBlock { tag: 0xdead, transfer_length: 0x11223344, flags: Direction::Out, lun: 0x66, cdb_length: 0x77, cd_code: CommandCode::EraseForce, cd_opcode: 0x10, cd_address: 0x11223344, cd_length: 0x5566, }; let mut b = [0u8; 31]; c.to_bytes(&mut b); let c2 = CommandBlock::from_bytes(&b).unwrap(); assert_eq!(c, c2); } }