commit 69cfc3cd4a9858d2a702faa40a168e5ac41c7945 Author: Casey Connolly Date: Thu Apr 2 23:27:13 2026 +0200 Initial commit Code dump with a few minor improvements. Let's develop here in the open and keep register maps in a private repo. Signed-off-by: Casey Connolly diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e0c019 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +src/html/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..231d575 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1203 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[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", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +dependencies = [ + "backtrace", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[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 = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dyn-clone" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[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 = "errno" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[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 = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[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", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45786cec4d5e54a224b15cb9f06751883103a27c19c93eda09b0b4f5f08fefac" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "serde", +] + +[[package]] +name = "logos" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 2.0.38", +] + +[[package]] +name = "logos-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + +[[package]] +name = "prettytable" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" +dependencies = [ + "csv", + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + +[[package]] +name = "proc-macro2" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +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 = "range-overlap" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306d64753c3718cf83da998dc175552739f66e8a816f50e98960f818e145480b" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "regwatch" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "clap", + "clap-num", + "colored", + "env_logger", + "log", + "logos", + "num-traits", + "parking_lot", + "prettytable", + "range-overlap", + "requestty", + "schemars", + "serde", + "serde_json", + "term_size", +] + +[[package]] +name = "requestty" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa883a1f3e288e65187f653e6ba2e84fdf810fe02f4c8074f9c723d1aa26e2ae" +dependencies = [ + "requestty-ui", + "shell-words", + "smallvec", + "tempfile", + "winsplit", +] + +[[package]] +name = "requestty-ui" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7549bab39cf982b629b68e7ec191a5574e85086e95c0ebe514c02d3b42ffe225" +dependencies = [ + "bitflags 1.3.2", + "crossterm", + "once_cell", + "textwrap", + "unicode-segmentation", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schemars" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[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 = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[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 = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[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" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winsplit" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab703352da6a72f35c39a533526393725640575bb211f61987a2748323ad956" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..eabfb07 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "regwatch" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { version = "1.0.71", features = ["backtrace"] } +chrono = "0.4.26" +clap = { version = "4.1.6", features = ["derive"] } +clap-num = "1.0.2" +colored = "2.0.4" +env_logger = "0.10.0" +log = { version = "0.4.19", features = ["serde", "std"] } +logos = "0.13.0" +num-traits = "0.2.16" +parking_lot = { version = "0.12.1", features = ["arc_lock"] } +prettytable = "0.10.0" +range-overlap = "0.0.1" +requestty = "0.5.0" +schemars = "0.8.15" +serde = { version = "1.0.164", features = ["derive"] } +serde_json = "1.0.97" +term_size = "0.3.2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..5189aa5 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Interactive register decoding for Qualcom SoCs + +This is a collection of tools for parsing the .FLAT and .per files which are +commonly included in Qualcomm chipcode releases. These files include reasonably +comprehensive register maps for the main SoC as well as co-processors and PMICs. +However, they are intended to be used with proprietary tools like Trace32, +having a much higher barrier of entry. + +![example output](images/example.jpg) + +## Usage + +### regdump + +The regdump tool implements register decoding for a given JSON register map. It +can be used to decode a single register with the `decode` command, however due +to the massive size of the register maps this can be quite slow (on my machine +is takes ~750ms to load the SDM845 register map). + +Instead, it can be run with no arguments to enter an interactive prompt, in this +mode you're prompted for address/value pairs until you quit, this is much faster as the JSON parsing only has to be done once. + +```txt +Run with no command for interactive mode. This is recommended +to avoid having to reload large JSON files on every invocation + +Usage: regdump [OPTIONS] --json [COMMAND] + +Commands: + decode Decode an address/value pair + parse Parse a file of address/value pairs each on their own line + print Print a list of registers and modules matching the given substring + codegen Create a header that can be used to decode registers at runtime (e.g. in U-Boot/Linux) for debugging + help Print this message or the help of the given subcommand(s) + +Options: + -j, --json Path to JSON register description (generate with reg2json) + -d, --debug Enable debug logs + -w, --max-width Override max table width in characters + -h, --help Print help + -V, --version Print version +``` + +#### Codegen + +Regdump codegen can be used to generate C code for runtime-decoding of register +values to assist with driver development. The generated code is written to +stdout. At runtime just call `regdump_decode(u64 addr, u32 val)` or +`regdump_read_decode(u64 addr)`. + +Due to the size of the register maps, it isn't suitable to include all possible +registers in the output, so the list of peripherals to include must be specified +on the cmdline. + +Some configuration can be adjusted by changing the macros at the top of the +generated output, for example to use the correct print function for the +platform. + +Example usage: + +```sh +regdump --json socs/example.json codegen DEMO_BLOCK_REGS > example_regdump.c +``` + +### reg2json + +reg2json is a tool for converting the `.FLAT` and `.per` file formats into a +standard json representation that can be used by other tools in this repo. The schema for the +resulting JSON files can be found in +[schemas/hwio_u64.json](/schemas/hwio_u64.json). + +> **WARNING:** .FLAT file support is known to have bugs, it may miss registers! + +```txt +Usage: reg2json [OPTIONS] --out + +Options: + -f, --flat Path to FLAT file address map + -e, --per Path to TRACE32 PER file address map + -o, --out Path to save JSON file as + -d, --debug Enable debug logs + -h, --help Print help + -V, --version Print version +``` + +## Finding HWIO register maps + +The .per files which document the register maps for a given SoC can usually be +found in a chipcode release. Do `find . -name "*.per"` and feed it to +`reg2json`. + +## Cross Compiling + +Install [cross](https://github.com/cross-rs/cross) + +```sh +# Build with glibc +cross build --target aarch64-unknown-linux-gnu --release +# OR musl +cross build --target aarch64-unknown-linux-musl --release +``` diff --git a/images/example.jpg b/images/example.jpg new file mode 100644 index 0000000..651008b Binary files /dev/null and b/images/example.jpg differ diff --git a/regdump-print b/regdump-print new file mode 100755 index 0000000..48b27f4 Binary files /dev/null and b/regdump-print differ diff --git a/schemas/hwio_u64.json b/schemas/hwio_u64.json new file mode 100644 index 0000000..bbec8cf --- /dev/null +++ b/schemas/hwio_u64.json @@ -0,0 +1,146 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HWIO register map", + "type": "object", + "required": [ + "name", + "peripherals" + ], + "properties": { + "name": { + "type": "string" + }, + "peripherals": { + "type": "array", + "items": { + "$ref": "#/definitions/Peripheral_for_uint64" + } + } + }, + "definitions": { + "FieldValue_for_uint64": { + "type": "object", + "required": [ + "applied", + "name", + "value" + ], + "properties": { + "applied": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "Field_for_uint64": { + "type": "object", + "required": [ + "mask", + "name", + "values" + ], + "properties": { + "mask": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "name": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "$ref": "#/definitions/FieldValue_for_uint64" + } + } + } + }, + "Peripheral_for_uint64": { + "type": "object", + "required": [ + "addr", + "name", + "registers" + ], + "properties": { + "addr": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "name": { + "type": "string" + }, + "registers": { + "type": "array", + "items": { + "$ref": "#/definitions/Register_for_uint64" + } + } + } + }, + "RegisterPerm": { + "type": "string", + "enum": [ + "Read", + "Write", + "ReadWrite" + ] + }, + "Register_for_uint64": { + "type": "object", + "required": [ + "addr", + "fields", + "name", + "offset", + "perm", + "reset_value", + "width" + ], + "properties": { + "addr": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/Field_for_uint64" + } + }, + "name": { + "type": "string" + }, + "offset": { + "description": "Offset from base", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "perm": { + "$ref": "#/definitions/RegisterPerm" + }, + "reset_value": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "width": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } +} \ No newline at end of file diff --git a/socs/example.json b/socs/example.json new file mode 100644 index 0000000..34397b9 --- /dev/null +++ b/socs/example.json @@ -0,0 +1,112 @@ +{ + "name": "example", + "peripherals": [ + { + "name": "DEMO_BLOCK_REGS", + "addr": 1048576, + "end": 1048612, + "registers": [ + { + "name": "DEMO_REGXY", + "addr": 1048576, + "offset": 0, + "width": 0, + "reset_value": 0, + "perm": "Read", + "fields": [ + { + "name": "Y", + "mask": 16711680, + "values": [] + }, + { + "name": "X", + "mask": 65535, + "values": [] + } + ] + }, + { + "name": "DEMO_BLOCK_ID", + "addr": 1048580, + "offset": 4, + "width": 0, + "reset_value": 0, + "perm": "Read", + "fields": [ + { + "name": "ID", + "mask": 4294967295, + "values": [] + } + ] + }, + { + "name": "DEMO_BLOCK_CFG", + "addr": 1048608, + "offset": 32, + "width": 0, + "reset_value": 0, + "perm": "Read", + "fields": [ + { + "name": "FIELD0", + "mask": 4290772992, + "values": [] + }, + { + "name": "ADDRSPACE", + "mask": 4128768, + "values": [] + }, + { + "name": "CFG1", + "mask": 49152, + "values": [] + }, + { + "name": "CFG2", + "mask": 14336, + "values": [] + }, + { + "name": "ERRCODE", + "mask": 1792, + "values": [] + }, + { + "name": "RSV0", + "mask": 128, + "values": [] + }, + { + "name": "OPCODE", + "mask": 112, + "values": [] + }, + { + "name": "DEVICE", + "mask": 8, + "values": [] + }, + { + "name": "SECURE", + "mask": 4, + "values": [] + }, + { + "name": "ERRSTATUS", + "mask": 2, + "values": [] + }, + { + "name": "ENABLE", + "mask": 1, + "values": [] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/socs/example.per b/socs/example.per new file mode 100644 index 0000000..a2fe1c5 --- /dev/null +++ b/socs/example.per @@ -0,0 +1,49 @@ +config 16. 8. +width 6. +tree "DEMO_BLOCK" + base ad:0x0100000 + tree "DEMO_BLOCK_REGS" + tree "DEMO_REGXY" + group.long 0x0--0x3 "DEMO_REGXY (at 0x0100000, Read)" + line.long 0x0 "VALUE" + textline "BITS" + textline "" + hexmask.long.byte 0x0 0x10--0x17 1 " [23:16] Y = " + textline "" + hexmask.long.word 0x0 0x0--0xf 1 " [15: 0] X = " + tree.end + tree "DEMO_BLOCK_ID" + group.long 0x4--0x7 "DEMO_BLOCK_ID (at 0x0100004, Read)" + line.long 0x0 "VALUE" + textline "BITS" + textline "" + hexmask.long.long 0x0 0x0--0x1f 1 " [31: 0] ID = " + tree.end + tree "DEMO_BLOCK_CFG" + group.long 0x20--0x23 "DEMO_BLOCK_CFG (at 0x0100020, Read)" + line.long 0x0 "VALUE" + textline "BITS" + textline "" + hexmask.long.word 0x0 0x16--0x1f 1 " [31:22] FIELD0 = " + textline "" + hexmask.long.byte 0x0 0x10--0x15 1 " [21:16] ADDRSPACE = " + textline "" + bitfld.long 0x0 0xe--0xf " [15:14] CFG1 = " "0, 1, 2, 3" + textline "" + bitfld.long 0x0 0xb--0xd " [13:11] CFG2 = " "0, 1, 2, 3, 4, 5, 6, 7" + textline "" + bitfld.long 0x0 0x8--0xa " [10: 8] ERRCODE = " "0, 1, 2, 3, 4, 5, 6, 7" + textline "" + bitfld.long 0x0 0x7--0x7 " [ 7] RSV0 = " "0, 1" + textline "" + bitfld.long 0x0 0x4--0x6 " [ 6: 4] OPCODE = " "0, 1, 2, 3, 4, 5, 6, 7" + textline "" + bitfld.long 0x0 0x3--0x3 " [ 3] DEVICE = " "0, 1" + textline "" + bitfld.long 0x0 0x2--0x2 " [ 2] SECURE = " "0, 1" + textline "" + bitfld.long 0x0 0x1--0x1 " [ 1] ERRSTATUS = " "0, 1" + textline "" + bitfld.long 0x0 0x0--0x0 " [ 0] ENABLE = " "0, 1" + tree.end +tree.end diff --git a/src/bin/genschema.rs b/src/bin/genschema.rs new file mode 100644 index 0000000..2b3cfc2 --- /dev/null +++ b/src/bin/genschema.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use clap::Parser; +use regwatch::parsers::Hwio; +use schemars::schema_for; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + #[arg(short, long, help = "Path to output schema")] + pub out: PathBuf, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let mut schema = schema_for!(Hwio); + let metadata = schema.schema.metadata(); + metadata.title = Some("HWIO".into()); + let mut file = File::create(args.out)?; + serde_json::to_writer_pretty(&mut file, &schema)?; + file.flush()?; + + Ok(()) +} diff --git a/src/bin/reg2json.rs b/src/bin/reg2json.rs new file mode 100644 index 0000000..d073bc6 --- /dev/null +++ b/src/bin/reg2json.rs @@ -0,0 +1,56 @@ +use anyhow::{bail, Result}; +use clap::Parser; +use regwatch::parsers::flat::{FlatParser}; +use std::io::Write; +use std::path::PathBuf; +use regwatch::parsers::per::PerParser; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + #[arg(short, long, help = "Path to FLAT file address map")] + pub flat: Option, + #[arg(short = 'e', long, help = "Path to TRACE32 PER file address map")] + pub per: Option, + #[arg(short, long, help = "Path to save JSON file as")] + pub out: PathBuf, + #[arg(short = 'd', long, help = "Enable debug logs", required = false)] + pub debug: bool +} + +fn main() -> Result<()> { + let args = Args::parse(); + setup_logging(args.debug); + + if let Some(p) = args.flat { + FlatParser::new().parse(p)?.save_to_json(&args.out) + } else if let Some(p) = args.per { + PerParser::new().parse(p)?.save_to_json(&args.out) + } else { + bail!("Must specify either --flat or --per"); + } +} + +fn setup_logging(debug: bool) { + if debug { + // #[cfg(debug_assertions)] + ::std::env::set_var("RUST_LOG", "trace"); + } else { + //#[cfg(not(debug_assertions))] + ::std::env::set_var("RUST_LOG", "info"); + } + + env_logger::Builder::from_default_env() + .format(|buf, record| { + let style = buf.default_level_style(record.level()); + + writeln!( + buf, + "{:<10}:{} [{}] {}", + record.file().unwrap_or("unknown"), record.line().unwrap_or(0), + style.value(record.level()), + record.args() + ) + }) + .init(); +} diff --git a/src/bin/regdump.rs b/src/bin/regdump.rs new file mode 100644 index 0000000..0c70943 --- /dev/null +++ b/src/bin/regdump.rs @@ -0,0 +1,711 @@ +use anyhow::{bail, Result}; +use std::error::Error; +use clap::{Parser, Subcommand}; +use num_traits::{Num, sign}; +use core::slice; +use std::cmp::{max, min}; +use std::fs::read_to_string; +use std::ops::Range; +use regwatch::parsers::{Hwio, Peripheral, Register, RegisterWidth}; +use regwatch::parsers::Field; +use std::{io::Write, num::ParseIntError}; +use std::path::PathBuf; +use colored::Colorize; +use prettytable::{Table, Row, Cell, Attr, format}; +use range_overlap::{RangeOverlap, classify_any}; + +static mut START_TIME: i64 = 0; + +/* Validate an unsigned int that is base-16 with or without the 0x prefix. */ +pub fn hex(s: &str) -> Result +where + ::FromStrRadixErr: std::fmt::Display, +{ + const HEX_PREFIX: &str = "0x"; + const HEX_PREFIX_LEN: usize = HEX_PREFIX.len(); + + let result = if s.to_ascii_lowercase().starts_with(HEX_PREFIX) { + T::from_str_radix(&s[HEX_PREFIX_LEN..], 16) + } else { + T::from_str_radix(s, 16) + }; + + match result { + Ok(v) => Ok(v), + Err(e) => Err(format!("{}", e)), + } +} + +#[derive(Parser, Debug)] +#[command(author, version, about = +"Run with no command for interactive mode. This is recommended +to avoid having to reload large JSON files on every invocation", +long_about = None)] +struct Args { + #[arg(short = 'j', long, help = "Path to JSON register description (generate with reg2json)")] + json: PathBuf, + #[arg(short = 'd', long, help = "Enable debug logs", required = false)] + debug: bool, + #[arg(short = 'w', long, help = "Override max table width in characters", required = false)] + max_width: Option, + + // FIXME: specified to `codegen` + #[arg(long, help = "Support debugfs in regdump", required = false)] + linux: bool, + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand, Debug)] +enum Command { + #[command(about = "Decode an address/value pair")] + Decode { + #[arg(value_parser=hex::, help = "Address of the register")] + addr: u64, + #[arg(value_parser=hex::, help = "Value for the register", required = false)] + value: Option, + }, + #[command(about = "Parse a file of address/value pairs each on their own line")] + Parse { + file: PathBuf, + #[arg(short, long, help = "Exit on error", required = false)] + strict: bool + }, + #[command(about = "Print a list of registers and modules matching the given substring")] + Print { + #[arg(help = "Substring/needle to search")] + substring: String, + }, + #[command(about = "Create a C file that can be used to decode registers at runtime (e.g. in U-Boot/Linux) for debugging", subcommand)] + Codegen(CodegenCommand), +} + +/// Parse a single key-value pair +fn parse_range(s: &str) -> Result, Box> +{ + let pos = s + .find(':') + .ok_or_else(|| format!("invalid range: no ` ` found in `{s}`"))?; + let addr: u64 = hex(&s[..pos])?; + let size: u64 = hex(&s[pos + 1..])?; + Ok(Range {start: addr, end: addr + size}) +} + +#[derive(Subcommand, Debug, Clone)] +enum CodegenCommand { + Peripherals { + #[arg(help = "Periphals to dump e.g. 'MDP_INTF_0 MDP_INTF_1', 'list' to show avilable peripherals")] + peripherals: Vec, + }, + // #[command(about = "Address ranges to dump (base, size pairs)")] + Ranges { + #[arg(help = "Address ranges to dump (base, size pairs)", value_parser = parse_range)] + ranges: Vec>, + } +} + +// FIXME: should align with CodegenCommand! +enum CodegenFilter { + Peripherals(Vec), + Ranges(Vec>), +} + +fn top_set_bit(mask: u64) -> u64 { + let mut bit = 1 << 63; + while bit > mask { + bit >>= 1; + } + bit +} + +fn field_get(val: u64, mask: u64) -> u64 { + return (val & mask) >> mask.trailing_zeros() +} + +fn column_title(field: &Field, last_bit: u64) -> (Option, String) + where W: RegisterWidth +{ + let fmask_top_bit = top_set_bit(field.mask.into()); + let mut t1 = None; + let t2; + + if last_bit > fmask_top_bit << 1 { + let hspan = ((field.mask.into() as usize).leading_zeros() - last_bit.leading_zeros()) as usize - 1; + log::trace!("reserved bits before {} // last_bit {:#x}", field.name, last_bit); + if hspan > 1 { + t1 = Some(format!("{} - {}", last_bit.trailing_zeros() - 1, fmask_top_bit.trailing_zeros() + 1)); + } else { + t1 = Some(format!("{}", last_bit.trailing_zeros() - 1)); + } + }; + + t2 = if field.mask.count_ones() > 1 { + format!("{} - {}", fmask_top_bit.trailing_zeros(), field.mask.trailing_zeros()) + } else { + format!("{}", fmask_top_bit.trailing_zeros()) + }; + + (t1, t2) +} + +fn tablify_range(reg: &Register, value: u64, range: &Range) -> Result<()> + where W: RegisterWidth +{ + let mut table = Table::new(); + let format = format::FormatBuilder::new() + .column_separator('│') + .borders('│') + .separators(&[format::LinePosition::Top], + format::LineSeparator::new('─', '┬', '╭', '╮')) + .separators(&[format::LinePosition::Title, format::LinePosition::Intern], + format::LineSeparator::new('─', '┼', '├', '┤')) + .separators(&[format::LinePosition::Bottom], + format::LineSeparator::new('─', '┴', '╰', '╯')) + .padding(1, 1) + .build(); + table.set_format(format); + + // for_each_set_bit(mask, |bit| { + // title_cells.push(Cell::new(&format!("{}", bit.trailing_zeros())).with_style(Attr::Bold)) + // }); + + let mut title_cells: Vec = vec![]; + let mut cells_row1: Vec = Vec::new(); + let mut cells_row2: Vec = Vec::new(); + let mut last_bit: u64 = 0; + + for field in reg.fields.iter().enumerate().filter_map(|(i, f)| + match range.contains(&i) { + true => Some(f), + false => None, + }) { + let fmask_top_bit = top_set_bit(field.mask.into()); + log::trace!("last_bit {:#x}, field {} mask {:#x}", last_bit, field.name, field.mask); + + let (tpad, title) = column_title(&field, last_bit); + + // Handle a gap between two fields + if let Some(tpad) = tpad { + // Insert an empty cell for the bits between two fields + title_cells.push(Cell::new(&tpad).with_style(Attr::Bold)); + cells_row1.push(Cell::new("?")); + + // Generate a mask of the reserved bits between the two fields + let mut mask: u64 = 1 << (last_bit.trailing_zeros() - 1); + while mask.trailing_zeros() > fmask_top_bit.trailing_zeros() { + mask |= mask >> 1; + } + let value = field_get(value, mask); + let col = if value > 0 { + prettytable::color::BRIGHT_RED + } else { + prettytable::color::WHITE + }; + cells_row2.push(Cell::new(&format!("{:#x}", value)).with_style(Attr::ForegroundColor(col))); + } + + let value = field_get(value, field.mask.into()); + let col = if value > 0 { + prettytable::color::BRIGHT_GREEN + } else { + prettytable::color::WHITE + }; + + title_cells.push(Cell::new(&title).with_style(Attr::Bold)); + cells_row1.push(Cell::new(&field.name).with_style(Attr::ForegroundColor(col))); + cells_row2.push(Cell::new(&format!("{:#x}", value)).with_style(Attr::ForegroundColor(col))); + + last_bit = 1 << (field.mask.into() as u64).trailing_zeros() + } + + // let nb = find_next_bit(last_bit, mask); + // if nb > 0 { + // let hspan = (last_bit-1).trailing_ones() as usize; + // cells_row1.push(Cell::new("").with_hspan(hspan)); + // cells_row2.push(Cell::new("").with_hspan(hspan)); + // } + + table.set_titles(Row::new(title_cells)); + table.add_row(Row::new(cells_row1)); + table.add_row(Row::new(cells_row2)); + + table.print_tty(false)?; + + Ok(()) +} + +fn term_width(max_width: Option) -> usize { + if let Some(w) = max_width { + w + } else if let Some((w, _)) = term_size::dimensions() { + w + } else { + 250 + } +} + +fn decode_one(hwio: &Hwio, addr: u64, value: u64, max_width: Option) -> Result<()> + where W: RegisterWidth +{ + let reg = match hwio.find_register(addr) { + Some(reg) => reg, + None => bail!("Couldn't find register @ {:#010x}", addr), + }; + + // println!("{:?}", reg); + + let termwidth = term_width(max_width); + + println!("{} // {}: {}", reg.name.blue().bold(), format!("{:#010x}", addr).bright_blue().bold(), format!("{:#010x}", value).bright_blue()); + + // println!("\n {:#010x}: {}", addr, reg.name); + // println!("val {:#010x} // {}", value, value); + + // We pre-calculate the table width in characters to ensure that we don't + // print a table which is wider than the terminal. + let mut fieldnames_len = 1; + let mut slice_indices: Vec> = vec![]; + let mut last_bit: u64 = 0; + let mut last_idx = 0; + for (i, field) in reg.fields.iter().enumerate() { + let (tpad, title) = column_title(&field, last_bit); + let fmask_top_bit = top_set_bit(field.mask.into()); + let mut field_len = 0; + + if let Some(tpad) = tpad { + field_len += 3; // table padding + field_len += max(tpad.len(), 4); + } + + field_len += 3; // table padding + field_len += max(max(field.name.len(), title.len()), 4); + + if fieldnames_len + field_len > termwidth { + // slice_indices.push(i); + slice_indices.push(last_idx..i); + last_idx = i; + // println!("Split at {} ({}) len {} / {}", i, fmask_top_bit.trailing_zeros(), fieldnames_len, termwidth); + fieldnames_len = field_len; + } else { + fieldnames_len += field_len; + } + + last_bit = 1 << (field.mask.into() as u64).trailing_zeros() + } + slice_indices.push(last_idx..reg.fields.len()); + + for r in slice_indices.iter() { + tablify_range(reg, value, r)?; + } + + // Now we iterate over the field indices we plan to linebreak on, + // create a mask that encompasses the fields before each slice and + // print a table for them + // let mut last_idx = 0; + // let mut last_mask = 0; + // for i in slice_indices.iter() { + // let mut mask: u64 = 0; + // for (j, field) in reg.fields.iter().enumerate() { + // if j < last_idx + 1 || j > *i { + // continue; + // } + + // mask |= field.mask.into(); + // } + + // /* Some bits have multiple definitions, skip repeated ones */ + // mask &= !last_mask; + // tablify_range(reg, value, mask)?; + // last_mask = mask; + + // last_idx = *i; + // } + + println!(); + + Ok(()) +} + +fn interactive(hwio: Hwio, max_width: Option) -> Result<()> + where W: RegisterWidth +{ + println!("Enter address/value pairs separated with any separateor"); + println!("Type 'q' to quit."); + loop { + let prompt = requestty::Question::input("addr-val") + .message(&format!("{}", hwio.name)) + .build(); + + let (addr, val) = match requestty::prompt_one(prompt) { + Ok(s) => { + // SAFETY: Question::input() always returns a string + let s = s.as_string().unwrap(); + if s.starts_with('q') { + println!("Quit!"); + return Ok(()) + } + match parse_addr_val(s) { + Ok(v) => (v[0].0, v[0].1), + Err(e) => { println!("{}", e); continue }, + } + }, + Err(requestty::ErrorKind::Interrupted) => continue, + Err(e) => bail!("{:?}", e), + }; + + decode_one(&hwio, addr, val, max_width)?; + } +} + +fn parse_addr_val(inp: &str) -> Result> { + if inp.len() == 0 { + bail!("No input"); + } + + let pat = [' ', ',', ':', '/']; + + if !inp.contains(&pat) { + bail!("No valid separator found") + } + + let split: Vec<&str> = inp.split_terminator(&pat).filter(|s| s.len() > 0).collect(); + if split.len() < 2 { + bail!("At least two values expected"); + } + + let vals: Result, ParseIntError> = split.iter().map(|s| { + let s = if &s[0..2] == "0x" { + &s[2..] + } else { + s + }; + + u64::from_str_radix(s, 16) + }).collect(); + + let vals = match vals { + Ok(v) => v, + Err(p) => bail!("Failed to parse {:?}", p), + }; + + if vals.len() == 2 { + Ok(vec![(vals[0], vals[1])]) + } else { + let mut addr = vals[0]; + let mut valpairs: Vec<(u64, u64)> = vec![]; + for val in vals[1..].iter() { + valpairs.push((addr, *val)); + addr += 4; + } + + Ok(valpairs) + } +} + +fn gen_headers(hwio: Hwio, args_str: String, debugfs: bool, filter: CodegenFilter) -> Result<()> + where W: RegisterWidth { + + let mut peripherals: Vec<&Peripheral> = vec![]; + + for p in hwio.peripherals.iter() { + if match filter { + CodegenFilter::Peripherals(ref peripheral_names) => { + peripheral_names.iter().any(|pn| *pn == p.name) + }, + // Exclude this peripheral it it doesn't overlap with any of the specified ranges + CodegenFilter::Ranges(ref ranges) => { + ranges.iter().any(|range| classify_any(Some(range.start), Some(range.end), Some(p.addr), Some(p.end), false).has_overlap()) + } + } { + peripherals.push(p); + } + } + + println!(r#"// Auto-generated by regwatch with +// {args_str} + +#include +#include +#include "#); + if debugfs { + println!(r#"#include +// Replace with your print function as appropriate +#define print(s, fmt, ...) do {{ \ + if (s) {{ \ + drm_printf(s, fmt "\n", __VA_ARGS__); \ + }} else {{ \ + pr_debug(fmt "\n", __VA_ARGS__); \ + }} \ + }} while (0) + + +"#); + } else { + println!(r#"#include +// Replace with your print function as appropriate +#define print(s, fmt, ...) printf(fmt "\n", __VA_ARGS__)"#); + } + println!(r#" +// Define to print unknown registers +//#define REGDUMP_PRINT_UNKNOWN + +// Set to true to print registers whos value is 0 +#define REGDUMP_PRINT_WHEN_ZERO false + +// Define to show values in decimal as well as hex +//#define REGDUMP_SHOW_DECIMAL + +#ifndef FIELD_GET +// FIXME: define our own FIELD_GET +#endif + +"#); + + for periph in peripherals.iter() { + println!("// {}: {:<#10x}", periph.name, periph.addr); + println!("#define {}_BASE {:<#10x}", periph.name, periph.addr); + + eprintln!("Including peripheral: {} @ {:<#10x} - {:<#10x}", periph.name, periph.addr, periph.end); + + for reg in periph.registers.iter() { + match filter { + CodegenFilter::Peripherals(_) => { }, + // Exclude this register if it isn't in a range + CodegenFilter::Ranges(ref ranges) => { + if !ranges.iter().any(|range| range.contains(®.addr)) { + continue; + } + } + } + + println!("#define {}__{} ({:#x} + {:#x}) // {:#x}", periph.name, reg.name, periph.addr, reg.offset, reg.addr); + for field in reg.fields.iter() { + // FIXME: use a macro for this!! + println!("#define {}__{}__{}_MASK {:#x}", periph.name, reg.name, field.name, field.mask); + } + println!("\n// {:#x} ({:#x} + {:#x})", reg.addr, periph.addr, reg.offset); + if debugfs { + println!("static void decode_{}__{}(void *printer, u32 val)\n{{", periph.name, reg.name); + } else { + println!("static void decode_{}__{}(u32 val)\n{{", periph.name, reg.name); + } + if reg.fields.len() > 0 { + println!("\tu32 fv;"); + } + println!("\tif (!REGDUMP_PRINT_WHEN_ZERO && !val)"); + println!("\t\treturn;\n"); + println!("\tprint(printer, \"{}: %#x\", val); ", reg.name); + + let mut last_bit: u64 = 0; + + for field in reg.fields.iter() { + let (_, title) = column_title(&field, last_bit); + + println!("\tif ((fv = FIELD_GET({}__{}__{}_MASK, val))) {{", periph.name, reg.name, field.name); + println!("#ifdef REGDUMP_SHOW_DECIMAL"); + println!("\t\tprint(printer, \" {} [{}]: %#x (%u)\", fv, fv);", field.name, title); + println!("#else"); + println!("\t\tprint(printer, \" {} [{}]: %#x\", fv);", field.name, title); + println!("#endif"); + for fv in field.values.iter() { + println!("\t\tif (fv == {:#x})", fv.value); + println!("\t\t\tprint(printer, \" {}\");", fv.name); + } + println!("\t}}"); + + last_bit = 1 << (field.mask as W).trailing_zeros() + } + println!("}}"); + } + } + + if debugfs { + println!(r#" +bool regdump_decode(void *printer, u64 addr, u32 val) +{{"#); + } else { + println!(r#" +bool regdump_decode(u64 addr, u32 val) +{{"#); + } + println!("\tswitch (addr) {{"); + + for periph in peripherals.into_iter() { + println!("// {}: {:<#10x}", periph.name, periph.addr); + + for reg in periph.registers.iter() { + match filter { + CodegenFilter::Peripherals(_) => { }, + // Exclude this register if it isn't in a range + CodegenFilter::Ranges(ref ranges) => { + if !ranges.iter().any(|range| range.contains(®.addr)) { + continue; + } + } + } + + println!("\tcase ({}__{}):", periph.name, reg.name); + if debugfs { + println!("\t\tdecode_{}__{}(printer, val);", periph.name, reg.name); + } else { + println!("\t\tdecode_{}__{}(val);", periph.name, reg.name); + } + println!("\t\tbreak;") + } + } + + println!("\tdefault:"); + println!("#ifdef REGDUMP_PRINT_UNKNOWN"); + println!("\t\tprint(printer, \"Unknown register %#llx\\n\", addr);"); + println!("#endif"); + println!("\t\treturn false;"); + + println!("\t}}"); + println!("\treturn true;"); + println!("}}"); + + if debugfs { + println!(r#" +bool regdump_read_decode(void *printer, u64 addr) +{{ + return regdump_decode(printer, addr, readl((void *)addr)); +}} +"#); + } else { + println!(r#" +bool regdump_read_decode(u64 addr) +{{ + return regdump_decode(addr, readl(addr)); +}} +"#); + } + + Ok(()) +} + +fn main() -> Result<()> { + let args_str = std::env::args().fold("".to_owned(), |mut a, b| { + a.push_str(" "); + a.push_str(&b); + a + }); + let args = Args::parse(); + setup_logging(args.debug); + + log::debug!("Loading HWIO..."); + let hwio: Hwio = Hwio::load_from_json(&args.json)?; + log::debug!("HWIO: \n{:#?}", hwio); + log::trace!("Done loading"); + + match args.command { + Some(c) => match c { + Command::Decode { addr, value } => { + decode_one(&hwio, addr, value.unwrap_or(0), args.max_width)? + } + Command::Parse { file, strict } => { + for (n, line) in read_to_string(file)?.lines().enumerate() { + if line.trim().len() == 0 { + continue; + } + let vals = match parse_addr_val(line) { + Ok(v) => v, + Err(e) => { + println!("Error parsing line {}: '{}': {}", n, line, e); + if strict { + continue; + } else{ + bail!("Parsing failed"); + } + } + }; + + for (addr, val) in vals.iter() { + if let Err(err) = decode_one(&hwio, *addr, *val, args.max_width) { + if strict { + return Err(err); + } else { + log::debug!("Decode failed @ {:#x} ({})", *addr, err); + } + } + } + } + } + Command::Print { substring } => { + let substring = substring.to_ascii_uppercase(); + for peripheral in hwio.peripherals.iter() { + for register in peripheral.registers.iter() { + if register.name.contains(substring.as_str()) { + let periph_len = peripheral.name.len(); + println!("{:<#10x}: {}.{:<3$}", register.addr, peripheral.name.blue(), register.name.bright_blue(), + (64 - periph_len)); + } + } + } + } + Command::Codegen (CodegenCommand::Peripherals { peripherals }) => { + let peripheral_names: Vec = peripherals.iter().map(|p| p.to_ascii_uppercase()).collect(); + if peripheral_names.len() == 0 { + bail!("Expected at least 1 peripheral or 'list [substr]' to list all periphals") + } + if peripheral_names[0] == "LIST" { + let substr: Option<&String> = if peripheral_names.len() > 1 { Some(&peripheral_names[1]) } else { None }; + for peripheral in hwio.peripherals.iter() { + if let Some(substr) = substr { + if !peripheral.name.contains(substr) { + continue; + } + } + println!("{:<#10x}: {}", peripheral.addr, peripheral.name.blue()); + } + return Ok(()); + } + + // let peripherals: Vec<&Peripheral> = hwio.peripherals.iter().filter(|p| { + // peripheral_names.iter().any(|pn| *pn == p.name) + // }).collect(); + + gen_headers(hwio, args_str, args.linux, CodegenFilter::Peripherals(peripheral_names))?; + } + Command::Codegen (CodegenCommand::Ranges { ranges }) => { + gen_headers(hwio, args_str, args.linux, CodegenFilter::Ranges(ranges))?; + } + } + None => interactive(hwio, args.max_width)? + } + + Ok(()) +} + +// fn ticks() -> i64 { +// // SAFETY: this is a single threaded application +// unsafe { +// chrono::offset::Local::now().timestamp_millis() - START_TIME +// } +// } + +fn setup_logging(debug: bool) { + if debug { + ::std::env::set_var("RUST_LOG", "trace"); + } else { + ::std::env::set_var("RUST_LOG", "info"); + } + + // SAFETY: this is a single threaded application + unsafe { + START_TIME = chrono::offset::Local::now().timestamp_millis(); + } + + env_logger::Builder::from_default_env() + .format(|buf, record| { + let style = buf.default_level_style(record.level()); + + writeln!( + buf, + "[{}] {}", + style.value(record.level()), + record.args() + ) + }) + .init(); +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..eb1f08a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ + +pub mod parsers; +pub mod regmap; diff --git a/src/parsers/flat.rs b/src/parsers/flat.rs new file mode 100644 index 0000000..5ac5f39 --- /dev/null +++ b/src/parsers/flat.rs @@ -0,0 +1,706 @@ +use anyhow::{bail, Result}; +use clap::Parser; +use logos::{Lexer, Logos}; + +use std::{ + borrow::Cow, collections::HashMap, fs::File, io::Read, path::PathBuf +}; + +use super::{Hwio, Peripheral, Register, Field, FieldValue, RegisterPerm}; + +type FlatPeripheral = Peripheral; +type FlatRegister = Register; +type FlatField = Field; + +#[derive(Debug, Default, PartialEq)] +enum PeripheralState { + #[default] + _Init, + Offset, + Max, + Apre, +} + +#[derive(Debug, Default, PartialEq)] +enum RegisterState { + #[default] + Address, + ResetValue, + Fields, + FieldValue, +} + +#[derive(Debug, PartialEq)] +enum ParserState { + Base { label: String, addr: Option, size: Option }, + Peripheral(PeripheralState, FlatPeripheral), + Register(RegisterState, FlatRegister), +} + +#[derive(Debug, Default)] +pub struct FlatParser { + modules: Vec, + state: Option, + bases: HashMap, + require_next: Option, + label: Option, + // FIXME: combine these into an enum + // so we don't get too confused about state. + // They ARE mutually exclusive + accumulator: Option, + field: Option, + is_array: bool, + // FIXME: integrate properly into Base somehow (need to avoid state.take() in LiteralHex for base so we can match it?) + size: bool, + //token_stack: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum FlatError { + #[default] + UnexpectedToken, +} + +#[derive(Logos, Debug, PartialEq)] +#[logos(extras = FlatParser)] +#[logos(error = FlatError)] +#[logos(subpattern xdigit = r"[0-9a-fA-FX]")] +#[logos(skip r"([\s\t\n\f]+|--.*)")] +enum Token { + // FIXME: use CoW to reduce copies! + #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*", |lex| lex.slice().to_string())] + Label(String), + + #[token("BASE")] + Base, + + #[token(r"SIZE")] + Size, + + /* Module */ + #[token("MODULE")] + Module, + + #[token("OFFSET")] + Offset, + + #[token("MAX")] + Max, + + #[token("APRE")] + Apre, + /* End Module */ + #[token("ADDRESS")] + Register, + + #[token("ARRAY")] + Array, + + #[regex(r"(R|RW|W)", |lex| { + match lex.slice().to_string().as_str() { + "R" => Some(RegisterPerm::Read), + "RW" => Some(RegisterPerm::ReadWrite), + "W" => Some(RegisterPerm::Write), + _ => None, + } + })] + Perm(RegisterPerm), + + #[token("RESET_VALUE")] + ResetValue, + + #[token("BIT")] + Field, + + #[token("VALUE")] + FieldValue, + + #[regex(r"\[[0-9]+(:[0-9]+)?\]", |lex| { + let s = lex.slice(); + if s.contains(':') { + let mut split = s[1..s.len()-1].split(':'); + if let Some(start) = split.next() { + if let Ok(start) = start.parse::() { + if let Some(end) = split.next() { + if let Ok(end) = end.parse::() { + if let Some(mask) = (1 as i128).checked_shl((start - end + 1) as u32) { + Ok((mask - 1) as u64) + } else { + log::error!("Left shift resulted in overflow: 1 << {} for {}", (start - end + 1), s); + Err(FlatError::UnexpectedToken) + } + } else { + log::error!("Couldn't parse {} as i64 in {}", start, s); + Err(FlatError::UnexpectedToken) + } + } else { + log::error!("No end bit in {}", s); + Err(FlatError::UnexpectedToken) + } + } else { + log::error!("Couldn't parse {} as i64 in {}", start, s); + Err(FlatError::UnexpectedToken) + } + } else { + log::error!("No start bit in {}", s); + Err(FlatError::UnexpectedToken) + } + } else { + Ok(1 << s[1..s.len()-1].parse::().unwrap()) + } + })] + BitMask(u64), + + #[token("=")] + Equals, + + #[token("+")] + Plus, + + #[regex("0[xX](?&xdigit)+", |lex| { + // Sometimes something like "0x0X" is used as a placeholder + // for unknown values. We'll just return 0 in that case. + u64::from_str_radix(&lex.slice()[2..], 16).unwrap_or(0) + })] + LiteralHex(u64), + + #[token("MEM")] + Mem, +} + +fn print_parse_error(path: Option<&PathBuf>, lexer: &Lexer) { + let mut linecount = 1; + let mut charcount = 0; + let mut linechars = 0; + for b in lexer.source().chars() { + if charcount == lexer.span().end { + break; + } + if b == '\n' { + linecount += 1; + linechars = 0; + } + linechars += 1; + charcount += 1; + } + match path { + Some(p) => log::error!("Failed to parse {}:{}:{} (symbol {:?})", p.display(), linecount, linechars, lexer.slice()), + None => log::error!("Failed to parse {}:{}:{} (symbol {:?})", "unknown ", linecount, linechars, lexer.slice()), + } +} + +impl FlatParser { + pub fn new() -> Self { + Self { + modules: Vec::new(), + state: None, + bases: HashMap::new(), + require_next: None, + accumulator: None, + label: None, + field: None, + is_array: false, + size: false, + //token_stack: Vec::new(), + } + } + + fn parse_one(lex: &mut Lexer, t: Token) -> Result<()> { + // Require a token + if let Some(next) = &lex.extras.require_next { + match *next == t { + true => lex.extras.require_next = None, + false => bail!("Expected {:?}, got {:?}", next, t), + } + } + + // Clear the size match toggle if the token isn't relevant + if lex.extras.size { + match t { + Token::LiteralHex(_) => {} + Token::Equals => {} + _ => lex.extras.size = false + } + } + + // State machine + match t { + Token::Label(l) => { + match lex.extras.state.take() { + Some(ParserState::Peripheral(s, mut m)) => { + match s { + PeripheralState::Apre => { + if m.apre.len() == 0 { + // APRE can be empty i guess, be sure to ignore additional properties + if l == "APOST".to_string() || l == "SPRE".to_string() || l == "SPOST".to_string() { + // SPOST is always last so at this point clear the state. + if l != "SPOST".to_string() { + lex.extras.state = Some(ParserState::Peripheral(s, m.clone())); + } + lex.extras.label = Some(l); + } else { + m.apre = l; + lex.extras.modules.push(m); + } + } else { + // We hit another label after setting APRE, just ignore it + } + } + // Ignore these additional properties and keep it rolling... + // PeripheralState::Apost | PeripheralState::Spost => { + // lex.extras.state = Some(ParserState::Peripheral(s, m)); + // } + s => { + if let Some(acc) = &mut lex.extras.accumulator { + match lex.extras.bases.get(&l) { + Some(a) => *acc += a, + None => bail!("Unknown base label: {}", l), + } + } else { + bail!("Accumulator not initialised, missing '='?") + } + lex.extras.state = Some(ParserState::Peripheral(s, m)); + } + } + } + s => { + lex.extras.label = Some(l); + lex.extras.state = s; + } + } + } + Token::Base => { + // BASE might be the register field name... Hacksssss + if lex.extras.label.is_none() || lex.extras.label == Some("RW".to_string()) || lex.extras.label == Some("RETENTION".to_string()) || lex.extras.label == Some("NO_CSR_TEST".to_string()) { + match &lex.extras.state { + Some(ParserState::Register(_, _)) => { + lex.extras.label = Some("BASE".to_string()); + return Ok(()); + } + _ => {} + } + } + + // The logic is handled in LiteralHex + match lex.extras.label.take() { + Some(l) => { + lex.extras.state = Some(ParserState::Base { label: l, addr: None, size: None }); + } + _ => bail!("BASE token without preceding label"), + }; + } + Token::Module => { + // Very very annoying, module can also be a register field + if lex.extras.label == Some("RETENTION".to_string()) { + if let Some(ParserState::Register(s, r)) = lex.extras.state.take() { + lex.extras.label = Some("MODULE".to_string()); + lex.extras.state = Some(ParserState::Register(s, r)) + } + return Ok(()) + } + let l = match lex.extras.label.take() { + Some(l) => l, + _ => bail!("Expected label before MODULE"), + }; + lex.extras.state = Some(ParserState::Peripheral( + PeripheralState::_Init, + FlatPeripheral { + name: l, + addr: 0, + end: 0, + apre: String::new(), + registers: Vec::new(), + }, + )); + + } + Token::Offset => { + match lex.extras.state.take() { + Some(ParserState::Peripheral(PeripheralState::_Init, m)) => { + lex.extras.state = Some(ParserState::Peripheral(PeripheralState::Offset, m)); + lex.extras.require_next = Some(Token::Equals); + } + Some(ParserState::Register(s, r)) => { + // NDFNVUIDF SOMEONE USED OFFSET AS A LABEL + lex.extras.label = Some("OFFSET".to_string()); + lex.extras.state = Some(ParserState::Register(s, r)); + } + s => bail!("Unexpected OFFSET at state: {:?}", s), + }; + } + Token::Max => { + match lex.extras.state.take() { + Some(ParserState::Peripheral(PeripheralState::Offset, mut m)) => { + // Offset will be in the accumulator + m.addr = match lex.extras.accumulator.take() { + Some(a) => a, + None => bail!("MODULE: Missing offset"), + }; + lex.extras.state = Some(ParserState::Peripheral(PeripheralState::Max, m)); + lex.extras.accumulator = Some(0); + } + // MAX might be the register field name + Some(ParserState::Register(s, r)) => { + lex.extras.state = Some(ParserState::Register(s, r)); + lex.extras.label = Some("MAX".to_string()); + return Ok(()); + } + _ => bail!("Unexpected MAX"), + }; + lex.extras.require_next = Some(Token::Equals); + } + Token::Apre => { + match lex.extras.state.take() { + Some(ParserState::Peripheral(PeripheralState::Max, mut m)) => { + // Max will be in the accumulator + m.end = match lex.extras.accumulator.take() { + Some(a) => a, + None => bail!("MODULE: Missing max"), + }; + lex.extras.state = Some(ParserState::Peripheral(PeripheralState::Apre, m)); + } + _ => bail!("Unexpected APRE"), + }; + lex.extras.require_next = Some(Token::Equals); + } + /* Register */ + Token::Register => { + // FIXME: hacky array skip + lex.extras.is_array = false; + let mut false_match = false; + match lex.extras.state.take() { + Some(ParserState::Register(s, r)) => { + match s { + RegisterState::Address => { + bail!("Expected ADDRESS, got REGISTER"); + } + // Of course someone names a register field "ADDRESS" + RegisterState::ResetValue | RegisterState::Fields => { + if lex.extras.label.is_none() { + lex.extras.label = Some("ADDRESS".to_string()); + lex.extras.state = Some(ParserState::Register(s, r.clone())); + false_match = true; + } else { + // bail!("Register with no fields! {}", r.name); + } + } + _ => {} + } + + if !false_match { + // Finished a register and are starting a new one + //log::trace!("Parsed Register: {:#?}\n\n", &r); + let registers = match lex.extras.modules.last_mut() { + Some(m) => &mut m.registers, + None => bail!("Register without module"), + }; + + registers.push(r); + } + } + Some(x) => bail!("Unexpected ADDRESS in state {:?}", x), + _ => {} + } + if !false_match { + let mut r = Register::default(); + r.name = match lex.extras.label.take() { + Some(l) => l, + _ => bail!("Expected label before ADDRESS"), + }; + lex.extras.state = Some(ParserState::Register(RegisterState::Address, r)); + } + } + Token::Perm(p) => match lex.extras.state.take() { + Some(ParserState::Register(RegisterState::Address, mut r)) => { + r.perm = p; + lex.extras.state = Some(ParserState::Register(RegisterState::Address, r)); + } + // FIXME: parse permissions for individual fields + Some(ParserState::Register(RegisterState::Fields, r)) => { + lex.extras.state = Some(ParserState::Register(RegisterState::Fields, r)); + // This could also be a label for a field lol + if lex.extras.label.is_none() { + lex.extras.label = Some(format!("{}", p)); + } + } + Some(ParserState::Register(RegisterState::ResetValue, r)) => { + lex.extras.state = Some(ParserState::Register(RegisterState::ResetValue, r)); + // This could also be a label for a field lol + if lex.extras.label.is_none() { + lex.extras.label = Some(format!("{}", p)); + } + } + s => bail!( + "Unexpected '{}' in state {:?}", + lex.slice(), + s + ), + }, + Token::ResetValue => match lex.extras.state.take() { + Some(ParserState::Register(RegisterState::Address, r)) => { + if let Some(l) = lex.extras.label.take() { + if r.name != l { + bail!("Mismatched label at RESET_VALUE: {} != {}", r.name, l); + } + lex.extras.state = + Some(ParserState::Register(RegisterState::ResetValue, r)); + } else { + bail!("Expected label before RESET_VALUE"); + } + } + _ => bail!("Unexpected RESET_VALUE"), + }, + Token::Field => match lex.extras.state.take() { + Some(ParserState::Register(s, r)) => { + match s { + RegisterState::Address => { + // bail!("Unexpected FIELD before ADDRESS in register '{}' state: {:?}", r.name, Some(ParserState::Register(s, r.clone()))); + // Address is the goddamn LABEL aaa + lex.extras.label = Some("ADDRESS".to_string()); + } + _ => {} + } + let l = match lex.extras.label.take() { + Some(l) => l, + _ => bail!("Expected label before BIT"), + }; + if lex.extras.field.is_some() { + bail!( + "Unexpected BIT after '{}', already parsing field '{}'", + l, + lex.extras.field.as_ref().unwrap().name + ); + } + lex.extras.field = Some(Field { + name: l, + mask: 0, + values: Vec::new(), + }); + lex.extras.state = Some(ParserState::Register(RegisterState::Fields, r)); + } + _ => bail!("Unexpected FIELD"), + }, + Token::BitMask(b) => match lex.extras.state.take() { + Some(ParserState::Register(RegisterState::Fields, mut r)) => { + match lex.extras.field.take() { + Some(mut f) => { + f.mask = b as u8; + r.fields.push(f); + } + _ => bail!("Unexpected BIT_MASK"), + } + lex.extras.state = Some(ParserState::Register(RegisterState::Fields, r)); + } + _ => bail!("Unexpected BIT_MASK"), + }, + Token::FieldValue => { + if lex.extras.label.is_none() { + //log::trace!("Probably 'VALUE' is the label...."); + lex.extras.label = Some(String::from(lex.slice())); + return Ok(()); + } + match lex.extras.state.take() { + Some(ParserState::Register(RegisterState::Fields, r)) => { + match r.fields.last() { + None => bail!("Unexpected 'VALUE', register {} has no fields", r.name), + _ => {} + } + lex.extras.state = Some(ParserState::Register(RegisterState::FieldValue, r)); + } + // VALUE is the name of the register + Some(ParserState::Register(RegisterState::Address, r)) => { + lex.extras.label = Some(String::from(lex.slice())); + lex.extras.state = Some(ParserState::Register(RegisterState::Address, r)); + return Ok(()); + } + _ => bail!("Unexpected 'VALUE' {:?}", lex.extras.state), + } + } + /* Maths */ + Token::Equals => { + match &lex.extras.state { + Some(ParserState::Peripheral(PeripheralState::Offset, _)) => {} + Some(ParserState::Peripheral(PeripheralState::Max, _)) => {} + Some(ParserState::Peripheral(PeripheralState::Apre, _)) => {} + // Match optional SIZE= parameter on BASE + Some(ParserState::Base { label: _, addr: Some(_), size: None }) => {} + _ => { + // ignore APOST and SPOST properties of MODULE + loop { + // size will be cleared in LiteralHex + if lex.extras.size { + break; + } + match lex.extras.label.take() { + Some(l) => { + if l == "APOST".to_string() || l == "SPOST".to_string() || l == "SPRE".to_string() { + break; + } + } + None => {} + } + bail!("Unexpected '=' in state: {:?}", lex.extras.state); + } + } + }; + lex.extras.accumulator = Some(0); + } + Token::Plus => {} + Token::LiteralHex(x) => { + if lex.extras.is_array { + return Ok(()); + } + match lex.extras.state.take() { + // Parsing base address + Some(ParserState::Base { label: l, addr: None, size: _ }) => { + if lex.extras.bases.get(&l).is_some() { + bail!("Base already set: {}", l); + } + lex.extras.bases.insert(l, x); + //log::trace!("\tParsed Base: {} = {:#x}\n\n", l, x); + } + // Parsing base size (currently unused) + Some(ParserState::Base { label: l, addr: Some(_), size: None }) => { + log::trace!("Base {} has size: {:#x}", l, x); + } + Some(ParserState::Peripheral(s, m)) => { + match s { + PeripheralState::_Init => { + bail!("Unexpected literal in module"); + } + PeripheralState::Apre => { + bail!("Unexpected literal in module"); + } + _ => {} + } + if let Some(acc) = &mut lex.extras.accumulator { + *acc += x as u64; + } else { + bail!("Accumulator not initialised, missing '='?") + } + lex.extras.state = Some(ParserState::Peripheral(s, m)); + } + Some(ParserState::Register(RegisterState::Address, mut r)) => { + r.offset = x as u64; + r.addr = x + match lex.extras.modules.last() { + Some(m) => { + //log::trace!("Register {} in module {}", r.name, m.name); + m.addr + } + None => bail!("Missing module"), + }; + lex.extras.state = Some(ParserState::Register(RegisterState::Address, r)); + } + Some(ParserState::Register(RegisterState::ResetValue, mut r)) => { + r.reset_value = x as u8; + lex.extras.state = + Some(ParserState::Register(RegisterState::ResetValue, r)); + } + Some(ParserState::Register(RegisterState::FieldValue, mut r)) => { + match r.fields.last_mut() { + Some(f) => { + let l = match lex.extras.label.take() { + Some(l) => l, + None => bail!( + "Missing label for value in field {}, register {}", + f.name, + r.name + ), + }; + f.values.push(FieldValue { + name: l, + value: x as u8, + applied: false, + }); + lex.extras.state = + Some(ParserState::Register(RegisterState::Fields, r)); + } + None => bail!("Missing field"), + } + } + _ => { + // FIXME: hack around SIZE argument, ignore it for now. + if lex.extras.size { + lex.extras.size = false; + } else { + bail!("Unexpected literal in state: {:?}", lex.extras.state) + } + } + }; + } + Token::Array => { + /* TODO */ + lex.extras.is_array = true; + } + // BASE size (implicitly parsed by state) + Token::Size => { + lex.extras.size = true; + if lex.extras.label.is_none() { + lex.extras.label = Some("SIZE".to_string()); + } + } + Token::Mem => { + } + } + //lex.extras.token_stack.push(t); + Ok(()) + } + + pub fn parse(self, path: PathBuf) -> Result> { + let mut file = File::open(&path)?; + let mut buf = String::new(); + file.read_to_string(&mut buf)?; + + let mut lexer = Token::lexer_with_extras(&buf, self); + + while let Some(t) = lexer.next() { + let t = match t { + Ok(Token::Mem) => break, + Ok(t) => t, + Err(_e) => { + //log::trace!("Error parsing token: {:?}", e); + continue; // FIXME + //bail!("Couldn't parse token {:?}\n{:#?}", e, lexer.extras) + } + }; + log::trace!("Token: {:?}, '{}' State: {:?}", t, lexer.slice(), lexer.extras.state); + //log::trace!("Token: {:?}, '{}', {:?}", t, lexer.slice(), lexer.extras); + // let should_drop_label = match t { + // Token::Module => false, + // Token::Base => false, + // Token::Register => false, + // Token::ResetValue => false, + // Token::Field => false, + // _ => false, + // }; + match Self::parse_one(&mut lexer, t) { + Ok(_) => {} + Err(e) => { + + print_parse_error(Some(&path), &lexer); + return Err(e); + + // bail!( + // "\nError parsing file: {:?}\n\n\nFailed at '{}'", + // e, + // lexer.slice() + // ) + }, + } + // if should_drop_label { + // lexer.extras.label = None; + // } + } + + let flat = Hwio { + name: path.file_stem().unwrap().to_str().unwrap().to_string(), + peripherals: lexer.extras.modules, + range: 0..0, + addrs: vec![], + changed_at: 0, + used_modules: vec![], + }; + + Ok(flat) + } +} diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs new file mode 100644 index 0000000..c1c3227 --- /dev/null +++ b/src/parsers/mod.rs @@ -0,0 +1,271 @@ +use anyhow::{bail, Result}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use std::{ + fmt::{Display, LowerHex}, + fs::File, + io::{Read, Write}, + ops::Range, + path::PathBuf, +}; +use num_traits::{Zero, PrimInt}; +use std::fmt::Debug; + +pub mod flat; +pub mod per; + +pub trait RegisterWidth: Zero + std::cmp::PartialEq + PrimInt + Serialize + Default + LowerHex + Into + Debug { } + +impl RegisterWidth for u8 { } +impl RegisterWidth for u16 { } +impl RegisterWidth for u32 { } +impl RegisterWidth for u64 { } + +#[derive(Debug, Serialize, Deserialize, JsonSchema)] +pub struct Hwio where W: RegisterWidth { + pub name: String, + pub peripherals: Vec>, + #[serde(skip)] + pub range: Range, + #[serde(skip)] + pub addrs: Vec, + #[serde(skip)] + pub changed_at: u64, + #[serde(skip)] + pub used_modules: Vec>, +} + +impl<'de, W> Hwio where W: RegisterWidth + DeserializeOwned { + pub fn load_from_json(path: &PathBuf) -> Result { + let mut file = + File::open(path).map_err(|e| anyhow::anyhow!("Failed to open file: {}", e))?; + let mut contents = vec![]; + file.read_to_end(&mut contents)?; + let flat: Hwio = serde_json::from_slice(&contents)?; + Ok(flat) + } +} + +impl Hwio where W: RegisterWidth { + pub fn apply_values(&mut self, values: &Vec<(u64, W)>) -> Result<()> { + if values.is_empty() { + bail!("No values to apply"); + } + self.addrs = values.iter().map(|v| v.0).collect::>(); + //log::trace!("{:?}", values); + + self.range = values[0].0..values[values.len() - 1].0; + // Modules that overlap with the range + self.used_modules = self + .peripherals + .iter_mut() + .filter(|m| { + (m.addr <= self.addrs[0] && m.end > self.addrs[0]) + || (m.addr <= *self.addrs.last().unwrap() + && m.end > *self.addrs.last().unwrap()) + }) + .map(|m| m.clone()) + .collect::>>(); + //log::trace!("Range: {:?}", self.range); + + // log::trace!("Found {:?} registers", regs.size_hint()); + // if regs.size_hint().0 == 0 { + // bail!("No registers found for range {:#x}-{:#x}", self.range.start, self.range.end); + // } + for (addr, val) in values { + //log::trace!("Value: {:#x} = {:#x}", addr, val); + let reg = match self + .used_modules + .iter_mut() + .find_map(|m| m.registers.iter_mut().find(|r| r.addr == *addr)) + { + Some(r) => r, + None => { + //log::warn!("No register found for address {:#x}", addr); + continue; + } + }; + let val: W = *val; + if reg.value != val { + reg.changed_at = chrono::Utc::now().timestamp_millis() as u64; + } + reg.value = val; + //log::trace!("Register: {}", reg.name); + for field in reg.fields.iter_mut() { + field.apply_value(reg.value); + } + } + self.changed_at = chrono::Utc::now().timestamp_millis() as u64; + Ok(()) + } + + pub fn save_to_json(&self, path: &PathBuf) -> Result<()> { + let mut file = File::create(path)?; + serde_json::to_writer(&mut file, self)?; + file.flush()?; + Ok(()) + } + + pub fn find_peripheral(&self, name: &str) -> Option<&Peripheral> { + self.peripherals.iter().find(|p| p.name == name) + } + + pub fn find_register(&self, addr: u64) -> Option<&Register> { + self.peripherals.iter().filter(|p| { + log::debug!("Checking peripheral {}: {:#x}", p.name, p.addr); + p.addr <= addr && match p.registers.last() { + Some(r) => r.addr >= addr, + None => false, + } + }).find_map(|p| { + p.find_register_by_addr(addr) + }) + } +} + +impl Display for Hwio where W: RegisterWidth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let now = chrono::Utc::now().timestamp_millis() as u64; + writeln!(f, "delta T: {}ms", now - self.changed_at)?; + for module in self.used_modules.iter() { + writeln!(f, "module {}", module.name)?; + for reg in module.registers.iter() { + if !self.addrs.contains(®.addr) { + continue; + } + // let (fmt_start, fmt_end) = if false && now - reg.changed_at > 5000 { + // ("\u{001b}[7m", "\u{001b}[0m") + // } else { + // ("", "") + // }; + writeln!( + f, + " {:>#06x} {:#04x} | {:2} ({:#04x}) {:<24}", + reg.addr, reg.value, reg.perm, reg.reset_value, reg.name + )?; + for field in reg.fields.iter() { + if !field.values.is_empty() && !field.values.iter().any(|v| v.applied) { + // There ought to be a value defined for 0x0! + log::warn!( + "No value applied for field {} in reg {:#>6x} {}", + field.name, + reg.addr, + reg.name + ); + } + write!( + f, + " [{}:{}] {:>#04x} | {:<32}", + 7 - field.mask.leading_zeros(), + field.mask.trailing_zeros(), + field.prepare(reg.value), + field.name + )?; + match field.values.iter().find(|v| v.applied) { + Some(v) => write!(f, ": {}", v.name)?, + None => (), + } + writeln!(f)?; + } + writeln!(f)?; + } + } + Ok(()) + } +} + +#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone, JsonSchema)] +pub enum RegisterPerm { + #[default] + Read, + Write, + ReadWrite, +} + +impl Display for RegisterPerm { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RegisterPerm::Read => write!(f, "R"), + RegisterPerm::Write => write!(f, "W"), + RegisterPerm::ReadWrite => write!(f, "RW"), + } + } +} + +#[derive(Debug, PartialEq, Deserialize, Serialize, Clone, JsonSchema)] +pub struct FieldValue where W: RegisterWidth { + pub name: String, + pub value: W, + // Mask + applied: bool, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default, JsonSchema)] +pub struct Field where W: RegisterWidth { + pub name: String, + pub mask: W, + pub values: Vec>, +} + +impl Field where W: RegisterWidth + { + fn apply_value(&mut self, value: W) { + let value: W = self.prepare(value); + for v in &mut self.values { + v.applied = v.value == value; + } + } + + fn prepare(&self, value: W) -> W { + (value & self.mask) >> self.mask.trailing_zeros().try_into().unwrap() + } +} + +#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone, JsonSchema)] +pub struct Register where W: RegisterWidth { + pub name: String, + // Absolute address + pub addr: u64, + /// Offset from base + pub offset: u64, + pub width: u32, + reset_value: W, + // For printing.... + #[serde(skip)] + pub value: W, + #[serde(skip)] + pub changed_at: u64, + pub perm: RegisterPerm, + pub fields: Vec>, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default, JsonSchema)] +pub struct Peripheral where W: RegisterWidth { + pub name: String, + pub addr: u64, + // #[serde(skip)] + pub end: u64, + #[serde(skip)] + pub apre: String, + pub registers: Vec>, +} + +impl Peripheral where W: RegisterWidth { + pub fn new(name: String, addr: u64) -> Peripheral { + Peripheral { + name, + addr, + end: 0, + apre: String::new(), + registers: vec![], + } + } + + pub fn find_register_by_name(&self, name: &str) -> Option<&Register> { + self.registers.iter().find(|r| r.name == name) + } + + pub fn find_register_by_addr(&self, addr: u64) -> Option<&Register> { + log::trace!("Finding register {:#x}", addr); + self.registers.iter().find(|r| r.addr == addr) + } +} diff --git a/src/parsers/per.rs b/src/parsers/per.rs new file mode 100644 index 0000000..e414d22 --- /dev/null +++ b/src/parsers/per.rs @@ -0,0 +1,440 @@ +use anyhow::{bail, Result, anyhow}; +use logos::{Lexer, Logos}; + +use std::{ + fs::File, + io::Read, + path::PathBuf, +}; + +use super::{Peripheral, Hwio, Register, Field, RegisterPerm}; + +type TreeField = Field; +type TreePeripheral = Peripheral; +type TreeRegister = Register; + +// #[derive(Debug, PartialEq)] +// struct TreePeripheral { +// name: String, +// subtrees: Vec>, +// } + +// #[derive(Debug, PartialEq)] +// struct TreePeripheral { +// base: u64, +// name: String, +// registers: Vec>, +// } + +#[derive(Debug, PartialEq, Default)] +struct ParserState { + trees: Vec, + in_copy: bool, +} + +#[derive(Debug, Default)] +pub struct PerParser { + state: ParserState, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum PerError { + #[default] + UnexpectedToken, +} + +#[derive(Debug, PartialEq)] +enum TreeState { + Start, + End, +} + +#[derive(Logos, Debug, PartialEq)] +#[logos(extras = PerParser)] +#[logos(error = PerError)] +#[logos(subpattern xdigit = r"[0-9a-fA-F]")] +#[logos(skip r#"[\s\t\n\f",]+"#)] +enum Token { + #[regex(r"tree(\.end)?", callback = |lex| if lex.slice().contains("end") { TreeState::End } else { TreeState::Start })] + Tree(TreeState), + + #[token("base")] + Base, + + #[token("\"")] + Quote, // The Label token also eats the quotes + + #[token("copy")] + Copy, + + #[regex(r"(Read/Write|Read|Write)( \(Command\))?", |lex| { + match lex.slice().to_string().replace(" (Command)", "").as_str() { + "Read" => Some(RegisterPerm::Read), + "Read/Write" => Some(RegisterPerm::ReadWrite), + "Write" => Some(RegisterPerm::Write), + _ => None, + } + })] + Perm(RegisterPerm), + + #[regex(r"(ad:|at )?0x(?&xdigit)+", |lex| { + let s = lex.slice(); + if s.contains(":") || s.contains("at") { + u64::from_str_radix(&s[5..], 16).unwrap() + } else { + u64::from_str_radix(&s[2..], 16).unwrap() + } + })] + Addr(u64), + + #[regex(r"group\.(long|quad)")] + Group, + + #[regex(r"(hexmask|bitfld)(\.(byte|word|long|quad))+ 0x0")] + Field, + + #[regex(r"0x(?&xdigit)+--0x(?&xdigit)+", |lex| { + let s = lex.slice(); + let mut split = s.split("--"); + let end = split.next().unwrap(); + let end = u128::from_str_radix(&end[2..], 16).unwrap(); + if end > 64 { + // Technically the 0xXX--0xXX syntax is a range and not a bitmask + // but we only care about it in the context where it's used as a bitmask + // soo... + 0 as u64 + } else { + let start = split.next().unwrap(); + let start = u128::from_str_radix(&start[2..], 16).unwrap(); + + let mask: u128 = (1 << (start - end + 1)) - 1; + (mask << end) as u64 + } + })] + BitMask(u64), + + #[regex(r"\[\s*[0-9]+(:\s*[0-9]+)?\]", |lex| { + let s = &lex.slice()[1..lex.slice().len()-1].trim(); + if s.contains(':') { + let mut split = s.split(':'); + let start = split.next().unwrap().trim().parse::().unwrap(); + let end = split.next().unwrap().trim().parse::().unwrap(); + let mask: u128 = (1 << (start - end + 1)) - 1; + mask as u64 + } else { + log::trace!("BitMaskLabel: {} {:?}", s, lex.span()); + let t = s.parse::(); + if !t.is_ok() { + print_parse_error(None, lex); + } + 1 << t.unwrap() + } + })] + BitMaskLabel(u64), // The type of bitmask used in the label string + + #[token("(")] + LParan, + + #[token(")")] + RParan, + + #[token("=")] + Equals, + + #[regex(r#"(line.(long|quad)|textline "\w*")"#)] + Stuff, + + #[regex(r"(config|width)( \d+\.)+")] + Preamble, + + #[regex(r"\d+")] + Digit, + + #[regex(r#"[a-zA-Z_][a-zA-Z0-9_]*"#, priority = 1, callback = |lex| lex.slice().to_string())] + //#[regex(r#""?[\w_]+"#, priority = 2, callback = |_| "nyaa".to_string())] + Label(String), +} + +macro_rules! token { + ($lex:ident) => { + match $lex.next() { + None => bail!("No tokens"), + Some(tok) => { + let tok = tok.map_err(|e| anyhow!("{:?}", e))?; + log::trace!("TOKEN {:?}", tok); + tok + }, + } + }; +} + +macro_rules! token_exp { + ($lex:ident, $exp:pat => $result:expr) => { + { + log::trace!("Expecting {:?}", stringify!($exp)); + match token!($lex) { + $exp => { + //log::trace!("Matched {:?}", $lex.slice()); + $result + }, + tok => { + //log::trace!("Expected {:?}, got {:?}", stringify!($exp), tok); + bail!("Unexpected token {:?}", tok); + }, + } + } + }; + ($lex:ident, $($exp:pat),+) => { + $( + token_exp!($lex, $exp => ()); + )* + }; + ($lex:ident, $($exp:pat => $result:expr$(,)?),+) => { + log::trace!("Expecting {:?}", stringify!($($exp),*)); + match token!($lex) { + $($exp => { + //log::trace!("Matched {:?}", $lex.slice()); + $result + },)+ + tok => { + log::trace!("Unexpected {:?}", tok); + bail!("Unexpected token {:?}", tok); + } + } + }; +} + +fn parse_tree_register(lex: &mut Lexer, tree: &mut TreePeripheral) -> Result<()> { + let mut reg: TreeRegister = Default::default(); + + //token_exp!(lex, Token::Tree(TreeState::Start)); + reg.name = token_exp!(lex, Token::Label(name) => name); + // Match e.g. group.long 0x0--0x3 "APCS_GICNOC_OBS_ID_COREID (at 0x0f11b000, Read)" + // up to... ^ here + loop { + token_exp! { lex, + Token::Group => {}, + Token::BitMask(bytes) => { + //log::error!("Bytes: {:#b} {:?}", bytes, reg); + reg.width = bytes.leading_ones(); // Width in bits + break; + }, + /* Sometimes for a bunch of similar registers there's an additional tree + * level where the first one is defined and the others are all "copy" */ + Token::Tree(TreeState::Start) => { + lex.extras.state.in_copy = true; + log::info!("NESTING!\n"); + parse_tree_register(lex, tree)?; + }, + Token::Tree(TreeState::End) => { + if lex.extras.state.in_copy { + lex.extras.state.in_copy = false; + return Ok(()); // We're done here + } + } + }; + } + token_exp!(lex, Token::Label(_), Token::LParan); // Duplicate of above label + reg.addr = token_exp!(lex, Token::Addr(a) => a); + reg.offset = reg.addr - tree.addr; + reg.perm = token_exp!(lex, Token::Perm(p) => p); + token_exp!(lex, Token::RParan); + + 'outer: loop { + let mut field: TreeField = Default::default(); + loop { + token_exp! { lex, + Token::Copy => { + if !lex.extras.state.in_copy { + bail!("Unexpected copy token"); + } + //log::debug!("Copy"); + // AAAAAAAAA + reg.fields = tree.registers.last().unwrap().fields.clone(); + }, + Token::Stuff => {}, + Token::Label(_) => {}, + Token::Field => {}, + Token::Addr(_) => {}, + Token::Tree(TreeState::End) => { + log::debug!("Tree end at start of field parse"); + break 'outer; + }, + Token::Equals => {}, + Token::BitMask(x) => { + field.mask = x; + break; + } + } + } + + loop { + token_exp! { lex, + Token::Digit => {}, + Token::BitMaskLabel(_) => { break; } + }; + } + let mut done: bool = false; + loop { + token_exp! { lex, + Token::Equals => {}, + Token::Digit => {}, + Token::LParan => {}, + Token::RParan => {}, + Token::Label(l) => { + field.name = l; + }, + Token::Stuff => break, + Token::Tree(TreeState::End) => { + done = true; + break; + }, + } + } + + if !done && field.name.is_empty() { + bail!("Missing field name!"); + } + + field.values = vec![]; // TODO: Parse values + //log::debug!("Parsed field: {:?}", field); + reg.fields.push(field); + + if done { + break; + } + } + + //log::debug!("Parsed register: {:?}", reg); + + tree.registers.push(reg); + Ok(()) +} + +// Layer 2, a register block +// e.g. GICD/GICR +fn parse_tree_peripherals(lex: &mut Lexer) -> Result<()> { + while let Some(tok) = lex.next() { + match tok { + Ok(Token::Base) => {}, + Ok(Token::Tree(TreeState::End)) => break, + Ok(_) => continue, + Err(e) => bail!("{:?}", e), + + } + // loop { + // while let tok = Some(tok) { + + // } + // token_exp!(lex, + // Token::Base => break, + // Token::Tree(TreeState::End) => break 'outer, + // ); + // } + let base = token_exp!(lex, Token::Addr(a) => a); + token_exp!(lex, Token::Tree(TreeState::Start)); + let label = token_exp!(lex, Token::Label(name) => name); + + let mut tree = TreePeripheral::new(label, base); + + while lex.peekable().next_if_eq(&Ok(Token::Tree(TreeState::Start))).is_some() { + parse_tree_register(lex, &mut tree)?; + } + + if let Some(last_reg) = tree.registers.last() { + tree.end = last_reg.addr + 4; + } + + //log::info!("Parsed tree: {:?}", tree); + lex.extras.state.trees.push(tree); + } + + //log::trace!("current slice {:?}", lex.slice()); + + Ok(()) +} + +// Layer 1 +fn parse_root(lex: &mut Lexer) -> Result<()> { + 'toploop: while match lex.next() { + Some(tok) => { + match tok { + Ok(Token::Preamble) => continue 'toploop, + Ok(Token::Tree(TreeState::Start)) => continue 'toploop, + Ok(Token::Label(_)) => true, + Ok(Token::Tree(TreeState::End)) => false, + Ok(_) => continue 'toploop, + Err(e) => bail!("{:?}", e), + } + }, + None => return Ok(()), + } { + parse_tree_peripherals(lex)?; + } + + Ok(()) +} + +fn print_parse_error(path: Option<&PathBuf>, lexer: &Lexer) { + let mut linecount = 1; + let mut charcount = 0; + let mut linechars = 0; + for b in lexer.source().chars() { + if charcount == lexer.span().end { + break; + } + if b == '\n' { + linecount += 1; + linechars = 0; + } + linechars += 1; + charcount += 1; + } + match path { + Some(p) => log::error!("Failed to parse {}:{}:{} (symbol {:?})", p.display(), linecount, linechars, lexer.slice()), + None => log::error!("Failed to parse {}:{}:{} (symbol {:?})", "unknown ", linecount, linechars, lexer.slice()), + } +} + +impl PerParser { + pub fn new() -> Self { + PerParser { state: ParserState::default() } + } + + pub fn parse(self, path: PathBuf) -> Result> { + let mut file = File::open(&path)?; + let mut buf = String::new(); + file.read_to_string(&mut buf)?; + + let mut lexer = Token::lexer_with_extras(&buf, self); + + log::info!("Parsing {}", path.as_os_str().display()); + match parse_root(&mut lexer) { + Ok(_) => (), + Err(e) => { + // yeah this is awful + log::error!("Parser error {}", e); + print_parse_error(Some(&path), &lexer); + if buf.len() - lexer.span().end > 10 { + return Err(e); + } + log::info!("less than 10 bytes away from the end of the file... close enough?"); + } + } + + log::info!("Sorting peripherals"); + lexer.extras.state.trees.sort_by_key(|p| p.addr); + log::info!("Done sorting peripherals"); + + let hwio: Hwio = Hwio { + name:path.file_stem().unwrap().to_str().unwrap().to_string(), + peripherals: lexer.extras.state.trees, + range: 0..0, + addrs: vec![], + changed_at: 0, + used_modules: vec![], + }; + + Ok(hwio) + } +} \ No newline at end of file diff --git a/src/regmap.rs b/src/regmap.rs new file mode 100644 index 0000000..9156957 --- /dev/null +++ b/src/regmap.rs @@ -0,0 +1,152 @@ +use std::{ + fs::File, + io::{Read, Write}, + path::PathBuf, +}; +use anyhow::{bail, Result}; +use num_traits::Num; +use crate::parsers::RegisterWidth; + +pub struct Regmap { + path: PathBuf, + name: String, + only_regs: Option>, + buf: String, +} + +impl Regmap { + pub fn new( + path: PathBuf, + start_addr: u16, + length: u16, + only_regs: Option>, + ) -> Result { + let r = Self { + name: format!( + "{}-{:02x}-{:02x}", + path.file_name().unwrap().to_str().unwrap(), + start_addr, + length + ), + path, + only_regs: match only_regs { + Some(mut r) => { + r.sort(); + Some(r) + } + None => None, + }, + buf: String::with_capacity(4096), + }; + r.set_addr(start_addr as u32)?; + r.set_length(length as u32)?; + Ok(r) + } + + fn read_prop(&self, prop: &str) -> Result { + let mut path = self.path.clone(); + path.push(prop); + //log::trace!("Reading {}", path.display()); + let mut file = File::open(path)?; + let mut buf = String::new(); + file.read_to_string(&mut buf)?; + //log::trace!("Read: {}", buf); + let buf = buf.trim_start_matches("0x"); + u64::from_str_radix(buf.trim(), 16).map_err(|e| e.into()) + } + + fn write_prop(&self, prop: &str, val: u32, file: Option) -> Result { + let mut file = match file { + None => { + let mut path = self.path.clone(); + path.push(prop); + if !path.exists() { + bail!("{} does not exist", path.display()); + } + File::create(path)? + } + Some(f) => f, + }; + file.write_all(val.to_string().as_bytes())?; + Ok(file) + } + + pub fn set_addr(&self, addr: u32) -> Result<()> { + self.write_prop("address", addr, None)?; + Ok(()) + } + + pub fn set_length(&self, length: u32) -> Result<()> { + self.write_prop("count", length, None)?; + Ok(()) + } + + pub fn addr(&self) -> Result { + self.read_prop("address") + } + + pub fn length(&self) -> Result { + self.read_prop("count") + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn read(&mut self, regs: &mut Vec<(u64, W)>) -> Result<()> where W: RegisterWidth, ::FromStrRadixErr: std::fmt::Debug { + let mut path = self.path.clone(); + path.push("data"); + let mut file = File::open(path)?; + let mut addr = self.addr()?; + let mut retry = 0; + 'outer: loop { + retry += 1; + self.buf.clear(); + file.read_to_string(&mut self.buf)?; + let mut lines = self.buf.lines(); + loop { + let line = match lines.next() { + Some(l) => { + retry = 0; + l + } + None => { + if retry > 3 { + break 'outer; + } + std::thread::sleep(std::time::Duration::from_millis(2)); + continue 'outer; + } + }; + //log::trace!("Line: {}", line); + if line.contains("XX") { + addr += 1; + continue; + } + let ndx = if let Some(ndx) = line.find(':') { + ndx + } else { + log::debug!("Couldn't parse line: {}", line); + continue; + }; + let (_addr, val) = line.split_at(ndx); + let _addr = u64::from_str_radix(_addr, 16)?; + if addr != _addr { + log::debug!("Address mismatch: {} != {}", addr, _addr); + } + //log::trace!("{}: {:08x}", addr, val); + if let Some(only) = self.only_regs.as_ref() { + if only.binary_search(&(_addr as u16)).is_err() { + addr += 1; + continue; + } + } + let val = val.trim_start_matches(':').trim(); + let val = W::from_str_radix(val, 16).unwrap(); + regs.push((_addr, val)); + addr += 1; + } + } + Ok(()) + } +}