diff options
author | Marcin Radomski <dextero@google.com> | 2024-03-13 16:54:30 +0000 |
---|---|---|
committer | Marcin Radomski <dextero@google.com> | 2024-03-13 16:55:00 +0000 |
commit | a55065196aa142344b2f13f2ab5057b78e7a1acd (patch) | |
tree | 2022376e2c2ee9bbaa0acf8edb35eaab210576e5 | |
parent | 3a0cb8eeb1aebb621aeb6079b668a19b3e270738 (diff) | |
download | drm-a55065196aa142344b2f13f2ab5057b78e7a1acd.tar.gz |
Import 'drm' crateupstream
Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
For Build Team: go/ab-third-party-imports
Bug: http://b/328180645
Test: m libdrm_rust (with aosp/2997556 checked out locally)
Change-Id: I32689719f1bd98740694b39e330518cba6b3e94d
38 files changed, 5240 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..2895e05 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "328742fddc675b3370057b382eb54acbc9b48c79" + }, + "path_in_vcs": "" +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..2684b07 --- /dev/null +++ b/Android.bp @@ -0,0 +1,26 @@ +// This file is generated by cargo_embargo. +// Do not modify this file as changes will be overridden on upgrade. + +// TODO: Add license. +rust_library { + name: "libdrm_rust", + host_supported: true, + crate_name: "drm", + cargo_env_compat: true, + cargo_pkg_version: "0.11.1", + srcs: ["src/lib.rs"], + edition: "2021", + rustlibs: [ + "libbitflags", + "libbytemuck", + "libdrm_ffi", + "libdrm_fourcc", + "librustix", + ], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + product_available: true, + vendor_available: true, +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3576e19 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,749 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bindgen" +version = "0.69.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ffcebc3849946a7170a05992aac39da343a90676ab392c51a4280981d6379c2" +dependencies = [ + "bitflags 2.4.1", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clipboard-win" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc" +dependencies = [ + "error-code", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "drm" +version = "0.11.1" +dependencies = [ + "bitflags 2.4.1", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "image", + "rustix", + "rustyline", +] + +[[package]] +name = "drm-ffi" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" +dependencies = [ + "drm-sys", + "rustix", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176" +dependencies = [ + "bindgen", + "libc", + "linux-raw-sys 0.6.3", + "pkg-config", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" + +[[package]] +name = "fd-lock" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "fdeflate" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "linux-raw-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab96045f1fabcc9fe043d9cb6900c5e1cba5c13f6aaa3d2295b496661924464" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pkg-config" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.12", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustyline" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..81d8cb0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,69 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.65" +name = "drm" +version = "0.11.1" +authors = [ + "Tyler Slabinski <tslabinski@slabity.net>", + "Victoria Brekenfeld <crates-io@drakulix.de>", +] +exclude = [ + ".gitignore", + ".github", +] +description = "Safe, low-level bindings to the Direct Rendering Manager API" +readme = "README.md" +license = "MIT" +repository = "https://github.com/Smithay/drm-rs" + +[dependencies.bitflags] +version = "2" + +[dependencies.bytemuck] +version = "1.12" +features = [ + "extern_crate_alloc", + "derive", +] + +[dependencies.drm-ffi] +version = "0.7.1" + +[dependencies.drm-fourcc] +version = "^2.2.0" + +[dependencies.rustix] +version = "0.38.22" +features = [ + "mm", + "fs", +] + +[dev-dependencies.image] +version = "0.24" +features = ["png"] +default-features = false + +[dev-dependencies.rustix] +version = "0.38.22" +features = [ + "event", + "mm", +] + +[dev-dependencies.rustyline] +version = "13" + +[features] +use_bindgen = ["drm-ffi/use_bindgen"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..9f9d9fc --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,31 @@ +[package] +name = "drm" +description = "Safe, low-level bindings to the Direct Rendering Manager API" +repository = "https://github.com/Smithay/drm-rs" +version = "0.11.1" +license = "MIT" +authors = ["Tyler Slabinski <tslabinski@slabity.net>", "Victoria Brekenfeld <crates-io@drakulix.de>"] +exclude = [".gitignore", ".github"] +rust-version = "1.65" +edition = "2021" + +[dependencies] +bitflags = "2" +bytemuck = { version = "1.12", features = ["extern_crate_alloc", "derive"] } +drm-ffi = { path = "drm-ffi", version = "0.7.1" } +drm-fourcc = "^2.2.0" +rustix = { version = "0.38.22", features = ["mm", "fs"] } + +[dev-dependencies] +image = { version = "0.24", default-features = false, features = ["png"] } +rustix = { version = "0.38.22", features = ["event", "mm"] } +rustyline = "13" + +[features] +use_bindgen = ["drm-ffi/use_bindgen"] + +[workspace] +members = [ + "drm-ffi", + "drm-ffi/drm-sys", +] @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..3630f06 --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "drm" +description: "Safe, low-level bindings to the Direct Rendering Manager API" +third_party { + identifier { + type: "crates.io" + value: "drm" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/drm/drm-0.11.1.crate" + primary_source: true + } + version: "0.11.1" + license_type: NOTICE + last_upgrade_date { + year: 2024 + month: 3 + day: 13 + } +} diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MIT @@ -0,0 +1,7 @@ +# Bug component: 688011 +include platform/prebuilts/rust:main:/OWNERS + +dextero@google.com +vill@google.com +nputikhin@google.com +istvannador@google.com diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2f46ec --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# drm-rs + +[![Crates.io](https://img.shields.io/crates/v/drm.svg)](https://crates.io/crates/drm) +[![docs.rs](https://docs.rs/drm/badge.svg)](https://docs.rs/drm) +[![Build Status](https://github.com/Smithay/drm-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/Smithay/drm-rs/actions/workflows/ci.yml) + +A safe interface to the Direct Rendering Manager. + +## Direct Rendering Manager + +The Direct Rendering Manager is a subsystem found on multiple Unix-based +operating systems that provides a userspace API to graphics hardware. +See the [Wikipedia article](https://en.wikipedia.org/wiki/Direct_Rendering_Manager) +for more details. + +## Usage + +### Basic + +The DRM is accessed using [ioctls](https://en.wikipedia.org/wiki/Ioctl) +on a file representing a graphics card. These can normally be +found in `/dev/dri`, but can also be opened in other ways (ex. udev). + +This crate does not provide a method of opening these files. Instead, the +user program must provide a way to access the file descriptor representing the +device through the [AsFd](https://doc.rust-lang.org/std/os/fd/trait.AsFd.html) +trait. Here is a basic example using `File` as a backend: + +```rust +/// A simple wrapper for a device node. +pub struct Card(std::fs::File); + +/// Implementing [`AsFd`] is a prerequisite to implementing the traits found +/// in this crate. Here, we are just calling [`File::as_fd()`] on the inner +/// [`File`]. +impl AsFd for Card { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +/// Simple helper methods for opening a `Card`. +impl Card { + pub fn open(path: &str) -> Self { + let mut options = std::fs::OpenOptions::new(); + options.read(true); + options.write(true); + Card(options.open(path).unwrap()) + } +} +``` + +Finally, you can implement `drm::Device` to gain access to the basic DRM +functionality: + +```rust +impl drm::Device for Card {} + +fn main() { + let gpu = Card::open("/dev/dri/card0"); + println!("{:#?}", gpu.get_driver().unwrap()); +} +``` + +### Control (modesetting) + +See [`drm::control::Device`](https://docs.rs/drm/*/drm/control/trait.Device.html) +as well as our mode-setting examples: [`atomic_modeset`](https://github.com/Smithay/drm-rs/blob/develop/examples/atomic_modeset.rs) +and [`legacy_modeset`](https://github.com/Smithay/drm-rs/blob/develop/examples/legacy_modeset.rs) + +### Rendering + +Rendering is done by [creating](https://docs.rs/drm/*/drm/control/trait.Device.html#method.add_framebuffer) and +[attaching](https://docs.rs/drm/*/drm/control/trait.Device.html#method.page_flip) [framebuffers](https://docs.rs/drm/*/drm/control/framebuffer/index.html) +to [crtcs](https://docs.rs/drm/*/drm/control/crtc/index.html). + +A framebuffer is created from anything implementing [`Buffer`](https://docs.rs/drm/*/drm/buffer/trait.Buffer.html) like the always +available, but very limited, [`DumbBuffer`](https://docs.rs/drm/*/drm/control/dumbbuffer/struct.DumbBuffer.html). + +For faster hardware-backed buffers, checkout [gbm.rs](https://github.com/Smithay/gbm.rs). diff --git a/cargo_embargo.json b/cargo_embargo.json new file mode 100644 index 0000000..ebae879 --- /dev/null +++ b/cargo_embargo.json @@ -0,0 +1,6 @@ +{ + "run_cargo": false, + "module_name_overrides": { + "libdrm": "libdrm_rust" + } +} diff --git a/examples/atomic_modeset.rs b/examples/atomic_modeset.rs new file mode 100644 index 0000000..56000ee --- /dev/null +++ b/examples/atomic_modeset.rs @@ -0,0 +1,198 @@ +mod utils; +use crate::utils::*; + +use drm::control::Device as ControlDevice; +use drm::Device as BasicDevice; + +use drm::buffer::DrmFourcc; + +use drm::control::{self, atomic, connector, crtc, property, AtomicCommitFlags}; + +pub fn main() { + let card = Card::open_global(); + + card.set_client_capability(drm::ClientCapability::UniversalPlanes, true) + .expect("Unable to request UniversalPlanes capability"); + card.set_client_capability(drm::ClientCapability::Atomic, true) + .expect("Unable to request Atomic capability"); + + // Load the information. + let res = card + .resource_handles() + .expect("Could not load normal resource ids."); + let coninfo: Vec<connector::Info> = res + .connectors() + .iter() + .flat_map(|con| card.get_connector(*con, true)) + .collect(); + let crtcinfo: Vec<crtc::Info> = res + .crtcs() + .iter() + .flat_map(|crtc| card.get_crtc(*crtc)) + .collect(); + + // Filter each connector until we find one that's connected. + let con = coninfo + .iter() + .find(|&i| i.state() == connector::State::Connected) + .expect("No connected connectors"); + + // Get the first (usually best) mode + let &mode = con.modes().first().expect("No modes found on connector"); + + let (disp_width, disp_height) = mode.size(); + + // Find a crtc and FB + let crtc = crtcinfo.first().expect("No crtcs found"); + + // Select the pixel format + let fmt = DrmFourcc::Xrgb8888; + + // Create a DB + // If buffer resolution is above display resolution, a ENOSPC (not enough GPU memory) error may + // occur + let mut db = card + .create_dumb_buffer((disp_width.into(), disp_height.into()), fmt, 32) + .expect("Could not create dumb buffer"); + + // Map it and grey it out. + { + let mut map = card + .map_dumb_buffer(&mut db) + .expect("Could not map dumbbuffer"); + for b in map.as_mut() { + *b = 128; + } + } + + // Create an FB: + let fb = card + .add_framebuffer(&db, 24, 32) + .expect("Could not create FB"); + + let planes = card.plane_handles().expect("Could not list planes"); + let (better_planes, compatible_planes): ( + Vec<control::plane::Handle>, + Vec<control::plane::Handle>, + ) = planes + .iter() + .filter(|&&plane| { + card.get_plane(plane) + .map(|plane_info| { + let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs()); + compatible_crtcs.contains(&crtc.handle()) + }) + .unwrap_or(false) + }) + .partition(|&&plane| { + if let Ok(props) = card.get_properties(plane) { + for (&id, &val) in props.iter() { + if let Ok(info) = card.get_property(id) { + if info.name().to_str().map(|x| x == "type").unwrap_or(false) { + return val == (drm::control::PlaneType::Primary as u32).into(); + } + } + } + } + false + }); + let plane = *better_planes.first().unwrap_or(&compatible_planes[0]); + + println!("{:#?}", mode); + println!("{:#?}", fb); + println!("{:#?}", db); + println!("{:#?}", plane); + + let con_props = card + .get_properties(con.handle()) + .expect("Could not get props of connector") + .as_hashmap(&card) + .expect("Could not get a prop from connector"); + let crtc_props = card + .get_properties(crtc.handle()) + .expect("Could not get props of crtc") + .as_hashmap(&card) + .expect("Could not get a prop from crtc"); + let plane_props = card + .get_properties(plane) + .expect("Could not get props of plane") + .as_hashmap(&card) + .expect("Could not get a prop from plane"); + + let mut atomic_req = atomic::AtomicModeReq::new(); + atomic_req.add_property( + con.handle(), + con_props["CRTC_ID"].handle(), + property::Value::CRTC(Some(crtc.handle())), + ); + let blob = card + .create_property_blob(&mode) + .expect("Failed to create blob"); + atomic_req.add_property(crtc.handle(), crtc_props["MODE_ID"].handle(), blob); + atomic_req.add_property( + crtc.handle(), + crtc_props["ACTIVE"].handle(), + property::Value::Boolean(true), + ); + atomic_req.add_property( + plane, + plane_props["FB_ID"].handle(), + property::Value::Framebuffer(Some(fb)), + ); + atomic_req.add_property( + plane, + plane_props["CRTC_ID"].handle(), + property::Value::CRTC(Some(crtc.handle())), + ); + atomic_req.add_property( + plane, + plane_props["SRC_X"].handle(), + property::Value::UnsignedRange(0), + ); + atomic_req.add_property( + plane, + plane_props["SRC_Y"].handle(), + property::Value::UnsignedRange(0), + ); + atomic_req.add_property( + plane, + plane_props["SRC_W"].handle(), + property::Value::UnsignedRange((mode.size().0 as u64) << 16), + ); + atomic_req.add_property( + plane, + plane_props["SRC_H"].handle(), + property::Value::UnsignedRange((mode.size().1 as u64) << 16), + ); + atomic_req.add_property( + plane, + plane_props["CRTC_X"].handle(), + property::Value::SignedRange(0), + ); + atomic_req.add_property( + plane, + plane_props["CRTC_Y"].handle(), + property::Value::SignedRange(0), + ); + atomic_req.add_property( + plane, + plane_props["CRTC_W"].handle(), + property::Value::UnsignedRange(mode.size().0 as u64), + ); + atomic_req.add_property( + plane, + plane_props["CRTC_H"].handle(), + property::Value::UnsignedRange(mode.size().1 as u64), + ); + + // Set the crtc + // On many setups, this requires root access. + card.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, atomic_req) + .expect("Failed to set mode"); + + let five_seconds = ::std::time::Duration::from_millis(5000); + ::std::thread::sleep(five_seconds); + + card.destroy_framebuffer(fb).unwrap(); + card.destroy_dumb_buffer(db).unwrap(); +} diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..5164fda --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,33 @@ +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; +use crate::utils::*; + +pub fn main() { + let card = Card::open_global(); + + // Attempt to acquire and release master lock + println!("Get Master lock: {:?}", card.acquire_master_lock()); + println!("Release Master lock: {:?}", card.release_master_lock()); + + // Get the Bus ID of the device + println!("Getting Bus ID: {:?}", card.get_bus_id().unwrap()); + + // Figure out driver in use + println!("Getting driver info"); + let driver = card.get_driver().unwrap(); + println!("\tName: {:?}", driver.name()); + println!("\tDate: {:?}", driver.date()); + println!("\tDesc: {:?}", driver.description()); + + // Enable all possible client capabilities + println!("Setting client capabilities"); + for &cap in capabilities::CLIENT_CAP_ENUMS { + println!("\t{:?}: {:?}", cap, card.set_client_capability(cap, true)); + } + + // Get driver capabilities + println!("Getting driver capabilities"); + for &cap in capabilities::DRIVER_CAP_ENUMS { + println!("\t{:?}: {:?}", cap, card.get_driver_capability(cap)); + } +} diff --git a/examples/ffi.rs b/examples/ffi.rs new file mode 100644 index 0000000..bcff231 --- /dev/null +++ b/examples/ffi.rs @@ -0,0 +1,76 @@ +use drm_ffi as ffi; + +use std::fs::{File, OpenOptions}; +use std::os::unix::io::{AsFd, BorrowedFd}; + +#[derive(Debug)] +// This is our customized struct that implements the traits in drm. +struct Card(File); + +// Need to implement AsRawFd before we can implement drm::Device +impl AsFd for Card { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl Card { + fn open(path: &str) -> Self { + let mut options = OpenOptions::new(); + options.read(true); + options.write(true); + Card(options.open(path).unwrap()) + } + + fn open_global() -> Self { + Self::open("/dev/dri/card0") + } +} + +fn print_busid(fd: BorrowedFd<'_>) { + let mut buffer = Vec::new(); + let busid = ffi::get_bus_id(fd, Some(&mut buffer)); + println!("{:#?}", busid); +} + +fn print_client(fd: BorrowedFd<'_>) { + let client = ffi::get_client(fd, 0); + println!("{:#?}", client); +} + +fn print_version(fd: BorrowedFd<'_>) { + let mut name = Vec::new(); + let mut date = Vec::new(); + let mut desc = Vec::new(); + + let version = ffi::get_version(fd, Some(&mut name), Some(&mut date), Some(&mut desc)); + + println!("{:#?}", version); +} + +fn print_capabilities(fd: BorrowedFd<'_>) { + for cty in 1.. { + let cap = ffi::get_capability(fd, cty); + match cap { + Ok(_) => println!("{:#?}", cap), + Err(_) => break, + } + } +} + +fn print_token(fd: BorrowedFd<'_>) { + let token = ffi::auth::get_magic_token(fd); + println!("{:#?}", token); +} + +fn main() { + let card = Card::open_global(); + let fd = card.as_fd(); + + print_busid(fd); + print_client(fd); + print_version(fd); + print_capabilities(fd); + print_token(fd); + //print_stats(fd); +} diff --git a/examples/images/1.png b/examples/images/1.png Binary files differnew file mode 100644 index 0000000..0862b32 --- /dev/null +++ b/examples/images/1.png diff --git a/examples/images/2.png b/examples/images/2.png Binary files differnew file mode 100644 index 0000000..88af7eb --- /dev/null +++ b/examples/images/2.png diff --git a/examples/images/3.png b/examples/images/3.png Binary files differnew file mode 100644 index 0000000..4c94b9e --- /dev/null +++ b/examples/images/3.png diff --git a/examples/images/4.png b/examples/images/4.png Binary files differnew file mode 100644 index 0000000..4062154 --- /dev/null +++ b/examples/images/4.png diff --git a/examples/kms_interactive.rs b/examples/kms_interactive.rs new file mode 100644 index 0000000..06c29d1 --- /dev/null +++ b/examples/kms_interactive.rs @@ -0,0 +1,208 @@ +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; +use crate::utils::*; +use drm::control::{from_u32, RawResourceHandle}; + +pub fn main() { + let card = Card::open_global(); + + // Enable all possible client capabilities + for &cap in capabilities::CLIENT_CAP_ENUMS { + if let Err(e) = card.set_client_capability(cap, true) { + eprintln!("Unable to activate capability {:?}: {}", cap, e); + return; + } + } + + run_repl(&card); +} + +fn run_repl(card: &Card) { + // Load a set of numbered images + let images = [ + images::load_image("1.png"), + images::load_image("2.png"), + images::load_image("3.png"), + images::load_image("4.png"), + ]; + + for image in &images { + // Create the Dumbbuffer + let fmt = drm::buffer::DrmFourcc::Xrgb8888; + let mut db = card + .create_dumb_buffer(image.dimensions(), fmt, 32) + .unwrap(); + + // Create a Framebuffer to represent it + let _fb = card.add_framebuffer(&db, 24, 32).unwrap(); + + // Load the image into the buffer + { + let mut mapping = card.map_dumb_buffer(&mut db).unwrap(); + let buffer = mapping.as_mut(); + for (img_px, map_px) in image.pixels().zip(buffer.chunks_exact_mut(4)) { + // Assuming little endian, it's BGRA + map_px[0] = img_px[0]; // Blue + map_px[1] = img_px[1]; // Green + map_px[2] = img_px[2]; // Red + map_px[3] = img_px[3]; // Alpha + } + }; + } + + // Using rustyline to create the interactive prompt. + let editor_config = rustyline::config::Builder::new() + .max_history_size(256) + .unwrap() + .completion_type(rustyline::config::CompletionType::List) + .edit_mode(rustyline::config::EditMode::Vi) + .auto_add_history(true) + .build(); + let mut kms_editor = rustyline::Editor::<(), _>::with_config(editor_config).unwrap(); + let mut atomic_editor = rustyline::Editor::<(), _>::with_config(editor_config).unwrap(); + + for line in kms_editor.iter("KMS>> ").map(|x| x.unwrap()) { + let args: Vec<_> = line.split_whitespace().collect(); + match &args[..] { + ["CreateAtomicSet"] => { + for line in atomic_editor.iter("Atomic>> ").map(|x| x.unwrap()) { + let args: Vec<_> = line.split_whitespace().collect(); + match &args[..] { + ["Quit"] => break, + args => println!("{:?}", args), + } + } + } + // Destroying a framebuffer + ["DestroyFramebuffer", handle] => { + let handle: u32 = str::parse(handle).unwrap(); + let handle: drm::control::framebuffer::Handle = from_u32(handle).unwrap(); + if let Err(err) = card.destroy_framebuffer(handle) { + println!("Unable to destroy framebuffer ({:?}): {}", handle, err); + } + } + // Print out all resources + ["GetResources"] => { + let resources = card.resource_handles().unwrap(); + println!("\tConnectors: {:?}", resources.connectors()); + println!("\tEncoders: {:?}", resources.encoders()); + println!("\tCRTCS: {:?}", resources.crtcs()); + println!("\tFramebuffers: {:?}", resources.framebuffers()); + let planes = card.plane_handles().unwrap(); + println!("\tPlanes: {:?}", planes); + } + // Print out the values of a specific property + ["GetProperty", handle] => { + let handle: u32 = str::parse(handle).unwrap(); + let handle: drm::control::property::Handle = from_u32(handle).unwrap(); + let property = card.get_property(handle).unwrap(); + println!("\tName: {:?}", property.name()); + println!("\tMutable: {:?}", property.mutable()); + println!("\tAtomic: {:?}", property.atomic()); + println!("\tValue: {:#?}", property.value_type()); + } + // Get the property-value pairs of a single resource + ["GetProperties", handle] => match HandleWithProperties::from_str(card, handle) { + Ok(handle) => { + let props = match handle { + HandleWithProperties::Connector(handle) => { + card.get_properties(handle).unwrap() + } + HandleWithProperties::CRTC(handle) => card.get_properties(handle).unwrap(), + HandleWithProperties::Plane(handle) => card.get_properties(handle).unwrap(), + }; + for (id, val) in props.iter() { + println!("\tProperty: {:?}\tValue: {:?}", id, val); + } + } + Err(_) => println!("Unknown handle or handle has no properties"), + }, + // Set a property's value on a resource + ["SetProperty", handle, property, value] => { + let property: u32 = str::parse(property).unwrap(); + let property: drm::control::property::Handle = from_u32(property).unwrap(); + let value: u64 = str::parse(value).unwrap(); + + match HandleWithProperties::from_str(card, handle) { + Ok(handle) => { + match handle { + HandleWithProperties::Connector(handle) => { + println!("\t{:?}", card.set_property(handle, property, value)); + } + HandleWithProperties::CRTC(handle) => { + println!("\t{:?}", card.set_property(handle, property, value)); + } + HandleWithProperties::Plane(handle) => { + println!("\t{:?}", card.set_property(handle, property, value)); + } + }; + } + Err(_) => println!("Unknown handle or handle has no properties"), + }; + } + ["GetModes", handle] => match HandleWithProperties::from_str(card, handle) { + Ok(HandleWithProperties::Connector(handle)) => { + let modes = card.get_modes(handle).unwrap(); + for mode in modes { + println!("\tName:\t{:?}", mode.name()); + println!("\t\tSize:\t{:?}", mode.size()); + println!("\t\tRefresh:\t{:?}", mode.vrefresh()); + } + } + _ => println!("Unknown handle or handle is not a connector"), + }, + ["help"] => { + println!("CreateAtomicSet"); + println!("DestroyFramebuffer <handle>"); + println!("GetResources"); + println!("GetProperty <handle>"); + println!("GetProperties <handle>"); + println!("SetProperty <handle> <poperty> <value>"); + println!("GetModes <handle>"); + } + ["quit"] => break, + [] => (), + _ => { + println!("Unknown command"); + } + } + } +} + +#[allow(clippy::upper_case_acronyms)] +enum HandleWithProperties { + Connector(drm::control::connector::Handle), + CRTC(drm::control::crtc::Handle), + Plane(drm::control::plane::Handle), +} + +impl HandleWithProperties { + // This is a helper command that will take a string of a number and lookup + // the corresponding resource. + fn from_str(card: &Card, handle: &str) -> Result<Self, ()> { + let handle: u32 = str::parse(handle).unwrap(); + let handle = RawResourceHandle::new(handle).unwrap(); + + let rhandles = card.resource_handles().unwrap(); + for connector in rhandles.connectors().iter().map(|h| (*h).into()) { + if handle == connector { + return Ok(HandleWithProperties::Connector(handle.into())); + } + } + + for crtc in rhandles.crtcs().iter().map(|h| (*h).into()) { + if handle == crtc { + return Ok(HandleWithProperties::CRTC(handle.into())); + } + } + + let phandles = card.plane_handles().unwrap(); + for plane in phandles.iter().map(|h| (*h).into()) { + if handle == plane { + return Ok(HandleWithProperties::Plane(handle.into())); + } + } + + Err(()) + } +} diff --git a/examples/legacy_modeset.rs b/examples/legacy_modeset.rs new file mode 100644 index 0000000..204e6d5 --- /dev/null +++ b/examples/legacy_modeset.rs @@ -0,0 +1,81 @@ +mod utils; +use crate::utils::*; + +use drm::control::Device as ControlDevice; + +use drm::buffer::DrmFourcc; + +use drm::control::{connector, crtc}; + +pub fn main() { + let card = Card::open_global(); + + // Load the information. + let res = card + .resource_handles() + .expect("Could not load normal resource ids."); + let coninfo: Vec<connector::Info> = res + .connectors() + .iter() + .flat_map(|con| card.get_connector(*con, true)) + .collect(); + let crtcinfo: Vec<crtc::Info> = res + .crtcs() + .iter() + .flat_map(|crtc| card.get_crtc(*crtc)) + .collect(); + + // Filter each connector until we find one that's connected. + let con = coninfo + .iter() + .find(|&i| i.state() == connector::State::Connected) + .expect("No connected connectors"); + + // Get the first (usually best) mode + let &mode = con.modes().first().expect("No modes found on connector"); + + let (disp_width, disp_height) = mode.size(); + + // Find a crtc and FB + let crtc = crtcinfo.first().expect("No crtcs found"); + + // Select the pixel format + let fmt = DrmFourcc::Xrgb8888; + + // Create a DB + // If buffer resolution is larger than display resolution, an ENOSPC (not enough video memory) + // error may occur + let mut db = card + .create_dumb_buffer((disp_width.into(), disp_height.into()), fmt, 32) + .expect("Could not create dumb buffer"); + + // Map it and grey it out. + { + let mut map = card + .map_dumb_buffer(&mut db) + .expect("Could not map dumbbuffer"); + for b in map.as_mut() { + *b = 128; + } + } + + // Create an FB: + let fb = card + .add_framebuffer(&db, 24, 32) + .expect("Could not create FB"); + + println!("{:#?}", mode); + println!("{:#?}", fb); + println!("{:#?}", db); + + // Set the crtc + // On many setups, this requires root access. + card.set_crtc(crtc.handle(), Some(fb), (0, 0), &[con.handle()], Some(mode)) + .expect("Could not set CRTC"); + + let five_seconds = ::std::time::Duration::from_millis(5000); + ::std::thread::sleep(five_seconds); + + card.destroy_framebuffer(fb).unwrap(); + card.destroy_dumb_buffer(db).unwrap(); +} diff --git a/examples/list_modes.rs b/examples/list_modes.rs new file mode 100644 index 0000000..bea9b18 --- /dev/null +++ b/examples/list_modes.rs @@ -0,0 +1,16 @@ +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; +use crate::utils::*; + +pub fn main() { + let card = Card::open_global(); + + let resources = card.resource_handles().unwrap(); + for connector in resources.connectors().iter() { + let info = card.get_connector(*connector, false).unwrap(); + println!("Connector {:?}: {:?}", info.interface(), info.state()); + if info.state() == drm::control::connector::State::Connected { + println!("\t Modes:\n{:#?}", info.modes()); + } + } +} diff --git a/examples/properties.rs b/examples/properties.rs new file mode 100644 index 0000000..83ffde3 --- /dev/null +++ b/examples/properties.rs @@ -0,0 +1,49 @@ +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; +use crate::utils::*; + +fn print_properties<T: drm::control::ResourceHandle>(card: &Card, handle: T) { + let props = card.get_properties(handle).unwrap(); + + for (&id, &val) in props.iter() { + println!("Property: {:?}", id); + let info = card.get_property(id).unwrap(); + println!("{:?}", info.name()); + println!("{:#?}", info.value_type()); + println!("Mutable: {}", info.mutable()); + println!("Atomic: {}", info.atomic()); + println!("Value: {:?}", info.value_type().convert_value(val)); + println!(); + } +} + +pub fn main() { + let card = Card::open_global(); + + // Enable all possible client capabilities + for &cap in capabilities::CLIENT_CAP_ENUMS { + if let Err(e) = card.set_client_capability(cap, true) { + eprintln!("Unable to activate capability {:?}: {}", cap, e); + return; + } + } + + let resources = card.resource_handles().unwrap(); + let plane_res = card.plane_handles().unwrap(); + + for &handle in resources.connectors() { + print_properties(&card, handle); + } + + for &handle in resources.framebuffers() { + print_properties(&card, handle); + } + + for &handle in resources.crtcs() { + print_properties(&card, handle); + } + + for handle in plane_res { + print_properties(&card, handle); + } +} diff --git a/examples/resources.rs b/examples/resources.rs new file mode 100644 index 0000000..0379c3f --- /dev/null +++ b/examples/resources.rs @@ -0,0 +1,77 @@ +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; +use crate::utils::*; + +pub fn main() { + let card = Card::open_global(); + + // Enable all possible client capabilities + for &cap in capabilities::CLIENT_CAP_ENUMS { + if let Err(e) = card.set_client_capability(cap, true) { + eprintln!("Unable to activate capability {:?}: {}", cap, e); + return; + } + } + + let resources = card.resource_handles().unwrap(); + let plane_res = card.plane_handles().unwrap(); + + // Print out all card resource handles + println!("Connectors:\t{:?}", resources.connectors()); + println!("Encoders:\t{:?}", resources.encoders()); + println!("CRTCs:\t\t{:?}", resources.crtcs()); + println!("Framebuffers:\t{:?}", resources.framebuffers()); + println!("Planes:\t\t{:?}", plane_res); + + for &handle in resources.connectors() { + let info = card.get_connector(handle, false).unwrap(); + println!("Connector: {:?}", handle); + println!("\t{:?}-{}", info.interface(), info.interface_id()); + println!("\t{:?}", info.state()); + println!("\t{:?}", info.size()); + println!("\t{:?}", info.encoders()); + println!("\t{:?}", info.current_encoder()); + + for mode in card.get_modes(handle).unwrap() { + println!("{:?}", mode); + } + } + println!("\n"); + + for &handle in resources.encoders() { + let info = card.get_encoder(handle).unwrap(); + println!("Encoder: {:?}", handle); + println!("\t{:?}", info.kind()); + println!("\t{:?}", info.crtc()); + } + println!("\n"); + + for &handle in resources.crtcs() { + let info = card.get_crtc(handle).unwrap(); + println!("CRTC: {:?}", handle); + println!("\tPosition: {:?}", info.position()); + println!("\tMode: {:?}", info.mode()); + println!("\tFramebuffer: {:?}", info.framebuffer()); + println!("\tGamma Length: {:?}", info.gamma_length()); + } + println!("\n"); + + for &handle in resources.framebuffers() { + let info = card.get_framebuffer(handle).unwrap(); + println!("Framebuffer: {:?}", handle); + println!("\tSize: {:?}", info.size()); + println!("\tPitch: {:?}", info.pitch()); + println!("\tBPP: {:?}", info.bpp()); + println!("\tDepth: {:?}", info.depth()); + } + + println!("\n"); + + for handle in plane_res { + let info = card.get_plane(handle).unwrap(); + println!("Plane: {:?}", handle); + println!("\tCRTC: {:?}", info.crtc()); + println!("\tFramebuffer: {:?}", info.framebuffer()); + println!("\tFormats: {:?}", info.formats()); + } +} diff --git a/examples/syncobj.rs b/examples/syncobj.rs new file mode 100644 index 0000000..05fbc50 --- /dev/null +++ b/examples/syncobj.rs @@ -0,0 +1,53 @@ +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; + +use crate::utils::*; +use rustix::event::PollFlags; +use std::{ + io, + os::unix::io::{AsFd, OwnedFd}, +}; + +impl Card { + fn simulate_command_submission(&self) -> io::Result<OwnedFd> { + // Create a temporary syncobj to receive the command fence. + let syncobj = self.create_syncobj(false)?; + + let sync_file = { + // Fake a command submission by signalling the syncobj immediately. The kernel + // attaches a null fence object which is always signalled. Other than this, there + // isn't a good way to create and signal a fence object from user-mode, so an actual + // device is required to test this properly. + // + // For a real device, the syncobj handle should be passed to a command submission + // which is expected to set a fence to be signalled upon completion. + self.syncobj_signal(&[syncobj])?; + + // Export fence set by previous ioctl to file descriptor. + self.syncobj_to_fd(syncobj, true) + }; + + // The sync file descriptor constitutes ownership of the fence, so the syncobj can be + // safely destroyed. + self.destroy_syncobj(syncobj)?; + + sync_file + } +} + +fn main() { + let card = Card::open_global(); + let sync_file = card.simulate_command_submission().unwrap(); + let fd = sync_file.as_fd(); + + // Poll for readability. The DRM fence object will directly wake the thread when signalled. + // + // Alternatively, Tokio's AsyncFd may be used like so: + // + // use tokio::io::{Interest, unix::AsyncFd}; + // let afd = AsyncFd::with_interest(sync_file, Interest::READABLE).unwrap(); + // let future = async move { afd.readable().await.unwrap().retain_ready() }; + // future.await; + let mut poll_fds = [rustix::event::PollFd::new(&fd, PollFlags::IN)]; + rustix::event::poll(&mut poll_fds, -1).unwrap(); +} diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs new file mode 100644 index 0000000..2001fbe --- /dev/null +++ b/examples/utils/mod.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] + +pub use drm::control::Device as ControlDevice; +pub use drm::Device; + +#[derive(Debug)] +/// A simple wrapper for a device node. +pub struct Card(std::fs::File); + +/// Implementing `AsFd` is a prerequisite to implementing the traits found +/// in this crate. Here, we are just calling `as_fd()` on the inner File. +impl std::os::unix::io::AsFd for Card { + fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { + self.0.as_fd() + } +} + +/// With `AsFd` implemented, we can now implement `drm::Device`. +impl Device for Card {} +impl ControlDevice for Card {} + +/// Simple helper methods for opening a `Card`. +impl Card { + pub fn open(path: &str) -> Self { + let mut options = std::fs::OpenOptions::new(); + options.read(true); + options.write(true); + Card(options.open(path).unwrap()) + } + + pub fn open_global() -> Self { + Self::open("/dev/dri/card0") + } +} + +pub mod capabilities { + use drm::ClientCapability as CC; + pub const CLIENT_CAP_ENUMS: &[CC] = &[CC::Stereo3D, CC::UniversalPlanes, CC::Atomic]; + + use drm::DriverCapability as DC; + pub const DRIVER_CAP_ENUMS: &[DC] = &[ + DC::DumbBuffer, + DC::VBlankHighCRTC, + DC::DumbPreferredDepth, + DC::DumbPreferShadow, + DC::Prime, + DC::MonotonicTimestamp, + DC::ASyncPageFlip, + DC::CursorWidth, + DC::CursorHeight, + DC::AddFB2Modifiers, + DC::PageFlipTarget, + DC::CRTCInVBlankEvent, + DC::SyncObj, + DC::TimelineSyncObj, + ]; +} + +pub mod images { + use image; + + pub fn load_image(name: &str) -> image::RgbaImage { + let path = format!("examples/images/{}", name); + image::open(path).unwrap().to_rgba8() + } +} diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs new file mode 100644 index 0000000..45de1b5 --- /dev/null +++ b/src/buffer/mod.rs @@ -0,0 +1,120 @@ +//! Memory management and buffer functionality that the DRM subsystem exposes. +//! +//! # Summary +//! +//! The DRM subsystem exposes functionality for managing memory on modern GPU +//! devices using a system called the Graphics Execution Manager (GEM). This +//! system manages GPU buffers and exposes them to userspace using 32-bit +//! handles. These handles are automatically reference counted in the kernel. +//! +//! GEM provides a small API for sharing buffers between processes. However, it +//! does not provide any generic API for creating these. Instead, each driver +//! provides its own method of creating these buffers. The `libgbm` library +//! (part of the mesa project) provides a driver agnostic method of creating +//! these buffers. +//! +//! There are two methods of sharing a GEM handle between processes: +//! +//! 1. Using `Flink` to globally publish a handle using a 32-bit 'name'. This +//! requires either holding the DRM Master lock or having the process' +//! [`AuthToken`](struct@crate::AuthToken) authenticated. However, any process can +//! open these handles if they know (or even guess) the global name. +//! +//! 2. Converting the GEM handle into a PRIME file descriptor, and passing it +//! like a regular one. This allows better control and security, and is the +//! recommended method of sharing buffers. + +use crate::control; +pub use drm_fourcc::{DrmFourcc, DrmModifier, DrmVendor, UnrecognizedFourcc, UnrecognizedVendor}; + +/// A handle to a GEM buffer +/// +/// # Notes +/// +/// There are no guarantees that this handle is valid. It is up to the user +/// to make sure this handle does not outlive the underlying buffer, and to +/// prevent buffers from leaking by properly closing them after they are done. +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("buffer::Handle").field(&self.0).finish() + } +} + +/// The name of a GEM buffer. +/// +/// # Notes +/// +/// There are no guarantees that this name is valid. It is up to the user +/// to make sure this name does not outlive the underlying buffer, and to +/// prevent buffers from leaking by properly closing them after they are done. +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Name(u32); + +impl From<Name> for u32 { + fn from(name: Name) -> u32 { + name.0 + } +} + +impl std::fmt::Debug for Name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("buffer::Name").field(&self.0).finish() + } +} + +/// Common functionality of all regular buffers. +pub trait Buffer { + /// The width and height of the buffer. + fn size(&self) -> (u32, u32); + /// The format of the buffer. + fn format(&self) -> DrmFourcc; + /// The pitch of the buffer. + fn pitch(&self) -> u32; + /// The handle to the buffer. + fn handle(&self) -> Handle; +} + +/// Planar buffers are buffers where each channel/plane is in its own buffer. +/// +/// Each plane has their own handle, pitch, and offsets. +pub trait PlanarBuffer { + /// The width and height of the buffer. + fn size(&self) -> (u32, u32); + /// The format of the buffer. + fn format(&self) -> DrmFourcc; + /// The modifier of the buffer. + fn modifier(&self) -> Option<DrmModifier>; + /// The pitches of the buffer. + fn pitches(&self) -> [u32; 4]; + /// The handles to the buffer. + fn handles(&self) -> [Option<Handle>; 4]; + /// The offsets of the buffer. + fn offsets(&self) -> [u32; 4]; +} diff --git a/src/control/atomic.rs b/src/control/atomic.rs new file mode 100644 index 0000000..e2a8c3a --- /dev/null +++ b/src/control/atomic.rs @@ -0,0 +1,71 @@ +//! Helpers for atomic modesetting. + +use crate::control; + +/// Helper struct to construct atomic commit requests +#[derive(Debug, Clone, Default)] +pub struct AtomicModeReq { + pub(super) objects: Vec<control::RawResourceHandle>, + pub(super) count_props_per_object: Vec<u32>, + pub(super) props: Vec<control::property::Handle>, + pub(super) values: Vec<control::property::RawValue>, +} + +impl AtomicModeReq { + /// Create a new and empty atomic commit request + pub fn new() -> AtomicModeReq { + Self::default() + } + + /// Add a property and value pair for a given raw resource to the request + pub fn add_raw_property( + &mut self, + obj_id: control::RawResourceHandle, + prop_id: control::property::Handle, + value: control::property::RawValue, + ) { + // add object if missing (also to count_props_per_object) + let (idx, prop_count) = match self.objects.binary_search(&obj_id) { + Ok(idx) => (idx, self.count_props_per_object[idx]), + Err(new_idx) => { + self.objects.insert(new_idx, obj_id); + self.count_props_per_object.insert(new_idx, 0); + (new_idx, 0) + } + }; + + // get start of our objects props + let prop_slice_start = self.count_props_per_object.iter().take(idx).sum::<u32>() as usize; + // get end + let prop_slice_end = prop_slice_start + prop_count as usize; + + // search for existing prop entry + match self.props[prop_slice_start..prop_slice_end] + .binary_search_by_key(&Into::<u32>::into(prop_id), |x| (*x).into()) + { + // prop exists, override + Ok(prop_idx) => { + self.values[prop_slice_start + prop_idx] = value; + } + Err(prop_idx) => { + // increase prop count + self.count_props_per_object[idx] += 1; + // insert prop, insert value + self.props.insert(prop_slice_start + prop_idx, prop_id); + self.values.insert(prop_slice_start + prop_idx, value); + } + } + } + + /// Add a property and value pair for a given handle to the request + pub fn add_property<H>( + &mut self, + handle: H, + property: control::property::Handle, + value: control::property::Value, + ) where + H: control::ResourceHandle, + { + self.add_raw_property(handle.into(), property, value.into()) + } +} diff --git a/src/control/connector.rs b/src/control/connector.rs new file mode 100644 index 0000000..9fc4755 --- /dev/null +++ b/src/control/connector.rs @@ -0,0 +1,297 @@ +//! # Connector +//! +//! Represents the physical output, such as a DisplayPort or VGA connector. +//! +//! A Connector is the physical connection between the display controller and +//! a display. These objects keep track of connection information and state, +//! including the modes that the current display supports. + +use crate::control; +use drm_ffi as ffi; + +/// A handle to a connector +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl control::ResourceHandle for Handle { + const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_CONNECTOR; +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("connector::Handle").field(&self.0).finish() + } +} + +/// Information about a connector +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Info { + pub(crate) handle: Handle, + pub(crate) interface: Interface, + pub(crate) interface_id: u32, + pub(crate) connection: State, + pub(crate) size: Option<(u32, u32)>, + pub(crate) modes: Vec<control::Mode>, + pub(crate) encoders: Vec<control::encoder::Handle>, + pub(crate) curr_enc: Option<control::encoder::Handle>, + pub(crate) subpixel: SubPixel, +} + +impl Info { + /// Returns the handle to this connector. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the type of `Interface` of this connector. + pub fn interface(&self) -> Interface { + self.interface + } + + /// Returns the interface ID of this connector. + /// + /// When multiple connectors have the same `Interface`, they will have + /// different interface IDs. + pub fn interface_id(&self) -> u32 { + self.interface_id + } + + /// Returns the `State` of this connector. + pub fn state(&self) -> State { + self.connection + } + + /// Returns the size of the display (in millimeters) if connected. + pub fn size(&self) -> Option<(u32, u32)> { + self.size + } + + /// Returns a list of encoders that can be possibly used by this connector. + pub fn encoders(&self) -> &[control::encoder::Handle] { + &self.encoders + } + + /// Returns a list of modes this connector reports as supported. + pub fn modes(&self) -> &[control::Mode] { + &self.modes + } + + /// Returns the current encoder attached to this connector. + pub fn current_encoder(&self) -> Option<control::encoder::Handle> { + self.curr_enc + } + + /// Subpixel order of the connected sink + pub fn subpixel(&self) -> SubPixel { + self.subpixel + } +} + +/// A physical interface type. +#[allow(missing_docs)] +#[allow(clippy::upper_case_acronyms)] +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum Interface { + Unknown, + VGA, + DVII, + DVID, + DVIA, + Composite, + SVideo, + LVDS, + Component, + NinePinDIN, + DisplayPort, + HDMIA, + HDMIB, + TV, + EmbeddedDisplayPort, + Virtual, + DSI, + DPI, + Writeback, + SPI, + USB, +} + +impl Interface { + /// Get interface name as string + pub fn as_str(&self) -> &'static str { + // source: https://github.com/torvalds/linux/blob/489fa31ea873282b41046d412ec741f93946fc2d/drivers/gpu/drm/drm_connector.c#L89 + match self { + Interface::Unknown => "Unknown", + Interface::VGA => "VGA", + Interface::DVII => "DVI-I", + Interface::DVID => "DVI-D", + Interface::DVIA => "DVI-A", + Interface::Composite => "Composite", + Interface::SVideo => "SVIDEO", + Interface::LVDS => "LVDS", + Interface::Component => "Component", + Interface::NinePinDIN => "DIN", + Interface::DisplayPort => "DP", + Interface::HDMIA => "HDMI-A", + Interface::HDMIB => "HDMI-B", + Interface::TV => "TV", + Interface::EmbeddedDisplayPort => "eDP", + Interface::Virtual => "Virtual", + Interface::DSI => "DSI", + Interface::DPI => "DPI", + Interface::Writeback => "Writeback", + Interface::SPI => "SPI", + Interface::USB => "USB", + } + } +} + +impl From<u32> for Interface { + fn from(n: u32) -> Self { + match n { + ffi::DRM_MODE_CONNECTOR_Unknown => Interface::Unknown, + ffi::DRM_MODE_CONNECTOR_VGA => Interface::VGA, + ffi::DRM_MODE_CONNECTOR_DVII => Interface::DVII, + ffi::DRM_MODE_CONNECTOR_DVID => Interface::DVID, + ffi::DRM_MODE_CONNECTOR_DVIA => Interface::DVIA, + ffi::DRM_MODE_CONNECTOR_Composite => Interface::Composite, + ffi::DRM_MODE_CONNECTOR_SVIDEO => Interface::SVideo, + ffi::DRM_MODE_CONNECTOR_LVDS => Interface::LVDS, + ffi::DRM_MODE_CONNECTOR_Component => Interface::Component, + ffi::DRM_MODE_CONNECTOR_9PinDIN => Interface::NinePinDIN, + ffi::DRM_MODE_CONNECTOR_DisplayPort => Interface::DisplayPort, + ffi::DRM_MODE_CONNECTOR_HDMIA => Interface::HDMIA, + ffi::DRM_MODE_CONNECTOR_HDMIB => Interface::HDMIB, + ffi::DRM_MODE_CONNECTOR_TV => Interface::TV, + ffi::DRM_MODE_CONNECTOR_eDP => Interface::EmbeddedDisplayPort, + ffi::DRM_MODE_CONNECTOR_VIRTUAL => Interface::Virtual, + ffi::DRM_MODE_CONNECTOR_DSI => Interface::DSI, + ffi::DRM_MODE_CONNECTOR_DPI => Interface::DPI, + ffi::DRM_MODE_CONNECTOR_WRITEBACK => Interface::Writeback, + ffi::DRM_MODE_CONNECTOR_SPI => Interface::SPI, + ffi::DRM_MODE_CONNECTOR_USB => Interface::USB, + _ => Interface::Unknown, + } + } +} + +impl From<Interface> for u32 { + fn from(interface: Interface) -> Self { + match interface { + Interface::Unknown => ffi::DRM_MODE_CONNECTOR_Unknown, + Interface::VGA => ffi::DRM_MODE_CONNECTOR_VGA, + Interface::DVII => ffi::DRM_MODE_CONNECTOR_DVII, + Interface::DVID => ffi::DRM_MODE_CONNECTOR_DVID, + Interface::DVIA => ffi::DRM_MODE_CONNECTOR_DVIA, + Interface::Composite => ffi::DRM_MODE_CONNECTOR_Composite, + Interface::SVideo => ffi::DRM_MODE_CONNECTOR_SVIDEO, + Interface::LVDS => ffi::DRM_MODE_CONNECTOR_LVDS, + Interface::Component => ffi::DRM_MODE_CONNECTOR_Component, + Interface::NinePinDIN => ffi::DRM_MODE_CONNECTOR_9PinDIN, + Interface::DisplayPort => ffi::DRM_MODE_CONNECTOR_DisplayPort, + Interface::HDMIA => ffi::DRM_MODE_CONNECTOR_HDMIA, + Interface::HDMIB => ffi::DRM_MODE_CONNECTOR_HDMIB, + Interface::TV => ffi::DRM_MODE_CONNECTOR_TV, + Interface::EmbeddedDisplayPort => ffi::DRM_MODE_CONNECTOR_eDP, + Interface::Virtual => ffi::DRM_MODE_CONNECTOR_VIRTUAL, + Interface::DSI => ffi::DRM_MODE_CONNECTOR_DSI, + Interface::DPI => ffi::DRM_MODE_CONNECTOR_DPI, + Interface::Writeback => ffi::DRM_MODE_CONNECTOR_WRITEBACK, + Interface::SPI => ffi::DRM_MODE_CONNECTOR_SPI, + Interface::USB => ffi::DRM_MODE_CONNECTOR_USB, + } + } +} + +/// The state of a connector. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum State { + Connected, + Disconnected, + Unknown, +} + +impl From<u32> for State { + fn from(n: u32) -> Self { + // These variables are not defined in drm_mode.h for some reason. + // They were copied from libdrm's xf86DrmMode.h + match n { + 1 => State::Connected, + 2 => State::Disconnected, + _ => State::Unknown, + } + } +} + +impl From<State> for u32 { + fn from(state: State) -> Self { + // These variables are not defined in drm_mode.h for some reason. + // They were copied from libdrm's xf86DrmMode.h + match state { + State::Connected => 1, + State::Disconnected => 2, + State::Unknown => 3, + } + } +} + +/// Subpixel order of the connected sink +#[non_exhaustive] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum SubPixel { + /// Unknown geometry + Unknown, + /// Horizontal RGB + HorizontalRgb, + /// Horizontal BGR + HorizontalBgr, + /// Vertical RGB + VerticalRgb, + /// Vertical BGR + VerticalBgr, + /// No geometry + None, + /// Encountered value not supported by drm-rs + NotImplemented, +} + +impl SubPixel { + pub(super) fn from_raw(n: u32) -> Self { + // These values are not defined in drm_mode.h + // They were copied from linux's drm_connector.h + // Don't mistake those for ones used in xf86DrmMode.h (libdrm offsets those by 1) + match n { + 0 => Self::Unknown, + 1 => Self::HorizontalRgb, + 2 => Self::HorizontalBgr, + 3 => Self::VerticalRgb, + 4 => Self::VerticalBgr, + 5 => Self::None, + _ => Self::NotImplemented, + } + } +} diff --git a/src/control/crtc.rs b/src/control/crtc.rs new file mode 100644 index 0000000..0827043 --- /dev/null +++ b/src/control/crtc.rs @@ -0,0 +1,90 @@ +//! # CRTC +//! +//! A CRTC is a display controller provided by your device. It's primary job is +//! to take pixel data and send it to a connector with the proper resolution and +//! frequencies. +//! +//! Specific CRTCs can only be attached to connectors that have an encoder it +//! supports. For example, you can have a CRTC that can not output to analog +//! connectors. These are built in hardware limitations. +//! +//! Each CRTC has a built in plane, which can have a framebuffer attached to it, +//! but they can also use pixel data from other planes to perform hardware +//! compositing. + +use crate::control; +use drm_ffi as ffi; + +/// A handle to a specific CRTC +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl control::ResourceHandle for Handle { + const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_CRTC; +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("crtc::Handle").field(&self.0).finish() + } +} + +/// Information about a specific CRTC +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct Info { + pub(crate) handle: Handle, + pub(crate) position: (u32, u32), + pub(crate) mode: Option<control::Mode>, + pub(crate) fb: Option<control::framebuffer::Handle>, + pub(crate) gamma_length: u32, +} + +impl Info { + /// Returns the handle to this CRTC. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the position of the CRTC. + pub fn position(&self) -> (u32, u32) { + self.position + } + + /// Returns the current mode of the CRTC. + pub fn mode(&self) -> Option<control::Mode> { + self.mode + } + + /// Returns the framebuffer currently attached to this CRTC. + pub fn framebuffer(&self) -> Option<control::framebuffer::Handle> { + self.fb + } + + /// Returns the size of the gamma LUT. + pub fn gamma_length(&self) -> u32 { + self.gamma_length + } +} diff --git a/src/control/dumbbuffer.rs b/src/control/dumbbuffer.rs new file mode 100644 index 0000000..d4a7ada --- /dev/null +++ b/src/control/dumbbuffer.rs @@ -0,0 +1,88 @@ +//! +//! # DumbBuffer +//! +//! Memory-supported, slow, but easy & cross-platform buffer implementation +//! + +use crate::buffer; + +use std::borrow::{Borrow, BorrowMut}; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// Slow, but generic [`buffer::Buffer`] implementation +pub struct DumbBuffer { + pub(crate) size: (u32, u32), + pub(crate) length: usize, + pub(crate) format: buffer::DrmFourcc, + pub(crate) pitch: u32, + pub(crate) handle: buffer::Handle, +} + +/// Mapping of a [`DumbBuffer`] +pub struct DumbMapping<'a> { + pub(crate) _phantom: core::marker::PhantomData<&'a ()>, + pub(crate) map: &'a mut [u8], +} + +impl AsRef<[u8]> for DumbMapping<'_> { + fn as_ref(&self) -> &[u8] { + self.map + } +} + +impl AsMut<[u8]> for DumbMapping<'_> { + fn as_mut(&mut self) -> &mut [u8] { + self.map + } +} + +impl Borrow<[u8]> for DumbMapping<'_> { + fn borrow(&self) -> &[u8] { + self.map + } +} + +impl BorrowMut<[u8]> for DumbMapping<'_> { + fn borrow_mut(&mut self) -> &mut [u8] { + self.map + } +} + +impl Deref for DumbMapping<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.map + } +} + +impl DerefMut for DumbMapping<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.map + } +} + +impl<'a> Drop for DumbMapping<'a> { + fn drop(&mut self) { + unsafe { + rustix::mm::munmap(self.map.as_mut_ptr() as *mut _, self.map.len()) + .expect("Unmap failed"); + } + } +} + +impl buffer::Buffer for DumbBuffer { + fn size(&self) -> (u32, u32) { + self.size + } + fn format(&self) -> buffer::DrmFourcc { + self.format + } + fn pitch(&self) -> u32 { + self.pitch + } + fn handle(&self) -> buffer::Handle { + self.handle + } +} diff --git a/src/control/encoder.rs b/src/control/encoder.rs new file mode 100644 index 0000000..fd194b1 --- /dev/null +++ b/src/control/encoder.rs @@ -0,0 +1,133 @@ +//! # Encoder +//! +//! An encoder is a bridge between a CRTC and a connector that takes the pixel +//! data of the CRTC and encodes it into a format the connector understands. + +use crate::control; +use drm_ffi as ffi; + +/// A handle to an encoder +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl control::ResourceHandle for Handle { + const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_ENCODER; +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("encoder::Handle").field(&self.0).finish() + } +} + +/// Information about an encoder +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct Info { + pub(crate) handle: Handle, + pub(crate) enc_type: Kind, + pub(crate) crtc: Option<control::crtc::Handle>, + pub(crate) pos_crtcs: u32, + pub(crate) pos_clones: u32, +} + +impl Info { + /// Returns the handle to this encoder. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the `Kind` of encoder this is. + pub fn kind(&self) -> Kind { + self.enc_type + } + + /// Returns a handle to the CRTC this encoder is attached to. + pub fn crtc(&self) -> Option<control::crtc::Handle> { + self.crtc + } + + /// Returns a filter for the possible CRTCs that can use this encoder. + /// + /// Use with [`control::ResourceHandles::filter_crtcs`] + /// to receive a list of crtcs. + pub fn possible_crtcs(&self) -> control::CrtcListFilter { + control::CrtcListFilter(self.pos_crtcs) + } + + /// Returns a filter for the possible encoders that clones this one. + pub fn possible_clones(&self) { + unimplemented!() + } +} + +/// The type of encoder. +#[allow(missing_docs)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum Kind { + None, + DAC, + TMDS, + LVDS, + TVDAC, + Virtual, + DSI, + DPMST, + DPI, +} + +impl From<u32> for Kind { + fn from(n: u32) -> Self { + match n { + ffi::DRM_MODE_ENCODER_NONE => Kind::None, + ffi::DRM_MODE_ENCODER_DAC => Kind::DAC, + ffi::DRM_MODE_ENCODER_TMDS => Kind::TMDS, + ffi::DRM_MODE_ENCODER_LVDS => Kind::LVDS, + ffi::DRM_MODE_ENCODER_TVDAC => Kind::TVDAC, + ffi::DRM_MODE_ENCODER_VIRTUAL => Kind::Virtual, + ffi::DRM_MODE_ENCODER_DSI => Kind::DSI, + ffi::DRM_MODE_ENCODER_DPMST => Kind::DPMST, + ffi::DRM_MODE_ENCODER_DPI => Kind::DPI, + _ => Kind::None, + } + } +} + +impl From<Kind> for u32 { + fn from(kind: Kind) -> Self { + match kind { + Kind::None => ffi::DRM_MODE_ENCODER_NONE, + Kind::DAC => ffi::DRM_MODE_ENCODER_DAC, + Kind::TMDS => ffi::DRM_MODE_ENCODER_TMDS, + Kind::LVDS => ffi::DRM_MODE_ENCODER_LVDS, + Kind::TVDAC => ffi::DRM_MODE_ENCODER_TVDAC, + Kind::Virtual => ffi::DRM_MODE_ENCODER_VIRTUAL, + Kind::DSI => ffi::DRM_MODE_ENCODER_DSI, + Kind::DPMST => ffi::DRM_MODE_ENCODER_DPMST, + Kind::DPI => ffi::DRM_MODE_ENCODER_DPI, + } + } +} diff --git a/src/control/framebuffer.rs b/src/control/framebuffer.rs new file mode 100644 index 0000000..1e80ceb --- /dev/null +++ b/src/control/framebuffer.rs @@ -0,0 +1,143 @@ +//! # Framebuffer +//! +//! Process specific GPU buffers that can be attached to a plane. + +use crate::buffer; +use crate::control; +use drm_ffi as ffi; +use drm_fourcc::{DrmFourcc, DrmModifier}; + +/// A handle to a framebuffer +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl control::ResourceHandle for Handle { + const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_FB; +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("framebuffer::Handle").field(&self.0).finish() + } +} + +/// Information about a framebuffer +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct Info { + pub(crate) handle: Handle, + pub(crate) size: (u32, u32), + pub(crate) pitch: u32, + pub(crate) bpp: u32, + pub(crate) depth: u32, + pub(crate) buffer: Option<buffer::Handle>, +} + +impl Info { + /// Returns the handle to this framebuffer. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the size of this framebuffer. + pub fn size(&self) -> (u32, u32) { + self.size + } + + /// Returns the pitch of this framebuffer. + pub fn pitch(&self) -> u32 { + self.pitch + } + + /// Returns the bits-per-pixel of this framebuffer. + pub fn bpp(&self) -> u32 { + self.bpp + } + + /// Returns the depth of this framebuffer. + pub fn depth(&self) -> u32 { + self.depth + } + + /// Returns the buffer handle of this framebuffer. + pub fn buffer(&self) -> Option<buffer::Handle> { + self.buffer + } +} + +/// Information about a framebuffer (with modifiers) +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct PlanarInfo { + pub(crate) handle: Handle, + pub(crate) size: (u32, u32), + pub(crate) pixel_format: DrmFourcc, + pub(crate) flags: control::FbCmd2Flags, + pub(crate) buffers: [Option<buffer::Handle>; 4], + pub(crate) pitches: [u32; 4], + pub(crate) offsets: [u32; 4], + pub(crate) modifier: Option<DrmModifier>, +} + +impl PlanarInfo { + /// Returns the handle to this framebuffer. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the size of this framebuffer. + pub fn size(&self) -> (u32, u32) { + self.size + } + + /// Returns the pixel format of this framebuffer. + pub fn pixel_format(&self) -> DrmFourcc { + self.pixel_format + } + + /// Returns the flags of this framebuffer. + pub fn flags(&self) -> control::FbCmd2Flags { + self.flags + } + + /// Returns the buffer handles of this framebuffer. + pub fn buffers(&self) -> [Option<buffer::Handle>; 4] { + self.buffers + } + + /// Returns the pitches of this framebuffer. + pub fn pitches(&self) -> [u32; 4] { + self.pitches + } + + /// Returns the offsets of this framebuffer. + pub fn offsets(&self) -> [u32; 4] { + self.offsets + } + + /// Returns the modifier of this framebuffer. + pub fn modifier(&self) -> Option<DrmModifier> { + self.modifier + } +} diff --git a/src/control/mod.rs b/src/control/mod.rs new file mode 100644 index 0000000..2b6f946 --- /dev/null +++ b/src/control/mod.rs @@ -0,0 +1,1567 @@ +//! Modesetting operations that the DRM subsystem exposes. +//! +//! # Summary +//! +//! The DRM subsystem provides Kernel Modesetting (KMS) functionality by +//! exposing the following resource types: +//! +//! * FrameBuffer - Specific to an individual process, these wrap around generic +//! GPU buffers so that they can be attached to a Plane. +//! +//! * Planes - Dedicated memory objects which contain a buffer that can then be +//! scanned out by a CRTC. There exist a few different types of planes depending +//! on the use case. +//! +//! * CRTC - Scanout engines that read pixel data from a Plane and sends it to +//! a Connector. Each CRTC has at least one Primary Plane. +//! +//! * Connector - Represents the physical output, such as a DisplayPort or +//! VGA connector. +//! +//! * Encoder - Encodes pixel data from a CRTC into something a Connector can +//! understand. +//! +//! Further details on each resource can be found in their respective modules. +//! +//! # Usage +//! +//! To begin using modesetting functionality, the [`Device`] trait +//! must be implemented on top of the basic [`super::Device`] trait. + +use drm_ffi as ffi; +use drm_fourcc::{DrmFourcc, DrmModifier, UnrecognizedFourcc}; + +use bytemuck::allocation::TransparentWrapperAlloc; +use rustix::io::Errno; + +pub mod atomic; +pub mod connector; +pub mod crtc; +pub mod dumbbuffer; +pub mod encoder; +pub mod framebuffer; +pub mod plane; +pub mod syncobj; + +pub mod property; + +use self::dumbbuffer::*; +use crate::buffer; + +use super::util::*; + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::error; +use std::fmt; +use std::io; +use std::iter::Zip; +use std::mem; +use std::ops::RangeBounds; +use std::os::unix::io::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; +use std::time::Duration; + +use core::num::NonZeroU32; + +/// Raw handle for a drm resource +pub type RawResourceHandle = NonZeroU32; + +/// Id of a Lease +pub type LeaseId = NonZeroU32; + +/// Handle for a drm resource +pub trait ResourceHandle: + From<RawResourceHandle> + Into<RawResourceHandle> + Into<u32> + Copy + Sized +{ + /// Associated encoded object type + const FFI_TYPE: u32; +} + +/// Convert from a raw drm object value to a typed Handle +/// +/// Note: This does no verification on the validity of the original value +pub fn from_u32<T: From<RawResourceHandle>>(raw: u32) -> Option<T> { + RawResourceHandle::new(raw).map(T::from) +} + +/// Error from [`Device::get_planar_framebuffer`] +#[derive(Debug)] +pub enum GetPlanarFramebufferError { + /// IO error + Io(io::Error), + /// Unrecognized fourcc format + UnrecognizedFourcc(drm_fourcc::UnrecognizedFourcc), +} + +impl fmt::Display for GetPlanarFramebufferError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Io(err) => write!(f, "{}", err), + Self::UnrecognizedFourcc(err) => write!(f, "{}", err), + } + } +} + +impl error::Error for GetPlanarFramebufferError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::Io(err) => Some(err), + Self::UnrecognizedFourcc(err) => Some(err), + } + } +} + +impl From<io::Error> for GetPlanarFramebufferError { + fn from(err: io::Error) -> Self { + Self::Io(err) + } +} + +impl From<UnrecognizedFourcc> for GetPlanarFramebufferError { + fn from(err: UnrecognizedFourcc) -> Self { + Self::UnrecognizedFourcc(err) + } +} + +/// This trait should be implemented by any object that acts as a DRM device and +/// provides modesetting functionality. +/// +/// Like the parent [`super::Device`] trait, this crate does not +/// provide a concrete object for this trait. +/// +/// # Example +/// ```ignore +/// use drm::control::Device as ControlDevice; +/// +/// /// Assuming the [`Card`] wrapper already implements [`drm::Device`] +/// impl ControlDevice for Card {} +/// ``` +pub trait Device: super::Device { + /// Gets the set of resource handles that this device currently controls + fn resource_handles(&self) -> io::Result<ResourceHandles> { + let mut fbs = Vec::new(); + let mut crtcs = Vec::new(); + let mut connectors = Vec::new(); + let mut encoders = Vec::new(); + + let ffi_res = ffi::mode::get_resources( + self.as_fd(), + Some(&mut fbs), + Some(&mut crtcs), + Some(&mut connectors), + Some(&mut encoders), + )?; + + let res = unsafe { + ResourceHandles { + fbs: transmute_vec_from_u32(fbs), + crtcs: transmute_vec_from_u32(crtcs), + connectors: transmute_vec_from_u32(connectors), + encoders: transmute_vec_from_u32(encoders), + width: (ffi_res.min_width, ffi_res.max_width), + height: (ffi_res.min_height, ffi_res.max_height), + } + }; + + Ok(res) + } + + /// Gets the set of plane handles that this device currently has + fn plane_handles(&self) -> io::Result<Vec<plane::Handle>> { + let mut planes = Vec::new(); + let _ = ffi::mode::get_plane_resources(self.as_fd(), Some(&mut planes))?; + Ok(unsafe { transmute_vec_from_u32(planes) }) + } + + /// Returns information about a specific connector + /// + /// ## Force-probing + /// + /// If `force_probe` is set to `true` and the DRM client is the current DRM master, + /// the kernel will perform a forced probe on the connector to refresh the connector status, modes and EDID. + /// A forced-probe can be slow, might cause flickering and the ioctl will block. + /// + /// - User needs to force-probe connectors to ensure their metadata is up-to-date at startup and after receiving a hot-plug event. + /// - User may perform a forced-probe when the user explicitly requests it. + /// - User shouldn’t perform a forced-probe in other situations. + fn get_connector( + &self, + handle: connector::Handle, + force_probe: bool, + ) -> io::Result<connector::Info> { + // Maximum number of encoders is 3 due to kernel restrictions + let mut encoders = Vec::new(); + let mut modes = Vec::new(); + + let ffi_info = ffi::mode::get_connector( + self.as_fd(), + handle.into(), + None, + None, + Some(&mut modes), + Some(&mut encoders), + force_probe, + )?; + + let connector = connector::Info { + handle, + interface: connector::Interface::from(ffi_info.connector_type), + interface_id: ffi_info.connector_type_id, + connection: connector::State::from(ffi_info.connection), + size: match (ffi_info.mm_width, ffi_info.mm_height) { + (0, 0) => None, + (x, y) => Some((x, y)), + }, + modes: Mode::wrap_vec(modes), + encoders: unsafe { transmute_vec_from_u32(encoders) }, + curr_enc: unsafe { mem::transmute(ffi_info.encoder_id) }, + subpixel: connector::SubPixel::from_raw(ffi_info.subpixel), + }; + + Ok(connector) + } + + /// Returns information about a specific encoder + fn get_encoder(&self, handle: encoder::Handle) -> io::Result<encoder::Info> { + let info = ffi::mode::get_encoder(self.as_fd(), handle.into())?; + + let enc = encoder::Info { + handle, + enc_type: encoder::Kind::from(info.encoder_type), + crtc: from_u32(info.crtc_id), + pos_crtcs: info.possible_crtcs, + pos_clones: info.possible_clones, + }; + + Ok(enc) + } + + /// Returns information about a specific CRTC + fn get_crtc(&self, handle: crtc::Handle) -> io::Result<crtc::Info> { + let info = ffi::mode::get_crtc(self.as_fd(), handle.into())?; + + let crtc = crtc::Info { + handle, + position: (info.x, info.y), + mode: match info.mode_valid { + 0 => None, + _ => Some(Mode::from(info.mode)), + }, + fb: from_u32(info.fb_id), + gamma_length: info.gamma_size, + }; + + Ok(crtc) + } + + /// Set CRTC state + fn set_crtc( + &self, + handle: crtc::Handle, + framebuffer: Option<framebuffer::Handle>, + pos: (u32, u32), + conns: &[connector::Handle], + mode: Option<Mode>, + ) -> io::Result<()> { + let _info = ffi::mode::set_crtc( + self.as_fd(), + handle.into(), + framebuffer.map(Into::into).unwrap_or(0), + pos.0, + pos.1, + unsafe { &*(conns as *const _ as *const [u32]) }, + mode.map(|m| m.into()), + )?; + + Ok(()) + } + + /// Returns information about a specific framebuffer + fn get_framebuffer(&self, handle: framebuffer::Handle) -> io::Result<framebuffer::Info> { + let info = ffi::mode::get_framebuffer(self.as_fd(), handle.into())?; + + let fb = framebuffer::Info { + handle, + size: (info.width, info.height), + pitch: info.pitch, + bpp: info.bpp, + depth: info.depth, + buffer: from_u32(info.handle), + }; + + Ok(fb) + } + + /// Returns information about a specific framebuffer (with modifiers) + fn get_planar_framebuffer( + &self, + handle: framebuffer::Handle, + ) -> Result<framebuffer::PlanarInfo, GetPlanarFramebufferError> { + let info = ffi::mode::get_framebuffer2(self.as_fd(), handle.into())?; + + let pixel_format = DrmFourcc::try_from(info.pixel_format)?; + + let flags = FbCmd2Flags::from_bits_truncate(info.flags); + let modifier = flags + .contains(FbCmd2Flags::MODIFIERS) + .then(|| DrmModifier::from(info.modifier[0])); + + let fb = framebuffer::PlanarInfo { + handle, + size: (info.width, info.height), + pixel_format, + flags, + buffers: bytemuck::cast(info.handles), + pitches: info.pitches, + offsets: info.offsets, + modifier, + }; + + Ok(fb) + } + + /// Add a new framebuffer + fn add_framebuffer<B>( + &self, + buffer: &B, + depth: u32, + bpp: u32, + ) -> io::Result<framebuffer::Handle> + where + B: buffer::Buffer + ?Sized, + { + let (w, h) = buffer.size(); + let info = ffi::mode::add_fb( + self.as_fd(), + w, + h, + buffer.pitch(), + bpp, + depth, + buffer.handle().into(), + )?; + + Ok(from_u32(info.fb_id).unwrap()) + } + + /// Add framebuffer (with modifiers) + fn add_planar_framebuffer<B>( + &self, + planar_buffer: &B, + flags: FbCmd2Flags, + ) -> io::Result<framebuffer::Handle> + where + B: buffer::PlanarBuffer + ?Sized, + { + let modifier = planar_buffer.modifier(); + let has_modifier = flags.contains(FbCmd2Flags::MODIFIERS); + assert!((has_modifier && modifier.is_some()) || (!has_modifier && modifier.is_none())); + let modifier = if let Some(modifier) = modifier { + u64::from(modifier) + } else { + 0 + }; + + let (w, h) = planar_buffer.size(); + let opt_handles = planar_buffer.handles(); + + let handles = bytemuck::cast(opt_handles); + let mods = [ + opt_handles[0].map_or(0, |_| modifier), + opt_handles[1].map_or(0, |_| modifier), + opt_handles[2].map_or(0, |_| modifier), + opt_handles[3].map_or(0, |_| modifier), + ]; + + let info = ffi::mode::add_fb2( + self.as_fd(), + w, + h, + planar_buffer.format() as u32, + &handles, + &planar_buffer.pitches(), + &planar_buffer.offsets(), + &mods, + flags.bits(), + )?; + + Ok(from_u32(info.fb_id).unwrap()) + } + + /// Mark parts of a framebuffer dirty + fn dirty_framebuffer(&self, handle: framebuffer::Handle, clips: &[ClipRect]) -> io::Result<()> { + ffi::mode::dirty_fb(self.as_fd(), handle.into(), unsafe { + // SAFETY: ClipRect is repr(transparent) for drm_clip_rect + core::slice::from_raw_parts(clips.as_ptr() as *const ffi::drm_clip_rect, clips.len()) + })?; + Ok(()) + } + + /// Destroy a framebuffer + fn destroy_framebuffer(&self, handle: framebuffer::Handle) -> io::Result<()> { + ffi::mode::rm_fb(self.as_fd(), handle.into()) + } + + /// Returns information about a specific plane + fn get_plane(&self, handle: plane::Handle) -> io::Result<plane::Info> { + let mut formats = Vec::new(); + + let info = ffi::mode::get_plane(self.as_fd(), handle.into(), Some(&mut formats))?; + + let plane = plane::Info { + handle, + crtc: from_u32(info.crtc_id), + fb: from_u32(info.fb_id), + pos_crtcs: info.possible_crtcs, + formats: unsafe { transmute_vec_from_u32(formats) }, + }; + + Ok(plane) + } + + /// Set plane state. + /// + /// Providing no framebuffer clears the plane. + fn set_plane( + &self, + handle: plane::Handle, + crtc: crtc::Handle, + framebuffer: Option<framebuffer::Handle>, + flags: u32, + crtc_rect: (i32, i32, u32, u32), + src_rect: (u32, u32, u32, u32), + ) -> io::Result<()> { + let _info = ffi::mode::set_plane( + self.as_fd(), + handle.into(), + crtc.into(), + framebuffer.map(Into::into).unwrap_or(0), + flags, + crtc_rect.0, + crtc_rect.1, + crtc_rect.2, + crtc_rect.3, + src_rect.0, + src_rect.1, + src_rect.2, + src_rect.3, + )?; + + Ok(()) + } + + /// Returns information about a specific property. + fn get_property(&self, handle: property::Handle) -> io::Result<property::Info> { + let mut values = Vec::new(); + let mut enums = Vec::new(); + + let info = ffi::mode::get_property( + self.as_fd(), + handle.into(), + Some(&mut values), + Some(&mut enums), + )?; + + let flags = ModePropFlags::from_bits_truncate(info.flags); + + let val_type = { + use self::property::ValueType; + + if flags.contains(ModePropFlags::RANGE) { + let min = values[0]; + let max = values[1]; + + match (min, max) { + (0, 1) => ValueType::Boolean, + (min, max) => ValueType::UnsignedRange(min, max), + } + } else if flags.contains(ModePropFlags::SIGNED_RANGE) { + let min = values[0]; + let max = values[1]; + + ValueType::SignedRange(min as i64, max as i64) + } else if flags.contains(ModePropFlags::ENUM) { + let enum_values = self::property::EnumValues { + values, + enums: property::EnumValue::wrap_vec(enums), + }; + + ValueType::Enum(enum_values) + } else if flags.contains(ModePropFlags::BLOB) { + ValueType::Blob + } else if flags.contains(ModePropFlags::BITMASK) { + ValueType::Bitmask + } else if flags.contains(ModePropFlags::OBJECT) { + match values[0] as u32 { + ffi::DRM_MODE_OBJECT_CRTC => ValueType::CRTC, + ffi::DRM_MODE_OBJECT_CONNECTOR => ValueType::Connector, + ffi::DRM_MODE_OBJECT_ENCODER => ValueType::Encoder, + ffi::DRM_MODE_OBJECT_FB => ValueType::Framebuffer, + ffi::DRM_MODE_OBJECT_PLANE => ValueType::Plane, + ffi::DRM_MODE_OBJECT_PROPERTY => ValueType::Property, + ffi::DRM_MODE_OBJECT_BLOB => ValueType::Blob, + ffi::DRM_MODE_OBJECT_ANY => ValueType::Object, + _ => ValueType::Unknown, + } + } else { + ValueType::Unknown + } + }; + + let property = property::Info { + handle, + val_type, + mutable: !flags.contains(ModePropFlags::IMMUTABLE), + atomic: flags.contains(ModePropFlags::ATOMIC), + info, + }; + + Ok(property) + } + + /// Sets a property for a specific resource. + fn set_property<T: ResourceHandle>( + &self, + handle: T, + prop: property::Handle, + value: property::RawValue, + ) -> io::Result<()> { + ffi::mode::set_property(self.as_fd(), prop.into(), handle.into(), T::FFI_TYPE, value)?; + + Ok(()) + } + + /// Create a property blob value from a given data blob + fn create_property_blob<T>(&self, data: &T) -> io::Result<property::Value<'static>> { + let data = unsafe { + std::slice::from_raw_parts_mut(data as *const _ as *mut u8, mem::size_of::<T>()) + }; + let blob = ffi::mode::create_property_blob(self.as_fd(), data)?; + + Ok(property::Value::Blob(blob.blob_id.into())) + } + + /// Get a property blob's data + fn get_property_blob(&self, blob: u64) -> io::Result<Vec<u8>> { + let mut data = Vec::new(); + let _ = ffi::mode::get_property_blob(self.as_fd(), blob as u32, Some(&mut data))?; + Ok(data) + } + + /// Destroy a given property blob value + fn destroy_property_blob(&self, blob: u64) -> io::Result<()> { + ffi::mode::destroy_property_blob(self.as_fd(), blob as u32)?; + + Ok(()) + } + + /// Returns the set of [`Mode`]s that a particular connector supports. + fn get_modes(&self, handle: connector::Handle) -> io::Result<Vec<Mode>> { + let mut modes = Vec::new(); + + let _ffi_info = ffi::mode::get_connector( + self.as_fd(), + handle.into(), + None, + None, + Some(&mut modes), + None, + false, + )?; + + Ok(Mode::wrap_vec(modes)) + } + + /// Gets a list of property handles and values for this resource. + fn get_properties<T: ResourceHandle>(&self, handle: T) -> io::Result<PropertyValueSet> { + let mut prop_ids = Vec::new(); + let mut prop_vals = Vec::new(); + + ffi::mode::get_properties( + self.as_fd(), + handle.into(), + T::FFI_TYPE, + Some(&mut prop_ids), + Some(&mut prop_vals), + )?; + + let prop_val_set = PropertyValueSet { + prop_ids: unsafe { transmute_vec_from_u32(prop_ids) }, + prop_vals, + }; + + Ok(prop_val_set) + } + + /// Receive the currently set gamma ramp of a crtc + fn get_gamma( + &self, + crtc: crtc::Handle, + red: &mut [u16], + green: &mut [u16], + blue: &mut [u16], + ) -> io::Result<()> { + let crtc_info = self.get_crtc(crtc)?; + if crtc_info.gamma_length as usize > red.len() + || crtc_info.gamma_length as usize > green.len() + || crtc_info.gamma_length as usize > blue.len() + { + return Err(Errno::INVAL.into()); + } + + ffi::mode::get_gamma( + self.as_fd(), + crtc.into(), + crtc_info.gamma_length as usize, + red, + green, + blue, + )?; + + Ok(()) + } + + /// Set a gamma ramp for the given crtc + fn set_gamma( + &self, + crtc: crtc::Handle, + red: &[u16], + green: &[u16], + blue: &[u16], + ) -> io::Result<()> { + let crtc_info = self.get_crtc(crtc)?; + if crtc_info.gamma_length as usize > red.len() + || crtc_info.gamma_length as usize > green.len() + || crtc_info.gamma_length as usize > blue.len() + { + return Err(Errno::INVAL.into()); + } + + ffi::mode::set_gamma( + self.as_fd(), + crtc.into(), + crtc_info.gamma_length as usize, + red, + green, + blue, + )?; + + Ok(()) + } + + /// Open a GEM buffer handle by name + fn open_buffer(&self, name: buffer::Name) -> io::Result<buffer::Handle> { + let info = drm_ffi::gem::open(self.as_fd(), name.into())?; + Ok(from_u32(info.handle).unwrap()) + } + + /// Close a GEM buffer handle + fn close_buffer(&self, handle: buffer::Handle) -> io::Result<()> { + let _info = drm_ffi::gem::close(self.as_fd(), handle.into())?; + Ok(()) + } + + /// Create a new dumb buffer with a given size and pixel format + fn create_dumb_buffer( + &self, + size: (u32, u32), + format: buffer::DrmFourcc, + bpp: u32, + ) -> io::Result<DumbBuffer> { + let info = drm_ffi::mode::dumbbuffer::create(self.as_fd(), size.0, size.1, bpp, 0)?; + + let dumb = DumbBuffer { + size: (info.width, info.height), + length: info.size as usize, + format, + pitch: info.pitch, + handle: from_u32(info.handle).unwrap(), + }; + + Ok(dumb) + } + /// Map the buffer for access + fn map_dumb_buffer<'a>(&self, buffer: &'a mut DumbBuffer) -> io::Result<DumbMapping<'a>> { + let info = drm_ffi::mode::dumbbuffer::map(self.as_fd(), buffer.handle.into(), 0, 0)?; + + let map = { + use rustix::mm; + let prot = mm::ProtFlags::READ | mm::ProtFlags::WRITE; + let flags = mm::MapFlags::SHARED; + let fd = self.as_fd(); + let offset = info.offset as _; + unsafe { mm::mmap(std::ptr::null_mut(), buffer.length, prot, flags, fd, offset)? } + }; + + let mapping = DumbMapping { + _phantom: std::marker::PhantomData, + map: unsafe { std::slice::from_raw_parts_mut(map as *mut _, buffer.length) }, + }; + + Ok(mapping) + } + + /// Free the memory resources of a dumb buffer + fn destroy_dumb_buffer(&self, buffer: DumbBuffer) -> io::Result<()> { + let _info = drm_ffi::mode::dumbbuffer::destroy(self.as_fd(), buffer.handle.into())?; + + Ok(()) + } + + /// Sets a hardware-cursor on the given crtc with the image of a given buffer + /// + /// A buffer argument of [`None`] will clear the cursor. + #[deprecated(note = "Usage of deprecated ioctl set_cursor: use a cursor plane instead")] + #[allow(deprecated)] + fn set_cursor<B>(&self, crtc: crtc::Handle, buffer: Option<&B>) -> io::Result<()> + where + B: buffer::Buffer + ?Sized, + { + let (id, w, h) = buffer + .map(|buf| { + let (w, h) = buf.size(); + (buf.handle().into(), w, h) + }) + .unwrap_or((0, 0, 0)); + drm_ffi::mode::set_cursor(self.as_fd(), crtc.into(), id, w, h)?; + + Ok(()) + } + + /// Sets a hardware-cursor on the given crtc with the image of a given buffer + /// and a hotspot marking the click point of the cursor. + /// + /// A buffer argument of [`None`] will clear the cursor. + #[deprecated(note = "Usage of deprecated ioctl set_cursor2: use a cursor plane instead")] + #[allow(deprecated)] + fn set_cursor2<B>( + &self, + crtc: crtc::Handle, + buffer: Option<&B>, + hotspot: (i32, i32), + ) -> io::Result<()> + where + B: buffer::Buffer + ?Sized, + { + let (id, w, h) = buffer + .map(|buf| { + let (w, h) = buf.size(); + (buf.handle().into(), w, h) + }) + .unwrap_or((0, 0, 0)); + drm_ffi::mode::set_cursor2(self.as_fd(), crtc.into(), id, w, h, hotspot.0, hotspot.1)?; + + Ok(()) + } + + /// Moves a set cursor on a given crtc + #[deprecated(note = "Usage of deprecated ioctl move_cursor: use a cursor plane instead")] + #[allow(deprecated)] + fn move_cursor(&self, crtc: crtc::Handle, pos: (i32, i32)) -> io::Result<()> { + drm_ffi::mode::move_cursor(self.as_fd(), crtc.into(), pos.0, pos.1)?; + + Ok(()) + } + + /// Request an atomic commit with given flags and property-value pair for a list of objects. + fn atomic_commit( + &self, + flags: AtomicCommitFlags, + mut req: atomic::AtomicModeReq, + ) -> io::Result<()> { + drm_ffi::mode::atomic_commit( + self.as_fd(), + flags.bits(), + unsafe { &mut *(&mut *req.objects as *mut _ as *mut [u32]) }, + &mut req.count_props_per_object, + unsafe { &mut *(&mut *req.props as *mut _ as *mut [u32]) }, + &mut req.values, + ) + } + + /// Convert a prime file descriptor to a GEM buffer handle + fn prime_fd_to_buffer(&self, fd: BorrowedFd<'_>) -> io::Result<buffer::Handle> { + let info = ffi::gem::fd_to_handle(self.as_fd(), fd)?; + Ok(from_u32(info.handle).unwrap()) + } + + /// Convert a GEM buffer handle to a prime file descriptor + fn buffer_to_prime_fd(&self, handle: buffer::Handle, flags: u32) -> io::Result<OwnedFd> { + let info = ffi::gem::handle_to_fd(self.as_fd(), handle.into(), flags)?; + Ok(unsafe { OwnedFd::from_raw_fd(info.fd) }) + } + + /// Queue a page flip on the given crtc + fn page_flip( + &self, + handle: crtc::Handle, + framebuffer: framebuffer::Handle, + flags: PageFlipFlags, + target_sequence: Option<PageFlipTarget>, + ) -> io::Result<()> { + let mut flags = flags.bits(); + + let sequence = match target_sequence { + Some(PageFlipTarget::Absolute(n)) => { + flags |= ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE; + n + } + Some(PageFlipTarget::Relative(n)) => { + flags |= ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET_RELATIVE; + n + } + None => 0, + }; + + ffi::mode::page_flip( + self.as_fd(), + handle.into(), + framebuffer.into(), + flags, + sequence, + )?; + + Ok(()) + } + + /// Creates a syncobj. + fn create_syncobj(&self, signalled: bool) -> io::Result<syncobj::Handle> { + let info = ffi::syncobj::create(self.as_fd(), signalled)?; + Ok(from_u32(info.handle).unwrap()) + } + + /// Destroys a syncobj. + fn destroy_syncobj(&self, handle: syncobj::Handle) -> io::Result<()> { + ffi::syncobj::destroy(self.as_fd(), handle.into())?; + Ok(()) + } + + /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. + fn syncobj_to_fd( + &self, + handle: syncobj::Handle, + export_sync_file: bool, + ) -> io::Result<OwnedFd> { + let info = ffi::syncobj::handle_to_fd(self.as_fd(), handle.into(), export_sync_file)?; + Ok(unsafe { OwnedFd::from_raw_fd(info.fd) }) + } + + /// Imports a file descriptor exported by [`Self::syncobj_to_fd`] back into a process-local handle. + fn fd_to_syncobj( + &self, + fd: BorrowedFd<'_>, + import_sync_file: bool, + ) -> io::Result<syncobj::Handle> { + let info = ffi::syncobj::fd_to_handle(self.as_fd(), fd, import_sync_file)?; + Ok(from_u32(info.handle).unwrap()) + } + + /// Waits for one or more syncobjs to become signalled. + fn syncobj_wait( + &self, + handles: &[syncobj::Handle], + timeout_nsec: i64, + wait_all: bool, + wait_for_submit: bool, + ) -> io::Result<u32> { + let info = ffi::syncobj::wait( + self.as_fd(), + bytemuck::cast_slice(handles), + timeout_nsec, + wait_all, + wait_for_submit, + )?; + Ok(info.first_signaled) + } + + /// Resets (un-signals) one or more syncobjs. + fn syncobj_reset(&self, handles: &[syncobj::Handle]) -> io::Result<()> { + ffi::syncobj::reset(self.as_fd(), bytemuck::cast_slice(handles))?; + Ok(()) + } + + /// Signals one or more syncobjs. + fn syncobj_signal(&self, handles: &[syncobj::Handle]) -> io::Result<()> { + ffi::syncobj::signal(self.as_fd(), bytemuck::cast_slice(handles))?; + Ok(()) + } + + /// Waits for one or more specific timeline syncobj points. + fn syncobj_timeline_wait( + &self, + handles: &[syncobj::Handle], + points: &[u64], + timeout_nsec: i64, + wait_all: bool, + wait_for_submit: bool, + wait_available: bool, + ) -> io::Result<u32> { + let info = ffi::syncobj::timeline_wait( + self.as_fd(), + bytemuck::cast_slice(handles), + points, + timeout_nsec, + wait_all, + wait_for_submit, + wait_available, + )?; + Ok(info.first_signaled) + } + + /// Queries for state of one or more timeline syncobjs. + fn syncobj_timeline_query( + &self, + handles: &[syncobj::Handle], + points: &mut [u64], + last_submitted: bool, + ) -> io::Result<()> { + ffi::syncobj::query( + self.as_fd(), + bytemuck::cast_slice(handles), + points, + last_submitted, + )?; + Ok(()) + } + + /// Transfers one timeline syncobj point to another. + fn syncobj_timeline_transfer( + &self, + src_handle: syncobj::Handle, + dst_handle: syncobj::Handle, + src_point: u64, + dst_point: u64, + ) -> io::Result<()> { + ffi::syncobj::transfer( + self.as_fd(), + src_handle.into(), + dst_handle.into(), + src_point, + dst_point, + )?; + Ok(()) + } + + /// Signals one or more specific timeline syncobj points. + fn syncobj_timeline_signal( + &self, + handles: &[syncobj::Handle], + points: &[u64], + ) -> io::Result<()> { + ffi::syncobj::timeline_signal(self.as_fd(), bytemuck::cast_slice(handles), points)?; + Ok(()) + } + + /// Create a drm lease + fn create_lease( + &self, + objects: &[RawResourceHandle], + flags: u32, + ) -> io::Result<(LeaseId, OwnedFd)> { + let lease = ffi::mode::create_lease(self.as_fd(), bytemuck::cast_slice(objects), flags)?; + Ok(( + unsafe { NonZeroU32::new_unchecked(lease.lessee_id) }, + unsafe { OwnedFd::from_raw_fd(lease.fd as RawFd) }, + )) + } + + /// List active lessees + fn list_lessees(&self) -> io::Result<Vec<LeaseId>> { + let mut lessees = Vec::new(); + ffi::mode::list_lessees(self.as_fd(), Some(&mut lessees))?; + Ok(unsafe { transmute_vec_from_u32(lessees) }) + } + + /// Revoke a previously issued drm lease + fn revoke_lease(&self, lessee_id: LeaseId) -> io::Result<()> { + ffi::mode::revoke_lease(self.as_fd(), lessee_id.get()) + } + + /// Receive pending events + fn receive_events(&self) -> io::Result<Events> + where + Self: Sized, + { + let mut event_buf: [u8; 1024] = [0; 1024]; + let amount = rustix::io::read(self.as_fd(), &mut event_buf)?; + + Ok(Events::with_event_buf(event_buf, amount)) + } +} + +/// List of leased resources +pub struct LeaseResources { + /// leased crtcs + pub crtcs: Vec<crtc::Handle>, + /// leased connectors + pub connectors: Vec<connector::Handle>, + /// leased planes + pub planes: Vec<plane::Handle>, +} + +/// Query lease resources +pub fn get_lease<D: AsFd>(lease: D) -> io::Result<LeaseResources> { + let mut crtcs = Vec::new(); + let mut connectors = Vec::new(); + let mut planes = Vec::new(); + let mut objects = Vec::new(); + + ffi::mode::get_lease(lease.as_fd(), Some(&mut objects))?; + + let _ = ffi::mode::get_resources( + lease.as_fd(), + None, + Some(&mut crtcs), + Some(&mut connectors), + None, + )?; + let _ = ffi::mode::get_plane_resources(lease.as_fd(), Some(&mut planes))?; + + unsafe { + Ok(LeaseResources { + crtcs: transmute_vec_from_u32::<crtc::Handle>( + crtcs + .into_iter() + .filter(|handle| objects.contains(handle)) + .collect(), + ), + connectors: transmute_vec_from_u32::<connector::Handle>( + connectors + .into_iter() + .filter(|handle| objects.contains(handle)) + .collect(), + ), + planes: transmute_vec_from_u32::<plane::Handle>( + planes + .into_iter() + .filter(|handle| objects.contains(handle)) + .collect(), + ), + }) + } +} + +bitflags::bitflags! { + /// Flags to alter the behaviour of a page flip + /// + /// Limited to the values in [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_FLAGS`], + /// minus [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET`] bits which are + /// passed through [`PageFlipTarget`]. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct PageFlipFlags : u32 { + /// Request a vblank event on page flip + const EVENT = ffi::drm_sys::DRM_MODE_PAGE_FLIP_EVENT; + /// Request page flip as soon as possible, not waiting for vblank + const ASYNC = ffi::drm_sys::DRM_MODE_PAGE_FLIP_ASYNC; + } +} + +/// Target to alter the sequence of page flips +/// +/// These represent the [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET`] bits +/// of [`PageFlipFlags`] wrapped in a regular `enum` due to their +/// mutual-exclusiveness. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PageFlipTarget { + /// Absolute Vblank Sequence + Absolute(u32), + /// Relative Vblank Sequence (to the current, when calling) + Relative(u32), +} + +/// Iterator over [`Event`]s of a device. Create via [`Device::receive_events()`]. +pub struct Events { + event_buf: [u8; 1024], + amount: usize, + i: usize, +} + +impl Events { + /// Create [`Event`]s iterator from buffer read using something other than + /// [`Device::receive_events()`]. + pub fn with_event_buf(event_buf: [u8; 1024], amount: usize) -> Self { + Events { + event_buf, + amount, + i: 0, + } + } +} + +/// An event from a device. +pub enum Event { + /// A vblank happened + Vblank(VblankEvent), + /// A page flip happened + PageFlip(PageFlipEvent), + /// Unknown event, raw data provided + Unknown(Vec<u8>), +} + +/// Vblank event +pub struct VblankEvent { + /// sequence of the frame + pub frame: u32, + /// time at which the vblank occurred + pub time: Duration, + /// crtc that did throw the event + pub crtc: crtc::Handle, + /// user data that was passed to wait_vblank + pub user_data: usize, +} + +/// Page Flip event +pub struct PageFlipEvent { + /// sequence of the frame + pub frame: u32, + /// duration between events + pub duration: Duration, + /// crtc that did throw the event + pub crtc: crtc::Handle, +} + +impl Iterator for Events { + type Item = Event; + + fn next(&mut self) -> Option<Event> { + if self.amount > 0 && self.i < self.amount { + let event = unsafe { &*(self.event_buf.as_ptr().add(self.i) as *const ffi::drm_event) }; + self.i += event.length as usize; + match event.type_ { + ffi::DRM_EVENT_VBLANK => { + let vblank_event = + unsafe { &*(event as *const _ as *const ffi::drm_event_vblank) }; + Some(Event::Vblank(VblankEvent { + frame: vblank_event.sequence, + time: Duration::new( + vblank_event.tv_sec as u64, + vblank_event.tv_usec * 1000, + ), + #[allow(clippy::unnecessary_cast)] + crtc: from_u32(vblank_event.crtc_id as u32).unwrap(), + user_data: vblank_event.user_data as usize, + })) + } + ffi::DRM_EVENT_FLIP_COMPLETE => { + let vblank_event = + unsafe { &*(event as *const _ as *const ffi::drm_event_vblank) }; + Some(Event::PageFlip(PageFlipEvent { + frame: vblank_event.sequence, + duration: Duration::new( + vblank_event.tv_sec as u64, + vblank_event.tv_usec * 1000, + ), + crtc: from_u32(if vblank_event.crtc_id != 0 { + vblank_event.crtc_id + } else { + vblank_event.user_data as u32 + }) + .unwrap(), + })) + } + _ => Some(Event::Unknown( + self.event_buf[self.i - (event.length as usize)..self.i].to_vec(), + )), + } + } else { + None + } + } +} + +/// The set of [`ResourceHandles`] that a +/// [`Device`] exposes. Excluding Plane resources. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct ResourceHandles { + /// Set of [`framebuffer::Handle`] + pub fbs: Vec<framebuffer::Handle>, + /// Set of [`crtc::Handle`] + pub crtcs: Vec<crtc::Handle>, + /// Set of [`connector::Handle`] + pub connectors: Vec<connector::Handle>, + /// Set of [`encoder::Handle`] + pub encoders: Vec<encoder::Handle>, + width: (u32, u32), + height: (u32, u32), +} + +impl ResourceHandles { + /// Returns the set of [`connector::Handle`] + pub fn connectors(&self) -> &[connector::Handle] { + &self.connectors + } + + /// Returns the set of [`encoder::Handle`] + pub fn encoders(&self) -> &[encoder::Handle] { + &self.encoders + } + + /// Returns the set of [`crtc::Handle`] + pub fn crtcs(&self) -> &[crtc::Handle] { + &self.crtcs + } + + /// Returns the set of [`framebuffer::Handle`] + pub fn framebuffers(&self) -> &[framebuffer::Handle] { + &self.fbs + } + + /// Returns the supported minimum and maximum width for framebuffers + pub fn supported_fb_width(&self) -> impl RangeBounds<u32> { + self.width.0..=self.width.1 + } + + /// Returns the supported minimum and maximum height for framebuffers + pub fn supported_fb_height(&self) -> impl RangeBounds<u32> { + self.height.0..=self.height.1 + } + + /// Apply a filter the all crtcs of these resources, resulting in a list of crtcs allowed. + pub fn filter_crtcs(&self, filter: CrtcListFilter) -> Vec<crtc::Handle> { + self.crtcs + .iter() + .enumerate() + .filter(|&(n, _)| (1 << n) & filter.0 != 0) + .map(|(_, &e)| e) + .collect() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// A filter that can be used with a [`ResourceHandles`] to determine the set of +/// Crtcs that can attach to a specific encoder. +pub struct CrtcListFilter(u32); + +/// Resolution and timing information for a display mode. +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, bytemuck::TransparentWrapper)] +pub struct Mode { + // We're using the FFI struct because the DRM API expects it when giving it + // to a CRTC or creating a blob from it. Rather than rearranging the fields + // to convert to/from an abstracted type, just use the raw object. + mode: ffi::drm_mode_modeinfo, +} + +impl Mode { + /// Returns the name of this mode. + pub fn name(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(&self.mode.name[0] as _) } + } + + /// Returns the clock speed of this mode. + pub fn clock(&self) -> u32 { + self.mode.clock + } + + /// Returns the size (resolution) of the mode. + pub fn size(&self) -> (u16, u16) { + (self.mode.hdisplay, self.mode.vdisplay) + } + + /// Returns the horizontal sync start, end, and total. + pub fn hsync(&self) -> (u16, u16, u16) { + (self.mode.hsync_start, self.mode.hsync_end, self.mode.htotal) + } + + /// Returns the vertical sync start, end, and total. + pub fn vsync(&self) -> (u16, u16, u16) { + (self.mode.vsync_start, self.mode.vsync_end, self.mode.vtotal) + } + + /// Returns the horizontal skew of this mode. + pub fn hskew(&self) -> u16 { + self.mode.hskew + } + + /// Returns the vertical scan of this mode. + pub fn vscan(&self) -> u16 { + self.mode.vscan + } + + /// Returns the vertical refresh rate of this mode + pub fn vrefresh(&self) -> u32 { + self.mode.vrefresh + } + + /// Returns the bitmask of this mode + pub fn mode_type(&self) -> ModeTypeFlags { + ModeTypeFlags::from_bits_truncate(self.mode.type_) + } + + /// Returns the flags of this mode + pub fn flags(&self) -> ModeFlags { + ModeFlags::from_bits_truncate(self.mode.flags) + } +} + +impl From<ffi::drm_mode_modeinfo> for Mode { + fn from(raw: ffi::drm_mode_modeinfo) -> Mode { + Mode { mode: raw } + } +} + +impl From<Mode> for ffi::drm_mode_modeinfo { + fn from(mode: Mode) -> Self { + mode.mode + } +} + +impl fmt::Debug for Mode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Mode") + .field("name", &self.name()) + .field("clock", &self.clock()) + .field("size", &self.size()) + .field("hsync", &self.hsync()) + .field("vsync", &self.vsync()) + .field("hskew", &self.hskew()) + .field("vscan", &self.vscan()) + .field("vrefresh", &self.vrefresh()) + .field("mode_type", &self.mode_type()) + .finish() + } +} + +bitflags::bitflags! { + /// Display mode type flags + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ModeTypeFlags : u32 { + /// Builtin mode type + #[deprecated] + const BUILTIN = ffi::DRM_MODE_TYPE_BUILTIN; + /// CLOCK_C mode type + #[deprecated] + const CLOCK_C = ffi::DRM_MODE_TYPE_CLOCK_C; + /// CRTC_C mode type + #[deprecated] + const CRTC_C = ffi::DRM_MODE_TYPE_CRTC_C; + /// Preferred mode + const PREFERRED = ffi::DRM_MODE_TYPE_PREFERRED; + /// Default mode + #[deprecated] + const DEFAULT = ffi::DRM_MODE_TYPE_DEFAULT; + /// User defined mode type + const USERDEF = ffi::DRM_MODE_TYPE_USERDEF; + /// Mode created by driver + const DRIVER = ffi::DRM_MODE_TYPE_DRIVER; + /// Bitmask of all valid (non-deprecated) mode type flags + const ALL = ffi::DRM_MODE_TYPE_ALL; + } +} + +bitflags::bitflags! { + /// Display mode flags + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ModeFlags: u32 { + /// PHSYNC flag + const PHSYNC = ffi::DRM_MODE_FLAG_PHSYNC; + /// NHSYNC flag + const NHSYNC = ffi::DRM_MODE_FLAG_NHSYNC; + /// PVSYNC flag + const PVSYNC = ffi::DRM_MODE_FLAG_PVSYNC; + /// NVSYNC flag + const NVSYNC = ffi::DRM_MODE_FLAG_NVSYNC; + /// Interlace flag + const INTERLACE = ffi::DRM_MODE_FLAG_INTERLACE; + /// DBLSCAN flag + const DBLSCAN = ffi::DRM_MODE_FLAG_DBLSCAN; + /// CSYNC flag + const CSYNC = ffi::DRM_MODE_FLAG_CSYNC; + /// PCSYNC flag + const PCSYNC = ffi::DRM_MODE_FLAG_PCSYNC; + /// NCSYNC flag + const NCSYNC = ffi::DRM_MODE_FLAG_NCSYNC; + /// HSKEW flag + const HSKEW = ffi::DRM_MODE_FLAG_HSKEW; + #[deprecated] + /// BCAST flag + const BCAST = ffi::DRM_MODE_FLAG_BCAST; + #[deprecated] + /// PIXMUX flag + const PIXMUX = ffi::DRM_MODE_FLAG_PIXMUX; + /// DBLCLK flag + const DBLCLK = ffi::DRM_MODE_FLAG_DBLCLK; + /// CLKDIV2 flag + const CLKDIV2 = ffi::DRM_MODE_FLAG_CLKDIV2; + /// Stereo 3D mode utilizing frame packing + const _3D_FRAME_PACKING = ffi::DRM_MODE_FLAG_3D_FRAME_PACKING; + /// Stereo 3D mode utilizing alternating fields + const _3D_FIELD_ALTERNATIVE = ffi::DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE; + /// Stereo 3D mode utilizing alternating lines + const _3D_LINE_ALTERNATIVE = ffi::DRM_MODE_FLAG_3D_LINE_ALTERNATIVE; + /// Stereo 3D mode utilizing side by side full size image + const _3D_SIDE_BY_SIDE_FULL = ffi::DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL; + /// Stereo 3D mode utilizing depth images + const _3D_L_DEPTH = ffi::DRM_MODE_FLAG_3D_L_DEPTH; + /// Stereo 3D mode utilizing depth images + const _3D_L_DEPTH_GFX_GFX_DEPTH = ffi::DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH; + /// Stereo 3D mode utilizing top and bottom images + const _3D_TOP_AND_BOTTOM = ffi::DRM_MODE_FLAG_3D_TOP_AND_BOTTOM; + /// Stereo 3D mode utilizing side by side half size image + const _3D_SIDE_BY_SIDE_HALF = ffi::DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF; + } +} + +/// Type of a plane +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PlaneType { + /// Overlay plane + Overlay = ffi::DRM_PLANE_TYPE_OVERLAY, + /// Primary plane + Primary = ffi::DRM_PLANE_TYPE_PRIMARY, + /// Cursor plane + Cursor = ffi::DRM_PLANE_TYPE_CURSOR, +} + +/// Wrapper around a set of property IDs and their raw values. +#[derive(Debug, Clone)] +pub struct PropertyValueSet { + prop_ids: Vec<property::Handle>, + prop_vals: Vec<property::RawValue>, +} + +impl PropertyValueSet { + /// Returns a HashMap mapping property names to info + pub fn as_hashmap(&self, device: &impl Device) -> io::Result<HashMap<String, property::Info>> { + let mut map = HashMap::new(); + for id in self.prop_ids.iter() { + let info = device.get_property(*id)?; + let name = info.name().to_str().unwrap().to_owned(); + map.insert(name, info); + } + Ok(map) + } + + /// Returns a pair representing a set of [`property::Handle`] and their raw values + pub fn as_props_and_values(&self) -> (&[property::Handle], &[property::RawValue]) { + (&self.prop_ids, &self.prop_vals) + } + + /// Returns iterator over pairs representing a set of [`property::Handle`] and their raw values + pub fn iter(&self) -> impl Iterator<Item = (&property::Handle, &property::RawValue)> { + self.into_iter() + } +} + +impl<'a> IntoIterator for &'a PropertyValueSet { + type Item = (&'a property::Handle, &'a property::RawValue); + type IntoIter = + Zip<std::slice::Iter<'a, property::Handle>, std::slice::Iter<'a, property::RawValue>>; + + fn into_iter(self) -> Self::IntoIter { + self.prop_ids.iter().zip(self.prop_vals.iter()) + } +} + +impl IntoIterator for PropertyValueSet { + type Item = (property::Handle, property::RawValue); + type IntoIter = + Zip<std::vec::IntoIter<property::Handle>, std::vec::IntoIter<property::RawValue>>; + + fn into_iter(self) -> Self::IntoIter { + self.prop_ids.into_iter().zip(self.prop_vals) + } +} + +/// Describes a rectangular region of a buffer +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] +pub struct ClipRect(ffi::drm_sys::drm_clip_rect); + +impl ClipRect { + /// Create a new clipping rectangle. + pub fn new(x1: u16, y1: u16, x2: u16, y2: u16) -> Self { + Self(ffi::drm_sys::drm_clip_rect { x1, y1, x2, y2 }) + } + + /// Get the X coordinate of the top left corner of the rectangle. + pub fn x1(self) -> u16 { + self.0.x1 + } + + /// Get the Y coordinate of the top left corner of the rectangle. + pub fn y1(self) -> u16 { + self.0.y1 + } + + /// Get the X coordinate of the bottom right corner of the rectangle + pub fn x2(self) -> u16 { + self.0.x2 + } + + /// Get the Y coordinate of the bottom right corner of the rectangle. + pub fn y2(self) -> u16 { + self.0.y2 + } +} + +bitflags::bitflags! { + /// Commit flags for atomic mode setting + /// + /// Limited to the values in [`ffi::drm_sys::DRM_MODE_ATOMIC_FLAGS`]. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct AtomicCommitFlags : u32 { + /// Generate a page flip event, when the changes are applied + const PAGE_FLIP_EVENT = ffi::drm_sys::DRM_MODE_PAGE_FLIP_EVENT; + /// Request page flip when the changes are applied, not waiting for vblank + const PAGE_FLIP_ASYNC = ffi::drm_sys::DRM_MODE_PAGE_FLIP_ASYNC; + /// Test only validity of the request, do not actually apply the requested changes + const TEST_ONLY = ffi::drm_sys::DRM_MODE_ATOMIC_TEST_ONLY; + /// Do not block on the request and return early + const NONBLOCK = ffi::drm_sys::DRM_MODE_ATOMIC_NONBLOCK; + /// Allow the changes to trigger a modeset, if necessary + /// + /// Changes requiring a modeset are rejected otherwise. + const ALLOW_MODESET = ffi::drm_sys::DRM_MODE_ATOMIC_ALLOW_MODESET; + } +} + +bitflags::bitflags! { + /// Mode property flags + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ModePropFlags : u32 { + /// Do not use + #[deprecated] + const PENDING = ffi::DRM_MODE_PROP_PENDING; + + /// Non-extended types: legacy bitmask, one bit per type: + const LEGACY_TYPE = ffi::DRM_MODE_PROP_LEGACY_TYPE; + /// An unsigned integer that has a min and max value + const RANGE = ffi::DRM_MODE_PROP_RANGE; + /// Set when this property is informational only and cannot be modified + const IMMUTABLE = ffi::DRM_MODE_PROP_IMMUTABLE; + /// Enumerated type with text strings + const ENUM = ffi::DRM_MODE_PROP_ENUM; + /// A chunk of binary data that must be acquired + const BLOB = ffi::DRM_MODE_PROP_BLOB; + /// Bitmask of enumerated types + const BITMASK = ffi::DRM_MODE_PROP_BITMASK; + + /// Extended-types: rather than continue to consume a bit per type, + /// grab a chunk of the bits to use as integer type id. + const EXTENDED_TYPE = ffi::DRM_MODE_PROP_EXTENDED_TYPE; + /// A DRM object that can have a specific type + /// + /// See `ffi::DRM_MODE_OBJECT_*` for specific types. + const OBJECT = ffi::DRM_MODE_PROP_OBJECT; + /// A signed integer that has a min and max value + const SIGNED_RANGE = ffi::DRM_MODE_PROP_SIGNED_RANGE; + /// the [`Self::ATOMIC`] flag is used to hide properties from userspace that + /// is not aware of atomic properties. This is mostly to work around + /// older userspace (DDX drivers) that read/write each prop they find, + /// witout being aware that this could be triggering a lengthy modeset. + const ATOMIC = ffi::DRM_MODE_PROP_ATOMIC; + } +} + +bitflags::bitflags! { + /// Planar framebuffer flags + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct FbCmd2Flags : u32 { + /// For interlaced framebuffers + const INTERLACED = ffi::DRM_MODE_FB_INTERLACED; + /// Enables .modifier + const MODIFIERS = ffi::DRM_MODE_FB_MODIFIERS; + } +} diff --git a/src/control/plane.rs b/src/control/plane.rs new file mode 100644 index 0000000..c287935 --- /dev/null +++ b/src/control/plane.rs @@ -0,0 +1,96 @@ +//! # Plane +//! +//! Attachment point for a Framebuffer. +//! +//! A Plane is a resource that can have a framebuffer attached to it, either for +//! hardware compositing or displaying directly to a screen. There are three +//! types of planes available for use: +//! +//! * Primary - A CRTC's built-in plane. When attaching a framebuffer to a CRTC, +//! it is actually being attached to this kind of plane. +//! +//! * Overlay - Can be overlaid on top of a primary plane, utilizing extremely +//! fast hardware compositing. +//! +//! * Cursor - Similar to an overlay plane, these are typically used to display +//! cursor type objects. + +use crate::control; +use drm_ffi as ffi; + +/// A handle to a plane +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl control::ResourceHandle for Handle { + const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_PLANE; +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("plane::Handle").field(&self.0).finish() + } +} + +/// Information about a plane +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Info { + pub(crate) handle: Handle, + pub(crate) crtc: Option<control::crtc::Handle>, + pub(crate) fb: Option<control::framebuffer::Handle>, + pub(crate) pos_crtcs: u32, + pub(crate) formats: Vec<u32>, +} + +impl Info { + /// Returns the handle to this plane. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the CRTC this plane is attached to. + pub fn crtc(&self) -> Option<control::crtc::Handle> { + self.crtc + } + + /// Returns a filter for supported crtcs of this plane. + /// + /// Use with [`control::ResourceHandles::filter_crtcs`] + /// to receive a list of crtcs. + pub fn possible_crtcs(&self) -> control::CrtcListFilter { + control::CrtcListFilter(self.pos_crtcs) + } + + /// Returns the framebuffer this plane is attached to. + pub fn framebuffer(&self) -> Option<control::framebuffer::Handle> { + self.fb + } + + /// Returns the formats this plane supports. + pub fn formats(&self) -> &[u32] { + &self.formats + } +} diff --git a/src/control/property.rs b/src/control/property.rs new file mode 100644 index 0000000..d1b6331 --- /dev/null +++ b/src/control/property.rs @@ -0,0 +1,342 @@ +//! # Property +//! +//! A property of a modesetting resource. +//! +//! All modesetting resources have a set of properties that have values that +//! can be modified. These properties are modesetting resources themselves, and +//! may even have their own set of properties. +//! +//! Properties may have mutable values attached to them. These can be changed by +//! either changing the state of a resource (thereby affecting the property), +//! directly changing the property value itself, or by batching property changes +//! together and executing them all atomically. + +use crate::control::{RawResourceHandle, ResourceHandle}; +use drm_ffi as ffi; + +/// A raw property value that does not have a specific property type +pub type RawValue = u64; + +/// A handle to a property +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} + +impl From<Handle> for RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<RawResourceHandle> for Handle { + fn from(handle: RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl ResourceHandle for Handle { + const FFI_TYPE: u32 = ffi::DRM_MODE_OBJECT_PROPERTY; +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("property::Handle").field(&self.0).finish() + } +} + +/// Information about a property +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Info { + pub(crate) handle: Handle, + pub(crate) val_type: ValueType, + pub(crate) mutable: bool, + pub(crate) atomic: bool, + pub(crate) info: ffi::drm_mode_get_property, +} + +impl Info { + /// Returns the handle to this property. + pub fn handle(&self) -> Handle { + self.handle + } + + /// Returns the name of this property. + pub fn name(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(&self.info.name[0] as _) } + } + + /// Returns the ValueType of this property. + pub fn value_type(&self) -> ValueType { + self.val_type.clone() + } + + /// Returns whether this property is mutable. + pub fn mutable(&self) -> bool { + self.mutable + } + + /// Returns whether this property can be atomically updated. + pub fn atomic(&self) -> bool { + self.atomic + } +} + +/// Describes the types of value that a property uses. +#[allow(clippy::upper_case_acronyms)] +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum ValueType { + /// A catch-all for any unknown types + Unknown, + /// A True or False type + Boolean, + /// An unsigned integer that has a min and max value + UnsignedRange(u64, u64), + /// A signed integer that has a min and max value + SignedRange(i64, i64), + /// A set of values that are mutually exclusive + Enum(EnumValues), + /// A set of values that can be combined + Bitmask, + /// A chunk of binary data that must be acquired + Blob, + /// A non-specific DRM object + Object, + /// A CRTC object + CRTC, + /// A Connector object + Connector, + /// An Encoder object + Encoder, + /// A Framebuffer object + Framebuffer, + /// A Plane object + Plane, + /// A Property object + Property, +} + +impl ValueType { + /// Given a [`RawValue`], convert it into a specific [`Value`] + pub fn convert_value(&self, value: RawValue) -> Value { + match self { + ValueType::Unknown => Value::Unknown(value), + ValueType::Boolean => Value::Boolean(value != 0), + ValueType::UnsignedRange(_, _) => Value::UnsignedRange(value), + ValueType::SignedRange(_, _) => Value::SignedRange(value as i64), + ValueType::Enum(values) => Value::Enum(values.get_value_from_raw_value(value)), + ValueType::Bitmask => Value::Bitmask(value), + ValueType::Blob => Value::Blob(value), + ValueType::Object => Value::Object(bytemuck::cast(value as u32)), + ValueType::CRTC => Value::CRTC(bytemuck::cast(value as u32)), + ValueType::Connector => Value::Connector(bytemuck::cast(value as u32)), + ValueType::Encoder => Value::Encoder(bytemuck::cast(value as u32)), + ValueType::Framebuffer => Value::Framebuffer(bytemuck::cast(value as u32)), + ValueType::Plane => Value::Plane(bytemuck::cast(value as u32)), + ValueType::Property => Value::Property(bytemuck::cast(value as u32)), + } + } +} + +/// The value of a property, in a typed format +#[allow(missing_docs)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum Value<'a> { + /// Unknown value + Unknown(RawValue), + /// Boolean value + Boolean(bool), + /// Unsigned range value + UnsignedRange(u64), + /// Signed range value + SignedRange(i64), + /// Enum Value + Enum(Option<&'a EnumValue>), + /// Bitmask value + Bitmask(u64), + /// Opaque (blob) value + Blob(u64), + /// Unknown object value + Object(Option<RawResourceHandle>), + /// Crtc object value + CRTC(Option<super::crtc::Handle>), + /// Connector object value + Connector(Option<super::connector::Handle>), + /// Encoder object value + Encoder(Option<super::encoder::Handle>), + /// Framebuffer object value + Framebuffer(Option<super::framebuffer::Handle>), + /// Plane object value + Plane(Option<super::plane::Handle>), + /// Property object value + Property(Option<Handle>), +} + +impl<'a> From<Value<'a>> for RawValue { + fn from(value: Value<'a>) -> Self { + match value { + Value::Unknown(x) => x, + Value::Boolean(true) => 1, + Value::Boolean(false) => 0, + Value::UnsignedRange(x) => x, + Value::SignedRange(x) => x as u64, + Value::Enum(val) => val.map_or(0, EnumValue::value), + Value::Bitmask(x) => x, + Value::Blob(x) => x, + Value::Object(x) => bytemuck::cast::<_, u32>(x) as u64, + Value::CRTC(x) => bytemuck::cast::<_, u32>(x) as u64, + Value::Connector(x) => bytemuck::cast::<_, u32>(x) as u64, + Value::Encoder(x) => bytemuck::cast::<_, u32>(x) as u64, + Value::Framebuffer(x) => bytemuck::cast::<_, u32>(x) as u64, + Value::Plane(x) => bytemuck::cast::<_, u32>(x) as u64, + Value::Property(x) => bytemuck::cast::<_, u32>(x) as u64, + } + } +} + +macro_rules! match_variant { + ($this:ident, $variant:ident) => { + if let Self::$variant(v) = *$this { + Some(v) + } else { + None + } + }; +} + +impl<'a> Value<'a> { + /// Boolean value + pub fn as_boolean(&self) -> Option<bool> { + match_variant!(self, Boolean) + } + + /// Unsigned range value + pub fn as_unsigned_range(&self) -> Option<u64> { + match_variant!(self, UnsignedRange) + } + + /// Signed range value + pub fn as_signed_range(&self) -> Option<i64> { + match_variant!(self, SignedRange) + } + + /// Enum Value + pub fn as_enum(&self) -> Option<&'a EnumValue> { + match_variant!(self, Enum).flatten() + } + + /// Bitmask value + pub fn as_bitmask(&self) -> Option<u64> { + match_variant!(self, Bitmask) + } + + /// Opaque (blob) value + pub fn as_blob(&self) -> Option<u64> { + match_variant!(self, Blob) + } + + /// Unknown object value + pub fn as_object(&self) -> Option<RawResourceHandle> { + match_variant!(self, Object).flatten() + } + + /// Crtc object value + pub fn as_crtc(&self) -> Option<super::crtc::Handle> { + match_variant!(self, CRTC).flatten() + } + + /// Connector object value + pub fn as_connector(&self) -> Option<super::connector::Handle> { + match_variant!(self, Connector).flatten() + } + + /// Encoder object value + pub fn as_encoder(&self) -> Option<super::encoder::Handle> { + match_variant!(self, Encoder).flatten() + } + + /// Framebuffer object value + pub fn as_framebuffer(&self) -> Option<super::framebuffer::Handle> { + match_variant!(self, Framebuffer).flatten() + } + + /// Plane object value + pub fn as_plane(&self) -> Option<super::plane::Handle> { + match_variant!(self, Plane).flatten() + } + + /// Property object value + pub fn as_property(&self) -> Option<Handle> { + match_variant!(self, Property).flatten() + } +} + +/// A single value of [`ValueType::Enum`] type +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, bytemuck::TransparentWrapper)] +pub struct EnumValue(ffi::drm_mode_property_enum); + +impl EnumValue { + /// Returns the [`RawValue`] of this value + pub fn value(&self) -> RawValue { + self.0.value + } + + /// Returns the name of this value + pub fn name(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(&self.0.name[0] as _) } + } +} + +impl From<ffi::drm_mode_property_enum> for EnumValue { + fn from(inner: ffi::drm_mode_property_enum) -> Self { + EnumValue(inner) + } +} + +impl std::fmt::Debug for EnumValue { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("EnumValue") + .field("value", &self.value()) + .field("name", &self.name()) + .finish() + } +} + +/// A set of [`EnumValue`]s for a single property +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct EnumValues { + pub(crate) values: Vec<u64>, + pub(crate) enums: Vec<EnumValue>, +} + +impl EnumValues { + /// Returns a tuple containing slices to the [`RawValue`]s and the [`EnumValue`]s + pub fn values(&self) -> (&[RawValue], &[EnumValue]) { + (&self.values, &self.enums) + } + + /// Returns an [`EnumValue`] for a [`RawValue`], or [`None`] if `value` is + /// not part of this [`EnumValues`]. + pub fn get_value_from_raw_value(&self, value: RawValue) -> Option<&EnumValue> { + let (values, enums) = self.values(); + let index = if values.get(value as usize) == Some(&value) { + // Early-out: indices match values + value as usize + } else { + values.iter().position(|&v| v == value)? + }; + Some(&enums[index]) + } +} diff --git a/src/control/syncobj.rs b/src/control/syncobj.rs new file mode 100644 index 0000000..b4106e6 --- /dev/null +++ b/src/control/syncobj.rs @@ -0,0 +1,49 @@ +//! # SyncObj +//! +//! A SyncObj is a binding point for the DRM subsystem to attach single-use fences which are +//! signalled when a device task completes. They are typically provided as optional arguments to +//! device-specific command submission IOCTLs. In practice, they are used to implement Vulkan +//! fence objects. +//! +//! After a submission IOCTL sets a fence into a SyncObj, it may be exported as a sync file +//! descriptor. This sync file may be epoll()'d for EPOLLIN to implement asynchronous waiting on +//! multiple events. This file descriptor is also compatible with [`tokio::io::unix::AsyncFd`] for +//! Rust async/await integration. +//! +//! [`tokio::io::unix::AsyncFd`]: <https://docs.rs/tokio/latest/tokio/io/unix/struct.AsyncFd.html> + +use crate::control; + +/// A handle to a specific syncobj +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} +unsafe impl bytemuck::NoUninit for Handle {} + +impl From<Handle> for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From<Handle> for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From<control::RawResourceHandle> for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("syncobj::Handle").field(&self.0).finish() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..76e80c3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,351 @@ +//! A safe interface to the Direct Rendering Manager subsystem found in various +//! operating systems. +//! +//! # Summary +//! +//! The Direct Rendering Manager (DRM) is subsystem found in various operating +//! systems that exposes graphical functionality to userspace processes. It can +//! be used to send data and commands to a GPU driver that implements the +//! interface. +//! +//! Userspace processes can access the DRM by opening a 'device node' (usually +//! found in `/dev/dri/*`) and using various `ioctl` commands on the open file +//! descriptor. Most processes use the libdrm library (part of the mesa project) +//! to execute these commands. This crate takes a more direct approach, +//! bypassing libdrm and executing the commands directly and doing minimal +//! abstraction to keep the interface safe. +//! +//! While the DRM subsystem exposes many powerful GPU interfaces, it is not +//! recommended for rendering or GPGPU operations. There are many standards made +//! for these use cases, and they are far more fitting for those sort of tasks. +//! +//! ## Usage +//! +//! To begin using this crate, the [`Device`] trait must be +//! implemented. See the trait's [example section](trait@Device#example) for +//! details on how to implement it. +//! + +#![warn(missing_docs)] + +pub(crate) mod util; + +pub mod buffer; +pub mod control; + +use std::ffi::{OsStr, OsString}; +use std::time::Duration; +use std::{ + io, + os::unix::{ffi::OsStringExt, io::AsFd}, +}; + +use rustix::io::Errno; + +use crate::util::*; + +pub use drm_ffi::{DRM_CLOEXEC as CLOEXEC, DRM_RDWR as RDWR}; + +/// This trait should be implemented by any object that acts as a DRM device. It +/// is a prerequisite for using any DRM functionality. +/// +/// This crate does not provide a concrete device object due to the various ways +/// it can be implemented. The user of this crate is expected to implement it +/// themselves and derive this trait as necessary. The example below +/// demonstrates how to do this using a small wrapper. +/// +/// # Example +/// +/// ``` +/// use drm::Device; +/// +/// use std::fs::File; +/// use std::fs::OpenOptions; +/// +/// use std::os::unix::io::AsFd; +/// use std::os::unix::io::BorrowedFd; +/// +/// #[derive(Debug)] +/// /// A simple wrapper for a device node. +/// struct Card(File); +/// +/// /// Implementing [`AsFd`] is a prerequisite to implementing the traits found +/// /// in this crate. Here, we are just calling [`File::as_fd()`] on the inner +/// /// [`File`]. +/// impl AsFd for Card { +/// fn as_fd(&self) -> BorrowedFd<'_> { +/// self.0.as_fd() +/// } +/// } +/// +/// /// With [`AsFd`] implemented, we can now implement [`drm::Device`]. +/// impl Device for Card {} +/// +/// impl Card { +/// /// Simple helper method for opening a [`Card`]. +/// fn open() -> Self { +/// let mut options = OpenOptions::new(); +/// options.read(true); +/// options.write(true); +/// +/// // The normal location of the primary device node on Linux +/// Card(options.open("/dev/dri/card0").unwrap()) +/// } +/// } +/// ``` +pub trait Device: AsFd { + /// Acquires the DRM Master lock for this process. + /// + /// # Notes + /// + /// Acquiring the DRM Master is done automatically when the primary device + /// node is opened. If you opened the primary device node and did not + /// acquire the lock, another process likely has the lock. + /// + /// This function is only available to processes with CAP_SYS_ADMIN + /// privileges (usually as root) + fn acquire_master_lock(&self) -> io::Result<()> { + drm_ffi::auth::acquire_master(self.as_fd())?; + Ok(()) + } + + /// Releases the DRM Master lock for another process to use. + fn release_master_lock(&self) -> io::Result<()> { + drm_ffi::auth::release_master(self.as_fd())?; + Ok(()) + } + + /// Generates an [`AuthToken`] for this process. + #[deprecated(note = "Consider opening a render node instead.")] + fn generate_auth_token(&self) -> io::Result<AuthToken> { + let token = drm_ffi::auth::get_magic_token(self.as_fd())?; + Ok(AuthToken(token.magic)) + } + + /// Authenticates an [`AuthToken`] from another process. + fn authenticate_auth_token(&self, token: AuthToken) -> io::Result<()> { + drm_ffi::auth::auth_magic_token(self.as_fd(), token.0)?; + Ok(()) + } + + /// Requests the driver to expose or hide certain capabilities. See + /// [`ClientCapability`] for more information. + fn set_client_capability(&self, cap: ClientCapability, enable: bool) -> io::Result<()> { + drm_ffi::set_capability(self.as_fd(), cap as u64, enable)?; + Ok(()) + } + + /// Gets the bus ID of this device. + fn get_bus_id(&self) -> io::Result<OsString> { + let mut buffer = Vec::new(); + let _ = drm_ffi::get_bus_id(self.as_fd(), Some(&mut buffer))?; + let bus_id = OsString::from_vec(buffer); + + Ok(bus_id) + } + + /// Check to see if our [`AuthToken`] has been authenticated + /// by the DRM Master + fn authenticated(&self) -> io::Result<bool> { + let client = drm_ffi::get_client(self.as_fd(), 0)?; + Ok(client.auth == 1) + } + + /// Gets the value of a capability. + fn get_driver_capability(&self, cap: DriverCapability) -> io::Result<u64> { + let cap = drm_ffi::get_capability(self.as_fd(), cap as u64)?; + Ok(cap.value) + } + + /// # Possible errors: + /// - `EFAULT`: Kernel could not copy fields into userspace + #[allow(missing_docs)] + fn get_driver(&self) -> io::Result<Driver> { + let mut name = Vec::new(); + let mut date = Vec::new(); + let mut desc = Vec::new(); + + let _ = drm_ffi::get_version( + self.as_fd(), + Some(&mut name), + Some(&mut date), + Some(&mut desc), + )?; + + let name = OsString::from_vec(unsafe { transmute_vec(name) }); + let date = OsString::from_vec(unsafe { transmute_vec(date) }); + let desc = OsString::from_vec(unsafe { transmute_vec(desc) }); + + let driver = Driver { name, date, desc }; + + Ok(driver) + } + + /// Waits for a vblank. + fn wait_vblank( + &self, + target_sequence: VblankWaitTarget, + flags: VblankWaitFlags, + high_crtc: u32, + user_data: usize, + ) -> io::Result<VblankWaitReply> { + use drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_HIGH_CRTC_MASK; + use drm_ffi::_DRM_VBLANK_HIGH_CRTC_SHIFT; + + let high_crtc_mask = _DRM_VBLANK_HIGH_CRTC_MASK >> _DRM_VBLANK_HIGH_CRTC_SHIFT; + if (high_crtc & !high_crtc_mask) != 0 { + return Err(Errno::INVAL.into()); + } + + let (sequence, wait_type) = match target_sequence { + VblankWaitTarget::Absolute(n) => { + (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_ABSOLUTE) + } + VblankWaitTarget::Relative(n) => { + (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_RELATIVE) + } + }; + + let type_ = wait_type | (high_crtc << _DRM_VBLANK_HIGH_CRTC_SHIFT) | flags.bits(); + let reply = drm_ffi::wait_vblank(self.as_fd(), type_, sequence, user_data)?; + + let time = match (reply.tval_sec, reply.tval_usec) { + (0, 0) => None, + (sec, usec) => Some(Duration::new(sec as u64, (usec * 1000) as u32)), + }; + + Ok(VblankWaitReply { + frame: reply.sequence, + time, + }) + } +} + +/// An authentication token, unique to the file descriptor of the device. +/// +/// This token can be sent to another process that owns the DRM Master lock to +/// allow unprivileged use of the device, such as rendering. +/// +/// # Deprecation Notes +/// +/// This method of authentication is somewhat deprecated. Accessing unprivileged +/// functionality is best done by opening a render node. However, some other +/// processes may still use this method of authentication. Therefore, we still +/// provide functionality for generating and authenticating these tokens. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct AuthToken(u32); + +/// Driver version of a device. +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Driver { + /// Name of the driver + pub name: OsString, + /// Date driver was published + pub date: OsString, + /// Driver description + pub desc: OsString, +} + +impl Driver { + /// Name of driver + pub fn name(&self) -> &OsStr { + self.name.as_ref() + } + + /// Date driver was published + pub fn date(&self) -> &OsStr { + self.date.as_ref() + } + + /// Driver description + pub fn description(&self) -> &OsStr { + self.desc.as_ref() + } +} + +/// Used to check which capabilities your graphics driver has. +#[allow(clippy::upper_case_acronyms)] +#[repr(u64)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum DriverCapability { + /// DumbBuffer support for scanout + DumbBuffer = drm_ffi::DRM_CAP_DUMB_BUFFER as u64, + /// Unknown + VBlankHighCRTC = drm_ffi::DRM_CAP_VBLANK_HIGH_CRTC as u64, + /// Preferred depth to use for dumb buffers + DumbPreferredDepth = drm_ffi::DRM_CAP_DUMB_PREFERRED_DEPTH as u64, + /// Unknown + DumbPreferShadow = drm_ffi::DRM_CAP_DUMB_PREFER_SHADOW as u64, + /// PRIME handles are supported + Prime = drm_ffi::DRM_CAP_PRIME as u64, + /// Unknown + MonotonicTimestamp = drm_ffi::DRM_CAP_TIMESTAMP_MONOTONIC as u64, + /// Asynchronous page flipping support + ASyncPageFlip = drm_ffi::DRM_CAP_ASYNC_PAGE_FLIP as u64, + /// Width of cursor buffers + CursorWidth = drm_ffi::DRM_CAP_CURSOR_WIDTH as u64, + /// Height of cursor buffers + CursorHeight = drm_ffi::DRM_CAP_CURSOR_HEIGHT as u64, + /// Create framebuffers with modifiers + AddFB2Modifiers = drm_ffi::DRM_CAP_ADDFB2_MODIFIERS as u64, + /// Unknown + PageFlipTarget = drm_ffi::DRM_CAP_PAGE_FLIP_TARGET as u64, + /// Uses the CRTC's ID in vblank events + CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64, + /// SyncObj support + SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64, + /// Timeline SyncObj support + TimelineSyncObj = drm_ffi::DRM_CAP_SYNCOBJ_TIMELINE as u64, +} + +/// Used to enable/disable capabilities for the process. +#[repr(u64)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum ClientCapability { + /// The driver provides 3D screen control + Stereo3D = drm_ffi::DRM_CLIENT_CAP_STEREO_3D as u64, + /// The driver provides more plane types for modesetting + UniversalPlanes = drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64, + /// The driver provides atomic modesetting + Atomic = drm_ffi::DRM_CLIENT_CAP_ATOMIC as u64, +} + +/// Used to specify a vblank sequence to wait for +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum VblankWaitTarget { + /// Wait for a specific vblank sequence number + Absolute(u32), + /// Wait for a given number of vblanks + Relative(u32), +} + +bitflags::bitflags! { + /// Flags to alter the behaviour when waiting for a vblank + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct VblankWaitFlags : u32 { + /// Send event instead of blocking + const EVENT = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_EVENT; + /// If missed, wait for next vblank + const NEXT_ON_MISS = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_NEXTONMISS; + } +} + +/// Data returned from a vblank wait +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct VblankWaitReply { + frame: u32, + time: Option<Duration>, +} + +impl VblankWaitReply { + /// Sequence of the frame + pub fn frame(&self) -> u32 { + self.frame + } + + /// Time at which the vblank occurred. [`None`] if an asynchronous event was + /// requested + pub fn time(&self) -> Option<Duration> { + self.time + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..6f2b8d6 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,19 @@ +//! Utilities used internally by this crate. + +use crate::control::{from_u32, RawResourceHandle}; + +pub unsafe fn transmute_vec<T, U>(from: Vec<T>) -> Vec<U> { + let mut from = std::mem::ManuallyDrop::new(from); + + Vec::from_raw_parts(from.as_mut_ptr() as *mut U, from.len(), from.capacity()) +} + +pub unsafe fn transmute_vec_from_u32<T: From<RawResourceHandle>>(raw: Vec<u32>) -> Vec<T> { + if cfg!(debug_assertions) { + raw.into_iter() + .map(|handle| from_u32(handle).unwrap()) + .collect() + } else { + transmute_vec(raw) + } +} |