dump of the functional dt-schema validator

this is slow but it works.

Change-Id: Ifdf94271680786c28365d861a1a0f13215378f8d
diff --git a/third_party/rust_crates/forks/dt-schema/Cargo.lock b/third_party/rust_crates/forks/dt-schema/Cargo.lock
new file mode 100644
index 0000000..9f5bf808
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/Cargo.lock
@@ -0,0 +1,938 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef"
+dependencies = [
+ "psl",
+ "psl-types",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+dependencies = [
+ "memchr",
+]
+
+[[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 = "anyhow"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
+
+[[package]]
+name = "argh"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c375edecfd2074d5edcc31396860b6e54b6f928714d0e097b983053fac0cabe3"
+dependencies = [
+ "argh_derive",
+ "argh_shared",
+]
+
+[[package]]
+name = "argh_derive"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa013479b80109a1bf01a039412b0f0013d716f36921226d86c6709032fb7a03"
+dependencies = [
+ "argh_shared",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "argh_shared"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "149f75bbec1827618262e0855a68f0f9a7f2edc13faebf33c4f16d6725edb6a9"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bumpalo"
+version = "3.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cc"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
+
+[[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.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "time",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "cxx"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dt-schema"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "argh",
+ "byteorder",
+ "fancy-regex",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "thiserror",
+ "tracing",
+ "tracing-subscriber",
+ "url",
+ "valico",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95b4efe5be9104a4a18a9916e86654319895138be727b229820c39257c30dda"
+dependencies = [
+ "bit-set",
+ "regex",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+
+[[package]]
+name = "js-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-pointer"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fe841b94e719a482213cee19dd04927cf412f26d8dc84c5a446c081e49c2997"
+dependencies = [
+ "serde_json",
+]
+
+[[package]]
+name = "jsonway"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "effcb749443c905fbaef49d214f8b1049c240e0adb7af9baa0e201e625e4f9de"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[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.135"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "psl"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07242622d9f4b9c1a6fe9c2691cf18ee7d34400a5eed2e1668c756bfaea93fb3"
+dependencies = [
+ "psl-types",
+]
+
+[[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "regex"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+
+[[package]]
+name = "ryu"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+
+[[package]]
+name = "scratch"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
+
+[[package]]
+name = "serde"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8613d593412a0deb7bbd8de9d908efff5a0cb9ccd8f62c641e7b2ed2f57291d1"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "syn"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
+dependencies = [
+ "nu-ansi-term",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
+
+[[package]]
+name = "uritemplate-next"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcde98d1fc3f528255b1ecb22fb688ee0d23deb672a8c57127df10b98b4bd18c"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "valico"
+version = "3.6.1-alpha.0"
+dependencies = [
+ "addr",
+ "base64",
+ "chrono",
+ "fancy-regex",
+ "json-pointer",
+ "jsonway",
+ "percent-encoding",
+ "phf",
+ "phf_codegen",
+ "serde",
+ "serde_json",
+ "uritemplate-next",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[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.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+
+[[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.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/third_party/rust_crates/forks/dt-schema/Cargo.toml b/third_party/rust_crates/forks/dt-schema/Cargo.toml
new file mode 100644
index 0000000..000537a
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "dt-schema"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0"
+argh = "0.1.9"
+#jsonschema = {version = "0.16", default-features = false, features = ["draft201909"]}
+valico = {path = "../valico"}
+url = "2.3"
+serde_yaml = "0.9.13"
+serde_json = "1.0.86"
+serde = {version = "1", features = ["derive"]}
+fancy-regex = "0.8"
+thiserror = "1.0"
+tracing = "0.1"
+tracing-subscriber = "0.3"
+byteorder = "1.4"
diff --git a/third_party/rust_crates/forks/dt-schema/README.fuchsia b/third_party/rust_crates/forks/dt-schema/README.fuchsia
new file mode 100644
index 0000000..01e1e6c
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/README.fuchsia
@@ -0,0 +1 @@
+This is not actually third-party code. This is currently the source of truth, but eventually it will exist as a separate repository that is rolled into fuchsia.git.
diff --git a/third_party/rust_crates/forks/dt-schema/README.md b/third_party/rust_crates/forks/dt-schema/README.md
new file mode 100644
index 0000000..c7ca325
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/README.md
@@ -0,0 +1,11 @@
+## dt-schema
+
+This is an implementation of the [dt-schema](https://github.com/devicetree-org/dt-schema) python tool in Rust.
+
+Currently it only supports validating schemas against devicetree blobs.
+
+Example usage:
+
+```
+cargo run validate my-schema.yaml my-other-schema.yaml --dtb my-devicetree.dtb
+```
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree.rs
new file mode 100644
index 0000000..d898796
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree.rs
@@ -0,0 +1,326 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{
+    collections::HashMap,
+    io::{Read, Seek},
+    rc::Rc,
+};
+
+use byteorder::{BigEndian, ByteOrder};
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::{devicetree::parser::Parser, path::JsonPath};
+
+use self::{
+    property::{DevicetreeJsonError, Property},
+    types::PropertyTypeLookup,
+    util::NodeAncestry,
+};
+
+mod fixups;
+mod parser;
+mod property;
+pub mod types;
+mod util;
+
+#[derive(Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum NodeOrProperty {
+    Node(serde_json::Map<String, serde_json::Value>),
+    Property(Vec<u8>),
+}
+
+#[derive(Debug, Clone)]
+/// Represents a node in a devicetree.
+pub struct Node {
+    name: String,
+    properties: Vec<Property>,
+    children: Vec<Rc<Node>>,
+}
+
+impl Node {
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    pub fn properties(&self) -> &Vec<Property> {
+        &self.properties
+    }
+
+    #[tracing::instrument(level = "info", skip_all, fields(name=self.name))]
+    pub fn as_json(
+        self: &Rc<Self>,
+        type_lookup: &dyn PropertyTypeLookup,
+        path: JsonPath,
+        ancestry: NodeAncestry,
+    ) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeJsonError> {
+        let mut ret = serde_json::Map::new();
+        for prop in self.properties.iter() {
+            ret.insert(
+                prop.key().clone(),
+                prop.value_json(&self.name, type_lookup, path.extend(prop.key()))?,
+            );
+        }
+
+        ret = fixups::fixup_node(&self.name, ret, &path, type_lookup, &ancestry)?;
+
+        for node in self.children.iter() {
+            ret.insert(
+                node.name().to_owned(),
+                json!(node.as_json(
+                    type_lookup,
+                    path.extend(node.name()),
+                    ancestry.visit(self.clone()),
+                )?),
+            );
+        }
+
+        let name = if self.name.is_empty() {
+            "/"
+        } else {
+            &self.name
+        };
+
+        ret.insert("$nodename".to_owned(), json!([name]));
+
+        Ok(ret)
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Represents a devicetree.
+pub struct Devicetree {
+    root_node: Rc<Node>,
+    phandle: HashMap<usize, (Rc<Node>, JsonPath)>,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("I/O error")]
+    IoError(#[from] std::io::Error),
+    #[error("Parse error")]
+    ParseError(#[from] parser::ParseError),
+}
+
+impl Devicetree {
+    pub fn from_reader(reader: impl Read + Seek) -> Result<Devicetree, Error> {
+        let mut parser = Parser::new(reader)?;
+        let mut ret = Devicetree {
+            root_node: parser.parse()?,
+            phandle: HashMap::new(),
+        };
+
+        let mut map = HashMap::new();
+        for (item, path) in ret.iter() {
+            if let Some(phandle) = item
+                .properties()
+                .iter()
+                .find(|prop| prop.key() == "phandle")
+            {
+                map.insert(BigEndian::read_u32(&phandle.value) as usize, (item, path));
+            }
+        }
+
+        ret.phandle = map;
+
+        Ok(ret)
+    }
+
+    pub fn iter(&self) -> Iter {
+        Iter::new(self.root_node.clone())
+    }
+
+    pub fn as_json(
+        &self,
+        lookup: &dyn PropertyTypeLookup,
+    ) -> Result<serde_json::Value, DevicetreeJsonError> {
+        self.root_node
+            .as_json(lookup, JsonPath::new(), NodeAncestry::new(self))
+            .map(|v| v.into())
+    }
+
+    pub fn by_phandle(&self, phandle: usize) -> Option<&(Rc<Node>, JsonPath)> {
+        self.phandle.get(&phandle)
+    }
+}
+
+pub struct Iter {
+    node_stack: Vec<(Rc<Node>, JsonPath, isize)>,
+}
+
+impl Iter {
+    fn new(root_node: Rc<Node>) -> Self {
+        Iter {
+            node_stack: vec![(root_node, JsonPath::new(), -1)],
+        }
+    }
+}
+
+impl Iterator for Iter {
+    type Item = (Rc<Node>, JsonPath);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let (next_node, path, child_index) = self.node_stack.pop()?;
+        // Record that we need to visit our next child at some point.
+        self.node_stack
+            .push((next_node.clone(), path.clone(), child_index + 1));
+
+        // If we haven't started, yield the root node first.
+        if child_index == -1 {
+            return Some((next_node, JsonPath::new()));
+        }
+
+        let child_index: usize = child_index.try_into().unwrap();
+        if next_node.children.len() > child_index {
+            // Visit the next child of |next_node| next.
+            let child = &next_node.children[child_index];
+            let child_path = path.extend(child.name());
+            self.node_stack.push((child.clone(), child_path.clone(), 0));
+            Some((next_node.children[child_index].clone(), child_path))
+        } else {
+            // Finished with children of |next_node|, so move up the stack.
+            self.node_stack.pop();
+            self.next()
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::collections::BTreeSet;
+
+    use crate::validator::{dimension::Dimension, property_type::PropertyType};
+
+    use super::*;
+
+    fn empty_node(name: &str) -> Rc<Node> {
+        Rc::new(Node {
+            name: name.to_owned(),
+            properties: vec![],
+            children: vec![],
+        })
+    }
+
+    fn make_prop(name: &str, data: &[u8]) -> Property {
+        Property {
+            key: name.to_owned(),
+            value: data.into(),
+        }
+    }
+
+    #[test]
+    fn test_iterator() {
+        let fake_tree = Devicetree {
+            root_node: Rc::new(Node {
+                name: "".to_owned(),
+                properties: vec![],
+                children: vec![
+                    empty_node("a"),
+                    Rc::new(Node {
+                        name: "b".to_owned(),
+                        properties: vec![],
+                        children: vec![empty_node("c"), empty_node("d")],
+                    }),
+                    empty_node("e"),
+                ],
+            }),
+            phandle: HashMap::new(),
+        };
+
+        let order: Vec<(String, String)> = fake_tree
+            .iter()
+            .map(|(n, p)| (n.name.clone(), p.to_string()))
+            .collect();
+        let expected: Vec<(String, String)> = [
+            ("", "/"),
+            ("a", "/a"),
+            ("b", "/b"),
+            ("c", "/b/c"),
+            ("d", "/b/d"),
+            ("e", "/e"),
+        ]
+        .into_iter()
+        .map(|(a, b)| (a.to_owned(), b.to_owned()))
+        .collect();
+        assert_eq!(order, expected);
+    }
+
+    struct FakeTypeLookup {
+        types: HashMap<String, (BTreeSet<PropertyType>, Option<Dimension>)>,
+    }
+
+    impl PropertyTypeLookup for FakeTypeLookup {
+        fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType> {
+            self.types
+                .get(propname)
+                .map(|v| v.0.clone())
+                .unwrap_or_default()
+        }
+
+        fn get_property_dimensions(&self, propname: &str) -> Option<Dimension> {
+            self.types.get(propname).and_then(|v| v.1)
+        }
+    }
+
+    #[test]
+    fn test_as_json() {
+        let fake_tree = Devicetree {
+            root_node: Rc::new(Node {
+                name: "".to_owned(),
+                properties: vec![
+                    make_prop("test,str-prop", &[b'a', b'b', 0]),
+                    make_prop("test,int16-array-prop", &[0x10, 0x01, 0x20, 0x02]),
+                ],
+                children: vec![
+                    empty_node("a"),
+                    Rc::new(Node {
+                        name: "b".to_owned(),
+                        properties: vec![
+                            make_prop("test,bool-prop", &[]),
+                            make_prop("test,int8-matrix-prop", &[0x10, 0x10, 0x20, 0x20]),
+                        ],
+                        children: vec![empty_node("c"), empty_node("d")],
+                    }),
+                    empty_node("e"),
+                ],
+            }),
+            phandle: HashMap::new(),
+        };
+        let types: HashMap<String, (BTreeSet<PropertyType>, Option<Dimension>)> = vec![
+            ("test,str-prop", ([PropertyType::String], None)),
+            ("test,int16-array-prop", ([PropertyType::Int16Array], None)),
+            ("test,bool-prop", ([PropertyType::Flag], None)),
+            (
+                "test,int8-matrix-prop",
+                ([PropertyType::Uint8Matrix], Some([[1, 0], [2, 2]].into())),
+            ),
+        ]
+        .into_iter()
+        .map(|(k, (i, d))| (k.to_owned(), (BTreeSet::from(i), d)))
+        .collect();
+        let lookup = FakeTypeLookup { types };
+
+        assert_eq!(
+            fake_tree.as_json(&lookup).expect("as json ok"),
+            json!({
+                "$nodename": ["/"],
+                "test,str-prop": ["ab"],
+                "test,int16-array-prop": [[0x1001, 0x2002]],
+                "a": {"$nodename": ["a"]},
+                "b": {
+                    "$nodename": ["b"],
+                    "test,bool-prop": true,
+                    "test,int8-matrix-prop": [[0x10, 0x10], [0x20, 0x20]],
+                    "c": {"$nodename": ["c"]},
+                    "d": {"$nodename": ["d"]}
+                },
+                "e": {
+                    "$nodename": ["e"],
+                }
+            })
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups.rs
new file mode 100644
index 0000000..09615c2
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups.rs
@@ -0,0 +1,113 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+mod addresses;
+mod gpios;
+mod interrupts;
+mod phandle;
+mod utils;
+
+use crate::path::JsonPath;
+
+pub use self::utils::PhandleError;
+
+use super::types::PropertyTypeLookup;
+
+#[derive(thiserror::Error, Debug)]
+pub enum DevicetreeFixupError {
+    #[error("Could not fixup property '{0}' as it was in an unexpected format: {1}")]
+    UnexpectedPropertyFormat(JsonPath, serde_json::Value),
+
+    #[error("Invalid phandle array")]
+    InvalidPhandle(#[from] PhandleError),
+
+    #[error("Missing property {0}")]
+    MissingProperty(JsonPath),
+
+    #[error("JSON error")]
+    JsonError(#[from] serde_json::Error),
+}
+
+/// Trait that is used by fixups to lookup information about the devicetree.
+/// A |DevicetreeLookup| has knowledge of the node it is associated with.
+pub trait DevicetreeLookup {
+    /// Gets value of the given |cell_prop| at |phandle|.
+    fn get_cells_size(
+        &self,
+        phandle_path: JsonPath,
+        phandle: u32,
+        cell_prop: &str,
+    ) -> Result<u32, PhandleError>;
+
+    /// Gets value of |prop| at this node's parent or an ancestor.
+    fn get_prop_from_parents(&self, prop: &str) -> Result<Option<u32>, PhandleError>;
+
+    fn get_cells_prop_for_prop(&self, prop: &str) -> String {
+        match prop {
+            "assigned-clocks" => "#clock-cells",
+            "assigned-clock-parents" => "#clock-cells",
+            "cooling-device" => "#cooling-cells",
+            "interrupts-extended" => "#interrupt-cells",
+            "interconnects" => "#interconnect-cells",
+            "mboxes" => "#mbox-cells",
+            "sound-dai" => "#sound-dai-cells",
+            "msi-parent" => "#msi-cells",
+            "msi-ranges" => "#interrupt-cells",
+            prop => {
+                if prop.ends_with('s') && !prop.contains("gpio") {
+                    let name = "#".to_owned() + &prop[0..prop.len() - 1] + "-cells";
+                    return name;
+                } else {
+                    prop
+                }
+            }
+        }
+        .to_owned()
+    }
+}
+
+pub trait DevicetreeFixup: Sized {
+    /// Make a new instance of this |fixup|. Returns None if the fixup is not applicable to this property.
+    /// Note that |node| is a full device tree node.
+    fn new(
+        nodename: &str,
+        node: &serde_json::Map<String, serde_json::Value>,
+        path: JsonPath,
+        type_lookup: &dyn PropertyTypeLookup,
+    ) -> Result<Option<Self>, DevicetreeFixupError>;
+
+    /// Run the fixup and give the fixed node back.
+    fn fixup(
+        self,
+        lookup: &dyn DevicetreeLookup,
+    ) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeFixupError>;
+}
+
+/// Helper function that does a fixup (if it is applicable, i.e. T::new() returns Ok(Some(...))), or just returns the given |value|.
+#[tracing::instrument(level = "debug", skip_all, fields(fixup=std::any::type_name::<T>(), path=%path))]
+fn do_fixup<T: DevicetreeFixup>(
+    nodename: &str,
+    value: serde_json::Map<String, serde_json::Value>,
+    path: &JsonPath,
+    type_lookup: &dyn PropertyTypeLookup,
+    lookup: &dyn DevicetreeLookup,
+) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeFixupError> {
+    T::new(nodename, &value, path.clone(), type_lookup)?
+        .map(|v| v.fixup(lookup))
+        .unwrap_or(Ok(value))
+}
+
+pub fn fixup_node(
+    nodename: &str,
+    value: serde_json::Map<String, serde_json::Value>,
+    path: &JsonPath,
+    type_lookup: &dyn PropertyTypeLookup,
+    lookup: &dyn DevicetreeLookup,
+) -> Result<serde_json::Map<String, serde_json::Value>, DevicetreeFixupError> {
+    let value = do_fixup::<gpios::GpioFixup>(nodename, value, path, type_lookup, lookup)?;
+    let value = do_fixup::<addresses::AddressFixup>(nodename, value, path, type_lookup, lookup)?;
+    let value = do_fixup::<interrupts::InterruptFixup>(nodename, value, path, type_lookup, lookup)?;
+    let value = do_fixup::<phandle::PhandleFixup>(nodename, value, path, type_lookup, lookup)?;
+    Ok(value)
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/addresses.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/addresses.rs
new file mode 100644
index 0000000..5e68a8e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/addresses.rs
@@ -0,0 +1,252 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::{devicetree::types::PropertyTypeLookup, path::JsonPath};
+
+use super::{utils::get_cells_property_from_json, DevicetreeFixup, DevicetreeFixupError};
+
+pub struct AddressFixup {
+    node: serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+}
+
+impl AddressFixup {
+    // Expect something of the format:
+    // reg = [[<data>...]]
+    // and output something of the format:
+    // reg = [[data...], [data...]]
+    fn fixup_reg(
+        &self,
+        value: &serde_json::Value,
+        address_cells: usize,
+        size_cells: usize,
+    ) -> Result<serde_json::Value, DevicetreeFixupError> {
+        match value {
+            serde_json::Value::Array(array) => {
+                if array.len() != 1 || !array[0].is_array() {
+                    tracing::error!("reg property should be an array containing an array.");
+                    return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                        self.path.clone(),
+                        array.clone().into(),
+                    ));
+                }
+
+                let array = array[0].as_array().unwrap();
+                let new_values: Option<Vec<Vec<u64>>> = array
+                    .chunks(size_cells + address_cells)
+                    .map(|v| v.iter().map(|i| i.as_u64()).collect::<Option<Vec<u64>>>())
+                    .collect();
+                if let Some(new_values) = new_values {
+                    Ok(serde_json::to_value(new_values)?)
+                } else {
+                    Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                        self.path.clone(),
+                        array.clone().into(),
+                    ))
+                }
+            }
+            other => {
+                tracing::error!("reg property should be an array.");
+                Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                    self.path.clone(),
+                    other.clone(),
+                ))
+            }
+        }
+    }
+
+    fn handle_ranges(
+        &self,
+        value: &serde_json::Value,
+        address_cells: usize,
+        prop: &str,
+    ) -> Result<Option<serde_json::Value>, DevicetreeFixupError> {
+        let array = match value {
+            serde_json::Value::Array(array) => {
+                if array.len() != 1 || !array[0].is_array() {
+                    tracing::error!("ranges property should be an array containing an array.");
+                    return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                        self.path.extend(prop),
+                        array.clone().into(),
+                    ));
+                }
+                array[0].as_array().unwrap()
+            }
+            serde_json::Value::Bool(true) => {
+                return Ok(None);
+            }
+            other => {
+                tracing::error!("ranges property should be an array.");
+                return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                    self.path.extend(prop),
+                    other.clone(),
+                ));
+            }
+        };
+
+        let child_address_cells =
+            get_cells_property_from_json(&self.node, &self.path, "#address-cells")?;
+        let child_size_cells = get_cells_property_from_json(&self.node, &self.path, "#size-cells")?;
+
+        let new_values: Option<Vec<Vec<u64>>> = array
+            .chunks(
+                (child_size_cells + child_address_cells + address_cells as u64)
+                    .try_into()
+                    .unwrap(),
+            )
+            .map(|v| v.iter().map(|i| i.as_u64()).collect::<Option<Vec<u64>>>())
+            .collect();
+        Ok(Some(serde_json::to_value(new_values.ok_or(
+            DevicetreeFixupError::UnexpectedPropertyFormat(
+                self.path.extend(prop),
+                array.clone().into(),
+            ),
+        )?)?))
+    }
+}
+
+impl DevicetreeFixup for AddressFixup {
+    fn new(
+        _nodename: &str,
+        node: &serde_json::Map<String, serde_json::Value>,
+        path: crate::path::JsonPath,
+        _type_lookup: &dyn PropertyTypeLookup,
+    ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+        if node.contains_key("reg")
+            || node.contains_key("ranges")
+            || node.contains_key("dma-ranges")
+        {
+            Ok(Some(AddressFixup {
+                node: node.clone(),
+                path,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn fixup(
+        mut self,
+        lookup: &dyn super::DevicetreeLookup,
+    ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+        let size_cells = lookup.get_prop_from_parents("#size-cells")?.unwrap_or(1) as usize;
+        let address_cells = lookup.get_prop_from_parents("#address-cells")?.unwrap_or(2) as usize;
+
+        if let Some(reg) = self.node.get("reg") {
+            self.node.insert(
+                "reg".to_owned(),
+                self.fixup_reg(reg, address_cells, size_cells)?,
+            );
+        }
+
+        if let Some(ranges) = self.node.get("ranges") {
+            if let Some(new_ranges) = self.handle_ranges(ranges, address_cells, "ranges")? {
+                self.node.insert("ranges".to_owned(), new_ranges);
+            }
+        }
+
+        if let Some(ranges) = self.node.get("dma-ranges") {
+            if let Some(new_ranges) = self.handle_ranges(ranges, address_cells, "dma-ranges")? {
+                self.node.insert("dma-ranges".to_owned(), new_ranges);
+            }
+        }
+
+        Ok(self.node)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use crate::devicetree::fixups::utils::for_tests::FakeLookup;
+
+    use super::*;
+    #[test]
+    fn test_fixup_reg() {
+        let test_node = json!({
+            "reg": [[0x0, 0xdead_beef_u32, 0x0, 0x1000, 0x0, 0x472_0000, 0x0, 0x1_0000]],
+        });
+
+        let test_lookup = FakeLookup::new()
+            .with_parent("#address-cells", 2)
+            .with_parent("#size-cells", 2);
+
+        let result = AddressFixup::new(
+            "",
+            test_node.as_object().unwrap(),
+            JsonPath::new(),
+            &test_lookup,
+        )
+        .expect("valid node")
+        .expect("fixup applies")
+        .fixup(&test_lookup)
+        .expect("fixup ok");
+
+        assert_eq!(
+            serde_json::Value::from(result),
+            json!({
+                "reg": [[0x0, 0xdead_beef_u32, 0x0, 0x1000], [0x0, 0x472_0000, 0x0, 0x1_0000]],
+            })
+        );
+    }
+
+    #[test]
+    fn test_reg_implicit_values() {
+        let test_node = json!({
+            "reg": [[0x0, 0xdead_beef_u32, 0x1000, 0x0, 0x472_0000, 0x1_0000]],
+        });
+        let test_lookup = FakeLookup::new();
+
+        let result = AddressFixup::new(
+            "",
+            test_node.as_object().unwrap(),
+            JsonPath::new(),
+            &test_lookup,
+        )
+        .expect("valid node")
+        .expect("fixup applies")
+        .fixup(&test_lookup)
+        .expect("fixup ok");
+
+        assert_eq!(
+            serde_json::Value::from(result),
+            json!({
+                "reg": [[0x0, 0xdead_beef_u32, 0x1000], [0x0, 0x472_0000, 0x1_0000]],
+            })
+        );
+    }
+
+    #[test]
+    fn test_range_conversion() {
+        let test_node = json!({
+            "#address-cells": [[1]],
+            "#size-cells": [[2]],
+            "ranges": [[0x1000, 0x0, 0x0, 0x0, 0x1000, 0x5000, 0x0, 0x4000, 0x0, 0x800]],
+            "dma-ranges": [[0x10, 0x0, 0x1000, 0x4010, 0x0]],
+        });
+        let test_lookup = FakeLookup::new();
+
+        let result = AddressFixup::new(
+            "",
+            test_node.as_object().unwrap(),
+            JsonPath::new(),
+            &test_lookup,
+        )
+        .expect("valid node")
+        .expect("fixup applies")
+        .fixup(&test_lookup)
+        .expect("fixup ok");
+
+        assert_eq!(
+            serde_json::Value::from(result),
+            json!({
+                "#address-cells": [[1]],
+                "#size-cells": [[2]],
+                "ranges": [[0x1000, 0x0, 0x0, 0x0, 0x1000], [0x5000, 0x0, 0x4000, 0x0, 0x800]],
+                "dma-ranges": [[0x10, 0x0, 0x1000, 0x4010, 0x0]],
+            })
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/gpios.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/gpios.rs
new file mode 100644
index 0000000..50f6fc8
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/gpios.rs
@@ -0,0 +1,103 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use crate::{devicetree::types::PropertyTypeLookup, path::JsonPath};
+
+use super::{
+    utils::{PhandleError, PhandleIterator},
+    DevicetreeFixup, DevicetreeFixupError,
+};
+
+/// |GpioFixup| converts any GPIO properties to matrices.
+pub struct GpioFixup {
+    node: serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+}
+
+fn key_is_gpio(key: &String) -> bool {
+    (key.ends_with("-gpio") || key.ends_with("-gpios") || key == "gpio" || key == "gpios")
+        && !key.ends_with(",nr-gpios")
+}
+
+impl DevicetreeFixup for GpioFixup {
+    fn new(
+        _nodename: &str,
+        node: &serde_json::Map<String, serde_json::Value>,
+        path: JsonPath,
+        _type_lookup: &dyn PropertyTypeLookup,
+    ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+        // This fixup applies to nodes with any -gpio properties, but not a gpio-hog one.
+        let applies = node.keys().any(key_is_gpio) && !node.keys().any(|key| key == "gpio-hog");
+        if !applies {
+            Ok(None)
+        } else {
+            Ok(Some(GpioFixup {
+                node: node.clone(),
+                path,
+            }))
+        }
+    }
+
+    fn fixup(
+        mut self,
+        lookup: &dyn super::DevicetreeLookup,
+    ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+        for (key, value) in self.node.iter_mut().filter(|(key, _)| key_is_gpio(key)) {
+            let array = match value
+                .as_array()
+                .filter(|v| v.len() == 1)
+                .and_then(|v| v[0].as_array())
+            {
+                Some(array) => array,
+                None => {
+                    return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                        self.path.extend(key),
+                        value.clone(),
+                    ))
+                }
+            };
+
+            let collected: Result<Vec<VecDeque<u64>>, PhandleError> =
+                PhandleIterator::new(array, lookup, self.path.extend(key), "#gpio-cells").collect();
+            *value = serde_json::to_value(collected?)?;
+        }
+
+        Ok(self.node)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use serde_json::json;
+
+    use crate::devicetree::fixups::utils::for_tests::FakeLookup;
+
+    use super::*;
+
+    #[test]
+    fn test_gpio_translation() {
+        let fake_node = json!({
+            "test-gpios": [[0xface, 0x1, 0x2, 0xbeef, 0x3, 0x4, 0x5]],
+        });
+        let lookup = FakeLookup::new()
+            .with_phandle(0xface, "#gpio-cells", 2)
+            .with_phandle(0xbeef, "#gpio-cells", 3);
+
+        let fixup = GpioFixup::new("", fake_node.as_object().unwrap(), JsonPath::new(), &lookup)
+            .expect("valid dt object")
+            .expect("schema applies")
+            .fixup(&lookup)
+            .expect("fixup ok");
+
+        assert_eq!(
+            Into::<serde_json::Value>::into(fixup),
+            json!({
+                "test-gpios": [[0xface, 0x1, 0x2], [0xbeef, 0x3, 0x4, 0x5]],
+            })
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/interrupts.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/interrupts.rs
new file mode 100644
index 0000000..fe6b28d
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/interrupts.rs
@@ -0,0 +1,210 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use crate::{devicetree::types::PropertyTypeLookup, path::JsonPath};
+
+use super::{utils::get_cells_property_from_json, DevicetreeFixup, DevicetreeFixupError};
+
+pub struct InterruptFixup {
+    value: serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+}
+
+/// Convert a matrix that looks like this:
+/// [[<values>]]
+/// into
+/// [<values>]
+fn flatten_matrix_with_one_row(val: &serde_json::Value) -> Option<&Vec<serde_json::Value>> {
+    val.as_array()
+        .and_then(|v| if v.len() == 1 { v.get(0) } else { None })
+        .and_then(|v| v.as_array())
+}
+
+impl DevicetreeFixup for InterruptFixup {
+    fn new(
+        _nodename: &str,
+        node: &serde_json::Map<String, serde_json::Value>,
+        path: JsonPath,
+        _type_lookup: &dyn PropertyTypeLookup,
+    ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+        if node.iter().any(|(k, v)| {
+            // interrupts and interrupt-map could either be a bytes array (i.e. a 1D array),
+            // or a uint32 matrix (an array of arrays).
+            // a byte array is OK, but this fixup does not apply to byte arrays so we ensure that
+            // the property is a matrix.
+            (k == "interrupts" || k == "interrupt-map") && flatten_matrix_with_one_row(v).is_some()
+        }) {
+            Ok(Some(InterruptFixup {
+                value: node.clone(),
+                path,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn fixup(
+        mut self,
+        lookup: &dyn super::DevicetreeLookup,
+    ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+        let interrupt_cells = if let Some(serde_json::Value::Array(parent)) = self
+            .value
+            .get("interrupt-parent")
+            .and_then(|v| v.as_array())
+            .and_then(|v| v.first())
+        {
+            let phandle = parent.first().and_then(|v| v.as_u64()).ok_or(
+                DevicetreeFixupError::UnexpectedPropertyFormat(
+                    self.path.extend("interrupt-parent"),
+                    parent.clone().into(),
+                ),
+            )?;
+
+            lookup.get_cells_size(
+                self.path.extend("interrupt-parent"),
+                phandle.try_into().unwrap(),
+                "#interrupt-cells",
+            )?
+        } else {
+            match lookup.get_prop_from_parents("interrupt-parent")? {
+                Some(phandle) => {
+                    lookup.get_cells_size(self.path.clone(), phandle, "#interrupt-cells")?
+                }
+                None => 1,
+            }
+        };
+
+        if let Some(interrupts) = self
+            .value
+            .get("interrupts")
+            .and_then(flatten_matrix_with_one_row)
+        {
+            let new_interrupts = interrupts
+                .chunks(interrupt_cells as usize)
+                .map(|v| v.to_vec())
+                .collect::<Vec<Vec<serde_json::Value>>>();
+            self.value.insert(
+                "interrupts".to_owned(),
+                serde_json::to_value(new_interrupts)?,
+            );
+        }
+
+        if let Some(map) = self
+            .value
+            .get("interrupt-map")
+            .and_then(flatten_matrix_with_one_row)
+        {
+            let my_path = self.path.extend("interrupt-map");
+            // interrupt-map is of the format
+            // <child-unit-address> <child-interrupt-specifier> <interrupt-parent> <parent-unit-address> <parent-interrupt-specifier>
+            let mut map = map
+                .iter()
+                .map(|v| v.as_u64())
+                .collect::<Option<VecDeque<u64>>>()
+                .ok_or(DevicetreeFixupError::UnexpectedPropertyFormat(
+                    my_path.clone(),
+                    map.clone().into(),
+                ))?;
+
+            let interrupt_cells =
+                get_cells_property_from_json(&self.value, &self.path, "#interrupt-cells")? as usize;
+            // Note that according to the spec this could actually come from the parent node,
+            // but in practice it appears to always come from our node.
+            let address_cells =
+                get_cells_property_from_json(&self.value, &self.path, "#address-cells")? as usize;
+            let mut ret = vec![];
+            while !map.is_empty() {
+                let phandle = map[interrupt_cells + address_cells].try_into().unwrap();
+                let parent_icells =
+                    lookup.get_cells_size(my_path.clone(), phandle, "#interrupt-cells")? as usize;
+                let parent_acells =
+                    match lookup.get_cells_size(my_path.clone(), phandle, "#address-cells") {
+                        Ok(v) => v as usize,
+                        Err(super::PhandleError::NodeHadNoProperty(..)) => 0,
+                        Err(e) => return Err(e.into()),
+                    };
+
+                let cells = interrupt_cells + address_cells + 1 + parent_acells + parent_icells;
+                ret.push(map.drain(0..cells).collect::<Vec<_>>());
+            }
+
+            self.value
+                .insert("interrupt-map".to_owned(), serde_json::to_value(ret)?);
+        }
+
+        Ok(self.value)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use crate::devicetree::fixups::utils::for_tests::FakeLookup;
+
+    use super::*;
+    #[test]
+    fn test_interrupts() {
+        let value = json!({
+            "interrupt-parent": [[0xcafe]],
+            "interrupts": [[0xa, 0xb, 0xc, 0xd, 0xe, 0xf]],
+        });
+        let lookup = FakeLookup::new().with_phandle(0xcafe, "#interrupt-cells", 3);
+
+        let result = InterruptFixup::new("", value.as_object().unwrap(), JsonPath::new(), &lookup)
+            .expect("node OK")
+            .expect("fixup applies")
+            .fixup(&lookup)
+            .expect("fixup ok");
+
+        assert_eq!(
+            serde_json::Value::from(result),
+            json!({
+                "interrupt-parent": [[0xcafe]],
+                "interrupts": [[0xa, 0xb, 0xc], [0xd, 0xe, 0xf]],
+            })
+        )
+    }
+
+    #[test]
+    fn test_interrupt_map() {
+        let value = json!({
+            "#interrupt-cells": [[1]],
+            "#address-cells": [[1]],
+            "interrupt-map": [[
+                0x0, 0x0, 0xfeed, 0xabc, 0xdef,
+                0x1, 0x0, 0xf00d, 0xaa, 0xbb, 0xcc,
+                0x1, 0x2, 0xcafe, 0xaa,
+            ]],
+        });
+
+        let lookup = FakeLookup::new()
+            .with_phandle(0xfeed, "#interrupt-cells", 1)
+            .with_phandle(0xfeed, "#address-cells", 1)
+            .with_phandle(0xf00d, "#interrupt-cells", 2)
+            .with_phandle(0xf00d, "#address-cells", 1)
+            .with_phandle(0xcafe, "#interrupt-cells", 1);
+
+        let result = InterruptFixup::new("", value.as_object().unwrap(), JsonPath::new(), &lookup)
+            .expect("node OK")
+            .expect("fixup applies")
+            .fixup(&lookup)
+            .expect("fixup ok");
+
+        assert_eq!(
+            serde_json::Value::from(result),
+            json!({
+                "#interrupt-cells": [[1]],
+                "#address-cells": [[1]],
+                "interrupt-map": [
+                    [0x0, 0x0, 0xfeed, 0xabc, 0xdef],
+                    [0x1, 0x0, 0xf00d, 0xaa, 0xbb, 0xcc],
+                    [0x1, 0x2, 0xcafe, 0xaa],
+                ],
+            })
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/phandle.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/phandle.rs
new file mode 100644
index 0000000..fc8e143
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/phandle.rs
@@ -0,0 +1,154 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::{
+    devicetree::types::PropertyTypeLookup, path::JsonPath, validator::property_type::PropertyType,
+};
+
+use super::{utils::PhandleIterator, DevicetreeFixup, PhandleError};
+
+pub struct PhandleFixup {
+    node: serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+    keys_to_visit: Vec<String>,
+}
+
+fn needs_fixup(lookup: &dyn PropertyTypeLookup, key: &str, value: &serde_json::Value) -> bool {
+    let types = lookup.get_property_type(key);
+    #[allow(clippy::if_same_then_else)]
+    if !types.contains(&PropertyType::PhandleArray) {
+        false
+    } else if lookup
+        .get_property_dimensions(key)
+        .map(|v| v.is_fixed())
+        .unwrap_or(false)
+    {
+        // Fixed dimensions, so skip.
+        false
+    } else {
+        // If this has already been fixed up, skip it.
+        !value
+            .as_array()
+            .map(|v| if v.len() == 1 { !v[0].is_array() } else { true })
+            .unwrap_or(true)
+    }
+}
+
+impl DevicetreeFixup for PhandleFixup {
+    fn new(
+        _nodename: &str,
+        node: &serde_json::Map<String, serde_json::Value>,
+        path: crate::path::JsonPath,
+        type_lookup: &dyn PropertyTypeLookup,
+    ) -> Result<Option<Self>, super::DevicetreeFixupError> {
+        let keys_to_visit: Vec<String> = node
+            .iter()
+            .filter(|(k, v)| needs_fixup(type_lookup, k, v))
+            .map(|(k, _)| k.clone())
+            .collect();
+        if !keys_to_visit.is_empty() {
+            Ok(Some(PhandleFixup {
+                node: node.clone(),
+                path,
+                keys_to_visit,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn fixup(
+        mut self,
+        lookup: &dyn super::DevicetreeLookup,
+    ) -> Result<serde_json::Map<String, serde_json::Value>, super::DevicetreeFixupError> {
+        for key in self.keys_to_visit.into_iter() {
+            let value = self.node.get(&key).unwrap().as_array().unwrap()[0]
+                .as_array()
+                .unwrap();
+            let iter = PhandleIterator::new(value, lookup, self.path.extend(&key), &key);
+
+            let translated = iter.collect::<Result<Vec<_>, _>>();
+            match translated {
+                Ok(value) => self.node.insert(key.clone(), serde_json::to_value(value)?),
+                Err(PhandleError::NodeHadNoProperty(..)) => continue,
+                Err(e) => return Err(e.into()),
+            };
+        }
+        Ok(self.node)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use crate::{devicetree::fixups::utils::for_tests::FakeLookup, path::JsonPath};
+
+    use super::*;
+    #[test]
+    fn test_simple_phandle_fixup() {
+        let node = json!({
+            "fudges": [[
+                0xcafe, 0x1, 0x2, 0x3,
+                0xfeed, 0x1,
+                0xd00d, 0x9, 0xa,
+            ]]
+        });
+
+        let lookup = FakeLookup::new()
+            .with_phandle(0xcafe, "#fudge-cells", 3)
+            .with_phandle(0xfeed, "#fudge-cells", 1)
+            .with_phandle(0xd00d, "#fudge-cells", 2)
+            .with_prop_type(
+                "fudges",
+                &[PropertyType::PhandleArray],
+                Some([[1, 0], [1, 0]].into()),
+            );
+
+        let result = PhandleFixup::new("", node.as_object().unwrap(), JsonPath::new(), &lookup)
+            .expect("node ok")
+            .expect("fixup applies")
+            .fixup(&lookup)
+            .expect("fixup ok");
+
+        assert_eq!(
+            serde_json::Value::from(result),
+            json!({
+                            "fudges": [
+                [0xcafe, 0x1, 0x2, 0x3],
+                [0xfeed, 0x1],
+                [0xd00d, 0x9, 0xa],
+            ]
+
+            })
+        )
+    }
+
+    #[test]
+    fn test_interconnects_fixup() {
+        let node = json!({
+            "interconnects": [[
+                0xcafe, 0x1, 0x2, 0x3,
+                0xfeed, 0x1,
+            ]]
+        });
+
+        let lookup = FakeLookup::new()
+            .with_phandle(0xcafe, "#interconnect-cells", 3)
+            .with_phandle(0xfeed, "#interconnect-cells", 1)
+            .with_prop_type(
+                "interconnects",
+                &[PropertyType::PhandleArray],
+                Some([[1, 0], [1, 0]].into()),
+            );
+
+        let result = PhandleFixup::new("", node.as_object().unwrap(), JsonPath::new(), &lookup)
+            .expect("node ok")
+            .expect("fixup applies")
+            .fixup(&lookup)
+            .expect("fixup ok");
+
+        assert_eq!(serde_json::Value::from(result), node);
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/utils.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/utils.rs
new file mode 100644
index 0000000..60ba45c
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/fixups/utils.rs
@@ -0,0 +1,248 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use crate::path::JsonPath;
+
+use super::{DevicetreeFixupError, DevicetreeLookup};
+
+#[derive(thiserror::Error, Debug)]
+pub enum PhandleError {
+    #[error("Invalid value in phandle array at {0}")]
+    InvalidValue(JsonPath),
+    #[error("node referenced by phandle ({1}) had no {0} value")]
+    NodeHadNoProperty(String, JsonPath),
+    #[error("phandle {0:x} is not valid in property {1}")]
+    InvalidPhandle(u32, JsonPath),
+}
+
+/// A |PhandleIterator| takes an array of integer values of the format
+/// [<phandle> <cell>...]... and uses a provided |propname| to determine
+/// the number of cells each phandle expects as arguments.
+/// For instance, take this simple example:
+/// ```
+/// gpio1 {
+///   #gpio-cells = <2>;
+/// }
+///
+/// gpio2 {
+///   #gpio-cells = <1>;
+/// }
+///
+/// my-gpios = <&gpio1 2 3>, <&gpio2 1>
+/// ```
+/// PhandleIterator over "my-gpios" with propname="#gpio-cells" would yield:
+/// [[x, 2, 3], [y, 1]] where x and y are the phandles of gpio1 and gpio2 respectively.
+pub struct PhandleIterator<'a> {
+    array: &'a [serde_json::Value],
+    lookup: &'a dyn DevicetreeLookup,
+    propname: &'a str,
+    path: JsonPath,
+    in_iter: bool,
+}
+
+impl<'a> PhandleIterator<'a> {
+    /// Create a new PhandleIterator.
+    /// |array|: slice of values in the phandle array.
+    /// |lookup|: helper used to lookup phandles in the tree.
+    /// |path|: path (including property name) to the property we are resolving.
+    /// |propname|: name of the property we are resolving.
+    pub fn new(
+        array: &'a [serde_json::Value],
+        lookup: &'a dyn DevicetreeLookup,
+        path: JsonPath,
+        propname: &'a str,
+    ) -> Self {
+        PhandleIterator {
+            array,
+            lookup,
+            propname,
+            path,
+            in_iter: false,
+        }
+    }
+}
+
+impl<'a> Iterator for PhandleIterator<'a> {
+    type Item = Result<VecDeque<u64>, PhandleError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let mut iter = self.array.iter();
+        let phandle: u32 = match iter
+            .next()
+            .map(|v| v.as_u64().and_then(|v| v.try_into().ok()))
+        {
+            Some(Some(p)) => p,
+            Some(None) => return Some(Err(PhandleError::InvalidValue(self.path.clone()))),
+            None => return None,
+        };
+
+        let taken: Option<VecDeque<u64>> = if phandle == 0xffff_ffff {
+            // Calculate the next value.
+            iter.take_while(|cell| cell.as_u64() != Some(0xffff_ffff))
+                .map(|cell| cell.as_u64())
+                .collect()
+        } else {
+            let mut cells = match self.lookup.get_cells_size(
+                self.path.clone(),
+                phandle,
+                &self.lookup.get_cells_prop_for_prop(self.propname),
+            ) {
+                Ok(cells) => cells,
+                Err(e) => return Some(Err(e)),
+            };
+            if self.propname == "msi-ranges" {
+                cells += 1;
+            }
+            iter.take(cells as usize)
+                .map(|cell| cell.as_u64())
+                .collect()
+        };
+
+        match taken {
+            Some(mut ret) => {
+                ret.push_front(phandle.into());
+                self.array = &self.array[ret.len()..];
+                if self.propname == "interconnects" && !self.in_iter {
+                    self.in_iter = true;
+                    // Do it again for interconnects, because it expects
+                    // two values at a time.
+                    let next = self.next();
+                    self.in_iter = false;
+                    match next {
+                        Some(Ok(v)) => ret.extend(v),
+                        Some(Err(e)) => return Some(Err(e)),
+                        None => {
+                            if crate::strict_mode() {
+                                return Some(Err(PhandleError::InvalidValue(self.path.clone())));
+                            } else {
+                                tracing::warn!("interconnects should have a pair of values!");
+                            }
+                        }
+                    }
+                }
+                Some(Ok(ret))
+            }
+            None => Some(Err(PhandleError::InvalidValue(self.path.clone()))),
+        }
+    }
+}
+
+pub fn get_cells_property_from_json(
+    node: &serde_json::Map<String, serde_json::Value>,
+    path: &JsonPath,
+    name: &str,
+) -> Result<u64, DevicetreeFixupError> {
+    match node.get(name) {
+        Some(serde_json::Value::Array(array)) => {
+            if array.len() != 1
+                || !array[0]
+                    .as_array()
+                    .map(|v| v.len() == 1 && v[0].is_u64())
+                    .unwrap_or(false)
+            {
+                return Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+                    path.extend(name),
+                    array.clone().into(),
+                ));
+            }
+
+            let value = array[0].as_array().unwrap()[0].as_u64().unwrap();
+            Ok(value)
+        }
+        Some(not_number) => Err(DevicetreeFixupError::UnexpectedPropertyFormat(
+            path.extend(name),
+            not_number.clone(),
+        )),
+        None => Err(DevicetreeFixupError::MissingProperty(path.extend(name))),
+    }
+}
+
+#[cfg(test)]
+pub mod for_tests {
+    use std::collections::{BTreeSet, HashMap};
+
+    use crate::{
+        devicetree::{fixups::DevicetreeLookup, types::PropertyTypeLookup},
+        path::JsonPath,
+        validator::{dimension::Dimension, property_type::PropertyType},
+    };
+
+    use super::PhandleError;
+
+    pub struct FakeLookup {
+        cells_by_phandle: HashMap<(u32, String), u32>,
+        values_from_parents: HashMap<String, u32>,
+        type_info: HashMap<String, (BTreeSet<PropertyType>, Option<Dimension>)>,
+    }
+
+    impl FakeLookup {
+        pub fn new() -> Self {
+            FakeLookup {
+                cells_by_phandle: HashMap::new(),
+                values_from_parents: HashMap::new(),
+                type_info: HashMap::new(),
+            }
+        }
+
+        /// Registers |prop| for |phandle| to be returned by |get_cells_size|.
+        pub fn with_phandle(mut self, phandle: u32, prop: &str, cells: u32) -> Self {
+            self.cells_by_phandle
+                .insert((phandle, prop.to_owned()), cells);
+            self
+        }
+
+        /// Registers |prop| to be returned by |get_cells_size_parents|.
+        pub fn with_parent(mut self, prop: &str, cells: u32) -> Self {
+            self.values_from_parents.insert(prop.to_owned(), cells);
+            self
+        }
+
+        pub fn with_prop_type(
+            mut self,
+            prop: &str,
+            types: &[PropertyType],
+            dim: Option<Dimension>,
+        ) -> Self {
+            self.type_info
+                .insert(prop.to_owned(), (types.iter().copied().collect(), dim));
+            self
+        }
+    }
+
+    impl DevicetreeLookup for FakeLookup {
+        fn get_cells_size(
+            &self,
+            phandle_path: JsonPath,
+            phandle: u32,
+            cell_prop: &str,
+        ) -> Result<u32, PhandleError> {
+            self.cells_by_phandle
+                .get(&(phandle, cell_prop.to_owned()))
+                .copied()
+                .ok_or_else(|| PhandleError::NodeHadNoProperty(cell_prop.to_owned(), phandle_path))
+        }
+
+        fn get_prop_from_parents(&self, prop: &str) -> Result<Option<u32>, PhandleError> {
+            Ok(self.values_from_parents.get(prop).copied())
+        }
+    }
+
+    impl PropertyTypeLookup for FakeLookup {
+        fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType> {
+            self.type_info
+                .get(propname)
+                .map(|v| v.0.clone())
+                .unwrap_or_default()
+        }
+
+        fn get_property_dimensions(
+            &self,
+            propname: &str,
+        ) -> Option<crate::validator::dimension::Dimension> {
+            self.type_info.get(propname).and_then(|v| v.1)
+        }
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/parser.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/parser.rs
new file mode 100644
index 0000000..a8809c9
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/parser.rs
@@ -0,0 +1,212 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use super::{property::Property, Node};
+use byteorder::{BigEndian, ReadBytesExt};
+use std::{
+    ffi::CStr,
+    io::{BufRead, BufReader, Read, Seek, SeekFrom},
+    rc::Rc,
+};
+
+const DTB_MAGIC: u32 = 0xd00dfeed;
+const DTB_MIN_VERSION: u32 = 16;
+
+#[derive(Debug)]
+pub enum Token {
+    BeginNode(String),
+    EndNode,
+    Prop(Property),
+    Nop,
+    End,
+}
+
+pub struct Parser<R> {
+    reader: BufReader<R>,
+    // TODO(simonshields): validate against this.
+    #[allow(unused)]
+    struct_end: u32,
+
+    strings: Vec<u8>,
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum ParseError {
+    #[error("Invalid magic")]
+    InvalidMagic,
+    #[error("Version is not supported: {0}")]
+    UnsupportedVersion(u32),
+    #[error("Invalid string offset: {0}")]
+    InvalidStringOffset(usize),
+    #[error("String not null-terminated: {0}")]
+    StringNotTerminated(usize),
+    #[error("Invalid string")]
+    InvalidString(#[from] std::ffi::FromBytesWithNulError),
+    #[error("Invalid string encoding")]
+    InvalidStringEncoding(#[from] std::str::Utf8Error),
+    #[error("Invalid token at offset {0:x}: {1:x}")]
+    InvalidToken(u64, u32),
+    #[error("Unexpected token")]
+    UnexpectedToken(Token),
+    #[error("I/O error")]
+    IoError(#[from] std::io::Error),
+}
+
+impl<R: Read + Seek> Parser<R> {
+    pub fn new(reader: R) -> Result<Self, ParseError> {
+        let mut reader = BufReader::new(reader);
+        let magic = reader.read_u32::<BigEndian>()?;
+        if magic != DTB_MAGIC {
+            return Err(ParseError::InvalidMagic);
+        }
+
+        let _ = reader.read_u32::<BigEndian>()?; // totalsize
+
+        let off_dt_struct = reader.read_u32::<BigEndian>()?;
+        let off_dt_string = reader.read_u32::<BigEndian>()?;
+        let _ = reader.read_u32::<BigEndian>()?; // off_mem_rsvmap
+        let version = reader.read_u32::<BigEndian>()?;
+        let last_comp_version = reader.read_u32::<BigEndian>()?;
+        if version < DTB_MIN_VERSION || DTB_MIN_VERSION < last_comp_version {
+            return Err(ParseError::UnsupportedVersion(version));
+        }
+
+        let _ = reader.read_u32::<BigEndian>()?; // boot_cpuid_phys
+
+        let size_dt_string = reader.read_u32::<BigEndian>()?;
+        let size_dt_struct = reader.read_u32::<BigEndian>()?;
+
+        reader.seek(SeekFrom::Start(off_dt_string.into()))?;
+        let mut strings_vec: Vec<u8> = vec![0; size_dt_string.try_into().unwrap()];
+
+        reader.read_exact(strings_vec.as_mut_slice())?;
+
+        reader.seek(SeekFrom::Start(off_dt_struct.into()))?;
+
+        Ok(Parser {
+            reader,
+            struct_end: off_dt_struct + size_dt_struct,
+            strings: strings_vec,
+        })
+    }
+
+    fn offset(&mut self) -> Result<u64, std::io::Error> {
+        self.reader.seek(SeekFrom::Current(0))
+    }
+
+    fn string_at(&self, offset: usize) -> Result<String, ParseError> {
+        if offset > self.strings.len() {
+            return Err(ParseError::InvalidStringOffset(offset));
+        }
+
+        let slice = &self.strings[offset..];
+        let (byte_index, _) = slice
+            .iter()
+            .enumerate()
+            .find(|(_, &v)| v == 0)
+            .ok_or(ParseError::StringNotTerminated(offset))?;
+
+        let slice = &self.strings[offset..byte_index + offset + 1];
+        let c_string = CStr::from_bytes_with_nul(slice)?;
+
+        Ok(c_string.to_str()?.to_owned())
+    }
+
+    /// Align the parser to the next 4 byte boundary.
+    fn align(&mut self) -> Result<(), ParseError> {
+        let offset = self.offset()? as usize;
+        if offset % std::mem::size_of::<u32>() != 0 {
+            let adjust = std::mem::size_of::<u32>() - (offset % std::mem::size_of::<u32>());
+            self.reader
+                .seek(SeekFrom::Current(adjust.try_into().unwrap()))?;
+        }
+        Ok(())
+    }
+
+    fn parse_token(&mut self) -> Result<Token, ParseError> {
+        self.align()?;
+        let token = self.reader.read_u32::<BigEndian>()?;
+
+        let parsed = match token {
+            0x1 => {
+                let mut buf: Vec<u8> = vec![];
+                self.reader.read_until(0, &mut buf)?;
+                if *buf.last().unwrap_or(&1) != 0 {
+                    // TODO(simonshields): make a better error here.
+                    return Err(ParseError::StringNotTerminated(0xffff));
+                }
+
+                let c_string = CStr::from_bytes_with_nul(&buf)?;
+                Token::BeginNode(c_string.to_str()?.to_owned())
+            }
+            0x2 => Token::EndNode,
+            0x3 => {
+                let len = self.reader.read_u32::<BigEndian>()?.try_into().unwrap();
+                let nameoff = self.reader.read_u32::<BigEndian>()?;
+                let key = self.string_at(nameoff.try_into().unwrap())?;
+                let mut value = vec![0; len];
+
+                self.reader.read_exact(value.as_mut_slice())?;
+
+                Token::Prop(Property { key, value })
+            }
+            0x4 => Token::Nop,
+            0x9 => Token::End,
+            t => {
+                return Err(ParseError::InvalidToken(
+                    self.offset()? - std::mem::size_of::<u32>() as u64,
+                    t,
+                ))
+            }
+        };
+
+        Ok(parsed)
+    }
+
+    fn parse_node(&mut self, name: String) -> Result<Rc<Node>, ParseError> {
+        let mut can_end = false;
+        let mut children = vec![];
+        let mut properties = vec![];
+        loop {
+            match self.parse_token()? {
+                Token::BeginNode(name) => {
+                    children.push(self.parse_node(name)?);
+                    can_end = true;
+                }
+                Token::EndNode => break,
+                Token::End => {
+                    if can_end {
+                        self.reader.seek(SeekFrom::Current(-4))?;
+                        break;
+                    } else {
+                        return Err(ParseError::UnexpectedToken(Token::End));
+                    }
+                }
+                Token::Prop(prop) => {
+                    properties.push(prop);
+                    can_end = false;
+                }
+                Token::Nop => {}
+            }
+        }
+
+        Ok(Rc::new(Node {
+            name,
+            children,
+            properties,
+        }))
+    }
+
+    pub fn parse(&mut self) -> Result<Rc<Node>, ParseError> {
+        loop {
+            match self.parse_token()? {
+                Token::BeginNode(name) => {
+                    return self.parse_node(name);
+                }
+                Token::Nop => {}
+                t => return Err(ParseError::UnexpectedToken(t)),
+            }
+        }
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/property.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/property.rs
new file mode 100644
index 0000000..82a0ba8
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/property.rs
@@ -0,0 +1,404 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{
+    collections::BTreeSet,
+    ffi::{CStr, FromBytesWithNulError},
+};
+
+use byteorder::{BigEndian, ByteOrder};
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::property_type::PropertyType};
+
+use super::{fixups::DevicetreeFixupError, types::PropertyTypeLookup};
+
+#[derive(thiserror::Error, Debug)]
+pub enum DevicetreeJsonError {
+    #[error("String was not valid: {1}")]
+    InvalidString(#[source] FromBytesWithNulError, JsonPath),
+
+    #[error("String has non-printable characters")]
+    NonPrintableString(JsonPath),
+
+    #[error("Error serialising JSON value")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Invalid unicode")]
+    Utf8Error(#[from] std::str::Utf8Error),
+
+    #[error("Error performing fixups")]
+    FixupError(#[from] DevicetreeFixupError),
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Property {
+    pub(super) key: String,
+    pub(super) value: Vec<u8>,
+}
+
+impl Property {
+    #[tracing::instrument(level = "info", skip_all, fields(property=self.key))]
+    pub fn value_json(
+        &self,
+        nodename: &str,
+        type_lookup: &dyn PropertyTypeLookup,
+        path: JsonPath,
+    ) -> Result<serde_json::Value, DevicetreeJsonError> {
+        if self.value.is_empty() {
+            return Ok(json!(true));
+        }
+
+        if nodename == "__fixups__" || nodename == "aliases" {
+            return self.as_json_string(path);
+        }
+
+        let mut types = type_lookup.get_property_type(&self.key);
+        types.remove(&PropertyType::Node);
+        if types.len() > 1 {
+            // If we have more than one possible type, try and reduce that set.
+            types = types
+                .iter()
+                .filter(|t| {
+                    // Filter out types which |self.value| is too big for.
+                    t.max_size()
+                        .map(|max| self.value.len() <= max)
+                        .unwrap_or(true)
+                })
+                .copied()
+                .collect::<BTreeSet<_>>();
+        }
+
+        tracing::trace!("Possible types: {:?}", types);
+
+        #[allow(clippy::comparison_chain)]
+        let chosen_type = if types.len() > 1 {
+            if types.contains(&PropertyType::String) || types.contains(&PropertyType::StringArray) {
+                // Try and treat it as a string.
+                if let Ok(str) = self.as_json_string(path.clone()) {
+                    return Ok(str);
+                }
+
+                let string_types =
+                    BTreeSet::from([PropertyType::String, PropertyType::StringArray]);
+                // There should only be one other type.
+                let difference = types.difference(&string_types).collect::<Vec<_>>();
+                if difference.len() == 1 {
+                    difference.into_iter().next().copied()
+                } else {
+                    tracing::warn!(
+                        "Property should only have one type left, but have these types: {:?}",
+                        difference
+                    );
+                    return self.as_json_bytes();
+                }
+            } else {
+                None
+            }
+        } else if types.len() == 1 {
+            types.pop_first()
+        } else {
+            None
+        };
+
+        let chosen_type = if let Some(ty) = chosen_type {
+            ty
+        } else {
+            if let Ok(value) = self.as_json_string(path.clone()) {
+                return Ok(value);
+            }
+
+            if self.value.len() % 4 == 0 {
+                PropertyType::Uint32Array
+            } else {
+                return self.as_json_bytes();
+            }
+        };
+        tracing::trace!("Chosen type: {:?}", chosen_type);
+
+        if chosen_type.is_string() {
+            return self.as_json_string(path);
+        }
+
+        if chosen_type == PropertyType::Flag {
+            assert!(
+                !self.value.is_empty(),
+                "If length is zero we should always return true above"
+            );
+            tracing::warn!("Property is a flag but has data!");
+            return self.as_json_bytes();
+        }
+
+        // This is slightly different than the upstream equivalent
+        // (see https://github.com/devicetree-org/dt-schema/blob/547c943ab55f4d0b44fd88e3c36c7f6fa49c6ae2/dtschema/dtb.py#L137)
+        // but I think it is functionally the same.
+        let bytes_per_element = match chosen_type.bytes_per_element() {
+            Some(bytes) => bytes,
+            None => {
+                tracing::warn!("Type {:?} has no bytes per element", chosen_type);
+                return self.as_json_bytes();
+            }
+        };
+
+        if self.value.len() % bytes_per_element != 0 {
+            tracing::warn!(
+                "Invalid size: {} (expected a multiple of {})",
+                self.value.len(),
+                bytes_per_element
+            );
+            return self.as_json_bytes();
+        }
+
+        let values_int = self
+            .value
+            .chunks_exact(bytes_per_element)
+            .map(|chunk| BigEndian::read_uint(chunk, bytes_per_element))
+            .collect::<Vec<_>>();
+
+        let dim = if chosen_type.is_matrix() {
+            type_lookup.get_property_dimensions(&self.key)
+        } else {
+            None
+        };
+
+        if let Some(dim) = dim {
+            let stride = dim.stride(values_int.len());
+
+            return Ok(serde_json::to_value(
+                values_int
+                    .chunks(stride)
+                    .map(Vec::from)
+                    .collect::<Vec<Vec<u64>>>(),
+            )?);
+        } else {
+            Ok(serde_json::to_value([values_int])?)
+        }
+    }
+
+    fn as_json_bytes(&self) -> Result<serde_json::Value, DevicetreeJsonError> {
+        Ok(serde_json::to_value(self.value.clone())?)
+    }
+
+    fn as_json_string(&self, path: JsonPath) -> Result<serde_json::Value, DevicetreeJsonError> {
+        let slices = self
+            .value
+            .split_inclusive(|&byte| byte == 0)
+            .map(|v| {
+                CStr::from_bytes_with_nul(v)
+                    .map_err(|e| DevicetreeJsonError::InvalidString(e, path.clone()))
+                    .and_then(|v| match v.to_str() {
+                        Ok(str) => {
+                            if str.chars().any(char::is_control) {
+                                Err(DevicetreeJsonError::NonPrintableString(path.clone()))
+                            } else {
+                                Ok(str)
+                            }
+                        }
+                        Err(e) => Err(e.into()),
+                    })
+            })
+            .collect::<Result<Vec<&str>, DevicetreeJsonError>>()?;
+
+        Ok(serde_json::to_value(slices)?)
+    }
+
+    pub fn key(&self) -> &String {
+        &self.key
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use crate::validator::dimension::Dimension;
+
+    use super::*;
+
+    fn make_prop(key: &str, value: &[u8]) -> Property {
+        Property {
+            key: key.to_owned(),
+            value: Vec::from(value),
+        }
+    }
+
+    struct FakeTypeLookup {
+        types: BTreeSet<PropertyType>,
+        dimensions: Option<Dimension>,
+    }
+    impl FakeTypeLookup {
+        fn empty() -> Self {
+            FakeTypeLookup {
+                types: BTreeSet::new(),
+                dimensions: None,
+            }
+        }
+
+        fn new(types: &[PropertyType], dim: Option<Dimension>) -> Self {
+            FakeTypeLookup {
+                types: types.iter().copied().collect(),
+                dimensions: dim,
+            }
+        }
+    }
+
+    impl PropertyTypeLookup for FakeTypeLookup {
+        fn get_property_type(&self, _propname: &str) -> BTreeSet<PropertyType> {
+            self.types.clone()
+        }
+
+        fn get_property_dimensions(
+            &self,
+            _propname: &str,
+        ) -> Option<crate::validator::dimension::Dimension> {
+            self.dimensions
+        }
+    }
+
+    #[test]
+    fn test_flag_property() {
+        let prop = make_prop("test", &[]);
+        assert_eq!(
+            prop.value_json("test", &FakeTypeLookup::empty(), JsonPath::new())
+                .expect("valid value"),
+            json!(true)
+        );
+    }
+
+    #[test]
+    fn test_no_types() {
+        let prop = make_prop("test", &[1, 2, 3]);
+        assert_eq!(
+            prop.value_json("test", &FakeTypeLookup::empty(), JsonPath::new())
+                .expect("valid value"),
+            json!([1, 2, 3])
+        )
+    }
+
+    #[test]
+    fn test_string_array() {
+        let prop = make_prop("test_string_array", &[b'h', b'i', 0, b'h', b'i', 0]);
+        let lookup = FakeTypeLookup::new(&[PropertyType::String], None);
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!(["hi", "hi"])
+        );
+    }
+
+    #[test]
+    fn test_single_string() {
+        let prop = make_prop("test_single_string", &[b'h', b'i', 0]);
+        let lookup = FakeTypeLookup::new(&[PropertyType::String], None);
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!(["hi"])
+        );
+    }
+
+    #[test]
+    fn test_string_ambiguous() {
+        let prop = make_prop(
+            "test_string_ambiguous",
+            &[0x04, 0x07, 0x08, 0x02, 0x03, 0x09, 0x04, 0x06],
+        );
+        let lookup = FakeTypeLookup::new(&[PropertyType::String, PropertyType::Uint32Array], None);
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!([[0x04070802, 0x03090406]])
+        );
+    }
+
+    #[test]
+    fn test_untyped_bytes() {
+        let prop = make_prop("untyped_bytes", &[0x04, 0x05]);
+        let lookup = FakeTypeLookup::new(&[], None);
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!([0x04, 0x05])
+        );
+    }
+
+    #[test]
+    fn test_explicit_flag_type_with_data() {
+        let prop = make_prop("explicit_flag_type", &[0x01, 0x02]);
+        let lookup = FakeTypeLookup::new(&[PropertyType::Flag], None);
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!([0x01, 0x02])
+        );
+    }
+
+    #[test]
+    fn test_u64_array() {
+        let prop = make_prop(
+            "u64_array",
+            &[
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, /* */
+                0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+            ],
+        );
+
+        let lookup = FakeTypeLookup::new(&[PropertyType::Uint64Array], None);
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!([[0xff, 0x1000000000000001_u64]])
+        );
+    }
+
+    #[test]
+    fn test_u32_matrix_simple() {
+        let prop = make_prop(
+            "u32_matrix",
+            &[
+                0x00, 0x00, 0x00, 0x01, /* */
+                0x00, 0x00, 0x00, 0x01, /* */
+                0x00, 0x00, 0x00, 0x04, /* */
+                0x00, 0x00, 0x00, 0x04,
+            ],
+        );
+
+        let lookup =
+            FakeTypeLookup::new(&[PropertyType::Uint32Matrix], Some([[1, 1], [2, 2]].into()));
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!([[1, 1], [4, 4]])
+        );
+    }
+
+    #[test]
+    fn test_u32_matrix_variable() {
+        let prop = make_prop(
+            "u32_matrix_variable",
+            &[
+                0x00, 0x00, 0x00, 0x01, /* */
+                0x00, 0x00, 0x00, 0x01, /* */
+                0x00, 0x00, 0x00, 0x04, /* */
+                0x00, 0x00, 0x00, 0x04, /* */
+                0x00, 0x00, 0x00, 0x05, /* */
+                0x00, 0x00, 0x00, 0x06, /* */
+            ],
+        );
+
+        let lookup =
+            FakeTypeLookup::new(&[PropertyType::Uint32Matrix], Some([[3, 3], [1, 2]].into()));
+
+        assert_eq!(
+            prop.value_json("test", &lookup, JsonPath::new())
+                .expect("valid value"),
+            json!([[1, 1, 4], [4, 5, 6]])
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/types.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/types.rs
new file mode 100644
index 0000000..94c8865
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/types.rs
@@ -0,0 +1,14 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::BTreeSet;
+
+use crate::validator::{dimension::Dimension, property_type::PropertyType};
+
+pub trait PropertyTypeLookup {
+    fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType>;
+
+    fn get_property_dimensions(&self, propname: &str) -> Option<Dimension>;
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/devicetree/util.rs b/third_party/rust_crates/forks/dt-schema/src/devicetree/util.rs
new file mode 100644
index 0000000..a266c20
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/devicetree/util.rs
@@ -0,0 +1,79 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{collections::VecDeque, rc::Rc};
+
+use byteorder::{BigEndian, ByteOrder};
+
+use crate::path::JsonPath;
+
+use super::{
+    fixups::{DevicetreeLookup, PhandleError},
+    Devicetree, Node,
+};
+
+#[derive(Clone)]
+pub struct NodeAncestry<'a> {
+    devicetree: &'a Devicetree,
+    nodes: VecDeque<Rc<Node>>,
+}
+
+impl<'a> NodeAncestry<'a> {
+    pub fn new(devicetree: &'a Devicetree) -> Self {
+        NodeAncestry {
+            devicetree,
+            nodes: VecDeque::new(),
+        }
+    }
+
+    pub fn visit(&self, node: Rc<Node>) -> Self {
+        let mut new = self.clone();
+        new.nodes.push_back(node);
+        new
+    }
+
+    pub fn history(&self) -> impl Iterator<Item = &Rc<Node>> {
+        self.nodes.iter().rev()
+    }
+}
+
+impl DevicetreeLookup for NodeAncestry<'_> {
+    fn get_cells_size(
+        &self,
+        phandle_path: JsonPath,
+        phandle: u32,
+        cell_prop: &str,
+    ) -> Result<u32, PhandleError> {
+        if phandle == 0 {
+            // :(
+            return Ok(1);
+        }
+        // Resolve the phandle
+        let (node, node_path) = self
+            .devicetree
+            .by_phandle(phandle as usize)
+            .ok_or(PhandleError::InvalidPhandle(phandle, phandle_path))?;
+
+        let prop = node
+            .properties()
+            .iter()
+            .find(|v| v.key() == cell_prop)
+            .ok_or_else(|| {
+                PhandleError::NodeHadNoProperty(cell_prop.to_owned(), node_path.clone())
+            })?;
+
+        let value = BigEndian::read_u32(&prop.value);
+        Ok(value)
+    }
+
+    fn get_prop_from_parents(&self, prop: &str) -> Result<Option<u32>, PhandleError> {
+        for node in self.history() {
+            if let Some(value) = node.properties.iter().find(|k| k.key == prop) {
+                return Ok(Some(BigEndian::read_u32(&value.value)));
+            }
+        }
+
+        Ok(None)
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/logging.rs b/third_party/rust_crates/forks/dt-schema/src/logging.rs
new file mode 100644
index 0000000..27cd44b
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/logging.rs
@@ -0,0 +1,51 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use tracing::{subscriber::Interest, Subscriber};
+use tracing_subscriber::Layer;
+
+pub struct LoggingMetadata {
+    prefixes: Vec<String>,
+}
+
+impl LoggingMetadata {
+    pub fn new(prefixes: Vec<String>) -> Self {
+        LoggingMetadata { prefixes }
+    }
+
+    fn is_interesting(&self, value: &str) -> bool {
+        for prefix in self.prefixes.iter() {
+            if prefix.ends_with('$') && &prefix[..prefix.len() - 1] == value {
+                return true;
+            }
+            if value.starts_with(prefix) {
+                return true;
+            }
+        }
+        false
+    }
+}
+
+impl<S> Layer<S> for LoggingMetadata
+where
+    S: Subscriber,
+{
+    fn register_callsite(
+        &self,
+        metadata: &'static tracing::Metadata<'static>,
+    ) -> tracing::subscriber::Interest {
+        if self.prefixes.is_empty() {
+            return Interest::always();
+        }
+        if let Some(module) = metadata.module_path() {
+            if self.is_interesting(module) {
+                Interest::always()
+            } else {
+                Interest::never()
+            }
+        } else {
+            Interest::never()
+        }
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/main.rs b/third_party/rust_crates/forks/dt-schema/src/main.rs
new file mode 100644
index 0000000..1fa179f
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/main.rs
@@ -0,0 +1,189 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::BTreeSet;
+
+use anyhow::{anyhow, Context, Error};
+use argh::FromArgs;
+use logging::LoggingMetadata;
+use path::JsonPath;
+use tracing::metadata::LevelFilter;
+use tracing_subscriber::{prelude::*, Layer, Registry};
+
+mod devicetree;
+mod logging;
+mod parallel;
+mod path;
+#[cfg(test)]
+mod test_util;
+mod validator;
+
+/// STRICT_MODE determines whether or not validation occurs strictly.
+/// This MUST NOT be modified outside of |main()|.
+static mut STRICT_MODE: bool = true;
+
+pub fn strict_mode() -> bool {
+    // Safe because we only set strict mode at the start of program execution.
+    unsafe { STRICT_MODE }
+}
+
+#[derive(FromArgs)]
+/// devicetree schema validation tool.
+struct Args {
+    #[argh(subcommand)]
+    command: Subcommand,
+
+    #[argh(switch, short = 'r')]
+    /// perform relaxed validation of devicetree.
+    /// this is needed for compatibility with some Linux dtbs.
+    relaxed: bool,
+
+    #[argh(switch, short = 'v')]
+    /// be verbose.
+    verbose: bool,
+
+    #[argh(option)]
+    /// list of prefixes to output log messages from. By default all log messages will be output.
+    log_filter: Vec<String>,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand)]
+enum Subcommand {
+    Validate(ValidateArgs),
+    DumpDtb(DumpDtbArgs),
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "validate")]
+/// Compile many devicetree schemas into one.
+struct ValidateArgs {
+    #[argh(option, short = 'd')]
+    /// device tree blob file
+    dtb: String,
+    #[argh(positional)]
+    /// files to compile
+    schemas: Vec<String>,
+
+    #[argh(option)]
+    /// for debugging, path to file to dump generated types to.
+    debug_dump_types: Option<String>,
+
+    #[argh(option)]
+    /// for debugging, path to jump json DTB to.
+    debug_dump_dtb: Option<String>,
+}
+
+#[derive(FromArgs, PartialEq, Debug)]
+#[argh(subcommand, name = "dump")]
+/// Dump device tree.
+struct DumpDtbArgs {
+    #[argh(positional)]
+    /// device tree blob file
+    dtb: String,
+}
+
+fn main() {
+    let args: Args = argh::from_env();
+
+    if args.relaxed {
+        unsafe {
+            // Safe because there is no other code running yet.
+            STRICT_MODE = false;
+        }
+    }
+
+    let filter_layer = LoggingMetadata::new(args.log_filter);
+
+    let fmt_layer = tracing_subscriber::fmt::layer()
+        .with_line_number(true)
+        .with_filter(if args.verbose {
+            LevelFilter::TRACE
+        } else {
+            LevelFilter::INFO
+        })
+        .boxed();
+    let subscriber = Registry::default().with(fmt_layer).with(filter_layer);
+
+    tracing::subscriber::set_global_default(subscriber).unwrap();
+    match args.command {
+        Subcommand::Validate(validate) => match do_validate(validate) {
+            Ok(()) => println!("Success."),
+            Err(e) => println!("Error: {:?}", e),
+        },
+        Subcommand::DumpDtb(args) => println!(
+            "{:?}",
+            devicetree::Devicetree::from_reader(std::fs::File::open(args.dtb).unwrap())
+        ),
+    };
+}
+
+fn validate_subtree(
+    start: &serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+    validator: &validator::Validator,
+) -> Result<(), BTreeSet<JsonPath>> {
+    let failed = !validator.validate(&start.clone().into(), path.clone());
+    let mut errors = BTreeSet::new();
+    for (k, v) in start
+        .iter()
+        .filter_map(|(k, v)| v.as_object().map(|v| (k, v)))
+    {
+        match validate_subtree(v, path.extend(k), validator) {
+            Ok(()) => {}
+            Err(new_errors) => {
+                errors.extend(new_errors.into_iter());
+            }
+        }
+    }
+
+    if failed {
+        errors.insert(path);
+        Err(errors)
+    } else if !errors.is_empty() {
+        Err(errors)
+    } else {
+        Ok(())
+    }
+}
+
+fn do_validate(args: ValidateArgs) -> Result<(), Error> {
+    let validator =
+        validator::Validator::new_with_schemas(&args.schemas).context("Loading schemas")?;
+
+    if let Some(dest) = args.debug_dump_types {
+        let out = std::fs::File::create(dest).context("Opening file")?;
+        validator
+            .dump_properties(out)
+            .context("dumping properties")?;
+    }
+
+    let devicetree = devicetree::Devicetree::from_reader(
+        std::fs::File::open(args.dtb.clone())
+            .with_context(|| format!("Opening file {}", args.dtb))?,
+    )
+    .context("parsing device tree blob")?;
+
+    let json = devicetree.as_json(&validator)?;
+
+    if let Some(dest) = args.debug_dump_dtb {
+        let out = std::fs::File::create(dest).context("Creating dtb debug file")?;
+        serde_json::to_writer_pretty(out, &json).context("Writing dtb debug file")?;
+    }
+
+    match validate_subtree(json.as_object().unwrap(), JsonPath::new(), &validator) {
+        Ok(()) => Ok(()),
+        Err(error_locations) => {
+            let mut paths_string: Vec<String> =
+                error_locations.into_iter().map(|s| s.to_string()).collect();
+            paths_string.sort();
+            tracing::error!(
+                "Errors were encountered in the following paths: '{}'",
+                paths_string.join("', '")
+            );
+
+            Err(anyhow!("Failed to validate device tree"))
+        }
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/parallel.rs b/third_party/rust_crates/forks/dt-schema/src/parallel.rs
new file mode 100644
index 0000000..38c60ee
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/parallel.rs
@@ -0,0 +1,150 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{
+    io::Write,
+    sync::{
+        atomic::{AtomicUsize, Ordering},
+        Arc, Condvar, Mutex,
+    },
+    time::{Duration, Instant},
+};
+
+struct SharedMutableState<I, E> {
+    results: Vec<Result<Vec<I>, E>>,
+    abort: bool,
+}
+
+struct SharedState<I, E> {
+    mutable: Mutex<SharedMutableState<I, E>>,
+    abort_cv: Condvar,
+    work_done: AtomicUsize,
+}
+
+impl<I, E> SharedState<I, E> {
+    fn new() -> Arc<Self> {
+        Arc::new(SharedState {
+            mutable: Mutex::new(SharedMutableState {
+                results: vec![],
+                abort: false,
+            }),
+            abort_cv: Condvar::new(),
+            work_done: AtomicUsize::new(0),
+        })
+    }
+
+    fn add_result(&self, res: Result<Vec<I>, E>) {
+        let mut mutable = self.mutable.lock().unwrap();
+        if res.is_err() {
+            mutable.abort = true;
+        }
+        mutable.results.push(res);
+        self.abort_cv.notify_all();
+    }
+
+    fn did_work(&self) {
+        self.work_done.fetch_add(1, Ordering::Relaxed);
+    }
+
+    fn wait_for_finish(&self) -> Result<usize, ()> {
+        let lock = self.mutable.lock().unwrap();
+        let (lock, _) = self
+            .abort_cv
+            .wait_timeout(lock, Duration::from_millis(400))
+            .unwrap();
+        let should_abort = lock.abort;
+        if should_abort {
+            Err(())
+        } else {
+            Ok(self.work_done.load(Ordering::SeqCst))
+        }
+    }
+
+    fn get_results(self: Arc<Self>) -> Result<Vec<I>, E> {
+        let nested: Result<Vec<Vec<_>>, _> = Arc::try_unwrap(self)
+            .unwrap_or_else(|_| panic!("stray references")) // Safe because this is called after all other references are dealt with.
+            .mutable
+            .into_inner()
+            .unwrap()
+            .results
+            .into_iter()
+            .collect();
+        Ok(nested?.into_iter().flatten().collect())
+    }
+}
+
+pub fn parallel<'a, Arg, I, E>(
+    callback: Arc<dyn Fn(&'a Arg) -> Result<I, E> + Sync + Send + 'a>,
+    values: &'a [Arg],
+    progress_text: &str,
+) -> Result<Vec<I>, E>
+where
+    I: Send + Sync + 'a,
+    E: Send + Sync + 'a,
+    Arg: Sync,
+{
+    let threads = std::thread::available_parallelism()
+        .map(|v| v.get())
+        .unwrap_or(4);
+
+    let work_per_thread = values.len() / threads;
+    let state = SharedState::<I, E>::new();
+
+    let start = Instant::now();
+    std::thread::scope(|s| {
+        #[allow(clippy::needless_collect)]
+        let threads = values
+            .chunks(work_per_thread)
+            .map(|chunk| {
+                let f = Arc::clone(&callback);
+                let state = Arc::clone(&state);
+
+                s.spawn(move || {
+                    let mut thread_results = vec![];
+                    for item in chunk.iter() {
+                        match f(item) {
+                            Ok(v) => thread_results.push(v),
+                            Err(e) => {
+                                state.add_result(Err(e));
+                                break;
+                            }
+                        }
+                        state.did_work();
+                    }
+                    state.add_result(Ok(thread_results));
+                })
+            })
+            .collect::<Vec<_>>();
+
+        let mut should_abort = false;
+        while !should_abort {
+            should_abort = match state.wait_for_finish() {
+                Ok(progress) => {
+                    print!(
+                        "{}: {}/{}... ({:.02}s)\r",
+                        progress_text,
+                        progress,
+                        values.len(),
+                        start.elapsed().as_secs_f64()
+                    );
+                    let _ = std::io::stdout().flush();
+                    progress == values.len()
+                }
+                Err(_) => true,
+            };
+        }
+
+        for thread in threads.into_iter() {
+            thread.join().unwrap();
+        }
+    });
+
+    println!(
+        "{}: done in {:.02}s                          ",
+        progress_text,
+        start.elapsed().as_secs_f64()
+    );
+
+    state.get_results()
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/path.rs b/third_party/rust_crates/forks/dt-schema/src/path.rs
new file mode 100644
index 0000000..f59a431
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/path.rs
@@ -0,0 +1,58 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{collections::VecDeque, fmt::Display};
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+/// Represents a path through a json object.
+pub struct JsonPath {
+    elements: VecDeque<String>,
+}
+
+impl JsonPath {
+    pub fn new() -> JsonPath {
+        JsonPath {
+            elements: VecDeque::new(),
+        }
+    }
+
+    pub fn extend(&self, elem: &str) -> Self {
+        let mut new = self.clone();
+        new.elements.push_back(elem.to_owned());
+        new
+    }
+
+    pub fn extend_index_only(&self, index: usize) -> Self {
+        let mut new = self.clone();
+        let back = new
+            .elements
+            .pop_back()
+            .unwrap_or_else(|| "/<root>".to_owned());
+        new.elements.push_back(format!("{}[{}]", back, index));
+        new
+    }
+
+    pub fn extend_array_index(&self, elem: &str, index: usize) -> Self {
+        let mut new = self.clone();
+        new.elements.push_back(format!("{}[{}]", elem, index));
+        new
+    }
+
+    pub fn back(&self) -> &str {
+        self.elements.back().map(|s| s.as_str()).unwrap_or("/")
+    }
+}
+
+impl Display for JsonPath {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if self.elements.is_empty() {
+            write!(f, "/")?;
+        }
+        for el in self.elements.iter() {
+            write!(f, "/{}", el)?;
+        }
+
+        Ok(())
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/test_util.rs b/third_party/rust_crates/forks/dt-schema/src/test_util.rs
new file mode 100644
index 0000000..9526a28b
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/test_util.rs
@@ -0,0 +1,18 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use tracing::Level;
+
+#[allow(unused)]
+/// This is intended to help with debugging test failures.
+/// Call `crate::test_util::enable_all_logs()` at the start of your test to get full tracing output.
+pub fn enable_all_logs() {
+    tracing::subscriber::set_global_default(
+        tracing_subscriber::fmt()
+            .with_max_level(Level::TRACE)
+            .with_line_number(true)
+            .finish(),
+    )
+    .unwrap();
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator.rs b/third_party/rust_crates/forks/dt-schema/src/validator.rs
new file mode 100644
index 0000000..a9bb1fe
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator.rs
@@ -0,0 +1,296 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+mod array;
+mod compatible;
+pub mod dimension;
+mod error;
+mod fixups;
+pub mod property_type;
+mod property_type_info;
+mod resolver;
+mod schema;
+mod util;
+
+use serde::Serialize;
+use tracing::Level;
+use valico::json_schema::{SchemaVersion, Scope};
+
+use std::{
+    collections::{BTreeMap, BTreeSet, HashSet},
+    io::Write,
+    sync::Arc,
+};
+
+use fancy_regex::Regex;
+
+use crate::{devicetree::types::PropertyTypeLookup, parallel::parallel, path::JsonPath};
+
+use self::{
+    dimension::Dimension, error::ValidatorError, fixups::FixupError, property_type::PropertyType,
+    resolver::LocalOnlyResolver, schema::Schema,
+};
+
+#[derive(Debug, Serialize)]
+/// Note that |PartialEq| is only implemented with respect to the type and dimensions.
+pub struct GeneratedPropertyType {
+    #[serde(skip)]
+    id: HashSet<String>,
+    r#type: Option<PropertyType>,
+    #[serde(skip)]
+    regex: Option<Regex>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    dim: Option<Dimension>,
+}
+
+impl PartialEq for GeneratedPropertyType {
+    fn eq(&self, other: &Self) -> bool {
+        self.r#type == other.r#type && self.dim == other.dim
+    }
+}
+
+impl Eq for GeneratedPropertyType {
+    fn assert_receiver_is_total_eq(&self) {}
+}
+
+impl PartialOrd for GeneratedPropertyType {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        match self.r#type.partial_cmp(&other.r#type) {
+            Some(core::cmp::Ordering::Equal) => {}
+            ord => return ord,
+        }
+        self.dim.partial_cmp(&other.dim)
+    }
+}
+
+impl Ord for GeneratedPropertyType {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.partial_cmp(other).unwrap()
+    }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum FileValidatorError {
+    #[error("validator error in file {1}: {0}")]
+    ValidatorError(ValidatorError, String),
+
+    #[error("fixup error in file {1}: {0}")]
+    FixupError(FixupError, String),
+}
+
+#[derive(Debug)]
+pub struct Validator {
+    schemas: Vec<Schema>,
+    /// Map of |property-name| to |possible-property-types|. Used when decoding the DTB.
+    property_types: BTreeMap<String, Vec<GeneratedPropertyType>>,
+    /// Map of |property-regex| to |possible-property-types|. Used when decoding the DTB.
+    pattern_property_types: BTreeMap<String, (Regex, Vec<GeneratedPropertyType>)>,
+    /// List of valid compatibles.
+    // TODO(simonshields): generate this and use it for making sure all nodes have schemas.
+    #[allow(unused)]
+    valid_compatibles: HashSet<String>,
+    /// Scope which schemas are compiled in to.
+    scope: Scope,
+}
+
+impl Validator {
+    pub fn new_with_schemas(paths: &[String]) -> Result<Self, FileValidatorError> {
+        let mut schemas = parallel(
+            Arc::new(|path: &String| -> Result<Schema, FileValidatorError> {
+                let file = std::fs::File::open(path)
+                    .map_err(|v| FileValidatorError::ValidatorError(v.into(), path.clone()))?;
+
+                let pre_fixup = serde_yaml::from_reader(file)
+                    .map_err(|v| FileValidatorError::ValidatorError(v.into(), path.clone()))?;
+
+                // Run fixup passes over schema before we parse it into an actual |Schema| object.
+                let value = fixups::schema_fixups::SchemaFixup::fixup(pre_fixup, path.clone())
+                    .map_err(|v| FileValidatorError::FixupError(v, path.clone()))?;
+                Schema::from_value(value, path.clone())
+                    .map_err(|v| FileValidatorError::ValidatorError(v, path.clone()))
+            }),
+            paths,
+            "loading schemas",
+        )?;
+
+        // Determine property types.
+        let mut property_types: BTreeMap<String, Vec<GeneratedPropertyType>> = BTreeMap::new();
+        let mut pattern_property_types: BTreeMap<String, (Regex, Vec<GeneratedPropertyType>)> =
+            BTreeMap::new();
+        for schema in schemas.iter_mut() {
+            let types = schema.generate_property_types(Some(schema), JsonPath::new());
+
+            let types = types
+                .map_err(|v| FileValidatorError::ValidatorError(v, schema.source_file().clone()))?;
+
+            // Distil types into the final output.
+            for (k, v) in types.into_iter() {
+                let ignore_untyped = |v: &GeneratedPropertyType| v.r#type.is_some();
+                if let Some(regex_item) = v.iter().find(|e| e.regex.is_some()) {
+                    // We skip these overly broad property types because they
+                    // cause properties to be wrongly detected as strings.
+                    if k == "^[a-z][a-z0-9\\-]*$"
+                        || k == "^[a-zA-Z][a-zA-Z0-9\\-_]{0,63}$"
+                        || k == "^.*$"
+                        || k == ".*"
+                    {
+                        continue;
+                    }
+
+                    match pattern_property_types.get_mut(&k) {
+                        Some((_re, list)) => list.extend(v.into_iter().filter(ignore_untyped)),
+                        None => {
+                            pattern_property_types.insert(
+                                k,
+                                (
+                                    regex_item.regex.clone().unwrap(),
+                                    v.into_iter().filter(ignore_untyped).collect(),
+                                ),
+                            );
+                        }
+                    }
+                } else {
+                    match property_types.get_mut(&k) {
+                        Some(list) => list.extend(v.into_iter().filter(ignore_untyped)),
+                        None => {
+                            property_types
+                                .insert(k, v.into_iter().filter(ignore_untyped).collect());
+                        }
+                    }
+                };
+            }
+        }
+
+        let mut scope = Scope::new().set_version(SchemaVersion::Draft2019_09);
+        let resolver = LocalOnlyResolver::new(&mut schemas.iter());
+        for schema in schemas.iter_mut() {
+            schema
+                .fixup_select_and_finalise(resolver.clone(), &mut scope)
+                .map_err(|e| {
+                    FileValidatorError::ValidatorError(e, schema.spec_id().clone().unwrap())
+                })?;
+        }
+
+        Ok(Validator {
+            schemas,
+            property_types,
+            pattern_property_types,
+            valid_compatibles: HashSet::new(),
+            scope,
+        })
+    }
+
+    pub fn dump_properties(&self, out: impl Write) -> Result<(), ValidatorError> {
+        let props_for_dump: BTreeMap<_, _> =
+            self.property_types
+                .iter()
+                .map(|(k, v)| {
+                    (
+                        k.clone(),
+                        BTreeSet::from_iter(v.iter().filter(|e| {
+                            e.r#type.unwrap_or(PropertyType::Node) != PropertyType::Node
+                        })),
+                    )
+                })
+                .filter(|(_, v)| !v.is_empty())
+                .collect();
+
+        serde_yaml::to_writer(out, &props_for_dump)?;
+        Ok(())
+    }
+
+    pub fn validate(&self, value: &serde_json::Value, path: JsonPath) -> bool {
+        let schemas: Vec<_> = self
+            .schemas
+            .iter()
+            .filter(|s| s.applies(value, &self.scope))
+            .map(|s| (s, s.validate(value, &path.to_string(), &self.scope)))
+            .collect();
+
+        let disabled = value
+            .as_object()
+            .and_then(|o| o.get("status"))
+            .and_then(|v| v.as_array())
+            .map(|v| v.len() == 1 && v[0] == "disabled")
+            .unwrap_or(false);
+        let span = tracing::error_span!("validate", path = %path);
+        let _entered = span.enter();
+        let mut ok = true;
+        for (schema, status) in schemas {
+            let schema_span = tracing::error_span!("schema", file=%schema.source_file());
+            let _entered = schema_span.enter();
+            if let Some(e) = status.filter(|v| !v.is_strictly_valid()) {
+                let level = if !disabled {
+                    ok = false;
+                    tracing::error!("Validation failed:");
+                    Level::ERROR
+                } else {
+                    tracing::info!("Validation of disabled node failed (this may be expected):");
+                    Level::INFO
+                };
+                for error in e.errors {
+                    if level == Level::INFO {
+                        tracing::event!(
+                            Level::INFO,
+                            "at {}: {} {}",
+                            error.get_path(),
+                            error.get_title(),
+                            error.get_detail().unwrap_or(""),
+                        );
+                    } else {
+                        tracing::event!(
+                            Level::ERROR,
+                            "at {}: {} {}",
+                            error.get_path(),
+                            error.get_title(),
+                            error.get_detail().unwrap_or(""),
+                        );
+                    }
+                }
+            }
+        }
+
+        ok
+    }
+}
+
+impl PropertyTypeLookup for Validator {
+    fn get_property_type(&self, propname: &str) -> BTreeSet<PropertyType> {
+        let mut types: BTreeSet<PropertyType> = self
+            .property_types
+            .get(propname)
+            .map(|v| v.iter().filter_map(|e| e.r#type).collect())
+            .unwrap_or_default();
+
+        if types.is_empty() {
+            types.extend(
+                self.pattern_property_types
+                    .iter()
+                    .filter(|(_, (regex, _))| regex.is_match(propname).unwrap_or(false))
+                    .flat_map(|(_, (_, ty))| ty.iter().filter_map(|e| e.r#type)),
+            );
+        }
+
+        types
+    }
+
+    fn get_property_dimensions(&self, propname: &str) -> Option<Dimension> {
+        if let Some(types) = self.property_types.get(propname) {
+            if let Some(dim) = types.iter().filter_map(|v| v.dim).next() {
+                return Some(dim);
+            }
+        }
+
+        self.pattern_property_types
+            .iter()
+            .filter_map(|(_, (regex, ty))| {
+                if regex.is_match(propname).unwrap_or(false) {
+                    ty.iter().filter_map(|e| e.dim).next()
+                } else {
+                    None
+                }
+            })
+            .next()
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/array.rs b/third_party/rust_crates/forks/dt-schema/src/validator/array.rs
new file mode 100644
index 0000000..91a67e7
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/array.rs
@@ -0,0 +1,179 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use fancy_regex::Regex;
+use serde::{Deserialize, Serialize};
+
+use super::util::{ContainsExt, IsSchema};
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(untagged)]
+/// Represents an "Items" declaration, which could be a bare list of items, or a dict with a min/max.
+/// This is only ever used in the context of |Array|, below.
+enum Items {
+    Array(Vec<serde_json::Value>),
+    Object(serde_json::Map<String, serde_json::Value>),
+}
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+/// Represents an array.
+pub struct Array {
+    items: Option<Items>,
+    min_items: Option<usize>,
+    max_items: Option<usize>,
+}
+
+impl Array {
+    /// Returns true if this |Array| represents an array of ints.
+    pub fn is_int_array_schema(&self, propname: &str) -> bool {
+        let unit_type_re = Regex::new("-(kBps|bits|percent|bp|m?hz|sec|ms|us|ns|ps|mm|nanoamp|(micro-)?ohms|micro(amp|watt)(-hours)?|milliwatt|microvolt|picofarads|(milli)?celsius|kelvin|kpascal)$").unwrap();
+        if unit_type_re.is_match(propname).unwrap_or(false) {
+            return true;
+        }
+        let int_array_re = Regex::new("int(8|16|32|64)-array").unwrap();
+        let map = match self.items {
+            Some(Items::Object(ref o)) => {
+                if let Some(all_of) = o.get("allOf").and_then(|v| v.as_array()) {
+                    let mut ret = o;
+                    for item in all_of.iter().filter_map(|v| v.as_object()) {
+                        if let Some(path) = item.get("$ref").and_then(|v| v.as_str()) {
+                            return int_array_re.is_match(path).unwrap_or(false);
+                        }
+                        if let Some(items) = item.get("items").and_then(|v| v.as_object()) {
+                            ret = items;
+                        }
+                    }
+                    ret
+                } else {
+                    o
+                }
+            }
+            _ => return false,
+        };
+
+        map.is_int_schema()
+    }
+
+    /// Returns |true| if this type represents a uint32 matrix.
+    pub fn is_uint32_matrix(&self, propname: &str) -> bool {
+        if !self.is_matrix() {
+            false
+        } else {
+            let unit_type_re = Regex::new("-(kBps|bits|percent|bp|m?hz|sec|ms|us|ns|ps|mm|nanoamp|(micro-)?ohms|micro(amp|watt)(-hours)?|milliwatt|microvolt|picofarads|(milli)?celsius|kelvin|kpascal)$").unwrap();
+            unit_type_re.is_match(propname).unwrap_or(false)
+        }
+    }
+
+    /// Returns true if this array is a matrix.
+    pub fn is_matrix(&self) -> bool {
+        match &self.items {
+            Some(Items::Object(map)) => map.contains_any(&["items", "maxItems", "minItems"]),
+            Some(Items::Array(array)) => {
+                for item in array.iter().filter_map(|f| f.as_object()) {
+                    if item.contains_any(&["items", "maxItems", "minItems"]) {
+                        return true;
+                    }
+                }
+                false
+            }
+            None => false,
+        }
+    }
+
+    /// If this array is a matrix, returns the inner dimension of the matrix.
+    pub fn get_child_dim(&self) -> Result<[usize; 2], serde_json::Error> {
+        let array: Array = match &self.items {
+            Some(Items::Array(vec)) => {
+                if vec.len() == 1 {
+                    serde_json::from_value(vec[0].clone())?
+                } else {
+                    return Ok([0, 0]);
+                }
+            }
+            Some(Items::Object(obj)) => serde_json::from_value(obj.clone().into())?,
+            None => return Ok([1, 0]),
+        };
+
+        Ok(array.get_dim())
+    }
+
+    /// Get the [minimum, maximum] length of this array.
+    pub fn get_dim(&self) -> [usize; 2] {
+        if let Some(Items::Array(ref items)) = self.items {
+            [self.min_items.unwrap_or(items.len()), items.len()]
+        } else {
+            [
+                self.min_items.unwrap_or(1),
+                self.max_items
+                    .unwrap_or_else(|| self.min_items.unwrap_or(0)),
+            ]
+        }
+    }
+
+    /// Returns true if this array has an explicit |items| definition associated with it.
+    pub fn has_items(&self) -> bool {
+        self.items.is_some()
+    }
+
+    pub fn is_string_schema(&self) -> bool {
+        match self.items.as_ref() {
+            // TODO(simonshields): should this be less aggressive? To match the upstream code it should be
+            // array.iter().first().and_then(|v| v.as_object()).any(|v| v.is_string_schema())
+            Some(Items::Array(array)) => array
+                .iter()
+                .filter_map(|v| v.as_object())
+                .any(|v| v.is_string_schema()),
+            Some(Items::Object(map)) => map.is_string_schema(),
+            None => false,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::*;
+
+    fn items_with_len(len: usize) -> Items {
+        let mut vec = Vec::new();
+        vec.resize(len, json!(null));
+
+        Items::Array(vec)
+    }
+
+    #[test]
+    fn test_array_get_dim() {
+        assert_eq!(
+            Array {
+                items: None,
+                min_items: Some(2),
+                max_items: Some(3),
+            }
+            .get_dim(),
+            [2, 3]
+        );
+
+        assert_eq!(
+            Array {
+                items: Some(items_with_len(3)),
+                min_items: None,
+                max_items: None,
+            }
+            .get_dim(),
+            [3, 3]
+        );
+
+        assert_eq!(
+            Array {
+                items: Some(items_with_len(4)),
+                min_items: Some(2),
+                max_items: Some(3),
+            }
+            .get_dim(),
+            [2, 4]
+        )
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/compatible.rs b/third_party/rust_crates/forks/dt-schema/src/validator/compatible.rs
new file mode 100644
index 0000000..8032098
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/compatible.rs
@@ -0,0 +1,95 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::HashSet;
+
+fn generate_items<'a>(
+    value: &'a serde_json::Value,
+    key: &'static str,
+) -> Box<dyn Iterator<Item = &'a serde_json::Value> + 'a> {
+    match value {
+        serde_json::Value::Object(o) => Box::new(o.iter().flat_map(move |(k, v)| {
+            if k == key {
+                Box::new(Some(v).into_iter())
+            } else {
+                generate_items(v, key)
+            }
+        })),
+        serde_json::Value::Array(a) => Box::new(a.iter().flat_map(move |v| generate_items(v, key))),
+        _ => Box::new([].into_iter()),
+    }
+}
+
+/// Given |property_schema| which is the schema for a "compatible" property, returns a set
+/// of all possible compatibles the schema could accept.
+pub fn extract_node_compatibles(property_schema: &serde_json::Value) -> HashSet<String> {
+    let mut result = HashSet::new();
+
+    result.extend(
+        generate_items(property_schema, "enum")
+            .filter_map(|v| {
+                v.as_array().map(|array| {
+                    array
+                        .iter()
+                        .filter_map(|v| v.as_str().map(|v| v.to_owned()))
+                })
+            })
+            .flatten(),
+    );
+    result.extend(
+        generate_items(property_schema, "const").filter_map(|v| v.as_str().map(|v| v.to_owned())),
+    );
+    result.extend(
+        generate_items(property_schema, "pattern").filter_map(|v| v.as_str().map(|v| v.to_owned())),
+    );
+
+    result
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::*;
+    #[test]
+    fn test_generate_items() {
+        let value = json!({
+            "array": [
+                {
+                    "hello": "one",
+                },
+                "two",
+                "three",
+            ],
+            "oneOf": {
+                "hello": "there",
+                "ignore": "me",
+            }
+        });
+
+        let result = generate_items(&value, "hello").collect::<Vec<_>>();
+
+        assert_eq!(result, vec!["one", "there"]);
+    }
+
+    #[test]
+    fn test_extract_compatibles() {
+        let value = json!({
+            "oneOf": {
+            "enum": ["hello,there", "another-one"],
+            "const": "value",
+            "pattern": "^[oO]+h,regexp?$"
+            }
+        });
+
+        let mut result = extract_node_compatibles(&value)
+            .into_iter()
+            .collect::<Vec<_>>();
+        result.sort();
+        assert_eq!(
+            result,
+            vec!["^[oO]+h,regexp?$", "another-one", "hello,there", "value"]
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/dimension.rs b/third_party/rust_crates/forks/dt-schema/src/validator/dimension.rs
new file mode 100644
index 0000000..8e47353
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/dimension.rs
@@ -0,0 +1,162 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde::Serialize;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
+struct Range {
+    min: usize,
+    max: usize,
+}
+
+impl Range {
+    /// True if both min and max are zero.
+    fn is_zero(&self) -> bool {
+        self.min == 0 && self.max == 0
+    }
+
+    /// True if either value is not zero.
+    fn has_value(&self) -> bool {
+        self.min != 0 || self.max != 0
+    }
+
+    /// True if min == max.
+    fn is_fixed(&self) -> bool {
+        self.min == self.max
+    }
+
+    /// Return a range that encompasses |self| and |other|.
+    fn merge(&self, other: &Range) -> Range {
+        if self.is_zero() {
+            *other
+        } else if other.is_zero() {
+            *self
+        } else {
+            Range {
+                min: std::cmp::min(self.min, other.min),
+                max: std::cmp::max(self.max, other.max),
+            }
+        }
+    }
+}
+
+impl From<[usize; 2]> for Range {
+    fn from(value: [usize; 2]) -> Self {
+        Range {
+            min: value[0],
+            max: value[1],
+        }
+    }
+}
+
+impl From<Range> for [usize; 2] {
+    fn from(value: Range) -> Self {
+        [value.min, value.max]
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Hash, PartialOrd, Ord, Default)]
+#[serde(from = "[[usize; 2]; 2]", into = "[[usize; 2]; 2]")]
+/// |Dimension| represents constraints on a matrix.
+pub struct Dimension {
+    /// Range of outer elements possible.
+    outer: Range,
+    /// Range of elements allowed in each inner element.
+    inner: Range,
+}
+
+impl From<[[usize; 2]; 2]> for Dimension {
+    fn from(value: [[usize; 2]; 2]) -> Self {
+        Dimension {
+            outer: value[0].into(),
+            inner: value[1].into(),
+        }
+    }
+}
+
+impl From<Dimension> for [[usize; 2]; 2] {
+    fn from(value: Dimension) -> Self {
+        [value.outer.into(), value.inner.into()]
+    }
+}
+
+impl Dimension {
+    /// Merge two dimensions into a third dimension.
+    pub fn merge(&self, other: Dimension) -> Dimension {
+        Dimension {
+            outer: self.outer.merge(&other.outer),
+            inner: self.inner.merge(&other.inner),
+        }
+    }
+
+    /// Given an array with dimensions 1xN, returns the stride
+    /// that should be used to turn it into array with dimensions matching this one.
+    /// TODO(simonshields): write tests.
+    pub fn stride(&self, array_length: usize) -> usize {
+        if !self.inner.is_zero() && self.inner.is_fixed() {
+            self.inner.max
+        } else if !self.outer.is_zero() && self.outer.is_fixed() {
+            if array_length % self.outer.max != 0 {
+                array_length
+            } else {
+                self.outer.max
+            }
+        } else if !self.inner.has_value() {
+            array_length
+        } else {
+            let mut matches = 0;
+            let mut ret = 0;
+            for dimension in self.inner.min..self.inner.max + 1 {
+                if array_length % dimension == 0 {
+                    matches += 1;
+                    ret = dimension;
+                }
+            }
+
+            if matches == 1 {
+                ret
+            } else {
+                array_length
+            }
+        }
+    }
+
+    pub fn is_fixed(&self) -> bool {
+        self.inner.is_fixed() || self.outer.is_fixed()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Dimension;
+    #[test]
+    fn test_dimension_merge() {
+        let a: Dimension = [[0, 0], [0, 0]].into();
+        let b: Dimension = [[1, 1], [2, 3]].into();
+        assert_eq!(a.merge(b), b);
+
+        let a: Dimension = [[1, 4], [5, 9]].into();
+        let b: Dimension = [[2, 5], [4, 7]].into();
+        assert_eq!(a.merge(b), [[1, 5], [4, 9]].into());
+    }
+
+    #[test]
+    fn test_stride_simple() {
+        let a: Dimension = [[1, 1], [4, 4]].into();
+        assert_eq!(a.stride(8), 4);
+
+        let b: Dimension = [[2, 2], [1, 2]].into();
+        assert_eq!(b.stride(4), 2);
+        assert_eq!(b.stride(3), 3);
+    }
+
+    #[test]
+    fn test_stride_variable() {
+        let a: Dimension = [[1, 2], [3, 8]].into();
+        assert_eq!(a.stride(10), 5);
+        assert_eq!(a.stride(8), 8);
+        assert_eq!(a.stride(7), 7);
+        assert_eq!(a.stride(6), 6);
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/error.rs b/third_party/rust_crates/forks/dt-schema/src/validator/error.rs
new file mode 100644
index 0000000..d84bf11
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/error.rs
@@ -0,0 +1,43 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use valico::json_schema::SchemaError;
+
+use crate::path::JsonPath;
+
+#[derive(thiserror::Error, Debug)]
+pub enum ValidatorError {
+    #[error("i/o error")]
+    IoError(#[from] std::io::Error),
+    #[error("serde yaml error")]
+    YamlError(#[from] serde_yaml::Error),
+    #[error("serde json error")]
+    JsonError(#[from] serde_json::Error),
+    #[error("invalid regular expression")]
+    RegexError(#[from] fancy_regex::Error),
+
+    #[error("invalid reference: {0}")]
+    InvalidReference(String),
+    #[error("unknown property type: {0}")]
+    UnknownPropType(String),
+
+    #[error("json schema error: {0}")]
+    SchemaError(#[from] SchemaError),
+
+    #[error("schema is missing key '{0}' at {1}")]
+    ExpectedKey(String, JsonPath),
+}
+
+/*
+impl<'a> From<jsonschema::error::ValidationError<'a>> for ValidatorError {
+    fn from(value: jsonschema::error::ValidationError<'a>) -> Self {
+        ValidatorError::SchemaError {
+            value: value.instance.into_owned(),
+            kind: value.kind,
+            instance_path: value.instance_path,
+            schema_path: value.schema_path,
+        }
+    }
+}
+*/
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups.rs
new file mode 100644
index 0000000..b1220e8
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups.rs
@@ -0,0 +1,78 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use crate::path::JsonPath;
+
+mod common_properties;
+mod empty_items;
+mod fixup_201909;
+mod int_items_to_matrix;
+mod int_minmax;
+mod interrupts;
+mod items_size;
+pub mod property_fixups;
+mod reg;
+pub mod schema_fixups;
+mod single_int;
+mod single_string;
+mod variable_matrix_fixup;
+
+#[derive(thiserror::Error, Debug)]
+pub enum FixupError {
+    #[error("JSON error {0} at {1}")]
+    JsonError(serde_json::Error, Box<std::backtrace::Backtrace>),
+
+    #[error("Could not parse schema as it was in an unexpected format: {0}. at {1}, value: {2}")]
+    UnexpectedSchemaError(String, JsonPath, serde_json::Value),
+}
+
+impl From<serde_json::Error> for FixupError {
+    fn from(value: serde_json::Error) -> Self {
+        FixupError::JsonError(value, Box::new(std::backtrace::Backtrace::force_capture()))
+    }
+}
+
+pub trait Fixup: Sized {
+    /// Make a new instance of this |fixup|. Returns None if the fixup is not applicable to this property.
+    fn new(
+        propname: &str,
+        value: &serde_json::Value,
+        path: JsonPath,
+    ) -> Result<Option<Self>, FixupError>;
+
+    /// Run the fixup and give the fixed |Value| back.
+    fn fixup(self) -> Result<serde_json::Value, FixupError>;
+}
+
+/// Helper function that does a fixup (if it is applicable, i.e. T::new() returns Ok(Some(...))), or just returns the given |value|.
+/// |path| should not include |propname|.
+fn do_fixup<T: Fixup>(
+    propname: &str,
+    value: serde_json::Value,
+    path: &JsonPath,
+) -> Result<serde_json::Value, FixupError> {
+    let extended = path.extend(propname);
+    let result = T::new(propname, &value, extended.clone())?
+        .map(|v| v.fixup())
+        .unwrap_or(Ok(value.clone()));
+    if Some(&value) != result.as_ref().ok() {
+        let string = serde_json::to_string(result.as_ref().unwrap_or(&serde_json::json!(null)))
+            .unwrap_or_else(|_| "N/A".to_owned());
+        if string.len() <= 100 {
+            tracing::trace!(
+                "applied fixup {} to {}: {}",
+                std::any::type_name::<T>(),
+                extended,
+                string,
+            );
+        } else {
+            tracing::trace!(
+                "applied fixup {} to {}: <omitted>",
+                std::any::type_name::<T>(),
+                extended,
+            );
+        }
+    }
+    result
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/common_properties.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/common_properties.rs
new file mode 100644
index 0000000..8e614eeb
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/common_properties.rs
@@ -0,0 +1,262 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::HashSet;
+
+use fancy_regex::Regex;
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::Fixup;
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct NodeWithProperties {
+    #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
+    properties: serde_json::Map<String, serde_json::Value>,
+    #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
+    pattern_properties: serde_json::Map<String, serde_json::Value>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    unevaluated_properties: Option<serde_json::Value>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    additional_properties: Option<serde_json::Value>,
+
+    #[serde(flatten)]
+    etc: serde_json::Map<String, serde_json::Value>,
+}
+
+impl NodeWithProperties {
+    fn has_properties(&self) -> bool {
+        !self.properties.is_empty()
+            || !self.pattern_properties.is_empty()
+            || self.unevaluated_properties.is_some()
+    }
+
+    fn is_incomplete_schema(&self) -> bool {
+        self.unevaluated_properties
+            .as_ref()
+            .and_then(|v| v.as_bool())
+            .unwrap_or(false)
+            || self
+                .additional_properties
+                .as_ref()
+                .and_then(|v| v.as_bool())
+                .unwrap_or(false)
+    }
+}
+
+/// Add missing node properties to schemas.
+/// This includes adding phandle, status, secure-status and $nodename unconditionally,
+/// adding pinctrl properties if needed and also adding assigned-clock properties if needed.
+pub struct CommonPropertiesFixup {
+    node: NodeWithProperties,
+}
+
+impl Fixup for CommonPropertiesFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        _path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        if !value.is_object() {
+            return Ok(None);
+        }
+        let value: NodeWithProperties = serde_json::from_value(value.clone())?;
+        if !value.has_properties() || value.is_incomplete_schema() {
+            Ok(None)
+        } else {
+            Ok(Some(CommonPropertiesFixup { node: value }))
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        for value in ["phandle", "status", "secure-status", "$nodename"] {
+            self.node.properties.entry(value).or_insert(json!(true));
+        }
+
+        let key_pinctrl_regex = Regex::new("^pinctrl-[0-9]").unwrap();
+        let all_keys: HashSet<String> = self
+            .node
+            .properties
+            .keys()
+            .chain(self.node.pattern_properties.keys())
+            .cloned()
+            .collect();
+
+        if !all_keys
+            .iter()
+            .any(|key| key_pinctrl_regex.is_match(key).unwrap())
+        {
+            self.node
+                .properties
+                .entry("pinctrl-names")
+                .or_insert(json!(true));
+            self.node
+                .pattern_properties
+                .insert("pinctrl-[0-9]+".to_owned(), json!(true));
+        }
+
+        if all_keys.contains("clocks") && !all_keys.contains("assigned-clocks") {
+            self.node
+                .properties
+                .insert("assigned-clocks".to_owned(), json!(true));
+            self.node
+                .properties
+                .insert("assigned-clock-rates".to_owned(), json!(true));
+            self.node
+                .properties
+                .insert("assigned-clock-parents".to_owned(), json!(true));
+        }
+
+        Ok(serde_json::to_value(self.node)?)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_incomplete() {
+        let schema = json!({
+            "properties": {
+                "clocks": 10,
+            },
+            "additionalProperties": true,
+        });
+
+        assert!(CommonPropertiesFixup::new("", &schema, JsonPath::new())
+            .expect("No errors")
+            .is_none());
+    }
+
+    #[test]
+    fn test_no_properties() {
+        let schema = json!({});
+
+        assert!(CommonPropertiesFixup::new("", &schema, JsonPath::new())
+            .expect("No errors")
+            .is_none());
+    }
+
+    #[test]
+    fn test_basic_properties() {
+        let schema = json!({"properties": { "example": true }});
+
+        let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+            .expect("No errors")
+            .expect("Applies to schema")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "example": true,
+                    "phandle": true,
+                    "status": true,
+                    "secure-status": true,
+                    "$nodename": true,
+                    "pinctrl-names": true,
+                },
+                "patternProperties": {
+                    "pinctrl-[0-9]+": true,
+                }
+            })
+        );
+    }
+
+    #[test]
+    fn test_basic_properties_not_overwritten() {
+        let schema = json!({"properties": {
+            "$nodename": {"const": "foo"}
+        }});
+
+        let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+            .expect("No errors")
+            .expect("Applies to schema")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "phandle": true,
+                    "status": true,
+                    "secure-status": true,
+                    "$nodename": {"const": "foo"},
+                    "pinctrl-names": true,
+                },
+                "patternProperties": {
+                    "pinctrl-[0-9]+": true,
+                }
+            })
+        );
+    }
+
+    #[test]
+    fn test_adds_assigned_clocks() {
+        let schema = json!({"properties": {
+            "clocks": {"maxItems": 2}
+        }});
+
+        let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+            .expect("No errors")
+            .expect("Applies to schema")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "clocks": {"maxItems": 2},
+                    "phandle": true,
+                    "status": true,
+                    "secure-status": true,
+                    "$nodename": true,
+                    "pinctrl-names": true,
+                    "assigned-clocks": true,
+                    "assigned-clock-rates": true,
+                    "assigned-clock-parents": true
+                },
+                "patternProperties": {
+                    "pinctrl-[0-9]+": true,
+                }
+            })
+        );
+    }
+
+    #[test]
+    fn test_explicit_pinctrl() {
+        let schema = json!({"properties": {
+            "pinctrl-0": true,
+            "pinctrl-1": true,
+        }});
+
+        let result = CommonPropertiesFixup::new("", &schema, JsonPath::new())
+            .expect("No errors")
+            .expect("Applies to schema")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "phandle": true,
+                    "status": true,
+                    "secure-status": true,
+                    "$nodename": true,
+                    "pinctrl-0": true,
+                    "pinctrl-1": true,
+                }
+            })
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/empty_items.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/empty_items.rs
new file mode 100644
index 0000000..962751d
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/empty_items.rs
@@ -0,0 +1,129 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::{Fixup, FixupError};
+
+pub struct EmptyItemsRemovalFixup {
+    object: serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+}
+
+impl EmptyItemsRemovalFixup {
+    fn fixup_one_object(
+        obj: &mut serde_json::Map<String, serde_json::Value>,
+        path: JsonPath,
+    ) -> Result<(), FixupError> {
+        let fixed_item_length = match obj.get_mut("items") {
+            Some(serde_json::Value::Array(items)) => {
+                let mut has_value = false;
+                for (i, item) in items
+                    .iter_mut()
+                    .enumerate()
+                    .filter_map(|(k, v)| v.as_object_mut().map(|v| (k, v)))
+                {
+                    item.remove("description");
+                    Self::fixup_one_object(item, path.extend_array_index("items", i))?;
+                    if !item.is_empty() {
+                        // We found a value, so we're not going to remove the "items" child from this array.
+                        has_value = true;
+                        break;
+                    }
+                }
+
+                if has_value {
+                    None
+                } else {
+                    Some(items.len())
+                }
+            }
+            Some(serde_json::Value::Object(o)) => {
+                Self::fixup_one_object(o, path.extend("items"))?;
+                None
+            }
+            None => return Ok(()),
+            _ => {
+                return Err(FixupError::UnexpectedSchemaError(
+                    "items should be array or object".to_owned(),
+                    path.extend("items"),
+                    obj.clone().into(),
+                ))
+            }
+        };
+
+        if let Some(length) = fixed_item_length {
+            obj.entry("type").or_insert(json!("array"));
+            obj.entry("maxItems").or_insert(json!(length));
+            obj.entry("minItems").or_insert(json!(length));
+            obj.remove("items");
+        }
+
+        Ok(())
+    }
+}
+
+impl Fixup for EmptyItemsRemovalFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        let map_ref = value.as_object().ok_or(FixupError::UnexpectedSchemaError(
+            "EmptyItemsRemovalFixup expects an object".to_owned(),
+            path.clone(),
+            value.clone(),
+        ))?;
+
+        if !map_ref.contains_key("items") {
+            Ok(None)
+        } else {
+            Ok(Some(EmptyItemsRemovalFixup {
+                object: map_ref.clone(),
+                path,
+            }))
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        Self::fixup_one_object(&mut self.object, self.path)?;
+        Ok(self.object.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_remove_empty_items() {
+        let data = json!({
+            "items": [{
+                "items": [{
+                    "description": "wow, some items",
+                }]
+            },
+            {"items": []},
+            {}
+            ]
+        });
+
+        let result = EmptyItemsRemovalFixup::new("", &data, JsonPath::new())
+            .expect("schema ok")
+            .unwrap()
+            .fixup()
+            .expect("fixup ok");
+        assert_eq!(
+            result,
+            json!({"items": [
+                {"minItems": 1, "maxItems": 1, "type": "array"},
+                {"items": []},
+                {}
+            ]})
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/fixup_201909.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/fixup_201909.rs
new file mode 100644
index 0000000..cd72b7a
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/fixup_201909.rs
@@ -0,0 +1,107 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::{Fixup, FixupError};
+
+/// Fix pre-201909 schemas to be compatible with 201909.
+/// The only transformation is splitting "dependencies" into "dependentRequired" and "dependentSchemas" per
+/// https://json-schema.org/understanding-json-schema/reference/conditionals.html (see "draft-specific info").
+pub struct Fixup201909 {
+    map: serde_json::Map<String, serde_json::Value>,
+    path: JsonPath,
+}
+
+impl Fixup for Fixup201909 {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        match value {
+            serde_json::Value::Object(o) => {
+                if o.contains_key("dependencies") {
+                    Ok(Some(Fixup201909 {
+                        map: o.clone(),
+                        path,
+                    }))
+                } else {
+                    Ok(None)
+                }
+            }
+            _ => Ok(None),
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        let value = self.map.remove("dependencies").unwrap();
+        let dependencies = match value {
+            serde_json::Value::Object(o) => o,
+            _ => {
+                return Err(FixupError::UnexpectedSchemaError(
+                    "dependencies should be a map".to_owned(),
+                    self.path.extend("dependencies"),
+                    value.clone(),
+                ));
+            }
+        };
+
+        for (k, v) in dependencies.into_iter() {
+            match v {
+                serde_json::Value::Array(array) => {
+                    let dependent_required = self.map.entry("dependentRequired");
+                    dependent_required
+                        .or_insert(json!({}))
+                        .as_object_mut()
+                        .unwrap()
+                        .insert(k, array.into());
+                }
+                value => {
+                    let dependent_schemas = self.map.entry("dependentSchemas");
+                    dependent_schemas
+                        .or_insert(json!({}))
+                        .as_object_mut()
+                        .unwrap()
+                        .insert(k, value);
+                }
+            }
+        }
+
+        Ok(self.map.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_translate_old_schema() {
+        let old_value = json!({
+            "dependencies": {
+                "propertyOne": ["propertyTwo"],
+                "propertyTwo": {
+                    "propertyThree": {"type": "string"}
+                }
+            }
+        });
+
+        let result = Fixup201909::new("", &old_value, JsonPath::new())
+            .expect("Schema ok")
+            .unwrap()
+            .fixup()
+            .expect("Fixup ok");
+        assert_eq!(
+            result,
+            json!({
+                "dependentRequired": {"propertyOne": ["propertyTwo"]},
+                "dependentSchemas": {"propertyTwo": {
+                    "propertyThree": {"type": "string"}
+                }}
+            })
+        );
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_items_to_matrix.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_items_to_matrix.rs
new file mode 100644
index 0000000..8883f02
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_items_to_matrix.rs
@@ -0,0 +1,173 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::BTreeMap;
+
+use serde_json::json;
+
+use crate::validator::array::Array;
+
+use super::Fixup;
+
+/// Convert an int array with an "items" declaration
+/// to a matrix.
+pub struct IntItemsToMatrix {
+    value: serde_json::Value,
+}
+
+impl Fixup for IntItemsToMatrix {
+    fn new(
+        propname: &str,
+        value: &serde_json::Value,
+        _path: crate::path::JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        let items: Array = serde_json::from_value(value.clone())?;
+        if !items.is_int_array_schema(propname) {
+            return Ok(None);
+        }
+
+        Ok(Some(IntItemsToMatrix {
+            value: value.clone(),
+        }))
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        let final_schema = if let Some(array) = self
+            .value
+            .as_object_mut()
+            .and_then(|e| e.get_mut("allOf"))
+            .and_then(|e| e.as_array_mut())
+        {
+            array
+                .iter_mut()
+                .filter_map(|v| v.as_object_mut())
+                .find(|o| o.contains_key("items"))
+        } else {
+            self.value.as_object_mut()
+        };
+
+        let object = if let Some(object) = final_schema {
+            let items: Array = serde_json::from_value(object.clone().into())?;
+            if !items.has_items() || items.is_matrix() {
+                return Ok(self.value);
+            }
+            object
+        } else {
+            return Ok(self.value);
+        };
+
+        let item_keys = ["items", "minItems", "maxItems", "uniqueItems", "default"];
+        let mut values: BTreeMap<String, serde_json::Value> = BTreeMap::new();
+        for key in item_keys {
+            if let Some(value) = object.remove(key) {
+                values.insert(key.to_owned(), value);
+            }
+        }
+
+        // Safe because we checked |items.has_items()| above.
+        match values.get_mut("items").unwrap() {
+            serde_json::Value::Array(_) => {
+                object.insert("items".to_owned(), json!([values]));
+            }
+            serde_json::Value::Object(_) => {
+                object.insert("items".to_owned(), json!(values));
+            }
+            _ => {}
+        };
+
+        Ok(self.value)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::path::JsonPath;
+
+    use super::*;
+
+    const PROP_NAME: &str = "test-percent";
+
+    #[test]
+    fn test_make_matrix_simple() {
+        let schema = json!({
+         "items": {
+             "minimum": 2,
+             "maximum": 10,
+         },
+         "maxItems": 2,
+         "minItems": 2,
+        });
+
+        let result = IntItemsToMatrix::new(PROP_NAME, &schema, JsonPath::new())
+            .expect("Valid schema")
+            .expect("Fixup applies")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "items": {
+                     "items": {
+                         "minimum": 2,
+                         "maximum": 10,
+                     },
+                     "maxItems": 2,
+                     "minItems": 2,
+                },
+            })
+        )
+    }
+
+    #[test]
+    fn test_make_matrix_with_allof() {
+        let schema = json!({
+            "allOf": [
+                true,
+                {"maxItems": 2, "minItems": 1, "items": [{"const": 4}]},
+            ]
+        });
+
+        let result = IntItemsToMatrix::new(PROP_NAME, &schema, JsonPath::new())
+            .expect("Valid schema")
+            .expect("Fixup applies")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "allOf": [
+                    true,
+                    {
+                        "items": [{
+                            "maxItems": 2,
+                            "minItems": 1,
+                            "items": [{"const": 4}]
+                        }]
+                    }
+                ]
+            })
+        );
+    }
+
+    #[test]
+    fn test_skips_existing_matrix() {
+        let schema = json!({
+            "items": {
+                "items": {
+                    "minItems": 2,
+                }
+            }
+        });
+
+        let result = IntItemsToMatrix::new(PROP_NAME, &schema, JsonPath::new())
+            .expect("Valid schema")
+            .expect("Fixup applies")
+            .fixup()
+            .expect("Fixup OK");
+
+        assert_eq!(result, schema);
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_minmax.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_minmax.rs
new file mode 100644
index 0000000..d9f5f5e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/int_minmax.rs
@@ -0,0 +1,202 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde_json::json;
+
+use crate::{
+    path::JsonPath,
+    validator::{array::Array, util::ContainsExt},
+};
+
+use super::{items_size::ItemsSizeFixup, Fixup, FixupError};
+
+/// Convert an int array defined by "minItems" and "maxItems" to a matrix.
+pub struct IntMinMaxToMatrixFixup {
+    object: serde_json::Value,
+    path: JsonPath,
+}
+
+impl IntMinMaxToMatrixFixup {
+    // Actually perform the fixup. Split like this to make the borrow-checker happy.
+    fn do_fixup_on_object(
+        map: &mut serde_json::Map<String, serde_json::Value>,
+        path: JsonPath,
+    ) -> Result<(), FixupError> {
+        if map.get_mut("items").and_then(|v| v.as_array()).is_some() {
+            return Ok(());
+        }
+
+        let items: Array = serde_json::from_value(map.clone().into())?;
+        if items.is_matrix() {
+            return Ok(());
+        }
+
+        if map.get("maxItems").and_then(|v| v.as_u64()) == Some(1) {
+            return Ok(());
+        }
+
+        let mut tmp_schema = serde_json::Map::<String, serde_json::Value>::new();
+        let min_items = if let Some(min) = map.remove("minItems") {
+            tmp_schema.insert("minItems".to_owned(), min.clone());
+            min.as_u64().unwrap_or(0)
+        } else {
+            0
+        };
+        if let Some(max) = map.remove("maxItems") {
+            tmp_schema.insert("maxItems".to_owned(), max);
+        }
+
+        if !tmp_schema.is_empty() {
+            let mut vec = vec![json!({"items": [tmp_schema.clone()]})];
+
+            tmp_schema.insert("items".to_owned(), json!({"maxItems": 1}));
+            if min_items == 1 {
+                tmp_schema.insert("minItems".to_owned(), json!(2));
+            }
+            vec.push(tmp_schema.into());
+
+            // We added "oneOf", so we need to manually do this fixup.
+            let value = serde_json::to_value(vec)?;
+            let size_fixup = ItemsSizeFixup::new("", &value, path.extend("oneOf"))?
+                .map(|v| v.fixup())
+                .ok_or(FixupError::UnexpectedSchemaError(
+                    "item size fixup should operate after minmax fixup".to_owned(),
+                    path.extend("oneOf"),
+                    value,
+                ))??;
+            map.insert("oneOf".to_owned(), size_fixup);
+        }
+
+        Ok(())
+    }
+}
+
+impl Fixup for IntMinMaxToMatrixFixup {
+    fn new(
+        propname: &str,
+        value: &serde_json::Value,
+        path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        let items: Array = serde_json::from_value(value.clone())?;
+        if !items.is_int_array_schema(propname) {
+            return Ok(None);
+        }
+
+        let value = value.clone();
+        match value.as_object() {
+            Some(_) => {}
+            None => {
+                return Ok(None);
+            }
+        };
+
+        Ok(Some(IntMinMaxToMatrixFixup {
+            object: value,
+            path,
+        }))
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        // Find the actual map we want to modify.
+        let map = self.object.as_object_mut().unwrap();
+        if let Some(all_of) = map.get_mut("allOf").and_then(|v| v.as_array_mut()) {
+            for (i, item) in all_of.iter_mut().enumerate() {
+                if item
+                    .as_object_mut()
+                    .map(|v| v.contains_any(&["minItems", "maxItems"]))
+                    .unwrap_or(false)
+                {
+                    Self::do_fixup_on_object(
+                        item.as_object_mut().unwrap(),
+                        self.path.extend_array_index("allOf", i),
+                    )?;
+                    break;
+                }
+            }
+        } else {
+            Self::do_fixup_on_object(map, self.path)?;
+        };
+
+        Ok(self.object)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    const PROP_NAME: &str = "test-percent";
+    #[test]
+    fn test_simple() {
+        let schema = json!({
+            "minItems": 2,
+            "maxItems": 3,
+        });
+
+        let result = IntMinMaxToMatrixFixup::new(PROP_NAME, &schema, JsonPath::new())
+            .expect("schema OK")
+            .expect("fixup applies")
+            .fixup()
+            .expect("fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "oneOf": [
+                    {
+                        "items": [
+                            {
+                                "minItems": 2,
+                                "maxItems": 3,
+                            }
+                        ],
+                        "minItems": 1,
+                        "maxItems": 1,
+                        "type": "array",
+                    },
+                    {
+                        "items": {
+                            "maxItems": 1,
+                            "minItems": 1,
+                        },
+                        "minItems": 2,
+                        "maxItems": 3,
+                        "type": "array",
+                    }
+                ]
+            })
+        );
+    }
+
+    #[test]
+    fn test_preserves_allof() {
+        let schema = json!({
+            "allOf": [
+                true,
+                {"minItems": 1, "maxItems": 4}
+            ]
+        });
+
+        let result = IntMinMaxToMatrixFixup::new(PROP_NAME, &schema, JsonPath::new())
+            .expect("schema OK")
+            .expect("fixup applies")
+            .fixup()
+            .expect("fixup OK");
+
+        assert_eq!(
+            result,
+            json!({
+                "allOf": [
+                    true,
+                    {
+                        "oneOf": [
+                            {"minItems": 1, "maxItems": 1, "items": [{"maxItems": 4, "minItems": 1}], "type": "array"},
+                            {"minItems": 2, "maxItems": 4, "items": {"minItems": 1, "maxItems": 1}, "type": "array"},
+                        ]
+                    }
+                ]
+            })
+        )
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/interrupts.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/interrupts.rs
new file mode 100644
index 0000000..52c19a7
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/interrupts.rs
@@ -0,0 +1,233 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::HashSet;
+
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::Fixup;
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+struct InterruptProps {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    interrupts: Option<serde_json::Value>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    interrupts_extended: Option<serde_json::Value>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    interrupt_controller: Option<serde_json::Value>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    interrupt_parent: Option<serde_json::Value>,
+
+    #[serde(flatten)]
+    etc: serde_json::Value,
+}
+
+#[derive(Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct Interrupt {
+    properties: InterruptProps,
+    #[serde(default, skip_serializing_if = "HashSet::is_empty")]
+    required: HashSet<String>,
+
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    one_of: Vec<serde_json::Value>,
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    all_of: Vec<serde_json::Value>,
+
+    #[serde(flatten)]
+    etc: serde_json::Value,
+}
+
+/// |InterruptsFixup| does two things:
+/// 1. Allows any node with "interrupts" or "interrupt-controller" properties to have an "interrupt-parent"
+/// 2. Allows any node with "interrupts" to have "interrupts-extended".
+pub struct InterruptsFixup {
+    interrupt: Interrupt,
+}
+
+impl Fixup for InterruptsFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        _path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        if value
+            .as_object()
+            .and_then(|v| v.get("properties"))
+            .is_some()
+        {
+            Ok(Some(InterruptsFixup {
+                interrupt: serde_json::from_value(value.clone())?,
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        let props = &mut self.interrupt.properties;
+
+        // Any node with interrupts can have 'interrupt-parent'.
+        if props
+            .interrupts
+            .as_ref()
+            .or(props.interrupt_controller.as_ref())
+            .is_some()
+            && props.interrupt_parent.is_none()
+        {
+            props.interrupt_parent = Some(json!(true));
+        }
+
+        // Any node with 'interrupts' can also have 'interrupts-extended'.
+        match (
+            props.interrupts.as_ref(),
+            props.interrupts_extended.as_ref(),
+        ) {
+            (None, _) => return Ok(serde_json::to_value(self.interrupt)?),
+            (Some(interrupts), None) => props.interrupts_extended = Some(interrupts.clone()),
+            (Some(_), Some(_)) => {}
+        }
+
+        if self.interrupt.required.remove("interrupts") {
+            let required = vec![
+                json!({"required": ["interrupts"]}),
+                json!({"required": ["interrupts-extended"]}),
+            ];
+            if !self.interrupt.one_of.is_empty() {
+                if self.interrupt.all_of.is_empty() {
+                    self.interrupt.all_of.push(json!({ "oneOf": required }));
+                } else {
+                    self.interrupt.all_of = vec![json!({ "oneOf": required })];
+                }
+            } else {
+                self.interrupt.one_of = required;
+            }
+        }
+        Ok(serde_json::to_value(self.interrupt)?)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_interrupt_controller() {
+        let schema = json!({
+            "properties": {
+                "interrupt-controller": true,
+            }
+        });
+
+        let result = InterruptsFixup::new("", &schema, JsonPath::new())
+            .expect("schema ok")
+            .expect("fixup applies")
+            .fixup()
+            .expect("fixup ok");
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "interrupt-controller": true,
+                    "interrupt-parent": true,
+                }
+            })
+        )
+    }
+
+    #[test]
+    fn test_interrupts() {
+        let schema = json!({
+            "properties": {
+                "interrupts": {"items": [{"description": "IRQ 1"}]},
+            }
+        });
+
+        let result = InterruptsFixup::new("", &schema, JsonPath::new())
+            .expect("schema ok")
+            .expect("fixup applies")
+            .fixup()
+            .expect("fixup ok");
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "interrupt-parent": true,
+                    "interrupts": {"items": [{"description": "IRQ 1"}]},
+                    "interrupts-extended": {"items": [{"description": "IRQ 1"}]},
+                }
+            })
+        )
+    }
+
+    #[test]
+    fn test_interrupts_required() {
+        let schema = json!({
+            "properties": {
+                "interrupts": {"items": [{"description": "IRQ 1"}]},
+            },
+            "required": ["interrupts"]
+        });
+
+        let result = InterruptsFixup::new("", &schema, JsonPath::new())
+            .expect("schema ok")
+            .expect("fixup applies")
+            .fixup()
+            .expect("fixup ok");
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "interrupt-parent": true,
+                    "interrupts": {"items": [{"description": "IRQ 1"}]},
+                    "interrupts-extended": {"items": [{"description": "IRQ 1"}]},
+                },
+                "oneOf": [
+                    {"required": ["interrupts"]},
+                    {"required": ["interrupts-extended"]}
+                ]
+            })
+        )
+    }
+
+    #[test]
+    fn test_interrupts_required_already_oneof() {
+        let schema = json!({
+            "properties": {
+                "interrupts": {"items": [{"description": "IRQ 1"}]},
+            },
+            "required": ["interrupts"],
+            "oneOf": [{"$nodename": "hello"}, {"$nodename": "hello2"}]
+        });
+
+        let result = InterruptsFixup::new("", &schema, JsonPath::new())
+            .expect("schema ok")
+            .expect("fixup applies")
+            .fixup()
+            .expect("fixup ok");
+        assert_eq!(
+            result,
+            json!({
+                "properties": {
+                    "interrupt-parent": true,
+                    "interrupts": {"items": [{"description": "IRQ 1"}]},
+                    "interrupts-extended": {"items": [{"description": "IRQ 1"}]},
+                },
+                "oneOf": [{"$nodename": "hello"}, {"$nodename": "hello2"}],
+                "allOf": [
+                    {
+                        "oneOf": [
+                            {"required": ["interrupts"]},
+                            {"required": ["interrupts-extended"]}
+                        ]
+                    },
+                ]
+            })
+        )
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/items_size.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/items_size.rs
new file mode 100644
index 0000000..192b05e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/items_size.rs
@@ -0,0 +1,141 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::json;
+
+use crate::path::JsonPath;
+
+use super::{Fixup, FixupError};
+
+pub struct ItemsSizeFixup {
+    value: serde_json::Value,
+    path: JsonPath,
+}
+
+impl ItemsSizeFixup {
+    fn do_one_fixup(item: &mut serde_json::Value, path: JsonPath) -> Result<(), FixupError> {
+        match item {
+            serde_json::Value::Array(array) => {
+                for (i, item) in array.iter_mut().enumerate() {
+                    Self::do_one_fixup(item, path.extend_index_only(i))?;
+                }
+            }
+            serde_json::Value::Object(obj) => {
+                obj.remove("description");
+                if let Some(items) = obj.get("items") {
+                    if let Some(array) = items.as_array() {
+                        let length = json!(array.len());
+                        obj.entry("minItems").or_insert(length.clone());
+                        obj.entry("maxItems").or_insert(length);
+                    }
+                    obj.insert("type".to_owned(), json!("array"));
+
+                    Self::do_one_fixup(obj.get_mut("items").unwrap(), path.extend("items"))?;
+                } else {
+                    match (obj.get("minItems"), obj.get("maxItems")) {
+                        (Some(min), None) => obj.insert("maxItems".to_owned(), min.clone()),
+                        (None, Some(max)) => obj.insert("minItems".to_owned(), max.clone()),
+                        _ => None,
+                    };
+                }
+            }
+            _ => {}
+        }
+
+        Ok(())
+    }
+}
+
+impl Fixup for ItemsSizeFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        match value {
+            serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
+                Ok(Some(ItemsSizeFixup {
+                    value: value.clone(),
+                    path,
+                }))
+            }
+            _ => Ok(None),
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        Self::do_one_fixup(&mut self.value, self.path)?;
+        Ok(self.value)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_implicit_size() {
+        let array = json!({
+            "items": [{"const": "a"}, {"const": "b"}]
+        });
+        let result = ItemsSizeFixup::new("", &array, JsonPath::new())
+            .unwrap()
+            .unwrap()
+            .fixup()
+            .expect("fixup ok");
+
+        assert_eq!(
+            result,
+            json!({
+                "minItems": 2,
+                "maxItems": 2,
+                "type": "array",
+                "items": [{"const": "a"}, {"const": "b"}]
+            })
+        )
+    }
+
+    #[test]
+    fn test_implicit_max_size() {
+        let array = json!({
+            "items": [{"const": "a"}, {"const": "b"}],
+            "minItems": 1
+        });
+        let result = ItemsSizeFixup::new("", &array, JsonPath::new())
+            .unwrap()
+            .unwrap()
+            .fixup()
+            .expect("fixup ok");
+
+        assert_eq!(
+            result,
+            json!({
+                "minItems": 1,
+                "maxItems": 2,
+                "type": "array",
+                "items": [{"const": "a"}, {"const": "b"}]
+            })
+        )
+    }
+
+    #[test]
+    fn test_no_items() {
+        let array = json!({
+            "minItems": 1
+        });
+        let result = ItemsSizeFixup::new("", &array, JsonPath::new())
+            .unwrap()
+            .unwrap()
+            .fixup()
+            .expect("fixup ok");
+
+        assert_eq!(
+            result,
+            json!({
+                "minItems": 1,
+                "maxItems": 1,
+            })
+        )
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/property_fixups.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/property_fixups.rs
new file mode 100644
index 0000000..0baa49b
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/property_fixups.rs
@@ -0,0 +1,61 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use super::{
+    do_fixup, empty_items::EmptyItemsRemovalFixup, fixup_201909::Fixup201909,
+    int_items_to_matrix::IntItemsToMatrix, int_minmax::IntMinMaxToMatrixFixup,
+    items_size::ItemsSizeFixup, reg::RegFixup, single_int::SingleIntFixup,
+    single_string::SingleStringFixup, variable_matrix_fixup::VariableIntMatrixFixup, FixupError,
+};
+use crate::path::JsonPath;
+
+pub fn walk_properties(
+    propname: &str,
+    mut value: serde_json::Value,
+    prop_path: JsonPath,
+) -> Result<serde_json::Value, FixupError> {
+    if let Some(map) = value.as_object_mut() {
+        for cond in ["allOf", "anyOf", "oneOf"] {
+            if let Some(serde_json::Value::Array(array)) = map.get_mut(cond) {
+                for (i, item) in array.iter_mut().enumerate() {
+                    *item = walk_properties(
+                        propname,
+                        item.clone(),
+                        prop_path.extend_array_index(cond, i),
+                    )?;
+                }
+            }
+        }
+
+        if let Some(value) = map.get_mut("then") {
+            *value = walk_properties(propname, value.clone(), prop_path.extend("then"))?;
+        }
+
+        fixup_properties(propname, value, &prop_path)
+    } else {
+        Ok(value)
+    }
+}
+
+fn fixup_properties(
+    propname: &str,
+    mut value: serde_json::Value,
+    path: &JsonPath,
+) -> Result<serde_json::Value, FixupError> {
+    value.as_object_mut().and_then(|v| v.remove("description"));
+
+    let value = do_fixup::<RegFixup>(propname, value, path)?;
+    let value = do_fixup::<EmptyItemsRemovalFixup>(propname, value, path)?;
+    let value = do_fixup::<VariableIntMatrixFixup>(propname, value, path)?;
+
+    let value = do_fixup::<IntMinMaxToMatrixFixup>(propname, value, path)?;
+    let value = do_fixup::<IntItemsToMatrix>(propname, value, path)?;
+
+    let value = do_fixup::<SingleStringFixup>(propname, value, path)?;
+    let value = do_fixup::<SingleIntFixup>(propname, value, path)?;
+    let value = do_fixup::<ItemsSizeFixup>(propname, value, path)?;
+    let value = do_fixup::<Fixup201909>(propname, value, path)?;
+
+    Ok(value)
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/reg.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/reg.rs
new file mode 100644
index 0000000..5c20d7e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/reg.rs
@@ -0,0 +1,120 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::HashMap;
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::util::IsSchema};
+
+use super::{Fixup, FixupError};
+
+/// Translate a "reg" property schema into a more fully formed schema that identifies the number
+/// of items in each reg "entry" as well as the total number of reg entries.
+pub struct RegFixup {
+    object: serde_json::Map<String, serde_json::Value>,
+}
+
+impl Fixup for RegFixup {
+    fn new(
+        propname: &str,
+        value: &serde_json::Value,
+        path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        if propname != "reg" {
+            return Ok(None);
+        }
+
+        if let Some(object) = value.as_object() {
+            Ok(Some(Self {
+                object: object.clone(),
+            }))
+        } else {
+            Err(FixupError::UnexpectedSchemaError(
+                "reg should be an object, but it was not".to_owned(),
+                path,
+                value.clone(),
+            ))
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        let map = self
+            .object
+            .get("items")
+            .and_then(|v| match v {
+                serde_json::Value::Array(array) => array.get(0),
+                other => Some(other),
+            })
+            .and_then(|v| v.as_object())
+            .unwrap_or(&self.object);
+        if !map.is_int_schema() {
+            return Ok(self.object.into());
+        }
+
+        let mut object: HashMap<String, serde_json::Value> = HashMap::new();
+        object.extend(
+            map.iter()
+                .filter(|(k, _)| {
+                    let k = k.as_str();
+                    k == "const" || k == "enum" || k == "minimum" || k == "maximum"
+                })
+                .map(|(k, v)| (k.clone(), v.clone())),
+        );
+
+        self.object
+            .insert("items".to_owned(), json!([{ "items": object }]));
+
+        Ok(self.object.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_reg_fixup_basic() {
+        let reg = json!({
+            "items": {
+                "minimum": 10,
+                "maximum": 12,
+            }
+        });
+
+        let result = RegFixup::new("reg", &reg, JsonPath::new())
+            .expect("reg schema valid")
+            .unwrap()
+            .fixup()
+            .expect("fixup succeeds");
+
+        assert_eq!(
+            result,
+            json!({
+                "items": [{
+                    "items": {"minimum": 10, "maximum": 12},
+                }]
+            })
+        );
+    }
+
+    #[test]
+    fn test_reg_fixup_list() {
+        let reg = json!({
+            "items": [{"enum": [2, 4]}]
+        });
+
+        let result = RegFixup::new("reg", &reg, JsonPath::new())
+            .expect("reg schema valid")
+            .unwrap()
+            .fixup()
+            .expect("fixup succeeds");
+        assert_eq!(
+            result,
+            json!({
+                "items": [{"items": {"enum": [2, 4]}}]
+            })
+        )
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/schema_fixups.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/schema_fixups.rs
new file mode 100644
index 0000000..801963e
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/schema_fixups.rs
@@ -0,0 +1,118 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::map::Entry;
+
+use super::{
+    common_properties::CommonPropertiesFixup, do_fixup, fixup_201909::Fixup201909,
+    interrupts::InterruptsFixup, property_fixups, FixupError,
+};
+use crate::path::JsonPath;
+
+pub struct SchemaFixup {}
+
+impl SchemaFixup {
+    #[tracing::instrument(level = "debug", skip(value))]
+    pub fn fixup(
+        mut value: serde_json::Value,
+        path: String,
+    ) -> Result<serde_json::Value, FixupError> {
+        let object = match value.as_object_mut() {
+            Some(o) => o,
+            None => {
+                return Err(FixupError::UnexpectedSchemaError(
+                    "Schema fixup expected object".to_owned(),
+                    JsonPath::new(),
+                    value,
+                ));
+            }
+        };
+        object.remove("examples");
+        object.remove("maintainers");
+        object.remove("historical");
+
+        let ret = Self::fixup_subschema(value, true, JsonPath::new())?;
+        Ok(ret)
+    }
+
+    #[tracing::instrument(level = "debug", skip(subschema), fields(path=path.back()))]
+    fn fixup_subschema(
+        mut subschema: serde_json::Value,
+        is_node_property: bool,
+        path: JsonPath,
+    ) -> Result<serde_json::Value, FixupError> {
+        match subschema.as_object_mut() {
+            Some(map) => {
+                map.remove("description");
+            }
+            None => {
+                return Ok(subschema);
+            }
+        }
+
+        subschema = do_fixup::<InterruptsFixup>("", subschema, &path)?;
+        if is_node_property {
+            subschema = do_fixup::<CommonPropertiesFixup>("", subschema, &path)?;
+        }
+
+        let object = subschema.as_object_mut().unwrap();
+
+        // "additionalProperties: true" doesn't work with "unevaluatedProperties", so remove it.
+        if let Entry::Occupied(e) = object.entry("additionalProperties") {
+            if let Some(true) = e.get().as_bool() {
+                e.remove();
+            }
+        }
+
+        for k in [
+            "select",
+            "if",
+            "then",
+            "else",
+            "additionalProperties",
+            "not",
+        ] {
+            if let Some(value) = object.remove(k) {
+                object.insert(
+                    k.to_owned(),
+                    Self::fixup_subschema(value, false, path.extend(k))?,
+                );
+            }
+        }
+
+        for k in ["allOf", "anyOf", "oneOf"] {
+            if let Some(array) = object.get_mut(k).and_then(|v| v.as_array_mut()) {
+                for item in array.iter_mut() {
+                    *item = Self::fixup_subschema(item.clone(), true, path.extend(k))?;
+                }
+            }
+        }
+
+        for k in [
+            "dependentRequired",
+            "dependentSchemas",
+            "dependencies",
+            "properties",
+            "patternProperties",
+            "$defs",
+        ] {
+            if let Some(map) = object.get_mut(k).and_then(|v| v.as_object_mut()) {
+                let path = path.extend(k);
+                for (propname, v) in map.iter_mut() {
+                    let new_value = property_fixups::walk_properties(
+                        propname,
+                        v.clone(),
+                        path.extend(propname),
+                    )?;
+                    *v = Self::fixup_subschema(new_value, true, path.extend(propname))?;
+                }
+            }
+        }
+
+        let subschema = do_fixup::<Fixup201909>("", subschema, &path)?;
+
+        Ok(subschema)
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_int.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_int.rs
new file mode 100644
index 0000000..94a1bdc
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_int.rs
@@ -0,0 +1,79 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use std::collections::HashMap;
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::util::IsSchema};
+
+use super::Fixup;
+
+pub struct SingleIntFixup {
+    map: serde_json::Map<String, serde_json::Value>,
+}
+
+/// Convert a single int value into an array.
+impl Fixup for SingleIntFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        _path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        match value.as_object() {
+            Some(o) => Ok(Some(SingleIntFixup { map: o.clone() })),
+            None => Ok(None),
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        if self.map.is_int_schema() {
+            let mut values = HashMap::new();
+            for key in ["const", "enum", "minimum", "maximum"] {
+                if let Some(value) = self.map.remove(key) {
+                    values.insert(key.to_owned(), value);
+                }
+            }
+            self.map
+                .insert("items".to_owned(), json!([{ "items": [values] }]));
+        }
+
+        Ok(self.map.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_single_int_becomes_many() {
+        let single_int = json!({
+            "const": 4
+        });
+
+        let result = SingleIntFixup::new("", &single_int, JsonPath::new())
+            .expect("Valid schema")
+            .unwrap()
+            .fixup()
+            .expect("Fixup succeeds");
+        assert_eq!(result, json!({"items": [{"items": [{"const": 4}]}]}));
+    }
+
+    #[test]
+    fn test_int_list_ignored() {
+        let int_list = json!({
+            "minItems": 2,
+            "maxItems": 3,
+        });
+
+        let result = SingleIntFixup::new("", &int_list, JsonPath::new())
+            .expect("Valid schema")
+            .unwrap()
+            .fixup()
+            .expect("Fixup succeeds");
+        assert_eq!(result, int_list);
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_string.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_string.rs
new file mode 100644
index 0000000..92dc3a4
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/single_string.rs
@@ -0,0 +1,77 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::collections::HashMap;
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::util::IsSchema};
+
+use super::Fixup;
+
+pub struct SingleStringFixup {
+    map: serde_json::Map<String, serde_json::Value>,
+}
+
+/// Convert a string into an array.
+impl Fixup for SingleStringFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        _path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        match value.as_object() {
+            Some(o) => Ok(Some(SingleStringFixup { map: o.clone() })),
+            None => Ok(None),
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        if self.map.is_string_schema() {
+            let mut values = HashMap::new();
+            for key in ["const", "enum", "pattern"] {
+                if let Some(value) = self.map.remove(key) {
+                    values.insert(key.to_owned(), value);
+                }
+            }
+            self.map.insert("items".to_owned(), json!(values));
+        }
+
+        Ok(self.map.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_single_string_becomes_many() {
+        let single_string = json!({
+            "const": "a-string"
+        });
+
+        let result = SingleStringFixup::new("", &single_string, JsonPath::new())
+            .expect("Valid schema")
+            .unwrap()
+            .fixup()
+            .expect("Fixup succeeds");
+        assert_eq!(result, json!({"items": {"const": "a-string"}}));
+    }
+
+    #[test]
+    fn test_string_list_ignored() {
+        let string_list = json!({
+            "minItems": 2,
+            "maxItems": 3,
+        });
+
+        let result = SingleStringFixup::new("", &string_list, JsonPath::new())
+            .expect("Valid schema")
+            .unwrap()
+            .fixup()
+            .expect("Fixup succeeds");
+        assert_eq!(result, string_list);
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/fixups/variable_matrix_fixup.rs b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/variable_matrix_fixup.rs
new file mode 100644
index 0000000..afc7dc7
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/fixups/variable_matrix_fixup.rs
@@ -0,0 +1,94 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+
+use serde_json::json;
+
+use crate::{path::JsonPath, validator::array::Array};
+
+use super::Fixup;
+
+pub struct VariableIntMatrixFixup {
+    object: serde_json::Map<String, serde_json::Value>,
+}
+
+impl Fixup for VariableIntMatrixFixup {
+    fn new(
+        _propname: &str,
+        value: &serde_json::Value,
+        _path: JsonPath,
+    ) -> Result<Option<Self>, super::FixupError> {
+        if let Some(object) = value.as_object() {
+            Ok(Some(VariableIntMatrixFixup {
+                object: object.clone(),
+            }))
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn fixup(mut self) -> Result<serde_json::Value, super::FixupError> {
+        let items: Array = serde_json::from_value(self.object.clone().into()).map_err(|e| {
+            println!("{:?}", self.object);
+            e
+        })?;
+        if !items.is_matrix() {
+            return Ok(self.object.into());
+        }
+
+        let inner_dim = items.get_child_dim()?;
+        let outer_dim = items.get_dim();
+
+        if outer_dim[0] != outer_dim[1] && inner_dim[0] != inner_dim[1] {
+            self.object.remove("items");
+            self.object.remove("maxItems");
+            self.object.remove("minItems");
+            self.object.insert("type".to_owned(), json!("array"));
+        }
+
+        Ok(self.object.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_variable_matrix_fixup() {
+        let matrix = json!({
+            "items": {
+                "minItems": 2,
+                "maxItems": 3,
+            },
+            "minItems": 1,
+            "maxItems": 4,
+        });
+
+        let result = VariableIntMatrixFixup::new("", &matrix, JsonPath::new())
+            .expect("Valid schema")
+            .unwrap()
+            .fixup()
+            .expect("Fixup ok");
+        assert_eq!(result, json!({"type": "array"}));
+    }
+
+    #[test]
+    fn test_ignores_nonvariable_matrix() {
+        let matrix = json!({
+            "items": {
+                "minItems": 2,
+                "maxItems": 2,
+            },
+            "minItems": 1,
+            "maxItems": 4,
+        });
+        let result = VariableIntMatrixFixup::new("", &matrix, JsonPath::new())
+            .expect("Valid schema")
+            .unwrap()
+            .fixup()
+            .expect("Fixup ok");
+
+        assert_eq!(result, matrix);
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/property_type.rs b/third_party/rust_crates/forks/dt-schema/src/validator/property_type.rs
new file mode 100644
index 0000000..0fde771
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/property_type.rs
@@ -0,0 +1,301 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use serde::Serialize;
+
+use super::error::ValidatorError;
+use std::str::FromStr;
+
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
+/// See types.yaml in the dt-schema repo.
+pub enum PropertyType {
+    Flag,
+    NonUniqueStringArray,
+    StringArray,
+    String,
+
+    Uint8Item,
+    Uint8Matrix,
+    Uint8Array,
+    Uint8,
+    Int8Item,
+    Int8Matrix,
+    Int8Array,
+    Int8,
+
+    Uint16Item,
+    Uint16Matrix,
+    Uint16Array,
+    Uint16,
+    Int16Item,
+    Int16Matrix,
+    Int16Array,
+    Int16,
+
+    Cell,
+    Uint32Matrix,
+    Uint32Array,
+    Uint32,
+    Int32Item,
+    Int32Matrix,
+    Int32Array,
+    Int32,
+
+    Uint64Item,
+    Uint64Matrix,
+    Uint64Array,
+    Uint64,
+    Int64Item,
+    Int64Matrix,
+    Int64Array,
+    Int64,
+
+    Phandle,
+    PhandleArray,
+
+    Node,
+}
+
+impl PropertyType {
+    pub fn is_matrix(&self) -> bool {
+        matches!(
+            self,
+            PropertyType::PhandleArray
+                | PropertyType::Int8Matrix
+                | PropertyType::Int16Matrix
+                | PropertyType::Int32Matrix
+                | PropertyType::Int64Matrix
+                | PropertyType::Uint8Matrix
+                | PropertyType::Uint16Matrix
+                | PropertyType::Uint32Matrix
+                | PropertyType::Uint64Matrix
+        )
+    }
+
+    pub fn is_looser(&self, other: &PropertyType) -> bool {
+        match self {
+            PropertyType::Int8 => {
+                other == &PropertyType::Int8
+                    || other == &PropertyType::Int8Item
+                    || other == &PropertyType::Int8Array
+                    || other == &PropertyType::Int8Matrix
+            }
+
+            PropertyType::Int16 => {
+                other == &PropertyType::Int16
+                    || other == &PropertyType::Int16Item
+                    || other == &PropertyType::Int16Array
+                    || other == &PropertyType::Int16Matrix
+            }
+            PropertyType::Int32 => {
+                other == &PropertyType::Int32
+                    || other == &PropertyType::Int32Item
+                    || other == &PropertyType::Int32Array
+                    || other == &PropertyType::Int32Matrix
+            }
+            PropertyType::Int64 => {
+                other == &PropertyType::Int64
+                    || other == &PropertyType::Int64Item
+                    || other == &PropertyType::Int64Array
+                    || other == &PropertyType::Int64Matrix
+            }
+            PropertyType::Uint8 => {
+                other == &PropertyType::Uint8
+                    || other == &PropertyType::Uint8Item
+                    || other == &PropertyType::Uint8Array
+                    || other == &PropertyType::Uint8Matrix
+            }
+
+            PropertyType::Uint16 => {
+                other == &PropertyType::Uint16
+                    || other == &PropertyType::Uint16Item
+                    || other == &PropertyType::Uint16Array
+                    || other == &PropertyType::Uint16Matrix
+            }
+            PropertyType::Uint32 => {
+                other == &PropertyType::Uint32
+                    || other == &PropertyType::Cell
+                    || other == &PropertyType::Uint32Array
+                    || other == &PropertyType::Uint32Matrix
+            }
+            PropertyType::Uint64 => {
+                other == &PropertyType::Uint64
+                    || other == &PropertyType::Uint64Item
+                    || other == &PropertyType::Uint64Array
+                    || other == &PropertyType::Uint64Matrix
+            }
+            PropertyType::Phandle => {
+                other == &PropertyType::Phandle || other == &PropertyType::PhandleArray
+            }
+            _ => false,
+        }
+    }
+
+    pub fn max_size(&self) -> Option<usize> {
+        match self {
+            PropertyType::Uint8 | PropertyType::Int8 => Some(std::mem::size_of::<u8>()),
+            PropertyType::Uint16 | PropertyType::Int16 => Some(std::mem::size_of::<u16>()),
+            PropertyType::Uint32 | PropertyType::Int32 => Some(std::mem::size_of::<u32>()),
+            PropertyType::Uint64 | PropertyType::Int64 => Some(std::mem::size_of::<u64>()),
+            PropertyType::Flag => Some(0),
+            _ => None,
+        }
+    }
+
+    pub fn bytes_per_element(&self) -> Option<usize> {
+        match self {
+            PropertyType::Uint8
+            | PropertyType::Uint8Array
+            | PropertyType::Uint8Matrix
+            | PropertyType::Int8
+            | PropertyType::Int8Array
+            | PropertyType::Int8Matrix => Some(std::mem::size_of::<u8>()),
+            PropertyType::Uint16
+            | PropertyType::Uint16Array
+            | PropertyType::Uint16Matrix
+            | PropertyType::Int16
+            | PropertyType::Int16Array
+            | PropertyType::Int16Matrix => Some(std::mem::size_of::<u16>()),
+            PropertyType::Uint32
+            | PropertyType::Uint32Array
+            | PropertyType::Uint32Matrix
+            | PropertyType::PhandleArray
+            | PropertyType::Phandle
+            | PropertyType::Int32
+            | PropertyType::Int32Array
+            | PropertyType::Int32Matrix => Some(std::mem::size_of::<u32>()),
+            PropertyType::Uint64
+            | PropertyType::Uint64Array
+            | PropertyType::Uint64Matrix
+            | PropertyType::Int64
+            | PropertyType::Int64Array
+            | PropertyType::Int64Matrix => Some(std::mem::size_of::<u64>()),
+            _ => None,
+        }
+    }
+
+    pub fn is_string(&self) -> bool {
+        matches!(
+            self,
+            PropertyType::NonUniqueStringArray | PropertyType::String | PropertyType::StringArray
+        )
+    }
+}
+
+impl FromStr for PropertyType {
+    type Err = ValidatorError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s {
+            "flag" => PropertyType::Flag,
+            "non-unique-string-array" => PropertyType::NonUniqueStringArray,
+            "string-array" => PropertyType::StringArray,
+            "string" => PropertyType::String,
+
+            "uint8-item" => PropertyType::Uint8Item,
+            "uint8-matrix" => PropertyType::Uint8Matrix,
+            "uint8-array" => PropertyType::Uint8Array,
+            "uint8" => PropertyType::Uint8,
+            "int8-item" => PropertyType::Int8Item,
+            "int8-matrix" => PropertyType::Int8Matrix,
+            "int8-array" => PropertyType::Int8Array,
+            "int8" => PropertyType::Int8,
+
+            "uint16-item" => PropertyType::Uint16Item,
+            "uint16-matrix" => PropertyType::Uint16Matrix,
+            "uint16-array" => PropertyType::Uint16Array,
+            "uint16" => PropertyType::Uint16,
+            "int16-item" => PropertyType::Int16Item,
+            "int16-matrix" => PropertyType::Int16Matrix,
+            "int16-array" => PropertyType::Int16Array,
+            "int16" => PropertyType::Int16,
+
+            "cell" => PropertyType::Cell,
+            "uint32-matrix" => PropertyType::Uint32Matrix,
+            "uint32-array" => PropertyType::Uint32Array,
+            "uint32" => PropertyType::Uint32,
+            "int32-item" => PropertyType::Int32Item,
+            "int32-matrix" => PropertyType::Int32Matrix,
+            "int32-array" => PropertyType::Int32Array,
+            "int32" => PropertyType::Int32,
+
+            "uint64-item" => PropertyType::Uint64Item,
+            "uint64-matrix" => PropertyType::Uint64Matrix,
+            "uint64-array" => PropertyType::Uint64Array,
+            "uint64" => PropertyType::Uint64,
+            "int64-item" => PropertyType::Int64Item,
+            "int64-matrix" => PropertyType::Int64Matrix,
+            "int64-array" => PropertyType::Int64Array,
+            "int64" => PropertyType::Int64,
+
+            "phandle" => PropertyType::Phandle,
+            "phandle-array" => PropertyType::PhandleArray,
+
+            "node" => PropertyType::Node,
+            _ => return Err(ValidatorError::UnknownPropType(s.to_owned())),
+        })
+    }
+}
+
+impl ToString for PropertyType {
+    fn to_string(&self) -> String {
+        match self {
+            PropertyType::Flag => "flag",
+            PropertyType::NonUniqueStringArray => "non-unique-string-array",
+            PropertyType::StringArray => "string-array",
+            PropertyType::String => "string",
+
+            PropertyType::Uint8Item => "uint8-item",
+            PropertyType::Uint8Matrix => "uint8-matrix",
+            PropertyType::Uint8Array => "uint8-array",
+            PropertyType::Uint8 => "uint8",
+            PropertyType::Int8Item => "int8-item",
+            PropertyType::Int8Matrix => "int8-matrix",
+            PropertyType::Int8Array => "int8-array",
+            PropertyType::Int8 => "int8",
+
+            PropertyType::Uint16Item => "uint16-item",
+            PropertyType::Uint16Matrix => "uint16-matrix",
+            PropertyType::Uint16Array => "uint16-array",
+            PropertyType::Uint16 => "uint16",
+            PropertyType::Int16Item => "int16-item",
+            PropertyType::Int16Matrix => "int16-matrix",
+            PropertyType::Int16Array => "int16-array",
+            PropertyType::Int16 => "int16",
+
+            PropertyType::Cell => "cell",
+            PropertyType::Uint32Matrix => "uint32-matrix",
+            PropertyType::Uint32Array => "uint32-array",
+            PropertyType::Uint32 => "uint32",
+            PropertyType::Int32Item => "int32-item",
+            PropertyType::Int32Matrix => "int32-matrix",
+            PropertyType::Int32Array => "int32-array",
+            PropertyType::Int32 => "int32",
+
+            PropertyType::Uint64Item => "uint64-item",
+            PropertyType::Uint64Matrix => "uint64-matrix",
+            PropertyType::Uint64Array => "uint64-array",
+            PropertyType::Uint64 => "uint64",
+            PropertyType::Int64Item => "int64-item",
+            PropertyType::Int64Matrix => "int64-matrix",
+            PropertyType::Int64Array => "int64-array",
+            PropertyType::Int64 => "int64",
+
+            PropertyType::Phandle => "phandle",
+            PropertyType::PhandleArray => "phandle-array",
+
+            PropertyType::Node => "node",
+        }
+        .to_owned()
+    }
+}
+
+impl Serialize for PropertyType {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        serializer.serialize_str(&self.to_string())
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/property_type_info.rs b/third_party/rust_crates/forks/dt-schema/src/validator/property_type_info.rs
new file mode 100644
index 0000000..8f5a385
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/property_type_info.rs
@@ -0,0 +1,222 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use fancy_regex::Regex;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::str::FromStr;
+
+use crate::path::JsonPath;
+
+use super::{
+    array::Array, error::ValidatorError, property_type::PropertyType, GeneratedPropertyType, Schema,
+};
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(untagged)]
+pub enum SchemaType {
+    Single(String),
+    Many(Vec<String>),
+}
+
+impl SchemaType {
+    pub fn is_exactly(&self, want: &str) -> bool {
+        match self {
+            SchemaType::Single(v) => v == want,
+            SchemaType::Many(_) => false,
+        }
+    }
+}
+
+#[derive(Deserialize, Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+/// Represents the structured data we need to determine the type of a schema.
+pub struct PropertyTypeInfo {
+    r#type: Option<SchemaType>,
+
+    #[serde(rename = "$ref")]
+    dollar_ref: Option<String>,
+
+    #[serde(flatten)]
+    array: Array,
+    dim: Option<Vec<Vec<u32>>>,
+
+    // These are actually used by |Schema| to recurse.
+    // TODO(simonshields): consider making Schema just poke |etc| directly.
+    pub one_of: Option<Vec<serde_json::Value>>,
+    pub any_of: Option<Vec<serde_json::Value>>,
+    pub all_of: Option<Vec<serde_json::Value>>,
+
+    #[serde(flatten)]
+    pub etc: HashMap<String, serde_json::Value>,
+}
+
+impl PropertyTypeInfo {
+    /// Determine type information represented by this |PropertyTypeInfo|.
+    #[tracing::instrument(level = "debug", skip(self, schema), fields(path=%path))]
+    pub fn extract_type(
+        &self,
+        schema: &Schema,
+        name: &str,
+        is_pattern: bool,
+        path: JsonPath,
+    ) -> Result<Option<GeneratedPropertyType>, ValidatorError> {
+        if name.starts_with('$') {
+            return Ok(None);
+        }
+
+        let type_re = Regex::new(
+            "(flag|u?int(8|16|32|64)(-(array|matrix))?|string(-array)?|phandle(-array)?)",
+        )
+        .unwrap();
+        // Determine type.
+        let prop_type = if self
+            .r#type
+            .as_ref()
+            .map(|x| x.is_exactly("object"))
+            .unwrap_or(false)
+        {
+            Some(PropertyType::Node)
+        } else if let Ok(Some(result)) =
+            type_re.find(&self.dollar_ref.clone().unwrap_or_else(|| "".to_owned()))
+        {
+            // Try and guess the reference type based on |type_re|.
+            tracing::debug!("Type found based on $ref");
+            Some(PropertyType::from_str(result.as_str())?)
+        } else if self
+            .r#type
+            .as_ref()
+            .map(|x| x.is_exactly("boolean"))
+            .unwrap_or(false)
+        {
+            Some(PropertyType::Flag)
+        } else if self.array.has_items() {
+            if self.array.is_string_schema() {
+                Some(PropertyType::StringArray)
+            } else if self.array.is_uint32_matrix(name) {
+                Some(PropertyType::Uint32Matrix)
+            } else {
+                None
+            }
+        } else if Regex::new("\\.yaml#?$")
+            .unwrap()
+            .is_match(&self.dollar_ref.clone().unwrap_or_else(|| "na".to_owned()))?
+        {
+            // Looks like a reference to another schema type.
+            Some(PropertyType::Node)
+        } else {
+            None
+        };
+
+        tracing::debug!(name = name, "Found type type={:?}", prop_type);
+
+        // If this is a matrix, determine its dimensions.
+        let dim = match prop_type.map(|v| v.is_matrix()) {
+            Some(true) => Some([self.array.get_dim(), self.array.get_child_dim()?].into()),
+            _ => None,
+        };
+
+        let new_prop = GeneratedPropertyType {
+            r#type: prop_type,
+            id: vec![schema.spec_id().clone().ok_or(ValidatorError::ExpectedKey(
+                "spec_id".to_owned(),
+                JsonPath::new(),
+            ))?]
+            .into_iter()
+            .collect(),
+            dim,
+            regex: if is_pattern {
+                Some(Regex::new(name)?)
+            } else {
+                None
+            },
+        };
+
+        Ok(Some(new_prop))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::*;
+    fn fake_schema() -> Schema {
+        Schema::from_value(
+            json!({
+                "$id": "fake-test-schema",
+                "properties": {
+                    "my_reference_property": {
+                        "$ref": "#/properties/my_other_prop",
+                    },
+                    "my_other_prop": {
+                        "type": "boolean"
+                    },
+                    "my_external_ref": {
+                        "$ref": "types.yaml#/definitions/string-array"
+                    },
+                    "implicit_string_array": {
+                        "items": {
+                            "enum": ["a", "b"]
+                        }
+                    },
+                    "implicit_u32_matrix-bits": {
+                        "items": {
+                            "items": [{}]
+                        }
+                    },
+                    "other_schema_ref": {
+                        "$ref": "my-type.yaml"
+                    }
+                }
+
+            }),
+            "test".to_owned(),
+        )
+        .unwrap()
+    }
+
+    fn get_property_decl(schema: &Schema, name: &str) -> PropertyTypeInfo {
+        serde_json::from_value(schema.properties().get(name).unwrap().clone()).unwrap()
+    }
+
+    #[test]
+    fn test_external_ref() {
+        let schema = fake_schema();
+        let val = get_property_decl(&schema, "my_external_ref")
+            .extract_type(&schema, "my_external_ref", false, JsonPath::new())
+            .unwrap();
+        assert_eq!(val.map(|v| v.r#type), Some(Some(PropertyType::StringArray)));
+    }
+
+    #[test]
+    fn test_implicit_string_array() {
+        let schema = fake_schema();
+        let val = get_property_decl(&schema, "implicit_string_array")
+            .extract_type(&schema, "implicit_string_array", false, JsonPath::new())
+            .unwrap();
+        assert_eq!(val.map(|v| v.r#type), Some(Some(PropertyType::StringArray)));
+    }
+
+    #[test]
+    fn test_implicit_u32_matrix() {
+        let schema = fake_schema();
+        let val = get_property_decl(&schema, "implicit_u32_matrix-bits")
+            .extract_type(&schema, "implicit_u32_matrix-bits", false, JsonPath::new())
+            .unwrap();
+        assert_eq!(
+            val.map(|v| v.r#type),
+            Some(Some(PropertyType::Uint32Matrix))
+        );
+    }
+
+    #[test]
+    fn test_other_schema_ref() {
+        let schema = fake_schema();
+        let val = get_property_decl(&schema, "other_schema_ref")
+            .extract_type(&schema, "other_schema_ref", false, JsonPath::new())
+            .unwrap();
+        assert_eq!(val.map(|v| v.r#type), Some(Some(PropertyType::Node)));
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/resolver.rs b/third_party/rust_crates/forks/dt-schema/src/validator/resolver.rs
new file mode 100644
index 0000000..c3e1dd5
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/resolver.rs
@@ -0,0 +1,58 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// TODO implement and wire up.
+
+use std::{collections::HashMap, sync::Arc};
+
+//use jsonschema::SchemaResolver;
+
+use super::schema::Schema;
+
+#[derive(Clone)]
+pub struct LocalOnlyResolver {
+    inner: Arc<Inner>,
+}
+
+struct Inner {
+    schemas: HashMap<String, Arc<serde_json::Value>>,
+}
+
+impl LocalOnlyResolver {
+    pub fn new(schemas: &mut dyn Iterator<Item = &Schema>) -> Self {
+        LocalOnlyResolver {
+            inner: Arc::new(Inner {
+                schemas: schemas
+                    .map(|i| {
+                        (
+                            i.spec_id()
+                                .as_ref()
+                                .unwrap()
+                                .trim_end_matches('#')
+                                .to_owned(),
+                            Arc::new(i.raw_schema()),
+                        )
+                    })
+                    .collect(),
+            }),
+        }
+    }
+}
+
+/*
+impl SchemaResolver for LocalOnlyResolver {
+    fn resolve(
+        &self,
+        _root_schema: &serde_json::Value,
+        url: &url::Url,
+        original_reference: &str,
+    ) -> Result<std::sync::Arc<serde_json::Value>, jsonschema::SchemaResolverError> {
+        tracing::trace!("resolve {} ref={}", url, original_reference);
+        if let Some(value) = self.inner.schemas.get(url.as_str()) {
+            return Ok(value.clone());
+        }
+        return Err(anyhow::anyhow!("not supported"));
+    }
+}
+*/
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/schema.rs b/third_party/rust_crates/forks/dt-schema/src/validator/schema.rs
new file mode 100644
index 0000000..5439cb5
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/schema.rs
@@ -0,0 +1,394 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//use jsonschema::{Draft, JSONSchema};
+use serde::Deserialize;
+use serde_json::json;
+use std::collections::HashMap;
+use valico::json_schema::{Schema as ValicoSchema, SchemaVersion, ValidationState};
+
+use crate::{
+    path::JsonPath,
+    validator::{
+        property_type::PropertyType, property_type_info::PropertyTypeInfo, util::Mergeable,
+    },
+};
+
+use super::{
+    compatible::extract_node_compatibles, error::ValidatorError, resolver::LocalOnlyResolver,
+    GeneratedPropertyType,
+};
+
+#[derive(Deserialize, Debug)]
+#[serde(untagged)]
+enum AdditionalProperties {
+    Bool(bool),
+    Object(serde_json::Value),
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Schema {
+    /// The select schema for this schema.
+    /// We validate this schema against a node to see if the node should be matched to the rest of this schema.
+    select: Option<serde_json::Value>,
+
+    /// Properties with regular expression-matched names.
+    #[serde(default)]
+    pattern_properties: HashMap<String, serde_json::Value>,
+    /// Properties with constant names.
+    #[serde(default)]
+    properties: HashMap<String, serde_json::Value>,
+
+    /// Additional properties.
+    additional_properties: Option<AdditionalProperties>,
+
+    #[serde(rename = "$id")]
+    spec_id: Option<String>,
+
+    /// Other data. Because properties are able to reference things in the schema,
+    /// we manually deserialize the schema object into this hashmap.
+    #[serde(skip)]
+    etc: serde_json::Map<String, serde_json::Value>,
+
+    #[serde(skip)]
+    select_id: Option<url::Url>,
+
+    #[serde(skip)]
+    schema_id: Option<url::Url>,
+
+    #[serde(skip)]
+    source_file: String,
+}
+
+impl Schema {
+    pub fn from_value(
+        value: serde_json::Value,
+        source_file: String,
+    ) -> Result<Self, ValidatorError> {
+        let mut ret: Schema = serde_json::from_value(value.clone())?;
+        ret.etc = serde_json::from_value(value)?;
+        ret.source_file = source_file;
+        Ok(ret)
+    }
+
+    pub fn spec_id(&self) -> &Option<String> {
+        &self.spec_id
+    }
+
+    pub fn source_file(&self) -> &String {
+        &self.source_file
+    }
+
+    #[cfg(test)]
+    pub fn properties(&self) -> &HashMap<String, serde_json::Value> {
+        &self.properties
+    }
+
+    pub(super) fn raw_schema(&self) -> serde_json::Value {
+        self.etc.clone().into()
+    }
+
+    fn generate_select(&mut self) -> Result<Option<serde_json::Value>, ValidatorError> {
+        if self.select.is_some() {
+            return Ok(None);
+        }
+
+        if self.properties.is_empty() {
+            return Ok(Some(json!(false)));
+        }
+
+        if let Some(compatibles) = self.properties.get("compatible") {
+            let mut compatible_list = extract_node_compatibles(compatibles);
+            // Remove meaningless compatibles.
+            compatible_list.remove("syscon");
+            compatible_list.remove("simple-mfd");
+
+            if !compatible_list.is_empty() {
+                let mut compatible_list = compatible_list.into_iter().collect::<Vec<_>>();
+                compatible_list.sort();
+                return Ok(Some(json!({
+                    "required": ["compatible"],
+                    "properties": {
+                        "compatible": {
+                            "contains": {
+                                "enum": compatible_list,
+                            }
+                        }
+                    }
+                })));
+            }
+        }
+
+        // Find a more interesting nodename schema than just "$nodename: true"
+        if let Some(nodename) = self
+            .properties
+            .get("$nodename")
+            .filter(|&v| !v.as_bool().unwrap_or(false))
+        {
+            return Ok(Some(json!({
+                "required": ["$nodename"],
+                "properties": {
+                    "$nodename": nodename,
+                }
+            })));
+        }
+
+        Ok(Some(json!(false)))
+    }
+
+    pub fn fixup_select_and_finalise(
+        &mut self,
+        resolver: LocalOnlyResolver,
+        scope: &mut valico::json_schema::Scope,
+    ) -> Result<(), ValidatorError> {
+        let select = self.generate_select()?;
+        if let Some(new_select) = select {
+            self.select = Some(new_select);
+        }
+
+        if let Some(ref select) = self.select {
+            self.etc.insert("select".to_owned(), select.clone());
+            let mut scope_url =
+                url::Url::parse(self.spec_id.as_ref().expect("have spec id")).unwrap();
+            scope_url.set_fragment(Some("/select"));
+            self.select_id = Some(scope_url);
+        }
+
+        self.schema_id = Some(scope.compile(self.etc.clone().into(), false)?);
+        Ok(())
+    }
+
+    pub fn applies(&self, value: &serde_json::Value, scope: &valico::json_schema::Scope) -> bool {
+        self.select_id
+            .as_ref()
+            .and_then(|url| scope.resolve(url))
+            .map(|schema| schema.validate(value).is_strictly_valid())
+            .unwrap_or(false)
+    }
+
+    pub fn validate<'a>(
+        &'a self,
+        value: &'a serde_json::Value,
+        path: &str,
+        scope: &valico::json_schema::Scope,
+    ) -> Option<ValidationState> {
+        scope
+            .resolve(self.schema_id.as_ref().unwrap())
+            .map(|v| v.validate_in(value, path))
+    }
+
+    fn resolve(&self, path: &str) -> Result<Option<&serde_json::Value>, ValidatorError> {
+        tracing::debug!("resolve {}", path);
+        if path.starts_with("#/") {
+            let mut iter = path.split('/').skip(1);
+            let path = iter
+                .next()
+                .ok_or(ValidatorError::InvalidReference(path.to_owned()))?;
+            let mut s = self
+                .etc
+                .get(path)
+                .ok_or(ValidatorError::InvalidReference(path.to_owned()))?;
+            for p in iter {
+                s = s
+                    .as_object()
+                    .and_then(|obj| obj.get(p))
+                    .ok_or(ValidatorError::InvalidReference(path.to_owned()))?;
+            }
+            Ok(Some(s))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Determine type of a single property.
+    /// |k|: name of property
+    /// |v|: value of property
+    /// |prop_types|: map of prop-name to list of types for that property so far.
+    /// |is_pattern|: true if this is in `patternProperties`.
+    fn handle_one_property<'a>(
+        &self,
+        root_schema: &'a Schema,
+        k: &String,
+        mut v: &'a serde_json::Value,
+        prop_types: &mut HashMap<String, Vec<GeneratedPropertyType>>,
+        is_pattern: bool,
+        path: JsonPath,
+    ) -> Result<(), ValidatorError> {
+        // If there's a reference to some other type in the schema, pull the information from there.
+        // Note that we only support references within the same file.
+        // There's logic in |PropertyTypeInfo::extract_type| to "guess" what type a reference to another file refers to.
+        while let Some(reffed) = v
+            .as_object()
+            .and_then(|e| e.get("$ref"))
+            .and_then(|v| v.as_str())
+            .map(|v| root_schema.resolve(v))
+        {
+            let reffed = match reffed? {
+                Some(r) => r,
+                None => break,
+            };
+
+            v = reffed;
+        }
+        if !v.is_object() {
+            return Ok(());
+        }
+        let info: PropertyTypeInfo = serde_json::from_value(v.clone())?;
+        // Recurse into all/any/one of:
+        for (key, item) in [
+            ("allOf", &info.all_of),
+            ("anyOf", &info.any_of),
+            ("oneOf", &info.one_of),
+        ]
+        .into_iter()
+        .filter(|(_, v)| v.is_some())
+        {
+            for (index, value) in item.as_ref().unwrap().iter().enumerate() {
+                self.handle_one_property(
+                    root_schema,
+                    k,
+                    value,
+                    prop_types,
+                    is_pattern,
+                    path.extend_array_index(key, index),
+                )?;
+            }
+        }
+
+        // and look for a type definition for this value.
+        let mut type_info = match info.extract_type(root_schema, k, is_pattern, path.clone())? {
+            Some(info) => info,
+            None => {
+                tracing::debug!("no type for {}", k);
+                return Ok(());
+            }
+        };
+        tracing::debug!("type for {}: {:?}", k, type_info);
+
+        // Grab the property type that corresponds to the final value.
+        let vec = match prop_types.get_mut(k) {
+            Some(v) => v,
+            None => {
+                prop_types.insert(k.clone(), vec![]);
+                prop_types.get_mut(k).unwrap()
+            }
+        };
+
+        // If we failed to infer a type, only store this value if we have no other information.
+        if type_info.r#type.is_none() {
+            if vec.is_empty() {
+                vec.push(type_info);
+            }
+            return Ok(());
+        }
+
+        // We know what the type is. Now we reconcile our "new" type with what we already know.
+        let new_type = type_info.r#type.unwrap();
+        let mut index_to_remove = None;
+        for (index, item) in vec.iter_mut().enumerate() {
+            // This has no known type, remove it.
+            if item.r#type.is_none() {
+                // remove a value with |type == None|.
+                index_to_remove = Some(index);
+                break;
+            }
+            let item_type = item.r#type.unwrap();
+
+            // Merge the two dimensions, if |item| is a matrix type.
+            if let Some(dim) = type_info.dim {
+                if item.r#type.map(|v| v.is_matrix()).unwrap_or(false) {
+                    match item.dim {
+                        None => item.dim = type_info.dim,
+                        Some(existing) => {
+                            if existing != dim {
+                                item.dim = Some(existing.merge(dim));
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Already have the same or looser type, so just add our id.
+            if item_type.is_looser(&new_type) {
+                item.id.insert(self.spec_id.clone().unwrap());
+
+                if new_type == PropertyType::Node {
+                    // Descend into child schemas if we haven't seen this node already.
+                    break;
+                }
+            } else if new_type.is_looser(&item_type) {
+                // Replace the scalar type with looser type.
+                type_info.id.extend(item.id.iter().cloned());
+                index_to_remove = Some(index);
+                break;
+            }
+        }
+
+        // If we found an existing item, remove it.
+        if let Some(remove) = index_to_remove {
+            vec.swap_remove(remove);
+        }
+
+        // Add our new type info.
+        vec.push(type_info);
+
+        // Descend and merge the subschemas in to our discovered types.
+        for (prop_name, value) in info.etc.iter().filter(|(k, _)| {
+            k == &"properties" || k == &"additionalProperties" || k == &"patternProperties"
+        }) {
+            if let Ok(mut schema) =
+                Schema::from_value(json!({prop_name: value.clone()}), self.source_file.clone())
+            {
+                tracing::debug!("descending to {}/{}", k, prop_name);
+                if schema.spec_id.is_none() {
+                    schema.spec_id = self.spec_id.clone();
+                }
+                let new_types =
+                    schema.generate_property_types(Some(root_schema), path.extend(prop_name))?;
+                prop_types.merge(new_types);
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Extract property types from this schema.
+    /// Returns a mapping of String (the property or pattern for the property) to a vector of possible property types.
+    pub fn generate_property_types(
+        &self,
+        root_schema: Option<&Schema>,
+        path: JsonPath,
+    ) -> Result<HashMap<String, Vec<GeneratedPropertyType>>, ValidatorError> {
+        let root_schema = root_schema.unwrap_or(self);
+        let mut prop_types = HashMap::new();
+        if let Some(AdditionalProperties::Object(ref extras)) = self.additional_properties {
+            let mut extras = Schema::from_value(extras.clone(), self.source_file.clone())?;
+            if extras.spec_id.is_none() {
+                extras.spec_id = self.spec_id.clone();
+            }
+            prop_types = extras
+                .generate_property_types(Some(root_schema), path.extend("additionalProperties"))?;
+        }
+
+        let prop_path = path.extend("properties");
+        for (k, v) in self.properties.iter() {
+            self.handle_one_property(
+                root_schema,
+                k,
+                v,
+                &mut prop_types,
+                false,
+                prop_path.extend(k),
+            )?;
+        }
+
+        let pat_path = path.extend("patternProperties");
+        for (k, v) in self.pattern_properties.iter() {
+            self.handle_one_property(root_schema, k, v, &mut prop_types, true, pat_path.extend(k))?;
+        }
+
+        Ok(prop_types)
+    }
+}
diff --git a/third_party/rust_crates/forks/dt-schema/src/validator/util.rs b/third_party/rust_crates/forks/dt-schema/src/validator/util.rs
new file mode 100644
index 0000000..c596189
--- /dev/null
+++ b/third_party/rust_crates/forks/dt-schema/src/validator/util.rs
@@ -0,0 +1,90 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+use std::{collections::HashMap, hash::Hash};
+
+pub trait Mergeable {
+    /// We use this to merge hashmaps with vector values.
+    /// e.g.
+    /// {"a": [1]}.merge({"a": [2]}) => {"a": [1, 2]}
+    fn merge(&mut self, other: Self);
+}
+
+impl<K: Hash + Eq, V> Mergeable for HashMap<K, Vec<V>> {
+    fn merge(&mut self, other: Self) {
+        for (k, v) in other {
+            match self.get_mut(&k) {
+                Some(value) => value.extend(v),
+                None => {
+                    self.insert(k, v);
+                }
+            }
+        }
+    }
+}
+
+pub trait ContainsExt<K> {
+    fn contains_any(&self, keys: &[K]) -> bool;
+    fn contains_all(&self, keys: &[K]) -> bool;
+}
+
+impl<V> ContainsExt<&str> for HashMap<String, V> {
+    fn contains_any(&self, keys: &[&str]) -> bool {
+        keys.iter().any(|&k| self.contains_key(k))
+    }
+
+    fn contains_all(&self, keys: &[&str]) -> bool {
+        keys.iter().all(|&k| self.contains_key(k))
+    }
+}
+
+impl ContainsExt<&str> for serde_json::Map<String, serde_json::Value> {
+    fn contains_any(&self, keys: &[&str]) -> bool {
+        keys.iter().any(|&k| self.contains_key(k))
+    }
+
+    fn contains_all(&self, keys: &[&str]) -> bool {
+        keys.iter().all(|&k| self.contains_key(k))
+    }
+}
+
+pub trait IsSchema {
+    fn is_string_schema(&self) -> bool;
+    fn is_int_schema(&self) -> bool;
+}
+
+impl IsSchema for serde_json::Map<String, serde_json::Value> {
+    fn is_string_schema(&self) -> bool {
+        for (_, v) in self
+            .iter()
+            .filter(|(k, _)| *k == "const" || *k == "enum" || *k == "pattern")
+        {
+            if let Some(array) = v.as_array() {
+                if array.iter().all(|e| e.is_string()) {
+                    return true;
+                }
+            } else if v.is_string() {
+                return true;
+            }
+        }
+
+        false
+    }
+
+    fn is_int_schema(&self) -> bool {
+        for (_, v) in self
+            .iter()
+            .filter(|(k, _)| *k == "const" || *k == "enum" || *k == "minimum" || *k == "maximum")
+        {
+            if let Some(array) = v.as_array() {
+                if array.iter().all(|e| e.is_i64()) {
+                    return true;
+                }
+            } else if v.is_i64() {
+                return true;
+            }
+        }
+        false
+    }
+}