diff --git a/.gitignore b/.gitignore index 607bbf772..0e43bb224 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ ci_logs/ zig-out/ .zig-cache/ venv/ +target/ __pycache__ .*.sw* *.dtb diff --git a/.reuse/dep5 b/.reuse/dep5 index 11bd3c99a..24c9262fa 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -4,6 +4,8 @@ Source: https://github.com/au-ts/sddf Files: flake.lock + *Cargo.lock + *aarch64-sel4-microkit-minimal.json CHANGES.md build.zig.zon docs/design/* @@ -34,6 +36,7 @@ Files: drivers/timer/goldfish/config.json drivers/timer/cdns/config.json drivers/timer/bcm2835/config.json + drivers/blk/mmc/core/config.json Copyright: UNSW License: BSD-2-Clause diff --git a/ci/matrix.py b/ci/matrix.py index e7e25700e..43ae34726 100644 --- a/ci/matrix.py +++ b/ci/matrix.py @@ -29,8 +29,18 @@ "blk": { "configs": ["debug", "release"], "build_systems": ["make", "zig"], - "boards_build": ["maaxboard", "qemu_virt_aarch64", "qemu_virt_riscv64"], - "boards_test": ["maaxboard", "qemu_virt_aarch64", "qemu_virt_riscv64"], + "boards_build": [ + "maaxboard", + "qemu_virt_aarch64", + "qemu_virt_riscv64", + "odroidc4", + ], + "boards_test": [ + "maaxboard", + "qemu_virt_aarch64", + "qemu_virt_riscv64", + "odroidc4", + ], }, "i2c": { "configs": ["debug", "release"], diff --git a/drivers/blk/mmc/core/Cargo.lock b/drivers/blk/mmc/core/Cargo.lock new file mode 100644 index 000000000..8e26ccc65 --- /dev/null +++ b/drivers/blk/mmc/core/Cargo.lock @@ -0,0 +1,867 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blk_driver" +version = "0.1.0" +dependencies = [ + "meson_hal", + "sdmmc_protocol", + "sel4-microkit", + "sel4-panicking-env", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dlmalloc" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa3a2dbee57b69fbb5dbe852fa9c0925697fb0c7fbcb1593e90e5ffaedf13d51" +dependencies = [ + "cfg-if", + "libc", + "windows-sys", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gimli" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93563d740bc9ef04104f9ed6f86f1e3275c2cdafb95664e26584b9ca807a8ffe" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "meson_hal" +version = "0.1.0" +dependencies = [ + "sdmmc_protocol", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "one-shot-mutex" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb10614a03e671fcbb7f1421656788f9a761aab44563b83b07140c354fa9334" +dependencies = [ + "lock_api", +] + +[[package]] +name = "pest" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdmmc_protocol" +version = "0.1.0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "sel4" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "cfg-if", + "sel4-config", + "sel4-sys", +] + +[[package]] +name = "sel4-alloca" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "sel4-bitfield-ops" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "sel4-build-env" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-config" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sel4-config-data", + "sel4-config-macros", + "sel4-config-types", + "syn", +] + +[[package]] +name = "sel4-config-data" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "lazy_static", + "sel4-build-env", + "sel4-config-types", + "serde_json", +] + +[[package]] +name = "sel4-config-macros" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "fallible-iterator", + "proc-macro2", + "quote", + "sel4-config-data", + "sel4-config-types", + "syn", +] + +[[package]] +name = "sel4-config-types" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "serde", +] + +[[package]] +name = "sel4-ctors-dtors" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-dlmalloc" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "dlmalloc", + "lock_api", +] + +[[package]] +name = "sel4-elf-header" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-immediate-sync-once-cell" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-immutable-cell" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-initialize-tls" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "cfg-if", + "sel4-alloca", +] + +[[package]] +name = "sel4-microkit" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "cfg-if", + "one-shot-mutex", + "sel4", + "sel4-dlmalloc", + "sel4-immediate-sync-once-cell", + "sel4-microkit-base", + "sel4-microkit-macros", + "sel4-panicking", + "sel4-panicking-env", + "sel4-runtime-common", +] + +[[package]] +name = "sel4-microkit-base" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "sel4", + "sel4-immutable-cell", +] + +[[package]] +name = "sel4-microkit-macros" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sel4-panicking" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "cfg-if", + "sel4-immediate-sync-once-cell", + "sel4-panicking-env", + "unwinding", +] + +[[package]] +name = "sel4-panicking-env" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-runtime-common" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "cfg-if", + "sel4", + "sel4-ctors-dtors", + "sel4-elf-header", + "sel4-initialize-tls", + "sel4-panicking-env", + "sel4-stack", + "unwinding", +] + +[[package]] +name = "sel4-stack" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" + +[[package]] +name = "sel4-sys" +version = "0.1.0" +source = "git+https://github.com/seL4/rust-sel4.git?rev=d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e#d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" +dependencies = [ + "bindgen", + "glob", + "log", + "pest", + "pest_derive", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "sel4-bitfield-ops", + "sel4-build-env", + "sel4-config", + "sel4-config-data", + "syn", + "xmltree", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unwinding" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60612c845ef41699f39dc8c5391f252942c0a88b7d15da672eff0d14101bbd6d" +dependencies = [ + "gimli", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "xml-rs" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" + +[[package]] +name = "xmltree" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6" +dependencies = [ + "xml-rs", +] diff --git a/drivers/blk/mmc/core/Cargo.toml b/drivers/blk/mmc/core/Cargo.toml new file mode 100644 index 000000000..a877869de --- /dev/null +++ b/drivers/blk/mmc/core/Cargo.toml @@ -0,0 +1,20 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +[package] +name = "blk_driver" +version = "0.1.0" +edition = "2024" + +[dependencies] +sel4-microkit = { git = "https://github.com/seL4/rust-sel4.git", rev = "d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e", features = ["alloc"] } +sel4-panicking-env = { git = "https://github.com/seL4/rust-sel4.git", rev = "d2bc5cf71c5455f85898a4768e9dcbaea39e1a7e" } +meson_hal = { path = "../meson", optional = true } +sdmmc_protocol = { path = "protocol" } + +[features] +dev-logs = ["sdmmc_protocol/dev-logs"] +meson = ["dep:meson_hal"] + +[build] +build = "build.rs" \ No newline at end of file diff --git a/drivers/blk/mmc/core/README.md b/drivers/blk/mmc/core/README.md new file mode 100644 index 000000000..ecc6be385 --- /dev/null +++ b/drivers/blk/mmc/core/README.md @@ -0,0 +1,40 @@ + +**Note:** This driver is in the early stages of development and does not yet implement the full feature set found in mature stacks like the Linux MMC subsystem. + +--- + +## Features + +### Supported +- **Card Types:** SDHC / SDXC +- **Bus Speeds:** High Speed (SDHS) / UHS-I +- **Core Operations:** Asynchronous Read, Write, and Erase + +### Not Yet Implemented or Tested +- **Card Types:** SDSC / SDUC, eMMC +- **Interface Modes:** SPI mode +- **Bus Speeds:** Speed classes higher than UHS-I (e.g., UHS-II, UHS-III) +- A comprehensive set of features found in mature MMC stacks. + +--- + +## Hardware Platform Support + +| Platform | Status | +| :------------- | :-------------- | +| Odroid C4 | ✅ Supported | +| sdhci-zynqmp | 🚧 In Progress | + +--- + +## Adding Support for a New Hardware Platform + +Porting the driver to a new hardware platform involves implementing the hardware-specific logic. Follow these steps: + +1. **Familiarize Yourself:** Study the driver's architecture and review the reference implementation for the Odroid C4 to understand the core components. +2. **Implement the HAL:** Create a new hardware abstraction layer (HAL) for your platform by implementing the `SdmmcHardware` trait. This trait defines the interface for all hardware-specific operations. +3. **Build with Logging:** Compile your HAL and the core protocol crate with the `dev-logs` feature enabled to get detailed diagnostic output. +4. **Test and Verify:** Run the driver on your hardware. Analyze the logs to verify that the initialization sequence and command responses are correct. \ No newline at end of file diff --git a/drivers/blk/mmc/core/blk_driver.mk b/drivers/blk/mmc/core/blk_driver.mk new file mode 100644 index 000000000..f7053acbe --- /dev/null +++ b/drivers/blk/mmc/core/blk_driver.mk @@ -0,0 +1,43 @@ +# +# Copyright 2024, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +# Get current dir +SDMMC_DRIVER_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))) + +# Allow for different build configurations (default is debug) +microkit_sdk_config_dir := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) +sel4_include_dirs := $(microkit_sdk_config_dir)/include + +# Ensure build directory exists +$(BUILD_DIR): + @echo "Creating build directory $(BUILD_DIR)..." + mkdir -p $@ + +blk/mmc/meson/sddf_helper.o: $(SDMMC_DRIVER_DIR)/src/sddf_helper.c |blk/mmc/meson + $(CC) -c $(CFLAGS) $< -o $@ + +blk/mmc/meson/libsddfblk.a: blk/mmc/meson/sddf_helper.o |blk/mmc/meson + ${AR} rcs $@ $< + +blk/mmc/meson: + mkdir -p $@ + +# Main build target +blk_driver.elf: $(BUILD_DIR) blk/mmc/meson/libsddfblk.a + @cd $(abspath ${SDMMC_DRIVER_DIR}) && \ + echo "Building blk_driver.elf for board $(MICROKIT_BOARD)..." && \ + echo "MICROKIT SDK config directory: $(microkit_sdk_config_dir)" && \ + echo "SEl4 include directories: $(sel4_include_dirs)" && \ + SEL4_INCLUDE_DIRS=$(abspath $(sel4_include_dirs)) \ + RUSTFLAGS="-L $(BUILD_DIR)/blk/mmc/meson/ -l static=sddfblk" \ + cargo build \ + -Z build-std=core,alloc,compiler_builtins \ + -Z build-std-features=compiler-builtins-mem \ + --target-dir $(BUILD_DIR)/blk/mmc/meson/ \ + --target support/targets/aarch64-sel4-microkit-minimal.json \ + --features "meson" + cp $(BUILD_DIR)/blk/mmc/meson/aarch64-sel4-microkit-minimal/debug/blk_driver.elf $(BUILD_DIR) + echo "Build complete: $(TARGET_ELF)" \ No newline at end of file diff --git a/drivers/blk/mmc/core/config.json b/drivers/blk/mmc/core/config.json new file mode 100644 index 000000000..fac1c91eb --- /dev/null +++ b/drivers/blk/mmc/core/config.json @@ -0,0 +1,23 @@ +{ + "compatible": [ + "amlogic,meson-axg-mmc" + ], + "resources": { + "regions": [ + { + "name": "regs", + "perms": "rw", + "dt_index": 0 + }, + { + "name": "init_data", + "size": 4096 + } + ], + "irqs": [ + { + "dt_index": 0 + } + ] + } +} diff --git a/drivers/blk/mmc/core/protocol/Cargo.toml b/drivers/blk/mmc/core/protocol/Cargo.toml new file mode 100644 index 000000000..f6845ba66 --- /dev/null +++ b/drivers/blk/mmc/core/protocol/Cargo.toml @@ -0,0 +1,20 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +[package] +name = "sdmmc_protocol" +version = "0.1.0" +edition = "2024" +authors = ["Cheng Li 李澄 "] + +[lib] +name = "sdmmc_protocol" +path = "lib.rs" + +[dependencies] +bitflags = "2.6.0" + +[features] +default = [] +# A feature specifically for enabling developer-level logging. The driver will always try to log errors. +dev-logs = [] \ No newline at end of file diff --git a/drivers/blk/mmc/core/protocol/lib.rs b/drivers/blk/mmc/core/protocol/lib.rs new file mode 100644 index 000000000..e0493da93 --- /dev/null +++ b/drivers/blk/mmc/core/protocol/lib.rs @@ -0,0 +1,8 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +#![no_std] // Don't link the standard library + +pub mod sdmmc; +pub mod sdmmc_os; +pub mod sdmmc_traits; diff --git a/drivers/blk/mmc/core/protocol/sdmmc.rs b/drivers/blk/mmc/core/protocol/sdmmc.rs new file mode 100644 index 000000000..b976da197 --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc.rs @@ -0,0 +1,1477 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +pub mod capability; +pub mod mmc_struct; +pub mod sd; + +mod constant; + +use core::{ + future::Future, + pin::Pin, + sync::atomic::Ordering, + task::{Context, Poll, Waker}, +}; + +use capability::{ + MMC_CAP_4_BIT_DATA, MMC_EMPTY_CAP, MMC_TIMING_LEGACY, MMC_TIMING_SD_HS, MMC_TIMING_UHS_DDR50, + MMC_TIMING_UHS_SDR12, MMC_TIMING_UHS_SDR25, MMC_TIMING_UHS_SDR50, MMC_TIMING_UHS_SDR104, + SdcardCapability, +}; +use constant::{ + MMC_CMD_ALL_SEND_CID, MMC_CMD_APP_CMD, MMC_CMD_ERASE, MMC_CMD_GO_IDLE_STATE, + MMC_CMD_READ_MULTIPLE_BLOCK, MMC_CMD_READ_SINGLE_BLOCK, MMC_CMD_SELECT_CARD, MMC_CMD_SEND_CSD, + MMC_CMD_SET_BLOCK_COUNT, MMC_CMD_STOP_TRANSMISSION, MMC_CMD_WRITE_MULTIPLE_BLOCK, + MMC_CMD_WRITE_SINGLE_BLOCK, OCR_BUSY, OCR_HCS, OCR_S18R, SD_CMD_APP_SEND_OP_COND, + SD_CMD_APP_SET_BUS_WIDTH, SD_CMD_ERASE_WR_BLK_END, SD_CMD_ERASE_WR_BLK_START, + SD_CMD_SEND_IF_COND, SD_CMD_SEND_RELATIVE_ADDR, SD_CMD_SWITCH_FUNC, SD_CMD_SWITCH_UHS18V, + SD_ERASE_ARG, SD_SWITCH_FUNCTION_GROUP_ONE, SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_SDHS, + SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_DDR50, SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR12, + SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR25, SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR50, + SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR104, SD_SWITCH_FUNCTION_GROUP_ONE_SET_LEGACY, + SD_SWITCH_FUNCTION_GROUP_ONE_SET_SDHS, SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_DDR50, + SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR12, SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR25, + SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR50, SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR104, + SD_SWITCH_FUNCTION_SELECTION_GROUP_ONE, +}; +use mmc_struct::{BlockTransmissionMode, MmcBusWidth, MmcDevice, MmcState, MmcTiming}; +use sd::{Cid, Csd, Scr, Sdcard}; + +pub const SDCARD_DEFAULT_SECTOR_SIZE: u32 = 512; + +use crate::{ + dev_log, + sdmmc::mmc_struct::CardInfo, + sdmmc_os::{Sleep, VoltageOps}, + sdmmc_traits::{SdmmcHardware, SdmmcOps}, +}; + +pub struct SdmmcCmd { + pub cmdidx: u32, + pub resp_type: u32, + pub cmdarg: u32, +} + +pub struct MmcData { + // The size of the block(sector size), for sdcard should almost always be 512 + pub blocksize: u32, + // Number of blocks to transfer + pub blockcnt: u32, + pub flags: MmcDataFlag, + pub addr: u64, +} + +pub enum MmcDataFlag { + SdmmcDataRead, + SdmmcDataWrite, +} + +#[derive(Debug)] +pub enum SdmmcError { + // Error for result not ready yet + EBUSY, + ETIMEDOUT, + EINVAL, + EIO, + // Error that I currently not sure how to deal with... + EUNKNOWN, + EUNSUPPORTEDCARD, + ENOTIMPLEMENTED, + // This error should not be triggered unless there are bugs in program + EUNDEFINED, + // The block transfer succeed, but fail to stop the read/write process + ESTOPCMD, + ENOCARD, + ECARDINACTIVE, +} + +// Define the MMC response flags +pub const MMC_RSP_PRESENT: u32 = 1 << 0; +pub const MMC_RSP_136: u32 = 1 << 1; // 136-bit response +pub const MMC_RSP_CRC: u32 = 1 << 2; // Expect valid CRC +pub const MMC_RSP_BUSY: u32 = 1 << 3; // Card may send busy +pub const MMC_RSP_OPCODE: u32 = 1 << 4; // Response contains opcode + +// How many times should we try to init the card +const CARD_INIT_RETRY: u16 = 2; + +// Define the MMC response types +pub const MMC_RSP_NONE: u32 = 0; +pub const MMC_RSP_R1: u32 = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE; +pub const MMC_RSP_R1B: u32 = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_RSP_BUSY; +pub const MMC_RSP_R2: u32 = MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC; +pub const MMC_RSP_R3: u32 = MMC_RSP_PRESENT; +pub const MMC_RSP_R4: u32 = MMC_RSP_PRESENT; +pub const MMC_RSP_R5: u32 = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE; +pub const MMC_RSP_R6: u32 = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE; +pub const MMC_RSP_R7: u32 = MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE; + +// Signal voltage +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MmcSignalVoltage { + Voltage330 = 0, + Voltage180 = 1, + Voltage120 = 2, +} + +// Driver type +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MmcDriverType { + TypeB = 0, + TypeA = 1, + TypeC = 2, + TypeD = 3, +} + +// Enums for chip_select +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MmcChipSelect { + DontCare = 0, + High = 1, + Low = 2, +} + +/// Settings specific to eMMC cards. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EmmcSettings { + /// The drive strength of the host driver, typically relevant for eMMC devices. + /// + /// - The drive strength affects signal integrity and is selected based on the card's + /// operating conditions, such as bus load and speed. + /// - The eMMC specification defines four possible driver types (A, B, C, D) that + /// optimize for different use cases and electrical environments: + /// - `DriverType::TypeB`: Default driver strength for most cases. + /// - `DriverType::TypeA`, `TypeC`, `TypeD`: Other driver types based on signal + /// strength requirements. + pub drv_type: MmcDriverType, + + /// Specifies whether **HS400 Enhanced Strobe** mode is enabled. + /// + /// - Enhanced Strobe is used in **HS400** mode for eMMC devices to improve data + /// reliability at high speeds. It allows more accurate data capture by aligning + /// strobe signals with data. + /// - This is only relevant for eMMC cards in **HS400ES** mode. + pub enhanced_strobe: bool, +} + +/// Settings specific to SPI communication mode. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SpiSettings { + /// The chip select mode used in **SPI mode** communication. + /// + /// - This field is relevant only when the SD/MMC host controller is operating in **SPI mode**. + /// In **native SD/MMC protocol**, this field is not used. + /// + /// - The **chip select (CS)** pin is used to activate or deactivate the SD/MMC card on the SPI bus. + /// It allows the host to select which device it is communicating with when multiple devices share the same bus. + /// + /// - Possible values: + /// - `MmcChipSelect::DontCare`: The chip select state is ignored by the host. + /// - `MmcChipSelect::High`: The chip select pin is driven high, indicating that the card is not selected. + /// - `MmcChipSelect::Low`: The chip select pin is driven low, indicating that the card is selected and active. + /// + /// **Note**: + /// - In **native SD/MMC mode**, communication happens via dedicated **command and data lines** without the need for chip select. + /// - In most applications, **SPI mode** is less commonly used, especially in high-performance systems. + pub chip_select: MmcChipSelect, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// The `MmcIos` struct represents the I/O settings for the SD/MMC controller, +/// configuring how the host communicates with the card during various operations. +pub struct MmcIos { + /// The clock rate (in Hz) used for communication with the SD/MMC card. + /// + /// - This field specifies the frequency at which data is transferred between + /// the host and the card. The clock can vary depending on the mode the card + /// is in (e.g., initialization, data transfer). + /// - Typically, initialization occurs at a lower clock rate, and high-speed + /// data transfer occurs at higher rates. + pub clock: u64, + + /// The width of the data bus used for communication between the host and the card. + /// + /// - This field specifies whether the bus operates in 1-bit, 4-bit, or 8-bit mode. + /// - Wider bus widths (4-bit, 8-bit) enable higher data transfer rates, but not all + /// cards or host controllers support every bus width. + /// - Common values: + /// - `BusWidth::Width1`: 1-bit data width (lowest speed, used during initialization). + /// - `BusWidth::Width4`: 4-bit data width (common for SD cards). + /// - `BusWidth::Width8`: 8-bit data width (mainly for eMMC). + pub bus_width: MmcBusWidth, + + /// The signaling voltage level used for communication with the card. + /// + /// - Different SD/MMC cards support different signaling voltage levels. This field + /// indicates the voltage level used for signaling between the host and the card. + /// - Common voltage levels: + /// - `SignalVoltage::Voltage330`: 3.3V signaling. + /// - `SignalVoltage::Voltage180`: 1.8V signaling. + /// - `SignalVoltage::Voltage120`: 1.2V signaling (mainly for newer eMMC devices). + pub signal_voltage: MmcSignalVoltage, + + /// Indicating if interrupt is enabled or not + pub enabled_irq: bool, + + /// eMMC-specific settings, if applicable. + /// + /// This field is `None` if the card is not an eMMC card. + pub emmc: Option, + + /// SPI-specific settings, if applicable. + /// + /// This field is `None` if the card is not operating in SPI mode. + pub spi: Option, +} + +#[derive(Debug, Clone)] +pub struct HostInfo { + pub max_frequency: u64, + pub min_frequency: u64, + pub max_block_per_req: u32, + /// The voltage range (VDD) used for powering the SD/MMC card. + /// + /// - This field stores the selected voltage range in a bit-encoded format. + /// It indicates the voltage level the card is operating at. + /// - Common voltage levels are 3.3V, 1.8V, and sometimes 1.2V (for eMMC). + /// - Cards often negotiate their operating voltage during initialization. + pub vdd: u32, + + /// The capability of the host, like the features the host supports + pub host_capability: u128, +} + +impl HostInfo { + /// Zero cost function to determine if the host has a set of specific capabilities or not + #[inline] + pub const fn has_capability(&self, capability: u128) -> bool { + capability::SdmmcHostCapability(self.host_capability) + .contains(capability::SdmmcHostCapability(capability)) + } +} + +pub struct SdmmcProtocol { + hardware: T, + + sleep: S, + + voltage_ops: Option, + + mmc_ios: MmcIos, + + /// This mmc device is optional because there may not always be a card in the slot! + mmc_device: Option, + + private_memory: Option<*mut [u8; 64]>, +} + +impl Unpin for SdmmcProtocol +where + T: Unpin + SdmmcHardware, + S: Unpin + Sleep, + V: Unpin + VoltageOps, +{ +} + +impl SdmmcProtocol { + pub fn new(mut hardware: T, sleep: S, voltage_ops: Option) -> Result { + let ios = hardware.sdmmc_init()?; + + Ok(SdmmcProtocol { + hardware, + sleep, + voltage_ops, + mmc_ios: ios, + mmc_device: None, + private_memory: None, + }) + } + + fn sdcard_init(&mut self, voltage_switch: bool) -> Result<(), SdmmcError> { + let mut resp: [u32; 4] = [0; 4]; + + let mut cmd = SdmmcCmd { + cmdidx: MMC_CMD_GO_IDLE_STATE, + resp_type: MMC_RSP_NONE, + cmdarg: 0, + }; + + dev_log!("Try to send go idle cmd\n"); + + // Go idle command does not expect a response + self.hardware.sdmmc_send_command(&cmd, None)?; + + // Linux use 1ms delay here, we use 2ms + self.sleep.usleep(2_000); + + dev_log!("Try to send check operating voltage cmd\n"); + + cmd = SdmmcCmd { + cmdidx: SD_CMD_SEND_IF_COND, + resp_type: MMC_RSP_R7, + cmdarg: 0x000001AA, // Voltage supply and check pattern + }; + + let res: Result<(), SdmmcError> = + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 1); + + // If the result is OK and the resp is 0x1AA, the card we are initializing is a SDHC/SDXC + // If the result is error, it is either the voltage not being set up correctly, which mean a bug in hardware layer + // or the card is eMMC or legacy sdcard + // For now, we only deal with the situation it is a sdcard + if res.is_ok() && resp[0] != 0x1AA { + return Err(SdmmcError::EUNSUPPORTEDCARD); + } + + // Uboot define this value to 1000... + let mut retry: u16 = 1000; + + loop { + dev_log!("Sending SD_CMD_APP_SEND_OP_COND!\n"); + // Prepare CMD55 (APP_CMD) + cmd = SdmmcCmd { + cmdidx: MMC_CMD_APP_CMD, + resp_type: MMC_RSP_R1, + cmdarg: 0, + }; + + // Send CMD55 + let res = self + .hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 0); + + match res { + Ok(_) => {} + Err(SdmmcError::ETIMEDOUT) => return Err(SdmmcError::EUNSUPPORTEDCARD), + Err(_) => return res, + } + + cmd = SdmmcCmd { + cmdidx: SD_CMD_APP_SEND_OP_COND, + resp_type: MMC_RSP_R3, + cmdarg: 0, + }; + + // Set the HCS bit if version is SD Version 2 + // Since we are only support SDHC card, the OCR_HCS bit should be supported by the card + cmd.cmdarg |= OCR_HCS; + + // Right now we deliberately not set XPC bit for maximum compatibility + + // Change this when we decide to support spi or SDSC as well + cmd.cmdarg |= T::HOST_INFO.vdd & 0xff8000; + + if voltage_switch == true && T::HOST_INFO.has_capability(MMC_TIMING_UHS_SDR12) { + cmd.cmdarg |= OCR_S18R; + // It seems that cards will not respond to commands that have MMC_VDD_165_195 bit set, even if the card supports UHS-I + // cmd.cmdarg |= MMC_VDD_165_195; + } + + // Send ACMD41 + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 0)?; + + dev_log!("OCR: {:08x}\n", resp[0]); + + // Check if card is ready (OCR_BUSY bit) + if (resp[0] & OCR_BUSY) != 0 { + break; + } + + // retry handling + if retry <= 0 { + dev_log!("SDMMC: SEND_OP_COND failed, card not supported!\n"); + return Err(SdmmcError::EUNSUPPORTEDCARD); + } + retry -= 1; + } + + // Checking if the host and card is eligible for voltage switch + if voltage_switch == true + && T::HOST_INFO.has_capability(MMC_TIMING_UHS_SDR12) + && resp[0] & OCR_HCS == OCR_HCS + && resp[0] & OCR_S18R == OCR_S18R + { + // If the sdcard fail at this stage, a power circle and reinit will be performed + self.tune_sdcard_switch_uhs18v()?; + self.mmc_ios.signal_voltage = MmcSignalVoltage::Voltage180; + } + + Ok(()) + } + + // Function that is not completed + pub fn setup_card(&mut self) -> Result<(), SdmmcError> { + // Disable all irqs here + self.hardware.sdmmc_config_interrupt(false, false)?; + + let clock = self.hardware.sdmmc_config_timing(MmcTiming::CardSetup)?; + + self.mmc_ios.clock = clock; + + // This line of code may not be needed? + self.hardware.sdmmc_config_bus_width(MmcBusWidth::Width1)?; + + self.mmc_ios.bus_width = MmcBusWidth::Width1; + + // Use labeled block here for better clarification + // For card initialization, we retry 2 times for each card + // There could be more complex retry logic implemented in the future + let res: Result<(), SdmmcError> = 'sdcard_init: { + let mut voltage_switch_init: bool = T::HOST_INFO.has_capability(MMC_TIMING_UHS_SDR12); + let mut init_error: SdmmcError = SdmmcError::EUNSUPPORTEDCARD; + for _ in 0..CARD_INIT_RETRY { + match self.sdcard_init(voltage_switch_init) { + Ok(_) => break 'sdcard_init Ok(()), + Err(SdmmcError::EUNSUPPORTEDCARD) => { + break 'sdcard_init Err(SdmmcError::EUNSUPPORTEDCARD); + } + Err(e) => { + init_error = e; + if let Some(ref mut voltage_ops) = self.voltage_ops { + // Reset the signaling voltage back to 3.3V + voltage_ops.card_voltage_switch(MmcSignalVoltage::Voltage330)?; + self.sleep.usleep(1_000); + + voltage_ops.card_power_cycling()?; + } + // One bug that does not break anything here is + // sdmmc_host_reset will reset the clock to CardSetup timing and turn off the irq + // But those variable in mmc_ios is not changed accordingly + self.mmc_ios = self.hardware.sdmmc_host_reset()?; + voltage_switch_init = false; + } + } + } + Err(init_error) + }; + + if res.is_ok() { + let card: Sdcard = self.setup_sdcard_cont()?; + self.mmc_device = Some(MmcDevice::Sdcard(card)); + return Ok(()); + } + + // Unsupported card + { + // If the result is error, it is either the voltage not being set up correctly, which mean a bug in hardware layer + // or the card is eMMC or legacy sdcard + // For now, we only deal with the situation it is a sdcard + // TODO: Implement setup for eMMC and legacy sdcard(SDSC) here + dev_log!( + "Driver right now only support SDHC/SDXC card, please check if you are running this driver on SDIO/SDSC/EMMC card!\n" + ); + res + } + } + + /// From uboot + /// Most cards do not answer if some reserved bits + /// in the ocr are set. However, Some controller + /// can set bit 7 (reserved for low voltages), but + /// how to manage low voltages SD card is not yet + /// specified. + /// Check mmc_sd_get_cid() in Linux for card init process + fn setup_sdcard_cont(&mut self) -> Result { + let mut resp: [u32; 4] = [0; 4]; + + // Send CMD2 to get the CID register + let mut cmd = SdmmcCmd { + cmdidx: MMC_CMD_ALL_SEND_CID, + resp_type: MMC_RSP_R2, + cmdarg: 0, + }; + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 1)?; + + let cid: Cid = Cid::new(resp); + + let card_id = ((resp[0] as u128) << 96) + | ((resp[1] as u128) << 64) + | ((resp[2] as u128) << 32) + | (resp[3] as u128); + + // Print out the CID number + dev_log!( + "CID: {:08x} {:08x} {:08x} {:08x}\n", + resp[0], + resp[1], + resp[2], + resp[3] + ); + + // Send CMD3 to set and receive the RCA + cmd = SdmmcCmd { + cmdidx: SD_CMD_SEND_RELATIVE_ADDR, + resp_type: MMC_RSP_R6, + cmdarg: 0, + }; + + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 1)?; + + let rca: u16 = (resp[0] >> 16) as u16; // Store RCA from response + + // Print out the RCA number we have got + dev_log!("RCA: {:04x}\n", rca); + + // Send CMD9 to get the CSD register + cmd = SdmmcCmd { + cmdidx: MMC_CMD_SEND_CSD, + resp_type: MMC_RSP_R2, + cmdarg: (rca as u32) << 16, + }; + + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 1)?; + + dev_log!( + "CSD: {:08x} {:08x} {:08x} {:08x}\n", + resp[0], + resp[1], + resp[2], + resp[3] + ); + + let (csd, card_version) = Csd::new(resp)?; + + // Send CMD7 to select the card + cmd = SdmmcCmd { + cmdidx: MMC_CMD_SELECT_CARD, + resp_type: MMC_RSP_R1, + cmdarg: (rca as u32) << 16, + }; + + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 1)?; + + // SDHC/SDXC default to 512 bytes sector size so I did not manually set it here + + self.mmc_ios.clock = self.hardware.sdmmc_config_timing(MmcTiming::Legacy)?; + + let card_state: MmcState = MmcState { + timing: MmcTiming::Legacy, + bus_width: MmcBusWidth::Width1, + }; + + // Continue working on it next week + Ok(Sdcard { + card_id, + manufacture_info: cid, + card_specific_data: csd, + card_version, + relative_card_addr: rca, + card_state, + card_cap: capability::SdcardCapability(MMC_EMPTY_CAP), + method: BlockTransmissionMode::StopTransmission, + card_config: None, + }) + } + + /// A function that tune the card speed + /// This function does not do roll back so if this function return an error, reset up the card + /// Do NOT call this function again if your card is already tuned as this function is not that cheap! + /// But you should call this function the card being turned into power saving mode + /// `stolen_memory` is a specific memory region used to read data from the SD card through CMD6. + /// To use this function safely, the memory pointer passed in must be from a valid memory address + /// And must match the physical memory address that is being DMA into + /// + /// ### Important Considerations: + /// + /// 1. **Memory Region Constraints**: + /// - `stolen_memory` is used as a buffer to hold the 64-byte response from the SD card + /// when executing CMD6. This response contains the function switch status and other + /// function-related information. + /// - The memory region from `stolen_memory` to `stolen_memory + 64 bytes` must not overlap + /// with any other data structures, device registers, or memory-mapped peripherals to + /// avoid conflicts or unintended behavior. + /// + /// 2. **Cache Considerations**: + /// - If the system uses caching, you may need to ensure that cache effects do not interfere + /// with the integrity of the data read from the SD card. + /// - If caching is enabled, consider using cache invalidation before reading and cache flushing + /// after writing data to ensure consistency between the memory and the SD card. + /// For example, you might need to use cache control instructions or APIs specific to your + /// platform to manage this. + /// + /// 3. **Alignment and Access Requirements**: + /// - `stolen_memory` should be aligned to at least 4 bytes (or preferably 8 bytes) to avoid + /// misaligned memory access issues, which could lead to performance penalties or even faults + /// on some architectures. + /// Tunes SD card performance by adjusting data bus width and speed mode. + /// + /// # Parameters + /// - `addr_and_invalidate_cache_fn`: An optional tuple containing: + /// - `*mut [u8; 64]`: A memory address used as a buffer for certain commands (e.g., CMD6). The memory + /// should be suitable for DMA into(e.g. aligned to 8 bytes memory border and not conflict + /// with other structure or device registers). + /// - `fn()`: A function pointer that, when called, invalidates the cache for the range + /// `addr` to `addr + 64 bytes`. This function should ensure cache consistency for + /// that specific memory range. If `None`, no buffer is used, and the tune performance function + /// will not attempt to change the card speed class. The fn should not take any variables and would + /// not be stored. By this way, the protocol layer only has the minimal privilege it required for cache invalidation. + /// + /// # Returns + /// - `Result<(), SdmmcError>`: `Ok(())` if tuning was successful, or an error otherwise. + /// UB here waiting to be fixed: memory being a mutable reference is also accessed by DMA + pub unsafe fn tune_performance( + &mut self, + memory: *mut [u8; 64], + cache_invalidate_function: fn(), + physical_memory_addr: u64, + ) -> Result<(), SdmmcError> { + // For testing + let mmc_device = self.mmc_device.as_mut().ok_or(SdmmcError::ENOCARD)?; + + // Turn down the clock frequency + self.mmc_ios.clock = self.hardware.sdmmc_config_timing(MmcTiming::CardSetup)?; + + // The private memory should be the physically memory + // However any attempt to dereference the pointer + // could crash the program if the driver don't have access + // to the phyical memory at the proper address + self.private_memory = Some(physical_memory_addr as *mut [u8; 64]); + + match mmc_device { + MmcDevice::Sdcard(sdcard) => { + sdcard.card_state.timing = MmcTiming::CardSetup; + self.tune_sdcard_performance( + memory, + cache_invalidate_function, + physical_memory_addr, + ) + } + MmcDevice::EMmc(_emmc) => Err(SdmmcError::ENOTIMPLEMENTED), + MmcDevice::Unknown => Err(SdmmcError::ENOTIMPLEMENTED), + } + } + + pub fn test_read_one_block(&mut self, start_idx: u64, destination: u64) { + let data: MmcData = MmcData { + blocksize: SDCARD_DEFAULT_SECTOR_SIZE, + blockcnt: 1, + flags: MmcDataFlag::SdmmcDataRead, + addr: destination, + }; + dev_log!("Gonna test read one block!\n"); + let mut resp: [u32; 4] = [0; 4]; + let cmd_arg: u64 = start_idx; + let cmd = SdmmcCmd { + cmdidx: MMC_CMD_READ_SINGLE_BLOCK, + resp_type: MMC_RSP_R1, + cmdarg: cmd_arg as u32, + }; + if let Err(error) = + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, Some(&data), &mut resp, 0) + { + dev_log!("Error: {:?} in reading\n", error); + } + unsafe { print_one_block(destination as *mut u8, 512) }; + } + + /// Switch voltage function + fn tune_sdcard_switch_uhs18v(&mut self) -> Result<(), SdmmcError> { + dev_log!("Entering tune sdcard signal voltage function!\n"); + + let mut resp: [u32; 4] = [0; 4]; + let cmd = SdmmcCmd { + cmdidx: SD_CMD_SWITCH_UHS18V, + resp_type: MMC_RSP_R1, + cmdarg: 0, // Argument for 4-bit mode (0 for 1-bit mode) + }; + + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 0)?; + + dev_log!("Switch voltage prepared!\n"); + + self.mmc_ios.clock = self.hardware.sdmmc_config_timing(MmcTiming::ClockStop)?; + + // TODO: figuring out the optimal delay + self.sleep.usleep(100); + + let mut signal: u8 = 0xFF; + + for _ in 0..100 { + signal = self.hardware.sdmmc_read_datalanes()?; + // TODO: figuring out the optimal delay + self.sleep.usleep(100); + dev_log!("data signal value: 0b{:b}\n", signal); + if signal & 0xF == 0x0 { + break; + } + } + + if signal & 0xF != 0x0 { + return Err(SdmmcError::ECARDINACTIVE); + } + + if let Some(ref mut voltage_ops) = self.voltage_ops { + voltage_ops.card_voltage_switch(MmcSignalVoltage::Voltage180)?; + } else { + self.hardware + .sdmmc_voltage_switch(MmcSignalVoltage::Voltage180)? + } + + // TODO: figuring out the optimal delay + self.sleep.usleep(10_000); + + self.mmc_ios.clock = self.hardware.sdmmc_config_timing(MmcTiming::CardSetup)?; + + // TODO: figuring out the optimal delay + self.sleep.usleep(100); + + for _ in 0..100 { + signal = self.hardware.sdmmc_read_datalanes()?; + // TODO: figuring out the optimal delay + self.sleep.usleep(100); + dev_log!("data signal value: 0b{:b}\n", signal); + if signal & 0xF == 0xF { + break; + } + } + + if signal & 0xF != 0xF { + return Err(SdmmcError::ECARDINACTIVE); + } + + Ok(()) + } + + /// Right now the speed switch is done hackily + /// The speed switch is hardcoded to switch to UHS SDR104 + /// If the card does not support UHS SDR104, the switch will fail! + /// Implement this sdcard switch function to avoid this hackiness! + /// Like first get the speed classes the sdcard support by this function + /// and then switch to the proper speed class! + unsafe fn sdcard_switch_speed( + &mut self, + target: MmcTiming, + raw_memory: *mut [u8; 64], + invalidate_cache_fn: fn(), + physical_memory_addr: u64, + ) -> Result<(), SdmmcError> { + let data: MmcData = MmcData { + blocksize: 64, + blockcnt: 1, + flags: MmcDataFlag::SdmmcDataRead, + addr: physical_memory_addr, + }; + + let mut resp: [u32; 4] = [0; 4]; + + // As this function only change speed class, + let mut cmdarg: u32 = 0x80FFFFF0; + match target { + MmcTiming::Legacy => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_LEGACY as u32, + MmcTiming::SdHs => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_SDHS as u32, + MmcTiming::UhsSdr12 => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR12 as u32, + MmcTiming::UhsSdr25 => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR25 as u32, + MmcTiming::UhsSdr50 => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR50 as u32, + MmcTiming::UhsSdr104 => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR104 as u32, + MmcTiming::UhsDdr50 => cmdarg |= SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_DDR50 as u32, + _ => return Err(SdmmcError::EUNDEFINED), + } + let cmd = SdmmcCmd { + cmdidx: SD_CMD_SWITCH_FUNC, + resp_type: MMC_RSP_R1, + cmdarg, + }; + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, Some(&data), &mut resp, 0)?; + + // The use of fence here is actually wrong + // As the fence(Ordering::Acquire) on arm platform + // The cache maintenance instructions are not ordered + // by the Load-Acquire and Store-Release instructions + // But I will just leave it here as I cannot figure out + // A more elegant way and code works fine anyway + core::sync::atomic::fence(Ordering::Acquire); + + // Error handling here + invalidate_cache_fn(); + + // Since we are using u8 here, the endianness does matter + // 0xF indicate function switch error + // TODO: Double check here and deliberately trigger switch error to see if the field + // is being set to 0xF + if unsafe { (*raw_memory)[SD_SWITCH_FUNCTION_SELECTION_GROUP_ONE] & 0xF == 0xF } { + return Err(SdmmcError::EINVAL); + } + Ok(()) + } + + /// Unsafe because trying to dereference raw pointer + unsafe fn sdcard_check_supported_speed_class( + &mut self, + raw_memory: *mut [u8; 64], + invalidate_cache_fn: fn(), + physical_memory_addr: u64, + ) -> Result<(), SdmmcError> { + let data: MmcData = MmcData { + blocksize: 64, + blockcnt: 1, + flags: MmcDataFlag::SdmmcDataRead, + addr: physical_memory_addr, + }; + + let mut resp: [u32; 4] = [0; 4]; + + let mut card_cap: SdcardCapability = capability::SdcardCapability(MMC_EMPTY_CAP); + let cmd: SdmmcCmd = SdmmcCmd { + cmdidx: SD_CMD_SWITCH_FUNC, + resp_type: MMC_RSP_R1, + cmdarg: 0x00FFFFFF, + }; + + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, Some(&data), &mut resp, 0)?; + + core::sync::atomic::fence(Ordering::Acquire); + + invalidate_cache_fn(); + // Typical speed class supported: 80 03 + match self.mmc_ios.signal_voltage { + MmcSignalVoltage::Voltage330 => { + let speed_class_byte: u8 = unsafe { (*raw_memory)[SD_SWITCH_FUNCTION_GROUP_ONE] }; + if speed_class_byte & SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_SDHS != 0 { + card_cap.insert(capability::SdcardCapability(MMC_TIMING_SD_HS)); + } + } + MmcSignalVoltage::Voltage180 => { + let speed_class_byte: u8 = unsafe { (*raw_memory)[SD_SWITCH_FUNCTION_GROUP_ONE] }; + if speed_class_byte & SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR12 != 0 { + card_cap.insert(capability::SdcardCapability(MMC_TIMING_UHS_SDR12)); + } + if speed_class_byte & SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR25 != 0 { + card_cap.insert(capability::SdcardCapability(MMC_TIMING_UHS_SDR25)); + } + if speed_class_byte & SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR50 != 0 { + card_cap.insert(capability::SdcardCapability(MMC_TIMING_UHS_SDR50)); + } + if speed_class_byte & SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR104 != 0 { + card_cap.insert(capability::SdcardCapability(MMC_TIMING_UHS_SDR104)); + } + if speed_class_byte & SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_DDR50 != 0 { + card_cap.insert(capability::SdcardCapability(MMC_TIMING_UHS_DDR50)); + } + } + // For sdcard, the signal voltage cannot be 1.2V + MmcSignalVoltage::Voltage120 => return Err(SdmmcError::EUNDEFINED), + } + // Add the card cap to self + if let Some(MmcDevice::Sdcard(sdcard)) = &mut self.mmc_device { + sdcard.card_cap |= card_cap; + } else { + return Err(SdmmcError::EINVAL); + } + Ok(()) + } + + /// Since we are using u8 here, the endianness does matter + /// I should change the process of access raw memory to using volatile read + fn tune_sdcard_performance( + &mut self, + memory: *mut [u8; 64], + cache_invalidate_function: fn(), + physical_memory_addr: u64, + ) -> Result<(), SdmmcError> { + let mut resp: [u32; 4] = [0; 4]; + + if let Some(MmcDevice::Sdcard(sdcard)) = &mut self.mmc_device { + let scr: Scr = unsafe { + Sdcard::sdcard_get_configuration_register( + &mut self.hardware, + &mut self.sleep, + physical_memory_addr, + memory, + cache_invalidate_function, + sdcard.relative_card_addr, + )? + }; + + if scr.support_set_block_count { + sdcard.method = BlockTransmissionMode::SetBlockCount; + } + + sdcard.card_config = Some(scr); + } + + if self.mmc_ios.bus_width == MmcBusWidth::Width1 + && T::HOST_INFO.has_capability(MMC_CAP_4_BIT_DATA) + { + // Switch data bits per transfer + let relative_card_address: u16; + if let Some(MmcDevice::Sdcard(ref sdcard)) = self.mmc_device { + relative_card_address = sdcard.relative_card_addr; + } else { + return Err(SdmmcError::EINVAL); + } + // CMD55 + ACMD6 to set the card to 4-bit mode (if supported by host and card) + let cmd = SdmmcCmd { + cmdidx: MMC_CMD_APP_CMD, + resp_type: MMC_RSP_R1, + cmdarg: (relative_card_address as u32) << 16, + }; + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 0)?; + + let cmd = SdmmcCmd { + cmdidx: SD_CMD_APP_SET_BUS_WIDTH, + resp_type: MMC_RSP_R1, + cmdarg: 2, // Argument for 4-bit mode (0 for 1-bit mode) + }; + self.hardware + .sdmmc_do_request(&mut self.sleep, &cmd, None, &mut resp, 0)?; + + self.hardware.sdmmc_config_bus_width(MmcBusWidth::Width4)?; + + dev_log!("Tuning datalanes succeed!\n"); + + // If any of the cmd above fail, the card should be completely reinit + self.mmc_ios.bus_width = MmcBusWidth::Width4; + } + + dev_log!("Checking supported speed classes\n"); + + if let Some(MmcDevice::Sdcard(ref mut sdcard)) = self.mmc_device { + match self.mmc_ios.signal_voltage { + MmcSignalVoltage::Voltage330 => { + sdcard.card_state.timing = MmcTiming::Legacy; + if !sdcard + .card_cap + .contains(SdcardCapability(MMC_TIMING_LEGACY)) + { + // When the target speed is None, the sdcard_switch_speed update sdcard speed capability + unsafe { + self.sdcard_check_supported_speed_class( + memory, + cache_invalidate_function, + physical_memory_addr, + )?; + } + } + } + MmcSignalVoltage::Voltage180 => { + sdcard.card_state.timing = MmcTiming::UhsSdr12; + if !sdcard + .card_cap + .contains(SdcardCapability(MMC_TIMING_UHS_SDR12)) + { + unsafe { + self.sdcard_check_supported_speed_class( + memory, + cache_invalidate_function, + physical_memory_addr, + )?; + } + } + } + MmcSignalVoltage::Voltage120 => return Err(SdmmcError::EUNDEFINED), + }; + } else { + return Err(SdmmcError::EUNDEFINED); + } + + let mut target_timing: MmcTiming; + let sdcard_cap: SdcardCapability; + if let Some(MmcDevice::Sdcard(ref sdcard)) = self.mmc_device { + dev_log!("Switch to higher speed class\n"); + sdcard_cap = sdcard.card_cap.clone(); + target_timing = sdcard.card_state.timing; + // This 'tune_speed thing is a feature called labeled block in Rust + 'tune_speed: { + match self.mmc_ios.signal_voltage { + MmcSignalVoltage::Voltage330 => { + if sdcard_cap.contains(SdcardCapability(MMC_TIMING_SD_HS)) + && T::HOST_INFO.has_capability(MMC_TIMING_SD_HS) + { + target_timing = MmcTiming::SdHs; + } + } + MmcSignalVoltage::Voltage180 => { + if sdcard_cap.contains(SdcardCapability(MMC_TIMING_UHS_SDR104)) + && T::HOST_INFO.has_capability(MMC_TIMING_UHS_SDR104) + { + target_timing = MmcTiming::UhsSdr104; + // If the switch speed succeed, terminate the block + break 'tune_speed; + } + if !sdcard_cap.contains(SdcardCapability(MMC_TIMING_UHS_DDR50)) + && T::HOST_INFO.has_capability(MMC_TIMING_UHS_DDR50) + { + target_timing = MmcTiming::UhsDdr50; + break 'tune_speed; + } + if !sdcard_cap.contains(SdcardCapability(MMC_TIMING_UHS_SDR50)) + && T::HOST_INFO.has_capability(MMC_TIMING_UHS_SDR50) + { + target_timing = MmcTiming::UhsSdr50; + break 'tune_speed; + } + if !sdcard_cap.contains(SdcardCapability(MMC_TIMING_UHS_SDR25)) + && T::HOST_INFO.has_capability(MMC_TIMING_UHS_SDR25) + { + target_timing = MmcTiming::UhsSdr25; + break 'tune_speed; + } + } + MmcSignalVoltage::Voltage120 => return Err(SdmmcError::EUNDEFINED), + } + }; + unsafe { + self.sdcard_switch_speed( + target_timing, + memory, + cache_invalidate_function, + physical_memory_addr, + )?; + } + self.mmc_ios.clock = self.hardware.sdmmc_config_timing(target_timing)?; + + self.hardware + .sdmmc_execute_tuning(physical_memory_addr as *mut [u8; 64], &mut self.sleep)?; + + dev_log!("Current frequency: {}Hz\n", self.mmc_ios.clock); + } else { + return Err(SdmmcError::EUNDEFINED); + } + if let Some(MmcDevice::Sdcard(ref mut sdcard)) = self.mmc_device { + sdcard.card_state.timing = target_timing; + } + + Ok(()) + } + + pub fn config_interrupt( + &mut self, + enable_irq: bool, + enable_sdio_irq: bool, + ) -> Result<(), SdmmcError> { + self.mmc_ios.enabled_irq = enable_irq | enable_sdio_irq; + self.hardware + .sdmmc_config_interrupt(enable_irq, enable_sdio_irq) + } + + pub fn ack_interrupt(&mut self) -> Result<(), SdmmcError> { + self.hardware.sdmmc_ack_interrupt() + } + + pub async fn read_block( + mut self, + blockcnt: u32, + start_idx: u64, + destination: u64, + ) -> (Result<(), SdmmcError>, SdmmcProtocol) { + let trans_meth: BlockTransmissionMode = { + if let Some(ref device) = self.mmc_device { + match device { + MmcDevice::Sdcard(sdcard) => sdcard.method.clone(), + MmcDevice::EMmc(emmc) => emmc.method.clone(), + MmcDevice::Unknown => return (Err(SdmmcError::EUNSUPPORTEDCARD), self), + } + } else { + return (Err(SdmmcError::ENOCARD), self); + } + }; + + let mut cmd: SdmmcCmd; + let mut res: Result<(), SdmmcError>; + let mut turing: bool = false; + + let data: MmcData = MmcData { + blocksize: SDCARD_DEFAULT_SECTOR_SIZE, + blockcnt, + flags: MmcDataFlag::SdmmcDataRead, + addr: destination, + }; + let mut resp: [u32; 4] = [0; 4]; + + // TODO: Add more validation check in the future + // Like sdmmc card usually cannot transfer arbitrary number of blocks at once + + // The cmd arg for read operation is different between some card variation as showed by uboot code below + /* + if (mmc->high_capacity) + cmd.cmdarg = start; + else + cmd.cmdarg = start * mmc->read_bl_len; + */ + // For now we default to assume the card is high_capacity + // TODO: If we boot the card by ourself or reset the card, remember to send block len cmd + loop { + if blockcnt == 1 { + cmd = SdmmcCmd { + cmdidx: MMC_CMD_READ_SINGLE_BLOCK, + resp_type: MMC_RSP_R1, + cmdarg: start_idx as u32, + }; + res = Self::sdmmc_async_request(&mut self.hardware, &cmd, Some(&data), &mut resp) + .await; + } else { + // TODO: Add if here to determine if the card support cmd23 or not to determine to use cmd23 or cmd12 + // Set the expected number of blocks + + cmd = SdmmcCmd { + cmdidx: MMC_CMD_READ_MULTIPLE_BLOCK, + resp_type: MMC_RSP_R1, + cmdarg: start_idx as u32, + }; + + res = Self::sdmmc_multi_blocks_io( + &mut self.hardware, + &cmd, + &data, + &mut resp, + trans_meth.clone(), + ) + .await; + } + match res { + Ok(()) => {} + Err(ref err) => { + let stop_cmd: SdmmcCmd = SdmmcCmd { + cmdidx: MMC_CMD_STOP_TRANSMISSION, + resp_type: MMC_RSP_R1B, + cmdarg: 0, + }; + // Sending stop command manually regardless of whether it has been sent or not + let _ = + Self::sdmmc_async_request(&mut self.hardware, &stop_cmd, None, &mut resp) + .await; + + if let SdmmcError::EIO = err { + if let Some(memory) = self.private_memory { + if turing == false { + turing = true; + if let Ok(()) = + self.hardware.sdmmc_execute_tuning(memory, &mut self.sleep) + { + continue; + } + } + } + } + } + } + break; + } + (res, self) + } + + // Almost the same with read_block aside from the cmd being sent is a bit different + // For any future code add to read_block/write_block, remember to change both + // Should read_block/write_block be the same function? + pub async fn write_block( + mut self, + blockcnt: u32, + start_idx: u64, + source: u64, + ) -> (Result<(), SdmmcError>, SdmmcProtocol) { + let trans_meth: BlockTransmissionMode = { + if let Some(ref device) = self.mmc_device { + match device { + MmcDevice::Sdcard(sdcard) => sdcard.method.clone(), + MmcDevice::EMmc(emmc) => emmc.method.clone(), + MmcDevice::Unknown => return (Err(SdmmcError::EUNSUPPORTEDCARD), self), + } + } else { + return (Err(SdmmcError::ENOCARD), self); + } + }; + + let cmd: SdmmcCmd; + let res: Result<(), SdmmcError>; + + let data: MmcData = MmcData { + blocksize: SDCARD_DEFAULT_SECTOR_SIZE, + blockcnt, + flags: MmcDataFlag::SdmmcDataWrite, + addr: source, + }; + let mut resp: [u32; 4] = [0; 4]; + // TODO: Add more validation check in the future + + if blockcnt == 1 { + cmd = SdmmcCmd { + cmdidx: MMC_CMD_WRITE_SINGLE_BLOCK, + resp_type: MMC_RSP_R1, + cmdarg: start_idx as u32, + }; + res = Self::sdmmc_async_request(&mut self.hardware, &cmd, Some(&data), &mut resp).await; + + return (res, self); + } else { + // TODO: Add if here to determine if the card support cmd23 or not to determine to use cmd23 or cmd12 + // Set the expected number of blocks + + cmd = SdmmcCmd { + cmdidx: MMC_CMD_WRITE_MULTIPLE_BLOCK, + resp_type: MMC_RSP_R1, + cmdarg: start_idx as u32, + }; + + res = + Self::sdmmc_multi_blocks_io(&mut self.hardware, &cmd, &data, &mut resp, trans_meth) + .await; + + match res { + Ok(()) => {} + Err(_) => { + let cmd: SdmmcCmd = SdmmcCmd { + cmdidx: MMC_CMD_STOP_TRANSMISSION, + resp_type: MMC_RSP_R1B, + cmdarg: 0, + }; + // Sending stop command manually regardless of whether it has been sent or not + let _ = + Self::sdmmc_async_request(&mut self.hardware, &cmd, None, &mut resp).await; + } + } + + return (res, self); + } + } + + // TODO: correct erase block alignment, error handling and sdcard internal erasure timing(this erasure seems to be non-blocking?) + // It is likely(from reading Linux source code), polling in some host controller could be necessary + pub async fn erase_block( + mut self, + start_idx: u64, + end_idx: u64, + ) -> (Result<(), SdmmcError>, SdmmcProtocol) { + let mut cmd: SdmmcCmd; + let mut res: Result<(), SdmmcError>; + + let mut resp: [u32; 4] = [0; 4]; + + cmd = SdmmcCmd { + cmdidx: SD_CMD_ERASE_WR_BLK_START, + resp_type: MMC_RSP_R1, + cmdarg: start_idx as u32, + }; + + res = Self::sdmmc_async_request(&mut self.hardware, &cmd, None, &mut resp).await; + + if let Err(_) = res { + return (res, self); + } + + cmd = SdmmcCmd { + cmdidx: SD_CMD_ERASE_WR_BLK_END, + resp_type: MMC_RSP_R1, + cmdarg: end_idx as u32, + }; + + res = Self::sdmmc_async_request(&mut self.hardware, &cmd, None, &mut resp).await; + + if let Err(_) = res { + return (res, self); + } + + cmd = SdmmcCmd { + cmdidx: MMC_CMD_ERASE, + resp_type: MMC_RSP_R1B, + cmdarg: SD_ERASE_ARG, + }; + + res = Self::sdmmc_async_request(&mut self.hardware, &cmd, None, &mut resp).await; + + return (res, self); + } + + /// Function to execute one sdmmc request asynchronously + /// The resp could be used for future error parsing + async fn sdmmc_async_request( + hardware: &mut T, + cmd: &SdmmcCmd, + data: Option<&MmcData>, + resp: &mut [u32; 4], + ) -> Result<(), SdmmcError> { + // Send the command to the host sdcard + hardware.sdmmc_send_command(cmd, data)?; + // Wait until sdcard return the result to the driver + let res: Result<(), SdmmcError> = SdmmcCmdFuture::new(hardware, cmd, resp).await; + // Acknowledge interrupts + hardware.sdmmc_ack_interrupt()?; + + res + } + + /// Change this function so that if a request report back error + /// MMC_CMD_STOP_TRANSMISSION must be sent in the end so the card + /// stop transmission correctly + async fn sdmmc_multi_blocks_io( + hardware: &mut T, + request_cmd: &SdmmcCmd, + data: &MmcData, + resp: &mut [u32; 4], + transmission_mode: BlockTransmissionMode, + ) -> Result<(), SdmmcError> { + // Trasmission stage + match transmission_mode { + BlockTransmissionMode::SetBlockCount => { + let cmd: SdmmcCmd = SdmmcCmd { + cmdidx: MMC_CMD_SET_BLOCK_COUNT, + resp_type: MMC_RSP_R1, + cmdarg: data.blockcnt, // block count for the upcoming CMD25 operation + }; + + Self::sdmmc_async_request(hardware, &cmd, None, resp).await?; + + Self::sdmmc_async_request(hardware, &request_cmd, Some(&data), resp).await + } + BlockTransmissionMode::StopTransmission => { + let temp_res: Result<(), SdmmcError> = + Self::sdmmc_async_request(hardware, &request_cmd, Some(&data), resp).await; + + // Dependent on whether the card is mmc or not, the response type is different + // TODO: Add mmc checks here + let cmd: SdmmcCmd = SdmmcCmd { + cmdidx: MMC_CMD_STOP_TRANSMISSION, + resp_type: MMC_RSP_R1B, + cmdarg: 0, + }; + + // Technically this command has very little possibility to fail + // Unless for hardware or environment issue + let _ = Self::sdmmc_async_request(hardware, &cmd, None, resp).await; + + temp_res + } + BlockTransmissionMode::AutoStop => { + Self::sdmmc_async_request(hardware, &request_cmd, Some(&data), resp).await + } + } + } + + pub fn print_card_info(&self) { + if let Some(ref device) = self.mmc_device { + match device { + MmcDevice::Sdcard(sdcard) => { + sdcard.print_info(); + } + MmcDevice::EMmc(_emmc) => { + dev_log!("eMMC card support is not available yet!\n"); + } + MmcDevice::Unknown => { + dev_log!("Unknown card!\n"); + } + } + } else { + dev_log!("No available device! Try initialize the card first\n"); + } + } + + pub const fn host_info() -> HostInfo { + T::HOST_INFO + } + + pub fn card_info(&self) -> Result { + let res: Result; + if let Some(ref device) = self.mmc_device { + match device { + MmcDevice::Sdcard(sdcard) => { + res = Ok(sdcard.sdcard_info()); + } + MmcDevice::EMmc(_emmc) => { + res = Err(SdmmcError::ENOTIMPLEMENTED); + } + MmcDevice::Unknown => { + res = Err(SdmmcError::ENOTIMPLEMENTED); + } + } + } else { + res = Err(SdmmcError::ENOCARD); + } + res + } +} + +enum CmdState { + // Waiting for the response + WaitingForResponse, + // Finished + Finished, +} + +pub struct SdmmcCmdFuture<'a, 'b, 'c> { + hardware: &'a mut dyn SdmmcOps, + cmd: &'b SdmmcCmd, + waker: Option, + state: CmdState, + response: &'c mut [u32; 4], +} + +impl<'a, 'b, 'c> SdmmcCmdFuture<'a, 'b, 'c> { + pub fn new( + hardware: &'a mut dyn SdmmcOps, + cmd: &'b SdmmcCmd, + response: &'c mut [u32; 4], + ) -> SdmmcCmdFuture<'a, 'b, 'c> { + SdmmcCmdFuture { + hardware, + cmd, + waker: None, + state: CmdState::WaitingForResponse, + response, + } + } +} + +/// SdmmcCmdFuture serves as the basic building block for async fn above +/// In the context of Sdmmc device, since the requests are executed linearly under the hood +/// We actually do not need an executor to execute the request +/// The context can be ignored unless someone insist to use an executor for the requests +/// So for now, the context is being stored in waker but this waker will not be used +impl<'a, 'b, 'c> Future for SdmmcCmdFuture<'a, 'b, 'c> { + type Output = Result<(), SdmmcError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // As I have said above, this waker is not being used so it do not need to be shared data + // But store the waker provided anyway + // Beware you need to update waker every polling, for more details about why + // read Asynchronous Programming in Rust + self.waker = Some(cx.waker().clone()); + + match self.state { + CmdState::WaitingForResponse => { + let res: Result<(), SdmmcError>; + { + let this: &mut SdmmcCmdFuture<'a, 'b, 'c> = self.as_mut().get_mut(); + let cmd: &SdmmcCmd = this.cmd; + let response: &mut [u32; 4] = this.response; + let hardware: &dyn SdmmcOps = this.hardware; + res = hardware.sdmmc_receive_response(cmd, response); + } + if let Err(SdmmcError::EBUSY) = res { + return Poll::Pending; + } else { + self.state = CmdState::Finished; + return Poll::Ready(res); + } + } + // Despite the status is ready, the state machine get polled again + CmdState::Finished => return Poll::Ready(Err(SdmmcError::EUNDEFINED)), + } + } +} + +/// Helper function to print out the content of one block +#[allow(dead_code)] +unsafe fn print_one_block(ptr: *const u8, num: usize) { + unsafe { + // Iterate over the number of bytes and print each one in hexadecimal format + for i in 0..num { + let byte = *ptr.add(i); + if i % 16 == 0 { + dev_log!("\n{:04x}: ", i); + } + dev_log!("{:02x} ", byte); + } + dev_log!("\n"); + } +} diff --git a/drivers/blk/mmc/core/protocol/sdmmc/capability.rs b/drivers/blk/mmc/core/protocol/sdmmc/capability.rs new file mode 100644 index 000000000..c19565642 --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc/capability.rs @@ -0,0 +1,114 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use bitflags::bitflags; + +// Linux protocol layer use two u32 to represent all capabilities +// In this Rust based protocol layer we use u128. +// I have thought about whether I should seperate the SdmmcHostCapability to two structs, one for sdcard, one for eMMC +// But give up this idea because I do not know if there are such host that support both sdcard and eMMC +pub(crate) struct SdmmcHostCapability(pub u128); + +bitflags! { + /// Represents the host capabilities for SD/MMC controllers + impl SdmmcHostCapability: u128 { + // Timing modes + const MMC_TIMING_LEGACY = MMC_TIMING_LEGACY; + const MMC_TIMING_MMC_HS = MMC_TIMING_MMC_HS; + const MMC_TIMING_SD_HS = MMC_TIMING_SD_HS; + const MMC_TIMING_UHS_SDR12 = MMC_TIMING_UHS_SDR12; + const MMC_TIMING_UHS_SDR25 = MMC_TIMING_UHS_SDR25; + const MMC_TIMING_UHS_SDR50 = MMC_TIMING_UHS_SDR50; + const MMC_TIMING_UHS_SDR104 = MMC_TIMING_UHS_SDR104; + const MMC_TIMING_UHS_DDR50 = MMC_TIMING_UHS_DDR50; + const MMC_TIMING_MMC_DDR52 = MMC_TIMING_MMC_DDR52; + const MMC_TIMING_MMC_HS200 = MMC_TIMING_MMC_HS200; + const MMC_TIMING_MMC_HS400 = MMC_TIMING_MMC_HS400; + const MMC_TIMING_SD_EXP = MMC_TIMING_SD_EXP; + const MMC_TIMING_SD_EXP_1_2V = MMC_TIMING_SD_EXP_1_2V; + + // Capabilities + const MMC_CAP_4_BIT_DATA = MMC_CAP_4_BIT_DATA; + const MMC_CAP_8_BIT_DATA = MMC_CAP_8_BIT_DATA; + const MMC_CAP_BUS_WIDTH_TEST = MMC_CAP_BUS_WIDTH_TEST; + + const MMC_CAP_AUTO_STOP = MMC_CAP_AUTO_STOP; + } +} +#[derive(Debug, Clone)] +pub(crate) struct SdcardCapability(pub u128); + +bitflags! { + /// Represents the host capabilities for SD/MMC controllers + impl SdcardCapability: u128 { + // Timing modes + const MMC_TIMING_LEGACY = MMC_TIMING_LEGACY; + const MMC_TIMING_MMC_HS = MMC_TIMING_MMC_HS; + const MMC_TIMING_SD_HS = MMC_TIMING_SD_HS; + const MMC_TIMING_UHS_SDR12 = MMC_TIMING_UHS_SDR12; + const MMC_TIMING_UHS_SDR25 = MMC_TIMING_UHS_SDR25; + const MMC_TIMING_UHS_SDR50 = MMC_TIMING_UHS_SDR50; + const MMC_TIMING_UHS_SDR104 = MMC_TIMING_UHS_SDR104; + const MMC_TIMING_UHS_DDR50 = MMC_TIMING_UHS_DDR50; + const MMC_TIMING_SD_EXP = MMC_TIMING_SD_EXP; + const MMC_TIMING_SD_EXP_1_2V = MMC_TIMING_SD_EXP_1_2V; + + // Capabilities + const MMC_CAP_4_BIT_DATA = MMC_CAP_4_BIT_DATA; + + const MMC_CAP_CMD23 = MMC_CAP_CMD23; + const MMC_CAP_AUTO_STOP = MMC_CAP_AUTO_STOP; + } +} + +pub const MMC_EMPTY_CAP: u128 = 0; + +// Timing modes (starting from bit 0) +pub const MMC_TIMING_LEGACY: u128 = 1 << 0; +pub const MMC_TIMING_MMC_HS: u128 = 1 << 1; +pub const MMC_TIMING_SD_HS: u128 = 1 << 2; +pub const MMC_TIMING_UHS_SDR12: u128 = 1 << 3; +pub const MMC_TIMING_UHS_SDR25: u128 = 1 << 4; +pub const MMC_TIMING_UHS_SDR50: u128 = 1 << 5; +pub const MMC_TIMING_UHS_DDR50: u128 = 1 << 6; +pub const MMC_TIMING_UHS_SDR104: u128 = 1 << 7; +pub const MMC_TIMING_MMC_DDR52: u128 = 1 << 8; +pub const MMC_TIMING_MMC_HS200: u128 = 1 << 9; +pub const MMC_TIMING_MMC_HS400: u128 = 1 << 10; + +pub const MMC_TIMING_UHS: u128 = MMC_TIMING_UHS_SDR12 + | MMC_TIMING_UHS_SDR25 + | MMC_TIMING_UHS_SDR50 + | MMC_TIMING_UHS_SDR104 + | MMC_TIMING_UHS_DDR50; + +pub const MMC_TIMING_SD_EXP: u128 = 1 << 11; +pub const MMC_TIMING_SD_EXP_1_2V: u128 = 1 << 12; + +// Capabilities +pub const MMC_CAP_4_BIT_DATA: u128 = 1 << 16; +pub const MMC_CAP_8_BIT_DATA: u128 = 1 << 17; + +pub const MMC_CAP_BUS_WIDTH_TEST: u128 = 1 << 18; + +pub const MMC_CAP_CMD23: u128 = 1 << 30; +pub const MMC_CAP_AUTO_STOP: u128 = 1 << 31; + +// Host VDD voltage levels for MMC/SD card +pub const MMC_VDD_165_195: u32 = 0x0000_0080; // VDD voltage 1.65 - 1.95V +pub const MMC_VDD_20_21: u32 = 0x0000_0100; // VDD voltage 2.0 - 2.1V +pub const MMC_VDD_21_22: u32 = 0x0000_0200; // VDD voltage 2.1 - 2.2V +pub const MMC_VDD_22_23: u32 = 0x0000_0400; // VDD voltage 2.2 - 2.3V +pub const MMC_VDD_23_24: u32 = 0x0000_0800; // VDD voltage 2.3 - 2.4V +pub const MMC_VDD_24_25: u32 = 0x0000_1000; // VDD voltage 2.4 - 2.5V +pub const MMC_VDD_25_26: u32 = 0x0000_2000; // VDD voltage 2.5 - 2.6V +pub const MMC_VDD_26_27: u32 = 0x0000_4000; // VDD voltage 2.6 - 2.7V +pub const MMC_VDD_27_28: u32 = 0x0000_8000; // VDD voltage 2.7 - 2.8V +pub const MMC_VDD_28_29: u32 = 0x0001_0000; // VDD voltage 2.8 - 2.9V +pub const MMC_VDD_29_30: u32 = 0x0002_0000; // VDD voltage 2.9 - 3.0V +pub const MMC_VDD_30_31: u32 = 0x0004_0000; // VDD voltage 3.0 - 3.1V +pub const MMC_VDD_31_32: u32 = 0x0008_0000; // VDD voltage 3.1 - 3.2V +pub const MMC_VDD_32_33: u32 = 0x0010_0000; // VDD voltage 3.2 - 3.3V +pub const MMC_VDD_33_34: u32 = 0x0020_0000; // VDD voltage 3.3 - 3.4V +pub const MMC_VDD_34_35: u32 = 0x0040_0000; // VDD voltage 3.4 - 3.5V +pub const MMC_VDD_35_36: u32 = 0x0080_0000; // VDD voltage 3.5 - 3.6V diff --git a/drivers/blk/mmc/core/protocol/sdmmc/constant.rs b/drivers/blk/mmc/core/protocol/sdmmc/constant.rs new file mode 100644 index 000000000..f621af67b --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc/constant.rs @@ -0,0 +1,96 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +#![allow(dead_code)] // Allow dead code for the entire module + +// Define constants for MMC data flags +pub const MMC_DATA_READ: u32 = 1; +pub const MMC_DATA_WRITE: u32 = 2; + +// Define constants for MMC commands +pub const MMC_CMD_GO_IDLE_STATE: u32 = 0; +pub const MMC_CMD_SEND_OP_COND: u32 = 1; +pub const MMC_CMD_ALL_SEND_CID: u32 = 2; +pub const MMC_CMD_SET_RELATIVE_ADDR: u32 = 3; +pub const MMC_CMD_SET_DSR: u32 = 4; +pub const MMC_CMD_SWITCH: u32 = 6; +pub const MMC_CMD_SELECT_CARD: u32 = 7; +pub const MMC_CMD_SEND_EXT_CSD: u32 = 8; +pub const MMC_CMD_SEND_CSD: u32 = 9; +pub const MMC_CMD_SEND_CID: u32 = 10; +pub const MMC_CMD_STOP_TRANSMISSION: u32 = 12; +pub const MMC_CMD_SEND_STATUS: u32 = 13; +pub const MMC_CMD_SET_BLOCKLEN: u32 = 16; +pub const MMC_CMD_READ_SINGLE_BLOCK: u32 = 17; +pub const MMC_CMD_READ_MULTIPLE_BLOCK: u32 = 18; +pub const MMC_CMD_SEND_TUNING_BLOCK: u32 = 19; +pub const MMC_CMD_SEND_TUNING_BLOCK_HS200: u32 = 21; +pub const MMC_CMD_SET_BLOCK_COUNT: u32 = 23; +pub const MMC_CMD_WRITE_SINGLE_BLOCK: u32 = 24; +pub const MMC_CMD_WRITE_MULTIPLE_BLOCK: u32 = 25; +pub const MMC_CMD_ERASE_GROUP_START: u32 = 35; +pub const MMC_CMD_ERASE_GROUP_END: u32 = 36; +pub const MMC_CMD_ERASE: u32 = 38; +pub const MMC_CMD_APP_CMD: u32 = 55; +pub const MMC_CMD_SPI_READ_OCR: u32 = 58; +pub const MMC_CMD_SPI_CRC_ON_OFF: u32 = 59; +pub const MMC_CMD_RES_MAN: u32 = 62; + +// Define constants for MMC command 62 arguments +pub const MMC_CMD62_ARG1: u32 = 0xefac62ec; +pub const MMC_CMD62_ARG2: u32 = 0xcbaea7; + +// Define constants for SD commands +pub const SD_CMD_SEND_RELATIVE_ADDR: u32 = 3; +pub const SD_CMD_SWITCH_FUNC: u32 = 6; +pub const SD_CMD_SEND_IF_COND: u32 = 8; +pub const SD_CMD_SWITCH_UHS18V: u32 = 11; + +pub const SD_CMD_APP_SET_BUS_WIDTH: u32 = 6; +pub const SD_CMD_APP_SD_STATUS: u32 = 13; + +pub const SD_CMD_ERASE_WR_BLK_START: u32 = 32; +pub const SD_CMD_ERASE_WR_BLK_END: u32 = 33; + +/* + * Erase/discard + */ +pub const SD_ERASE_ARG: u32 = 0x00000000; +pub const SD_DISCARD_ARG: u32 = 0x00000001; + +pub const SD_CMD_APP_SEND_OP_COND: u32 = 41; +pub const SD_CMD_APP_SEND_SCR: u32 = 51; + +pub const OCR_BUSY: u32 = 0x8000_0000; +pub const OCR_XPC: u32 = 0x1000_0000; +pub const OCR_HCS: u32 = 0x4000_0000; +pub const OCR_S18R: u32 = 0x0100_0000; +pub const OCR_VOLTAGE_MASK: u32 = 0x007F_FF80; +pub const OCR_ACCESS_MODE: u32 = 0x6000_0000; + +// The index to get the speed class information from SD switch function cmd +// Check Part 1 Physical Layer Simplified Specification Ver9.10 table 4-11 to see if I am wrong +pub const SD_SWITCH_FUNCTION_GROUP_ONE: usize = 13; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_LEGACY: u8 = 0x0; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_SDHS: u8 = 0x1; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR12: u8 = 0x0; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR25: u8 = 0x1; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR50: u8 = 0x2; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR104: u8 = 0x3; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_DDR50: u8 = 0x4; + +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_LEGACY: u8 = + 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_LEGACY; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_SDHS: u8 = 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_SDHS; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR12: u8 = + 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR12; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR25: u8 = + 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR25; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR50: u8 = + 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR50; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_SDR104: u8 = + 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_SDR104; +pub const SD_SWITCH_FUNCTION_GROUP_ONE_CHECK_UHS_DDR50: u8 = + 1 << SD_SWITCH_FUNCTION_GROUP_ONE_SET_UHS_DDR50; + +pub const SD_SWITCH_FUNCTION_SELECTION_GROUP_ONE: usize = 16; diff --git a/drivers/blk/mmc/core/protocol/sdmmc/mmc_struct.rs b/drivers/blk/mmc/core/protocol/sdmmc/mmc_struct.rs new file mode 100644 index 000000000..c59ec6367 --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc/mmc_struct.rs @@ -0,0 +1,101 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use super::sd::{EMmc, Sdcard}; + +// Enums for bus_width +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MmcBusWidth { + Width1 = 0, + // One is skipped because for SD_ACMD_SET_BUS_WIDTH, setting cmdargs to 2 indicate 4 datalanes + Width4 = 2, + Width8 = 3, +} + +// Timing modes (could be an enum or use the bitflags constants defined earlier) +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum MmcTiming { + Legacy = 0, + MmcHs = 1, + SdHs = 2, + UhsSdr12 = 3, + UhsSdr25 = 4, + UhsSdr50 = 5, + UhsSdr104 = 6, + UhsDdr50 = 7, + MmcDdr52 = 8, + MmcHs200 = 9, + MmcHs400 = 10, + SdExp = 11, + SdExp12V = 12, + CardSetup = 13, // Additional frequency for card setup + CardSleep = 14, + ClockStop = 15, +} + +#[derive(Debug, Clone)] +pub struct MmcState { + /// The timing specification that dictates how data is transferred between the host + /// and the card. + /// + /// - The timing mode defines the protocol and speed class for communication, such as + /// legacy modes, high-speed modes, or ultra-high-speed modes. + /// - Examples include: + /// - `Timing::Legacy`: Legacy slower transfer mode. + /// - `Timing::SdHs`: SD high-speed mode. + /// - `Timing::MmcHs200`: eMMC HS200 mode for high-speed data transfers. + pub timing: MmcTiming, + + /// The width of the data bus used for communication between the host and the card. + /// + /// - This field specifies whether the bus operates in 1-bit, 4-bit, or 8-bit mode. + /// - Wider bus widths (4-bit, 8-bit) enable higher data transfer rates, but not all + /// cards or host controllers support every bus width. + /// - Common values: + /// - `BusWidth::Width1`: 1-bit data width (lowest speed, used during initialization). + /// - `BusWidth::Width4`: 4-bit data width (common for SD cards). + /// - `BusWidth::Width8`: 8-bit data width (mainly for eMMC). + pub bus_width: MmcBusWidth, +} + +/// Some of the MmcDevice is reserved for future use +#[allow(dead_code)] +pub(crate) enum MmcDevice { + Sdcard(Sdcard), + EMmc(EMmc), + Unknown, +} + +/// Represents the different states of an SD or eMMC card. +/// Not used yet +#[allow(dead_code)] +#[derive(Debug, PartialEq)] +enum CardState { + Idle, + Ready, + Identification, + Standby, + Transfer, + SendingData, + ReceiveData, + Programming, + Disconnect, + Unknown, +} + +#[derive(Debug, Clone)] +pub enum BlockTransmissionMode { + // Using set block count cmd for multiblock transfer + SetBlockCount = 0, + // Using stop transmission count cmd for multiblock transfer + StopTransmission = 1, + // Host automatically send stop command without the need to driver interference + AutoStop = 2, +} + +#[derive(Debug)] +pub struct CardInfo { + pub card_id: u128, + pub card_capacity: u64, + pub card_state: MmcState, +} diff --git a/drivers/blk/mmc/core/protocol/sdmmc/sd.rs b/drivers/blk/mmc/core/protocol/sdmmc/sd.rs new file mode 100644 index 000000000..9e4bafa2d --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc/sd.rs @@ -0,0 +1,431 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use core::sync::atomic::Ordering; + +use crate::{ + dev_log, info, + sdmmc::{ + MMC_RSP_R1, MmcData, MmcDataFlag, SdmmcCmd, + constant::{MMC_CMD_APP_CMD, SD_CMD_APP_SEND_SCR, SD_CMD_SWITCH_FUNC}, + mmc_struct::CardInfo, + }, + sdmmc_os::Sleep, + sdmmc_traits::SdmmcHardware, +}; + +use super::{ + SdmmcError, + capability::SdcardCapability, + mmc_struct::{BlockTransmissionMode, MmcBusWidth, MmcState}, +}; + +#[allow(dead_code)] +pub struct Sdcard { + pub(crate) card_id: u128, + pub(crate) manufacture_info: Cid, + pub(crate) card_specific_data: Csd, + pub(crate) card_version: SdVersion, + pub(crate) relative_card_addr: u16, + pub(crate) card_state: MmcState, + pub(crate) card_cap: SdcardCapability, + pub(crate) method: BlockTransmissionMode, + pub(crate) card_config: Option, +} + +impl Sdcard { + /// Unsafe because dereference raw pointer + pub(crate) unsafe fn sdcard_get_configuration_register( + hardware: &mut T, + sleep: &mut dyn Sleep, + physical_memory: u64, + raw_memory: *mut [u8; 64], + invalidate_cache_fn: fn(), + rca: u16, + ) -> Result { + let mut resp: [u32; 4] = [0; 4]; + let mut cmd: SdmmcCmd = SdmmcCmd { + cmdidx: MMC_CMD_APP_CMD, + resp_type: MMC_RSP_R1, + cmdarg: (rca as u32) << 16, + }; + hardware.sdmmc_do_request(sleep, &cmd, None, &mut resp, 0)?; + + cmd = SdmmcCmd { + cmdidx: SD_CMD_APP_SEND_SCR, + resp_type: MMC_RSP_R1, + cmdarg: 0, + }; + let data: MmcData = MmcData { + blocksize: 8, + blockcnt: 1, + flags: MmcDataFlag::SdmmcDataRead, + addr: physical_memory, + }; + + hardware.sdmmc_do_request(sleep, &cmd, Some(&data), &mut resp, 0)?; + + core::sync::atomic::fence(Ordering::Acquire); + + invalidate_cache_fn(); + + // print out the content of the SCR register + crate::dev_log!("SCR register content: "); + unsafe { crate::sdmmc::print_one_block(raw_memory as *const u8, 8) }; + + // The sdcard register data is always in big endian format + // Now we construct the last 32 bits of the scr register + let scr_raw: u64 = unsafe { + ((((*raw_memory)[0] as u64) << 24) + + (((*raw_memory)[1] as u64) << 16) + + (((*raw_memory)[2] as u64) << 8) + + ((*raw_memory)[3] as u64)) + << 32 + }; + + let scr: Scr = Scr::new(scr_raw)?; + + Ok(scr) + } + + pub fn sdcard_test_tuning( + hardware: &mut T, + sleep: &mut dyn Sleep, + memory: *mut [u8; 64], + ) -> Result<(), SdmmcError> { + let mut resp: [u32; 4] = [0; 4]; + + let data = MmcData { + blocksize: 64, + blockcnt: 1, + flags: MmcDataFlag::SdmmcDataRead, + addr: memory as u64, + }; + + let cmd = SdmmcCmd { + cmdidx: SD_CMD_SWITCH_FUNC, + resp_type: MMC_RSP_R1, + cmdarg: 0x00FFFFFF, + }; + + hardware.sdmmc_do_request(sleep, &cmd, Some(&data), &mut resp, 1) + } + + pub fn print_info(&self) { + const LABEL_WIDTH: usize = 20; + const DATA_WIDTH: usize = 25; + let capacity_bytes = self.card_specific_data.card_capacity; + + const KB: u64 = 1024; + const MB: u64 = 1024 * KB; + const GB: u64 = 1024 * MB; + const TB: u64 = 1024 * GB; + + info!("\n\n╔═════════════════════════════════════════════════╗"); + info!("║ SDCARD INFORMATION ║"); + info!("╠═════════════════════════════════════════════════╣"); + info!( + "║ {:= TB => (c / TB, (c % TB) / GB, "TB"), + c if c >= GB => (c / GB, (c % GB) / MB, "GB"), + c if c >= MB => (c / MB, (c % MB) / KB, "MB"), + c if c >= KB => (c / KB, c % KB, "KB"), + c => (c, 0, "Bytes"), + }; + if fraction == 0 { + info!( + "║ {: CardInfo { + CardInfo { + card_id: self.card_id, + card_capacity: self.card_specific_data.card_capacity, + card_state: self.card_state.clone(), + } + } +} + +/// Placeholder eMMC struct that is not implemented +pub struct EMmc { + pub card_id: u128, + pub method: BlockTransmissionMode, +} + +// Beware this struct is meant to track the cmd set that the sdcard should support +// For example, if the SdVersion is set to V3_0, it does not mean the card version is 3.0 +// But mean that the sdcard support cmd at least up to specification 3.0 +// The SD card specification is cumulative, meaning that if an SD card reports support for a +// particular version (say 4.0), it implicitly supports all earlier versions as well. +#[allow(dead_code)] +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum SdVersion { + V1_0 = 1, + V2_0 = 2, + V3_0 = 3, + V4_0 = 4, +} + +#[derive(Debug)] +pub(crate) struct Cid { + manufacturer_id: u8, + oem_id: u16, + product_name: [u8; 5], + product_revision: u8, + serial_number: u32, + manufacturing_date: (u32, u8), // (year, month) +} + +impl Cid { + pub fn new(cid: [u32; 4]) -> Cid { + // Combine the 4 u32 words into a single 128-bit CID value for easy bit manipulation + let cid_combined: u128 = ((cid[0] as u128) << 96) + | ((cid[1] as u128) << 64) + | ((cid[2] as u128) << 32) + | (cid[3] as u128); + + // Extract each field based on the CID structure + let manufacturer_id = ((cid_combined >> 120) & 0xFF) as u8; + let oem_id = ((cid_combined >> 104) & 0xFFFF) as u16; + + // Extract product name, which is 5 bytes (40 bits) + let product_name: [u8; 5] = [ + ((cid_combined >> 96) & 0xFF) as u8, + ((cid_combined >> 88) & 0xFF) as u8, + ((cid_combined >> 80) & 0xFF) as u8, + ((cid_combined >> 72) & 0xFF) as u8, + ((cid_combined >> 64) & 0xFF) as u8, + ]; + + let product_revision: u8 = ((cid_combined >> 56) & 0xFF) as u8; + let serial_number: u32 = ((cid_combined >> 24) & 0xFFFFFFFF) as u32; + + // Extract year and month from the manufacturing date + let year: u32 = ((cid_combined >> 12) & 0xFF) as u32 + 2000; + let month: u8 = ((cid_combined >> 8) & 0x0F) as u8; + + Cid { + manufacturer_id, + oem_id, + product_name, + product_revision, + serial_number, + manufacturing_date: (year, month), + } + } +} + +// This struct is super unreliable, I am thinking +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) struct Csd { + csd_structure: u8, + card_capacity: u64, + max_read_block_len: u16, + max_write_block_len: u16, + erase_sector_size: u32, + supports_partial_write: bool, +} + +impl Csd { + pub fn new(csd: [u32; 4]) -> Result<(Csd, SdVersion), SdmmcError> { + // Combine the four 32-bit words into a single 128-bit value for easier bit manipulation + let csd_combined: u128 = ((csd[0] as u128) << 96) + | ((csd[1] as u128) << 64) + | ((csd[2] as u128) << 32) + | (csd[3] as u128); + + // Extract the CSD structure version + let csd_structure: u8 = ((csd_combined >> 126) & 0x3) as u8; // Bits 126–127 + let sd_version: SdVersion = match csd_structure { + 0 => SdVersion::V1_0, // CSD Version 1.0 + 1 => SdVersion::V2_0, // CSD Version 2.0 + // Even if the parsing csd fails, it should not crash the driver completely + _ => return Err(SdmmcError::EUNSUPPORTEDCARD), // CSD structures beyond 2.0 are not supported here + // Actually SDUC card are using CSD 3.0 so maybe add something here later. + }; + + // Parse fields based on CSD version + let (card_capacity, erase_sector_size) = match sd_version { + SdVersion::V1_0 => { + // CSD Version 1.0 capacity calculation + let c_size: u64 = ((csd_combined >> 62) & 0xFFF) as u64; // Bits 62–73 + let c_size_mult: u64 = ((csd_combined >> 47) & 0x7) as u64; // Bits 47–49 + let read_bl_len: u64 = ((csd_combined >> 80) & 0xF) as u64; // Bits 80–83 + let card_capacity: u64 = + (c_size + 1) * (1 << (c_size_mult + 2)) * (1 << read_bl_len); + + // Erase sector size is calculated differently in CSD Version 1.0 + let sector_size: u32 = ((csd_combined >> 39) & 0x7F) as u32 + 1; // Bits 39–45 + (card_capacity, sector_size) + } + SdVersion::V2_0 => { + // CSD Version 2.0 capacity calculation for SDHC/SDXC + let c_size: u64 = ((csd_combined >> 48) & 0x3FFFFF) as u64; // Bits 48–69 + let card_capacity: u64 = (c_size + 1) * 512 * 1024; // Capacity formula for SDHC/SDXC + + // Erase sector size calculation for CSD Version 2.0 + let sector_size: u32 = ((csd_combined >> 39) & 0x7F + 1) as u32 * 512; // Bits 39–45 + + (card_capacity, sector_size) + } + SdVersion::V3_0 => return Err(SdmmcError::EINVAL), + SdVersion::V4_0 => return Err(SdmmcError::EINVAL), + }; + + // Block lengths (same for both versions) + let read_bl_len: u16 = ((csd_combined >> 80) & 0xF) as u16; // Bits 80–83 + let max_read_block_len: u16 = 1 << read_bl_len; + + let write_bl_len: u16 = ((csd_combined >> 22) & 0xF) as u16; // Bits 22–25 + let max_write_block_len: u16 = 1 << write_bl_len; + + // Partial write support (same for both versions) + let supports_partial_write: bool = ((csd_combined >> 21) & 0x1) != 0; // Bit 21 + + // Return the constructed CSD struct along with the SD version + Ok(( + Csd { + csd_structure, + card_capacity, + max_read_block_len, + max_write_block_len, + erase_sector_size, + supports_partial_write, + }, + sd_version, + )) + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) struct Scr { + // Not extracted from Scr parsing yet + pub sd_spec: u32, + pub data_stat_after_erase: bool, + // Not extracted from Scr parsing yet + sd_security: u8, + pub sd_bus_width: MmcBusWidth, + pub support_speed_class_control: bool, + pub support_set_block_count: bool, + pub support_extersion_register_single_block: bool, + pub support_extersion_register_multi_block: bool, + pub support_security_transmission: bool, +} + +impl Scr { + // Input variable: raw scr data + pub fn new(scr_raw: u64) -> Result { + if scr_raw & (0b1 << 48) != (0b1 << 48) { + return Err(SdmmcError::EINVAL); + } + + let mut sd_bus_width: MmcBusWidth = MmcBusWidth::Width1; + + if scr_raw & (0b1 << 50) == (0b1 << 50) { + sd_bus_width = MmcBusWidth::Width4; + } + + // Extract bits 32-36 as a single value (5 bits = 0-31 range) + // Shift right 32 bits and mask with 0b11111 (31) + let command_support_bits = (scr_raw >> 32) & 0b11111; + + // Convert to bool array + let mut supported_cmd: [bool; 5] = [false; 5]; + for i in 0..5 { + supported_cmd[i] = (command_support_bits & (1 << i)) != 0; + } + + dev_log!("Supported cmd: {:?}\n", supported_cmd); + + let mut data_stat_after_erase: bool = false; + if scr_raw & (0b1 << 55) != 0 { + data_stat_after_erase = true; + } + dev_log!("Data status after erase: {:?}\n", data_stat_after_erase); + + Ok(Scr { + sd_spec: 0, + data_stat_after_erase, + sd_security: 0, + sd_bus_width, + support_speed_class_control: supported_cmd[0], + support_set_block_count: supported_cmd[1], + support_extersion_register_single_block: supported_cmd[2], + support_extersion_register_multi_block: supported_cmd[3], + support_security_transmission: supported_cmd[4], + }) + } +} diff --git a/drivers/blk/mmc/core/protocol/sdmmc_os.rs b/drivers/blk/mmc/core/protocol/sdmmc_os.rs new file mode 100644 index 000000000..9f27d503c --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc_os.rs @@ -0,0 +1,119 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use crate::sdmmc::MmcSignalVoltage; +use crate::sdmmc::SdmmcError; +use core::sync::atomic::AtomicU8; +use core::sync::atomic::Ordering; + +#[allow(unused_variables)] +pub trait Sleep { + /// For putting the process to sleep for a while, + /// The default spinning implementation is a very unreliable way to put the process to sleep + fn usleep(&mut self, time_us: u32) { + let time_ns: u64 = time_us as u64 * 1000; + for _ in 0..time_ns { + core::hint::spin_loop(); // Use spin loop hint to reduce contention during the wait + } + } +} + +#[allow(unused_variables)] +pub trait VoltageOps { + fn card_voltage_switch(&mut self, voltage: MmcSignalVoltage) -> Result<(), SdmmcError> { + core::panic!("Voltage switch not implemented!"); + } + + fn card_power_cycling(&mut self) -> Result<(), SdmmcError> { + core::panic!("Power cycling not implemented!"); + } +} + +pub fn process_wait_unreliable(time_ns: u64) { + for _ in 0..time_ns { + core::hint::spin_loop(); // Use spin loop hint to reduce contention during the wait + } +} + +#[allow(unused_variables)] +pub trait Log { + fn log(&self, args: core::fmt::Arguments) { + core::panic!("Logging not implemented!"); + } +} + +const UNINITIALIZED: u8 = 0; +const INITIALIZING: u8 = 1; +const INITIALIZED: u8 = 2; +static LOGGER_STATE: AtomicU8 = AtomicU8::new(UNINITIALIZED); +static mut LOGGER: Option<&'static dyn Log> = None; + +/// Why it is unsafe: A trait object of Log should provide a thread safe implementation of Log object +/// The crate treat the log function provided as a THREAD SAFE object! +pub unsafe fn set_logger(logger: &'static dyn Log) -> Result<(), ()> { + // 1. Try to acquire the "write lock". + // Try to change `UNINITIALIZED` to `INITIALIZING`. If it was not `UNINITIALIZED`, another thread is setting it. + if LOGGER_STATE + .compare_exchange( + UNINITIALIZED, + INITIALIZING, + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_ok() + { + // 2. Write to the non-atomic static mut. + // This is the operation we need to protect. + unsafe { + LOGGER = Some(logger); + } + + // Signal that the data is ready. + // We use Release ordering here. This is the crucial part! + LOGGER_STATE.store(INITIALIZED, Ordering::Release); + return Ok(()); + } + + Err(()) +} + +#[doc(hidden)] +pub fn __logging_helper(args: core::fmt::Arguments) { + // 1. Check if the logger is initialized. + // We use Acquire ordering here. This pairs with the `Release` store above. + unsafe { + if LOGGER_STATE.load(Ordering::Acquire) == INITIALIZED { + if let Some(logger) = LOGGER { + logger.log(args); + } + } + } +} + +#[macro_export] +macro_rules! print_info { + ($($arg:tt)*) => { + $crate::sdmmc_os::__logging_helper(format_args!($($arg)*)); + } +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => { + $crate::print_info!("{}\n", format_args!($($arg)*)); + } +} + +#[cfg(not(feature = "dev-logs"))] +#[macro_export] +macro_rules! dev_log { + ($($arg:tt)*) => {}; +} + +#[cfg(feature = "dev-logs")] +#[macro_export] +macro_rules! dev_log { + ($($arg:tt)*) => { + $crate::sdmmc_os::__logging_helper(format_args!($($arg)*)); + } +} diff --git a/drivers/blk/mmc/core/protocol/sdmmc_traits.rs b/drivers/blk/mmc/core/protocol/sdmmc_traits.rs new file mode 100644 index 000000000..b08fe6be6 --- /dev/null +++ b/drivers/blk/mmc/core/protocol/sdmmc_traits.rs @@ -0,0 +1,216 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use crate::{ + dev_log, + sdmmc::{ + HostInfo, MmcData, MmcIos, MmcSignalVoltage, SdmmcCmd, SdmmcError, + mmc_struct::{MmcBusWidth, MmcTiming}, + }, + sdmmc_os::Sleep, +}; + +// const POLLING_INTERVAL_TIME_US: u32 = 1024; +const DATA_TRANSFER_POLLING_INTERVAL_TIME_US: u32 = 4096; + +// The polling chances before time out is deliberately being set to a large value +// as the host is supposed to catch thetimeout request and report to us +const POLLING_CHANCE_BEFORE_TIME_OUT: u32 = 10240; +const DATA_TRANSFER_POLLING_CHANCE_BEFORE_TIME_OUT: u32 = 2048; + +#[allow(unused_variables)] +pub trait SdmmcOps { + fn sdmmc_init(&mut self) -> Result { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + /// Change the clock, return the value or do not change it at all + /// If the freq is set to zero, this function should try to stop the clock completely + /// Beware at higher frequency, you may need to play with delay, adjust and clock phase + /// to ensure that the clock edges (sampling points) occur just in time for the valid data window. + fn sdmmc_config_timing(&mut self, timing: MmcTiming) -> Result { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + fn sdmmc_config_bus_width(&mut self, bus_width: MmcBusWidth) -> Result<(), SdmmcError> { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + /// Reads the current state of the SD card data lanes. + /// + /// This function is specifically used during voltage switching to check if the SD card + /// acknowledges the switch by setting the data signal to low or high. + /// + /// Returns: + /// - `u8`: The current state of the data lanes, where each bit represents a data line: + /// - `DAT0` corresponds to the least significant bit (LSB). + /// - `DAT7` corresponds to the most significant bit (MSB). + /// + /// Note: + /// - This function is not yet implemented and currently returns an `ENOTIMPLEMENTED` error. + fn sdmmc_read_datalanes(&self) -> Result { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + /// Sends a command to the SD/MMC card, ensuring that busy signal handling is managed appropriately. + /// + /// ### Busy Signal Handling + /// The hardware layer is responsible for delaying the actual sending of the command if the card is busy. + /// For example, when the protocol layer sends a command expecting an R1B response (which indicates a busy state), + /// and immediately sends another command afterward, the hardware layer must ensure that the new command is sent + /// only after the busy signal from the card has cleared. + /// + /// ### Hardware Busy Signal Detection + /// Many modern host controllers support automatic busy signal detection, in which case the hardware layer + /// does not need to implement any additional checks or delays—the controller will wait internally until + /// the busy state is cleared before completing the command. + /// + /// ### Manual Busy Waiting + /// If the host controller does not support hardware busy signal detection, the hardware layer must + /// implement this behavior manually by monitoring the card's busy state and delaying the next command + /// until the card is ready. This approach aligns with Linux’s handling of busy signals in its MMC/SD subsystem. + /// + /// ### Parameters + /// * `cmd` - The SD/MMC command to send. + /// * `data` - Optional data associated with the command, if applicable. + /// + /// ### Returns + /// * `Ok(())` on success. + /// * `Err(SdmmcError::ENOTIMPLEMENTED)` if the function is not implemented. + /// + fn sdmmc_send_command( + &mut self, + cmd: &SdmmcCmd, + data: Option<&MmcData>, + ) -> Result<(), SdmmcError> { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + fn sdmmc_receive_response( + &self, + cmd: &SdmmcCmd, + response: &mut [u32; 4], + ) -> Result<(), SdmmcError> { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + // Change the function signature to something like sdmmc_config_interrupt(&mut self, enable_irq: bool, enable_sdio_irq: bool); + fn sdmmc_config_interrupt( + &mut self, + enable_irq: bool, + enable_sdio_irq: bool, + ) -> Result<(), SdmmcError> { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + fn sdmmc_ack_interrupt(&mut self) -> Result<(), SdmmcError> { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + /// At higher clock frequencies, timing mismatches can occur between the host's sampling point and the valid data window + /// from the SD card during read operations. This can lead to CRC errors, as the host may sample incoming data outside the + /// stable data window, even when the SD card’s response appears normal. + /// + /// To address this, the `sdmmc_tune_sampling` function is introduced. This function aims to adjust the host's sampling + /// timing to align with the SD card’s data output window, reducing errors caused by timing misalignment. + /// + /// In some cases, a similar function (e.g., `sdmmc_tune_sending_data_window`) may be needed to tune the timing of data + /// signals sent from the host to the SD card. This would ensure that the SD card reliably receives data, especially + /// at high frequencies. However, output timing tends to be more stable, and a specific function for tuning host-to-card + /// data timing is often not implemented or needed, as seen in the Linux driver. + fn sdmmc_execute_tuning( + &mut self, + memory: *mut [u8; 64], + sleep: &mut dyn Sleep, + ) -> Result<(), SdmmcError> { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + fn sdmmc_host_reset(&mut self) -> Result { + Err(SdmmcError::ENOTIMPLEMENTED) + } + + /// This function implements the bare metal version of adjust signaling voltage + /// If in the SdmmcProtocol::new() function, there are different voltage switch + /// methods provided, this sdmmc_voltage_switch function will not be used + /// Power cycling is assumed in host reset function if voltage_switch is implemented here + fn sdmmc_voltage_switch(&mut self, voltage: MmcSignalVoltage) -> Result<(), SdmmcError> { + // Default behavior should be rushing instead of returning one error + panic!("sdmmc: voltage switch is not implemented!") + } + + fn sdmmc_do_request( + &mut self, + sleep: &mut dyn Sleep, + cmd: &SdmmcCmd, + data: Option<&MmcData>, + resp: &mut [u32; 4], + mut retry: u32, + ) -> Result<(), SdmmcError> { + 'command_retry: loop { + // Send the command using send command function provided by the hardware layer + self.sdmmc_send_command(cmd, data)?; + + let mut res: Result<(), SdmmcError>; + + match data { + // The flow with data transfer + Some(_) => { + for _ in 0..DATA_TRANSFER_POLLING_CHANCE_BEFORE_TIME_OUT { + sleep.usleep(DATA_TRANSFER_POLLING_INTERVAL_TIME_US); + res = self.sdmmc_receive_response(cmd, resp); + match res { + Err(SdmmcError::ETIMEDOUT) => { + if retry == 0 { + return Err(SdmmcError::ETIMEDOUT); + } + retry -= 1; + continue 'command_retry; + } + Err(SdmmcError::EBUSY) => continue, + Err(e) => return Err(e), + Ok(_) => return Ok(()), + } + } + } + // The flow without data transfer + None => { + for _ in 0..POLLING_CHANCE_BEFORE_TIME_OUT { + // There seems to be card that are actually time-sensitive to certain command + // Like if the driver polling the voltage switch command too slow and switch voltage a bit late + // Card will not switch voltage successfully. + // So choosing the fitting polling interval here is very important to correctness + // For correctness reason, right now we don't add any polling interval + // process_wait_unreliable(POLLING_INTERVAL_TIME_US as u64 * 1000); + res = self.sdmmc_receive_response(cmd, resp); + match res { + Err(SdmmcError::ETIMEDOUT) => { + if retry == 0 { + return Err(SdmmcError::ETIMEDOUT); + } + retry -= 1; + continue 'command_retry; + } + Err(SdmmcError::EBUSY) => continue, + Err(e) => return Err(e), + Ok(_) => return Ok(()), + } + } + } + } + break 'command_retry; + } + dev_log!("A timeout request not reported by the host, the host might be unreliable\n"); + Err(SdmmcError::EUNDEFINED) + } +} + +#[allow(unused_variables)] +/// Trait to be implemented by the sdcard hal +pub trait SdmmcHardware: SdmmcOps { + const HOST_INFO: HostInfo; + + /// This function is NOT meant for initialization of the host + /// It should be marked as const once the const traits feature in Rust is stable + unsafe fn new(sdmmc_register_base: u64) -> Self; +} diff --git a/drivers/blk/mmc/core/rust-toolchain.toml b/drivers/blk/mmc/core/rust-toolchain.toml new file mode 100644 index 000000000..65e0b332e --- /dev/null +++ b/drivers/blk/mmc/core/rust-toolchain.toml @@ -0,0 +1,10 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# + +[toolchain] +channel = "nightly-2025-07-07" +components = [ "rust-src", "rustc-dev", "llvm-tools-preview" ] +profile = "default" diff --git a/drivers/blk/mmc/core/src/main.rs b/drivers/blk/mmc/core/src/main.rs new file mode 100644 index 000000000..1c753c737 --- /dev/null +++ b/drivers/blk/mmc/core/src/main.rs @@ -0,0 +1,360 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +#![no_std] // Don't link the standard library +#![no_main] // Don't use the default entry point +#![feature(used_with_arg)] + +extern crate alloc; + +mod sddf_blk; +mod sel4_microkit_os; + +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +use alloc::boxed::Box; +use sddf_blk::{ + BlkOp, BlkRequest, BlkStatus, blk_dequeue_req_helper, blk_device_init_data_ioaddr, + blk_device_init_data_vaddr, blk_device_regs_vaddr, blk_enqueue_resp_helper, + blk_queue_empty_req_helper, blk_queue_full_resp_helper, blk_queue_init_helper, +}; + +use crate::sel4_microkit_os::{SerialOps, TimerOps, platform::VOLTAGE}; + +const INTERRUPT: sel4_microkit::Channel = sel4_microkit::Channel::new(0); +const BLK_VIRTUALIZER: sel4_microkit::Channel = sel4_microkit::Channel::new(1); +const TIMER: TimerOps = TimerOps::new(); +const SERIAL: SerialOps = SerialOps::new(); +const HOST_INFO: HostInfo = crate::sel4_microkit_os::platform::host_info(); + +use sdmmc_protocol::{ + sdmmc::{mmc_struct::CardInfo, HostInfo, SDCARD_DEFAULT_SECTOR_SIZE}, + sdmmc_traits::SdmmcHardware, +}; +use sdmmc_protocol::{ + sdmmc::{SdmmcError, SdmmcProtocol}, + sdmmc_os::{Sleep, VoltageOps}, +}; +use sel4_microkit::{ChannelSet, Handler, Infallible, debug_println, protection_domain}; + +const SDDF_TRANSFER_SIZE: u32 = 4096; +const SDDF_TO_REAL_SECTOR: u32 = SDDF_TRANSFER_SIZE / SDCARD_DEFAULT_SECTOR_SIZE; + +// No-op waker implementations, they do nothing. +unsafe fn noop(_data: *const ()) {} +unsafe fn noop_clone(_data: *const ()) -> RawWaker { + RawWaker::new(_data, &VTABLE) +} + +/// Since in .system file, the page we are providing to tune_performance function is uncached +/// we do not need to provide a real cache invalidate function +fn dummy_cache_invalidate_function() {} + +// A VTable that points to the no-op functions +static VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop); + +// Function to create a dummy Waker +fn create_dummy_waker() -> Waker { + let raw_waker = RawWaker::new(core::ptr::null(), &VTABLE); + unsafe { Waker::from_raw(raw_waker) } +} + +#[protection_domain(heap_size = 0x10000)] +fn init() -> impl Handler { + unsafe { + sdmmc_protocol::sdmmc_os::set_logger(&SERIAL).unwrap(); + } + + let regs_base = unsafe { blk_device_regs_vaddr() }; + + let hal = unsafe { crate::sel4_microkit_os::platform::platform_hal(regs_base) }; + + let unsafe_stolen_memory: &mut [u8; 64]; + + // This line of code actually is very unsafe! + // Considering the memory is stolen from the memory that has sdcard registers mapped in + // A data region for card init is needed because some information is passed to the driver + // by the card transferring data to normal memory instead of reading a register. + let init_data_vaddr = unsafe { blk_device_init_data_vaddr() }; + let init_data_ioaddr = unsafe { blk_device_init_data_ioaddr() }; + + unsafe { + let stolen_memory_addr = init_data_vaddr as *mut [u8; 64]; + assert!(stolen_memory_addr as usize % 8 == 0); + unsafe_stolen_memory = &mut (*stolen_memory_addr); + } + + // Handling result in two different ways, by matching and unwrap_or_else + let res = SdmmcProtocol::new(hal, TIMER, Some(VOLTAGE)); + let mut sdmmc_host = match res { + Ok(host) => host, + Err(err) => panic!("SDMMC: Error at init {:?}", err), + }; + + sdmmc_host + .setup_card() + .unwrap_or_else(|error| panic!("SDMMC: Error at setup {:?}", error)); + + sdmmc_host.print_card_info(); + + let card_info: CardInfo = sdmmc_host.card_info().unwrap(); + + let _ = sdmmc_host.config_interrupt(false, false); + + // Should tuning be possible to fail? + unsafe { + sdmmc_host + .tune_performance( + unsafe_stolen_memory, + dummy_cache_invalidate_function, + init_data_ioaddr, + ) + .unwrap_or_else(|error| panic!("SDMMC: Error at tuning performance {:?}", error)); + } + + // Should always succeed, at least for odroid C4 + let _ = sdmmc_host.config_interrupt(true, false); + + unsafe { + blk_queue_init_helper(card_info.card_capacity); + } + + HandlerImpl { + future: None, + sdmmc: Some(sdmmc_host), + request: None, + card_info, + } +} + +struct HandlerImpl { + future: Option, SdmmcProtocol)>>>>, + sdmmc: Option>, + request: Option, + card_info: CardInfo, +} + +impl Handler for HandlerImpl +where + T: SdmmcHardware + 'static, + S: Sleep + 'static, + V: VoltageOps + 'static, +{ + type Error = Infallible; + + fn notified(&mut self, channel_set: ChannelSet) -> Result<(), Self::Error> { + for channel in channel_set.iter() { + if channel.index() != INTERRUPT.index() && channel.index() != BLK_VIRTUALIZER.index() { + debug_println!( + "SDMMC_DRIVER: Unknown channel sent me message: {}", + channel.index() + ); + return Ok(()); + } + + let mut notify_virt: bool = false; + + 'process_notification: { + // Polling if receive interrupt notification + if channel.index() == INTERRUPT.index() { + if let Some(request) = &mut self.request { + if let Some(future) = &mut self.future { + let waker = create_dummy_waker(); + let mut cx = Context::from_waker(&waker); + match future.as_mut().poll(&mut cx) { + Poll::Ready((result, sdmmc)) => { + self.future = None; // Reset the future once done + self.sdmmc = Some(sdmmc); + if result.is_ok() { + // Deduct finished count from count + request.success_count += request.count_to_do; + request.count -= request.count_to_do; + + if request.count == 0 { + let resp_status = BlkStatus::BlkRespOk; + notify_virt = true; + unsafe { + // The using try_into() to convert u32 to u16 should not be necessary unless + // there are bugs in the code cause the operation to fail + blk_enqueue_resp_helper( + resp_status, + (request.success_count / SDDF_TO_REAL_SECTOR) + .try_into() + .unwrap(), + request.id, + ); + } + self.request = None; + } + } else { + let resp_status = BlkStatus::BlkRespSeekError; + notify_virt = true; + unsafe { + blk_enqueue_resp_helper( + resp_status, + (request.success_count / SDDF_TO_REAL_SECTOR) + .try_into() + .unwrap(), + request.id, + ); + } + self.request = None; + } + } + Poll::Pending => { + // Since the future is not ready, no other request can be dequeued, exit the big loop + break 'process_notification; + } + } + } else { + panic!( + "SDMMC: Receive a hardware interrupt despite not having a future!" + ); + } + } + } + + while self.request.is_none() + && unsafe { + blk_queue_empty_req_helper() == 0 && blk_queue_full_resp_helper() == 0 + } + { + let mut request: BlkRequest = BlkRequest { + request_code: BlkOp::BlkReqFlush, + io_or_offset: 0, + block_number: 0, + count: 0, + success_count: 0, + count_to_do: 0, + id: 0, + }; + let mut sddf_count: u16 = 0; + unsafe { + blk_dequeue_req_helper( + &mut request.request_code as *mut BlkOp, + &mut request.io_or_offset as *mut u64, + &mut request.block_number as *mut u64, + &mut sddf_count as *mut u16, + &mut request.id as *mut u32, + ); + } + // TODO: Consider to add integer overflow check here + request.block_number = request.block_number * SDDF_TO_REAL_SECTOR as u64; + request.count = (sddf_count as u32) * SDDF_TO_REAL_SECTOR; + + // Print the retrieved values + #[cfg(feature = "dev-logs")] + { + debug_println!("io_or_offset: 0x{:x}", request.io_or_offset); // Simple u64 + debug_println!("block_number: {}", request.block_number); // Simple u32 + debug_println!("count: {}", request.count); // Simple u16 + debug_println!("id: {}", request.id); // Simple u32 + } + + // Check if the request is valid + if (request.count as u64 + request.block_number as u64) + * SDCARD_DEFAULT_SECTOR_SIZE as u64 + > self.card_info.card_capacity + { + unsafe { + blk_enqueue_resp_helper(BlkStatus::BlkRespSeekError, 0, request.id); + } + } + + match request.request_code { + BlkOp::BlkReqRead => { + self.request = Some(request); + break; + } + BlkOp::BlkReqWrite => { + self.request = Some(request); + break; + } + _ => { + // For other request, enqueue response + notify_virt = true; + unsafe { + blk_enqueue_resp_helper(BlkStatus::BlkRespOk, 0, request.id); + } + } + } + } + + // If future is empty + if let Some(request) = &mut self.request { + if let None = self.future { + match request.request_code { + BlkOp::BlkReqRead => { + request.count_to_do = + core::cmp::min(request.count, HOST_INFO.max_block_per_req); + if let Some(sdmmc) = self.sdmmc.take() { + self.future = Some(Box::pin(sdmmc.read_block( + request.count_to_do, + request.block_number as u64 + request.success_count as u64, + request.io_or_offset + + request.success_count as u64 + * SDCARD_DEFAULT_SECTOR_SIZE as u64, + ))); + } else { + panic!( + "SDMMC_DRIVER: The sdmmc should be here since the future should be empty!!!" + ) + } + } + BlkOp::BlkReqWrite => { + request.count_to_do = + core::cmp::min(request.count, HOST_INFO.max_block_per_req); + if let Some(sdmmc) = self.sdmmc.take() { + self.future = Some(Box::pin(sdmmc.write_block( + request.count_to_do, + request.block_number as u64 + request.success_count as u64, + request.io_or_offset + + request.success_count as u64 + * SDCARD_DEFAULT_SECTOR_SIZE as u64, + ))); + } else { + panic!( + "SDMMC_DRIVER: The sdmmc should be here and the future should be empty!!!" + ) + } + } + _ => { + panic!("SDMMC_DRIVER: You should not reach here!") + } + } + let waker = create_dummy_waker(); + let mut cx = Context::from_waker(&waker); + // Poll the future once to make it start working! + if let Some(ref mut future) = self.future { + match future.as_mut().poll(&mut cx) { + Poll::Ready(_) => { + panic!( + "SDMMC: RECEIVED INVALID REQUEST! Check request validation code!" + ); + } + Poll::Pending => break 'process_notification, + } + } + } + } + } + + if notify_virt == true { + // debug_println!("SDMMC_DRIVER: Notify the BLK_VIRTUALIZER!"); + BLK_VIRTUALIZER.notify(); + } + // Ack irq + if channel.index() == INTERRUPT.index() { + let err = channel.irq_ack(); + if err.is_err() { + panic!("SDMMC: Cannot acknowledge interrupt for CPU!") + } + } + } + Ok(()) + } +} diff --git a/drivers/blk/mmc/core/src/sddf_blk/mod.rs b/drivers/blk/mmc/core/src/sddf_blk/mod.rs new file mode 100644 index 000000000..6a2ce2647 --- /dev/null +++ b/drivers/blk/mmc/core/src/sddf_blk/mod.rs @@ -0,0 +1,47 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +#![allow(dead_code)] + +unsafe extern "C" { + pub unsafe fn blk_queue_init_helper(capacity: u64); + + pub unsafe fn blk_device_regs_vaddr() -> u64; + pub unsafe fn blk_device_init_data_vaddr() -> u64; + pub unsafe fn blk_device_init_data_ioaddr() -> u64; + + pub unsafe fn blk_queue_empty_req_helper() -> u8; + pub unsafe fn blk_queue_full_resp_helper() -> u8; + pub unsafe fn blk_enqueue_resp_helper(status: BlkStatus, success: u16, id: u32) -> u8; + pub unsafe fn blk_dequeue_req_helper( + code: *mut BlkOp, + io_or_offset: *mut u64, + block_number: *mut u64, + count: *mut u16, + id: *mut u32, + ); +} + +#[repr(C)] +pub enum BlkOp { + BlkReqRead, + BlkReqWrite, + BlkReqFlush, + BlkReqBarrier, +} + +#[repr(C)] +pub enum BlkStatus { + BlkRespOk, + BlkRespSeekError, +} + +pub struct BlkRequest { + pub request_code: BlkOp, + pub io_or_offset: u64, + pub block_number: u64, + pub count: u32, + pub success_count: u32, + pub count_to_do: u32, + pub id: u32, +} diff --git a/drivers/blk/mmc/core/src/sddf_helper.c b/drivers/blk/mmc/core/src/sddf_helper.c new file mode 100644 index 000000000..d1be4a149 --- /dev/null +++ b/drivers/blk/mmc/core/src/sddf_helper.c @@ -0,0 +1,72 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +__attribute__((__section__(".device_resources"))) device_resources_t device_resources; +__attribute__((__section__(".blk_driver_config"))) blk_driver_config_t config; + +blk_queue_handle_t blk_queue; + +void blk_queue_init_helper(uint64_t capacity) +{ + blk_queue_init(&blk_queue, config.virt.req_queue.vaddr, config.virt.resp_queue.vaddr, config.virt.num_buffers); + + blk_storage_info_t *storage_info = config.virt.storage_info.vaddr; + storage_info->sector_size = 512; + storage_info->block_size = 1; + storage_info->capacity = capacity; + blk_storage_set_ready(storage_info, true); + storage_info->ready = true; +} + +uint64_t blk_device_regs_vaddr() +{ + return (uint64_t)device_resources.regions[0].region.vaddr; +} + +uint64_t blk_device_init_data_vaddr() +{ + return (uint64_t)device_resources.regions[1].region.vaddr; +} + +uint64_t blk_device_init_data_ioaddr() +{ + return (uint64_t)device_resources.regions[1].io_addr; +} + +uint8_t blk_queue_empty_req_helper() +{ + return blk_queue_empty_req(&blk_queue); +} + +uint8_t blk_queue_full_resp_helper() +{ + return blk_queue_full_resp(&blk_queue); +} + +uint8_t blk_enqueue_resp_helper(uint8_t status, uint16_t success, uint32_t id) +{ + // It would be better if we do not use int but use int8_t + if (blk_enqueue_resp(&blk_queue, status, success, id) == 0) { + return 0; + } + return 1; +} + +uint8_t blk_dequeue_req_helper(uint8_t *code, uintptr_t *io_or_offset, uint64_t *block_number, uint16_t *count, + uint32_t *id) +{ + // It would be better if we do not use int but use int8_t + // uint16_t temp_count = 0; + if (blk_dequeue_req(&blk_queue, (blk_req_code_t *)code, io_or_offset, block_number, count, id) == 0) { + return 0; + } + // *count = temp_count; + return 1; +} diff --git a/drivers/blk/mmc/core/src/sel4_microkit_os/mod.rs b/drivers/blk/mmc/core/src/sel4_microkit_os/mod.rs new file mode 100644 index 000000000..9ea53b558 --- /dev/null +++ b/drivers/blk/mmc/core/src/sel4_microkit_os/mod.rs @@ -0,0 +1,44 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use sdmmc_protocol::sdmmc_os::{Log, Sleep}; +use sel4_panicking_env::__debug_print_macro_helper; + +#[cfg(feature = "meson")] +mod odroidc4; + +#[cfg(feature = "meson")] +pub(crate) mod platform { + pub(crate) use crate::sel4_microkit_os::odroidc4::{VOLTAGE, host_info, platform_hal}; +} + +const NS_IN_US: u64 = 1000; + +/// Wrapper to work around Rust's orphan rule +pub struct TimerOps {} + +impl TimerOps { + pub const fn new() -> Self { + TimerOps {} + } +} + +impl Sleep for TimerOps { + fn usleep(&mut self, time_us: u32) { + sdmmc_protocol::sdmmc_os::process_wait_unreliable(time_us as u64 * NS_IN_US); + } +} + +pub struct SerialOps {} + +impl SerialOps { + pub const fn new() -> Self { + SerialOps {} + } +} + +impl Log for SerialOps { + fn log(&self, args: core::fmt::Arguments) { + __debug_print_macro_helper(args); + } +} diff --git a/drivers/blk/mmc/core/src/sel4_microkit_os/odroidc4.rs b/drivers/blk/mmc/core/src/sel4_microkit_os/odroidc4.rs new file mode 100644 index 000000000..c9547cba6 --- /dev/null +++ b/drivers/blk/mmc/core/src/sel4_microkit_os/odroidc4.rs @@ -0,0 +1,144 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +use core::ptr; + +use sdmmc_protocol::{ + sdmmc::{HostInfo, MmcSignalVoltage, SdmmcError}, + sdmmc_os::{Sleep, VoltageOps}, + sdmmc_traits::SdmmcHardware, +}; + +use meson_hal::meson_gx_mmc::SdmmcMesonHardware; + +use crate::sel4_microkit_os::TimerOps; + +pub struct Odroidc4VoltageSwitch; +pub(crate) const VOLTAGE: Odroidc4VoltageSwitch = Odroidc4VoltageSwitch::new(); + +/// Check the AO_GPIO_O_EN_N register in S905X3 datasheet +const AO_RTI_OUTPUT_ENABLE_REG: u64 = 0xff800024; +const AO_RTI_OUTPUT_LEVEL_REG: u64 = 0xff800034; +const AO_RTI_PULL_UP_EN_REG: u64 = 0xff800030; + +/// Check GPIOAO_x Multi-Function Pin register in in S905X3 datasheet +/// We probably need to ensure the pinmux is set up correctly to use the correct pin func +/// Which is missing now!!! This is especially important for GPIOAO_6 as the default function +/// does not seems to be the one we want to use, for GPIO_AO_3 it should be fine as the default +/// one is the one we want to use +const GPIO_AO_3: u32 = 1 << 3; +const GPIO_AO_6: u32 = 1 << 6; + +impl Odroidc4VoltageSwitch { + pub const fn new() -> Self { + Odroidc4VoltageSwitch {} + } +} + +impl VoltageOps for Odroidc4VoltageSwitch { + fn card_voltage_switch(&mut self, voltage: MmcSignalVoltage) -> Result<(), SdmmcError> { + match voltage { + MmcSignalVoltage::Voltage330 => { + let mut value: u32; + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_ENABLE_REG as *const u32); + } + value &= !GPIO_AO_6; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_ENABLE_REG as *mut u32, value); + } + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_LEVEL_REG as *const u32); + } + value &= !GPIO_AO_6; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_LEVEL_REG as *mut u32, value); + } + } + MmcSignalVoltage::Voltage180 => { + let mut value: u32; + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_ENABLE_REG as *const u32); + } + value &= !GPIO_AO_6; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_ENABLE_REG as *mut u32, value); + } + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_LEVEL_REG as *const u32); + } + value |= GPIO_AO_6; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_LEVEL_REG as *mut u32, value); + } + } + MmcSignalVoltage::Voltage120 => return Err(SdmmcError::EINVAL), + } + // Disable pull-up/down for gpioAO_6 + let mut value: u32; + + unsafe { + value = ptr::read_volatile(AO_RTI_PULL_UP_EN_REG as *const u32); + } + value &= !GPIO_AO_6; + + unsafe { + ptr::write_volatile(AO_RTI_PULL_UP_EN_REG as *mut u32, value); + } + + Ok(()) + } + + fn card_power_cycling(&mut self) -> Result<(), SdmmcError> { + let mut value: u32; + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_ENABLE_REG as *const u32); + } + // If the GPIO pin is not being set as output, set it to output first + if value & GPIO_AO_3 != 0 { + value &= !GPIO_AO_3; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_ENABLE_REG as *mut u32, value); + } + } + + // Turning the power off + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_LEVEL_REG as *const u32); + } + if value & GPIO_AO_3 != 0 { + value &= !GPIO_AO_3; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_LEVEL_REG as *mut u32, value); + } + } + + // Sleep for 5ms + TimerOps::new().usleep(5000); + + // Turning the power on + unsafe { + value = ptr::read_volatile(AO_RTI_OUTPUT_LEVEL_REG as *const u32); + } + if value & GPIO_AO_3 == 0 { + value |= GPIO_AO_3; + unsafe { + ptr::write_volatile(AO_RTI_OUTPUT_LEVEL_REG as *mut u32, value); + } + } + self.card_voltage_switch(MmcSignalVoltage::Voltage330)?; + + // Sleep for another 5ms + TimerOps::new().usleep(5000); + + Ok(()) + } +} + +pub unsafe fn platform_hal(regs_base: u64) -> SdmmcMesonHardware { + unsafe { SdmmcMesonHardware::new(regs_base) } +} + +pub const fn host_info() -> HostInfo { + SdmmcMesonHardware::HOST_INFO +} diff --git a/drivers/blk/mmc/core/support/targets/aarch64-sel4-microkit-minimal.json b/drivers/blk/mmc/core/support/targets/aarch64-sel4-microkit-minimal.json new file mode 100644 index 000000000..d8cea28f7 --- /dev/null +++ b/drivers/blk/mmc/core/support/targets/aarch64-sel4-microkit-minimal.json @@ -0,0 +1,35 @@ +{ + "arch": "aarch64", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", + "disable-redzone": true, + "exe-suffix": ".elf", + "features": "+v8a,+strict-align,+neon,+fp-armv8", + "link-script": "__sel4_ipc_buffer_obj = (__ehdr_start & ~(4096 - 1)) - 4096;", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-target": "aarch64-unknown-none", + "max-atomic-width": 128, + "metadata": { + "description": null, + "host_tools": null, + "std": null, + "tier": null + }, + "panic-strategy": "abort", + "pre-link-args": { + "gnu-lld": [ + "-z", + "max-page-size=4096" + ] + }, + "relocation-model": "static", + "stack-probes": { + "kind": "inline" + }, + "supported-sanitizers": [ + "kcfi", + "kernel-address" + ], + "target-pointer-width": "64" +} \ No newline at end of file diff --git a/drivers/blk/mmc/meson/Cargo.toml b/drivers/blk/mmc/meson/Cargo.toml new file mode 100644 index 000000000..b24715e63 --- /dev/null +++ b/drivers/blk/mmc/meson/Cargo.toml @@ -0,0 +1,23 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +[package] +name = "meson_hal" +version = "0.1.0" +edition = "2024" +authors = ["Cheng Li 李澄 "] + +[lib] +name = "meson_hal" +path = "lib.rs" + +# The Hardware Abstraction Layer (HAL) is designed to be: +# - Bare-metal compatible +# - Operating system (OS) agnostic +# - Minimally dependent to ensure cross-platform flexibility +# +# Required Dependencies: +# - sdmmc_protocol: Essential for core functionality. + +[dependencies] +sdmmc_protocol = { path = "../core/protocol" } diff --git a/drivers/blk/mmc/meson/lib.rs b/drivers/blk/mmc/meson/lib.rs new file mode 100644 index 000000000..37c879d3e --- /dev/null +++ b/drivers/blk/mmc/meson/lib.rs @@ -0,0 +1,6 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +#![no_std] // Don't link the standard library + +pub mod meson_gx_mmc; diff --git a/drivers/blk/mmc/meson/meson_gx_mmc.rs b/drivers/blk/mmc/meson/meson_gx_mmc.rs new file mode 100644 index 000000000..2e4688b1b --- /dev/null +++ b/drivers/blk/mmc/meson/meson_gx_mmc.rs @@ -0,0 +1,800 @@ +// Copyright 2025, UNSW +// SPDX-License-Identifier: BSD-2-Clause + +#![allow(dead_code)] + +use core::ptr; + +use sdmmc_protocol::{ + dev_log, info, + sdmmc::{ + HostInfo, MmcData, MmcDataFlag, MmcIos, MmcSignalVoltage, SdmmcCmd, SdmmcError, + capability::{ + MMC_CAP_4_BIT_DATA, MMC_TIMING_LEGACY, MMC_TIMING_SD_HS, MMC_TIMING_UHS, MMC_VDD_31_32, + MMC_VDD_32_33, MMC_VDD_33_34, + }, + mmc_struct::{MmcBusWidth, MmcTiming}, + sd::Sdcard, + }, + sdmmc_os::{Sleep, process_wait_unreliable}, + sdmmc_traits::{SdmmcHardware, SdmmcOps}, +}; + +// The driver is targeting the sdmmc host controller at this address: SDIO 0xffe05000 + +macro_rules! div_round_up { + ($n:expr, $d:expr) => { + (($n + $d - 1) / $d) + }; +} + +const SD_EMMC_ADJ: u32 = 16; +const SD_EMMC_ADJUST_ADJ_DELAY_MASK: u32 = 0x3F << SD_EMMC_ADJ; +const SD_EMMC_ADJ_ENABLE: u32 = 0x2000; + +// Constants translated from the C version +// Clock related constant +const SD_EMMC_CLKSRC_24M: u32 = 24000000; // 24 MHz +const SD_EMMC_CLKSRC_DIV2: u32 = 1000000000; // 1 GHz +const MESON_MIN_FREQUENCY: u32 = div_round_up!(SD_EMMC_CLKSRC_24M, CLK_MAX_DIV); +const MESON_MAX_FREQUENCY: u32 = 200000000; // 200 Mhz +const CLK_MAX_DIV: u32 = 63; +const CLK_SRC_MASK: u32 = 0b11000000; +const CLK_SRC_24M: u32 = 0 << 6; +const CLK_SRC_DIV2: u32 = 1 << 6; +const CLK_CO_PHASE_000: u32 = 0 << 8; +const CLK_CO_PHASE_090: u32 = 1 << 8; +const CLK_CO_PHASE_180: u32 = 2 << 8; +const CLK_CO_PHASE_270: u32 = 3 << 8; +const CLK_TX_PHASE_000: u32 = 0 << 10; +const CLK_TX_PHASE_180: u32 = 2 << 10; +const CLK_ALWAYS_ON: u32 = 1 << 24; + +pub const CFG_BUS_WIDTH_MASK: u32 = 0b11; +pub const CFG_BUS_WIDTH_1: u32 = 0; +pub const CFG_BUS_WIDTH_4: u32 = 1; +pub const CFG_BUS_WIDTH_8: u32 = 2; +pub const CFG_DDR_MODE: u32 = 1 << 2; +pub const CFG_BL_LEN_MASK: u32 = 0b1111 << 4; +pub const CFG_BL_LEN_SHIFT: u32 = 4; +pub const CFG_BL_LEN_512: u32 = 0b1001 << 4; +pub const CFG_RESP_TIMEOUT_MASK: u32 = 0b1111 << 8; +pub const CFG_RESP_TIMEOUT_256: u32 = 0b1000 << 8; +pub const CFG_RC_CC_MASK: u32 = 0b1111 << 12; +pub const CFG_RC_CC_16: u32 = 0b0100 << 12; +pub const CFG_RC_CC_256: u32 = 0b1000 << 12; +pub const CFG_SDCLK_ALWAYS_ON: u32 = 1 << 18; +pub const CFG_STOP_CLOCK: u32 = 1 << 22; +pub const CFG_AUTO_CLK: u32 = 1 << 23; +pub const CFG_ERR_ABORT: u32 = 1 << 27; + +// CMD_CFG constants +const CMD_CFG_CMD_INDEX_SHIFT: u32 = 24; +const CMD_CFG_RESP_128: u32 = 1 << 21; +const CMD_CFG_R1B: u32 = 1 << 10; +const CMD_CFG_RESP_NOCRC: u32 = 1 << 20; +const CMD_CFG_NO_RESP: u32 = 1 << 16; +const CMD_CFG_DATA_WR: u32 = 1 << 19; +const CMD_CFG_DATA_IO: u32 = 1 << 18; +const CMD_CFG_BLOCK_MODE: u32 = 1 << 9; +const CMD_CFG_TIMEOUT_4S: u32 = 12 << 12; +const CMD_CFG_OWNER: u32 = 1 << 31; +const CMD_CFG_END_OF_CHAIN: u32 = 1 << 11; + +// STATUS register masks and flags +const STATUS_MASK: u32 = 0xFFFF; // GENMASK(15, 0) +const STATUS_ERR_MASK: u32 = 0x1FFF; // GENMASK(12, 0) +const STATUS_RXD_ERR_MASK: u32 = 0xFF; // GENMASK(7, 0) +const STATUS_TXD_ERR: u32 = 1 << 8; // BIT(8) +const STATUS_EIO_ERR: u32 = STATUS_RXD_ERR_MASK | STATUS_TXD_ERR; // GENMASK(8, 0) +const STATUS_DESC_ERR: u32 = 1 << 9; // BIT(9) +const STATUS_RESP_ERR: u32 = 1 << 10; // BIT(10) +const STATUS_RESP_TIMEOUT: u32 = 1 << 11; // BIT(11) +const STATUS_DESC_TIMEOUT: u32 = 1 << 12; // BIT(12) +const STATUS_END_OF_CHAIN: u32 = 1 << 13; // BIT(13) +const STATUS_BUSY: u32 = 1 << 31; +const STATUS_DESC_BUSY: u32 = 1 << 30; +const STATUS_DAT_MASK: u32 = 0xFF << 16; // Equivalent to GENMASK(23, 16) +const STATUS_DAT_SHIFT: u32 = 16; + +// IRQ enable register masks and flags +const IRQ_RXD_ERR_MASK: u32 = 0xFF; // Equivalent to GENMASK(7, 0) +const IRQ_TXD_ERR: u32 = 1 << 8; +const IRQ_DESC_ERR: u32 = 1 << 9; +const IRQ_RESP_ERR: u32 = 1 << 10; +const IRQ_CRC_ERR: u32 = IRQ_RXD_ERR_MASK | IRQ_TXD_ERR | IRQ_DESC_ERR | IRQ_RESP_ERR; +const IRQ_RESP_TIMEOUT: u32 = 1 << 11; +const IRQ_DESC_TIMEOUT: u32 = 1 << 12; + +const IRQ_TIMEOUTS: u32 = IRQ_RESP_TIMEOUT | IRQ_DESC_TIMEOUT; +const IRQ_END_OF_CHAIN: u32 = 1 << 13; +const IRQ_RESP_STATUS: u32 = 1 << 14; +const IRQ_SDIO: u32 = 1 << 15; +const IRQ_ERR_MASK: u32 = IRQ_CRC_ERR | IRQ_TIMEOUTS; + +const IRQ_EN_MASK: u32 = IRQ_ERR_MASK | IRQ_END_OF_CHAIN; + +pub const MAX_BLOCK_PER_TRANSFER: u32 = 0x1FF; + +const WRITE_ADDR_UPPER: u32 = 0xFFFE0000; + +// const VALID_DMA_ADDR_LOWER: u32 = 0x2000000; +// const VALID_DMA_ADDR_UPPER: u32 = 0x10000000; +// const VALID_DMA_ADDR_MASK: u32 = 0x80000003; + +// Structure representing the SDIO controller's registers +/* + * Those register mapping are taken from meson-gx-mmc.c in Linux source code, + * meson_gx_mmc.h in uboot source code and S905X3 datasheet. + * Despite Odroid C4 belong to Meson GX Family, the sdmmc register mapping + * seems to be the same with the register mapping for meson_axg according to documentation + * and the register mapping defined in Linux kernel. + * + * #define MESON_SD_EMMC_CLOCK 0x00 + * #define SD_EMMC_START 0x40 + * #define MESON_SD_EMMC_CFG 0x44 + * #define MESON_SD_EMMC_STATUS 0x48 + * #define MESON_SD_EMMC_IRQ_EN 0x4c + * #define MESON_SD_EMMC_CMD_CFG 0x50 + * #define MESON_SD_EMMC_CMD_ARG 0x54 + * #define MESON_SD_EMMC_CMD_DAT 0x58 + * #define MESON_SD_EMMC_CMD_RSP 0x5c + * #define MESON_SD_EMMC_CMD_RSP1 0x60 + * #define MESON_SD_EMMC_CMD_RSP2 0x64 + * #define MESON_SD_EMMC_CMD_RSP3 0x68 + * #define SD_EMMC_RXD 0x94 + * #define SD_EMMC_TXD 0x94 + * #define SD_EMMC_LAST_REG SD_EMMC_TXD + */ +#[repr(C)] +struct MesonSdmmcRegisters { + clock: u32, // 0x00: Clock control register + delay1: u32, // 0x04: Delay register one + delay2: u32, // 0x08: Delay register two + adjust: u32, // 0x0C: Adjust register + _reserved0: [u32; 12], // Padding for other unused registers (0x04 - 0x3C) + start: u32, // 0x40: Start register + cfg: u32, // 0x44: Configuration register + status: u32, // 0x48: Status register + irq_en: u32, // 0x4C: Interrupt enable register + cmd_cfg: u32, // 0x50: Command configuration register + cmd_arg: u32, // 0x54: Command argument register + cmd_dat: u32, // 0x58: Command data register (for DMA address) + cmd_rsp: u32, // 0x5C: Command response register + cmd_rsp1: u32, // 0x60: Command response register 1 + cmd_rsp2: u32, // 0x64: Command response register 2 + cmd_rsp3: u32, // 0x68: Command response register 3 + _reserved1: [u32; 9], // Padding for other unused registers (0x6C - 0x90) + rxd: u32, // 0x94: Receive data register (not used) + txd: u32, // 0x94: Transmit data register (not used, same as RXD) + // Add other registers as needed +} + +impl MesonSdmmcRegisters { + /// This function is only safe to use if the sdmmc_register_base is the correct memory addr + /// of the sdmmc register base and accessible for the driver + const unsafe fn new(sdmmc_register_base: u64) -> &'static mut MesonSdmmcRegisters { + unsafe { &mut *(sdmmc_register_base as *mut MesonSdmmcRegisters) } + } +} + +struct DelayConfig { + timing: MmcTiming, // Clock rate in Hz + current_delay: u32, // Delay value in some unit, e.g., nanoseconds +} + +pub struct SdmmcMesonHardware { + register: &'static mut MesonSdmmcRegisters, + delay: Option, + // Current timing + timing: MmcTiming, + // Current frequency + frequency: u32, + // Irq enabled + enabled_irq: u32, +} + +impl SdmmcMesonHardware { + /// The meson_reset function reset the host register state + /// However, this function does not try to reset the power state like operating voltage and signal voltage + fn meson_reset(&mut self) { + // Stop execution + unsafe { + ptr::write_volatile(&mut self.register.start, 0); + } + + // Disable interrupt + unsafe { + ptr::write_volatile(&mut self.register.irq_en, 0); + } + + // Acknowledge interrupt + unsafe { + ptr::write_volatile(&mut self.register.status, IRQ_EN_MASK | IRQ_SDIO); + } + + // Reset delay and adjust registers + unsafe { + ptr::write_volatile(&mut self.register.delay1, 0); + } + unsafe { + ptr::write_volatile(&mut self.register.delay2, 0); + } + unsafe { + ptr::write_volatile(&mut self.register.adjust, 0); + } + + // Set clock to a low freq + let _ = self.sdmmc_config_timing(MmcTiming::CardSetup); + + // Reset config register + let mut cfg: u32 = 0; + + // Set timeout bits + cfg |= CFG_RESP_TIMEOUT_256 & CFG_RESP_TIMEOUT_MASK; + + // Set cmd interval + // TODO: Both Linux and uboot use 16 cycle interval but is it the right value? + cfg |= CFG_RC_CC_16 & CFG_RC_CC_MASK; + + // Set block len to 512 + cfg |= CFG_BL_LEN_512 & CFG_BL_LEN_MASK; + + // Set task abort bit when error is encountered + cfg |= CFG_ERR_ABORT; + + unsafe { + ptr::write_volatile(&mut self.register.cfg, cfg); + } + + self.delay = None; + } + + // This function can be seen as a Rust version of meson_mmc_setup_cmd function in uboot + fn meson_mmc_set_up_cmd_cfg_and_cfg(&mut self, cmd: &SdmmcCmd, data: Option<&MmcData>) { + let mut meson_mmc_cmd: u32 = 0u32; + + meson_mmc_cmd |= (cmd.cmdidx & 0x3F) << CMD_CFG_CMD_INDEX_SHIFT; + + if cmd.resp_type & sdmmc_protocol::sdmmc::MMC_RSP_PRESENT != 0 { + if cmd.resp_type & sdmmc_protocol::sdmmc::MMC_RSP_136 != 0 { + meson_mmc_cmd |= CMD_CFG_RESP_128; + } + + // If the hardware does not have busy detection, polling for card datalines to be high is needed + // as the card will signal "busy" (by pulling the DAT line low) + // Odroid C4 have feature to wait until hardware exit busy state so we do not need to worry about it + if cmd.resp_type & sdmmc_protocol::sdmmc::MMC_RSP_BUSY != 0 { + meson_mmc_cmd |= CMD_CFG_R1B; + } + + if cmd.resp_type & sdmmc_protocol::sdmmc::MMC_RSP_CRC == 0 { + meson_mmc_cmd |= CMD_CFG_RESP_NOCRC; + } + } else { + meson_mmc_cmd |= CMD_CFG_NO_RESP; + } + + if let Some(data) = data { + let mut cfg: u32 = unsafe { ptr::read_volatile(&self.register.cfg) }; + + cfg &= !CFG_BL_LEN_MASK; + + cfg |= data.blocksize.ilog2() << CFG_BL_LEN_SHIFT; + + // TODO: Maybe add blocksize is power of 2 check here? + // dev_log!("Configure register value: 0x{:08x}", cfg); + + unsafe { + ptr::write_volatile(&mut self.register.cfg, cfg); + }; + + if let MmcDataFlag::SdmmcDataWrite = data.flags { + meson_mmc_cmd |= CMD_CFG_DATA_WR; + } + + meson_mmc_cmd |= CMD_CFG_DATA_IO | CMD_CFG_BLOCK_MODE | data.blockcnt; + } + + meson_mmc_cmd |= CMD_CFG_TIMEOUT_4S | CMD_CFG_OWNER | CMD_CFG_END_OF_CHAIN; + + unsafe { + ptr::write_volatile(&mut self.register.cmd_cfg, meson_mmc_cmd); + } + } + + fn meson_read_response(&self, cmd: &SdmmcCmd, response: &mut [u32; 4]) { + let [rsp0, rsp1, rsp2, rsp3] = response; + + // Assign values by reading the respective registers + if cmd.resp_type & sdmmc_protocol::sdmmc::MMC_RSP_136 != 0 { + unsafe { + // Yes, this is in a reverse order as rsp0 and self.cmd_rsp3 is the least significant + // Check uboot read response code for more details + *rsp0 = ptr::read_volatile(&self.register.cmd_rsp3); + *rsp1 = ptr::read_volatile(&self.register.cmd_rsp2); + *rsp2 = ptr::read_volatile(&self.register.cmd_rsp1); + *rsp3 = ptr::read_volatile(&self.register.cmd_rsp); + } + } else if cmd.resp_type & sdmmc_protocol::sdmmc::MMC_RSP_PRESENT != 0 { + unsafe { + *rsp0 = ptr::read_volatile(&self.register.cmd_rsp); + } + } + } + + /// The result that such function exists instead of using generic frequency is that different + /// hosts may have preferred frequency for speed classes. For example, in meson, the frequency for + /// UhsSdr104 would be 200Mhz instead of 208Mhz + fn meson_frequency(timing: MmcTiming) -> Result { + let freq: u32 = match timing { + MmcTiming::Legacy => 25000000, + MmcTiming::MmcHs => 26000000, + MmcTiming::SdHs => 50000000, + MmcTiming::UhsSdr12 => 25000000, + MmcTiming::UhsSdr25 => 50000000, + MmcTiming::UhsSdr50 => 100000000, + MmcTiming::UhsSdr104 => MESON_MAX_FREQUENCY, + MmcTiming::UhsDdr50 => 50000000, + MmcTiming::MmcDdr52 => 52000000, + MmcTiming::MmcHs200 => MESON_MAX_FREQUENCY, + MmcTiming::MmcHs400 => MESON_MAX_FREQUENCY, + // Typical low frequency for card initialization + MmcTiming::CardSetup => MESON_MIN_FREQUENCY, + MmcTiming::CardSleep => MESON_MIN_FREQUENCY, + _ => { + return Err(SdmmcError::EINVAL); + } + }; + Ok(freq) + } + + fn meson_stop_clock(&mut self, stop: bool) { + unsafe { + // Read the current configuration register value + let mut meson_mmc_cfg: u32 = ptr::read_volatile(&self.register.cfg); + + // Update the CFG_STOP_CLOCK bit based on the `stop` parameter + meson_mmc_cfg = (meson_mmc_cfg & !CFG_STOP_CLOCK) | ((stop as u32) * CFG_STOP_CLOCK); + + // Write the updated value back to the configuration register + ptr::write_volatile(&mut self.register.cfg, meson_mmc_cfg); + } + } + + fn meson_enable_ddr(&mut self, enable: bool) { + unsafe { + // Read the current configuration register value + let mut meson_mmc_cfg: u32 = ptr::read_volatile(&self.register.cfg); + + // Update the CFG_DDR_MODE bit based on the `enable` parameter + meson_mmc_cfg = (meson_mmc_cfg & !CFG_DDR_MODE) | ((enable as u32) * CFG_DDR_MODE); + + // Write the updated value back to the configuration register + ptr::write_volatile(&mut self.register.cfg, meson_mmc_cfg); + } + } + + const DESC_STOP_TIMEOUT_NS: u32 = 5_000_000; // 5ms in nanoseconds + const POLL_INTERVAL_NS: u32 = 100_000; // 100µs in nanoseconds + /// Waits for the descriptor engine to stop, with a timeout + fn meson_wait_desc_stop(&self) -> Result<(), SdmmcError> { + let mut start_time: u32 = 0; + + while unsafe { ptr::read_volatile(&self.register.status) } + & (STATUS_DESC_BUSY | STATUS_BUSY) + != 0 + { + if start_time > Self::DESC_STOP_TIMEOUT_NS { + return Err(SdmmcError::EUNDEFINED); + } + // Use the provided wait function instead of sleep + process_wait_unreliable(Self::POLL_INTERVAL_NS as u64); + start_time += Self::POLL_INTERVAL_NS; + } + Ok(()) + } +} + +impl SdmmcOps for SdmmcMesonHardware { + fn sdmmc_init(&mut self) -> Result { + // Reset host state + self.meson_reset(); + + let ios: MmcIos = MmcIos { + clock: MESON_MIN_FREQUENCY as u64, + bus_width: MmcBusWidth::Width1, + signal_voltage: MmcSignalVoltage::Voltage330, + enabled_irq: false, + emmc: None, + spi: None, + }; + + return Ok(ios); + } + + fn sdmmc_execute_tuning( + &mut self, + memory: *mut [u8; 64], + sleep: &mut dyn Sleep, + ) -> Result<(), SdmmcError> { + let mut current_delay: u32 = 0; + + if let Some(ref config) = self.delay { + if config.timing == self.timing { + current_delay = config.current_delay; + } + } + // If there is no existing delay config, clear the adjust register anyway + else { + unsafe { + ptr::write_volatile(&mut self.register.adjust, 0); + } + } + + let mut adjust: u32 = SD_EMMC_ADJ_ENABLE; + let clk: u32; + + unsafe { + clk = ptr::read_volatile(&self.register.clock); + } + + let clk_src: u32 = clk & CLK_SRC_MASK; + + let mux_clk_freq: u32 = match clk_src { + CLK_SRC_24M => SD_EMMC_CLKSRC_24M, + CLK_SRC_DIV2 => SD_EMMC_CLKSRC_DIV2, + _ => return Err(SdmmcError::EUNDEFINED), + }; + + let max_div: u32 = div_round_up!(mux_clk_freq, self.frequency); + + let mut tried_lowest_delay: u32 = current_delay; + + let mut tried_highest_delay: u32 = current_delay; + + loop { + dev_log!( + "current delay: {}, lowest_tried: {}, highest_tried: {}\n", + current_delay, + tried_lowest_delay, + tried_highest_delay + ); + let res: Result<(), SdmmcError> = Sdcard::sdcard_test_tuning(self, sleep, memory); + + match res { + Ok(_) => { + self.delay = Some(DelayConfig { + timing: self.timing, + current_delay, + }); + break Ok(()); + } + Err(SdmmcError::EIO) => {} + Err(_) => { + self.delay = None; + unsafe { + ptr::write_volatile(&mut self.register.adjust, 0); + } + break Err(SdmmcError::EUNSUPPORTEDCARD); + } + } + + if max_div - tried_highest_delay == 0 { + self.delay = None; + unsafe { + ptr::write_volatile(&mut self.register.adjust, 0); + } + break Err(SdmmcError::EUNSUPPORTEDCARD); + } + + // The tuning process start from the current delay in the middle + // then approach both ends + if max_div - tried_highest_delay >= tried_lowest_delay { + tried_highest_delay += 1; + current_delay = tried_highest_delay; + } else { + tried_lowest_delay -= 1; + current_delay = tried_lowest_delay; + } + + adjust |= (current_delay << SD_EMMC_ADJ) & SD_EMMC_ADJUST_ADJ_DELAY_MASK; + + unsafe { + ptr::write_volatile(&mut self.register.adjust, adjust); + } + } + } + + fn sdmmc_config_timing(&mut self, timing: MmcTiming) -> Result { + // Why calling this function if the timing does not change? + if self.timing == timing { + return Ok(self.frequency as u64); + } + + if timing == MmcTiming::ClockStop { + // Disable the clock completely + self.meson_stop_clock(true); + + // Change the timing + self.timing = timing; + self.frequency = 0; + + return Ok(0); + } + + let freq: u32 = Self::meson_frequency(timing)?; + + if freq > MESON_MAX_FREQUENCY || freq < MESON_MIN_FREQUENCY { + return Err(SdmmcError::EINVAL); + } + // #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) + let mut meson_mmc_clk: u32 = 0; + + // Valid clock freq range: + // f_min = div_round_up!(SD_EMMC_CLKSRC_24M, CLK_MAX_DIV); + // f_max = 100000000; /* 100 MHz */ + let clk: u32; + let clk_src: u32; + // 400 khz for init the card + let clock_freq: u32 = freq as u32; + if clock_freq > 16000000 { + clk = SD_EMMC_CLKSRC_DIV2; + clk_src = CLK_SRC_DIV2; + } else { + clk = SD_EMMC_CLKSRC_24M; + clk_src = CLK_SRC_24M; + } + + let clk_div: u32 = div_round_up!(clk, clock_freq); + /* + * From uboot meson_gx_mmc.c + * SM1 SoCs doesn't work fine over 50MHz with CLK_CO_PHASE_180 + * If CLK_CO_PHASE_270 is used, it's more stable than other. + * Other SoCs use CLK_CO_PHASE_180 by default. + * Linux default to use CLK_CO_PHASE_180 + * However, if CLK_CO_PHASE_180 is used without tuning, + * Sdcard will not work in High speed mode on Odroid C4 + */ + meson_mmc_clk |= CLK_CO_PHASE_180; + // meson_mmc_clk |= CLK_CO_PHASE_270; + meson_mmc_clk |= CLK_TX_PHASE_000; + + meson_mmc_clk |= clk_src; + meson_mmc_clk |= clk_div; + + unsafe { + ptr::write_volatile(&mut self.register.clock, meson_mmc_clk); + } + + // If pervious timing is clock stop, renable the clock here + if self.timing == MmcTiming::ClockStop { + self.meson_stop_clock(false); + } + + // If the pervious timing mode is using ddr, then disable ddr + if self.timing == MmcTiming::UhsDdr50 || self.timing == MmcTiming::MmcDdr52 { + self.meson_enable_ddr(false); + } + + // If the next timing mode is using ddr, enable ddr + if timing == MmcTiming::UhsDdr50 || timing == MmcTiming::MmcDdr52 { + self.meson_enable_ddr(true); + } + + // Update timing + self.timing = timing; + self.frequency = clk / clk_div; + + Ok(self.frequency as u64) + } + + fn sdmmc_config_bus_width(&mut self, bus_width: MmcBusWidth) -> Result<(), SdmmcError> { + let mut meson_mmc_cfg: u32; + unsafe { + meson_mmc_cfg = ptr::read_volatile(&self.register.cfg); + } + match bus_width { + MmcBusWidth::Width1 => meson_mmc_cfg |= CFG_BUS_WIDTH_1, + MmcBusWidth::Width4 => meson_mmc_cfg |= CFG_BUS_WIDTH_4, + MmcBusWidth::Width8 => meson_mmc_cfg |= CFG_BUS_WIDTH_8, + } + unsafe { + ptr::write_volatile(&mut self.register.cfg, meson_mmc_cfg); + } + Ok(()) + } + + fn sdmmc_read_datalanes(&self) -> Result { + unsafe { + // Read the status register + let status = ptr::read_volatile(&self.register.status); + + // Extract and return the DAT signal state + Ok(((status & STATUS_DAT_MASK) >> STATUS_DAT_SHIFT) as u8) + } + } + + fn sdmmc_send_command( + &mut self, + cmd: &SdmmcCmd, + data: Option<&MmcData>, + ) -> Result<(), SdmmcError> { + // Set up the data addr + let mut data_addr: u32 = 0u32; + if let Some(mmc_data) = data { + // TODO: Check what if the addr is u32::MAX, will the sdcard still working? + if mmc_data.addr >= (WRITE_ADDR_UPPER as u64) + || mmc_data.blockcnt == 0 + || mmc_data.blockcnt > MAX_BLOCK_PER_TRANSFER + { + info!("SDMMC: INVALID INPUT VARIABLE!"); + return Err(SdmmcError::EINVAL); + } + // Depend on the flag and hardware, the cache should be flushed accordingly + data_addr = mmc_data.addr as u32; + } + + // Stop data transfer + unsafe { + ptr::write_volatile(&mut self.register.start, 0u32); + } + + unsafe { + ptr::write_volatile(&mut self.register.cmd_dat, data_addr); + } + + self.meson_mmc_set_up_cmd_cfg_and_cfg(&cmd, data); + + // I am still keeping this line of code here but I think it is not necessary + unsafe { + ptr::write_volatile(&mut self.register.status, STATUS_MASK); + } + + // Clear the response register, for testing & debugging + unsafe { + ptr::write_volatile(&mut self.register.cmd_rsp, 0u32); + } + + unsafe { + ptr::write_volatile(&mut self.register.cmd_arg, cmd.cmdarg); + } + + Ok(()) + } + + fn sdmmc_receive_response( + &self, + cmd: &SdmmcCmd, + response: &mut [u32; 4], + ) -> Result<(), SdmmcError> { + let status: u32; + + unsafe { + status = ptr::read_volatile(&self.register.status); + } + + if (status & STATUS_END_OF_CHAIN) == 0 { + return Err(SdmmcError::EBUSY); + } + + self.meson_read_response(cmd, response); + + if (status & STATUS_ERR_MASK) != 0 { + // For debug + info!("SDMMC: Print out error request:"); + info!("cmd idx: {}", cmd.cmdidx); + info!("cmd arg: 0x{:x}", cmd.cmdarg); + + info!("Odroidc4 status register: 0x{:08x}", status); + + info!("Card's first response register: 0x{:08x}", response[0]); + + if (status & STATUS_RESP_TIMEOUT) != 0 { + info!("SDMMC: CARD TIMEOUT!"); + + // The card will try to polling the status register until + // both descriptor and card are not in busy state + self.meson_wait_desc_stop()?; + return Err(SdmmcError::ETIMEDOUT); + } + + if (status & STATUS_EIO_ERR) != 0 { + info!("SDMMC: CARD IO ERROR! Perform retuning"); + + self.meson_wait_desc_stop()?; + // Notified the card to retune the card + return Err(SdmmcError::EIO); + } + + info!( + "SDMMC: Unknown error, Please copy the entire log from driver and send it to Cheng Li lichengchaoreng@gmail.com" + ); + + self.meson_wait_desc_stop()?; + return Err(SdmmcError::EUNKNOWN); + } + + Ok(()) + } + + /// This function is meant to clear, acknowledge and then reenable the interrupt + fn sdmmc_config_interrupt( + &mut self, + enable_irq: bool, + enable_sdio_irq: bool, + ) -> Result<(), SdmmcError> { + // Disable interrupt + unsafe { + ptr::write_volatile(&mut self.register.irq_en, 0); + } + + // Acknowledge interrupt + unsafe { + ptr::write_volatile(&mut self.register.status, IRQ_EN_MASK | IRQ_SDIO); + } + let mut irq_bits_to_set: u32 = 0; + if enable_irq == true { + irq_bits_to_set |= IRQ_EN_MASK; + } + if enable_sdio_irq == true { + irq_bits_to_set |= IRQ_SDIO; + } + unsafe { + ptr::write_volatile(&mut self.register.irq_en, irq_bits_to_set); + } + self.enabled_irq = irq_bits_to_set; + return Ok(()); + } + + fn sdmmc_ack_interrupt(&mut self) -> Result<(), SdmmcError> { + if self.enabled_irq != 0 { + unsafe { + ptr::write_volatile(&mut self.register.status, self.enabled_irq); + } + } + return Ok(()); + } + + fn sdmmc_host_reset(&mut self) -> Result { + Self::meson_reset(self); + + let ios: MmcIos = MmcIos { + clock: MESON_MIN_FREQUENCY as u64, + bus_width: MmcBusWidth::Width1, + signal_voltage: MmcSignalVoltage::Voltage330, + enabled_irq: false, + emmc: None, + spi: None, + }; + + Ok(ios) + } +} + +impl SdmmcHardware for SdmmcMesonHardware { + const HOST_INFO: HostInfo = HostInfo { + max_frequency: MESON_MAX_FREQUENCY as u64, + min_frequency: MESON_MIN_FREQUENCY as u64, + max_block_per_req: MAX_BLOCK_PER_TRANSFER, + // On odroid c4, the operating voltage is default to 3.3V + vdd: (MMC_VDD_33_34 | MMC_VDD_32_33 | MMC_VDD_31_32), + host_capability: MMC_TIMING_LEGACY | MMC_TIMING_SD_HS | MMC_TIMING_UHS | MMC_CAP_4_BIT_DATA, + }; + + /// This function should be CONST!!! It is just Rust does not support it yet + unsafe fn new(sdmmc_register_base: u64) -> Self { + let register: &'static mut MesonSdmmcRegisters = + unsafe { MesonSdmmcRegisters::new(sdmmc_register_base) }; + + SdmmcMesonHardware { + register, + delay: None, + // Default uboot speed class + timing: MmcTiming::SdHs, + // Wrong value but should not have much impact + frequency: MESON_MIN_FREQUENCY, + enabled_irq: 0, + } + } +} diff --git a/examples/blk/blk.mk b/examples/blk/blk.mk index f9c4fb27a..e977bbcd9 100644 --- a/examples/blk/blk.mk +++ b/examples/blk/blk.mk @@ -60,6 +60,11 @@ else ifeq ($(strip $(MICROKIT_BOARD)), maaxboard) SERIAL_DRIVER_DIR := imx BLK_DRIVER_DIR := mmc/imx TIMER_DRIVER_DIR := imx +else ifeq ($(strip $(MICROKIT_BOARD)), odroidc4) + ARCH := aarch64 + CPU := cortex-a55 + SERIAL_DRIVER_DIR := meson + BLK_DRIVER_DIR := mmc/core else $(error Unsupported MICROKIT_BOARD given) endif @@ -80,7 +85,9 @@ ARCH := ${shell grep 'CONFIG_SEL4_ARCH ' $(BOARD_DIR)/include/kernel/gen_config SDDF_CUSTOM_LIBC := 1 IMAGES := blk_driver.elf client.elf blk_virt.elf serial_virt_tx.elf serial_driver.elf -CFLAGS := -nostdlib \ + +CFLAGS := -mstrict-align \ + -nostdlib \ -ffreestanding \ -g3 \ -O3 \ diff --git a/examples/blk/meta.py b/examples/blk/meta.py index 420bc9f70..b04996b9b 100644 --- a/examples/blk/meta.py +++ b/examples/blk/meta.py @@ -9,6 +9,8 @@ assert version("sdfgen").split(".")[1] == "27", "Unexpected sdfgen version" ProtectionDomain = SystemDescription.ProtectionDomain +MemoryRegion = SystemDescription.MemoryRegion +Map = SystemDescription.Map @dataclass @@ -45,6 +47,15 @@ class Board: timer="soc@0/bus@30000000/timer@302d0000", serial="soc@0/bus@30800000/serial@30860000", ), + Board( + name="odroidc4", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0x80000000, + partition=0, + blk="soc/sd@ffe05000", + timer=None, + serial="soc/bus@ff800000/serial@3000", + ), Board( name="qemu_virt_riscv64", arch=SystemDescription.Arch.RISCV64, @@ -95,6 +106,11 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): partition = int(args.partition) if args.partition else board.partition blk_system.add_client(client, partition=partition) + if board.name == "odroidc4": + gpio_mr = MemoryRegion(sdf, name="gpio", size=0x1000, paddr=0xFF800000) + blk_driver.add_map(Map(gpio_mr, 0xFF800000, "rw", cached=False)) + sdf.add_mr(gpio_mr) + serial_system.add_client(client) pds = [serial_driver, serial_virt_tx, blk_driver, blk_virt, client]