From 7d377eb5932830be8f044ebd1ecbf6e58f630b15 Mon Sep 17 00:00:00 2001 From: Ein Terakawa Date: Sun, 14 Jun 2020 22:25:59 +0900 Subject: [PATCH] initial commint: v0.5.0 --- .gitignore | 1 + COPYRIGHT.md | 8 + Cargo.lock | 481 +++++++++++++++++++++++++++ Cargo.toml | 12 + LICENSE-Apache | 176 ++++++++++ LICENSE-MIT | 21 ++ README.md | 2 + nu-isp-cli/Cargo.toml | 28 ++ nu-isp-cli/LICENSE-Apache | 176 ++++++++++ nu-isp-cli/LICENSE-MIT | 21 ++ nu-isp-cli/README.md | 86 +++++ nu-isp-cli/src/main.rs | 667 ++++++++++++++++++++++++++++++++++++++ nu-isp/Cargo.toml | 15 + nu-isp/LICENSE-Apache | 176 ++++++++++ nu-isp/LICENSE-MIT | 21 ++ nu-isp/README.md | 3 + nu-isp/src/lib.rs | 416 ++++++++++++++++++++++++ 17 files changed, 2310 insertions(+) create mode 100755 .gitignore create mode 100644 COPYRIGHT.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE-Apache create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 nu-isp-cli/Cargo.toml create mode 100644 nu-isp-cli/LICENSE-Apache create mode 100644 nu-isp-cli/LICENSE-MIT create mode 100644 nu-isp-cli/README.md create mode 100644 nu-isp-cli/src/main.rs create mode 100644 nu-isp/Cargo.toml create mode 100644 nu-isp/LICENSE-Apache create mode 100644 nu-isp/LICENSE-MIT create mode 100644 nu-isp/README.md create mode 100644 nu-isp/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/COPYRIGHT.md b/COPYRIGHT.md new file mode 100644 index 0000000..f95436f --- /dev/null +++ b/COPYRIGHT.md @@ -0,0 +1,8 @@ +Copyright (c) 2020 Ein Terakawa + +Crates is this repository are dual-licensed under Apache 2.0 and MIT terms. + + +Except as otherwise noted (below and/or in individual files), these crates are licensed under +the Apache License, Version 2.0 [LICENSE-Apache](./LICENSE-Apache) +or the MIT license [LICENSE-MIT](./LICENSE-MIT) , at your option. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..92593bb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,481 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "console" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "termios", + "unicode-width", + "winapi", + "winapi-util", +] + +[[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.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "goblin" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20fd25aa456527ce4f544271ae4fea65d2eda4a6561ea56f39fb3ee4f7e3884" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "hermit-abi" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +dependencies = [ + "libc", +] + +[[package]] +name = "hidapi" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95d0287c6c92c3fe5426a25dace368e0cf1b93e63beacbd7720176e713b001d" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "ihex" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9394ec1fbc3afbdfb357ba267a7fffdc39f6e3d8ba88c0af6d9476e6d3c889d5" + +[[package]] +name = "indicatif" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a68371cf417889c9d7f98235b7102ea7c54fc59bcbd22f3dea785be9d27e40" +dependencies = [ + "console", + "lazy_static", + "number_prefix", + "regex", +] + +[[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.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "nu-isp" +version = "0.5.0" +dependencies = [ + "hidapi", + "log", +] + +[[package]] +name = "nu-isp-cli" +version = "0.5.0" +dependencies = [ + "atty", + "clap", + "goblin", + "hidapi", + "ihex", + "indicatif", + "log", + "maplit", + "nu-isp", + "pretty_env_logger", + "termcolor", + "termcolor_output", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "number_prefix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "pretty_env_logger" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" +dependencies = [ + "chrono", + "env_logger", + "log", +] + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "scroll" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e367622f934864ffa1c704ba2b82280aab856e3d8213c84c5720257eb34b15b9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termcolor_output" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0363afbf20990ea53a69c03b71800480aaf90e8f49f6fd5385ecc302062895ff" +dependencies = [ + "termcolor", + "termcolor_output_impl", +] + +[[package]] +name = "termcolor_output_impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34dde0bb841eb3762b42bdff8db11bbdbc0a3bd7b32012955f5ce1d081f86c1" + +[[package]] +name = "terminal_size" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8038f95fc7a6f351163f4b964af631bd26c9e828f7db085f2a84aca56f70d13b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "termios" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2" +dependencies = [ + "libc", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..79fd56a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = [ + "nu-isp", + "nu-isp-cli", +] + +# refer to https://doc.rust-lang.org/cargo/reference/profiles.html +[profile.release] +#codegen-units = 1 +#debug = true +#lto = true +#opt-level = 'z' diff --git a/LICENSE-Apache b/LICENSE-Apache new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE-Apache @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..791d363 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ein Terakawa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0910db9 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +## [nu-isp-cli](./nu-isp-cli/README.md) +## [nu-isp](./nu-isp/README.md) diff --git a/nu-isp-cli/Cargo.toml b/nu-isp-cli/Cargo.toml new file mode 100644 index 0000000..2f944b6 --- /dev/null +++ b/nu-isp-cli/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "nu-isp-cli" +version = "0.5.0" +authors = ["Ein Terakawa "] +edition = "2018" +description = "CLI tool for Nuvoton ISP_HID Bootloaders" +repository = "https://github.com/elfmimi/nu-isp-rs" +keywords = ["nuvoton", "isp", "hid", "flash"] +categories = ["command-line-utilities", "development-tools", "embedded"] +license = "MIT OR Apache-2.0" +readme = "README.md" + +[dependencies.nu-isp] +version = "0.5.0" +path = "../nu-isp" + +[dependencies] +clap = "2.33.1" +log = { version = "0.4.8", features = ["std"] } +pretty_env_logger = "0.3.0" +termcolor = "1.1.0" +termcolor_output = "1.0.1" +atty = "0.2.5" +indicatif = "0.14.0" +hidapi = "1.2.1" +goblin = "0.2.0" +ihex = "1.1.2" +maplit = "1.0.2" diff --git a/nu-isp-cli/LICENSE-Apache b/nu-isp-cli/LICENSE-Apache new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/nu-isp-cli/LICENSE-Apache @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/nu-isp-cli/LICENSE-MIT b/nu-isp-cli/LICENSE-MIT new file mode 100644 index 0000000..791d363 --- /dev/null +++ b/nu-isp-cli/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ein Terakawa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/nu-isp-cli/README.md b/nu-isp-cli/README.md new file mode 100644 index 0000000..7a2d282 --- /dev/null +++ b/nu-isp-cli/README.md @@ -0,0 +1,86 @@ +# nu-isp-cli + +Command line implementation of [nu-isp flashing protocol over hid](https://github.com/OpenNuvoton/Nuvoton_Tools/blob/master/doc/NuMicro_ISP_Flow_And_Command_Set.pdf) used by Nuvoton microcontrollers. + +## setup + +### windows + +No special preparaiton needed. Install it, and it just works. + +### mac + +No special preparaiton needed. Install it, and it just works. + +### linux + +You'll need libusb. Depending on your distribution, you might need to `sudo apt-get install libudev-dev libusb-1.0-0-dev`. + +If you'd like not to use sudo everytime, you'll need udev rules. With your board plugged in and in bootloader mode, use `lsusb` to find its vendor-id and product-id, seen here as 0416:a316. + +```bash +$ lsusb +... +Bus 001 Device 002: ID 0416:a316 Winbond Electronics Corp. +... +``` + +Then put them in the following format and save it to something like /etc/udev/rules.d/99-nuvoton-isp.rules + +```bash +SUBSYSTEM=="usb", ATTR{idVendor}=="0416", ATTR{idProduct}=="3f00", MODE="666" +SUBSYSTEM=="usb", ATTR{idVendor}=="0416", ATTR{idProduct}=="a316", MODE="666" +``` + +Then replug your board and let it into bootloader mode again. + +## install + +`cargo install nu-isp-cli` + +## use + +```bash +$ nu-isp-cli +Nuvoton NuMicro ISP_HID Programming Tool [unofficial] +Version 0.5.0 + +Quick Reference: + + nu-isp-cli + + nu-isp-cli info + + nu-isp-cli erase + + nu-isp-cli flash + + nu-isp-cli info + + nu-isp-cli erase + + nu-isp-cli flash + + nu-isp-cli --help +``` + +It will attempt to autodetect a device from list of known device-ids and using the first one available or you can specify vid and pid (before the subcommand) instead. + +```bash +$ nu-isp-cli info +Printing info... +DEVICE NUC126LG4AE (PDID: 00C05204) +CONFIG FFFFFF7E:0001C000 +Done. +``` + +It accepts binary files, elf files or ihex format text files for your convenience. + +```bash +$ nu-isp-cli firmware.elf +``` + +## supported chips + +Currently it is only tested with NUC123 and NUC126 but it should work with many other chips with conformant bootloaders. +I'll happily add new chips to the list upon your report confirming its correct operation. diff --git a/nu-isp-cli/src/main.rs b/nu-isp-cli/src/main.rs new file mode 100644 index 0000000..1dafa54 --- /dev/null +++ b/nu-isp-cli/src/main.rs @@ -0,0 +1,667 @@ +use std::ops::Not; +use std::path::PathBuf; + +extern crate clap; +use clap::{App, AppSettings, Arg, SubCommand}; +use maplit::hashmap; + +use termcolor::{Color, ColorChoice}; + +use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; + +use hidapi::{HidApi, HidDevice}; +use ihex::{ + reader::{Reader as HexReader, ReaderError as HexReaderError}, + record::Record as HexRecord, +}; + +use nu_isp::get_partinfo; +use nu_isp::{Context as NuIspContext, Progress as NuIspProgress, ProgressEvent}; + +/* +#[derive(Debug)] +enum Error { + HidError(hidapi::HidError), + GoblinError(goblin::error::Error), + HexReaderError(HexReaderError), + IoError(std::io::Error), +} +*/ + +macro_rules! error_from_list { + ( $( $enum:ident ( $error:ty ) ), + ) => { + #[derive(Debug)] + enum Error { + NoLoadableSection, + FileSizeTooLarge, + $($enum($error),)* + } + $(impl From<$error> for Error { fn from(error: $error) -> Self { Error::$enum(error) } }) * + } +} + +error_from_list!( + HidError(hidapi::HidError), + GoblinError(goblin::error::Error), + HexReaderError(HexReaderError), + IoError(std::io::Error), + NuIspError(nu_isp::error::Error) +); + +type Result = std::result::Result; + +/* +use std::fmt; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + // Both underlying errors already impl `Display`, so we defer to + // their implementations. + Error::HidError(ref err) => write!(f, "HID error: {}", err), + Error::GoblinError(ref err) => write!(f, "ELF Parse error: {}", err), + Error::HexReaderError(ref err) => write!(f, "HEX Parse error: {}", err), + Error::IoError(ref err) => write!(f, "IO error: {}", err), + } + } +} +*/ + +/* +impl std::error::Error for Error { + fn cause(&self) -> Option<&std::error::Error> { + match *self { + // N.B. Both of these implicitly cast `err` from their concrete + // types (either `&io::Error` or `&num::ParseIntError`) + // to a trait object `&Error`. This works because both error types + // implement `Error`. + Error::HidError(ref err) => Some(err), + Error::GoblinError(ref err) => Some(err), + Error::HexReaderError(ref err) => Some(err), + Error::IoError(ref err) => Some(err), + } + } +} +*/ + +macro_rules! error { + ($err:expr) => { + log::error!("{}({}): {}", file!(), line!(), $err) + }; +} + +// This is ugly. It should be able to do better than this. +#[allow(unused)] +macro_rules! colored { + ($($arg:tt)*) => {{ + // termcolor fails to auto detect terminal in certain environment + let color_choise = if atty::is(atty::Stream::Stdout) { ColorChoice::Auto } else { ColorChoice::Never }; + let writer = termcolor::BufferWriter::stdout(color_choise); + let mut buffer = writer.buffer(); + termcolor_output::colored!(buffer, $($arg)*).unwrap(); + writer.print(&buffer).unwrap(); + }}; +} + +/* +// For some reason this does not compile. +// The reason seems to be that termcolor_output_impl::colored_derive() +// is not good at parsing macro invocation as a format string. +macro_rules! coloredln { + ($fmt:tt, $($arg:tt)+) => { + colored!(concat!($fmt, "\n"), $($arg)+) + }; +} +*/ + +macro_rules! coloredln { + ($($arg:tt)*) => {{ + // termcolor fails to auto detect terminal in certain environment + let color_choise = if atty::is(atty::Stream::Stdout) { ColorChoice::Auto } else { ColorChoice::Never }; + let writer = termcolor::BufferWriter::stdout(color_choise); + let mut buffer = writer.buffer(); + termcolor_output::colored!(buffer, $($arg)*).unwrap(); + write!(&mut buffer as &mut dyn std::io::Write, "\n").unwrap(); + // above is the same as the following + /* { + use std::io::Write; + write!(buffer, "\n").unwrap(); + } */ + writer.print(&buffer).unwrap(); + }}; +} + +macro_rules! ecoloredln { + ($($arg:tt)*) => {{ + // termcolor fails to auto detect terminal in certain environment + let color_choise = if atty::is(atty::Stream::Stderr) { ColorChoice::Auto } else { ColorChoice::Never }; + let writer = termcolor::BufferWriter::stderr(color_choise); + let mut buffer = writer.buffer(); + termcolor_output::colored!(buffer, $($arg)*).unwrap(); + write!(&mut buffer as &mut dyn std::io::Write, "\n").unwrap(); + writer.print(&buffer).unwrap(); + }}; +} + +const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +fn main() { + let mut app = App::new("Nuvoton NuMicro ISP_HID Programming Tool [unofficial]\nVersion ") + .setting(AppSettings::DisableHelpSubcommand) + .setting(AppSettings::DisableVersion) + .version(VERSION) + .author(clap::crate_authors!()) + .arg( + Arg::with_name("INPUT") + .help("Sets the input file to use") + .required(false) + .index(1), + ) + .arg( + Arg::with_name("v") + .short("v") + .multiple(true) + .help("Sets the level of verbosity"), + ) + .arg( + Arg::with_name("VID") + .long("vid") + .takes_value(true) + .help("vendor id"), + ) + .arg( + Arg::with_name("PID") + .long("pid") + .takes_value(true) + .help("product id"), + ) + .arg( + Arg::with_name("disable-progressbar") + .long("disable-progressbar") + .help("diasble progressbar") + .alias("no-progress"), + ) + .arg( + Arg::with_name("version") + .short("V") + .long("version") + .help("Show version"), + ) + .subcommand(SubCommand::with_name("info").about("print target information")) + .subcommand( + SubCommand::with_name("erase").about("erase APROM ( DATA flash will be kept intact )"), + ) + .subcommand( + SubCommand::with_name("flash") + .about("flash binary to target") + .arg( + Arg::with_name("INPUT") + .help("Sets the input file to use") + .required(true), + ), + ); + let matches = app + .get_matches_from_safe_borrow(std::env::args()) + .unwrap_or_else(|err| { + eprintln!("{}", err); + std::process::exit(1) + }); + + // pretty_env_logger::init(); + { + log::set_max_level(log::LevelFilter::Off); + let mut builder = pretty_env_logger::formatted_builder(); + + let environment_variable_name = "RUST_LOG"; + if let Ok(s) = ::std::env::var(environment_variable_name) { + builder.parse_filters(&s); + } else { + match matches.occurrences_of("v") { + 0 => (), + 1 => { + builder.parse_filters("info"); + } + 2 => { + builder.parse_filters("debug"); + } + 3 | _ => { + builder.parse_filters("trace"); + } + } + } + + builder.try_init().unwrap(); + } + + log::trace!("initialized logger"); + + let info = matches.subcommand_matches("info").is_some(); + if info { + println!("Printing info..."); + } + + let mut vid_pid: Option<(u16, u16)> = None; + let mut input: Option<&str> = None; + if let Some(flash) = matches.subcommand_matches("flash") { + let file = flash.value_of("INPUT").unwrap(); + input = Some(file) + } + + if matches.is_present("flash") || matches.is_present("info") || matches.is_present("erase") { + if let Some(param) = matches.value_of("INPUT") { + let pv: Vec = param + .split(':') + .map(|item| u16::from_str_radix(item, 16).unwrap()) + .collect(); + if pv.len() == 2 { + vid_pid = Some((pv[0], pv[1])) + } + } + } else if let Some(file) = matches.value_of("INPUT") { + input = Some(file) + } else { + coloredln!( + "{}{}Nuvoton{} {}NuMicro {}ISP_HID {}Programming {}Tool {}[unofficial]{}", + fg!(Some(Color::Ansi256(196))), + bold!(true), + bold!(false), + fg!(Some(Color::Ansi256(202))), + fg!(Some(Color::Ansi256(208))), + fg!(Some(Color::Ansi256(214))), + fg!(Some(Color::Ansi256(220))), + fg!(Some(Color::Ansi256(244))), + reset!() + ); + coloredln!( + "{}Version {}{}", + fg!(Some(Color::Ansi256(226))), + VERSION, + reset!() + ); + + let exe = std::env::current_exe(); + let bin_name = exe + .as_ref() + .ok() + .and_then(|p| p.file_stem()) + .and_then(|f| f.to_str()) + .unwrap_or("nu-isp-cli"); + + if matches.is_present("version").not() { + println!("\nQuick Reference:\n"); + println!(" {} \n", bin_name); + println!(" {} info\n", bin_name); + println!(" {} erase\n", bin_name); + println!(" {} flash \n", bin_name); + println!(" {} info\n", bin_name); + println!(" {} erase\n", bin_name); + println!(" {} flash \n", bin_name); + println!(" {} --help", bin_name); + } + return; + } + + if matches.is_present("VID") && matches.is_present("PID") { + let vid = matches.value_of("VID").unwrap(); + let pid = matches.value_of("PID").unwrap(); + let vid = u16::from_str_radix(vid, 16).unwrap(); + let pid = u16::from_str_radix(pid, 16).unwrap(); + if vid_pid.is_some() && vid_pid != Some((vid, pid)) { + error!("PID:VID should be specified only once"); + std::process::exit(1) + } + vid_pid = Some((vid, pid)) + } + + let progress_event_handler = { + let pb = ProgressBar::hidden(); + let template = "{msg:.color1.bold} {spinner:.color1} [{elapsed_precise}] [{bar:25.cyan/blue}] {bytes}/{total_bytes}"; + let template_erase = "{msg:.color1.bold} {spinner:.color1} [{elapsed_precise}]"; + let style = ProgressStyle::default_bar() + .tick_chars("⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ ") + .progress_chars("##-"); + move |event| { + use ProgressEvent::*; + match event { + Started { total_bytes } => { + pb.set_style( + style + .clone() + .template(&template.replace("color1", "yellow")), + ); + pb.set_message(" Erasing"); + pb.set_length(total_bytes as u64); + pb.set_position(0); + pb.set_draw_target(ProgressDrawTarget::stderr()); + pb.enable_steady_tick(120); + } + Erased => { + // Started Programming + pb.disable_steady_tick(); + pb.set_style(style.clone().template(&template.replace("color1", "cyan"))); + pb.set_message("Programming"); + pb.enable_steady_tick(120); + } + Programmed { bytes, .. } => { + pb.set_position(bytes as u64); + } + Finished => { + pb.disable_steady_tick(); + pb.set_style( + style + .clone() + .template(&template.replace("color1", "green")) + .tick_chars("✔"), + ); + pb.finish_with_message(" Completed"); + } + Aborted => { + pb.disable_steady_tick(); + let style = style.clone(); + pb.set_style( + style + .clone() + .template(&template.replace("color1", "red")) + .tick_chars("✘"), + ); + pb.abandon_with_message(" Aborted"); + } + StartedErasing => { + pb.set_style( + style + .clone() + .template(&template_erase.replace("color1", "yellow")), + ); + pb.set_message(" Erasing"); + pb.set_draw_target(ProgressDrawTarget::stderr()); + pb.enable_steady_tick(120); + } + FinishedErasing => { + pb.disable_steady_tick(); + pb.set_style( + style + .clone() + .template(&template_erase.replace("color1", "green")) + .tick_chars("✔"), + ); + pb.finish_with_message(" Completed"); + } + AbortedErasing => { + pb.disable_steady_tick(); + pb.set_style( + style + .clone() + .template(&template_erase.replace("color1", "red")) + .tick_chars("✘"), + ); + pb.abandon_with_message(" Aborted"); + } + _ => {} + } + } + }; + let progress = if matches.is_present("disable-progressbar") { + NuIspProgress::new(|_| {}) + } else { + NuIspProgress::new(progress_event_handler) + }; + + let api = HidApi::new().expect("Couldn't find system usb"); + + let (d, v, p) = if let Some((v, p)) = vid_pid { + match api.open(v, p) { + Err(e) => { + error!(e); + eprintln!("Are you sure device is plugged in and in bootloader mode?"); + std::process::exit(1) + } + Ok(device) => (device, v, p), + } + } else { + // no vid:pid provided + + let mut device: Option<(HidDevice, u16, u16)> = None; + + let vendor = hashmap! { + 0x0416 => vec![0xA316, 0x3F00], + }; + + for device_info in api.device_list() { + if let Some(products) = vendor.get(&device_info.vendor_id()) { + if products.contains(&device_info.product_id()) { + if let Ok(d) = device_info.open_device(&api) { + device = Some((d, device_info.vendor_id(), device_info.product_id())); + break; + } + } + } + } + if device.is_none() { + eprintln!("Are you sure device is plugged in and in bootloader mode?"); + std::process::exit(1) + } + device.unwrap() + }; + + log::info!( + "found {:04X}:{:04X} ( {:?} {:?} )", + v, + p, + d.get_manufacturer_string().expect("no mfg str").unwrap(), + d.get_product_string().expect("no prd str").unwrap() + ); + + let context = NuIspContext::new(&d, &progress); + + let do_info = |context: &NuIspContext| { + let info = context + .nu_isp_info() + .map_err(|err| { + eprintln!("{}!", err); + ecoloredln!( + "{}{}ERROR!{} {:?}", + fg!(Some(Color::Red)), + bold!(true), + reset!(), + err + ); + std::process::exit(1) + }) + .unwrap(); + match get_partinfo(info.pdid) { + None => println!("DEVICE PDID: {:08X}", info.pdid), + Some(partinfo) => coloredln!( + "DEVICE {}{}{} (PDID: {:08X})", + bold!(true), + partinfo.name, + reset!(), + info.pdid + ), + } + println!("CONFIG {:08X}:{:08X}", info.config[0], info.config[1]); + }; + + if matches.is_present("info") { + do_info(&context); + println!("Done."); + } else if matches.is_present("erase") { + println!("Erasing APROM..."); + do_info(&context); + context + .nu_isp_erase() + .map_err(|err| { + eprintln!("{}!", err); + ecoloredln!( + "{}{}ERROR!{} {:?}", + fg!(Some(Color::Red)), + bold!(true), + reset!(), + err + ); + std::process::exit(1) + }) + .unwrap(); + coloredln!( + "{}Erased{} and Rebooting...", + fg!(Some(Color::Yellow)), + reset!() + ); + } else if let Some(file) = input { + log::info!("input file: {}", file); + let path = std::path::Path::new(file); + match std::fs::File::open(path) { + Ok(file) => drop(file), + Err(e) => { + eprintln!("{}", e); + return; + } + }; + load_binary_file(path.to_path_buf()) + .and_then(|binary| { + do_info(&context); + let start_time = std::time::Instant::now(); + let len = binary.len(); + match context.nu_isp_download(binary) { + Ok(ok) => { + println!( + " {} bytes in {:.3} sec", + len, + start_time.elapsed().as_secs_f32() + ); + Ok(ok) + } + Err(err) => { + eprintln!("{}!", err); + Err(err.into()) + } + } + }) + .map_err(|err| { + ecoloredln!( + "{}{}ERROR!{} {:?}", + fg!(Some(Color::Red)), + bold!(true), + reset!(), + err + ); + std::process::exit(1) + }) + .unwrap(); + coloredln!( + "{}{}SUCCESS!{} and launching...", + fg!(Some(Color::Green)), + bold!(true), + reset!() + ); + } +} + +fn load_binary_file(path: PathBuf) -> Result> { + let is_elf = match std::fs::File::open(&path)? { + mut file => { + use std::io::Read; + let mut buf = [0; 4]; + file.read(&mut buf)?; + buf == [0x7F, b'E', b'L', b'F'] + } + }; + match path.extension() { + _ if is_elf => load_elf(path), + Some(ext) if ext == "hex" => load_hex(path), + _ => load_bin(path), + } +} + +fn load_elf(path: PathBuf) -> Result> { + use goblin::elf::program_header::*; + use std::io::prelude::*; + + let mut buffer = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut buffer)?; + + let mut flash_buf = [0xFF_u8; 256 * 1024]; + let mut flash_end = 0; + + let binary = goblin::elf::Elf::parse(&buffer.as_slice())?; + for ph in &binary.program_headers { + if ph.p_type == PT_LOAD && ph.p_filesz > 0 { + // log::debug!("Found loadable segment."); + // eprintln!("addr: {:08X} size: {:08X}", ph.p_paddr as u32, ph.p_filesz as usize); + if ph.p_paddr + ph.p_filesz <= flash_buf.len() as u64 { + let data = &buffer[ph.p_offset as usize..][..ph.p_filesz as usize]; + flash_buf[ph.p_paddr as usize..][..ph.p_filesz as usize].copy_from_slice(data); + if (ph.p_paddr + ph.p_filesz) > flash_end { + flash_end = ph.p_paddr + ph.p_filesz + } + } + } + } + if flash_end == 0 { + error!("No loadable section found in the ELF file."); + Err(Error::NoLoadableSection) + } else { + Ok(flash_buf[0..flash_end as usize].to_vec()) + } +} + +fn load_hex(path: PathBuf) -> Result> { + use std::io::prelude::*; + + let mut buffer = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut buffer)?; + + let mut flash_buf = [0xFF_u8; 256 * 1024]; + let mut flash_end = 0; + + // let utf8str = Reader::new(str::from_utf8(buffer.as_slice()).unwrap()); + let u8str: String = buffer.iter().map(|&b| b as char).collect(); + + let mut linear_offset = 0 as u32; + HexReader::new(&u8str).try_for_each(|item| match item { + Err(err) => { + error!(err); + Err(err) + } + Ok(HexRecord::ExtendedSegmentAddress(segment_addr)) => { + linear_offset = (segment_addr as u32) << 4; + Ok(()) + } + Ok(HexRecord::ExtendedLinearAddress(linear_addr)) => { + linear_offset = (linear_addr as u32) << 16; + Ok(()) + } + Ok(HexRecord::Data { offset, value }) => { + let offset = linear_offset as usize + offset as usize; + flash_buf[offset..][..value.len()].copy_from_slice(&value); + if (offset + value.len()) > flash_end { + flash_end = offset + value.len() + } + Ok(()) + } + _ => Ok(()), + })?; + + Ok(flash_buf[0..flash_end as usize].to_vec()) +} + +fn load_bin(path: PathBuf) -> Result> { + use std::io::prelude::*; + + let mut buffer = vec![]; + let mut file = std::fs::File::open(path)?; + file.read_to_end(&mut buffer)?; + + let mut flash_buf = [0xFF_u8; 256 * 1024]; + let flash_end; + + if buffer.len() > flash_buf.len() { + error!("File size too large!"); + return Err(Error::FileSizeTooLarge); + } + flash_end = buffer.len(); + flash_buf[0..flash_end].copy_from_slice(&buffer); + + Ok(flash_buf[0..flash_end as usize].to_vec()) +} diff --git a/nu-isp/Cargo.toml b/nu-isp/Cargo.toml new file mode 100644 index 0000000..82e9c70 --- /dev/null +++ b/nu-isp/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nu-isp" +version = "0.5.0" +authors = ["Ein Terakawa "] +edition = "2018" +description = "Nuvoton ISP_HID protocol library" +repository = "https://github.com/elfmimi/nu-isp-rs" +keywords = ["nuvoton", "isp", "hid", "flash"] +categories = ["development-tools", "embedded"] +license = "MIT OR Apache-2.0" +readme = "README.md" + +[dependencies] +log = "0.4.6" +hidapi = "1.2.1" diff --git a/nu-isp/LICENSE-Apache b/nu-isp/LICENSE-Apache new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/nu-isp/LICENSE-Apache @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/nu-isp/LICENSE-MIT b/nu-isp/LICENSE-MIT new file mode 100644 index 0000000..791d363 --- /dev/null +++ b/nu-isp/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ein Terakawa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/nu-isp/README.md b/nu-isp/README.md new file mode 100644 index 0000000..9b26dff --- /dev/null +++ b/nu-isp/README.md @@ -0,0 +1,3 @@ +# nu-isp + +Library implementation of [nu-isp flashing protocol over hid](https://github.com/OpenNuvoton/Nuvoton_Tools/blob/master/doc/NuMicro_ISP_Flow_And_Command_Set.pdf) used by Nuvoton microcontrollers. \ No newline at end of file diff --git a/nu-isp/src/lib.rs b/nu-isp/src/lib.rs new file mode 100644 index 0000000..ad60ca5 --- /dev/null +++ b/nu-isp/src/lib.rs @@ -0,0 +1,416 @@ +use std::cell::Cell; +use std::convert::TryInto; + +use hidapi::HidDevice; + +#[allow(unused)] +pub struct PartInfo { + pub name: &'static str, + pub flash_size: u32, +} + +macro_rules! pdid_map { + { $( $name:ident { $pdid:expr, $size:expr }, ) + } => + ( |pdid| ( match pdid { $( + $pdid => Some((stringify!($name), $size)), + ) * _ => None } ).map(|(name, flash_size)| PartInfo{name, flash_size}) ); +} + +/// Lookup chip product name from its PDID. +pub fn get_partinfo(pdid: u32) -> Option { + let pdid_map = pdid_map! { + // PARTNAME { pdid, flash_size } + // NUC123ZC2AN { 0x00012345, 36*1024 }, + // NUC123ZD4AN { 0x00012355, 68*1024 }, + // NUC123LC2AN { 0x00012325, 36*1024 }, + NUC123LD4AN { 0x00012335, 68*1024 }, + // NUC123SC2AN { 0x00012305, 36*1024 }, + // NUC123SD4AN { 0x00012315, 68*1024 }, + // NUC123ZC2AE { 0x10012345, 36*1024 }, + // NUC123ZD4AE { 0x10012355, 68*1024 }, + // NUC123LC2AE { 0x10012325, 36*1024 }, + // NUC123LD4AE { 0x10012335, 68*1024 }, + // NUC123SC2AE { 0x10012305, 36*1024 }, + // NUC123SD4AE { 0x10012315, 68*1024 }, + NUC126LE4AE { 0x00C05205, 128*1024 }, + NUC126LG4AE { 0x00C05204, 256*1024 }, + // NUC126SE4AE { 0x00C05213, 128*1024 }, + // NUC126SG4AE { 0x00C05212, 256*1024 }, + // NUC126VG4AE { 0x00C05231, 256*1024 }, + }; + pdid_map(pdid) +} + +pub mod error { + #[derive(Debug)] + pub enum Error { + HidError(hidapi::HidError), + ChecksumError, + PacketNumberError, + } + + impl From for Error { + fn from(error: hidapi::HidError) -> Self { + Error::HidError(error) + } + } + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + Error::HidError(ref err) => write!(f, "{}", err), + Error::ChecksumError => write!(f, "Checksum Error"), + Error::PacketNumberError => write!(f, "Packet Number Error"), + } + } + } +} + +type Result = std::result::Result; + +#[derive(Debug)] +pub enum ProgressEvent { + Started { total_bytes: u32 }, + Erased, + Programmed { bytes: u32 }, + Finished, + Aborted, + StartedErasing, + FinishedErasing, + AbortedErasing, + _Reserved_, +} + +pub struct Progress { + handler: Box, +} + +impl Progress { + /// Create a new `Progress` structure with a given `handler` to be called on events. + // pub fn new(handler: impl Fn(ProgressEvent) + 'static) -> Self { + pub fn new(handler: F) -> Self + where + F: Fn(ProgressEvent), + { + Self { + handler: Box::new(handler), + } + } + + /// Emit a flashing progress event. + fn emit(&self, event: ProgressEvent) { + (self.handler)(event); + } + + pub fn started(&self, total_bytes: u32) { + self.emit(ProgressEvent::Started { total_bytes }); + } + + pub fn erased(&self) { + self.emit(ProgressEvent::Erased); + } + + pub fn programmed(&self, bytes: u32) { + self.emit(ProgressEvent::Programmed { bytes }); + } + + pub fn finished(&self) { + self.emit(ProgressEvent::Finished); + } + + pub fn aborted(&self) { + self.emit(ProgressEvent::Aborted); + } +} + +mod nu_isp_cmd { + pub const UPDATE_APROM: u8 = 0xA0; + pub const READ_CONFIG: u8 = 0xA2; + pub const SYNC_PACKNO: u8 = 0xA4; + pub const GET_FWVER: u8 = 0xA6; + pub const RUN_APROM: u8 = 0xAB; + pub const RUN_LDROM: u8 = 0xAC; + pub const CONNECT: u8 = 0xAE; + pub const GET_DEVICEID: u8 = 0xB1; + + pub const DATA_PACKET: u8 = 0x00; +} + +pub struct NuIspInfo { + pub pdid: u32, + pub config: [u32; 2], +} + +pub struct Context<'a> { + device: &'a HidDevice, + progress: &'a Progress, + rpn: Cell, +} + +impl<'a> Context<'a> { + pub fn new(device: &'a hidapi::HidDevice, progress: &'a Progress) -> Self { + Context { + device, + progress, + rpn: Cell::new(0), + } + } + + fn nu_isp_connect(self: &Context<'a>) -> Result { + let d = self.device; + self.rpn.set(0); + + // CONNECT + let pn = 1 as u32; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::CONNECT; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + d.read(&mut buffer[0..64])?; + let rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + if rpn != pn + 1 { + log::debug!("rpn = {:08X}", rpn); + return Err(error::Error::PacketNumberError); + } + log::debug!("CONNECT"); + + // SYNC_PACKNO + let pn = rpn + 1; + let sync_pn = 0x01234567 as u32; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::SYNC_PACKNO; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + buffer[8..12].copy_from_slice(&sync_pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + d.read(&mut buffer[0..64])?; + let rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + if rpn != sync_pn + 1 { + return Err(error::Error::PacketNumberError); + } + log::debug!("SYNC rpn: {:08X} ( sent: {:08X} )", rpn, sync_pn); + + // GET_DEVICEID + let pn = rpn + 1; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::GET_DEVICEID; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + d.read(&mut buffer[0..64])?; + let rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + let pdid = u32::from_le_bytes(buffer[8..12].try_into().unwrap()); + if rpn != pn + 1 { + return Err(error::Error::PacketNumberError); + } + + // GET_FWVER + let pn = rpn + 1; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::GET_FWVER; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + d.read(&mut buffer[0..64])?; + let rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + let fwver = buffer[8]; + if rpn != pn + 1 { + return Err(error::Error::PacketNumberError); + } + log::info!("FWVER {:#04X}", fwver); + + // READ_CONFIG + let pn = rpn + 1; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::READ_CONFIG; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + d.read(&mut buffer[0..64])?; + let rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + if rpn != pn + 1 { + return Err(error::Error::PacketNumberError); + } + let config0 = u32::from_le_bytes(buffer[8..12].try_into().unwrap()); + let config1 = u32::from_le_bytes(buffer[12..16].try_into().unwrap()); + log::debug!("CONFIG {:08X}:{:08X}", config0, config1); + + self.rpn.set(rpn); + Ok(NuIspInfo { + pdid, + config: [config0, config1], + }) + } + + pub fn nu_isp_info(self: &Context<'a>) -> Result { + self.nu_isp_connect() + } + + pub fn nu_isp_erase(self: &Context<'a>) -> Result<()> { + let d = self.device; + if self.rpn.get() == 0 { + self.nu_isp_connect()?; + }; + + // This will only erase APROM. + let rpn = match self.update_aprom(vec![]) { + Err(err) => { + self.progress.emit(ProgressEvent::AbortedErasing); + return Err(err); + } + Ok(_) => self.rpn.get(), + }; + + // Reset and reboot the bootloader + // RUN_LDROM + let pn = rpn + 1; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::RUN_LDROM; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + // d.read(&mut buffer[0..64])?; + // let _rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + + self.rpn.set(0); + Ok(()) + } + + pub fn nu_isp_download(self: &Context<'a>, binary: Vec) -> Result<()> { + // TODO check flash size + let d = self.device; + + let rpn = self.rpn.get(); + if rpn == 0 { + self.nu_isp_connect()?; + }; + + // UPDATE_APROM + let rpn = match self.update_aprom(binary) { + Err(err) => { + self.progress.aborted(); + return Err(err); + } + Ok(_) => self.rpn.get(), + }; + + // Reset and boot from application + { + // RUN_APROM + let pn = rpn + 1; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::RUN_APROM; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + } + d.write(&buffer[0..65])?; + // d.read(&mut buffer[0..64])?; + // let _rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + } + + self.rpn.set(0); + Ok(()) + } + + fn update_aprom(self: &Context<'a>, data: Vec) -> Result<()> { + let d = self.device; + let rpn = self.rpn.get(); + let len = data.len(); + + // if length == 0 we are going to only erase + if len == 0 { + self.progress.emit(ProgressEvent::StartedErasing); + } else { + self.progress.started(len as u32); + } + + // UPDATE_APROM + let pn = rpn + 1; + let mut rpn; + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::UPDATE_APROM; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + buffer[8..12].copy_from_slice(&(0_u32).to_le_bytes()); // start address, this is unused + buffer[12..16].copy_from_slice(&(len as u32).to_le_bytes()); + if len == 0 { + buffer[16..].copy_from_slice(&[0xFF_u8; 48]) + } else if len >= 48 { + buffer[16..].copy_from_slice(&data[0..48]); + } else { + buffer[16..][..len].copy_from_slice(&data[0..len]); + buffer[16..][len..].copy_from_slice(&[0xFF_u8; 48][..48 - len]); + } + } + d.write(&buffer[0..65])?; + let checksum = buffer[1..] + .iter() + .fold(0_u16, |sum, &b| sum.wrapping_add(b as u16)); + d.read(&mut buffer[0..64])?; + let rsum = u16::from_le_bytes(buffer[0..2].try_into().unwrap()); + rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + if rsum != checksum { + return Err(error::Error::ChecksumError); + } + if rpn != pn + 1 { + return Err(error::Error::PacketNumberError); + } + + if len == 0 { + self.progress.emit(ProgressEvent::FinishedErasing); + self.rpn.set(rpn); + return Ok(()); + } + + self.progress.erased(); + + let mut pn = rpn + 1; + let mut idx = 48; + while idx < len { + self.progress.programmed(idx as u32); + let buffer = &mut [0_u8; 65]; + { + let buffer = &mut buffer[1..]; + buffer[0] = nu_isp_cmd::DATA_PACKET; + buffer[4..8].copy_from_slice(&pn.to_le_bytes()); + let len = std::cmp::min(len - idx, 56); + buffer[8..][..len].copy_from_slice(&data[idx..][..len]); + } + d.write(&buffer[0..65])?; + let checksum = buffer[1..] + .iter() + .fold(0_u16, |sum, &b| sum.wrapping_add(b as u16)); + d.read(&mut buffer[0..64])?; + let rsum = u16::from_le_bytes(buffer[0..2].try_into().unwrap()); + rpn = u32::from_le_bytes(buffer[4..8].try_into().unwrap()); + if rsum != checksum { + return Err(error::Error::ChecksumError); + } + if rpn != pn + 1 { + return Err(error::Error::PacketNumberError); + } + + pn = rpn + 1; + idx += 56; + } + self.progress.finished(); + + self.rpn.set(rpn); + return Ok(()); + } +}