mirror of
https://github.com/aljazceru/kata-containers.git
synced 2025-12-18 14:54:19 +01:00
Dragonball: migrate dragonball-sandbox crates to Kata
In order to make it easier for developers to contribute to Dragonball, we decide to migrate all dragonball-sandbox crates to Kata. fixes: #7262 Signed-off-by: Chao Wu <chaowu@linux.alibaba.com>
This commit is contained in:
290
src/dragonball/Cargo.lock
generated
290
src/dragonball/Cargo.lock
generated
@@ -210,8 +210,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-address-space"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95e20d28a9cd13bf00d0ecd1bd073d242242b04f0acb663d7adfc659f8879322"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"lazy_static",
|
||||
@@ -225,8 +223,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-allocator"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "543711b94b4bc1437d2ebb45f856452e96a45a67ab39f8dcf8c887c2a3701004"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@@ -234,8 +230,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-arch"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194c844946cd1d13f7a9eb29b84afbc5354578eee2b06fea96226bc3872e7424"
|
||||
dependencies = [
|
||||
"kvm-bindings",
|
||||
"kvm-ioctls",
|
||||
@@ -249,8 +243,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-boot"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5466a92f75aa928a9103dcb2088f6d1638ef9da8945fad7389a73864dfa0182c"
|
||||
dependencies = [
|
||||
"dbs-arch",
|
||||
"kvm-bindings",
|
||||
@@ -265,8 +257,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-device"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14ecea44b4bc861c0c2ccb51868bea781286dc70e40ae46b54d4511e690a654a"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@@ -274,8 +264,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-interrupt"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1eb2c5bb9f8f123ace33b1b2e8d53dd2d87331ee770ad1f82e56c3382c6bed6d"
|
||||
dependencies = [
|
||||
"dbs-arch",
|
||||
"dbs-device",
|
||||
@@ -288,11 +276,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-legacy-devices"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4d089ac1c4d186c8133be59de09462e9793f7add10017c5b040318a3a7f431f"
|
||||
dependencies = [
|
||||
"dbs-device",
|
||||
"dbs-utils",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"vm-superio",
|
||||
@@ -302,8 +289,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-upcall"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea3a78128fd0be8b8b10257675c262b378dc5d00b1e18157736a6c27e45ce4fb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dbs-utils",
|
||||
@@ -316,8 +301,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-utils"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cb6ff873451b76e22789af7fbe1d0478c42c717f817e66908be7a3a2288068c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"event-manager",
|
||||
@@ -332,8 +315,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "dbs-virtio-devices"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d671cc3e5f98b84ef6b6bed007d28f72f16d3aea8eb38e2d42b00b2973c1d8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"caps",
|
||||
@@ -349,9 +330,10 @@ dependencies = [
|
||||
"log",
|
||||
"nix 0.24.3",
|
||||
"nydus-api",
|
||||
"nydus-blobfs",
|
||||
"nydus-rafs",
|
||||
"nydus-storage",
|
||||
"rlimit",
|
||||
"sendfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
@@ -498,10 +480,25 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuse-backend-rs"
|
||||
version = "0.10.2"
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08af89cb80a7c5693bd63a2b1ee7ac31a307670977c18fda036b3aa94be8c47f"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fuse-backend-rs"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc24820b14267bec37fa87f5c2a32b5f1c5405b8c60cc3aa77afd481bd2628a6"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bitflags",
|
||||
@@ -518,95 +515,6 @@ dependencies = [
|
||||
"vmm-sys-util 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.6"
|
||||
@@ -891,82 +799,45 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nydus-api"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1899def1a22ed32b1d60de4e444f525c4023a208ee0d1136a65399cff82837ce"
|
||||
checksum = "33a6ca41dd10813e3d29397550fbb0f15ad149381f312e04659d39e0adcf2002"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"libc",
|
||||
"log",
|
||||
"nydus-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nydus-blobfs"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784cf6e1319da7a94734987dcc71d2940f74231256922431a505c832fc778dd3"
|
||||
dependencies = [
|
||||
"fuse-backend-rs",
|
||||
"libc",
|
||||
"log",
|
||||
"nydus-api",
|
||||
"nydus-error",
|
||||
"nydus-rafs",
|
||||
"nydus-storage",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nydus-error"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae2ec1efd1589377dbefca6b1047294c71b2fbab164d93319f97b20faae92001"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"httpdate",
|
||||
"libc",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nydus-rafs"
|
||||
version = "0.2.2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0ace6945daa16842e72e9fe7647e2b8715856f50f07350cce82bd68db1ed02c"
|
||||
checksum = "ed21e44a99472850d2afc4fb07427ed46d4e6a8b1cce28b42bd689319e45076d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
"bitflags",
|
||||
"blake3",
|
||||
"fuse-backend-rs",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"lz4-sys",
|
||||
"nix 0.24.3",
|
||||
"nydus-api",
|
||||
"nydus-error",
|
||||
"nydus-storage",
|
||||
"nydus-utils",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spmc",
|
||||
"vm-memory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nydus-storage"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08bc5ea9054fca2ec8b19dcce25ea600679b7fbf035aad86cfe4a659002c88b"
|
||||
checksum = "9591fbee1875895bf1f765656695d0be6887fe65372fbf4924b8b3959bd61375"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bitflags",
|
||||
@@ -978,7 +849,6 @@ dependencies = [
|
||||
"log",
|
||||
"nix 0.24.3",
|
||||
"nydus-api",
|
||||
"nydus-error",
|
||||
"nydus-utils",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -989,12 +859,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nydus-utils"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1e681d7207a1ec500323d5ca39ebb7e381fc4f14db5ff0c532c18ff1226a81f"
|
||||
checksum = "fe8b9269e3a370682f272a1b2cac4bdaf6d6657f3f6966560c4fedab36548362"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"flate2",
|
||||
"httpdate",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
@@ -1002,7 +873,8 @@ dependencies = [
|
||||
"lz4",
|
||||
"lz4-sys",
|
||||
"nix 0.24.3",
|
||||
"nydus-error",
|
||||
"nydus-api",
|
||||
"openssl",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -1025,6 +897,54 @@ version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "111.26.0+1.1.1u"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@@ -1054,12 +974,6 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.26"
|
||||
@@ -1068,18 +982,18 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1166,6 +1080,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sendfd"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604b71b8fc267e13bb3023a2c901126c8f349393666a6d98ac1ae5729b701798"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.156"
|
||||
@@ -1183,7 +1106,7 @@ checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1275,12 +1198,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spmc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a8428da277a8e3a15271d79943e80ccc2ef254e78813a166a08d65e4c3ece5"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
@@ -1298,6 +1215,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
@@ -1350,7 +1278,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1434,7 +1362,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1480,7 +1408,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -12,16 +12,16 @@ edition = "2018"
|
||||
[dependencies]
|
||||
arc-swap = "1.5.0"
|
||||
bytes = "1.1.0"
|
||||
dbs-address-space = "0.3.0"
|
||||
dbs-allocator = "0.1.0"
|
||||
dbs-arch = "0.2.0"
|
||||
dbs-boot = "0.4.0"
|
||||
dbs-device = "0.2.0"
|
||||
dbs-interrupt = { version = "0.2.0", features = ["kvm-irq"] }
|
||||
dbs-legacy-devices = "0.1.0"
|
||||
dbs-upcall = { version = "0.3.0", optional = true }
|
||||
dbs-utils = "0.2.0"
|
||||
dbs-virtio-devices = { version = "0.3.1", optional = true, features = ["virtio-mmio"] }
|
||||
dbs-address-space = { path = "./src/dbs_address_space" }
|
||||
dbs-allocator = { path = "./src/dbs_allocator" }
|
||||
dbs-arch = { path = "./src/dbs_arch" }
|
||||
dbs-boot = { path = "./src/dbs_boot" }
|
||||
dbs-device = { path = "./src/dbs_device" }
|
||||
dbs-interrupt = { path = "./src/dbs_interrupt", features = ["kvm-irq"] }
|
||||
dbs-legacy-devices = { path = "./src/dbs_legacy_devices" }
|
||||
dbs-upcall = { path = "./src/dbs_upcall" , optional = true }
|
||||
dbs-utils = { path = "./src/dbs_utils" }
|
||||
dbs-virtio-devices = { path = "./src/dbs_virtio_devices", optional = true, features = ["virtio-mmio"] }
|
||||
kvm-bindings = "0.6.0"
|
||||
kvm-ioctls = "0.12.0"
|
||||
lazy_static = "1.2"
|
||||
|
||||
@@ -16,10 +16,22 @@ and configuration process.
|
||||
|
||||
# Documentation
|
||||
|
||||
Device: [Device Document](docs/device.md)
|
||||
vCPU: [vCPU Document](docs/vcpu.md)
|
||||
API: [API Document](docs/api.md)
|
||||
`Upcall`: [`Upcall` Document](docs/upcall.md)
|
||||
- Device: [Device Document](docs/device.md)
|
||||
- vCPU: [vCPU Document](docs/vcpu.md)
|
||||
- API: [API Document](docs/api.md)
|
||||
- `Upcall`: [`Upcall` Document](docs/upcall.md)
|
||||
- `dbs_acpi`: [`dbs_acpi` Document](src/dbs_acpi/README.md)
|
||||
- `dbs_address_space`: [`dbs_address_space` Document](src/dbs_address_space/README.md)
|
||||
- `dbs_allocator`: [`dbs_allocator` Document](src/dbs_allocator/README.md)
|
||||
- `dbs_arch`: [`dbs_arch` Document](src/dbs_arch/README.md)
|
||||
- `dbs_boot`: [`dbs_boot` Document](src/dbs_boot/README.md)
|
||||
- `dbs_device`: [`dbs_device` Document](src/dbs_device/README.md)
|
||||
- `dbs_interrupt`: [`dbs_interrput` Document](src/dbs_interrupt/README.md)
|
||||
- `dbs_legacy_devices`: [`dbs_legacy_devices` Document](src/dbs_legacy_devices/README.md)
|
||||
- `dbs_tdx`: [`dbs_tdx` Document](src/dbs_tdx/README.md)
|
||||
- `dbs_upcall`: [`dbs_upcall` Document](src/dbs_upcall/README.md)
|
||||
- `dbs_utils`: [`dbs_utils` Document](src/dbs_utils/README.md)
|
||||
- `dbs_virtio_devices`: [`dbs_virtio_devices` Document](src/dbs_virtio_devices/README.md)
|
||||
|
||||
Currently, the documents are still actively adding.
|
||||
You could see the [official documentation](docs/) page for more details.
|
||||
|
||||
14
src/dragonball/src/dbs_acpi/Cargo.toml
Normal file
14
src/dragonball/src/dbs_acpi/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "dbs-acpi"
|
||||
version = "0.1.0"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
description = "acpi definitions for virtual machines."
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball", "acpi", "vmm", "secure-sandbox"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.9.0"
|
||||
11
src/dragonball/src/dbs_acpi/README.md
Normal file
11
src/dragonball/src/dbs_acpi/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# dbs-acpi
|
||||
|
||||
`dbs-acpi` provides ACPI data structures for VMM to emulate ACPI behavior.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Part of the code is derived from the [Cloud Hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
29
src/dragonball/src/dbs_acpi/src/lib.rs
Normal file
29
src/dragonball/src/dbs_acpi/src/lib.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2019 Intel Corporation
|
||||
// Copyright (c) 2023 Alibaba Cloud
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pub mod rsdp;
|
||||
pub mod sdt;
|
||||
|
||||
fn generate_checksum(data: &[u8]) -> u8 {
|
||||
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_generate_checksum() {
|
||||
let mut buf = [0x00; 8];
|
||||
let sum = generate_checksum(&buf);
|
||||
assert_eq!(sum, 0);
|
||||
buf[0] = 0xff;
|
||||
let sum = generate_checksum(&buf);
|
||||
assert_eq!(sum, 1);
|
||||
buf[0] = 0xaa;
|
||||
buf[1] = 0xcc;
|
||||
buf[4] = generate_checksum(&buf);
|
||||
let sum = buf.iter().fold(0u8, |s, v| s.wrapping_add(*v));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
60
src/dragonball/src/dbs_acpi/src/rsdp.rs
Normal file
60
src/dragonball/src/dbs_acpi/src/rsdp.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2019 Intel Corporation
|
||||
// Copyright (c) 2023 Alibaba Cloud
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// RSDP (Root System Description Pointer) is a data structure used in the ACPI programming interface.
|
||||
use vm_memory::ByteValued;
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Rsdp {
|
||||
pub signature: [u8; 8],
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
pub revision: u8,
|
||||
_rsdt_addr: u32,
|
||||
pub length: u32,
|
||||
pub xsdt_addr: u64,
|
||||
pub extended_checksum: u8,
|
||||
_reserved: [u8; 3],
|
||||
}
|
||||
|
||||
// SAFETY: Rsdp only contains a series of integers
|
||||
unsafe impl ByteValued for Rsdp {}
|
||||
|
||||
impl Rsdp {
|
||||
pub fn new(xsdt_addr: u64) -> Self {
|
||||
let mut rsdp = Rsdp {
|
||||
signature: *b"RSD PTR ",
|
||||
checksum: 0,
|
||||
oem_id: *b"ALICLD",
|
||||
revision: 1,
|
||||
_rsdt_addr: 0,
|
||||
length: std::mem::size_of::<Rsdp>() as u32,
|
||||
xsdt_addr,
|
||||
extended_checksum: 0,
|
||||
_reserved: [0; 3],
|
||||
};
|
||||
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||
rsdp.extended_checksum = super::generate_checksum(rsdp.as_slice());
|
||||
rsdp
|
||||
}
|
||||
|
||||
pub fn len() -> usize {
|
||||
std::mem::size_of::<Rsdp>()
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Rsdp;
|
||||
use vm_memory::bytes::ByteValued;
|
||||
#[test]
|
||||
fn test_rsdp() {
|
||||
let rsdp = Rsdp::new(0xa0000);
|
||||
let sum = rsdp
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
137
src/dragonball/src/dbs_acpi/src/sdt.rs
Normal file
137
src/dragonball/src/dbs_acpi/src/sdt.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) 2019 Intel Corporation
|
||||
// Copyright (c) 2023 Alibaba Cloud
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#[repr(packed)]
|
||||
pub struct GenericAddress {
|
||||
pub address_space_id: u8,
|
||||
pub register_bit_width: u8,
|
||||
pub register_bit_offset: u8,
|
||||
pub access_size: u8,
|
||||
pub address: u64,
|
||||
}
|
||||
|
||||
impl GenericAddress {
|
||||
pub fn io_port_address<T>(address: u16) -> Self {
|
||||
GenericAddress {
|
||||
address_space_id: 1,
|
||||
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
|
||||
register_bit_offset: 0,
|
||||
access_size: std::mem::size_of::<T>() as u8,
|
||||
address: u64::from(address),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mmio_address<T>(address: u64) -> Self {
|
||||
GenericAddress {
|
||||
address_space_id: 0,
|
||||
register_bit_width: 8 * std::mem::size_of::<T>() as u8,
|
||||
register_bit_offset: 0,
|
||||
access_size: std::mem::size_of::<T>() as u8,
|
||||
address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sdt {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
impl Sdt {
|
||||
pub fn new(signature: [u8; 4], length: u32, revision: u8) -> Self {
|
||||
assert!(length >= 36);
|
||||
const OEM_ID: [u8; 6] = *b"ALICLD";
|
||||
const OEM_TABLE: [u8; 8] = *b"RUND ";
|
||||
const CREATOR_ID: [u8; 4] = *b"ALIC";
|
||||
let mut data = Vec::with_capacity(length as usize);
|
||||
data.extend_from_slice(&signature);
|
||||
data.extend_from_slice(&length.to_le_bytes());
|
||||
data.push(revision);
|
||||
data.push(0); // checksum
|
||||
data.extend_from_slice(&OEM_ID); // oem id u32
|
||||
data.extend_from_slice(&OEM_TABLE); // oem table
|
||||
data.extend_from_slice(&1u32.to_le_bytes()); // oem revision u32
|
||||
data.extend_from_slice(&CREATOR_ID); // creator id u32
|
||||
data.extend_from_slice(&1u32.to_le_bytes()); // creator revison u32
|
||||
assert_eq!(data.len(), 36);
|
||||
data.resize(length as usize, 0);
|
||||
let mut sdt = Sdt { data };
|
||||
sdt.update_checksum();
|
||||
sdt
|
||||
}
|
||||
|
||||
pub fn update_checksum(&mut self) {
|
||||
self.data[9] = 0;
|
||||
let checksum = super::generate_checksum(self.data.as_slice());
|
||||
self.data[9] = checksum
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
self.data.as_slice()
|
||||
}
|
||||
|
||||
pub fn append<T>(&mut self, value: T) {
|
||||
let orig_length = self.data.len();
|
||||
let new_length = orig_length + std::mem::size_of::<T>();
|
||||
self.data.resize(new_length, 0);
|
||||
self.write_u32(4, new_length as u32);
|
||||
self.write(orig_length, value);
|
||||
}
|
||||
|
||||
pub fn append_slice(&mut self, data: &[u8]) {
|
||||
let orig_length = self.data.len();
|
||||
let new_length = orig_length + data.len();
|
||||
self.write_u32(4, new_length as u32);
|
||||
self.data.extend_from_slice(data);
|
||||
self.update_checksum();
|
||||
}
|
||||
|
||||
/// Write a value at the given offset
|
||||
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||
unsafe {
|
||||
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
||||
}
|
||||
self.update_checksum();
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
||||
self.write(offset, val);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Sdt;
|
||||
#[test]
|
||||
fn test_sdt() {
|
||||
let mut sdt = Sdt::new(*b"TEST", 40, 1);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
sdt.write_u32(36, 0x12345678);
|
||||
let sum: u8 = sdt
|
||||
.as_slice()
|
||||
.iter()
|
||||
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||
assert_eq!(sum, 0);
|
||||
}
|
||||
}
|
||||
20
src/dragonball/src/dbs_address_space/Cargo.toml
Normal file
20
src/dragonball/src/dbs_address_space/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "dbs-address-space"
|
||||
version = "0.3.0"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
description = "address space manager for virtual machines."
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball", "address", "vmm", "secure-sandbox"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
arc-swap = ">=0.4.8"
|
||||
libc = "0.2.39"
|
||||
nix = "0.23.1"
|
||||
lazy_static = "1"
|
||||
thiserror = "1"
|
||||
vmm-sys-util = "0.11.0"
|
||||
vm-memory = { version = "0.9", features = ["backend-mmap", "backend-atomic"] }
|
||||
1
src/dragonball/src/dbs_address_space/LICENSE
Symbolic link
1
src/dragonball/src/dbs_address_space/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
80
src/dragonball/src/dbs_address_space/README.md
Normal file
80
src/dragonball/src/dbs_address_space/README.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# dbs-address-space
|
||||
|
||||
## Design
|
||||
|
||||
The `dbs-address-space` crate is an address space manager for virtual machines, which manages memory and MMIO resources resident in the guest physical address space.
|
||||
|
||||
Main components are:
|
||||
- `AddressSpaceRegion`: Struct to maintain configuration information about a guest address region.
|
||||
```rust
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AddressSpaceRegion {
|
||||
/// Type of address space regions.
|
||||
pub ty: AddressSpaceRegionType,
|
||||
/// Base address of the region in virtual machine's physical address space.
|
||||
pub base: GuestAddress,
|
||||
/// Size of the address space region.
|
||||
pub size: GuestUsize,
|
||||
/// Host NUMA node ids assigned to this region.
|
||||
pub host_numa_node_id: Option<u32>,
|
||||
|
||||
/// File/offset tuple to back the memory allocation.
|
||||
file_offset: Option<FileOffset>,
|
||||
/// Mmap permission flags.
|
||||
perm_flags: i32,
|
||||
/// Hugepage madvise hint.
|
||||
///
|
||||
/// It needs 'advise' or 'always' policy in host shmem config.
|
||||
is_hugepage: bool,
|
||||
/// Hotplug hint.
|
||||
is_hotplug: bool,
|
||||
/// Anonymous memory hint.
|
||||
///
|
||||
/// It should be true for regions with the MADV_DONTFORK flag enabled.
|
||||
is_anon: bool,
|
||||
}
|
||||
```
|
||||
- `AddressSpaceBase`: Base implementation to manage guest physical address space, without support of region hotplug.
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct AddressSpaceBase {
|
||||
regions: Vec<Arc<AddressSpaceRegion>>,
|
||||
layout: AddressSpaceLayout,
|
||||
}
|
||||
```
|
||||
- `AddressSpaceBase`: An address space implementation with region hotplug capability.
|
||||
```rust
|
||||
/// The `AddressSpace` is a wrapper over [AddressSpaceBase] to support hotplug of
|
||||
/// address space regions.
|
||||
#[derive(Clone)]
|
||||
pub struct AddressSpace {
|
||||
state: Arc<ArcSwap<AddressSpaceBase>>,
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
```rust
|
||||
// 1. create several memory regions
|
||||
let reg = Arc::new(
|
||||
AddressSpaceRegion::create_default_memory_region(
|
||||
GuestAddress(0x100000),
|
||||
0x100000,
|
||||
None,
|
||||
"shmem",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
let regions = vec![reg];
|
||||
// 2. create layout (depending on archs)
|
||||
let layout = AddressSpaceLayout::new(GUEST_PHYS_END, GUEST_MEM_START, GUEST_MEM_END);
|
||||
// 3. create address space from regions and layout
|
||||
let address_space = AddressSpace::from_regions(regions, layout.clone());
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0.
|
||||
830
src/dragonball/src/dbs_address_space/src/address_space.rs
Normal file
830
src/dragonball/src/dbs_address_space/src/address_space.rs
Normal file
@@ -0,0 +1,830 @@
|
||||
// Copyright (C) 2021 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Physical address space manager for virtual machines.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
use crate::{AddressSpaceError, AddressSpaceLayout, AddressSpaceRegion, AddressSpaceRegionType};
|
||||
|
||||
/// Base implementation to manage guest physical address space, without support of region hotplug.
|
||||
#[derive(Clone)]
|
||||
pub struct AddressSpaceBase {
|
||||
regions: Vec<Arc<AddressSpaceRegion>>,
|
||||
layout: AddressSpaceLayout,
|
||||
}
|
||||
|
||||
impl AddressSpaceBase {
|
||||
/// Create an instance of `AddressSpaceBase` from an `AddressSpaceRegion` array.
|
||||
///
|
||||
/// To achieve better performance by using binary search algorithm, the `regions` vector
|
||||
/// will gotten sorted by guest physical address.
|
||||
///
|
||||
/// Note, panicking if some regions intersects with each other.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `regions` - prepared regions to managed by the address space instance.
|
||||
/// * `layout` - prepared address space layout configuration.
|
||||
pub fn from_regions(
|
||||
mut regions: Vec<Arc<AddressSpaceRegion>>,
|
||||
layout: AddressSpaceLayout,
|
||||
) -> Self {
|
||||
regions.sort_unstable_by_key(|v| v.base);
|
||||
for region in regions.iter() {
|
||||
if !layout.is_region_valid(region) {
|
||||
panic!(
|
||||
"Invalid region {:?} for address space layout {:?}",
|
||||
region, layout
|
||||
);
|
||||
}
|
||||
}
|
||||
for idx in 1..regions.len() {
|
||||
if regions[idx].intersect_with(®ions[idx - 1]) {
|
||||
panic!("address space regions intersect with each other");
|
||||
}
|
||||
}
|
||||
AddressSpaceBase { regions, layout }
|
||||
}
|
||||
|
||||
/// Insert a new address space region into the address space.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `region` - the new region to be inserted.
|
||||
pub fn insert_region(
|
||||
&mut self,
|
||||
region: Arc<AddressSpaceRegion>,
|
||||
) -> Result<(), AddressSpaceError> {
|
||||
if !self.layout.is_region_valid(®ion) {
|
||||
return Err(AddressSpaceError::InvalidAddressRange(
|
||||
region.start_addr().0,
|
||||
region.len(),
|
||||
));
|
||||
}
|
||||
for idx in 0..self.regions.len() {
|
||||
if self.regions[idx].intersect_with(®ion) {
|
||||
return Err(AddressSpaceError::InvalidAddressRange(
|
||||
region.start_addr().0,
|
||||
region.len(),
|
||||
));
|
||||
}
|
||||
}
|
||||
self.regions.push(region);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enumerate all regions in the address space.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cb` - the callback function to apply to each region.
|
||||
pub fn walk_regions<F>(&self, mut cb: F) -> Result<(), AddressSpaceError>
|
||||
where
|
||||
F: FnMut(&Arc<AddressSpaceRegion>) -> Result<(), AddressSpaceError>,
|
||||
{
|
||||
for reg in self.regions.iter() {
|
||||
cb(reg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get address space layout associated with the address space.
|
||||
pub fn layout(&self) -> AddressSpaceLayout {
|
||||
self.layout.clone()
|
||||
}
|
||||
|
||||
/// Get maximum of guest physical address in the address space.
|
||||
pub fn last_addr(&self) -> GuestAddress {
|
||||
let mut last_addr = GuestAddress(self.layout.mem_start);
|
||||
for reg in self.regions.iter() {
|
||||
if reg.ty != AddressSpaceRegionType::DAXMemory && reg.last_addr() > last_addr {
|
||||
last_addr = reg.last_addr();
|
||||
}
|
||||
}
|
||||
last_addr
|
||||
}
|
||||
|
||||
/// Check whether the guest physical address `guest_addr` belongs to a DAX memory region.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `guest_addr` - the guest physical address to inquire
|
||||
pub fn is_dax_region(&self, guest_addr: GuestAddress) -> bool {
|
||||
for reg in self.regions.iter() {
|
||||
// Safe because we have validate the region when creating the address space object.
|
||||
if reg.region_type() == AddressSpaceRegionType::DAXMemory
|
||||
&& reg.start_addr() <= guest_addr
|
||||
&& reg.start_addr().0 + reg.len() > guest_addr.0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Get protection flags of memory region that guest physical address `guest_addr` belongs to.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `guest_addr` - the guest physical address to inquire
|
||||
pub fn prot_flags(&self, guest_addr: GuestAddress) -> Result<i32, AddressSpaceError> {
|
||||
for reg in self.regions.iter() {
|
||||
if reg.start_addr() <= guest_addr && reg.start_addr().0 + reg.len() > guest_addr.0 {
|
||||
return Ok(reg.prot_flags());
|
||||
}
|
||||
}
|
||||
|
||||
Err(AddressSpaceError::InvalidRegionType)
|
||||
}
|
||||
|
||||
/// Get optional NUMA node id associated with guest physical address `gpa`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `gpa` - guest physical address to query.
|
||||
pub fn numa_node_id(&self, gpa: u64) -> Option<u32> {
|
||||
for reg in self.regions.iter() {
|
||||
if gpa >= reg.base.0 && gpa < (reg.base.0 + reg.size) {
|
||||
return reg.host_numa_node_id;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An address space implementation with region hotplug capability.
|
||||
///
|
||||
/// The `AddressSpace` is a wrapper over [AddressSpaceBase] to support hotplug of
|
||||
/// address space regions.
|
||||
#[derive(Clone)]
|
||||
pub struct AddressSpace {
|
||||
state: Arc<ArcSwap<AddressSpaceBase>>,
|
||||
}
|
||||
|
||||
impl AddressSpace {
|
||||
/// Convert a [GuestMemoryMmap] object into `GuestMemoryAtomic<GuestMemoryMmap>`.
|
||||
pub fn convert_into_vm_as(
|
||||
gm: GuestMemoryMmap,
|
||||
) -> vm_memory::atomic::GuestMemoryAtomic<GuestMemoryMmap> {
|
||||
vm_memory::atomic::GuestMemoryAtomic::from(Arc::new(gm))
|
||||
}
|
||||
|
||||
/// Create an instance of `AddressSpace` from an `AddressSpaceRegion` array.
|
||||
///
|
||||
/// To achieve better performance by using binary search algorithm, the `regions` vector
|
||||
/// will gotten sorted by guest physical address.
|
||||
///
|
||||
/// Note, panicking if some regions intersects with each other.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `regions` - prepared regions to managed by the address space instance.
|
||||
/// * `layout` - prepared address space layout configuration.
|
||||
pub fn from_regions(regions: Vec<Arc<AddressSpaceRegion>>, layout: AddressSpaceLayout) -> Self {
|
||||
let base = AddressSpaceBase::from_regions(regions, layout);
|
||||
|
||||
AddressSpace {
|
||||
state: Arc::new(ArcSwap::new(Arc::new(base))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new address space region into the address space.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `region` - the new region to be inserted.
|
||||
pub fn insert_region(
|
||||
&mut self,
|
||||
region: Arc<AddressSpaceRegion>,
|
||||
) -> Result<(), AddressSpaceError> {
|
||||
let curr = self.state.load().regions.clone();
|
||||
let layout = self.state.load().layout.clone();
|
||||
let mut base = AddressSpaceBase::from_regions(curr, layout);
|
||||
base.insert_region(region)?;
|
||||
let _old = self.state.swap(Arc::new(base));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enumerate all regions in the address space.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cb` - the callback function to apply to each region.
|
||||
pub fn walk_regions<F>(&self, cb: F) -> Result<(), AddressSpaceError>
|
||||
where
|
||||
F: FnMut(&Arc<AddressSpaceRegion>) -> Result<(), AddressSpaceError>,
|
||||
{
|
||||
self.state.load().walk_regions(cb)
|
||||
}
|
||||
|
||||
/// Get address space layout associated with the address space.
|
||||
pub fn layout(&self) -> AddressSpaceLayout {
|
||||
self.state.load().layout()
|
||||
}
|
||||
|
||||
/// Get maximum of guest physical address in the address space.
|
||||
pub fn last_addr(&self) -> GuestAddress {
|
||||
self.state.load().last_addr()
|
||||
}
|
||||
|
||||
/// Check whether the guest physical address `guest_addr` belongs to a DAX memory region.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `guest_addr` - the guest physical address to inquire
|
||||
pub fn is_dax_region(&self, guest_addr: GuestAddress) -> bool {
|
||||
self.state.load().is_dax_region(guest_addr)
|
||||
}
|
||||
|
||||
/// Get protection flags of memory region that guest physical address `guest_addr` belongs to.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `guest_addr` - the guest physical address to inquire
|
||||
pub fn prot_flags(&self, guest_addr: GuestAddress) -> Result<i32, AddressSpaceError> {
|
||||
self.state.load().prot_flags(guest_addr)
|
||||
}
|
||||
|
||||
/// Get optional NUMA node id associated with guest physical address `gpa`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `gpa` - guest physical address to query.
|
||||
pub fn numa_node_id(&self, gpa: u64) -> Option<u32> {
|
||||
self.state.load().numa_node_id(gpa)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use vm_memory::GuestUsize;
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
// define macros for unit test
|
||||
const GUEST_PHYS_END: u64 = (1 << 46) - 1;
|
||||
const GUEST_MEM_START: u64 = 0;
|
||||
const GUEST_MEM_END: u64 = GUEST_PHYS_END >> 1;
|
||||
const GUEST_DEVICE_START: u64 = GUEST_MEM_END + 1;
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_from_regions() {
|
||||
let mut file = TempFile::new().unwrap().into_file();
|
||||
let sample_buf = &[1, 2, 3, 4, 5];
|
||||
assert!(file.write_all(sample_buf).is_ok());
|
||||
file.set_len(0x10000).unwrap();
|
||||
|
||||
let reg = Arc::new(
|
||||
AddressSpaceRegion::create_device_region(GuestAddress(GUEST_DEVICE_START), 0x1000)
|
||||
.unwrap(),
|
||||
);
|
||||
let regions = vec![reg];
|
||||
let layout = AddressSpaceLayout::new(GUEST_PHYS_END, GUEST_MEM_START, GUEST_MEM_END);
|
||||
let address_space = AddressSpaceBase::from_regions(regions, layout.clone());
|
||||
assert_eq!(address_space.layout(), layout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Invalid region")]
|
||||
fn test_address_space_base_from_regions_when_region_invalid() {
|
||||
let reg = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x1000,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x200, 0x1800);
|
||||
let _address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "address space regions intersect with each other")]
|
||||
fn test_address_space_base_from_regions_when_region_intersected() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x200),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let _address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_insert_region() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x100, 0x1800);
|
||||
let mut address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
|
||||
// Normal case.
|
||||
address_space.insert_region(reg2).unwrap();
|
||||
assert!(!address_space.regions[1].intersect_with(&address_space.regions[0]));
|
||||
|
||||
// Error invalid address range case when region invaled.
|
||||
let invalid_reg = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x0),
|
||||
0x100,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
address_space.insert_region(invalid_reg).err().unwrap()
|
||||
),
|
||||
format!("InvalidAddressRange({:?}, {:?})", 0x0, 0x100)
|
||||
);
|
||||
|
||||
// Error Error invalid address range case when region to be inserted will intersect
|
||||
// exsisting regions.
|
||||
let intersected_reg = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x400),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
address_space.insert_region(intersected_reg).err().unwrap()
|
||||
),
|
||||
format!("InvalidAddressRange({:?}, {:?})", 0x400, 0x200)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_walk_regions() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
|
||||
// The argument of walk_regions is a function which takes a &Arc<AddressSpaceRegion>
|
||||
// and returns result. This function will be applied to all regions.
|
||||
fn do_not_have_hotplug(region: &Arc<AddressSpaceRegion>) -> Result<(), AddressSpaceError> {
|
||||
if region.is_hotplug() {
|
||||
Err(AddressSpaceError::InvalidRegionType) // The Error type is dictated to AddressSpaceError.
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
assert!(matches!(
|
||||
address_space.walk_regions(do_not_have_hotplug).unwrap(),
|
||||
()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_last_addr() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
|
||||
assert_eq!(address_space.last_addr(), GuestAddress(0x500 - 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_is_dax_region() {
|
||||
let page_size = 4096;
|
||||
let address_space_region = vec![
|
||||
Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(page_size),
|
||||
page_size as GuestUsize,
|
||||
)),
|
||||
Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(page_size * 2),
|
||||
page_size as GuestUsize,
|
||||
)),
|
||||
Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DAXMemory,
|
||||
GuestAddress(GUEST_DEVICE_START),
|
||||
page_size as GuestUsize,
|
||||
)),
|
||||
];
|
||||
let layout = AddressSpaceLayout::new(GUEST_PHYS_END, GUEST_MEM_START, GUEST_MEM_END);
|
||||
let address_space = AddressSpaceBase::from_regions(address_space_region, layout);
|
||||
|
||||
assert!(!address_space.is_dax_region(GuestAddress(page_size)));
|
||||
assert!(!address_space.is_dax_region(GuestAddress(page_size * 2)));
|
||||
assert!(address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START)));
|
||||
assert!(address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START + 1)));
|
||||
assert!(!address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START + page_size)));
|
||||
assert!(address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START + page_size - 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_prot_flags() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
Some(0),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x300,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
|
||||
// Normal case, reg1.
|
||||
assert_eq!(address_space.prot_flags(GuestAddress(0x200)).unwrap(), 0);
|
||||
// Normal case, reg2.
|
||||
assert_eq!(
|
||||
address_space.prot_flags(GuestAddress(0x500)).unwrap(),
|
||||
libc::PROT_READ | libc::PROT_WRITE
|
||||
);
|
||||
// Inquire gpa where no region is set.
|
||||
assert!(matches!(
|
||||
address_space.prot_flags(GuestAddress(0x600)),
|
||||
Err(AddressSpaceError::InvalidRegionType)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_base_numa_node_id() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
Some(0),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x300,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpaceBase::from_regions(regions, layout);
|
||||
|
||||
// Normal case.
|
||||
assert_eq!(address_space.numa_node_id(0x200).unwrap(), 0);
|
||||
// Inquire region with None as its numa node id.
|
||||
assert_eq!(address_space.numa_node_id(0x400), None);
|
||||
// Inquire gpa where no region is set.
|
||||
assert_eq!(address_space.numa_node_id(0x600), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_convert_into_vm_as() {
|
||||
// ! Further and detailed test is needed here.
|
||||
let gmm = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0x0), 0x400)]).unwrap();
|
||||
let _vm = AddressSpace::convert_into_vm_as(gmm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_insert_region() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x100, 0x1800);
|
||||
let mut address_space = AddressSpace::from_regions(regions, layout);
|
||||
|
||||
// Normal case.
|
||||
assert!(matches!(address_space.insert_region(reg2).unwrap(), ()));
|
||||
|
||||
// Error invalid address range case when region invaled.
|
||||
let invalid_reg = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x0),
|
||||
0x100,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
address_space.insert_region(invalid_reg).err().unwrap()
|
||||
),
|
||||
format!("InvalidAddressRange({:?}, {:?})", 0x0, 0x100)
|
||||
);
|
||||
|
||||
// Error Error invalid address range case when region to be inserted will intersect
|
||||
// exsisting regions.
|
||||
let intersected_reg = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x400),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
address_space.insert_region(intersected_reg).err().unwrap()
|
||||
),
|
||||
format!("InvalidAddressRange({:?}, {:?})", 0x400, 0x200)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_walk_regions() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpace::from_regions(regions, layout);
|
||||
|
||||
fn access_all_hotplug_flag(
|
||||
region: &Arc<AddressSpaceRegion>,
|
||||
) -> Result<(), AddressSpaceError> {
|
||||
region.is_hotplug();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
assert!(matches!(
|
||||
address_space.walk_regions(access_all_hotplug_flag).unwrap(),
|
||||
()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_layout() {
|
||||
let reg = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x1000,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpace::from_regions(regions, layout.clone());
|
||||
|
||||
assert_eq!(layout, address_space.layout());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_last_addr() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x200,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpace::from_regions(regions, layout);
|
||||
|
||||
assert_eq!(address_space.last_addr(), GuestAddress(0x500 - 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_is_dax_region() {
|
||||
let page_size = 4096;
|
||||
let address_space_region = vec![
|
||||
Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(page_size),
|
||||
page_size as GuestUsize,
|
||||
)),
|
||||
Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(page_size * 2),
|
||||
page_size as GuestUsize,
|
||||
)),
|
||||
Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DAXMemory,
|
||||
GuestAddress(GUEST_DEVICE_START),
|
||||
page_size as GuestUsize,
|
||||
)),
|
||||
];
|
||||
let layout = AddressSpaceLayout::new(GUEST_PHYS_END, GUEST_MEM_START, GUEST_MEM_END);
|
||||
let address_space = AddressSpace::from_regions(address_space_region, layout);
|
||||
|
||||
assert!(!address_space.is_dax_region(GuestAddress(page_size)));
|
||||
assert!(!address_space.is_dax_region(GuestAddress(page_size * 2)));
|
||||
assert!(address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START)));
|
||||
assert!(address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START + 1)));
|
||||
assert!(!address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START + page_size)));
|
||||
assert!(address_space.is_dax_region(GuestAddress(GUEST_DEVICE_START + page_size - 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_prot_flags() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
Some(0),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x300,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpace::from_regions(regions, layout);
|
||||
|
||||
// Normal case, reg1.
|
||||
assert_eq!(address_space.prot_flags(GuestAddress(0x200)).unwrap(), 0);
|
||||
// Normal case, reg2.
|
||||
assert_eq!(
|
||||
address_space.prot_flags(GuestAddress(0x500)).unwrap(),
|
||||
libc::PROT_READ | libc::PROT_WRITE
|
||||
);
|
||||
// Inquire gpa where no region is set.
|
||||
assert!(matches!(
|
||||
address_space.prot_flags(GuestAddress(0x600)),
|
||||
Err(AddressSpaceError::InvalidRegionType)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_numa_node_id() {
|
||||
let reg1 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x100),
|
||||
0x200,
|
||||
Some(0),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let reg2 = Arc::new(AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x300),
|
||||
0x300,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
));
|
||||
let regions = vec![reg1, reg2];
|
||||
let layout = AddressSpaceLayout::new(0x2000, 0x0, 0x1800);
|
||||
let address_space = AddressSpace::from_regions(regions, layout);
|
||||
|
||||
// Normal case.
|
||||
assert_eq!(address_space.numa_node_id(0x200).unwrap(), 0);
|
||||
// Inquire region with None as its numa node id.
|
||||
assert_eq!(address_space.numa_node_id(0x400), None);
|
||||
// Inquire gpa where no region is set.
|
||||
assert_eq!(address_space.numa_node_id(0x600), None);
|
||||
}
|
||||
}
|
||||
154
src/dragonball/src/dbs_address_space/src/layout.rs
Normal file
154
src/dragonball/src/dbs_address_space/src/layout.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (C) 2021 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::{AddressSpaceRegion, AddressSpaceRegionType};
|
||||
|
||||
// Max retry times for reading /proc
|
||||
const PROC_READ_RETRY: u64 = 5;
|
||||
|
||||
lazy_static! {
|
||||
/// Upper bound of host memory.
|
||||
pub static ref USABLE_END: u64 = {
|
||||
for _ in 0..PROC_READ_RETRY {
|
||||
if let Ok(buf) = std::fs::read("/proc/meminfo") {
|
||||
let content = String::from_utf8_lossy(&buf);
|
||||
for line in content.lines() {
|
||||
if line.starts_with("MemTotal:") {
|
||||
if let Some(end) = line.find(" kB") {
|
||||
if let Ok(size) = line[9..end].trim().parse::<u64>() {
|
||||
return (size << 10) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("Exceed max retry times. Cannot get total mem size from /proc/meminfo");
|
||||
};
|
||||
}
|
||||
|
||||
/// Address space layout configuration.
|
||||
///
|
||||
/// The layout configuration must guarantee that `mem_start` <= `mem_end` <= `phys_end`.
|
||||
/// Non-memory region should be arranged into the range [mem_end, phys_end).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AddressSpaceLayout {
|
||||
/// end of guest physical address
|
||||
pub phys_end: u64,
|
||||
/// start of guest memory address
|
||||
pub mem_start: u64,
|
||||
/// end of guest memory address
|
||||
pub mem_end: u64,
|
||||
/// end of usable memory address
|
||||
pub usable_end: u64,
|
||||
}
|
||||
|
||||
impl AddressSpaceLayout {
|
||||
/// Create a new instance of `AddressSpaceLayout`.
|
||||
pub fn new(phys_end: u64, mem_start: u64, mem_end: u64) -> Self {
|
||||
AddressSpaceLayout {
|
||||
phys_end,
|
||||
mem_start,
|
||||
mem_end,
|
||||
usable_end: *USABLE_END,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether an region is valid with the constraints of the layout.
|
||||
pub fn is_region_valid(&self, region: &AddressSpaceRegion) -> bool {
|
||||
let region_end = match region.base.0.checked_add(region.size) {
|
||||
None => return false,
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
match region.ty {
|
||||
AddressSpaceRegionType::DefaultMemory => {
|
||||
if region.base.0 < self.mem_start || region_end > self.mem_end {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
AddressSpaceRegionType::DeviceMemory | AddressSpaceRegionType::DAXMemory => {
|
||||
if region.base.0 < self.mem_end || region_end > self.phys_end {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[test]
|
||||
fn test_is_region_valid() {
|
||||
let layout = AddressSpaceLayout::new(0x1_0000_0000, 0x1000_0000, 0x2000_0000);
|
||||
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x0),
|
||||
0x1_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x2000_0000),
|
||||
0x1_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x1_0000),
|
||||
0x2000_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(u64::MAX),
|
||||
0x1_0000_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x1000_0000),
|
||||
0x1_0000,
|
||||
);
|
||||
assert!(layout.is_region_valid(®ion));
|
||||
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
GuestAddress(0x1000_0000),
|
||||
0x1_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
GuestAddress(0x1_0000_0000),
|
||||
0x1_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
GuestAddress(0x1_0000),
|
||||
0x1_0000_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
GuestAddress(u64::MAX),
|
||||
0x1_0000_0000,
|
||||
);
|
||||
assert!(!layout.is_region_valid(®ion));
|
||||
let region = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
GuestAddress(0x8000_0000),
|
||||
0x1_0000,
|
||||
);
|
||||
assert!(layout.is_region_valid(®ion));
|
||||
}
|
||||
}
|
||||
87
src/dragonball/src/dbs_address_space/src/lib.rs
Normal file
87
src/dragonball/src/dbs_address_space/src/lib.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2021 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Traits and Structs to manage guest physical address space for virtual machines.
|
||||
//!
|
||||
//! The [vm-memory](https://crates.io/crates/vm-memory) implements mechanisms to manage and access
|
||||
//! guest memory resident in guest physical address space. In addition to guest memory, there may
|
||||
//! be other type of devices resident in the same guest physical address space.
|
||||
//!
|
||||
//! The `dbs-address-space` crate provides traits and structs to manage the guest physical address
|
||||
//! space for virtual machines, and mechanisms to coordinate all the devices resident in the
|
||||
//! guest physical address space.
|
||||
|
||||
use vm_memory::GuestUsize;
|
||||
|
||||
mod address_space;
|
||||
pub use self::address_space::{AddressSpace, AddressSpaceBase};
|
||||
|
||||
mod layout;
|
||||
pub use layout::{AddressSpaceLayout, USABLE_END};
|
||||
|
||||
mod memory;
|
||||
pub use memory::{GuestMemoryHybrid, GuestMemoryManager, GuestRegionHybrid, GuestRegionRaw};
|
||||
|
||||
mod numa;
|
||||
pub use self::numa::{NumaIdTable, NumaNode, NumaNodeInfo, MPOL_MF_MOVE, MPOL_PREFERRED};
|
||||
|
||||
mod region;
|
||||
pub use region::{AddressSpaceRegion, AddressSpaceRegionType};
|
||||
|
||||
/// Errors associated with virtual machine address space management.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AddressSpaceError {
|
||||
/// Invalid address space region type.
|
||||
#[error("invalid address space region type")]
|
||||
InvalidRegionType,
|
||||
|
||||
/// Invalid address range.
|
||||
#[error("invalid address space region (0x{0:x}, 0x{1:x})")]
|
||||
InvalidAddressRange(u64, GuestUsize),
|
||||
|
||||
/// Invalid guest memory source type.
|
||||
#[error("invalid memory source type {0}")]
|
||||
InvalidMemorySourceType(String),
|
||||
|
||||
/// Failed to create memfd to map anonymous memory.
|
||||
#[error("can not create memfd to map anonymous memory")]
|
||||
CreateMemFd(#[source] nix::Error),
|
||||
|
||||
/// Failed to open memory file.
|
||||
#[error("can not open memory file")]
|
||||
OpenFile(#[source] std::io::Error),
|
||||
|
||||
/// Failed to create directory.
|
||||
#[error("can not create directory")]
|
||||
CreateDir(#[source] std::io::Error),
|
||||
|
||||
/// Failed to set size for memory file.
|
||||
#[error("can not set size for memory file")]
|
||||
SetFileSize(#[source] std::io::Error),
|
||||
|
||||
/// Failed to unlink memory file.
|
||||
#[error("can not unlink memory file")]
|
||||
UnlinkFile(#[source] nix::Error),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_error_code() {
|
||||
let e = AddressSpaceError::InvalidRegionType;
|
||||
|
||||
assert_eq!(format!("{e}"), "invalid address space region type");
|
||||
assert_eq!(format!("{e:?}"), "InvalidRegionType");
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
AddressSpaceError::InvalidMemorySourceType("test".to_string())
|
||||
),
|
||||
"invalid memory source type test"
|
||||
);
|
||||
}
|
||||
}
|
||||
1105
src/dragonball/src/dbs_address_space/src/memory/hybrid.rs
Normal file
1105
src/dragonball/src/dbs_address_space/src/memory/hybrid.rs
Normal file
File diff suppressed because it is too large
Load Diff
193
src/dragonball/src/dbs_address_space/src/memory/mod.rs
Normal file
193
src/dragonball/src/dbs_address_space/src/memory/mod.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Structs to manage guest memory for virtual machines.
|
||||
//!
|
||||
//! The `vm-memory` crate only provides traits and structs to access normal guest memory,
|
||||
//! it doesn't support special guest memory like virtio-fs/virtio-pmem DAX window etc.
|
||||
//! So this crate provides `GuestMemoryManager` over `vm-memory` to provide uniform abstraction
|
||||
//! for all guest memory.
|
||||
//!
|
||||
//! It also provides interfaces to coordinate guest memory hotplug events.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap};
|
||||
|
||||
mod raw_region;
|
||||
pub use raw_region::GuestRegionRaw;
|
||||
|
||||
mod hybrid;
|
||||
pub use hybrid::{GuestMemoryHybrid, GuestRegionHybrid};
|
||||
|
||||
/// Type of source to allocate memory for virtual machines.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum MemorySourceType {
|
||||
/// File on HugeTlbFs.
|
||||
FileOnHugeTlbFs,
|
||||
/// mmap() without flag `MAP_HUGETLB`.
|
||||
MmapAnonymous,
|
||||
/// mmap() with flag `MAP_HUGETLB`.
|
||||
MmapAnonymousHugeTlbFs,
|
||||
/// memfd() without flag `MFD_HUGETLB`.
|
||||
MemFdShared,
|
||||
/// memfd() with flag `MFD_HUGETLB`.
|
||||
MemFdOnHugeTlbFs,
|
||||
}
|
||||
|
||||
impl MemorySourceType {
|
||||
/// Check whether the memory source is huge page.
|
||||
pub fn is_hugepage(&self) -> bool {
|
||||
*self == Self::FileOnHugeTlbFs
|
||||
|| *self == Self::MmapAnonymousHugeTlbFs
|
||||
|| *self == Self::MemFdOnHugeTlbFs
|
||||
}
|
||||
|
||||
/// Check whether the memory source is anonymous memory.
|
||||
pub fn is_mmap_anonymous(&self) -> bool {
|
||||
*self == Self::MmapAnonymous || *self == Self::MmapAnonymousHugeTlbFs
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MemorySourceType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"hugetlbfs" => Ok(MemorySourceType::FileOnHugeTlbFs),
|
||||
"memfd" => Ok(MemorySourceType::MemFdShared),
|
||||
"shmem" => Ok(MemorySourceType::MemFdShared),
|
||||
"hugememfd" => Ok(MemorySourceType::MemFdOnHugeTlbFs),
|
||||
"hugeshmem" => Ok(MemorySourceType::MemFdOnHugeTlbFs),
|
||||
"anon" => Ok(MemorySourceType::MmapAnonymous),
|
||||
"mmap" => Ok(MemorySourceType::MmapAnonymous),
|
||||
"hugeanon" => Ok(MemorySourceType::MmapAnonymousHugeTlbFs),
|
||||
"hugemmap" => Ok(MemorySourceType::MmapAnonymousHugeTlbFs),
|
||||
_ => Err(format!("unknown memory source type {s}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct GuestMemoryHotplugManager {}
|
||||
|
||||
/// The `GuestMemoryManager` manages all guest memory for virtual machines.
|
||||
///
|
||||
/// The `GuestMemoryManager` fulfills several different responsibilities.
|
||||
/// - First, it manages different types of guest memory, such as normal guest memory, virtio-fs
|
||||
/// DAX window and virtio-pmem DAX window etc. Different clients may want to access different
|
||||
/// types of memory. So the manager maintains two GuestMemory objects, one contains all guest
|
||||
/// memory, the other contains only normal guest memory.
|
||||
/// - Second, it coordinates memory/DAX window hotplug events, so clients may register hooks
|
||||
/// to receive hotplug notifications.
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GuestMemoryManager {
|
||||
default: GuestMemoryAtomic<GuestMemoryHybrid>,
|
||||
/// GuestMemory object hosts all guest memory.
|
||||
hybrid: GuestMemoryAtomic<GuestMemoryHybrid>,
|
||||
/// GuestMemory object for vIOMMU.
|
||||
iommu: GuestMemoryAtomic<GuestMemoryHybrid>,
|
||||
/// GuestMemory object hosts normal guest memory.
|
||||
normal: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
hotplug: Arc<GuestMemoryHotplugManager>,
|
||||
}
|
||||
|
||||
impl GuestMemoryManager {
|
||||
/// Create a new instance of `GuestMemoryManager`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Get a reference to the normal `GuestMemory` object.
|
||||
pub fn get_normal_guest_memory(&self) -> &GuestMemoryAtomic<GuestMemoryMmap> {
|
||||
&self.normal
|
||||
}
|
||||
|
||||
/// Try to downcast the `GuestAddressSpace` object to a `GuestMemoryManager` object.
|
||||
pub fn to_manager<AS: GuestAddressSpace>(_m: &AS) -> Option<&Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GuestMemoryManager {
|
||||
fn default() -> Self {
|
||||
let hybrid = GuestMemoryAtomic::new(GuestMemoryHybrid::new());
|
||||
let iommu = GuestMemoryAtomic::new(GuestMemoryHybrid::new());
|
||||
let normal = GuestMemoryAtomic::new(GuestMemoryMmap::new());
|
||||
// By default, it provides to the `GuestMemoryHybrid` object containing all guest memory.
|
||||
let default = hybrid.clone();
|
||||
|
||||
GuestMemoryManager {
|
||||
default,
|
||||
hybrid,
|
||||
iommu,
|
||||
normal,
|
||||
hotplug: Arc::new(GuestMemoryHotplugManager::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestAddressSpace for GuestMemoryManager {
|
||||
type M = GuestMemoryHybrid;
|
||||
type T = GuestMemoryLoadGuard<GuestMemoryHybrid>;
|
||||
|
||||
fn memory(&self) -> Self::T {
|
||||
self.default.memory()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_memory_source_type() {
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("hugetlbfs").unwrap(),
|
||||
MemorySourceType::FileOnHugeTlbFs
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("memfd").unwrap(),
|
||||
MemorySourceType::MemFdShared
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("shmem").unwrap(),
|
||||
MemorySourceType::MemFdShared
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("hugememfd").unwrap(),
|
||||
MemorySourceType::MemFdOnHugeTlbFs
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("hugeshmem").unwrap(),
|
||||
MemorySourceType::MemFdOnHugeTlbFs
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("anon").unwrap(),
|
||||
MemorySourceType::MmapAnonymous
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("mmap").unwrap(),
|
||||
MemorySourceType::MmapAnonymous
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("hugeanon").unwrap(),
|
||||
MemorySourceType::MmapAnonymousHugeTlbFs
|
||||
);
|
||||
assert_eq!(
|
||||
MemorySourceType::from_str("hugemmap").unwrap(),
|
||||
MemorySourceType::MmapAnonymousHugeTlbFs
|
||||
);
|
||||
assert!(MemorySourceType::from_str("test").is_err());
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_to_manager() {
|
||||
let manager = GuestMemoryManager::new();
|
||||
let mgr = GuestMemoryManager::to_manager(&manager).unwrap();
|
||||
|
||||
assert_eq!(&manager as *const _, mgr as *const _);
|
||||
}
|
||||
}
|
||||
990
src/dragonball/src/dbs_address_space/src/memory/raw_region.rs
Normal file
990
src/dragonball/src/dbs_address_space/src/memory/raw_region.rs
Normal file
@@ -0,0 +1,990 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use vm_memory::bitmap::{Bitmap, BS};
|
||||
use vm_memory::mmap::NewBitmap;
|
||||
use vm_memory::volatile_memory::compute_offset;
|
||||
use vm_memory::{
|
||||
guest_memory, volatile_memory, Address, AtomicAccess, Bytes, FileOffset, GuestAddress,
|
||||
GuestMemoryRegion, GuestUsize, MemoryRegionAddress, VolatileSlice,
|
||||
};
|
||||
|
||||
/// Guest memory region for virtio-fs DAX window.
|
||||
#[derive(Debug)]
|
||||
pub struct GuestRegionRaw<B = ()> {
|
||||
guest_base: GuestAddress,
|
||||
addr: *mut u8,
|
||||
size: usize,
|
||||
bitmap: B,
|
||||
}
|
||||
|
||||
impl<B: NewBitmap> GuestRegionRaw<B> {
|
||||
/// Create a `GuestRegionRaw` object from raw pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// Caller needs to ensure `addr` and `size` are valid with static lifetime.
|
||||
pub unsafe fn new(guest_base: GuestAddress, addr: *mut u8, size: usize) -> Self {
|
||||
let bitmap = B::with_len(size);
|
||||
|
||||
GuestRegionRaw {
|
||||
guest_base,
|
||||
addr,
|
||||
size,
|
||||
bitmap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Bitmap> Bytes<MemoryRegionAddress> for GuestRegionRaw<B> {
|
||||
type E = guest_memory::Error;
|
||||
|
||||
fn write(&self, buf: &[u8], addr: MemoryRegionAddress) -> guest_memory::Result<usize> {
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.write(buf, maddr)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn read(&self, buf: &mut [u8], addr: MemoryRegionAddress) -> guest_memory::Result<usize> {
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.read(buf, maddr)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn write_slice(&self, buf: &[u8], addr: MemoryRegionAddress) -> guest_memory::Result<()> {
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.write_slice(buf, maddr)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn read_slice(&self, buf: &mut [u8], addr: MemoryRegionAddress) -> guest_memory::Result<()> {
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.read_slice(buf, maddr)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn read_from<F>(
|
||||
&self,
|
||||
addr: MemoryRegionAddress,
|
||||
src: &mut F,
|
||||
count: usize,
|
||||
) -> guest_memory::Result<usize>
|
||||
where
|
||||
F: Read,
|
||||
{
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.read_from::<F>(maddr, src, count)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn read_exact_from<F>(
|
||||
&self,
|
||||
addr: MemoryRegionAddress,
|
||||
src: &mut F,
|
||||
count: usize,
|
||||
) -> guest_memory::Result<()>
|
||||
where
|
||||
F: Read,
|
||||
{
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.read_exact_from::<F>(maddr, src, count)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn write_to<F>(
|
||||
&self,
|
||||
addr: MemoryRegionAddress,
|
||||
dst: &mut F,
|
||||
count: usize,
|
||||
) -> guest_memory::Result<usize>
|
||||
where
|
||||
F: Write,
|
||||
{
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.write_to::<F>(maddr, dst, count)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn write_all_to<F>(
|
||||
&self,
|
||||
addr: MemoryRegionAddress,
|
||||
dst: &mut F,
|
||||
count: usize,
|
||||
) -> guest_memory::Result<()>
|
||||
where
|
||||
F: Write,
|
||||
{
|
||||
let maddr = addr.raw_value() as usize;
|
||||
self.as_volatile_slice()
|
||||
.unwrap()
|
||||
.write_all_to::<F>(maddr, dst, count)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn store<T: AtomicAccess>(
|
||||
&self,
|
||||
val: T,
|
||||
addr: MemoryRegionAddress,
|
||||
order: Ordering,
|
||||
) -> guest_memory::Result<()> {
|
||||
self.as_volatile_slice().and_then(|s| {
|
||||
s.store(val, addr.raw_value() as usize, order)
|
||||
.map_err(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
fn load<T: AtomicAccess>(
|
||||
&self,
|
||||
addr: MemoryRegionAddress,
|
||||
order: Ordering,
|
||||
) -> guest_memory::Result<T> {
|
||||
self.as_volatile_slice()
|
||||
.and_then(|s| s.load(addr.raw_value() as usize, order).map_err(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Bitmap> GuestMemoryRegion for GuestRegionRaw<B> {
|
||||
type B = B;
|
||||
|
||||
fn len(&self) -> GuestUsize {
|
||||
self.size as GuestUsize
|
||||
}
|
||||
|
||||
fn start_addr(&self) -> GuestAddress {
|
||||
self.guest_base
|
||||
}
|
||||
|
||||
fn bitmap(&self) -> &Self::B {
|
||||
&self.bitmap
|
||||
}
|
||||
|
||||
fn get_host_address(&self, addr: MemoryRegionAddress) -> guest_memory::Result<*mut u8> {
|
||||
// Not sure why wrapping_offset is not unsafe. Anyway this
|
||||
// is safe because we've just range-checked addr using check_address.
|
||||
self.check_address(addr)
|
||||
.ok_or(guest_memory::Error::InvalidBackendAddress)
|
||||
.map(|addr| self.addr.wrapping_offset(addr.raw_value() as isize))
|
||||
}
|
||||
|
||||
fn file_offset(&self) -> Option<&FileOffset> {
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn as_slice(&self) -> Option<&[u8]> {
|
||||
// This is safe because we mapped the area at addr ourselves, so this slice will not
|
||||
// overflow. However, it is possible to alias.
|
||||
Some(std::slice::from_raw_parts(self.addr, self.size))
|
||||
}
|
||||
|
||||
unsafe fn as_mut_slice(&self) -> Option<&mut [u8]> {
|
||||
// This is safe because we mapped the area at addr ourselves, so this slice will not
|
||||
// overflow. However, it is possible to alias.
|
||||
Some(std::slice::from_raw_parts_mut(self.addr, self.size))
|
||||
}
|
||||
|
||||
fn get_slice(
|
||||
&self,
|
||||
offset: MemoryRegionAddress,
|
||||
count: usize,
|
||||
) -> guest_memory::Result<VolatileSlice<BS<B>>> {
|
||||
let offset = offset.raw_value() as usize;
|
||||
let end = compute_offset(offset, count)?;
|
||||
if end > self.size {
|
||||
return Err(volatile_memory::Error::OutOfBounds { addr: end }.into());
|
||||
}
|
||||
|
||||
// Safe because we checked that offset + count was within our range and we only ever hand
|
||||
// out volatile accessors.
|
||||
Ok(unsafe {
|
||||
VolatileSlice::with_bitmap(
|
||||
(self.addr as usize + offset) as *mut _,
|
||||
count,
|
||||
self.bitmap.slice_at(offset),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_hugetlbfs(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
use super::*;
|
||||
use crate::{GuestMemoryHybrid, GuestRegionHybrid};
|
||||
use std::sync::Arc;
|
||||
use vm_memory::{GuestAddressSpace, GuestMemory, VolatileMemory};
|
||||
|
||||
/*
|
||||
use crate::bitmap::tests::test_guest_memory_and_region;
|
||||
use crate::bitmap::AtomicBitmap;
|
||||
use crate::GuestAddressSpace;
|
||||
|
||||
use std::fs::File;
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
type GuestMemoryMmap = super::GuestMemoryMmap<()>;
|
||||
type GuestRegionMmap = super::GuestRegionMmap<()>;
|
||||
type MmapRegion = super::MmapRegion<()>;
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_region_raw_new() {
|
||||
let mut buf = [0u8; 1024];
|
||||
let m =
|
||||
unsafe { GuestRegionRaw::<()>::new(GuestAddress(0x10_0000), &mut buf as *mut _, 1024) };
|
||||
|
||||
assert_eq!(m.start_addr(), GuestAddress(0x10_0000));
|
||||
assert_eq!(m.len(), 1024);
|
||||
}
|
||||
|
||||
/*
|
||||
fn check_guest_memory_mmap(
|
||||
maybe_guest_mem: Result<GuestMemoryMmap, Error>,
|
||||
expected_regions_summary: &[(GuestAddress, usize)],
|
||||
) {
|
||||
assert!(maybe_guest_mem.is_ok());
|
||||
|
||||
let guest_mem = maybe_guest_mem.unwrap();
|
||||
assert_eq!(guest_mem.num_regions(), expected_regions_summary.len());
|
||||
let maybe_last_mem_reg = expected_regions_summary.last();
|
||||
if let Some((region_addr, region_size)) = maybe_last_mem_reg {
|
||||
let mut last_addr = region_addr.unchecked_add(*region_size as u64);
|
||||
if last_addr.raw_value() != 0 {
|
||||
last_addr = last_addr.unchecked_sub(1);
|
||||
}
|
||||
assert_eq!(guest_mem.last_addr(), last_addr);
|
||||
}
|
||||
for ((region_addr, region_size), mmap) in expected_regions_summary
|
||||
.iter()
|
||||
.zip(guest_mem.regions.iter())
|
||||
{
|
||||
assert_eq!(region_addr, &mmap.guest_base);
|
||||
assert_eq!(region_size, &mmap.mapping.size());
|
||||
|
||||
assert!(guest_mem.find_region(*region_addr).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
fn new_guest_memory_mmap(
|
||||
regions_summary: &[(GuestAddress, usize)],
|
||||
) -> Result<GuestMemoryMmap, Error> {
|
||||
GuestMemoryMmap::from_ranges(regions_summary)
|
||||
}
|
||||
|
||||
fn new_guest_memory_mmap_from_regions(
|
||||
regions_summary: &[(GuestAddress, usize)],
|
||||
) -> Result<GuestMemoryMmap, Error> {
|
||||
GuestMemoryMmap::from_regions(
|
||||
regions_summary
|
||||
.iter()
|
||||
.map(|(region_addr, region_size)| {
|
||||
GuestRegionMmap::new(MmapRegion::new(*region_size).unwrap(), *region_addr)
|
||||
.unwrap()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_guest_memory_mmap_from_arc_regions(
|
||||
regions_summary: &[(GuestAddress, usize)],
|
||||
) -> Result<GuestMemoryMmap, Error> {
|
||||
GuestMemoryMmap::from_arc_regions(
|
||||
regions_summary
|
||||
.iter()
|
||||
.map(|(region_addr, region_size)| {
|
||||
Arc::new(
|
||||
GuestRegionMmap::new(MmapRegion::new(*region_size).unwrap(), *region_addr)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_guest_memory_mmap_with_files(
|
||||
regions_summary: &[(GuestAddress, usize)],
|
||||
) -> Result<GuestMemoryMmap, Error> {
|
||||
let regions: Vec<(GuestAddress, usize, Option<FileOffset>)> = regions_summary
|
||||
.iter()
|
||||
.map(|(region_addr, region_size)| {
|
||||
let f = TempFile::new().unwrap().into_file();
|
||||
f.set_len(*region_size as u64).unwrap();
|
||||
|
||||
(*region_addr, *region_size, Some(FileOffset::new(f, 0)))
|
||||
})
|
||||
.collect();
|
||||
|
||||
GuestMemoryMmap::from_ranges_with_files(®ions)
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn slice_addr() {
|
||||
let mut buf = [0u8; 1024];
|
||||
let m =
|
||||
unsafe { GuestRegionRaw::<()>::new(GuestAddress(0x10_0000), &mut buf as *mut _, 1024) };
|
||||
|
||||
let s = m.get_slice(MemoryRegionAddress(2), 3).unwrap();
|
||||
assert_eq!(s.as_ptr(), &mut buf[2] as *mut _);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn test_address_in_range() {
|
||||
let f1 = TempFile::new().unwrap().into_file();
|
||||
f1.set_len(0x400).unwrap();
|
||||
let f2 = TempFile::new().unwrap().into_file();
|
||||
f2.set_len(0x400).unwrap();
|
||||
|
||||
let start_addr1 = GuestAddress(0x0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
let guest_mem =
|
||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
|
||||
let guest_mem_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[
|
||||
(start_addr1, 0x400, Some(FileOffset::new(f1, 0))),
|
||||
(start_addr2, 0x400, Some(FileOffset::new(f2, 0))),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let guest_mem_list = vec![guest_mem, guest_mem_backed_by_file];
|
||||
for guest_mem in guest_mem_list.iter() {
|
||||
assert!(guest_mem.address_in_range(GuestAddress(0x200)));
|
||||
assert!(!guest_mem.address_in_range(GuestAddress(0x600)));
|
||||
assert!(guest_mem.address_in_range(GuestAddress(0xa00)));
|
||||
assert!(!guest_mem.address_in_range(GuestAddress(0xc00)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_address() {
|
||||
let f1 = TempFile::new().unwrap().into_file();
|
||||
f1.set_len(0x400).unwrap();
|
||||
let f2 = TempFile::new().unwrap().into_file();
|
||||
f2.set_len(0x400).unwrap();
|
||||
|
||||
let start_addr1 = GuestAddress(0x0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
let guest_mem =
|
||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
|
||||
let guest_mem_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[
|
||||
(start_addr1, 0x400, Some(FileOffset::new(f1, 0))),
|
||||
(start_addr2, 0x400, Some(FileOffset::new(f2, 0))),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let guest_mem_list = vec![guest_mem, guest_mem_backed_by_file];
|
||||
for guest_mem in guest_mem_list.iter() {
|
||||
assert_eq!(
|
||||
guest_mem.check_address(GuestAddress(0x200)),
|
||||
Some(GuestAddress(0x200))
|
||||
);
|
||||
assert_eq!(guest_mem.check_address(GuestAddress(0x600)), None);
|
||||
assert_eq!(
|
||||
guest_mem.check_address(GuestAddress(0xa00)),
|
||||
Some(GuestAddress(0xa00))
|
||||
);
|
||||
assert_eq!(guest_mem.check_address(GuestAddress(0xc00)), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_region_addr() {
|
||||
let f1 = TempFile::new().unwrap().into_file();
|
||||
f1.set_len(0x400).unwrap();
|
||||
let f2 = TempFile::new().unwrap().into_file();
|
||||
f2.set_len(0x400).unwrap();
|
||||
|
||||
let start_addr1 = GuestAddress(0x0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
let guest_mem =
|
||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
|
||||
let guest_mem_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[
|
||||
(start_addr1, 0x400, Some(FileOffset::new(f1, 0))),
|
||||
(start_addr2, 0x400, Some(FileOffset::new(f2, 0))),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let guest_mem_list = vec![guest_mem, guest_mem_backed_by_file];
|
||||
for guest_mem in guest_mem_list.iter() {
|
||||
assert!(guest_mem.to_region_addr(GuestAddress(0x600)).is_none());
|
||||
let (r0, addr0) = guest_mem.to_region_addr(GuestAddress(0x800)).unwrap();
|
||||
let (r1, addr1) = guest_mem.to_region_addr(GuestAddress(0xa00)).unwrap();
|
||||
assert!(r0.as_ptr() == r1.as_ptr());
|
||||
assert_eq!(addr0, MemoryRegionAddress(0));
|
||||
assert_eq!(addr1, MemoryRegionAddress(0x200));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_host_address() {
|
||||
let f1 = TempFile::new().unwrap().into_file();
|
||||
f1.set_len(0x400).unwrap();
|
||||
let f2 = TempFile::new().unwrap().into_file();
|
||||
f2.set_len(0x400).unwrap();
|
||||
|
||||
let start_addr1 = GuestAddress(0x0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
let guest_mem =
|
||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
|
||||
let guest_mem_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[
|
||||
(start_addr1, 0x400, Some(FileOffset::new(f1, 0))),
|
||||
(start_addr2, 0x400, Some(FileOffset::new(f2, 0))),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let guest_mem_list = vec![guest_mem, guest_mem_backed_by_file];
|
||||
for guest_mem in guest_mem_list.iter() {
|
||||
assert!(guest_mem.get_host_address(GuestAddress(0x600)).is_err());
|
||||
let ptr0 = guest_mem.get_host_address(GuestAddress(0x800)).unwrap();
|
||||
let ptr1 = guest_mem.get_host_address(GuestAddress(0xa00)).unwrap();
|
||||
assert_eq!(
|
||||
ptr0,
|
||||
guest_mem.find_region(GuestAddress(0x800)).unwrap().as_ptr()
|
||||
);
|
||||
assert_eq!(unsafe { ptr0.offset(0x200) }, ptr1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deref() {
|
||||
let f = TempFile::new().unwrap().into_file();
|
||||
f.set_len(0x400).unwrap();
|
||||
|
||||
let start_addr = GuestAddress(0x0);
|
||||
let guest_mem = GuestMemoryMmap::from_ranges(&[(start_addr, 0x400)]).unwrap();
|
||||
let guest_mem_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[(
|
||||
start_addr,
|
||||
0x400,
|
||||
Some(FileOffset::new(f, 0)),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
let guest_mem_list = vec![guest_mem, guest_mem_backed_by_file];
|
||||
for guest_mem in guest_mem_list.iter() {
|
||||
let sample_buf = &[1, 2, 3, 4, 5];
|
||||
|
||||
assert_eq!(guest_mem.write(sample_buf, start_addr).unwrap(), 5);
|
||||
let slice = guest_mem
|
||||
.find_region(GuestAddress(0))
|
||||
.unwrap()
|
||||
.as_volatile_slice()
|
||||
.unwrap();
|
||||
|
||||
let buf = &mut [0, 0, 0, 0, 0];
|
||||
assert_eq!(slice.read(buf, 0).unwrap(), 5);
|
||||
assert_eq!(buf, sample_buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u64() {
|
||||
let f1 = TempFile::new().unwrap().into_file();
|
||||
f1.set_len(0x1000).unwrap();
|
||||
let f2 = TempFile::new().unwrap().into_file();
|
||||
f2.set_len(0x1000).unwrap();
|
||||
|
||||
let start_addr1 = GuestAddress(0x0);
|
||||
let start_addr2 = GuestAddress(0x1000);
|
||||
let bad_addr = GuestAddress(0x2001);
|
||||
let bad_addr2 = GuestAddress(0x1ffc);
|
||||
let max_addr = GuestAddress(0x2000);
|
||||
|
||||
let gm =
|
||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
|
||||
let gm_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[
|
||||
(start_addr1, 0x1000, Some(FileOffset::new(f1, 0))),
|
||||
(start_addr2, 0x1000, Some(FileOffset::new(f2, 0))),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let gm_list = vec![gm, gm_backed_by_file];
|
||||
for gm in gm_list.iter() {
|
||||
let val1: u64 = 0xaa55_aa55_aa55_aa55;
|
||||
let val2: u64 = 0x55aa_55aa_55aa_55aa;
|
||||
assert_eq!(
|
||||
format!("{:?}", gm.write_obj(val1, bad_addr).err().unwrap()),
|
||||
format!("InvalidGuestAddress({:?})", bad_addr,)
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", gm.write_obj(val1, bad_addr2).err().unwrap()),
|
||||
format!(
|
||||
"PartialBuffer {{ expected: {:?}, completed: {:?} }}",
|
||||
mem::size_of::<u64>(),
|
||||
max_addr.checked_offset_from(bad_addr2).unwrap()
|
||||
)
|
||||
);
|
||||
|
||||
gm.write_obj(val1, GuestAddress(0x500)).unwrap();
|
||||
gm.write_obj(val2, GuestAddress(0x1000 + 32)).unwrap();
|
||||
let num1: u64 = gm.read_obj(GuestAddress(0x500)).unwrap();
|
||||
let num2: u64 = gm.read_obj(GuestAddress(0x1000 + 32)).unwrap();
|
||||
assert_eq!(val1, num1);
|
||||
assert_eq!(val2, num2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_and_read() {
|
||||
let f = TempFile::new().unwrap().into_file();
|
||||
f.set_len(0x400).unwrap();
|
||||
|
||||
let mut start_addr = GuestAddress(0x1000);
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(start_addr, 0x400)]).unwrap();
|
||||
let gm_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[(
|
||||
start_addr,
|
||||
0x400,
|
||||
Some(FileOffset::new(f, 0)),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
let gm_list = vec![gm, gm_backed_by_file];
|
||||
for gm in gm_list.iter() {
|
||||
let sample_buf = &[1, 2, 3, 4, 5];
|
||||
|
||||
assert_eq!(gm.write(sample_buf, start_addr).unwrap(), 5);
|
||||
|
||||
let buf = &mut [0u8; 5];
|
||||
assert_eq!(gm.read(buf, start_addr).unwrap(), 5);
|
||||
assert_eq!(buf, sample_buf);
|
||||
|
||||
start_addr = GuestAddress(0x13ff);
|
||||
assert_eq!(gm.write(sample_buf, start_addr).unwrap(), 1);
|
||||
assert_eq!(gm.read(buf, start_addr).unwrap(), 1);
|
||||
assert_eq!(buf[0], sample_buf[0]);
|
||||
start_addr = GuestAddress(0x1000);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_to_and_write_from_mem() {
|
||||
let f = TempFile::new().unwrap().into_file();
|
||||
f.set_len(0x400).unwrap();
|
||||
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x1000), 0x400)]).unwrap();
|
||||
let gm_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[(
|
||||
GuestAddress(0x1000),
|
||||
0x400,
|
||||
Some(FileOffset::new(f, 0)),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
let gm_list = vec![gm, gm_backed_by_file];
|
||||
for gm in gm_list.iter() {
|
||||
let addr = GuestAddress(0x1010);
|
||||
let mut file = if cfg!(unix) {
|
||||
File::open(Path::new("/dev/zero")).unwrap()
|
||||
} else {
|
||||
File::open(Path::new("c:\\Windows\\system32\\ntoskrnl.exe")).unwrap()
|
||||
};
|
||||
gm.write_obj(!0u32, addr).unwrap();
|
||||
gm.read_exact_from(addr, &mut file, mem::size_of::<u32>())
|
||||
.unwrap();
|
||||
let value: u32 = gm.read_obj(addr).unwrap();
|
||||
if cfg!(unix) {
|
||||
assert_eq!(value, 0);
|
||||
} else {
|
||||
assert_eq!(value, 0x0090_5a4d);
|
||||
}
|
||||
|
||||
let mut sink = Vec::new();
|
||||
gm.write_all_to(addr, &mut sink, mem::size_of::<u32>())
|
||||
.unwrap();
|
||||
if cfg!(unix) {
|
||||
assert_eq!(sink, vec![0; mem::size_of::<u32>()]);
|
||||
} else {
|
||||
assert_eq!(sink, vec![0x4d, 0x5a, 0x90, 0x00]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_vec_with_regions() {
|
||||
let region_size = 0x400;
|
||||
let regions = vec![
|
||||
(GuestAddress(0x0), region_size),
|
||||
(GuestAddress(0x1000), region_size),
|
||||
];
|
||||
let mut iterated_regions = Vec::new();
|
||||
let gm = GuestMemoryMmap::from_ranges(®ions).unwrap();
|
||||
|
||||
for region in gm.iter() {
|
||||
assert_eq!(region.len(), region_size as GuestUsize);
|
||||
}
|
||||
|
||||
for region in gm.iter() {
|
||||
iterated_regions.push((region.start_addr(), region.len() as usize));
|
||||
}
|
||||
assert_eq!(regions, iterated_regions);
|
||||
|
||||
assert!(regions
|
||||
.iter()
|
||||
.map(|x| (x.0, x.1))
|
||||
.eq(iterated_regions.iter().copied()));
|
||||
|
||||
assert_eq!(gm.regions[0].guest_base, regions[0].0);
|
||||
assert_eq!(gm.regions[1].guest_base, regions[1].0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memory() {
|
||||
let region_size = 0x400;
|
||||
let regions = vec![
|
||||
(GuestAddress(0x0), region_size),
|
||||
(GuestAddress(0x1000), region_size),
|
||||
];
|
||||
let mut iterated_regions = Vec::new();
|
||||
let gm = Arc::new(GuestMemoryMmap::from_ranges(®ions).unwrap());
|
||||
let mem = gm.memory();
|
||||
|
||||
for region in mem.iter() {
|
||||
assert_eq!(region.len(), region_size as GuestUsize);
|
||||
}
|
||||
|
||||
for region in mem.iter() {
|
||||
iterated_regions.push((region.start_addr(), region.len() as usize));
|
||||
}
|
||||
assert_eq!(regions, iterated_regions);
|
||||
|
||||
assert!(regions
|
||||
.iter()
|
||||
.map(|x| (x.0, x.1))
|
||||
.eq(iterated_regions.iter().copied()));
|
||||
|
||||
assert_eq!(gm.regions[0].guest_base, regions[0].0);
|
||||
assert_eq!(gm.regions[1].guest_base, regions[1].0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_cross_boundary() {
|
||||
let f1 = TempFile::new().unwrap().into_file();
|
||||
f1.set_len(0x1000).unwrap();
|
||||
let f2 = TempFile::new().unwrap().into_file();
|
||||
f2.set_len(0x1000).unwrap();
|
||||
|
||||
let start_addr1 = GuestAddress(0x0);
|
||||
let start_addr2 = GuestAddress(0x1000);
|
||||
let gm =
|
||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
|
||||
let gm_backed_by_file = GuestMemoryMmap::from_ranges_with_files(&[
|
||||
(start_addr1, 0x1000, Some(FileOffset::new(f1, 0))),
|
||||
(start_addr2, 0x1000, Some(FileOffset::new(f2, 0))),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let gm_list = vec![gm, gm_backed_by_file];
|
||||
for gm in gm_list.iter() {
|
||||
let sample_buf = &[1, 2, 3, 4, 5];
|
||||
assert_eq!(gm.write(sample_buf, GuestAddress(0xffc)).unwrap(), 5);
|
||||
let buf = &mut [0u8; 5];
|
||||
assert_eq!(gm.read(buf, GuestAddress(0xffc)).unwrap(), 5);
|
||||
assert_eq!(buf, sample_buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retrieve_fd_backing_memory_region() {
|
||||
let f = TempFile::new().unwrap().into_file();
|
||||
f.set_len(0x400).unwrap();
|
||||
|
||||
let start_addr = GuestAddress(0x0);
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(start_addr, 0x400)]).unwrap();
|
||||
assert!(gm.find_region(start_addr).is_some());
|
||||
let region = gm.find_region(start_addr).unwrap();
|
||||
assert!(region.file_offset().is_none());
|
||||
|
||||
let gm = GuestMemoryMmap::from_ranges_with_files(&[(
|
||||
start_addr,
|
||||
0x400,
|
||||
Some(FileOffset::new(f, 0)),
|
||||
)])
|
||||
.unwrap();
|
||||
assert!(gm.find_region(start_addr).is_some());
|
||||
let region = gm.find_region(start_addr).unwrap();
|
||||
assert!(region.file_offset().is_some());
|
||||
}
|
||||
|
||||
// Windows needs a dedicated test where it will retrieve the allocation
|
||||
// granularity to determine a proper offset (other than 0) that can be
|
||||
// used for the backing file. Refer to Microsoft docs here:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-mapviewoffile
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_retrieve_offset_from_fd_backing_memory_region() {
|
||||
let f = TempFile::new().unwrap().into_file();
|
||||
f.set_len(0x1400).unwrap();
|
||||
// Needs to be aligned on 4k, otherwise mmap will fail.
|
||||
let offset = 0x1000;
|
||||
|
||||
let start_addr = GuestAddress(0x0);
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(start_addr, 0x400)]).unwrap();
|
||||
assert!(gm.find_region(start_addr).is_some());
|
||||
let region = gm.find_region(start_addr).unwrap();
|
||||
assert!(region.file_offset().is_none());
|
||||
|
||||
let gm = GuestMemoryMmap::from_ranges_with_files(&[(
|
||||
start_addr,
|
||||
0x400,
|
||||
Some(FileOffset::new(f, offset)),
|
||||
)])
|
||||
.unwrap();
|
||||
assert!(gm.find_region(start_addr).is_some());
|
||||
let region = gm.find_region(start_addr).unwrap();
|
||||
assert!(region.file_offset().is_some());
|
||||
assert_eq!(region.file_offset().unwrap().start(), offset);
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_mmap_insert_region() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let start_addr2 = GuestAddress(0x10_0000);
|
||||
|
||||
let guest_mem = GuestMemoryHybrid::<()>::new();
|
||||
let mut raw_buf = [0u8; 0x1000];
|
||||
let raw_ptr = &mut raw_buf as *mut u8;
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr1, raw_ptr, 0x1000) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr2, raw_ptr, 0x1000) };
|
||||
let gm = &guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let mem_orig = gm.memory();
|
||||
assert_eq!(mem_orig.num_regions(), 2);
|
||||
|
||||
let reg = unsafe { GuestRegionRaw::new(GuestAddress(0x8000), raw_ptr, 0x1000) };
|
||||
let mmap = Arc::new(GuestRegionHybrid::from_raw_region(reg));
|
||||
let gm = gm.insert_region(mmap).unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::new(GuestAddress(0x4000), raw_ptr, 0x1000) };
|
||||
let mmap = Arc::new(GuestRegionHybrid::from_raw_region(reg));
|
||||
let gm = gm.insert_region(mmap).unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::new(GuestAddress(0xc000), raw_ptr, 0x1000) };
|
||||
let mmap = Arc::new(GuestRegionHybrid::from_raw_region(reg));
|
||||
let gm = gm.insert_region(mmap).unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::new(GuestAddress(0xc000), raw_ptr, 0x1000) };
|
||||
let mmap = Arc::new(GuestRegionHybrid::from_raw_region(reg));
|
||||
gm.insert_region(mmap).unwrap_err();
|
||||
|
||||
assert_eq!(mem_orig.num_regions(), 2);
|
||||
assert_eq!(gm.num_regions(), 5);
|
||||
|
||||
assert_eq!(gm.regions[0].start_addr(), GuestAddress(0x0000));
|
||||
assert_eq!(gm.regions[1].start_addr(), GuestAddress(0x4000));
|
||||
assert_eq!(gm.regions[2].start_addr(), GuestAddress(0x8000));
|
||||
assert_eq!(gm.regions[3].start_addr(), GuestAddress(0xc000));
|
||||
assert_eq!(gm.regions[4].start_addr(), GuestAddress(0x10_0000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmap_remove_region() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let start_addr2 = GuestAddress(0x10_0000);
|
||||
|
||||
let guest_mem = GuestMemoryHybrid::<()>::new();
|
||||
let mut raw_buf = [0u8; 0x1000];
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr1, &mut raw_buf as *mut _, 0x1000) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr2, &mut raw_buf as *mut _, 0x1000) };
|
||||
let gm = &guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let mem_orig = gm.memory();
|
||||
assert_eq!(mem_orig.num_regions(), 2);
|
||||
|
||||
gm.remove_region(GuestAddress(0), 128).unwrap_err();
|
||||
gm.remove_region(GuestAddress(0x4000), 128).unwrap_err();
|
||||
let (gm, region) = gm.remove_region(GuestAddress(0x10_0000), 0x1000).unwrap();
|
||||
|
||||
assert_eq!(mem_orig.num_regions(), 2);
|
||||
assert_eq!(gm.num_regions(), 1);
|
||||
|
||||
assert_eq!(gm.regions[0].start_addr(), GuestAddress(0x0000));
|
||||
assert_eq!(region.start_addr(), GuestAddress(0x10_0000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guest_memory_mmap_get_slice() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let mut raw_buf = [0u8; 0x400];
|
||||
let region =
|
||||
unsafe { GuestRegionRaw::<()>::new(start_addr1, &mut raw_buf as *mut _, 0x400) };
|
||||
|
||||
// Normal case.
|
||||
let slice_addr = MemoryRegionAddress(0x100);
|
||||
let slice_size = 0x200;
|
||||
let slice = region.get_slice(slice_addr, slice_size).unwrap();
|
||||
assert_eq!(slice.len(), slice_size);
|
||||
|
||||
// Empty slice.
|
||||
let slice_addr = MemoryRegionAddress(0x200);
|
||||
let slice_size = 0x0;
|
||||
let slice = region.get_slice(slice_addr, slice_size).unwrap();
|
||||
assert!(slice.is_empty());
|
||||
|
||||
// Error case when slice_size is beyond the boundary.
|
||||
let slice_addr = MemoryRegionAddress(0x300);
|
||||
let slice_size = 0x200;
|
||||
assert!(region.get_slice(slice_addr, slice_size).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guest_memory_mmap_as_volatile_slice() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let mut raw_buf = [0u8; 0x400];
|
||||
let region =
|
||||
unsafe { GuestRegionRaw::<()>::new(start_addr1, &mut raw_buf as *mut _, 0x400) };
|
||||
let region_size = 0x400;
|
||||
|
||||
// Test slice length.
|
||||
let slice = region.as_volatile_slice().unwrap();
|
||||
assert_eq!(slice.len(), region_size);
|
||||
|
||||
// Test slice data.
|
||||
let v = 0x1234_5678u32;
|
||||
let r = slice.get_ref::<u32>(0x200).unwrap();
|
||||
r.store(v);
|
||||
assert_eq!(r.load(), v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guest_memory_get_slice() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
|
||||
let guest_mem = GuestMemoryHybrid::<()>::new();
|
||||
let mut raw_buf = [0u8; 0x400];
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr1, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr2, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
|
||||
// Normal cases.
|
||||
let slice_size = 0x200;
|
||||
let slice = guest_mem
|
||||
.get_slice(GuestAddress(0x100), slice_size)
|
||||
.unwrap();
|
||||
assert_eq!(slice.len(), slice_size);
|
||||
|
||||
let slice_size = 0x400;
|
||||
let slice = guest_mem
|
||||
.get_slice(GuestAddress(0x800), slice_size)
|
||||
.unwrap();
|
||||
assert_eq!(slice.len(), slice_size);
|
||||
|
||||
// Empty slice.
|
||||
assert!(guest_mem
|
||||
.get_slice(GuestAddress(0x900), 0)
|
||||
.unwrap()
|
||||
.is_empty());
|
||||
|
||||
// Error cases, wrong size or base address.
|
||||
assert!(guest_mem.get_slice(GuestAddress(0), 0x500).is_err());
|
||||
assert!(guest_mem.get_slice(GuestAddress(0x600), 0x100).is_err());
|
||||
assert!(guest_mem.get_slice(GuestAddress(0xc00), 0x100).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checked_offset() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
let start_addr3 = GuestAddress(0xc00);
|
||||
|
||||
let guest_mem = GuestMemoryHybrid::<()>::new();
|
||||
let mut raw_buf = [0u8; 0x400];
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr1, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr2, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr3, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
guest_mem.checked_offset(start_addr1, 0x200),
|
||||
Some(GuestAddress(0x200))
|
||||
);
|
||||
assert_eq!(
|
||||
guest_mem.checked_offset(start_addr1, 0xa00),
|
||||
Some(GuestAddress(0xa00))
|
||||
);
|
||||
assert_eq!(
|
||||
guest_mem.checked_offset(start_addr2, 0x7ff),
|
||||
Some(GuestAddress(0xfff))
|
||||
);
|
||||
assert_eq!(guest_mem.checked_offset(start_addr2, 0xc00), None);
|
||||
assert_eq!(guest_mem.checked_offset(start_addr1, std::usize::MAX), None);
|
||||
|
||||
assert_eq!(guest_mem.checked_offset(start_addr1, 0x400), None);
|
||||
assert_eq!(
|
||||
guest_mem.checked_offset(start_addr1, 0x400 - 1),
|
||||
Some(GuestAddress(0x400 - 1))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_range() {
|
||||
let start_addr1 = GuestAddress(0);
|
||||
let start_addr2 = GuestAddress(0x800);
|
||||
let start_addr3 = GuestAddress(0xc00);
|
||||
|
||||
let guest_mem = GuestMemoryHybrid::<()>::new();
|
||||
let mut raw_buf = [0u8; 0x400];
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr1, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr2, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
let reg = unsafe { GuestRegionRaw::<()>::new(start_addr3, &mut raw_buf as *mut _, 0x400) };
|
||||
let guest_mem = guest_mem
|
||||
.insert_region(Arc::new(GuestRegionHybrid::from_raw_region(reg)))
|
||||
.unwrap();
|
||||
|
||||
assert!(guest_mem.check_range(start_addr1, 0x0));
|
||||
assert!(guest_mem.check_range(start_addr1, 0x200));
|
||||
assert!(guest_mem.check_range(start_addr1, 0x400));
|
||||
assert!(!guest_mem.check_range(start_addr1, 0xa00));
|
||||
assert!(guest_mem.check_range(start_addr2, 0x7ff));
|
||||
assert!(guest_mem.check_range(start_addr2, 0x800));
|
||||
assert!(!guest_mem.check_range(start_addr2, 0x801));
|
||||
assert!(!guest_mem.check_range(start_addr2, 0xc00));
|
||||
assert!(!guest_mem.check_range(start_addr1, usize::MAX));
|
||||
}
|
||||
}
|
||||
85
src/dragonball/src/dbs_address_space/src/numa.rs
Normal file
85
src/dragonball/src/dbs_address_space/src/numa.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (C) 2021 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Types for NUMA information.
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
/// Strategy of mbind() and don't lead to OOM.
|
||||
pub const MPOL_PREFERRED: u32 = 1;
|
||||
|
||||
/// Strategy of mbind()
|
||||
pub const MPOL_MF_MOVE: u32 = 2;
|
||||
|
||||
/// Type for recording numa ids of different devices
|
||||
pub struct NumaIdTable {
|
||||
/// vectors of numa id for each memory region
|
||||
pub memory: Vec<u32>,
|
||||
/// vectors of numa id for each cpu
|
||||
pub cpu: Vec<u32>,
|
||||
}
|
||||
|
||||
/// Record numa node memory information.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct NumaNodeInfo {
|
||||
/// Base address of the region in guest physical address space.
|
||||
pub base: GuestAddress,
|
||||
/// Size of the address region.
|
||||
pub size: GuestUsize,
|
||||
}
|
||||
|
||||
/// Record all region's info of a numa node.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct NumaNode {
|
||||
region_infos: Vec<NumaNodeInfo>,
|
||||
vcpu_ids: Vec<u32>,
|
||||
}
|
||||
|
||||
impl NumaNode {
|
||||
/// get reference of region_infos in numa node.
|
||||
pub fn region_infos(&self) -> &Vec<NumaNodeInfo> {
|
||||
&self.region_infos
|
||||
}
|
||||
|
||||
/// get vcpu ids belonging to a numa node.
|
||||
pub fn vcpu_ids(&self) -> &Vec<u32> {
|
||||
&self.vcpu_ids
|
||||
}
|
||||
|
||||
/// add a new numa region info into this numa node.
|
||||
pub fn add_info(&mut self, info: &NumaNodeInfo) {
|
||||
self.region_infos.push(*info);
|
||||
}
|
||||
|
||||
/// add a group of vcpu ids belong to this numa node
|
||||
pub fn add_vcpu_ids(&mut self, vcpu_ids: &[u32]) {
|
||||
self.vcpu_ids.extend(vcpu_ids)
|
||||
}
|
||||
|
||||
/// create a new numa node struct
|
||||
pub fn new() -> NumaNode {
|
||||
NumaNode {
|
||||
region_infos: Vec::new(),
|
||||
vcpu_ids: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_numa_node() {
|
||||
let mut numa_node = NumaNode::new();
|
||||
let info = NumaNodeInfo {
|
||||
base: GuestAddress(0),
|
||||
size: 1024,
|
||||
};
|
||||
numa_node.add_info(&info);
|
||||
assert_eq!(*numa_node.region_infos(), vec![info]);
|
||||
let vcpu_ids = vec![0, 1, 2, 3];
|
||||
numa_node.add_vcpu_ids(&vcpu_ids);
|
||||
assert_eq!(*numa_node.vcpu_ids(), vcpu_ids);
|
||||
}
|
||||
}
|
||||
564
src/dragonball/src/dbs_address_space/src/region.rs
Normal file
564
src/dragonball/src/dbs_address_space/src/region.rs
Normal file
@@ -0,0 +1,564 @@
|
||||
// Copyright (C) 2021 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use nix::sys::memfd;
|
||||
use vm_memory::{Address, FileOffset, GuestAddress, GuestUsize};
|
||||
|
||||
use crate::memory::MemorySourceType;
|
||||
use crate::memory::MemorySourceType::MemFdShared;
|
||||
use crate::AddressSpaceError;
|
||||
|
||||
/// Type of address space regions.
|
||||
///
|
||||
/// On physical machines, physical memory may have different properties, such as
|
||||
/// volatile vs non-volatile, read-only vs read-write, non-executable vs executable etc.
|
||||
/// On virtual machines, the concept of memory property may be extended to support better
|
||||
/// cooperation between the hypervisor and the guest kernel. Here address space region type means
|
||||
/// what the region will be used for by the guest OS, and different permissions and policies may
|
||||
/// be applied to different address space regions.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum AddressSpaceRegionType {
|
||||
/// Normal memory accessible by CPUs and IO devices.
|
||||
DefaultMemory,
|
||||
/// MMIO address region for Devices.
|
||||
DeviceMemory,
|
||||
/// DAX address region for virtio-fs/virtio-pmem.
|
||||
DAXMemory,
|
||||
}
|
||||
|
||||
/// Struct to maintain configuration information about a guest address region.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AddressSpaceRegion {
|
||||
/// Type of address space regions.
|
||||
pub ty: AddressSpaceRegionType,
|
||||
/// Base address of the region in virtual machine's physical address space.
|
||||
pub base: GuestAddress,
|
||||
/// Size of the address space region.
|
||||
pub size: GuestUsize,
|
||||
/// Host NUMA node ids assigned to this region.
|
||||
pub host_numa_node_id: Option<u32>,
|
||||
|
||||
/// File/offset tuple to back the memory allocation.
|
||||
file_offset: Option<FileOffset>,
|
||||
/// Mmap permission flags.
|
||||
perm_flags: i32,
|
||||
/// Mmap protection flags.
|
||||
prot_flags: i32,
|
||||
/// Hugepage madvise hint.
|
||||
///
|
||||
/// It needs 'advise' or 'always' policy in host shmem config.
|
||||
is_hugepage: bool,
|
||||
/// Hotplug hint.
|
||||
is_hotplug: bool,
|
||||
/// Anonymous memory hint.
|
||||
///
|
||||
/// It should be true for regions with the MADV_DONTFORK flag enabled.
|
||||
is_anon: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl AddressSpaceRegion {
|
||||
/// Create an address space region with default configuration.
|
||||
pub fn new(ty: AddressSpaceRegionType, base: GuestAddress, size: GuestUsize) -> Self {
|
||||
AddressSpaceRegion {
|
||||
ty,
|
||||
base,
|
||||
size,
|
||||
host_numa_node_id: None,
|
||||
file_offset: None,
|
||||
perm_flags: libc::MAP_SHARED,
|
||||
prot_flags: libc::PROT_READ | libc::PROT_WRITE,
|
||||
is_hugepage: false,
|
||||
is_hotplug: false,
|
||||
is_anon: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an address space region with all configurable information.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ty` - Type of the address region
|
||||
/// * `base` - Base address in VM to map content
|
||||
/// * `size` - Length of content to map
|
||||
/// * `numa_node_id` - Optional NUMA node id to allocate memory from
|
||||
/// * `file_offset` - Optional file descriptor and offset to map content from
|
||||
/// * `perm_flags` - mmap permission flags
|
||||
/// * `prot_flags` - mmap protection flags
|
||||
/// * `is_hotplug` - Whether it's a region for hotplug.
|
||||
pub fn build(
|
||||
ty: AddressSpaceRegionType,
|
||||
base: GuestAddress,
|
||||
size: GuestUsize,
|
||||
host_numa_node_id: Option<u32>,
|
||||
file_offset: Option<FileOffset>,
|
||||
perm_flags: i32,
|
||||
prot_flags: i32,
|
||||
is_hotplug: bool,
|
||||
) -> Self {
|
||||
let mut region = Self::new(ty, base, size);
|
||||
|
||||
region.set_host_numa_node_id(host_numa_node_id);
|
||||
region.set_file_offset(file_offset);
|
||||
region.set_perm_flags(perm_flags);
|
||||
region.set_prot_flags(prot_flags);
|
||||
if is_hotplug {
|
||||
region.set_hotplug();
|
||||
}
|
||||
|
||||
region
|
||||
}
|
||||
|
||||
/// Create an address space region to map memory into the virtual machine.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Base address in VM to map content
|
||||
/// * `size` - Length of content to map
|
||||
/// * `numa_node_id` - Optional NUMA node id to allocate memory from
|
||||
/// * `mem_type` - Memory mapping from, 'shmem' or 'hugetlbfs'
|
||||
/// * `mem_file_path` - Memory file path
|
||||
/// * `mem_prealloc` - Whether to enable pre-allocation of guest memory
|
||||
/// * `is_hotplug` - Whether it's a region for hotplug.
|
||||
pub fn create_default_memory_region(
|
||||
base: GuestAddress,
|
||||
size: GuestUsize,
|
||||
numa_node_id: Option<u32>,
|
||||
mem_type: &str,
|
||||
mem_file_path: &str,
|
||||
mem_prealloc: bool,
|
||||
is_hotplug: bool,
|
||||
) -> Result<AddressSpaceRegion, AddressSpaceError> {
|
||||
Self::create_memory_region(
|
||||
base,
|
||||
size,
|
||||
numa_node_id,
|
||||
mem_type,
|
||||
mem_file_path,
|
||||
mem_prealloc,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
is_hotplug,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create an address space region to map memory from memfd/hugetlbfs into the virtual machine.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Base address in VM to map content
|
||||
/// * `size` - Length of content to map
|
||||
/// * `numa_node_id` - Optional NUMA node id to allocate memory from
|
||||
/// * `mem_type` - Memory mapping from, 'shmem' or 'hugetlbfs'
|
||||
/// * `mem_file_path` - Memory file path
|
||||
/// * `mem_prealloc` - Whether to enable pre-allocation of guest memory
|
||||
/// * `is_hotplug` - Whether it's a region for hotplug.
|
||||
/// * `prot_flags` - mmap protection flags
|
||||
pub fn create_memory_region(
|
||||
base: GuestAddress,
|
||||
size: GuestUsize,
|
||||
numa_node_id: Option<u32>,
|
||||
mem_type: &str,
|
||||
mem_file_path: &str,
|
||||
mem_prealloc: bool,
|
||||
prot_flags: i32,
|
||||
is_hotplug: bool,
|
||||
) -> Result<AddressSpaceRegion, AddressSpaceError> {
|
||||
let perm_flags = if mem_prealloc {
|
||||
libc::MAP_SHARED | libc::MAP_POPULATE
|
||||
} else {
|
||||
libc::MAP_SHARED
|
||||
};
|
||||
let source_type = MemorySourceType::from_str(mem_type)
|
||||
.map_err(|_e| AddressSpaceError::InvalidMemorySourceType(mem_type.to_string()))?;
|
||||
let mut reg = match source_type {
|
||||
MemorySourceType::MemFdShared | MemorySourceType::MemFdOnHugeTlbFs => {
|
||||
let fn_str = if source_type == MemFdShared {
|
||||
CString::new("shmem").expect("CString::new('shmem') failed")
|
||||
} else {
|
||||
CString::new("hugeshmem").expect("CString::new('hugeshmem') failed")
|
||||
};
|
||||
let filename = fn_str.as_c_str();
|
||||
let fd = memfd::memfd_create(filename, memfd::MemFdCreateFlag::empty())
|
||||
.map_err(AddressSpaceError::CreateMemFd)?;
|
||||
// Safe because we have just created the fd.
|
||||
let file: File = unsafe { File::from_raw_fd(fd) };
|
||||
file.set_len(size).map_err(AddressSpaceError::SetFileSize)?;
|
||||
Self::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
base,
|
||||
size,
|
||||
numa_node_id,
|
||||
Some(FileOffset::new(file, 0)),
|
||||
perm_flags,
|
||||
prot_flags,
|
||||
is_hotplug,
|
||||
)
|
||||
}
|
||||
MemorySourceType::MmapAnonymous | MemorySourceType::MmapAnonymousHugeTlbFs => {
|
||||
let mut perm_flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;
|
||||
if mem_prealloc {
|
||||
perm_flags |= libc::MAP_POPULATE
|
||||
}
|
||||
Self::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
base,
|
||||
size,
|
||||
numa_node_id,
|
||||
None,
|
||||
perm_flags,
|
||||
prot_flags,
|
||||
is_hotplug,
|
||||
)
|
||||
}
|
||||
MemorySourceType::FileOnHugeTlbFs => {
|
||||
let path = Path::new(mem_file_path);
|
||||
if let Some(parent_dir) = path.parent() {
|
||||
// Ensure that the parent directory is existed for the mem file path.
|
||||
std::fs::create_dir_all(parent_dir).map_err(AddressSpaceError::CreateDir)?;
|
||||
}
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(mem_file_path)
|
||||
.map_err(AddressSpaceError::OpenFile)?;
|
||||
nix::unistd::unlink(mem_file_path).map_err(AddressSpaceError::UnlinkFile)?;
|
||||
file.set_len(size).map_err(AddressSpaceError::SetFileSize)?;
|
||||
let file_offset = FileOffset::new(file, 0);
|
||||
Self::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
base,
|
||||
size,
|
||||
numa_node_id,
|
||||
Some(file_offset),
|
||||
perm_flags,
|
||||
prot_flags,
|
||||
is_hotplug,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if source_type.is_hugepage() {
|
||||
reg.set_hugepage();
|
||||
}
|
||||
if source_type.is_mmap_anonymous() {
|
||||
reg.set_anonpage();
|
||||
}
|
||||
|
||||
Ok(reg)
|
||||
}
|
||||
|
||||
/// Create an address region for device MMIO.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Base address in VM to map content
|
||||
/// * `size` - Length of content to map
|
||||
pub fn create_device_region(
|
||||
base: GuestAddress,
|
||||
size: GuestUsize,
|
||||
) -> Result<AddressSpaceRegion, AddressSpaceError> {
|
||||
Ok(Self::build(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
base,
|
||||
size,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
/// Get type of the address space region.
|
||||
pub fn region_type(&self) -> AddressSpaceRegionType {
|
||||
self.ty
|
||||
}
|
||||
|
||||
/// Get size of region.
|
||||
pub fn len(&self) -> GuestUsize {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Get the inclusive start physical address of the region.
|
||||
pub fn start_addr(&self) -> GuestAddress {
|
||||
self.base
|
||||
}
|
||||
|
||||
/// Get the inclusive end physical address of the region.
|
||||
pub fn last_addr(&self) -> GuestAddress {
|
||||
debug_assert!(self.size > 0 && self.base.checked_add(self.size).is_some());
|
||||
GuestAddress(self.base.raw_value() + self.size - 1)
|
||||
}
|
||||
|
||||
/// Get mmap permission flags of the address space region.
|
||||
pub fn perm_flags(&self) -> i32 {
|
||||
self.perm_flags
|
||||
}
|
||||
|
||||
/// Set mmap permission flags for the address space region.
|
||||
pub fn set_perm_flags(&mut self, perm_flags: i32) {
|
||||
self.perm_flags = perm_flags;
|
||||
}
|
||||
|
||||
/// Get mmap protection flags of the address space region.
|
||||
pub fn prot_flags(&self) -> i32 {
|
||||
self.prot_flags
|
||||
}
|
||||
|
||||
/// Set mmap protection flags for the address space region.
|
||||
pub fn set_prot_flags(&mut self, prot_flags: i32) {
|
||||
self.prot_flags = prot_flags;
|
||||
}
|
||||
|
||||
/// Get host_numa_node_id flags
|
||||
pub fn host_numa_node_id(&self) -> Option<u32> {
|
||||
self.host_numa_node_id
|
||||
}
|
||||
|
||||
/// Set associated NUMA node ID to allocate memory from for this region.
|
||||
pub fn set_host_numa_node_id(&mut self, host_numa_node_id: Option<u32>) {
|
||||
self.host_numa_node_id = host_numa_node_id;
|
||||
}
|
||||
|
||||
/// Check whether the address space region is backed by a memory file.
|
||||
pub fn has_file(&self) -> bool {
|
||||
self.file_offset.is_some()
|
||||
}
|
||||
|
||||
/// Get optional file associated with the region.
|
||||
pub fn file_offset(&self) -> Option<&FileOffset> {
|
||||
self.file_offset.as_ref()
|
||||
}
|
||||
|
||||
/// Set associated file/offset pair for the region.
|
||||
pub fn set_file_offset(&mut self, file_offset: Option<FileOffset>) {
|
||||
self.file_offset = file_offset;
|
||||
}
|
||||
|
||||
/// Set the hotplug hint.
|
||||
pub fn set_hotplug(&mut self) {
|
||||
self.is_hotplug = true
|
||||
}
|
||||
|
||||
/// Get the hotplug hint.
|
||||
pub fn is_hotplug(&self) -> bool {
|
||||
self.is_hotplug
|
||||
}
|
||||
|
||||
/// Set hugepage hint for `madvise()`, only takes effect when the memory type is `shmem`.
|
||||
pub fn set_hugepage(&mut self) {
|
||||
self.is_hugepage = true
|
||||
}
|
||||
|
||||
/// Get the hugepage hint.
|
||||
pub fn is_hugepage(&self) -> bool {
|
||||
self.is_hugepage
|
||||
}
|
||||
|
||||
/// Set the anonymous memory hint.
|
||||
pub fn set_anonpage(&mut self) {
|
||||
self.is_anon = true
|
||||
}
|
||||
|
||||
/// Get the anonymous memory hint.
|
||||
pub fn is_anonpage(&self) -> bool {
|
||||
self.is_anon
|
||||
}
|
||||
|
||||
/// Check whether the address space region is valid.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.size > 0 && self.base.checked_add(self.size).is_some()
|
||||
}
|
||||
|
||||
/// Check whether the address space region intersects with another one.
|
||||
pub fn intersect_with(&self, other: &AddressSpaceRegion) -> bool {
|
||||
// Treat invalid address region as intersecting always
|
||||
let end1 = match self.base.checked_add(self.size) {
|
||||
Some(addr) => addr,
|
||||
None => return true,
|
||||
};
|
||||
let end2 = match other.base.checked_add(other.size) {
|
||||
Some(addr) => addr,
|
||||
None => return true,
|
||||
};
|
||||
|
||||
!(end1 <= other.base || self.base >= end2)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
|
||||
#[test]
|
||||
fn test_address_space_region_valid() {
|
||||
let reg1 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0xFFFFFFFFFFFFF000),
|
||||
0x2000,
|
||||
);
|
||||
assert!(!reg1.is_valid());
|
||||
let reg1 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0xFFFFFFFFFFFFF000),
|
||||
0x1000,
|
||||
);
|
||||
assert!(!reg1.is_valid());
|
||||
let reg1 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DeviceMemory,
|
||||
GuestAddress(0xFFFFFFFFFFFFE000),
|
||||
0x1000,
|
||||
);
|
||||
assert!(reg1.is_valid());
|
||||
assert_eq!(reg1.start_addr(), GuestAddress(0xFFFFFFFFFFFFE000));
|
||||
assert_eq!(reg1.len(), 0x1000);
|
||||
assert!(!reg1.has_file());
|
||||
assert!(reg1.file_offset().is_none());
|
||||
assert_eq!(reg1.perm_flags(), libc::MAP_SHARED);
|
||||
assert_eq!(reg1.prot_flags(), libc::PROT_READ | libc::PROT_WRITE);
|
||||
assert_eq!(reg1.region_type(), AddressSpaceRegionType::DeviceMemory);
|
||||
|
||||
let tmp_file = TempFile::new().unwrap();
|
||||
let mut f = tmp_file.into_file();
|
||||
let sample_buf = &[1, 2, 3, 4, 5];
|
||||
assert!(f.write_all(sample_buf).is_ok());
|
||||
let reg2 = AddressSpaceRegion::build(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x1000),
|
||||
0x1000,
|
||||
None,
|
||||
Some(FileOffset::new(f, 0x0)),
|
||||
0x5a,
|
||||
0x5a,
|
||||
false,
|
||||
);
|
||||
assert_eq!(reg2.region_type(), AddressSpaceRegionType::DefaultMemory);
|
||||
assert!(reg2.is_valid());
|
||||
assert_eq!(reg2.start_addr(), GuestAddress(0x1000));
|
||||
assert_eq!(reg2.len(), 0x1000);
|
||||
assert!(reg2.has_file());
|
||||
assert!(reg2.file_offset().is_some());
|
||||
assert_eq!(reg2.perm_flags(), 0x5a);
|
||||
assert_eq!(reg2.prot_flags(), 0x5a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_address_space_region_intersect() {
|
||||
let reg1 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x1000),
|
||||
0x1000,
|
||||
);
|
||||
let reg2 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x2000),
|
||||
0x1000,
|
||||
);
|
||||
let reg3 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x1000),
|
||||
0x1001,
|
||||
);
|
||||
let reg4 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0x1100),
|
||||
0x100,
|
||||
);
|
||||
let reg5 = AddressSpaceRegion::new(
|
||||
AddressSpaceRegionType::DefaultMemory,
|
||||
GuestAddress(0xFFFFFFFFFFFFF000),
|
||||
0x2000,
|
||||
);
|
||||
|
||||
assert!(!reg1.intersect_with(®2));
|
||||
assert!(!reg2.intersect_with(®1));
|
||||
|
||||
// intersect with self
|
||||
assert!(reg1.intersect_with(®1));
|
||||
|
||||
// intersect with others
|
||||
assert!(reg3.intersect_with(®2));
|
||||
assert!(reg2.intersect_with(®3));
|
||||
assert!(reg1.intersect_with(®4));
|
||||
assert!(reg4.intersect_with(®1));
|
||||
assert!(reg1.intersect_with(®5));
|
||||
assert!(reg5.intersect_with(®1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_device_region() {
|
||||
let reg = AddressSpaceRegion::create_device_region(GuestAddress(0x10000), 0x1000).unwrap();
|
||||
assert_eq!(reg.region_type(), AddressSpaceRegionType::DeviceMemory);
|
||||
assert_eq!(reg.start_addr(), GuestAddress(0x10000));
|
||||
assert_eq!(reg.len(), 0x1000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_default_memory_region() {
|
||||
AddressSpaceRegion::create_default_memory_region(
|
||||
GuestAddress(0x100000),
|
||||
0x100000,
|
||||
None,
|
||||
"invalid",
|
||||
"invalid",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
let reg = AddressSpaceRegion::create_default_memory_region(
|
||||
GuestAddress(0x100000),
|
||||
0x100000,
|
||||
None,
|
||||
"shmem",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
|
||||
assert_eq!(reg.start_addr(), GuestAddress(0x100000));
|
||||
assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
|
||||
assert_eq!(reg.len(), 0x100000);
|
||||
assert!(reg.file_offset().is_some());
|
||||
|
||||
let reg = AddressSpaceRegion::create_default_memory_region(
|
||||
GuestAddress(0x100000),
|
||||
0x100000,
|
||||
None,
|
||||
"hugeshmem",
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
|
||||
assert_eq!(reg.start_addr(), GuestAddress(0x100000));
|
||||
assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
|
||||
assert_eq!(reg.len(), 0x100000);
|
||||
assert!(reg.file_offset().is_some());
|
||||
|
||||
let reg = AddressSpaceRegion::create_default_memory_region(
|
||||
GuestAddress(0x100000),
|
||||
0x100000,
|
||||
None,
|
||||
"mmap",
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(reg.region_type(), AddressSpaceRegionType::DefaultMemory);
|
||||
assert_eq!(reg.start_addr(), GuestAddress(0x100000));
|
||||
assert_eq!(reg.last_addr(), GuestAddress(0x1fffff));
|
||||
assert_eq!(reg.len(), 0x100000);
|
||||
assert!(reg.file_offset().is_none());
|
||||
|
||||
// TODO: test hugetlbfs
|
||||
}
|
||||
}
|
||||
14
src/dragonball/src/dbs_allocator/Cargo.toml
Normal file
14
src/dragonball/src/dbs_allocator/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "dbs-allocator"
|
||||
version = "0.1.1"
|
||||
authors = ["Liu Jiang <gerry@linux.alibaba.com>"]
|
||||
description = "a resource allocator for virtual machine manager"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
1
src/dragonball/src/dbs_allocator/LICENSE
Symbolic link
1
src/dragonball/src/dbs_allocator/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
106
src/dragonball/src/dbs_allocator/README.md
Normal file
106
src/dragonball/src/dbs_allocator/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# dbs-allocator
|
||||
|
||||
## Design
|
||||
|
||||
The resource manager in the `Dragonball Sandbox` needs to manage and allocate different kinds of resource for the
|
||||
sandbox (virtual machine), such as memory-mapped I/O address space, port I/O address space, legacy IRQ numbers,
|
||||
MSI/MSI-X vectors, device instance id, etc. The `dbs-allocator` crate is designed to help the resource manager
|
||||
to track and allocate these types of resources.
|
||||
|
||||
Main components are:
|
||||
- *Constraints*: struct to declare constraints for resource allocation.
|
||||
```rust
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Constraint {
|
||||
/// Size of resource to allocate.
|
||||
pub size: u64,
|
||||
/// Lower boundary for resource allocation.
|
||||
pub min: u64,
|
||||
/// Upper boundary for resource allocation.
|
||||
pub max: u64,
|
||||
/// Alignment for allocated resource.
|
||||
pub align: u64,
|
||||
/// Policy for resource allocation.
|
||||
pub policy: AllocPolicy,
|
||||
}
|
||||
```
|
||||
- `IntervalTree`: An interval tree implementation specialized for VMM resource management.
|
||||
```rust
|
||||
pub struct IntervalTree<T> {
|
||||
pub(crate) root: Option<Node<T>>,
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, constraint: &Constraint) -> Option<Range>
|
||||
pub fn free(&mut self, key: &Range) -> Option<T>
|
||||
pub fn insert(&mut self, key: Range, data: Option<T>) -> Self
|
||||
pub fn update(&mut self, key: &Range, data: T) -> Option<T>
|
||||
pub fn delete(&mut self, key: &Range) -> Option<T>
|
||||
pub fn get(&self, key: &Range) -> Option<NodeState<&T>>
|
||||
```
|
||||
|
||||
## Usage
|
||||
The concept of Interval Tree may seem complicated, but using dbs-allocator to do resource allocation and release is simple and straightforward.
|
||||
You can following these steps to allocate your VMM resource.
|
||||
```rust
|
||||
// 1. To start with, we should create an interval tree for some specific resouces and give maximum address/id range as root node. The range here could be address range, id range, etc.
|
||||
|
||||
let mut resources_pool = IntervalTree::new();
|
||||
resources_pool.insert(Range::new(MIN_RANGE, MAX_RANGE), None);
|
||||
|
||||
// 2. Next, create a constraint with the size for your resource, you could also assign the maximum, minimum and alignment for the constraint. Then we could use the constraint to allocate the resource in the range we previously decided. Interval Tree will give you the appropriate range.
|
||||
let mut constraint = Constraint::new(SIZE);
|
||||
let mut resources_range = self.resources_pool.allocate(&constraint);
|
||||
|
||||
// 3. Then we could use the resource range to let other crates like vm-pci / vm-device to create and maintain the device
|
||||
let mut device = Device::create(resources_range, ..)
|
||||
```
|
||||
|
||||
## Example
|
||||
We will show examples for allocating an unused PCI device ID from the PCI device ID pool and allocating memory address using dbs-allocator
|
||||
```rust
|
||||
use dbs_allocator::{Constraint, IntervalTree, Range};
|
||||
|
||||
// Init a dbs-allocator IntervalTree
|
||||
let mut pci_device_pool = IntervalTree::new();
|
||||
|
||||
// Init PCI device id pool with the range 0 to 255
|
||||
pci_device_pool.insert(Range::new(0x0u8, 0xffu8), None);
|
||||
|
||||
// Construct a constraint with size 1 and alignment 1 to ask for an ID.
|
||||
let mut constraint = Constraint::new(1u64).align(1u64);
|
||||
|
||||
// Get an ID from the pci_device_pool
|
||||
let mut id = pci_device_pool.allocate(&constraint).map(|e| e.min as u8);
|
||||
|
||||
// Pass the ID generated from dbs-allocator to vm-pci specified functions to create pci devices
|
||||
let mut pci_device = PciDevice::new(id as u8, ..);
|
||||
|
||||
```
|
||||
|
||||
```rust
|
||||
use dbs_allocator::{Constraint, IntervalTree, Range};
|
||||
|
||||
// Init a dbs-allocator IntervalTree
|
||||
let mut mem_pool = IntervalTree::new();
|
||||
|
||||
// Init memory address from GUEST_MEM_START to GUEST_MEM_END
|
||||
mem_pool.insert(Range::new(GUEST_MEM_START, GUEST_MEM_END), None);
|
||||
|
||||
// Construct a constraint with size, maximum addr and minimum address of memory region to ask for an memory allocation range.
|
||||
let constraint = Constraint::new(region.len())
|
||||
.min(region.start_addr().raw_value())
|
||||
.max(region.last_addr().raw_value());
|
||||
|
||||
// Get the memory allocation range from the pci_device_pool
|
||||
let mem_range = mem_pool.allocate(&constraint).unwrap();
|
||||
|
||||
// Update the mem_range in IntervalTree with memory region info
|
||||
mem_pool.update(&mem_range, region);
|
||||
|
||||
// After allocation, we can use the memory range to do mapping and other memory related work.
|
||||
...
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0.
|
||||
1297
src/dragonball/src/dbs_allocator/src/interval_tree.rs
Normal file
1297
src/dragonball/src/dbs_allocator/src/interval_tree.rs
Normal file
File diff suppressed because it is too large
Load Diff
164
src/dragonball/src/dbs_allocator/src/lib.rs
Normal file
164
src/dragonball/src/dbs_allocator/src/lib.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright (C) 2019, 2022 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Data structures and algorithms to support resource allocation and management.
|
||||
//!
|
||||
//! The `dbs-allocator` crate provides data structures and algorithms to manage and allocate
|
||||
//! integer identifiable resources. The resource manager in virtual machine monitor (VMM) may
|
||||
//! manage and allocate resources for virtual machines by using:
|
||||
//! - [Constraint]: Struct to declare constraints for resource allocation.
|
||||
//! - [IntervalTree]: An interval tree implementation specialized for VMM resource management.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod interval_tree;
|
||||
pub use interval_tree::{IntervalTree, NodeState, Range};
|
||||
|
||||
/// Error codes for resource allocation operations.
|
||||
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Invalid boundary for resource allocation.
|
||||
#[error("invalid boundary constraint: min ({0}), max ({1})")]
|
||||
InvalidBoundary(u64, u64),
|
||||
}
|
||||
|
||||
/// Specialized version of [`std::result::Result`] for resource allocation operations.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Resource allocation policies.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum AllocPolicy {
|
||||
/// Default resource allocation policy.
|
||||
Default,
|
||||
/// Return the first available resource matching the allocation constraints.
|
||||
FirstMatch,
|
||||
}
|
||||
|
||||
/// Struct to declare resource allocation constraints.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Constraint {
|
||||
/// Size of resource to allocate.
|
||||
pub size: u64,
|
||||
/// Lower boundary for resource allocation.
|
||||
pub min: u64,
|
||||
/// Upper boundary for resource allocation.
|
||||
pub max: u64,
|
||||
/// Alignment for allocated resource.
|
||||
pub align: u64,
|
||||
/// Policy for resource allocation.
|
||||
pub policy: AllocPolicy,
|
||||
}
|
||||
|
||||
impl Constraint {
|
||||
/// Create a new instance of [`Constraint`] with default settings.
|
||||
pub fn new<T>(size: T) -> Self
|
||||
where
|
||||
u64: From<T>,
|
||||
{
|
||||
Constraint {
|
||||
size: u64::from(size),
|
||||
min: 0,
|
||||
max: u64::MAX,
|
||||
align: 1,
|
||||
policy: AllocPolicy::Default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the lower boundary constraint for resource allocation.
|
||||
pub fn min<T>(mut self, min: T) -> Self
|
||||
where
|
||||
u64: From<T>,
|
||||
{
|
||||
self.min = u64::from(min);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the upper boundary constraint for resource allocation.
|
||||
pub fn max<T>(mut self, max: T) -> Self
|
||||
where
|
||||
u64: From<T>,
|
||||
{
|
||||
self.max = u64::from(max);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the alignment constraint for allocated resource.
|
||||
pub fn align<T>(mut self, align: T) -> Self
|
||||
where
|
||||
u64: From<T>,
|
||||
{
|
||||
self.align = u64::from(align);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the resource allocation policy.
|
||||
pub fn policy(mut self, policy: AllocPolicy) -> Self {
|
||||
self.policy = policy;
|
||||
self
|
||||
}
|
||||
|
||||
/// Validate the resource allocation constraints.
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.max < self.min {
|
||||
return Err(Error::InvalidBoundary(self.min, self.max));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_set_min() {
|
||||
let constraint = Constraint::new(2_u64).min(1_u64);
|
||||
assert_eq!(constraint.min, 1_u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_max() {
|
||||
let constraint = Constraint::new(2_u64).max(100_u64);
|
||||
assert_eq!(constraint.max, 100_u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_align() {
|
||||
let constraint = Constraint::new(2_u64).align(8_u64);
|
||||
assert_eq!(constraint.align, 8_u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_policy() {
|
||||
let mut constraint = Constraint::new(2_u64).policy(AllocPolicy::FirstMatch);
|
||||
assert_eq!(constraint.policy, AllocPolicy::FirstMatch);
|
||||
constraint = constraint.policy(AllocPolicy::Default);
|
||||
assert_eq!(constraint.policy, AllocPolicy::Default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_consistently_change_constraint() {
|
||||
let constraint = Constraint::new(2_u64)
|
||||
.min(1_u64)
|
||||
.max(100_u64)
|
||||
.align(8_u64)
|
||||
.policy(AllocPolicy::FirstMatch);
|
||||
assert_eq!(constraint.min, 1_u64);
|
||||
assert_eq!(constraint.max, 100_u64);
|
||||
assert_eq!(constraint.align, 8_u64);
|
||||
assert_eq!(constraint.policy, AllocPolicy::FirstMatch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_invalid_boundary() {
|
||||
// Normal case.
|
||||
let constraint = Constraint::new(2_u64).max(1000_u64).min(999_u64);
|
||||
assert!(constraint.validate().is_ok());
|
||||
|
||||
// Error case.
|
||||
let constraint = Constraint::new(2_u64).max(999_u64).min(1000_u64);
|
||||
assert_eq!(
|
||||
constraint.validate(),
|
||||
Err(Error::InvalidBoundary(1000u64, 999u64))
|
||||
);
|
||||
}
|
||||
}
|
||||
26
src/dragonball/src/dbs_arch/Cargo.toml
Normal file
26
src/dragonball/src/dbs_arch/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "dbs-arch"
|
||||
version = "0.2.3"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
edition = "2018"
|
||||
description = "A collection of CPU architecture specific constants and utilities."
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball", "secure-sandbox", "arch", "ARM64", "x86"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
memoffset = "0.6"
|
||||
kvm-bindings = { version = "0.6.0", features = ["fam-wrappers"] }
|
||||
kvm-ioctls = "0.12.0"
|
||||
thiserror = "1"
|
||||
vm-memory = { version = "0.9" }
|
||||
vmm-sys-util = "0.11.0"
|
||||
libc = ">=0.2.39"
|
||||
|
||||
[dev-dependencies]
|
||||
vm-memory = { version = "0.9", features = ["backend-mmap"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
1
src/dragonball/src/dbs_arch/LICENSE
Symbolic link
1
src/dragonball/src/dbs_arch/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
29
src/dragonball/src/dbs_arch/README.md
Normal file
29
src/dragonball/src/dbs_arch/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# dbs-arch
|
||||
|
||||
## Design
|
||||
|
||||
The `dbs-arch` crate is a collection of CPU architecture specific constants and utilities to hide CPU architecture details away from the Dragonball Sandbox or other VMMs.
|
||||
Also, we have provided x86_64 CPUID support in this crate, for more details you could look at [this document](docs/x86_64_cpuid.md)
|
||||
|
||||
## Supported Architectures
|
||||
|
||||
- AMD64 (x86_64)
|
||||
- ARM64 (aarch64)
|
||||
|
||||
## Submodule List
|
||||
|
||||
This repository contains the following submodules:
|
||||
| Name | Arch| Description |
|
||||
| --- | --- | --- |
|
||||
| [x86_64::cpuid](src/x86_64/cpuid/) | x86_64 |Facilities to process CPUID information. |
|
||||
| [x86_64::msr](src/x86_64/msr.rs) | x86_64 | Constants and functions for Model Specific Registers |
|
||||
| [aarch64::gic](src/aarch64/gic) | aarch64 | Structures to manage GICv2/GICv3/ITS devices for ARM64 |
|
||||
| [aarch64::regs](src/aarch64/regs.rs) | aarch64 | Constants and functions to configure and manage CPU registers |
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Part of the code is derived from the [Firecracker](https://github.com/firecracker-microvm/firecracker) project.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0.
|
||||
1
src/dragonball/src/dbs_arch/THIRD-PARTY
Symbolic link
1
src/dragonball/src/dbs_arch/THIRD-PARTY
Symbolic link
@@ -0,0 +1 @@
|
||||
../../THIRD-PARTY
|
||||
68
src/dragonball/src/dbs_arch/docs/x86_64_cpuid.md
Normal file
68
src/dragonball/src/dbs_arch/docs/x86_64_cpuid.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# CPUID
|
||||
|
||||
## Design
|
||||
|
||||
CPUID is designed as the CPUID filter for Intel and AMD CPU Identification. Through CPUID configuration, we could set CPU topology, Cache topology, PMU status and other features for the VMs.
|
||||
|
||||
CPUID is developed based on the Firecracker CPUID code while we add other extensions such as CPU Topology and VPMU features.
|
||||
|
||||
## Usage
|
||||
To use CPUID, you should first use KVM_GET_CPUID2 ioctl to get the original CPUID then use process_cpuid() provided by the db-arch to filter CPUID with the information you want and suitable for VM conditions.
|
||||
|
||||
Currently, we support following specifications that db-arch could use to filter CPUID:
|
||||
```rust
|
||||
pub struct VmSpec {
|
||||
/// The vendor id of the CPU
|
||||
cpu_vendor_id: [u8; 12],
|
||||
/// The id of the current logical cpu in the range [0..cpu_count].
|
||||
cpu_id: u8,
|
||||
/// The total number of logical cpus (includes cpus that could be hotplugged).
|
||||
cpu_count: u8,
|
||||
/// The desired brand string for the guest.
|
||||
brand_string: BrandString,
|
||||
/// threads per core for cpu topology information
|
||||
threads_per_core: u8,
|
||||
/// cores per die for cpu topology information
|
||||
cores_per_die: u8,
|
||||
/// dies per socket for cpu topology information
|
||||
dies_per_socket: u8,
|
||||
/// if vpmu feature is Disabled, it means vpmu feature is off (by default)
|
||||
/// if vpmu feature is LimitedlyEnabled, it means minimal vpmu counters are supported (cycles and instructions)
|
||||
/// if vpmu feature is FullyEnabled, it means all vpmu counters are supported
|
||||
vpmu_feature: VpmuFeatureLevel,
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
We will show examples for filtering CPUID.
|
||||
First, you need to use KVM_GET_CPUID2 ioctl to get the original CPUID, this part is not included in the db-cpuid.
|
||||
|
||||
```rust
|
||||
// an example for getting the cpuid in the vmm.
|
||||
let mut cpuid = CpuId::new(num_entries).map_err(|_| errno::Error::new(libc::ENOMEM))?;
|
||||
let ret = unsafe {ioctl_with_mut_ptr(self, KVM_GET_CPUID2(), cpuid.as_mut_fam_struct_ptr())};
|
||||
if ret != 0 {
|
||||
return Err(errno::Error::last());
|
||||
}
|
||||
```
|
||||
|
||||
Then we could create the `VmSpec` to describe the VM specification we want and use process_cpuid() to filter CPUID.
|
||||
|
||||
```rust
|
||||
let cpuid_vm_spec = VmSpec::new(
|
||||
self.id,
|
||||
vcpu_config.max_all_vcpu_count as u8,
|
||||
vcpu_config.threads_per_core,
|
||||
vcpu_config.cores_per_die,
|
||||
vcpu_config.dies_per_socket,
|
||||
vcpu_config.vpmu_feature,
|
||||
)
|
||||
.map_err(VcpuError::CpuId)?;
|
||||
process_cpuid(&mut self.cpuid, &cpuid_vm_spec).map_err(|e| {
|
||||
METRICS.vcpu.process_cpuid.inc();
|
||||
error!("Failure in configuring CPUID for vcpu {}: {:?}", self.id, e);
|
||||
VcpuError::CpuId(e)
|
||||
})?;
|
||||
```
|
||||
|
||||
After the CPUID is filtered, we could use it to set the guest's CPUID.
|
||||
110
src/dragonball/src/dbs_arch/src/aarch64/gic/gicv2.rs
Normal file
110
src/dragonball/src/dbs_arch/src/aarch64/gic/gicv2.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use kvm_ioctls::DeviceFd;
|
||||
|
||||
use super::{GICDevice, Result};
|
||||
|
||||
/// Represent a GIC v2 device
|
||||
pub struct GICv2 {
|
||||
/// The file descriptor for the KVM device
|
||||
fd: DeviceFd,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
properties: [u64; 4],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl GICv2 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
const KVM_VGIC_V2_DIST_SIZE: u64 = 0x1000;
|
||||
const KVM_VGIC_V2_CPU_SIZE: u64 = 0x2000;
|
||||
|
||||
// Device trees specific constants
|
||||
const ARCH_GIC_V2_MAINT_IRQ: u32 = 8;
|
||||
|
||||
/// Get the address of the GICv2 distributor.
|
||||
const fn get_dist_addr() -> u64 {
|
||||
crate::aarch64::gic::GIC_REG_END_ADDRESS - GICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 distributor.
|
||||
const fn get_dist_size() -> u64 {
|
||||
GICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC_v2 CPU.
|
||||
const fn get_cpu_addr() -> u64 {
|
||||
GICv2::get_dist_addr() - GICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 CPU.
|
||||
const fn get_cpu_size() -> u64 {
|
||||
GICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for GICv2 {
|
||||
fn device_fd(&self) -> &DeviceFd {
|
||||
&self.fd
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-400"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
GICv2::ARCH_GIC_V2_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2
|
||||
}
|
||||
|
||||
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice> {
|
||||
Box::new(GICv2 {
|
||||
fd,
|
||||
properties: [
|
||||
GICv2::get_dist_addr(),
|
||||
GICv2::get_dist_size(),
|
||||
GICv2::get_cpu_addr(),
|
||||
GICv2::get_cpu_size(),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(gic_device: &dyn GICDevice) -> Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor. */
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_DIST),
|
||||
&GICv2::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the CPU attribute. */
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_CPU),
|
||||
&GICv2::get_cpu_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
136
src/dragonball/src/dbs_arch/src/aarch64/gic/gicv3.rs
Normal file
136
src/dragonball/src/dbs_arch/src/aarch64/gic/gicv3.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use kvm_ioctls::{DeviceFd, VmFd};
|
||||
|
||||
use super::its::ItsType::{PciMsiIts, PlatformMsiIts};
|
||||
use super::its::{ItsType, ITS};
|
||||
use super::{GICDevice, Result};
|
||||
|
||||
/// GICv3 instance
|
||||
pub struct GICv3 {
|
||||
/// The file descriptor for the KVM device
|
||||
fd: DeviceFd,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
properties: [u64; 4],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
|
||||
/// ITS instance of this gic control
|
||||
its: HashMap<ItsType, ITS>,
|
||||
}
|
||||
|
||||
impl GICv3 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
const SZ_64K: u64 = 0x0001_0000;
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = GICv3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * GICv3::SZ_64K);
|
||||
|
||||
// Device trees specific constants
|
||||
const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
|
||||
|
||||
/// Get the address of the GIC distributor.
|
||||
fn get_dist_addr() -> u64 {
|
||||
crate::aarch64::gic::GIC_REG_END_ADDRESS - GICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC distributor.
|
||||
fn get_dist_size() -> u64 {
|
||||
GICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC redistributors.
|
||||
pub fn get_redists_addr(vcpu_count: u64) -> u64 {
|
||||
GICv3::get_dist_addr() - GICv3::get_redists_size(vcpu_count)
|
||||
}
|
||||
|
||||
/// Get the size of the GIC redistributors.
|
||||
fn get_redists_size(vcpu_count: u64) -> u64 {
|
||||
vcpu_count * GICv3::KVM_VGIC_V3_REDIST_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for GICv3 {
|
||||
fn device_fd(&self) -> &DeviceFd {
|
||||
&self.fd
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-v3"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
GICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn get_its_reg_range(&self, its_type: &ItsType) -> Option<[u64; 2]> {
|
||||
self.its.get(its_type).map(|its| its.get_reg_range())
|
||||
}
|
||||
|
||||
fn attach_its(&mut self, vm: &VmFd) -> Result<()> {
|
||||
let its = ITS::new(vm, self, PlatformMsiIts)?;
|
||||
self.its.insert(PlatformMsiIts, its);
|
||||
let its = ITS::new(vm, self, PciMsiIts)?;
|
||||
self.its.insert(PciMsiIts, its);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
|
||||
}
|
||||
|
||||
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice> {
|
||||
Box::new(GICv3 {
|
||||
fd,
|
||||
properties: [
|
||||
GICv3::get_dist_addr(),
|
||||
GICv3::get_dist_size(),
|
||||
GICv3::get_redists_addr(vcpu_count),
|
||||
GICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count,
|
||||
its: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(gic_device: &dyn GICDevice) -> Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST.into(),
|
||||
&GICv3::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the redistributors' attribute.
|
||||
We are calculating here the start of the redistributors address. We have one per CPU.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST.into(),
|
||||
&GICv3::get_redists_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
81
src/dragonball/src/dbs_arch/src/aarch64/gic/its.rs
Normal file
81
src/dragonball/src/dbs_arch/src/aarch64/gic/its.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2022 Alibaba Cloud. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use kvm_ioctls::{DeviceFd, VmFd};
|
||||
|
||||
use super::gicv3::GICv3;
|
||||
use super::{Error, GICDevice, Result};
|
||||
|
||||
// ITS register range
|
||||
const REG_RANGE_LEN: u64 = 0x20000;
|
||||
|
||||
/// ITS type
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
pub enum ItsType {
|
||||
/// platform msi its
|
||||
PlatformMsiIts,
|
||||
/// pci msi its
|
||||
PciMsiIts,
|
||||
}
|
||||
|
||||
/// Only GIC-V3 can use ITS
|
||||
pub struct ITS {
|
||||
/// The file descriptor for the KVM device
|
||||
fd: DeviceFd,
|
||||
reg_range: [u64; 2],
|
||||
}
|
||||
|
||||
impl ITS {
|
||||
/// Create an ITS device
|
||||
pub fn new(vm: &VmFd, gic_ctl: &GICv3, its_type: ItsType) -> Result<ITS> {
|
||||
let fd = ITS::create_device_fd(vm)?;
|
||||
// Define the mmio space of platform msi its after the mmio space of pci msi its
|
||||
let offset = match its_type {
|
||||
ItsType::PlatformMsiIts => REG_RANGE_LEN,
|
||||
ItsType::PciMsiIts => REG_RANGE_LEN * 2,
|
||||
};
|
||||
let vcpu_count = gic_ctl.vcpu_count();
|
||||
// No document has been found to accurately describe the storage location and
|
||||
// length of the ITS register. Currently, we store the ITS register in front of
|
||||
// the redistributor register. And temporarily refer to the "arm, gic-v3-its"
|
||||
// kernel document to set the ITS register length to 0x20000.In addition,
|
||||
// reg_range is a two-tuple, representing the register base address and the
|
||||
// length of the register address space.
|
||||
let reg_range: [u64; 2] = [GICv3::get_redists_addr(vcpu_count) - offset, REG_RANGE_LEN];
|
||||
let its = ITS { fd, reg_range };
|
||||
let reg_base_addr = its.get_reg_range_base_addr();
|
||||
its.set_attribute(reg_base_addr)?;
|
||||
Ok(its)
|
||||
}
|
||||
|
||||
fn create_device_fd(vm: &VmFd) -> Result<DeviceFd> {
|
||||
let mut its_device = kvm_bindings::kvm_create_device {
|
||||
type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS,
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
vm.create_device(&mut its_device).map_err(Error::CreateITS)
|
||||
}
|
||||
|
||||
fn set_attribute(&self, reg_base_addr: u64) -> Result<()> {
|
||||
let attribute = kvm_bindings::kvm_device_attr {
|
||||
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
attr: u64::from(kvm_bindings::KVM_VGIC_ITS_ADDR_TYPE),
|
||||
addr: ®_base_addr as *const u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
self.fd
|
||||
.set_device_attr(&attribute)
|
||||
.map_err(Error::SetITSAttribute)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_reg_range_base_addr(&self) -> u64 {
|
||||
self.reg_range[0]
|
||||
}
|
||||
|
||||
/// Get its reg range
|
||||
pub fn get_reg_range(&self) -> [u64; 2] {
|
||||
self.reg_range
|
||||
}
|
||||
}
|
||||
218
src/dragonball/src/dbs_arch/src/aarch64/gic/mod.rs
Normal file
218
src/dragonball/src/dbs_arch/src/aarch64/gic/mod.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// Export gicv2 interface
|
||||
pub mod gicv2;
|
||||
/// Export gicv3 interface
|
||||
pub mod gicv3;
|
||||
/// Export ITS interface
|
||||
pub mod its;
|
||||
|
||||
use std::{boxed::Box, result};
|
||||
|
||||
use kvm_ioctls::{DeviceFd, VmFd};
|
||||
|
||||
use gicv2::GICv2;
|
||||
use gicv3::GICv3;
|
||||
|
||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
||||
// the number of interrupts our GIC will support to be:
|
||||
// * bigger than 32
|
||||
// * less than 1023 and
|
||||
// * a multiple of 32.
|
||||
// We are setting up our interrupt controller to support a maximum of 128 interrupts.
|
||||
|
||||
/// First usable interrupt on aarch64.
|
||||
pub const IRQ_BASE: u32 = 32;
|
||||
|
||||
/// Last usable interrupt on aarch64.
|
||||
pub const IRQ_MAX: u32 = 159;
|
||||
|
||||
/// Define the gic register end address.
|
||||
pub const GIC_REG_END_ADDRESS: u64 = 1 << 30; // 1GB
|
||||
|
||||
/// Errors thrown while setting up the GIC.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||
CreateGIC(kvm_ioctls::Error),
|
||||
/// Error while setting device attributes for the GIC.
|
||||
SetDeviceAttribute(kvm_ioctls::Error),
|
||||
/// The number of vCPUs in the GicState doesn't match the number of vCPUs on the system
|
||||
InconsistentVcpuCount,
|
||||
/// The VgicSysRegsState is invalid
|
||||
InvalidVgicSysRegState,
|
||||
/// ERROR while create ITS fail
|
||||
CreateITS(kvm_ioctls::Error),
|
||||
/// ERROR while set ITS attr fail
|
||||
SetITSAttribute(kvm_ioctls::Error),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Function that flushes `RDIST` pending tables into guest RAM.
|
||||
///
|
||||
/// The tables get flushed to guest RAM whenever the VM gets stopped.
|
||||
pub fn save_pending_tables(fd: &DeviceFd) -> Result<()> {
|
||||
let init_gic_attr = kvm_bindings::kvm_device_attr {
|
||||
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||
attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
|
||||
addr: 0,
|
||||
flags: 0,
|
||||
};
|
||||
fd.set_device_attr(&init_gic_attr)
|
||||
.map_err(Error::SetDeviceAttribute)
|
||||
}
|
||||
|
||||
/// Trait for GIC devices.
|
||||
pub trait GICDevice: Send {
|
||||
/// Returns the file descriptor of the GIC device
|
||||
fn device_fd(&self) -> &DeviceFd;
|
||||
|
||||
/// Returns an array with GIC device properties
|
||||
fn device_properties(&self) -> &[u64];
|
||||
|
||||
/// Returns the number of vCPUs this GIC handles
|
||||
fn vcpu_count(&self) -> u64;
|
||||
|
||||
/// Returns the fdt compatibility property of the device
|
||||
fn fdt_compatibility(&self) -> &str;
|
||||
|
||||
/// Returns the maint_irq fdt property of the device
|
||||
fn fdt_maint_irq(&self) -> u32;
|
||||
|
||||
/// Get ITS reg range
|
||||
fn get_its_reg_range(&self, _its_type: &its::ItsType) -> Option<[u64; 2]> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Only gic-v3 has its
|
||||
fn attach_its(&mut self, _vm: &VmFd) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the GIC version of the device
|
||||
fn version() -> u32
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Create the GIC device object
|
||||
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Setup the device-specific attributes
|
||||
fn init_device_attributes(gic_device: &dyn GICDevice) -> Result<()>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Initialize a GIC device
|
||||
fn init_device(vm: &VmFd) -> Result<DeviceFd>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut gic_device = kvm_bindings::kvm_create_device {
|
||||
type_: Self::version(),
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
vm.create_device(&mut gic_device).map_err(Error::CreateGIC)
|
||||
}
|
||||
|
||||
/// Set a GIC device attribute
|
||||
fn set_device_attribute(
|
||||
fd: &DeviceFd,
|
||||
group: u32,
|
||||
attr: u64,
|
||||
addr: u64,
|
||||
flags: u32,
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let attr = kvm_bindings::kvm_device_attr {
|
||||
group,
|
||||
attr,
|
||||
addr,
|
||||
flags,
|
||||
};
|
||||
fd.set_device_attr(&attr)
|
||||
.map_err(Error::SetDeviceAttribute)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finalize the setup of a GIC device
|
||||
fn finalize_device(gic_device: &dyn GICDevice) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/* We need to tell the kernel how many irqs to support with this vgic.
|
||||
* See the `layout` module for details.
|
||||
*/
|
||||
let nr_irqs: u32 = IRQ_MAX - IRQ_BASE + 1;
|
||||
let nr_irqs_ptr = &nr_irqs as *const u32;
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
0,
|
||||
nr_irqs_ptr as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Finalize the GIC.
|
||||
* See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
|
||||
0,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
/// Method to initialize the GIC device
|
||||
fn new(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let vgic_fd = Self::init_device(vm)?;
|
||||
|
||||
let mut device = Self::create_device(vgic_fd, vcpu_count);
|
||||
|
||||
device.attach_its(vm)?;
|
||||
|
||||
Self::init_device_attributes(device.as_ref())?;
|
||||
|
||||
Self::finalize_device(device.as_ref())?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a GIC device.
|
||||
///
|
||||
/// It will try to create by default a GICv3 device. If that fails it will try
|
||||
/// to fall-back to a GICv2 device.
|
||||
pub fn create_gic(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
GICv3::new(vm, vcpu_count).or_else(|_| GICv2::new(vm, vcpu_count))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
#[test]
|
||||
fn test_create_gic() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
assert!(create_gic(&vm, 1).is_ok());
|
||||
}
|
||||
}
|
||||
139
src/dragonball/src/dbs_arch/src/aarch64/mod.rs
Normal file
139
src/dragonball/src/dbs_arch/src/aarch64/mod.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! CPU architecture specific constants, structures and utilities for the `aarch64` architecture.
|
||||
|
||||
/// Module for the global interrupt controller configuration.
|
||||
pub mod gic;
|
||||
/// Module for PMU virtualization.
|
||||
pub mod pmu;
|
||||
/// Logic for configuring aarch64 registers.
|
||||
pub mod regs;
|
||||
|
||||
use std::{fmt, result};
|
||||
|
||||
const MMIO_DEVICE_LEGACY_IRQ_NUMBER: usize = 1;
|
||||
|
||||
/// Error for ARM64 architecture information
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// MMIO device information error
|
||||
MMIODeviceInfoError,
|
||||
/// Invalid arguments
|
||||
InvalidArguments,
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Types of devices that can get attached to this platform.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
|
||||
pub enum DeviceType {
|
||||
/// Device Type: Virtio.
|
||||
Virtio(u32),
|
||||
/// Device Type: Serial.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
RTC,
|
||||
}
|
||||
|
||||
impl fmt::Display for DeviceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for devices to be added to the Flattened Device Tree.
|
||||
pub trait DeviceInfoForFDT {
|
||||
/// Returns the address where this device will be loaded.
|
||||
fn addr(&self) -> u64;
|
||||
/// Returns the amount of memory that needs to be reserved for this device.
|
||||
fn length(&self) -> u64;
|
||||
/// Returns the associated interrupt for this device.
|
||||
fn irq(&self) -> Result<u32>;
|
||||
/// Get device id
|
||||
fn get_device_id(&self) -> Option<u32>;
|
||||
}
|
||||
|
||||
/// MMIO device info used for FDT generating.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MMIODeviceInfo {
|
||||
/// MMIO address base
|
||||
pub base: u64,
|
||||
/// MMIO address size
|
||||
pub size: u64,
|
||||
/// Device irq
|
||||
pub irqs: Vec<u32>,
|
||||
/// Only virtio devices that support platform msi have device id
|
||||
pub device_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl MMIODeviceInfo {
|
||||
/// Create mmio device info.
|
||||
pub fn new(base: u64, size: u64, irqs: Vec<u32>, device_id: Option<u32>) -> Self {
|
||||
MMIODeviceInfo {
|
||||
base,
|
||||
size,
|
||||
irqs,
|
||||
device_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.base
|
||||
}
|
||||
|
||||
fn length(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn irq(&self) -> Result<u32> {
|
||||
// Currently mmio devices have only one legacy irq.
|
||||
if self.irqs.len() != MMIO_DEVICE_LEGACY_IRQ_NUMBER {
|
||||
return Err(Error::MMIODeviceInfoError);
|
||||
}
|
||||
let irq = self.irqs[0];
|
||||
if !(gic::IRQ_BASE..=gic::IRQ_MAX).contains(&irq) {
|
||||
return Err(Error::MMIODeviceInfoError);
|
||||
}
|
||||
|
||||
Ok(irq)
|
||||
}
|
||||
|
||||
fn get_device_id(&self) -> Option<u32> {
|
||||
self.device_id
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mmo_device_info() {
|
||||
let info = MMIODeviceInfo::new(0x1000, 0x2000, vec![gic::IRQ_BASE], Some(5));
|
||||
assert_eq!(info.addr(), 0x1000);
|
||||
assert_eq!(info.length(), 0x2000);
|
||||
assert_eq!(info.irq().unwrap(), gic::IRQ_BASE);
|
||||
assert_eq!(info.get_device_id(), Some(5));
|
||||
|
||||
let info = MMIODeviceInfo::new(0x1000, 0x2000, vec![gic::IRQ_BASE], None);
|
||||
assert_eq!(info.get_device_id(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmo_device_info_get_irq() {
|
||||
let info = MMIODeviceInfo::new(0x1000, 0x1000, vec![], None);
|
||||
assert!(info.irq().is_err());
|
||||
let info = MMIODeviceInfo::new(0x1000, 0x1000, vec![1, 2], None);
|
||||
assert!(info.irq().is_err());
|
||||
let info = MMIODeviceInfo::new(0x1000, 0x1000, vec![gic::IRQ_BASE - 1], None);
|
||||
assert!(info.irq().is_err());
|
||||
let info = MMIODeviceInfo::new(0x1000, 0x1000, vec![gic::IRQ_MAX + 1], None);
|
||||
assert!(info.irq().is_err());
|
||||
}
|
||||
}
|
||||
172
src/dragonball/src/dbs_arch/src/aarch64/pmu.rs
Normal file
172
src/dragonball/src/dbs_arch/src/aarch64/pmu.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Constants and utilities for aarch64 PMU virtualization.
|
||||
|
||||
use kvm_bindings::{
|
||||
kvm_device_attr, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_INIT, KVM_ARM_VCPU_PMU_V3_IRQ,
|
||||
};
|
||||
use kvm_ioctls::{Error as KvmError, VcpuFd, VmFd};
|
||||
use thiserror::Error;
|
||||
|
||||
/// PPI base number on aarch64.
|
||||
pub const PPI_BASE: u32 = 16;
|
||||
/// Pmu ppi number
|
||||
pub const VIRTUAL_PMU_IRQ: u32 = 7;
|
||||
|
||||
/// Errors thrown while setting up the PMU.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PmuError {
|
||||
/// Error while check kvm pmu capability
|
||||
#[error("Check kvm pmu capability failed: {0}")]
|
||||
CheckKvmPmuCap(#[source] KvmError),
|
||||
/// Error while check pmu irq.
|
||||
#[error("Check pmu irq error: {0}")]
|
||||
HasPmuIrq(#[source] KvmError),
|
||||
/// Error while check pmu init.
|
||||
#[error("Check pmu init error: {0}")]
|
||||
HasPmuInit(#[source] KvmError),
|
||||
/// Error while set pmu irq.
|
||||
#[error("Set pmu irq error: {0}")]
|
||||
SetPmuIrq(#[source] KvmError),
|
||||
/// Error while set pmu init.
|
||||
#[error("Set pmu init error: {0}")]
|
||||
SetPmuInit(#[source] KvmError),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, PmuError>;
|
||||
|
||||
/// Tests whether a cpu supports KVM_ARM_VCPU_PMU_V3_IRQ attribute.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
fn has_pmu_irq(vcpu: &VcpuFd) -> Result<()> {
|
||||
let irq = (VIRTUAL_PMU_IRQ + PPI_BASE) as u64;
|
||||
let attribute = kvm_device_attr {
|
||||
group: KVM_ARM_VCPU_PMU_V3_CTRL,
|
||||
attr: u64::from(KVM_ARM_VCPU_PMU_V3_IRQ),
|
||||
addr: &irq as *const u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
vcpu.has_device_attr(&attribute)
|
||||
.map_err(PmuError::HasPmuIrq)
|
||||
}
|
||||
|
||||
/// Tests whether a cpu supports KVM_ARM_VCPU_PMU_V3_INIT attribute.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
fn has_pmu_init(vcpu: &VcpuFd) -> Result<()> {
|
||||
let attribute = kvm_device_attr {
|
||||
group: KVM_ARM_VCPU_PMU_V3_CTRL,
|
||||
attr: u64::from(KVM_ARM_VCPU_PMU_V3_INIT),
|
||||
addr: 0,
|
||||
flags: 0,
|
||||
};
|
||||
vcpu.has_device_attr(&attribute)
|
||||
.map_err(PmuError::HasPmuInit)
|
||||
}
|
||||
|
||||
/// Set KVM_ARM_VCPU_PMU_V3_IRQ for a specific vcpu.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
fn set_pmu_irq(vcpu: &VcpuFd) -> Result<()> {
|
||||
let irq = (VIRTUAL_PMU_IRQ + PPI_BASE) as u64;
|
||||
let attribute = kvm_device_attr {
|
||||
group: KVM_ARM_VCPU_PMU_V3_CTRL,
|
||||
attr: u64::from(KVM_ARM_VCPU_PMU_V3_IRQ),
|
||||
addr: &irq as *const u64 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
vcpu.set_device_attr(&attribute)
|
||||
.map_err(PmuError::SetPmuIrq)
|
||||
}
|
||||
|
||||
/// Set KVM_ARM_VCPU_PMU_V3_INIT for a specific vcpu.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
fn set_pmu_init(vcpu: &VcpuFd) -> Result<()> {
|
||||
let attribute = kvm_device_attr {
|
||||
group: KVM_ARM_VCPU_PMU_V3_CTRL,
|
||||
attr: u64::from(KVM_ARM_VCPU_PMU_V3_INIT),
|
||||
addr: 0,
|
||||
flags: 0,
|
||||
};
|
||||
vcpu.set_device_attr(&attribute)
|
||||
.map_err(PmuError::SetPmuInit)
|
||||
}
|
||||
|
||||
/// Check kvm pmu capability
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vm` - The VM file descriptor
|
||||
fn check_kvm_pmu_cap(_vm: &VmFd) -> Result<()> {
|
||||
// TODO: check KVM_CAP_ARM_PMU_V3 capability before setting PMU
|
||||
// Cap for KVM_CAP_ARM_PMU_V3 isn't supported in kvm-ioctls upstream, so
|
||||
// leave a todo here for supporting this check in the future.
|
||||
// Interface: vm.check_extension(kvm_ioctls::Cap)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check pmu feature
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
fn check_pmu_feature(vcpu: &VcpuFd) -> Result<()> {
|
||||
has_pmu_irq(vcpu)?;
|
||||
has_pmu_init(vcpu)
|
||||
}
|
||||
|
||||
/// Set pmu feature
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
fn set_pmu_feature(vcpu: &VcpuFd) -> Result<()> {
|
||||
set_pmu_irq(vcpu)?;
|
||||
set_pmu_init(vcpu)
|
||||
}
|
||||
|
||||
/// Initialize PMU in for vcpu
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vm` - The VM file descriptor
|
||||
/// * `vcpu` - The VCPU file descriptor
|
||||
pub fn initialize_pmu(vm: &VmFd, vcpu: &VcpuFd) -> Result<()> {
|
||||
check_kvm_pmu_cap(vm)?;
|
||||
check_pmu_feature(vcpu)?;
|
||||
set_pmu_feature(vcpu)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kvm_bindings::{kvm_vcpu_init, KVM_ARM_VCPU_PMU_V3, KVM_ARM_VCPU_PSCI_0_2};
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
use super::*;
|
||||
use crate::gic::create_gic;
|
||||
|
||||
#[test]
|
||||
fn test_create_pmu() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
|
||||
assert!(create_gic(&vm, 1).is_ok());
|
||||
assert!(initialize_pmu(&vm, &vcpu).is_err());
|
||||
|
||||
if check_kvm_pmu_cap(&vm).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi)
|
||||
.expect("Cannot get preferred target");
|
||||
kvi.features[0] = 1 << KVM_ARM_VCPU_PSCI_0_2 | 1 << KVM_ARM_VCPU_PMU_V3;
|
||||
|
||||
assert!(vcpu.vcpu_init(&kvi).is_ok());
|
||||
assert!(initialize_pmu(&vm, &vcpu).is_ok());
|
||||
}
|
||||
}
|
||||
200
src/dragonball/src/dbs_arch/src/aarch64/regs.rs
Normal file
200
src/dragonball/src/dbs_arch/src/aarch64/regs.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Constants and utilities for aarch64 CPU generic, system and model specific registers.
|
||||
|
||||
use std::{mem, result};
|
||||
|
||||
use kvm_bindings::*;
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use memoffset::offset_of;
|
||||
use vmm_sys_util;
|
||||
|
||||
/// Errors thrown while setting aarch64 registers.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get core register (PC, PSTATE or general purpose ones).
|
||||
GetCoreRegister(kvm_ioctls::Error),
|
||||
/// Failed to set core register (PC, PSTATE or general purpose ones).
|
||||
SetCoreRegister(kvm_ioctls::Error),
|
||||
/// Failed to get a system register.
|
||||
GetSysRegister(kvm_ioctls::Error),
|
||||
/// Failed to get the register list.
|
||||
GetRegList(kvm_ioctls::Error),
|
||||
/// Failed to get a system register.
|
||||
SetRegister(kvm_ioctls::Error),
|
||||
/// Failed to init fam reglist
|
||||
FamRegister(vmm_sys_util::fam::Error),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
// PSR (Processor State Register) bits.
|
||||
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
|
||||
const PSR_MODE_EL1h: u64 = 0x0000_0005;
|
||||
const PSR_F_BIT: u64 = 0x0000_0040;
|
||||
const PSR_I_BIT: u64 = 0x0000_0080;
|
||||
const PSR_A_BIT: u64 = 0x0000_0100;
|
||||
const PSR_D_BIT: u64 = 0x0000_0200;
|
||||
// Taken from arch/arm64/kvm/inject_fault.c.
|
||||
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
|
||||
|
||||
// Following are macros that help with getting the ID of a aarch64 core register.
|
||||
// The core register are represented by the user_pt_regs structure. Look for it in
|
||||
// arch/arm64/include/uapi/asm/ptrace.h.
|
||||
|
||||
macro_rules! arm64_core_reg {
|
||||
($reg: tt) => {
|
||||
// As per `kvm_arm_copy_reg_indices`, the id of a core register can be obtained like this:
|
||||
// `const u64 core_reg = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | i`, where i is obtained with:
|
||||
// `for (i = 0; i < sizeof(struct kvm_regs) / sizeof(__u32); i++) {`
|
||||
// We are using here `user_pt_regs` since this structure contains the core register and it is at
|
||||
// the start of `kvm_regs`.
|
||||
// struct kvm_regs {
|
||||
// struct user_pt_regs regs; /* sp = sp_el0 */
|
||||
//
|
||||
// __u64 sp_el1;
|
||||
// __u64 elr_el1;
|
||||
//
|
||||
// __u64 spsr[KVM_NR_SPSR];
|
||||
//
|
||||
// struct user_fpsimd_state fp_regs;
|
||||
//};
|
||||
// struct user_pt_regs {
|
||||
// __u64 regs[31];
|
||||
// __u64 sp;
|
||||
// __u64 pc;
|
||||
// __u64 pstate;
|
||||
//};
|
||||
// In our implementation we need: pc, pstate and user_pt_regs->regs[0].
|
||||
KVM_REG_ARM64 as u64
|
||||
| KVM_REG_SIZE_U64 as u64
|
||||
| u64::from(KVM_REG_ARM_CORE)
|
||||
| ((offset_of!(user_pt_regs, $reg) / mem::size_of::<u32>()) as u64)
|
||||
};
|
||||
}
|
||||
|
||||
// This macro computes the ID of a specific ARM64 system register similar to how
|
||||
// the kernel C macro does.
|
||||
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203
|
||||
macro_rules! arm64_sys_reg {
|
||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||
const $name: u64 = KVM_REG_ARM64 as u64
|
||||
| KVM_REG_SIZE_U64 as u64
|
||||
| KVM_REG_ARM64_SYSREG as u64
|
||||
| ((($op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
|
||||
| ((($op1 as u64) << KVM_REG_ARM64_SYSREG_OP1_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP1_MASK as u64)
|
||||
| ((($crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_CRN_MASK as u64)
|
||||
| ((($crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_CRM_MASK as u64)
|
||||
| ((($op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
|
||||
};
|
||||
}
|
||||
|
||||
// Constant imported from the Linux kernel:
|
||||
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/asm/sysreg.h#L135
|
||||
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
||||
|
||||
/// Configure core registers for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `cpu_id` - Index of current vcpu.
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `mem` - Reserved DRAM for current VM.
|
||||
pub fn setup_regs(vcpu: &VcpuFd, cpu_id: u8, boot_ip: u64, fdt_address: u64) -> Result<()> {
|
||||
// Get the register index of the PSTATE (Processor State) register.
|
||||
vcpu.set_one_reg(arm64_core_reg!(pstate), PSTATE_FAULT_BITS_64 as u128)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
|
||||
// Other vCPUs are powered off initially awaiting PSCI wakeup.
|
||||
if cpu_id == 0 {
|
||||
// Setting the PC (Processor Counter) to the current program address (kernel address).
|
||||
vcpu.set_one_reg(arm64_core_reg!(pc), boot_ip as u128)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
|
||||
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
|
||||
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
|
||||
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
|
||||
vcpu.set_one_reg(arm64_core_reg!(regs), fdt_address as u128)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Specifies whether a particular register is a system register or not.
|
||||
/// The kernel splits the registers on aarch64 in core registers and system registers.
|
||||
/// So, below we get the system registers by checking that they are not core registers.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `regid` - The index of the register we are checking.
|
||||
pub fn is_system_register(regid: u64) -> bool {
|
||||
if (regid & KVM_REG_ARM_COPROC_MASK as u64) == KVM_REG_ARM_CORE as u64 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let size = regid & KVM_REG_SIZE_MASK;
|
||||
if size != KVM_REG_SIZE_U32 && size != KVM_REG_SIZE_U64 {
|
||||
panic!("Unexpected register size for system register {}", size);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Read the MPIDR - Multiprocessor Affinity Register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn read_mpidr(vcpu: &VcpuFd) -> Result<u64> {
|
||||
vcpu.get_one_reg(MPIDR_EL1)
|
||||
.map(|value| value as u64)
|
||||
.map_err(Error::GetSysRegister)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
#[test]
|
||||
fn test_setup_regs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
match setup_regs(&vcpu, 0, 0x0, crate::gic::GIC_REG_END_ADDRESS).unwrap_err() {
|
||||
Error::SetCoreRegister(ref e) => assert_eq!(e.errno(), libc::ENOEXEC),
|
||||
_ => panic!("Expected to receive Error::SetCoreRegister"),
|
||||
}
|
||||
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
vcpu.vcpu_init(&kvi).unwrap();
|
||||
|
||||
assert!(setup_regs(&vcpu, 0, 0x0, crate::gic::GIC_REG_END_ADDRESS).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_mpidr() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
|
||||
// Must fail when vcpu is not initialized yet.
|
||||
assert!(read_mpidr(&vcpu).is_err());
|
||||
|
||||
vcpu.vcpu_init(&kvi).unwrap();
|
||||
assert_eq!(read_mpidr(&vcpu).unwrap(), 0x80000000);
|
||||
}
|
||||
}
|
||||
67
src/dragonball/src/dbs_arch/src/lib.rs
Normal file
67
src/dragonball/src/dbs_arch/src/lib.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2021-2022 Alibaba Cloud. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! CPU architecture specific constants, structures and utilities.
|
||||
//!
|
||||
//! This crate provides CPU architecture specific constants, structures and utilities to abstract
|
||||
//! away CPU architecture specific details from the Dragonball Secure Sandbox or other VMMs.
|
||||
//!
|
||||
//! # Supported CPU Architectures
|
||||
//! - **x86_64**: x86_64 (also known as x64, x86-64, AMD64, and Intel 64) is a 64-bit
|
||||
//! version of the x86 instruction set.
|
||||
//! - **ARM64**: AArch64 or ARM64 is the 64-bit extension of the ARM architecture.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::*;
|
||||
|
||||
/// Enum indicating vpmu feature level
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum VpmuFeatureLevel {
|
||||
/// Disabled means vpmu feature is off (by default)
|
||||
Disabled,
|
||||
/// LimitedlyEnabled means minimal vpmu counters are supported( only cycles and instructions )
|
||||
/// For aarch64, LimitedlyEnabled isn't supported currently. The ability will be implemented in the future.
|
||||
LimitedlyEnabled,
|
||||
/// FullyEnabled means all vpmu counters are supported
|
||||
FullyEnabled,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_debug_trait() {
|
||||
let level = VpmuFeatureLevel::Disabled;
|
||||
assert_eq!(format!("{level:#?}"), "Disabled");
|
||||
|
||||
let level = VpmuFeatureLevel::LimitedlyEnabled;
|
||||
assert_eq!(format!("{level:#?}"), "LimitedlyEnabled");
|
||||
|
||||
let level = VpmuFeatureLevel::FullyEnabled;
|
||||
assert_eq!(format!("{level:#?}"), "FullyEnabled");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq_trait() {
|
||||
let level = VpmuFeatureLevel::Disabled;
|
||||
assert!(level == VpmuFeatureLevel::Disabled);
|
||||
assert!(level != VpmuFeatureLevel::LimitedlyEnabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_trait() {
|
||||
let level1 = VpmuFeatureLevel::Disabled;
|
||||
let level2 = level1;
|
||||
assert_eq!(level1, level2);
|
||||
}
|
||||
}
|
||||
599
src/dragonball/src/dbs_arch/src/x86_64/cpuid/bit_helper.rs
Normal file
599
src/dragonball/src/dbs_arch/src/x86_64/cpuid/bit_helper.rs
Normal file
@@ -0,0 +1,599 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Helper to manipulate CPUID register content.
|
||||
|
||||
#![macro_use]
|
||||
|
||||
/// Structure representing a range of bits in a number.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// use dbs_arch::cpuid::bit_helper::*;
|
||||
///
|
||||
/// let range = BitRange {
|
||||
/// msb_index: 7,
|
||||
/// lsb_index: 3,
|
||||
/// };
|
||||
/// ```
|
||||
/// The BitRange specified above will represent the following part of the number 72:
|
||||
/// +-------------------------------------+---+---+---+---+---+---+---+---+---+---+
|
||||
/// | Base 2 Representation of the number | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
|
||||
/// +-------------------------------------+---+---+---+---+---+---+---+---+---+---+
|
||||
/// | bits indexes | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||
/// +-------------------------------------+---+---+---+---+---+---+---+---+---+---+
|
||||
/// | BitRange | | | * | * | * | * | * | | | |
|
||||
/// +-------------------------------------+---+---+---+---+---+---+---+---+---+---+
|
||||
pub struct BitRange {
|
||||
/// most significant bit index
|
||||
pub msb_index: u32,
|
||||
/// least significant bit index
|
||||
pub lsb_index: u32,
|
||||
}
|
||||
|
||||
/// Trait containing helper methods for [`BitRange`](struct.BitRange.html)
|
||||
///
|
||||
/// The methods are needed for:
|
||||
/// - checking if the `BitRange` is valid for a type `T`
|
||||
/// - creating masks for a type `T`
|
||||
pub trait BitRangeExt<T> {
|
||||
/// Returns a value of type `T` that has all the bits in the specified bit range set to 1.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// use dbs_arch::cpuid::bit_helper::*;
|
||||
///
|
||||
/// let range = BitRange {
|
||||
/// msb_index: 7,
|
||||
/// lsb_index: 3,
|
||||
/// };
|
||||
/// println!("binary value: {:b}", range.get_mask());
|
||||
/// ```
|
||||
/// The code above will print:
|
||||
/// ```bash
|
||||
/// binary value: 11111000
|
||||
/// ```
|
||||
fn get_mask(&self) -> T;
|
||||
|
||||
/// Checks if the current BitRange is valid for type `T`.
|
||||
fn is_valid(&self) -> bool;
|
||||
|
||||
/// Asserts if `self.is_valid()` returns true.
|
||||
fn check(&self) {
|
||||
assert!(self.is_valid(), "Invalid BitRange");
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_U32_BIT_INDEX: u32 = 31;
|
||||
|
||||
impl BitRangeExt<u32> for BitRange {
|
||||
fn get_mask(&self) -> u32 {
|
||||
self.check();
|
||||
|
||||
((((1_u64) << (self.msb_index - self.lsb_index + 1)) - 1) << self.lsb_index) as u32
|
||||
}
|
||||
|
||||
fn is_valid(&self) -> bool {
|
||||
self.msb_index >= self.lsb_index && self.msb_index <= MAX_U32_BIT_INDEX
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bit_range {
|
||||
($msb_index:expr, $lsb_index:expr) => {
|
||||
BitRange {
|
||||
msb_index: $msb_index,
|
||||
lsb_index: $lsb_index,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait containing helper methods for bit operations.
|
||||
pub trait BitHelper {
|
||||
/// Reads the value of the bit at position `pos`
|
||||
fn read_bit(&self, pos: u32) -> bool;
|
||||
|
||||
/// Changes the value of the bit at position `pos` to `val`
|
||||
fn write_bit(&mut self, pos: u32, val: bool) -> &mut Self;
|
||||
|
||||
/// Reads the value stored within the specified range of bits
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// use dbs_arch::cpuid::bit_helper::*;
|
||||
///
|
||||
/// let val: u32 = 0b000010001000;
|
||||
/// let range = BitRange {
|
||||
/// msb_index: 7,
|
||||
/// lsb_index: 3,
|
||||
/// };
|
||||
/// println!("binary value: {:b}", val.read_bits_in_range(&range));
|
||||
/// ```
|
||||
/// The code above will print:
|
||||
/// ```bash
|
||||
/// binary value: 10001
|
||||
/// ```
|
||||
fn read_bits_in_range(&self, bit_range: &BitRange) -> Self;
|
||||
|
||||
/// Stores a value within the specified range of bits
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// use dbs_arch::cpuid::bit_helper::*;
|
||||
///
|
||||
/// let mut val: u32 = 0;
|
||||
/// let range = BitRange {
|
||||
/// msb_index: 7,
|
||||
/// lsb_index: 3,
|
||||
/// };
|
||||
/// val.write_bits_in_range(&range, 0b10001 as u32);
|
||||
/// println!("binary value: {:b}", val);
|
||||
/// ```
|
||||
/// The code above will print:
|
||||
/// ```bash
|
||||
/// binary value: 10001000
|
||||
/// ```
|
||||
fn write_bits_in_range(&mut self, bit_range: &BitRange, val: Self) -> &mut Self;
|
||||
}
|
||||
|
||||
impl BitHelper for u32 {
|
||||
fn read_bit(&self, pos: u32) -> bool {
|
||||
assert!(pos <= MAX_U32_BIT_INDEX, "Invalid pos");
|
||||
|
||||
(*self & (1 << pos)) > 0
|
||||
}
|
||||
|
||||
fn write_bit(&mut self, pos: u32, val: bool) -> &mut Self {
|
||||
assert!(pos <= MAX_U32_BIT_INDEX, "Invalid pos");
|
||||
|
||||
*self &= !(1 << pos);
|
||||
*self |= (val as u32) << pos;
|
||||
self
|
||||
}
|
||||
|
||||
fn read_bits_in_range(&self, range: &BitRange) -> Self {
|
||||
range.check();
|
||||
|
||||
(self & range.get_mask()) >> range.lsb_index
|
||||
}
|
||||
|
||||
fn write_bits_in_range(&mut self, range: &BitRange, val: Self) -> &mut Self {
|
||||
range.check();
|
||||
let mask = range.get_mask();
|
||||
let max_val = mask >> range.lsb_index;
|
||||
assert!(val <= max_val, "Invalid val");
|
||||
|
||||
*self &= !mask;
|
||||
*self |= val << range.lsb_index;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cpuid::bit_helper::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_msb_index() {
|
||||
let range = BitRange {
|
||||
msb_index: 32,
|
||||
lsb_index: 2,
|
||||
};
|
||||
range.check();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_range() {
|
||||
let range = BitRange {
|
||||
msb_index: 10,
|
||||
lsb_index: 15,
|
||||
};
|
||||
range.check();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_write_bit() {
|
||||
// Set bit to 1
|
||||
let mut val: u32 = 0;
|
||||
val.write_bit(32, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_write_bit() {
|
||||
// Set bit to 1
|
||||
let mut val: u32 = 0;
|
||||
val.write_bit(5, true);
|
||||
assert!(val == 1 << 5);
|
||||
|
||||
// Set bit to 0
|
||||
val = 1 << 5;
|
||||
val.write_bit(5, false);
|
||||
assert!(val == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_read_bit() {
|
||||
// Set bit to 1
|
||||
let val: u32 = 0;
|
||||
val.read_bit(32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_read_bit() {
|
||||
// Set bit to 1
|
||||
let val: u32 = 0b10_0000;
|
||||
assert!(val.read_bit(5));
|
||||
assert!(!val.read_bit(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chained_write_bit() {
|
||||
let mut val: u32 = 1 << 12;
|
||||
|
||||
val.write_bit(5, true)
|
||||
.write_bit(10, true)
|
||||
.write_bit(15, true)
|
||||
.write_bit(12, false);
|
||||
assert!(val == 1 << 5 | 1 << 10 | 1 << 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_u32_mask_for_range() {
|
||||
// Test a couple of successive ranges
|
||||
assert!(
|
||||
BitRange {
|
||||
msb_index: 3,
|
||||
lsb_index: 2
|
||||
}
|
||||
.get_mask()
|
||||
== 0b1100
|
||||
);
|
||||
assert!(
|
||||
BitRange {
|
||||
msb_index: 4,
|
||||
lsb_index: 2
|
||||
}
|
||||
.get_mask()
|
||||
== 0b11100
|
||||
);
|
||||
assert!(
|
||||
BitRange {
|
||||
msb_index: 5,
|
||||
lsb_index: 2
|
||||
}
|
||||
.get_mask()
|
||||
== 0b11_1100
|
||||
);
|
||||
assert!(
|
||||
BitRange {
|
||||
msb_index: 6,
|
||||
lsb_index: 2
|
||||
}
|
||||
.get_mask()
|
||||
== 0b111_1100
|
||||
);
|
||||
assert!(
|
||||
BitRange {
|
||||
msb_index: 7,
|
||||
lsb_index: 2
|
||||
}
|
||||
.get_mask()
|
||||
== 0b1111_1100
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_read_bits() {
|
||||
let val: u32 = 30;
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 32,
|
||||
lsb_index: 2,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_bits() {
|
||||
let val: u32 = 0b1000_0000_0000_0000_0011_0101_0001_0000;
|
||||
|
||||
// Test a couple of successive ranges
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 3,
|
||||
lsb_index: 2
|
||||
}) == 0b00
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 4,
|
||||
lsb_index: 2
|
||||
}) == 0b100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 5,
|
||||
lsb_index: 2
|
||||
}) == 0b0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 6,
|
||||
lsb_index: 2
|
||||
}) == 0b00100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 7,
|
||||
lsb_index: 2
|
||||
}) == 0b00_0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 8,
|
||||
lsb_index: 2
|
||||
}) == 0b100_0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 9,
|
||||
lsb_index: 2
|
||||
}) == 0b0100_0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 10,
|
||||
lsb_index: 2
|
||||
}) == 0b1_0100_0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 11,
|
||||
lsb_index: 2
|
||||
}) == 0b01_0100_0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 12,
|
||||
lsb_index: 2
|
||||
}) == 0b101_0100_0100
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 13,
|
||||
lsb_index: 2
|
||||
}) == 0b1101_0100_0100
|
||||
);
|
||||
|
||||
// Test max left and max right
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 31,
|
||||
lsb_index: 15
|
||||
}) == 0b1_0000_0000_0000_0000
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 14,
|
||||
lsb_index: 0
|
||||
}) == 0b011_0101_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.read_bits_in_range(&BitRange {
|
||||
msb_index: 31,
|
||||
lsb_index: 0
|
||||
}) == 0b1000_0000_0000_0000_0011_0101_0001_0000
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_invalid_write_bits() {
|
||||
let mut val: u32 = 0;
|
||||
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 32,
|
||||
lsb_index: 2,
|
||||
},
|
||||
0b100,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_overflow_write_bits() {
|
||||
let mut val: u32 = 0;
|
||||
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 3,
|
||||
lsb_index: 2,
|
||||
},
|
||||
0b100,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_write_bits() {
|
||||
let mut val: u32 = 0;
|
||||
|
||||
// Test a couple of successive ranges
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 3,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b00
|
||||
) == &0b0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 4,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b100
|
||||
) == &0b10000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 5,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b0100
|
||||
) == &0b01_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 6,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b0_0100
|
||||
) == &0b001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 7,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b00_0100
|
||||
) == &0b0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 8,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b100_0100
|
||||
) == &0b1_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 9,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b0100_0100
|
||||
) == &0b01_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 10,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b1_0100_0100
|
||||
) == &0b101_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 11,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b01_0100_0100
|
||||
) == &0b0101_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 12,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b101_0100_0100
|
||||
) == &0b1_0101_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 13,
|
||||
lsb_index: 2
|
||||
},
|
||||
0b1101_0100_0100
|
||||
) == &0b11_0101_0001_0000
|
||||
);
|
||||
|
||||
// Test max left and max right
|
||||
val = 0;
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 31,
|
||||
lsb_index: 15
|
||||
},
|
||||
0b1_0000_0000_0000_0000
|
||||
) == &0b1000_0000_0000_0000_0000_0000_0000_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 14,
|
||||
lsb_index: 0
|
||||
},
|
||||
0b011_0101_0001_0000
|
||||
) == &0b1000_0000_0000_0000_0011_0101_0001_0000
|
||||
);
|
||||
assert!(
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 31,
|
||||
lsb_index: 0
|
||||
},
|
||||
0b1000_0000_0000_0000_0011_0101_0001_0000
|
||||
) == &0b1000_0000_0000_0000_0011_0101_0001_0000
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chained_write_bits() {
|
||||
let mut val: u32 = 0;
|
||||
|
||||
// Test a couple of ranges
|
||||
val.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 4,
|
||||
lsb_index: 2,
|
||||
},
|
||||
0b100,
|
||||
)
|
||||
.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 12,
|
||||
lsb_index: 10,
|
||||
},
|
||||
0b110,
|
||||
)
|
||||
.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 24,
|
||||
lsb_index: 20,
|
||||
},
|
||||
0b10101,
|
||||
)
|
||||
.write_bits_in_range(
|
||||
&BitRange {
|
||||
msb_index: 31,
|
||||
lsb_index: 28,
|
||||
},
|
||||
0b1011,
|
||||
);
|
||||
|
||||
assert!(val == 0b1011_0001_0101_0000_0001_1000_0001_0000);
|
||||
}
|
||||
}
|
||||
462
src/dragonball/src/dbs_arch/src/x86_64/cpuid/brand_string.rs
Normal file
462
src/dragonball/src/dbs_arch/src/x86_64/cpuid/brand_string.rs
Normal file
@@ -0,0 +1,462 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::arch::x86_64::__cpuid as host_cpuid;
|
||||
use std::slice;
|
||||
|
||||
use crate::cpuid::common::{VENDOR_ID_AMD, VENDOR_ID_INTEL};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum Error {
|
||||
NotSupported,
|
||||
Overflow(String),
|
||||
}
|
||||
|
||||
/// Register designations used to get/set specific register values within the brand string buffer.
|
||||
pub enum Reg {
|
||||
Eax = 0,
|
||||
Ebx = 1,
|
||||
Ecx = 2,
|
||||
Edx = 3,
|
||||
}
|
||||
|
||||
const BRAND_STRING_INTEL: &[u8] = b"Intel(R) Xeon(R) Processor";
|
||||
const BRAND_STRING_AMD: &[u8] = b"AMD EPYC";
|
||||
|
||||
/// A CPUID brand string wrapper, providing some efficient manipulation primitives.
|
||||
///
|
||||
/// This is achieved by bypassing the `O(n)` indexing, heap allocation, and the unicode checks
|
||||
/// done by `std::string::String`.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BrandString {
|
||||
/// Flattened buffer, holding an array of 32-bit register values.
|
||||
///
|
||||
/// It has the following layout:
|
||||
/// reg_buf[0] = leaf_0x80000002.Eax
|
||||
/// reg_buf[1] = leaf_0x80000002.Ebx
|
||||
/// reg_buf[2] = leaf_0x80000002.Ecx
|
||||
/// reg_buf[3] = leaf_0x80000002.Edx
|
||||
/// reg_buf[4] = leaf_0x80000003.Eax
|
||||
/// ...
|
||||
/// reg_buf[10] = leaf_0x80000004.Ecx
|
||||
/// reg_buf[11] = leaf_0x80000004.Edx
|
||||
/// When seen as a byte-array, this buffer holds the ASCII-encoded CPU brand string.
|
||||
reg_buf: [u32; BrandString::REG_BUF_SIZE],
|
||||
|
||||
/// Actual string length, in bytes.
|
||||
///
|
||||
/// E.g. For "Intel CPU", this would be `strlen("Intel CPU") == 9`.
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl BrandString {
|
||||
/// Register buffer size (in number of registers).
|
||||
///
|
||||
/// There are 3 leaves (0x800000002 through 0x80000004), each with 4 regs (Eax, Ebx, Ecx, Edx).
|
||||
const REG_BUF_SIZE: usize = 3 * 4;
|
||||
|
||||
/// Max Brand string length, in bytes (also in chars, since it is ASCII-encoded).
|
||||
///
|
||||
/// The string is NULL-terminated, so the max string length is actually one byte
|
||||
/// less than the buffer size in bytes
|
||||
const MAX_LEN: usize = Self::REG_BUF_SIZE * 4 - 1;
|
||||
|
||||
/// Creates an empty brand string (0-initialized)
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Generates the emulated brand string.
|
||||
///
|
||||
/// For Intel CPUs, the brand string we expose will be:
|
||||
/// "Intel(R) Xeon(R) Processor @ {host freq}"
|
||||
/// where {host freq} is the CPU frequency, as present in the
|
||||
/// host brand string (e.g. 4.01GHz).
|
||||
///
|
||||
/// For AMD CPUs, the brand string we expose will be AMD EPYC.
|
||||
///
|
||||
/// For other CPUs, we'll just expose an empty string.
|
||||
///
|
||||
/// This is safe because we know BRAND_STRING_INTEL and BRAND_STRING_AMD to hold valid data
|
||||
/// (allowed length and holding only valid ASCII chars).
|
||||
pub fn from_vendor_id(vendor_id: &[u8; 12]) -> Result<BrandString, Error> {
|
||||
let brand = match vendor_id {
|
||||
VENDOR_ID_INTEL => {
|
||||
let mut this = BrandString::from_bytes_unchecked(BRAND_STRING_INTEL);
|
||||
if let Ok(host_bstr) = BrandString::from_host_cpuid() {
|
||||
if let Some(freq) = host_bstr.find_freq() {
|
||||
this.push_bytes(b" @ ")?;
|
||||
this.push_bytes(freq)?;
|
||||
}
|
||||
}
|
||||
this
|
||||
}
|
||||
VENDOR_ID_AMD => BrandString::from_bytes_unchecked(BRAND_STRING_AMD),
|
||||
_ => BrandString::from_bytes_unchecked(b""),
|
||||
};
|
||||
|
||||
Ok(brand)
|
||||
}
|
||||
|
||||
/// Creates a brand string, initialized from the CPUID leaves 0x80000002 through 0x80000004
|
||||
/// of the host CPU.
|
||||
fn from_host_cpuid() -> Result<Self, Error> {
|
||||
let mut this = Self::new();
|
||||
let mut cpuid_regs = unsafe { host_cpuid(0x8000_0000) };
|
||||
|
||||
if cpuid_regs.eax < 0x8000_0004 {
|
||||
// Brand string not supported by the host CPU
|
||||
return Err(Error::NotSupported);
|
||||
}
|
||||
|
||||
for leaf in 0x8000_0002..=0x8000_0004 {
|
||||
cpuid_regs = unsafe { host_cpuid(leaf) };
|
||||
this.set_reg_for_leaf(leaf, Reg::Eax, cpuid_regs.eax);
|
||||
this.set_reg_for_leaf(leaf, Reg::Ebx, cpuid_regs.ebx);
|
||||
this.set_reg_for_leaf(leaf, Reg::Ecx, cpuid_regs.ecx);
|
||||
this.set_reg_for_leaf(leaf, Reg::Edx, cpuid_regs.edx);
|
||||
}
|
||||
|
||||
let mut len = Self::MAX_LEN;
|
||||
{
|
||||
let this_bytes = this.as_bytes();
|
||||
while this_bytes[len - 1] == 0 && len > 0 {
|
||||
len -= 1;
|
||||
}
|
||||
}
|
||||
this.len = len;
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// Creates a (custom) brand string, initialized from `src`.
|
||||
///
|
||||
/// No checks are performed on the length of `src` or its contents (`src` should be an
|
||||
/// ASCII-encoded string).
|
||||
#[inline]
|
||||
fn from_bytes_unchecked(src: &[u8]) -> Self {
|
||||
let mut this = Self::new();
|
||||
this.len = src.len();
|
||||
this.as_bytes_mut()[..src.len()].copy_from_slice(src);
|
||||
this
|
||||
}
|
||||
|
||||
/// Returns the given register value for the given CPUID leaf.
|
||||
///
|
||||
/// `leaf` must be between 0x80000002 and 0x80000004.
|
||||
#[inline]
|
||||
pub fn get_reg_for_leaf(&self, leaf: u32, reg: Reg) -> u32 {
|
||||
if (0x80000002u32..=0x80000004).contains(&leaf) {
|
||||
// It's ok not to validate parameters here, leaf and reg should
|
||||
// both be compile-time constants. If there's something wrong with them,
|
||||
// that's a programming error and we should panic anyway.
|
||||
self.reg_buf[(leaf - 0x8000_0002) as usize * 4 + reg as usize]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value for the given leaf/register pair.
|
||||
///
|
||||
/// `leaf` must be between 0x80000002 and 0x80000004.
|
||||
#[inline]
|
||||
fn set_reg_for_leaf(&mut self, leaf: u32, reg: Reg, val: u32) {
|
||||
// It's ok not to validate parameters here, leaf and reg should
|
||||
// both be compile-time constants. If there's something wrong with them,
|
||||
// that's a programming error and we should panic anyway.
|
||||
self.reg_buf[(leaf - 0x8000_0002) as usize * 4 + reg as usize] = val;
|
||||
}
|
||||
|
||||
/// Gets an immutable `u8` slice view into the brand string buffer.
|
||||
#[inline]
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
// This is actually safe, because self.reg_buf has a fixed, known size,
|
||||
// and also there's no risk of misalignment, since we're downgrading
|
||||
// alignment constraints from dword to byte.
|
||||
unsafe { slice::from_raw_parts(self.reg_buf.as_ptr() as *const u8, Self::REG_BUF_SIZE * 4) }
|
||||
}
|
||||
|
||||
/// Gets a mutable `u8` slice view into the brand string buffer.
|
||||
#[inline]
|
||||
fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(self.reg_buf.as_mut_ptr() as *mut u8, Self::REG_BUF_SIZE * 4)
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts whether or not there is enough room to append `src` to the brand string.
|
||||
fn check_push(&mut self, src: &[u8]) -> bool {
|
||||
src.len() <= Self::MAX_LEN - self.len
|
||||
}
|
||||
|
||||
/// Appends `src` to the brand string if there is enough room to append it.
|
||||
fn push_bytes(&mut self, src: &[u8]) -> Result<(), Error> {
|
||||
if !self.check_push(src) {
|
||||
// No room to push all of src.
|
||||
return Err(Error::Overflow(
|
||||
"Appending to the brand string failed.".to_string(),
|
||||
));
|
||||
}
|
||||
let start = self.len;
|
||||
let count = src.len();
|
||||
self.len += count;
|
||||
self.as_bytes_mut()[start..(start + count)].copy_from_slice(src);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Searches the brand string for the CPU frequency data it may contain (e.g. 4.01GHz),
|
||||
/// and, if found, returns it as an `u8` slice.
|
||||
///
|
||||
/// Basically, we're implementing a search for this regex: "([0-9]+\.[0-9]+[MGT]Hz)".
|
||||
fn find_freq(&self) -> Option<&[u8]> {
|
||||
// The algorithm for matching the regular expression above is based
|
||||
// on a Moore machine, and 'stage' represents the current state of
|
||||
// the machine.
|
||||
enum Stages {
|
||||
/// Initial state, looking for a digit.
|
||||
Initial,
|
||||
/// Found integer part of the frequency.
|
||||
FoundFreqIntPart,
|
||||
/// Found the decimal point.
|
||||
FoundFreqDecimalPoint,
|
||||
/// Found the decimal part.
|
||||
FoundFreqDecimalPart,
|
||||
/// Found the unit size.
|
||||
FoundFreqUnitSize,
|
||||
/// Found the H in 'Hz'.
|
||||
FoundH,
|
||||
}
|
||||
|
||||
let mut freq_start = 0;
|
||||
let mut decimal_start = 0;
|
||||
|
||||
let mut stage = Stages::Initial;
|
||||
|
||||
for (i, &ch) in self.as_bytes().iter().enumerate() {
|
||||
match stage {
|
||||
Stages::Initial => {
|
||||
// Looking for one or more digits.
|
||||
if ch.is_ascii_digit() {
|
||||
freq_start = i;
|
||||
stage = Stages::FoundFreqIntPart;
|
||||
}
|
||||
}
|
||||
Stages::FoundFreqIntPart => {
|
||||
// Looking for a decimal point.
|
||||
if !ch.is_ascii_digit() {
|
||||
if ch == b'.' {
|
||||
stage = Stages::FoundFreqDecimalPoint;
|
||||
} else {
|
||||
stage = Stages::Initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
Stages::FoundFreqDecimalPoint => {
|
||||
// Looking for the decimal part.
|
||||
if ch.is_ascii_digit() {
|
||||
stage = Stages::FoundFreqDecimalPart;
|
||||
decimal_start = i;
|
||||
} else {
|
||||
stage = Stages::Initial;
|
||||
}
|
||||
}
|
||||
Stages::FoundFreqDecimalPart => {
|
||||
// Looking for the unit of measure.
|
||||
if !ch.is_ascii_digit() {
|
||||
if ch == b'.' {
|
||||
stage = Stages::FoundFreqDecimalPoint;
|
||||
freq_start = decimal_start;
|
||||
} else if ch == b'M' || ch == b'G' || ch == b'T' {
|
||||
stage = Stages::FoundFreqUnitSize;
|
||||
} else {
|
||||
stage = Stages::Initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
Stages::FoundFreqUnitSize => {
|
||||
// Looking for the 'H' in 'Hz'.
|
||||
if ch == b'H' {
|
||||
stage = Stages::FoundH;
|
||||
} else if ch.is_ascii_digit() {
|
||||
stage = Stages::FoundFreqIntPart;
|
||||
freq_start = i;
|
||||
} else {
|
||||
stage = Stages::Initial;
|
||||
}
|
||||
}
|
||||
Stages::FoundH => {
|
||||
// Looking for the 'z' in 'Hz'.
|
||||
// If found, we stop the search and return the slice.
|
||||
if ch == b'z' {
|
||||
let freq_end = i + 1;
|
||||
return Some(&self.as_bytes()[freq_start..freq_end]);
|
||||
} else if ch.is_ascii_digit() {
|
||||
stage = Stages::FoundFreqIntPart;
|
||||
freq_start = i;
|
||||
} else {
|
||||
stage = Stages::Initial;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter::repeat;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_brand_string() {
|
||||
#[inline]
|
||||
fn pack_u32(src: &[u8]) -> u32 {
|
||||
assert!(src.len() >= 4);
|
||||
u32::from(src[0])
|
||||
| (u32::from(src[1]) << 8)
|
||||
| (u32::from(src[2]) << 16)
|
||||
| (u32::from(src[3]) << 24)
|
||||
}
|
||||
|
||||
const TEST_STR: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let mut bstr = BrandString::from_bytes_unchecked(TEST_STR);
|
||||
|
||||
// Test the immutable bitwise casts
|
||||
//
|
||||
{
|
||||
for i in 0_usize..=1_usize {
|
||||
let eax_offs = (4 * 4) * i;
|
||||
let ebx_offs = (4 * 4) * i + 4;
|
||||
let ecx_offs = (4 * 4) * i + 8;
|
||||
let edx_offs = (4 * 4) * i + 12;
|
||||
assert_eq!(
|
||||
bstr.get_reg_for_leaf(0x8000_0002 + i as u32, Reg::Eax),
|
||||
pack_u32(&TEST_STR[eax_offs..(eax_offs + 4)])
|
||||
);
|
||||
assert_eq!(
|
||||
bstr.get_reg_for_leaf(0x8000_0002 + i as u32, Reg::Ebx),
|
||||
pack_u32(&TEST_STR[ebx_offs..(ebx_offs + 4)])
|
||||
);
|
||||
assert_eq!(
|
||||
bstr.get_reg_for_leaf(0x8000_0002 + i as u32, Reg::Ecx),
|
||||
pack_u32(&TEST_STR[ecx_offs..(ecx_offs + 4)])
|
||||
);
|
||||
assert_eq!(
|
||||
bstr.get_reg_for_leaf(0x8000_0002 + i as u32, Reg::Edx),
|
||||
pack_u32(&TEST_STR[edx_offs..(edx_offs + 4)])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(bstr.get_reg_for_leaf(0x8000_0005, Reg::Eax), 0);
|
||||
|
||||
// Test find_freq() failure path
|
||||
//
|
||||
assert!(bstr.find_freq().is_none());
|
||||
|
||||
// Test mutable bitwise casting and finding the frequency substring
|
||||
//
|
||||
bstr.set_reg_for_leaf(0x8000_0003, Reg::Ebx, pack_u32(b"5.20"));
|
||||
bstr.set_reg_for_leaf(0x8000_0003, Reg::Ecx, pack_u32(b"GHz "));
|
||||
assert_eq!(bstr.find_freq().unwrap(), b"5.20GHz");
|
||||
|
||||
let _overflow: [u8; 50] = [b'a'; 50];
|
||||
|
||||
// Test BrandString::check_push()
|
||||
//
|
||||
bstr = BrandString::new();
|
||||
assert!(bstr.check_push(b"Hello"));
|
||||
bstr.push_bytes(b"Hello").unwrap();
|
||||
assert!(bstr.check_push(b", world!"));
|
||||
bstr.push_bytes(b", world!").unwrap();
|
||||
|
||||
assert!(!bstr.check_push(&_overflow));
|
||||
|
||||
// Test BrandString::push_bytes()
|
||||
//
|
||||
let actual_len = bstr.as_bytes().len();
|
||||
let mut old_bytes: Vec<u8> = repeat(0).take(actual_len).collect();
|
||||
old_bytes.copy_from_slice(bstr.as_bytes());
|
||||
assert_eq!(
|
||||
bstr.push_bytes(&_overflow),
|
||||
Err(Error::Overflow(
|
||||
"Appending to the brand string failed.".to_string()
|
||||
))
|
||||
);
|
||||
assert!(bstr.as_bytes().to_vec() == old_bytes);
|
||||
|
||||
// Test BrandString::from_host_cpuid() and get_reg_for_leaf()
|
||||
//
|
||||
match BrandString::from_host_cpuid() {
|
||||
Ok(bstr) => {
|
||||
for leaf in 0x8000_0002..=0x8000_0004_u32 {
|
||||
let host_regs = unsafe { host_cpuid(leaf) };
|
||||
assert_eq!(bstr.get_reg_for_leaf(leaf, Reg::Eax), host_regs.eax);
|
||||
assert_eq!(bstr.get_reg_for_leaf(leaf, Reg::Ebx), host_regs.ebx);
|
||||
assert_eq!(bstr.get_reg_for_leaf(leaf, Reg::Ecx), host_regs.ecx);
|
||||
assert_eq!(bstr.get_reg_for_leaf(leaf, Reg::Edx), host_regs.edx);
|
||||
}
|
||||
}
|
||||
Err(Error::NotSupported) => {
|
||||
// from_host_cpuid() should only fail if the host CPU doesn't support
|
||||
// CPUID leaves up to 0x80000004, so let's make sure that's what happened.
|
||||
let host_regs = unsafe { host_cpuid(0x8000_0000) };
|
||||
assert!(host_regs.eax < 0x8000_0004);
|
||||
}
|
||||
_ => panic!("This function should not return another type of error"),
|
||||
}
|
||||
|
||||
// Test BrandString::from_vendor_id()
|
||||
let bstr = BrandString::from_vendor_id(VENDOR_ID_INTEL).unwrap();
|
||||
assert!(bstr.as_bytes().starts_with(BRAND_STRING_INTEL));
|
||||
let bstr = BrandString::from_vendor_id(VENDOR_ID_AMD).unwrap();
|
||||
assert!(bstr.as_bytes().starts_with(BRAND_STRING_AMD));
|
||||
let bstr = BrandString::from_vendor_id(b"............").unwrap();
|
||||
assert!(bstr.as_bytes() == vec![b'\0'; 48].as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_freq_fails() {
|
||||
let bstr_thz = BrandString::from_bytes_unchecked(b"5.20THz");
|
||||
assert_eq!(bstr_thz.find_freq().unwrap(), b"5.20THz");
|
||||
|
||||
let bstr_unused_end = BrandString::from_bytes_unchecked(b"AAA5.20MHzXz");
|
||||
assert_eq!(bstr_unused_end.find_freq().unwrap(), b"5.20MHz");
|
||||
|
||||
let bstr_faulty_unit = BrandString::from_bytes_unchecked(b"5.20BHz ");
|
||||
assert!(bstr_faulty_unit.find_freq().is_none());
|
||||
|
||||
let short_bstr = BrandString::from_bytes_unchecked(b"z");
|
||||
assert!(short_bstr.find_freq().is_none());
|
||||
|
||||
let skip_from_unit = BrandString::from_bytes_unchecked(b"Mz");
|
||||
assert!(skip_from_unit.find_freq().is_none());
|
||||
|
||||
let short_bstr = BrandString::from_bytes_unchecked(b"Hz");
|
||||
assert!(short_bstr.find_freq().is_none());
|
||||
|
||||
let short_bstr = BrandString::from_bytes_unchecked(b"GHz");
|
||||
assert!(short_bstr.find_freq().is_none());
|
||||
|
||||
let multiple_points_bstr = BrandString::from_bytes_unchecked(b"50.5.20GHz");
|
||||
assert_eq!(multiple_points_bstr.find_freq().unwrap(), b"5.20GHz");
|
||||
|
||||
let no_decimal_bstr = BrandString::from_bytes_unchecked(b"5GHz");
|
||||
assert!(no_decimal_bstr.find_freq().is_none());
|
||||
|
||||
let interrupted_bstr = BrandString::from_bytes_unchecked(b"500.00M5.20GHz");
|
||||
assert_eq!(interrupted_bstr.find_freq().unwrap(), b"5.20GHz");
|
||||
|
||||
let split_bstr = BrandString::from_bytes_unchecked(b"5.30AMHz");
|
||||
assert!(split_bstr.find_freq().is_none());
|
||||
|
||||
let long_bstr = BrandString::from_bytes_unchecked(b"1.12bc5.30MaHz2.4.25THz");
|
||||
assert_eq!(long_bstr.find_freq().unwrap(), b"4.25THz");
|
||||
|
||||
let found_h_bstr = BrandString::from_bytes_unchecked(b"1.A5.2MH3.20GHx4.30GHz");
|
||||
assert_eq!(found_h_bstr.find_freq().unwrap(), b"4.30GHz");
|
||||
}
|
||||
}
|
||||
105
src/dragonball/src/dbs_arch/src/x86_64/cpuid/common.rs
Normal file
105
src/dragonball/src/dbs_arch/src/x86_64/cpuid/common.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::arch::x86_64::{CpuidResult, __cpuid_count, __get_cpuid_max};
|
||||
|
||||
use super::cpu_leaf::*;
|
||||
|
||||
pub(crate) const VENDOR_ID_INTEL: &[u8; 12] = b"GenuineIntel";
|
||||
pub(crate) const VENDOR_ID_AMD: &[u8; 12] = b"AuthenticAMD";
|
||||
pub(crate) const VENDOR_ID_HYGON: &[u8; 12] = b"HygonGenuine";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Error {
|
||||
InvalidParameters(String),
|
||||
NotSupported,
|
||||
}
|
||||
|
||||
/// Get CPUID value for (`function`, `count`).
|
||||
pub fn get_cpuid(function: u32, count: u32) -> Result<CpuidResult, Error> {
|
||||
#[cfg(target_env = "sgx")]
|
||||
{
|
||||
return Err(Error::NotSupported);
|
||||
}
|
||||
|
||||
// TODO: replace with validation based on `has_cpuid()` when it becomes stable:
|
||||
// https://doc.rust-lang.org/core/arch/x86/fn.has_cpuid.html
|
||||
// this is safe because the host supports the `cpuid` instruction
|
||||
let max_function = unsafe { __get_cpuid_max(function & leaf_0x80000000::LEAF_NUM).0 };
|
||||
if function > max_function {
|
||||
return Err(Error::InvalidParameters(format!(
|
||||
"Function not supported: 0x{function:x}",
|
||||
)));
|
||||
}
|
||||
|
||||
// this is safe because the host supports the `cpuid` instruction
|
||||
let entry = unsafe { __cpuid_count(function, count) };
|
||||
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
|
||||
return Err(Error::InvalidParameters(format!("Invalid count: {count}")));
|
||||
}
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Extracts the CPU vendor id from leaf 0x0.
|
||||
pub fn get_vendor_id() -> Result<[u8; 12], Error> {
|
||||
let vendor_entry = get_cpuid(0, 0)?;
|
||||
let bytes: [u8; 12] =
|
||||
unsafe { std::mem::transmute([vendor_entry.ebx, vendor_entry.edx, vendor_entry.ecx]) };
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
pub fn get_topoext_fn() -> u32 {
|
||||
let vendor_id = get_vendor_id();
|
||||
assert!(vendor_id.is_ok());
|
||||
let function = match &vendor_id.ok().unwrap() {
|
||||
VENDOR_ID_INTEL => leaf_0x4::LEAF_NUM,
|
||||
VENDOR_ID_AMD => leaf_0x8000001d::LEAF_NUM,
|
||||
_ => 0,
|
||||
};
|
||||
assert!(function != 0);
|
||||
|
||||
function
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_cpu_id() {
|
||||
// get_cpu_id should work correctly here
|
||||
let topoext_fn = get_topoext_fn();
|
||||
|
||||
// check that get_cpuid works for valid parameters
|
||||
match get_cpuid(topoext_fn, 0) {
|
||||
Ok(topoext_entry) => {
|
||||
assert!(topoext_entry.eax != 0);
|
||||
}
|
||||
_ => panic!("Wrong behavior"),
|
||||
}
|
||||
|
||||
// check that get_cpuid returns correct error for invalid `function`
|
||||
match get_cpuid(0x9000_0000, 0) {
|
||||
Err(Error::InvalidParameters(s)) => {
|
||||
assert!(s == "Function not supported: 0x90000000");
|
||||
}
|
||||
_ => panic!("Wrong behavior"),
|
||||
}
|
||||
|
||||
// check that get_cpuid returns correct error for invalid `count`
|
||||
match get_cpuid(topoext_fn, 100) {
|
||||
Err(Error::InvalidParameters(s)) => {
|
||||
assert!(s == "Invalid count: 100");
|
||||
}
|
||||
_ => panic!("Wrong behavior"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_vendor_id() {
|
||||
let vendor_id = get_vendor_id().unwrap();
|
||||
assert!(matches!(&vendor_id, VENDOR_ID_INTEL | VENDOR_ID_AMD));
|
||||
}
|
||||
}
|
||||
439
src/dragonball/src/dbs_arch/src/x86_64/cpuid/cpu_leaf.rs
Normal file
439
src/dragonball/src/dbs_arch/src/x86_64/cpuid/cpu_leaf.rs
Normal file
@@ -0,0 +1,439 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
//! CPUID leaf registers constant values.
|
||||
|
||||
#![allow(unused)]
|
||||
pub mod leaf_0x0 {
|
||||
pub const LEAF_NUM: u32 = 0x0;
|
||||
}
|
||||
|
||||
pub mod leaf_0x1 {
|
||||
pub const LEAF_NUM: u32 = 0x1;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const EXTENDED_FAMILY_ID_BITRANGE: BitRange = bit_range!(27, 20);
|
||||
pub const EXTENDED_PROCESSOR_MODEL_BITRANGE: BitRange = bit_range!(19, 16);
|
||||
pub const PROCESSOR_TYPE_BITRANGE: BitRange = bit_range!(13, 12);
|
||||
pub const PROCESSOR_FAMILY_BITRANGE: BitRange = bit_range!(11, 8);
|
||||
pub const PROCESSOR_MODEL_BITRANGE: BitRange = bit_range!(7, 4);
|
||||
pub const STEPPING_BITRANGE: BitRange = bit_range!(3, 0);
|
||||
}
|
||||
|
||||
pub mod ebx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The bit-range containing the (fixed) default APIC ID.
|
||||
pub const APICID_BITRANGE: BitRange = bit_range!(31, 24);
|
||||
// The bit-range containing the logical processor count.
|
||||
pub const CPU_COUNT_BITRANGE: BitRange = bit_range!(23, 16);
|
||||
// The bit-range containing the number of bytes flushed when executing CLFLUSH.
|
||||
pub const CLFLUSH_SIZE_BITRANGE: BitRange = bit_range!(15, 8);
|
||||
}
|
||||
|
||||
pub mod ecx {
|
||||
// DTES64 = 64-bit debug store
|
||||
pub const DTES64_BITINDEX: u32 = 2;
|
||||
// MONITOR = Monitor/MWAIT
|
||||
pub const MONITOR_BITINDEX: u32 = 3;
|
||||
// CPL Qualified Debug Store
|
||||
pub const DS_CPL_SHIFT: u32 = 4;
|
||||
// 5 = VMX (Virtual Machine Extensions)
|
||||
// 6 = SMX (Safer Mode Extensions)
|
||||
// 7 = EIST (Enhanced Intel SpeedStep® technology)
|
||||
// TM2 = Thermal Monitor 2
|
||||
pub const TM2_BITINDEX: u32 = 8;
|
||||
// CNXT_ID = L1 Context ID (L1 data cache can be set to adaptive/shared mode)
|
||||
pub const CNXT_ID_BITINDEX: u32 = 10;
|
||||
// SDBG (cpu supports IA32_DEBUG_INTERFACE MSR for silicon debug)
|
||||
pub const SDBG_BITINDEX: u32 = 11;
|
||||
pub const FMA_BITINDEX: u32 = 12;
|
||||
// XTPR_UPDATE = xTPR Update Control
|
||||
pub const XTPR_UPDATE_BITINDEX: u32 = 14;
|
||||
// PDCM = Perfmon and Debug Capability
|
||||
pub const PDCM_BITINDEX: u32 = 15;
|
||||
// 18 = DCA Direct Cache Access (prefetch data from a memory mapped device)
|
||||
pub const MOVBE_BITINDEX: u32 = 22;
|
||||
pub const TSC_DEADLINE_TIMER_BITINDEX: u32 = 24;
|
||||
pub const OSXSAVE_BITINDEX: u32 = 27;
|
||||
// Cpu is running on a hypervisor.
|
||||
pub const HYPERVISOR_BITINDEX: u32 = 31;
|
||||
}
|
||||
|
||||
pub mod edx {
|
||||
pub const PSN_BITINDEX: u32 = 18; // Processor Serial Number
|
||||
pub const DS_BITINDEX: u32 = 21; // Debug Store.
|
||||
pub const ACPI_BITINDEX: u32 = 22; // Thermal Monitor and Software Controlled Clock Facilities.
|
||||
pub const SS_BITINDEX: u32 = 27; // Self Snoop
|
||||
pub const HTT_BITINDEX: u32 = 28; // Max APIC IDs reserved field is valid
|
||||
pub const TM_BITINDEX: u32 = 29; // Thermal Monitor.
|
||||
pub const PBE_BITINDEX: u32 = 31; // Pending Break Enable.
|
||||
}
|
||||
}
|
||||
|
||||
pub mod leaf_cache_parameters {
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const CACHE_LEVEL_BITRANGE: BitRange = bit_range!(7, 5);
|
||||
pub const MAX_CPUS_PER_CORE_BITRANGE: BitRange = bit_range!(25, 14);
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic Cache Parameters Leaf
|
||||
pub mod leaf_0x4 {
|
||||
pub const LEAF_NUM: u32 = 0x4;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// inherit eax from leaf_cache_parameters
|
||||
pub use crate::cpuid::cpu_leaf::leaf_cache_parameters::eax::*;
|
||||
|
||||
pub const MAX_CORES_PER_PACKAGE_BITRANGE: BitRange = bit_range!(31, 26);
|
||||
}
|
||||
}
|
||||
|
||||
// Thermal and Power Management Leaf
|
||||
#[allow(dead_code)]
|
||||
pub mod leaf_0x6 {
|
||||
pub const LEAF_NUM: u32 = 0x6;
|
||||
|
||||
pub mod eax {
|
||||
pub const TURBO_BOOST_BITINDEX: u32 = 1;
|
||||
}
|
||||
|
||||
pub mod ecx {
|
||||
// "Energy Performance Bias" bit.
|
||||
pub const EPB_BITINDEX: u32 = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Structured Extended Feature Flags Enumeration Leaf
|
||||
pub mod leaf_0x7 {
|
||||
pub const LEAF_NUM: u32 = 0x7;
|
||||
|
||||
pub mod index0 {
|
||||
pub mod ebx {
|
||||
// 1 = TSC_ADJUST
|
||||
pub const SGX_BITINDEX: u32 = 2;
|
||||
pub const BMI1_BITINDEX: u32 = 3;
|
||||
pub const HLE_BITINDEX: u32 = 4;
|
||||
pub const AVX2_BITINDEX: u32 = 5;
|
||||
// FPU Data Pointer updated only on x87 exceptions if 1.
|
||||
pub const FPDP_BITINDEX: u32 = 6;
|
||||
// 7 = SMEP (Supervisor-Mode Execution Prevention if 1)
|
||||
pub const BMI2_BITINDEX: u32 = 8;
|
||||
// 9 = Enhanced REP MOVSB/STOSB if 1
|
||||
// 10 = INVPCID
|
||||
pub const INVPCID_BITINDEX: u32 = 10;
|
||||
pub const RTM_BITINDEX: u32 = 11;
|
||||
// Intel® Resource Director Technology (Intel® RDT) Monitoring
|
||||
pub const RDT_M_BITINDEX: u32 = 12;
|
||||
// 13 = Deprecates FPU CS and FPU DS values if 1
|
||||
// Memory Protection Extensions
|
||||
pub const MPX_BITINDEX: u32 = 14;
|
||||
// RDT = Intel® Resource Director Technology
|
||||
pub const RDT_A_BITINDEX: u32 = 15;
|
||||
// AVX-512 Foundation instructions
|
||||
pub const AVX512F_BITINDEX: u32 = 16;
|
||||
// AVX-512 Doubleword and Quadword Instructions
|
||||
pub const AVX512DQ_BITINDEX: u32 = 17;
|
||||
pub const RDSEED_BITINDEX: u32 = 18;
|
||||
pub const ADX_BITINDEX: u32 = 19;
|
||||
// 20 = SMAP (Supervisor-Mode Access Prevention)
|
||||
// AVX512IFMA = AVX-512 Integer Fused Multiply-Add Instructions
|
||||
pub const AVX512IFMA_BITINDEX: u32 = 21;
|
||||
// 21 = PCOMMIT intruction
|
||||
// 22 reserved
|
||||
// CLFLUSHOPT (flushing multiple cache lines in parallel within a single logical processor)
|
||||
pub const CLFLUSHOPT_BITINDEX: u32 = 23;
|
||||
// CLWB = Cache Line Write Back
|
||||
pub const CLWB_BITINDEX: u32 = 24;
|
||||
// PT = Intel Processor Trace
|
||||
pub const PT_BITINDEX: u32 = 25;
|
||||
// AVX512PF = AVX512 Prefetch Instructions
|
||||
pub const AVX512PF_BITINDEX: u32 = 26;
|
||||
// AVX512ER = AVX-512 Exponential and Reciprocal Instructions
|
||||
pub const AVX512ER_BITINDEX: u32 = 27;
|
||||
// AVX512CD = AVX-512 Conflict Detection Instructions
|
||||
pub const AVX512CD_BITINDEX: u32 = 28;
|
||||
// Intel Secure Hash Algorithm Extensions
|
||||
pub const SHA_BITINDEX: u32 = 29;
|
||||
// AVX-512 Byte and Word Instructions
|
||||
pub const AVX512BW_BITINDEX: u32 = 30;
|
||||
// AVX-512 Vector Length Extensions
|
||||
pub const AVX512VL_BITINDEX: u32 = 31;
|
||||
}
|
||||
|
||||
pub mod ecx {
|
||||
// 0 = PREFETCHWT1 (move data closer to the processor in anticipation of future use)
|
||||
// AVX512_VBMI = AVX-512 Vector Byte Manipulation Instructions
|
||||
pub const AVX512_VBMI_BITINDEX: u32 = 1;
|
||||
// 2 = UMIP (User Mode Instruction Prevention)
|
||||
// PKU = Protection Keys for user-mode pages
|
||||
pub const PKU_BITINDEX: u32 = 3;
|
||||
// OSPKE = If 1, OS has set CR4.PKE to enable protection keys
|
||||
pub const OSPKE_BITINDEX: u32 = 4;
|
||||
// 5 = WAITPKG
|
||||
// 7-6 reserved
|
||||
// 8 = GFNI
|
||||
// 13-09 reserved
|
||||
// AVX512_VPOPCNTDQ = Vector population count instruction (Intel® Xeon Phi™ only.)
|
||||
pub const AVX512_VPOPCNTDQ_BITINDEX: u32 = 14;
|
||||
// 21 - 17 = The value of MAWAU used by the BNDLDX and BNDSTX instructions in 64-bit mode.
|
||||
// Read Processor ID
|
||||
pub const RDPID_BITINDEX: u32 = 22;
|
||||
// 23 - 29 reserved
|
||||
// SGX_LC = SGX Launch Configuration
|
||||
pub const SGX_LC_BITINDEX: u32 = 30;
|
||||
// 31 reserved
|
||||
}
|
||||
|
||||
pub mod edx {
|
||||
// AVX-512 4-register Neural Network Instructions
|
||||
pub const AVX512_4VNNIW_BITINDEX: u32 = 2;
|
||||
// AVX-512 4-register Multiply Accumulation Single precision
|
||||
pub const AVX512_4FMAPS_BITINDEX: u32 = 3;
|
||||
pub const ARCH_CAPABILITIES_BITINDEX: u32 = 29;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Architecture Performance Monitor Features
|
||||
pub mod leaf_0xa {
|
||||
pub const LEAF_NUM: u32 = 0xa;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
pub const PMC_VERSION_ID: BitRange = bit_range!(7, 0);
|
||||
pub const BIT_LEN_PMEVENT: BitRange = bit_range!(31, 24);
|
||||
}
|
||||
|
||||
pub mod ebx {
|
||||
pub const CORE_CYCLES_BITINDEX: u32 = 0;
|
||||
pub const INST_RETIRED_BITINDEX: u32 = 1;
|
||||
pub const REF_CYCLES_BITINDEX: u32 = 2;
|
||||
pub const LLC_REF_BITINDEX: u32 = 3;
|
||||
pub const LLC_MISSES_BITINDEX: u32 = 4;
|
||||
pub const BR_INST_RETIRED_BITINDEX: u32 = 5;
|
||||
pub const BR_MIS_RETIRED_BITINDEX: u32 = 6;
|
||||
}
|
||||
}
|
||||
|
||||
// Extended Topology Leaf
|
||||
pub mod leaf_0xb {
|
||||
pub const LEAF_NUM: u32 = 0xb;
|
||||
|
||||
pub const LEVEL_TYPE_THREAD: u32 = 1;
|
||||
pub const LEVEL_TYPE_CORE: u32 = 2;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The bit-range containing the number of bits to shift right the APIC ID in order to get
|
||||
// the next level APIC ID
|
||||
pub const APICID_BITRANGE: BitRange = bit_range!(4, 0);
|
||||
}
|
||||
|
||||
pub mod ebx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The bit-range containing the number of factory-configured logical processors
|
||||
// at the current cache level
|
||||
pub const NUM_LOGICAL_PROCESSORS_BITRANGE: BitRange = bit_range!(15, 0);
|
||||
}
|
||||
|
||||
pub mod ecx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const LEVEL_TYPE_BITRANGE: BitRange = bit_range!(15, 8);
|
||||
pub const LEVEL_NUMBER_BITRANGE: BitRange = bit_range!(7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Processor Extended State Enumeration Sub-leaves
|
||||
pub mod leaf_0xd {
|
||||
pub const LEAF_NUM: u32 = 0xd;
|
||||
|
||||
pub mod index0 {
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const MPX_STATE_BITRANGE: BitRange = bit_range!(4, 3);
|
||||
pub const AVX512_STATE_BITRANGE: BitRange = bit_range!(7, 5);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod index1 {
|
||||
pub mod eax {
|
||||
pub const XSAVEC_SHIFT: u32 = 1;
|
||||
pub const XGETBV_SHIFT: u32 = 2;
|
||||
pub const XSAVES_SHIFT: u32 = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// V2 Extended Topology Enumeration Leaf
|
||||
pub mod leaf_0x1f {
|
||||
pub const LEAF_NUM: u32 = 0x1f;
|
||||
|
||||
pub const LEVEL_TYPE_THREAD: u32 = 1;
|
||||
pub const LEVEL_TYPE_CORE: u32 = 2;
|
||||
pub const LEVEL_TYPE_DIE: u32 = 5;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The bit-range containing the number of bits to shift right the APIC ID in order to get
|
||||
// the next level APIC ID
|
||||
pub const APICID_BITRANGE: BitRange = bit_range!(4, 0);
|
||||
}
|
||||
|
||||
pub mod ebx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The bit-range containing the number of factory-configured logical processors
|
||||
// at the current cache level
|
||||
pub const NUM_LOGICAL_PROCESSORS_BITRANGE: BitRange = bit_range!(15, 0);
|
||||
}
|
||||
|
||||
pub mod ecx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const LEVEL_TYPE_BITRANGE: BitRange = bit_range!(15, 8);
|
||||
pub const LEVEL_NUMBER_BITRANGE: BitRange = bit_range!(7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// KVM CPUID bits
|
||||
/// A guest running on a kvm host, can check some of its features using cpuid. This is not always guaranteed to work,
|
||||
/// since userspace can mask-out some, or even all KVM-related cpuid features before launching a guest.
|
||||
/// More information: https://docs.kernel.org/virt/kvm/x86/cpuid.html
|
||||
pub mod leaf_0x4000_0001 {
|
||||
pub const LEAF_NUM: u32 = 0x4000_0001;
|
||||
pub mod eax {
|
||||
/// kvmclock available at msrs 0x11 and 0x12
|
||||
pub const KVM_FEATURE_CLOCKSOURCE_BITINDEX: u32 = 0;
|
||||
/// not necessary to perform delays on PIO operations
|
||||
pub const KVM_FEATURE_NOP_IO_DELAY_BITINDEX: u32 = 1;
|
||||
/// deprecated
|
||||
pub const KVM_FEATURE_MMU_OP_BITINDEX: u32 = 2;
|
||||
/// kvmclock available at msrs 0x4b564d00 and 0x4b564d01
|
||||
pub const KVM_FEATURE_CLOCKSOURCE2_BITINDEX: u32 = 3;
|
||||
/// async pf can be enabled by writing to msr 0x4b564d02
|
||||
pub const KVM_FEATURE_ASYNC_PF_BITINDEX: u32 = 4;
|
||||
/// steal time can be enabled by writing to msr 0x4b564d03
|
||||
pub const KVM_FEATURE_STEAL_TIME_BITINDEX: u32 = 5;
|
||||
/// paravirtualized end of interrupt handler can be enabled by writing to msr 0x4b564d04
|
||||
pub const KVM_FEATURE_PV_EOI_BITINDEX: u32 = 6;
|
||||
/// guest checks this feature bit before enabling paravirtualized spinlock support
|
||||
pub const KVM_FEATURE_PV_UNHALT_BITINDEX: u32 = 7;
|
||||
/// guest checks this feature bit before enabling paravirtualized tlb flush
|
||||
pub const KVM_FEATURE_PV_TLB_FLUSH_BITINDEX: u32 = 9;
|
||||
/// paravirtualized async PF VM EXIT can be enabled by setting bit 2 when writing to msr 0x4b564d02
|
||||
pub const KVM_FEATURE_ASYNC_PF_VMEXIT_BITINDEX: u32 = 10;
|
||||
/// guest checks this feature bit before enabling paravirtualized send IPIs
|
||||
pub const KVM_FEATURE_PV_SEND_IPI_BITINDEX: u32 = 11;
|
||||
/// host-side polling on HLT can be disabled by writing to msr 0x4b564d05.
|
||||
pub const KVM_FEATURE_POLL_CONTROL_BITINDEX: u32 = 12;
|
||||
/// guest checks this feature bit before using paravirtualized sched yield.
|
||||
pub const KVM_FEATURE_PV_SCHED_YIELD_BITINDEX: u32 = 13;
|
||||
/// guest checks this feature bit before using the second async pf control msr 0x4b564d06 and async pf acknowledgment msr 0x4b564d07.
|
||||
pub const KVM_FEATURE_ASYNC_PF_INT_BITINDEX: u32 = 14;
|
||||
/// guest checks this feature bit before using extended destination ID bits in MSI address bits 11-5.
|
||||
pub const KVM_FEATURE_MSI_EXT_DEST_ID_BITINDEX: u32 = 15;
|
||||
/// guest checks this feature bit before using the map gpa range hypercall to notify the page state change
|
||||
pub const KVM_FEATURE_HC_MAP_GPA_RANGE_BITINDEX: u32 = 16;
|
||||
/// guest checks this feature bit before using MSR_KVM_MIGRATION_CONTROL
|
||||
pub const KVM_FEATURE_MIGRATION_CONTROL_BITINDEX: u32 = 17;
|
||||
/// host will warn if no guest-side per-cpu warps are expected in kvmclock
|
||||
pub const KVM_FEATURE_CLOCKSOURCE_STABLE_BITINDEX: u32 = 24;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod leaf_0x80000000 {
|
||||
pub const LEAF_NUM: u32 = 0x8000_0000;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const LARGEST_EXTENDED_FN_BITRANGE: BitRange = bit_range!(31, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod leaf_0x80000001 {
|
||||
pub const LEAF_NUM: u32 = 0x8000_0001;
|
||||
|
||||
pub mod ecx {
|
||||
pub const TOPOEXT_INDEX: u32 = 22;
|
||||
pub const PREFETCH_BITINDEX: u32 = 8; // 3DNow! PREFETCH/PREFETCHW instructions
|
||||
pub const LZCNT_BITINDEX: u32 = 5; // advanced bit manipulation
|
||||
}
|
||||
|
||||
pub mod edx {
|
||||
pub const PDPE1GB_BITINDEX: u32 = 26; // 1-GByte pages are available if 1.
|
||||
}
|
||||
}
|
||||
|
||||
pub mod leaf_0x80000008 {
|
||||
pub const LEAF_NUM: u32 = 0x8000_0008;
|
||||
|
||||
pub mod ecx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The number of bits in the initial ApicId value that indicate thread ID within a package
|
||||
// Possible values:
|
||||
// 0-3 -> Reserved
|
||||
// 4 -> 1 Die, up to 16 threads
|
||||
// 5 -> 2 Die, up to 32 threads
|
||||
// 6 -> 3,4 Die, up to 64 threads
|
||||
pub const THREAD_ID_SIZE_BITRANGE: BitRange = bit_range!(15, 12);
|
||||
// The number of threads in the package - 1
|
||||
pub const NUM_THREADS_BITRANGE: BitRange = bit_range!(7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Extended Cache Topology Leaf
|
||||
pub mod leaf_0x8000001d {
|
||||
pub const LEAF_NUM: u32 = 0x8000_001d;
|
||||
|
||||
// inherit eax from leaf_cache_parameters
|
||||
pub use crate::cpuid::cpu_leaf::leaf_cache_parameters::eax;
|
||||
}
|
||||
|
||||
// Extended APIC ID Leaf
|
||||
pub mod leaf_0x8000001e {
|
||||
pub const LEAF_NUM: u32 = 0x8000_001e;
|
||||
|
||||
pub mod eax {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
pub const EXTENDED_APIC_ID_BITRANGE: BitRange = bit_range!(31, 0);
|
||||
}
|
||||
|
||||
pub mod ebx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The number of threads per core - 1
|
||||
pub const THREADS_PER_CORE_BITRANGE: BitRange = bit_range!(15, 8);
|
||||
pub const CORE_ID_BITRANGE: BitRange = bit_range!(7, 0);
|
||||
}
|
||||
|
||||
pub mod ecx {
|
||||
use crate::cpuid::bit_helper::BitRange;
|
||||
|
||||
// The number of nodes per processor. Possible values:
|
||||
// 0 -> 1 node per processor
|
||||
// 1 -> 2 nodes per processor
|
||||
// 2 -> Reserved
|
||||
// 3 -> 4 nodes per processor
|
||||
pub const NODES_PER_PROCESSOR_BITRANGE: BitRange = bit_range!(10, 8);
|
||||
pub const NODE_ID_BITRANGE: BitRange = bit_range!(7, 0);
|
||||
}
|
||||
}
|
||||
76
src/dragonball/src/dbs_arch/src/x86_64/cpuid/mod.rs
Normal file
76
src/dragonball/src/dbs_arch/src/x86_64/cpuid/mod.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Utilities for configuring the CPUID (CPU identification) for the guest microVM.
|
||||
|
||||
pub mod bit_helper;
|
||||
pub mod cpu_leaf;
|
||||
|
||||
mod brand_string;
|
||||
mod common;
|
||||
mod transformer;
|
||||
|
||||
pub use transformer::{Error, VmSpec};
|
||||
|
||||
pub use crate::VpmuFeatureLevel;
|
||||
|
||||
type CpuId = kvm_bindings::CpuId;
|
||||
type CpuIdEntry = kvm_bindings::kvm_cpuid_entry2;
|
||||
|
||||
/// Setup CPUID entries for the given vCPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kvm_cpuid` - KVM related structure holding the relevant CPUID info.
|
||||
/// * `vm_spec` - The specifications of the VM.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use dbs_arch::cpuid::{process_cpuid, VmSpec, VpmuFeatureLevel};
|
||||
/// use kvm_bindings::{CpuId, KVM_MAX_CPUID_ENTRIES};
|
||||
/// use kvm_ioctls::Kvm;
|
||||
///
|
||||
/// let kvm = Kvm::new().unwrap();
|
||||
/// let mut kvm_cpuid: CpuId = kvm.get_supported_cpuid(KVM_MAX_CPUID_ENTRIES).unwrap();
|
||||
///
|
||||
/// let vm_spec = VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).unwrap();
|
||||
///
|
||||
/// process_cpuid(&mut kvm_cpuid, &vm_spec).unwrap();
|
||||
///
|
||||
/// // Get expected `kvm_cpuid` entries.
|
||||
/// let entries = kvm_cpuid.as_mut_slice();
|
||||
/// ```
|
||||
pub fn process_cpuid(kvm_cpuid: &mut CpuId, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use transformer::CpuidTransformer;
|
||||
|
||||
match vm_spec.cpu_vendor_id() {
|
||||
self::common::VENDOR_ID_INTEL => {
|
||||
self::transformer::intel::IntelCpuidTransformer::new().process_cpuid(kvm_cpuid, vm_spec)
|
||||
}
|
||||
self::common::VENDOR_ID_AMD => {
|
||||
self::transformer::amd::AmdCpuidTransformer::new().process_cpuid(kvm_cpuid, vm_spec)
|
||||
}
|
||||
self::common::VENDOR_ID_HYGON => {
|
||||
self::transformer::amd::AmdCpuidTransformer::new().process_cpuid(kvm_cpuid, vm_spec)
|
||||
}
|
||||
_ => Err(Error::CpuNotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_invalid_cpuid() {
|
||||
let mut cpuid = CpuId::new(0).unwrap();
|
||||
let vm_spec = VmSpec::new(0, 2, 1, 1, 1, VpmuFeatureLevel::Disabled).unwrap();
|
||||
|
||||
process_cpuid(&mut cpuid, &vm_spec).unwrap();
|
||||
}
|
||||
}
|
||||
412
src/dragonball/src/dbs_arch/src/x86_64/cpuid/transformer/amd.rs
Normal file
412
src/dragonball/src/dbs_arch/src/x86_64/cpuid/transformer/amd.rs
Normal file
@@ -0,0 +1,412 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX;
|
||||
|
||||
use super::super::bit_helper::BitHelper;
|
||||
use super::super::cpu_leaf;
|
||||
use super::*;
|
||||
|
||||
// Largest extended function. It has to be larger than 0x8000001d (Extended Cache Topology).
|
||||
const LARGEST_EXTENDED_FN: u32 = 0x8000_001f;
|
||||
// This value allows at most 256 logical threads within a package. But we currently only support
|
||||
// less than or equal to 254vcpus.
|
||||
// See also the documentation for leaf_0x80000008::ecx::THREAD_ID_SIZE_BITRANGE
|
||||
const THREAD_ID_MAX_SIZE: u32 = 8;
|
||||
// This value means there is 1 node per processor.
|
||||
// See also the documentation for leaf_0x8000001e::ecx::NODES_PER_PROCESSOR_BITRANGE.
|
||||
const NODES_PER_PROCESSOR: u32 = 0;
|
||||
|
||||
fn update_structured_extended_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
_vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0x7::index0::*;
|
||||
|
||||
// according to the EPYC PPR, only the leaf 0x7 with index 0 contains the
|
||||
// structured extended feature identifiers
|
||||
if entry.index == 0 {
|
||||
// KVM sets this bit no matter what but this feature is not supported by hardware
|
||||
entry.edx.write_bit(edx::ARCH_CAPABILITIES_BITINDEX, false);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_largest_extended_fn_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
_vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0x80000000::*;
|
||||
|
||||
// KVM sets the largest extended function to 0x80000000. Change it to 0x8000001f
|
||||
// Since we also use the leaf 0x8000001d (Extended Cache Topology).
|
||||
entry
|
||||
.eax
|
||||
.write_bits_in_range(&eax::LARGEST_EXTENDED_FN_BITRANGE, LARGEST_EXTENDED_FN);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_extended_feature_info_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
_vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x80000001::*;
|
||||
|
||||
// set the Topology Extension bit since we use the Extended Cache Topology leaf
|
||||
entry.ecx.write_bit(ecx::TOPOEXT_INDEX, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_amd_features_entry(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0x80000008::*;
|
||||
|
||||
// We don't support more then 254 threads right now.
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::THREAD_ID_SIZE_BITRANGE, THREAD_ID_MAX_SIZE)
|
||||
.write_bits_in_range(&ecx::NUM_THREADS_BITRANGE, u32::from(vm_spec.cpu_count - 1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_extended_cache_topology_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
entry.flags |= KVM_CPUID_FLAG_SIGNIFCANT_INDEX;
|
||||
|
||||
common::update_cache_parameters_entry(entry, vm_spec)
|
||||
}
|
||||
|
||||
fn update_extended_apic_id_entry(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x8000001e::*;
|
||||
|
||||
let mut core_id = u32::from(vm_spec.cpu_id);
|
||||
// When hyper-threading is enabled each pair of 2 consecutive logical CPUs
|
||||
// will have the same core id since they represent 2 threads in the same core.
|
||||
// For Example:
|
||||
// logical CPU 0 -> core id: 0
|
||||
// logical CPU 1 -> core id: 0
|
||||
// logical CPU 2 -> core id: 1
|
||||
// logical CPU 3 -> core id: 1
|
||||
if vm_spec.threads_per_core == 2 {
|
||||
core_id /= 2;
|
||||
}
|
||||
|
||||
entry
|
||||
.eax
|
||||
// the Extended APIC ID is the id of the current logical CPU
|
||||
.write_bits_in_range(&eax::EXTENDED_APIC_ID_BITRANGE, u32::from(vm_spec.cpu_id));
|
||||
|
||||
entry
|
||||
.ebx
|
||||
.write_bits_in_range(&ebx::CORE_ID_BITRANGE, core_id)
|
||||
.write_bits_in_range(
|
||||
&ebx::THREADS_PER_CORE_BITRANGE,
|
||||
u32::from(vm_spec.threads_per_core - 1),
|
||||
);
|
||||
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::NODES_PER_PROCESSOR_BITRANGE, NODES_PER_PROCESSOR)
|
||||
// Put all the cpus in the same node.
|
||||
.write_bits_in_range(&ecx::NODE_ID_BITRANGE, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AmdCpuidTransformer {}
|
||||
|
||||
impl AmdCpuidTransformer {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl CpuidTransformer for AmdCpuidTransformer {
|
||||
fn process_cpuid(&self, cpuid: &mut CpuId, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use cpu_leaf::*;
|
||||
|
||||
common::use_host_cpuid_function(cpuid, leaf_0x0::LEAF_NUM, false)?;
|
||||
common::use_host_cpuid_function(cpuid, leaf_0x8000001d::LEAF_NUM, false)?;
|
||||
common::use_host_cpuid_function(cpuid, leaf_0x8000001d::LEAF_NUM, true)?;
|
||||
self.process_entries(cpuid, vm_spec)
|
||||
}
|
||||
|
||||
fn entry_transformer_fn(&self, entry: &mut CpuIdEntry) -> Option<EntryTransformerFn> {
|
||||
use cpu_leaf::*;
|
||||
|
||||
match entry.function {
|
||||
leaf_0x1::LEAF_NUM => Some(common::update_feature_info_entry),
|
||||
leaf_0x7::LEAF_NUM => Some(update_structured_extended_entry),
|
||||
leaf_0xb::LEAF_NUM => Some(common::update_extended_topology_entry),
|
||||
leaf_0x1f::LEAF_NUM => Some(common::update_extended_topology_v2_entry),
|
||||
leaf_0x80000000::LEAF_NUM => Some(update_largest_extended_fn_entry),
|
||||
leaf_0x80000001::LEAF_NUM => Some(update_extended_feature_info_entry),
|
||||
leaf_0x80000008::LEAF_NUM => Some(update_amd_features_entry),
|
||||
leaf_0x8000001d::LEAF_NUM => Some(update_extended_cache_topology_entry),
|
||||
leaf_0x8000001e::LEAF_NUM => Some(update_extended_apic_id_entry),
|
||||
0x8000_0002..=0x8000_0004 => Some(common::update_brand_string_entry),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_transformer_construct() {
|
||||
use cpu_leaf::leaf_0x7::index0::*;
|
||||
|
||||
let transformer = AmdCpuidTransformer::new();
|
||||
|
||||
let vm_spec =
|
||||
VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).expect("Error creating vm_spec");
|
||||
let mut cpuid = CpuId::from_entries(&[CpuIdEntry {
|
||||
function: cpu_leaf::leaf_0x7::LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: *(0_u32).write_bit(edx::ARCH_CAPABILITIES_BITINDEX, true),
|
||||
padding: [0, 0, 0],
|
||||
}])
|
||||
.unwrap();
|
||||
|
||||
transformer.process_cpuid(&mut cpuid, &vm_spec).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_structured_extended_entry() {
|
||||
use cpu_leaf::leaf_0x7::index0::*;
|
||||
|
||||
// Check that if index == 0 the entry is processed
|
||||
let vm_spec =
|
||||
VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).expect("Error creating vm_spec");
|
||||
let entry = &mut CpuIdEntry {
|
||||
function: cpu_leaf::leaf_0x7::LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: *(0_u32).write_bit(edx::ARCH_CAPABILITIES_BITINDEX, true),
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
assert!(update_structured_extended_entry(entry, &vm_spec).is_ok());
|
||||
assert!(!entry.edx.read_bit(edx::ARCH_CAPABILITIES_BITINDEX));
|
||||
|
||||
// Check that if index != 0 the entry is not processed
|
||||
entry.index = 1;
|
||||
entry.edx.write_bit(edx::ARCH_CAPABILITIES_BITINDEX, true);
|
||||
assert!(update_structured_extended_entry(entry, &vm_spec).is_ok());
|
||||
assert!(entry.edx.read_bit(edx::ARCH_CAPABILITIES_BITINDEX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_largest_extended_fn_entry() {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x80000000::*;
|
||||
|
||||
let vm_spec =
|
||||
VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).expect("Error creating vm_spec");
|
||||
let entry = &mut CpuIdEntry {
|
||||
function: LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_largest_extended_fn_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert_eq!(
|
||||
entry
|
||||
.eax
|
||||
.read_bits_in_range(&eax::LARGEST_EXTENDED_FN_BITRANGE),
|
||||
LARGEST_EXTENDED_FN
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_extended_feature_info_entry() {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x80000001::*;
|
||||
|
||||
let vm_spec =
|
||||
VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).expect("Error creating vm_spec");
|
||||
let entry = &mut CpuIdEntry {
|
||||
function: LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_extended_feature_info_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert!(entry.ecx.read_bit(ecx::TOPOEXT_INDEX));
|
||||
}
|
||||
|
||||
fn check_update_amd_features_entry(
|
||||
cpu_count: u8,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x80000008::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
0,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut CpuIdEntry {
|
||||
function: LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_amd_features_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert_eq!(
|
||||
entry.ecx.read_bits_in_range(&ecx::NUM_THREADS_BITRANGE),
|
||||
u32::from(cpu_count - 1)
|
||||
);
|
||||
assert_eq!(
|
||||
entry.ecx.read_bits_in_range(&ecx::THREAD_ID_SIZE_BITRANGE),
|
||||
THREAD_ID_MAX_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
fn check_update_extended_apic_id_entry(
|
||||
cpu_id: u8,
|
||||
cpu_count: u8,
|
||||
expected_core_id: u32,
|
||||
expected_threads_per_core: u32,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x8000001e::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
cpu_id,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut CpuIdEntry {
|
||||
function: LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_extended_apic_id_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert_eq!(
|
||||
entry
|
||||
.eax
|
||||
.read_bits_in_range(&eax::EXTENDED_APIC_ID_BITRANGE),
|
||||
u32::from(cpu_id)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
entry.ebx.read_bits_in_range(&ebx::CORE_ID_BITRANGE),
|
||||
expected_core_id
|
||||
);
|
||||
assert_eq!(
|
||||
entry
|
||||
.ebx
|
||||
.read_bits_in_range(&ebx::THREADS_PER_CORE_BITRANGE),
|
||||
expected_threads_per_core
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
entry
|
||||
.ecx
|
||||
.read_bits_in_range(&ecx::NODES_PER_PROCESSOR_BITRANGE),
|
||||
NODES_PER_PROCESSOR
|
||||
);
|
||||
assert_eq!(entry.ecx.read_bits_in_range(&ecx::NODE_ID_BITRANGE), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_extended_cache_topology_entry() {
|
||||
let vm_spec =
|
||||
VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).expect("Error creating vm_spec");
|
||||
let entry = &mut CpuIdEntry {
|
||||
function: cpu_leaf::leaf_0x8000001d::LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_extended_cache_topology_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert_eq!(entry.flags & KVM_CPUID_FLAG_SIGNIFCANT_INDEX, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1vcpu_ht_off() {
|
||||
check_update_amd_features_entry(1, 1, 1, 1);
|
||||
|
||||
check_update_extended_apic_id_entry(0, 1, 0, 0, 1, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1vcpu_ht_on() {
|
||||
check_update_amd_features_entry(1, 2, 1, 1);
|
||||
|
||||
check_update_extended_apic_id_entry(0, 1, 0, 1, 2, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2vcpu_ht_off() {
|
||||
check_update_amd_features_entry(2, 1, 2, 1);
|
||||
|
||||
check_update_extended_apic_id_entry(0, 2, 0, 0, 1, 2, 1);
|
||||
check_update_extended_apic_id_entry(1, 2, 1, 0, 1, 2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2vcpu_ht_on() {
|
||||
check_update_amd_features_entry(2, 2, 2, 1);
|
||||
|
||||
check_update_extended_apic_id_entry(0, 2, 0, 1, 2, 2, 1);
|
||||
check_update_extended_apic_id_entry(1, 2, 0, 1, 2, 2, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
// Copyright 2019 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::super::bit_helper::BitHelper;
|
||||
use super::super::common::get_cpuid;
|
||||
use super::super::cpu_leaf;
|
||||
use super::*;
|
||||
|
||||
// constants for setting the fields of kvm_cpuid2 structures
|
||||
// CPUID bits in ebx, ecx, and edx.
|
||||
const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
|
||||
|
||||
/// Prepare content for CPUID standard level 0000_0001h: get processor type/family/model/stepping
|
||||
/// and feature flags
|
||||
pub fn update_feature_info_entry(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0x1::*;
|
||||
|
||||
// ECX bit 31 (HV): hypervisor present (and intercepting this bit, to advertise its presence)
|
||||
// ECX bit 24 (TSCD): local APIC supports one-shot operation using TSC deadline value
|
||||
entry
|
||||
.ecx
|
||||
.write_bit(ecx::TSC_DEADLINE_TIMER_BITINDEX, true)
|
||||
.write_bit(ecx::HYPERVISOR_BITINDEX, true);
|
||||
|
||||
// EBX bit 8-15: The CLFLUSH (8-byte) chunk count
|
||||
// EBX bit 16-23: The logical processor count
|
||||
// EBX bit 24-31: The (fixed) default APIC ID
|
||||
entry
|
||||
.ebx
|
||||
.write_bits_in_range(&ebx::APICID_BITRANGE, u32::from(vm_spec.cpu_id))
|
||||
.write_bits_in_range(&ebx::CLFLUSH_SIZE_BITRANGE, EBX_CLFLUSH_CACHELINE)
|
||||
.write_bits_in_range(
|
||||
&ebx::CPU_COUNT_BITRANGE,
|
||||
u32::from(vm_spec.threads_per_core * vm_spec.cores_per_die * vm_spec.dies_per_socket),
|
||||
);
|
||||
|
||||
// EDX bit 28: Hyper-Threading Technology, PAUSE. A value of 1 for HTT indicates the value in
|
||||
// CPUID.1.Ebx[23:16] (the Maximum number of addressable IDs for logical processors in this
|
||||
// package) is valid for the package
|
||||
entry
|
||||
.edx
|
||||
.write_bit(edx::HTT_BITINDEX, vm_spec.cpu_count > 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare content for CPUID standard level 0000_000Bh: get topology enumeration information.
|
||||
pub fn update_extended_topology_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0xb::*;
|
||||
let thread_width = 8 - (vm_spec.threads_per_core - 1).leading_zeros();
|
||||
let core_width = (8 - (vm_spec.cores_per_die - 1).leading_zeros()) + thread_width;
|
||||
|
||||
// EAX bit 0-4: number of bits to shift x2APIC ID right to get unique topology ID of
|
||||
// next level type all logical processors with same next level ID share current level
|
||||
// EBX bit 0-15: number of enabled logical processors at this level
|
||||
// ECX bit 0-8: level number (same as input)
|
||||
// ECX bit 8-15: level type (00h=invalid, 01h=SMT, 02h=core, 03h...FFh=reserved)
|
||||
// EDX bits 0-31 contain x2APIC ID of current logical processor
|
||||
entry.eax = 0_u32;
|
||||
entry.ebx = 0_u32;
|
||||
entry.ecx = 0_u32;
|
||||
entry.edx = u32::from(vm_spec.cpu_id);
|
||||
|
||||
match entry.index {
|
||||
// Thread Level Topology; index = 0
|
||||
0 => {
|
||||
// To get the next level APIC ID, shift right with at most 1 because we have
|
||||
// maximum 2 hyperthreads per core that can be represented by 1 bit.
|
||||
entry
|
||||
.eax
|
||||
.write_bits_in_range(&eax::APICID_BITRANGE, thread_width);
|
||||
// When cpu_count == 1 or HT is disabled, there is 1 logical core at this level
|
||||
// Otherwise there are 2
|
||||
entry.ebx.write_bits_in_range(
|
||||
&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE,
|
||||
vm_spec.threads_per_core as u32,
|
||||
);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE, LEVEL_TYPE_THREAD);
|
||||
}
|
||||
|
||||
// Core Level Processor Topology; index = 1
|
||||
1 => {
|
||||
entry
|
||||
.eax
|
||||
.write_bits_in_range(&eax::APICID_BITRANGE, core_width);
|
||||
entry.ebx.write_bits_in_range(
|
||||
&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE,
|
||||
u32::from(vm_spec.threads_per_core * vm_spec.cores_per_die),
|
||||
);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_NUMBER_BITRANGE, entry.index);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE, LEVEL_TYPE_CORE);
|
||||
}
|
||||
// Core Level Processor Topology; index >=2
|
||||
// No other levels available; This should already be set to correctly,
|
||||
// and it is added here as a "re-enforcement" in case we run on
|
||||
// different hardware
|
||||
level => {
|
||||
entry.ecx = level;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare content for Intel V2 Extended Topology Enumeration Leaf.
|
||||
///
|
||||
/// Leaf_0x1f is a superset of leaf_0xb. It gives extra information like die_per_socket.
|
||||
/// When CPUID executes with EAX set to 1FH, the processor returns information about extended
|
||||
/// topology enumeration data. Software must detect the presence of CPUID leaf 1FH by verifying
|
||||
/// - the highest leaf index supported by CPUID is >= 1FH
|
||||
/// - CPUID.1FH:EBX[15:0] reports a non-zero value
|
||||
/// If leaf_0x1f is not implemented in cpu used in host, guest OS should turn to leaf_0xb to
|
||||
/// determine the cpu topology.
|
||||
pub fn update_extended_topology_v2_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0x1f::*;
|
||||
let thread_width = 8 - (vm_spec.threads_per_core - 1).leading_zeros();
|
||||
let core_width = (8 - (vm_spec.cores_per_die - 1).leading_zeros()) + thread_width;
|
||||
let die_width = (8 - (vm_spec.dies_per_socket - 1).leading_zeros()) + core_width;
|
||||
|
||||
// EAX bit 0-4: number of bits to shift x2APIC ID right to get unique topology ID of
|
||||
// next level type all logical processors with same next level ID share current level
|
||||
// EBX bit 0-15: number of enabled logical processors at this level
|
||||
// ECX bit 0-8: level number (same as input)
|
||||
// ECX bit 8-15: level type (00h=invalid, 01h=SMT, 02h=core, 05h=die, otherwise=reserved)
|
||||
// EDX bits 0-31 contain x2APIC ID of current logical processor
|
||||
entry.eax = 0_u32;
|
||||
entry.ebx = 0_u32;
|
||||
entry.ecx = 0_u32;
|
||||
entry.edx = u32::from(vm_spec.cpu_id);
|
||||
|
||||
match entry.index {
|
||||
// Thread Level Topology; index = 0
|
||||
0 => {
|
||||
// To get the next level APIC ID, shift right with at most 1 because we have
|
||||
// maximum 2 hyperthreads per core that can be represented by 1 bit.
|
||||
entry
|
||||
.eax
|
||||
.write_bits_in_range(&eax::APICID_BITRANGE, thread_width);
|
||||
// When cpu_count == 1 or HT is disabled, there is 1 logical core at this level
|
||||
// Otherwise there are 2
|
||||
entry.ebx.write_bits_in_range(
|
||||
&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE,
|
||||
vm_spec.threads_per_core as u32,
|
||||
);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE, LEVEL_TYPE_THREAD);
|
||||
}
|
||||
// Core Level Processor Topology; index = 1
|
||||
1 => {
|
||||
entry
|
||||
.eax
|
||||
.write_bits_in_range(&eax::APICID_BITRANGE, core_width);
|
||||
entry.ebx.write_bits_in_range(
|
||||
&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE,
|
||||
u32::from(vm_spec.threads_per_core * vm_spec.cores_per_die),
|
||||
);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_NUMBER_BITRANGE, entry.index);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE, LEVEL_TYPE_CORE);
|
||||
}
|
||||
// Die Level Processor Topology; index = 5
|
||||
5 => {
|
||||
entry
|
||||
.eax
|
||||
.write_bits_in_range(&eax::APICID_BITRANGE, die_width);
|
||||
entry.ebx.write_bits_in_range(
|
||||
&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE,
|
||||
u32::from(
|
||||
vm_spec.threads_per_core * vm_spec.cores_per_die * vm_spec.dies_per_socket,
|
||||
),
|
||||
);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_NUMBER_BITRANGE, entry.index);
|
||||
entry
|
||||
.ecx
|
||||
.write_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE, LEVEL_TYPE_DIE);
|
||||
}
|
||||
level => {
|
||||
entry.ecx = level;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare content for CPUID standard level 8000_0002/3/4h: get processor name string.
|
||||
pub fn update_brand_string_entry(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
let brand_string = &vm_spec.brand_string;
|
||||
entry.eax = brand_string.get_reg_for_leaf(entry.function, BsReg::Eax);
|
||||
entry.ebx = brand_string.get_reg_for_leaf(entry.function, BsReg::Ebx);
|
||||
entry.ecx = brand_string.get_reg_for_leaf(entry.function, BsReg::Ecx);
|
||||
entry.edx = brand_string.get_reg_for_leaf(entry.function, BsReg::Edx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare content for CPUID extended level 8000_001Dh: get cache configuration descriptors.
|
||||
pub fn update_cache_parameters_entry(
|
||||
entry: &mut CpuIdEntry,
|
||||
vm_spec: &VmSpec,
|
||||
) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_cache_parameters::*;
|
||||
|
||||
// EAX bit 14-25: cores per cache - 1
|
||||
|
||||
match entry.eax.read_bits_in_range(&eax::CACHE_LEVEL_BITRANGE) {
|
||||
// L1 & L2 Cache
|
||||
1 | 2 => {
|
||||
// The L1 & L2 cache is shared by at most 2 hyperthreads
|
||||
entry.eax.write_bits_in_range(
|
||||
&eax::MAX_CPUS_PER_CORE_BITRANGE,
|
||||
(vm_spec.cpu_count > 1 && vm_spec.threads_per_core == 2) as u32,
|
||||
);
|
||||
}
|
||||
// L3 Cache
|
||||
3 => {
|
||||
// The L3 cache is shared among all the logical threads
|
||||
entry.eax.write_bits_in_range(
|
||||
&eax::MAX_CPUS_PER_CORE_BITRANGE,
|
||||
u32::from(vm_spec.cpu_count - 1),
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replaces the `cpuid` entries corresponding to `function` with the entries from the host's cpuid.
|
||||
pub fn use_host_cpuid_function(
|
||||
cpuid: &mut CpuId,
|
||||
function: u32,
|
||||
use_count: bool,
|
||||
) -> Result<(), Error> {
|
||||
// copy all the CpuId entries, except for the ones with the provided function
|
||||
cpuid.retain(|entry| entry.function != function);
|
||||
|
||||
// add all the host leaves with the provided function
|
||||
let mut count: u32 = 0;
|
||||
while let Ok(entry) = get_cpuid(function, count) {
|
||||
if count > 0 && !use_count {
|
||||
break;
|
||||
}
|
||||
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function,
|
||||
index: count,
|
||||
flags: 0,
|
||||
eax: entry.eax,
|
||||
ebx: entry.ebx,
|
||||
ecx: entry.ecx,
|
||||
edx: entry.edx,
|
||||
padding: [0, 0, 0],
|
||||
})
|
||||
.map_err(Error::FamError)?;
|
||||
|
||||
count += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use kvm_bindings::kvm_cpuid_entry2;
|
||||
|
||||
use super::*;
|
||||
use crate::cpuid::common::tests::get_topoext_fn;
|
||||
use crate::cpuid::cpu_leaf::leaf_0x1f::LEVEL_TYPE_DIE;
|
||||
use crate::cpuid::cpu_leaf::leaf_0xb::LEVEL_TYPE_CORE;
|
||||
use crate::cpuid::cpu_leaf::leaf_0xb::LEVEL_TYPE_THREAD;
|
||||
use crate::cpuid::transformer::VmSpec;
|
||||
|
||||
fn check_update_feature_info_entry(
|
||||
cpu_count: u8,
|
||||
expected_htt: bool,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x1::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
0,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_feature_info_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert!(entry.edx.read_bit(edx::HTT_BITINDEX) == expected_htt);
|
||||
assert!(entry.ecx.read_bit(ecx::TSC_DEADLINE_TIMER_BITINDEX));
|
||||
}
|
||||
|
||||
fn check_update_cache_parameters_entry(
|
||||
cpu_count: u8,
|
||||
cache_level: u32,
|
||||
expected_max_cpus_per_core: u32,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_cache_parameters::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
0,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: *(0_u32).write_bits_in_range(&eax::CACHE_LEVEL_BITRANGE, cache_level),
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_cache_parameters_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert!(
|
||||
entry
|
||||
.eax
|
||||
.read_bits_in_range(&eax::MAX_CPUS_PER_CORE_BITRANGE)
|
||||
== expected_max_cpus_per_core
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_update_extended_topology_entry(
|
||||
cpu_count: u8,
|
||||
index: u32,
|
||||
expected_apicid_shift_bit: u32,
|
||||
expected_num_logical_processors: u32,
|
||||
expected_level_type: u32,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_0xb::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
0,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_extended_topology_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert!(entry.eax.read_bits_in_range(&eax::APICID_BITRANGE) == expected_apicid_shift_bit);
|
||||
assert!(
|
||||
entry
|
||||
.ebx
|
||||
.read_bits_in_range(&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE)
|
||||
== expected_num_logical_processors
|
||||
);
|
||||
assert!(entry.ecx.read_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE) == expected_level_type);
|
||||
assert!(entry.ecx.read_bits_in_range(&ecx::LEVEL_NUMBER_BITRANGE) == index);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_update_extended_topology_v2_entry(
|
||||
cpu_count: u8,
|
||||
index: u32,
|
||||
expected_apicid_shift_bit: u32,
|
||||
expected_num_logical_processors: u32,
|
||||
expected_level_type: u32,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x1f::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
0,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_extended_topology_v2_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert!(entry.eax.read_bits_in_range(&eax::APICID_BITRANGE) == expected_apicid_shift_bit);
|
||||
assert!(
|
||||
entry
|
||||
.ebx
|
||||
.read_bits_in_range(&ebx::NUM_LOGICAL_PROCESSORS_BITRANGE)
|
||||
== expected_num_logical_processors
|
||||
);
|
||||
assert!(entry.ecx.read_bits_in_range(&ecx::LEVEL_TYPE_BITRANGE) == expected_level_type);
|
||||
assert!(entry.ecx.read_bits_in_range(&ecx::LEVEL_NUMBER_BITRANGE) == index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1vcpu_ht_off() {
|
||||
check_update_feature_info_entry(1, false, 1, 1, 1);
|
||||
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_cache_parameters_entry(1, 1, 0, 1, 1, 1);
|
||||
// test L2
|
||||
check_update_cache_parameters_entry(1, 2, 0, 1, 1, 1);
|
||||
// test L3
|
||||
check_update_cache_parameters_entry(1, 3, 0, 1, 1, 1);
|
||||
|
||||
// test update_extended_topology_entry
|
||||
// index 0
|
||||
check_update_extended_topology_entry(1, 0, 0, 1, LEVEL_TYPE_THREAD, 1, 1, 1);
|
||||
check_update_extended_topology_v2_entry(1, 0, 0, 1, LEVEL_TYPE_THREAD, 1, 1, 1);
|
||||
// index 1
|
||||
check_update_extended_topology_entry(1, 1, 0, 1, LEVEL_TYPE_CORE, 1, 1, 1);
|
||||
check_update_extended_topology_v2_entry(1, 1, 0, 1, LEVEL_TYPE_CORE, 1, 1, 1);
|
||||
// index 5
|
||||
check_update_extended_topology_v2_entry(1, 5, 0, 1, LEVEL_TYPE_DIE, 1, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1vcpu_ht_on() {
|
||||
check_update_feature_info_entry(1, false, 2, 1, 1);
|
||||
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_cache_parameters_entry(1, 1, 0, 2, 1, 1);
|
||||
// test L2
|
||||
check_update_cache_parameters_entry(1, 2, 0, 2, 1, 1);
|
||||
// test L3
|
||||
check_update_cache_parameters_entry(1, 3, 0, 2, 1, 1);
|
||||
|
||||
// test update_extended_topology_entry
|
||||
// index 0
|
||||
check_update_extended_topology_entry(1, 0, 1, 2, LEVEL_TYPE_THREAD, 2, 1, 1);
|
||||
check_update_extended_topology_v2_entry(1, 0, 1, 2, LEVEL_TYPE_THREAD, 2, 1, 1);
|
||||
// index 1
|
||||
check_update_extended_topology_entry(1, 1, 1, 2, LEVEL_TYPE_CORE, 2, 1, 1);
|
||||
check_update_extended_topology_v2_entry(1, 1, 1, 2, LEVEL_TYPE_CORE, 2, 1, 1);
|
||||
// index 5
|
||||
check_update_extended_topology_v2_entry(1, 5, 1, 2, LEVEL_TYPE_DIE, 2, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2vcpu_ht_off() {
|
||||
check_update_feature_info_entry(2, true, 1, 2, 1);
|
||||
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_cache_parameters_entry(2, 1, 0, 1, 2, 1);
|
||||
// test L2
|
||||
check_update_cache_parameters_entry(2, 2, 0, 1, 2, 1);
|
||||
// test L3
|
||||
check_update_cache_parameters_entry(2, 3, 1, 1, 2, 1);
|
||||
|
||||
// test update_extended_topology_entry
|
||||
// index 0
|
||||
check_update_extended_topology_entry(2, 0, 0, 1, LEVEL_TYPE_THREAD, 1, 2, 1);
|
||||
check_update_extended_topology_v2_entry(2, 0, 0, 1, LEVEL_TYPE_THREAD, 1, 2, 1);
|
||||
// index 1
|
||||
check_update_extended_topology_entry(2, 1, 1, 2, LEVEL_TYPE_CORE, 1, 2, 1);
|
||||
check_update_extended_topology_v2_entry(2, 1, 1, 2, LEVEL_TYPE_CORE, 1, 2, 1);
|
||||
// index 5
|
||||
check_update_extended_topology_v2_entry(2, 5, 1, 2, LEVEL_TYPE_DIE, 1, 2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2vcpu_ht_on() {
|
||||
check_update_feature_info_entry(2, true, 2, 2, 1);
|
||||
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_cache_parameters_entry(2, 1, 1, 2, 2, 1);
|
||||
// test L2
|
||||
check_update_cache_parameters_entry(2, 2, 1, 2, 2, 1);
|
||||
// test L3
|
||||
check_update_cache_parameters_entry(2, 3, 1, 2, 2, 1);
|
||||
|
||||
// test update_extended_topology_entry
|
||||
// index 0
|
||||
check_update_extended_topology_entry(2, 0, 1, 2, LEVEL_TYPE_THREAD, 2, 2, 1);
|
||||
check_update_extended_topology_v2_entry(2, 0, 1, 2, LEVEL_TYPE_THREAD, 2, 2, 1);
|
||||
// index 1
|
||||
check_update_extended_topology_entry(2, 1, 2, 4, LEVEL_TYPE_CORE, 2, 2, 1);
|
||||
check_update_extended_topology_v2_entry(2, 1, 2, 4, LEVEL_TYPE_CORE, 2, 2, 1);
|
||||
// index 5
|
||||
check_update_extended_topology_v2_entry(2, 5, 2, 4, LEVEL_TYPE_DIE, 2, 2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2dies_2vcpu_ht_off() {
|
||||
// test update_extended_topology_entry
|
||||
// index 0
|
||||
check_update_extended_topology_entry(2, 0, 0, 1, LEVEL_TYPE_THREAD, 1, 1, 2);
|
||||
check_update_extended_topology_v2_entry(2, 0, 0, 1, LEVEL_TYPE_THREAD, 1, 1, 2);
|
||||
// index 1
|
||||
check_update_extended_topology_entry(2, 1, 0, 1, LEVEL_TYPE_CORE, 1, 1, 2);
|
||||
check_update_extended_topology_v2_entry(2, 1, 0, 1, LEVEL_TYPE_CORE, 1, 1, 2);
|
||||
// index 5
|
||||
check_update_extended_topology_v2_entry(2, 5, 1, 2, LEVEL_TYPE_DIE, 1, 1, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2dies_4vcpu_ht_on() {
|
||||
// test update_extended_topology_entry
|
||||
// index 0
|
||||
check_update_extended_topology_entry(4, 0, 1, 2, LEVEL_TYPE_THREAD, 2, 1, 2);
|
||||
check_update_extended_topology_v2_entry(4, 0, 1, 2, LEVEL_TYPE_THREAD, 2, 1, 2);
|
||||
// index 1
|
||||
check_update_extended_topology_entry(4, 1, 1, 2, LEVEL_TYPE_CORE, 2, 1, 2);
|
||||
check_update_extended_topology_v2_entry(4, 1, 1, 2, LEVEL_TYPE_CORE, 2, 1, 2);
|
||||
// index 5
|
||||
check_update_extended_topology_v2_entry(4, 5, 2, 4, LEVEL_TYPE_DIE, 2, 1, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn test_use_host_cpuid_function_with_count() {
|
||||
// try to emulate the extended cache topology leaves
|
||||
let topoext_fn = get_topoext_fn();
|
||||
|
||||
// check that it behaves correctly for TOPOEXT function
|
||||
let mut cpuid = CpuId::new(1).unwrap();
|
||||
cpuid.as_mut_slice()[0].function = topoext_fn;
|
||||
assert!(use_host_cpuid_function(&mut cpuid, topoext_fn, true).is_ok());
|
||||
let entries = cpuid.as_mut_slice();
|
||||
assert!(entries.len() > 1);
|
||||
for (count, entry) in entries.iter_mut().enumerate() {
|
||||
assert!(entry.function == topoext_fn);
|
||||
assert!(entry.index == count as u32);
|
||||
assert!(entry.eax != 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn test_use_host_cpuid_function_without_count() {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x1::*;
|
||||
// try to emulate the extended cache topology leaves
|
||||
let feature_info_fn = LEAF_NUM;
|
||||
|
||||
// check that it behaves correctly for TOPOEXT function
|
||||
let mut cpuid = CpuId::new(1).unwrap();
|
||||
cpuid.as_mut_slice()[0].function = feature_info_fn;
|
||||
assert!(use_host_cpuid_function(&mut cpuid, feature_info_fn, false).is_ok());
|
||||
let entries = cpuid.as_mut_slice();
|
||||
assert!(entries.len() == 1);
|
||||
let entry = entries[0];
|
||||
|
||||
assert!(entry.function == feature_info_fn);
|
||||
assert!(entry.index == 0);
|
||||
assert!(entry.eax != 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn test_use_host_cpuid_function_err() {
|
||||
let topoext_fn = get_topoext_fn();
|
||||
// check that it returns Err when there are too many entriesentry.function == topoext_fn
|
||||
let mut cpuid = CpuId::new(kvm_bindings::KVM_MAX_CPUID_ENTRIES).unwrap();
|
||||
match use_host_cpuid_function(&mut cpuid, topoext_fn, true) {
|
||||
Err(Error::FamError(vmm_sys_util::fam::Error::SizeLimitExceeded)) => {}
|
||||
_ => panic!("Wrong behavior"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::super::bit_helper::BitHelper;
|
||||
use super::super::cpu_leaf;
|
||||
use super::*;
|
||||
|
||||
fn update_deterministic_cache_entry(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0x4::*;
|
||||
|
||||
common::update_cache_parameters_entry(entry, vm_spec)?;
|
||||
|
||||
// If leaf_0xB or leaf_0x1F is enabled, leaf0x4 won't be used to generate topology information.
|
||||
// In most cases, we could have leaf_0xB in our host cpu. But we keep the leaf_0x4 eax[26,31]
|
||||
// to prevent rare cases.
|
||||
if vm_spec.cpu_count <= 64 {
|
||||
entry.eax.write_bits_in_range(
|
||||
&eax::MAX_CORES_PER_PACKAGE_BITRANGE,
|
||||
u32::from(vm_spec.cpu_count - 1),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_power_management_entry(entry: &mut CpuIdEntry, _vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
// disable pstate feature
|
||||
entry.eax = 0;
|
||||
entry.ebx = 0;
|
||||
entry.ecx = 0;
|
||||
entry.edx = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_perf_mon_entry(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
use cpu_leaf::leaf_0xa::*;
|
||||
|
||||
// Architectural Performance Monitor Leaf
|
||||
match vm_spec.vpmu_feature {
|
||||
VpmuFeatureLevel::Disabled => {
|
||||
// Disable PMU
|
||||
entry.eax = 0;
|
||||
entry.ebx = 0;
|
||||
entry.ecx = 0;
|
||||
entry.edx = 0;
|
||||
}
|
||||
VpmuFeatureLevel::LimitedlyEnabled => {
|
||||
// Allow minimal vpmu ability (only instuctions and cycles pmu).
|
||||
entry.eax.write_bits_in_range(&eax::PMC_VERSION_ID, 2);
|
||||
entry.eax.write_bits_in_range(&eax::BIT_LEN_PMEVENT, 7);
|
||||
|
||||
// 0(false) means support for the targeted performance monitoring event
|
||||
entry.ebx.write_bit(ebx::CORE_CYCLES_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::REF_CYCLES_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::INST_RETIRED_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::BR_INST_RETIRED_BITINDEX, true);
|
||||
entry.ebx.write_bit(ebx::LLC_MISSES_BITINDEX, true);
|
||||
entry.ebx.write_bit(ebx::LLC_REF_BITINDEX, true);
|
||||
entry.ebx.write_bit(ebx::BR_MIS_RETIRED_BITINDEX, true);
|
||||
}
|
||||
VpmuFeatureLevel::FullyEnabled => {
|
||||
// Allow all supported vpmu ability
|
||||
entry.eax.write_bits_in_range(&eax::PMC_VERSION_ID, 2);
|
||||
entry.eax.write_bits_in_range(&eax::BIT_LEN_PMEVENT, 7);
|
||||
|
||||
// 0(false) means support for the targeted performance monitoring event
|
||||
entry.ebx.write_bit(ebx::CORE_CYCLES_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::REF_CYCLES_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::INST_RETIRED_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::BR_INST_RETIRED_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::LLC_MISSES_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::LLC_REF_BITINDEX, false);
|
||||
entry.ebx.write_bit(ebx::BR_MIS_RETIRED_BITINDEX, false);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct IntelCpuidTransformer {}
|
||||
|
||||
impl IntelCpuidTransformer {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl CpuidTransformer for IntelCpuidTransformer {
|
||||
fn process_cpuid(&self, cpuid: &mut CpuId, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
common::use_host_cpuid_function(cpuid, cpu_leaf::leaf_0x0::LEAF_NUM, false)?;
|
||||
self.process_entries(cpuid, vm_spec)
|
||||
}
|
||||
|
||||
fn entry_transformer_fn(&self, entry: &mut CpuIdEntry) -> Option<EntryTransformerFn> {
|
||||
use cpu_leaf::*;
|
||||
|
||||
match entry.function {
|
||||
leaf_0x1::LEAF_NUM => Some(common::update_feature_info_entry),
|
||||
leaf_0x4::LEAF_NUM => Some(intel::update_deterministic_cache_entry),
|
||||
leaf_0x6::LEAF_NUM => Some(intel::update_power_management_entry),
|
||||
leaf_0xa::LEAF_NUM => Some(intel::update_perf_mon_entry),
|
||||
leaf_0xb::LEAF_NUM => Some(common::update_extended_topology_entry),
|
||||
leaf_0x1f::LEAF_NUM => Some(common::update_extended_topology_v2_entry),
|
||||
0x8000_0002..=0x8000_0004 => Some(common::update_brand_string_entry),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use kvm_bindings::kvm_cpuid_entry2;
|
||||
|
||||
use super::*;
|
||||
use crate::cpuid::transformer::VmSpec;
|
||||
|
||||
#[test]
|
||||
fn test_update_perf_mon_entry() {
|
||||
use crate::cpuid::cpu_leaf::leaf_0xa::*;
|
||||
// Test when vpmu is off (level Disabled)
|
||||
let vm_spec =
|
||||
VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: LEAF_NUM,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 1,
|
||||
ebx: 1,
|
||||
ecx: 1,
|
||||
edx: 1,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_perf_mon_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert_eq!(entry.eax, 0);
|
||||
assert_eq!(entry.ebx, 0);
|
||||
assert_eq!(entry.ecx, 0);
|
||||
assert_eq!(entry.edx, 0);
|
||||
|
||||
// Test when only instructions and cycles pmu are enabled (level LimitedlyEnabled)
|
||||
let vm_spec = VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::LimitedlyEnabled)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_perf_mon_entry(entry, &vm_spec).is_ok());
|
||||
assert_eq!(entry.eax.read_bits_in_range(&eax::PMC_VERSION_ID), 2);
|
||||
assert_eq!(entry.eax.read_bits_in_range(&eax::BIT_LEN_PMEVENT), 7);
|
||||
|
||||
assert!(!entry.ebx.read_bit(ebx::CORE_CYCLES_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::INST_RETIRED_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::REF_CYCLES_BITINDEX));
|
||||
assert!(entry.ebx.read_bit(ebx::LLC_REF_BITINDEX));
|
||||
assert!(entry.ebx.read_bit(ebx::LLC_MISSES_BITINDEX));
|
||||
assert!(entry.ebx.read_bit(ebx::BR_INST_RETIRED_BITINDEX));
|
||||
assert!(entry.ebx.read_bit(ebx::BR_MIS_RETIRED_BITINDEX));
|
||||
|
||||
// Test when all vpmu features are enabled (level FullyEnabled)
|
||||
let vm_spec = VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::FullyEnabled)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: 0,
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_perf_mon_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert_eq!(entry.eax.read_bits_in_range(&eax::PMC_VERSION_ID), 2);
|
||||
assert_eq!(entry.eax.read_bits_in_range(&eax::BIT_LEN_PMEVENT), 7);
|
||||
|
||||
assert!(!entry.ebx.read_bit(ebx::CORE_CYCLES_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::INST_RETIRED_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::REF_CYCLES_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::LLC_REF_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::LLC_MISSES_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::BR_INST_RETIRED_BITINDEX));
|
||||
assert!(!entry.ebx.read_bit(ebx::BR_MIS_RETIRED_BITINDEX));
|
||||
}
|
||||
|
||||
fn check_update_deterministic_cache_entry(
|
||||
cpu_count: u8,
|
||||
cache_level: u32,
|
||||
expected_max_cores_per_package: u32,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
) {
|
||||
use crate::cpuid::cpu_leaf::leaf_0x4::*;
|
||||
|
||||
let vm_spec = VmSpec::new(
|
||||
0,
|
||||
cpu_count,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
VpmuFeatureLevel::Disabled,
|
||||
)
|
||||
.expect("Error creating vm_spec");
|
||||
let entry = &mut kvm_cpuid_entry2 {
|
||||
function: 0x0,
|
||||
index: 0,
|
||||
flags: 0,
|
||||
eax: *(0_u32).write_bits_in_range(&eax::CACHE_LEVEL_BITRANGE, cache_level),
|
||||
ebx: 0,
|
||||
ecx: 0,
|
||||
edx: 0,
|
||||
padding: [0, 0, 0],
|
||||
};
|
||||
|
||||
assert!(update_deterministic_cache_entry(entry, &vm_spec).is_ok());
|
||||
|
||||
assert!(
|
||||
entry
|
||||
.eax
|
||||
.read_bits_in_range(&eax::MAX_CORES_PER_PACKAGE_BITRANGE)
|
||||
== expected_max_cores_per_package
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1vcpu_ht_off() {
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_deterministic_cache_entry(1, 1, 0, 1, 1, 1);
|
||||
// test L2
|
||||
check_update_deterministic_cache_entry(1, 2, 0, 1, 1, 1);
|
||||
// test L3
|
||||
check_update_deterministic_cache_entry(1, 3, 0, 1, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_1vcpu_ht_on() {
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_deterministic_cache_entry(1, 1, 0, 2, 1, 1);
|
||||
// test L2
|
||||
check_update_deterministic_cache_entry(1, 2, 0, 2, 1, 1);
|
||||
// test L3
|
||||
check_update_deterministic_cache_entry(1, 3, 0, 2, 1, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2vcpu_ht_off() {
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_deterministic_cache_entry(2, 1, 1, 1, 2, 1);
|
||||
// test L2
|
||||
check_update_deterministic_cache_entry(2, 2, 1, 1, 2, 1);
|
||||
// test L3
|
||||
check_update_deterministic_cache_entry(2, 3, 1, 1, 2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2vcpu_ht_on() {
|
||||
// test update_deterministic_cache_entry
|
||||
// test L1
|
||||
check_update_deterministic_cache_entry(2, 1, 1, 2, 2, 1);
|
||||
// test L2
|
||||
check_update_deterministic_cache_entry(2, 2, 1, 2, 2, 1);
|
||||
// test L3
|
||||
check_update_deterministic_cache_entry(2, 3, 1, 2, 2, 1);
|
||||
}
|
||||
}
|
||||
172
src/dragonball/src/dbs_arch/src/x86_64/cpuid/transformer/mod.rs
Normal file
172
src/dragonball/src/dbs_arch/src/x86_64/cpuid/transformer/mod.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::brand_string::{BrandString, Reg as BsReg};
|
||||
use super::common::get_vendor_id;
|
||||
use super::{CpuId, CpuIdEntry};
|
||||
use crate::VpmuFeatureLevel;
|
||||
|
||||
pub mod amd;
|
||||
pub mod common;
|
||||
pub mod intel;
|
||||
|
||||
/// Structure containing the specifications of the VM
|
||||
pub struct VmSpec {
|
||||
/// The vendor id of the CPU
|
||||
cpu_vendor_id: [u8; 12],
|
||||
/// The id of the current logical cpu in the range [0..cpu_count].
|
||||
cpu_id: u8,
|
||||
/// The total number of logical cpus (includes cpus that could be hotplugged).
|
||||
cpu_count: u8,
|
||||
/// The desired brand string for the guest.
|
||||
brand_string: BrandString,
|
||||
/// threads per core for cpu topology information
|
||||
threads_per_core: u8,
|
||||
/// cores per die for cpu topology information
|
||||
cores_per_die: u8,
|
||||
/// dies per socket for cpu topology information
|
||||
dies_per_socket: u8,
|
||||
/// if vpmu feature is Disabled, it means vpmu feature is off (by default)
|
||||
/// if vpmu feature is LimitedlyEnabled, it means minimal vpmu counters are supported (cycles and instructions)
|
||||
/// if vpmu feature is FullyEnabled, it means all vpmu counters are supported
|
||||
vpmu_feature: VpmuFeatureLevel,
|
||||
}
|
||||
|
||||
impl VmSpec {
|
||||
/// Creates a new instance of VmSpec with the specified parameters
|
||||
/// The brand string is deduced from the vendor_id
|
||||
pub fn new(
|
||||
cpu_id: u8,
|
||||
cpu_count: u8,
|
||||
threads_per_core: u8,
|
||||
cores_per_die: u8,
|
||||
dies_per_socket: u8,
|
||||
vpmu_feature: VpmuFeatureLevel,
|
||||
) -> Result<VmSpec, Error> {
|
||||
let cpu_vendor_id = get_vendor_id().map_err(Error::InternalError)?;
|
||||
let brand_string =
|
||||
BrandString::from_vendor_id(&cpu_vendor_id).map_err(Error::BrandString)?;
|
||||
|
||||
Ok(VmSpec {
|
||||
cpu_vendor_id,
|
||||
cpu_id,
|
||||
cpu_count,
|
||||
brand_string,
|
||||
threads_per_core,
|
||||
cores_per_die,
|
||||
dies_per_socket,
|
||||
vpmu_feature,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to cpu_vendor_id
|
||||
pub fn cpu_vendor_id(&self) -> &[u8; 12] {
|
||||
&self.cpu_vendor_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors associated with processing the CPUID leaves.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Error {
|
||||
/// Failed to parse CPU brand string
|
||||
BrandString(super::brand_string::Error),
|
||||
/// The CPU architecture is not supported
|
||||
CpuNotSupported,
|
||||
/// A FamStructWrapper operation has failed
|
||||
FamError(vmm_sys_util::fam::Error),
|
||||
/// A call to an internal helper method failed
|
||||
InternalError(super::common::Error),
|
||||
/// The maximum number of addressable logical CPUs cannot be stored in an `u8`.
|
||||
VcpuCountOverflow,
|
||||
}
|
||||
|
||||
pub type EntryTransformerFn = fn(entry: &mut CpuIdEntry, vm_spec: &VmSpec) -> Result<(), Error>;
|
||||
|
||||
/// Generic trait that provides methods for transforming the cpuid
|
||||
pub trait CpuidTransformer {
|
||||
/// Process the cpuid array and make the desired transformations.
|
||||
fn process_cpuid(&self, cpuid: &mut CpuId, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
self.process_entries(cpuid, vm_spec)
|
||||
}
|
||||
|
||||
/// Iterate through all the cpuid entries and calls the associated transformer for each one.
|
||||
fn process_entries(&self, cpuid: &mut CpuId, vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
for entry in cpuid.as_mut_slice().iter_mut() {
|
||||
let maybe_transformer_fn = self.entry_transformer_fn(entry);
|
||||
|
||||
if let Some(transformer_fn) = maybe_transformer_fn {
|
||||
transformer_fn(entry, vm_spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the associated transformer for a cpuid entry
|
||||
fn entry_transformer_fn(&self, _entry: &mut CpuIdEntry) -> Option<EntryTransformerFn> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use kvm_bindings::kvm_cpuid_entry2;
|
||||
|
||||
const PROCESSED_FN: u32 = 1;
|
||||
const EXPECTED_INDEX: u32 = 100;
|
||||
|
||||
fn transform_entry(entry: &mut kvm_cpuid_entry2, _vm_spec: &VmSpec) -> Result<(), Error> {
|
||||
entry.index = EXPECTED_INDEX;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct MockCpuidTransformer {}
|
||||
|
||||
impl CpuidTransformer for MockCpuidTransformer {
|
||||
fn entry_transformer_fn(&self, entry: &mut kvm_cpuid_entry2) -> Option<EntryTransformerFn> {
|
||||
match entry.function {
|
||||
PROCESSED_FN => Some(transform_entry),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_cpuid() {
|
||||
let num_entries = 5;
|
||||
|
||||
let mut cpuid = CpuId::new(num_entries).unwrap();
|
||||
let vm_spec = VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled);
|
||||
cpuid.as_mut_slice()[0].function = PROCESSED_FN;
|
||||
assert!(MockCpuidTransformer {}
|
||||
.process_cpuid(&mut cpuid, &vm_spec.unwrap())
|
||||
.is_ok());
|
||||
|
||||
assert!(cpuid.as_mut_slice().len() == num_entries);
|
||||
for entry in cpuid.as_mut_slice().iter() {
|
||||
match entry.function {
|
||||
PROCESSED_FN => {
|
||||
assert_eq!(entry.index, EXPECTED_INDEX);
|
||||
}
|
||||
_ => {
|
||||
assert_ne!(entry.index, EXPECTED_INDEX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_cpu_architecture_cpuid() {
|
||||
use crate::cpuid::process_cpuid;
|
||||
let num_entries = 5;
|
||||
|
||||
let mut cpuid = CpuId::new(num_entries).unwrap();
|
||||
let mut vm_spec = VmSpec::new(0, 1, 1, 1, 1, VpmuFeatureLevel::Disabled).unwrap();
|
||||
|
||||
vm_spec.cpu_vendor_id = [1; 12];
|
||||
assert!(process_cpuid(&mut cpuid, &vm_spec).is_err());
|
||||
}
|
||||
}
|
||||
119
src/dragonball/src/dbs_arch/src/x86_64/gdt.rs
Normal file
119
src/dragonball/src/dbs_arch/src/x86_64/gdt.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
// For GDT details see arch/x86/include/asm/segment.h
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use kvm_bindings::kvm_segment;
|
||||
|
||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||
#[allow(unused_parens)]
|
||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||
(((u64::from(base) & 0xff00_0000u64) << (56 - 24))
|
||||
| ((u64::from(flags) & 0x0000_f0ffu64) << 40)
|
||||
| ((u64::from(limit) & 0x000f_0000u64) << (48 - 16))
|
||||
| ((u64::from(base) & 0x00ff_ffffu64) << 16)
|
||||
| (u64::from(limit) & 0x0000_ffffu64))
|
||||
}
|
||||
|
||||
#[allow(unused_parens)]
|
||||
fn get_base(entry: u64) -> u64 {
|
||||
((((entry) & 0xFF00_0000_0000_0000) >> 32)
|
||||
| (((entry) & 0x0000_00FF_0000_0000) >> 16)
|
||||
| (((entry) & 0x0000_0000_FFFF_0000) >> 16))
|
||||
}
|
||||
|
||||
fn get_limit(entry: u64) -> u32 {
|
||||
((((entry) & 0x000F_0000_0000_0000) >> 32) | ((entry) & 0x0000_0000_0000_FFFF)) as u32
|
||||
}
|
||||
|
||||
fn get_g(entry: u64) -> u8 {
|
||||
((entry & 0x0080_0000_0000_0000) >> 55) as u8
|
||||
}
|
||||
|
||||
fn get_db(entry: u64) -> u8 {
|
||||
((entry & 0x0040_0000_0000_0000) >> 54) as u8
|
||||
}
|
||||
|
||||
fn get_l(entry: u64) -> u8 {
|
||||
((entry & 0x0020_0000_0000_0000) >> 53) as u8
|
||||
}
|
||||
|
||||
fn get_avl(entry: u64) -> u8 {
|
||||
((entry & 0x0010_0000_0000_0000) >> 52) as u8
|
||||
}
|
||||
|
||||
fn get_p(entry: u64) -> u8 {
|
||||
((entry & 0x0000_8000_0000_0000) >> 47) as u8
|
||||
}
|
||||
|
||||
fn get_dpl(entry: u64) -> u8 {
|
||||
((entry & 0x0000_6000_0000_0000) >> 45) as u8
|
||||
}
|
||||
|
||||
fn get_s(entry: u64) -> u8 {
|
||||
((entry & 0x0000_1000_0000_0000) >> 44) as u8
|
||||
}
|
||||
|
||||
fn get_type(entry: u64) -> u8 {
|
||||
((entry & 0x0000_0F00_0000_0000) >> 40) as u8
|
||||
}
|
||||
|
||||
/// Automatically build the kvm struct for SET_SREGS from the kernel bit fields.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `entry` - The gdt entry.
|
||||
/// * `table_index` - Index of the entry in the gdt table.
|
||||
pub fn kvm_segment_from_gdt(entry: u64, table_index: u8) -> kvm_segment {
|
||||
kvm_segment {
|
||||
base: get_base(entry),
|
||||
limit: get_limit(entry),
|
||||
selector: u16::from(table_index * 8),
|
||||
type_: get_type(entry),
|
||||
present: get_p(entry),
|
||||
dpl: get_dpl(entry),
|
||||
db: get_db(entry),
|
||||
s: get_s(entry),
|
||||
l: get_l(entry),
|
||||
g: get_g(entry),
|
||||
avl: get_avl(entry),
|
||||
padding: 0,
|
||||
unusable: match get_p(entry) {
|
||||
0 => 1,
|
||||
_ => 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn field_parse() {
|
||||
let gdt = gdt_entry(0xA09B, 0x10_0000, 0xfffff);
|
||||
let seg = kvm_segment_from_gdt(gdt, 0);
|
||||
// 0xA09B
|
||||
// 'A'
|
||||
assert_eq!(0x1, seg.g);
|
||||
assert_eq!(0x0, seg.db);
|
||||
assert_eq!(0x1, seg.l);
|
||||
assert_eq!(0x0, seg.avl);
|
||||
// '9'
|
||||
assert_eq!(0x1, seg.present);
|
||||
assert_eq!(0x0, seg.dpl);
|
||||
assert_eq!(0x1, seg.s);
|
||||
// 'B'
|
||||
assert_eq!(0xB, seg.type_);
|
||||
// base and limit
|
||||
assert_eq!(0x10_0000, seg.base);
|
||||
assert_eq!(0xfffff, seg.limit);
|
||||
assert_eq!(0x0, seg.unusable);
|
||||
}
|
||||
}
|
||||
136
src/dragonball/src/dbs_arch/src/x86_64/interrupts.rs
Normal file
136
src/dragonball/src/dbs_arch/src/x86_64/interrupts.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use kvm_bindings::kvm_lapic_state;
|
||||
use kvm_ioctls::VcpuFd;
|
||||
|
||||
/// Errors thrown while configuring the LAPIC.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failure in retrieving the LAPIC configuration.
|
||||
GetLapic(kvm_ioctls::Error),
|
||||
/// Failure in modifying the LAPIC configuration.
|
||||
SetLapic(kvm_ioctls::Error),
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// Defines poached from apicdef.h kernel header.
|
||||
const APIC_LVT0: usize = 0x350;
|
||||
const APIC_LVT1: usize = 0x360;
|
||||
const APIC_MODE_NMI: u32 = 0x4;
|
||||
const APIC_MODE_EXTINT: u32 = 0x7;
|
||||
|
||||
fn get_klapic_reg(klapic: &kvm_lapic_state, reg_offset: usize) -> u32 {
|
||||
let range = reg_offset..reg_offset + 4;
|
||||
let reg = klapic.regs.get(range).expect("get_klapic_reg range");
|
||||
|
||||
let mut reg_bytes = [0u8; 4];
|
||||
for (byte, read) in reg_bytes.iter_mut().zip(reg.iter().cloned()) {
|
||||
*byte = read as u8;
|
||||
}
|
||||
|
||||
u32::from_le_bytes(reg_bytes)
|
||||
}
|
||||
|
||||
fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
|
||||
let range = reg_offset..reg_offset + 4;
|
||||
let reg = klapic.regs.get_mut(range).expect("set_klapic_reg range");
|
||||
|
||||
let value = u32::to_le_bytes(value);
|
||||
for (byte, read) in reg.iter_mut().zip(value.iter().cloned()) {
|
||||
*byte = read as i8;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_parens)]
|
||||
fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
(((reg) & !0x700) | ((mode) << 8))
|
||||
}
|
||||
|
||||
/// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vcpu` - The VCPU object to configure.
|
||||
pub fn set_lint(vcpu: &VcpuFd) -> Result<()> {
|
||||
let mut klapic = vcpu.get_lapic().map_err(Error::GetLapic)?;
|
||||
|
||||
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
|
||||
set_klapic_reg(
|
||||
&mut klapic,
|
||||
APIC_LVT0,
|
||||
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
|
||||
);
|
||||
let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
|
||||
set_klapic_reg(
|
||||
&mut klapic,
|
||||
APIC_LVT1,
|
||||
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
|
||||
);
|
||||
|
||||
vcpu.set_lapic(&klapic).map_err(Error::SetLapic)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
const KVM_APIC_REG_SIZE: usize = 0x400;
|
||||
|
||||
#[test]
|
||||
fn test_set_and_get_klapic_reg() {
|
||||
let reg_offset = 0x340;
|
||||
let mut klapic = kvm_lapic_state::default();
|
||||
set_klapic_reg(&mut klapic, reg_offset, 3);
|
||||
let value = get_klapic_reg(&klapic, reg_offset);
|
||||
assert_eq!(value, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_set_and_get_klapic_out_of_bounds() {
|
||||
let reg_offset = KVM_APIC_REG_SIZE + 10;
|
||||
let mut klapic = kvm_lapic_state::default();
|
||||
set_klapic_reg(&mut klapic, reg_offset, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setlint() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
assert!(kvm.check_extension(kvm_ioctls::Cap::Irqchip));
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
//the get_lapic ioctl will fail if there is no irqchip created beforehand.
|
||||
assert!(vm.create_irq_chip().is_ok());
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let klapic_before: kvm_lapic_state = vcpu.get_lapic().unwrap();
|
||||
|
||||
// Compute the value that is expected to represent LVT0 and LVT1.
|
||||
let lint0 = get_klapic_reg(&klapic_before, APIC_LVT0);
|
||||
let lint1 = get_klapic_reg(&klapic_before, APIC_LVT1);
|
||||
let lint0_mode_expected = set_apic_delivery_mode(lint0, APIC_MODE_EXTINT);
|
||||
let lint1_mode_expected = set_apic_delivery_mode(lint1, APIC_MODE_NMI);
|
||||
|
||||
set_lint(&vcpu).unwrap();
|
||||
|
||||
// Compute the value that represents LVT0 and LVT1 after set_lint.
|
||||
let klapic_actual: kvm_lapic_state = vcpu.get_lapic().unwrap();
|
||||
let lint0_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT0);
|
||||
let lint1_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT1);
|
||||
assert_eq!(lint0_mode_expected, lint0_mode_actual);
|
||||
assert_eq!(lint1_mode_expected, lint1_mode_actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setlint_fails() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
// 'get_lapic' ioctl triggered by the 'set_lint' function will fail if there is no
|
||||
// irqchip created beforehand.
|
||||
assert!(set_lint(&vcpu).is_err());
|
||||
}
|
||||
}
|
||||
15
src/dragonball/src/dbs_arch/src/x86_64/mod.rs
Normal file
15
src/dragonball/src/dbs_arch/src/x86_64/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! CPU architecture specific constants and utilities for the `x86_64` architecture.
|
||||
|
||||
/// Definitions for x86 CPUID
|
||||
pub mod cpuid;
|
||||
/// Definitions for x86 Global Descriptor Table
|
||||
pub mod gdt;
|
||||
/// Definitions for x86 interrupts
|
||||
pub mod interrupts;
|
||||
/// Definitions for x86 Model Specific Registers(MSR).
|
||||
pub mod msr;
|
||||
/// Definitions for x86 Registers
|
||||
pub mod regs;
|
||||
778
src/dragonball/src/dbs_arch/src/x86_64/msr.rs
Normal file
778
src/dragonball/src/dbs_arch/src/x86_64/msr.rs
Normal file
@@ -0,0 +1,778 @@
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
/// Model Specific Registers (MSRs) related functionality.
|
||||
use std::result;
|
||||
|
||||
use kvm_bindings::MsrList;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// MSR related errors.
|
||||
pub enum Error {
|
||||
/// Getting supported MSRs failed.
|
||||
GetSupportedModelSpecificRegisters(kvm_ioctls::Error),
|
||||
/// Setting up MSRs failed.
|
||||
SetModelSpecificRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set all MSRs.
|
||||
SetModelSpecificRegistersCount,
|
||||
/// Msr error
|
||||
Msr(vmm_sys_util::fam::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// MSR range
|
||||
struct MsrRange {
|
||||
/// Base MSR address
|
||||
base: u32,
|
||||
/// Number of MSRs
|
||||
nmsrs: u32,
|
||||
}
|
||||
|
||||
impl MsrRange {
|
||||
/// Returns whether `msr` is contained in this MSR range.
|
||||
fn contains(&self, msr: u32) -> bool {
|
||||
self.base <= msr && msr < self.base + self.nmsrs
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a MsrRange of one msr given as argument.
|
||||
macro_rules! SINGLE_MSR {
|
||||
($msr:expr) => {
|
||||
MsrRange {
|
||||
base: $msr,
|
||||
nmsrs: 1,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a MsrRange of with msr base and count given as arguments.
|
||||
macro_rules! MSR_RANGE {
|
||||
($first:expr, $count:expr) => {
|
||||
MsrRange {
|
||||
base: $first,
|
||||
nmsrs: $count,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// List of MSRs that can be serialized. List is sorted in ascending order of MSRs addresses.
|
||||
static WHITELISTED_MSR_RANGES: &[MsrRange] = &[
|
||||
SINGLE_MSR!(MSR_IA32_P5_MC_ADDR),
|
||||
SINGLE_MSR!(MSR_IA32_P5_MC_TYPE),
|
||||
SINGLE_MSR!(MSR_IA32_TSC),
|
||||
SINGLE_MSR!(MSR_IA32_PLATFORM_ID),
|
||||
SINGLE_MSR!(MSR_IA32_APICBASE),
|
||||
SINGLE_MSR!(MSR_IA32_EBL_CR_POWERON),
|
||||
SINGLE_MSR!(MSR_EBC_FREQUENCY_ID),
|
||||
SINGLE_MSR!(MSR_SMI_COUNT),
|
||||
SINGLE_MSR!(MSR_IA32_FEATURE_CONTROL),
|
||||
SINGLE_MSR!(MSR_IA32_TSC_ADJUST),
|
||||
SINGLE_MSR!(MSR_IA32_SPEC_CTRL),
|
||||
SINGLE_MSR!(MSR_IA32_PRED_CMD),
|
||||
SINGLE_MSR!(MSR_IA32_UCODE_WRITE),
|
||||
SINGLE_MSR!(MSR_IA32_UCODE_REV),
|
||||
SINGLE_MSR!(MSR_IA32_SMBASE),
|
||||
SINGLE_MSR!(MSR_FSB_FREQ),
|
||||
SINGLE_MSR!(MSR_PLATFORM_INFO),
|
||||
SINGLE_MSR!(MSR_PKG_CST_CONFIG_CONTROL),
|
||||
SINGLE_MSR!(MSR_IA32_MPERF),
|
||||
SINGLE_MSR!(MSR_IA32_APERF),
|
||||
SINGLE_MSR!(MSR_MTRRcap),
|
||||
SINGLE_MSR!(MSR_IA32_BBL_CR_CTL3),
|
||||
SINGLE_MSR!(MSR_IA32_SYSENTER_CS),
|
||||
SINGLE_MSR!(MSR_IA32_SYSENTER_ESP),
|
||||
SINGLE_MSR!(MSR_IA32_SYSENTER_EIP),
|
||||
SINGLE_MSR!(MSR_IA32_MCG_CAP),
|
||||
SINGLE_MSR!(MSR_IA32_MCG_STATUS),
|
||||
SINGLE_MSR!(MSR_IA32_MCG_CTL),
|
||||
SINGLE_MSR!(MSR_IA32_PERF_STATUS),
|
||||
SINGLE_MSR!(MSR_IA32_MISC_ENABLE),
|
||||
SINGLE_MSR!(MSR_MISC_FEATURE_CONTROL),
|
||||
SINGLE_MSR!(MSR_MISC_PWR_MGMT),
|
||||
SINGLE_MSR!(MSR_TURBO_RATIO_LIMIT),
|
||||
SINGLE_MSR!(MSR_TURBO_RATIO_LIMIT1),
|
||||
SINGLE_MSR!(MSR_IA32_DEBUGCTLMSR),
|
||||
SINGLE_MSR!(MSR_IA32_LASTBRANCHFROMIP),
|
||||
SINGLE_MSR!(MSR_IA32_LASTBRANCHTOIP),
|
||||
SINGLE_MSR!(MSR_IA32_LASTINTFROMIP),
|
||||
SINGLE_MSR!(MSR_IA32_LASTINTTOIP),
|
||||
SINGLE_MSR!(MSR_IA32_POWER_CTL),
|
||||
MSR_RANGE!(
|
||||
// IA32_MTRR_PHYSBASE0
|
||||
0x200, 0x100
|
||||
),
|
||||
MSR_RANGE!(
|
||||
// MSR_CORE_C3_RESIDENCY
|
||||
// MSR_CORE_C6_RESIDENCY
|
||||
// MSR_CORE_C7_RESIDENCY
|
||||
MSR_CORE_C3_RESIDENCY,
|
||||
3
|
||||
),
|
||||
MSR_RANGE!(MSR_IA32_MC0_CTL, 0x80),
|
||||
SINGLE_MSR!(MSR_RAPL_POWER_UNIT),
|
||||
MSR_RANGE!(
|
||||
// MSR_PKGC3_IRTL
|
||||
// MSR_PKGC6_IRTL
|
||||
// MSR_PKGC7_IRTL
|
||||
MSR_PKGC3_IRTL,
|
||||
3
|
||||
),
|
||||
SINGLE_MSR!(MSR_PKG_POWER_LIMIT),
|
||||
SINGLE_MSR!(MSR_PKG_ENERGY_STATUS),
|
||||
SINGLE_MSR!(MSR_PKG_PERF_STATUS),
|
||||
SINGLE_MSR!(MSR_PKG_POWER_INFO),
|
||||
SINGLE_MSR!(MSR_DRAM_POWER_LIMIT),
|
||||
SINGLE_MSR!(MSR_DRAM_ENERGY_STATUS),
|
||||
SINGLE_MSR!(MSR_DRAM_PERF_STATUS),
|
||||
SINGLE_MSR!(MSR_DRAM_POWER_INFO),
|
||||
SINGLE_MSR!(MSR_CONFIG_TDP_NOMINAL),
|
||||
SINGLE_MSR!(MSR_CONFIG_TDP_LEVEL_1),
|
||||
SINGLE_MSR!(MSR_CONFIG_TDP_LEVEL_2),
|
||||
SINGLE_MSR!(MSR_CONFIG_TDP_CONTROL),
|
||||
SINGLE_MSR!(MSR_TURBO_ACTIVATION_RATIO),
|
||||
SINGLE_MSR!(MSR_IA32_TSCDEADLINE),
|
||||
MSR_RANGE!(APIC_BASE_MSR, APIC_MSR_INDEXES),
|
||||
SINGLE_MSR!(MSR_IA32_BNDCFGS),
|
||||
SINGLE_MSR!(MSR_KVM_WALL_CLOCK_NEW),
|
||||
SINGLE_MSR!(MSR_KVM_SYSTEM_TIME_NEW),
|
||||
SINGLE_MSR!(MSR_KVM_ASYNC_PF_EN),
|
||||
SINGLE_MSR!(MSR_KVM_STEAL_TIME),
|
||||
SINGLE_MSR!(MSR_KVM_PV_EOI_EN),
|
||||
SINGLE_MSR!(MSR_EFER),
|
||||
SINGLE_MSR!(MSR_STAR),
|
||||
SINGLE_MSR!(MSR_LSTAR),
|
||||
SINGLE_MSR!(MSR_CSTAR),
|
||||
SINGLE_MSR!(MSR_SYSCALL_MASK),
|
||||
SINGLE_MSR!(MSR_FS_BASE),
|
||||
SINGLE_MSR!(MSR_GS_BASE),
|
||||
SINGLE_MSR!(MSR_KERNEL_GS_BASE),
|
||||
SINGLE_MSR!(MSR_TSC_AUX),
|
||||
];
|
||||
|
||||
/// Specifies whether a particular MSR should be included in vcpu serialization.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the MSR that is checked whether it's needed for serialization.
|
||||
pub fn msr_should_serialize(index: u32) -> bool {
|
||||
// Blacklisted MSRs not exported by Linux: IA32_FEATURE_CONTROL and IA32_MCG_CTL
|
||||
if index == MSR_IA32_FEATURE_CONTROL || index == MSR_IA32_MCG_CTL {
|
||||
return false;
|
||||
};
|
||||
WHITELISTED_MSR_RANGES
|
||||
.iter()
|
||||
.any(|range| range.contains(index))
|
||||
}
|
||||
|
||||
/// Returns the list of supported, serializable MSRs.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `kvm_fd` - Structure that holds the KVM's fd.
|
||||
pub fn supported_guest_msrs(kvm_fd: &Kvm) -> Result<MsrList> {
|
||||
let mut msr_list = kvm_fd
|
||||
.get_msr_index_list()
|
||||
.map_err(Error::GetSupportedModelSpecificRegisters)?;
|
||||
|
||||
msr_list.retain(|msr_index| msr_should_serialize(*msr_index));
|
||||
|
||||
Ok(msr_list)
|
||||
}
|
||||
|
||||
/// Base MSR for APIC
|
||||
pub const APIC_BASE_MSR: u32 = 0x800;
|
||||
|
||||
/// Number of APIC MSR indexes
|
||||
pub const APIC_MSR_INDEXES: u32 = 0x400;
|
||||
|
||||
/// Custom MSRs fall in the range 0x4b564d00-0x4b564dff
|
||||
pub const MSR_KVM_WALL_CLOCK_NEW: u32 = 0x4b56_4d00;
|
||||
pub const MSR_KVM_SYSTEM_TIME_NEW: u32 = 0x4b56_4d01;
|
||||
pub const MSR_KVM_ASYNC_PF_EN: u32 = 0x4b56_4d02;
|
||||
pub const MSR_KVM_STEAL_TIME: u32 = 0x4b56_4d03;
|
||||
pub const MSR_KVM_PV_EOI_EN: u32 = 0x4b56_4d04;
|
||||
|
||||
pub const MSR_EFER: u32 = 3221225600;
|
||||
pub const MSR_STAR: u32 = 3221225601;
|
||||
pub const MSR_LSTAR: u32 = 3221225602;
|
||||
pub const MSR_CSTAR: u32 = 3221225603;
|
||||
pub const MSR_SYSCALL_MASK: u32 = 3221225604;
|
||||
pub const MSR_FS_BASE: u32 = 3221225728;
|
||||
pub const MSR_GS_BASE: u32 = 3221225729;
|
||||
pub const MSR_KERNEL_GS_BASE: u32 = 3221225730;
|
||||
pub const MSR_TSC_AUX: u32 = 3221225731;
|
||||
pub const _EFER_SCE: u32 = 0;
|
||||
pub const _EFER_LME: u32 = 8;
|
||||
pub const _EFER_LMA: u32 = 10;
|
||||
pub const _EFER_NX: u32 = 11;
|
||||
pub const _EFER_SVME: u32 = 12;
|
||||
pub const _EFER_LMSLE: u32 = 13;
|
||||
pub const _EFER_FFXSR: u32 = 14;
|
||||
pub const EFER_SCE: u32 = 1;
|
||||
pub const EFER_LME: u32 = 256;
|
||||
pub const EFER_LMA: u32 = 1024;
|
||||
pub const EFER_NX: u32 = 2048;
|
||||
pub const EFER_SVME: u32 = 4096;
|
||||
pub const EFER_LMSLE: u32 = 8192;
|
||||
pub const EFER_FFXSR: u32 = 16384;
|
||||
pub const MSR_IA32_SPEC_CTRL: u32 = 72;
|
||||
pub const SPEC_CTRL_IBRS: u32 = 1;
|
||||
pub const SPEC_CTRL_STIBP: u32 = 2;
|
||||
pub const SPEC_CTRL_SSBD_SHIFT: u32 = 2;
|
||||
pub const SPEC_CTRL_SSBD: u32 = 4;
|
||||
pub const MSR_IA32_PRED_CMD: u32 = 73;
|
||||
pub const PRED_CMD_IBPB: u32 = 1;
|
||||
pub const MSR_IA32_PERFCTR0: u32 = 193;
|
||||
pub const MSR_IA32_PERFCTR1: u32 = 194;
|
||||
pub const MSR_FSB_FREQ: u32 = 205;
|
||||
pub const MSR_PLATFORM_INFO: u32 = 206;
|
||||
pub const MSR_NHM_SNB_PKG_CST_CFG_CTL: u32 = 226;
|
||||
pub const NHM_C3_AUTO_DEMOTE: u32 = 33554432;
|
||||
pub const NHM_C1_AUTO_DEMOTE: u32 = 67108864;
|
||||
pub const ATM_LNC_C6_AUTO_DEMOTE: u32 = 33554432;
|
||||
pub const SNB_C1_AUTO_UNDEMOTE: u32 = 134217728;
|
||||
pub const SNB_C3_AUTO_UNDEMOTE: u32 = 268435456;
|
||||
pub const MSR_MTRRcap: u32 = 254;
|
||||
pub const MSR_IA32_ARCH_CAPABILITIES: u32 = 266;
|
||||
pub const ARCH_CAP_RDCL_NO: u32 = 1;
|
||||
pub const ARCH_CAP_IBRS_ALL: u32 = 2;
|
||||
pub const ARCH_CAP_SKIP_VMENTRY_L1DFLUSH: u32 = 8;
|
||||
pub const ARCH_CAP_SSB_NO: u32 = 16;
|
||||
pub const MSR_IA32_FLUSH_CMD: u32 = 267;
|
||||
pub const L1D_FLUSH: u32 = 1;
|
||||
pub const MSR_PKG_CST_CONFIG_CONTROL: u32 = 226;
|
||||
pub const MSR_IA32_BBL_CR_CTL: u32 = 281;
|
||||
pub const MSR_IA32_BBL_CR_CTL3: u32 = 286;
|
||||
pub const MSR_IA32_SYSENTER_CS: u32 = 372;
|
||||
pub const MSR_IA32_SYSENTER_ESP: u32 = 373;
|
||||
pub const MSR_IA32_SYSENTER_EIP: u32 = 374;
|
||||
pub const MSR_IA32_MCG_CAP: u32 = 377;
|
||||
pub const MSR_IA32_MCG_STATUS: u32 = 378;
|
||||
pub const MSR_IA32_MCG_CTL: u32 = 379;
|
||||
pub const MSR_IA32_MCG_EXT_CTL: u32 = 1232;
|
||||
pub const MSR_OFFCORE_RSP_0: u32 = 422;
|
||||
pub const MSR_OFFCORE_RSP_1: u32 = 423;
|
||||
pub const MSR_TURBO_RATIO_LIMIT: u32 = 429;
|
||||
pub const MSR_TURBO_RATIO_LIMIT1: u32 = 430;
|
||||
pub const MSR_TURBO_RATIO_LIMIT2: u32 = 431;
|
||||
pub const MSR_LBR_SELECT: u32 = 456;
|
||||
pub const MSR_LBR_TOS: u32 = 457;
|
||||
pub const MSR_LBR_NHM_FROM: u32 = 1664;
|
||||
pub const MSR_LBR_NHM_TO: u32 = 1728;
|
||||
pub const MSR_LBR_CORE_FROM: u32 = 64;
|
||||
pub const MSR_LBR_CORE_TO: u32 = 96;
|
||||
pub const MSR_LBR_INFO_0: u32 = 3520;
|
||||
pub const LBR_INFO_CYCLES: u32 = 65535;
|
||||
pub const MSR_IA32_PEBS_ENABLE: u32 = 1009;
|
||||
pub const MSR_IA32_DS_AREA: u32 = 1536;
|
||||
pub const MSR_IA32_PERF_CAPABILITIES: u32 = 837;
|
||||
pub const MSR_PEBS_LD_LAT_THRESHOLD: u32 = 1014;
|
||||
pub const MSR_IA32_RTIT_CTL: u32 = 1392;
|
||||
pub const MSR_IA32_RTIT_STATUS: u32 = 1393;
|
||||
pub const MSR_IA32_RTIT_ADDR0_A: u32 = 1408;
|
||||
pub const MSR_IA32_RTIT_ADDR0_B: u32 = 1409;
|
||||
pub const MSR_IA32_RTIT_ADDR1_A: u32 = 1410;
|
||||
pub const MSR_IA32_RTIT_ADDR1_B: u32 = 1411;
|
||||
pub const MSR_IA32_RTIT_ADDR2_A: u32 = 1412;
|
||||
pub const MSR_IA32_RTIT_ADDR2_B: u32 = 1413;
|
||||
pub const MSR_IA32_RTIT_ADDR3_A: u32 = 1414;
|
||||
pub const MSR_IA32_RTIT_ADDR3_B: u32 = 1415;
|
||||
pub const MSR_IA32_RTIT_CR3_MATCH: u32 = 1394;
|
||||
pub const MSR_IA32_RTIT_OUTPUT_BASE: u32 = 1376;
|
||||
pub const MSR_IA32_RTIT_OUTPUT_MASK: u32 = 1377;
|
||||
pub const MSR_MTRRfix64K_00000: u32 = 592;
|
||||
pub const MSR_MTRRfix16K_80000: u32 = 600;
|
||||
pub const MSR_MTRRfix16K_A0000: u32 = 601;
|
||||
pub const MSR_MTRRfix4K_C0000: u32 = 616;
|
||||
pub const MSR_MTRRfix4K_C8000: u32 = 617;
|
||||
pub const MSR_MTRRfix4K_D0000: u32 = 618;
|
||||
pub const MSR_MTRRfix4K_D8000: u32 = 619;
|
||||
pub const MSR_MTRRfix4K_E0000: u32 = 620;
|
||||
pub const MSR_MTRRfix4K_E8000: u32 = 621;
|
||||
pub const MSR_MTRRfix4K_F0000: u32 = 622;
|
||||
pub const MSR_MTRRfix4K_F8000: u32 = 623;
|
||||
pub const MSR_MTRRdefType: u32 = 767;
|
||||
pub const MSR_IA32_CR_PAT: u32 = 631;
|
||||
pub const MSR_IA32_DEBUGCTLMSR: u32 = 473;
|
||||
pub const MSR_IA32_LASTBRANCHFROMIP: u32 = 475;
|
||||
pub const MSR_IA32_LASTBRANCHTOIP: u32 = 476;
|
||||
pub const MSR_IA32_LASTINTFROMIP: u32 = 477;
|
||||
pub const MSR_IA32_LASTINTTOIP: u32 = 478;
|
||||
pub const DEBUGCTLMSR_LBR: u32 = 1;
|
||||
pub const DEBUGCTLMSR_BTF_SHIFT: u32 = 1;
|
||||
pub const DEBUGCTLMSR_BTF: u32 = 2;
|
||||
pub const DEBUGCTLMSR_TR: u32 = 64;
|
||||
pub const DEBUGCTLMSR_BTS: u32 = 128;
|
||||
pub const DEBUGCTLMSR_BTINT: u32 = 256;
|
||||
pub const DEBUGCTLMSR_BTS_OFF_OS: u32 = 512;
|
||||
pub const DEBUGCTLMSR_BTS_OFF_USR: u32 = 1024;
|
||||
pub const DEBUGCTLMSR_FREEZE_LBRS_ON_PMI: u32 = 2048;
|
||||
pub const MSR_PEBS_FRONTEND: u32 = 1015;
|
||||
pub const MSR_IA32_POWER_CTL: u32 = 508;
|
||||
pub const MSR_IA32_MC0_CTL: u32 = 1024;
|
||||
pub const MSR_IA32_MC0_STATUS: u32 = 1025;
|
||||
pub const MSR_IA32_MC0_ADDR: u32 = 1026;
|
||||
pub const MSR_IA32_MC0_MISC: u32 = 1027;
|
||||
pub const MSR_PKG_C3_RESIDENCY: u32 = 1016;
|
||||
pub const MSR_PKG_C6_RESIDENCY: u32 = 1017;
|
||||
pub const MSR_PKG_C7_RESIDENCY: u32 = 1018;
|
||||
pub const MSR_CORE_C3_RESIDENCY: u32 = 1020;
|
||||
pub const MSR_CORE_C6_RESIDENCY: u32 = 1021;
|
||||
pub const MSR_CORE_C7_RESIDENCY: u32 = 1022;
|
||||
pub const MSR_KNL_CORE_C6_RESIDENCY: u32 = 1023;
|
||||
pub const MSR_PKG_C2_RESIDENCY: u32 = 1549;
|
||||
pub const MSR_PKG_C8_RESIDENCY: u32 = 1584;
|
||||
pub const MSR_PKG_C9_RESIDENCY: u32 = 1585;
|
||||
pub const MSR_PKG_C10_RESIDENCY: u32 = 1586;
|
||||
pub const MSR_PKGC3_IRTL: u32 = 1546;
|
||||
pub const MSR_PKGC6_IRTL: u32 = 1547;
|
||||
pub const MSR_PKGC7_IRTL: u32 = 1548;
|
||||
pub const MSR_PKGC8_IRTL: u32 = 1587;
|
||||
pub const MSR_PKGC9_IRTL: u32 = 1588;
|
||||
pub const MSR_PKGC10_IRTL: u32 = 1589;
|
||||
pub const MSR_RAPL_POWER_UNIT: u32 = 1542;
|
||||
pub const MSR_PKG_POWER_LIMIT: u32 = 1552;
|
||||
pub const MSR_PKG_ENERGY_STATUS: u32 = 1553;
|
||||
pub const MSR_PKG_PERF_STATUS: u32 = 1555;
|
||||
pub const MSR_PKG_POWER_INFO: u32 = 1556;
|
||||
pub const MSR_DRAM_POWER_LIMIT: u32 = 1560;
|
||||
pub const MSR_DRAM_ENERGY_STATUS: u32 = 1561;
|
||||
pub const MSR_DRAM_PERF_STATUS: u32 = 1563;
|
||||
pub const MSR_DRAM_POWER_INFO: u32 = 1564;
|
||||
pub const MSR_PP0_POWER_LIMIT: u32 = 1592;
|
||||
pub const MSR_PP0_ENERGY_STATUS: u32 = 1593;
|
||||
pub const MSR_PP0_POLICY: u32 = 1594;
|
||||
pub const MSR_PP0_PERF_STATUS: u32 = 1595;
|
||||
pub const MSR_PP1_POWER_LIMIT: u32 = 1600;
|
||||
pub const MSR_PP1_ENERGY_STATUS: u32 = 1601;
|
||||
pub const MSR_PP1_POLICY: u32 = 1602;
|
||||
pub const MSR_CONFIG_TDP_NOMINAL: u32 = 1608;
|
||||
pub const MSR_CONFIG_TDP_LEVEL_1: u32 = 1609;
|
||||
pub const MSR_CONFIG_TDP_LEVEL_2: u32 = 1610;
|
||||
pub const MSR_CONFIG_TDP_CONTROL: u32 = 1611;
|
||||
pub const MSR_TURBO_ACTIVATION_RATIO: u32 = 1612;
|
||||
pub const MSR_PLATFORM_ENERGY_STATUS: u32 = 1613;
|
||||
pub const MSR_PKG_WEIGHTED_CORE_C0_RES: u32 = 1624;
|
||||
pub const MSR_PKG_ANY_CORE_C0_RES: u32 = 1625;
|
||||
pub const MSR_PKG_ANY_GFXE_C0_RES: u32 = 1626;
|
||||
pub const MSR_PKG_BOTH_CORE_GFXE_C0_RES: u32 = 1627;
|
||||
pub const MSR_CORE_C1_RES: u32 = 1632;
|
||||
pub const MSR_CC6_DEMOTION_POLICY_CONFIG: u32 = 1640;
|
||||
pub const MSR_MC6_DEMOTION_POLICY_CONFIG: u32 = 1641;
|
||||
pub const MSR_CORE_PERF_LIMIT_REASONS: u32 = 1680;
|
||||
pub const MSR_GFX_PERF_LIMIT_REASONS: u32 = 1712;
|
||||
pub const MSR_RING_PERF_LIMIT_REASONS: u32 = 1713;
|
||||
pub const MSR_PPERF: u32 = 1614;
|
||||
pub const MSR_PERF_LIMIT_REASONS: u32 = 1615;
|
||||
pub const MSR_PM_ENABLE: u32 = 1904;
|
||||
pub const MSR_HWP_CAPABILITIES: u32 = 1905;
|
||||
pub const MSR_HWP_REQUEST_PKG: u32 = 1906;
|
||||
pub const MSR_HWP_INTERRUPT: u32 = 1907;
|
||||
pub const MSR_HWP_REQUEST: u32 = 1908;
|
||||
pub const MSR_HWP_STATUS: u32 = 1911;
|
||||
pub const HWP_BASE_BIT: u32 = 128;
|
||||
pub const HWP_NOTIFICATIONS_BIT: u32 = 256;
|
||||
pub const HWP_ACTIVITY_WINDOW_BIT: u32 = 512;
|
||||
pub const HWP_ENERGY_PERF_PREFERENCE_BIT: u32 = 1024;
|
||||
pub const HWP_PACKAGE_LEVEL_REQUEST_BIT: u32 = 2048;
|
||||
pub const MSR_AMD64_MC0_MASK: u32 = 3221291076;
|
||||
pub const MSR_IA32_MC0_CTL2: u32 = 640;
|
||||
pub const MSR_P6_PERFCTR0: u32 = 193;
|
||||
pub const MSR_P6_PERFCTR1: u32 = 194;
|
||||
pub const MSR_P6_EVNTSEL0: u32 = 390;
|
||||
pub const MSR_P6_EVNTSEL1: u32 = 391;
|
||||
pub const MSR_KNC_PERFCTR0: u32 = 32;
|
||||
pub const MSR_KNC_PERFCTR1: u32 = 33;
|
||||
pub const MSR_KNC_EVNTSEL0: u32 = 40;
|
||||
pub const MSR_KNC_EVNTSEL1: u32 = 41;
|
||||
pub const MSR_IA32_PMC0: u32 = 1217;
|
||||
pub const MSR_AMD64_PATCH_LEVEL: u32 = 139;
|
||||
pub const MSR_AMD64_TSC_RATIO: u32 = 3221225732;
|
||||
pub const MSR_AMD64_NB_CFG: u32 = 3221291039;
|
||||
pub const MSR_AMD64_PATCH_LOADER: u32 = 3221291040;
|
||||
pub const MSR_AMD64_OSVW_ID_LENGTH: u32 = 3221291328;
|
||||
pub const MSR_AMD64_OSVW_STATUS: u32 = 3221291329;
|
||||
pub const MSR_AMD64_LS_CFG: u32 = 3221295136;
|
||||
pub const MSR_AMD64_DC_CFG: u32 = 3221295138;
|
||||
pub const MSR_AMD64_BU_CFG2: u32 = 3221295146;
|
||||
pub const MSR_AMD64_IBSFETCHCTL: u32 = 3221295152;
|
||||
pub const MSR_AMD64_IBSFETCHLINAD: u32 = 3221295153;
|
||||
pub const MSR_AMD64_IBSFETCHPHYSAD: u32 = 3221295154;
|
||||
pub const MSR_AMD64_IBSFETCH_REG_COUNT: u32 = 3;
|
||||
pub const MSR_AMD64_IBSFETCH_REG_MASK: u32 = 7;
|
||||
pub const MSR_AMD64_IBSOPCTL: u32 = 3221295155;
|
||||
pub const MSR_AMD64_IBSOPRIP: u32 = 3221295156;
|
||||
pub const MSR_AMD64_IBSOPDATA: u32 = 3221295157;
|
||||
pub const MSR_AMD64_IBSOPDATA2: u32 = 3221295158;
|
||||
pub const MSR_AMD64_IBSOPDATA3: u32 = 3221295159;
|
||||
pub const MSR_AMD64_IBSDCLINAD: u32 = 3221295160;
|
||||
pub const MSR_AMD64_IBSDCPHYSAD: u32 = 3221295161;
|
||||
pub const MSR_AMD64_IBSOP_REG_COUNT: u32 = 7;
|
||||
pub const MSR_AMD64_IBSOP_REG_MASK: u32 = 127;
|
||||
pub const MSR_AMD64_IBSCTL: u32 = 3221295162;
|
||||
pub const MSR_AMD64_IBSBRTARGET: u32 = 3221295163;
|
||||
pub const MSR_AMD64_IBSOPDATA4: u32 = 3221295165;
|
||||
pub const MSR_AMD64_IBS_REG_COUNT_MAX: u32 = 8;
|
||||
pub const MSR_AMD64_VIRT_SPEC_CTRL: u32 = 3221291295;
|
||||
pub const MSR_F17H_IRPERF: u32 = 3221225705;
|
||||
pub const MSR_F16H_L2I_PERF_CTL: u32 = 3221291568;
|
||||
pub const MSR_F16H_L2I_PERF_CTR: u32 = 3221291569;
|
||||
pub const MSR_F16H_DR1_ADDR_MASK: u32 = 3221295129;
|
||||
pub const MSR_F16H_DR2_ADDR_MASK: u32 = 3221295130;
|
||||
pub const MSR_F16H_DR3_ADDR_MASK: u32 = 3221295131;
|
||||
pub const MSR_F16H_DR0_ADDR_MASK: u32 = 3221295143;
|
||||
pub const MSR_F15H_PERF_CTL: u32 = 3221291520;
|
||||
pub const MSR_F15H_PERF_CTR: u32 = 3221291521;
|
||||
pub const MSR_F15H_NB_PERF_CTL: u32 = 3221291584;
|
||||
pub const MSR_F15H_NB_PERF_CTR: u32 = 3221291585;
|
||||
pub const MSR_F15H_PTSC: u32 = 3221291648;
|
||||
pub const MSR_F15H_IC_CFG: u32 = 3221295137;
|
||||
pub const MSR_FAM10H_MMIO_CONF_BASE: u32 = 3221291096;
|
||||
pub const FAM10H_MMIO_CONF_ENABLE: u32 = 1;
|
||||
pub const FAM10H_MMIO_CONF_BUSRANGE_MASK: u32 = 15;
|
||||
pub const FAM10H_MMIO_CONF_BUSRANGE_SHIFT: u32 = 2;
|
||||
pub const FAM10H_MMIO_CONF_BASE_MASK: u32 = 268435455;
|
||||
pub const FAM10H_MMIO_CONF_BASE_SHIFT: u32 = 20;
|
||||
pub const MSR_FAM10H_NODE_ID: u32 = 3221295116;
|
||||
pub const MSR_F10H_DECFG: u32 = 3221295145;
|
||||
pub const MSR_F10H_DECFG_LFENCE_SERIALIZE_BIT: u32 = 1;
|
||||
pub const MSR_K8_TOP_MEM1: u32 = 3221291034;
|
||||
pub const MSR_K8_TOP_MEM2: u32 = 3221291037;
|
||||
pub const MSR_K8_SYSCFG: u32 = 3221291024;
|
||||
pub const MSR_K8_INT_PENDING_MSG: u32 = 3221291093;
|
||||
pub const K8_INTP_C1E_ACTIVE_MASK: u32 = 402653184;
|
||||
pub const MSR_K8_TSEG_ADDR: u32 = 3221291282;
|
||||
pub const MSR_K8_TSEG_MASK: u32 = 3221291283;
|
||||
pub const K8_MTRRFIXRANGE_DRAM_ENABLE: u32 = 262144;
|
||||
pub const K8_MTRRFIXRANGE_DRAM_MODIFY: u32 = 524288;
|
||||
pub const K8_MTRR_RDMEM_WRMEM_MASK: u32 = 404232216;
|
||||
pub const MSR_K7_EVNTSEL0: u32 = 3221291008;
|
||||
pub const MSR_K7_PERFCTR0: u32 = 3221291012;
|
||||
pub const MSR_K7_EVNTSEL1: u32 = 3221291009;
|
||||
pub const MSR_K7_PERFCTR1: u32 = 3221291013;
|
||||
pub const MSR_K7_EVNTSEL2: u32 = 3221291010;
|
||||
pub const MSR_K7_PERFCTR2: u32 = 3221291014;
|
||||
pub const MSR_K7_EVNTSEL3: u32 = 3221291011;
|
||||
pub const MSR_K7_PERFCTR3: u32 = 3221291015;
|
||||
pub const MSR_K7_CLK_CTL: u32 = 3221291035;
|
||||
pub const MSR_K7_HWCR: u32 = 3221291029;
|
||||
pub const MSR_K7_FID_VID_CTL: u32 = 3221291073;
|
||||
pub const MSR_K7_FID_VID_STATUS: u32 = 3221291074;
|
||||
pub const MSR_K6_WHCR: u32 = 3221225602;
|
||||
pub const MSR_K6_UWCCR: u32 = 3221225605;
|
||||
pub const MSR_K6_EPMR: u32 = 3221225606;
|
||||
pub const MSR_K6_PSOR: u32 = 3221225607;
|
||||
pub const MSR_K6_PFIR: u32 = 3221225608;
|
||||
pub const MSR_IDT_FCR1: u32 = 263;
|
||||
pub const MSR_IDT_FCR2: u32 = 264;
|
||||
pub const MSR_IDT_FCR3: u32 = 265;
|
||||
pub const MSR_IDT_FCR4: u32 = 266;
|
||||
pub const MSR_IDT_MCR0: u32 = 272;
|
||||
pub const MSR_IDT_MCR1: u32 = 273;
|
||||
pub const MSR_IDT_MCR2: u32 = 274;
|
||||
pub const MSR_IDT_MCR3: u32 = 275;
|
||||
pub const MSR_IDT_MCR4: u32 = 276;
|
||||
pub const MSR_IDT_MCR5: u32 = 277;
|
||||
pub const MSR_IDT_MCR6: u32 = 278;
|
||||
pub const MSR_IDT_MCR7: u32 = 279;
|
||||
pub const MSR_IDT_MCR_CTRL: u32 = 288;
|
||||
pub const MSR_VIA_FCR: u32 = 4359;
|
||||
pub const MSR_VIA_LONGHAUL: u32 = 4362;
|
||||
pub const MSR_VIA_RNG: u32 = 4363;
|
||||
pub const MSR_VIA_BCR2: u32 = 4423;
|
||||
pub const MSR_TMTA_LONGRUN_CTRL: u32 = 2156298256;
|
||||
pub const MSR_TMTA_LONGRUN_FLAGS: u32 = 2156298257;
|
||||
pub const MSR_TMTA_LRTI_READOUT: u32 = 2156298264;
|
||||
pub const MSR_TMTA_LRTI_VOLT_MHZ: u32 = 2156298266;
|
||||
pub const MSR_IA32_P5_MC_ADDR: u32 = 0;
|
||||
pub const MSR_IA32_P5_MC_TYPE: u32 = 1;
|
||||
pub const MSR_IA32_TSC: u32 = 16;
|
||||
pub const MSR_IA32_PLATFORM_ID: u32 = 23;
|
||||
pub const MSR_IA32_EBL_CR_POWERON: u32 = 42;
|
||||
pub const MSR_EBC_FREQUENCY_ID: u32 = 44;
|
||||
pub const MSR_SMI_COUNT: u32 = 52;
|
||||
pub const MSR_IA32_FEATURE_CONTROL: u32 = 58;
|
||||
pub const MSR_IA32_TSC_ADJUST: u32 = 59;
|
||||
pub const MSR_IA32_BNDCFGS: u32 = 3472;
|
||||
pub const MSR_IA32_BNDCFGS_RSVD: u32 = 4092;
|
||||
pub const MSR_IA32_XSS: u32 = 3488;
|
||||
pub const FEATURE_CONTROL_LOCKED: u32 = 1;
|
||||
pub const FEATURE_CONTROL_VMXON_ENABLED_INSIDE_SMX: u32 = 2;
|
||||
pub const FEATURE_CONTROL_VMXON_ENABLED_OUTSIDE_SMX: u32 = 4;
|
||||
pub const FEATURE_CONTROL_LMCE: u32 = 1048576;
|
||||
pub const MSR_IA32_APICBASE: u32 = 27;
|
||||
pub const MSR_IA32_APICBASE_BSP: u32 = 256;
|
||||
pub const MSR_IA32_APICBASE_ENABLE: u32 = 2048;
|
||||
pub const MSR_IA32_APICBASE_BASE: u32 = 4294963200;
|
||||
pub const MSR_IA32_TSCDEADLINE: u32 = 1760;
|
||||
pub const MSR_IA32_UCODE_WRITE: u32 = 121;
|
||||
pub const MSR_IA32_UCODE_REV: u32 = 139;
|
||||
pub const MSR_IA32_SMM_MONITOR_CTL: u32 = 155;
|
||||
pub const MSR_IA32_SMBASE: u32 = 158;
|
||||
pub const MSR_IA32_PERF_STATUS: u32 = 408;
|
||||
pub const MSR_IA32_PERF_CTL: u32 = 409;
|
||||
pub const INTEL_PERF_CTL_MASK: u32 = 65535;
|
||||
pub const MSR_AMD_PSTATE_DEF_BASE: u32 = 3221291108;
|
||||
pub const MSR_AMD_PERF_STATUS: u32 = 3221291107;
|
||||
pub const MSR_AMD_PERF_CTL: u32 = 3221291106;
|
||||
pub const MSR_IA32_MPERF: u32 = 231;
|
||||
pub const MSR_IA32_APERF: u32 = 232;
|
||||
pub const MSR_IA32_THERM_CONTROL: u32 = 410;
|
||||
pub const MSR_IA32_THERM_INTERRUPT: u32 = 411;
|
||||
pub const THERM_INT_HIGH_ENABLE: u32 = 1;
|
||||
pub const THERM_INT_LOW_ENABLE: u32 = 2;
|
||||
pub const THERM_INT_PLN_ENABLE: u32 = 16777216;
|
||||
pub const MSR_IA32_THERM_STATUS: u32 = 412;
|
||||
pub const THERM_STATUS_PROCHOT: u32 = 1;
|
||||
pub const THERM_STATUS_POWER_LIMIT: u32 = 1024;
|
||||
pub const MSR_THERM2_CTL: u32 = 413;
|
||||
pub const MSR_THERM2_CTL_TM_SELECT: u32 = 65536;
|
||||
pub const MSR_IA32_MISC_ENABLE: u32 = 416;
|
||||
pub const MSR_IA32_TEMPERATURE_TARGET: u32 = 418;
|
||||
pub const MSR_MISC_FEATURE_CONTROL: u32 = 420;
|
||||
pub const MSR_MISC_PWR_MGMT: u32 = 426;
|
||||
pub const MSR_IA32_ENERGY_PERF_BIAS: u32 = 432;
|
||||
pub const ENERGY_PERF_BIAS_PERFORMANCE: u32 = 0;
|
||||
pub const ENERGY_PERF_BIAS_NORMAL: u32 = 6;
|
||||
pub const ENERGY_PERF_BIAS_POWERSAVE: u32 = 15;
|
||||
pub const MSR_IA32_PACKAGE_THERM_STATUS: u32 = 433;
|
||||
pub const PACKAGE_THERM_STATUS_PROCHOT: u32 = 1;
|
||||
pub const PACKAGE_THERM_STATUS_POWER_LIMIT: u32 = 1024;
|
||||
pub const MSR_IA32_PACKAGE_THERM_INTERRUPT: u32 = 434;
|
||||
pub const PACKAGE_THERM_INT_HIGH_ENABLE: u32 = 1;
|
||||
pub const PACKAGE_THERM_INT_LOW_ENABLE: u32 = 2;
|
||||
pub const PACKAGE_THERM_INT_PLN_ENABLE: u32 = 16777216;
|
||||
pub const THERM_INT_THRESHOLD0_ENABLE: u32 = 32768;
|
||||
pub const THERM_SHIFT_THRESHOLD0: u32 = 8;
|
||||
pub const THERM_MASK_THRESHOLD0: u32 = 32512;
|
||||
pub const THERM_INT_THRESHOLD1_ENABLE: u32 = 8388608;
|
||||
pub const THERM_SHIFT_THRESHOLD1: u32 = 16;
|
||||
pub const THERM_MASK_THRESHOLD1: u32 = 8323072;
|
||||
pub const THERM_STATUS_THRESHOLD0: u32 = 64;
|
||||
pub const THERM_LOG_THRESHOLD0: u32 = 128;
|
||||
pub const THERM_STATUS_THRESHOLD1: u32 = 256;
|
||||
pub const THERM_LOG_THRESHOLD1: u32 = 512;
|
||||
pub const MSR_IA32_MISC_ENABLE_FAST_STRING_BIT: u32 = 0;
|
||||
pub const MSR_IA32_MISC_ENABLE_FAST_STRING: u32 = 1;
|
||||
pub const MSR_IA32_MISC_ENABLE_TCC_BIT: u32 = 1;
|
||||
pub const MSR_IA32_MISC_ENABLE_TCC: u32 = 2;
|
||||
pub const MSR_IA32_MISC_ENABLE_EMON_BIT: u32 = 7;
|
||||
pub const MSR_IA32_MISC_ENABLE_EMON: u32 = 128;
|
||||
pub const MSR_IA32_MISC_ENABLE_BTS_UNAVAIL_BIT: u32 = 11;
|
||||
pub const MSR_IA32_MISC_ENABLE_BTS_UNAVAIL: u32 = 2048;
|
||||
pub const MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL_BIT: u32 = 12;
|
||||
pub const MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL: u32 = 4096;
|
||||
pub const MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP_BIT: u32 = 16;
|
||||
pub const MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP: u32 = 65536;
|
||||
pub const MSR_IA32_MISC_ENABLE_MWAIT_BIT: u32 = 18;
|
||||
pub const MSR_IA32_MISC_ENABLE_MWAIT: u32 = 262144;
|
||||
pub const MSR_IA32_MISC_ENABLE_LIMIT_CPUID_BIT: u32 = 22;
|
||||
pub const MSR_IA32_MISC_ENABLE_LIMIT_CPUID: u32 = 4194304;
|
||||
pub const MSR_IA32_MISC_ENABLE_XTPR_DISABLE_BIT: u32 = 23;
|
||||
pub const MSR_IA32_MISC_ENABLE_XTPR_DISABLE: u32 = 8388608;
|
||||
pub const MSR_IA32_MISC_ENABLE_XD_DISABLE_BIT: u32 = 34;
|
||||
pub const MSR_IA32_MISC_ENABLE_XD_DISABLE: u64 = 17179869184;
|
||||
pub const MSR_IA32_MISC_ENABLE_X87_COMPAT_BIT: u32 = 2;
|
||||
pub const MSR_IA32_MISC_ENABLE_X87_COMPAT: u32 = 4;
|
||||
pub const MSR_IA32_MISC_ENABLE_TM1_BIT: u32 = 3;
|
||||
pub const MSR_IA32_MISC_ENABLE_TM1: u32 = 8;
|
||||
pub const MSR_IA32_MISC_ENABLE_SPLIT_LOCK_DISABLE_BIT: u32 = 4;
|
||||
pub const MSR_IA32_MISC_ENABLE_SPLIT_LOCK_DISABLE: u32 = 16;
|
||||
pub const MSR_IA32_MISC_ENABLE_L3CACHE_DISABLE_BIT: u32 = 6;
|
||||
pub const MSR_IA32_MISC_ENABLE_L3CACHE_DISABLE: u32 = 64;
|
||||
pub const MSR_IA32_MISC_ENABLE_SUPPRESS_LOCK_BIT: u32 = 8;
|
||||
pub const MSR_IA32_MISC_ENABLE_SUPPRESS_LOCK: u32 = 256;
|
||||
pub const MSR_IA32_MISC_ENABLE_PREFETCH_DISABLE_BIT: u32 = 9;
|
||||
pub const MSR_IA32_MISC_ENABLE_PREFETCH_DISABLE: u32 = 512;
|
||||
pub const MSR_IA32_MISC_ENABLE_FERR_BIT: u32 = 10;
|
||||
pub const MSR_IA32_MISC_ENABLE_FERR: u32 = 1024;
|
||||
pub const MSR_IA32_MISC_ENABLE_FERR_MULTIPLEX_BIT: u32 = 10;
|
||||
pub const MSR_IA32_MISC_ENABLE_FERR_MULTIPLEX: u32 = 1024;
|
||||
pub const MSR_IA32_MISC_ENABLE_TM2_BIT: u32 = 13;
|
||||
pub const MSR_IA32_MISC_ENABLE_TM2: u32 = 8192;
|
||||
pub const MSR_IA32_MISC_ENABLE_ADJ_PREF_DISABLE_BIT: u32 = 19;
|
||||
pub const MSR_IA32_MISC_ENABLE_ADJ_PREF_DISABLE: u32 = 524288;
|
||||
pub const MSR_IA32_MISC_ENABLE_SPEEDSTEP_LOCK_BIT: u32 = 20;
|
||||
pub const MSR_IA32_MISC_ENABLE_SPEEDSTEP_LOCK: u32 = 1048576;
|
||||
pub const MSR_IA32_MISC_ENABLE_L1D_CONTEXT_BIT: u32 = 24;
|
||||
pub const MSR_IA32_MISC_ENABLE_L1D_CONTEXT: u32 = 16777216;
|
||||
pub const MSR_IA32_MISC_ENABLE_DCU_PREF_DISABLE_BIT: u32 = 37;
|
||||
pub const MSR_IA32_MISC_ENABLE_DCU_PREF_DISABLE: u64 = 137438953472;
|
||||
pub const MSR_IA32_MISC_ENABLE_TURBO_DISABLE_BIT: u32 = 38;
|
||||
pub const MSR_IA32_MISC_ENABLE_TURBO_DISABLE: u64 = 274877906944;
|
||||
pub const MSR_IA32_MISC_ENABLE_IP_PREF_DISABLE_BIT: u32 = 39;
|
||||
pub const MSR_IA32_MISC_ENABLE_IP_PREF_DISABLE: u64 = 549755813888;
|
||||
pub const MSR_IA32_TSC_DEADLINE: u32 = 1760;
|
||||
pub const MSR_TSX_FORCE_ABORT: u32 = 271;
|
||||
pub const MSR_TFA_RTM_FORCE_ABORT_BIT: u32 = 0;
|
||||
pub const MSR_IA32_MCG_EAX: u32 = 384;
|
||||
pub const MSR_IA32_MCG_EBX: u32 = 385;
|
||||
pub const MSR_IA32_MCG_ECX: u32 = 386;
|
||||
pub const MSR_IA32_MCG_EDX: u32 = 387;
|
||||
pub const MSR_IA32_MCG_ESI: u32 = 388;
|
||||
pub const MSR_IA32_MCG_EDI: u32 = 389;
|
||||
pub const MSR_IA32_MCG_EBP: u32 = 390;
|
||||
pub const MSR_IA32_MCG_ESP: u32 = 391;
|
||||
pub const MSR_IA32_MCG_EFLAGS: u32 = 392;
|
||||
pub const MSR_IA32_MCG_EIP: u32 = 393;
|
||||
pub const MSR_IA32_MCG_RESERVED: u32 = 394;
|
||||
pub const MSR_P4_BPU_PERFCTR0: u32 = 768;
|
||||
pub const MSR_P4_BPU_PERFCTR1: u32 = 769;
|
||||
pub const MSR_P4_BPU_PERFCTR2: u32 = 770;
|
||||
pub const MSR_P4_BPU_PERFCTR3: u32 = 771;
|
||||
pub const MSR_P4_MS_PERFCTR0: u32 = 772;
|
||||
pub const MSR_P4_MS_PERFCTR1: u32 = 773;
|
||||
pub const MSR_P4_MS_PERFCTR2: u32 = 774;
|
||||
pub const MSR_P4_MS_PERFCTR3: u32 = 775;
|
||||
pub const MSR_P4_FLAME_PERFCTR0: u32 = 776;
|
||||
pub const MSR_P4_FLAME_PERFCTR1: u32 = 777;
|
||||
pub const MSR_P4_FLAME_PERFCTR2: u32 = 778;
|
||||
pub const MSR_P4_FLAME_PERFCTR3: u32 = 779;
|
||||
pub const MSR_P4_IQ_PERFCTR0: u32 = 780;
|
||||
pub const MSR_P4_IQ_PERFCTR1: u32 = 781;
|
||||
pub const MSR_P4_IQ_PERFCTR2: u32 = 782;
|
||||
pub const MSR_P4_IQ_PERFCTR3: u32 = 783;
|
||||
pub const MSR_P4_IQ_PERFCTR4: u32 = 784;
|
||||
pub const MSR_P4_IQ_PERFCTR5: u32 = 785;
|
||||
pub const MSR_P4_BPU_CCCR0: u32 = 864;
|
||||
pub const MSR_P4_BPU_CCCR1: u32 = 865;
|
||||
pub const MSR_P4_BPU_CCCR2: u32 = 866;
|
||||
pub const MSR_P4_BPU_CCCR3: u32 = 867;
|
||||
pub const MSR_P4_MS_CCCR0: u32 = 868;
|
||||
pub const MSR_P4_MS_CCCR1: u32 = 869;
|
||||
pub const MSR_P4_MS_CCCR2: u32 = 870;
|
||||
pub const MSR_P4_MS_CCCR3: u32 = 871;
|
||||
pub const MSR_P4_FLAME_CCCR0: u32 = 872;
|
||||
pub const MSR_P4_FLAME_CCCR1: u32 = 873;
|
||||
pub const MSR_P4_FLAME_CCCR2: u32 = 874;
|
||||
pub const MSR_P4_FLAME_CCCR3: u32 = 875;
|
||||
pub const MSR_P4_IQ_CCCR0: u32 = 876;
|
||||
pub const MSR_P4_IQ_CCCR1: u32 = 877;
|
||||
pub const MSR_P4_IQ_CCCR2: u32 = 878;
|
||||
pub const MSR_P4_IQ_CCCR3: u32 = 879;
|
||||
pub const MSR_P4_IQ_CCCR4: u32 = 880;
|
||||
pub const MSR_P4_IQ_CCCR5: u32 = 881;
|
||||
pub const MSR_P4_ALF_ESCR0: u32 = 970;
|
||||
pub const MSR_P4_ALF_ESCR1: u32 = 971;
|
||||
pub const MSR_P4_BPU_ESCR0: u32 = 946;
|
||||
pub const MSR_P4_BPU_ESCR1: u32 = 947;
|
||||
pub const MSR_P4_BSU_ESCR0: u32 = 928;
|
||||
pub const MSR_P4_BSU_ESCR1: u32 = 929;
|
||||
pub const MSR_P4_CRU_ESCR0: u32 = 952;
|
||||
pub const MSR_P4_CRU_ESCR1: u32 = 953;
|
||||
pub const MSR_P4_CRU_ESCR2: u32 = 972;
|
||||
pub const MSR_P4_CRU_ESCR3: u32 = 973;
|
||||
pub const MSR_P4_CRU_ESCR4: u32 = 992;
|
||||
pub const MSR_P4_CRU_ESCR5: u32 = 993;
|
||||
pub const MSR_P4_DAC_ESCR0: u32 = 936;
|
||||
pub const MSR_P4_DAC_ESCR1: u32 = 937;
|
||||
pub const MSR_P4_FIRM_ESCR0: u32 = 932;
|
||||
pub const MSR_P4_FIRM_ESCR1: u32 = 933;
|
||||
pub const MSR_P4_FLAME_ESCR0: u32 = 934;
|
||||
pub const MSR_P4_FLAME_ESCR1: u32 = 935;
|
||||
pub const MSR_P4_FSB_ESCR0: u32 = 930;
|
||||
pub const MSR_P4_FSB_ESCR1: u32 = 931;
|
||||
pub const MSR_P4_IQ_ESCR0: u32 = 954;
|
||||
pub const MSR_P4_IQ_ESCR1: u32 = 955;
|
||||
pub const MSR_P4_IS_ESCR0: u32 = 948;
|
||||
pub const MSR_P4_IS_ESCR1: u32 = 949;
|
||||
pub const MSR_P4_ITLB_ESCR0: u32 = 950;
|
||||
pub const MSR_P4_ITLB_ESCR1: u32 = 951;
|
||||
pub const MSR_P4_IX_ESCR0: u32 = 968;
|
||||
pub const MSR_P4_IX_ESCR1: u32 = 969;
|
||||
pub const MSR_P4_MOB_ESCR0: u32 = 938;
|
||||
pub const MSR_P4_MOB_ESCR1: u32 = 939;
|
||||
pub const MSR_P4_MS_ESCR0: u32 = 960;
|
||||
pub const MSR_P4_MS_ESCR1: u32 = 961;
|
||||
pub const MSR_P4_PMH_ESCR0: u32 = 940;
|
||||
pub const MSR_P4_PMH_ESCR1: u32 = 941;
|
||||
pub const MSR_P4_RAT_ESCR0: u32 = 956;
|
||||
pub const MSR_P4_RAT_ESCR1: u32 = 957;
|
||||
pub const MSR_P4_SAAT_ESCR0: u32 = 942;
|
||||
pub const MSR_P4_SAAT_ESCR1: u32 = 943;
|
||||
pub const MSR_P4_SSU_ESCR0: u32 = 958;
|
||||
pub const MSR_P4_SSU_ESCR1: u32 = 959;
|
||||
pub const MSR_P4_TBPU_ESCR0: u32 = 962;
|
||||
pub const MSR_P4_TBPU_ESCR1: u32 = 963;
|
||||
pub const MSR_P4_TC_ESCR0: u32 = 964;
|
||||
pub const MSR_P4_TC_ESCR1: u32 = 965;
|
||||
pub const MSR_P4_U2L_ESCR0: u32 = 944;
|
||||
pub const MSR_P4_U2L_ESCR1: u32 = 945;
|
||||
pub const MSR_P4_PEBS_MATRIX_VERT: u32 = 1010;
|
||||
pub const MSR_CORE_PERF_FIXED_CTR0: u32 = 777;
|
||||
pub const MSR_CORE_PERF_FIXED_CTR1: u32 = 778;
|
||||
pub const MSR_CORE_PERF_FIXED_CTR2: u32 = 779;
|
||||
pub const MSR_CORE_PERF_FIXED_CTR_CTRL: u32 = 909;
|
||||
pub const MSR_CORE_PERF_GLOBAL_STATUS: u32 = 910;
|
||||
pub const MSR_CORE_PERF_GLOBAL_CTRL: u32 = 911;
|
||||
pub const MSR_CORE_PERF_GLOBAL_OVF_CTRL: u32 = 912;
|
||||
pub const MSR_GEODE_BUSCONT_CONF0: u32 = 6400;
|
||||
pub const MSR_IA32_VMX_BASIC: u32 = 1152;
|
||||
pub const MSR_IA32_VMX_PINBASED_CTLS: u32 = 1153;
|
||||
pub const MSR_IA32_VMX_PROCBASED_CTLS: u32 = 1154;
|
||||
pub const MSR_IA32_VMX_EXIT_CTLS: u32 = 1155;
|
||||
pub const MSR_IA32_VMX_ENTRY_CTLS: u32 = 1156;
|
||||
pub const MSR_IA32_VMX_MISC: u32 = 1157;
|
||||
pub const MSR_IA32_VMX_CR0_FIXED0: u32 = 1158;
|
||||
pub const MSR_IA32_VMX_CR0_FIXED1: u32 = 1159;
|
||||
pub const MSR_IA32_VMX_CR4_FIXED0: u32 = 1160;
|
||||
pub const MSR_IA32_VMX_CR4_FIXED1: u32 = 1161;
|
||||
pub const MSR_IA32_VMX_VMCS_ENUM: u32 = 1162;
|
||||
pub const MSR_IA32_VMX_PROCBASED_CTLS2: u32 = 1163;
|
||||
pub const MSR_IA32_VMX_EPT_VPID_CAP: u32 = 1164;
|
||||
pub const MSR_IA32_VMX_TRUE_PINBASED_CTLS: u32 = 1165;
|
||||
pub const MSR_IA32_VMX_TRUE_PROCBASED_CTLS: u32 = 1166;
|
||||
pub const MSR_IA32_VMX_TRUE_EXIT_CTLS: u32 = 1167;
|
||||
pub const MSR_IA32_VMX_TRUE_ENTRY_CTLS: u32 = 1168;
|
||||
pub const MSR_IA32_VMX_VMFUNC: u32 = 1169;
|
||||
pub const VMX_BASIC_VMCS_SIZE_SHIFT: u32 = 32;
|
||||
pub const VMX_BASIC_TRUE_CTLS: u64 = 36028797018963968;
|
||||
pub const VMX_BASIC_64: u64 = 281474976710656;
|
||||
pub const VMX_BASIC_MEM_TYPE_SHIFT: u32 = 50;
|
||||
pub const VMX_BASIC_MEM_TYPE_MASK: u64 = 16888498602639360;
|
||||
pub const VMX_BASIC_MEM_TYPE_WB: u32 = 6;
|
||||
pub const VMX_BASIC_INOUT: u64 = 18014398509481984;
|
||||
pub const MSR_IA32_VMX_MISC_VMWRITE_SHADOW_RO_FIELDS: u32 = 536870912;
|
||||
pub const MSR_IA32_VMX_MISC_PREEMPTION_TIMER_SCALE: u32 = 31;
|
||||
pub const MSR_VM_CR: u32 = 3221291284;
|
||||
pub const MSR_VM_IGNNE: u32 = 3221291285;
|
||||
pub const MSR_VM_HSAVE_PA: u32 = 3221291287;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_msr_whitelist() {
|
||||
for range in WHITELISTED_MSR_RANGES.iter() {
|
||||
for msr in range.base..(range.base + range.nmsrs) {
|
||||
let should = !matches!(msr, MSR_IA32_FEATURE_CONTROL | MSR_IA32_MCG_CTL);
|
||||
assert_eq!(msr_should_serialize(msr), should);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msr_contains() {
|
||||
let msr_range_a = MSR_RANGE!(0xEA, 9);
|
||||
let msr_a = 0x8888;
|
||||
assert!(!msr_range_a.contains(msr_a));
|
||||
|
||||
let msr_range_b = MSR_RANGE!(0xCCCC, 5);
|
||||
let msr_b = 0xCCCD;
|
||||
assert!(msr_range_b.contains(msr_b));
|
||||
}
|
||||
|
||||
fn test_supported_msrs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
assert!(supported_guest_msrs(&kvm).is_ok());
|
||||
}
|
||||
}
|
||||
402
src/dragonball/src/dbs_arch/src/x86_64/regs.rs
Normal file
402
src/dragonball/src/dbs_arch/src/x86_64/regs.rs
Normal file
@@ -0,0 +1,402 @@
|
||||
// Copyright 2021-2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Constants and utilities for x86 CPU generic, system and model specific registers.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
use super::gdt::kvm_segment_from_gdt;
|
||||
use super::msr;
|
||||
|
||||
/// Non-Executable bit in EFER MSR.
|
||||
pub const EFER_NX: u64 = 0x800;
|
||||
/// Long-mode active bit in EFER MSR.
|
||||
pub const EFER_LMA: u64 = 0x400;
|
||||
/// Long-mode enable bit in EFER MSR.
|
||||
pub const EFER_LME: u64 = 0x100;
|
||||
|
||||
/// Protection mode enable bit in CR0.
|
||||
pub const X86_CR0_PE: u64 = 0x1;
|
||||
/// Paging enable bit in CR0.
|
||||
pub const X86_CR0_PG: u64 = 0x8000_0000;
|
||||
/// Physical Address Extension bit in CR4.
|
||||
pub const X86_CR4_PAE: u64 = 0x20;
|
||||
|
||||
/// Errors thrown while setting up x86_64 registers.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
GetStatusRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set base registers for this CPU.
|
||||
SetBaseRegisters(kvm_ioctls::Error),
|
||||
/// Failed to configure the FPU.
|
||||
SetFPURegisters(kvm_ioctls::Error),
|
||||
/// Setting up MSRs failed.
|
||||
SetModelSpecificRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set all MSRs.
|
||||
SetModelSpecificRegistersCount,
|
||||
/// Failed to set SREGs for this CPU.
|
||||
SetStatusRegisters(kvm_ioctls::Error),
|
||||
/// Writing the GDT to RAM failed.
|
||||
WriteGDT,
|
||||
/// Writing the IDT to RAM failed.
|
||||
WriteIDT,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Configure Floating-Point Unit (FPU) registers for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
||||
let fpu: kvm_fpu = kvm_fpu {
|
||||
fcw: 0x37f,
|
||||
mxcsr: 0x1f80,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
vcpu.set_fpu(&fpu).map_err(Error::SetFPURegisters)
|
||||
}
|
||||
|
||||
/// Configure Model Specific Registers (MSRs) for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
let entry_vec = create_msr_entries();
|
||||
let kvm_msrs =
|
||||
Msrs::from_entries(&entry_vec).map_err(|_| Error::SetModelSpecificRegistersCount)?;
|
||||
|
||||
vcpu.set_msrs(&kvm_msrs)
|
||||
.map_err(Error::SetModelSpecificRegisters)
|
||||
.and_then(|msrs_written| {
|
||||
if msrs_written as u32 != kvm_msrs.as_fam_struct_ref().nmsrs {
|
||||
Err(Error::SetModelSpecificRegistersCount)
|
||||
} else {
|
||||
Ok(msrs_written)
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure base registers for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `rsp` - Value for RSP register
|
||||
/// * `rbp` - Value for RBP register
|
||||
/// * `rsi` - Value for RSI register
|
||||
pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, rsp: u64, rbp: u64, rsi: u64) -> Result<()> {
|
||||
let regs: kvm_regs = kvm_regs {
|
||||
rflags: 0x0000_0000_0000_0002u64,
|
||||
rip: boot_ip,
|
||||
rsp,
|
||||
rbp,
|
||||
rsi,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
|
||||
/// Configures the segment registers for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `pgtable_addr` - Address of the vcpu pgtable.
|
||||
/// * `gdt_table` - Content of the global descriptor table.
|
||||
/// * `gdt_addr` - Address of the global descriptor table.
|
||||
/// * `idt_addr` - Address of the interrupt descriptor table.
|
||||
pub fn setup_sregs<M: GuestMemory>(
|
||||
mem: &M,
|
||||
vcpu: &VcpuFd,
|
||||
pgtable_addr: GuestAddress,
|
||||
gdt_table: &[u64],
|
||||
gdt_addr: u64,
|
||||
idt_addr: u64,
|
||||
) -> Result<()> {
|
||||
let mut sregs: kvm_sregs = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
configure_segments_and_sregs(mem, &mut sregs, pgtable_addr, gdt_table, gdt_addr, idt_addr)?;
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
|
||||
fn configure_segments_and_sregs<M: GuestMemory>(
|
||||
mem: &M,
|
||||
sregs: &mut kvm_sregs,
|
||||
pgtable_addr: GuestAddress,
|
||||
gdt_table: &[u64],
|
||||
gdt_addr: u64,
|
||||
idt_addr: u64,
|
||||
) -> Result<()> {
|
||||
assert!(gdt_table.len() >= 4);
|
||||
let code_seg = kvm_segment_from_gdt(gdt_table[1], 1);
|
||||
let data_seg = kvm_segment_from_gdt(gdt_table[2], 2);
|
||||
let tss_seg = kvm_segment_from_gdt(gdt_table[3], 3);
|
||||
|
||||
// Write segments
|
||||
write_gdt_table(gdt_table, gdt_addr, mem)?;
|
||||
sregs.gdt.base = gdt_addr;
|
||||
sregs.gdt.limit = std::mem::size_of_val(gdt_table) as u16 - 1;
|
||||
|
||||
write_idt_value(0, idt_addr, mem)?;
|
||||
sregs.idt.base = idt_addr;
|
||||
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
|
||||
|
||||
sregs.cs = code_seg;
|
||||
sregs.ds = data_seg;
|
||||
sregs.es = data_seg;
|
||||
sregs.fs = data_seg;
|
||||
sregs.gs = data_seg;
|
||||
sregs.ss = data_seg;
|
||||
sregs.tr = tss_seg;
|
||||
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.cr3 = pgtable_addr.raw_value();
|
||||
sregs.cr4 |= X86_CR4_PAE;
|
||||
sregs.cr0 |= X86_CR0_PG;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_gdt_table<M: GuestMemory>(gdt_table: &[u64], gdt_addr: u64, guest_mem: &M) -> Result<()> {
|
||||
let boot_gdt_addr = GuestAddress(gdt_addr);
|
||||
for (index, entry) in gdt_table.iter().enumerate() {
|
||||
let addr = guest_mem
|
||||
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||
.ok_or(Error::WriteGDT)?;
|
||||
guest_mem
|
||||
.write_obj(*entry, addr)
|
||||
.map_err(|_| Error::WriteGDT)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_idt_value<M: GuestMemory>(idt_table: u64, idt_addr: u64, guest_mem: &M) -> Result<()> {
|
||||
let boot_idt_addr = GuestAddress(idt_addr);
|
||||
guest_mem
|
||||
.write_obj(idt_table, boot_idt_addr)
|
||||
.map_err(|_| Error::WriteIDT)
|
||||
}
|
||||
|
||||
#[allow(clippy::vec_init_then_push)]
|
||||
fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_IA32_SYSENTER_CS,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_IA32_SYSENTER_ESP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_IA32_SYSENTER_EIP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// x86_64 specific msrs, we only run on x86_64 not x86.
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_STAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_CSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_KERNEL_GS_BASE,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_SYSCALL_MASK,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_LSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// end of x86_64 specific code
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_IA32_TSC,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr::MSR_IA32_MISC_ENABLE,
|
||||
data: u64::from(msr::MSR_IA32_MISC_ENABLE_FAST_STRING),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::x86_64::gdt::gdt_entry;
|
||||
use kvm_ioctls::Kvm;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
|
||||
const BOOT_GDT_OFFSET: u64 = 0x500;
|
||||
const BOOT_IDT_OFFSET: u64 = 0x520;
|
||||
const BOOT_STACK_POINTER: u64 = 0x100_0000;
|
||||
const ZERO_PAGE_START: u64 = 0x7_C000;
|
||||
const BOOT_GDT_MAX: usize = 4;
|
||||
const PML4_START: u64 = 0x9000;
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
}
|
||||
|
||||
fn read_u64(gm: &GuestMemoryMmap, offset: u64) -> u64 {
|
||||
let read_addr = GuestAddress(offset);
|
||||
gm.read_obj(read_addr).unwrap()
|
||||
}
|
||||
|
||||
fn validate_segments_and_sregs(gm: &GuestMemoryMmap, sregs: &kvm_sregs) {
|
||||
assert_eq!(0x0, read_u64(gm, BOOT_GDT_OFFSET));
|
||||
assert_eq!(0xaf_9b00_0000_ffff, read_u64(gm, BOOT_GDT_OFFSET + 8));
|
||||
assert_eq!(0xcf_9300_0000_ffff, read_u64(gm, BOOT_GDT_OFFSET + 16));
|
||||
assert_eq!(0x8f_8b00_0000_ffff, read_u64(gm, BOOT_GDT_OFFSET + 24));
|
||||
assert_eq!(0x0, read_u64(gm, BOOT_IDT_OFFSET));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xfffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xfffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert!(sregs.cr0 & X86_CR0_PE != 0);
|
||||
assert!(sregs.efer & EFER_LME != 0 && sregs.efer & EFER_LMA != 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_configure_segments_and_sregs() {
|
||||
let mut sregs: kvm_sregs = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
let gdt_table: [u64; BOOT_GDT_MAX] = [
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
];
|
||||
configure_segments_and_sregs(
|
||||
&gm,
|
||||
&mut sregs,
|
||||
GuestAddress(PML4_START),
|
||||
&gdt_table,
|
||||
BOOT_GDT_OFFSET,
|
||||
BOOT_IDT_OFFSET,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
validate_segments_and_sregs(&gm, &sregs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_fpu() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
setup_fpu(&vcpu).unwrap();
|
||||
|
||||
let expected_fpu: kvm_fpu = kvm_fpu {
|
||||
fcw: 0x37f,
|
||||
mxcsr: 0x1f80,
|
||||
..Default::default()
|
||||
};
|
||||
let actual_fpu: kvm_fpu = vcpu.get_fpu().unwrap();
|
||||
assert_eq!(expected_fpu.fcw, actual_fpu.fcw);
|
||||
// Setting the mxcsr register from kvm_fpu inside setup_fpu does not influence anything.
|
||||
// See 'kvm_arch_vcpu_ioctl_set_fpu' from arch/x86/kvm/x86.c.
|
||||
// The mxcsr will stay 0 and the assert below fails. Decide whether or not we should
|
||||
// remove it at all.
|
||||
// assert!(expected_fpu.mxcsr == actual_fpu.mxcsr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
fn test_setup_msrs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
setup_msrs(&vcpu).unwrap();
|
||||
|
||||
// This test will check against the last MSR entry configured (the tenth one).
|
||||
// See create_msr_entries() for details.
|
||||
let test_kvm_msrs_entry = [kvm_msr_entry {
|
||||
index: msr::MSR_IA32_MISC_ENABLE,
|
||||
..Default::default()
|
||||
}];
|
||||
let mut kvm_msrs = Msrs::from_entries(&test_kvm_msrs_entry).unwrap();
|
||||
|
||||
// kvm_ioctls::get_msrs() returns the number of msrs that it succeeded in reading.
|
||||
// We only want to read one in this test case scenario.
|
||||
let read_nmsrs = vcpu.get_msrs(&mut kvm_msrs).unwrap();
|
||||
// Validate it only read one.
|
||||
assert_eq!(read_nmsrs, 1);
|
||||
|
||||
// Official entries that were setup when we did setup_msrs. We need to assert that the
|
||||
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||
// expect.
|
||||
let entry_vec = create_msr_entries();
|
||||
assert_eq!(entry_vec[9], kvm_msrs.as_slice()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_regs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
|
||||
let expected_regs: kvm_regs = kvm_regs {
|
||||
rflags: 0x0000_0000_0000_0002u64,
|
||||
rip: 1,
|
||||
rsp: BOOT_STACK_POINTER,
|
||||
rbp: BOOT_STACK_POINTER,
|
||||
rsi: ZERO_PAGE_START,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
setup_regs(
|
||||
&vcpu,
|
||||
expected_regs.rip,
|
||||
BOOT_STACK_POINTER,
|
||||
BOOT_STACK_POINTER,
|
||||
ZERO_PAGE_START,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let actual_regs: kvm_regs = vcpu.get_regs().unwrap();
|
||||
assert_eq!(actual_regs, expected_regs);
|
||||
}
|
||||
}
|
||||
26
src/dragonball/src/dbs_boot/Cargo.toml
Normal file
26
src/dragonball/src/dbs_boot/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "dbs-boot"
|
||||
version = "0.4.0"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
description = "Traits and structs for booting sandbox"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball", "boot", "VMM"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
dbs-arch = { path = "../dbs_arch" }
|
||||
kvm-bindings = { version = "0.6.0", features = ["fam-wrappers"] }
|
||||
kvm-ioctls = "0.12.0"
|
||||
lazy_static = "1"
|
||||
libc = "0.2.39"
|
||||
thiserror = "1"
|
||||
vm-memory = "0.9.0"
|
||||
vm-fdt = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
vm-memory = { version = "0.9.0", features = ["backend-mmap"] }
|
||||
device_tree = ">=1.1.0"
|
||||
dbs-device = { path = "../dbs_device" }
|
||||
1
src/dragonball/src/dbs_boot/LICENSE
Symbolic link
1
src/dragonball/src/dbs_boot/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
24
src/dragonball/src/dbs_boot/README.md
Normal file
24
src/dragonball/src/dbs_boot/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# dbs-boot
|
||||
|
||||
## Design
|
||||
|
||||
The `dbs-boot` crate is a collection of constants, structs and utilities used to boot virtual machines.
|
||||
|
||||
## Submodule List
|
||||
|
||||
This repository contains the following submodules:
|
||||
| Name | Arch| Description |
|
||||
| --- | --- | --- |
|
||||
| [`bootparam`](src/x86_64/bootparam.rs) | x86_64 | Magic addresses externally used to lay out x86_64 VMs |
|
||||
| [fdt](src/aarch64/fdt.rs) | aarch64| Create FDT for Aarch64 systems |
|
||||
| [layout](src/x86_64/layout.rs) | x86_64 | x86_64 layout constants |
|
||||
| [layout](src/aarch64/layout.rs/) | aarch64 | aarch64 layout constants |
|
||||
| [mptable](src/x86_64/mptable.rs) | x86_64 | MP Table configurations used for defining VM boot status |
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Part of the code is derived from the [Firecracker](https://github.com/firecracker-microvm/firecracker) project.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0.
|
||||
1
src/dragonball/src/dbs_boot/THIRD-PARTY
Symbolic link
1
src/dragonball/src/dbs_boot/THIRD-PARTY
Symbolic link
@@ -0,0 +1 @@
|
||||
../../THIRD-PARTY
|
||||
608
src/dragonball/src/dbs_boot/src/aarch64/fdt.rs
Normal file
608
src/dragonball/src/dbs_boot/src/aarch64/fdt.rs
Normal file
@@ -0,0 +1,608 @@
|
||||
// Copyright 2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Create Flatten Device Tree (FDT) for ARM64 systems.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use dbs_arch::gic::its::ItsType::{self, PciMsiIts, PlatformMsiIts};
|
||||
use dbs_arch::gic::GICDevice;
|
||||
use dbs_arch::{pmu::VIRTUAL_PMU_IRQ, VpmuFeatureLevel};
|
||||
use dbs_arch::{DeviceInfoForFDT, DeviceType};
|
||||
|
||||
use vm_fdt::FdtWriter;
|
||||
use vm_memory::GuestMemoryRegion;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
use super::fdt_utils::*;
|
||||
use super::Error;
|
||||
use crate::Result;
|
||||
|
||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||
const GIC_PHANDLE: u32 = 1;
|
||||
// This is a value for uniquely identifying the FDT node containing the clock definition.
|
||||
const CLOCK_PHANDLE: u32 = 2;
|
||||
// This is a value for uniquely identifying the FDT node containing the plaform msi ITS definition.
|
||||
const GIC_PLATFORM_MSI_ITS_PHANDLE: u32 = 3;
|
||||
// This is a value for uniquely identifying the FDT node containing the pci msi ITS definition.
|
||||
const GIC_PCI_MSI_ITS_PHANDLE: u32 = 4;
|
||||
// According to the arm, gic-v3.txt document, ITS' #msi-cells is fixed at 1.
|
||||
const GIC_PLATFORM_MSI_ITS_CELLS_SIZE: u32 = 1;
|
||||
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
const SIZE_CELLS: u32 = 0x2;
|
||||
|
||||
// As per kvm tool and
|
||||
// https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
|
||||
// Look for "The 1st cell..."
|
||||
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
|
||||
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||
|
||||
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
|
||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
/// Creates the flattened device tree for this aarch64 microVM.
|
||||
pub fn create_fdt<T>(
|
||||
fdt_vm_info: FdtVmInfo,
|
||||
_fdt_numa_info: FdtNumaInfo,
|
||||
fdt_device_info: FdtDeviceInfo<T>,
|
||||
) -> Result<Vec<u8>>
|
||||
where
|
||||
T: DeviceInfoForFDT + Clone + Debug,
|
||||
{
|
||||
let mut fdt = FdtWriter::new()?;
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||
// Look for "Required nodes and properties".
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
let root_node = fdt.begin_node("")?;
|
||||
fdt.property_string("compatible", "linux,dummy-virt")?;
|
||||
// For info on #address-cells and size-cells read "Note about cells and address representation"
|
||||
// from the above mentioned txt file.
|
||||
fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
|
||||
fdt.property_u32("#size-cells", SIZE_CELLS)?;
|
||||
// This is not mandatory but we use it to point the root node to the node
|
||||
// containing description of the interrupt controller for this VM.
|
||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||
create_cpu_nodes(&mut fdt, &fdt_vm_info)?;
|
||||
create_memory_node(&mut fdt, fdt_vm_info.get_guest_memory())?;
|
||||
create_chosen_node(&mut fdt, &fdt_vm_info)?;
|
||||
create_gic_node(&mut fdt, fdt_device_info.get_irqchip())?;
|
||||
create_timer_node(&mut fdt)?;
|
||||
create_clock_node(&mut fdt)?;
|
||||
create_psci_node(&mut fdt)?;
|
||||
fdt_device_info
|
||||
.get_mmio_device_info()
|
||||
.map_or(Ok(()), |v| create_devices_node(&mut fdt, v))?;
|
||||
create_pmu_node(&mut fdt, fdt_vm_info.get_vpmu_feature())?;
|
||||
|
||||
// End Header node.
|
||||
fdt.end_node(root_node)?;
|
||||
|
||||
// Allocate another buffer so we can format and then write fdt to guest.
|
||||
let fdt_final = fdt.finish()?;
|
||||
|
||||
// Write FDT to memory.
|
||||
let fdt_address = GuestAddress(super::get_fdt_addr(fdt_vm_info.get_guest_memory()));
|
||||
fdt_vm_info
|
||||
.get_guest_memory()
|
||||
.write_slice(fdt_final.as_slice(), fdt_address)?;
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||
fn create_cpu_nodes(fdt: &mut FdtWriter, fdt_vm_info: &FdtVmInfo) -> Result<()> {
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||
let cpus_node = fdt.begin_node("cpus")?;
|
||||
// As per documentation, on ARM v8 64-bit systems value should be set to 2.
|
||||
fdt.property_u32("#address-cells", 0x02)?;
|
||||
fdt.property_u32("#size-cells", 0x0)?;
|
||||
let vcpu_mpidr = fdt_vm_info.get_vcpu_mpidr();
|
||||
let vcpu_boot_onlined = fdt_vm_info.get_boot_onlined();
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
|
||||
for (cpu_index, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
|
||||
let cpu_name = format!("cpu@{cpu_index:x}");
|
||||
let cpu_node = fdt.begin_node(&cpu_name)?;
|
||||
fdt.property_string("device_type", "cpu")?;
|
||||
fdt.property_string("compatible", "arm,arm-v8")?;
|
||||
if num_cpus > 1 {
|
||||
// This is required on armv8 64-bit. See aforementioned documentation.
|
||||
fdt.property_string("enable-method", "psci")?;
|
||||
}
|
||||
// boot-onlined attribute is used to indicate whether this cpu should be onlined at boot.
|
||||
// 0 means offline, 1 means online.
|
||||
fdt.property_u32("boot-onlined", vcpu_boot_onlined[cpu_index])?;
|
||||
// Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
|
||||
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
|
||||
fdt.property_u64("reg", mpidr & 0x7FFFFF)?;
|
||||
fdt.end_node(cpu_node)?;
|
||||
}
|
||||
fdt.end_node(cpus_node)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_memory_node<M: GuestMemory>(fdt: &mut FdtWriter, guest_mem: &M) -> Result<()> {
|
||||
// See https://github.com/torvalds/linux/blob/v5.9/Documentation/devicetree/booting-without-of.rst
|
||||
for region in guest_mem.iter() {
|
||||
let memory_name = format!("memory@{:x}", region.start_addr().raw_value());
|
||||
let mem_reg_prop = &[region.start_addr().raw_value(), region.len()];
|
||||
let memory_node = fdt.begin_node(&memory_name)?;
|
||||
fdt.property_string("device_type", "memory")?;
|
||||
fdt.property_array_u64("reg", mem_reg_prop)?;
|
||||
fdt.end_node(memory_node)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(fdt: &mut FdtWriter, fdt_vm_info: &FdtVmInfo) -> Result<()> {
|
||||
let chosen_node = fdt.begin_node("chosen")?;
|
||||
fdt.property_string("bootargs", fdt_vm_info.get_cmdline())?;
|
||||
|
||||
if let Some(initrd_config) = fdt_vm_info.get_initrd_config() {
|
||||
fdt.property_u64("linux,initrd-start", initrd_config.address.raw_value())?;
|
||||
fdt.property_u64(
|
||||
"linux,initrd-end",
|
||||
initrd_config.address.raw_value() + initrd_config.size as u64,
|
||||
)?;
|
||||
}
|
||||
|
||||
fdt.end_node(chosen_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_its_common_property(fdt: &mut FdtWriter, registers_prop: &[u64]) -> Result<()> {
|
||||
fdt.property_string("compatible", "arm,gic-v3-its")?;
|
||||
fdt.property_null("msi-controller")?;
|
||||
fdt.property_array_u64("reg", registers_prop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_its_node(
|
||||
fdt: &mut FdtWriter,
|
||||
gic_device: &dyn GICDevice,
|
||||
its_type: ItsType,
|
||||
) -> Result<()> {
|
||||
let reg = gic_device.get_its_reg_range(&its_type);
|
||||
if let Some(registers) = reg {
|
||||
// There are two types of its, pci_msi_its and platform_msi_its.
|
||||
// If this is pci_msi_its, the fdt node of its is required to have no
|
||||
// #msi-cells attribute. If this is platform_msi_its, the #msi-cells
|
||||
// attribute of its fdt node is required, and the value is 1.
|
||||
match its_type {
|
||||
PlatformMsiIts => {
|
||||
let its_node = fdt.begin_node("gic-platform-its")?;
|
||||
append_its_common_property(fdt, ®isters)?;
|
||||
fdt.property_u32("phandle", GIC_PLATFORM_MSI_ITS_PHANDLE)?;
|
||||
fdt.property_u32("#msi-cells", GIC_PLATFORM_MSI_ITS_CELLS_SIZE)?;
|
||||
fdt.end_node(its_node)?;
|
||||
}
|
||||
PciMsiIts => {
|
||||
let its_node = fdt.begin_node("gic-pci-its")?;
|
||||
append_its_common_property(fdt, ®isters)?;
|
||||
fdt.property_u32("phandle", GIC_PCI_MSI_ITS_PHANDLE)?;
|
||||
fdt.end_node(its_node)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_gic_node(fdt: &mut FdtWriter, gic_device: &dyn GICDevice) -> Result<()> {
|
||||
let gic_reg_prop = gic_device.device_properties();
|
||||
|
||||
let intc_node = fdt.begin_node("intc")?;
|
||||
fdt.property_string("compatible", gic_device.fdt_compatibility())?;
|
||||
fdt.property_null("interrupt-controller")?;
|
||||
// "interrupt-cells" field specifies the number of cells needed to encode an
|
||||
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
|
||||
// is required.
|
||||
fdt.property_u32("#interrupt-cells", 3)?;
|
||||
fdt.property_array_u64("reg", gic_reg_prop)?;
|
||||
fdt.property_u32("phandle", GIC_PHANDLE)?;
|
||||
fdt.property_u32("#address-cells", 2)?;
|
||||
fdt.property_u32("#size-cells", 2)?;
|
||||
fdt.property_null("ranges")?;
|
||||
let gic_intr_prop = &[
|
||||
GIC_FDT_IRQ_TYPE_PPI,
|
||||
gic_device.fdt_maint_irq(),
|
||||
IRQ_TYPE_LEVEL_HI,
|
||||
];
|
||||
|
||||
fdt.property_array_u32("interrupts", gic_intr_prop)?;
|
||||
create_its_node(fdt, gic_device, PlatformMsiIts)?;
|
||||
create_its_node(fdt, gic_device, PciMsiIts)?;
|
||||
fdt.end_node(intc_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_clock_node(fdt: &mut FdtWriter) -> Result<()> {
|
||||
// The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture
|
||||
// (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power
|
||||
// consumption and reduced interface complexity.
|
||||
// PCLK is the clock source and this node defines exactly the clock for the APB.
|
||||
let clock_node = fdt.begin_node("apb-pclk")?;
|
||||
fdt.property_string("compatible", "fixed-clock")?;
|
||||
fdt.property_u32("#clock-cells", 0x0)?;
|
||||
fdt.property_u32("clock-frequency", 24000000)?;
|
||||
fdt.property_string("clock-output-names", "clk24mhz")?;
|
||||
fdt.property_u32("phandle", CLOCK_PHANDLE)?;
|
||||
fdt.end_node(clock_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_timer_node(fdt: &mut FdtWriter) -> Result<()> {
|
||||
// See
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
||||
// These are fixed interrupt numbers for the timer device.
|
||||
let irqs = [13, 14, 11, 10];
|
||||
let compatible = "arm,armv8-timer";
|
||||
|
||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||
for &irq in irqs.iter() {
|
||||
timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI);
|
||||
timer_reg_cells.push(irq);
|
||||
timer_reg_cells.push(IRQ_TYPE_LEVEL_HI);
|
||||
}
|
||||
|
||||
let timer_node = fdt.begin_node("timer")?;
|
||||
fdt.property_string("compatible", compatible)?;
|
||||
fdt.property_null("always-on")?;
|
||||
fdt.property_array_u32("interrupts", &timer_reg_cells)?;
|
||||
fdt.end_node(timer_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_psci_node(fdt: &mut FdtWriter) -> Result<()> {
|
||||
let compatible = "arm,psci-0.2";
|
||||
let psci_node = fdt.begin_node("psci")?;
|
||||
fdt.property_string("compatible", compatible)?;
|
||||
// Two methods available: hvc and smc.
|
||||
// As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC.
|
||||
// So, since we are using kvm, we need to use hvc.
|
||||
fdt.property_string("method", "hvc")?;
|
||||
fdt.end_node(psci_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_virtio_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let device_reg_prop = &[dev_info.addr(), dev_info.length()];
|
||||
let irq_number = dev_info.irq().map_err(|_| Error::InvalidArguments)?;
|
||||
let irq_property = &[GIC_FDT_IRQ_TYPE_SPI, irq_number, IRQ_TYPE_EDGE_RISING];
|
||||
|
||||
let virtio_mmio_node = fdt.begin_node(&format!("virtio_mmio@{:x}", dev_info.addr()))?;
|
||||
fdt.property_string("compatible", "virtio,mmio")?;
|
||||
fdt.property_array_u64("reg", device_reg_prop)?;
|
||||
fdt.property_array_u32("interrupts", irq_property)?;
|
||||
fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
|
||||
fdt.end_node(virtio_mmio_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_serial_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let serial_reg_prop = &[dev_info.addr(), dev_info.length()];
|
||||
let irq_number = dev_info.irq().map_err(|_| Error::InvalidArguments)?;
|
||||
let irq_property = &[GIC_FDT_IRQ_TYPE_SPI, irq_number, IRQ_TYPE_EDGE_RISING];
|
||||
|
||||
let uart_node = fdt.begin_node(&format!("uart@{:x}", dev_info.addr()))?;
|
||||
fdt.property_string("compatible", "ns16550a")?;
|
||||
fdt.property_array_u64("reg", serial_reg_prop)?;
|
||||
fdt.property_u32("clocks", CLOCK_PHANDLE)?;
|
||||
fdt.property_string("clock-names", "apb_pclk")?;
|
||||
fdt.property_array_u32("interrupts", irq_property)?;
|
||||
fdt.end_node(uart_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_rtc_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let compatible = b"arm,pl031\0arm,primecell\0";
|
||||
let rtc_reg_prop = &[dev_info.addr(), dev_info.length()];
|
||||
let irq_number = dev_info.irq().map_err(|_| Error::InvalidArguments)?;
|
||||
let irq_property = &[GIC_FDT_IRQ_TYPE_SPI, irq_number, IRQ_TYPE_LEVEL_HI];
|
||||
|
||||
let rtc_node = fdt.begin_node(&format!("rtc@{:x}", dev_info.addr()))?;
|
||||
fdt.property("compatible", compatible)?;
|
||||
fdt.property_array_u64("reg", rtc_reg_prop)?;
|
||||
fdt.property_array_u32("interrupts", irq_property)?;
|
||||
fdt.property_u32("clocks", CLOCK_PHANDLE)?;
|
||||
fdt.property_string("clock-names", "apb_pclk")?;
|
||||
fdt.end_node(rtc_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut FdtWriter,
|
||||
dev_info: &HashMap<(DeviceType, String), T>,
|
||||
) -> Result<()> {
|
||||
// Serial devices need to be registered in order
|
||||
let mut ordered_serial_device: Vec<&T> = Vec::new();
|
||||
// Create one temp Vec to store all virtio devices
|
||||
let mut ordered_virtio_device: Vec<&T> = Vec::new();
|
||||
|
||||
for ((device_type, _device_id), info) in dev_info {
|
||||
match device_type {
|
||||
DeviceType::RTC => create_rtc_node(fdt, info)?,
|
||||
DeviceType::Serial => {
|
||||
ordered_serial_device.push(info);
|
||||
}
|
||||
DeviceType::Virtio(_) => {
|
||||
ordered_virtio_device.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort out serial devices by address from low to high and insert them into fdt table.
|
||||
ordered_serial_device.sort_by_key(|a| a.addr());
|
||||
for serial_device_info in ordered_serial_device.drain(..) {
|
||||
create_serial_node(fdt, serial_device_info)?;
|
||||
}
|
||||
// Sort out virtio devices by address from low to high and insert them into fdt table.
|
||||
ordered_virtio_device.sort_by_key(|a| a.addr());
|
||||
for ordered_device_info in ordered_virtio_device.drain(..) {
|
||||
create_virtio_node(fdt, ordered_device_info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_pmu_node(fdt: &mut FdtWriter, vpmu_feature: VpmuFeatureLevel) -> Result<()> {
|
||||
if vpmu_feature == VpmuFeatureLevel::Disabled {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let pmu_node = fdt.begin_node("pmu")?;
|
||||
fdt.property_string("compatible", "arm,armv8-pmuv3")?;
|
||||
let pmu_intr_prop = [GIC_FDT_IRQ_TYPE_PPI, VIRTUAL_PMU_IRQ, IRQ_TYPE_LEVEL_HI];
|
||||
fdt.property_array_u32("interrupts", &pmu_intr_prop)?;
|
||||
fdt.end_node(pmu_node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use dbs_arch::{gic::create_gic, pmu::initialize_pmu};
|
||||
use device_tree::DeviceTree;
|
||||
use kvm_bindings::{kvm_vcpu_init, KVM_ARM_VCPU_PMU_V3, KVM_ARM_VCPU_PSCI_0_2};
|
||||
use kvm_ioctls::{Kvm, VcpuFd, VmFd};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
|
||||
use super::super::tests::MMIODeviceInfo;
|
||||
use super::*;
|
||||
use crate::layout::{DRAM_MEM_MAX_SIZE, DRAM_MEM_START, FDT_MAX_SIZE};
|
||||
use crate::InitrdConfig;
|
||||
|
||||
const LEN: u64 = 4096;
|
||||
|
||||
fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize)> {
|
||||
let dram_size = min(size as u64, DRAM_MEM_MAX_SIZE) as usize;
|
||||
vec![(GuestAddress(DRAM_MEM_START), dram_size)]
|
||||
}
|
||||
|
||||
// The `load` function from the `device_tree` will mistakenly check the actual size
|
||||
// of the buffer with the allocated size. This works around that.
|
||||
fn set_size(buf: &mut [u8], pos: usize, val: usize) {
|
||||
buf[pos] = ((val >> 24) & 0xff) as u8;
|
||||
buf[pos + 1] = ((val >> 16) & 0xff) as u8;
|
||||
buf[pos + 2] = ((val >> 8) & 0xff) as u8;
|
||||
buf[pos + 3] = (val & 0xff) as u8;
|
||||
}
|
||||
|
||||
// Initialize vcpu for pmu test
|
||||
fn initialize_vcpu_with_pmu(vm: &VmFd, vcpu: &VcpuFd) -> Result<()> {
|
||||
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi)
|
||||
.expect("Cannot get preferred target");
|
||||
kvi.features[0] = 1 << KVM_ARM_VCPU_PSCI_0_2 | 1 << KVM_ARM_VCPU_PMU_V3;
|
||||
vcpu.vcpu_init(&kvi).map_err(|_| Error::InvalidArguments)?;
|
||||
initialize_pmu(vm, vcpu).map_err(|_| Error::InvalidArguments)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Create fdt dtb file
|
||||
fn create_dtb_file(name: &str, dtb: &[u8]) {
|
||||
// Control whether to create new dtb files for unit test.
|
||||
// Usage: FDT_CREATE_DTB=1 cargo test
|
||||
if env::var("FDT_CREATE_DTB").is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use this code when wanting to generate a new DTB sample.
|
||||
// Do manually check dtb files with dtc
|
||||
// See https://git.kernel.org/pub/scm/utils/dtc/dtc.git/plain/Documentation/manual.txt
|
||||
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let mut output = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path.join(format!("src/aarch64/test/{name}")))
|
||||
.unwrap();
|
||||
output
|
||||
.set_len(FDT_MAX_SIZE as u64)
|
||||
.map_err(|_| Error::InvalidArguments)
|
||||
.unwrap();
|
||||
output.write_all(dtb).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_fdt_with_devices() {
|
||||
let regions = arch_memory_regions(FDT_MAX_SIZE + 0x1000);
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
let dev_info: HashMap<(DeviceType, String), MMIODeviceInfo> = [
|
||||
(
|
||||
(DeviceType::Serial, DeviceType::Serial.to_string()),
|
||||
MMIODeviceInfo::new(0, 1),
|
||||
),
|
||||
(
|
||||
(DeviceType::Virtio(1), "virtio".to_string()),
|
||||
MMIODeviceInfo::new(LEN, 2),
|
||||
),
|
||||
(
|
||||
(DeviceType::RTC, "rtc".to_string()),
|
||||
MMIODeviceInfo::new(2 * LEN, 3),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let gic = create_gic(&vm, 1).unwrap();
|
||||
let vpmu_feature = VpmuFeatureLevel::Disabled;
|
||||
assert!(create_fdt(
|
||||
FdtVmInfo::new(
|
||||
&mem,
|
||||
"console=tty0",
|
||||
None,
|
||||
FdtVcpuInfo::new(vec![0], vec![1], vpmu_feature, false)
|
||||
),
|
||||
FdtNumaInfo::default(),
|
||||
FdtDeviceInfo::new(Some(&dev_info), gic.as_ref())
|
||||
)
|
||||
.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_fdt() {
|
||||
let regions = arch_memory_regions(FDT_MAX_SIZE + 0x1000);
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let gic = create_gic(&vm, 1).unwrap();
|
||||
let vpmu_feature = VpmuFeatureLevel::Disabled;
|
||||
let dtb = create_fdt(
|
||||
FdtVmInfo::new(
|
||||
&mem,
|
||||
"console=tty0",
|
||||
None,
|
||||
FdtVcpuInfo::new(vec![0], vec![1], vpmu_feature, false),
|
||||
),
|
||||
FdtNumaInfo::default(),
|
||||
FdtDeviceInfo::<MMIODeviceInfo>::new(None, gic.as_ref()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
create_dtb_file("output.dtb", &dtb);
|
||||
|
||||
let bytes = include_bytes!("test/output.dtb");
|
||||
let pos = 4;
|
||||
let val = FDT_MAX_SIZE;
|
||||
let mut buf = vec![];
|
||||
buf.extend_from_slice(bytes);
|
||||
set_size(&mut buf, pos, val);
|
||||
|
||||
let original_fdt = DeviceTree::load(&buf).unwrap();
|
||||
let generated_fdt = DeviceTree::load(&dtb).unwrap();
|
||||
assert_eq!(format!("{original_fdt:?}"), format!("{generated_fdt:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_fdt_with_initrd() {
|
||||
let regions = arch_memory_regions(FDT_MAX_SIZE + 0x1000);
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let gic = create_gic(&vm, 1).unwrap();
|
||||
let initrd = InitrdConfig {
|
||||
address: GuestAddress(0x10000000),
|
||||
size: 0x1000,
|
||||
};
|
||||
let vpmu_feature = VpmuFeatureLevel::Disabled;
|
||||
let dtb = create_fdt(
|
||||
FdtVmInfo::new(
|
||||
&mem,
|
||||
"console=tty0",
|
||||
Some(&initrd),
|
||||
FdtVcpuInfo::new(vec![0], vec![1], vpmu_feature, false),
|
||||
),
|
||||
FdtNumaInfo::default(),
|
||||
FdtDeviceInfo::<MMIODeviceInfo>::new(None, gic.as_ref()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
create_dtb_file("output_with_initrd.dtb", &dtb);
|
||||
|
||||
let bytes = include_bytes!("test/output_with_initrd.dtb");
|
||||
let pos = 4;
|
||||
let val = FDT_MAX_SIZE;
|
||||
let mut buf = vec![];
|
||||
buf.extend_from_slice(bytes);
|
||||
set_size(&mut buf, pos, val);
|
||||
|
||||
let original_fdt = DeviceTree::load(&buf).unwrap();
|
||||
let generated_fdt = DeviceTree::load(&dtb).unwrap();
|
||||
assert_eq!(format!("{original_fdt:?}"), format!("{generated_fdt:?}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_fdt_with_pmu() {
|
||||
let regions = arch_memory_regions(FDT_MAX_SIZE + 0x1000);
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let gic = create_gic(&vm, 1).unwrap();
|
||||
|
||||
assert!(initialize_vcpu_with_pmu(&vm, &vcpu).is_ok());
|
||||
|
||||
let vpmu_feature = VpmuFeatureLevel::FullyEnabled;
|
||||
let dtb = create_fdt(
|
||||
FdtVmInfo::new(
|
||||
&mem,
|
||||
"console=tty0",
|
||||
None,
|
||||
FdtVcpuInfo::new(vec![0], vec![1], vpmu_feature, false),
|
||||
),
|
||||
FdtNumaInfo::default(),
|
||||
FdtDeviceInfo::<MMIODeviceInfo>::new(None, gic.as_ref()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
create_dtb_file("output_with_pmu.dtb", &dtb);
|
||||
|
||||
let bytes = include_bytes!("test/output_with_pmu.dtb");
|
||||
let pos = 4;
|
||||
let val = FDT_MAX_SIZE;
|
||||
let mut buf = vec![];
|
||||
buf.extend_from_slice(bytes);
|
||||
set_size(&mut buf, pos, val);
|
||||
|
||||
let original_fdt = DeviceTree::load(&buf).unwrap();
|
||||
let generated_fdt = DeviceTree::load(&dtb).unwrap();
|
||||
assert_eq!(format!("{original_fdt:?}"), format!("{generated_fdt:?}"));
|
||||
}
|
||||
}
|
||||
373
src/dragonball/src/dbs_boot/src/aarch64/fdt_utils.rs
Normal file
373
src/dragonball/src/dbs_boot/src/aarch64/fdt_utils.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
// Copyright 2023 Alibaba Cloud. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! This module abstract some structs for constructing fdt. Instead of using
|
||||
//! multiple parameters.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dbs_arch::{gic::GICDevice, DeviceInfoForFDT, DeviceType, VpmuFeatureLevel};
|
||||
use vm_memory::mmap::GuestMemoryMmap;
|
||||
|
||||
use crate::InitrdConfig;
|
||||
|
||||
/// Struct to save vcpu information
|
||||
pub struct FdtVcpuInfo {
|
||||
/// vcpu mpidrs
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
/// vcpu boot-onlined
|
||||
vcpu_boot_onlined: Vec<u32>,
|
||||
/// vpmu feature
|
||||
vpmu_feature: VpmuFeatureLevel,
|
||||
// TODO: #274 cache passthrough
|
||||
/// cache passthrough
|
||||
cache_passthrough_enabled: bool,
|
||||
}
|
||||
|
||||
impl FdtVcpuInfo {
|
||||
/// Generate FdtVcpuInfo
|
||||
pub fn new(
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
vcpu_boot_onlined: Vec<u32>,
|
||||
vpmu_feature: VpmuFeatureLevel,
|
||||
cache_passthrough_enabled: bool,
|
||||
) -> Self {
|
||||
FdtVcpuInfo {
|
||||
vcpu_mpidr,
|
||||
vcpu_boot_onlined,
|
||||
vpmu_feature,
|
||||
cache_passthrough_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to save vm information.
|
||||
pub struct FdtVmInfo<'a> {
|
||||
/// guest meory
|
||||
guest_memory: &'a GuestMemoryMmap,
|
||||
/// command line
|
||||
cmdline: &'a str,
|
||||
/// initrd config
|
||||
initrd_config: Option<&'a InitrdConfig>,
|
||||
/// vcpu information
|
||||
vcpu_info: FdtVcpuInfo,
|
||||
}
|
||||
|
||||
impl FdtVmInfo<'_> {
|
||||
/// Generate FdtVmInfo.
|
||||
pub fn new<'a>(
|
||||
guest_memory: &'a GuestMemoryMmap,
|
||||
cmdline: &'a str,
|
||||
initrd_config: Option<&'a InitrdConfig>,
|
||||
vcpu_info: FdtVcpuInfo,
|
||||
) -> FdtVmInfo<'a> {
|
||||
FdtVmInfo {
|
||||
guest_memory,
|
||||
cmdline,
|
||||
initrd_config,
|
||||
vcpu_info,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get guest_memory.
|
||||
pub fn get_guest_memory(&self) -> &GuestMemoryMmap {
|
||||
self.guest_memory
|
||||
}
|
||||
|
||||
/// Get cmdline.
|
||||
pub fn get_cmdline(&self) -> &str {
|
||||
self.cmdline
|
||||
}
|
||||
|
||||
/// Get initrd_config.
|
||||
pub fn get_initrd_config(&self) -> Option<&InitrdConfig> {
|
||||
self.initrd_config
|
||||
}
|
||||
|
||||
/// Get vcpu_mpidr.
|
||||
pub fn get_vcpu_mpidr(&self) -> &[u64] {
|
||||
self.vcpu_info.vcpu_mpidr.as_slice()
|
||||
}
|
||||
|
||||
/// Get vpmu_feature.
|
||||
pub fn get_boot_onlined(&self) -> &[u32] {
|
||||
self.vcpu_info.vcpu_boot_onlined.as_slice()
|
||||
}
|
||||
|
||||
/// Get vpmu_feature.
|
||||
pub fn get_vpmu_feature(&self) -> VpmuFeatureLevel {
|
||||
self.vcpu_info.vpmu_feature
|
||||
}
|
||||
|
||||
/// Get cache_passthrough_enabled.
|
||||
pub fn get_cache_passthrough_enabled(&self) -> bool {
|
||||
self.vcpu_info.cache_passthrough_enabled
|
||||
}
|
||||
}
|
||||
|
||||
// This struct is used for cache passthrough and numa passthrough
|
||||
// TODO: #274 cache passthrough
|
||||
// TODO: #275 numa passthrough
|
||||
/// Struct to save numa information.
|
||||
#[derive(Default)]
|
||||
pub struct FdtNumaInfo {
|
||||
/// vcpu -> pcpu maps
|
||||
cpu_maps: Option<Vec<u8>>,
|
||||
/// numa id map vector for memory
|
||||
memory_numa_id_map: Option<Vec<u32>>,
|
||||
/// numa id map vector for vcpu
|
||||
vcpu_numa_id_map: Option<Vec<u32>>,
|
||||
}
|
||||
|
||||
impl FdtNumaInfo {
|
||||
/// Generate FdtNumaInfo.
|
||||
pub fn new(
|
||||
cpu_maps: Option<Vec<u8>>,
|
||||
memory_numa_id_map: Option<Vec<u32>>,
|
||||
vcpu_numa_id_map: Option<Vec<u32>>,
|
||||
) -> Self {
|
||||
FdtNumaInfo {
|
||||
cpu_maps,
|
||||
memory_numa_id_map,
|
||||
vcpu_numa_id_map,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cpu_maps struct.
|
||||
pub fn get_cpu_maps(&self) -> Option<Vec<u8>> {
|
||||
self.cpu_maps.clone()
|
||||
}
|
||||
|
||||
/// Get memory_numa_id_map struct.
|
||||
pub fn get_memory_numa_id_map(&self) -> Option<&Vec<u32>> {
|
||||
self.memory_numa_id_map.as_ref()
|
||||
}
|
||||
|
||||
/// Get vcpu_numa_id_map struct.
|
||||
pub fn get_vcpu_numa_id_map(&self) -> Option<&Vec<u32>> {
|
||||
self.vcpu_numa_id_map.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to save device information.
|
||||
pub struct FdtDeviceInfo<'a, T: DeviceInfoForFDT> {
|
||||
/// mmio device information
|
||||
mmio_device_info: Option<&'a HashMap<(DeviceType, String), T>>,
|
||||
/// interrupt controller
|
||||
irq_chip: &'a dyn GICDevice,
|
||||
}
|
||||
|
||||
impl<T: DeviceInfoForFDT> FdtDeviceInfo<'_, T> {
|
||||
/// Generate FdtDeviceInfo.
|
||||
pub fn new<'a>(
|
||||
mmio_device_info: Option<&'a HashMap<(DeviceType, String), T>>,
|
||||
irq_chip: &'a dyn GICDevice,
|
||||
) -> FdtDeviceInfo<'a, T> {
|
||||
FdtDeviceInfo {
|
||||
mmio_device_info,
|
||||
irq_chip,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mmio device information.
|
||||
pub fn get_mmio_device_info(&self) -> Option<&HashMap<(DeviceType, String), T>> {
|
||||
self.mmio_device_info
|
||||
}
|
||||
|
||||
/// Get interrupt controller.
|
||||
pub fn get_irqchip(&self) -> &dyn GICDevice {
|
||||
self.irq_chip
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use dbs_arch::gic::create_gic;
|
||||
use vm_memory::{GuestAddress, GuestMemory};
|
||||
|
||||
const CMDLINE: &str = "console=tty0";
|
||||
const INITRD_CONFIG: InitrdConfig = InitrdConfig {
|
||||
address: GuestAddress(0x10000000),
|
||||
size: 0x1000,
|
||||
};
|
||||
const VCPU_MPIDR: [u64; 1] = [0];
|
||||
const VCPU_BOOT_ONLINED: [u32; 1] = [1];
|
||||
const VPMU_FEATURE: VpmuFeatureLevel = VpmuFeatureLevel::Disabled;
|
||||
const CACHE_PASSTHROUGH_ENABLED: bool = false;
|
||||
|
||||
#[inline]
|
||||
fn helper_generate_fdt_vm_info(guest_memory: &GuestMemoryMmap) -> FdtVmInfo<'_> {
|
||||
FdtVmInfo::new(
|
||||
guest_memory,
|
||||
CMDLINE,
|
||||
Some(&INITRD_CONFIG),
|
||||
FdtVcpuInfo::new(
|
||||
VCPU_MPIDR.to_vec(),
|
||||
VCPU_BOOT_ONLINED.to_vec(),
|
||||
VPMU_FEATURE,
|
||||
CACHE_PASSTHROUGH_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fdtutils_fdt_vm_info() {
|
||||
let ranges = vec![(GuestAddress(0x80000000), 0x40000)];
|
||||
let guest_memory: GuestMemoryMmap<()> =
|
||||
GuestMemoryMmap::<()>::from_ranges(ranges.as_slice())
|
||||
.expect("Cannot initialize memory");
|
||||
let vm_info = helper_generate_fdt_vm_info(&guest_memory);
|
||||
|
||||
assert_eq!(
|
||||
guest_memory.check_address(GuestAddress(0x80001000)),
|
||||
Some(GuestAddress(0x80001000))
|
||||
);
|
||||
assert_eq!(guest_memory.check_address(GuestAddress(0x80050000)), None);
|
||||
assert!(guest_memory.check_range(GuestAddress(0x80000000), 0x40000));
|
||||
assert_eq!(vm_info.get_cmdline(), CMDLINE);
|
||||
assert_eq!(
|
||||
vm_info.get_initrd_config().unwrap().address,
|
||||
INITRD_CONFIG.address
|
||||
);
|
||||
assert_eq!(
|
||||
vm_info.get_initrd_config().unwrap().size,
|
||||
INITRD_CONFIG.size
|
||||
);
|
||||
assert_eq!(vm_info.get_vcpu_mpidr(), VCPU_MPIDR.as_slice());
|
||||
assert_eq!(vm_info.get_boot_onlined(), VCPU_BOOT_ONLINED.as_slice());
|
||||
assert_eq!(vm_info.get_vpmu_feature(), VPMU_FEATURE);
|
||||
assert_eq!(
|
||||
vm_info.get_cache_passthrough_enabled(),
|
||||
CACHE_PASSTHROUGH_ENABLED
|
||||
);
|
||||
}
|
||||
|
||||
const CPU_MAPS: [u8; 5] = [1, 2, 3, 4, 5];
|
||||
const MEMORY_VEC: [u32; 2] = [0, 1];
|
||||
const CPU_VEC: [u32; 5] = [0, 0, 0, 1, 1];
|
||||
|
||||
#[inline]
|
||||
fn helper_generate_fdt_numa_info() -> FdtNumaInfo {
|
||||
FdtNumaInfo::new(
|
||||
Some(CPU_MAPS.to_vec()),
|
||||
Some(MEMORY_VEC.to_vec()),
|
||||
Some(CPU_VEC.to_vec()),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fdtutils_fdt_numa_info() {
|
||||
// test default
|
||||
let numa_info = FdtNumaInfo::default();
|
||||
assert_eq!(numa_info.get_cpu_maps(), None);
|
||||
assert_eq!(numa_info.get_memory_numa_id_map(), None);
|
||||
assert_eq!(numa_info.get_vcpu_numa_id_map(), None);
|
||||
|
||||
let numa_info = helper_generate_fdt_numa_info();
|
||||
assert_eq!(
|
||||
numa_info.get_cpu_maps().unwrap().as_slice(),
|
||||
CPU_MAPS.as_slice()
|
||||
);
|
||||
assert_eq!(
|
||||
numa_info.get_memory_numa_id_map().unwrap().as_slice(),
|
||||
MEMORY_VEC.as_slice()
|
||||
);
|
||||
assert_eq!(
|
||||
numa_info.get_vcpu_numa_id_map().unwrap().as_slice(),
|
||||
CPU_VEC.as_slice()
|
||||
);
|
||||
}
|
||||
|
||||
use dbs_arch::gic::its::ItsType;
|
||||
use dbs_device::resources::{DeviceResources, Resource};
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
use super::super::tests::MMIODeviceInfo;
|
||||
|
||||
const MEMORY_SIZE: u64 = 4096;
|
||||
const ECAM_SPACE: [Resource; 1] = [Resource::MmioAddressRange {
|
||||
base: 0x40000000,
|
||||
size: 0x1000,
|
||||
}];
|
||||
const BAR_SPACE: [Resource; 2] = [
|
||||
Resource::MmioAddressRange {
|
||||
base: 0x40001000,
|
||||
size: 0x1000,
|
||||
},
|
||||
Resource::MmioAddressRange {
|
||||
base: 0x40002000,
|
||||
size: 0x1000,
|
||||
},
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_fdtutils_fdt_device_info() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let gic = create_gic(&vm, 0).unwrap();
|
||||
let mmio_device_info: Option<HashMap<(DeviceType, String), MMIODeviceInfo>> = Some(
|
||||
[
|
||||
(
|
||||
(DeviceType::Serial, DeviceType::Serial.to_string()),
|
||||
MMIODeviceInfo::new(0, 1),
|
||||
),
|
||||
(
|
||||
(DeviceType::Virtio(1), "virtio".to_string()),
|
||||
MMIODeviceInfo::new(MEMORY_SIZE, 2),
|
||||
),
|
||||
(
|
||||
(DeviceType::RTC, "rtc".to_string()),
|
||||
MMIODeviceInfo::new(2 * MEMORY_SIZE, 3),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
let mut ecam_space = DeviceResources::new();
|
||||
ecam_space.append(ECAM_SPACE.as_slice()[0].clone());
|
||||
|
||||
let mut bar_space = DeviceResources::new();
|
||||
bar_space.append(BAR_SPACE.as_slice()[0].clone());
|
||||
bar_space.append(BAR_SPACE.as_slice()[1].clone());
|
||||
|
||||
let its_type1 = ItsType::PciMsiIts;
|
||||
let its_type2 = ItsType::PlatformMsiIts;
|
||||
|
||||
let device_info = FdtDeviceInfo::new(mmio_device_info.as_ref(), gic.as_ref());
|
||||
assert_eq!(
|
||||
device_info.get_mmio_device_info(),
|
||||
mmio_device_info.as_ref()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", device_info.get_irqchip().device_fd()),
|
||||
format!("{:?}", gic.as_ref().device_fd())
|
||||
);
|
||||
assert_eq!(
|
||||
device_info.get_irqchip().device_properties(),
|
||||
gic.as_ref().device_properties()
|
||||
);
|
||||
assert_eq!(
|
||||
device_info.get_irqchip().fdt_compatibility(),
|
||||
gic.as_ref().fdt_compatibility()
|
||||
);
|
||||
assert_eq!(
|
||||
device_info.get_irqchip().fdt_maint_irq(),
|
||||
gic.as_ref().fdt_maint_irq()
|
||||
);
|
||||
assert_eq!(
|
||||
device_info.get_irqchip().vcpu_count(),
|
||||
gic.as_ref().vcpu_count()
|
||||
);
|
||||
assert_eq!(
|
||||
device_info.get_irqchip().get_its_reg_range(&its_type1),
|
||||
gic.as_ref().get_its_reg_range(&its_type1)
|
||||
);
|
||||
assert_eq!(
|
||||
device_info.get_irqchip().get_its_reg_range(&its_type2),
|
||||
gic.as_ref().get_its_reg_range(&its_type2)
|
||||
);
|
||||
}
|
||||
}
|
||||
94
src/dragonball/src/dbs_boot/src/aarch64/layout.rs
Normal file
94
src/dragonball/src/dbs_boot/src/aarch64/layout.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2021-2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// ==== Address map in use in ARM development systems today ====
|
||||
//
|
||||
// - 32-bit - - 36-bit - - 40-bit -
|
||||
//1024GB + + +-------------------+ <- 40-bit
|
||||
// | | DRAM |
|
||||
// ~ ~ ~ ~
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
//544GB + + +-------------------+
|
||||
// | | Hole or DRAM |
|
||||
// | | |
|
||||
//512GB + + +-------------------+
|
||||
// | | Mapped |
|
||||
// | | I/O |
|
||||
// ~ ~ ~ ~
|
||||
// | | |
|
||||
//256GB + + +-------------------+
|
||||
// | | Reserved |
|
||||
// ~ ~ ~ ~
|
||||
// | | |
|
||||
//64GB + +-----------------------+-------------------+ <- 36-bit
|
||||
// | | DRAM |
|
||||
// ~ ~ ~ ~
|
||||
// | | |
|
||||
// | | |
|
||||
//34GB + +-----------------------+-------------------+
|
||||
// | | Hole or DRAM |
|
||||
//32GB + +-----------------------+-------------------+
|
||||
// | | Mapped I/O |
|
||||
// ~ ~ ~ ~
|
||||
// | | |
|
||||
//16GB + +-----------------------+-------------------+
|
||||
// | | Reserved |
|
||||
// ~ ~ ~ ~
|
||||
//4GB +-------------------+-----------------------+-------------------+ <- 32-bit
|
||||
// | 2GB of DRAM |
|
||||
// | |
|
||||
//2GB +-------------------+-----------------------+-------------------+
|
||||
// | Mapped I/O |
|
||||
//1GB +-------------------+-----------------------+-------------------+
|
||||
// | ROM & RAM & I/O |
|
||||
//0GB +-------------------+-----------------------+-------------------+ 0
|
||||
// - 32-bit - - 36-bit - - 40-bit -
|
||||
//
|
||||
// Taken from (http://infocenter.arm.com/help/topic/com.arm.doc.den0001c/DEN0001C_principles_of_arm_memory_maps.pdf).
|
||||
|
||||
/// Start of RAM on 64 bit ARM.
|
||||
pub const DRAM_MEM_START: u64 = 0x8000_0000; // 2 GB.
|
||||
/// The maximum addressable RAM address.
|
||||
pub const DRAM_MEM_END: u64 = 0x00F8_0000_0000; // 1024 - 32 = 992 GB.
|
||||
/// The maximum RAM size.
|
||||
pub const DRAM_MEM_MAX_SIZE: u64 = DRAM_MEM_END - DRAM_MEM_START;
|
||||
|
||||
/// Kernel command line maximum size.
|
||||
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||
|
||||
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
pub const FDT_MAX_SIZE: usize = 0x20_0000;
|
||||
|
||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
||||
// the number of interrupts our GIC will support to be:
|
||||
// * bigger than 32
|
||||
// * less than 1023 and
|
||||
// * a multiple of 32.
|
||||
// We are setting up our interrupt controller to support a maximum of 128 interrupts.
|
||||
/// First usable interrupt on aarch64.
|
||||
pub const IRQ_BASE: u32 = dbs_arch::gic::IRQ_BASE;
|
||||
|
||||
/// Last usable interrupt on aarch64.
|
||||
pub const IRQ_MAX: u32 = dbs_arch::gic::IRQ_MAX;
|
||||
|
||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
||||
pub const MAPPED_IO_START: u64 = dbs_arch::gic::GIC_REG_END_ADDRESS; // 1 GB
|
||||
/// End address (inclusive) of the MMIO window.
|
||||
pub const MAPPED_IO_END: u64 = (2 << 30) - 1; // 1 GB
|
||||
|
||||
/// Maximum guest physical address supported.
|
||||
pub static GUEST_PHYS_END: &u64 = &((1u64 << 40) - 1);
|
||||
/// Upper bound of guest memory.
|
||||
pub static GUEST_MEM_END: &u64 = &(DRAM_MEM_END - 1);
|
||||
/// Lower bound of guest memory.
|
||||
pub const GUEST_MEM_START: u64 = DRAM_MEM_START;
|
||||
/// Start address of the lower MMIO window.
|
||||
pub const MMIO_LOW_START: u64 = MAPPED_IO_START;
|
||||
/// End address (inclusive) of the lower MMIO window.
|
||||
pub const MMIO_LOW_END: u64 = MAPPED_IO_END;
|
||||
/// Size of memory below MMIO hole.
|
||||
pub const GUEST_MEM_LOW_SIZE: u64 = 0u64;
|
||||
103
src/dragonball/src/dbs_boot/src/aarch64/mod.rs
Normal file
103
src/dragonball/src/dbs_boot/src/aarch64/mod.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! VM boot related constants and utilities for `aarch64` architecture.
|
||||
|
||||
use vm_fdt::Error as VmFdtError;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
/// Magic addresses externally used to lay out aarch64 VMs.
|
||||
pub mod layout;
|
||||
|
||||
/// FDT is used to inform the guest kernel of device tree information.
|
||||
pub mod fdt;
|
||||
|
||||
/// Helper structs for constructing fdt.
|
||||
pub mod fdt_utils;
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Failure in creating FDT
|
||||
#[error("create fdt fail: {0}")]
|
||||
CreateFdt(#[from] VmFdtError),
|
||||
/// Failure in writing FDT in memory.
|
||||
#[error("write fdt to memory fail: {0}")]
|
||||
WriteFDTToMemory(#[from] GuestMemoryError),
|
||||
/// Failed to compute the initrd address.
|
||||
#[error("Failed to compute the initrd address.")]
|
||||
InitrdAddress,
|
||||
/// Invalid arguments
|
||||
#[error("invalid arguments")]
|
||||
InvalidArguments,
|
||||
}
|
||||
|
||||
/// Returns the memory address where the kernel could be loaded.
|
||||
pub fn get_kernel_start() -> u64 {
|
||||
layout::DRAM_MEM_START
|
||||
}
|
||||
|
||||
/// Auxiliary function to get the address where the device tree blob is loaded.
|
||||
pub fn get_fdt_addr<M: GuestMemory>(mem: &M) -> u64 {
|
||||
// If the memory allocated is smaller than the size allocated for the FDT,
|
||||
// we return the start of the DRAM so that
|
||||
// we allow the code to try and load the FDT.
|
||||
if let Some(offset) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
|
||||
if mem.address_in_range(offset) {
|
||||
return offset.raw_value();
|
||||
}
|
||||
}
|
||||
layout::DRAM_MEM_START
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initrd could be loaded.
|
||||
pub fn initrd_load_addr<M: GuestMemory>(guest_mem: &M, initrd_size: u64) -> super::Result<u64> {
|
||||
let round_to_pagesize = |size| (size + (PAGE_SIZE as u64 - 1)) & !(PAGE_SIZE as u64 - 1);
|
||||
match GuestAddress(get_fdt_addr(guest_mem)).checked_sub(round_to_pagesize(initrd_size)) {
|
||||
Some(offset) => {
|
||||
if guest_mem.address_in_range(offset) {
|
||||
Ok(offset.raw_value())
|
||||
} else {
|
||||
Err(Error::InitrdAddress)
|
||||
}
|
||||
}
|
||||
None => Err(Error::InitrdAddress),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use dbs_arch::{DeviceInfoForFDT, Error as ArchError};
|
||||
|
||||
const LEN: u64 = 4096;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MMIODeviceInfo {
|
||||
addr: u64,
|
||||
irq: u32,
|
||||
}
|
||||
|
||||
impl MMIODeviceInfo {
|
||||
pub fn new(addr: u64, irq: u32) -> Self {
|
||||
MMIODeviceInfo { addr, irq }
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
}
|
||||
fn irq(&self) -> std::result::Result<u32, ArchError> {
|
||||
Ok(self.irq)
|
||||
}
|
||||
fn length(&self) -> u64 {
|
||||
LEN
|
||||
}
|
||||
fn get_device_id(&self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/dragonball/src/dbs_boot/src/aarch64/test/output.dtb
Normal file
BIN
src/dragonball/src/dbs_boot/src/aarch64/test/output.dtb
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/dragonball/src/dbs_boot/src/aarch64/test/output_with_pmu.dtb
Normal file
BIN
src/dragonball/src/dbs_boot/src/aarch64/test/output_with_pmu.dtb
Normal file
Binary file not shown.
27
src/dragonball/src/dbs_boot/src/lib.rs
Normal file
27
src/dragonball/src/dbs_boot/src/lib.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2021-2022 Alibaba Cloud. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Constants, Structs and Utilities to setup boot environment for virtual machines.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::*;
|
||||
|
||||
/// Specialized [std::result::Result] for boot related operations.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Type for passing information about the initrd in the guest memory.
|
||||
pub struct InitrdConfig {
|
||||
/// Load address of initrd in guest memory
|
||||
pub address: vm_memory::GuestAddress,
|
||||
/// Size of initrd in guest memory
|
||||
pub size: usize,
|
||||
}
|
||||
41
src/dragonball/src/dbs_boot/src/vendor/bootparam.rs
vendored
Normal file
41
src/dragonball/src/dbs_boot/src/vendor/bootparam.rs
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2023 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use arch_gen::x86::bootparam::{__u32, __u64};
|
||||
use vm_memory::bytes::Bytes;
|
||||
use vm_memory::guest_memory::GuestAddress;
|
||||
use vm_memory::{ByteValued, GuestMemory};
|
||||
|
||||
use super::layout;
|
||||
|
||||
/// With reference to the x86_hardware_subarch enumeration type of the
|
||||
/// kernel, we newly added the X86_SUBARCH_DRAGONBALL type and defined
|
||||
/// it as 0xdbdbdb01 to mark this as a guest kernel.
|
||||
#[allow(dead_code)]
|
||||
pub enum X86HardwareSubarch {
|
||||
X86SubarchPC = 0,
|
||||
X86SubarchLGUEST = 1,
|
||||
X86SubarchXEN = 2,
|
||||
X86SubarchIntelMID = 3,
|
||||
X86SubarchCE4100 = 4,
|
||||
X86SubarchDragonball = 0xdbdbdb01,
|
||||
}
|
||||
|
||||
/// Recorded in subarch_data, used to verify the validity of dragonball subarch_data.
|
||||
pub const DB_BOOT_PARAM_SIGNATURE: u64 = 0xdbdbb007700bbdbd;
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Error dragonball boot parameter length
|
||||
#[error("dragonball boot param exceeds max size")]
|
||||
DragonballBootParamPastMaxSize,
|
||||
|
||||
/// Error dragonball boot parameter location
|
||||
#[error("dragonball boot param past ram end")]
|
||||
DragonballBootParamPastRamEnd,
|
||||
|
||||
/// Error writing dragonball boot parameter
|
||||
#[error("dragonball boot param setup fail")]
|
||||
WriteDragonballBootParam,
|
||||
}
|
||||
|
||||
4628
src/dragonball/src/dbs_boot/src/x86_64/bootparam.rs
Normal file
4628
src/dragonball/src/dbs_boot/src/x86_64/bootparam.rs
Normal file
File diff suppressed because it is too large
Load Diff
100
src/dragonball/src/dbs_boot/src/x86_64/layout.rs
Normal file
100
src/dragonball/src/dbs_boot/src/x86_64/layout.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2021-2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
/// Magic addresses externally used to lay out x86_64 VMs.
|
||||
|
||||
/// Global Descriptor Table Offset
|
||||
pub const BOOT_GDT_OFFSET: u64 = 0x500;
|
||||
/// Interrupt Descriptor Table Offset
|
||||
pub const BOOT_IDT_OFFSET: u64 = 0x520;
|
||||
|
||||
/// Address of Global Descriptor Table (GDT)
|
||||
pub const BOOT_GDT_ADDRESS: u64 = 0x500;
|
||||
/// Number of initial GDT entries.
|
||||
pub const BOOT_GDT_MAX: usize = 4;
|
||||
|
||||
/// Address of Interrupt Descriptor Table (IDT)
|
||||
pub const BOOT_IDT_ADDRESS: u64 = 0x520;
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: u64 = 0x7000;
|
||||
|
||||
/// Initial stack for the boot CPU.
|
||||
pub const BOOT_STACK_POINTER: u64 = 0x8ff0;
|
||||
|
||||
/// Address of page table level 4 page
|
||||
pub const PML4_START: u64 = 0x9000;
|
||||
/// Address of page table level 3 page
|
||||
pub const PDPTE_START: u64 = 0xa000;
|
||||
/// Address of page table level 2 page
|
||||
pub const PDE_START: u64 = 0xb000;
|
||||
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: u64 = 0x20000;
|
||||
/// Kernel command line start address maximum size.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 0x10000;
|
||||
|
||||
/// Kernel dragonball boot parameters start address.
|
||||
pub const DB_BOOT_PARAM_START: u64 = 0x30000;
|
||||
/// Kernel dragonball boot parameters length maximum size.
|
||||
pub const DB_BOOT_PARAM_MAX_SIZE: u32 = 0x10000;
|
||||
|
||||
/// Start of the high memory.
|
||||
pub const HIMEM_START: u64 = 0x0010_0000; //1 MB.
|
||||
|
||||
// Typically, on x86 systems 16 IRQs are used (0-15).
|
||||
/// First usable IRQ ID for virtio device interrupts on x86_64.
|
||||
pub const IRQ_BASE: u32 = 5;
|
||||
/// Last usable IRQ ID for virtio device interrupts on x86_64.
|
||||
pub const IRQ_MAX: u32 = 15;
|
||||
|
||||
/// Address for the TSS setup.
|
||||
pub const KVM_TSS_ADDRESS: u64 = 0xfffb_d000;
|
||||
|
||||
/// Where BIOS/VGA magic would live on a real PC.
|
||||
pub const EBDA_START: u64 = 0x9fc00;
|
||||
|
||||
/// Start address of the lower MMIO window.
|
||||
pub const MMIO_LOW_START: u64 = 3u64 << 30;
|
||||
/// End address (inclusive) of the lower MMIO window.
|
||||
pub const MMIO_LOW_END: u64 = (4u64 << 30) - 1;
|
||||
/// Lower bound of guest memory.
|
||||
pub const GUEST_MEM_START: u64 = 0u64;
|
||||
/// Size of memory below MMIO hole.
|
||||
pub const GUEST_MEM_LOW_SIZE: u64 = MMIO_LOW_START - GUEST_MEM_START;
|
||||
|
||||
/// Max retry times for reading /proc/cpuinfo
|
||||
const CPUINFO_READ_RETRY: u64 = 5;
|
||||
|
||||
lazy_static! {
|
||||
/// Maximum guest physical address supported.
|
||||
pub static ref GUEST_PHYS_END: u64 = {
|
||||
for _ in 0..CPUINFO_READ_RETRY {
|
||||
if let Ok(buf) = std::fs::read("/proc/cpuinfo") {
|
||||
let content = String::from_utf8_lossy(&buf);
|
||||
for line in content.lines() {
|
||||
if line.starts_with("address sizes : ") {
|
||||
if let Some(end) = line.find(" bits physical") {
|
||||
if let Ok(size) = line[16..end].parse::<u64>() {
|
||||
if (36..=64).contains(&size) {
|
||||
return (1u64 << size) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("Exceed max retry times. Cannot get physical address size from /proc/cpuinfo");
|
||||
};
|
||||
|
||||
/// Upper bound of guest memory.
|
||||
pub static ref GUEST_MEM_END: u64 = *GUEST_PHYS_END >> 1;
|
||||
}
|
||||
325
src/dragonball/src/dbs_boot/src/x86_64/mod.rs
Normal file
325
src/dragonball/src/dbs_boot/src/x86_64/mod.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright 2021 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! VM boot related constants and utilities for `x86_64` architecture.
|
||||
|
||||
use dbs_arch::gdt::gdt_entry;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryRegion};
|
||||
|
||||
use self::layout::{BOOT_GDT_ADDRESS, BOOT_GDT_MAX, BOOT_IDT_ADDRESS};
|
||||
use super::Result;
|
||||
|
||||
/// Magic addresses externally used to lay out x86_64 VMs.
|
||||
pub mod layout;
|
||||
|
||||
/// Structure definitions for SMP machines following the Intel Multiprocessing Specification 1.1 and 1.4.
|
||||
pub mod mpspec;
|
||||
|
||||
/// MP Table configurations used for defining VM boot status.
|
||||
pub mod mptable;
|
||||
|
||||
/// Guest boot parameters used for config guest information.
|
||||
pub mod bootparam;
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
/// Boot parameters wrapper for ByteValue trait
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct BootParamsWrapper(pub bootparam::boot_params);
|
||||
|
||||
// It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints).
|
||||
unsafe impl ByteValued for BootParamsWrapper {}
|
||||
|
||||
/// Errors thrown while configuring x86_64 system.
|
||||
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Invalid e820 setup params.
|
||||
#[error("invalid e820 setup parameters")]
|
||||
E820Configuration,
|
||||
|
||||
/// Error writing MP table to memory.
|
||||
#[error("failed to write MP table to guest memory")]
|
||||
MpTableSetup(#[source] mptable::Error),
|
||||
|
||||
/// The zero page extends past the end of guest_mem.
|
||||
#[error("the guest zero page extends past the end of guest memory")]
|
||||
ZeroPagePastRamEnd,
|
||||
|
||||
/// Error writing the zero page of guest memory.
|
||||
#[error("failed to write to guest zero page")]
|
||||
ZeroPageSetup,
|
||||
|
||||
/// Failed to compute initrd address.
|
||||
#[error("invalid guest memory address for Initrd")]
|
||||
InitrdAddress,
|
||||
|
||||
/// boot parameter setup fail.
|
||||
#[error("write boot parameter fail")]
|
||||
BootParamSetup,
|
||||
|
||||
/// Empty AddressSpace from parameters.
|
||||
#[error("Empty AddressSpace from parameters")]
|
||||
AddressSpace,
|
||||
|
||||
/// Writing PDPTE to RAM failed.
|
||||
#[error("Writing PDPTE to RAM failed.")]
|
||||
WritePDPTEAddress,
|
||||
|
||||
/// Writing PDE to RAM failed.
|
||||
#[error("Writing PDE to RAM failed.")]
|
||||
WritePDEAddress,
|
||||
|
||||
#[error("Writing PML4 to RAM failed.")]
|
||||
/// Writing PML4 to RAM failed.
|
||||
WritePML4Address,
|
||||
}
|
||||
|
||||
/// Initialize the 1:1 identity mapping table for guest memory range [0..1G).
|
||||
///
|
||||
/// Also, return the pml4 address for sregs setting and AP boot
|
||||
pub fn setup_identity_mapping<M: GuestMemory>(mem: &M) -> Result<GuestAddress> {
|
||||
// Puts PML4 right after zero page but aligned to 4k.
|
||||
let boot_pml4_addr = GuestAddress(layout::PML4_START);
|
||||
let boot_pdpte_addr = GuestAddress(layout::PDPTE_START);
|
||||
let boot_pde_addr = GuestAddress(layout::PDE_START);
|
||||
|
||||
// Entry covering VA [0..512GB)
|
||||
mem.write_obj(boot_pdpte_addr.raw_value() | 0x03, boot_pml4_addr)
|
||||
.map_err(|_| Error::WritePML4Address)?;
|
||||
|
||||
// Entry covering VA [0..1GB)
|
||||
mem.write_obj(boot_pde_addr.raw_value() | 0x03, boot_pdpte_addr)
|
||||
.map_err(|_| Error::WritePDPTEAddress)?;
|
||||
|
||||
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||
for i in 0..512 {
|
||||
mem.write_obj((i << 21) + 0x83u64, boot_pde_addr.unchecked_add(i * 8))
|
||||
.map_err(|_| Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
// return the pml4 address that could be used for AP boot up and later sreg setting process.
|
||||
Ok(boot_pml4_addr)
|
||||
}
|
||||
|
||||
/// Get information to configure GDT/IDT.
|
||||
pub fn get_descriptor_config_info() -> ([u64; BOOT_GDT_MAX], u64, u64) {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX] = [
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
];
|
||||
|
||||
(gdt_table, BOOT_GDT_ADDRESS, BOOT_IDT_ADDRESS)
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initrd could be loaded.
|
||||
pub fn initrd_load_addr<M: GuestMemory>(guest_mem: &M, initrd_size: u64) -> Result<u64> {
|
||||
let lowmem_size = guest_mem
|
||||
.find_region(GuestAddress(0))
|
||||
.ok_or(Error::InitrdAddress)
|
||||
.map(|r| r.len())?;
|
||||
|
||||
// For safety to avoid overlap, reserve 32M for kernel and boot params in low end.
|
||||
if lowmem_size < initrd_size + (32 << 20) {
|
||||
return Err(Error::InitrdAddress);
|
||||
}
|
||||
|
||||
let align_to_pagesize = |address| address & !(PAGE_SIZE as u64 - 1);
|
||||
Ok(align_to_pagesize(lowmem_size - initrd_size))
|
||||
}
|
||||
|
||||
/// Returns the memory address where the kernel could be loaded.
|
||||
pub fn get_kernel_start() -> u64 {
|
||||
layout::HIMEM_START
|
||||
}
|
||||
|
||||
/// Add an e820 region to the e820 map.
|
||||
/// Returns Ok(()) if successful, or an error if there is no space left in the map.
|
||||
pub fn add_e820_entry(
|
||||
params: &mut bootparam::boot_params,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<()> {
|
||||
if params.e820_entries >= params.e820_table.len() as u8 {
|
||||
return Err(Error::E820Configuration);
|
||||
}
|
||||
|
||||
params.e820_table[params.e820_entries as usize].addr = addr;
|
||||
params.e820_table[params.e820_entries as usize].size = size;
|
||||
params.e820_table[params.e820_entries as usize].type_ = mem_type;
|
||||
params.e820_entries += 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bootparam::{boot_e820_entry, boot_params};
|
||||
use crate::layout::{PDE_START, PDPTE_START, PML4_START};
|
||||
use kvm_bindings::kvm_sregs;
|
||||
use kvm_ioctls::Kvm;
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
|
||||
const BOOT_GDT_OFFSET: u64 = 0x500;
|
||||
const BOOT_IDT_OFFSET: u64 = 0x520;
|
||||
|
||||
fn read_u64(gm: &GuestMemoryMmap, offset: u64) -> u64 {
|
||||
let read_addr = GuestAddress(offset);
|
||||
gm.read_obj(read_addr).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_descriptor_config_info() {
|
||||
let (gdt_table, gdt_addr, idt_addr) = get_descriptor_config_info();
|
||||
|
||||
assert_eq!(gdt_table.len(), BOOT_GDT_MAX);
|
||||
assert_eq!(gdt_addr, BOOT_GDT_ADDRESS);
|
||||
assert_eq!(idt_addr, BOOT_IDT_ADDRESS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_identity_mapping() {
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
setup_identity_mapping(&gm).unwrap();
|
||||
assert_eq!(0xa003, read_u64(&gm, PML4_START));
|
||||
assert_eq!(0xb003, read_u64(&gm, PDPTE_START));
|
||||
for i in 0..512 {
|
||||
assert_eq!((i << 21) + 0x83u64, read_u64(&gm, PDE_START + (i * 8)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_boot_param() {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x0100_0000; // Must be non-zero.
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(bootparam::boot_params::default());
|
||||
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
|
||||
params.0.hdr.header = KERNEL_HDR_MAGIC;
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
|
||||
assert_eq!(params.0.hdr.type_of_loader, KERNEL_LOADER_OTHER);
|
||||
assert_eq!(
|
||||
unsafe { std::ptr::addr_of!(params.0.hdr.boot_flag).read_unaligned() },
|
||||
KERNEL_BOOT_FLAG_MAGIC
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { std::ptr::addr_of!(params.0.hdr.header).read_unaligned() },
|
||||
KERNEL_HDR_MAGIC
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { std::ptr::addr_of!(params.0.hdr.kernel_alignment).read_unaligned() },
|
||||
KERNEL_MIN_ALIGNMENT_BYTES
|
||||
);
|
||||
}
|
||||
|
||||
fn validate_page_tables(
|
||||
gm: &GuestMemoryMmap,
|
||||
sregs: &kvm_sregs,
|
||||
existing_pgtable: Option<GuestAddress>,
|
||||
) {
|
||||
assert_eq!(0xa003, read_u64(gm, PML4_START));
|
||||
assert_eq!(0xb003, read_u64(gm, PDPTE_START));
|
||||
for i in 0..512 {
|
||||
assert_eq!((i << 21) + 0x83u64, read_u64(gm, PDE_START + (i * 8)));
|
||||
}
|
||||
|
||||
if let Some(pgtable_base) = existing_pgtable {
|
||||
assert_eq!(pgtable_base.raw_value(), sregs.cr3);
|
||||
} else {
|
||||
assert_eq!(PML4_START, sregs.cr3);
|
||||
}
|
||||
assert!(sregs.cr4 & dbs_arch::regs::X86_CR4_PAE != 0);
|
||||
assert!(sregs.cr0 & dbs_arch::regs::X86_CR0_PG != 0);
|
||||
}
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setup_page_tables() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let gm = create_guest_mem();
|
||||
let gdt_table: [u64; layout::BOOT_GDT_MAX] = [
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
];
|
||||
|
||||
let page_address = setup_identity_mapping(&gm).unwrap();
|
||||
dbs_arch::regs::setup_sregs(
|
||||
&gm,
|
||||
&vcpu,
|
||||
page_address,
|
||||
&gdt_table,
|
||||
BOOT_GDT_OFFSET,
|
||||
BOOT_IDT_OFFSET,
|
||||
)
|
||||
.unwrap();
|
||||
let sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
validate_page_tables(&gm, &sregs, Some(page_address));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_e820_entry() {
|
||||
let e820_table = [(boot_e820_entry {
|
||||
addr: 0x1,
|
||||
size: 4,
|
||||
type_: 1,
|
||||
}); 128];
|
||||
|
||||
let expected_params = boot_params {
|
||||
e820_table,
|
||||
e820_entries: 1,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut params: boot_params = Default::default();
|
||||
add_e820_entry(
|
||||
&mut params,
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
format!("{:?}", params.e820_table[0]),
|
||||
format!("{:?}", expected_params.e820_table[0])
|
||||
);
|
||||
assert_eq!(params.e820_entries, expected_params.e820_entries);
|
||||
|
||||
// Exercise the scenario where the field storing the length of the e820 entry table is
|
||||
// is bigger than the allocated memory.
|
||||
params.e820_entries = params.e820_table.len() as u8 + 1;
|
||||
assert!(add_e820_entry(
|
||||
&mut params,
|
||||
e820_table[0].addr,
|
||||
e820_table[0].size,
|
||||
e820_table[0].type_
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
936
src/dragonball/src/dbs_boot/src/x86_64/mpspec.rs
Normal file
936
src/dragonball/src/dbs_boot/src/x86_64/mpspec.rs
Normal file
@@ -0,0 +1,936 @@
|
||||
// Copyright 2023 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Structure definitions for SMP machines following the Intel Multiprocessing Specification 1.1 and 1.4.
|
||||
|
||||
/* automatically generated by rust-bindgen */
|
||||
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(deref_nullptr)]
|
||||
|
||||
pub const MPC_SIGNATURE: &[u8; 5usize] = b"PCMP\x00";
|
||||
pub const MP_PROCESSOR: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_BUS: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IOAPIC: ::std::os::raw::c_uint = 2;
|
||||
pub const MP_INTSRC: ::std::os::raw::c_uint = 3;
|
||||
pub const MP_LINTSRC: ::std::os::raw::c_uint = 4;
|
||||
pub const MP_TRANSLATION: ::std::os::raw::c_uint = 192;
|
||||
pub const CPU_ENABLED: ::std::os::raw::c_uint = 1;
|
||||
pub const CPU_BOOTPROCESSOR: ::std::os::raw::c_uint = 2;
|
||||
pub const CPU_STEPPING_MASK: ::std::os::raw::c_uint = 15;
|
||||
pub const CPU_MODEL_MASK: ::std::os::raw::c_uint = 240;
|
||||
pub const CPU_FAMILY_MASK: ::std::os::raw::c_uint = 3840;
|
||||
pub const BUSTYPE_EISA: &[u8; 5usize] = b"EISA\x00";
|
||||
pub const BUSTYPE_ISA: &[u8; 4usize] = b"ISA\x00";
|
||||
pub const BUSTYPE_INTERN: &[u8; 7usize] = b"INTERN\x00";
|
||||
pub const BUSTYPE_MCA: &[u8; 4usize] = b"MCA\x00";
|
||||
pub const BUSTYPE_VL: &[u8; 3usize] = b"VL\x00";
|
||||
pub const BUSTYPE_PCI: &[u8; 4usize] = b"PCI\x00";
|
||||
pub const BUSTYPE_PCMCIA: &[u8; 7usize] = b"PCMCIA\x00";
|
||||
pub const BUSTYPE_CBUS: &[u8; 5usize] = b"CBUS\x00";
|
||||
pub const BUSTYPE_CBUSII: &[u8; 7usize] = b"CBUSII\x00";
|
||||
pub const BUSTYPE_FUTURE: &[u8; 7usize] = b"FUTURE\x00";
|
||||
pub const BUSTYPE_MBI: &[u8; 4usize] = b"MBI\x00";
|
||||
pub const BUSTYPE_MBII: &[u8; 5usize] = b"MBII\x00";
|
||||
pub const BUSTYPE_MPI: &[u8; 4usize] = b"MPI\x00";
|
||||
pub const BUSTYPE_MPSA: &[u8; 5usize] = b"MPSA\x00";
|
||||
pub const BUSTYPE_NUBUS: &[u8; 6usize] = b"NUBUS\x00";
|
||||
pub const BUSTYPE_TC: &[u8; 3usize] = b"TC\x00";
|
||||
pub const BUSTYPE_VME: &[u8; 4usize] = b"VME\x00";
|
||||
pub const BUSTYPE_XPRESS: &[u8; 7usize] = b"XPRESS\x00";
|
||||
pub const MPC_APIC_USABLE: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQDIR_DEFAULT: ::std::os::raw::c_uint = 0;
|
||||
pub const MP_IRQDIR_HIGH: ::std::os::raw::c_uint = 1;
|
||||
pub const MP_IRQDIR_LOW: ::std::os::raw::c_uint = 3;
|
||||
pub const MP_APIC_ALL: ::std::os::raw::c_uint = 255;
|
||||
pub const MPC_OEM_SIGNATURE: &[u8; 5usize] = b"_OEM\x00";
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpf_intel {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub physptr: ::std::os::raw::c_uint,
|
||||
pub length: ::std::os::raw::c_uchar,
|
||||
pub specification: ::std::os::raw::c_uchar,
|
||||
pub checksum: ::std::os::raw::c_uchar,
|
||||
pub feature1: ::std::os::raw::c_uchar,
|
||||
pub feature2: ::std::os::raw::c_uchar,
|
||||
pub feature3: ::std::os::raw::c_uchar,
|
||||
pub feature4: ::std::os::raw::c_uchar,
|
||||
pub feature5: ::std::os::raw::c_uchar,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_mpf_intel() {
|
||||
let mpf_intel = mpf_intel::default();
|
||||
assert_eq!(mpf_intel.signature, [0i8, 0i8, 0i8, 0i8]);
|
||||
assert_eq!(mpf_intel.physptr, 0u32);
|
||||
assert_eq!(mpf_intel.length, 0u8);
|
||||
assert_eq!(mpf_intel.specification, 0u8);
|
||||
assert_eq!(mpf_intel.checksum, 0u8);
|
||||
assert_eq!(mpf_intel.feature1, 0u8);
|
||||
assert_eq!(mpf_intel.feature2, 0u8);
|
||||
assert_eq!(mpf_intel.feature3, 0u8);
|
||||
assert_eq!(mpf_intel.feature4, 0u8);
|
||||
assert_eq!(mpf_intel.feature5, 0u8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpf_intel() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpf_intel>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(mpf_intel))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpf_intel>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpf_intel))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(signature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).physptr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(physptr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).length as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(length)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).specification as *const _ as usize },
|
||||
9usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(specification)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).checksum as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(checksum)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).feature1 as *const _ as usize },
|
||||
11usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature1)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).feature2 as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature2)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).feature3 as *const _ as usize },
|
||||
13usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature3)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).feature4 as *const _ as usize },
|
||||
14usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature4)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpf_intel>())).feature5 as *const _ as usize },
|
||||
15usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpf_intel),
|
||||
"::",
|
||||
stringify!(feature5)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
impl Clone for mpf_intel {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_table {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub spec: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub oem: [::std::os::raw::c_char; 8usize],
|
||||
pub productid: [::std::os::raw::c_char; 12usize],
|
||||
pub oemptr: ::std::os::raw::c_uint,
|
||||
pub oemsize: ::std::os::raw::c_ushort,
|
||||
pub oemcount: ::std::os::raw::c_ushort,
|
||||
pub lapic: ::std::os::raw::c_uint,
|
||||
pub reserved: ::std::os::raw::c_uint,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_mpc_table() {
|
||||
let mpc_table = mpc_table::default();
|
||||
assert_eq!(mpc_table.signature, [0i8, 0i8, 0i8, 0i8]);
|
||||
assert_eq!(mpc_table.length, 0u16);
|
||||
assert_eq!(mpc_table.spec, 0i8);
|
||||
assert_eq!(mpc_table.checksum, 0i8);
|
||||
assert_eq!(mpc_table.oem, [0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8]);
|
||||
assert_eq!(
|
||||
mpc_table.productid,
|
||||
[0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8]
|
||||
);
|
||||
assert_eq!(mpc_table.oemptr, 0u32);
|
||||
assert_eq!(mpc_table.oemsize, 0u16);
|
||||
assert_eq!(mpc_table.oemcount, 0u16);
|
||||
assert_eq!(mpc_table.lapic, 0u32);
|
||||
assert_eq!(mpc_table.reserved, 0u32);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_table() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_table>(),
|
||||
44usize,
|
||||
concat!("Size of: ", stringify!(mpc_table))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_table>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpc_table))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(signature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(length)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).spec as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(spec)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(checksum)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).oem as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oem)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).productid as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(productid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).oemptr as *const _ as usize },
|
||||
28usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oemptr)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).oemsize as *const _ as usize },
|
||||
32usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oemsize)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).oemcount as *const _ as usize },
|
||||
34usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(oemcount)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).lapic as *const _ as usize },
|
||||
36usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(lapic)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_table>())).reserved as *const _ as usize },
|
||||
40usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_table),
|
||||
"::",
|
||||
stringify!(reserved)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_table {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_cpu {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub apicid: ::std::os::raw::c_uchar,
|
||||
pub apicver: ::std::os::raw::c_uchar,
|
||||
pub cpuflag: ::std::os::raw::c_uchar,
|
||||
pub cpufeature: ::std::os::raw::c_uint,
|
||||
pub featureflag: ::std::os::raw::c_uint,
|
||||
pub reserved: [::std::os::raw::c_uint; 2usize],
|
||||
}
|
||||
#[test]
|
||||
fn default_mpc_cpu() {
|
||||
let mpc_cpu = mpc_cpu::default();
|
||||
assert_eq!(mpc_cpu.type_, 0u8);
|
||||
assert_eq!(mpc_cpu.apicid, 0u8);
|
||||
assert_eq!(mpc_cpu.apicver, 0u8);
|
||||
assert_eq!(mpc_cpu.cpuflag, 0u8);
|
||||
assert_eq!(mpc_cpu.cpufeature, 0u32);
|
||||
assert_eq!(mpc_cpu.featureflag, 0u32);
|
||||
assert_eq!(mpc_cpu.reserved, [0u32, 0u32]);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_cpu() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_cpu>(),
|
||||
20usize,
|
||||
concat!("Size of: ", stringify!(mpc_cpu))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_cpu>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpc_cpu))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(apicid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(apicver)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).cpuflag as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(cpuflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).cpufeature as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(cpufeature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).featureflag as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(featureflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_cpu>())).reserved as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_cpu),
|
||||
"::",
|
||||
stringify!(reserved)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_cpu {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_bus {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub busid: ::std::os::raw::c_uchar,
|
||||
pub bustype: [::std::os::raw::c_uchar; 6usize],
|
||||
}
|
||||
#[test]
|
||||
fn default_mpc_bus() {
|
||||
let mpc_bus = mpc_bus::default();
|
||||
assert_eq!(mpc_bus.type_, 0u8);
|
||||
assert_eq!(mpc_bus.busid, 0u8);
|
||||
assert_eq!(mpc_bus.bustype, [0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_bus() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_bus>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_bus))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_bus>(),
|
||||
1usize,
|
||||
concat!("Alignment of ", stringify!(mpc_bus))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_bus>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_bus),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_bus>())).busid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_bus),
|
||||
"::",
|
||||
stringify!(busid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_bus>())).bustype as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_bus),
|
||||
"::",
|
||||
stringify!(bustype)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_bus {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_ioapic {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub apicid: ::std::os::raw::c_uchar,
|
||||
pub apicver: ::std::os::raw::c_uchar,
|
||||
pub flags: ::std::os::raw::c_uchar,
|
||||
pub apicaddr: ::std::os::raw::c_uint,
|
||||
}
|
||||
#[test]
|
||||
fn default_mpc_ioapic() {
|
||||
let mpc_ioapic = mpc_ioapic::default();
|
||||
assert_eq!(mpc_ioapic.type_, 0u8);
|
||||
assert_eq!(mpc_ioapic.apicid, 0u8);
|
||||
assert_eq!(mpc_ioapic.apicver, 0u8);
|
||||
assert_eq!(mpc_ioapic.flags, 0u8);
|
||||
assert_eq!(mpc_ioapic.apicaddr, 0u32);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_ioapic() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_ioapic>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_ioapic))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_ioapic>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(mpc_ioapic))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_ioapic>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_ioapic>())).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(apicid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_ioapic>())).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(apicver)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_ioapic>())).flags as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(flags)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_ioapic>())).apicaddr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_ioapic),
|
||||
"::",
|
||||
stringify!(apicaddr)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_ioapic {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_intsrc {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub irqtype: ::std::os::raw::c_uchar,
|
||||
pub irqflag: ::std::os::raw::c_ushort,
|
||||
pub srcbus: ::std::os::raw::c_uchar,
|
||||
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||
pub dstapic: ::std::os::raw::c_uchar,
|
||||
pub dstirq: ::std::os::raw::c_uchar,
|
||||
}
|
||||
#[test]
|
||||
fn default_mpc_intsrc() {
|
||||
let mpc_intsrc = mpc_intsrc::default();
|
||||
assert_eq!(mpc_intsrc.type_, 0u8);
|
||||
assert_eq!(mpc_intsrc.irqtype, 0u8);
|
||||
assert_eq!(mpc_intsrc.irqflag, 0u16);
|
||||
assert_eq!(mpc_intsrc.srcbus, 0u8);
|
||||
assert_eq!(mpc_intsrc.srcbusirq, 0u8);
|
||||
assert_eq!(mpc_intsrc.dstapic, 0u8);
|
||||
assert_eq!(mpc_intsrc.dstirq, 0u8);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_intsrc() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_intsrc>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_intsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_intsrc>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(mpc_intsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(irqtype)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(irqflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).srcbus as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(srcbus)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(srcbusirq)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).dstapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(dstapic)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_intsrc>())).dstirq as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_intsrc),
|
||||
"::",
|
||||
stringify!(dstirq)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_intsrc {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
pub const mp_irq_source_types_mp_INT: mp_irq_source_types = 0;
|
||||
pub const mp_irq_source_types_mp_NMI: mp_irq_source_types = 1;
|
||||
pub const mp_irq_source_types_mp_SMI: mp_irq_source_types = 2;
|
||||
pub const mp_irq_source_types_mp_ExtINT: mp_irq_source_types = 3;
|
||||
pub type mp_irq_source_types = ::std::os::raw::c_uint;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_lintsrc {
|
||||
pub type_: ::std::os::raw::c_uchar,
|
||||
pub irqtype: ::std::os::raw::c_uchar,
|
||||
pub irqflag: ::std::os::raw::c_ushort,
|
||||
pub srcbusid: ::std::os::raw::c_uchar,
|
||||
pub srcbusirq: ::std::os::raw::c_uchar,
|
||||
pub destapic: ::std::os::raw::c_uchar,
|
||||
pub destapiclint: ::std::os::raw::c_uchar,
|
||||
}
|
||||
#[test]
|
||||
fn default_mpc_lintsrc() {
|
||||
let mpc_lintsrc = mpc_lintsrc::default();
|
||||
assert_eq!(mpc_lintsrc.type_, 0u8);
|
||||
assert_eq!(mpc_lintsrc.irqtype, 0u8);
|
||||
assert_eq!(mpc_lintsrc.irqflag, 0u16);
|
||||
assert_eq!(mpc_lintsrc.srcbusid, 0u8);
|
||||
assert_eq!(mpc_lintsrc.srcbusirq, 0u8);
|
||||
assert_eq!(mpc_lintsrc.destapic, 0u8);
|
||||
assert_eq!(mpc_lintsrc.destapiclint, 0u8);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_lintsrc() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_lintsrc>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(mpc_lintsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_lintsrc>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(mpc_lintsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(type_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(irqtype)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(irqflag)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).srcbusid as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(srcbusid)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(srcbusirq)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).destapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(destapic)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_lintsrc>())).destapiclint as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_lintsrc),
|
||||
"::",
|
||||
stringify!(destapiclint)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_lintsrc {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy)]
|
||||
pub struct mpc_oemtable {
|
||||
pub signature: [::std::os::raw::c_char; 4usize],
|
||||
pub length: ::std::os::raw::c_ushort,
|
||||
pub rev: ::std::os::raw::c_char,
|
||||
pub checksum: ::std::os::raw::c_char,
|
||||
pub mpc: [::std::os::raw::c_char; 8usize],
|
||||
}
|
||||
#[test]
|
||||
fn default_mpc_oemtable() {
|
||||
let mpc_oemtable = mpc_oemtable::default();
|
||||
assert_eq!(mpc_oemtable.signature, [0i8, 0i8, 0i8, 0i8]);
|
||||
assert_eq!(mpc_oemtable.length, 0u16);
|
||||
assert_eq!(mpc_oemtable.rev, 0i8);
|
||||
assert_eq!(mpc_oemtable.checksum, 0i8);
|
||||
assert_eq!(mpc_oemtable.mpc, [0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8]);
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_mpc_oemtable() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<mpc_oemtable>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(mpc_oemtable))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<mpc_oemtable>(),
|
||||
2usize,
|
||||
concat!("Alignment of ", stringify!(mpc_oemtable))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_oemtable>())).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(signature)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_oemtable>())).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(length)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_oemtable>())).rev as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(rev)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_oemtable>())).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(checksum)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(std::ptr::null::<mpc_oemtable>())).mpc as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
stringify!(mpc_oemtable),
|
||||
"::",
|
||||
stringify!(mpc)
|
||||
)
|
||||
);
|
||||
}
|
||||
impl Clone for mpc_oemtable {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
pub const mp_bustype_MP_BUS_ISA: mp_bustype = 1;
|
||||
pub const mp_bustype_MP_BUS_EISA: mp_bustype = 2;
|
||||
pub const mp_bustype_MP_BUS_PCI: mp_bustype = 3;
|
||||
pub type mp_bustype = ::std::os::raw::c_uint;
|
||||
523
src/dragonball/src/dbs_boot/src/x86_64/mptable.rs
Normal file
523
src/dragonball/src/dbs_boot/src/x86_64/mptable.rs
Normal file
@@ -0,0 +1,523 @@
|
||||
// Copyright 2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! MP Table configurations used for defining VM boot status.
|
||||
|
||||
use libc::c_char;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
use std::slice;
|
||||
|
||||
use super::mpspec;
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpcBusWrapper(mpspec::mpc_bus);
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpcCpuWrapper(mpspec::mpc_cpu);
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpcIntsrcWrapper(mpspec::mpc_intsrc);
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpcIoapicWrapper(mpspec::mpc_ioapic);
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpcTableWrapper(mpspec::mpc_table);
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpcLintsrcWrapper(mpspec::mpc_lintsrc);
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MpfIntelWrapper(mpspec::mpf_intel);
|
||||
|
||||
// These `mpspec` wrapper types are only data, reading them from data is a safe initialization.
|
||||
unsafe impl ByteValued for MpcBusWrapper {}
|
||||
unsafe impl ByteValued for MpcCpuWrapper {}
|
||||
unsafe impl ByteValued for MpcIntsrcWrapper {}
|
||||
unsafe impl ByteValued for MpcIoapicWrapper {}
|
||||
unsafe impl ByteValued for MpcTableWrapper {}
|
||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
// MPTABLE, describing VCPUS.
|
||||
const MPTABLE_START: u64 = 0x9fc00;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
|
||||
/// MP Table related errors
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire MP table.
|
||||
#[error("too little guest memory to store the entire MP table")]
|
||||
NotEnoughMemory,
|
||||
|
||||
/// The MP table has too little address space to be stored.
|
||||
#[error("the MP table has no enough space")]
|
||||
AddressOverflow,
|
||||
|
||||
/// Failure while zeroing out the memory for the MP table.
|
||||
#[error("failure while zeroing out the memory for the MP table")]
|
||||
Clear,
|
||||
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
#[error("number of CPUs exceeds the maximum supported CPUs")]
|
||||
TooManyCpus,
|
||||
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
#[error("number of boot CPUs exceeds the maximum number of CPUs")]
|
||||
TooManyBootCpus,
|
||||
|
||||
/// Failure to write the MP floating pointer.
|
||||
#[error("failure to write the MP floating pointer")]
|
||||
WriteMpfIntel,
|
||||
|
||||
/// Failure to write MP CPU entry.
|
||||
#[error("failure to write MP CPU entry")]
|
||||
WriteMpcCpu,
|
||||
|
||||
/// Failure to write MP ioapic entry.
|
||||
#[error("failure to write MP ioapic entry")]
|
||||
WriteMpcIoapic,
|
||||
|
||||
/// Failure to write MP bus entry.
|
||||
#[error("failure to write MP bus entry")]
|
||||
WriteMpcBus,
|
||||
|
||||
/// Failure to write MP interrupt source entry.
|
||||
#[error("failure to write MP interrupt source entry")]
|
||||
WriteMpcIntsrc,
|
||||
|
||||
/// Failure to write MP local interrupt source entry.
|
||||
#[error("failure to write MP local interrupt source entry")]
|
||||
WriteMpcLintsrc,
|
||||
|
||||
/// Failure to write MP OEM table entry.
|
||||
#[error("failure to write MP OEM table entry")]
|
||||
WriteMpcOemtable,
|
||||
|
||||
/// Failure to write MP table header.
|
||||
#[error("failure to write MP table header")]
|
||||
WriteMpcTable,
|
||||
}
|
||||
|
||||
/// Generic type for MP Table Results.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// With APIC/xAPIC, there are only 255 APIC IDs available. And IOAPIC occupies
|
||||
/// one APIC ID, so only 254 CPUs at maximum may be supported. Actually it's
|
||||
/// a large number for Dragonball usecases.
|
||||
pub const MAX_SUPPORTED_CPUS: u32 = 254;
|
||||
|
||||
// Convenience macro for making arrays of diverse character types.
|
||||
macro_rules! char_array {
|
||||
($t:ty; $( $c:expr ),*) => ( [ $( $c as $t ),* ] )
|
||||
}
|
||||
|
||||
// Most of these variables are sourced from the Intel MP Spec 1.4.
|
||||
const SMP_MAGIC_IDENT: [c_char; 4] = char_array!(c_char; '_', 'M', 'P', '_');
|
||||
const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P');
|
||||
const MPC_SPEC: i8 = 4;
|
||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'A', 'L', 'I', 'C', 'L', 'O', 'U', 'D');
|
||||
const MPC_PRODUCT_ID: [c_char; 12] =
|
||||
char_array!(c_char; 'D', 'R', 'A', 'G', 'O', 'N', 'B', 'A', 'L', 'L', '1', '0');
|
||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||
const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec0_0000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee0_0000; // source: linux/arch/x86/include/asm/apicdef.h
|
||||
|
||||
/// APIC version in mptable
|
||||
pub const APIC_VERSION: u8 = 0x14;
|
||||
|
||||
const CPU_STEPPING: u32 = 0x600;
|
||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||
const CPU_FEATURE_FPU: u32 = 0x001;
|
||||
|
||||
const BUS_ID_ISA: u8 = 0;
|
||||
|
||||
fn compute_checksum<T: Copy>(v: &T) -> u8 {
|
||||
// Safe because we are only reading the bytes within the size of the `T` reference `v`.
|
||||
let v_slice = unsafe { slice::from_raw_parts(v as *const T as *const u8, mem::size_of::<T>()) };
|
||||
let mut checksum: u8 = 0;
|
||||
for i in v_slice.iter() {
|
||||
checksum = checksum.wrapping_add(*i);
|
||||
}
|
||||
checksum
|
||||
}
|
||||
|
||||
fn mpf_intel_compute_checksum(v: &mpspec::mpf_intel) -> u8 {
|
||||
let checksum = compute_checksum(v).wrapping_sub(v.checksum);
|
||||
(!checksum).wrapping_add(1)
|
||||
}
|
||||
|
||||
fn compute_mp_size(num_cpus: u8) -> usize {
|
||||
mem::size_of::<MpfIntelWrapper>()
|
||||
+ mem::size_of::<MpcTableWrapper>()
|
||||
+ mem::size_of::<MpcCpuWrapper>() * (num_cpus as usize)
|
||||
+ mem::size_of::<MpcIoapicWrapper>()
|
||||
+ mem::size_of::<MpcBusWrapper>() * 2
|
||||
+ mem::size_of::<MpcIntsrcWrapper>() * 16
|
||||
+ mem::size_of::<MpcLintsrcWrapper>() * 2
|
||||
}
|
||||
|
||||
/// Performs setup of the MP table for the given `num_cpus`
|
||||
pub fn setup_mptable<M: GuestMemory>(mem: &M, boot_cpus: u8, max_cpus: u8) -> Result<()> {
|
||||
if boot_cpus > max_cpus {
|
||||
return Err(Error::TooManyBootCpus);
|
||||
}
|
||||
if u32::from(max_cpus) > MAX_SUPPORTED_CPUS {
|
||||
return Err(Error::TooManyCpus);
|
||||
}
|
||||
|
||||
// Used to keep track of the next base pointer into the MP table.
|
||||
let mut base_mp = GuestAddress(MPTABLE_START);
|
||||
|
||||
let mp_size = compute_mp_size(max_cpus);
|
||||
|
||||
let mut checksum: u8 = 0;
|
||||
let ioapicid: u8 = max_cpus + 1;
|
||||
|
||||
// The checked_add here ensures the all of the following base_mp.unchecked_add's will be without
|
||||
// overflow.
|
||||
if let Some(end_mp) = base_mp.checked_add((mp_size - 1) as u64) {
|
||||
if !mem.address_in_range(end_mp) {
|
||||
return Err(Error::NotEnoughMemory);
|
||||
}
|
||||
} else {
|
||||
return Err(Error::AddressOverflow);
|
||||
}
|
||||
|
||||
mem.read_from(base_mp, &mut io::repeat(0), mp_size)
|
||||
.map_err(|_| Error::Clear)?;
|
||||
|
||||
{
|
||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||
let size = mem::size_of::<MpfIntelWrapper>() as u64;
|
||||
mpf_intel.0.signature = SMP_MAGIC_IDENT;
|
||||
mpf_intel.0.length = 1;
|
||||
mpf_intel.0.specification = 4;
|
||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||
mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0);
|
||||
mem.write_obj(mpf_intel, base_mp)
|
||||
.map_err(|_| Error::WriteMpfIntel)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
}
|
||||
|
||||
// We set the location of the mpc_table here but we can't fill it out until we have the length
|
||||
// of the entire table later.
|
||||
let table_base = base_mp;
|
||||
base_mp = base_mp.unchecked_add(mem::size_of::<MpcTableWrapper>() as u64);
|
||||
|
||||
{
|
||||
let size = mem::size_of::<MpcCpuWrapper>() as u64;
|
||||
for cpu_id in 0..max_cpus {
|
||||
let mut mpc_cpu = MpcCpuWrapper(mpspec::mpc_cpu::default());
|
||||
mpc_cpu.0.type_ = mpspec::MP_PROCESSOR as u8;
|
||||
mpc_cpu.0.apicid = cpu_id;
|
||||
mpc_cpu.0.apicver = APIC_VERSION;
|
||||
if cpu_id < boot_cpus {
|
||||
mpc_cpu.0.cpuflag |= mpspec::CPU_ENABLED as u8;
|
||||
}
|
||||
if cpu_id == 0 {
|
||||
mpc_cpu.0.cpuflag |= mpspec::CPU_BOOTPROCESSOR as u8;
|
||||
}
|
||||
mpc_cpu.0.cpufeature = CPU_STEPPING;
|
||||
mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
|
||||
mem.write_obj(mpc_cpu, base_mp)
|
||||
.map_err(|_| Error::WriteMpcCpu)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let size = mem::size_of::<MpcBusWrapper>() as u64;
|
||||
let mut mpc_bus = MpcBusWrapper(mpspec::mpc_bus::default());
|
||||
mpc_bus.0.type_ = mpspec::MP_BUS as u8;
|
||||
mpc_bus.0.busid = BUS_ID_ISA;
|
||||
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||
mem.write_obj(mpc_bus, base_mp)
|
||||
.map_err(|_| Error::WriteMpcBus)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
|
||||
}
|
||||
|
||||
{
|
||||
let size = mem::size_of::<MpcIoapicWrapper>() as u64;
|
||||
let mut mpc_ioapic = MpcIoapicWrapper(mpspec::mpc_ioapic::default());
|
||||
mpc_ioapic.0.type_ = mpspec::MP_IOAPIC as u8;
|
||||
mpc_ioapic.0.apicid = ioapicid;
|
||||
mpc_ioapic.0.apicver = APIC_VERSION;
|
||||
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
|
||||
mpc_ioapic.0.apicaddr = IO_APIC_DEFAULT_PHYS_BASE;
|
||||
mem.write_obj(mpc_ioapic, base_mp)
|
||||
.map_err(|_| Error::WriteMpcIoapic)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0));
|
||||
}
|
||||
// Per kvm_setup_default_irq_routing() in kernel
|
||||
for i in 0..16 {
|
||||
let size = mem::size_of::<MpcIntsrcWrapper>() as u64;
|
||||
let mut mpc_intsrc = MpcIntsrcWrapper(mpspec::mpc_intsrc::default());
|
||||
mpc_intsrc.0.type_ = mpspec::MP_INTSRC as u8;
|
||||
mpc_intsrc.0.irqtype = mpspec::mp_irq_source_types_mp_INT as u8;
|
||||
mpc_intsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||
mpc_intsrc.0.srcbus = BUS_ID_ISA;
|
||||
mpc_intsrc.0.srcbusirq = i;
|
||||
mpc_intsrc.0.dstapic = ioapicid;
|
||||
mpc_intsrc.0.dstirq = i;
|
||||
mem.write_obj(mpc_intsrc, base_mp)
|
||||
.map_err(|_| Error::WriteMpcIntsrc)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
|
||||
}
|
||||
{
|
||||
let size = mem::size_of::<MpcLintsrcWrapper>() as u64;
|
||||
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
||||
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
||||
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_ExtINT as u8;
|
||||
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||
mpc_lintsrc.0.srcbusid = 0;
|
||||
mpc_lintsrc.0.srcbusirq = 0;
|
||||
mpc_lintsrc.0.destapic = 0;
|
||||
mpc_lintsrc.0.destapiclint = 0;
|
||||
mem.write_obj(mpc_lintsrc, base_mp)
|
||||
.map_err(|_| Error::WriteMpcLintsrc)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||
}
|
||||
{
|
||||
let size = mem::size_of::<MpcLintsrcWrapper>() as u64;
|
||||
let mut mpc_lintsrc = MpcLintsrcWrapper(mpspec::mpc_lintsrc::default());
|
||||
mpc_lintsrc.0.type_ = mpspec::MP_LINTSRC as u8;
|
||||
mpc_lintsrc.0.irqtype = mpspec::mp_irq_source_types_mp_NMI as u8;
|
||||
mpc_lintsrc.0.irqflag = mpspec::MP_IRQDIR_DEFAULT as u16;
|
||||
mpc_lintsrc.0.srcbusid = 0;
|
||||
mpc_lintsrc.0.srcbusirq = 0;
|
||||
mpc_lintsrc.0.destapic = 0xFF; /* to all local APICs */
|
||||
mpc_lintsrc.0.destapiclint = 1;
|
||||
mem.write_obj(mpc_lintsrc, base_mp)
|
||||
.map_err(|_| Error::WriteMpcLintsrc)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||
}
|
||||
|
||||
// At this point we know the size of the mp_table.
|
||||
let table_end = base_mp;
|
||||
|
||||
let mpc_table_size = mem::size_of::<MpcTableWrapper>() as u64;
|
||||
base_mp = base_mp.unchecked_add(mpc_table_size);
|
||||
let oem_count = 0;
|
||||
let oem_size = 0;
|
||||
let oem_ptr = base_mp;
|
||||
|
||||
{
|
||||
let mut mpc_table = MpcTableWrapper(mpspec::mpc_table::default());
|
||||
mpc_table.0.signature = MPC_SIGNATURE;
|
||||
// it's safe to use unchecked_offset_from because
|
||||
// table_end > table_base
|
||||
mpc_table.0.length = table_end.unchecked_offset_from(table_base) as u16;
|
||||
mpc_table.0.spec = MPC_SPEC;
|
||||
mpc_table.0.oem = MPC_OEM;
|
||||
mpc_table.0.oemcount = oem_count;
|
||||
mpc_table.0.oemptr = oem_ptr.0 as u32;
|
||||
mpc_table.0.oemsize = oem_size as u16;
|
||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||
mpc_table.0.lapic = APIC_DEFAULT_PHYS_BASE;
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||
mem.write_obj(mpc_table, table_base)
|
||||
.map_err(|_| Error::WriteMpcTable)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
match u32::from(type_) {
|
||||
mpspec::MP_PROCESSOR => mem::size_of::<MpcCpuWrapper>(),
|
||||
mpspec::MP_BUS => mem::size_of::<MpcBusWrapper>(),
|
||||
mpspec::MP_IOAPIC => mem::size_of::<MpcIoapicWrapper>(),
|
||||
mpspec::MP_INTSRC => mem::size_of::<MpcIntsrcWrapper>(),
|
||||
mpspec::MP_LINTSRC => mem::size_of::<MpcLintsrcWrapper>(),
|
||||
_ => panic!("unrecognized mpc table entry type: {}", type_),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounds_check() {
|
||||
let num_cpus = 4;
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(num_cpus),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus, num_cpus).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounds_check_fails() {
|
||||
let num_cpus = 4;
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(num_cpus) - 1,
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
assert!(setup_mptable(&mem, num_cpus, num_cpus).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpf_intel_checksum() {
|
||||
let num_cpus = 1;
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(num_cpus),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus, num_cpus).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(GuestAddress(MPTABLE_START)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
mpf_intel_compute_checksum(&mpf_intel.0),
|
||||
mpf_intel.0.checksum
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mpc_table_checksum() {
|
||||
let num_cpus = 4;
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(num_cpus),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus, num_cpus).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(GuestAddress(MPTABLE_START)).unwrap();
|
||||
let mpc_offset = GuestAddress(u64::from(mpf_intel.0.physptr));
|
||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||
|
||||
struct Sum(u8);
|
||||
impl io::Write for Sum {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
for v in buf.iter() {
|
||||
self.0 = self.0.wrapping_add(*v);
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut sum = Sum(0);
|
||||
mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize)
|
||||
.unwrap();
|
||||
assert_eq!(sum.0, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_cpu_entry_count() {
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||
setup_mptable(&mem, i, i).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(GuestAddress(MPTABLE_START)).unwrap();
|
||||
let mpc_offset = GuestAddress(u64::from(mpf_intel.0.physptr));
|
||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||
let mpc_end = mpc_offset
|
||||
.checked_add(u64::from(mpc_table.0.length))
|
||||
.unwrap();
|
||||
|
||||
let mut entry_offset = mpc_offset
|
||||
.checked_add(mem::size_of::<MpcTableWrapper>() as u64)
|
||||
.unwrap();
|
||||
let mut max_cpu_count = 0;
|
||||
while entry_offset < mpc_end {
|
||||
let entry_type: u8 = mem.read_obj(entry_offset).unwrap();
|
||||
entry_offset = entry_offset
|
||||
.checked_add(table_entry_size(entry_type) as u64)
|
||||
.unwrap();
|
||||
assert!(entry_offset <= mpc_end);
|
||||
if u32::from(entry_type) == mpspec::MP_PROCESSOR {
|
||||
max_cpu_count += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(max_cpu_count, i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boot_cpu_entry_count() {
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||
setup_mptable(&mem, i, MAX_SUPPORTED_CPUS as u8).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(GuestAddress(MPTABLE_START)).unwrap();
|
||||
let mpc_offset = GuestAddress(u64::from(mpf_intel.0.physptr));
|
||||
let mpc_table: MpcTableWrapper = mem.read_obj(mpc_offset).unwrap();
|
||||
let mpc_end = mpc_offset
|
||||
.checked_add(u64::from(mpc_table.0.length))
|
||||
.unwrap();
|
||||
|
||||
let mut entry_offset = mpc_offset
|
||||
.checked_add(mem::size_of::<MpcTableWrapper>() as u64)
|
||||
.unwrap();
|
||||
let mut boot_cpu_count = 0;
|
||||
for _ in 0..MAX_SUPPORTED_CPUS {
|
||||
let mpc_cpu: MpcCpuWrapper = mem.read_obj(entry_offset).unwrap();
|
||||
if mpc_cpu.0.cpuflag & mpspec::CPU_ENABLED as u8 != 0 {
|
||||
boot_cpu_count += 1;
|
||||
}
|
||||
entry_offset = entry_offset
|
||||
.checked_add(table_entry_size(mpc_cpu.0.type_) as u64)
|
||||
.unwrap();
|
||||
assert!(entry_offset <= mpc_end);
|
||||
}
|
||||
assert_eq!(boot_cpu_count, i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cpu_entry_count_max() {
|
||||
let cpus = MAX_SUPPORTED_CPUS + 1;
|
||||
let mem = GuestMemoryMmap::<()>::from_ranges(&[(
|
||||
GuestAddress(MPTABLE_START),
|
||||
compute_mp_size(cpus as u8),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
let result = setup_mptable(&mem, cpus as u8, cpus as u8).unwrap_err();
|
||||
assert_eq!(result, Error::TooManyCpus);
|
||||
}
|
||||
}
|
||||
14
src/dragonball/src/dbs_device/Cargo.toml
Normal file
14
src/dragonball/src/dbs_device/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "dbs-device"
|
||||
version = "0.2.0"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
description = "Device model for Dragonball Sandbox"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox/tree/main/crates/dbs-device"
|
||||
keywords = ["dragonball", "secure-sandbox", "device", "resource"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1"
|
||||
1
src/dragonball/src/dbs_device/LICENSE
Symbolic link
1
src/dragonball/src/dbs_device/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
141
src/dragonball/src/dbs_device/README.md
Normal file
141
src/dragonball/src/dbs_device/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# dbs-device
|
||||
|
||||
The `dbs-device` crate, as a counterpart of [`vm-device`], defines device model for the Dragonball Secure Sandbox.
|
||||
The `dbs-device` crate shares some common concepts and data structures with [`vm-device`], but it also diverges from
|
||||
[`vm-device`] due to different VMM designs.
|
||||
|
||||
The dbs-device crate provides:
|
||||
|
||||
- [`DeviceIo`] and [`DeviceIoMut`]: trait for device to handle trapped MMIO/PIO access requests.
|
||||
- [`IoManager`]: IO manager to handle trapped MMIO/PIO access requests.
|
||||
- [`IoManagerContext`]: trait for IO manager context object to support device hotplug at runtime.
|
||||
- [`ResourceConstraint`], [Resource] and [`DeviceResources`]: resource allocation requirements and constraints.
|
||||
|
||||
## Design
|
||||
|
||||
The dbs-device crate is designed to support the virtual machine's device model.
|
||||
|
||||
The core concepts of device model are [Port I/O](https://wiki.osdev.org/I/O_Ports) and
|
||||
[Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O),
|
||||
which are two main methods of performing I/O between CPU and devices.
|
||||
|
||||
The device model provided by the dbs-device crate works as below:
|
||||
- The VMM creates a global resource manager, device manager and IO manager.
|
||||
- The device manager creates virtual devices configured by the VMM
|
||||
- create device object
|
||||
- query device allocation requirements and constraints, the device returns an array of [`ResourceConstraint`].
|
||||
- allocate resources for device from the resource manager, resource manager returns a [`DeviceResources`] object.
|
||||
- assign the allocated resources to the device.
|
||||
- The device manager register devices to the IO manager.
|
||||
- query trapped address ranges by calling [`DeviceIo::get_trapped_io_resources()`]
|
||||
- register the device to the IO manager with trapped address range
|
||||
- The guest access those trapped MMIO/PIO address ranges, and triggers VM IO Exit events to trap into the VMM.
|
||||
- The VMM parses the VM exit events and dispatch those events to the IO manager.
|
||||
- The IO manager looks up device by searching trapped address ranges, and call the device's [`DeviceIO`]
|
||||
handler to process those trapped MMIO/PIO access requests.
|
||||
|
||||
## Usage
|
||||
|
||||
First, a VM needs to create an [`IoManager`] to help it dispatch I/O events to devices.
|
||||
And an [`IoManager`] has two types of bus, the PIO bus and the MMIO bus, to handle different types of IO.
|
||||
|
||||
Then, when creating a device, it needs to implement the [`DeviceIo`] or [`DeviceIoMut`] trait to receive read or write
|
||||
events send by driver in guest OS:
|
||||
- `read()` and `write()` methods is used to deal with MMIO events
|
||||
- `pio_read()` and `pio_write()` methods is used to deal with PIO events
|
||||
- `get_assigned_resources()` method is used to get all resources assigned to the device
|
||||
- `get_trapped_io_resources()` method is used to get only MMIO/PIO resources assigned to the device
|
||||
|
||||
The difference of [`DeviceIo`] and [`DeviceIoMut`] is the reference type of `self` passed to method:
|
||||
- [`DeviceIo`] trait would pass a immutable reference `&self` to method, so the implementation of device would provide
|
||||
interior mutability and thread-safe protection itself
|
||||
- [`DeviceIoMut`] trait would pass a mutable reference `&mut self` to method, and it can give mutability to device
|
||||
which is wrapped by `Mutex` directly to simplify the difficulty of achieving interior mutability.
|
||||
|
||||
Additionally, the [`DeviceIo`] trait has an auto implement for `Mutex<T: DeviceIoMut>`
|
||||
|
||||
Last, the device needs to be added to [`IoManager`] by using `register_device_io()`, and the function would add device
|
||||
to PIO bus and/or MMIO bus by the resources it have. If a device has not only MMIO resource but PIO resource,
|
||||
it would be added to both pio bus and mmio bus. So the device would wrapped by `Arc<T>`.
|
||||
|
||||
From now on, the [`IoManager`] will dispatch I/O requests for the registered address ranges to the device.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
|
||||
use dbs_device::device_manager::IoManager;
|
||||
use dbs_device::resources::{DeviceResources, Resource};
|
||||
use dbs_device::{DeviceIo, IoAddress, PioAddress};
|
||||
|
||||
struct DummyDevice {}
|
||||
|
||||
impl DeviceIo for DummyDevice {
|
||||
fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]) {
|
||||
println!(
|
||||
"mmio read, base: 0x{:x}, offset: 0x{:x}",
|
||||
base.raw_value(),
|
||||
offset.raw_value()
|
||||
);
|
||||
}
|
||||
|
||||
fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]) {
|
||||
println!(
|
||||
"mmio write, base: 0x{:x}, offset: 0x{:x}",
|
||||
base.raw_value(),
|
||||
offset.raw_value()
|
||||
);
|
||||
}
|
||||
|
||||
fn pio_read(&self, base: PioAddress, offset: PioAddress, data: &mut [u8]) {
|
||||
println!(
|
||||
"pio read, base: 0x{:x}, offset: 0x{:x}",
|
||||
base.raw_value(),
|
||||
offset.raw_value()
|
||||
);
|
||||
}
|
||||
|
||||
fn pio_write(&self, base: PioAddress, offset: PioAddress, data: &[u8]) {
|
||||
println!(
|
||||
"pio write, base: 0x{:x}, offset: 0x{:x}",
|
||||
base.raw_value(),
|
||||
offset.raw_value()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate resources for device
|
||||
let mut resources = DeviceResources::new();
|
||||
resources.append(Resource::MmioAddressRange {
|
||||
base: 0,
|
||||
size: 4096,
|
||||
});
|
||||
resources.append(Resource::PioAddressRange { base: 0, size: 32 });
|
||||
|
||||
// Register device to `IoManager` with resources
|
||||
let device = Arc::new(DummyDevice {});
|
||||
let mut manager = IoManager::new();
|
||||
manager.register_device_io(device, &resources).unwrap();
|
||||
|
||||
// Dispatch I/O event from `IoManager` to device
|
||||
manager.mmio_write(0, &vec![0, 1]).unwrap();
|
||||
|
||||
let mut buffer = vec![0; 4];
|
||||
manager.pio_read(0, &mut buffer);
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
[DeviceIo::get_trapped_io_resources()]: https://docs.rs/dbs-device/0.1.0/dbs_device/trait.DeviceIo.html#method.get_trapped_io_resources
|
||||
[DeviceIo]: src/lib.rs
|
||||
[DeviceIoMut]: src/lib.rs
|
||||
[IoManager]: src/device_manager.rs
|
||||
[IoManagerContext]: src/device_manager.rs
|
||||
[ResourceConstraint]: src/resources.rs
|
||||
[Resource]: src/resources.rs
|
||||
[DeviceResources]: src/resources.rs
|
||||
[vm-device]: https://github.com/rust-vmm/vm-device
|
||||
695
src/dragonball/src/dbs_device/src/device_manager.rs
Normal file
695
src/dragonball/src/dbs_device/src/device_manager.rs
Normal file
@@ -0,0 +1,695 @@
|
||||
// Copyright 2020-2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright © 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! IO Device Manager to handle trapped MMIO/PIO access requests.
|
||||
//!
|
||||
//! The [IoManager](self::IoManager) is responsible for managing all trapped MMIO/PIO accesses for
|
||||
//! virtual devices. It cooperates with the Secure Sandbox/VMM and device drivers to handle trapped
|
||||
//! accesses. The flow is as below:
|
||||
//! - device drivers allocate resources from the VMM/resource manager, including trapped MMIO/PIO
|
||||
//! address ranges.
|
||||
//! - the device manager registers devices to the [IoManager](self::IoManager) with trapped MMIO/PIO
|
||||
//! address ranges.
|
||||
//! - VM IO Exit events get triggered when the guest accesses those trapped address ranges.
|
||||
//! - the vmm handle those VM IO Exit events, and dispatch them to the [IoManager].
|
||||
//! - the [IoManager] invokes registered callbacks/device drivers to handle those accesses, if there
|
||||
//! is a device registered for the address.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Creating a dummy deivce which implement DeviceIo trait, and register it to [IoManager] with
|
||||
//! trapped MMIO/PIO address ranges:
|
||||
//!
|
||||
//! ```
|
||||
//! use std::sync::Arc;
|
||||
//! use std::any::Any;
|
||||
//!
|
||||
//! use dbs_device::device_manager::IoManager;
|
||||
//! use dbs_device::resources::{DeviceResources, Resource};
|
||||
//! use dbs_device::{DeviceIo, IoAddress, PioAddress};
|
||||
//!
|
||||
//! struct DummyDevice {}
|
||||
//!
|
||||
//! impl DeviceIo for DummyDevice {
|
||||
//! fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]) {
|
||||
//! println!(
|
||||
//! "mmio read, base: 0x{:x}, offset: 0x{:x}",
|
||||
//! base.raw_value(),
|
||||
//! offset.raw_value()
|
||||
//! );
|
||||
//! }
|
||||
//!
|
||||
//! fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]) {
|
||||
//! println!(
|
||||
//! "mmio write, base: 0x{:x}, offset: 0x{:x}",
|
||||
//! base.raw_value(),
|
||||
//! offset.raw_value()
|
||||
//! );
|
||||
//! }
|
||||
//!
|
||||
//! fn pio_read(&self, base: PioAddress, offset: PioAddress, data: &mut [u8]) {
|
||||
//! println!(
|
||||
//! "pio read, base: 0x{:x}, offset: 0x{:x}",
|
||||
//! base.raw_value(),
|
||||
//! offset.raw_value()
|
||||
//! );
|
||||
//! }
|
||||
//!
|
||||
//! fn pio_write(&self, base: PioAddress, offset: PioAddress, data: &[u8]) {
|
||||
//! println!(
|
||||
//! "pio write, base: 0x{:x}, offset: 0x{:x}",
|
||||
//! base.raw_value(),
|
||||
//! offset.raw_value()
|
||||
//! );
|
||||
//! }
|
||||
//!
|
||||
//! fn as_any(&self) -> &dyn Any {
|
||||
//! self
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Allocate resources for device
|
||||
//! let mut resources = DeviceResources::new();
|
||||
//! resources.append(Resource::MmioAddressRange {
|
||||
//! base: 0,
|
||||
//! size: 4096,
|
||||
//! });
|
||||
//! resources.append(Resource::PioAddressRange { base: 0, size: 32 });
|
||||
//!
|
||||
//! // Register device to `IoManager` with resources
|
||||
//! let device = Arc::new(DummyDevice {});
|
||||
//! let mut manager = IoManager::new();
|
||||
//! manager.register_device_io(device, &resources).unwrap();
|
||||
//!
|
||||
//! // Dispatch I/O event from `IoManager` to device
|
||||
//! manager.mmio_write(0, &vec![0, 1]).unwrap();
|
||||
//! {
|
||||
//! let mut buffer = vec![0; 4];
|
||||
//! manager.pio_read(0, &mut buffer);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::resources::Resource;
|
||||
use crate::{DeviceIo, IoAddress, IoSize, PioAddress};
|
||||
|
||||
/// Error types for `IoManager` related operations.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// The inserting device overlaps with a current device.
|
||||
#[error("device address conflicts with existing devices")]
|
||||
DeviceOverlap,
|
||||
/// The device doesn't exist.
|
||||
#[error("no such device")]
|
||||
NoDevice,
|
||||
}
|
||||
|
||||
/// A specialized version of [std::result::Result] for [IoManager] realted operations.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Structure representing an IO address range.
|
||||
#[derive(Debug, Copy, Clone, Eq)]
|
||||
pub struct IoRange {
|
||||
base: IoAddress,
|
||||
size: IoSize,
|
||||
}
|
||||
|
||||
impl IoRange {
|
||||
fn new_pio_range(base: u16, size: u16) -> Self {
|
||||
IoRange {
|
||||
base: IoAddress(base as u64),
|
||||
size: IoSize(size as u64),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_mmio_range(base: u64, size: u64) -> Self {
|
||||
IoRange {
|
||||
base: IoAddress(base),
|
||||
size: IoSize(size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for IoRange {
|
||||
fn eq(&self, other: &IoRange) -> bool {
|
||||
self.base == other.base
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for IoRange {
|
||||
fn cmp(&self, other: &IoRange) -> Ordering {
|
||||
self.base.cmp(&other.base)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for IoRange {
|
||||
fn partial_cmp(&self, other: &IoRange) -> Option<Ordering> {
|
||||
self.base.partial_cmp(&other.base)
|
||||
}
|
||||
}
|
||||
|
||||
/// IO manager to handle all trapped MMIO/PIO access requests.
|
||||
///
|
||||
/// All devices handling trapped MMIO/PIO accesses should register themself to the IO manager
|
||||
/// with trapped address ranges. When guest vm accesses those trapped MMIO/PIO address ranges,
|
||||
/// VM IO Exit events will be triggered and the VMM dispatches those events to IO manager.
|
||||
/// And then the registered callbacks will invoked by IO manager.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct IoManager {
|
||||
/// Range mapping for VM exit pio operations.
|
||||
pio_bus: BTreeMap<IoRange, Arc<dyn DeviceIo>>,
|
||||
/// Range mapping for VM exit mmio operations.
|
||||
mmio_bus: BTreeMap<IoRange, Arc<dyn DeviceIo>>,
|
||||
}
|
||||
|
||||
impl IoManager {
|
||||
/// Create a new instance of [IoManager].
|
||||
pub fn new() -> Self {
|
||||
IoManager::default()
|
||||
}
|
||||
|
||||
/// Register a new device to the [IoManager], with trapped MMIO/PIO address ranges.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device`: device object to handle trapped IO access requests
|
||||
/// * `resources`: resources representing trapped MMIO/PIO address ranges. Only MMIO/PIO address
|
||||
/// ranges will be handled, and other types of resource will be ignored. So the caller does
|
||||
/// not need to filter out non-MMIO/PIO resources.
|
||||
pub fn register_device_io(
|
||||
&mut self,
|
||||
device: Arc<dyn DeviceIo>,
|
||||
resources: &[Resource],
|
||||
) -> Result<()> {
|
||||
for (idx, res) in resources.iter().enumerate() {
|
||||
match *res {
|
||||
Resource::PioAddressRange { base, size } => {
|
||||
if self
|
||||
.pio_bus
|
||||
.insert(IoRange::new_pio_range(base, size), device.clone())
|
||||
.is_some()
|
||||
{
|
||||
// Rollback registered resources.
|
||||
self.unregister_device_io(&resources[0..idx])
|
||||
.expect("failed to unregister devices");
|
||||
|
||||
return Err(Error::DeviceOverlap);
|
||||
}
|
||||
}
|
||||
Resource::MmioAddressRange { base, size } => {
|
||||
if self
|
||||
.mmio_bus
|
||||
.insert(IoRange::new_mmio_range(base, size), device.clone())
|
||||
.is_some()
|
||||
{
|
||||
// Rollback registered resources.
|
||||
self.unregister_device_io(&resources[0..idx])
|
||||
.expect("failed to unregister devices");
|
||||
|
||||
return Err(Error::DeviceOverlap);
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unregister a device from `IoManager`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `resources`: resource list containing all trapped address ranges for the device.
|
||||
pub fn unregister_device_io(&mut self, resources: &[Resource]) -> Result<()> {
|
||||
for res in resources.iter() {
|
||||
match *res {
|
||||
Resource::PioAddressRange { base, size } => {
|
||||
self.pio_bus.remove(&IoRange::new_pio_range(base, size));
|
||||
}
|
||||
Resource::MmioAddressRange { base, size } => {
|
||||
self.mmio_bus.remove(&IoRange::new_mmio_range(base, size));
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle VM IO Exit events triggered by trapped MMIO read accesses.
|
||||
///
|
||||
/// Return error if failed to get the device.
|
||||
pub fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Result<()> {
|
||||
self.get_mmio_device(IoAddress(addr))
|
||||
.map(|(device, base)| device.read(base, IoAddress(addr - base.raw_value()), data))
|
||||
.ok_or(Error::NoDevice)
|
||||
}
|
||||
|
||||
/// Handle VM IO Exit events triggered by trapped MMIO write accesses.
|
||||
///
|
||||
/// Return error if failed to get the device.
|
||||
pub fn mmio_write(&self, addr: u64, data: &[u8]) -> Result<()> {
|
||||
self.get_mmio_device(IoAddress(addr))
|
||||
.map(|(device, base)| device.write(base, IoAddress(addr - base.raw_value()), data))
|
||||
.ok_or(Error::NoDevice)
|
||||
}
|
||||
|
||||
/// Get the registered device handling the trapped MMIO address `addr`.
|
||||
fn get_mmio_device(&self, addr: IoAddress) -> Option<(&Arc<dyn DeviceIo>, IoAddress)> {
|
||||
let range = IoRange::new_mmio_range(addr.raw_value(), 0);
|
||||
if let Some((range, dev)) = self.mmio_bus.range(..=&range).nth_back(0) {
|
||||
if (addr.raw_value() - range.base.raw_value()) < range.size.raw_value() {
|
||||
return Some((dev, range.base));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl IoManager {
|
||||
/// Handle VM IO Exit events triggered by trapped PIO read accesses.
|
||||
///
|
||||
/// Return error if failed to get the device.
|
||||
pub fn pio_read(&self, addr: u16, data: &mut [u8]) -> Result<()> {
|
||||
self.get_pio_device(PioAddress(addr))
|
||||
.map(|(device, base)| device.pio_read(base, PioAddress(addr - base.raw_value()), data))
|
||||
.ok_or(Error::NoDevice)
|
||||
}
|
||||
|
||||
/// Handle VM IO Exit events triggered by trapped PIO write accesses.
|
||||
///
|
||||
/// Return error if failed to get the device.
|
||||
pub fn pio_write(&self, addr: u16, data: &[u8]) -> Result<()> {
|
||||
self.get_pio_device(PioAddress(addr))
|
||||
.map(|(device, base)| device.pio_write(base, PioAddress(addr - base.raw_value()), data))
|
||||
.ok_or(Error::NoDevice)
|
||||
}
|
||||
|
||||
/// Get the registered device handling the trapped PIO address `addr`.
|
||||
fn get_pio_device(&self, addr: PioAddress) -> Option<(&Arc<dyn DeviceIo>, PioAddress)> {
|
||||
let range = IoRange::new_pio_range(addr.raw_value(), 0);
|
||||
if let Some((range, dev)) = self.pio_bus.range(..=&range).nth_back(0) {
|
||||
if (addr.raw_value() as u64 - range.base.raw_value()) < range.size.raw_value() {
|
||||
return Some((dev, PioAddress(range.base.0 as u16)));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for IoManager {
|
||||
fn eq(&self, other: &IoManager) -> bool {
|
||||
if self.pio_bus.len() != other.pio_bus.len() {
|
||||
return false;
|
||||
}
|
||||
if self.mmio_bus.len() != other.mmio_bus.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (io_range, device_io) in self.pio_bus.iter() {
|
||||
if !other.pio_bus.contains_key(io_range) {
|
||||
return false;
|
||||
}
|
||||
let other_device_io = &other.pio_bus[io_range];
|
||||
if device_io.get_trapped_io_resources() != other_device_io.get_trapped_io_resources() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (io_range, device_io) in self.mmio_bus.iter() {
|
||||
if !other.mmio_bus.contains_key(io_range) {
|
||||
return false;
|
||||
}
|
||||
let other_device_io = &other.mmio_bus[io_range];
|
||||
if device_io.get_trapped_io_resources() != other_device_io.get_trapped_io_resources() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for IO manager context object to support device hotplug at runtime.
|
||||
///
|
||||
/// The `IoManagerContext` objects are passed to devices by the IO manager, so the devices could
|
||||
/// use it to hot-add/hot-remove other devices at runtime. It provides a transaction mechanism
|
||||
/// to hot-add/hot-remove devices.
|
||||
pub trait IoManagerContext {
|
||||
/// Type of context object passed to the callbacks.
|
||||
type Context;
|
||||
|
||||
/// Begin a transaction and return a context object.
|
||||
///
|
||||
/// The returned context object must be passed to commit_tx() or cancel_tx() later.
|
||||
fn begin_tx(&self) -> Self::Context;
|
||||
|
||||
/// Commit the transaction.
|
||||
fn commit_tx(&self, ctx: Self::Context);
|
||||
|
||||
/// Cancel the transaction.
|
||||
fn cancel_tx(&self, ctx: Self::Context);
|
||||
|
||||
/// Register a new device with its associated resources to the IO manager.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ctx`: context object returned by begin_tx().
|
||||
/// * `device`: device instance object to be registered
|
||||
/// * `resources`: resources representing trapped MMIO/PIO address ranges. Only MMIO/PIO address
|
||||
/// ranges will be handled, and other types of resource will be ignored. So the caller does
|
||||
/// not need to filter out non-MMIO/PIO resources.
|
||||
fn register_device_io(
|
||||
&self,
|
||||
ctx: &mut Self::Context,
|
||||
device: Arc<dyn DeviceIo>,
|
||||
resources: &[Resource],
|
||||
) -> Result<()>;
|
||||
|
||||
/// Unregister a device from the IO manager.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ctx`: context object returned by begin_tx().
|
||||
/// * `resources`: resource list containing all trapped address ranges for the device.
|
||||
fn unregister_device_io(&self, ctx: &mut Self::Context, resources: &[Resource]) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: IoManagerContext> IoManagerContext for Arc<T> {
|
||||
type Context = T::Context;
|
||||
|
||||
fn begin_tx(&self) -> Self::Context {
|
||||
self.deref().begin_tx()
|
||||
}
|
||||
|
||||
fn commit_tx(&self, ctx: Self::Context) {
|
||||
self.deref().commit_tx(ctx)
|
||||
}
|
||||
|
||||
fn cancel_tx(&self, ctx: Self::Context) {
|
||||
self.deref().cancel_tx(ctx)
|
||||
}
|
||||
|
||||
fn register_device_io(
|
||||
&self,
|
||||
ctx: &mut Self::Context,
|
||||
device: Arc<dyn DeviceIo>,
|
||||
resources: &[Resource],
|
||||
) -> std::result::Result<(), Error> {
|
||||
self.deref().register_device_io(ctx, device, resources)
|
||||
}
|
||||
|
||||
fn unregister_device_io(
|
||||
&self,
|
||||
ctx: &mut Self::Context,
|
||||
resources: &[Resource],
|
||||
) -> std::result::Result<(), Error> {
|
||||
self.deref().unregister_device_io(ctx, resources)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::error::Error;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::*;
|
||||
use crate::resources::DeviceResources;
|
||||
|
||||
const PIO_ADDRESS_SIZE: u16 = 4;
|
||||
const PIO_ADDRESS_BASE: u16 = 0x40;
|
||||
const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321;
|
||||
const MMIO_ADDRESS_BASE: u64 = 0x1234_5678;
|
||||
const LEGACY_IRQ: u32 = 4;
|
||||
const CONFIG_DATA: u32 = 0x1234;
|
||||
|
||||
struct DummyDevice {
|
||||
config: Mutex<u32>,
|
||||
}
|
||||
|
||||
impl DummyDevice {
|
||||
fn new(config: u32) -> Self {
|
||||
DummyDevice {
|
||||
config: Mutex::new(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceIo for DummyDevice {
|
||||
fn read(&self, _base: IoAddress, _offset: IoAddress, data: &mut [u8]) {
|
||||
if data.len() > 4 {
|
||||
return;
|
||||
}
|
||||
for (idx, iter) in data.iter_mut().enumerate() {
|
||||
let config = self.config.lock().expect("failed to acquire lock");
|
||||
*iter = (*config >> (idx * 8) & 0xff) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&self, _base: IoAddress, _offset: IoAddress, data: &[u8]) {
|
||||
let mut config = self.config.lock().expect("failed to acquire lock");
|
||||
*config = u32::from(data[0]) & 0xff;
|
||||
}
|
||||
|
||||
fn pio_read(&self, _base: PioAddress, _offset: PioAddress, data: &mut [u8]) {
|
||||
if data.len() > 4 {
|
||||
return;
|
||||
}
|
||||
for (idx, iter) in data.iter_mut().enumerate() {
|
||||
let config = self.config.lock().expect("failed to acquire lock");
|
||||
*iter = (*config >> (idx * 8) & 0xff) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
fn pio_write(&self, _base: PioAddress, _offset: PioAddress, data: &[u8]) {
|
||||
let mut config = self.config.lock().expect("failed to acquire lock");
|
||||
*config = u32::from(data[0]) & 0xff;
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_io_manager() {
|
||||
let mut io_mgr = IoManager::new();
|
||||
let dummy = DummyDevice::new(0);
|
||||
let dum = Arc::new(dummy);
|
||||
|
||||
let mut resource: Vec<Resource> = Vec::new();
|
||||
let mmio = Resource::MmioAddressRange {
|
||||
base: MMIO_ADDRESS_BASE,
|
||||
size: MMIO_ADDRESS_SIZE,
|
||||
};
|
||||
let irq = Resource::LegacyIrq(LEGACY_IRQ);
|
||||
|
||||
resource.push(mmio);
|
||||
resource.push(irq);
|
||||
|
||||
let pio = Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
};
|
||||
resource.push(pio);
|
||||
|
||||
assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());
|
||||
|
||||
let io_mgr2 = io_mgr.clone();
|
||||
assert_eq!(io_mgr2.mmio_bus.len(), 1);
|
||||
|
||||
assert_eq!(io_mgr2.pio_bus.len(), 1);
|
||||
|
||||
let (dev, addr) = io_mgr2
|
||||
.get_mmio_device(IoAddress(MMIO_ADDRESS_BASE + 1))
|
||||
.unwrap();
|
||||
assert_eq!(Arc::strong_count(dev), 5);
|
||||
|
||||
assert_eq!(addr, IoAddress(MMIO_ADDRESS_BASE));
|
||||
|
||||
drop(io_mgr);
|
||||
assert_eq!(Arc::strong_count(dev), 3);
|
||||
|
||||
drop(io_mgr2);
|
||||
assert_eq!(Arc::strong_count(&dum), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_unregister_device_io() {
|
||||
let mut io_mgr = IoManager::new();
|
||||
let dummy = DummyDevice::new(0);
|
||||
let dum = Arc::new(dummy);
|
||||
|
||||
let mut resources = DeviceResources::new();
|
||||
let mmio = Resource::MmioAddressRange {
|
||||
base: MMIO_ADDRESS_BASE,
|
||||
size: MMIO_ADDRESS_SIZE,
|
||||
};
|
||||
let pio = Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
};
|
||||
let irq = Resource::LegacyIrq(LEGACY_IRQ);
|
||||
|
||||
resources.append(mmio);
|
||||
resources.append(pio);
|
||||
resources.append(irq);
|
||||
|
||||
assert!(io_mgr.register_device_io(dum.clone(), &resources).is_ok());
|
||||
assert!(io_mgr.register_device_io(dum, &resources).is_err());
|
||||
assert!(io_mgr.unregister_device_io(&resources).is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmio_read_write() {
|
||||
let mut io_mgr: IoManager = Default::default();
|
||||
let dum = Arc::new(DummyDevice::new(CONFIG_DATA));
|
||||
let mut resource: Vec<Resource> = Vec::new();
|
||||
|
||||
let mmio = Resource::MmioAddressRange {
|
||||
base: MMIO_ADDRESS_BASE,
|
||||
size: MMIO_ADDRESS_SIZE,
|
||||
};
|
||||
resource.push(mmio);
|
||||
assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());
|
||||
|
||||
let mut data = [0; 4];
|
||||
assert!(io_mgr.mmio_read(MMIO_ADDRESS_BASE, &mut data).is_ok());
|
||||
assert_eq!(data, [0x34, 0x12, 0, 0]);
|
||||
|
||||
assert!(io_mgr
|
||||
.mmio_read(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE, &mut data)
|
||||
.is_err());
|
||||
|
||||
data = [0; 4];
|
||||
assert!(io_mgr.mmio_write(MMIO_ADDRESS_BASE, &data).is_ok());
|
||||
assert_eq!(*dum.config.lock().unwrap(), 0);
|
||||
|
||||
assert!(io_mgr
|
||||
.mmio_write(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE, &data)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pio_read_write() {
|
||||
let mut io_mgr: IoManager = Default::default();
|
||||
let dum = Arc::new(DummyDevice::new(CONFIG_DATA));
|
||||
let mut resource: Vec<Resource> = Vec::new();
|
||||
|
||||
let pio = Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
};
|
||||
resource.push(pio);
|
||||
assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok());
|
||||
|
||||
let mut data = [0; 4];
|
||||
assert!(io_mgr.pio_read(PIO_ADDRESS_BASE, &mut data).is_ok());
|
||||
assert_eq!(data, [0x34, 0x12, 0, 0]);
|
||||
|
||||
assert!(io_mgr
|
||||
.pio_read(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &mut data)
|
||||
.is_err());
|
||||
|
||||
data = [0; 4];
|
||||
assert!(io_mgr.pio_write(PIO_ADDRESS_BASE, &data).is_ok());
|
||||
assert_eq!(*dum.config.lock().unwrap(), 0);
|
||||
|
||||
assert!(io_mgr
|
||||
.pio_write(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &data)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_manager_data_structs() {
|
||||
let range1 = IoRange::new_mmio_range(0x1000, 0x1000);
|
||||
let range2 = IoRange::new_mmio_range(0x1000, 0x2000);
|
||||
let range3 = IoRange::new_mmio_range(0x2000, 0x1000);
|
||||
|
||||
assert_eq!(range1, range1.clone());
|
||||
assert_eq!(range1, range2);
|
||||
assert!(range1 < range3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_code() {
|
||||
let err = super::Error::DeviceOverlap;
|
||||
|
||||
assert!(err.source().is_none());
|
||||
assert_eq!(
|
||||
format!("{err}"),
|
||||
"device address conflicts with existing devices"
|
||||
);
|
||||
|
||||
let err = super::Error::NoDevice;
|
||||
assert!(err.source().is_none());
|
||||
assert_eq!(format!("{err:#?}"), "NoDevice");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_manager_partial_eq() {
|
||||
let mut io_mgr1 = IoManager::new();
|
||||
let mut io_mgr2 = IoManager::new();
|
||||
let dummy1 = Arc::new(DummyDevice::new(0));
|
||||
let dummy2 = Arc::new(DummyDevice::new(0));
|
||||
|
||||
let mut resources1 = DeviceResources::new();
|
||||
let mut resources2 = DeviceResources::new();
|
||||
|
||||
let mmio = Resource::MmioAddressRange {
|
||||
base: MMIO_ADDRESS_BASE,
|
||||
size: MMIO_ADDRESS_SIZE,
|
||||
};
|
||||
let pio = Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
};
|
||||
|
||||
resources1.append(mmio.clone());
|
||||
resources1.append(pio.clone());
|
||||
|
||||
resources2.append(mmio);
|
||||
resources2.append(pio);
|
||||
|
||||
io_mgr1.register_device_io(dummy1, &resources1).unwrap();
|
||||
io_mgr2.register_device_io(dummy2, &resources2).unwrap();
|
||||
|
||||
assert!(io_mgr1 == io_mgr2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_manager_partial_neq() {
|
||||
let mut io_mgr1 = IoManager::new();
|
||||
let mut io_mgr2 = IoManager::new();
|
||||
let dummy1 = Arc::new(DummyDevice::new(0));
|
||||
let dummy2 = Arc::new(DummyDevice::new(0));
|
||||
|
||||
let mut resources1 = DeviceResources::new();
|
||||
let mut resources2 = DeviceResources::new();
|
||||
|
||||
let mmio = Resource::MmioAddressRange {
|
||||
base: MMIO_ADDRESS_BASE,
|
||||
size: MMIO_ADDRESS_SIZE,
|
||||
};
|
||||
let pio = Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
};
|
||||
|
||||
resources1.append(mmio.clone());
|
||||
resources1.append(pio);
|
||||
|
||||
resources2.append(mmio);
|
||||
|
||||
io_mgr1.register_device_io(dummy1, &resources1).unwrap();
|
||||
io_mgr2.register_device_io(dummy2, &resources2).unwrap();
|
||||
|
||||
assert!(io_mgr1 != io_mgr2);
|
||||
}
|
||||
}
|
||||
420
src/dragonball/src/dbs_device/src/lib.rs
Normal file
420
src/dragonball/src/dbs_device/src/lib.rs
Normal file
@@ -0,0 +1,420 @@
|
||||
// Copyright 2020 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright © 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Device model for Dragonball Secure Sandbox.
|
||||
//!
|
||||
//! The `dbs-device` crate, as a counterpart of [vm-device], defines device model for the
|
||||
//! Dragonball Secure Sandbox. The `dbs-device` crate shares some common concepts and data structures
|
||||
//! with [vm-device], but it also diverges from [vm-device] due to different VMM designs.
|
||||
//!
|
||||
//! [vm-device]: https://github.com/rust-vmm/vm-device
|
||||
|
||||
use std::any::Any;
|
||||
use std::cmp::{Ord, PartialOrd};
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use self::resources::DeviceResources;
|
||||
|
||||
pub mod device_manager;
|
||||
pub mod resources;
|
||||
|
||||
/// Size of MMIO range/access request.
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct IoSize(pub u64);
|
||||
|
||||
impl IoSize {
|
||||
/// Get the raw value as u64 to make operation simple.
|
||||
#[inline]
|
||||
pub fn raw_value(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for IoSize {
|
||||
#[inline]
|
||||
fn from(size: u64) -> Self {
|
||||
IoSize(size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoSize> for u64 {
|
||||
#[inline]
|
||||
fn from(size: IoSize) -> Self {
|
||||
size.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory Mapped IO (MMIO) address.
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct IoAddress(pub u64);
|
||||
|
||||
impl IoAddress {
|
||||
/// Get the raw value of IO Address to make operation simple.
|
||||
#[inline]
|
||||
pub fn raw_value(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for IoAddress {
|
||||
#[inline]
|
||||
fn from(addr: u64) -> Self {
|
||||
IoAddress(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoAddress> for u64 {
|
||||
#[inline]
|
||||
fn from(addr: IoAddress) -> Self {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
type PioAddressType = u16;
|
||||
|
||||
/// Size of Port I/O range/request.
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct PioSize(pub PioAddressType);
|
||||
|
||||
impl PioSize {
|
||||
/// Get the raw value as u64 to make operation simple.
|
||||
#[inline]
|
||||
pub fn raw_value(self) -> PioAddressType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PioAddressType> for PioSize {
|
||||
#[inline]
|
||||
fn from(size: PioAddressType) -> Self {
|
||||
PioSize(size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PioSize> for PioAddressType {
|
||||
#[inline]
|
||||
fn from(size: PioSize) -> Self {
|
||||
size.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<IoSize> for PioSize {
|
||||
type Error = IoSize;
|
||||
|
||||
#[inline]
|
||||
fn try_from(size: IoSize) -> Result<Self, Self::Error> {
|
||||
if size.raw_value() <= std::u16::MAX as u64 {
|
||||
Ok(PioSize(size.raw_value() as PioAddressType))
|
||||
} else {
|
||||
Err(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PioSize> for IoSize {
|
||||
#[inline]
|
||||
fn from(size: PioSize) -> Self {
|
||||
IoSize(size.raw_value() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// Port IO (PIO) address.
|
||||
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct PioAddress(pub PioAddressType);
|
||||
|
||||
impl PioAddress {
|
||||
/// Get the raw value of IO Address to make operation simple.
|
||||
#[inline]
|
||||
pub fn raw_value(self) -> PioAddressType {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PioAddressType> for PioAddress {
|
||||
#[inline]
|
||||
fn from(addr: PioAddressType) -> Self {
|
||||
PioAddress(addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PioAddress> for PioAddressType {
|
||||
#[inline]
|
||||
fn from(addr: PioAddress) -> Self {
|
||||
addr.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<IoAddress> for PioAddress {
|
||||
type Error = IoAddress;
|
||||
|
||||
#[inline]
|
||||
fn try_from(addr: IoAddress) -> Result<Self, Self::Error> {
|
||||
if addr.0 <= std::u16::MAX as u64 {
|
||||
Ok(PioAddress(addr.raw_value() as PioAddressType))
|
||||
} else {
|
||||
Err(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PioAddress> for IoAddress {
|
||||
#[inline]
|
||||
fn from(addr: PioAddress) -> Self {
|
||||
IoAddress(addr.raw_value() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for device to handle trapped MMIO/PIO access requests with interior mutability
|
||||
/// for high performance.
|
||||
///
|
||||
/// Any device which needs to trap MMIO/PIO access requests should implement the [DeviceIo] or
|
||||
/// [DeviceIoMut] trait and register itself to the [IoManager](crate::device_manager::IoManager)
|
||||
/// with those trapped IO address ranges. When the guest access those trapped address ranges,
|
||||
/// the access request will be routed to the registered callbacks.
|
||||
///
|
||||
/// The [DeviceIo] trait adopts the interior mutability pattern so we can get a real concurrent
|
||||
/// multiple threads handling. For device backend drivers not focusing on high performance,
|
||||
/// the Mutex<T: DeviceIoMut> adapter may be used to simplify the implementation.
|
||||
#[allow(unused_variables)]
|
||||
pub trait DeviceIo: Send + Sync {
|
||||
/// Read from the MMIO address `base + offset` into `data`.
|
||||
fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]) {}
|
||||
|
||||
/// Write from `data` to the MMIO address `base + offset`.
|
||||
fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]) {}
|
||||
|
||||
/// Read from port `base + offset` into `data`.
|
||||
fn pio_read(&self, base: PioAddress, offset: PioAddress, data: &mut [u8]) {}
|
||||
|
||||
/// Write from `data` to the port `base + offset`.
|
||||
fn pio_write(&self, base: PioAddress, offset: PioAddress, data: &[u8]) {}
|
||||
|
||||
/// Get resources assigned to the device.
|
||||
fn get_assigned_resources(&self) -> DeviceResources {
|
||||
DeviceResources::new()
|
||||
}
|
||||
|
||||
/// Get the trapped IO address ranges for the device.
|
||||
///
|
||||
/// Only MMIO/PIO address ranges in the resource list will be handled, other resources will be
|
||||
/// ignored. So the device does not need to filter out non-MMIO/PIO resources.
|
||||
fn get_trapped_io_resources(&self) -> DeviceResources {
|
||||
self.get_assigned_resources()
|
||||
}
|
||||
|
||||
/// Used to downcast to the specific type.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
/// Trait for device to handle trapped MMIO/PIO access requests.
|
||||
///
|
||||
/// Many device backend drivers will mutate itself when handling IO requests. The [DeviceIo] trait
|
||||
/// assumes interior mutability, but it's a little complex to support interior mutability.
|
||||
/// So the Mutex<T: DeviceIoMut> adapter may be used to ease device backend driver implementations.
|
||||
///
|
||||
/// The Mutex<T: DeviceIoMut> adapter is an zero overhead abstraction without performance penalty.
|
||||
#[allow(unused_variables)]
|
||||
pub trait DeviceIoMut {
|
||||
/// Read from the MMIO address `base + offset` into `data`.
|
||||
fn read(&mut self, base: IoAddress, offset: IoAddress, data: &mut [u8]) {}
|
||||
|
||||
/// Write from `data` to the MMIO address `base + offset`.
|
||||
fn write(&mut self, base: IoAddress, offset: IoAddress, data: &[u8]) {}
|
||||
|
||||
/// Read from port `base + offset` into `data`.
|
||||
fn pio_read(&mut self, base: PioAddress, offset: PioAddress, data: &mut [u8]) {}
|
||||
|
||||
/// Write from `data` to the port `base + offset`.
|
||||
fn pio_write(&mut self, base: PioAddress, offset: PioAddress, data: &[u8]) {}
|
||||
|
||||
/// Get resources assigned to the device.
|
||||
fn get_assigned_resources(&self) -> DeviceResources {
|
||||
DeviceResources::new()
|
||||
}
|
||||
|
||||
/// Get the trapped IO address ranges for the device.
|
||||
///
|
||||
/// Only MMIO/PIO address ranges in the resource list will be handled, other resources will be
|
||||
/// ignored. So the device does not need to filter out non-MMIO/PIO resources.
|
||||
fn get_trapped_io_resources(&self) -> DeviceResources {
|
||||
self.get_assigned_resources()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeviceIoMut + Send + 'static> DeviceIo for Mutex<T> {
|
||||
fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]) {
|
||||
// Safe to unwrap() because we don't expect poisoned lock here.
|
||||
self.lock().unwrap().read(base, offset, data)
|
||||
}
|
||||
|
||||
fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]) {
|
||||
// Safe to unwrap() because we don't expect poisoned lock here.
|
||||
self.lock().unwrap().write(base, offset, data)
|
||||
}
|
||||
|
||||
fn pio_read(&self, base: PioAddress, offset: PioAddress, data: &mut [u8]) {
|
||||
// Safe to unwrap() because we don't expect poisoned lock here.
|
||||
self.lock().unwrap().pio_read(base, offset, data)
|
||||
}
|
||||
|
||||
fn pio_write(&self, base: PioAddress, offset: PioAddress, data: &[u8]) {
|
||||
// Safe to unwrap() because we don't expect poisoned lock here.
|
||||
self.lock().unwrap().pio_write(base, offset, data)
|
||||
}
|
||||
|
||||
fn get_assigned_resources(&self) -> DeviceResources {
|
||||
// Safe to unwrap() because we don't expect poisoned lock here.
|
||||
self.lock().unwrap().get_assigned_resources()
|
||||
}
|
||||
|
||||
fn get_trapped_io_resources(&self) -> DeviceResources {
|
||||
// Safe to unwrap() because we don't expect poisoned lock here.
|
||||
self.lock().unwrap().get_trapped_io_resources()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
struct MockDevice {
|
||||
data: Mutex<u8>,
|
||||
}
|
||||
|
||||
impl DeviceIo for MockDevice {
|
||||
fn read(&self, _base: IoAddress, _offset: IoAddress, data: &mut [u8]) {
|
||||
data[0] = *self.data.lock().unwrap();
|
||||
}
|
||||
|
||||
fn write(&self, _base: IoAddress, _offset: IoAddress, data: &[u8]) {
|
||||
*self.data.lock().unwrap() = data[0];
|
||||
}
|
||||
|
||||
fn pio_read(&self, _base: PioAddress, _offset: PioAddress, data: &mut [u8]) {
|
||||
data[0] = *self.data.lock().unwrap();
|
||||
}
|
||||
|
||||
fn pio_write(&self, _base: PioAddress, _offset: PioAddress, data: &[u8]) {
|
||||
*self.data.lock().unwrap() = data[0];
|
||||
}
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MockDeviceMut {
|
||||
data: u8,
|
||||
}
|
||||
|
||||
impl DeviceIoMut for MockDeviceMut {
|
||||
fn read(&mut self, _base: IoAddress, _offset: IoAddress, data: &mut [u8]) {
|
||||
data[0] = self.data;
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: IoAddress, _offset: IoAddress, data: &[u8]) {
|
||||
self.data = data[0];
|
||||
}
|
||||
|
||||
fn pio_read(&mut self, _base: PioAddress, _offset: PioAddress, data: &mut [u8]) {
|
||||
data[0] = self.data;
|
||||
}
|
||||
|
||||
fn pio_write(&mut self, _base: PioAddress, _offset: PioAddress, data: &[u8]) {
|
||||
self.data = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
fn register_device(device: Arc<dyn DeviceIo>) {
|
||||
device.write(IoAddress(0), IoAddress(0), &[0x10u8]);
|
||||
let mut buf = [0x0u8];
|
||||
device.read(IoAddress(0), IoAddress(0), &mut buf);
|
||||
assert_eq!(buf[0], 0x10);
|
||||
|
||||
{
|
||||
device.pio_write(PioAddress(0), PioAddress(0), &[0x10u8]);
|
||||
let mut buf = [0x0u8];
|
||||
device.pio_read(PioAddress(0), PioAddress(0), &mut buf);
|
||||
assert_eq!(buf[0], 0x10);
|
||||
}
|
||||
|
||||
// test trait's default implementation
|
||||
let resource = DeviceResources::new();
|
||||
assert_eq!(resource, device.get_assigned_resources());
|
||||
assert_eq!(resource, device.get_trapped_io_resources());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_io_adapter() {
|
||||
let device = Arc::new(MockDevice::default());
|
||||
|
||||
register_device(device.clone());
|
||||
assert_eq!(*device.data.lock().unwrap(), 0x010);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_io_mut_adapter() {
|
||||
let device_mut = Arc::new(Mutex::new(MockDeviceMut::default()));
|
||||
|
||||
register_device(device_mut.clone());
|
||||
assert_eq!(device_mut.lock().unwrap().data, 0x010);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_data_struct() {
|
||||
let io_size = IoSize::from(0x1111u64);
|
||||
assert_eq!(io_size.raw_value(), 0x1111u64);
|
||||
assert_eq!(u64::from(io_size), 0x1111u64);
|
||||
assert_eq!(io_size, io_size.clone());
|
||||
let io_size1 = IoSize::from(0x1112u64);
|
||||
assert!(io_size < io_size1);
|
||||
|
||||
let io_addr = IoAddress::from(0x1234u64);
|
||||
assert_eq!(io_addr.raw_value(), 0x1234u64);
|
||||
assert_eq!(u64::from(io_addr), 0x1234u64);
|
||||
assert_eq!(io_addr, io_addr.clone());
|
||||
let io_addr1 = IoAddress::from(0x1235u64);
|
||||
assert!(io_addr < io_addr1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pio_data_struct() {
|
||||
let pio_size = PioSize::from(0x1111u16);
|
||||
assert_eq!(pio_size.raw_value(), 0x1111u16);
|
||||
assert_eq!(u16::from(pio_size), 0x1111u16);
|
||||
assert_eq!(pio_size, pio_size.clone());
|
||||
let pio_size1 = PioSize::from(0x1112u16);
|
||||
assert!(pio_size < pio_size1);
|
||||
|
||||
let pio_size = PioSize::try_from(IoSize(0x1111u64)).unwrap();
|
||||
assert_eq!(pio_size.raw_value(), 0x1111u16);
|
||||
|
||||
assert!(PioSize::try_from(IoSize(std::u16::MAX as u64 + 1)).is_err());
|
||||
|
||||
let io_size = IoSize::from(PioSize::from(0x1111u16));
|
||||
assert_eq!(io_size.raw_value(), 0x1111u64);
|
||||
|
||||
let pio_addr = PioAddress::from(0x1234u16);
|
||||
assert_eq!(pio_addr.raw_value(), 0x1234u16);
|
||||
assert_eq!(u16::from(pio_addr), 0x1234u16);
|
||||
assert_eq!(pio_addr, pio_addr.clone());
|
||||
let pio_addr1 = PioAddress::from(0x1235u16);
|
||||
assert!(pio_addr < pio_addr1);
|
||||
|
||||
assert!(PioAddress::try_from(IoAddress::from(0x12_3456u64)).is_err());
|
||||
assert!(PioAddress::try_from(IoAddress::from(0x1234u64)).is_ok());
|
||||
assert_eq!(IoAddress::from(pio_addr).raw_value(), 0x1234u64);
|
||||
}
|
||||
}
|
||||
649
src/dragonball/src/dbs_device/src/resources.rs
Normal file
649
src/dragonball/src/dbs_device/src/resources.rs
Normal file
@@ -0,0 +1,649 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Descriptors representing device resource allocation requirements and assigned resources.
|
||||
//!
|
||||
//! There are several components related to resource management:
|
||||
//! - the Dragonball Secure Sandbox (VMM), which is responsible for creating and registering devices
|
||||
//! to the device manager.
|
||||
//! - the device manager, which manages all devices of a Dragonball Secure Sandbox instance.
|
||||
//! - the devices, which implement virtual device backends for the guest.
|
||||
//!
|
||||
//! They cooperate with each to provide resources required by each device. The high level flow of
|
||||
//! resource management is as below:
|
||||
//! 1) the VMM creates a new device object.
|
||||
//! 2) the device returns an array of [ResourceConstraint](self::ResourceConstraint),
|
||||
//! describing the required resources and resource allocation constraints.
|
||||
//! 3) the VMM allocates required resources from a resource manager,
|
||||
//! 4) the VMM passes the allocated resources [DeviceResources](self::DeviceResources),
|
||||
//! which is an array of [Resource](self::Resource), to the device object.
|
||||
//! 5) the VMM registers the new device onto corresponding device managers according the allocated
|
||||
//! resources.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Enumeration describing a device's resource allocation requirements and constraints.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ResourceConstraint {
|
||||
/// Constraint for an IO Port address range.
|
||||
PioAddress {
|
||||
/// Allocating resource within the range [`min`, `max`] if specified.
|
||||
range: Option<(u16, u16)>,
|
||||
/// Alignment for the allocated address.
|
||||
align: u16,
|
||||
/// Size for the allocated address range.
|
||||
size: u16,
|
||||
},
|
||||
/// Constraint for a Memory Mapped IO address range.
|
||||
MmioAddress {
|
||||
/// Allocating resource within the range [`min`, `max`] if specified.
|
||||
range: Option<(u64, u64)>,
|
||||
/// Alignment for the allocated address.
|
||||
align: u64,
|
||||
/// Size for the allocated address range.
|
||||
size: u64,
|
||||
},
|
||||
/// Constraint for a Guest Mem address range.
|
||||
MemAddress {
|
||||
/// Allocating resource within the range [`min`, `max`] if specified.
|
||||
range: Option<(u64, u64)>,
|
||||
/// Alignment for the allocated address.
|
||||
align: u64,
|
||||
/// Size for the allocated address range.
|
||||
size: u64,
|
||||
},
|
||||
/// Constraint for a legacy IRQ.
|
||||
LegacyIrq {
|
||||
/// Reserving the pre-allocated IRQ if it's specified.
|
||||
irq: Option<u32>,
|
||||
},
|
||||
/// Constraint for PCI MSI IRQs.
|
||||
PciMsiIrq {
|
||||
/// Number of Irqs to allocate.
|
||||
size: u32,
|
||||
},
|
||||
/// Constraint for PCI MSIx IRQs.
|
||||
PciMsixIrq {
|
||||
/// Number of Irqs to allocate.
|
||||
size: u32,
|
||||
},
|
||||
/// Constraint for generic IRQs.
|
||||
GenericIrq {
|
||||
/// Number of Irqs to allocate.
|
||||
size: u32,
|
||||
},
|
||||
/// Constraint for KVM mem_slot indexes to map memory into the guest.
|
||||
KvmMemSlot {
|
||||
/// Allocating kvm memory slots starting from the index `slot` if
|
||||
/// specified.
|
||||
slot: Option<u32>,
|
||||
/// Number of slots to allocate.
|
||||
size: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl ResourceConstraint {
|
||||
/// Create a new PIO address constraint object with default configuration.
|
||||
pub fn new_pio(size: u16) -> Self {
|
||||
ResourceConstraint::PioAddress {
|
||||
range: None,
|
||||
align: 0x1,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new PIO address constraint object.
|
||||
pub fn pio_with_constraints(size: u16, range: Option<(u16, u16)>, align: u16) -> Self {
|
||||
ResourceConstraint::PioAddress { range, align, size }
|
||||
}
|
||||
|
||||
/// Create a new MMIO address constraint object with default configuration.
|
||||
pub fn new_mmio(size: u64) -> Self {
|
||||
ResourceConstraint::MmioAddress {
|
||||
range: None,
|
||||
align: 0x1000,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new MMIO address constraint object.
|
||||
pub fn mmio_with_constraints(size: u64, range: Option<(u64, u64)>, align: u64) -> Self {
|
||||
ResourceConstraint::MmioAddress { range, align, size }
|
||||
}
|
||||
|
||||
/// Create a new Mem address constraint object with default configuration.
|
||||
pub fn new_mem(size: u64) -> Self {
|
||||
ResourceConstraint::MemAddress {
|
||||
range: None,
|
||||
align: 0x1000,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Mem address constraint object.
|
||||
pub fn mem_with_constraints(size: u64, range: Option<(u64, u64)>, align: u64) -> Self {
|
||||
ResourceConstraint::MemAddress { range, align, size }
|
||||
}
|
||||
|
||||
/// Create a new legacy IRQ constraint object.
|
||||
///
|
||||
/// Allocating the pre-allocated legacy Irq `irq` if specified.
|
||||
pub fn new_legacy_irq(irq: Option<u32>) -> Self {
|
||||
ResourceConstraint::LegacyIrq { irq }
|
||||
}
|
||||
|
||||
/// Create a new PCI MSI IRQ constraint object.
|
||||
pub fn new_pci_msi_irq(size: u32) -> Self {
|
||||
ResourceConstraint::PciMsiIrq { size }
|
||||
}
|
||||
|
||||
/// Create a new PCI MSIX IRQ constraint object.
|
||||
pub fn new_pci_msix_irq(size: u32) -> Self {
|
||||
ResourceConstraint::PciMsixIrq { size }
|
||||
}
|
||||
|
||||
/// Create a new Generic IRQ constraint object.
|
||||
pub fn new_generic_irq(size: u32) -> Self {
|
||||
ResourceConstraint::GenericIrq { size }
|
||||
}
|
||||
|
||||
/// Create a new KVM memory slot constraint object.
|
||||
///
|
||||
/// Allocating kvm memory slots starting from the index `slot` if specified.
|
||||
pub fn new_kvm_mem_slot(size: u32, slot: Option<u32>) -> Self {
|
||||
ResourceConstraint::KvmMemSlot { slot, size }
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of Message Singaled Interrupt
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum MsiIrqType {
|
||||
/// PCI MSI IRQ numbers.
|
||||
PciMsi,
|
||||
/// PCI MSIx IRQ numbers.
|
||||
PciMsix,
|
||||
/// Generic MSI IRQ numbers.
|
||||
GenericMsi,
|
||||
}
|
||||
|
||||
/// Enumeration for device resources.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Resource {
|
||||
/// IO Port resource range.
|
||||
PioAddressRange {
|
||||
/// Pio resource base
|
||||
base: u16,
|
||||
/// Pio resource size
|
||||
size: u16,
|
||||
},
|
||||
/// Memory Mapped IO resource range.
|
||||
MmioAddressRange {
|
||||
/// Mmio resource base
|
||||
base: u64,
|
||||
/// Mmio resource size
|
||||
size: u64,
|
||||
},
|
||||
/// Guest Mem resource range.
|
||||
MemAddressRange {
|
||||
/// Mem resource base
|
||||
base: u64,
|
||||
/// Mem resource size
|
||||
size: u64,
|
||||
},
|
||||
/// Legacy IRQ number.
|
||||
LegacyIrq(u32),
|
||||
/// Message Signaled Interrupt
|
||||
MsiIrq {
|
||||
/// Msi irq type
|
||||
ty: MsiIrqType,
|
||||
/// Msi irq base
|
||||
base: u32,
|
||||
/// Msi irq size
|
||||
size: u32,
|
||||
},
|
||||
/// Network Interface Card MAC address.
|
||||
MacAddresss(String),
|
||||
/// KVM memslot index.
|
||||
KvmMemSlot(u32),
|
||||
}
|
||||
|
||||
/// Newtype to store a set of device resources.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct DeviceResources(Vec<Resource>);
|
||||
|
||||
impl DeviceResources {
|
||||
/// Create a container object to store device resources.
|
||||
pub fn new() -> Self {
|
||||
DeviceResources(Vec::new())
|
||||
}
|
||||
|
||||
/// Append a device resource to the container object.
|
||||
pub fn append(&mut self, entry: Resource) {
|
||||
self.0.push(entry);
|
||||
}
|
||||
|
||||
/// Get the IO port address resources.
|
||||
pub fn get_pio_address_ranges(&self) -> Vec<(u16, u16)> {
|
||||
let mut vec = Vec::new();
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::PioAddressRange { base, size } = entry {
|
||||
vec.push((*base, *size));
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
/// Get the Memory Mapped IO address resources.
|
||||
pub fn get_mmio_address_ranges(&self) -> Vec<(u64, u64)> {
|
||||
let mut vec = Vec::new();
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::MmioAddressRange { base, size } = entry {
|
||||
vec.push((*base, *size));
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
/// Get the Guest Memory address resources.
|
||||
pub fn get_mem_address_ranges(&self) -> Vec<(u64, u64)> {
|
||||
let mut vec = Vec::new();
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::MemAddressRange { base, size } = entry {
|
||||
vec.push((*base, *size));
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
/// Get the first legacy interrupt number(IRQ).
|
||||
pub fn get_legacy_irq(&self) -> Option<u32> {
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::LegacyIrq(base) = entry {
|
||||
return Some(*base);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get information about the first PCI MSI interrupt resource.
|
||||
pub fn get_pci_msi_irqs(&self) -> Option<(u32, u32)> {
|
||||
self.get_msi_irqs(MsiIrqType::PciMsi)
|
||||
}
|
||||
|
||||
/// Get information about the first PCI MSIx interrupt resource.
|
||||
pub fn get_pci_msix_irqs(&self) -> Option<(u32, u32)> {
|
||||
self.get_msi_irqs(MsiIrqType::PciMsix)
|
||||
}
|
||||
|
||||
/// Get information about the first Generic MSI interrupt resource.
|
||||
pub fn get_generic_msi_irqs(&self) -> Option<(u32, u32)> {
|
||||
self.get_msi_irqs(MsiIrqType::GenericMsi)
|
||||
}
|
||||
|
||||
fn get_msi_irqs(&self, ty: MsiIrqType) -> Option<(u32, u32)> {
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::MsiIrq {
|
||||
ty: msi_type,
|
||||
base,
|
||||
size,
|
||||
} = entry
|
||||
{
|
||||
if ty == *msi_type {
|
||||
return Some((*base, *size));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the KVM memory slots to map memory into the guest.
|
||||
pub fn get_kvm_mem_slots(&self) -> Vec<u32> {
|
||||
let mut vec = Vec::new();
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::KvmMemSlot(index) = entry {
|
||||
vec.push(*index);
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
/// Get the first resource information for NIC MAC address.
|
||||
pub fn get_mac_address(&self) -> Option<String> {
|
||||
for entry in self.0.iter().as_ref() {
|
||||
if let Resource::MacAddresss(addr) = entry {
|
||||
return Some(addr.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get immutable reference to all the resources.
|
||||
pub fn get_all_resources(&self) -> &[Resource] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DeviceResources {
|
||||
type Target = [Resource];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
const PIO_ADDRESS_SIZE: u16 = 5;
|
||||
const PIO_ADDRESS_BASE: u16 = 0;
|
||||
const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321;
|
||||
const MMIO_ADDRESS_BASE: u64 = 0x1234_5678;
|
||||
const MEM_ADDRESS_SIZE: u64 = 0x8765_4321;
|
||||
const MEM_ADDRESS_BASE: u64 = 0x1234_5678;
|
||||
const LEGACY_IRQ: u32 = 0x168;
|
||||
const PCI_MSI_IRQ_SIZE: u32 = 0x8888;
|
||||
const PCI_MSI_IRQ_BASE: u32 = 0x6666;
|
||||
const PCI_MSIX_IRQ_SIZE: u32 = 0x16666;
|
||||
const PCI_MSIX_IRQ_BASE: u32 = 0x8888;
|
||||
const GENERIC_MSI_IRQS_SIZE: u32 = 0x16888;
|
||||
const GENERIC_MSI_IRQS_BASE: u32 = 0x16688;
|
||||
const MAC_ADDRESS: &str = "00:08:63:66:86:88";
|
||||
const KVM_SLOT_ID: u32 = 0x0100;
|
||||
|
||||
pub fn get_device_resource() -> DeviceResources {
|
||||
let mut resource = DeviceResources::new();
|
||||
|
||||
let entry = Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
};
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[0]);
|
||||
|
||||
let entry = Resource::MmioAddressRange {
|
||||
base: MMIO_ADDRESS_BASE,
|
||||
size: MMIO_ADDRESS_SIZE,
|
||||
};
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[1]);
|
||||
|
||||
let entry = Resource::MemAddressRange {
|
||||
base: MEM_ADDRESS_BASE,
|
||||
size: MEM_ADDRESS_SIZE,
|
||||
};
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[2]);
|
||||
|
||||
let entry = Resource::LegacyIrq(LEGACY_IRQ);
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[3]);
|
||||
|
||||
let entry = Resource::MsiIrq {
|
||||
ty: MsiIrqType::PciMsi,
|
||||
base: PCI_MSI_IRQ_BASE,
|
||||
size: PCI_MSI_IRQ_SIZE,
|
||||
};
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[4]);
|
||||
|
||||
let entry = Resource::MsiIrq {
|
||||
ty: MsiIrqType::PciMsix,
|
||||
base: PCI_MSIX_IRQ_BASE,
|
||||
size: PCI_MSIX_IRQ_SIZE,
|
||||
};
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[5]);
|
||||
|
||||
let entry = Resource::MsiIrq {
|
||||
ty: MsiIrqType::GenericMsi,
|
||||
base: GENERIC_MSI_IRQS_BASE,
|
||||
size: GENERIC_MSI_IRQS_SIZE,
|
||||
};
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[6]);
|
||||
|
||||
let entry = Resource::MacAddresss(MAC_ADDRESS.to_string());
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[7]);
|
||||
|
||||
let entry = Resource::KvmMemSlot(KVM_SLOT_ID);
|
||||
resource.append(entry.clone());
|
||||
assert_eq!(entry, resource[8]);
|
||||
|
||||
resource
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_pio_address_ranges() {
|
||||
let resources = get_device_resource();
|
||||
assert!(
|
||||
resources.get_pio_address_ranges()[0].0 == PIO_ADDRESS_BASE
|
||||
&& resources.get_pio_address_ranges()[0].1 == PIO_ADDRESS_SIZE
|
||||
);
|
||||
assert_eq!(
|
||||
resources[0],
|
||||
Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
}
|
||||
);
|
||||
assert_ne!(resources[0], resources[1]);
|
||||
|
||||
let resources2 = resources.clone();
|
||||
assert_eq!(resources.len(), resources2.len());
|
||||
drop(resources);
|
||||
assert_eq!(
|
||||
resources2[0],
|
||||
Resource::PioAddressRange {
|
||||
base: PIO_ADDRESS_BASE,
|
||||
size: PIO_ADDRESS_SIZE,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mmio_address_ranges() {
|
||||
let resources = get_device_resource();
|
||||
assert!(
|
||||
resources.get_mmio_address_ranges()[0].0 == MMIO_ADDRESS_BASE
|
||||
&& resources.get_mmio_address_ranges()[0].1 == MMIO_ADDRESS_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mem_address_ranges() {
|
||||
let resources = get_device_resource();
|
||||
assert!(
|
||||
resources.get_mem_address_ranges()[0].0 == MEM_ADDRESS_BASE
|
||||
&& resources.get_mem_address_ranges()[0].1 == MEM_ADDRESS_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_legacy_irq() {
|
||||
let resources = get_device_resource();
|
||||
assert!(resources.get_legacy_irq().unwrap() == LEGACY_IRQ);
|
||||
|
||||
// None case.
|
||||
let resources = DeviceResources::new();
|
||||
assert!(resources.get_legacy_irq().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_pci_msi_irqs() {
|
||||
let resources = get_device_resource();
|
||||
assert!(
|
||||
resources.get_pci_msi_irqs().unwrap().0 == PCI_MSI_IRQ_BASE
|
||||
&& resources.get_pci_msi_irqs().unwrap().1 == PCI_MSI_IRQ_SIZE
|
||||
);
|
||||
|
||||
// None case.
|
||||
let resources = DeviceResources::new();
|
||||
assert!(resources.get_generic_msi_irqs().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_pci_msix_irqs() {
|
||||
let resources = get_device_resource();
|
||||
assert!(
|
||||
resources.get_pci_msix_irqs().unwrap().0 == PCI_MSIX_IRQ_BASE
|
||||
&& resources.get_pci_msix_irqs().unwrap().1 == PCI_MSIX_IRQ_SIZE
|
||||
);
|
||||
|
||||
// None case.
|
||||
let resources = DeviceResources::new();
|
||||
assert!(resources.get_generic_msi_irqs().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_generic_msi_irqs() {
|
||||
let resources = get_device_resource();
|
||||
assert!(
|
||||
resources.get_generic_msi_irqs().unwrap().0 == GENERIC_MSI_IRQS_BASE
|
||||
&& resources.get_generic_msi_irqs().unwrap().1 == GENERIC_MSI_IRQS_SIZE
|
||||
);
|
||||
|
||||
// None case.
|
||||
let resources = DeviceResources::new();
|
||||
assert!(resources.get_generic_msi_irqs().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mac_address() {
|
||||
let resources = get_device_resource();
|
||||
assert_eq!(resources.get_mac_address().unwrap(), MAC_ADDRESS);
|
||||
|
||||
// None case.
|
||||
let resources = DeviceResources::new();
|
||||
assert!(resources.get_mac_address().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_kvm_slot() {
|
||||
let resources = get_device_resource();
|
||||
assert_eq!(resources.get_kvm_mem_slots(), vec![KVM_SLOT_ID]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_all_resources() {
|
||||
let resources = get_device_resource();
|
||||
assert_eq!(resources.get_all_resources().len(), 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_constraint() {
|
||||
let pio = ResourceConstraint::new_pio(2);
|
||||
let pio2 = pio;
|
||||
let mmio = ResourceConstraint::new_mmio(0x1000);
|
||||
assert_eq!(pio, pio2);
|
||||
assert_ne!(pio, mmio);
|
||||
|
||||
if let ResourceConstraint::PioAddress { range, align, size } =
|
||||
ResourceConstraint::new_pio(2)
|
||||
{
|
||||
assert_eq!(range, None);
|
||||
assert_eq!(align, 1);
|
||||
assert_eq!(size, 2);
|
||||
} else {
|
||||
panic!("Pio resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::PioAddress { range, align, size } =
|
||||
ResourceConstraint::pio_with_constraints(2, Some((15, 16)), 2)
|
||||
{
|
||||
assert_eq!(range, Some((15, 16)));
|
||||
assert_eq!(align, 2);
|
||||
assert_eq!(size, 2);
|
||||
} else {
|
||||
panic!("Pio resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::MmioAddress { range, align, size } =
|
||||
ResourceConstraint::new_mmio(0x2000)
|
||||
{
|
||||
assert_eq!(range, None);
|
||||
assert_eq!(align, 0x1000);
|
||||
assert_eq!(size, 0x2000);
|
||||
} else {
|
||||
panic!("Mmio resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::MmioAddress { range, align, size } =
|
||||
ResourceConstraint::mmio_with_constraints(0x2000, Some((0x0, 0x2000)), 0x2000)
|
||||
{
|
||||
assert_eq!(range, Some((0x0, 0x2000)));
|
||||
assert_eq!(align, 0x2000);
|
||||
assert_eq!(size, 0x2000);
|
||||
} else {
|
||||
panic!("Mmio resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::MemAddress { range, align, size } =
|
||||
ResourceConstraint::new_mem(0x2000)
|
||||
{
|
||||
assert_eq!(range, None);
|
||||
assert_eq!(align, 0x1000);
|
||||
assert_eq!(size, 0x2000);
|
||||
} else {
|
||||
panic!("Mem resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::MemAddress { range, align, size } =
|
||||
ResourceConstraint::mem_with_constraints(0x2000, Some((0x0, 0x2000)), 0x2000)
|
||||
{
|
||||
assert_eq!(range, Some((0x0, 0x2000)));
|
||||
assert_eq!(align, 0x2000);
|
||||
assert_eq!(size, 0x2000);
|
||||
} else {
|
||||
panic!("Mem resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::LegacyIrq { irq } =
|
||||
ResourceConstraint::new_legacy_irq(Some(0x123))
|
||||
{
|
||||
assert_eq!(irq, Some(0x123));
|
||||
} else {
|
||||
panic!("IRQ resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::PciMsiIrq { size } = ResourceConstraint::new_pci_msi_irq(0x123) {
|
||||
assert_eq!(size, 0x123);
|
||||
} else {
|
||||
panic!("Pci MSI irq resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::PciMsixIrq { size } = ResourceConstraint::new_pci_msix_irq(0x123)
|
||||
{
|
||||
assert_eq!(size, 0x123);
|
||||
} else {
|
||||
panic!("Pci MSIx irq resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::GenericIrq { size } = ResourceConstraint::new_generic_irq(0x123)
|
||||
{
|
||||
assert_eq!(size, 0x123);
|
||||
} else {
|
||||
panic!("generic irq resource constraint is invalid.");
|
||||
}
|
||||
|
||||
if let ResourceConstraint::KvmMemSlot { slot, size } =
|
||||
ResourceConstraint::new_kvm_mem_slot(0x1000, Some(0x2000))
|
||||
{
|
||||
assert_eq!(slot, Some(0x2000));
|
||||
assert_eq!(size, 0x1000);
|
||||
} else {
|
||||
panic!("KVM slot resource constraint is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resources_deref() {
|
||||
let resources = get_device_resource();
|
||||
let mut count = 0;
|
||||
for _res in resources.iter() {
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(count, resources.0.len());
|
||||
}
|
||||
}
|
||||
30
src/dragonball/src/dbs_interrupt/Cargo.toml
Normal file
30
src/dragonball/src/dbs_interrupt/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "dbs-interrupt"
|
||||
version = "0.2.2"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
description = "Traits and structs to manage interrupts for virtual devices"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox/tree/main/crates/dbs-interrupt"
|
||||
keywords = ["dragonball", "secure-sandbox", "device", "interrupt"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
dbs-device = { path = "../dbs_device" }
|
||||
dbs-arch = { path = "../dbs_arch" }
|
||||
kvm-bindings = { version = "0.6.0", optional = true }
|
||||
kvm-ioctls = { version = "0.12.0", optional = true }
|
||||
libc = "0.2"
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
[features]
|
||||
default = ["legacy-irq", "msi-irq"]
|
||||
|
||||
legacy-irq = []
|
||||
msi-irq = []
|
||||
|
||||
kvm-irq = ["kvm-ioctls", "kvm-bindings"]
|
||||
kvm-legacy-irq = ["legacy-irq", "kvm-irq"]
|
||||
kvm-msi-generic = ["msi-irq", "kvm-irq"]
|
||||
kvm-msi-irq = ["kvm-msi-generic"]
|
||||
1
src/dragonball/src/dbs_interrupt/LICENSE
Symbolic link
1
src/dragonball/src/dbs_interrupt/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
73
src/dragonball/src/dbs_interrupt/README.md
Normal file
73
src/dragonball/src/dbs_interrupt/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# dbs-interrupt
|
||||
|
||||
Interrupts are used by hardware devices to indicate asynchronous events to the processor.
|
||||
The `dbs-interrupt` crate provides traits and data structures for the `Dragonball Sandbox` to manage
|
||||
interrupts for virtual and physical devices.
|
||||
|
||||
An interrupt alerts the processor to a high-priority condition requiring the interruption of
|
||||
the current code the processor is executing. The processor responds by suspending its current activities,
|
||||
saving its state, and executing a function called an interrupt handler (or an interrupt service routine, ISR)
|
||||
to deal with the event. This interruption is temporary, and, after the interrupt handler finishes,
|
||||
unless handling the interrupt has emitted a fatal error, the processor resumes normal activities.
|
||||
|
||||
Hardware interrupts are used by devices to communicate that they require attention from the
|
||||
operating system, or a bare-metal program running on the CPU if there are no OSes. The act of
|
||||
initiating a hardware interrupt is referred to as an interrupt request (IRQ). Different devices are
|
||||
usually associated with different interrupts using a unique value associated with each interrupt.
|
||||
This makes it possible to know which hardware device caused which interrupts. These interrupt values
|
||||
are often called IRQ lines, or just interrupt lines.
|
||||
|
||||
Nowadays, IRQ lines is not the only mechanism to deliver device interrupts to processors. MSI
|
||||
(Message Signaled Interrupt) is another commonly used alternative in-band method of signaling an
|
||||
interrupt, using special in-band messages to replace traditional out-of-band assertion of dedicated
|
||||
interrupt lines. While more complex to implement in a device, message signaled interrupts have some
|
||||
significant advantages over pin-based out-of-band interrupt signaling. Message signaled interrupts
|
||||
are supported in PCI bus since its version 2.2, and in later available PCI Express bus. Some non-PCI
|
||||
architectures also use message signaled interrupts.
|
||||
|
||||
While IRQ is a term commonly used by Operating Systems when dealing with hardware interrupts, the
|
||||
IRQ numbers managed by OSes are independent of the ones managed by VMM. For simplicity sake, the
|
||||
term Interrupt Source is used instead of IRQ to represent both pin-based interrupts and MSI
|
||||
interrupts.
|
||||
|
||||
A device may support multiple types of interrupts, and each type of interrupt may support one or
|
||||
multiple interrupt sources. For example, a PCI device may support:
|
||||
|
||||
- Legacy Irq: exactly one interrupt source.
|
||||
- PCI MSI Irq: 1,2,4,8,16,32 interrupt sources.
|
||||
- PCI MSIx Irq: 2^n(n=0-11) interrupt sources.
|
||||
|
||||
A distinct Interrupt Source Identifier (ISID) will be assigned to each interrupt source. An ID
|
||||
allocator will be used to allocate and free Interrupt Source Identifiers for devices. To decouple
|
||||
this crate from the ID allocator, here we doesn't take the responsibility to allocate/free Interrupt
|
||||
Source IDs but only makes use of assigned IDs.
|
||||
|
||||
The overall flow to deal with interrupts is:
|
||||
|
||||
- the VMM creates an interrupt manager
|
||||
- the VMM creates a device manager, passing on an reference to the interrupt manager
|
||||
- the device manager passes on an reference to the interrupt manager to all registered devices
|
||||
- guest kernel loads drivers for virtual devices
|
||||
- guest device driver determines the type and number of interrupts needed, and update the device
|
||||
configuration
|
||||
- the virtual device backend requests the interrupt manager to create an interrupt group according to guest configuration information
|
||||
|
||||
The dbs-device crate provides:
|
||||
|
||||
- [trait `InterruptManager`]: manage interrupt sources for virtual device backend
|
||||
- [struct `DeviceInterruptManager`]: an implementation of [`InterruptManager`], manage interrupts and interrupt modes for a device
|
||||
- [trait `InterruptSourceGroup`]: manage a group of interrupt sources for a device, provide methods to control the interrupts
|
||||
- [enum `InterruptSourceType`]: type of interrupt source
|
||||
- [enum `InterruptSourceConfig`], [struct `LegacyIrqSourceConfig`] and [struct `MsiIrqSourceConfig`]: configuration data for interrupt sources
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
[trait InterruptManager]: src/lib.rs
|
||||
[struct DeviceInterruptManager]: src/manager.rs
|
||||
[trait InterruptSourceGroup]: src/lib.rs
|
||||
[enum InterruptSourceType]: src/lib.rs
|
||||
[enum InterruptSourceConfig]: src/lib.rs
|
||||
[struct LegacyIrqSourceConfig]: src/lib.rs
|
||||
[struct MsiIrqSourceConfig]: src/lib.rs
|
||||
351
src/dragonball/src/dbs_interrupt/src/kvm/legacy_irq.rs
Normal file
351
src/dragonball/src/dbs_interrupt/src/kvm/legacy_irq.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Manage virtual device's legacy interrupts based on Linux KVM framework.
|
||||
//!
|
||||
//! On x86 platforms, legacy interrupts are those managed by the Master PIC, the slave PIC and
|
||||
//! IOAPICs.
|
||||
|
||||
use kvm_bindings::KVM_IRQ_ROUTING_IRQCHIP;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{KVM_IRQCHIP_IOAPIC, KVM_IRQCHIP_PIC_MASTER, KVM_IRQCHIP_PIC_SLAVE};
|
||||
use vmm_sys_util::eventfd::EFD_NONBLOCK;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// Maximum number of legacy interrupts supported.
|
||||
pub const MAX_LEGACY_IRQS: u32 = 24;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Maximum number of legacy interrupts supported.
|
||||
pub const MAX_LEGACY_IRQS: u32 = 128;
|
||||
|
||||
pub(super) struct LegacyIrq {
|
||||
base: u32,
|
||||
vmfd: Arc<VmFd>,
|
||||
irqfd: EventFd,
|
||||
}
|
||||
|
||||
impl LegacyIrq {
|
||||
pub(super) fn new(
|
||||
base: InterruptIndex,
|
||||
count: InterruptIndex,
|
||||
vmfd: Arc<VmFd>,
|
||||
_routes: Arc<KvmIrqRouting>,
|
||||
) -> Result<Self> {
|
||||
if count != 1 {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
if base >= MAX_LEGACY_IRQS {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
Ok(LegacyIrq {
|
||||
base,
|
||||
vmfd,
|
||||
irqfd: EventFd::new(EFD_NONBLOCK)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn add_legacy_entry(
|
||||
gsi: u32,
|
||||
chip: u32,
|
||||
pin: u32,
|
||||
routes: &mut HashMap<u64, kvm_irq_routing_entry>,
|
||||
) -> Result<()> {
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi,
|
||||
type_: KVM_IRQ_ROUTING_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
// Safe because we are initializing all fields of the `irqchip` struct.
|
||||
entry.u.irqchip.irqchip = chip;
|
||||
entry.u.irqchip.pin = pin;
|
||||
routes.insert(hash_key(&entry), entry);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build routings for IRQs connected to the master PIC, the slave PIC or the first IOAPIC.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(super) fn initialize_legacy(
|
||||
routes: &mut HashMap<u64, kvm_irq_routing_entry>,
|
||||
) -> Result<()> {
|
||||
// Build routings for the master PIC
|
||||
for i in 0..8 {
|
||||
if i != 2 {
|
||||
Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_MASTER, i, routes)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Build routings for the slave PIC
|
||||
for i in 8..16 {
|
||||
Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_SLAVE, i - 8, routes)?;
|
||||
}
|
||||
|
||||
// Build routings for the first IOAPIC
|
||||
for i in 0..MAX_LEGACY_IRQS {
|
||||
if i == 0 {
|
||||
Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, 2, routes)?;
|
||||
} else if i != 2 {
|
||||
Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, i, routes)?;
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(super) fn initialize_legacy(
|
||||
routes: &mut HashMap<u64, kvm_irq_routing_entry>,
|
||||
) -> Result<()> {
|
||||
for i in 0..MAX_LEGACY_IRQS {
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi: i,
|
||||
type_: KVM_IRQ_ROUTING_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
entry.u.irqchip.irqchip = 0;
|
||||
entry.u.irqchip.pin = i;
|
||||
routes.insert(hash_key(&entry), entry);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptSourceGroup for LegacyIrq {
|
||||
fn interrupt_type(&self) -> InterruptSourceType {
|
||||
InterruptSourceType::LegacyIrq
|
||||
}
|
||||
|
||||
fn len(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn base(&self) -> u32 {
|
||||
self.base
|
||||
}
|
||||
|
||||
fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()> {
|
||||
if configs.len() != 1 {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
// The IRQ routings for legacy IRQs have been configured during KvmIrqManager::initialize(),
|
||||
// so only need to register irqfd to the KVM driver.
|
||||
self.vmfd
|
||||
.register_irqfd(&self.irqfd, self.base)
|
||||
.map_err(from_sys_util_errno)
|
||||
}
|
||||
|
||||
fn disable(&self) -> Result<()> {
|
||||
self.vmfd
|
||||
.unregister_irqfd(&self.irqfd, self.base)
|
||||
.map_err(from_sys_util_errno)
|
||||
}
|
||||
|
||||
fn update(&self, index: InterruptIndex, _config: &InterruptSourceConfig) -> Result<()> {
|
||||
// For legacy interrupts, the routing configuration is managed by the PIC/IOAPIC interrupt
|
||||
// controller drivers, so nothing to do here.
|
||||
if index != 0 {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, index: InterruptIndex) -> Option<&EventFd> {
|
||||
if index != 0 {
|
||||
None
|
||||
} else {
|
||||
Some(&self.irqfd)
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger(&self, index: InterruptIndex) -> Result<()> {
|
||||
if index != 0 {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
self.irqfd.write(1)
|
||||
}
|
||||
|
||||
fn mask(&self, index: InterruptIndex) -> Result<()> {
|
||||
if index > 1 {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
self.vmfd
|
||||
.unregister_irqfd(&self.irqfd, self.base + index)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmask(&self, index: InterruptIndex) -> Result<()> {
|
||||
if index > 1 {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
self.vmfd
|
||||
.register_irqfd(&self.irqfd, self.base + index)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pending_state(&self, index: InterruptIndex) -> bool {
|
||||
if index > 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Peak the EventFd.count by reading and writing back.
|
||||
// The irqfd must be in NON-BLOCKING mode.
|
||||
match self.irqfd.read() {
|
||||
Err(_) => false,
|
||||
Ok(count) => {
|
||||
if count != 0 && self.irqfd.write(count).is_err() {
|
||||
// Hope the caller will handle the pending state corrrectly,
|
||||
// then no interrupt will be lost.
|
||||
}
|
||||
count != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::manager::tests::create_vm_fd;
|
||||
|
||||
const MASTER_PIC: usize = 7;
|
||||
const SLAVE_PIC: usize = 8;
|
||||
const IOAPIC: usize = 23;
|
||||
|
||||
#[test]
|
||||
#[allow(unreachable_patterns)]
|
||||
fn test_legacy_interrupt_group() {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
let rounting = Arc::new(KvmIrqRouting::new(vmfd.clone()));
|
||||
let base = 0;
|
||||
let count = 1;
|
||||
let group = LegacyIrq::new(base, count, vmfd.clone(), rounting.clone()).unwrap();
|
||||
|
||||
let legacy_fds = vec![InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {})];
|
||||
|
||||
match group.interrupt_type() {
|
||||
InterruptSourceType::LegacyIrq => {}
|
||||
_ => {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
assert_eq!(group.len(), 1);
|
||||
assert_eq!(group.base(), base);
|
||||
group.enable(&legacy_fds).unwrap();
|
||||
group.notifier(0).unwrap().write(1).unwrap();
|
||||
group.trigger(0).unwrap();
|
||||
assert!(group.trigger(1).is_err());
|
||||
group
|
||||
.update(
|
||||
0,
|
||||
&InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {}),
|
||||
)
|
||||
.unwrap();
|
||||
group.disable().unwrap();
|
||||
|
||||
assert!(LegacyIrq::new(base, 2, vmfd.clone(), rounting.clone()).is_err());
|
||||
assert!(LegacyIrq::new(110, 1, vmfd, rounting).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_irq_routing_initialize_legacy() {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
let routing = KvmIrqRouting::new(vmfd.clone());
|
||||
|
||||
// this would ok on 4.9 kernel
|
||||
assert!(routing.initialize().is_err());
|
||||
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
routing.initialize().unwrap();
|
||||
|
||||
let routes = &routing.routes.lock().unwrap();
|
||||
assert_eq!(routes.len(), MASTER_PIC + SLAVE_PIC + IOAPIC);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_routing_opt() {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
let routing = KvmIrqRouting::new(vmfd.clone());
|
||||
|
||||
// this would ok on 4.9 kernel
|
||||
assert!(routing.initialize().is_err());
|
||||
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
routing.initialize().unwrap();
|
||||
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi: 8,
|
||||
type_: kvm_bindings::KVM_IRQ_ROUTING_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Safe because we are initializing all fields of the `irqchip` struct.
|
||||
entry.u.irqchip.irqchip = 0;
|
||||
entry.u.irqchip.pin = 3;
|
||||
|
||||
let entrys = vec![entry];
|
||||
|
||||
assert!(routing.modify(&entry).is_err());
|
||||
routing.add(&entrys).unwrap();
|
||||
entry.u.irqchip.pin = 4;
|
||||
routing.modify(&entry).unwrap();
|
||||
routing.remove(&entrys).unwrap();
|
||||
assert!(routing.modify(&entry).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_routing_set_routing() {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
let routing = KvmIrqRouting::new(vmfd.clone());
|
||||
|
||||
// this would ok on 4.9 kernel
|
||||
assert!(routing.initialize().is_err());
|
||||
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
routing.initialize().unwrap();
|
||||
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi: 8,
|
||||
type_: kvm_bindings::KVM_IRQ_ROUTING_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
entry.u.irqchip.irqchip = 0;
|
||||
entry.u.irqchip.pin = 3;
|
||||
|
||||
routing
|
||||
.routes
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(hash_key(&entry), entry);
|
||||
let routes = routing.routes.lock().unwrap();
|
||||
routing.set_routing(&routes).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_key() {
|
||||
let gsi = 4;
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi,
|
||||
type_: kvm_bindings::KVM_IRQ_ROUTING_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
// Safe because we are initializing all fields of the `irqchip` struct.
|
||||
entry.u.irqchip.irqchip = kvm_bindings::KVM_IRQCHIP_PIC_MASTER;
|
||||
entry.u.irqchip.pin = gsi;
|
||||
assert_eq!(hash_key(&entry), 0x0001_0000_0004);
|
||||
}
|
||||
}
|
||||
340
src/dragonball/src/dbs_interrupt/src/kvm/mod.rs
Normal file
340
src/dragonball/src/dbs_interrupt/src/kvm/mod.rs
Normal file
@@ -0,0 +1,340 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Manage virtual device's interrupts based on the Linux KVM framework.
|
||||
//!
|
||||
//! When updaing KVM IRQ routing by ioctl(KVM_SET_GSI_ROUTING), all interrupts of the virtual
|
||||
//! machine must be updated all together. The [KvmIrqRouting](struct.KvmIrqRouting.html) structure
|
||||
//! is to maintain the global interrupt routing table.
|
||||
//!
|
||||
//! It deserves a good documentation about the way that KVM based vmms manages interrupts. From the
|
||||
//! KVM hypervisor side, it provides three mechanism to support injecting interrupts into guests:
|
||||
//! 1) Irqfd. When data is written to an irqfd, it triggers KVM to inject an interrupt into guest.
|
||||
//! 2) Irq routing. Irq routing determines the way to inject an irq into guest.
|
||||
//! 3) Signal MSI. Vmm can inject an MSI interrupt into guest by issuing KVM_SIGNAL_MSI ioctl.
|
||||
//!
|
||||
//! Most VMMs use irqfd + irq routing to support interrupt injecting, so we will focus on this mode.
|
||||
//! The flow to enable interrupt injecting is:
|
||||
//! 1) VMM creates an irqfd
|
||||
//! 2) VMM invokes KVM_IRQFD to bind the irqfd to an interrupt source
|
||||
//! 3) VMM invokes KVM_SET_GSI_ROUTING to configure the way to inject the interrupt into guest
|
||||
//! 4) device backend driver writes to the irqfd
|
||||
//! 5) an interurpt is injected into the guest
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use kvm_bindings::{kvm_irq_routing, kvm_irq_routing_entry};
|
||||
use kvm_ioctls::VmFd;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "kvm-legacy-irq")]
|
||||
use legacy_irq::LegacyIrq;
|
||||
#[cfg(feature = "kvm-msi-irq")]
|
||||
use msi_irq::MsiIrq;
|
||||
|
||||
#[cfg(feature = "kvm-legacy-irq")]
|
||||
mod legacy_irq;
|
||||
#[cfg(feature = "kvm-msi-generic")]
|
||||
mod msi_generic;
|
||||
#[cfg(feature = "kvm-msi-irq")]
|
||||
mod msi_irq;
|
||||
|
||||
/// Maximum number of global interrupt sources.
|
||||
pub const MAX_IRQS: InterruptIndex = 1024;
|
||||
|
||||
/// Default maximum number of Message Signaled Interrupts per device.
|
||||
pub const DEFAULT_MAX_MSI_IRQS_PER_DEVICE: InterruptIndex = 256;
|
||||
|
||||
/// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework.
|
||||
///
|
||||
/// The KVM framework provides methods to inject interrupts into the target virtual machines, which
|
||||
/// uses irqfd to notity the KVM kernel module for injecting interrupts. When the interrupt source,
|
||||
/// usually a virtual device backend in userspace, writes to the irqfd file descriptor, the KVM
|
||||
/// kernel module will inject a corresponding interrupt into the target VM according to the IRQ
|
||||
/// routing configuration.
|
||||
pub struct KvmIrqManager {
|
||||
mgr: Mutex<KvmIrqManagerObj>,
|
||||
}
|
||||
|
||||
impl KvmIrqManager {
|
||||
/// Create a new interrupt manager based on the Linux KVM framework.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `vmfd`: The KVM VM file descriptor, which will be used to access the KVM subsystem.
|
||||
pub fn new(vmfd: Arc<VmFd>) -> Self {
|
||||
KvmIrqManager {
|
||||
mgr: Mutex::new(KvmIrqManagerObj {
|
||||
vmfd: vmfd.clone(),
|
||||
groups: HashMap::new(),
|
||||
routes: Arc::new(KvmIrqRouting::new(vmfd)),
|
||||
max_msi_irqs: DEFAULT_MAX_MSI_IRQS_PER_DEVICE,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the interrupt manager for generating interrupts into the target VM.
|
||||
pub fn initialize(&self) -> Result<()> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let mgr = self.mgr.lock().unwrap();
|
||||
mgr.initialize()
|
||||
}
|
||||
|
||||
/// Set maximum supported MSI interrupts per device.
|
||||
pub fn set_max_msi_irqs(&self, max_msi_irqs: InterruptIndex) {
|
||||
let mut mgr = self.mgr.lock().unwrap();
|
||||
mgr.max_msi_irqs = max_msi_irqs;
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptManager for KvmIrqManager {
|
||||
fn create_group(
|
||||
&self,
|
||||
ty: InterruptSourceType,
|
||||
base: InterruptIndex,
|
||||
count: u32,
|
||||
) -> Result<Arc<Box<dyn InterruptSourceGroup>>> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let mut mgr = self.mgr.lock().unwrap();
|
||||
mgr.create_group(ty, base, count)
|
||||
}
|
||||
|
||||
fn destroy_group(&self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let mut mgr = self.mgr.lock().unwrap();
|
||||
mgr.destroy_group(group)
|
||||
}
|
||||
}
|
||||
|
||||
struct KvmIrqManagerObj {
|
||||
vmfd: Arc<VmFd>,
|
||||
routes: Arc<KvmIrqRouting>,
|
||||
groups: HashMap<InterruptIndex, Arc<Box<dyn InterruptSourceGroup>>>,
|
||||
max_msi_irqs: InterruptIndex,
|
||||
}
|
||||
|
||||
impl KvmIrqManagerObj {
|
||||
fn initialize(&self) -> Result<()> {
|
||||
self.routes.initialize()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_group(
|
||||
&mut self,
|
||||
ty: InterruptSourceType,
|
||||
base: InterruptIndex,
|
||||
count: u32,
|
||||
) -> Result<Arc<Box<dyn InterruptSourceGroup>>> {
|
||||
#[allow(unreachable_patterns)]
|
||||
let group: Arc<Box<dyn InterruptSourceGroup>> = match ty {
|
||||
#[cfg(feature = "kvm-legacy-irq")]
|
||||
InterruptSourceType::LegacyIrq => Arc::new(Box::new(LegacyIrq::new(
|
||||
base,
|
||||
count,
|
||||
self.vmfd.clone(),
|
||||
self.routes.clone(),
|
||||
)?)),
|
||||
#[cfg(feature = "kvm-msi-irq")]
|
||||
InterruptSourceType::MsiIrq => Arc::new(Box::new(MsiIrq::new(
|
||||
base,
|
||||
count,
|
||||
self.max_msi_irqs,
|
||||
self.vmfd.clone(),
|
||||
self.routes.clone(),
|
||||
)?)),
|
||||
_ => return Err(Error::from(ErrorKind::InvalidInput)),
|
||||
};
|
||||
|
||||
self.groups.insert(base, group.clone());
|
||||
|
||||
Ok(group)
|
||||
}
|
||||
|
||||
fn destroy_group(&mut self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()> {
|
||||
self.groups.remove(&group.base());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Use (entry.type, entry.gsi) as the hash key because entry.gsi can't uniquely identify an
|
||||
// interrupt source on x86 platforms. The PIC and IOAPIC may share the same GSI on x86 platforms.
|
||||
fn hash_key(entry: &kvm_irq_routing_entry) -> u64 {
|
||||
let type1 = match entry.type_ {
|
||||
#[cfg(feature = "kvm-legacy-irq")]
|
||||
kvm_bindings::KVM_IRQ_ROUTING_IRQCHIP => unsafe { entry.u.irqchip.irqchip },
|
||||
_ => 0u32,
|
||||
};
|
||||
(u64::from(type1) << 48 | u64::from(entry.type_) << 32) | u64::from(entry.gsi)
|
||||
}
|
||||
|
||||
pub(super) struct KvmIrqRouting {
|
||||
vm_fd: Arc<VmFd>,
|
||||
routes: Mutex<HashMap<u64, kvm_irq_routing_entry>>,
|
||||
}
|
||||
|
||||
impl KvmIrqRouting {
|
||||
pub(super) fn new(vm_fd: Arc<VmFd>) -> Self {
|
||||
KvmIrqRouting {
|
||||
vm_fd,
|
||||
routes: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn initialize(&self) -> Result<()> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
#[allow(unused_mut)]
|
||||
let mut routes = self.routes.lock().unwrap();
|
||||
|
||||
#[cfg(feature = "kvm-legacy-irq")]
|
||||
LegacyIrq::initialize_legacy(&mut routes)?;
|
||||
|
||||
self.set_routing(&routes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_routing(&self, routes: &HashMap<u64, kvm_irq_routing_entry>) -> Result<()> {
|
||||
// Allocate enough buffer memory.
|
||||
let elem_sz = std::mem::size_of::<kvm_irq_routing>();
|
||||
let total_sz = std::mem::size_of::<kvm_irq_routing_entry>() * routes.len() + elem_sz;
|
||||
let elem_cnt = (total_sz + elem_sz - 1) / elem_sz;
|
||||
let mut irq_routings = Vec::<kvm_irq_routing>::with_capacity(elem_cnt);
|
||||
irq_routings.resize_with(elem_cnt, Default::default);
|
||||
|
||||
// Prepare the irq_routing header.
|
||||
let irq_routing = &mut irq_routings[0];
|
||||
irq_routing.nr = routes.len() as u32;
|
||||
irq_routing.flags = 0;
|
||||
|
||||
// Safe because we have just allocated enough memory above.
|
||||
let irq_routing_entries = unsafe { irq_routing.entries.as_mut_slice(routes.len()) };
|
||||
for (idx, entry) in routes.values().enumerate() {
|
||||
irq_routing_entries[idx] = *entry;
|
||||
}
|
||||
|
||||
self.vm_fd
|
||||
.set_gsi_routing(irq_routing)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "kvm-msi-generic")]
|
||||
impl KvmIrqRouting {
|
||||
pub(super) fn add(&self, entries: &[kvm_irq_routing_entry]) -> Result<()> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let mut routes = self.routes.lock().unwrap();
|
||||
for entry in entries {
|
||||
if entry.gsi >= MAX_IRQS {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
} else if routes.contains_key(&hash_key(entry)) {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EEXIST));
|
||||
}
|
||||
}
|
||||
|
||||
for entry in entries {
|
||||
let _ = routes.insert(hash_key(entry), *entry);
|
||||
}
|
||||
self.set_routing(&routes)
|
||||
}
|
||||
|
||||
pub(super) fn remove(&self, entries: &[kvm_irq_routing_entry]) -> Result<()> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let mut routes = self.routes.lock().unwrap();
|
||||
for entry in entries {
|
||||
let _ = routes.remove(&hash_key(entry));
|
||||
}
|
||||
self.set_routing(&routes)
|
||||
}
|
||||
|
||||
pub(super) fn modify(&self, entry: &kvm_irq_routing_entry) -> Result<()> {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let mut routes = self.routes.lock().unwrap();
|
||||
if !routes.contains_key(&hash_key(entry)) {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::ENOENT));
|
||||
}
|
||||
|
||||
let _ = routes.insert(hash_key(entry), *entry);
|
||||
self.set_routing(&routes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function convert from vmm_sys_util::errno::Error to std::io::Error.
|
||||
pub fn from_sys_util_errno(e: vmm_sys_util::errno::Error) -> std::io::Error {
|
||||
std::io::Error::from_raw_os_error(e.errno())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::manager::tests::create_vm_fd;
|
||||
|
||||
fn create_irq_group(
|
||||
manager: Arc<KvmIrqManager>,
|
||||
_vmfd: Arc<VmFd>,
|
||||
) -> Arc<Box<dyn InterruptSourceGroup>> {
|
||||
let base = 0;
|
||||
let count = 1;
|
||||
|
||||
manager
|
||||
.create_group(InterruptSourceType::LegacyIrq, base, count)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn create_msi_group(
|
||||
manager: Arc<KvmIrqManager>,
|
||||
_vmfd: Arc<VmFd>,
|
||||
) -> Arc<Box<dyn InterruptSourceGroup>> {
|
||||
let base = 168;
|
||||
let count = 32;
|
||||
|
||||
manager
|
||||
.create_group(InterruptSourceType::MsiIrq, base, count)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn create_kvm_irq_manager() -> (Arc<VmFd>, KvmIrqManager) {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
let manager = KvmIrqManager::new(vmfd.clone());
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
manager.initialize().unwrap();
|
||||
(vmfd, manager)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_kvm_irq_manager() {
|
||||
let _ = create_kvm_irq_manager();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kvm_irq_manager_opt() {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
let manager = Arc::new(KvmIrqManager::new(vmfd.clone()));
|
||||
manager.initialize().unwrap();
|
||||
|
||||
// set max irqs
|
||||
manager.set_max_msi_irqs(0x128);
|
||||
assert_eq!(manager.mgr.lock().unwrap().max_msi_irqs, 0x128);
|
||||
|
||||
// irq
|
||||
let group = create_irq_group(manager.clone(), vmfd.clone());
|
||||
let _ = group.clone();
|
||||
manager.destroy_group(group).unwrap();
|
||||
|
||||
// msi
|
||||
let group = create_msi_group(manager.clone(), vmfd);
|
||||
let _ = group.clone();
|
||||
manager.destroy_group(group).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_sys_util_errno() {
|
||||
let error = vmm_sys_util::errno::Error::new(1);
|
||||
let io_error = from_sys_util_errno(error);
|
||||
assert_eq!(io_error.kind(), std::io::ErrorKind::PermissionDenied);
|
||||
}
|
||||
}
|
||||
132
src/dragonball/src/dbs_interrupt/src/kvm/msi_generic.rs
Normal file
132
src/dragonball/src/dbs_interrupt/src/kvm/msi_generic.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Helper utilities for handling MSI interrupts.
|
||||
|
||||
use kvm_bindings::{kvm_irq_routing_entry, KVM_IRQ_ROUTING_MSI};
|
||||
use vmm_sys_util::eventfd::EFD_NONBLOCK;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) struct MsiConfig {
|
||||
pub(super) irqfd: EventFd,
|
||||
pub(crate) config: Mutex<MsiIrqSourceConfig>,
|
||||
}
|
||||
|
||||
impl MsiConfig {
|
||||
pub(crate) fn new() -> Self {
|
||||
MsiConfig {
|
||||
irqfd: EventFd::new(EFD_NONBLOCK).unwrap(),
|
||||
config: Mutex::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn new_msi_routing_entry(
|
||||
gsi: InterruptIndex,
|
||||
msicfg: &MsiIrqSourceConfig,
|
||||
) -> kvm_irq_routing_entry {
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi,
|
||||
type_: KVM_IRQ_ROUTING_MSI,
|
||||
..Default::default()
|
||||
};
|
||||
entry.u.msi.address_hi = msicfg.high_addr;
|
||||
entry.u.msi.address_lo = msicfg.low_addr;
|
||||
entry.u.msi.data = msicfg.data;
|
||||
if let Some(dev_id) = msicfg.device_id {
|
||||
entry.u.msi.__bindgen_anon_1.devid = dev_id;
|
||||
entry.flags = kvm_bindings::KVM_MSI_VALID_DEVID;
|
||||
}
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub(super) fn create_msi_routing_entries(
|
||||
base: InterruptIndex,
|
||||
configs: &[InterruptSourceConfig],
|
||||
) -> Result<Vec<kvm_irq_routing_entry>> {
|
||||
let _ = base
|
||||
.checked_add(configs.len() as u32)
|
||||
.ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?;
|
||||
let mut entries = Vec::with_capacity(configs.len());
|
||||
for (i, ref val) in configs.iter().enumerate() {
|
||||
if let InterruptSourceConfig::MsiIrq(msicfg) = val {
|
||||
let entry = new_msi_routing_entry(base + i as u32, msicfg);
|
||||
entries.push(entry);
|
||||
} else {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_msiconfig() {
|
||||
let config = MsiConfig::new();
|
||||
config.irqfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_msi_routing_single() {
|
||||
let test_gsi = 4;
|
||||
let msi_source_config = MsiIrqSourceConfig {
|
||||
high_addr: 0x1234,
|
||||
low_addr: 0x5678,
|
||||
data: 0x9876,
|
||||
msg_ctl: 0,
|
||||
device_id: None,
|
||||
};
|
||||
let entry = new_msi_routing_entry(test_gsi, &msi_source_config);
|
||||
assert_eq!(entry.gsi, test_gsi);
|
||||
assert_eq!(entry.type_, KVM_IRQ_ROUTING_MSI);
|
||||
unsafe {
|
||||
assert_eq!(entry.u.msi.address_hi, msi_source_config.high_addr);
|
||||
assert_eq!(entry.u.msi.address_lo, msi_source_config.low_addr);
|
||||
assert_eq!(entry.u.msi.data, msi_source_config.data);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "legacy_irq", target_arch = "x86_64"))]
|
||||
#[test]
|
||||
fn test_new_msi_routing_multi() {
|
||||
let mut msi_fds = Vec::with_capacity(16);
|
||||
for _ in 0..16 {
|
||||
msi_fds.push(InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig {
|
||||
high_addr: 0x1234,
|
||||
low_addr: 0x5678,
|
||||
data: 0x9876,
|
||||
msg_ctl: 0,
|
||||
device_id: None,
|
||||
}));
|
||||
}
|
||||
let mut legacy_fds = Vec::with_capacity(16);
|
||||
for _ in 0..16 {
|
||||
legacy_fds.push(InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {}));
|
||||
}
|
||||
|
||||
let base = 0;
|
||||
let entrys = create_msi_routing_entries(0, &msi_fds).unwrap();
|
||||
|
||||
for (i, entry) in entrys.iter().enumerate() {
|
||||
assert_eq!(entry.gsi, (base + i) as u32);
|
||||
assert_eq!(entry.type_, KVM_IRQ_ROUTING_MSI);
|
||||
if let InterruptSourceConfig::MsiIrq(config) = &msi_fds[i] {
|
||||
unsafe {
|
||||
assert_eq!(entry.u.msi.address_hi, config.high_addr);
|
||||
assert_eq!(entry.u.msi.address_lo, config.low_addr);
|
||||
assert_eq!(entry.u.msi.data, config.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(create_msi_routing_entries(0, &legacy_fds).is_err());
|
||||
assert!(create_msi_routing_entries(!0, &msi_fds).is_err());
|
||||
}
|
||||
}
|
||||
276
src/dragonball/src/dbs_interrupt/src/kvm/msi_irq.rs
Normal file
276
src/dragonball/src/dbs_interrupt/src/kvm/msi_irq.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Manage virtual device's PCI MSI/PCI MSIx interrupts based on Linux KVM framework.
|
||||
//!
|
||||
//! To optimize for performance by avoiding unnecessary locking and state checking, we assume that
|
||||
//! the caller will take the responsibility to maintain the interrupt states and only issue valid
|
||||
//! requests to this driver. If the caller doesn't obey the contract, only the current virtual
|
||||
//! machine will be affected, it shouldn't break the host or other virtual machines.
|
||||
|
||||
use super::msi_generic::{create_msi_routing_entries, new_msi_routing_entry, MsiConfig};
|
||||
use super::*;
|
||||
|
||||
pub(super) struct MsiIrq {
|
||||
base: InterruptIndex,
|
||||
count: InterruptIndex,
|
||||
vmfd: Arc<VmFd>,
|
||||
irq_routing: Arc<KvmIrqRouting>,
|
||||
msi_configs: Vec<MsiConfig>,
|
||||
}
|
||||
|
||||
impl MsiIrq {
|
||||
pub(super) fn new(
|
||||
base: InterruptIndex,
|
||||
count: InterruptIndex,
|
||||
max_msi_irqs: InterruptIndex,
|
||||
vmfd: Arc<VmFd>,
|
||||
irq_routing: Arc<KvmIrqRouting>,
|
||||
) -> Result<Self> {
|
||||
if count > max_msi_irqs || base >= MAX_IRQS || base + count > MAX_IRQS {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
let mut msi_configs = Vec::with_capacity(count as usize);
|
||||
for _ in 0..count {
|
||||
msi_configs.push(MsiConfig::new());
|
||||
}
|
||||
|
||||
Ok(MsiIrq {
|
||||
base,
|
||||
count,
|
||||
vmfd,
|
||||
irq_routing,
|
||||
msi_configs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptSourceGroup for MsiIrq {
|
||||
fn interrupt_type(&self) -> InterruptSourceType {
|
||||
InterruptSourceType::MsiIrq
|
||||
}
|
||||
|
||||
fn len(&self) -> u32 {
|
||||
self.count
|
||||
}
|
||||
|
||||
fn base(&self) -> u32 {
|
||||
self.base
|
||||
}
|
||||
|
||||
fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()> {
|
||||
if configs.len() != self.count as usize {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
// First add IRQ routings for all the MSI interrupts.
|
||||
let entries = create_msi_routing_entries(self.base, configs)?;
|
||||
|
||||
self.irq_routing
|
||||
.add(&entries)
|
||||
.or_else(|err| match err.kind() {
|
||||
// The irq_routing was already restored when the snapshot was restored, so the AlreadyExists error is ignored here.
|
||||
std::io::ErrorKind::AlreadyExists => Ok(()),
|
||||
_ => Err(err),
|
||||
})?;
|
||||
|
||||
// Then register irqfds to the KVM module.
|
||||
for i in 0..self.count {
|
||||
let irqfd = &self.msi_configs[i as usize].irqfd;
|
||||
self.vmfd
|
||||
.register_irqfd(irqfd, self.base + i)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable(&self) -> Result<()> {
|
||||
// First unregister all irqfds, so it won't trigger anymore.
|
||||
for i in 0..self.count {
|
||||
let irqfd = &self.msi_configs[i as usize].irqfd;
|
||||
self.vmfd
|
||||
.unregister_irqfd(irqfd, self.base + i)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
}
|
||||
|
||||
// Then tear down the IRQ routings for all the MSI interrupts.
|
||||
let mut entries = Vec::with_capacity(self.count as usize);
|
||||
for i in 0..self.count {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let msicfg = self.msi_configs[i as usize].config.lock().unwrap();
|
||||
let entry = new_msi_routing_entry(self.base + i, &msicfg);
|
||||
entries.push(entry);
|
||||
}
|
||||
self.irq_routing.remove(&entries)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
fn update(&self, index: InterruptIndex, config: &InterruptSourceConfig) -> Result<()> {
|
||||
if index >= self.count {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
if let InterruptSourceConfig::MsiIrq(ref cfg) = config {
|
||||
// Safe to unwrap because there's no legal way to break the mutex.
|
||||
let entry = {
|
||||
let mut msicfg = self.msi_configs[index as usize].config.lock().unwrap();
|
||||
msicfg.high_addr = cfg.high_addr;
|
||||
msicfg.low_addr = cfg.low_addr;
|
||||
msicfg.data = cfg.data;
|
||||
msicfg.device_id = cfg.device_id;
|
||||
new_msi_routing_entry(self.base + index, &msicfg)
|
||||
};
|
||||
self.irq_routing.modify(&entry)
|
||||
} else {
|
||||
Err(std::io::Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
}
|
||||
|
||||
fn notifier(&self, index: InterruptIndex) -> Option<&EventFd> {
|
||||
if index >= self.count {
|
||||
None
|
||||
} else {
|
||||
let msi_config = &self.msi_configs[index as usize];
|
||||
Some(&msi_config.irqfd)
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger(&self, index: InterruptIndex) -> Result<()> {
|
||||
// Assume that the caller will maintain the interrupt states and only call this function
|
||||
// when suitable.
|
||||
if index >= self.count {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
let msi_config = &self.msi_configs[index as usize];
|
||||
msi_config.irqfd.write(1)
|
||||
}
|
||||
|
||||
fn mask(&self, index: InterruptIndex) -> Result<()> {
|
||||
if index >= self.count {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
let irqfd = &self.msi_configs[index as usize].irqfd;
|
||||
self.vmfd
|
||||
.unregister_irqfd(irqfd, self.base + index)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmask(&self, index: InterruptIndex) -> Result<()> {
|
||||
if index >= self.count {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
let irqfd = &self.msi_configs[index as usize].irqfd;
|
||||
self.vmfd
|
||||
.register_irqfd(irqfd, self.base + index)
|
||||
.map_err(from_sys_util_errno)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pending_state(&self, index: InterruptIndex) -> bool {
|
||||
if index >= self.count {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Peak the EventFd.count by reading and writing back.
|
||||
// The irqfd must be in NON-BLOCKING mode.
|
||||
let irqfd = &self.msi_configs[index as usize].irqfd;
|
||||
match irqfd.read() {
|
||||
Err(_) => false,
|
||||
Ok(count) => {
|
||||
if count != 0 && irqfd.write(count).is_err() {
|
||||
// Hope the caller will handle the pending state corrrectly,
|
||||
// then no interrupt will be lost.
|
||||
// Really no way to recover here!
|
||||
}
|
||||
count != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::manager::tests::create_vm_fd;
|
||||
|
||||
#[test]
|
||||
#[allow(unreachable_patterns)]
|
||||
fn test_msi_interrupt_group() {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
|
||||
let rounting = Arc::new(KvmIrqRouting::new(vmfd.clone()));
|
||||
rounting.initialize().unwrap();
|
||||
|
||||
let base = 168;
|
||||
let count = 32;
|
||||
let group = MsiIrq::new(
|
||||
base,
|
||||
count,
|
||||
DEFAULT_MAX_MSI_IRQS_PER_DEVICE,
|
||||
vmfd.clone(),
|
||||
rounting.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut msi_fds = Vec::with_capacity(count as usize);
|
||||
|
||||
match group.interrupt_type() {
|
||||
InterruptSourceType::MsiIrq => {}
|
||||
_ => {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..count {
|
||||
let msi_source_config = MsiIrqSourceConfig {
|
||||
high_addr: 0x1234,
|
||||
low_addr: 0x5678,
|
||||
data: 0x9876,
|
||||
msg_ctl: 0x6789,
|
||||
device_id: None,
|
||||
};
|
||||
msi_fds.push(InterruptSourceConfig::MsiIrq(msi_source_config));
|
||||
}
|
||||
|
||||
group.enable(&msi_fds).unwrap();
|
||||
assert_eq!(group.len(), count);
|
||||
assert_eq!(group.base(), base);
|
||||
|
||||
for i in 0..count {
|
||||
let msi_source_config = MsiIrqSourceConfig {
|
||||
high_addr: i + 0x1234,
|
||||
low_addr: i + 0x5678,
|
||||
data: i + 0x9876,
|
||||
msg_ctl: i + 0x6789,
|
||||
device_id: None,
|
||||
};
|
||||
group.notifier(i).unwrap().write(1).unwrap();
|
||||
group.trigger(i).unwrap();
|
||||
group
|
||||
.update(0, &InterruptSourceConfig::MsiIrq(msi_source_config))
|
||||
.unwrap();
|
||||
}
|
||||
assert!(group.trigger(33).is_err());
|
||||
group.disable().unwrap();
|
||||
|
||||
assert!(MsiIrq::new(
|
||||
base,
|
||||
DEFAULT_MAX_MSI_IRQS_PER_DEVICE + 1,
|
||||
DEFAULT_MAX_MSI_IRQS_PER_DEVICE,
|
||||
vmfd.clone(),
|
||||
rounting.clone()
|
||||
)
|
||||
.is_err());
|
||||
assert!(MsiIrq::new(1100, 1, DEFAULT_MAX_MSI_IRQS_PER_DEVICE, vmfd, rounting).is_err());
|
||||
}
|
||||
}
|
||||
244
src/dragonball/src/dbs_interrupt/src/lib.rs
Normal file
244
src/dragonball/src/dbs_interrupt/src/lib.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
// Copyright (C) 2019-2020 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs to manage interrupt sources for devices.
|
||||
//!
|
||||
//! Software indicating an event that needs immediate attention. An interrupt alerts the processor
|
||||
//! to a high-priority condition requiring the interruption of the current code the processor is
|
||||
//! executing. The processor responds by suspending its current activities, saving its state, and
|
||||
//! executing a function called an interrupt handler (or an interrupt service routine, ISR) to deal
|
||||
//! with the event. This interruption is temporary, and, after the interrupt handler finishes,
|
||||
//! unless handling the interrupt has emitted a fatal error, the processor resumes normal
|
||||
//! activities.
|
||||
//!
|
||||
//! Hardware interrupts are used by devices to communicate that they require attention from the
|
||||
//! operating system, or a bare-metal program running on the CPU if there are no OSes. The act of
|
||||
//! initiating a hardware interrupt is referred to as an interrupt request (IRQ). Different devices
|
||||
//! are usually associated with different interrupts using a unique value associated with each
|
||||
//! interrupt. This makes it possible to know which hardware device caused which interrupts. These
|
||||
//! interrupt values are often called IRQ lines, or just interrupt lines.
|
||||
//!
|
||||
//! Nowadays, IRQ lines is not the only mechanism to deliver device interrupts to processors. MSI
|
||||
//! [(Message Signaled Interrupt)](https://en.wikipedia.org/wiki/Message_Signaled_Interrupts) is
|
||||
//! another commonly used alternative in-band method of signaling an interrupt, using special
|
||||
//! in-band messages to replace traditional out-of-band assertion of dedicated interrupt lines.
|
||||
//! While more complex to implement in a device, message signaled interrupts have some significant
|
||||
//! advantages over pin-based out-of-band interrupt signaling. Message signaled interrupts are
|
||||
//! supported in PCI bus since its version 2.2, and in later available PCI Express bus. Some non-PCI
|
||||
//! architectures also use message signaled interrupts.
|
||||
//!
|
||||
//! While IRQ is a term commonly used by Operating Systems when dealing with hardware interrupts,
|
||||
//! the IRQ numbers managed by OSes are independent of the ones managed by VMM. For simplicity sake,
|
||||
//! the term `Interrupt Source` is used instead of IRQ to represent both pin-based interrupts and
|
||||
//! MSI interrupts.
|
||||
//!
|
||||
//! A device may support multiple types of interrupts, and each type of interrupt may support one or
|
||||
//! multiple interrupt sources. For example, a PCI device may support:
|
||||
//! * Legacy Irq: exactly one interrupt source.
|
||||
//! * PCI MSI Irq: 1,2,4,8,16,32 interrupt sources.
|
||||
//! * PCI MSIx Irq: 2^n(n=0-11) interrupt sources.
|
||||
//!
|
||||
//! A distinct Interrupt Source Identifier (ISID) will be assigned to each interrupt source. An ID
|
||||
//! allocator will be used to allocate and free Interrupt Source Identifiers for devices. To
|
||||
//! decouple this crate from the ID allocator, here we doesn't take the responsibility to
|
||||
//! allocate/free Interrupt Source IDs but only makes use of assigned IDs.
|
||||
//!
|
||||
//! The overall flow to deal with interrupts is:
|
||||
//! * the VMM creates an interrupt manager
|
||||
//! * the VMM creates a device manager, passing on an reference to the interrupt manager
|
||||
//! * the device manager passes on an reference to the interrupt manager to all registered devices
|
||||
//! * guest kernel loads drivers for virtual devices
|
||||
//! * guest device driver determines the type and number of interrupts needed, and update the device
|
||||
//! configuration
|
||||
//! * the virtual device backend requests the interrupt manager to create an interrupt group
|
||||
//! according to guest configuration information
|
||||
|
||||
use std::io::Error;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
mod manager;
|
||||
pub use manager::MSI_DEVICE_ID_SHIFT;
|
||||
pub use manager::{DeviceInterruptManager, DeviceInterruptMode, InterruptStatusRegister32};
|
||||
|
||||
mod notifier;
|
||||
pub use self::notifier::*;
|
||||
|
||||
#[cfg(feature = "kvm-irq")]
|
||||
pub mod kvm;
|
||||
#[cfg(feature = "kvm-irq")]
|
||||
pub use self::kvm::KvmIrqManager;
|
||||
|
||||
/// Reuse std::io::Result to simplify interoperability among crates.
|
||||
pub type Result<T> = std::io::Result<T>;
|
||||
|
||||
/// Data type to store an interrupt source identifier.
|
||||
pub type InterruptIndex = u32;
|
||||
|
||||
/// Type of interrupt source.
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum InterruptSourceType {
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
/// Legacy Pin-based Interrupt.
|
||||
/// On x86 platforms, legacy interrupts are routed through 8259 PICs and/or IOAPICs.
|
||||
LegacyIrq,
|
||||
#[cfg(feature = "msi-irq")]
|
||||
/// Message Signaled Interrupt (PCI MSI/PCI MSIx etc).
|
||||
/// Some non-PCI devices (like HPET on x86) make use of generic MSI in platform specific ways.
|
||||
MsiIrq,
|
||||
}
|
||||
|
||||
/// Configuration data for an interrupt source.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum InterruptSourceConfig {
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
/// Configuration data for Legacy interrupts.
|
||||
LegacyIrq(LegacyIrqSourceConfig),
|
||||
#[cfg(feature = "msi-irq")]
|
||||
/// Configuration data for PciMsi, PciMsix and generic MSI interrupts.
|
||||
MsiIrq(MsiIrqSourceConfig),
|
||||
}
|
||||
|
||||
/// Configuration data for legacy interrupts.
|
||||
///
|
||||
/// On x86 platforms, legacy interrupts means those interrupts routed through PICs or IOAPICs.
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct LegacyIrqSourceConfig {}
|
||||
|
||||
/// Configuration data for GenericMsi, PciMsi, PciMsix interrupts.
|
||||
#[cfg(feature = "msi-irq")]
|
||||
#[derive(Default, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct MsiIrqSourceConfig {
|
||||
/// High address to deliver message signaled interrupt.
|
||||
pub high_addr: u32,
|
||||
/// Low address to deliver message signaled interrupt.
|
||||
pub low_addr: u32,
|
||||
/// Data to write to deliver message signaled interrupt.
|
||||
pub data: u32,
|
||||
/// Interrupt control state.
|
||||
pub msg_ctl: u32,
|
||||
/// Device id indicate the device who triggers this msi irq.
|
||||
pub device_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// Trait to manage interrupt sources for virtual device backends.
|
||||
///
|
||||
/// The InterruptManager implementations should protect itself from concurrent accesses internally,
|
||||
/// so it could be invoked from multi-threaded context.
|
||||
pub trait InterruptManager {
|
||||
/// Create an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object to manage interrupt
|
||||
/// sources for a virtual device.
|
||||
///
|
||||
/// An [InterruptSourceGroup](trait.InterruptSourceGroup.html) object manages all interrupt
|
||||
/// sources of the same type for a virtual device.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * type_: type of interrupt source.
|
||||
/// * base: base Interrupt Source ID to be managed by the group object.
|
||||
/// * count: number of Interrupt Sources to be managed by the group object.
|
||||
fn create_group(
|
||||
&self,
|
||||
type_: InterruptSourceType,
|
||||
base: InterruptIndex,
|
||||
count: InterruptIndex,
|
||||
) -> Result<Arc<Box<dyn InterruptSourceGroup>>>;
|
||||
|
||||
/// Destroy an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object created by
|
||||
/// [create_group()](trait.InterruptManager.html#tymethod.create_group).
|
||||
///
|
||||
/// Assume the caller takes the responsibility to disable all interrupt sources of the group
|
||||
/// before calling destroy_group(). This assumption helps to simplify InterruptSourceGroup
|
||||
/// implementations.
|
||||
fn destroy_group(&self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: InterruptManager> InterruptManager for Arc<T> {
|
||||
fn create_group(
|
||||
&self,
|
||||
type_: InterruptSourceType,
|
||||
base: u32,
|
||||
count: u32,
|
||||
) -> std::result::Result<Arc<Box<dyn InterruptSourceGroup>>, Error> {
|
||||
self.deref().create_group(type_, base, count)
|
||||
}
|
||||
|
||||
fn destroy_group(
|
||||
&self,
|
||||
group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
) -> std::result::Result<(), Error> {
|
||||
self.deref().destroy_group(group)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait to manage a group of interrupt sources for a device.
|
||||
///
|
||||
/// A device may support several types of interrupts, and each type of interrupt may contain one or
|
||||
/// multiple continuous interrupt sources. For example, a PCI device may concurrently support:
|
||||
/// * Legacy Irq: exactly one interrupt source.
|
||||
/// * PCI MSI Irq: 1,2,4,8,16,32 interrupt sources.
|
||||
/// * PCI MSIx Irq: 2^n(n=0-11) interrupt sources.
|
||||
///
|
||||
/// PCI MSI interrupts of a device may not be configured individually, and must configured as a
|
||||
/// whole block. So all interrupts of the same type of a device are abstracted as an
|
||||
/// [InterruptSourceGroup](trait.InterruptSourceGroup.html) object, instead of abstracting each
|
||||
/// interrupt source as a distinct InterruptSource.
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub trait InterruptSourceGroup: Send + Sync {
|
||||
/// Get type of interrupt sources managed by the group.
|
||||
fn interrupt_type(&self) -> InterruptSourceType;
|
||||
|
||||
/// Get number of interrupt sources managed by the group.
|
||||
fn len(&self) -> InterruptIndex;
|
||||
|
||||
/// Get base of the assigned Interrupt Source Identifiers.
|
||||
fn base(&self) -> InterruptIndex;
|
||||
|
||||
/// Enable the interrupt sources in the group to generate interrupts.
|
||||
fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()>;
|
||||
|
||||
/// Disable the interrupt sources in the group to generate interrupts.
|
||||
fn disable(&self) -> Result<()>;
|
||||
|
||||
/// Update the interrupt source group configuration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * index: sub-index into the group.
|
||||
/// * config: configuration data for the interrupt source.
|
||||
fn update(&self, index: InterruptIndex, config: &InterruptSourceConfig) -> Result<()>;
|
||||
|
||||
/// Returns an interrupt notifier from this interrupt.
|
||||
///
|
||||
/// An interrupt notifier allows for external components and processes to inject interrupts into
|
||||
/// guest, by writing to the file returned by this method.
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Inject an interrupt from this interrupt source into the guest.
|
||||
///
|
||||
/// If the interrupt has an associated `interrupt_status` register, all bits set in `flag` will
|
||||
/// be atomically ORed into the `interrupt_status` register.
|
||||
fn trigger(&self, index: InterruptIndex) -> Result<()>;
|
||||
|
||||
/// Mask an interrupt from this interrupt source.
|
||||
fn mask(&self, _index: InterruptIndex) -> Result<()> {
|
||||
// Not all interrupt sources can be disabled.
|
||||
// To accommodate this, we can have a no-op here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unmask an interrupt from this interrupt source.
|
||||
fn unmask(&self, _index: InterruptIndex) -> Result<()> {
|
||||
// Not all interrupt sources can be disabled.
|
||||
// To accommodate this, we can have a no-op here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check whether there's pending interrupt.
|
||||
fn get_pending_state(&self, _index: InterruptIndex) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
794
src/dragonball/src/dbs_interrupt/src/manager.rs
Normal file
794
src/dragonball/src/dbs_interrupt/src/manager.rs
Normal file
@@ -0,0 +1,794 @@
|
||||
// Copyright (C) 2019-2020 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Interrupt manager to manage and switch device interrupt modes.
|
||||
//!
|
||||
//! A device may support multiple interrupt modes. For example, a PCI device may support legacy, PCI
|
||||
//! MSI and PCI MSIx interrupts. This interrupt manager helps a device backend driver to manage its
|
||||
//! interrupts and provides interfaces to switch interrupt working modes.
|
||||
use std::io::{Error, Result};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use dbs_device::resources::DeviceResources;
|
||||
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
use super::LegacyIrqSourceConfig;
|
||||
#[cfg(feature = "msi-irq")]
|
||||
use super::MsiIrqSourceConfig;
|
||||
use super::{InterruptManager, InterruptSourceConfig, InterruptSourceGroup, InterruptSourceType};
|
||||
|
||||
/// Defines the offset when device_id is recorded to msi.
|
||||
///
|
||||
/// For the origin of this value, please refer to the comment of set_msi_device_id function.
|
||||
pub const MSI_DEVICE_ID_SHIFT: u8 = 3;
|
||||
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
const LEGACY_CONFIGS: [InterruptSourceConfig; 1] =
|
||||
[InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {})];
|
||||
|
||||
#[cfg(feature = "msi-irq")]
|
||||
const MSI_INT_MASK_BIT: u8 = 0;
|
||||
#[cfg(feature = "msi-irq")]
|
||||
const MSI_INT_MASK: u32 = (1 << MSI_INT_MASK_BIT) as u32;
|
||||
|
||||
/// Device interrupt working modes.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum DeviceInterruptMode {
|
||||
/// The device interrupt manager has been disabled.
|
||||
Disabled = 0,
|
||||
/// The device interrupt manager works in legacy irq mode.
|
||||
LegacyIrq = 1,
|
||||
/// The device interrupt manager works in generic MSI mode.
|
||||
GenericMsiIrq = 2,
|
||||
/// The device interrupt manager works in PCI MSI mode.
|
||||
PciMsiIrq = 3,
|
||||
/// The device interrupt manager works in PCI MSI-x mode.
|
||||
PciMsixIrq = 4,
|
||||
}
|
||||
|
||||
/// A struct to manage interrupts and interrupt modes for a device.
|
||||
///
|
||||
/// The interrupt manager may support multiple working mode. For example, an interrupt manager for a
|
||||
/// PCI device may work in legacy mode, PCI MSI mode or PCI MSIx mode. Under certain conditions, the
|
||||
/// interrupt manager may switch between interrupt working modes. To simplify implementation,
|
||||
/// switching working mode is only supported at configuration stage and will be disabled at runtime
|
||||
/// stage. The DeviceInterruptManager::enable() switches the interrupt manager from configuration
|
||||
/// stage into runtime stage. And DeviceInterruptManager::reset() switches from runtime stage back
|
||||
/// to initial configuration stage.
|
||||
pub struct DeviceInterruptManager<T: InterruptManager> {
|
||||
mode: DeviceInterruptMode,
|
||||
activated: bool,
|
||||
current_idx: usize,
|
||||
mode2idx: [usize; 5],
|
||||
intr_mgr: T,
|
||||
intr_groups: Vec<Arc<Box<dyn InterruptSourceGroup>>>,
|
||||
#[cfg(feature = "msi-irq")]
|
||||
msi_config: Vec<InterruptSourceConfig>,
|
||||
/// Device id indicate the device who triggers msi irq.
|
||||
device_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl<T: InterruptManager> DeviceInterruptManager<T> {
|
||||
/// Create an interrupt manager for a device.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `intr_mgr`: underline interrupt manager to allocate/free interrupt groups.
|
||||
/// * `resources`: resources assigned to the device, including assigned interrupt resources.
|
||||
pub fn new(intr_mgr: T, resources: &DeviceResources) -> Result<Self> {
|
||||
let mut mgr = DeviceInterruptManager {
|
||||
mode: DeviceInterruptMode::Disabled,
|
||||
activated: false,
|
||||
current_idx: usize::MAX,
|
||||
mode2idx: [usize::MAX; 5],
|
||||
intr_mgr,
|
||||
intr_groups: Vec::new(),
|
||||
#[cfg(feature = "msi-irq")]
|
||||
msi_config: Vec::new(),
|
||||
device_id: None,
|
||||
};
|
||||
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
{
|
||||
if let Some(irq) = resources.get_legacy_irq() {
|
||||
let group = mgr
|
||||
.intr_mgr
|
||||
.create_group(InterruptSourceType::LegacyIrq, irq, 1)?;
|
||||
mgr.mode2idx[DeviceInterruptMode::LegacyIrq as usize] = mgr.intr_groups.len();
|
||||
mgr.intr_groups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "msi-irq")]
|
||||
{
|
||||
if let Some(msi) = resources.get_generic_msi_irqs() {
|
||||
let group = mgr
|
||||
.intr_mgr
|
||||
.create_group(InterruptSourceType::MsiIrq, msi.0, msi.1)?;
|
||||
mgr.resize_msi_config_space(group.len());
|
||||
mgr.mode2idx[DeviceInterruptMode::GenericMsiIrq as usize] = mgr.intr_groups.len();
|
||||
mgr.intr_groups.push(group);
|
||||
}
|
||||
|
||||
if let Some(msi) = resources.get_pci_msi_irqs() {
|
||||
let group = mgr
|
||||
.intr_mgr
|
||||
.create_group(InterruptSourceType::MsiIrq, msi.0, msi.1)?;
|
||||
mgr.resize_msi_config_space(group.len());
|
||||
mgr.mode2idx[DeviceInterruptMode::PciMsiIrq as usize] = mgr.intr_groups.len();
|
||||
mgr.intr_groups.push(group);
|
||||
}
|
||||
|
||||
if let Some(msi) = resources.get_pci_msix_irqs() {
|
||||
let group = mgr
|
||||
.intr_mgr
|
||||
.create_group(InterruptSourceType::MsiIrq, msi.0, msi.1)?;
|
||||
mgr.resize_msi_config_space(group.len());
|
||||
mgr.mode2idx[DeviceInterruptMode::PciMsixIrq as usize] = mgr.intr_groups.len();
|
||||
mgr.intr_groups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mgr)
|
||||
}
|
||||
|
||||
/// Set device_id for MSI routing
|
||||
pub fn set_device_id(&mut self, device_id: Option<u32>) {
|
||||
self.device_id = device_id;
|
||||
}
|
||||
|
||||
/// Check whether the interrupt manager has been activated.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.activated
|
||||
}
|
||||
|
||||
/// Switch the interrupt manager from configuration stage into runtime stage.
|
||||
///
|
||||
/// The working mode could only be changed at configuration stage, and all requests to change
|
||||
/// working mode at runtime stage will be rejected.
|
||||
///
|
||||
/// If the interrupt manager is still in DISABLED mode when DeviceInterruptManager::enable() is
|
||||
/// called, it will be put into LEGACY mode if LEGACY mode is supported.
|
||||
pub fn enable(&mut self) -> Result<()> {
|
||||
if self.activated {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Enter Legacy mode by default if Legacy mode is supported.
|
||||
if self.mode == DeviceInterruptMode::Disabled
|
||||
&& self.mode2idx[DeviceInterruptMode::LegacyIrq as usize] != usize::MAX
|
||||
{
|
||||
self.set_working_mode(DeviceInterruptMode::LegacyIrq)?;
|
||||
}
|
||||
if self.mode == DeviceInterruptMode::Disabled {
|
||||
return Err(Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
self.intr_groups[self.current_idx].enable(self.get_configs(self.mode))?;
|
||||
self.activated = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch the interrupt manager from runtime stage back into initial configuration stage.
|
||||
///
|
||||
/// Currently we doesn't track the usage of interrupt group object given out by `get_group()`,
|
||||
/// so the the caller needs to take the responsibility to release all interrupt group object
|
||||
/// reference before calling DeviceInterruptManager::reset().
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
if self.activated {
|
||||
self.activated = false;
|
||||
self.intr_groups[self.current_idx].disable()?;
|
||||
}
|
||||
self.set_working_mode(DeviceInterruptMode::Disabled)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current interrupt working mode.
|
||||
pub fn get_working_mode(&mut self) -> DeviceInterruptMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
/// Switch interrupt working mode.
|
||||
///
|
||||
/// Currently switching working mode is only supported during device configuration stage and
|
||||
/// will always return failure if called during device runtime stage. The device switches from
|
||||
/// configuration stage to runtime stage by invoking `DeviceInterruptManager::enable()`. With
|
||||
/// this constraint, the device drivers may call `DeviceInterruptManager::get_group()` to get
|
||||
/// the underline active interrupt group object, and directly calls the interrupt group object's
|
||||
/// methods to trigger/acknowledge interrupts.
|
||||
///
|
||||
/// This is a key design decision for optimizing performance. Though the DeviceInterruptManager
|
||||
/// object itself is not multi-thread safe and must be protected from concurrent access by the
|
||||
/// caller, the interrupt source group object is multi-thread safe and could be called
|
||||
/// concurrently to trigger/acknowledge interrupts. This design may help to improve performance
|
||||
/// for MSI interrupts.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `mode`: target working mode.
|
||||
pub fn set_working_mode(&mut self, mode: DeviceInterruptMode) -> Result<()> {
|
||||
// Can't switch mode agian once enabled.
|
||||
if self.activated {
|
||||
return Err(Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
if mode != self.mode {
|
||||
// Supported state transitions:
|
||||
// - other state -> DISABLED
|
||||
// - DISABLED -> other
|
||||
// - non-legacy -> legacy
|
||||
// - legacy -> non-legacy
|
||||
if self.mode != DeviceInterruptMode::Disabled
|
||||
&& self.mode != DeviceInterruptMode::LegacyIrq
|
||||
&& mode != DeviceInterruptMode::LegacyIrq
|
||||
&& mode != DeviceInterruptMode::Disabled
|
||||
{
|
||||
return Err(Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
// Then enter new state
|
||||
if mode != DeviceInterruptMode::Disabled {
|
||||
self.current_idx = self.mode2idx[mode as usize];
|
||||
} else {
|
||||
// We should reset irq configs when disable interrupt
|
||||
self.reset_configs(mode);
|
||||
}
|
||||
self.mode = mode;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the underline interrupt source group object, so the device driver could concurrently
|
||||
/// trigger/acknowledge interrupts by using the returned group object.
|
||||
pub fn get_group(&self) -> Option<Arc<Box<dyn InterruptSourceGroup>>> {
|
||||
if !self.activated || self.mode == DeviceInterruptMode::Disabled {
|
||||
None
|
||||
} else {
|
||||
Some(self.intr_groups[self.current_idx].clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the underline interrupt source group object, ignore the mode
|
||||
pub fn get_group_unchecked(&self) -> Arc<Box<dyn InterruptSourceGroup>> {
|
||||
self.intr_groups[self.current_idx].clone()
|
||||
}
|
||||
|
||||
/// Reconfigure a specific interrupt in current working mode at configuration or runtime stage.
|
||||
///
|
||||
/// It's mainly used to reconfigure Generic MSI/PCI MSI/PCI MSIx interrupts. Actually legacy
|
||||
/// interrupts don't support reconfiguration yet.
|
||||
#[allow(unused_variables)]
|
||||
pub fn update(&mut self, index: u32) -> Result<()> {
|
||||
if !self.activated {
|
||||
return Err(Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
#[cfg(feature = "msi-irq")]
|
||||
DeviceInterruptMode::GenericMsiIrq
|
||||
| DeviceInterruptMode::PciMsiIrq
|
||||
| DeviceInterruptMode::PciMsixIrq => {
|
||||
let group = &self.intr_groups[self.current_idx];
|
||||
if index >= group.len() || index >= self.msi_config.len() as u32 {
|
||||
return Err(Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
group.update(index, &self.msi_config[index as usize])?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::from_raw_os_error(libc::EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_configs(&self, mode: DeviceInterruptMode) -> &[InterruptSourceConfig] {
|
||||
match mode {
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
DeviceInterruptMode::LegacyIrq => &LEGACY_CONFIGS[..],
|
||||
#[cfg(feature = "msi-irq")]
|
||||
DeviceInterruptMode::GenericMsiIrq
|
||||
| DeviceInterruptMode::PciMsiIrq
|
||||
| DeviceInterruptMode::PciMsixIrq => {
|
||||
let idx = self.mode2idx[mode as usize];
|
||||
let group_len = self.intr_groups[idx].len() as usize;
|
||||
&self.msi_config[0..group_len]
|
||||
}
|
||||
_ => panic!("unhandled interrupt type in get_configs()"),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_configs(&mut self, mode: DeviceInterruptMode) {
|
||||
match mode {
|
||||
#[cfg(feature = "msi-irq")]
|
||||
DeviceInterruptMode::GenericMsiIrq
|
||||
| DeviceInterruptMode::PciMsiIrq
|
||||
| DeviceInterruptMode::PciMsixIrq => {
|
||||
self.msi_config = vec![
|
||||
InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig::default());
|
||||
self.msi_config.len()
|
||||
];
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "msi-irq")]
|
||||
impl<T: InterruptManager> DeviceInterruptManager<T> {
|
||||
/// Set the high address for a MSI message.
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn set_msi_high_address(&mut self, index: u32, data: u32) -> Result<()> {
|
||||
if (index as usize) < self.msi_config.len() {
|
||||
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
|
||||
msi.high_addr = data;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
|
||||
/// Set the low address for a MSI message.
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn set_msi_low_address(&mut self, index: u32, data: u32) -> Result<()> {
|
||||
if (index as usize) < self.msi_config.len() {
|
||||
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
|
||||
msi.low_addr = data;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
|
||||
/// Set the data for a MSI message.
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn set_msi_data(&mut self, index: u32, data: u32) -> Result<()> {
|
||||
if (index as usize) < self.msi_config.len() {
|
||||
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
|
||||
msi.data = data;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
|
||||
/// Set msi irq MASK bit
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn set_msi_mask(&mut self, index: u32, mask: bool) -> Result<()> {
|
||||
if (index as usize) < self.msi_config.len() {
|
||||
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
|
||||
let mut msg_ctl = msi.msg_ctl;
|
||||
msg_ctl &= !MSI_INT_MASK;
|
||||
if mask {
|
||||
msg_ctl |= MSI_INT_MASK;
|
||||
}
|
||||
msi.msg_ctl = msg_ctl;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
|
||||
/// Get msi irq MASK state
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn get_msi_mask(&self, index: u32) -> Result<bool> {
|
||||
if (index as usize) < self.msi_config.len() {
|
||||
if let InterruptSourceConfig::MsiIrq(ref msi) = self.msi_config[index as usize] {
|
||||
return Ok((msi.msg_ctl & MSI_INT_MASK) == MSI_INT_MASK);
|
||||
}
|
||||
}
|
||||
Err(Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// Set the device id for a MSI irq
|
||||
pub fn set_msi_device_id(&mut self, index: u32) -> Result<()> {
|
||||
if (index as usize) < self.msi_config.len() {
|
||||
if let InterruptSourceConfig::MsiIrq(ref mut msi) = self.msi_config[index as usize] {
|
||||
msi.device_id = self.device_id.map(|dev_id| {
|
||||
// An pci device attach to ITS will have a new device id which is use for msi
|
||||
// irq routing. It is calculated according to kernel function PCI_DEVID(),
|
||||
// new_dev_id = (bus << 8) | devfn. In addition, devfn = device_id << 3,
|
||||
// according to pci-host-ecam-generic's spec, and we implement bus = 0.
|
||||
dev_id << MSI_DEVICE_ID_SHIFT
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::from_raw_os_error(libc::EINVAL))
|
||||
}
|
||||
|
||||
fn resize_msi_config_space(&mut self, size: u32) {
|
||||
if self.msi_config.len() < size as usize {
|
||||
self.msi_config =
|
||||
vec![InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig::default()); size as usize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to implement a 32-bit interrupt status register.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct InterruptStatusRegister32 {
|
||||
status: AtomicU32,
|
||||
}
|
||||
|
||||
impl InterruptStatusRegister32 {
|
||||
/// Create a status register instance.
|
||||
pub fn new() -> Self {
|
||||
InterruptStatusRegister32 {
|
||||
status: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read current value of the status register.
|
||||
pub fn read(&self) -> u32 {
|
||||
self.status.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Write value to the status register.
|
||||
pub fn write(&self, value: u32) {
|
||||
self.status.store(value, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Read current value and reset the status register to 0.
|
||||
pub fn read_and_clear(&self) -> u32 {
|
||||
self.status.swap(0, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Set bits into `value`.
|
||||
pub fn set_bits(&self, value: u32) {
|
||||
self.status.fetch_or(value, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Clear bits present in `value`.
|
||||
pub fn clear_bits(&self, value: u32) {
|
||||
self.status.fetch_and(!value, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "kvm-legacy-irq", feature = "kvm-msi-irq"))]
|
||||
pub(crate) mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use dbs_device::resources::{DeviceResources, MsiIrqType, Resource};
|
||||
use kvm_ioctls::{Kvm, VmFd};
|
||||
|
||||
use super::*;
|
||||
use crate::KvmIrqManager;
|
||||
|
||||
pub(crate) fn create_vm_fd() -> VmFd {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
kvm.create_vm().unwrap()
|
||||
}
|
||||
|
||||
fn create_init_resources() -> DeviceResources {
|
||||
let mut resources = DeviceResources::new();
|
||||
|
||||
resources.append(Resource::MmioAddressRange {
|
||||
base: 0xd000_0000,
|
||||
size: 0x10_0000,
|
||||
});
|
||||
resources.append(Resource::LegacyIrq(0));
|
||||
resources.append(Resource::MsiIrq {
|
||||
ty: MsiIrqType::GenericMsi,
|
||||
base: 0x200,
|
||||
size: 0x10,
|
||||
});
|
||||
resources.append(Resource::MsiIrq {
|
||||
ty: MsiIrqType::PciMsi,
|
||||
base: 0x100,
|
||||
size: 0x20,
|
||||
});
|
||||
resources.append(Resource::MsiIrq {
|
||||
ty: MsiIrqType::PciMsix,
|
||||
base: 0x300,
|
||||
size: 0x30,
|
||||
});
|
||||
|
||||
resources
|
||||
}
|
||||
|
||||
fn create_interrupt_manager() -> DeviceInterruptManager<Arc<KvmIrqManager>> {
|
||||
let vmfd = Arc::new(create_vm_fd());
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
vmfd.create_irq_chip().unwrap();
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
let _ = dbs_arch::gic::create_gic(&vmfd, 1);
|
||||
let intr_mgr = Arc::new(KvmIrqManager::new(vmfd));
|
||||
|
||||
let resource = create_init_resources();
|
||||
intr_mgr.initialize().unwrap();
|
||||
DeviceInterruptManager::new(intr_mgr, &resource).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_device_interrupt_manager() {
|
||||
let mut mgr = create_interrupt_manager();
|
||||
|
||||
assert_eq!(mgr.mode, DeviceInterruptMode::Disabled);
|
||||
assert!(!mgr.activated);
|
||||
assert_eq!(mgr.current_idx, usize::MAX);
|
||||
assert_eq!(mgr.intr_groups.len(), 4);
|
||||
assert!(!mgr.is_enabled());
|
||||
assert!(mgr.get_group().is_none());
|
||||
|
||||
// Enter legacy mode by default
|
||||
mgr.enable().unwrap();
|
||||
assert!(mgr.is_enabled());
|
||||
assert_eq!(
|
||||
mgr.mode2idx[DeviceInterruptMode::LegacyIrq as usize],
|
||||
mgr.current_idx
|
||||
);
|
||||
assert!(mgr.get_group().is_some());
|
||||
assert_eq!(
|
||||
mgr.get_group_unchecked().interrupt_type(),
|
||||
InterruptSourceType::LegacyIrq
|
||||
);
|
||||
|
||||
// Disable interrupt manager
|
||||
mgr.reset().unwrap();
|
||||
assert!(!mgr.is_enabled());
|
||||
assert_eq!(
|
||||
mgr.mode2idx[DeviceInterruptMode::LegacyIrq as usize],
|
||||
mgr.current_idx
|
||||
);
|
||||
assert_eq!(mgr.get_working_mode(), DeviceInterruptMode::Disabled);
|
||||
assert!(mgr.get_group().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_interrupt_manager_switch_mode() {
|
||||
let mut mgr = create_interrupt_manager();
|
||||
|
||||
// Can't switch working mode in enabled state.
|
||||
mgr.enable().unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap_err();
|
||||
mgr.reset().unwrap();
|
||||
|
||||
// Switch from LEGACY to PciMsi mode
|
||||
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap_err();
|
||||
|
||||
// Switch from LEGACY to PciMsix mode
|
||||
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap_err();
|
||||
|
||||
// Switch from LEGACY to GenericMsi mode
|
||||
mgr.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap_err();
|
||||
|
||||
// Switch from DISABLED to PciMsi mode
|
||||
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap_err();
|
||||
|
||||
// Switch from DISABLED to PciMsix mode
|
||||
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap_err();
|
||||
|
||||
// Switch from DISABLED to GenericMsi mode
|
||||
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap_err();
|
||||
mgr.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap_err();
|
||||
|
||||
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
|
||||
mgr.set_working_mode(DeviceInterruptMode::Disabled).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_msi_config() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
|
||||
assert!(interrupt_manager.set_msi_data(512, 0).is_err());
|
||||
interrupt_manager.set_msi_data(0, 0).unwrap();
|
||||
assert!(interrupt_manager.set_msi_high_address(512, 0).is_err());
|
||||
interrupt_manager.set_msi_high_address(0, 0).unwrap();
|
||||
assert!(interrupt_manager.set_msi_low_address(512, 0).is_err());
|
||||
interrupt_manager.set_msi_low_address(0, 0).unwrap();
|
||||
assert!(interrupt_manager.get_msi_mask(512).is_err());
|
||||
assert!(!interrupt_manager.get_msi_mask(0).unwrap());
|
||||
assert!(interrupt_manager.set_msi_mask(512, true).is_err());
|
||||
interrupt_manager.set_msi_mask(0, true).unwrap();
|
||||
assert!(interrupt_manager.get_msi_mask(0).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_working_mode_after_activated() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager.activated = true;
|
||||
assert!(interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::Disabled)
|
||||
.is_err());
|
||||
assert!(interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.is_err());
|
||||
assert!(interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.is_err());
|
||||
assert!(interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.is_err());
|
||||
assert!(interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disable2legacy() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager.activated = false;
|
||||
interrupt_manager.mode = DeviceInterruptMode::Disabled;
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disable2nonlegacy() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager.activated = false;
|
||||
interrupt_manager.mode = DeviceInterruptMode::Disabled;
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_legacy2nonlegacy() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager.activated = false;
|
||||
interrupt_manager.mode = DeviceInterruptMode::Disabled;
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonlegacy2legacy() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager.activated = false;
|
||||
interrupt_manager.mode = DeviceInterruptMode::Disabled;
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
interrupt_manager.enable().unwrap();
|
||||
assert!(interrupt_manager.update(0x10).is_err());
|
||||
interrupt_manager.update(0x01).unwrap();
|
||||
interrupt_manager.reset().unwrap();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::LegacyIrq)
|
||||
.unwrap();
|
||||
assert!(interrupt_manager.update(0x10).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_configs() {
|
||||
// legacy irq config
|
||||
{
|
||||
let interrupt_manager = create_interrupt_manager();
|
||||
|
||||
let legacy_config = interrupt_manager.get_configs(DeviceInterruptMode::LegacyIrq);
|
||||
assert_eq!(legacy_config, LEGACY_CONFIGS);
|
||||
}
|
||||
|
||||
// generic irq config
|
||||
{
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::GenericMsiIrq)
|
||||
.unwrap();
|
||||
let msi_config = interrupt_manager.get_configs(DeviceInterruptMode::GenericMsiIrq);
|
||||
assert_eq!(msi_config.len(), 0x10);
|
||||
}
|
||||
|
||||
// msi irq config
|
||||
{
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::PciMsiIrq)
|
||||
.unwrap();
|
||||
let msi_config = interrupt_manager.get_configs(DeviceInterruptMode::PciMsiIrq);
|
||||
assert_eq!(msi_config.len(), 0x20);
|
||||
}
|
||||
|
||||
// msix irq config
|
||||
{
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
interrupt_manager
|
||||
.set_working_mode(DeviceInterruptMode::PciMsixIrq)
|
||||
.unwrap();
|
||||
let msi_config = interrupt_manager.get_configs(DeviceInterruptMode::PciMsixIrq);
|
||||
assert_eq!(msi_config.len(), 0x30);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_configs() {
|
||||
let mut interrupt_manager = create_interrupt_manager();
|
||||
|
||||
interrupt_manager.reset_configs(DeviceInterruptMode::LegacyIrq);
|
||||
interrupt_manager.reset_configs(DeviceInterruptMode::LegacyIrq);
|
||||
|
||||
interrupt_manager.set_msi_data(0, 100).unwrap();
|
||||
interrupt_manager.set_msi_high_address(0, 200).unwrap();
|
||||
interrupt_manager.set_msi_low_address(0, 300).unwrap();
|
||||
|
||||
interrupt_manager.reset_configs(DeviceInterruptMode::GenericMsiIrq);
|
||||
assert_eq!(
|
||||
interrupt_manager.msi_config[0],
|
||||
InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig::default())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interrupt_status_register() {
|
||||
let status = InterruptStatusRegister32::new();
|
||||
|
||||
assert_eq!(status.read(), 0);
|
||||
status.write(0x13);
|
||||
assert_eq!(status.read(), 0x13);
|
||||
status.clear_bits(0x11);
|
||||
assert_eq!(status.read(), 0x2);
|
||||
status.set_bits(0x100);
|
||||
assert_eq!(status.read_and_clear(), 0x102);
|
||||
assert_eq!(status.read(), 0);
|
||||
}
|
||||
}
|
||||
230
src/dragonball/src/dbs_interrupt/src/notifier.rs
Normal file
230
src/dragonball/src/dbs_interrupt/src/notifier.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2019 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
//! Event notifier to inject device interrupts to virtual machines.
|
||||
|
||||
use std::any::Any;
|
||||
use std::io::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::{InterruptIndex, InterruptSourceGroup, InterruptStatusRegister32};
|
||||
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
pub use self::legacy::*;
|
||||
#[cfg(feature = "msi-irq")]
|
||||
pub use self::msi::*;
|
||||
|
||||
/// Trait to inject device interrupts to virtual machines.
|
||||
pub trait InterruptNotifier: Send + Sync {
|
||||
/// Inject a device interrupt to the virtual machine.
|
||||
fn notify(&self) -> Result<(), Error>;
|
||||
|
||||
/// Get the optional `EventFd` object to inject interrupt to the virtual machine.
|
||||
fn notifier(&self) -> Option<&EventFd>;
|
||||
|
||||
/// Clone a boxed dyn trait object.
|
||||
fn clone_boxed(&self) -> Box<dyn InterruptNotifier>;
|
||||
|
||||
/// Convert `self` to `std::any::Any`.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
#[cfg(feature = "legacy-irq")]
|
||||
mod legacy {
|
||||
use super::*;
|
||||
|
||||
/// Struct to inject legacy interrupt to guest.
|
||||
#[derive(Clone)]
|
||||
pub struct LegacyNotifier {
|
||||
pub(crate) intr_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
pub(crate) intr_status: Arc<InterruptStatusRegister32>,
|
||||
pub(crate) status_bits: u32,
|
||||
}
|
||||
|
||||
impl LegacyNotifier {
|
||||
/// Create a legacy notifier.
|
||||
pub fn new(
|
||||
intr_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
intr_status: Arc<InterruptStatusRegister32>,
|
||||
status_bits: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
intr_group,
|
||||
intr_status,
|
||||
status_bits,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptNotifier for LegacyNotifier {
|
||||
fn notify(&self) -> Result<(), Error> {
|
||||
self.intr_status.set_bits(self.status_bits);
|
||||
self.intr_group.trigger(0)
|
||||
}
|
||||
|
||||
fn notifier(&self) -> Option<&EventFd> {
|
||||
self.intr_group.notifier(0)
|
||||
}
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn InterruptNotifier> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "msi-irq")]
|
||||
mod msi {
|
||||
use super::*;
|
||||
|
||||
/// Struct to inject message signalled interrupt to guest.
|
||||
#[derive(Clone)]
|
||||
pub struct MsiNotifier {
|
||||
pub(crate) intr_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
pub(crate) intr_index: InterruptIndex,
|
||||
}
|
||||
|
||||
impl MsiNotifier {
|
||||
/// Create a notifier to inject message signalled interrupt to guest.
|
||||
pub fn new(
|
||||
intr_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
intr_index: InterruptIndex,
|
||||
) -> Self {
|
||||
MsiNotifier {
|
||||
intr_group,
|
||||
intr_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptNotifier for MsiNotifier {
|
||||
fn notify(&self) -> Result<(), Error> {
|
||||
self.intr_group.trigger(self.intr_index)
|
||||
}
|
||||
|
||||
fn notifier(&self) -> Option<&EventFd> {
|
||||
self.intr_group.notifier(self.intr_index)
|
||||
}
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn InterruptNotifier> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct to discard interrupts.
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct NoopNotifier {}
|
||||
|
||||
impl NoopNotifier {
|
||||
/// Create a noop notifier to discard interrupts.
|
||||
pub fn new() -> Self {
|
||||
NoopNotifier {}
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptNotifier for NoopNotifier {
|
||||
fn notify(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self) -> Option<&EventFd> {
|
||||
None
|
||||
}
|
||||
|
||||
fn clone_boxed(&self) -> Box<dyn InterruptNotifier> {
|
||||
Box::new(*self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone a boxed interrupt notifier object.
|
||||
pub fn clone_notifier(notifier: &dyn InterruptNotifier) -> Box<dyn InterruptNotifier> {
|
||||
notifier.clone_boxed()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_imports)]
|
||||
#![allow(dead_code)]
|
||||
use super::*;
|
||||
|
||||
use crate::{InterruptManager, InterruptSourceType};
|
||||
|
||||
const VIRTIO_INTR_VRING: u32 = 0x01;
|
||||
const VIRTIO_INTR_CONFIG: u32 = 0x02;
|
||||
|
||||
#[test]
|
||||
fn create_virtio_null_notifier() {
|
||||
let notifier = NoopNotifier::new();
|
||||
|
||||
notifier.notify().unwrap();
|
||||
assert!(notifier.notifier().is_none());
|
||||
}
|
||||
|
||||
#[cfg(feature = "kvm-legacy-irq")]
|
||||
#[test]
|
||||
fn test_create_legacy_notifier() {
|
||||
let (_vmfd, irq_manager) = crate::kvm::tests::create_kvm_irq_manager();
|
||||
let group = irq_manager
|
||||
.create_group(InterruptSourceType::LegacyIrq, 0, 1)
|
||||
.unwrap();
|
||||
let status = Arc::new(InterruptStatusRegister32::new());
|
||||
assert_eq!(status.read(), 0);
|
||||
|
||||
let notifer = LegacyNotifier::new(group.clone(), status.clone(), VIRTIO_INTR_CONFIG);
|
||||
notifer.notify().unwrap();
|
||||
assert!(notifer.notifier().is_some());
|
||||
assert_eq!(notifer.status_bits, VIRTIO_INTR_CONFIG);
|
||||
assert_eq!(status.read_and_clear(), VIRTIO_INTR_CONFIG);
|
||||
assert_eq!(status.read(), 0);
|
||||
|
||||
let notifier = LegacyNotifier::new(group.clone(), status.clone(), VIRTIO_INTR_VRING);
|
||||
notifier.notify().unwrap();
|
||||
assert!(notifier.notifier().is_some());
|
||||
assert_eq!(status.read(), VIRTIO_INTR_VRING);
|
||||
status.clear_bits(VIRTIO_INTR_VRING);
|
||||
assert_eq!(status.read(), 0);
|
||||
let eventfd = notifier.notifier().unwrap();
|
||||
assert_eq!(eventfd.read().unwrap(), 2);
|
||||
|
||||
let clone = clone_notifier(¬ifier);
|
||||
assert_eq!(clone.as_any().type_id(), notifier.as_any().type_id());
|
||||
}
|
||||
|
||||
#[cfg(feature = "kvm-msi-irq")]
|
||||
#[test]
|
||||
fn test_virtio_msi_notifier() {
|
||||
let (_vmfd, irq_manager) = crate::kvm::tests::create_kvm_irq_manager();
|
||||
let group = irq_manager
|
||||
.create_group(InterruptSourceType::MsiIrq, 0, 3)
|
||||
.unwrap();
|
||||
let notifier1 = MsiNotifier::new(group.clone(), 1);
|
||||
let notifier2 = MsiNotifier::new(group.clone(), 2);
|
||||
let notifier3 = MsiNotifier::new(group.clone(), 3);
|
||||
assert!(notifier1.notifier().is_some());
|
||||
assert!(notifier2.notifier().is_some());
|
||||
assert!(notifier3.notifier().is_none());
|
||||
|
||||
notifier1.notify().unwrap();
|
||||
notifier1.notify().unwrap();
|
||||
notifier2.notify().unwrap();
|
||||
assert_eq!(notifier1.notifier().unwrap().read().unwrap(), 2);
|
||||
assert_eq!(notifier2.notifier().unwrap().read().unwrap(), 1);
|
||||
|
||||
let clone = clone_notifier(¬ifier1);
|
||||
assert_eq!(clone.as_any().type_id(), notifier1.as_any().type_id());
|
||||
}
|
||||
}
|
||||
23
src/dragonball/src/dbs_legacy_devices/Cargo.toml
Normal file
23
src/dragonball/src/dbs_legacy_devices/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "dbs-legacy-devices"
|
||||
version = "0.1.1"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
edition = "2018"
|
||||
description = "dbs-legacy-devices provides emulation for legacy devices."
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball", "secure-sandbox", "devices", "legacy"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
dbs-device = { path = "../dbs_device" }
|
||||
dbs-utils = { path = "../dbs_utils" }
|
||||
libc = "0.2.39"
|
||||
log = "0.4.14"
|
||||
serde = { version = "1.0.27", features = ["derive", "rc"] }
|
||||
vm-superio = "0.5.0"
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
[dev-dependencies]
|
||||
libc = "0.2.39"
|
||||
1
src/dragonball/src/dbs_legacy_devices/LICENSE
Symbolic link
1
src/dragonball/src/dbs_legacy_devices/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
26
src/dragonball/src/dbs_legacy_devices/README.md
Normal file
26
src/dragonball/src/dbs_legacy_devices/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# dbs-legacy-devices
|
||||
|
||||
`dbs-legacy-devices` provides emulation for legacy devices.
|
||||
|
||||
## Serial Devices
|
||||
|
||||
Defined a wrapper over the Serial of [`vm-superio`](https://github.com/rust-vmm/vm-superio).
|
||||
This wrapper is needed because [Orphan rules](https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules),
|
||||
which is one crate can not implement a trait for a struct defined in
|
||||
another crate. This wrapper also contains the input field that is
|
||||
missing from upstream implementation.
|
||||
|
||||
## i8042 Devices
|
||||
|
||||
Defined a wrapper over the `i8042 PS/2 Controller` of [`vm-superio`](https://github.com/rust-vmm/vm-superio).
|
||||
The i8042 PS/2 controller emulates, at this point, only the CPU reset command which is needed for announcing the VMM about the guest's shutdown.
|
||||
|
||||
### Acknowledgement
|
||||
|
||||
Part of the code is derived from the [Firecracker](https://github.com/firecracker-microvm/firecracker) project.
|
||||
And modified to use [`DeviceIoMut`](../dbs_device/src/lib.rs) to support serial port to Bus.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
1
src/dragonball/src/dbs_legacy_devices/THIRD-PARTY
Symbolic link
1
src/dragonball/src/dbs_legacy_devices/THIRD-PARTY
Symbolic link
@@ -0,0 +1 @@
|
||||
../../THIRD-PARTY
|
||||
137
src/dragonball/src/dbs_legacy_devices/src/cmos.rs
Normal file
137
src/dragonball/src/dbs_legacy_devices/src/cmos.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2023 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
|
||||
use libc::{clock_gettime, gmtime_r, timespec, tm, CLOCK_REALTIME};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use dbs_device::{DeviceIoMut, PioAddress};
|
||||
|
||||
/// The value of index offset register is always guaranteed to be in range via INDEX_MASK.
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
/// Offset of index offset register.
|
||||
const INDEX_OFFSET: u16 = 0x0;
|
||||
/// Offset of data offset register.
|
||||
const DATA_OFFSET: u16 = 0x1;
|
||||
/// Length of Cmos memory.
|
||||
const DATA_LEN: usize = 128;
|
||||
|
||||
/// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71.
|
||||
pub struct CmosDevice {
|
||||
index: u8,
|
||||
data: [u8; DATA_LEN],
|
||||
reset_evt: EventFd,
|
||||
}
|
||||
|
||||
impl CmosDevice {
|
||||
/// Constructs a CMOS/RTC device with initial data.
|
||||
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||
pub fn new(mem_below_4g: u64, mem_above_4g: u64, reset_evt: EventFd) -> CmosDevice {
|
||||
let mut data = [0u8; DATA_LEN];
|
||||
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||
let ext_mem = min(
|
||||
0xFFFF,
|
||||
mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
|
||||
);
|
||||
data[0x34] = ext_mem as u8;
|
||||
data[0x35] = (ext_mem >> 8) as u8;
|
||||
// High memory (> 4GB) in units of 64 KB
|
||||
let high_mem = min(0x00FF_FFFF, mem_above_4g / (64 * 1024));
|
||||
data[0x5b] = high_mem as u8;
|
||||
data[0x5c] = (high_mem >> 8) as u8;
|
||||
data[0x5d] = (high_mem >> 16) as u8;
|
||||
CmosDevice {
|
||||
index: 0,
|
||||
data,
|
||||
reset_evt,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl DeviceIoMut for CmosDevice {
|
||||
fn pio_write(&mut self, _base: PioAddress, offset: PioAddress, data: &[u8]) {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
}
|
||||
match offset.raw_value() {
|
||||
INDEX_OFFSET => self.index = data[0],
|
||||
DATA_OFFSET => {
|
||||
if self.index == 0x8f && data[0] == 0 {
|
||||
self.reset_evt.write(1).unwrap();
|
||||
} else {
|
||||
self.data[(self.index & INDEX_MASK) as usize] = data[0]
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
fn pio_read(&mut self, _base: PioAddress, offset: PioAddress, data: &mut [u8]) {
|
||||
fn to_bcd(v: u8) -> u8 {
|
||||
assert!(v < 100);
|
||||
((v / 10) << 4) | (v % 10)
|
||||
}
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
}
|
||||
data[0] = match offset.raw_value() {
|
||||
INDEX_OFFSET => self.index,
|
||||
DATA_OFFSET => {
|
||||
let seconds;
|
||||
let minutes;
|
||||
let hours;
|
||||
let week_day;
|
||||
let day;
|
||||
let month;
|
||||
let year;
|
||||
// The clock_gettime and gmtime_r calls are safe as long as the structs they are
|
||||
// given are large enough, and neither of them fail. It is safe to zero initialize
|
||||
// the tm and timespec struct because it contains only plain data.
|
||||
let update_in_progress = unsafe {
|
||||
let mut timespec: timespec = mem::zeroed();
|
||||
clock_gettime(CLOCK_REALTIME, &mut timespec as *mut _);
|
||||
let now = timespec.tv_sec;
|
||||
let mut tm: tm = mem::zeroed();
|
||||
gmtime_r(&now, &mut tm as *mut _);
|
||||
// The following lines of code are safe but depend on tm being in scope.
|
||||
seconds = tm.tm_sec;
|
||||
minutes = tm.tm_min;
|
||||
hours = tm.tm_hour;
|
||||
week_day = tm.tm_wday + 1;
|
||||
day = tm.tm_mday;
|
||||
month = tm.tm_mon + 1;
|
||||
year = tm.tm_year;
|
||||
// Update in Progress bit held for last 224us of each second
|
||||
const NANOSECONDS_PER_SECOND: i64 = 1_000_000_000;
|
||||
const UIP_HOLD_LENGTH: i64 = 8 * NANOSECONDS_PER_SECOND / 32768;
|
||||
timespec.tv_nsec >= (NANOSECONDS_PER_SECOND - UIP_HOLD_LENGTH)
|
||||
};
|
||||
match self.index {
|
||||
0x00 => to_bcd(seconds as u8),
|
||||
0x02 => to_bcd(minutes as u8),
|
||||
0x04 => to_bcd(hours as u8),
|
||||
0x06 => to_bcd(week_day as u8),
|
||||
0x07 => to_bcd(day as u8),
|
||||
0x08 => to_bcd(month as u8),
|
||||
0x09 => to_bcd((year % 100) as u8),
|
||||
// Bit 5 for 32kHz clock. Bit 7 for Update in Progress
|
||||
0x0a => 1 << 5 | (update_in_progress as u8) << 7,
|
||||
// Bit 0-6 are reserved and must be 0.
|
||||
// Bit 7 must be 1 (CMOS has power)
|
||||
0x0d => 1 << 7,
|
||||
0x32 => to_bcd(((year + 1900) / 100) as u8),
|
||||
_ => {
|
||||
// self.index is always guaranteed to be in range via INDEX_MASK.
|
||||
self.data[(self.index & INDEX_MASK) as usize]
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/dragonball/src/dbs_legacy_devices/src/i8042.rs
Normal file
136
src/dragonball/src/dbs_legacy_devices/src/i8042.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 Alibaba Cloud. All rights reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use dbs_device::{DeviceIoMut, PioAddress};
|
||||
use dbs_utils::metric::{IncMetric, SharedIncMetric};
|
||||
use log::error;
|
||||
use serde::Serialize;
|
||||
use vm_superio::{I8042Device as I8042Dev, Trigger};
|
||||
|
||||
use crate::EventFdTrigger;
|
||||
|
||||
/// Metrics specific to the i8042 device.
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct I8042DeviceMetrics {
|
||||
/// Errors triggered while using the i8042 device.
|
||||
pub error_count: SharedIncMetric,
|
||||
/// Number of superfluous read intents on this i8042 device.
|
||||
pub missed_read_count: SharedIncMetric,
|
||||
/// Number of superfluous read intents on this i8042 device.
|
||||
pub missed_write_count: SharedIncMetric,
|
||||
/// Bytes read by this device.
|
||||
pub read_count: SharedIncMetric,
|
||||
/// Bytes written by this device.
|
||||
pub write_count: SharedIncMetric,
|
||||
}
|
||||
|
||||
pub type I8042Device = I8042Wrapper<EventFdTrigger>;
|
||||
|
||||
pub struct I8042Wrapper<T: Trigger> {
|
||||
pub device: I8042Dev<T>,
|
||||
pub metrics: Arc<I8042DeviceMetrics>,
|
||||
}
|
||||
|
||||
impl I8042Device {
|
||||
pub fn new(event: EventFdTrigger, metrics: Arc<I8042DeviceMetrics>) -> Self {
|
||||
Self {
|
||||
device: I8042Dev::new(event),
|
||||
metrics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceIoMut for I8042Wrapper<EventFdTrigger> {
|
||||
fn pio_read(&mut self, _base: PioAddress, offset: PioAddress, data: &mut [u8]) {
|
||||
if data.len() != 1 {
|
||||
self.metrics.missed_read_count.inc();
|
||||
return;
|
||||
}
|
||||
data[0] = self.device.read(offset.raw_value() as u8);
|
||||
self.metrics.read_count.inc();
|
||||
}
|
||||
|
||||
fn pio_write(&mut self, _base: PioAddress, offset: PioAddress, data: &[u8]) {
|
||||
if data.len() != 1 {
|
||||
self.metrics.missed_write_count.inc();
|
||||
return;
|
||||
}
|
||||
if let Err(e) = self.device.write(offset.raw_value() as u8, data[0]) {
|
||||
self.metrics.error_count.inc();
|
||||
error!("Failed to trigger i8042 reset event: {:?}", e);
|
||||
} else {
|
||||
self.metrics.write_count.inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::os::unix::prelude::FromRawFd;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::*;
|
||||
|
||||
const COMMAND_OFFSET: u8 = 4;
|
||||
const CMD_RESET_CPU: u8 = 0xFE;
|
||||
|
||||
#[test]
|
||||
fn test_i8042_valid_ops() {
|
||||
let reset_evt = EventFdTrigger::new(EventFd::new(libc::EFD_NONBLOCK).unwrap());
|
||||
let metrics = Arc::new(I8042DeviceMetrics::default());
|
||||
let mut i8042 = I8042Device::new(reset_evt.try_clone().unwrap(), metrics);
|
||||
|
||||
let mut v = [0x00u8; 1];
|
||||
i8042.pio_read(PioAddress(0), PioAddress(0), &mut v);
|
||||
assert_eq!(v[0], 0);
|
||||
assert_eq!(i8042.metrics.read_count.count(), 1);
|
||||
|
||||
// Check if reset works.
|
||||
i8042.pio_write(
|
||||
PioAddress(0),
|
||||
PioAddress(COMMAND_OFFSET as u16),
|
||||
&[CMD_RESET_CPU],
|
||||
);
|
||||
assert_eq!(i8042.metrics.write_count.count(), 1);
|
||||
reset_evt.read().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i8042_invalid_ops() {
|
||||
let reset_evt = EventFdTrigger::new(EventFd::new(libc::EFD_NONBLOCK).unwrap());
|
||||
let metrics = Arc::new(I8042DeviceMetrics::default());
|
||||
let mut i8042 = I8042Device::new(reset_evt.try_clone().unwrap(), metrics);
|
||||
|
||||
let mut v = [0x00u8; 2];
|
||||
i8042.pio_read(PioAddress(0), PioAddress(0), &mut v);
|
||||
assert_eq!(v[0], 0);
|
||||
assert_eq!(i8042.metrics.read_count.count(), 0);
|
||||
assert_eq!(i8042.metrics.missed_read_count.count(), 1);
|
||||
|
||||
i8042.pio_write(
|
||||
PioAddress(0),
|
||||
PioAddress(COMMAND_OFFSET as u16),
|
||||
&[CMD_RESET_CPU, 0],
|
||||
);
|
||||
assert_eq!(i8042.metrics.write_count.count(), 0);
|
||||
assert_eq!(i8042.metrics.missed_write_count.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_i8042_reset_err() {
|
||||
let reset_evt = EventFdTrigger::new(unsafe { EventFd::from_raw_fd(i32::MAX) });
|
||||
let metrics = Arc::new(I8042DeviceMetrics::default());
|
||||
let mut i8042 = I8042Device::new(reset_evt, metrics);
|
||||
i8042.pio_write(
|
||||
PioAddress(0),
|
||||
PioAddress(COMMAND_OFFSET as u16),
|
||||
&[CMD_RESET_CPU],
|
||||
);
|
||||
assert_eq!(i8042.metrics.write_count.count(), 0);
|
||||
assert_eq!(i8042.metrics.error_count.count(), 1);
|
||||
}
|
||||
}
|
||||
76
src/dragonball/src/dbs_legacy_devices/src/lib.rs
Normal file
76
src/dragonball/src/dbs_legacy_devices/src/lib.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Emulates virtual and hardware devices.
|
||||
mod serial;
|
||||
pub use self::serial::*;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod cmos;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::cmos::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod i8042;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use self::i8042::*;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod rtc_pl031;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::rtc_pl031::*;
|
||||
|
||||
use vm_superio::Trigger;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
/// Newtype for implementing the trigger functionality for `EventFd`.
|
||||
///
|
||||
/// The trigger is used for handling events in the legacy devices.
|
||||
pub struct EventFdTrigger(EventFd);
|
||||
|
||||
impl Trigger for EventFdTrigger {
|
||||
type E = std::io::Error;
|
||||
|
||||
fn trigger(&self) -> std::io::Result<()> {
|
||||
self.write(1)
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for EventFdTrigger {
|
||||
type Target = EventFd;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl EventFdTrigger {
|
||||
pub fn try_clone(&self) -> std::io::Result<Self> {
|
||||
Ok(EventFdTrigger((**self).try_clone()?))
|
||||
}
|
||||
pub fn new(evt: EventFd) -> Self {
|
||||
Self(evt)
|
||||
}
|
||||
|
||||
pub fn get_event(&self) -> EventFd {
|
||||
self.0.try_clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Deref;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_eventfd_trigger() {
|
||||
let intr_evt = EventFdTrigger::new(EventFd::new(libc::EFD_NONBLOCK).unwrap());
|
||||
intr_evt.trigger().unwrap();
|
||||
assert_eq!(intr_evt.get_event().read().unwrap(), 1);
|
||||
intr_evt.try_clone().unwrap().trigger().unwrap();
|
||||
assert_eq!(intr_evt.deref().read().unwrap(), 1);
|
||||
}
|
||||
}
|
||||
128
src/dragonball/src/dbs_legacy_devices/src/rtc_pl031.rs
Normal file
128
src/dragonball/src/dbs_legacy_devices/src/rtc_pl031.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2022 Alibaba Cloud. All Rights Reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! ARM PL031 Real Time Clock
|
||||
//!
|
||||
//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter.
|
||||
use std::convert::TryInto;
|
||||
|
||||
use dbs_device::{DeviceIoMut, IoAddress};
|
||||
use dbs_utils::metric::{IncMetric, SharedIncMetric};
|
||||
use log::warn;
|
||||
use vm_superio::rtc_pl031::{Rtc, RtcEvents};
|
||||
|
||||
/// Metrics specific to the RTC device
|
||||
#[derive(Default)]
|
||||
pub struct RTCDeviceMetrics {
|
||||
/// Errors triggered while using the RTC device.
|
||||
pub error_count: SharedIncMetric,
|
||||
/// Number of superfluous read intents on this RTC device.
|
||||
pub missed_read_count: SharedIncMetric,
|
||||
/// Number of superfluous write intents on this RTC device.
|
||||
pub missed_write_count: SharedIncMetric,
|
||||
}
|
||||
|
||||
impl RtcEvents for RTCDeviceMetrics {
|
||||
fn invalid_read(&self) {
|
||||
self.missed_read_count.inc();
|
||||
self.error_count.inc();
|
||||
}
|
||||
|
||||
fn invalid_write(&self) {
|
||||
self.missed_write_count.inc();
|
||||
self.error_count.inc();
|
||||
}
|
||||
}
|
||||
|
||||
/// The wrapper of Rtc struct to implement DeviceIoMut trait.
|
||||
pub struct RTCDevice {
|
||||
pub rtc: Rtc<RTCDeviceMetrics>,
|
||||
}
|
||||
|
||||
impl Default for RTCDevice {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RTCDevice {
|
||||
pub fn new() -> Self {
|
||||
let metrics = RTCDeviceMetrics::default();
|
||||
Self {
|
||||
rtc: Rtc::with_events(metrics),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceIoMut for RTCDevice {
|
||||
fn read(&mut self, _base: IoAddress, offset: IoAddress, data: &mut [u8]) {
|
||||
if data.len() == 4 {
|
||||
self.rtc
|
||||
.read(offset.raw_value() as u16, data.try_into().unwrap())
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid RTC PL031 read: offset {}, data length {}",
|
||||
offset.raw_value(),
|
||||
data.len()
|
||||
);
|
||||
self.rtc.events().invalid_read();
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: IoAddress, offset: IoAddress, data: &[u8]) {
|
||||
if data.len() == 4 {
|
||||
self.rtc
|
||||
.write(offset.raw_value() as u16, data.try_into().unwrap())
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid RTC PL031 write: offset {}, data length {}",
|
||||
offset.raw_value(),
|
||||
data.len()
|
||||
);
|
||||
self.rtc.events().invalid_write();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl RTCDevice {
|
||||
fn read(&mut self, offset: u64, data: &mut [u8]) {
|
||||
DeviceIoMut::read(self, IoAddress::from(0), IoAddress::from(offset), data)
|
||||
}
|
||||
|
||||
fn write(&mut self, offset: u64, data: &[u8]) {
|
||||
DeviceIoMut::write(self, IoAddress::from(0), IoAddress::from(offset), data)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rtc_read_write_and_event() {
|
||||
let mut rtc_device = RTCDevice::new();
|
||||
let data = [0; 4];
|
||||
|
||||
// Write to the DR register. Since this is a RO register, the write
|
||||
// function should fail.
|
||||
let invalid_writes_before = rtc_device.rtc.events().missed_write_count.count();
|
||||
let error_count_before = rtc_device.rtc.events().error_count.count();
|
||||
rtc_device.rtc.write(0x000, &data);
|
||||
let invalid_writes_after = rtc_device.rtc.events().missed_write_count.count();
|
||||
let error_count_after = rtc_device.rtc.events().error_count.count();
|
||||
assert_eq!(invalid_writes_after - invalid_writes_before, 1);
|
||||
assert_eq!(error_count_after - error_count_before, 1);
|
||||
|
||||
let write_data_good = 123u32.to_le_bytes();
|
||||
let mut data_bad = [0; 2];
|
||||
let mut read_data_good = [0; 4];
|
||||
|
||||
rtc_device.write(0x008, &write_data_good);
|
||||
rtc_device.write(0x008, &data_bad);
|
||||
rtc_device.read(0x008, &mut read_data_good);
|
||||
rtc_device.read(0x008, &mut data_bad);
|
||||
assert_eq!(u32::from_le_bytes(read_data_good), 123);
|
||||
assert_eq!(u16::from_le_bytes(data_bad), 0);
|
||||
}
|
||||
}
|
||||
291
src/dragonball/src/dbs_legacy_devices/src/serial.rs
Normal file
291
src/dragonball/src/dbs_legacy_devices/src/serial.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
|
||||
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use dbs_device::{DeviceIoMut, IoAddress, PioAddress};
|
||||
use dbs_utils::metric::{IncMetric, SharedIncMetric};
|
||||
use log::error;
|
||||
use serde::Serialize;
|
||||
use vm_superio::{serial::SerialEvents, Serial, Trigger};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::EventFdTrigger;
|
||||
|
||||
/// Trait for devices that handle raw non-blocking I/O requests.
|
||||
pub trait ConsoleHandler {
|
||||
/// Send raw input to this emulated device.
|
||||
fn raw_input(&mut self, _data: &[u8]) -> std::io::Result<usize> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Set the stream to receive raw output from this emulated device.
|
||||
fn set_output_stream(&mut self, out: Option<Box<dyn Write + Send>>);
|
||||
}
|
||||
|
||||
/// Metrics specific to the UART device.
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct SerialDeviceMetrics {
|
||||
/// Errors triggered while using the UART device.
|
||||
pub error_count: SharedIncMetric,
|
||||
/// Number of flush operations.
|
||||
pub flush_count: SharedIncMetric,
|
||||
/// Number of read calls that did not trigger a read.
|
||||
pub missed_read_count: SharedIncMetric,
|
||||
/// Number of write calls that did not trigger a write.
|
||||
pub missed_write_count: SharedIncMetric,
|
||||
/// Number of succeeded read calls.
|
||||
pub read_count: SharedIncMetric,
|
||||
/// Number of succeeded write calls.
|
||||
pub write_count: SharedIncMetric,
|
||||
}
|
||||
|
||||
pub struct SerialEventsWrapper {
|
||||
pub metrics: Arc<SerialDeviceMetrics>,
|
||||
pub buffer_ready_event_fd: Option<EventFdTrigger>,
|
||||
}
|
||||
|
||||
impl SerialEvents for SerialEventsWrapper {
|
||||
fn buffer_read(&self) {
|
||||
self.metrics.read_count.inc();
|
||||
}
|
||||
|
||||
fn out_byte(&self) {
|
||||
self.metrics.write_count.inc();
|
||||
}
|
||||
|
||||
fn tx_lost_byte(&self) {
|
||||
self.metrics.missed_write_count.inc();
|
||||
}
|
||||
|
||||
fn in_buffer_empty(&self) {
|
||||
match self
|
||||
.buffer_ready_event_fd
|
||||
.as_ref()
|
||||
.map_or(Ok(()), |buf_ready| buf_ready.write(1))
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => error!(
|
||||
"Could not signal that serial device buffer is ready: {:?}",
|
||||
err
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type SerialDevice = SerialWrapper<EventFdTrigger, SerialEventsWrapper>;
|
||||
|
||||
impl SerialDevice {
|
||||
/// Creates a new SerialDevice instance.
|
||||
pub fn new(event: EventFd) -> Self {
|
||||
let out = Arc::new(Mutex::new(None));
|
||||
Self {
|
||||
serial: Serial::with_events(
|
||||
EventFdTrigger::new(event),
|
||||
SerialEventsWrapper {
|
||||
metrics: Arc::new(SerialDeviceMetrics::default()),
|
||||
buffer_ready_event_fd: None,
|
||||
},
|
||||
AdapterWriter(out.clone()),
|
||||
),
|
||||
out,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SerialWrapper<T: Trigger, EV: SerialEvents> {
|
||||
pub serial: Serial<T, EV, AdapterWriter>,
|
||||
pub out: Arc<Mutex<Option<Box<dyn Write + Send>>>>,
|
||||
}
|
||||
|
||||
impl ConsoleHandler for SerialWrapper<EventFdTrigger, SerialEventsWrapper> {
|
||||
fn raw_input(&mut self, data: &[u8]) -> std::io::Result<usize> {
|
||||
self.serial
|
||||
.enqueue_raw_bytes(data)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))
|
||||
}
|
||||
|
||||
fn set_output_stream(&mut self, out: Option<Box<dyn Write + Send>>) {
|
||||
*self.out.lock().unwrap() = out;
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceIoMut for SerialWrapper<EventFdTrigger, SerialEventsWrapper> {
|
||||
fn pio_read(&mut self, _base: PioAddress, offset: PioAddress, data: &mut [u8]) {
|
||||
if data.len() != 1 {
|
||||
self.serial.events().metrics.missed_read_count.inc();
|
||||
return;
|
||||
}
|
||||
data[0] = self.serial.read(offset.raw_value() as u8);
|
||||
}
|
||||
fn pio_write(&mut self, _base: PioAddress, offset: PioAddress, data: &[u8]) {
|
||||
if data.len() != 1 {
|
||||
self.serial.events().metrics.missed_write_count.inc();
|
||||
return;
|
||||
}
|
||||
if let Err(e) = self.serial.write(offset.raw_value() as u8, data[0]) {
|
||||
error!("Failed the pio write to serial: {:?}", e);
|
||||
self.serial.events().metrics.error_count.inc();
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, _base: IoAddress, offset: IoAddress, data: &mut [u8]) {
|
||||
if data.len() != 1 {
|
||||
self.serial.events().metrics.missed_read_count.inc();
|
||||
return;
|
||||
}
|
||||
data[0] = self.serial.read(offset.raw_value() as u8);
|
||||
}
|
||||
fn write(&mut self, _base: IoAddress, offset: IoAddress, data: &[u8]) {
|
||||
if data.len() != 1 {
|
||||
self.serial.events().metrics.missed_write_count.inc();
|
||||
return;
|
||||
}
|
||||
if let Err(e) = self.serial.write(offset.raw_value() as u8, data[0]) {
|
||||
error!("Failed the write to serial: {:?}", e);
|
||||
self.serial.events().metrics.error_count.inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdapterWriter(pub Arc<Mutex<Option<Box<dyn Write + Send>>>>);
|
||||
|
||||
impl Write for AdapterWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if let Some(w) = self.0.lock().unwrap().as_mut() {
|
||||
w.write(buf)
|
||||
} else {
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
if let Some(w) = self.0.lock().unwrap().as_mut() {
|
||||
w.flush()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SharedBuffer {
|
||||
buf: Arc<Mutex<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl io::Write for SharedBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.lock().unwrap().write(buf)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.buf.lock().unwrap().flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serial_bus_write() {
|
||||
let serial_out_buf = Arc::new(Mutex::new(Vec::new()));
|
||||
let serial_out = Box::new(SharedBuffer {
|
||||
buf: serial_out_buf.clone(),
|
||||
});
|
||||
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
||||
|
||||
let mut serial = SerialDevice::new(intr_evt);
|
||||
let metrics = serial.serial.events().metrics.clone();
|
||||
|
||||
serial.set_output_stream(Some(serial_out));
|
||||
|
||||
let invalid_writes_before = serial.serial.events().metrics.missed_write_count.count();
|
||||
<dyn DeviceIoMut>::pio_write(&mut serial, PioAddress(0), PioAddress(0), &[b'x', b'y']);
|
||||
let writes_before = metrics.write_count.count();
|
||||
|
||||
let invalid_writes_after = metrics.missed_write_count.count();
|
||||
assert_eq!(invalid_writes_before + 1, invalid_writes_after);
|
||||
|
||||
assert_eq!(serial_out_buf.lock().unwrap().as_slice().len(), 0);
|
||||
<dyn DeviceIoMut>::write(&mut serial, IoAddress(0), IoAddress(0), &[b'x', b'y']);
|
||||
assert_eq!(serial_out_buf.lock().unwrap().as_slice().len(), 0);
|
||||
|
||||
let invalid_writes_after = metrics.missed_write_count.count();
|
||||
assert_eq!(invalid_writes_before + 2, invalid_writes_after);
|
||||
|
||||
<dyn DeviceIoMut>::pio_write(&mut serial, PioAddress(0), PioAddress(0), &[b'a']);
|
||||
<dyn DeviceIoMut>::pio_write(&mut serial, PioAddress(0), PioAddress(0), &[b'b']);
|
||||
<dyn DeviceIoMut>::write(&mut serial, IoAddress(0), IoAddress(0), &[b'c']);
|
||||
assert_eq!(
|
||||
serial_out_buf.lock().unwrap().as_slice(),
|
||||
&[b'a', b'b', b'c']
|
||||
);
|
||||
|
||||
let invalid_writes_after_2 = metrics.missed_write_count.count();
|
||||
let writes_after = metrics.write_count.count();
|
||||
// The `invalid_write_count` metric should be the same as before the one-byte writes.
|
||||
assert_eq!(invalid_writes_after_2, invalid_writes_after);
|
||||
assert_eq!(writes_after, writes_before + 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serial_bus_read() {
|
||||
let intr_evt = EventFdTrigger::new(EventFd::new(libc::EFD_NONBLOCK).unwrap());
|
||||
|
||||
let metrics = Arc::new(SerialDeviceMetrics::default());
|
||||
|
||||
let out: Arc<Mutex<Option<Box<(dyn std::io::Write + Send + 'static)>>>> =
|
||||
Arc::new(Mutex::new(Some(Box::new(std::io::sink()))));
|
||||
let mut serial = SerialDevice {
|
||||
serial: Serial::with_events(
|
||||
intr_evt,
|
||||
SerialEventsWrapper {
|
||||
metrics: metrics.clone(),
|
||||
buffer_ready_event_fd: None,
|
||||
},
|
||||
AdapterWriter(out.clone()),
|
||||
),
|
||||
out,
|
||||
};
|
||||
serial
|
||||
.serial
|
||||
.enqueue_raw_bytes(&[b'a', b'b', b'c'])
|
||||
.unwrap();
|
||||
|
||||
let invalid_reads_before = metrics.missed_read_count.count();
|
||||
|
||||
let mut v = [0x00; 2];
|
||||
<dyn DeviceIoMut>::pio_read(&mut serial, PioAddress(0), PioAddress(0), &mut v);
|
||||
assert_eq!(v[0], b'\0');
|
||||
|
||||
let invalid_reads_after = metrics.missed_read_count.count();
|
||||
assert_eq!(invalid_reads_before + 1, invalid_reads_after);
|
||||
|
||||
<dyn DeviceIoMut>::read(&mut serial, IoAddress(0), IoAddress(0), &mut v);
|
||||
assert_eq!(v[0], b'\0');
|
||||
|
||||
let invalid_reads_after = metrics.missed_read_count.count();
|
||||
assert_eq!(invalid_reads_before + 2, invalid_reads_after);
|
||||
|
||||
let mut v = [0x00; 1];
|
||||
<dyn DeviceIoMut>::pio_read(&mut serial, PioAddress(0), PioAddress(0), &mut v);
|
||||
assert_eq!(v[0], b'a');
|
||||
|
||||
<dyn DeviceIoMut>::pio_read(&mut serial, PioAddress(0), PioAddress(0), &mut v);
|
||||
assert_eq!(v[0], b'b');
|
||||
|
||||
<dyn DeviceIoMut>::read(&mut serial, IoAddress(0), IoAddress(0), &mut v);
|
||||
assert_eq!(v[0], b'c');
|
||||
|
||||
let invalid_reads_after_2 = metrics.missed_read_count.count();
|
||||
// The `invalid_read_count` metric should be the same as before the one-byte reads.
|
||||
assert_eq!(invalid_reads_after_2, invalid_reads_after);
|
||||
}
|
||||
}
|
||||
19
src/dragonball/src/dbs_tdx/Cargo.toml
Normal file
19
src/dragonball/src/dbs_tdx/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "dbs-tdx"
|
||||
version = "0.1.0"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
description = "helpers and utilities to create TDX VM"
|
||||
license = "Apache-2.0 AND BSD-3-Clause"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox"
|
||||
keywords = ["dragonball", "secure-sandbox", "TDX", "confidential container"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
kvm-bindings = { version = "0.6.0", features = ["fam-wrappers"] }
|
||||
vmm-sys-util = "0.11.0"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.9"
|
||||
14
src/dragonball/src/dbs_tdx/README.md
Normal file
14
src/dragonball/src/dbs_tdx/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# dbs-tdx
|
||||
|
||||
This crate is a collection of modules that provides helpers and utilities to create a TDX Dragonball VM.
|
||||
|
||||
Currently this crate involves:
|
||||
- tdx-ioctls
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Part of the code is derived from the [Cloud Hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
5
src/dragonball/src/dbs_tdx/src/lib.rs
Normal file
5
src/dragonball/src/dbs_tdx/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright (C) 2023 Alibaba Cloud. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod tdx_ioctls;
|
||||
220
src/dragonball/src/dbs_tdx/src/tdx_ioctls.rs
Normal file
220
src/dragonball/src/dbs_tdx/src/tdx_ioctls.rs
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// Copyright (c) 2023 Alibaba Cloud.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
use kvm_bindings::{CpuId, __IncompleteArrayField, KVMIO};
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::fam::{FamStruct, FamStructWrapper};
|
||||
use vmm_sys_util::ioctl::ioctl_with_val;
|
||||
use vmm_sys_util::{generate_fam_struct_impl, ioctl_ioc_nr, ioctl_iowr_nr};
|
||||
|
||||
/// Tdx capability list.
|
||||
pub type TdxCaps = FamStructWrapper<TdxCapabilities>;
|
||||
|
||||
/// Cpuid configs entry counts.
|
||||
const TDX1_MAX_NR_CPUID_CONFIGS: usize = 6;
|
||||
|
||||
generate_fam_struct_impl!(
|
||||
TdxCapabilities,
|
||||
TdxCpuidConfig,
|
||||
cpuid_configs,
|
||||
u32,
|
||||
nr_cpuid_configs,
|
||||
TDX1_MAX_NR_CPUID_CONFIGS
|
||||
);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
/// Tdx cpuid config.
|
||||
pub struct TdxCpuidConfig {
|
||||
/// cpuid leaf
|
||||
pub leaf: u32,
|
||||
/// cpuid sub leaf
|
||||
pub sub_leaf: u32,
|
||||
/// eax
|
||||
pub eax: u32,
|
||||
/// ebx
|
||||
pub ebx: u32,
|
||||
/// ecx
|
||||
pub ecx: u32,
|
||||
/// edx
|
||||
pub edx: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
/// Tdx capabilities.
|
||||
pub struct TdxCapabilities {
|
||||
/// cpuid bits need to be fixed to 0.
|
||||
pub attrs_fixed0: u64,
|
||||
/// cpuid bits need to be fixed to 1.
|
||||
pub attrs_fixed1: u64,
|
||||
/// xfam bits need to be fixed to 0.
|
||||
pub xfam_fixed0: u64,
|
||||
/// xfam bits need to be fixed to 1.
|
||||
pub xfam_fixed1: u64,
|
||||
/// cpuid configs entry number.
|
||||
pub nr_cpuid_configs: u32,
|
||||
/// padding.
|
||||
pub padding: u32,
|
||||
/// cpuid config list
|
||||
pub cpuid_configs: __IncompleteArrayField<TdxCpuidConfig>,
|
||||
}
|
||||
|
||||
ioctl_iowr_nr!(KVM_MEMORY_ENCRYPT_OP, KVMIO, 0xba, std::os::raw::c_ulong);
|
||||
/// TDX module related errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TdxIoctlError {
|
||||
/// Failed to create TdxCaps
|
||||
#[error("Failed to create TdxCaps")]
|
||||
TdxCapabilitiesCreate,
|
||||
/// Failed to get TDX Capbilities
|
||||
#[error("Failed to get TDX Capbilities: {0}")]
|
||||
TdxCapabilities(#[source] std::io::Error),
|
||||
/// Failed to init TDX.
|
||||
#[error("Failed to init TDX: {0}")]
|
||||
TdxInit(#[source] std::io::Error),
|
||||
/// Failed to finalize TDX.
|
||||
#[error("Failed to finalize TDX: {0}")]
|
||||
TdxFinalize(#[source] std::io::Error),
|
||||
/// Failed to init TDX memory region.
|
||||
#[error("Failed to init TDX memory region: {0}")]
|
||||
TdxInitMemRegion(#[source] std::io::Error),
|
||||
/// Failed to init TDX vcpu.
|
||||
#[error("Failed to init TDX vcpu: {0}")]
|
||||
TdxInitVcpu(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
/// TDX related ioctl command
|
||||
#[repr(u32)]
|
||||
enum TdxCommand {
|
||||
/// Get Capability
|
||||
Capabilities = 0,
|
||||
/// Init TD
|
||||
InitVm = 1,
|
||||
/// Init vcpu for TD
|
||||
InitVcpu = 2,
|
||||
/// Init memory region for TD
|
||||
InitMemRegion = 3,
|
||||
/// Finalize TD
|
||||
Finalize = 4,
|
||||
}
|
||||
|
||||
/// TDX related ioctl command
|
||||
fn tdx_command(
|
||||
fd: &RawFd,
|
||||
command: TdxCommand,
|
||||
metadata: u32,
|
||||
data: u64,
|
||||
) -> std::result::Result<(), std::io::Error> {
|
||||
#[repr(C)]
|
||||
struct TdxIoctlCmd {
|
||||
command: TdxCommand,
|
||||
metadata: u32,
|
||||
data: u64,
|
||||
}
|
||||
let cmd = TdxIoctlCmd {
|
||||
command,
|
||||
metadata,
|
||||
data,
|
||||
};
|
||||
let ret = unsafe {
|
||||
ioctl_with_val(
|
||||
fd,
|
||||
KVM_MEMORY_ENCRYPT_OP(),
|
||||
&cmd as *const TdxIoctlCmd as std::os::raw::c_ulong,
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Init TDX
|
||||
pub fn tdx_init(
|
||||
vm_fd: &RawFd,
|
||||
cpu_id: &CpuId,
|
||||
max_vcpus: u32,
|
||||
) -> std::result::Result<(), TdxIoctlError> {
|
||||
#[repr(C)]
|
||||
struct TdxInitVm {
|
||||
max_vcpus: u32,
|
||||
tsc_khz: u32,
|
||||
attributes: u64,
|
||||
cpuid: u64,
|
||||
mrconfigid: [u64; 6],
|
||||
mrowner: [u64; 6],
|
||||
mrownerconfig: [u64; 6],
|
||||
reserved: [u64; 43],
|
||||
}
|
||||
let data = TdxInitVm {
|
||||
max_vcpus,
|
||||
tsc_khz: 0,
|
||||
attributes: 0, // TDX1_TD_ATTRIBUTE_DEBUG,
|
||||
cpuid: cpu_id.as_fam_struct_ptr() as u64,
|
||||
mrconfigid: [0; 6],
|
||||
mrowner: [0; 6],
|
||||
mrownerconfig: [0; 6],
|
||||
reserved: [0; 43],
|
||||
};
|
||||
tdx_command(vm_fd, TdxCommand::InitVm, 0, &data as *const _ as u64)
|
||||
.map_err(TdxIoctlError::TdxInit)
|
||||
}
|
||||
|
||||
/// Finalize the TDX setup for this VM
|
||||
pub fn tdx_finalize(vm_fd: &RawFd) -> std::result::Result<(), TdxIoctlError> {
|
||||
tdx_command(vm_fd, TdxCommand::Finalize, 0, 0).map_err(TdxIoctlError::TdxFinalize)
|
||||
}
|
||||
|
||||
/// Initialize TDX memory Region
|
||||
pub fn tdx_init_memory_region(
|
||||
vm_fd: &RawFd,
|
||||
host_address: u64,
|
||||
guest_address: u64,
|
||||
size: u64,
|
||||
measure: bool,
|
||||
) -> std::result::Result<(), TdxIoctlError> {
|
||||
#[repr(C)]
|
||||
struct TdxInitMemRegion {
|
||||
host_address: u64,
|
||||
guest_address: u64,
|
||||
pages: u64,
|
||||
}
|
||||
let data = TdxInitMemRegion {
|
||||
host_address,
|
||||
guest_address,
|
||||
pages: size / 4096,
|
||||
};
|
||||
tdx_command(
|
||||
vm_fd,
|
||||
TdxCommand::InitMemRegion,
|
||||
if measure { 1 } else { 0 },
|
||||
&data as *const _ as u64,
|
||||
)
|
||||
.map_err(TdxIoctlError::TdxInitMemRegion)
|
||||
}
|
||||
|
||||
/// Initialize TDX vcpu
|
||||
pub fn tdx_init_vcpu(vcpu_fd: &RawFd, hob_address: u64) -> std::result::Result<(), TdxIoctlError> {
|
||||
tdx_command(vcpu_fd, TdxCommand::InitVcpu, 0, hob_address).map_err(TdxIoctlError::TdxInitVcpu)
|
||||
}
|
||||
|
||||
/// Get tdx capabilities.
|
||||
pub fn tdx_get_caps(kvm_fd: &RawFd) -> std::result::Result<TdxCaps, TdxIoctlError> {
|
||||
let mut tdx_caps = TdxCaps::new(TDX1_MAX_NR_CPUID_CONFIGS)
|
||||
.map_err(|_| TdxIoctlError::TdxCapabilitiesCreate)?;
|
||||
tdx_command(
|
||||
kvm_fd,
|
||||
TdxCommand::Capabilities,
|
||||
0,
|
||||
tdx_caps.as_mut_fam_struct_ptr() as *const _ as u64,
|
||||
)
|
||||
.map_err(TdxIoctlError::TdxCapabilities)?;
|
||||
Ok(tdx_caps)
|
||||
}
|
||||
20
src/dragonball/src/dbs_upcall/Cargo.toml
Executable file
20
src/dragonball/src/dbs_upcall/Cargo.toml
Executable file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "dbs-upcall"
|
||||
version = "0.3.0"
|
||||
authors = ["Alibaba Dragonball Team"]
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
description = "dbs-upcall is a direct communication tool between VMM and guest"
|
||||
homepage = "https://github.com/openanolis/dragonball-sandbox"
|
||||
repository = "https://github.com/openanolis/dragonball-sandbox/tree/main/crates/dbs-virtio-devices"
|
||||
keywords = ["dragonball", "secure-sandbox", "devices", "upcall", "virtio"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
log = "0.4.14"
|
||||
thiserror = "1"
|
||||
timerfd = "1.2.0"
|
||||
|
||||
dbs-utils = { path = "../dbs_utils" }
|
||||
dbs-virtio-devices = { path = "../dbs_virtio_devices", features = ["virtio-vsock"] }
|
||||
1
src/dragonball/src/dbs_upcall/LICENSE
Symbolic link
1
src/dragonball/src/dbs_upcall/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user