commit ee007da5d4f11cf4680297839aee368f6f55417c Author: Keenan Tims Date: Fri Nov 3 17:47:18 2023 -0700 Initial commit, some working features Signed-off-by: Keenan Tims diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b8da5cb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,823 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[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.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-num" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "clap_builder" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hidapi" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723777263b0dcc5730aec947496bd8c3940ba63c15f5633b288cc615f4f6af79" +dependencies = [ + "cc", + "libc", + "pkg-config", + "winapi", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ihex" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "365a784774bb381e8c19edb91190a90d7f2625e057b55de2bc0f6b57bc779ff2" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lpc55prog" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "clap", + "clap-num", + "console", + "env_logger", + "hidapi", + "ihex", + "indicatif", + "log", + "nom", + "num_enum", + "object", + "strum", + "strum_macros", + "uuid", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "crc32fast", + "hashbrown", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "portable-atomic" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[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-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[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_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092cd76b01a033a9965b9097da258689d9e17c69ded5dcf41bca001dd20ebc6d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13a20a7c6a90e2034bcc65495799da92efcec6a8dd4f3fcb6f7a48988637ead" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d66c9fd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "lpc55prog" +version = "0.1.0" +edition = "2021" +description = "A flash programmer for NXP LP55xx microcontrollers' ISP boot rom" +authors = ["Keenan Tims "] + +[profile.release] +strip="debuginfo" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +bitflags = "1.3.2" +byteorder = "1.5.0" +clap = { version = "4.4.6", features = ["derive"] } +clap-num = "1.0.2" +console = "0.15.7" +env_logger = "0.10.0" +hidapi = "2.1.3" +ihex = "3.0.0" +indicatif = "0.17.7" +log = "0.4.20" +nom = "7.1.3" +num_enum = "0.5.11" +object = { version = "0.32.1", default-features = false, features = ["elf", "write_std", "read_core"] } +strum = "0.24.1" +strum_macros = "0.24.3" +uuid = "1.4.1" diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..5504945 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,194 @@ +extern crate object; + +use clap::ValueEnum; +use log::debug; +use log::warn; +use object::elf; +use object::Endianness; + +use ihex; +use object::write::elf::{FileHeader, SectionHeader}; + +// Perhaps should refactor so we don't have UI ValueEnum here? +#[derive(Debug, Clone, ValueEnum)] +pub enum MemoryFileType { + Elf, + Ihex, + Bin, +} + +const M33_FLAGS: u32 = elf::EF_ARM_EABI_VER5 | elf::EF_ARM_ABI_FLOAT_HARD; // processor has hard float, even if code may not use it + +struct ElfWriter; +struct IhexWriter; +struct BinWriter; + +/// Build a new object to manage writing type T +impl MemoryFileType { + pub fn mem_writer(&self) -> Box { + match *self { + MemoryFileType::Elf => Box::new(ElfWriter), + MemoryFileType::Ihex => Box::new(IhexWriter), + MemoryFileType::Bin => Box::new(BinWriter), + } + } + pub fn mem_writer_from_filename(filename: &str) -> Box { + let filename = filename.to_lowercase(); + let (_, extension) = filename.rsplit_once('.').unwrap_or(("", "")); + match extension { + "elf" => Box::new(ElfWriter), + "hex" | "ihex" => Box::new(IhexWriter), + "bin" | "raw" => Box::new(BinWriter), + _ => { + warn!("Can't determine an appropriate output type based on filename {}, writing as raw binary", filename); + Box::new(BinWriter) + } + } + } +} + +/// An object that can write a block of memory to a file +pub trait MemoryWriter { + fn write_mem( + &mut self, + output: &mut dyn std::io::Write, + addr: u32, + data: &[u8], + ) -> Result<(), Box>; +} + +// object is not well documented, see how this API must be used here: +// https://github.com/gimli-rs/object/blob/master/src/write/elf/object.rs#L217 +impl MemoryWriter for ElfWriter { + fn write_mem( + &mut self, + output: &mut dyn std::io::Write, + addr: u32, + data: &[u8], + ) -> Result<(), Box> { + debug!("Writing as ELF with base address 0x{:x}", addr); + let mut buf = Vec::new(); + + let mut writer = object::write::elf::Writer::new(Endianness::Little, false, &mut buf); + + // Calculating offsets + + writer.reserve_file_header(); + + let _text_id = writer.reserve_section_index(); + let data_ofs = writer.reserve(data.len(), 4); + let text_name = writer.add_section_name(".text".as_bytes()); + + writer.reserve_null_symbol_index(); + let symtab_num_local = writer.symbol_count(); + writer.reserve_symtab_section_index(); + writer.reserve_symtab(); + writer.reserve_symtab_shndx(); + writer.reserve_strtab_section_index(); + writer.reserve_strtab(); + writer.reserve_shstrtab_section_index(); + writer.reserve_shstrtab(); + writer.reserve_section_headers(); + + // Writing + + writer.write_file_header(&FileHeader { + abi_version: elf::EV_CURRENT, + os_abi: elf::ELFOSABI_NONE, + e_type: elf::ET_EXEC, + e_machine: elf::EM_ARM, + e_entry: addr.into(), + e_flags: M33_FLAGS, + // e_ident: elf::Ident, + // e_type: elf::ET_EXEC, + // e_entry: addr, + // e_flags: elf::EF_ARM_ABI_FLOAT_HARD | elf::EF_ARM_EABI_VER5, + // e_machine: elf::EM_ARM, + // e_version: elf::EV_CURRENT, + })?; + writer.write_align(4); + writer.write(&data); + writer.write_null_symbol(); + writer.write_symtab_shndx(); + writer.write_strtab(); + + writer.write_shstrtab(); + writer.write_null_section_header(); + + writer.write_section_header(&SectionHeader { + name: Some(text_name), + sh_type: elf::SHT_PROGBITS, + sh_flags: (elf::SHF_ALLOC | elf::SHF_EXECINSTR).into(), + sh_addr: addr.into(), + sh_offset: data_ofs as u64, + sh_size: data.len() as u64, + sh_link: elf::SHN_UNDEF.into(), + sh_info: 0, + sh_addralign: 4, + sh_entsize: 0, + }); + + writer.write_symtab_section_header(symtab_num_local); + writer.write_symtab_shndx_section_header(); + writer.write_strtab_section_header(); + writer.write_shstrtab_section_header(); + + output.write_all(&buf)?; + + Ok(()) + // let triple = target_lexicon::Triple { + // architecture: Architecture::Arm(target_lexicon::ArmArchitecture::Armv8mBase), + // vendor: Vendor::Unknown, + // operating_system: OperatingSystem::None_, + // environment: Environment::Eabihf, + // binary_format: BinaryFormat::Elf, + // }; + } +} + +impl MemoryWriter for IhexWriter { + fn write_mem( + &mut self, + output: &mut dyn std::io::Write, + addr: u32, + data: &[u8], + ) -> Result<(), Box> { + let mut records = Vec::new(); + + const BLOCK_SIZE: u32 = u16::MAX as u32 + 1; + const CHUNK_SIZE: u32 = 16; + + for (block, data) in data.chunks(BLOCK_SIZE as usize).enumerate() { + records.push(ihex::Record::ExtendedLinearAddress( + (((addr + block as u32 * BLOCK_SIZE) & 0xffff0000) >> 16) + .try_into() + .unwrap(), + )); + records.extend( + data.chunks(CHUNK_SIZE as usize) + .enumerate() + .map(|(offset, chunk)| ihex::Record::Data { + offset: ((offset as u32 * CHUNK_SIZE) & 0xffff).try_into().unwrap(), + value: chunk.to_vec(), + }), + ); + } + + records.push(ihex::Record::EndOfFile {}); + + let obj = ihex::create_object_file_representation(&records)?; + + Ok(output.write_all(obj.as_bytes())?) + } +} + +impl MemoryWriter for BinWriter { + fn write_mem( + &mut self, + output: &mut dyn std::io::Write, + _addr: u32, // raw bin writer doesn't care about address + data: &[u8], + ) -> Result<(), Box> { + Ok(output.write_all(data)?) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b92f8cd --- /dev/null +++ b/src/main.rs @@ -0,0 +1,417 @@ +use core::fmt; +use std::{ + ffi::CString, + fs::File, + fs::{self, OpenOptions}, + io::BufWriter, + os::unix::prelude::OsStrExt, + path::PathBuf, +}; + +use anyhow::{anyhow, Context, Error}; +use clap_num::maybe_hex; +use env_logger::Env; +use log::{debug, error, info, log_enabled, warn, Level}; + +use hidapi::{DeviceInfo, HidApi}; +use object::Object; + +use crate::file::{MemoryFileType, MemoryWriter}; +use clap::{ + builder::{TypedValueParser, ValueParserFactory}, + error::ErrorKind, + ArgAction, Args, Parser, Subcommand, ValueEnum, +}; +use console::style; +use indicatif::{HumanBytes, ProgressBar, ProgressStyle}; + +pub mod file; +pub mod packet; +pub mod protocol; + +#[derive(Debug)] +struct NoDevicesError; +impl std::error::Error for NoDevicesError { + fn cause(&self) -> Option<&dyn std::error::Error> { + None + } +} +impl fmt::Display for NoDevicesError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("No devices found") + } +} + +#[derive(Clone, Debug)] +struct VidPid { + vid: u16, + pid: u16, +} + +#[derive(Clone)] +struct VidPidParser; + +impl TypedValueParser for VidPidParser { + type Value = VidPid; + fn parse_ref( + &self, + cmd: &clap::builder::Command, + _: Option<&clap::builder::Arg>, + value: &std::ffi::OsStr, + ) -> Result { + let value = value + .to_str() + .ok_or(clap::Error::new(ErrorKind::InvalidUtf8).with_cmd(cmd))?; + if let Some((raw_vid, raw_pid)) = value.split_once(':') { + if let (Ok(vid), Ok(pid)) = ( + u16::from_str_radix(raw_vid, 16), + u16::from_str_radix(raw_pid, 16), + ) { + return Ok(VidPid { vid, pid }); + } + } + Err(clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd)) + } +} + +impl ValueParserFactory for VidPid { + type Parser = VidPidParser; + fn value_parser() -> Self::Parser { + VidPidParser + } +} + +#[derive(Parser, Debug)] +#[command(author, version, about)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Args, Debug)] +struct UsbDeviceSpecifier { + /// USB VID:PID as colon-separated pair + #[arg(short = 'i', long, default_value = "1fc9:0021", group = "device")] + usb_id: VidPid, + + /// hidraw device node path + #[arg(short, long, group = "device", conflicts_with = "usb_id")] + dev_path: Option, +} + +#[derive(ValueEnum, Debug, Clone)] +enum ObjectFileType { + Elf, + Ihex, + Bin, + Auto, +} + +impl ObjectFileType { + fn mem_writer(&self, filename: &str) -> Box { + match self { + Self::Auto => MemoryFileType::mem_writer_from_filename(filename), + Self::Bin => MemoryFileType::Bin.mem_writer(), + Self::Ihex => MemoryFileType::Ihex.mem_writer(), + Self::Elf => MemoryFileType::Elf.mem_writer(), + } + } +} + +#[derive(Args, Debug)] +struct ReadArgs { + file: PathBuf, + #[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)] + /// Type of object file to generate + filetype: ObjectFileType, + + /// USB device to act on + #[command(flatten)] + devspec: UsbDeviceSpecifier, + /// Base memory address on microcontroller (defaults to start of flash) + #[arg(short = 'a', long = "base-address", value_parser=maybe_hex::)] + addr: Option, + /// Size to read in bytes (defaults to size of flash) + #[arg(short, long, value_parser=maybe_hex::)] + size: Option, +} + +#[derive(Args, Debug)] +struct WriteArgs { + file: PathBuf, + + /// USB device to act on + #[command(flatten)] + devspec: UsbDeviceSpecifier, + /// Base memory address on microcontroller (defaults to start of flash) + #[arg(short = 'a', long = "write-address", value_parser=maybe_hex::)] + addr: Option, + /// Base memory address in (defaults to the start of the first TEXT block in the file) + #[arg(long="read-address", value_parser=maybe_hex::)] + read_addr: Option, + /// Size to write in bytes (defaults to size of flash) + #[arg(short, long, value_parser=maybe_hex::)] + size: Option, + /// Don't reset the microcontroller after writing + #[arg(short, long, action=ArgAction::SetFalse)] + no_reset: bool, + #[arg(short, long)] + /// Erase all flash before writing + erase: bool, +} + +#[derive(Args, Debug)] +struct ListArgs { + /// USB device to act on + #[command(flatten)] + devspec: UsbDeviceSpecifier, +} + +#[derive(Args, Debug)] +struct EraseArgs { + /// USB device to act on + #[command(flatten)] + devspec: UsbDeviceSpecifier, + + /// Address to start erasing at + #[arg(short = 'a', long="base-address", value_parser=maybe_hex::, requires="size")] + addr: Option, + #[arg(short, long, value_parser=maybe_hex::)] + size: Option, +} + +#[derive(Args, Debug)] +struct ResetArgs { + #[command(flatten)] + devspec: UsbDeviceSpecifier, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Read microcontroller memory to a file + Read(ReadArgs), + /// Write a file to microcontroller memory + Write(WriteArgs), + /// Erase the microcontroller's flash + Erase(EraseArgs), + /// List available ISP devices + List(ListArgs), + /// Reset the microcontroller + Reset(ResetArgs), +} + +fn read_write_style() -> ProgressStyle { + ProgressStyle::with_template( + "[{elapsed}] {msg:4}{spinner} {prefix:20} {bar:40.cyan/blue} {bytes:>7}/{total_bytes:7}", + ) + .unwrap() + .progress_chars("##-") +} + +fn write_file_to_flash(args: &WriteArgs) -> Result<(), Error> { + let api = HidApi::new()?; + let isp = connect_device(&api, &args.devspec)?; + + let infile = fs::read(args.file.as_path()) + .with_context(|| format!("Opening {} for reading", args.file.display()))?; + let in_obj = object::File::parse(&*infile) + .with_context(|| format!("Parsing {} as a binary object", args.file.display()))?; + + println!("{:?}", in_obj.sections()); + + let flash_start = args.addr.unwrap_or(isp.GetFlashStartAddress()?); + let flash_size = args.size.unwrap_or(isp.GetFlashSizeInBytes()?); + + if !args.no_reset { + isp.reset()?; + } + + Ok(()) +} + +fn read_flash_to_file(args: &ReadArgs) -> Result<(), Error> { + let api = hidapi::HidApi::new()?; // is this free, or should it be passed? + let isp = connect_device(&api, &args.devspec)?; + + let flash_start = match args.addr { + None => isp.GetFlashStartAddress()?, + Some(addr) => addr, + }; + + let flash_size = match args.size { + None => isp.GetFlashSizeInBytes()?, + Some(size) => size, + }; + + let mut buf = Vec::with_capacity(flash_size as usize); + + let read_pb = ProgressBar::new(flash_size.into()) + .with_style(read_write_style()) + .with_prefix("Reading flash"); + // .with_finish(indicatif::ProgressFinish::WithMessage("Done".into())); + + // Allow failure, we might still have some data to write + match isp.ReadMemory( + &mut read_pb.wrap_write(&mut buf), + flash_start, + flash_size, + None, + ) { + Ok(_) => read_pb.finish_with_message(style("DONE").bold().green().to_string()), + Err(e) => { + read_pb.abandon_with_message(style("FAIL").bold().red().to_string()); + error!( + "reading from flash: {}. Continuing with incomplete data.", + e + ); + } + }; + + let mut writer = args.filetype.mem_writer(args.file.to_str().unwrap_or("")); + + let write_pb: ProgressBar = ProgressBar::new(buf.len() as u64) + .with_style(read_write_style()) + .with_prefix("Writing object file") + .with_finish(indicatif::ProgressFinish::WithMessage( + style("DONE").bold().green().to_string().into(), + )); + + let output = BufWriter::new(File::create(args.file.as_path())?); + writer + .write_mem(&mut write_pb.wrap_write(output), flash_start, &buf) + .or_else(|e| Err(anyhow!("Error writing to object file: {}", e)))?; + write_pb.finish_using_style(); + + Ok(()) +} + +fn erase_flash(args: &EraseArgs) -> Result<(), Error> { + let api = hidapi::HidApi::new()?; + let isp = connect_device(&api, &args.devspec)?; + + let erase_pb = ProgressBar::new(1) + .with_style(read_write_style()) + .with_prefix("Erasing flash"); + if args.addr.is_none() && args.size.is_none() { + isp.flash_erase_all(None)?; + erase_pb.finish(); + } else { + let flash_start = isp.GetFlashStartAddress()?; + let flash_size = isp.GetFlashSizeInBytes()?; + let start_addr = args.addr.unwrap_or(flash_start); + let size = args.addr.unwrap_or(flash_size); + if start_addr + size > flash_start + flash_size { + warn!("Looks like you're attempting to erase beyond the end of flash (0x{:x}-0x{:x})! This will probably fail.", start_addr, start_addr+size); + } + isp.flash_erase_region(None, start_addr, size)?; + erase_pb.finish(); + } + Ok(()) +} + +fn reset(args: &ResetArgs) -> Result<(), Error> { + let api = hidapi::HidApi::new()?; + let isp = connect_device(&api, &args.devspec)?; + + isp.reset() +} + +fn get_matching_devices<'a>( + api: &'a hidapi::HidApi, + devspec: &UsbDeviceSpecifier, +) -> Result, Error> { + match &devspec.dev_path { + Some(path) => { + let path_ref: &std::ffi::OsStr = path.as_ref(); + let path_str = CString::new(path_ref.as_bytes())?; + let v: Vec<_> = api + .device_list() + .filter(|dev| dev.path().eq(&path_str)) + .collect(); + Ok(v) + } + None => { + let v: Vec<_> = api + .device_list() + .filter(|dev| { + dev.vendor_id() == devspec.usb_id.vid && dev.product_id() == devspec.usb_id.pid + }) + .collect(); + Ok(v) + } + } +} + +fn connect_device( + api: &hidapi::HidApi, + devspec: &UsbDeviceSpecifier, +) -> Result { + let matches = get_matching_devices(api, devspec)?; + if matches.len() < 1 { + return Err(NoDevicesError {}.into()); + } else if matches.len() > 1 { + warn!("More than one device matched specifier, connecting to first match."); + } + debug!( + "Connecting to device {} {} at `{}`", + matches[0].manufacturer_string().unwrap_or(""), + matches[0].product_string().unwrap_or("device"), + matches[0].path().to_str()? + ); + let dev = matches[0].open_device(api)?; + let proto = protocol::UsbIsp::new(dev); + debug!("Connected to device {}", proto.GetUniqueDeviceId()?); + Ok(proto) +} + +fn print_device(api: &HidApi, dev: &DeviceInfo) -> Result<(), Error> { + let hid_dev = dev.open_device(&api)?; + let isp = protocol::UsbIsp::new(hid_dev); + println!( + "{:<14} [{:04x}:{:04x}] {:<13} {:10} {}", + style(dev.path().to_str()?).magenta(), + style(dev.vendor_id()).cyan(), + style(dev.product_id()).cyan(), + style(isp.GetSystemDeviceId()?).yellow(), + style(HumanBytes(isp.GetFlashSizeInBytes()? as u64)).green(), + style(isp.GetUniqueDeviceId()?).white() + ); + Ok(()) +} + +fn print_all_devices(devspec: &UsbDeviceSpecifier) -> Result<(), Error> { + let api = hidapi::HidApi::new()?; + + let devices: Vec<_> = get_matching_devices(&api, &devspec)?; + if devices.len() > 0 { + println!( + "{:^14}|{:^11}|{:^13}|{:^10}|{:^36}", + "Path", "VID:PID", "Chip", "Flash", "UUID" + ); + println!( + "{:-<14}/{:-<11}/{:-<13}/{:-<10}/{:-<36}", + "", "", "", "", "" + ); + + for dev in devices { + print_device(&api, dev)?; + } + } else { + println!("No devices found :("); + } + + Ok(()) +} + +fn main() -> Result<(), Error> { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + let cli = Cli::parse(); + + match cli.command { + Commands::Read(args) => read_flash_to_file(&args), + Commands::Write(args) => write_file_to_flash(&args), + Commands::Erase(args) => erase_flash(&args), + Commands::Reset(args) => reset(&args), + Commands::List(args) => print_all_devices(&args.devspec), + } +} diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..c6b3869 --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,352 @@ +use nom::bytes::complete::take; +use nom::error::{Error, ErrorKind}; +use nom::multi::many0; +use nom::number::complete; +use nom::IResult; +use nom::{Err, InputTake}; + +use nom::sequence::Tuple; +use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; +use std::convert::TryFrom; +use std::fmt::Display; + +use bitflags::bitflags; +use byteorder::{LittleEndian, WriteBytesExt}; + +use strum_macros::{EnumIter, IntoStaticStr}; + +#[repr(u8)] +#[derive(Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug)] +pub enum ReportId { + CommandOut = 1, + DataOut = 2, + CommandIn = 3, + DataIn = 4, +} + +#[repr(u8)] +#[derive(Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug, EnumIter)] +pub enum CommandTag { + FlashEraseAll = 0x01, + FlashEraseRegion = 0x02, + ReadMemory = 0x03, + WriteMemory = 0x04, + FillMemory = 0x05, + GetProperty = 0x07, + ReceiveSbFile = 0x08, + Execute = 0x09, + Call = 0x0a, + Reset = 0x0b, + SetProperty = 0x0c, + ConfigureMemory = 0x11, + KeyProvision = 0x15, +} + +#[repr(u8)] +#[derive(Copy, Clone, TryFromPrimitive, IntoPrimitive, Debug)] +pub enum ResponseTag { + GenericResponse = 0xa0, + ReadMemoryResponse = 0xa3, + GetPropertyResponse = 0xa7, + FlashReadOnceResponse = 0xaf, + KeyProvisionResponse = 0xb5, +} +#[repr(u32)] +#[derive(Copy, Clone, PartialEq, IntoStaticStr, FromPrimitive, IntoPrimitive, Debug)] +pub enum StatusCode { + Success = 0, + StatusFlashAlignmentError = 101, + StatusFlashEccError = 116, + StatusMemoryRangeInvalid = 10200, + StatusMemoryBlankPageReadDisallowed = 10211, + #[num_enum(catch_all)] + Unknown(u32), +} + +impl Display for StatusCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StatusCode::Success => f.write_str("Success"), + StatusCode::Unknown(code) => f.write_fmt(format_args!("Error({})", code)), + code => { + let name: &'static str = code.into(); + f.write_fmt(format_args!("{}({})", name, u32::from(code.to_owned()))) + } + } + } +} + +#[derive(Debug)] +pub enum ResponseParameters { + GenericResponse(GenericResponseParams), + ReadMemoryResponse(ReadMemoryResponseParams), + GetPropertyResponse(GetPropertyResponseParams), + FlashReadOnceResponse(FlashReadOnceResponseParams), + KeyProvisionResponse(KeyProvisionResponseParams), +} +#[derive(Debug)] +pub struct GenericResponseParams { + pub status: StatusCode, + pub command: CommandTag, +} +#[derive(Debug)] +pub struct ReadMemoryResponseParams { + pub status: StatusCode, + pub data_bytes: u32, +} +#[derive(Debug)] +pub struct GetPropertyResponseParams { + pub status: StatusCode, + pub properties: Vec, +} +#[derive(Debug)] +pub struct FlashReadOnceResponseParams { + status: StatusCode, + byte_count: u32, + read_data: [u8; 20], +} +#[derive(Debug)] +pub struct KeyProvisionResponseParams { + status: StatusCode, + data_bytes: u32, +} + +bitflags! { + pub struct CommandFlags: u8 { + const DATA_FOLLOWS = 0b00000001; + } +} + +impl Default for CommandFlags { + fn default() -> Self { + CommandFlags { bits: 0 } + } +} + +#[derive(Debug, Clone)] +pub struct CommandPacket { + pub tag: CommandTag, + pub flags: CommandFlags, + pub reserved: u8, + pub param_count: u8, + pub params: Vec, +} + +#[derive(Debug)] +pub struct ResponsePacket { + pub tag: ResponseTag, + pub flags: CommandFlags, + pub reserved: u8, + pub param_count: u8, + pub params: ResponseParameters, +} + +#[derive(Debug)] +pub struct DataPacket { + pub bytes: Vec, +} + +pub struct UsbPacket { + report_id: ReportId, + _padding: u8, + packet_length: u16, + packet: Packet, +} + +#[derive(Debug)] +//TODO: Refactor this to use traits and borrows +pub enum Packet { + CommandPacket(CommandPacket), + ResponsePacket(ResponsePacket), + DataPacket(DataPacket), +} + +fn status_code(input: &[u8]) -> IResult<&[u8], StatusCode> { + let (input, code) = complete::le_u32(input)?; + Ok((input, StatusCode::from(code))) +} + +fn response_tag(input: &[u8]) -> IResult<&[u8], ResponseTag> { + let res = take(1u8)(input); + match res { + Ok(ir) => match ResponseTag::try_from(ir.1[0]) { + Ok(val) => Ok((ir.0, val)), + Err(e) => { + println!("{:?}", e); + unimplemented!() + } + }, + Err(e) => Err(e), + } +} + +fn command_tag(input: &[u8]) -> IResult<&[u8], CommandTag> { + let res = take(1u8)(input); + match res { + Ok(ir) => match CommandTag::try_from(ir.1[0]) { + Ok(val) => Ok((ir.0, val)), + Err(e) => { + println!("{:?}", e); + unimplemented!() + } + }, + Err(e) => Err(e), + } +} + +fn generic_response_params(input: &[u8]) -> IResult<&[u8], ResponseParameters> { + let (input, (status, command)) = (status_code, complete::u8).parse(input)?; + Ok(( + input, + ResponseParameters::GenericResponse(GenericResponseParams { + status, + command: CommandTag::try_from(command).unwrap(), + }), + )) +} + +fn get_property_response_params(input: &[u8]) -> IResult<&[u8], ResponseParameters> { + let (input, (status, properties)) = (status_code, many0(complete::le_u32)).parse(input)?; + Ok(( + input, + ResponseParameters::GetPropertyResponse(GetPropertyResponseParams { status, properties }), + )) +} + +fn get_read_memory_response_params(input: &[u8]) -> IResult<&[u8], ResponseParameters> { + let (input, (status, properties)) = (status_code, many0(complete::le_u32)).parse(input)?; + assert!(properties.len() == 1); + Ok(( + input, + ResponseParameters::ReadMemoryResponse(ReadMemoryResponseParams { + status, + data_bytes: properties[0], + }), + )) +} + +fn report_id(input: &[u8]) -> IResult<&[u8], ReportId> { + let (input, id) = complete::u8(input)?; + match ReportId::try_from(id) { + Ok(val) => Ok((input, val)), + Err(_) => Err(Err::Error(Error::new(input, ErrorKind::Fail))), + } +} + +pub fn usb_packet(input: &[u8]) -> IResult<&[u8], Packet> { + let (input, (reportid, _, packet_length)) = + (report_id, complete::u8, complete::le_u16).parse(input)?; + let (input, buf) = take(packet_length)(input)?; + + let pack = match reportid { + ReportId::CommandOut => command_packet(buf)?, + ReportId::DataOut => unimplemented!(), + ReportId::CommandIn => response_packet(buf)?, + ReportId::DataIn => data_packet(buf, packet_length)?, + }; + + Ok((input, pack.1)) +} + +pub fn response_packet(input: &[u8]) -> IResult<&[u8], Packet> { + let (input, (tag, flags, reserved, param_count)) = + (response_tag, complete::u8, complete::u8, complete::u8).parse(input)?; + let (input, params) = match tag { + ResponseTag::GenericResponse => generic_response_params(input)?, + ResponseTag::GetPropertyResponse => get_property_response_params(input)?, + ResponseTag::ReadMemoryResponse => get_read_memory_response_params(input)?, + ResponseTag::FlashReadOnceResponse => unimplemented!(), + ResponseTag::KeyProvisionResponse => unimplemented!(), + }; + Ok(( + input, + Packet::ResponsePacket(ResponsePacket { + tag, + flags: CommandFlags::from_bits_truncate(flags), + reserved, + param_count, + params, + }), + )) +} + +pub fn data_packet(input: &[u8], packet_length: u16) -> IResult<&[u8], Packet> { + let data = input.take(packet_length as usize); + Ok(( + input, + Packet::DataPacket(DataPacket { + bytes: data.to_vec(), + }), + )) +} + +pub fn command_packet(input: &[u8]) -> IResult<&[u8], Packet> { + let (input, (tag, flags, reserved, param_count)) = + (command_tag, complete::u8, complete::u8, complete::u8).parse(input)?; + Ok(( + input, + Packet::CommandPacket(CommandPacket { + tag, + flags: CommandFlags::from_bits_truncate(flags), + reserved, + param_count, + params: Vec::new(), + }), + )) +} + +impl Packet { + pub fn write(&self, w: &mut W) -> Result<(), std::io::Error> { + //TODO: Refactor to a trait + match self { + Packet::CommandPacket(p) => { + w.write(&[ + p.tag as u8, + p.flags.bits() as u8, + p.reserved as u8, + p.param_count as u8, + ])?; + for param in &p.params { + w.write_u32::(*param)?; + } + } + Packet::DataPacket(p) => { + w.write(p.bytes.as_slice())?; + } + Packet::ResponsePacket(p) => { + unimplemented!("Serializing response packets is not implemented"); + } + } + Ok(()) + } + pub fn length(&self) -> u16 { + let header_len = 4u16; + match self { + Packet::CommandPacket(p) => header_len + (p.param_count as u16) * 4, + Packet::ResponsePacket(p) => header_len + (p.param_count as u16) * 4, + Packet::DataPacket(p) => p.bytes.len() as u16, + } + } +} + +impl UsbPacket { + pub fn new(report_id: ReportId, packet: Packet) -> UsbPacket { + UsbPacket { + report_id, + _padding: 0, + packet_length: packet.length(), + packet, + } + } + + pub fn length(&self) -> usize { + self.packet_length as usize + 4 + } + + pub fn write(&self, w: &mut W) -> Result<(), std::io::Error> { + w.write(&[self.report_id as u8, 0])?; + w.write_u16::(self.packet_length)?; + self.packet.write(w)?; + Ok(()) + } +} diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..c1bd09b --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,635 @@ +#![allow(non_snake_case)] + +use anyhow::{anyhow, Error}; +use byteorder::{ByteOrder, LittleEndian, NativeEndian}; +use hidapi::{HidApi, HidDevice}; +use log::debug; +use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive}; +use std::fmt::{Debug, Display}; +use std::io::{Cursor, Write}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; +use uuid::Uuid; + +use crate::packet::{ + self, GenericResponseParams, GetPropertyResponseParams, Packet, ResponsePacket, + ResponseParameters, StatusCode, UsbPacket, +}; +use crate::packet::{CommandFlags, CommandPacket, CommandTag}; + +#[repr(u32)] +#[derive(IntoPrimitive, EnumIter, Debug, PartialEq, Clone)] +pub enum MemoryId { + Internal = 0x000, + XoInternal = 0x010, + QspiNor = 0x001, + SemcNor = 0x008, + FlexSpiNor = 0x009, + SemcRawNand = 0x100, + FlexSpiNand = 0x101, + SpiNorEeprom = 0x110, + Sd = 0x120, + Emmc = 0x121, +} + +#[repr(u8)] +#[derive(IntoPrimitive, EnumIter, Debug, PartialEq, Clone)] +pub enum Property { + CurrentVersion = 1, + AvailablePeripherals = 2, + FlashStartAddress = 3, + FlashSizeInBytes = 4, + Availablecommands = 7, + CheckStatus = 8, + MaxPacketSize = 0x0b, + ReservedRegions = 0x0c, + SystemDeviceId = 0x10, + LifeCycleState = 0x11, + UniqueDeviceId = 0x12, + ExternalMemoryAttributes = 0x19, + IrqNotifierPin = 0x1c, +} + +#[repr(u32)] +#[derive(IntoPrimitive, FromPrimitive, EnumIter, Debug, PartialEq, Clone)] +pub enum LifeCycleState { + Development = 0x5aa55aa5, + Deployment = 0xc33cc33c, + #[num_enum(catch_all)] + Unknown(u32), +} +#[derive(Debug)] +pub struct BootLoaderVersion { + pub name: char, + pub major: u8, + pub minor: u8, + pub bugfix: u8, +} + +impl From for BootLoaderVersion { + fn from(value: u32) -> Self { + Self { + name: char::from_u32(value >> 24).unwrap(), + major: (value >> 16 & 0xff) as u8, + minor: (value >> 8 & 0xff) as u8, + bugfix: (value & 0xff) as u8, + } + } +} + +#[derive(Debug)] +pub struct AvailablePeripherals { + pub usb_hid: bool, + pub spi_slave: bool, + pub i2c_slave: bool, + pub lpuart: bool, +} + +impl From for AvailablePeripherals { + fn from(value: u32) -> Self { + let bits = value; + Self { + usb_hid: bits & (1 << 4) != 0, + spi_slave: bits & (1 << 2) != 0, + i2c_slave: bits & (1 << 1) != 0, + lpuart: bits & 1 != 0, + } + } +} + +pub struct AvailableCommands { + bits: u32, +} + +impl AvailableCommands { + fn command_available(&self, tag: &packet::CommandTag) -> bool { + let mask = 1 << (*tag as u8 - 1); + self.bits & mask != 0 + } +} + +impl From for AvailableCommands { + fn from(value: u32) -> Self { + Self { bits: value } + } +} + +impl Debug for AvailableCommands { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(packet::CommandTag::iter().filter(|tag| self.command_available(tag))) + .finish() + } +} + +#[derive(Debug)] +pub struct ReservedRegion { + start: u32, + end: u32, +} + +#[derive(Debug)] +pub struct ReservedRegions { + count: u8, + regions: Vec, +} + +impl From for ReservedRegions { + fn from(value: GetPropertyResponseParams) -> Self { + let mut regions = Vec::new(); + let count = value.properties[0] as usize; + for i in 1..count { + regions.push(ReservedRegion { + start: value.properties[i], + end: value.properties[i + 1], + }); + } + Self { + count: count.try_into().unwrap(), + regions, + } + } +} + +#[repr(u32)] +#[derive(IntoPrimitive, FromPrimitive, EnumIter, PartialEq, Clone)] +pub enum LpcDeviceId { + LPC55S28 = 0xA010119C, + LPC55S26 = 0xA010229A, + LPC5528 = 0xA010111C, + LPC5526 = 0xA010221A, + #[num_enum(catch_all)] + Unknown(u32), +} + +impl Display for LpcDeviceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::LPC5526 => write!(f, "LPC5526"), + Self::LPC5528 => write!(f, "LPC5528"), + Self::LPC55S26 => write!(f, "LPC55S26"), + Self::LPC55S28 => write!(f, "LPC55S28"), + Self::Unknown(i) => write!(f, "0x{:x}", i), + } + } +} + +#[repr(u32)] +#[derive(IntoPrimitive, EnumIter, PartialEq, Clone)] +pub enum LpcDieId { + Rev0a = 0x0, + Rev1b = 0x1, + #[num_enum(catch_all)] + Unknown(u32), +} + +impl From for LpcDieId { + fn from(value: u32) -> Self { + match value & 0xf { + 0x0 => Self::Rev0a, + 0x1 => Self::Rev1b, + i => Self::Unknown(i), + } + } +} + +impl Display for LpcDieId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Rev0a => write!(f, "0A"), + Self::Rev1b => write!(f, "1B"), + Self::Unknown(i) => write!(f, "0x{:x}", i), + } + } +} + +pub struct SystemDeviceId { + pub device_id: LpcDeviceId, + pub die_id: LpcDieId, +} +impl From for SystemDeviceId { + fn from(value: GetPropertyResponseParams) -> Self { + Self { + device_id: value.properties[0].into(), + die_id: value.properties[1].into(), + } + } +} +impl Display for SystemDeviceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.pad(&format!("{} ({})", self.device_id, self.die_id)) + } +} + +#[derive(Debug)] +pub struct IrqNotifierPinSetting { + pub gpio_pin: u8, + pub gpio_port: u8, + pub enabled: bool, +} +impl From for IrqNotifierPinSetting { + fn from(bits: u32) -> Self { + Self { + gpio_pin: (bits & 0xff) as u8, + gpio_port: ((bits >> 8) & 0xff) as u8, + enabled: bits & 0x80000000 != 0, + } + } +} +impl Into for &IrqNotifierPinSetting { + fn into(self) -> u32 { + (if self.enabled { 1u32 << 31 } else { 0u32 }) + | ((self.gpio_port as u32) << 8) + | self.gpio_pin as u32 + } +} + +#[derive(Debug)] +pub struct ExternalMemoryAttributes { + pub start_address: Option, + pub size_kb: Option, + pub page_size_bytes: Option, + pub sector_size_bytes: Option, + pub block_size_bytes: Option, +} + +impl From for ExternalMemoryAttributes { + fn from(value: GetPropertyResponseParams) -> Self { + //TODO: use nom here?? + let props = value.properties; + let supported = u32::from_le(props[0]); + Self { + start_address: if supported & 1 != 0 { + Some(u32::from_le(props[1])) + } else { + None + }, + size_kb: if supported & 2 != 0 { + Some(u32::from_le(props[2])) + } else { + None + }, + page_size_bytes: if supported & 4 != 0 { + Some(u32::from_le(props[3])) + } else { + None + }, + sector_size_bytes: if supported & 8 != 0 { + Some(u32::from_le(props[4])) + } else { + None + }, + block_size_bytes: if supported & 0x10 != 0 { + Some(u32::from_le(props[5])) + } else { + None + }, + } + } +} + +#[derive(Debug)] +pub struct CheckStatus { + pub crc: u32, + pub last_error: u32, +} + +impl From for CheckStatus { + fn from(value: GetPropertyResponseParams) -> Self { + Self { + crc: value.properties[0], + last_error: value.properties[0], + } + } +} + +pub struct UsbIsp { + device: HidDevice, +} + +impl UsbIsp { + pub fn new(device: HidDevice) -> Self { + Self { device } + } + pub fn send_command(&self, p: CommandPacket) -> Result { + debug!("Writing packet: {:?}", p); + self.write_packet(Packet::CommandPacket(p)) + } + pub fn write_packet(&self, p: Packet) -> Result { + let usb_packet = match p { + Packet::CommandPacket(p) => { + UsbPacket::new(packet::ReportId::CommandOut, Packet::CommandPacket(p)) + } + Packet::ResponsePacket(_) => panic!("We should not be writing responses"), + Packet::DataPacket(p) => { + UsbPacket::new(packet::ReportId::DataOut, Packet::DataPacket(p)) + } + }; + let mut buf = Vec::with_capacity(usb_packet.length()); + usb_packet.write(&mut buf)?; + + //FIXME: don't expect a response on data packets + let rsize = self.device.write(&buf)?; + let mut rbuf = [0u8; 64]; + let res_size = self.device.read(&mut rbuf[..])?; + let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap(); //TODO: handle more gracefully, but requires ownership of I + + match parsed.1 { + Packet::ResponsePacket(p) => Ok(p), + Packet::CommandPacket(_) => Err(anyhow!("Unexpected command packet on IN stream")), + Packet::DataPacket(_) => { + Err(anyhow!("Data IN packets not expected in reply to commands")) + } + } + } + + pub fn read_data(&self, buf: &mut impl Write, byte_count: usize) -> Result<(), Error> { + let mut rbuf = [0u8; 64]; + let mut read = 0; + while read < byte_count { + let res_size = self.device.read(&mut rbuf[..])?; + let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap(); //TODO: handle more gracefully, but requires ownership of I + match parsed.1 { + Packet::DataPacket(p) => { + read += buf.write(&p.bytes)?; + } + Packet::ResponsePacket(r) => match r.params { + ResponseParameters::GenericResponse(rp) if rp.status != StatusCode::Success => { + // Success here is unexpected, let it fall through to the default case + return Err(anyhow!("Error while reading data: {}", rp.status)); + } + _ => { + return Err(anyhow!( + "Unexpected response params when reading data {:?}", + r.params + )) + } + }, + p => return Err(anyhow!("Unexpected packet type when reading data {:?}", p)), + } + } + Ok(()) + } + + pub fn get_last_error(&self) -> u32 { + // Same as get_property but less checking + let command = CommandPacket { + tag: CommandTag::GetProperty, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 1, + params: vec![Property::CheckStatus as u32], + }; + let pr = self.send_command(command).ok(); + match pr { + Some(pr) => match pr.params { + ResponseParameters::GetPropertyResponse(resp) => match resp.status { + packet::StatusCode::Success => CheckStatus::from(resp).last_error, + _ => 0, + }, + _ => 0, + }, + None => 0, + } + } + pub fn get_property( + &self, + prop: Property, + extra_params: Option>, + ) -> Result { + let mut command = CommandPacket { + tag: CommandTag::GetProperty, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 1, + params: vec![prop as u32], + }; + match extra_params { + Some(v) => { + command.params.extend(v.iter()); + command.param_count += v.len() as u8; + } + None => {} + } + let pr = self.send_command(command)?; + match pr.params { + ResponseParameters::GetPropertyResponse(resp) => match resp.status { + packet::StatusCode::Success => Ok(resp), + code => Err(anyhow!("Error status returned ({})", code)), + }, + _ => Err(anyhow!( + "Unexpected response to GetBootloaderVersion command" + )), + } + } + pub fn set_property( + &self, + prop: Property, + params: Vec, + ) -> Result { + let command = CommandPacket { + tag: CommandTag::SetProperty, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 1 + params.len() as u8, + params: vec![prop as u32], + }; + let pr = self.send_command(command)?; + match pr.params { + ResponseParameters::GenericResponse(resp) => match resp.status { + packet::StatusCode::Success => Ok(resp), + code => Err(anyhow!("Error status returned ({:?})", code)), + }, + _ => Err(anyhow!( + "Unexpected response to GetBootloaderVersion command" + )), + } + } + pub fn get_basic_property( + &self, + prop: Property, + ) -> Result { + self.get_property(prop, None) + } + + pub fn flash_erase_all(&self, memory_id: Option) -> Result<(), Error> { + let memory_id = memory_id.unwrap_or(MemoryId::Internal); + let command = CommandPacket { + tag: CommandTag::FlashEraseAll, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 1, + params: vec![memory_id.into()], + }; + self.send_command(command).map(|_| ()) + } + + pub fn flash_erase_region( + &self, + memory_id: Option, + start_address: u32, + byte_count: u32, + ) -> Result<(), Error> { + if start_address % 4 != 0 || byte_count % 4 != 0 { + return Err(anyhow!( + "Erase address and size must be aligned on a 4-byte boundary" + )); + } + + let memory_id = memory_id.unwrap_or(MemoryId::Internal); + let command = CommandPacket { + tag: CommandTag::FlashEraseRegion, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 3, + params: vec![start_address, byte_count, memory_id.into()], + }; + self.send_command(command).map(|_| ()) + } + + pub fn reset(&self) -> Result<(), Error> { + let command = CommandPacket { + tag: CommandTag::Reset, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 0, + params: vec![], + }; + self.send_command(command).map(|_| ()) + } + + pub fn GetBootloaderVersion(&self) -> Result { + let prop_value = self + .get_basic_property(Property::CurrentVersion)? + .properties[0]; + Ok(prop_value.into()) + } + pub fn GetAvailablePeripherals(&self) -> Result { + let prop_value = self + .get_basic_property(Property::AvailablePeripherals)? + .properties[0]; + Ok(prop_value.into()) + } + pub fn GetAvailableCommands(&self) -> Result { + let prop_value = self + .get_basic_property(Property::Availablecommands)? + .properties[0]; + Ok(prop_value.into()) + } + pub fn GetFlashStartAddress(&self) -> Result { + let prop_value = self.get_basic_property(Property::FlashStartAddress)?; + Ok(prop_value.properties[0]) + } + pub fn GetFlashSizeInBytes(&self) -> Result { + let prop_value = self.get_basic_property(Property::FlashSizeInBytes)?; + Ok(prop_value.properties[0]) + } + pub fn GetMaxPacketSize(&self) -> Result { + let prop_value = self.get_basic_property(Property::MaxPacketSize)?; + Ok(prop_value.properties[0]) + } + pub fn GetReservedRegions(&self) -> Result { + let prop_value = self.get_basic_property(Property::ReservedRegions)?; + Ok(prop_value.into()) + } + pub fn GetSystemDeviceId(&self) -> Result { + let prop_value = self.get_basic_property(Property::SystemDeviceId)?; + Ok(prop_value.into()) + } + pub fn GetLifeCycleState(&self) -> Result { + let prop_value = self.get_basic_property(Property::LifeCycleState)?; + Ok(LifeCycleState::try_from(prop_value.properties[0])?) + } + pub fn GetUniqueDeviceId(&self) -> Result { + let prop_value = self.get_basic_property(Property::UniqueDeviceId)?; + let mut buf = [0u8; 16]; + for i in 0..4 { + buf[i * 4..i * 4 + 4].copy_from_slice(&prop_value.properties[i].to_ne_bytes()); + } + Ok(Uuid::from_bytes(buf)) + } + pub fn GetIrqNotifierPin(&self) -> Result { + let prop_value = self.get_basic_property(Property::IrqNotifierPin)?; + Ok(prop_value.properties[0].into()) + } + pub fn SetIrqNotifierPin(&self, setting: &IrqNotifierPinSetting) -> Result<(), Error> { + let command = CommandPacket { + tag: CommandTag::SetProperty, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 2, + params: vec![Property::IrqNotifierPin as u32, setting.into()], + }; + let pr = self.send_command(command.clone())?; + match pr.params { + ResponseParameters::GenericResponse(resp) => match resp.status { + packet::StatusCode::Success => Ok(()), + code => Err(anyhow!("Error status returned ({:?})", code)), + }, + _ => Err(anyhow!( + "Unexpected response to {:?} command ({:?})", + command.tag, + pr.params + )), + } + } + pub fn GetExternalMemoryAttributes( + &self, + id: MemoryId, + ) -> Result { + let prop_value = + self.get_property(Property::ExternalMemoryAttributes, Some(vec![id as u32]))?; + Ok(prop_value.into()) + } + + pub fn ReadMemory( + &self, + buf: &mut impl std::io::Write, + start_address: u32, + byte_count: u32, + memory_id: Option, + ) -> Result { + let command = CommandPacket { + tag: CommandTag::ReadMemory, + flags: CommandFlags::empty(), + reserved: 0, + param_count: 3, + params: vec![ + start_address, + byte_count, + match memory_id { + Some(v) => v.into(), + None => MemoryId::Internal.into(), + }, + ], + }; + let pr = self.send_command(command)?; + let params = match pr.params { + ResponseParameters::ReadMemoryResponse(params) => match params.status { + StatusCode::Success => params, + _ => return Err(anyhow!("Error returned from device: {:?}", params.status)), + }, + _ => return Err(anyhow!("Unexpected reply to ReadMemory {:?}", pr.params)), + }; + self.read_data(buf, params.data_bytes as usize)?; + + // We expect a GenericResponse with success after the transfer + let mut rbuf = vec![0u8; 64]; + let res_size = self.device.read(&mut rbuf[..])?; + let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap().1; //TODO: handle more gracefully, but requires ownership of I + + match parsed { + Packet::ResponsePacket(p) => match p.params { + ResponseParameters::GenericResponse(rp) => match rp.status { + StatusCode::Success => Ok(params.data_bytes), + _ => Err(anyhow!("After read memory transfer: {}", rp.status)), + }, + _ => Err(anyhow!("Unexpected response after read memory: {:?}", p)), + }, + _ => Err(anyhow!( + "Unexpected packet type after read memory: {:?}", + parsed + )), + } + } +}