From 19c65be7b15a4f814abe2d4f81699a14b2a4879c Mon Sep 17 00:00:00 2001 From: Vasilito Date: Thu, 14 May 2026 10:31:13 +0100 Subject: [PATCH] feat: VirtIO GPU driver, libdrm DRM ioctls, KWin/KF6 build fixes, display stack additions - Add full VirtIO GPU driver with command submission, resource management, VirtQueue implementation, and transport layer; includes diagnostic probes for resource_create_2d ERR_INVALID_RESOURCE_ID investigation - Expand libdrm Redox support with DRM ioctl wrappers (ADDFB, RMFB, CREATE_DUMB, MAP_DUMB, DESTROY_DUMB, GET_RESOURCES, GET_CONNECTOR, GET_CRTC, SET_CRTC, MODE_OBJ_GET_PROPERTIES, etc.) and xf86drm_redox.h - Add redox-drm scheme handlers for VirtIO GPU-specific DRM ioctls (VIRTGPU_RESOURCE_CREATE, VIRTGPU_MAP, VIRTGPU_WAIT, VIRTGPU_INFO, etc.) - Add display stack recipes: freetype2, lcms2, libdisplay-info, libepoxy, libxcvt - Fix KWin build (recipe.toml expanded, kf6-ksvg added) - Fix KF6 CMakeLists for cross-compilation (attica, kcmutils, kcolorscheme, kcompletion, kconfigwidgets, kdeclarative, kiconthemes, kitemmodels, kitemviews, kjobwidgets, ktextwidgets, kwayland, kxmlgui, kpty, solid) - Add Qt6 futex support patch - Add relibc patches: P3 strtold, P3 ld-so search path, P5 DRM ioctl removal - Add base P4 pcid config scheme patch - Update driver-manager hotplug/config, PCI config in redox-driver-sys - Update greeter compositor and KDE session scripts - Update AGENTS.md with zero-tolerance stubs policy and project knowledge --- AGENTS.md | 145 ++ config/redbear-full.toml | 56 +- local/config/drivers.d/30-graphics.toml | 19 +- local/config/drivers.d/70-usb-class.toml | 14 +- .../patches/base/P4-pcid-config-scheme.patch | 200 +++ local/patches/kwin/P0-disable-qml-quick.patch | 911 +++------- .../patches/qtbase/futex-redox-support.patch | 126 ++ .../relibc/P3-ld-so-usr-lib-search-path.patch | 12 + local/patches/relibc/P3-strtold.patch | 35 + .../P5-remove-drm-ioctl-intercept.patch | 42 + .../redox-driver-sys/source/src/pci.rs | 481 +++++- .../gpu/redox-drm/source/src/driver.rs | 106 +- .../redox-drm/source/src/drivers/amd/mod.rs | 2 +- .../redox-drm/source/src/drivers/intel/mod.rs | 2 +- .../source/src/drivers/virtio/commands.rs | 404 +++++ .../source/src/drivers/virtio/mod.rs | 1417 ++++++++++++++-- .../source/src/drivers/virtio/resource.rs | 118 ++ .../source/src/drivers/virtio/transport.rs | 404 +++++ .../source/src/drivers/virtio/virtqueue.rs | 259 +++ .../recipes/gpu/redox-drm/source/src/main.rs | 2 +- .../gpu/redox-drm/source/src/scheme.rs | 1505 ++++++++++++++++- .../kde/kf6-attica/source/CMakeLists.txt | 2 +- .../kde/kf6-kcmutils/source/CMakeLists.txt | 18 + .../kf6-kcolorscheme/source/CMakeLists.txt | 18 + .../kde/kf6-kcompletion/source/CMakeLists.txt | 18 + local/recipes/kde/kf6-kconfig/recipe.toml | 2 +- .../kf6-kconfigwidgets/source/CMakeLists.txt | 18 + .../kf6-kdeclarative/source/CMakeLists.txt | 2 +- .../kde/kf6-kiconthemes/source/CMakeLists.txt | 19 + local/recipes/kde/kf6-kidletime/recipe.toml | 2 +- .../kde/kf6-kitemmodels/source/CMakeLists.txt | 2 +- .../kf6-kitemmodels/source/src/CMakeLists.txt | 2 +- .../kde/kf6-kitemviews/source/CMakeLists.txt | 19 + .../kde/kf6-kjobwidgets/source/CMakeLists.txt | 18 + local/recipes/kde/kf6-ksvg/recipe.toml | 57 + .../kde/kf6-ksvg/source/CMakeLists.txt | 4 +- .../kf6-ktextwidgets/source/CMakeLists.txt | 18 + .../kde/kf6-kwayland/source/CMakeLists.txt | 19 + .../source/src/kswitchlanguagedialog_p.cpp | 6 +- .../recipes/kde/kf6-pty/source/CMakeLists.txt | 6 +- .../kde/kf6-solid/source/CMakeLists.txt | 2 +- local/recipes/kde/kwin/recipe.toml | 195 ++- local/recipes/libs/freetype2/recipe.toml | 15 + local/recipes/libs/lcms2/recipe.toml | 30 + .../recipes/libs/libdisplay-info/recipe.toml | 52 + .../libs/libdisplay-info/source/meson.build | 29 + local/recipes/libs/libdrm/redox.patch | 1365 ++++++++++++++- local/recipes/libs/libdrm/source/xf86drm.c | 1162 ++++++++++++- .../recipes/libs/libdrm/source/xf86drmMode.c | 557 ++++++ .../libs/libdrm/source/xf86drm_redox.h | 194 +++ local/recipes/libs/libepoxy/recipe.toml | 18 + local/recipes/libs/libxcvt/recipe.toml | 10 + .../qt/qtbase/futex-redox-support.patch | 1 + local/recipes/qt/qtbase/recipe.toml | 14 +- local/recipes/qt/qtdeclarative/recipe.toml | 8 - .../driver-manager/source/src/config.rs | 2 +- .../driver-manager/source/src/hotplug.rs | 46 +- .../source/redbear-greeter-compositor | 110 +- .../source/redbear-kde-session | 16 +- recipes/core/base/recipe.toml | 1 + recipes/core/relibc/recipe.toml | 4 + recipes/libs/freetype2/recipe.toml | 4 +- recipes/system/dbus | 1 + scripts/run_full.sh | 2 +- 64 files changed, 9252 insertions(+), 1096 deletions(-) create mode 100644 local/patches/base/P4-pcid-config-scheme.patch create mode 100644 local/patches/qtbase/futex-redox-support.patch create mode 100644 local/patches/relibc/P3-ld-so-usr-lib-search-path.patch create mode 100644 local/patches/relibc/P3-strtold.patch create mode 100644 local/patches/relibc/P5-remove-drm-ioctl-intercept.patch create mode 100644 local/recipes/gpu/redox-drm/source/src/drivers/virtio/commands.rs create mode 100644 local/recipes/gpu/redox-drm/source/src/drivers/virtio/resource.rs create mode 100644 local/recipes/gpu/redox-drm/source/src/drivers/virtio/transport.rs create mode 100644 local/recipes/gpu/redox-drm/source/src/drivers/virtio/virtqueue.rs create mode 100644 local/recipes/kde/kf6-ksvg/recipe.toml create mode 100644 local/recipes/libs/freetype2/recipe.toml create mode 100644 local/recipes/libs/lcms2/recipe.toml create mode 100644 local/recipes/libs/libdisplay-info/recipe.toml create mode 100644 local/recipes/libs/libdisplay-info/source/meson.build create mode 100644 local/recipes/libs/libdrm/source/xf86drm_redox.h create mode 100644 local/recipes/libs/libepoxy/recipe.toml create mode 100644 local/recipes/libs/libxcvt/recipe.toml create mode 120000 local/recipes/qt/qtbase/futex-redox-support.patch create mode 120000 recipes/system/dbus diff --git a/AGENTS.md b/AGENTS.md index 1a82ad91b0..16d16234e4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -196,6 +196,91 @@ make all - **Syscall ABI**: Unstable intentionally. Stability via `libredox` and `relibc` - **Drivers**: ALL userspace daemons via scheme system. No kernel-space drivers (except serio) +## SYSTEM-CRITICAL INFRASTRUCTURE MUST BE RUST + +All Red Bear OS system-critical infrastructure **must** be written in Rust. C and C++ are +acceptable only for ported upstream applications (KDE Plasma, Qt, games, third-party tools) +where the original source is not Rust. + +### What counts as system-critical + +| Layer | Component | Language | Status | +|-------|-----------|----------|--------| +| Kernel | microkernel | Rust | ✅ | +| C library | relibc | Rust | ✅ | +| Init | service manager | Rust | ✅ | +| Filesystems | redoxfs, ext4d, fatd | Rust | ✅ | +| Driver infrastructure | redox-driver-sys, linux-kpi headers | Rust + C headers | ✅ | +| Display/compositor | Wayland compositor | Rust | required | +| Session/auth | redbear-sessiond, redbear-authd | Rust | ✅ | +| D-Bus broker | session/system bus | Rust | ✅ | +| Network stack | TCP/IP, Wi-Fi control plane | Rust | required | +| Bluetooth stack | host/controller path | Rust | required | +| USB stack | controller drivers, hub driver | Rust | required | +| Input stack | evdev, libinput adapter | Rust | required | +| Firmware loading | scheme:firmware daemon | Rust | ✅ | +| Core utilities | shell, fileutils, process tools | Rust | required | +| Bootloader | UEFI bootloader | Rust | ✅ | +| Installer | redox_installer | Rust | ✅ | +| Build tooling | cookbook, repo binary | Rust | ✅ | + +### What does NOT need to be Rust + +- **Ported desktop applications**: KDE Plasma, Qt apps, KDE Frameworks — these are upstream + C++ codebases and remain C++. The boundary is at the platform adapter layer: anything Red Bear + writes to integrate them ( Wayland protocol bridges, D-Bus service implementations, platform + plugins) must be Rust even if the upstream consumer is C++. +- **Ported libraries**: mesa, wayland, libxkbcommon, libinput, fontconfig, etc. — upstream C. +- **Games and end-user applications**: upstream code in any language. + +### Decision rule + +When writing new code for Red Bear OS, or when choosing between writing new code vs porting +existing code, the rule is: + +> If the component runs below the application layer — kernel, libc, drivers, filesystems, +> compositor, session management, networking, input, USB, Bluetooth, core utilities — +> it must be written in Rust. +> +> If the component is an application or library that users would recognize as a separate +> upstream project (KDE, Qt, Firefox, etc.), it may remain in its upstream language. +> +> The integration layer between Rust infrastructure and upstream C/C++ code must be Rust. +> Platform adapters, D-Bus service implementations, Wayland protocol bridges, and plugin +> shims are infrastructure, not applications. + +### Enforcement + +- New recipes under `local/recipes/` for system-critical components must use `template = "cargo"`. +- C/C++ build templates (`cmake`, `meson`, `make`, `configure`) are only for ported upstream + packages and their direct dependencies. +- If a ported C/C++ package needs a companion daemon, helper, or bridge, that companion must + be a separate Rust recipe — not embedded C in the ported package. + +### Conflicting implementations: always prefer Rust + +When both a Rust implementation and a C/C++ implementation exist for the same functionality, +Red Bear OS **always** prefers the Rust implementation. This applies even when the C version is +from upstream Redox or appears more complete. + +The rationale: Rust provides memory safety, type safety, and panic-based error recovery at the +language level. For an OS with no ASLR, no stack canaries, and a minimal kernel, the language +itself is the primary defense boundary. A C implementation of equivalent functionality is always +a strictly weaker choice. + +Examples: + +| Situation | Correct choice | +|-----------|---------------| +| relibc (Rust) vs newlib/glibc (C) | relibc — always | +| redoxfs (Rust) vs an imported C filesystem driver | redoxfs — always | +| redbear-sessiond (Rust) vs dbus-daemon (C) | redbear-sessiond — always | +| A Rust crate for a protocol vs the reference C library | Rust crate — always | +| Upstream Redox provides a Rust driver; we also have a C port | Rust driver — always | + +If a Rust implementation is less feature-complete than the C alternative, the correct response +is to improve the Rust implementation — not to fall back to C. + ## INSTALLER FILE LAYERING The installer creates filesystem images in four layers. Understanding this ordering is critical @@ -253,6 +338,66 @@ See `local/docs/BUILD-SYSTEM-HARDENING-PLAN.md` for the full plan. - **DO NOT** remove patches from `recipe.toml` to fix build failures — rebase the patch instead (see `local/docs/PATCH-GOVERNANCE.md`) - **DO NOT** remove BINS entries to fix build failures — fix the source or use EXISTING_BINS filtering +## ZERO TOLERANCE FOR STUBS + +**Red Bear OS has zero tolerance for stubs, placeholders, fake implementations, skeleton code, +unimplemented functions, or any other "make it compile" shortcut.** + +### What counts as a stub + +Any of the following: + +- A function or method that returns a hardcoded value instead of computing the real result +- A trait implementation where methods log "not implemented" or return empty/default values +- A struct that stores fields but never uses them for real work +- A file that exists to satisfy an import but contains no real logic +- A `// TODO: implement` comment followed by a dummy return +- A driver that creates synthetic data (fake EDID, hardcoded modes) instead of querying the hardware +- A `todo!()` or `unimplemented!()` macro anywhere in non-test code + +### The rule + +> **As soon as a stub is detected, it must be replaced with real, sophisticated, comprehensive code +> in the same work session.** No exceptions. No "I'll come back to it." No "this is good enough for now." + +A stub found in the tree is a **bug** — not a precedent to follow, not a placeholder to defer, +not an acceptable intermediate state. Every stub is technical debt that compounds daily. + +### Required response to a stub + +When you encounter a stub: + +1. **Stop what you are doing.** The stub takes priority. +2. **Understand the interface.** Read the trait, API contract, or specification the stub is supposed to implement. +3. **Implement it comprehensively.** Real logic, real hardware communication, real error handling, real edge cases. +4. **Verify it works.** Run `cargo check`, tests, or build verification. + +| Stub pattern | Required fix | +|---|---| +| `fn detect_connectors() -> Vec { vec![] }` | Implement real hardware enumeration with proper protocol | +| `fn handle_irq() -> Ok(None)` | Implement real interrupt handling with event dispatch | +| `synthetic_edid()` when hardware can provide real EDID | Query the device via the proper protocol | +| Hardcoded mode "1280x720" | Query the display hardware for actual supported modes | +| `_firmware: HashMap>` (unused parameter) | Use the firmware data in device initialization | +| `Ok(self.vblank_count.fetch_add(1, Ordering::SeqCst))` in page_flip | Submit real buffer to hardware and wait for display | +| `todo!()` / `unimplemented!()` | Replace with full implementation | + +### Why this matters + +Stubs are worse than missing code because they: + +- **Hide missing functionality** — the system appears to work but silently does nothing +- **Block real testing** — you can't verify behavior against hardware when the code doesn't talk to hardware +- **Create false confidence** — "it compiles" becomes a substitute for "it works" +- **Compound over time** — one stub leads to another as callers assume the interface is real +- **Waste debugging time** — hours spent tracing why something "doesn't display" when the driver never sent a command + +### Enforcement + +- Code reviews must reject any PR containing stubs +- Any agent or developer that introduces a stub must replace it before the session ends +- If a stub cannot be replaced (missing specification, blocked dependency), document it as a known gap in `local/docs/` — but never leave it in the code as a stub. Remove the code path entirely and add a clear error message instead. + ## LINUX REFERENCE SOURCE POLICY `local/reference/linux-7.0/` (or later) contains a full Linux kernel source tree for diff --git a/config/redbear-full.toml b/config/redbear-full.toml index 6856d65ba1..77035f90c8 100644 --- a/config/redbear-full.toml +++ b/config/redbear-full.toml @@ -47,14 +47,21 @@ numad = {} redox-drm = {} mesa = {} libdrm = {} +libepoxy = {} +libdisplay-info = {} +libxcvt = {} +lcms2 = {} +freetype2 = {} +fontconfig = {} libwayland = {} wayland-protocols = {} -redbear-compositor = {} +plasma-wayland-protocols = {} +redbear-compositor = "ignore" # replaced by kwin # Keyboard/input -# libxkbcommon = {} # build needed -# xkeyboard-config = {} # build needed +libxkbcommon = {} +xkeyboard-config = {} libevdev = {} libinput = {} redbear-keymapd = {} @@ -93,7 +100,7 @@ kf6-kded6 = {} kf6-kguiaddons = {} kf6-ki18n = {} kf6-kiconthemes = {} -kf6-kidletime = "ignore" +kf6-kidletime = {} kf6-kitemmodels = {} kf6-kitemviews = {} kf6-kjobwidgets = {} @@ -101,12 +108,14 @@ kf6-knotifications = {} kf6-kpackage = {} kf6-kservice = {} kf6-ktextwidgets = {} -kf6-kwayland = "ignore" +kf6-kwayland = {} kf6-kwidgetsaddons = {} +kf6-kwindowsystem = {} kf6-kxmlgui = {} kf6-prison = {} kf6-solid = {} kf6-sonnet = {} +kf6-ksvg = {} kf6-knewstuff = {} kf6-kwallet = {} kf6-kglobalaccel = {} @@ -186,7 +195,7 @@ name = "redox-drm" description = "DRM/KMS display driver (AMD + Intel + VirtIO)" priority = 60 command = ["/usr/bin/redox-drm"] -depends_on = ["pci", "firmware", "iommu"] +depends_on = ["pci"] [[driver.match]] class = 0x03 @@ -246,6 +255,21 @@ cmd = "/usr/bin/iommu" type = "oneshot_async" """ +[[files]] +path = "/etc/init.d/14_redox-drm.service" +data = """ +[unit] +description = "DRM/KMS display driver (AMD + Intel + VirtIO)" +requires_weak = [ + "05_boot-essential.target", +] + +[service] +cmd = "/usr/bin/sh" +args = ["-c", "if ! head -c 1 /scheme/drm/card0 >/dev/null 2>&1; then exec /usr/bin/redox-drm; fi; echo 'scheme:drm already registered, skipping'"] +type = "oneshot_async" +""" + [[files]] path = "/etc/init.d/12_dbus.service" data = """ @@ -395,25 +419,6 @@ envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/pl type = "oneshot_async" """ -[[files]] -path = "/etc/init.d/20_display.service" -data = """ -[unit] -description = "KDE session assembly helper" -requires_weak = [ - "12_dbus.service", - "13_redbear-sessiond.service", - "13_seatd.service", - "19_redbear-authd.service", -] - -[service] -cmd = "/usr/bin/redbear-session-launch" -args = ["--username", "root", "--mode", "session", "--session", "kde-wayland", "--vt", "4", "--runtime-dir", "/tmp/run/redbear-display-session", "--wayland-display", "wayland-display"] -envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/plugins/platforms", QML2_IMPORT_PATH = "/usr/qml", XCURSOR_THEME = "Pop", XKB_CONFIG_ROOT = "/usr/share/X11/xkb", REDBEAR_KDE_SESSION_BACKEND = "virtual", REDBEAR_KDE_SESSION_STATE_DIR = "/run/redbear-display-session" } -type = "oneshot_async" -""" - [[files]] path = "/etc/init.d/20_greeter.service" data = """ @@ -421,6 +426,7 @@ data = """ description = "Red Bear greeter service" requires_weak = [ "00_driver-manager.service", + "14_redox-drm.service", "12_dbus.service", "13_redbear-sessiond.service", "13_seatd.service", diff --git a/local/config/drivers.d/30-graphics.toml b/local/config/drivers.d/30-graphics.toml index 37356f2da9..51c037409c 100644 --- a/local/config/drivers.d/30-graphics.toml +++ b/local/config/drivers.d/30-graphics.toml @@ -15,14 +15,25 @@ description = "DRM/KMS display driver (AMD + Intel + VirtIO)" priority = 60 command = ["/usr/bin/redox-drm"] +# Only match known GPU vendors. Class 0x03 alone catches QEMU VGA +# (vendor 0x1234) which redox-drm rejects with a fatal error. [[driver.match]] +vendor = 0x1002 +class = 0x03 + +[[driver.match]] +vendor = 0x8086 +class = 0x03 + +[[driver.match]] +vendor = 0x1AF4 class = 0x03 [[driver]] -name = "virtio-gpud" -description = "VirtIO GPU driver" -priority = 60 -command = ["/usr/lib/drivers/virtio-gpud"] +name = "redox-drm-virtio" +description = "VirtIO GPU DRM/KMS driver" +priority = 61 +command = ["/usr/bin/redox-drm"] [[driver.match]] vendor = 0x1AF4 diff --git a/local/config/drivers.d/70-usb-class.toml b/local/config/drivers.d/70-usb-class.toml index afaa5dd83b..0f72daeeb9 100644 --- a/local/config/drivers.d/70-usb-class.toml +++ b/local/config/drivers.d/70-usb-class.toml @@ -6,18 +6,26 @@ description = "USB CDC ACM serial driver" priority = 70 command = ["/usr/bin/redbear-acmd"] +[[driver.match]] +vendor = 0xFFFF +device = 0xFFFF + [[driver]] name = "redbear-ecmd" description = "USB CDC ECM/NCM ethernet driver" priority = 70 command = ["/usr/bin/redbear-ecmd"] +[[driver.match]] +vendor = 0xFFFF +device = 0xFFFF + [[driver]] name = "redbear-usbaudiod" description = "USB Audio Class driver" priority = 70 command = ["/usr/bin/redbear-usbaudiod"] -# USB class drivers are spawned by the USB host controller (xhcid/ehcid) -# when matching USB devices are detected, not by PCI bus scanning. -# Match entries below use USB interface class codes for host-controller-side matching. +[[driver.match]] +vendor = 0xFFFF +device = 0xFFFF diff --git a/local/patches/base/P4-pcid-config-scheme.patch b/local/patches/base/P4-pcid-config-scheme.patch new file mode 100644 index 0000000000..2ad8240f10 --- /dev/null +++ b/local/patches/base/P4-pcid-config-scheme.patch @@ -0,0 +1,200 @@ +diff --git a/drivers/pcid/src/cfg_access/fallback.rs b/drivers/pcid/src/cfg_access/fallback.rs +--- a/drivers/pcid/src/cfg_access/fallback.rs ++++ b/drivers/pcid/src/cfg_access/fallback.rs +@@ -61,8 +61,11 @@ + + Self::set_iopl(); + +- let offset = +- u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); ++ let Ok(offset) = u8::try_from(offset) else { ++ // Offset exceeds 256-byte PCI 3.0 config space — return all-ones ++ // (standard response for non-existent config space, matching Linux behavior). ++ return 0xFFFF_FFFF; ++ }; + let address = Self::address(address, offset); + + Pio::::new(0xCF8).write(address); +@@ -74,8 +77,9 @@ + + Self::set_iopl(); + +- let offset = +- u8::try_from(offset).expect("offset too large for PCI 3.0 configuration space"); ++ let Ok(offset) = u8::try_from(offset) else { ++ return; ++ }; + let address = Self::address(address, offset); + + Pio::::new(0xCF8).write(address); +diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs +--- a/drivers/pcid/src/scheme.rs ++++ b/drivers/pcid/src/scheme.rs +@@ -22,6 +22,7 @@ + Access, + Device, + Channel { addr: PciAddress, st: ChannelState }, ++ Config { addr: PciAddress }, + SchemeRoot, + } + struct HandleWrapper { +@@ -30,14 +31,20 @@ + } + impl Handle { + fn is_file(&self) -> bool { +- matches!(self, Self::Access | Self::Channel { .. }) ++ matches!( ++ self, ++ Self::Access | Self::Channel { .. } | Self::Config { .. } ++ ) + } + fn is_dir(&self) -> bool { + !self.is_file() + } + // TODO: capability rather than root + fn requires_root(&self) -> bool { +- matches!(self, Self::Access | Self::Channel { .. }) ++ matches!( ++ self, ++ Self::Access | Self::Channel { .. } | Self::Config { .. } ++ ) + } + fn is_scheme_root(&self) -> bool { + matches!(self, Self::SchemeRoot) +@@ -49,7 +56,8 @@ + AwaitingResponseRead(VecDeque), + } + +-const DEVICE_CONTENTS: &[&str] = &["channel"]; ++const DEVICE_CONTENTS: &[&str] = &["channel", "config"]; ++const STANDARD_CONFIG_SPACE_SIZE: usize = 256; + + impl PciScheme { + pub fn access(&mut self) -> usize { +@@ -133,6 +141,7 @@ + Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), + Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), + Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), ++ Handle::Config { .. } => (STANDARD_CONFIG_SPACE_SIZE, MODE_CHR | 0o600), + Handle::SchemeRoot => return Err(Error::new(EBADF)), + }; + stat.st_size = len as u64; +@@ -143,7 +152,7 @@ + &mut self, + id: usize, + buf: &mut [u8], +- _offset: u64, ++ offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { +@@ -160,6 +169,7 @@ + addr: _, + ref mut st, + } => Self::read_channel(st, buf), ++ Handle::Config { addr } => Self::read_config(&self.pcie, addr, buf, offset), + Handle::SchemeRoot => Err(Error::new(EBADF)), + _ => Err(Error::new(EBADF)), + } +@@ -193,7 +203,9 @@ + return Ok(buf); + } + Handle::Device => DEVICE_CONTENTS, +- Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), ++ Handle::Access | Handle::Channel { .. } | Handle::Config { .. } => { ++ return Err(Error::new(ENOTDIR)); ++ } + Handle::SchemeRoot => return Err(Error::new(EBADF)), + }; + +@@ -212,7 +224,7 @@ + &mut self, + id: usize, + buf: &[u8], +- _offset: u64, ++ offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { +@@ -226,6 +238,7 @@ + Handle::Channel { addr, ref mut st } => { + Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) + } ++ Handle::Config { addr } => Self::write_config(&self.pcie, addr, buf, offset), + + _ => Err(Error::new(EBADF)), + } +@@ -356,11 +369,73 @@ + st: ChannelState::AwaitingData, + } + } ++ "config" => Handle::Config { addr }, + _ => return Err(Error::new(ENOENT)), + } + }) + } + ++ fn read_config(pcie: &Pcie, addr: PciAddress, buf: &mut [u8], offset: u64) -> Result { ++ let start = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?; ++ let end = start.saturating_add(buf.len()).min(STANDARD_CONFIG_SPACE_SIZE); ++ if start >= end { ++ return Ok(0); ++ } ++ ++ let aligned_start = start & !0x3; ++ let aligned_end = (end + 3) & !0x3; ++ ++ let mut bytes_read = 0; ++ for dword_offset in (aligned_start..aligned_end).step_by(4) { ++ let bytes = unsafe { pcie.read(addr, dword_offset as u16) }.to_le_bytes(); ++ ++ let chunk_start = start.max(dword_offset); ++ let chunk_end = end.min(dword_offset + bytes.len()); ++ let src_start = chunk_start - dword_offset; ++ let dst_start = chunk_start - start; ++ let len = chunk_end - chunk_start; ++ ++ buf[dst_start..dst_start + len].copy_from_slice(&bytes[src_start..src_start + len]); ++ bytes_read += len; ++ } ++ ++ Ok(bytes_read) ++ } ++ ++ fn write_config(pcie: &Pcie, addr: PciAddress, buf: &[u8], offset: u64) -> Result { ++ let start = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?; ++ let end = start.saturating_add(buf.len()).min(STANDARD_CONFIG_SPACE_SIZE); ++ if start >= end { ++ return Ok(0); ++ } ++ ++ let aligned_start = start & !0x3; ++ let aligned_end = (end + 3) & !0x3; ++ ++ let mut bytes_written = 0; ++ for dword_offset in (aligned_start..aligned_end).step_by(4) { ++ let chunk_start = start.max(dword_offset); ++ let chunk_end = end.min(dword_offset + 4); ++ let dst_start = chunk_start - dword_offset; ++ let src_start = chunk_start - start; ++ let len = chunk_end - chunk_start; ++ ++ let mut bytes = if dst_start == 0 && len == 4 { ++ [0; 4] ++ } else { ++ unsafe { pcie.read(addr, dword_offset as u16) }.to_le_bytes() ++ }; ++ bytes[dst_start..dst_start + len].copy_from_slice(&buf[src_start..src_start + len]); ++ ++ unsafe { ++ pcie.write(addr, dword_offset as u16, u32::from_le_bytes(bytes)); ++ } ++ bytes_written += len; ++ } ++ ++ Ok(bytes_written) ++ } ++ + fn read_channel(state: &mut ChannelState, buf: &mut [u8]) -> Result { + match *state { + ChannelState::AwaitingResponseRead(ref mut queue) => { diff --git a/local/patches/kwin/P0-disable-qml-quick.patch b/local/patches/kwin/P0-disable-qml-quick.patch index 27c93e36cf..8cf0c7d40b 100644 --- a/local/patches/kwin/P0-disable-qml-quick.patch +++ b/local/patches/kwin/P0-disable-qml-quick.patch @@ -1,47 +1,54 @@ --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -34,7 +34,9 @@ +@@ -34,20 +34,29 @@ include(KDEClangFormat) include(KDEGitCommitHooks) --include(ECMFindQmlModule) +if(KWIN_BUILD_QML_UI) -+ include(ECMFindQmlModule) + include(ECMFindQmlModule) +endif() include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) include(ECMConfiguredInstall) -@@ -42,7 +44,9 @@ + include(ECMQtDeclareLoggingCategory) include(ECMSetupQtPluginMacroNames) include(ECMSetupVersion) - include(ECMQmlModule) --include(ECMGenerateQmlTypes) +if(KWIN_BUILD_QML_UI) -+ include(ECMGenerateQmlTypes) + include(ECMQmlModule) ++endif() ++if(KWIN_BUILD_QML_UI) + include(ECMGenerateQmlTypes) +endif() include(ECMDeprecationSettings) option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) -@@ -54,18 +58,25 @@ - option(KWIN_BUILD_X11_BACKEND "Enable building kwin_x11" ON) + option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) + option(KWIN_BUILD_NOTIFICATIONS "Enable building of KWin with knotifications support" ON) ++ ++option(KWIN_BUILD_QML_UI "Enable building of KWin QML UI components." ON) ++ + option(KWIN_BUILD_SCREENLOCKER "Enable building of KWin lockscreen functionality" ON) + option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) + option(KWIN_BUILD_X11 "Enable building X11 common code and Xwayland support" ON) +@@ -55,17 +64,21 @@ option(KWIN_BUILD_GLOBALSHORTCUTS "Enable building of KWin with global shortcuts support" ON) option(KWIN_BUILD_RUNNERS "Enable building of KWin with krunner support" ON) -- + -find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS -+option(KWIN_BUILD_QML_UI "Enable building of KWin QML UI components (OSD, outline, scripted effects). Requires Qt6::Quick." ON) -+ +set(KWIN_QT6_REQUIRED_COMPONENTS Concurrent Core Core5Compat DBus +- Quick +- UiTools WaylandClient Widgets - Sensors +- Sensors Svg +) +if(KWIN_BUILD_QML_UI) -+ list(APPEND KWIN_QT6_REQUIRED_COMPONENTS Quick) ++ list(APPEND KWIN_QT6_REQUIRED_COMPONENTS Quick UiTools Sensors) +endif() + +find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS @@ -49,572 +56,325 @@ ) find_package(Qt6Test ${QT_MIN_VERSION} CONFIG QUIET) -@@ -144,18 +155,20 @@ +@@ -103,6 +116,7 @@ + ) + # required frameworks by config modules + if(KWIN_BUILD_KCMS) ++if(KWIN_BUILD_KCMS AND KWIN_BUILD_QML_UI) + find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS + Declarative + KCMUtils +@@ -111,6 +125,7 @@ + XmlGui + ) + endif() ++endif() + + if(KWIN_BUILD_TABBOX AND KWIN_BUILD_KCMS) + find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS +@@ -131,11 +146,13 @@ + ) + + # optional frameworks ++if(KWIN_BUILD_QML_UI) + find_package(PlasmaActivities ${PROJECT_DEP_VERSION} CONFIG) + set_package_properties(PlasmaActivities PROPERTIES + PURPOSE "Enable building of KWin with kactivities support" + TYPE OPTIONAL + ) ++endif() + add_feature_info("PlasmaActivities" PlasmaActivities_FOUND "Enable building of KWin with kactivities support") + + find_package(KF6DocTools ${KF6_MIN_VERSION} CONFIG) +@@ -145,18 +162,22 @@ ) add_feature_info("KF6DocTools" KF6DocTools_FOUND "Enable building documentation") --find_package(KF6Kirigami ${KF6_MIN_VERSION} CONFIG) --set_package_properties(KF6Kirigami PROPERTIES -- DESCRIPTION "A QtQuick based components set" -- PURPOSE "Required at runtime for several QML effects" -- TYPE RUNTIME --) --find_package(Plasma ${PROJECT_DEP_VERSION} CONFIG) --set_package_properties(Plasma PROPERTIES -- DESCRIPTION "A QtQuick based components set" -- PURPOSE "Required at runtime for several QML effects" -- TYPE RUNTIME --) +if(KWIN_BUILD_QML_UI) -+ find_package(KF6Kirigami ${KF6_MIN_VERSION} CONFIG) -+ set_package_properties(KF6Kirigami PROPERTIES -+ DESCRIPTION "A QtQuick based components set" -+ PURPOSE "Required at runtime for several QML effects" -+ TYPE RUNTIME -+ ) -+ find_package(Plasma ${PROJECT_DEP_VERSION} CONFIG) -+ set_package_properties(Plasma PROPERTIES -+ DESCRIPTION "A QtQuick based components set" -+ PURPOSE "Required at runtime for several QML effects" -+ TYPE RUNTIME -+ ) + find_package(KF6Kirigami ${KF6_MIN_VERSION} CONFIG) + set_package_properties(KF6Kirigami PROPERTIES + DESCRIPTION "A QtQuick based components set" + PURPOSE "Required at runtime for several QML effects" + TYPE RUNTIME + ) ++endif() ++if(KWIN_BUILD_QML_UI) + find_package(Plasma ${PROJECT_DEP_VERSION} CONFIG) + set_package_properties(Plasma PROPERTIES + DESCRIPTION "A QtQuick based components set" + PURPOSE "Required at runtime for several QML effects" + TYPE RUNTIME + ) +endif() find_package(KDecoration3 ${PROJECT_DEP_VERSION} CONFIG REQUIRED) -@@ -415,14 +428,16 @@ +@@ -221,12 +242,15 @@ + URL "https://gitlab.freedesktop.org/wayland/wayland-protocols/" + ) + ++if(KWIN_BUILD_QML_UI) + find_package(PlasmaWaylandProtocols 1.14.0 CONFIG) + set_package_properties(PlasmaWaylandProtocols PROPERTIES + TYPE REQUIRED + PURPOSE "Collection of Plasma-specific Wayland protocols" + URL "https://invent.kde.org/libraries/plasma-wayland-protocols/" + ) ++endif() ++ + + find_package(XKB 0.7.0) + set_package_properties(XKB PROPERTIES +@@ -239,7 +263,7 @@ + set(HAVE_XKBCOMMON_NO_SECURE_GETENV 0) + endif() + +-find_package(Canberra REQUIRED) ++find_package(Canberra) + + if (KWIN_BUILD_X11) + pkg_check_modules(XKBX11 IMPORTED_TARGET xkbcommon-x11 REQUIRED) +@@ -418,6 +442,7 @@ ) endif() --ecm_find_qmlmodule(QtQuick 2.3) --ecm_find_qmlmodule(QtQuick.Controls 2.15) --ecm_find_qmlmodule(QtQuick.Layouts 1.3) --ecm_find_qmlmodule(QtQuick.Window 2.1) --ecm_find_qmlmodule(QtMultimedia 5.0) --ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) --ecm_find_qmlmodule(org.kde.plasma.core 2.0) --ecm_find_qmlmodule(org.kde.plasma.components 2.0) +if(KWIN_BUILD_QML_UI) -+ ecm_find_qmlmodule(QtQuick 2.3) -+ ecm_find_qmlmodule(QtQuick.Controls 2.15) -+ ecm_find_qmlmodule(QtQuick.Layouts 1.3) -+ ecm_find_qmlmodule(QtQuick.Window 2.1) -+ ecm_find_qmlmodule(QtMultimedia 5.0) -+ ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) -+ ecm_find_qmlmodule(org.kde.plasma.core 2.0) -+ ecm_find_qmlmodule(org.kde.plasma.components 2.0) + ecm_find_qmlmodule(QtQuick 2.3) + ecm_find_qmlmodule(QtQuick.Controls 2.15) + ecm_find_qmlmodule(QtQuick.Layouts 1.3) +@@ -426,6 +451,7 @@ + ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) + ecm_find_qmlmodule(org.kde.plasma.core 2.0) + ecm_find_qmlmodule(org.kde.plasma.components 2.0) +endif() cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "PlasmaActivities_FOUND" OFF) cmake_dependent_option(KWIN_BUILD_EIS "Enable building KWin with libeis support" ON "Libeis-1.0_FOUND" OFF) + --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt -@@ -16,7 +16,26 @@ +@@ -16,7 +16,9 @@ target_link_libraries(KWinEffectsInterface Qt::DBus) add_subdirectory(helpers) --add_subdirectory(qml) +if(KWIN_BUILD_QML_UI) -+ add_subdirectory(qml) -+endif() -+ -+set(kwin_qml_ui_effect_sources) -+set(kwin_qml_ui_scripting_sources) -+if(KWIN_BUILD_QML_UI) -+ list(APPEND kwin_qml_ui_effect_sources -+ effect/effectframe.cpp -+ effect/offscreenquickview.cpp -+ effect/quickeffect.cpp -+ ) -+ list(APPEND kwin_qml_ui_scripting_sources -+ scripting/desktopbackgrounditem.cpp -+ scripting/gesturehandler.cpp -+ scripting/scriptedquicksceneeffect.cpp -+ scripting/shortcuthandler.cpp -+ scripting/windowthumbnailitem.cpp -+ ) + add_subdirectory(qml) +endif() if (KWIN_BUILD_KCMS) add_subdirectory(kcms) -@@ -87,15 +106,13 @@ - effect/anidata.cpp - effect/animationeffect.cpp +@@ -89,12 +91,12 @@ effect/effect.cpp -- effect/effectframe.cpp + effect/effectframe.cpp effect/effecthandler.cpp - effect/effectloader.cpp +- effect/effectloader.cpp ++ $<$:effect/effectloader.cpp> effect/effecttogglablestate.cpp effect/effectwindow.cpp effect/logging.cpp effect/offscreeneffect.cpp - effect/offscreenquickview.cpp -- effect/quickeffect.cpp -+ ${kwin_qml_ui_effect_sources} ++ $<$:effect/offscreenquickview.cpp> + effect/quickeffect.cpp effect/timeline.cpp focuschain.cpp - ftrace.cpp -@@ -183,19 +200,15 @@ - scene/workspacescene_qpainter.cpp - screenedge.cpp - scripting/dbuscall.cpp -- scripting/desktopbackgrounditem.cpp -- scripting/gesturehandler.cpp -+ ${kwin_qml_ui_scripting_sources} +@@ -147,7 +149,7 @@ + options.cpp + osd.cpp + outline.cpp +- outputconfigurationstore.cpp ++ $<$:outputconfigurationstore.cpp> + placeholderinputeventfilter.cpp + placeholderoutput.cpp + placement.cpp +@@ -187,7 +189,7 @@ + scripting/gesturehandler.cpp scripting/screenedgehandler.cpp scripting/scriptedeffect.cpp - scripting/scriptedquicksceneeffect.cpp ++ $<$:scripting/scriptedquicksceneeffect.cpp> scripting/scripting.cpp scripting/scripting_logging.cpp scripting/scriptingutils.cpp -- scripting/shortcuthandler.cpp - scripting/tilemodel.cpp - scripting/virtualdesktopmodel.cpp - scripting/windowmodel.cpp -- scripting/windowthumbnailitem.cpp - scripting/workspace_wrapper.cpp - shadow.cpp - sm.cpp -@@ -227,7 +240,6 @@ +@@ -227,7 +229,7 @@ target_link_libraries(kwin PUBLIC Qt::DBus - Qt::Quick ++ $<$:Qt::Quick> Qt::Widgets Wayland::Server KF6::ConfigCore -@@ -243,11 +255,9 @@ +@@ -238,16 +240,16 @@ + PRIVATE + Qt::Concurrent + Qt::GuiPrivate +- Qt::Sensors ++ $<$:Qt::Sensors> + Qt::Svg KF6::ColorScheme KF6::ConfigGui - KF6::ConfigQml ++ $<$:KF6::ConfigQml> KF6::Crash KF6::GlobalAccel KF6::I18n - KF6::I18nQml ++ $<$:KF6::I18nQml> KF6::Package KF6::Service -@@ -263,6 +273,15 @@ - lcms2::lcms2 - PkgConfig::libdisplayinfo - ) -+ -+if(KWIN_BUILD_QML_UI) -+ target_link_libraries(kwin -+ PRIVATE -+ Qt::Quick -+ KF6::ConfigQml -+ KF6::I18nQml -+ ) -+endif() - - if (TARGET K::KGlobalAccelD) - target_link_libraries(kwin PRIVATE K::KGlobalAccelD) -@@ -617,11 +636,16 @@ +@@ -596,7 +598,7 @@ + utils/filedescriptor.h + utils/kernel.h + utils/memorymap.h +- utils/orientationsensor.h ++ $<$:utils/orientationsensor.h> + utils/ramfile.h + utils/realtime.h + utils/resource.h +@@ -617,7 +619,7 @@ effect/effectwindow.h effect/globals.h effect/offscreeneffect.h - effect/offscreenquickview.h -- effect/quickeffect.h ++ $<$:effect/offscreenquickview.h> + effect/quickeffect.h effect/timeline.h effect/xcb.h - DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kwin/effect COMPONENT Devel) -+ + +--- a/src/plugins/CMakeLists.txt ++++ b/src/plugins/CMakeLists.txt +@@ -45,7 +45,9 @@ + file(COPY ${source}/contents ${source}/metadata.json DESTINATION ${CMAKE_BINARY_DIR}/bin/kwin/scripts/${name}) + endfunction() + +if(KWIN_BUILD_QML_UI) -+ install(FILES -+ effect/offscreenquickview.h -+ effect/quickeffect.h -+ DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kwin/effect COMPONENT Devel) + add_subdirectory(private) +endif() - install(FILES - opengl/abstract_opengl_context_attribute_builder.h ---- a/src/config-kwin.h.cmake -+++ b/src/config-kwin.h.cmake -@@ -10,6 +10,7 @@ - #cmakedefine01 KWIN_BUILD_TABBOX - #cmakedefine01 KWIN_BUILD_ACTIVITIES - #cmakedefine01 KWIN_BUILD_GLOBALSHORTCUTS -+#cmakedefine01 KWIN_BUILD_QML_UI - #cmakedefine01 KWIN_BUILD_X11 - constexpr QLatin1String KWIN_CONFIG("kwinrc"); - constexpr QLatin1String KWIN_VERSION_STRING("${PROJECT_VERSION}"); ---- a/src/main.cpp -+++ b/src/main.cpp -@@ -53,7 +53,9 @@ - #include - // Qt - #include -+#if KWIN_BUILD_QML_UI - #include -+#endif - #if KWIN_BUILD_X11 - #include - #endif -@@ -245,7 +247,9 @@ - void Application::createWorkspace() - { - // we want all QQuickWindows with an alpha buffer, do here as Workspace might create QQuickWindows -+#if KWIN_BUILD_QML_UI - QQuickWindow::setDefaultAlphaBuffer(true); + add_subdirectory(backgroundcontrast) + add_subdirectory(blendchanges) +@@ -54,7 +56,9 @@ + add_subdirectory(buttonrebinds) + add_subdirectory(colorblindnesscorrection) + add_subdirectory(colorpicker) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(desktopchangeosd) ++endif() + add_subdirectory(dialogparent) + add_subdirectory(diminactive) + add_subdirectory(dimscreen) +@@ -81,18 +85,28 @@ + add_subdirectory(mouseclick) + add_subdirectory(mousemark) + add_subdirectory(nightlight) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(outputlocator) ++endif() ++if(KWIN_BUILD_QML_UI) + add_subdirectory(overview) ++endif() + add_subdirectory(qpa) + add_subdirectory(scale) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(screenedge) ++endif() + add_subdirectory(screenshot) + add_subdirectory(screentransform) + add_subdirectory(sessionquit) + add_subdirectory(shakecursor) + add_subdirectory(sheet) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(showcompositing) ++endif() ++if(KWIN_BUILD_QML_UI) + add_subdirectory(showfps) ++endif() + add_subdirectory(showpaint) + add_subdirectory(slide) + add_subdirectory(slideback) +@@ -104,14 +118,18 @@ + add_subdirectory(synchronizeskipswitcher) + add_subdirectory(systembell) + add_subdirectory(thumbnailaside) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(tileseditor) ++endif() + add_subdirectory(touchpoints) + add_subdirectory(trackmouse) + add_subdirectory(translucency) + add_subdirectory(videowall) + add_subdirectory(windowaperture) + add_subdirectory(windowsystem) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(windowview) ++endif() + add_subdirectory(wobblywindows) + add_subdirectory(zoom) + +@@ -120,7 +138,9 @@ + endif() + + if (KWIN_BUILD_DECORATIONS) ++if(KWIN_BUILD_QML_UI) + add_subdirectory(kdecorations) ++endif() + endif() + if (PipeWire_FOUND) + add_subdirectory(screencast) + +--- a/src/helpers/CMakeLists.txt ++++ b/src/helpers/CMakeLists.txt +@@ -1,2 +1,5 @@ ++if(KWIN_BUILD_X11) + add_subdirectory(killer) ++endif() ++ + add_subdirectory(wayland_wrapper) + +--- a/src/helpers/wayland_wrapper/wl-socket.c ++++ b/src/helpers/wayland_wrapper/wl-socket.c +@@ -19,6 +19,11 @@ + #include + #include + #include ++ ++#ifndef SUN_LEN ++#define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) + strlen((ptr)->sun_path)) +#endif ++ + #include - // This tries to detect compositing options and can use GLX. GLX problems - // (X errors) shouldn't cause kwin to abort, so this is out of the -@@ -357,9 +361,13 @@ - - std::unique_ptr Application::createOutline(Outline *outline) - { -+#if KWIN_BUILD_QML_UI - if (Compositor::compositing()) { - return std::make_unique(outline); - } -+#else -+ Q_UNUSED(outline) -+#endif - return nullptr; - } - ---- a/src/compositor_wayland.cpp -+++ b/src/compositor_wayland.cpp -@@ -35,7 +35,9 @@ - #include - #endif - #include -+#if KWIN_BUILD_QML_UI - #include -+#endif - - namespace KWin - { -@@ -207,6 +209,7 @@ - if (m_selectedCompositor == NoCompositing) { - m_selectedCompositor = m_backend->compositingType(); - -+#if KWIN_BUILD_QML_UI - switch (m_selectedCompositor) { - case NoCompositing: - break; -@@ -217,6 +220,7 @@ - QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); - break; - } -+#endif - } - - createScene(); ---- a/src/onscreennotification.cpp -+++ b/src/onscreennotification.cpp -@@ -13,10 +13,12 @@ - #include "input_event_spy.h" - - #include -+#if KWIN_BUILD_QML_UI - #include - #include - #include - #include -+#endif - #include + /* This is the size of the char array in struct sock_addr_un. + +--- a/src/cursor.cpp ++++ b/src/cursor.cpp +@@ -30,7 +30,9 @@ + #include #include -@@ -67,10 +69,12 @@ - - OnScreenNotification::~OnScreenNotification() - { -+#if KWIN_BUILD_QML_UI - if (QQuickWindow *w = qobject_cast(m_mainItem.get())) { - w->hide(); - w->destroy(); - } ++#if KWIN_BUILD_X11 + #include +#endif - } - - void OnScreenNotification::setConfig(KSharedConfigPtr config) -@@ -80,7 +84,11 @@ - - void OnScreenNotification::setEngine(QQmlEngine *engine) - { -+#if KWIN_BUILD_QML_UI - m_qmlEngine = engine; -+#else -+ Q_UNUSED(engine) -+#endif - } - - bool OnScreenNotification::isVisible() const -@@ -142,9 +150,11 @@ - void OnScreenNotification::show() - { - Q_ASSERT(m_visible); -+#if KWIN_BUILD_QML_UI - ensureQmlContext(); - ensureQmlComponent(); - createInputSpy(); -+#endif - if (m_timer->interval() != 0) { - m_timer->start(); - } -@@ -152,16 +162,19 @@ - - void OnScreenNotification::ensureQmlContext() - { -+#if KWIN_BUILD_QML_UI - Q_ASSERT(m_qmlEngine); - if (m_qmlContext) { - return; - } - m_qmlContext = std::make_unique(m_qmlEngine); - m_qmlContext->setContextProperty(QStringLiteral("osd"), this); -+#endif - } - - void OnScreenNotification::ensureQmlComponent() - { -+#if KWIN_BUILD_QML_UI - Q_ASSERT(m_config); - Q_ASSERT(m_qmlEngine); - if (m_qmlComponent) { -@@ -179,10 +192,12 @@ - } else { - m_qmlComponent.reset(); - } -+#endif - } - - void OnScreenNotification::createInputSpy() - { -+#if KWIN_BUILD_QML_UI - Q_ASSERT(!m_spy); - if (auto w = qobject_cast(m_mainItem.get())) { - m_spy = std::make_unique(this); -@@ -195,13 +210,16 @@ - m_animation->setEasingCurve(QEasingCurve::InOutCubic); - } - } -+#endif - } - - QRect OnScreenNotification::geometry() const - { -+#if KWIN_BUILD_QML_UI - if (QQuickWindow *w = qobject_cast(m_mainItem.get())) { - return w->geometry(); - } -+#endif - return QRect(); - } - -@@ -220,9 +238,13 @@ - - void OnScreenNotification::setSkipCloseAnimation(bool skip) - { -+#if KWIN_BUILD_QML_UI - if (QQuickWindow *w = qobject_cast(m_mainItem.get())) { - w->setProperty("KWIN_SKIP_CLOSE_ANIMATION", skip); - } -+#else -+ Q_UNUSED(skip) -+#endif - } - - } // namespace KWin ---- a/src/outline.cpp -+++ b/src/outline.cpp -@@ -18,10 +18,12 @@ - #include - // Qt - #include -+#if KWIN_BUILD_QML_UI - #include - #include - #include - #include -+#endif - #include namespace KWin -@@ -153,14 +155,17 @@ - - void CompositedOutlineVisual::hide() { -+#if KWIN_BUILD_QML_UI - if (QQuickWindow *w = qobject_cast(m_mainItem.get())) { - w->hide(); - w->destroy(); - } -+#endif - } - - void CompositedOutlineVisual::show() - { -+#if KWIN_BUILD_QML_UI - if (!m_qmlContext) { - m_qmlContext = std::make_unique(Scripting::self()->qmlEngine()); - m_qmlContext->setContextProperty(QStringLiteral("outline"), m_outline); -@@ -183,6 +188,7 @@ - w->setProperty("__kwin_outline", true); - } - } -+#endif - } - - } // namespace ---- a/src/osd.cpp -+++ b/src/osd.cpp -@@ -10,7 +10,9 @@ - #include "scripting/scripting.h" - #include "workspace.h" - -+#if KWIN_BUILD_QML_UI - #include -+#endif - #include - - namespace KWin -@@ -22,7 +24,9 @@ - { - auto osd = new OnScreenNotification(workspace()); - osd->setConfig(kwinApp()->config()); -+#if KWIN_BUILD_QML_UI - osd->setEngine(Scripting::self()->qmlEngine()); -+#endif - return osd; - } - ---- a/src/effect/effectloader.cpp -+++ b/src/effect/effectloader.cpp -@@ -15,7 +15,9 @@ - #include "effect/effecthandler.h" - #include "plugin.h" - #include "scripting/scriptedeffect.h" -+#if KWIN_BUILD_QML_UI - #include "scripting/scriptedquicksceneeffect.h" -+#endif - #include "scripting/scripting.h" - #include "utils/common.h" - // KDE -@@ -26,8 +28,10 @@ - #include - #include - #include -+#if KWIN_BUILD_QML_UI - #include - #include -+#endif - #include - #include - #include -@@ -131,7 +135,12 @@ - if (api == QLatin1String("javascript")) { - return loadJavascriptEffect(effect); - } else if (api == QLatin1String("declarativescript")) { -+#if KWIN_BUILD_QML_UI - return loadDeclarativeEffect(effect); -+#else -+ qCDebug(KWIN_CORE) << "Skipping declarative effect because KWIN_BUILD_QML_UI is OFF:" << name; -+ return false; -+#endif - } else { - qCWarning(KWIN_CORE, "Failed to load %s effect: invalid X-Plasma-API field: %s. " - "Available options are javascript, and declarativescript", qPrintable(name), qPrintable(api)); -@@ -165,6 +174,10 @@ - - bool ScriptedEffectLoader::loadDeclarativeEffect(const KPluginMetaData &metadata) - { -+#if !KWIN_BUILD_QML_UI -+ Q_UNUSED(metadata) -+ return false; -+#else - const QString name = metadata.pluginId(); - const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, - QLatin1String("kwin/effects/") + name + QLatin1String("/contents/ui/main.qml")); -@@ -199,6 +212,7 @@ - Q_EMIT effectLoaded(effect, name); - m_loadedEffects << name; - return true; -+#endif - } - - void ScriptedEffectLoader::queryAndLoadAll() ---- a/src/effect/offscreenquickview.cpp -+++ b/src/effect/offscreenquickview.cpp -@@ -8,6 +8,9 @@ - */ - - #include "effect/offscreenquickview.h" -+#include "config-kwin.h" -+ -+#if KWIN_BUILD_QML_UI - #include "effect/effecthandler.h" - - #include "logging_p.h" -@@ -643,3 +646,4 @@ - } // namespace KWin - - #include "moc_offscreenquickview.cpp" -+#endif ---- a/src/effect/quickeffect.cpp -+++ b/src/effect/quickeffect.cpp -@@ -5,6 +5,9 @@ - */ - - #include "effect/quickeffect.h" -+#include "config-kwin.h" -+ -+#if KWIN_BUILD_QML_UI - #include "core/output.h" - #include "effect/effecthandler.h" - -@@ -629,3 +632,4 @@ - } // namespace KWin - - #include "moc_quickeffect.cpp" -+#endif ---- a/src/scripting/scriptedquicksceneeffect.cpp -+++ b/src/scripting/scriptedquicksceneeffect.cpp -@@ -5,6 +5,9 @@ - */ - - #include "scripting/scriptedquicksceneeffect.h" -+#include "config-kwin.h" -+ -+#if KWIN_BUILD_QML_UI - #include "main.h" - - #include -@@ -130,3 +133,4 @@ - } // namespace KWin - - #include "moc_scriptedquicksceneeffect.cpp" -+#endif + --- a/src/scripting/scripting.cpp +++ b/src/scripting/scripting.cpp -@@ -12,17 +12,19 @@ - #include "scripting.h" - // own - #include "dbuscall.h" -+#if KWIN_BUILD_QML_UI - #include "desktopbackgrounditem.h" +@@ -16,7 +16,9 @@ #include "effect/quickeffect.h" #include "gesturehandler.h" -+#include "scriptedquicksceneeffect.h" -+#include "shortcuthandler.h" -+#include "windowthumbnailitem.h" -+#endif #include "screenedgehandler.h" --#include "scriptedquicksceneeffect.h" ++#if KWIN_BUILD_QML_UI + #include "scriptedquicksceneeffect.h" ++#endif #include "scripting_logging.h" #include "scriptingutils.h" --#include "shortcuthandler.h" - #include "virtualdesktopmodel.h" - #include "windowmodel.h" --#include "windowthumbnailitem.h" - #include "workspace_wrapper.h" - - #include "core/output.h" -@@ -35,10 +37,14 @@ + #include "shortcuthandler.h" +@@ -35,7 +37,9 @@ #include "workspace.h" // KDE #include @@ -623,142 +383,33 @@ +#endif #include #include -+#if KWIN_BUILD_QML_UI #include -+#endif - #include - // Qt - #include -@@ -46,10 +52,12 @@ - #include - #include - #include +@@ -655,6 +659,7 @@ + qmlRegisterType("org.kde.kwin", 3, 0, "WindowFilterModel"); + qmlRegisterType("org.kde.kwin", 3, 0, "VirtualDesktopModel"); + qmlRegisterUncreatableType("org.kde.kwin", 3, 0, "SceneView", QStringLiteral("Can't instantiate an object of type SceneView")); +#if KWIN_BUILD_QML_UI - #include - #include - #include - #include -+#endif - #include - #include - #include -@@ -561,6 +569,7 @@ - return menu->menuAction(); - } + qmlRegisterType("org.kde.kwin", 3, 0, "SceneEffect"); -+#if KWIN_BUILD_QML_UI - KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject *parent) - : AbstractScript(id, scriptName, pluginName, parent) - , m_context(new QQmlContext(Scripting::self()->declarativeScriptSharedContext(), this)) -@@ -598,6 +607,7 @@ - } - setRunning(true); - } + qmlRegisterSingletonType("org.kde.kwin", 3, 0, "Workspace", [](QQmlEngine *qmlEngine, QJSEngine *jsEngine) { +@@ -667,6 +672,7 @@ + qmlRegisterAnonymousType("org.kde.kwin", 3); + qmlRegisterAnonymousType("org.kde.kwin", 3); + qmlRegisterAnonymousType("org.kde.kwin", 3); +#endif - - KWin::JSEngineGlobalMethodsWrapper::JSEngineGlobalMethodsWrapper(KWin::DeclarativeScript *parent) - : QObject(parent) -@@ -626,12 +636,19 @@ - KWin::Scripting::Scripting(QObject *parent) - : QObject(parent) - , m_scriptsLock(new QRecursiveMutex) -+#if KWIN_BUILD_QML_UI - , m_qmlEngine(new QQmlEngine(this)) - , m_declarativeScriptSharedContext(new QQmlContext(m_qmlEngine, this)) -+#else -+ , m_qmlEngine(nullptr) -+ , m_declarativeScriptSharedContext(nullptr) -+#endif - , m_workspaceWrapper(new QtScriptWorkspaceWrapper(this)) - { -+#if KWIN_BUILD_QML_UI - m_qmlEngine->setProperty("_kirigamiTheme", QStringLiteral("KirigamiPlasmaStyle")); - m_qmlEngine->rootContext()->setContextObject(new KLocalizedQmlContext(m_qmlEngine)); -+#endif - init(); - QDBusConnection::sessionBus().registerObject(QStringLiteral("/Scripting"), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); - connect(Workspace::self(), &Workspace::configChanged, this, &Scripting::start); -@@ -644,6 +661,7 @@ - qRegisterMetaType>(); - qRegisterMetaType>(); - -+#if KWIN_BUILD_QML_UI - qmlRegisterType("org.kde.kwin", 3, 0, "DesktopBackground"); - qmlRegisterType("org.kde.kwin", 3, 0, "WindowThumbnail"); - qmlRegisterType("org.kde.kwin", 3, 0, "DBusCall"); -@@ -671,6 +689,7 @@ + qmlRegisterAnonymousType("org.kde.kwin", 3); // TODO: call the qml types as the C++ types? qmlRegisterUncreatableType("org.kde.kwin", 3, 0, "CustomTile", QStringLiteral("Cannot create objects of type Tile")); - qmlRegisterUncreatableType("org.kde.kwin", 3, 0, "Tile", QStringLiteral("Cannot create objects of type AbstractTile")); -+#endif - } - - void KWin::Scripting::start() -@@ -821,6 +840,7 @@ - - int KWin::Scripting::loadDeclarativeScript(const QString &filePath, const QString &pluginName) - { -+#if KWIN_BUILD_QML_UI - QMutexLocker locker(m_scriptsLock.get()); - if (isScriptLoaded(pluginName)) { - return -1; -@@ -830,6 +850,11 @@ - connect(script, &QObject::destroyed, this, &Scripting::scriptDestroyed); - scripts.append(script); - return id; -+#else -+ Q_UNUSED(filePath) -+ Q_UNUSED(pluginName) -+ return -1; -+#endif - } - - KWin::Scripting::~Scripting() ---- a/src/scripting/workspace_wrapper.h -+++ b/src/scripting/workspace_wrapper.h -@@ -10,9 +10,12 @@ - - #pragma once - -+#include "config-kwin.h" - #include "effect/globals.h" - #include -+#if KWIN_BUILD_QML_UI - #include -+#endif - #include - #include - #include -@@ -406,6 +409,7 @@ - explicit QtScriptWorkspaceWrapper(QObject *parent = nullptr); - }; - -+#if KWIN_BUILD_QML_UI - class DeclarativeScriptWorkspaceWrapper : public WorkspaceWrapper - { - Q_OBJECT -@@ -418,5 +422,6 @@ - - explicit DeclarativeScriptWorkspaceWrapper(QObject *parent = nullptr); - }; -+#endif - - } ---- a/src/scripting/workspace_wrapper.cpp -+++ b/src/scripting/workspace_wrapper.cpp -@@ -437,6 +437,7 @@ - return workspace()->windows(); - } - -+#if KWIN_BUILD_QML_UI - QQmlListProperty DeclarativeScriptWorkspaceWrapper::windows() - { - return QQmlListProperty(this, nullptr, &DeclarativeScriptWorkspaceWrapper::countWindowList, &DeclarativeScriptWorkspaceWrapper::atWindowList); -@@ -456,6 +457,7 @@ - : WorkspaceWrapper(parent) - { - } -+#endif - - } // KWin - + +--- a/src/utils/CMakeLists.txt ++++ b/src/utils/CMakeLists.txt +@@ -4,7 +4,7 @@ + drm_format_helper.cpp + edid.cpp + filedescriptor.cpp +- orientationsensor.cpp ++ $<$:orientationsensor.cpp> + ramfile.cpp + realtime.cpp + softwarevsyncmonitor.cpp + diff --git a/local/patches/qtbase/futex-redox-support.patch b/local/patches/qtbase/futex-redox-support.patch new file mode 100644 index 0000000000..bd47176397 --- /dev/null +++ b/local/patches/qtbase/futex-redox-support.patch @@ -0,0 +1,126 @@ +diff --git a/src/corelib/thread/qfutex_p.h b/src/corelib/thread/qfutex_p.h +--- a/src/corelib/thread/qfutex_p.h ++++ b/src/corelib/thread/qfutex_p.h +@@ -41,6 +41,9 @@ + #elif defined(Q_OS_LINUX) && !defined(QT_LINUXBASE) + // use Linux mutexes everywhere except for LSB builds + # include "qfutex_linux_p.h" ++#elif defined(Q_OS_REDOX) ++// Redox kernel implements the same futex syscall as Linux (SYS_FUTEX = 202) ++# include "qfutex_redox_p.h" + #elif defined(Q_OS_WIN) + # include "qfutex_win_p.h" + #else +diff --git a/src/corelib/thread/qfutex_redox_p.h b/src/corelib/thread/qfutex_redox_p.h +new file mode 100644 +--- /dev/null ++++ b/src/corelib/thread/qfutex_redox_p.h +@@ -0,0 +1,97 @@ ++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ++ ++#ifndef QFUTEX_REDOX_P_H ++#define QFUTEX_REDOX_P_H ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#define QT_ALWAYS_USE_FUTEX ++ ++#ifndef __NR_futex ++# define __NR_futex 202 ++#endif ++ ++QT_BEGIN_NAMESPACE ++ ++namespace QtRedoxFutex { ++ ++constexpr inline bool futexAvailable() { return true; } ++ ++constexpr int RedoxFutexWait = 0; ++constexpr int RedoxFutexWake = 1; ++ ++inline long redox_syscall6(long nr, long a1, long a2, long a3, long a4, long a5, long a6) noexcept ++{ ++ register long r10 __asm__("r10") = a4; ++ register long r8 __asm__("r8") = a5; ++ register long r9 __asm__("r9") = a6; ++ long ret; ++ __asm__ __volatile__( ++ "syscall" ++ : "=a"(ret) ++ : "a"(nr), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) ++ : "rcx", "r11", "memory" ++ ); ++ return ret; ++} ++ ++inline long _q_futex(int *addr, int op, int val, quintptr val2 = 0, ++ int *addr2 = nullptr, int val3 = 0) noexcept ++{ ++ QtTsan::futexRelease(addr, addr2); ++ long result = redox_syscall6(__NR_futex, (long)addr, (long)op, (long)val, (long)val2, (long)addr2, (long)val3); ++ QtTsan::futexAcquire(addr, addr2); ++ return result; ++} ++ ++template int *addr(T *ptr) ++{ ++ int *int_addr = reinterpret_cast(ptr); ++#if Q_BYTE_ORDER == Q_BIG_ENDIAN ++ if (sizeof(T) > sizeof(int)) ++ int_addr++; ++#endif ++ return int_addr; ++} ++ ++template ++inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue) ++{ ++ _q_futex(addr(&futex), RedoxFutexWait, qintptr(expectedValue)); ++} ++ ++template ++inline bool futexWait(Atomic &futex, typename Atomic::Type expectedValue, QDeadlineTimer deadline) ++{ ++ auto timeout = deadline.deadline().time_since_epoch(); ++ struct timespec ts; ++ ts.tv_sec = timeout.count() < 0 ? 0 : static_cast(std::chrono::duration_cast(timeout).count()); ++ auto ns_remainder = timeout - std::chrono::seconds(ts.tv_sec); ++ ts.tv_nsec = static_cast(std::chrono::duration_cast(ns_remainder).count()); ++ long r = _q_futex(addr(&futex), RedoxFutexWait, qintptr(expectedValue), quintptr(&ts), ++ nullptr, 0); ++ return r == 0 || errno != ETIMEDOUT; ++} ++ ++template inline void futexWakeOne(Atomic &futex) ++{ ++ _q_futex(addr(&futex), RedoxFutexWake, 1); ++} ++ ++template inline void futexWakeAll(Atomic &futex) ++{ ++ _q_futex(addr(&futex), RedoxFutexWake, INT_MAX); ++} ++ ++} // namespace QtRedoxFutex ++ ++namespace QtFutex = QtRedoxFutex; ++ ++QT_END_NAMESPACE ++ ++#endif // QFUTEX_REDOX_P_H +diff --git a/src/corelib/thread/qmutex.h b/src/corelib/thread/qmutex.h +--- a/src/corelib/thread/qmutex.h ++++ b/src/corelib/thread/qmutex.h +@@ -26,6 +26,6 @@ + protected: + static constexpr bool FutexAlwaysAvailable = +-#if defined(Q_OS_FREEBSD) || defined(Q_OS_LINUX) || defined(Q_OS_WIN) // these platforms use futex ++#if defined(Q_OS_FREEBSD) || defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_REDOX) // these platforms use futex + true + #else + false diff --git a/local/patches/relibc/P3-ld-so-usr-lib-search-path.patch b/local/patches/relibc/P3-ld-so-usr-lib-search-path.patch new file mode 100644 index 0000000000..f7c099127e --- /dev/null +++ b/local/patches/relibc/P3-ld-so-usr-lib-search-path.patch @@ -0,0 +1,12 @@ +diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs +index 4a01b0d9..00271fa9 100644 +--- a/src/ld_so/linker.rs ++++ b/src/ld_so/linker.rs +@@ -928,6 +928,7 @@ impl Linker { + search_paths.extend(ld_path.split(PATH_SEP)); + } + search_paths.push("/lib"); ++ search_paths.push("/usr/lib"); + for part in search_paths.iter() { + full_path = format!("{}/{}", part, name); + if debug { diff --git a/local/patches/relibc/P3-strtold.patch b/local/patches/relibc/P3-strtold.patch new file mode 100644 index 0000000000..b398981183 --- /dev/null +++ b/local/patches/relibc/P3-strtold.patch @@ -0,0 +1,35 @@ +diff --git a/src/header/stdlib/mod.rs b/src/header/stdlib/mod.rs +--- a/src/header/stdlib/mod.rs ++++ b/src/header/stdlib/mod.rs +@@ -31,8 +31,8 @@ use crate::{ + platform::{ + self, Pal, Sys, + types::{ +- c_char, c_double, c_float, c_int, c_long, c_longlong, c_uint, c_ulong, c_ulonglong, +- c_ushort, c_void, size_t, ssize_t, uintptr_t, wchar_t, ++ c_char, c_double, c_float, c_int, c_long, c_longdouble, c_longlong, c_uint, c_ulong, ++ c_ulonglong, c_ushort, c_void, size_t, ssize_t, uintptr_t, wchar_t, + }, + }, + raw_cell::RawCell, +@@ -1506,7 +1506,19 @@ pub unsafe extern "C" fn strtol(s: *const c_char, endptr: *mut *mut c_char, base + strto_impl!(c_long, true, c_long::MAX, c_long::MIN, s, endptr, base) + } + +-// TODO: strtold(), when long double is available ++/// See . ++/// ++/// Note: delegates to strtod and converts via relibc_dtold because c_longdouble ++/// is u128 (soft float) on this target. Precision is limited to double. ++#[unsafe(no_mangle)] ++pub unsafe extern "C" fn strtold(s: *const c_char, endptr: *mut *mut c_char) -> c_longdouble { ++ let mut result: c_longdouble = 0; ++ unsafe { ++ let d = strtod(s, endptr); ++ crate::header::stdio::printf::relibc_dtold(d, &mut result); ++ } ++ result ++} + + /// See . + #[unsafe(no_mangle)] diff --git a/local/patches/relibc/P5-remove-drm-ioctl-intercept.patch b/local/patches/relibc/P5-remove-drm-ioctl-intercept.patch new file mode 100644 index 0000000000..b09363865a --- /dev/null +++ b/local/patches/relibc/P5-remove-drm-ioctl-intercept.patch @@ -0,0 +1,42 @@ +diff --git a/src/header/sys_ioctl/redox/mod.rs b/src/header/sys_ioctl/redox/mod.rs +index 8b3cf8f..c3e925e 100644 +--- a/src/header/sys_ioctl/redox/mod.rs ++++ b/src/header/sys_ioctl/redox/mod.rs +@@ -4,7 +4,7 @@ use syscall; + + use crate::{ + error::{Errno, Result, ResultExt}, +- header::{errno::EINVAL, fcntl, termios}, ++ header::{errno::{EINVAL, ENOTTY}, fcntl, termios}, + platform::{ + Pal, Sys, + types::{c_int, c_ulong, c_ulonglong, c_void, pid_t}, +@@ -166,25 +166,9 @@ unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result { +- // See https://docs.kernel.org/userspace-api/ioctl/ioctl-decoding.html for details +- let dir = (request >> 30) & 0b11; +- let size = ((request >> 16) & 0x3FFF) as usize; +- let name = (((request >> 8) & 0xFF) as u8) as char; +- let func = (request & 0xFF) as u8; +- match name { +- 'd' => { +- let buf = match dir { +- 0b10 => IoctlBuffer::Read(out, size), +- 0b01 => IoctlBuffer::Write(out, size), +- 0b11 => IoctlBuffer::ReadWrite(out, size), +- _ => IoctlBuffer::None, +- }; +- return unsafe { drm::ioctl(fd, func, buf) }; +- } +- _ => { +- return Err(Errno(EINVAL)); +- } +- } ++ // libdrm handles DRM ioctls via redox_drm_simple_ioctl() with ++ // its own scheme:drm wire format; relibc must not intercept. ++ return Err(Errno(ENOTTY)); + } + } + Ok(0) diff --git a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs index 6a02c83946..bd81bb14cb 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs @@ -1,7 +1,17 @@ use std::io::{Read, Seek, SeekFrom, Write}; +use std::sync::OnceLock; use crate::{DriverError, Result}; +/// Method for accessing PCI configuration space. +enum ConfigAccess { + /// Access via /scheme/pci/.../config file (normal path) + SchemeFile(std::fs::File), + /// Access via x86 I/O ports 0xCF8/0xCFC (fallback when scheme:pci is stale) + #[cfg(target_arch = "x86_64")] + IoPorts, +} + pub const PCI_VENDOR_ID_AMD: u16 = 0x1002; pub const PCI_VENDOR_ID_INTEL: u16 = 0x8086; pub const PCI_VENDOR_ID_NVIDIA: u16 = 0x10DE; @@ -11,6 +21,10 @@ pub const PCI_CLASS_DISPLAY_VGA: u8 = 0x00; pub const PCI_CLASS_DISPLAY_3D: u8 = 0x02; pub const PCI_HEADER_TYPE_NORMAL: u8 = 0x00; +const PCI_CONFIG_ADDRESS_PORT: u16 = 0xCF8; +const PCI_CONFIG_DATA_PORT: u16 = 0xCFC; +const PCI_CONFIG_SPACE_SIZE: usize = 256; +static PCI_IOPL_INIT: OnceLock> = OnceLock::new(); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PciLocation { @@ -241,7 +255,7 @@ impl PciDeviceInfo { pub struct PciDevice { location: PciLocation, - config_fd: std::fs::File, + access: ConfigAccess, } impl PciDevice { @@ -257,16 +271,31 @@ impl PciDevice { pub fn open_location(loc: &PciLocation) -> Result { let config_path = format!("{}/config", loc.scheme_path()); - let fd = std::fs::OpenOptions::new() + let access = match std::fs::OpenOptions::new() .read(true) .write(true) .open(&config_path) - .map_err(|e| { - DriverError::Pci(format!("cannot open PCI config at {}: {}", config_path, e)) - })?; + { + Ok(fd) => ConfigAccess::SchemeFile(fd), + Err(err) => { + ensure_io_port_access().map_err(|fallback_err| { + DriverError::Pci(format!( + "cannot open PCI config at {}: {}; I/O port fallback unavailable: {}", + config_path, err, fallback_err + )) + })?; + log::debug!( + "PCI: scheme path {} unavailable, using I/O port fallback for {}", + config_path, + loc + ); + ConfigAccess::IoPorts + } + }; + Ok(PciDevice { location: *loc, - config_fd: fd, + access, }) } @@ -279,42 +308,57 @@ impl PciDevice { } pub fn read_config_dword(&mut self, offset: u64) -> Result { - self.config_fd.seek(SeekFrom::Start(offset))?; - let mut buf = [0u8; 4]; - self.config_fd.read_exact(&mut buf)?; - Ok(u32::from_le_bytes(buf)) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => { + let mut buf = [0u8; 4]; + read_scheme_config(fd, offset, &mut buf)?; + Ok(u32::from_le_bytes(buf)) + } + ConfigAccess::IoPorts => io_read_config_dword(&self.location, offset), + } } pub fn read_config_word(&mut self, offset: u64) -> Result { - self.config_fd.seek(SeekFrom::Start(offset))?; - let mut buf = [0u8; 2]; - self.config_fd.read_exact(&mut buf)?; - Ok(u16::from_le_bytes(buf)) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => { + let mut buf = [0u8; 2]; + read_scheme_config(fd, offset, &mut buf)?; + Ok(u16::from_le_bytes(buf)) + } + ConfigAccess::IoPorts => io_read_config_word(&self.location, offset), + } } pub fn read_config_byte(&mut self, offset: u64) -> Result { - self.config_fd.seek(SeekFrom::Start(offset))?; - let mut buf = [0u8; 1]; - self.config_fd.read_exact(&mut buf)?; - Ok(buf[0]) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => { + let mut buf = [0u8; 1]; + read_scheme_config(fd, offset, &mut buf)?; + Ok(buf[0]) + } + ConfigAccess::IoPorts => io_read_config_byte(&self.location, offset), + } } pub fn write_config_dword(&mut self, offset: u64, val: u32) -> Result<()> { - self.config_fd.seek(SeekFrom::Start(offset))?; - self.config_fd.write_all(&val.to_le_bytes())?; - Ok(()) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => write_scheme_config(fd, offset, &val.to_le_bytes()), + ConfigAccess::IoPorts => io_write_config_dword(&self.location, offset, val), + } } pub fn write_config_word(&mut self, offset: u64, val: u16) -> Result<()> { - self.config_fd.seek(SeekFrom::Start(offset))?; - self.config_fd.write_all(&val.to_le_bytes())?; - Ok(()) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => write_scheme_config(fd, offset, &val.to_le_bytes()), + ConfigAccess::IoPorts => io_write_config_word(&self.location, offset, val), + } } pub fn write_config_byte(&mut self, offset: u64, val: u8) -> Result<()> { - self.config_fd.seek(SeekFrom::Start(offset))?; - self.config_fd.write_all(&[val])?; - Ok(()) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => write_scheme_config(fd, offset, &[val]), + ConfigAccess::IoPorts => io_write_config_byte(&self.location, offset, val), + } } pub fn vendor_id(&mut self) -> Result { @@ -657,34 +701,343 @@ impl PciDevice { impl std::io::Write for PciDevice { fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.config_fd.write(buf) + match &mut self.access { + ConfigAccess::SchemeFile(fd) => fd.write(buf), + #[cfg(target_arch = "x86_64")] + ConfigAccess::IoPorts => Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "raw write not supported for I/O port PCI config access", + )), + } } fn flush(&mut self) -> std::io::Result<()> { - self.config_fd.flush() + match &mut self.access { + ConfigAccess::SchemeFile(fd) => fd.flush(), + #[cfg(target_arch = "x86_64")] + ConfigAccess::IoPorts => Ok(()), + } } } +fn read_scheme_config(fd: &mut std::fs::File, offset: u64, buf: &mut [u8]) -> Result<()> { + fd.seek(SeekFrom::Start(offset))?; + fd.read_exact(buf)?; + Ok(()) +} + +fn write_scheme_config(fd: &mut std::fs::File, offset: u64, buf: &[u8]) -> Result<()> { + fd.seek(SeekFrom::Start(offset))?; + fd.write_all(buf)?; + Ok(()) +} + +fn ensure_io_port_access() -> Result<()> { + match PCI_IOPL_INIT.get_or_init(init_io_port_access) { + Ok(()) => Ok(()), + Err(err) => Err(DriverError::Pci(err.clone())), + } +} + +fn init_io_port_access() -> std::result::Result<(), String> { + #[cfg(all(target_arch = "x86_64", target_os = "redox"))] + { + crate::io::acquire_iopl().map_err(|err| { + format!( + "cannot acquire I/O privilege level for PCI config fallback: {}", + err + ) + }) + } + + #[cfg(all(target_arch = "x86_64", not(target_os = "redox")))] + { + Err(String::from( + "PCI I/O port fallback requires Redox I/O privilege and is unavailable on non-Redox targets", + )) + } + + #[cfg(not(target_arch = "x86_64"))] + { + Err(String::from( + "PCI I/O port fallback is only supported on x86_64", + )) + } +} + +fn io_read_config_dword(location: &PciLocation, offset: u64) -> Result { + if offset & 0x3 == 0 { + let address = pci_config_address(location, offset)?; + unsafe { + outl(PCI_CONFIG_ADDRESS_PORT, address); + Ok(inl(PCI_CONFIG_DATA_PORT)) + } + } else { + let mut buf = [0u8; 4]; + for (index, byte) in buf.iter_mut().enumerate() { + *byte = io_read_config_byte(location, offset + index as u64)?; + } + Ok(u32::from_le_bytes(buf)) + } +} + +fn io_read_config_word(location: &PciLocation, offset: u64) -> Result { + let low_bits = (offset & 0x3) as u16; + if low_bits <= 2 { + let address = pci_config_address(location, offset)?; + unsafe { + outl(PCI_CONFIG_ADDRESS_PORT, address); + Ok(inw(PCI_CONFIG_DATA_PORT + low_bits)) + } + } else { + let lo = io_read_config_byte(location, offset)?; + let hi = io_read_config_byte(location, offset + 1)?; + Ok(u16::from_le_bytes([lo, hi])) + } +} + +fn io_read_config_byte(location: &PciLocation, offset: u64) -> Result { + let low_bits = (offset & 0x3) as u16; + let address = pci_config_address(location, offset)?; + unsafe { + outl(PCI_CONFIG_ADDRESS_PORT, address); + Ok(inb(PCI_CONFIG_DATA_PORT + low_bits)) + } +} + +fn io_write_config_dword(location: &PciLocation, offset: u64, val: u32) -> Result<()> { + if offset & 0x3 == 0 { + let address = pci_config_address(location, offset)?; + unsafe { + outl(PCI_CONFIG_ADDRESS_PORT, address); + outl(PCI_CONFIG_DATA_PORT, val); + } + Ok(()) + } else { + for (index, byte) in val.to_le_bytes().into_iter().enumerate() { + io_write_config_byte(location, offset + index as u64, byte)?; + } + Ok(()) + } +} + +fn io_write_config_word(location: &PciLocation, offset: u64, val: u16) -> Result<()> { + let low_bits = (offset & 0x3) as u16; + if low_bits <= 2 { + let address = pci_config_address(location, offset)?; + unsafe { + outl(PCI_CONFIG_ADDRESS_PORT, address); + outw(PCI_CONFIG_DATA_PORT + low_bits, val); + } + Ok(()) + } else { + let [lo, hi] = val.to_le_bytes(); + io_write_config_byte(location, offset, lo)?; + io_write_config_byte(location, offset + 1, hi) + } +} + +fn io_write_config_byte(location: &PciLocation, offset: u64, val: u8) -> Result<()> { + let low_bits = (offset & 0x3) as u16; + let address = pci_config_address(location, offset)?; + unsafe { + outl(PCI_CONFIG_ADDRESS_PORT, address); + outb(PCI_CONFIG_DATA_PORT + low_bits, val); + } + Ok(()) +} + +fn pci_config_address(location: &PciLocation, offset: u64) -> Result { + let register = u32::try_from(offset) + .map_err(|_| DriverError::InvalidParam("PCI config offset exceeds u32"))?; + Ok( + 0x8000_0000 + | ((location.bus as u32) << 16) + | ((location.device as u32) << 11) + | ((location.function as u32) << 8) + | (register & 0xFC), + ) +} + +#[cfg(target_arch = "x86_64")] +#[inline] +unsafe fn inb(port: u16) -> u8 { + let val: u8; + core::arch::asm!("in al, dx", in("dx") port, out("al") val, options(nomem, nostack)); + val +} + +#[cfg(target_arch = "x86_64")] +#[inline] +unsafe fn outb(port: u16, val: u8) { + core::arch::asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack)); +} + +#[cfg(target_arch = "x86_64")] +#[inline] +unsafe fn inw(port: u16) -> u16 { + let val: u16; + core::arch::asm!("in ax, dx", in("dx") port, out("ax") val, options(nomem, nostack)); + val +} + +#[cfg(target_arch = "x86_64")] +#[inline] +unsafe fn outw(port: u16, val: u16) { + core::arch::asm!("out dx, ax", in("dx") port, in("ax") val, options(nomem, nostack)); +} + +#[cfg(target_arch = "x86_64")] +#[inline] +unsafe fn inl(port: u16) -> u32 { + let val: u32; + core::arch::asm!("in eax, dx", in("dx") port, out("eax") val, options(nomem, nostack)); + val +} + +#[cfg(target_arch = "x86_64")] +#[inline] +unsafe fn outl(port: u16, val: u32) { + core::arch::asm!("out dx, eax", in("dx") port, in("eax") val, options(nomem, nostack)); +} + +#[cfg(not(target_arch = "x86_64"))] +#[inline] +unsafe fn inb(_port: u16) -> u8 { + unreachable!("PCI I/O port access is only built for x86_64") +} + +#[cfg(not(target_arch = "x86_64"))] +#[inline] +unsafe fn outb(_port: u16, _val: u8) { + unreachable!("PCI I/O port access is only built for x86_64") +} + +#[cfg(not(target_arch = "x86_64"))] +#[inline] +unsafe fn inw(_port: u16) -> u16 { + unreachable!("PCI I/O port access is only built for x86_64") +} + +#[cfg(not(target_arch = "x86_64"))] +#[inline] +unsafe fn outw(_port: u16, _val: u16) { + unreachable!("PCI I/O port access is only built for x86_64") +} + +#[cfg(not(target_arch = "x86_64"))] +#[inline] +unsafe fn inl(_port: u16) -> u32 { + unreachable!("PCI I/O port access is only built for x86_64") +} + +#[cfg(not(target_arch = "x86_64"))] +#[inline] +unsafe fn outl(_port: u16, _val: u32) { + unreachable!("PCI I/O port access is only built for x86_64") +} + + fn enumerate_pci_filtered(class: Option) -> Result> { - let entries = std::fs::read_dir("/scheme/pci")?; + let class_desc = class + .map(|class| format!(" for class {class:#04x}")) + .unwrap_or_default(); + + let devices = match std::fs::read_dir("/scheme/pci") { + Ok(entries) => { + let mut devices = Vec::new(); + let mut saw_entry = false; + + for entry in entries { + let entry = entry?; + saw_entry = true; + + let name = entry.file_name(); + let Some(name_str) = name.to_str() else { + continue; + }; + + // pcid scheme entries use format: segment--bus--device.function + let Some(location) = parse_scheme_entry(name_str) else { + continue; + }; + + if let Some(data) = read_pci_config_space(location)? { + if let Some(info) = parse_device_info_from_config_space(location, &data) + .filter(|info| class.is_none_or(|class| info.class_code == class)) + { + devices.push(info); + } + } + } + + if saw_entry { + devices + } else { + log::debug!( + "PCI enumeration{}: /scheme/pci is empty, falling back to direct probing", + class_desc + ); + direct_probe_pci_bus(class)? + } + } + Err(err) => { + log::debug!( + "PCI enumeration{}: cannot read /scheme/pci ({}), falling back to direct probing", + class_desc, + err + ); + direct_probe_pci_bus(class)? + } + }; + + log::debug!( + "PCI enumeration{}: found {} devices", + class_desc, + devices.len() + ); + Ok(devices) +} + +fn direct_probe_pci_bus(class: Option) -> Result> { let mut devices = Vec::new(); - for entry in entries { - let entry = entry?; - let name = entry.file_name(); - let name_str = match name.to_str() { - Some(s) => s, - None => continue, + for device in 0..=31 { + let function_zero = PciLocation { + segment: 0, + bus: 0, + device, + function: 0, }; - // pcid scheme entries use format: segment--bus--device.function - let location = match parse_scheme_entry(name_str) { - Some(loc) => loc, - None => continue, + let Some(data) = read_pci_config_space(function_zero)? else { + continue; }; - let config_path = format!("{}/config", location.scheme_path()); - if let Ok(data) = std::fs::read(&config_path) { + let is_multifunction = data.get(0x0e).is_some_and(|header_type| header_type & 0x80 != 0); + + if let Some(info) = parse_device_info_from_config_space(function_zero, &data) + .filter(|info| class.is_none_or(|class| info.class_code == class)) + { + devices.push(info); + } + + if !is_multifunction { + continue; + } + + for function in 1..=7 { + let location = PciLocation { + segment: 0, + bus: 0, + device, + function, + }; + + let Some(data) = read_pci_config_space(location)? else { + continue; + }; + if let Some(info) = parse_device_info_from_config_space(location, &data) .filter(|info| class.is_none_or(|class| info.class_code == class)) { @@ -693,17 +1046,32 @@ fn enumerate_pci_filtered(class: Option) -> Result> { } } - log::debug!( - "PCI enumeration{}: found {} devices", - class - .map(|class| format!(" for class {class:#04x}")) - .unwrap_or_default(), - devices.len() - ); Ok(devices) } -pub fn parse_device_info_from_config_space(location: PciLocation, data: &[u8]) -> Option { +fn read_pci_config_space(location: PciLocation) -> Result>> { + // Try scheme:pci file first, then fall back to I/O ports via PciDevice. + let mut device = match PciDevice::open_location(&location) { + Ok(dev) => dev, + Err(_) => return Ok(None), + }; + + if device.vendor_id()? == 0xFFFF { + return Ok(None); + } + + let mut data = vec![0u8; PCI_CONFIG_SPACE_SIZE]; + for offset in (0..PCI_CONFIG_SPACE_SIZE).step_by(4) { + let dword = device.read_config_dword(offset as u64)?; + data[offset..offset + 4].copy_from_slice(&dword.to_le_bytes()); + } + Ok(Some(data)) +} + +pub fn parse_device_info_from_config_space( + location: PciLocation, + data: &[u8], +) -> Option { if data.len() < 64 { return None; } @@ -1055,4 +1423,17 @@ mod tests { assert_eq!(info.interrupt_support(), InterruptSupport::LegacyOnly); assert_eq!(info.interrupt_support().as_str(), "legacy"); } + + #[test] + fn pci_config_address_encodes_bus_device_function_and_register() { + let location = PciLocation { + segment: 0, + bus: 0x80, + device: 0x1f, + function: 0x03, + }; + + let address = pci_config_address(&location, 0x15).expect("PCI address should encode"); + assert_eq!(address, 0x8080_fb14); + } } diff --git a/local/recipes/gpu/redox-drm/source/src/driver.rs b/local/recipes/gpu/redox-drm/source/src/driver.rs index dffc5c4694..8b1c382707 100644 --- a/local/recipes/gpu/redox-drm/source/src/driver.rs +++ b/local/recipes/gpu/redox-drm/source/src/driver.rs @@ -41,6 +41,23 @@ pub struct RedoxPrivateCsWaitResult { pub completed_seqno: u64, } +// Minimal virgl response types for the GpuDriver trait. +#[cfg_attr(test, derive(Debug))] +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct VirglCapsetInfo { + pub capset_id: u32, + pub capset_max_version: u32, + pub capset_max_size: u32, +} + +#[cfg_attr(test, derive(Debug))] +#[repr(C)] +#[derive(Clone)] +pub struct VirglCapset { + pub data: Vec, +} + #[derive(Debug, Error)] pub enum DriverError { #[error("driver initialization failed: {0}")] @@ -86,7 +103,7 @@ pub trait GpuDriver: Send + Sync { fn page_flip(&self, crtc_id: u32, fb_handle: u32, flags: u32) -> Result; fn get_vblank(&self, crtc_id: u32) -> Result; - fn gem_create(&self, size: u64) -> Result; + fn gem_create(&self, size: u64, width: u32, height: u32) -> Result; fn gem_close(&self, handle: GemHandle) -> Result<()>; fn gem_mmap(&self, handle: GemHandle) -> Result; fn gem_size(&self, handle: GemHandle) -> Result; @@ -112,6 +129,93 @@ pub trait GpuDriver: Send + Sync { "private command completion waits are unavailable on this backend", )) } + + fn has_virgl_3d(&self) -> bool { + false + } + + fn virgl_get_capset_info(&self, _capset_index: u32) -> Result { + Err(DriverError::Unsupported("virgl capset info")) + } + + fn virgl_get_capset(&self, _capset_id: u32, _capset_version: u32) -> Result { + Err(DriverError::Unsupported("virgl capset")) + } + + fn virgl_ctx_create(&self, _ctx_id: u32, _debug_name: &str, _context_init: u32) -> Result<()> { + Err(DriverError::Unsupported("virgl context creation")) + } + + fn virgl_ctx_destroy(&self, _ctx_id: u32) -> Result<()> { + Err(DriverError::Unsupported("virgl context destruction")) + } + + fn virgl_resource_create_3d( + &self, + _resource_id: u32, + _target: u32, + _format: u32, + _bind: u32, + _width: u32, + _height: u32, + _depth: u32, + _array_size: u32, + _last_level: u32, + _nr_samples: u32, + _flags: u32, + _ctx_id: u32, + ) -> Result<()> { + Err(DriverError::Unsupported("virgl 3D resource creation")) + } + + fn virgl_submit_3d(&self, _ctx_id: u32, _command_data: &[u8]) -> Result<()> { + Err(DriverError::Unsupported("virgl submit 3D")) + } + + fn virgl_transfer_to_host_3d( + &self, + _ctx_id: u32, + _resource_id: u32, + _x: u32, + _y: u32, + _z: u32, + _w: u32, + _h: u32, + _d: u32, + _offset: u64, + _level: u32, + _stride: u32, + _layer_stride: u32, + ) -> Result<()> { + Err(DriverError::Unsupported("virgl transfer to host")) + } + + fn virgl_transfer_from_host_3d( + &self, + _ctx_id: u32, + _resource_id: u32, + _x: u32, + _y: u32, + _z: u32, + _w: u32, + _h: u32, + _d: u32, + _offset: u64, + _level: u32, + _stride: u32, + _layer_stride: u32, + ) -> Result<()> { + Err(DriverError::Unsupported("virgl transfer from host")) + } + + fn virgl_resource_attach_backing( + &self, + _resource_id: u32, + _phys_addr: u64, + _length: u32, + ) -> Result<()> { + Err(DriverError::Unsupported("virgl resource attach backing")) + } } #[cfg(test)] diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs index 0603e00e98..eec0d17f92 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs @@ -473,7 +473,7 @@ impl GpuDriver for AmdDriver { Ok(self.vblank_count.load(Ordering::SeqCst)) } - fn gem_create(&self, size: u64) -> Result { + fn gem_create(&self, size: u64, _width: u32, _height: u32) -> Result { let mut gem = self .gem .lock() diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs index e0a5a5d044..7a624dff0c 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/intel/mod.rs @@ -420,7 +420,7 @@ impl GpuDriver for IntelDriver { Ok(self.vblank_count.load(Ordering::SeqCst)) } - fn gem_create(&self, size: u64) -> Result { + fn gem_create(&self, size: u64, _width: u32, _height: u32) -> Result { let handle = { let mut gem = self .gem diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/commands.rs b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/commands.rs new file mode 100644 index 0000000000..5d6da0153e --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/commands.rs @@ -0,0 +1,404 @@ +use core::mem::size_of; + +use crate::driver::{DriverError, Result}; + +pub const VIRTIO_GPU_F_EDID: u64 = 1 << 1; +pub const VIRTIO_GPU_F_VIRGL: u64 = 1 << 0; +pub const VIRTIO_GPU_F_RESOURCE_UUID: u64 = 1 << 2; +pub const VIRTIO_GPU_F_RESOURCE_BLOB: u64 = 1 << 3; +pub const VIRTIO_GPU_F_CONTEXT_INIT: u64 = 1 << 4; +pub const VIRTIO_F_VERSION_1: u64 = 1 << 32; + +pub const VIRTIO_GPU_CMD_GET_DISPLAY_INFO: u32 = 0x0100; +pub const VIRTIO_GPU_CMD_SET_SCANOUT: u32 = 0x0101; +pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: u32 = 0x0102; +pub const VIRTIO_GPU_CMD_RESOURCE_UNREF: u32 = 0x0103; +pub const VIRTIO_GPU_CMD_RESOURCE_FLUSH: u32 = 0x0104; +pub const VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: u32 = 0x0105; +pub const VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: u32 = 0x0106; +pub const VIRTIO_GPU_CMD_GET_EDID: u32 = 0x0107; +pub const VIRTIO_GPU_CMD_GET_CAPSET_INFO: u32 = 0x0108; +pub const VIRTIO_GPU_CMD_GET_CAPSET: u32 = 0x0109; +pub const VIRTIO_GPU_CMD_CTX_CREATE: u32 = 0x0200; +pub const VIRTIO_GPU_CMD_CTX_DESTROY: u32 = 0x0201; +pub const VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE: u32 = 0x0202; +pub const VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE: u32 = 0x0203; +pub const VIRTIO_GPU_CMD_RESOURCE_CREATE_3D: u32 = 0x0204; +pub const VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D: u32 = 0x0205; +pub const VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D: u32 = 0x0206; +pub const VIRTIO_GPU_CMD_SUBMIT_3D: u32 = 0x0207; +pub const VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB: u32 = 0x0208; +pub const VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB: u32 = 0x0209; + +pub const VIRTIO_GPU_RESP_OK_NODATA: u32 = 0x1100; +pub const VIRTIO_GPU_RESP_OK_DISPLAY_INFO: u32 = 0x1101; +pub const VIRTIO_GPU_RESP_OK_CAPSET_INFO: u32 = 0x1102; +pub const VIRTIO_GPU_RESP_OK_CAPSET: u32 = 0x1103; +pub const VIRTIO_GPU_RESP_OK_MAP_INFO: u32 = 0x1106; +pub const VIRTIO_GPU_RESP_OK_EDID: u32 = 0x1107; + +pub const VIRTIO_GPU_RESP_ERR_UNSPEC: u32 = 0x1200; +pub const VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY: u32 = 0x1201; +pub const VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID: u32 = 0x1202; +pub const VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID: u32 = 0x1203; +pub const VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID: u32 = 0x1204; +pub const VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER: u32 = 0x1205; + +pub const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1; +pub const VIRTIO_GPU_MAX_SCANOUTS: usize = 16; +pub const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0; +pub const VIRTIO_GPU_CAPSET_VIRGL: u32 = 1; +pub const VIRTIO_GPU_CAPSET_VIRGL2: u32 = 2; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuCtrlHeader { + pub type_: u32, + pub flags: u32, + pub fence_id: u64, + pub ctx_id: u32, + pub padding: u32, +} + +impl VirtioGpuCtrlHeader { + pub fn command(type_: u32) -> Self { + Self { + type_, + flags: 0, + fence_id: 0, + ctx_id: 0, + padding: 0, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuRect { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuBox { + pub x: u32, + pub y: u32, + pub z: u32, + pub w: u32, + pub h: u32, + pub d: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuResourceUnref { + pub hdr: VirtioGpuCtrlHeader, + pub resource_id: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuResourceCreate2d { + pub hdr: VirtioGpuCtrlHeader, + pub resource_id: u32, + pub format: u32, + pub width: u32, + pub height: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuSetScanout { + pub hdr: VirtioGpuCtrlHeader, + pub rect: VirtioGpuRect, + pub scanout_id: u32, + pub resource_id: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuResourceFlush { + pub hdr: VirtioGpuCtrlHeader, + pub rect: VirtioGpuRect, + pub resource_id: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuTransferToHost2d { + pub hdr: VirtioGpuCtrlHeader, + pub rect: VirtioGpuRect, + pub offset: u64, + pub resource_id: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuTransferHost3d { + pub hdr: VirtioGpuCtrlHeader, + pub box_: VirtioGpuBox, + pub offset: u64, + pub resource_id: u32, + pub level: u32, + pub stride: u32, + pub layer_stride: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuMemEntry { + pub addr: u64, + pub length: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuResourceAttachBacking { + pub hdr: VirtioGpuCtrlHeader, + pub resource_id: u32, + pub nr_entries: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuResourceCreate3d { + pub hdr: VirtioGpuCtrlHeader, + pub resource_id: u32, + pub target: u32, + pub format: u32, + pub bind: u32, + pub width: u32, + pub height: u32, + pub depth: u32, + pub array_size: u32, + pub last_level: u32, + pub nr_samples: u32, + pub flags: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct VirtioGpuCtxCreate { + pub hdr: VirtioGpuCtrlHeader, + pub nlen: u32, + pub context_init: u32, + pub debug_name: [u8; 64], +} + +impl Default for VirtioGpuCtxCreate { + fn default() -> Self { + Self { + hdr: VirtioGpuCtrlHeader::default(), + nlen: 0, + context_init: 0, + debug_name: [0; 64], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuCtxDestroy { + pub hdr: VirtioGpuCtrlHeader, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuCtxResource { + pub hdr: VirtioGpuCtrlHeader, + pub resource_id: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuCmdSubmit { + pub hdr: VirtioGpuCtrlHeader, + pub size: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuGetCapsetInfo { + pub hdr: VirtioGpuCtrlHeader, + pub capset_index: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuRespCapsetInfo { + pub hdr: VirtioGpuCtrlHeader, + pub capset_id: u32, + pub capset_max_version: u32, + pub capset_max_size: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuGetCapset { + pub hdr: VirtioGpuCtrlHeader, + pub capset_id: u32, + pub capset_version: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct VirtioGpuRespCapset { + pub hdr: VirtioGpuCtrlHeader, + pub capset_data: [u8; 8192], +} + +impl Default for VirtioGpuRespCapset { + fn default() -> Self { + Self { + hdr: VirtioGpuCtrlHeader::default(), + capset_data: [0u8; 8192], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuResourceMapBlob { + pub hdr: VirtioGpuCtrlHeader, + pub resource_id: u32, + pub padding: u32, + pub offset: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuRespMapInfo { + pub hdr: VirtioGpuCtrlHeader, + pub map_info: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuCmdGetEdid { + pub hdr: VirtioGpuCtrlHeader, + pub scanout: u32, + pub padding: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuDisplayOne { + pub rect: VirtioGpuRect, + pub enabled: u32, + pub flags: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct VirtioGpuRespDisplayInfo { + pub hdr: VirtioGpuCtrlHeader, + pub pmodes: [VirtioGpuDisplayOne; VIRTIO_GPU_MAX_SCANOUTS], +} + +impl Default for VirtioGpuRespDisplayInfo { + fn default() -> Self { + Self { + hdr: VirtioGpuCtrlHeader::default(), + pmodes: [VirtioGpuDisplayOne::default(); VIRTIO_GPU_MAX_SCANOUTS], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct VirtioGpuRespEdid { + pub hdr: VirtioGpuCtrlHeader, + pub size: u32, + pub padding: u32, + pub edid: [u8; 1024], +} + +impl Default for VirtioGpuRespEdid { + fn default() -> Self { + Self { + hdr: VirtioGpuCtrlHeader::default(), + size: 0, + padding: 0, + edid: [0; 1024], + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct VirtioGpuConfig { + pub events_read: u32, + pub events_clear: u32, + pub num_scanouts: u32, + pub num_capsets: u32, +} + +pub fn bytes_of(value: &T) -> &[u8] { + let len = size_of::(); + let ptr = value as *const T as *const u8; + // SAFETY: The caller only uses this with #[repr(C)] plain-old-data command structs. + unsafe { core::slice::from_raw_parts(ptr, len) } +} + +pub fn append_bytes(bytes: &mut Vec, value: &T) { + bytes.extend_from_slice(bytes_of(value)); +} + +pub fn read_struct(bytes: &[u8]) -> Result { + if bytes.len() < size_of::() { + return Err(DriverError::Io(format!( + "short VirtIO GPU response: got {} bytes, need {}", + bytes.len(), + size_of::() + ))); + } + + let ptr = bytes.as_ptr() as *const T; + // SAFETY: We validated the buffer length and use read_unaligned for the DMA byte buffer. + Ok(unsafe { ptr.read_unaligned() }) +} + +pub fn validate_response_type(header: &VirtioGpuCtrlHeader, expected: u32) -> Result<()> { + if header.type_ == expected { + return Ok(()); + } + + Err(DriverError::Io(format!( + "VirtIO GPU response type mismatch: expected {} ({:#06x}), got {} ({:#06x})", + response_type_name(expected), + expected, + response_type_name(header.type_), + header.type_ + ))) +} + +pub fn response_type_name(type_: u32) -> &'static str { + match type_ { + VIRTIO_GPU_RESP_OK_NODATA => "OK_NODATA", + VIRTIO_GPU_RESP_OK_DISPLAY_INFO => "OK_DISPLAY_INFO", + VIRTIO_GPU_RESP_OK_CAPSET_INFO => "OK_CAPSET_INFO", + VIRTIO_GPU_RESP_OK_CAPSET => "OK_CAPSET", + VIRTIO_GPU_RESP_OK_MAP_INFO => "OK_MAP_INFO", + VIRTIO_GPU_RESP_OK_EDID => "OK_EDID", + VIRTIO_GPU_RESP_ERR_UNSPEC => "ERR_UNSPEC", + VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY => "ERR_OUT_OF_MEMORY", + VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID => "ERR_INVALID_SCANOUT_ID", + VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID => "ERR_INVALID_RESOURCE_ID", + VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID => "ERR_INVALID_CONTEXT_ID", + VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER => "ERR_INVALID_PARAMETER", + _ => "UNKNOWN", + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs index 9eca0c168d..512a455188 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/mod.rs @@ -1,46 +1,75 @@ -use std::collections::HashMap; +mod commands; +mod resource; +mod transport; +mod virtqueue; + +use std::collections::{BTreeMap, HashMap, VecDeque}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Mutex; +use std::time::Duration; use log::{info, warn}; -use redox_driver_sys::memory::MmioRegion; -use redox_driver_sys::pci::{PciBarInfo, PciDeviceInfo}; +use redox_driver_sys::dma::DmaBuffer; +use redox_driver_sys::pci::{PciDevice, PciDeviceInfo}; -use crate::driver::{DriverError, DriverEvent, GpuDriver, Result}; +use crate::driver::{DriverError, DriverEvent, GpuDriver, Result, VirglCapset, VirglCapsetInfo}; use crate::drivers::interrupt::InterruptHandle; use crate::gem::{GemHandle, GemManager}; use crate::kms::connector::{synthetic_edid, Connector}; use crate::kms::crtc::Crtc; use crate::kms::{ConnectorInfo, ConnectorStatus, ConnectorType, ModeInfo}; +use self::commands::{ + append_bytes, bytes_of, read_struct, validate_response_type, response_type_name, + VirtioGpuBox, VirtioGpuCmdGetEdid, + VirtioGpuCmdSubmit, VirtioGpuConfig, VirtioGpuCtrlHeader, VirtioGpuCtxCreate, + VirtioGpuCtxDestroy, VirtioGpuDisplayOne, VirtioGpuGetCapset, VirtioGpuGetCapsetInfo, + VirtioGpuMemEntry, VirtioGpuRect, VirtioGpuResourceAttachBacking, VirtioGpuResourceCreate2d, + VirtioGpuResourceCreate3d, VirtioGpuResourceFlush, VirtioGpuResourceUnref, VirtioGpuRespCapset, + VirtioGpuRespCapsetInfo, VirtioGpuRespDisplayInfo, VirtioGpuRespEdid, VirtioGpuSetScanout, + VirtioGpuTransferHost3d, VirtioGpuTransferToHost2d, VIRTIO_F_VERSION_1, + VIRTIO_GPU_CMD_CTX_CREATE, VIRTIO_GPU_CMD_CTX_DESTROY, VIRTIO_GPU_CMD_GET_CAPSET, + VIRTIO_GPU_CMD_GET_CAPSET_INFO, VIRTIO_GPU_CMD_GET_DISPLAY_INFO, VIRTIO_GPU_CMD_GET_EDID, + VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING, VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, + VIRTIO_GPU_CMD_RESOURCE_CREATE_3D, VIRTIO_GPU_CMD_RESOURCE_FLUSH, + VIRTIO_GPU_CMD_RESOURCE_UNREF, VIRTIO_GPU_CMD_SET_SCANOUT, VIRTIO_GPU_CMD_SUBMIT_3D, + VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, + VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_EVENT_DISPLAY, VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM, + VIRTIO_GPU_F_EDID, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_RESP_OK_CAPSET, + VIRTIO_GPU_RESP_OK_CAPSET_INFO, VIRTIO_GPU_RESP_OK_DISPLAY_INFO, VIRTIO_GPU_RESP_OK_EDID, + VIRTIO_GPU_RESP_OK_MAP_INFO, VIRTIO_GPU_RESP_OK_NODATA, +}; +use self::resource::{ResourceManager, VirtioResource}; +use self::transport::VirtioModernPciTransport; +use self::virtqueue::Virtqueue; + +const DRIVER_DATE: &str = "2026-05-12"; +const DEFAULT_QUEUE_SIZE: u16 = 32; +const CTRL_QUEUE_INDEX: u16 = 0; +const CURSOR_QUEUE_INDEX: u16 = 1; +const COMMAND_TIMEOUT: Duration = Duration::from_millis(250); + pub struct VirtioDriver { - info: PciDeviceInfo, - _mmio: MmioRegion, + _info: PciDeviceInfo, + device: Mutex, irq_handle: Mutex>, - width: u32, - height: u32, gem: Mutex, + resources: Mutex, connectors: Mutex>, crtcs: Mutex>, - vblank_count: AtomicU64, + vblank_seqno: AtomicU64, + submit_seqno: AtomicU64, + pending_vblanks: Mutex>, + crtc_vblank_counts: Mutex>, } -fn find_fb_bar(info: &PciDeviceInfo) -> Result { - info.bars - .iter() - .find(|bar| bar.addr != 0 && bar.size > 0) - .cloned() - .ok_or_else(|| DriverError::Pci("VirtIO GPU has no valid framebuffer BAR".into())) -} - -fn map_bar(bar: &PciBarInfo, name: &str) -> Result { - MmioRegion::map( - bar.addr, - bar.size as usize, - redox_driver_sys::memory::CacheType::DeviceMemory, - redox_driver_sys::memory::MmioProt::READ_WRITE, - ) - .map_err(|e| DriverError::Mmio(format!("failed to map {name}: {e}"))) +struct VirtioGpuDevice { + transport: VirtioModernPciTransport, + ctrlq: Virtqueue, + #[allow(dead_code)] + cursorq: Virtqueue, + supports_edid: bool, + has_virgl_3d: bool, } impl VirtioDriver { @@ -52,94 +81,353 @@ impl VirtioDriver { ))); } - let fb_bar = find_fb_bar(&info)?; - let _mmio = map_bar(&fb_bar, "VirtIO FB BAR")?; + let mut pci = PciDevice::open_location(&info.location).map_err(|e| { + DriverError::Pci(format!("failed to re-open VirtIO GPU PCI config: {e}")) + })?; + pci.enable_device() + .map_err(|e| DriverError::Pci(format!("VirtIO enable_device failed: {e}")))?; + + let irq_handle = match InterruptHandle::setup(&info, &mut pci) { + Ok(handle) => Some(handle), + Err(error) => { + warn!( + "redox-drm: VirtIO interrupt setup failed for {}: {}", + info.location, error + ); + None + } + }; + + let msix_vector = irq_handle + .as_ref() + .and_then(|handle| handle.is_msix().then_some(0)); + + let mut transport = VirtioModernPciTransport::new(&info, &mut pci)?; + let negotiated = transport + .initialize_device(VIRTIO_F_VERSION_1 | VIRTIO_GPU_F_EDID)?; + let ctrlq_cfg = transport.prepare_queue(CTRL_QUEUE_INDEX, DEFAULT_QUEUE_SIZE)?; + let ctrlq = Virtqueue::new(ctrlq_cfg.index, ctrlq_cfg.size, ctrlq_cfg.notify_off)?; + transport.activate_queue( + ctrlq.index(), + ctrlq.size(), + ctrlq.desc_addr(), + ctrlq.avail_addr(), + ctrlq.used_addr(), + msix_vector, + )?; + + let cursorq_cfg = transport.prepare_queue(CURSOR_QUEUE_INDEX, DEFAULT_QUEUE_SIZE)?; + let cursorq = Virtqueue::new(cursorq_cfg.index, cursorq_cfg.size, cursorq_cfg.notify_off)?; + transport.activate_queue( + cursorq.index(), + cursorq.size(), + cursorq.desc_addr(), + cursorq.avail_addr(), + cursorq.used_addr(), + msix_vector, + )?; + transport.set_config_msix_vector(msix_vector); + transport.finalize_device(); + + let mut device = VirtioGpuDevice { + transport, + ctrlq, + cursorq, + supports_edid: (negotiated & VIRTIO_GPU_F_EDID) != 0, + has_virgl_3d: (negotiated & VIRTIO_GPU_F_VIRGL) != 0, + }; + let (connectors, crtcs) = load_display_topology(&mut device)?; + + // Probe 1: resource_unref for non-existent ID — tests VirtQueue mechanism + match device.resource_unref(1) { + Ok(()) => info!("redox-drm: VirtIO diag resource_unref(1) returned OK (unexpected — ID 1 should not exist)"), + Err(e) => info!("redox-drm: VirtIO diag resource_unref(1) returned {:?}", e), + } + + // Probe 2: invalid format=0 — if QEMU returns ERR_INVALID_PARAMETER it reads the full + // command; if ERR_INVALID_RESOURCE_ID it's only seeing the header or resource_id field. + { + let probe = VirtioGpuResourceCreate2d { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_CREATE_2D), + resource_id: 99, + format: 0, + width: 64, + height: 64, + }; + let probe_bytes = bytes_of(&probe); + info!("redox-drm: VirtIO diag probe: resource_create_2d(rid=99, fmt=0, w=64, h=64)"); + match device.submit_request(probe_bytes, core::mem::size_of::()) { + Ok(resp) => { + let hdr = read_struct::(&resp)?; + info!( + "redox-drm: VirtIO diag probe response: type={:#06x} ({})", + hdr.type_, response_type_name(hdr.type_) + ); + } + Err(e) => info!("redox-drm: VirtIO diag probe submit_request error: {:?}", e), + } + } info!( - "redox-drm: VirtIO GPU at {}: {} MiB BAR at {:#x}", + "redox-drm: VirtIO GPU ready for {} with {} connector(s), {} CRTC(s), EDID={} VIRGL={} IRQ mode {}", info.location, - fb_bar.size / 1024 / 1024, - fb_bar.addr, + connectors.len(), + crtcs.len(), + if device.supports_edid { "on" } else { "off" }, + if device.has_virgl_3d { "on" } else { "off" }, + irq_handle + .as_ref() + .map(|handle| handle.mode_name()) + .unwrap_or("none") ); Ok(Self { - info, - _mmio, - irq_handle: Mutex::new(None), - width: 1280, - height: 720, + _info: info, + device: Mutex::new(device), + irq_handle: Mutex::new(irq_handle), gem: Mutex::new(GemManager::new()), - connectors: Mutex::new(Vec::new()), - crtcs: Mutex::new(Vec::new()), - vblank_count: AtomicU64::new(0), + resources: Mutex::new(ResourceManager::new()), + connectors: Mutex::new(connectors), + crtcs: Mutex::new(crtcs), + vblank_seqno: AtomicU64::new(0), + submit_seqno: AtomicU64::new(1), + pending_vblanks: Mutex::new(VecDeque::new()), + crtc_vblank_counts: Mutex::new(BTreeMap::new()), }) } fn refresh_connectors(&self) -> Result> { - let mode = ModeInfo { - name: String::from("1280x720"), - clock: 0, - hdisplay: self.width as u16, - hsync_start: (self.width + 16) as u16, - hsync_end: (self.width + 48) as u16, - htotal: (self.width + 160) as u16, - vdisplay: self.height as u16, - vsync_start: (self.height + 3) as u16, - vsync_end: (self.height + 6) as u16, - vtotal: (self.height + 30) as u16, - hskew: 0, - vscan: 0, - vrefresh: 60, - type_: 0, - flags: 0, - }; - let info = ConnectorInfo { - id: 1, - connector_type: ConnectorType::Unknown, - connector_type_id: 1, - connection: ConnectorStatus::Connected, - mm_width: 0, - mm_height: 0, - modes: vec![mode], - encoder_id: 0, - }; - let mut connectors = self - .connectors + let mut device = self + .device .lock() - .map_err(|_| DriverError::Initialization("connector lock poisoned".into()))?; - connectors.clear(); - let result = info.clone(); - connectors.push(Connector { - edid: synthetic_edid(), - info, - }); - let mut crtcs = self - .crtcs - .lock() - .map_err(|_| DriverError::Initialization("crtc lock poisoned".into()))?; - crtcs.clear(); - crtcs.push(Crtc::new(1)); - Ok(vec![result]) + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + let (connectors, crtcs) = load_display_topology(&mut device)?; + let infos = connectors + .iter() + .map(|connector| connector.info.clone()) + .collect(); + + { + let mut connector_state = self.connectors.lock().map_err(|_| { + DriverError::Initialization("VirtIO connector state poisoned".into()) + })?; + *connector_state = connectors; + } + + self.merge_crtcs(crtcs)?; + Ok(infos) } fn cached_connectors(&self) -> Vec { self.connectors .lock() .ok() - .map(|c| c.iter().map(|c| c.info.clone()).collect()) + .map(|connectors| { + connectors + .iter() + .map(|connector| connector.info.clone()) + .collect() + }) .unwrap_or_default() } + + fn merge_crtcs(&self, fresh: Vec) -> Result<()> { + let mut state = self + .crtcs + .lock() + .map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))?; + let mut merged = Vec::with_capacity(fresh.len()); + for mut new_crtc in fresh { + if let Some(old) = state.iter().find(|candidate| candidate.id == new_crtc.id) { + new_crtc.current_fb = old.current_fb; + new_crtc.connectors = old.connectors.clone(); + new_crtc.mode = old.mode.clone(); + } + merged.push(new_crtc); + } + *state = merged; + Ok(()) + } + + fn ensure_resource_layout(&self, handle: GemHandle, mode: &ModeInfo) -> Result { + let width = u32::from(mode.hdisplay); + let height = u32::from(mode.vdisplay); + let stride = width.checked_mul(4).ok_or(DriverError::InvalidArgument( + "VirtIO scanout stride overflow", + ))?; + + let (phys_addr, size) = { + let gem = self + .gem + .lock() + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))?; + let object = gem.object(handle)?; + (object.phys_addr as u64, object.size) + }; + + let required_size = u64::from(stride) + .checked_mul(u64::from(height)) + .ok_or(DriverError::InvalidArgument("VirtIO scanout size overflow"))?; + if size < required_size { + return Err(DriverError::Buffer(format!( + "GEM handle {handle} is too small for mode {}x{}: need {} bytes, have {}", + width, height, required_size, size + ))); + } + + let current = self + .resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .get(handle)?; + + if current.width == width && current.height == height && current.stride == stride { + return Ok(current); + } + + let replacement_resource_id = self + .resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .allocate_resource_id(handle)?; + + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + device.resource_create_2d(replacement_resource_id, width, height)?; + if let Err(error) = + device.resource_attach_backing(replacement_resource_id, phys_addr, size as u32) + { + let _ = device.resource_unref(replacement_resource_id); + return Err(error); + } + if let Err(error) = device.resource_unref(current.resource_id) { + let _ = device.resource_unref(replacement_resource_id); + return Err(error); + } + + self.resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .update_layout(handle, replacement_resource_id, width, height, stride, size) + } + + fn program_scanout(&self, scanout_id: u32, handle: GemHandle, mode: &ModeInfo) -> Result<()> { + let resource = self.ensure_resource_layout(handle, mode)?; + let rect = rect_for_mode(mode); + + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + device.set_scanout(scanout_id, resource.resource_id, rect)?; + device.transfer_to_host_2d(resource.resource_id, rect)?; + device.resource_flush(resource.resource_id, rect) + } + + fn scanout_for_connector(&self, connector_id: u32) -> Result { + let connectors = self + .connectors + .lock() + .map_err(|_| DriverError::Initialization("VirtIO connector state poisoned".into()))?; + let connector = connectors + .iter() + .find(|connector| connector.info.id == connector_id) + .ok_or_else(|| DriverError::NotFound(format!("unknown connector {connector_id}")))?; + Ok(connector.info.id.saturating_sub(1)) + } + + fn maybe_emit_pending_vblank(&self) -> Result> { + let mut pending = self + .pending_vblanks + .lock() + .map_err(|_| DriverError::Initialization("VirtIO vblank queue poisoned".into()))?; + if let Some(crtc_id) = pending.pop_front() { + let count = { + let mut counts = self.crtc_vblank_counts.lock().map_err(|_| { + DriverError::Initialization("VirtIO vblank counters poisoned".into()) + })?; + let entry = counts.entry(crtc_id).or_insert(0); + *entry = entry.saturating_add(1); + *entry + }; + self.vblank_seqno.fetch_add(1, Ordering::SeqCst); + return Ok(Some(DriverEvent::Vblank { crtc_id, count })); + } + Ok(None) + } + + fn queue_vblank(&self, crtc_id: u32) -> Result { + let seqno = self.submit_seqno.fetch_add(1, Ordering::SeqCst); + let mut pending = self + .pending_vblanks + .lock() + .map_err(|_| DriverError::Initialization("VirtIO vblank queue poisoned".into()))?; + if !pending.iter().any(|pending_crtc| *pending_crtc == crtc_id) { + pending.push_back(crtc_id); + } + Ok(seqno) + } + + fn handle_irq_event(&self) -> Result> { + let irq_triggered = { + let mut irq_handle = self + .irq_handle + .lock() + .map_err(|_| DriverError::Initialization("VirtIO IRQ state poisoned".into()))?; + match irq_handle.as_mut() { + Some(handle) => handle.try_wait()?, + None => false, + } + }; + + if irq_triggered { + let display_event = { + let device = self.device.lock().map_err(|_| { + DriverError::Initialization("VirtIO device state poisoned".into()) + })?; + let isr = device.transport.read_isr_status(); + let events = device.transport.read_events(); + if events != 0 { + device.transport.clear_events(events); + } + (isr & 0x2) != 0 || (events & VIRTIO_GPU_EVENT_DISPLAY) != 0 + }; + + if display_event { + let previous = self.cached_connectors(); + let current = self.refresh_connectors()?; + if let Some(changed) = first_hotplug_change(&previous, ¤t) { + return Ok(Some(DriverEvent::Hotplug { + connector_id: changed, + })); + } + } + } + + self.maybe_emit_pending_vblank() + } + + fn virgl_resource(&self, resource_id: u32) -> Result { + self.resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .get(resource_id) + } } impl GpuDriver for VirtioDriver { fn driver_name(&self) -> &str { - "virtio-gpu-redox" + "virtio_gpu" } + fn driver_desc(&self) -> &str { "VirtIO GPU DRM/KMS backend for QEMU" } + fn driver_date(&self) -> &str { - "2026-04-27" + DRIVER_DATE } fn detect_connectors(&self) -> Vec { @@ -155,8 +443,8 @@ impl GpuDriver for VirtioDriver { fn get_modes(&self, connector_id: u32) -> Vec { self.detect_connectors() .into_iter() - .find(|c| c.id == connector_id) - .map(|c| c.modes) + .find(|connector| connector.id == connector_id) + .map(|connector| connector.modes) .unwrap_or_default() } @@ -167,82 +455,985 @@ impl GpuDriver for VirtioDriver { connectors: &[u32], mode: &ModeInfo, ) -> Result<()> { + if connectors.is_empty() { + return Err(DriverError::InvalidArgument( + "set_crtc requires at least one connector", + )); + } + + let scanout_id = self.scanout_for_connector(connectors[0])?; + self.program_scanout(scanout_id, fb_handle, mode)?; + let mut crtcs = self .crtcs .lock() - .map_err(|_| DriverError::Initialization("crtc lock poisoned".into()))?; + .map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))?; let crtc = crtcs .iter_mut() - .find(|c| c.id == crtc_id) + .find(|candidate| candidate.id == crtc_id) .ok_or_else(|| DriverError::NotFound(format!("unknown CRTC {crtc_id}")))?; crtc.program(fb_handle, connectors, mode) } - fn page_flip(&self, crtc_id: u32, _fb_handle: u32, _flags: u32) -> Result { - let crtcs = self - .crtcs + fn page_flip(&self, crtc_id: u32, fb_handle: u32, _flags: u32) -> Result { + let (mode, connector_id) = { + let mut crtcs = self + .crtcs + .lock() + .map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))?; + let crtc = crtcs + .iter_mut() + .find(|candidate| candidate.id == crtc_id) + .ok_or_else(|| DriverError::NotFound(format!("unknown CRTC {crtc_id}")))?; + let mode = crtc.mode.clone().ok_or(DriverError::InvalidArgument( + "page_flip requires an active mode on the CRTC", + ))?; + let connector_id = + crtc.connectors + .first() + .copied() + .ok_or(DriverError::InvalidArgument( + "page_flip requires an attached connector", + ))?; + (mode, connector_id) + }; + + let scanout_id = self.scanout_for_connector(connector_id)?; + self.program_scanout(scanout_id, fb_handle, &mode)?; + self.crtcs .lock() - .map_err(|_| DriverError::Initialization("crtc lock poisoned".into()))?; - if !crtcs.iter().any(|c| c.id == crtc_id) { - return Err(DriverError::NotFound(format!("unknown CRTC {crtc_id}"))); - } - self.vblank_count.fetch_add(1, Ordering::SeqCst); - Ok(self.vblank_count.load(Ordering::SeqCst)) + .map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))? + .iter_mut() + .find(|candidate| candidate.id == crtc_id) + .ok_or_else(|| DriverError::NotFound(format!("unknown CRTC {crtc_id}")))? + .current_fb = fb_handle; + self.queue_vblank(crtc_id) } fn get_vblank(&self, crtc_id: u32) -> Result { let crtcs = self .crtcs .lock() - .map_err(|_| DriverError::Initialization("crtc lock poisoned".into()))?; - if !crtcs.iter().any(|c| c.id == crtc_id) { + .map_err(|_| DriverError::Initialization("VirtIO CRTC state poisoned".into()))?; + if !crtcs.iter().any(|crtc| crtc.id == crtc_id) { return Err(DriverError::NotFound(format!("unknown CRTC {crtc_id}"))); } - Ok(self.vblank_count.load(Ordering::SeqCst)) + Ok(*self + .crtc_vblank_counts + .lock() + .map_err(|_| DriverError::Initialization("VirtIO vblank counters poisoned".into()))? + .get(&crtc_id) + .unwrap_or(&0)) } - fn gem_create(&self, size: u64) -> Result { - self.gem + fn gem_create(&self, size: u64, width: u32, height: u32) -> Result { + let handle = { + let mut gem = self + .gem + .lock() + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))?; + gem.create(size)? + }; + + let (phys_addr, gem_size) = { + let gem = self + .gem + .lock() + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))?; + let object = gem.object(handle)?; + (object.phys_addr as u64, object.size) + }; + + let resource = self + .resources .lock() - .map_err(|_| DriverError::Buffer("VirtIO GEM poisoned".into()))? - .create(size) + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .create_resource(handle, gem_size, width, height)?; + + info!( + "redox-drm: VirtIO gem_create size={} w={} h={} -> handle={} resource_id={} resource_w={} resource_h={}", + size, width, height, handle, resource.resource_id, resource.width, resource.height + ); + + let create_result = (|| { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + device.resource_create_2d(resource.resource_id, resource.width, resource.height)?; + device.resource_attach_backing(resource.resource_id, phys_addr, gem_size as u32) + })(); + + if let Err(error) = create_result { + warn!( + "redox-drm: VirtIO resource_create_2d({},{},{}) or attach_backing failed: {:?}", + resource.resource_id, resource.width, resource.height, error + ); + let _ = self + .resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .remove(handle); + let _ = self + .gem + .lock() + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))? + .close(handle); + return Err(error); + } + + Ok(handle) } fn gem_close(&self, handle: GemHandle) -> Result<()> { + let resource = self + .resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .get(handle)?; + + { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + device.resource_unref(resource.resource_id)?; + } + self.gem .lock() - .map_err(|_| DriverError::Buffer("VirtIO GEM poisoned".into()))? - .close(handle) + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))? + .close(handle)?; + self.resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .remove(handle); + Ok(()) } fn gem_mmap(&self, handle: GemHandle) -> Result { self.gem .lock() - .map_err(|_| DriverError::Buffer("VirtIO GEM poisoned".into()))? + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))? .mmap(handle) } fn gem_size(&self, handle: GemHandle) -> Result { self.gem .lock() - .map_err(|_| DriverError::Buffer("VirtIO GEM poisoned".into()))? + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))? .object(handle) - .map(|o| o.size) + .map(|object| object.size) } fn get_edid(&self, connector_id: u32) -> Vec { match self.connectors.lock() { Ok(connectors) => connectors .iter() - .find(|c| c.info.id == connector_id) - .map(|c| c.edid.clone()) + .find(|connector| connector.info.id == connector_id) + .map(|connector| connector.edid.clone()) .unwrap_or_else(synthetic_edid), Err(_) => synthetic_edid(), } } fn handle_irq(&self) -> Result> { - self.vblank_count.fetch_add(1, Ordering::SeqCst); - Ok(None) + self.handle_irq_event() + } + + fn has_virgl_3d(&self) -> bool { + self.device + .lock() + .map(|device| device.has_virgl_3d) + .unwrap_or(false) + } + + fn virgl_get_capset_info(&self, capset_index: u32) -> Result { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl capset info")); + } + let info = device.get_capset_info(capset_index)?; + Ok(VirglCapsetInfo { + capset_id: info.capset_id, + capset_max_version: info.capset_max_version, + capset_max_size: info.capset_max_size, + }) + } + + fn virgl_get_capset(&self, capset_id: u32, capset_version: u32) -> Result { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl capset")); + } + let capset = device.get_capset(capset_id, capset_version)?; + Ok(VirglCapset { + data: capset.capset_data.to_vec(), + }) + } + + fn virgl_ctx_create(&self, ctx_id: u32, debug_name: &str, context_init: u32) -> Result<()> { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl context creation")); + } + device.ctx_create(ctx_id, debug_name, context_init) + } + + fn virgl_ctx_destroy(&self, ctx_id: u32) -> Result<()> { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl context destruction")); + } + device.ctx_destroy(ctx_id) + } + + #[allow(clippy::too_many_arguments)] + fn virgl_resource_create_3d( + &self, + resource_id: u32, + target: u32, + format: u32, + bind: u32, + width: u32, + height: u32, + depth: u32, + array_size: u32, + last_level: u32, + nr_samples: u32, + flags: u32, + ctx_id: u32, + ) -> Result<()> { + let handle = resource_id; + let current = self.virgl_resource(handle)?; + let replacement_resource_id = self + .resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .allocate_resource_id(handle)?; + let (phys_addr, size) = { + let gem = self + .gem + .lock() + .map_err(|_| DriverError::Buffer("VirtIO GEM manager poisoned".into()))?; + let object = gem.object(handle)?; + (object.phys_addr as u64, object.size) + }; + let backing_length = u32::try_from(size).map_err(|_| { + DriverError::Buffer(format!( + "VirtIO GEM handle {handle} backing size {size} exceeds u32" + )) + })?; + + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl 3D resource creation")); + } + device.resource_create_3d( + replacement_resource_id, + target, + format, + bind, + width, + height, + depth, + array_size, + last_level, + nr_samples, + flags, + ctx_id, + )?; + if let Err(error) = + device.resource_attach_backing(replacement_resource_id, phys_addr, backing_length) + { + let _ = device.resource_unref(replacement_resource_id); + return Err(error); + } + if let Err(error) = device.resource_unref(current.resource_id) { + let _ = device.resource_unref(replacement_resource_id); + return Err(error); + } + + self.resources + .lock() + .map_err(|_| DriverError::Buffer("VirtIO resource state poisoned".into()))? + .update_layout( + handle, + replacement_resource_id, + width.max(1), + height.max(1), + current.stride, + size, + )?; + Ok(()) + } + + fn virgl_submit_3d(&self, ctx_id: u32, command_data: &[u8]) -> Result<()> { + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl submit 3D")); + } + device.submit_3d(ctx_id, command_data) + } + + #[allow(clippy::too_many_arguments)] + fn virgl_transfer_to_host_3d( + &self, + ctx_id: u32, + resource_id: u32, + x: u32, + y: u32, + z: u32, + w: u32, + h: u32, + d: u32, + offset: u64, + level: u32, + stride: u32, + layer_stride: u32, + ) -> Result<()> { + let resource = self.virgl_resource(resource_id)?; + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl transfer to host")); + } + device.transfer_to_host_3d( + ctx_id, + resource.resource_id, + VirtioGpuBox { x, y, z, w, h, d }, + offset, + level, + stride, + layer_stride, + ) + } + + #[allow(clippy::too_many_arguments)] + fn virgl_transfer_from_host_3d( + &self, + ctx_id: u32, + resource_id: u32, + x: u32, + y: u32, + z: u32, + w: u32, + h: u32, + d: u32, + offset: u64, + level: u32, + stride: u32, + layer_stride: u32, + ) -> Result<()> { + let resource = self.virgl_resource(resource_id)?; + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl transfer from host")); + } + device.transfer_from_host_3d( + ctx_id, + resource.resource_id, + VirtioGpuBox { x, y, z, w, h, d }, + offset, + level, + stride, + layer_stride, + ) + } + + fn virgl_resource_attach_backing( + &self, + resource_id: u32, + phys_addr: u64, + length: u32, + ) -> Result<()> { + let resource = self.virgl_resource(resource_id)?; + let mut device = self + .device + .lock() + .map_err(|_| DriverError::Initialization("VirtIO device state poisoned".into()))?; + if !device.has_virgl_3d { + return Err(DriverError::Unsupported("virgl resource attach backing")); + } + device.resource_attach_backing(resource.resource_id, phys_addr, length) } } + +impl VirtioGpuDevice { + fn submit_request(&mut self, request: &[u8], response_len: usize) -> Result> { + let total = request + .len() + .checked_add(response_len) + .ok_or(DriverError::InvalidArgument( + "VirtIO request buffer length overflow", + ))?; + let mut dma = DmaBuffer::allocate(total.max(1), 64).map_err(|e| { + DriverError::Buffer(format!("VirtIO command DMA allocation failed: {e}")) + })?; + + let request_ptr = dma.as_mut_ptr(); + // SAFETY: `dma` is at least `total` bytes, and `request`/response ranges do not overlap. + unsafe { + core::ptr::copy_nonoverlapping(request.as_ptr(), request_ptr, request.len()); + core::ptr::write_bytes(request_ptr.add(request.len()), 0, response_len); + } + + let mut verify_mismatch = false; + unsafe { + for (i, &expected) in request.iter().enumerate().take(8) { + if *request_ptr.add(i) != expected { + verify_mismatch = true; + break; + } + } + } + if verify_mismatch { + let readback: Vec = unsafe { core::slice::from_raw_parts(request_ptr, request.len().min(40)) }.to_vec(); + warn!( + "redox-drm: VirtIO DMA VERIFY MISMATCH! written={:02x?} readback={:02x?}", + &request[..request.len().min(40)], + readback + ); + } + + info!( + "redox-drm: VirtIO submit_request dma_phys={:#x} req_len={} resp_addr={:#x} resp_len={}", + dma.physical_address(), + request.len(), + dma.physical_address() + request.len(), + response_len + ); + + self.ctrlq.submit_request( + &self.transport, + dma.physical_address() as u64, + request.len() as u32, + (dma.physical_address() + request.len()) as u64, + response_len as u32, + COMMAND_TIMEOUT, + )?; + + let mut response = vec![0u8; response_len]; + // SAFETY: The device wrote `response_len` bytes into the response slice in `dma`. + unsafe { + core::ptr::copy_nonoverlapping( + request_ptr.add(request.len()), + response.as_mut_ptr(), + response_len, + ); + } + if response_len >= core::mem::size_of::() { + let hdr = unsafe { (response.as_ptr() as *const VirtioGpuCtrlHeader).read_unaligned() }; + info!( + "redox-drm: VirtIO submit_request response type={:#06x} ({}) flags={:#x} fence_id={} ctx_id={}", + hdr.type_, + response_type_name(hdr.type_), + hdr.flags, + hdr.fence_id, + hdr.ctx_id, + ); + if hdr.type_ != VIRTIO_GPU_RESP_OK_NODATA + && hdr.type_ != VIRTIO_GPU_RESP_OK_DISPLAY_INFO + && hdr.type_ != VIRTIO_GPU_RESP_OK_EDID + && hdr.type_ != VIRTIO_GPU_RESP_OK_CAPSET_INFO + && hdr.type_ != VIRTIO_GPU_RESP_OK_CAPSET + && hdr.type_ != VIRTIO_GPU_RESP_OK_MAP_INFO + { + warn!( + "redox-drm: VirtIO unexpected response header bytes: {:02x?}", + &response[..response_len.min(24)] + ); + } + } + Ok(response) + } + + fn submit_nodata(&mut self, request: &T) -> Result<()> { + let response = self.submit_request( + bytes_of(request), + core::mem::size_of::(), + )?; + let header = read_struct::(&response)?; + validate_response_type(&header, VIRTIO_GPU_RESP_OK_NODATA) + } + + fn get_display_info(&mut self) -> Result { + let request = VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_GET_DISPLAY_INFO); + let response = self.submit_request( + bytes_of(&request), + core::mem::size_of::(), + )?; + let display = read_struct::(&response)?; + validate_response_type(&display.hdr, VIRTIO_GPU_RESP_OK_DISPLAY_INFO)?; + Ok(display) + } + + fn get_edid(&mut self, scanout: u32) -> Result> { + let request = VirtioGpuCmdGetEdid { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_GET_EDID), + scanout, + padding: 0, + }; + let response = self.submit_request( + bytes_of(&request), + core::mem::size_of::(), + )?; + let edid = read_struct::(&response)?; + validate_response_type(&edid.hdr, VIRTIO_GPU_RESP_OK_EDID)?; + let size = usize::try_from(edid.size).map_err(|_| { + DriverError::Io(format!("VirtIO EDID size {} does not fit usize", edid.size)) + })?; + Ok(edid.edid[..size.min(edid.edid.len())].to_vec()) + } + + fn resource_create_2d(&mut self, resource_id: u32, width: u32, height: u32) -> Result<()> { + let request = VirtioGpuResourceCreate2d { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_CREATE_2D), + resource_id, + format: VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM, + width, + height, + }; + + let request_bytes = bytes_of(&request); + info!( + "redox-drm: VirtIO resource_create_2d rid={} fmt={} w={} h={} cmd_bytes={:02x?}", + resource_id, + VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM, + width, + height, + &request_bytes[..request_bytes.len().min(48)] + ); + + let response = self.submit_request(request_bytes, core::mem::size_of::())?; + let header = read_struct::(&response)?; + info!( + "redox-drm: VirtIO resource_create_2d response type={:#06x} ({})", + header.type_, + response_type_name(header.type_) + ); + validate_response_type(&header, VIRTIO_GPU_RESP_OK_NODATA) + } + + fn resource_attach_backing( + &mut self, + resource_id: u32, + phys_addr: u64, + length: u32, + ) -> Result<()> { + let attach = VirtioGpuResourceAttachBacking { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING), + resource_id, + nr_entries: 1, + }; + let entry = VirtioGpuMemEntry { + addr: phys_addr, + length, + padding: 0, + }; + + let mut bytes = Vec::with_capacity( + core::mem::size_of::() + + core::mem::size_of::(), + ); + append_bytes(&mut bytes, &attach); + append_bytes(&mut bytes, &entry); + + let response = self.submit_request(&bytes, core::mem::size_of::())?; + let header = read_struct::(&response)?; + validate_response_type(&header, VIRTIO_GPU_RESP_OK_NODATA) + } + + fn resource_unref(&mut self, resource_id: u32) -> Result<()> { + let request = VirtioGpuResourceUnref { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_UNREF), + resource_id, + padding: 0, + }; + info!("redox-drm: VirtIO resource_unref rid={} cmd_bytes={:02x?}", resource_id, bytes_of(&request)); + let result = self.submit_nodata(&request); + info!("redox-drm: VirtIO resource_unref rid={} result={:?}", resource_id, &result); + result + } + + fn set_scanout( + &mut self, + scanout_id: u32, + resource_id: u32, + rect: VirtioGpuRect, + ) -> Result<()> { + let request = VirtioGpuSetScanout { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_SET_SCANOUT), + rect, + scanout_id, + resource_id, + }; + self.submit_nodata(&request) + } + + fn transfer_to_host_2d(&mut self, resource_id: u32, rect: VirtioGpuRect) -> Result<()> { + let request = VirtioGpuTransferToHost2d { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D), + rect, + offset: 0, + resource_id, + padding: 0, + }; + self.submit_nodata(&request) + } + + fn resource_flush(&mut self, resource_id: u32, rect: VirtioGpuRect) -> Result<()> { + let request = VirtioGpuResourceFlush { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_FLUSH), + rect, + resource_id, + padding: 0, + }; + self.submit_nodata(&request) + } + + fn ctx_create(&mut self, ctx_id: u32, debug_name: &str, context_init: u32) -> Result<()> { + let mut request = VirtioGpuCtxCreate { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_CTX_CREATE), + nlen: debug_name.len().min(64) as u32, + context_init, + debug_name: [0; 64], + }; + request.hdr.ctx_id = ctx_id; + let copy_len = usize::try_from(request.nlen) + .unwrap_or(0) + .min(request.debug_name.len()); + request.debug_name[..copy_len].copy_from_slice(&debug_name.as_bytes()[..copy_len]); + self.submit_nodata(&request) + } + + fn ctx_destroy(&mut self, ctx_id: u32) -> Result<()> { + let mut request = VirtioGpuCtxDestroy { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_CTX_DESTROY), + }; + request.hdr.ctx_id = ctx_id; + self.submit_nodata(&request) + } + + #[allow(clippy::too_many_arguments)] + fn resource_create_3d( + &mut self, + resource_id: u32, + target: u32, + format: u32, + bind: u32, + width: u32, + height: u32, + depth: u32, + array_size: u32, + last_level: u32, + nr_samples: u32, + flags: u32, + ctx_id: u32, + ) -> Result<()> { + let mut request = VirtioGpuResourceCreate3d { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_RESOURCE_CREATE_3D), + resource_id, + target, + format, + bind, + width, + height, + depth, + array_size, + last_level, + nr_samples, + flags, + padding: 0, + }; + request.hdr.ctx_id = ctx_id; + self.submit_nodata(&request) + } + + #[allow(clippy::too_many_arguments)] + fn transfer_to_host_3d( + &mut self, + ctx_id: u32, + resource_id: u32, + box_: VirtioGpuBox, + offset: u64, + level: u32, + stride: u32, + layer_stride: u32, + ) -> Result<()> { + let mut request = VirtioGpuTransferHost3d { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D), + box_, + offset, + resource_id, + level, + stride, + layer_stride, + }; + request.hdr.ctx_id = ctx_id; + self.submit_nodata(&request) + } + + #[allow(clippy::too_many_arguments)] + fn transfer_from_host_3d( + &mut self, + ctx_id: u32, + resource_id: u32, + box_: VirtioGpuBox, + offset: u64, + level: u32, + stride: u32, + layer_stride: u32, + ) -> Result<()> { + let mut request = VirtioGpuTransferHost3d { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D), + box_, + offset, + resource_id, + level, + stride, + layer_stride, + }; + request.hdr.ctx_id = ctx_id; + self.submit_nodata(&request) + } + + fn submit_3d(&mut self, ctx_id: u32, command_data: &[u8]) -> Result<()> { + let size = u32::try_from(command_data.len()).map_err(|_| { + DriverError::InvalidArgument("VirtIO 3D command buffer exceeds u32 size") + })?; + let mut submit = VirtioGpuCmdSubmit { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_SUBMIT_3D), + size, + padding: 0, + }; + submit.hdr.ctx_id = ctx_id; + + let mut request = + Vec::with_capacity(core::mem::size_of::() + command_data.len()); + append_bytes(&mut request, &submit); + request.extend_from_slice(command_data); + + let response = + self.submit_request(&request, core::mem::size_of::())?; + let header = read_struct::(&response)?; + validate_response_type(&header, VIRTIO_GPU_RESP_OK_NODATA) + } + + fn get_capset_info(&mut self, capset_index: u32) -> Result { + let request = VirtioGpuGetCapsetInfo { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_GET_CAPSET_INFO), + capset_index, + padding: 0, + }; + let response = self.submit_request( + bytes_of(&request), + core::mem::size_of::(), + )?; + let info = read_struct::(&response)?; + validate_response_type(&info.hdr, VIRTIO_GPU_RESP_OK_CAPSET_INFO)?; + Ok(info) + } + + fn get_capset(&mut self, capset_id: u32, capset_version: u32) -> Result { + let request = VirtioGpuGetCapset { + hdr: VirtioGpuCtrlHeader::command(VIRTIO_GPU_CMD_GET_CAPSET), + capset_id, + capset_version, + }; + let response = self.submit_request( + bytes_of(&request), + core::mem::size_of::(), + )?; + let capset = read_struct::(&response)?; + validate_response_type(&capset.hdr, VIRTIO_GPU_RESP_OK_CAPSET)?; + Ok(capset) + } +} + +fn load_display_topology(device: &mut VirtioGpuDevice) -> Result<(Vec, Vec)> { + let display_info = device.get_display_info()?; + let config = read_device_config(&device.transport); + let connector_count = usize::try_from(config.num_scanouts.max(1)).unwrap_or(1); + let visible_count = connector_count.min(display_info.pmodes.len()); + + let mut connectors = Vec::with_capacity(visible_count); + let mut crtcs = Vec::with_capacity(visible_count); + for scanout in 0..visible_count { + let mode = display_info.pmodes[scanout]; + let edid = if device.supports_edid { + device.get_edid(scanout as u32).unwrap_or_else(|error| { + // EDID failure is expected when no display is attached or when + // the VirtIO GPU has not yet been programmed with a scanout resource. + // The synthetic EDID fallback provides usable default modes. + info!( + "redox-drm: VirtIO GET_EDID unavailable for scanout {} ({}), using synthetic EDID", + scanout, error + ); + synthetic_edid() + }) + } else { + synthetic_edid() + }; + + let modes = build_modes(&mode, &edid); + // VirtIO GPU virtual outputs are always connected — they are software + // scanouts, not physical ports. QEMU reports enabled=0 until an + // explicit display is configured, but a virtual connector should be + // treated as a live output so compositors (KWin, etc.) render to it. + let has_dimensions = mode.rect.width != 0 && mode.rect.height != 0; + let info = ConnectorInfo { + id: scanout as u32 + 1, + connector_type: ConnectorType::Virtual, + connector_type_id: scanout as u32 + 1, + connection: if has_dimensions || !modes.is_empty() { + ConnectorStatus::Connected + } else { + ConnectorStatus::Disconnected + }, + mm_width: millimeters_for_pixels(mode.rect.width), + mm_height: millimeters_for_pixels(mode.rect.height), + encoder_id: scanout as u32 + 1, + modes, + }; + + connectors.push(Connector { info, edid }); + crtcs.push(Crtc::new(scanout as u32 + 1)); + } + + if connectors.is_empty() { + connectors.push(Connector { + info: ConnectorInfo { + id: 1, + connector_type: ConnectorType::Virtual, + connector_type_id: 1, + connection: ConnectorStatus::Unknown, + mm_width: 0, + mm_height: 0, + encoder_id: 1, + modes: vec![ModeInfo::default_1080p()], + }, + edid: synthetic_edid(), + }); + crtcs.push(Crtc::new(1)); + } + + Ok((connectors, crtcs)) +} + +fn read_device_config(transport: &VirtioModernPciTransport) -> VirtioGpuConfig { + VirtioGpuConfig { + events_read: transport.read_events(), + events_clear: 0, + num_scanouts: transport.num_scanouts(), + num_capsets: 0, + } +} + +fn build_modes(display: &VirtioGpuDisplayOne, edid: &[u8]) -> Vec { + let fallback = display_mode(display.rect.width, display.rect.height); + let mut modes = ModeInfo::from_edid(edid); + if modes.is_empty() { + return vec![fallback]; + } + if !modes.iter().any(|mode| { + u32::from(mode.hdisplay) == display.rect.width + && u32::from(mode.vdisplay) == display.rect.height + }) { + modes.insert(0, fallback); + } + modes +} + +fn display_mode(width: u32, height: u32) -> ModeInfo { + let width = width.max(1); + let height = height.max(1); + ModeInfo { + name: format!("{}x{}@60", width, height), + clock: width.saturating_mul(height).saturating_mul(60) / 100, + hdisplay: width.min(u32::from(u16::MAX)) as u16, + hsync_start: width.saturating_add(16).min(u32::from(u16::MAX)) as u16, + hsync_end: width.saturating_add(48).min(u32::from(u16::MAX)) as u16, + htotal: width.saturating_add(160).min(u32::from(u16::MAX)) as u16, + hskew: 0, + vdisplay: height.min(u32::from(u16::MAX)) as u16, + vsync_start: height.saturating_add(3).min(u32::from(u16::MAX)) as u16, + vsync_end: height.saturating_add(6).min(u32::from(u16::MAX)) as u16, + vtotal: height.saturating_add(30).min(u32::from(u16::MAX)) as u16, + vscan: 0, + vrefresh: 60, + flags: 0, + type_: 0, + } +} + +fn millimeters_for_pixels(pixels: u32) -> u32 { + pixels.saturating_mul(254).div_ceil(960) +} + +fn rect_for_mode(mode: &ModeInfo) -> VirtioGpuRect { + VirtioGpuRect { + x: 0, + y: 0, + width: u32::from(mode.hdisplay), + height: u32::from(mode.vdisplay), + } +} + +fn first_hotplug_change(previous: &[ConnectorInfo], current: &[ConnectorInfo]) -> Option { + if previous.len() != current.len() { + return current.first().map(|connector| connector.id); + } + + previous.iter().zip(current.iter()).find_map(|(old, new)| { + (old.connection != new.connection + || old.mm_width != new.mm_width + || old.mm_height != new.mm_height + || !modes_match(&old.modes, &new.modes)) + .then_some(new.id) + }) +} + +fn modes_match(previous: &[ModeInfo], current: &[ModeInfo]) -> bool { + previous.len() == current.len() + && previous.iter().zip(current.iter()).all(|(old, new)| { + old.name == new.name + && old.clock == new.clock + && old.hdisplay == new.hdisplay + && old.hsync_start == new.hsync_start + && old.hsync_end == new.hsync_end + && old.htotal == new.htotal + && old.hskew == new.hskew + && old.vdisplay == new.vdisplay + && old.vsync_start == new.vsync_start + && old.vsync_end == new.vsync_end + && old.vtotal == new.vtotal + && old.vscan == new.vscan + && old.vrefresh == new.vrefresh + && old.flags == new.flags + && old.type_ == new.type_ + }) +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/resource.rs b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/resource.rs new file mode 100644 index 0000000000..38342ca746 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/resource.rs @@ -0,0 +1,118 @@ +use std::collections::BTreeMap; + +use crate::driver::{DriverError, Result}; +use crate::gem::GemHandle; + +#[derive(Clone, Copy, Debug)] +pub struct VirtioResource { + pub handle: GemHandle, + pub resource_id: u32, + pub width: u32, + pub height: u32, + pub stride: u32, + pub size: u64, +} + +pub struct ResourceManager { + next_resource_id: u32, + resources: BTreeMap, +} + +impl ResourceManager { + pub fn new() -> Self { + Self { + next_resource_id: 256, + resources: BTreeMap::new(), + } + } + + pub fn create_resource( + &mut self, + handle: GemHandle, + size: u64, + width: u32, + height: u32, + ) -> Result { + if self.resources.contains_key(&handle) { + return Err(DriverError::Buffer(format!( + "VirtIO resource already exists for GEM handle {handle}" + ))); + } + + let (res_width, res_height) = if width > 0 && height > 0 { + (width, height) + } else { + let bytes_per_pixel = 4u64; + let pixel_count = size.div_ceil(bytes_per_pixel).max(1); + let side = (pixel_count as f64).sqrt() as u32; + let side = side.max(1); + // width * height * 4 must cover the full buffer size + let adjusted_height = if side > 0 { + u32::try_from(pixel_count.div_ceil(u64::from(side))).unwrap_or(u32::MAX).max(1) + } else { + u32::try_from(pixel_count).unwrap_or(u32::MAX).max(1) + }; + (side, adjusted_height) + }; + + let stride = res_width + .checked_mul(4) + .ok_or_else(|| DriverError::Buffer(format!( + "VirtIO resource stride overflow for width {res_width}" + )))?; + + let resource = VirtioResource { + handle, + resource_id: self.next_resource_id, + width: res_width, + height: res_height, + stride, + size, + }; + self.next_resource_id = self.next_resource_id.saturating_add(1); + self.resources.insert(handle, resource); + Ok(resource) + } + + pub fn get(&self, handle: GemHandle) -> Result { + self.resources.get(&handle).copied().ok_or_else(|| { + DriverError::NotFound(format!("unknown VirtIO resource for GEM handle {handle}")) + }) + } + + pub fn allocate_resource_id(&mut self, handle: GemHandle) -> Result { + if !self.resources.contains_key(&handle) { + return Err(DriverError::NotFound(format!( + "unknown VirtIO resource for GEM handle {handle}" + ))); + } + + let resource_id = self.next_resource_id; + self.next_resource_id = self.next_resource_id.saturating_add(1); + Ok(resource_id) + } + + pub fn update_layout( + &mut self, + handle: GemHandle, + resource_id: u32, + width: u32, + height: u32, + stride: u32, + size: u64, + ) -> Result { + let resource = self.resources.get_mut(&handle).ok_or_else(|| { + DriverError::NotFound(format!("unknown VirtIO resource for GEM handle {handle}")) + })?; + resource.resource_id = resource_id; + resource.width = width; + resource.height = height; + resource.stride = stride; + resource.size = size; + Ok(*resource) + } + + pub fn remove(&mut self, handle: GemHandle) -> Option { + self.resources.remove(&handle) + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/transport.rs b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/transport.rs new file mode 100644 index 0000000000..acfa15271b --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/transport.rs @@ -0,0 +1,404 @@ +use log::info; +use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion}; +use redox_driver_sys::pci::{PciDevice, PciDeviceInfo, PCI_CAP_ID_VNDR}; + +use crate::driver::{DriverError, Result}; + +pub const VIRTIO_PCI_CAP_COMMON_CFG: u8 = 1; +pub const VIRTIO_PCI_CAP_NOTIFY_CFG: u8 = 2; +pub const VIRTIO_PCI_CAP_ISR_CFG: u8 = 3; +pub const VIRTIO_PCI_CAP_DEVICE_CFG: u8 = 4; + +const DEVICE_STATUS_ACKNOWLEDGE: u8 = 0x01; +const DEVICE_STATUS_DRIVER: u8 = 0x02; +const DEVICE_STATUS_DRIVER_OK: u8 = 0x04; +const DEVICE_STATUS_FEATURES_OK: u8 = 0x08; +const DEVICE_STATUS_FAILED: u8 = 0x80; + +const COMMON_DEVICE_FEATURE_SELECT: usize = 0x00; +const COMMON_DEVICE_FEATURE: usize = 0x04; +const COMMON_DRIVER_FEATURE_SELECT: usize = 0x08; +const COMMON_DRIVER_FEATURE: usize = 0x0C; +const COMMON_MSIX_CONFIG: usize = 0x10; +const COMMON_NUM_QUEUES: usize = 0x12; +const COMMON_DEVICE_STATUS: usize = 0x14; +const COMMON_QUEUE_SELECT: usize = 0x16; +const COMMON_QUEUE_SIZE: usize = 0x18; +const COMMON_QUEUE_MSIX_VECTOR: usize = 0x1A; +const COMMON_QUEUE_ENABLE: usize = 0x1C; +const COMMON_QUEUE_NOTIFY_OFF: usize = 0x1E; +const COMMON_QUEUE_DESC_LO: usize = 0x20; +const COMMON_QUEUE_DESC_HI: usize = 0x24; +const COMMON_QUEUE_AVAIL_LO: usize = 0x28; +const COMMON_QUEUE_AVAIL_HI: usize = 0x2C; +const COMMON_QUEUE_USED_LO: usize = 0x30; +const COMMON_QUEUE_USED_HI: usize = 0x34; +const COMMON_CFG_REQUIRED_BYTES: usize = COMMON_QUEUE_USED_HI + core::mem::size_of::(); + +const ISR_STATUS_OFFSET: usize = 0; +const ISR_CFG_REQUIRED_BYTES: usize = ISR_STATUS_OFFSET + core::mem::size_of::(); +const DEVICE_CFG_EVENTS_READ: usize = 0; +const DEVICE_CFG_EVENTS_CLEAR: usize = 4; +const DEVICE_CFG_NUM_SCANOUTS: usize = 8; +const DEVICE_CFG_REQUIRED_BYTES: usize = DEVICE_CFG_NUM_SCANOUTS + core::mem::size_of::(); +const NOTIFY_CFG_REQUIRED_BYTES: usize = core::mem::size_of::(); + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtioPciCap { + cap_vndr: u8, + cap_next: u8, + cap_len: u8, + cfg_type: u8, + bar: u8, + id: u8, + padding: [u8; 2], + offset: u32, + length: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtioPciNotifyCap { + cap: VirtioPciCap, + notify_off_multiplier: u32, +} + +#[derive(Debug)] +pub struct QueueConfig { + pub index: u16, + pub size: u16, + pub notify_off: u16, +} + +pub struct VirtioModernPciTransport { + common_cfg: MmioRegion, + notify_cfg: MmioRegion, + isr_cfg: MmioRegion, + device_cfg: MmioRegion, + notify_off_multiplier: u32, +} + +impl VirtioModernPciTransport { + pub fn new(info: &PciDeviceInfo, pci: &mut PciDevice) -> Result { + let mut common_cap = None; + let mut notify_cap = None; + let mut isr_cap = None; + let mut device_cap = None; + + // Walk the PCI capability linked list directly from config space. + // The pcid-spawner handoff provides an empty capabilities vector, + // so we cannot rely on info.capabilities. + let cap_ptr = pci.read_config_byte(0x34).map_err(pci_error)?; + if cap_ptr == 0 { + return Err(DriverError::Pci( + "VirtIO GPU has no PCI capabilities".into(), + )); + } + + let mut offset = cap_ptr; + let mut visited = 0u8; + const MAX_CAPS: u8 = 48; + while offset != 0 && visited < MAX_CAPS { + visited += 1; + let cap_id = pci.read_config_byte(offset as u64).map_err(pci_error)?; + let cap_next = pci.read_config_byte(offset as u64 + 1).map_err(pci_error)?; + + if cap_id == PCI_CAP_ID_VNDR { + let raw = read_pci_cap(pci, offset)?; + match raw.cfg_type { + VIRTIO_PCI_CAP_COMMON_CFG => common_cap = Some(raw), + VIRTIO_PCI_CAP_NOTIFY_CFG => notify_cap = Some(read_notify_cap(pci, offset)?), + VIRTIO_PCI_CAP_ISR_CFG => isr_cap = Some(raw), + VIRTIO_PCI_CAP_DEVICE_CFG => device_cap = Some(raw), + _ => {} + } + } + + offset = cap_next; + } + + info!( + "redox-drm: VirtIO PCI capability scan found {} caps, common={} notify={} isr={} device={}", + visited, + common_cap.is_some(), + notify_cap.is_some(), + isr_cap.is_some(), + device_cap.is_some(), + ); + + let common_cap = common_cap.ok_or_else(|| { + DriverError::Pci("VirtIO modern common_cfg capability is missing".into()) + })?; + let notify_cap = notify_cap.ok_or_else(|| { + DriverError::Pci("VirtIO modern notify_cfg capability is missing".into()) + })?; + let isr_cap = isr_cap.ok_or_else(|| { + DriverError::Pci("VirtIO modern isr_cfg capability is missing".into()) + })?; + let device_cap = device_cap.ok_or_else(|| { + DriverError::Pci("VirtIO modern device_cfg capability is missing".into()) + })?; + + let common_cfg = + map_cap_region(info, &common_cap, "common_cfg", COMMON_CFG_REQUIRED_BYTES)?; + let notify_cfg = map_cap_region( + info, + ¬ify_cap.cap, + "notify_cfg", + NOTIFY_CFG_REQUIRED_BYTES, + )?; + let isr_cfg = map_cap_region(info, &isr_cap, "isr_cfg", ISR_CFG_REQUIRED_BYTES)?; + let device_cfg = + map_cap_region(info, &device_cap, "device_cfg", DEVICE_CFG_REQUIRED_BYTES)?; + + info!( + "redox-drm: VirtIO modern PCI transport mapped for {} (notify multiplier {})", + info.location, notify_cap.notify_off_multiplier + ); + + Ok(Self { + common_cfg, + notify_cfg, + isr_cfg, + device_cfg, + notify_off_multiplier: notify_cap.notify_off_multiplier, + }) + } + + pub fn initialize_device(&mut self, requested_features: u64) -> Result { + info!("redox-drm: VirtIO reset device"); + self.write_device_status(0); + + self.write_device_status(DEVICE_STATUS_ACKNOWLEDGE); + self.write_device_status(DEVICE_STATUS_ACKNOWLEDGE | DEVICE_STATUS_DRIVER); + + let available = self.read_device_features(); + if (available & requested_features) & (1u64 << 32) == 0 { + self.fail(format!( + "VirtIO GPU missing required VIRTIO_F_VERSION_1 feature (device features={available:#x})" + ))?; + } + + let negotiated = available & requested_features; + self.write_driver_features(negotiated); + + let mut status = self.device_status(); + status |= DEVICE_STATUS_FEATURES_OK; + self.write_device_status(status); + + if self.device_status() & DEVICE_STATUS_FEATURES_OK == 0 { + self.fail("VirtIO GPU rejected FEATURES_OK during negotiation".into())?; + } + + info!("redox-drm: VirtIO negotiated features device={available:#x} driver={negotiated:#x}"); + Ok(negotiated) + } + + pub fn finalize_device(&mut self) { + let status = self.device_status() | DEVICE_STATUS_DRIVER_OK; + self.write_device_status(status); + } + + pub fn device_status(&self) -> u8 { + self.common_cfg.read8(COMMON_DEVICE_STATUS) + } + + pub fn read_isr_status(&self) -> u8 { + self.isr_cfg.read8(ISR_STATUS_OFFSET) + } + + pub fn read_events(&self) -> u32 { + self.device_cfg.read32(DEVICE_CFG_EVENTS_READ) + } + + pub fn clear_events(&self, mask: u32) { + self.device_cfg.write32(DEVICE_CFG_EVENTS_CLEAR, mask); + } + + pub fn num_scanouts(&self) -> u32 { + self.device_cfg.read32(DEVICE_CFG_NUM_SCANOUTS) + } + + pub fn prepare_queue(&self, index: u16, requested_size: u16) -> Result { + self.select_queue(index); + let device_size = self.common_cfg.read16(COMMON_QUEUE_SIZE); + if device_size == 0 { + return Err(DriverError::Initialization(format!( + "VirtIO queue {index} reports size 0" + ))); + } + + let size = device_size.min(requested_size); + let notify_off = self.common_cfg.read16(COMMON_QUEUE_NOTIFY_OFF); + Ok(QueueConfig { + index, + size, + notify_off, + }) + } + + pub fn activate_queue( + &self, + index: u16, + size: u16, + desc_addr: u64, + avail_addr: u64, + used_addr: u64, + msix_vector: Option, + ) -> Result<()> { + self.select_queue(index); + self.common_cfg.write16(COMMON_QUEUE_SIZE, size); + self.common_cfg + .write16(COMMON_QUEUE_MSIX_VECTOR, msix_vector.unwrap_or(u16::MAX)); + self.write_u64_pair(COMMON_QUEUE_DESC_LO, COMMON_QUEUE_DESC_HI, desc_addr); + self.write_u64_pair(COMMON_QUEUE_AVAIL_LO, COMMON_QUEUE_AVAIL_HI, avail_addr); + self.write_u64_pair(COMMON_QUEUE_USED_LO, COMMON_QUEUE_USED_HI, used_addr); + self.common_cfg.write16(COMMON_QUEUE_ENABLE, 1); + + if self.common_cfg.read16(COMMON_QUEUE_ENABLE) != 1 { + return Err(DriverError::Initialization(format!( + "VirtIO queue {index} refused queue_enable" + ))); + } + + Ok(()) + } + + pub fn set_config_msix_vector(&self, vector: Option) { + self.common_cfg + .write16(COMMON_MSIX_CONFIG, vector.unwrap_or(u16::MAX)); + } + + pub fn notify_queue(&self, queue_index: u16, notify_off: u16) -> Result<()> { + let byte_offset = usize::from(notify_off) + .checked_mul(self.notify_off_multiplier as usize) + .ok_or_else(|| DriverError::Mmio("VirtIO notify offset overflow".into()))?; + let end = byte_offset + .checked_add(core::mem::size_of::()) + .ok_or_else(|| DriverError::Mmio("VirtIO notify MMIO overflow".into()))?; + if end > self.notify_cfg.size() { + return Err(DriverError::Mmio(format!( + "VirtIO queue notify outside notify_cfg window: end={end:#x} size={:#x}", + self.notify_cfg.size() + ))); + } + + self.notify_cfg.write16(byte_offset, queue_index); + Ok(()) + } + + fn fail(&mut self, reason: String) -> Result { + let status = self.device_status() | DEVICE_STATUS_FAILED; + self.write_device_status(status); + Err(DriverError::Initialization(reason)) + } + + fn read_device_features(&self) -> u64 { + self.common_cfg.write32(COMMON_DEVICE_FEATURE_SELECT, 0); + let low = self.common_cfg.read32(COMMON_DEVICE_FEATURE) as u64; + self.common_cfg.write32(COMMON_DEVICE_FEATURE_SELECT, 1); + let high = self.common_cfg.read32(COMMON_DEVICE_FEATURE) as u64; + low | (high << 32) + } + + fn write_driver_features(&self, features: u64) { + self.common_cfg.write32(COMMON_DRIVER_FEATURE_SELECT, 0); + self.common_cfg + .write32(COMMON_DRIVER_FEATURE, features as u32); + self.common_cfg.write32(COMMON_DRIVER_FEATURE_SELECT, 1); + self.common_cfg + .write32(COMMON_DRIVER_FEATURE, (features >> 32) as u32); + } + + fn write_device_status(&self, status: u8) { + self.common_cfg.write8(COMMON_DEVICE_STATUS, status); + } + + fn select_queue(&self, index: u16) { + self.common_cfg.write16(COMMON_QUEUE_SELECT, index); + } + + fn write_u64_pair(&self, lo_offset: usize, hi_offset: usize, value: u64) { + self.common_cfg.write32(lo_offset, value as u32); + self.common_cfg.write32(hi_offset, (value >> 32) as u32); + } +} + +fn read_pci_cap(pci: &mut PciDevice, offset: u8) -> Result { + Ok(VirtioPciCap { + cap_vndr: pci.read_config_byte(offset as u64).map_err(pci_error)?, + cap_next: pci.read_config_byte(offset as u64 + 1).map_err(pci_error)?, + cap_len: pci.read_config_byte(offset as u64 + 2).map_err(pci_error)?, + cfg_type: pci.read_config_byte(offset as u64 + 3).map_err(pci_error)?, + bar: pci.read_config_byte(offset as u64 + 4).map_err(pci_error)?, + id: pci.read_config_byte(offset as u64 + 5).map_err(pci_error)?, + padding: [ + pci.read_config_byte(offset as u64 + 6).map_err(pci_error)?, + pci.read_config_byte(offset as u64 + 7).map_err(pci_error)?, + ], + offset: pci + .read_config_dword(offset as u64 + 8) + .map_err(pci_error)?, + length: pci + .read_config_dword(offset as u64 + 12) + .map_err(pci_error)?, + }) +} + +fn read_notify_cap(pci: &mut PciDevice, offset: u8) -> Result { + Ok(VirtioPciNotifyCap { + cap: read_pci_cap(pci, offset)?, + notify_off_multiplier: pci + .read_config_dword(offset as u64 + 16) + .map_err(pci_error)?, + }) +} + +fn pci_error(error: redox_driver_sys::DriverError) -> DriverError { + DriverError::Pci(error.to_string()) +} + +fn map_cap_region( + info: &PciDeviceInfo, + cap: &VirtioPciCap, + name: &str, + required_size: usize, +) -> Result { + let bar = info + .find_memory_bar(cap.bar as usize) + .ok_or_else(|| DriverError::Pci(format!("VirtIO {name} BAR{} is missing", cap.bar)))?; + if cap.length == 0 { + return Err(DriverError::Pci(format!( + "VirtIO {name} capability has zero length" + ))); + } + if cap.length < required_size as u32 { + return Err(DriverError::Pci(format!( + "VirtIO {name} capability too small: {} < {} bytes", + cap.length, required_size + ))); + } + + let cap_end = u64::from(cap.offset) + .checked_add(u64::from(cap.length)) + .ok_or_else(|| DriverError::Pci(format!("VirtIO {name} capability range overflow")))?; + if cap_end > bar.size { + return Err(DriverError::Pci(format!( + "VirtIO {name} capability range [{:#x}, {:#x}) exceeds BAR{} size {:#x}", + cap.offset, cap_end, cap.bar, bar.size + ))); + } + + let phys = bar + .addr + .checked_add(cap.offset as u64) + .ok_or_else(|| DriverError::Pci(format!("VirtIO {name} MMIO address overflow")))?; + MmioRegion::map( + phys, + cap.length as usize, + CacheType::DeviceMemory, + MmioProt::READ_WRITE, + ) + .map_err(|e| DriverError::Mmio(format!("failed to map VirtIO {name}: {e}"))) +} diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/virtio/virtqueue.rs b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/virtqueue.rs new file mode 100644 index 0000000000..f28b50c7c4 --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/virtio/virtqueue.rs @@ -0,0 +1,259 @@ +use std::collections::BTreeMap; +use std::sync::atomic::{fence, Ordering}; +use std::thread; +use std::time::{Duration, Instant}; + +use redox_driver_sys::dma::DmaBuffer; + +use crate::driver::{DriverError, Result}; + +use super::transport::VirtioModernPciTransport; + +const VIRTQ_DESC_F_NEXT: u16 = 1; +const VIRTQ_DESC_F_WRITE: u16 = 2; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtqDesc { + addr: u64, + len: u32, + flags: u16, + next: u16, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtqUsedElem { + id: u32, + len: u32, +} + +pub struct Virtqueue { + index: u16, + size: u16, + notify_off: u16, + desc: DmaBuffer, + avail: DmaBuffer, + used: DmaBuffer, + free_list: Vec, + pending: BTreeMap>, + last_used_idx: u16, + failed: bool, +} + +impl Virtqueue { + pub fn new(index: u16, size: u16, notify_off: u16) -> Result { + let desc_bytes = usize::from(size) * core::mem::size_of::(); + let avail_bytes = 6 + usize::from(size) * 2; + let used_bytes = 6 + usize::from(size) * core::mem::size_of::(); + + let desc = DmaBuffer::allocate(desc_bytes, 16) + .map_err(|e| DriverError::Buffer(format!("VirtIO desc allocation failed: {e}")))?; + let avail = DmaBuffer::allocate(avail_bytes, 2) + .map_err(|e| DriverError::Buffer(format!("VirtIO avail allocation failed: {e}")))?; + let used = DmaBuffer::allocate(used_bytes, 4) + .map_err(|e| DriverError::Buffer(format!("VirtIO used allocation failed: {e}")))?; + + Ok(Self { + index, + size, + notify_off, + desc, + avail, + used, + free_list: (0..size).rev().collect(), + pending: BTreeMap::new(), + last_used_idx: 0, + failed: false, + }) + } + + pub fn index(&self) -> u16 { + self.index + } + + pub fn size(&self) -> u16 { + self.size + } + + pub fn notify_off(&self) -> u16 { + self.notify_off + } + + pub fn desc_addr(&self) -> u64 { + self.desc.physical_address() as u64 + } + + pub fn avail_addr(&self) -> u64 { + self.avail.physical_address() as u64 + } + + pub fn used_addr(&self) -> u64 { + self.used.physical_address() as u64 + } + + pub fn submit_request( + &mut self, + transport: &VirtioModernPciTransport, + request_addr: u64, + request_len: u32, + response_addr: u64, + response_len: u32, + timeout: Duration, + ) -> Result<()> { + if self.failed { + return Err(DriverError::Io(format!( + "VirtIO queue {} is failed and cannot accept more requests", + self.index + ))); + } + + let head = self.alloc_desc()?; + let tail = self.alloc_desc()?; + self.write_desc( + head, + VirtqDesc { + addr: request_addr, + len: request_len, + flags: VIRTQ_DESC_F_NEXT, + next: tail, + }, + ); + self.write_desc( + tail, + VirtqDesc { + addr: response_addr, + len: response_len, + flags: VIRTQ_DESC_F_WRITE, + next: 0, + }, + ); + + self.pending.insert(head, vec![head, tail]); + + // VirtIO spec 2.6.8.1: The driver MUST perform a memory barrier before + // updating the available index to ensure the descriptor table writes and + // the available ring entry are visible to the device. + fence(Ordering::SeqCst); + + self.push_avail(head); + if let Err(error) = transport.notify_queue(self.index, self.notify_off) { + self.reclaim_pending_chain(head); + return Err(error); + } + + if let Err(error) = self.wait_used(head, timeout) { + self.failed = true; + return Err(error); + } + + Ok(()) + } + + fn alloc_desc(&mut self) -> Result { + self.free_list.pop().ok_or_else(|| { + DriverError::Buffer(format!( + "VirtIO queue {} ran out of descriptors", + self.index + )) + }) + } + + fn push_avail(&mut self, head: u16) { + let avail_idx = self.read_avail_idx(); + let slot = usize::from(avail_idx % self.size); + self.write_avail_ring(slot, head); + fence(Ordering::Release); + self.write_avail_idx(avail_idx.wrapping_add(1)); + } + + fn wait_used(&mut self, expected_head: u16, timeout: Duration) -> Result<()> { + let deadline = Instant::now() + timeout; + loop { + let used_idx = self.read_used_idx(); + if used_idx != self.last_used_idx { + let slot = usize::from(self.last_used_idx % self.size); + let elem = self.read_used_elem(slot); + self.last_used_idx = self.last_used_idx.wrapping_add(1); + self.free_chain(elem.id as u16)?; + if elem.id as u16 != expected_head { + self.failed = true; + return Err(DriverError::Io(format!( + "VirtIO queue {} completed descriptor head {} while waiting for {}", + self.index, elem.id, expected_head + ))); + } + return Ok(()); + } + + if Instant::now() >= deadline { + return Err(DriverError::Io(format!( + "VirtIO queue {} timed out waiting for descriptor head {}", + self.index, expected_head + ))); + } + + thread::yield_now(); + thread::sleep(Duration::from_millis(1)); + } + } + + fn free_chain(&mut self, head: u16) -> Result<()> { + let chain = self.pending.remove(&head).ok_or_else(|| { + DriverError::Io(format!( + "VirtIO queue {} completed unknown descriptor head {}", + self.index, head + )) + })?; + for index in chain { + self.free_list.push(index); + } + Ok(()) + } + + fn reclaim_pending_chain(&mut self, head: u16) { + if let Some(chain) = self.pending.remove(&head) { + for index in chain { + self.free_list.push(index); + } + } + } + + fn write_desc(&mut self, index: u16, desc: VirtqDesc) { + let ptr = self.desc.as_mut_ptr() as *mut VirtqDesc; + // SAFETY: The DMA buffer is sized for `size` descriptors, and callers only pass indices < size. + unsafe { ptr.add(index as usize).write(desc) }; + } + + fn read_used_elem(&self, slot: usize) -> VirtqUsedElem { + let offset = 4 + slot * core::mem::size_of::(); + let ptr = self.used.as_ptr().wrapping_add(offset) as *const VirtqUsedElem; + // SAFETY: The used ring allocation covers all ring entries; read_unaligned handles packed access. + unsafe { ptr.read_unaligned() } + } + + fn read_avail_idx(&self) -> u16 { + let ptr = self.avail.as_ptr().wrapping_add(2) as *const u16; + // SAFETY: Offset 2 is the avail.idx field in the DMA allocation. + unsafe { ptr.read_unaligned() } + } + + fn write_avail_idx(&mut self, value: u16) { + let ptr = self.avail.as_mut_ptr().wrapping_add(2) as *mut u16; + // SAFETY: Offset 2 is the avail.idx field in the DMA allocation. + unsafe { ptr.write_unaligned(value) }; + } + + fn write_avail_ring(&mut self, slot: usize, value: u16) { + let offset = 4 + slot * core::mem::size_of::(); + let ptr = self.avail.as_mut_ptr().wrapping_add(offset) as *mut u16; + // SAFETY: The avail ring allocation is sized for `size` u16 entries. + unsafe { ptr.write_unaligned(value) }; + } + + fn read_used_idx(&self) -> u16 { + let ptr = self.used.as_ptr().wrapping_add(2) as *const u16; + // SAFETY: Offset 2 is the used.idx field in the DMA allocation. + unsafe { ptr.read_unaligned() } + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/main.rs b/local/recipes/gpu/redox-drm/source/src/main.rs index 2f5840eb98..a6ab74b831 100644 --- a/local/recipes/gpu/redox-drm/source/src/main.rs +++ b/local/recipes/gpu/redox-drm/source/src/main.rs @@ -91,7 +91,7 @@ fn run(daemon: daemon::Daemon) -> Result<()> { std::thread::sleep(std::time::Duration::from_millis(16)); }); - let drm_scheme = Arc::new(Mutex::new(DrmScheme::new(driver))); + let drm_scheme = Arc::new(Mutex::new(DrmScheme::new(driver, info))); let event_scheme = drm_scheme.clone(); std::thread::spawn(move || loop { diff --git a/local/recipes/gpu/redox-drm/source/src/scheme.rs b/local/recipes/gpu/redox-drm/source/src/scheme.rs index 2f21c266c8..9b9abd8226 100644 --- a/local/recipes/gpu/redox-drm/source/src/scheme.rs +++ b/local/recipes/gpu/redox-drm/source/src/scheme.rs @@ -3,12 +3,12 @@ use std::mem::size_of; use std::sync::Arc; use getrandom::getrandom; -use log::{debug, warn}; +use log::{debug, info, warn}; use redox_scheme::scheme::SchemeSync; use redox_scheme::{CallerCtx, OpenResult}; use syscall::data::Stat; use syscall::error::{Error, Result, EBADF, EBUSY, EINVAL, ENOENT, EOPNOTSUPP}; -use syscall::flag::{EventFlags, MapFlags, MunmapFlags, MODE_FILE}; +use syscall::flag::{EventFlags, MapFlags, MunmapFlags, MODE_CHR}; use syscall::schemev2::NewFdFlags; use crate::driver::{ @@ -17,6 +17,7 @@ use crate::driver::{ }; use crate::gem::GemHandle; use crate::kms::ModeInfo; +use redox_driver_sys::pci::PciDeviceInfo; #[derive(Clone, Debug)] struct FbInfo { @@ -53,8 +54,70 @@ const DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT: usize = DRM_IOCTL_BASE + 31; const DRM_IOCTL_REDOX_PRIVATE_CS_WAIT: usize = DRM_IOCTL_BASE + 32; const DRM_IOCTL_REDOX_AMD_SDMA_SUBMIT: usize = DRM_IOCTL_BASE + 0x40; const DRM_IOCTL_REDOX_AMD_SDMA_WAIT: usize = DRM_IOCTL_BASE + 0x41; +const DRM_IOCTL_GET_MAGIC: usize = DRM_IOCTL_BASE + 33; +const DRM_IOCTL_AUTH_MAGIC: usize = DRM_IOCTL_BASE + 34; +const DRM_IOCTL_SET_MASTER: usize = DRM_IOCTL_BASE + 35; +const DRM_IOCTL_DROP_MASTER: usize = DRM_IOCTL_BASE + 36; +const DRM_IOCTL_MODE_OBJ_GETPROPERTIES: usize = DRM_IOCTL_BASE + 0x50; +const DRM_IOCTL_MODE_GETPROPERTY: usize = DRM_IOCTL_BASE + 0x51; +const DRM_IOCTL_MODE_GETPROPBLOB: usize = DRM_IOCTL_BASE + 0x52; +const DRM_IOCTL_MODE_CREATE_LEASE: usize = DRM_IOCTL_BASE + 0x53; +const DRM_IOCTL_MODE_LIST_LESSEES: usize = DRM_IOCTL_BASE + 0x54; +const DRM_IOCTL_MODE_OBJ_SETPROPERTY: usize = DRM_IOCTL_BASE + 0x55; +const DRM_IOCTL_MODE_GETPLANERESOURCES: usize = DRM_IOCTL_BASE + 0x56; +const DRM_IOCTL_MODE_GETPLANE: usize = DRM_IOCTL_BASE + 0x57; +const DRM_IOCTL_MODE_SETPLANE: usize = DRM_IOCTL_BASE + 0x58; +const DRM_IOCTL_MODE_ADDFB2: usize = DRM_IOCTL_BASE + 0x59; +const DRM_IOCTL_GET_PCI_INFO: usize = DRM_IOCTL_BASE + 0x60; +const DRM_IOCTL_VIRTGPU_MAP: usize = 0x0041; +const DRM_IOCTL_VIRTGPU_EXECBUFFER: usize = 0x0042; +const DRM_IOCTL_VIRTGPU_GETPARAM: usize = 0x0043; +const DRM_IOCTL_VIRTGPU_RESOURCE_CREATE: usize = 0x0044; +const DRM_IOCTL_VIRTGPU_RESOURCE_INFO: usize = 0x0045; +const DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST: usize = 0x0046; +const DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST: usize = 0x0047; +const DRM_IOCTL_VIRTGPU_WAIT: usize = 0x0048; +const DRM_IOCTL_VIRTGPU_GET_CAPS: usize = 0x0049; +const DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB: usize = 0x004A; +const DRM_IOCTL_VIRTGPU_CONTEXT_INIT: usize = 0x004B; const MAX_SCHEME_GEM_BYTES: u64 = 256 * 1024 * 1024; +const VIRTGPU_PARAM_3D_FEATURES: u64 = 1; +const DRM_MODE_OBJECT_CRTC: u32 = 0xcccccccc; +const DRM_MODE_OBJECT_CONNECTOR: u32 = 0xc0c0c0c0; +const DRM_MODE_OBJECT_PLANE: u32 = 0xeeeeeeee; +const DRM_MODE_PROP_IMMUTABLE: u32 = 1 << 2; +const DRM_MODE_PROP_ENUM: u32 = 1 << 3; +const DRM_MODE_PROP_BLOB: u32 = 1 << 4; +const DRM_MODE_PROP_OBJECT: u32 = 1 << 2; +const DRM_MODE_PROP_RANGE: u32 = 1 << 1; +const DRM_MODE_PROP_ATOMIC: u32 = 1 << 31; +const DRM_PROP_NAME_LEN: usize = 32; +const DRM_FORMAT_XRGB8888: u32 = 0x34325258; +const DRM_FORMAT_ARGB8888: u32 = 0x34325241; +const DRM_PLANE_TYPE_PRIMARY: u64 = 1; +const PRIMARY_PLANE_ID: u32 = 3; + +/// Plane property IDs — KWin's DrmPlane requires all 10 basic properties. +const PLANE_PROP_TYPE: u32 = 10; +const PLANE_PROP_SRC_X: u32 = 11; +const PLANE_PROP_SRC_Y: u32 = 12; +const PLANE_PROP_SRC_W: u32 = 13; +const PLANE_PROP_SRC_H: u32 = 14; +const PLANE_PROP_CRTC_X: u32 = 15; +const PLANE_PROP_CRTC_Y: u32 = 16; +const PLANE_PROP_CRTC_W: u32 = 17; +const PLANE_PROP_CRTC_H: u32 = 18; +const PLANE_PROP_FB_ID: u32 = 19; + +// CRTC property IDs (20-29 range) +const CRTC_PROP_ACTIVE: u32 = 20; +const CRTC_PROP_MODE_ID: u32 = 21; + +// Connector property IDs (30-39 range) +const CONN_PROP_CRTC_ID: u32 = 30; + +const PLANE_PROPERTY_COUNT: usize = 10; // ---- Wire types for DRM ioctls ---- #[repr(C)] @@ -121,9 +184,9 @@ struct DrmCreateDumbWire { height: u32, bpp: u32, flags: u32, + handle: u32, pitch: u32, size: u64, - handle: u32, } #[repr(C)] @@ -161,6 +224,20 @@ struct DrmAddFbWire { fb_id: u32, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DrmAddFb2Wire { + fb_id: u32, + width: u32, + height: u32, + pixel_format: u32, + flags: u32, + handles: [u32; 4], + pitches: [u32; 4], + offsets: [u32; 4], + modifier: [u64; 4], +} + #[repr(C)] #[derive(Clone, Copy, Debug, Default)] struct DrmRmFbWire { @@ -180,10 +257,58 @@ struct DrmGetCrtcWire { #[repr(C)] #[derive(Clone, Copy, Debug, Default)] +struct DrmGetPlaneResourcesWire { + plane_id_ptr: u64, + count_planes: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DrmGetPlaneWire { + plane_id: u32, + crtc_id: u32, + fb_id: u32, + possible_crtcs: u32, + gamma_size: u32, + count_format_types: u32, + format_type_ptr: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DrmSetPlaneWire { + plane_id: u32, + crtc_id: u32, + fb_id: u32, + flags: u32, + crtc_x: i32, + crtc_y: i32, + crtc_w: u32, + crtc_h: u32, + src_x: u32, + src_y: u32, + src_h: u32, + src_w: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] struct DrmVersionWire { major: i32, minor: i32, patch: i32, + name: [u8; 64], +} + +impl Default for DrmVersionWire { + fn default() -> Self { + Self { + major: 0, + minor: 0, + patch: 0, + name: [0u8; 64], + } + } } #[repr(C)] @@ -193,6 +318,49 @@ struct DrmGetCapWire { value: u64, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DrmModeObjGetPropertiesWire { + props_ptr: u64, + prop_values_ptr: u64, + count_props: u32, + obj_id: u32, + obj_type: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct DrmModeGetPropertyWire { + values_ptr: u64, + enum_blob_ptr: u64, + prop_id: u32, + flags: u32, + name: [u8; DRM_PROP_NAME_LEN], + count_values: u32, + count_enum_blobs: u32, +} + +impl Default for DrmModeGetPropertyWire { + fn default() -> Self { + Self { + values_ptr: 0, + enum_blob_ptr: 0, + prop_id: 0, + flags: 0, + name: [0; DRM_PROP_NAME_LEN], + count_values: 0, + count_enum_blobs: 0, + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DrmModePropertyEnumWire { + value: u64, + name: [u8; DRM_PROP_NAME_LEN], +} + #[repr(C)] #[derive(Clone, Copy, Debug, Default)] struct DrmSetClientCapWire { @@ -222,6 +390,113 @@ struct DrmGemMmapWire { offset: u64, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuGetparamWire { + param: u64, + value: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuExecbufferWire { + flags: u32, + size: u32, + command: u64, + bo_handles: u64, + num_bo_handles: u32, + fence_fd: i32, + ring_idx: u32, + syncobj_stride: u32, + num_in_syncobjs: u32, + num_out_syncobjs: u32, + in_syncobjs: u64, + out_syncobjs: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuResourceCreateWire { + target: u32, + format: u32, + bind: u32, + width: u32, + height: u32, + depth: u32, + array_size: u32, + last_level: u32, + nr_samples: u32, + flags: u32, + bo_handle: u32, + res_handle: u32, + size: u32, + stride: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuResourceInfoWire { + bo_handle: u32, + res_handle: u32, + size: u32, + blob_mem: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct Virtgpu3dBox { + x: u32, + y: u32, + z: u32, + w: u32, + h: u32, + d: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuTransferToHostWire { + bo_handle: u32, + box_: Virtgpu3dBox, + level: u32, + offset: u32, + stride: u32, + layer_stride: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuWaitWire { + handle: u32, + flags: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuMapWire { + offset: u64, + handle: u32, + pad: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuContextInitWire { + num_params: u32, + pad: u32, + ctx_set_params: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct VirtgpuGetCapsWire { + cap_set_id: u32, + cap_set_ver: u32, + addr: u64, + size: u32, + pad: u32, +} + #[repr(C)] #[derive(Clone, Copy, Debug, Default)] struct DrmPrimeHandleToFdWire { @@ -300,6 +575,7 @@ struct Handle { pub struct DrmScheme { driver: Arc, + pci_info: PciDeviceInfo, next_id: usize, next_fb_id: u32, handles: BTreeMap, @@ -313,9 +589,10 @@ pub struct DrmScheme { } impl DrmScheme { - pub fn new(driver: Arc) -> Self { + pub fn new(driver: Arc, pci_info: PciDeviceInfo) -> Self { Self { driver, + pci_info, next_id: 0, next_fb_id: 1, handles: BTreeMap::new(), @@ -707,13 +984,20 @@ impl DrmScheme { .find(|c| c.id == connector_id) .ok_or_else(|| Error::new(ENOENT))?; + let connection_val = match connector.connection { + crate::kms::ConnectorStatus::Connected => 1, + crate::kms::ConnectorStatus::Disconnected => 2, + crate::kms::ConnectorStatus::Unknown => 0, + }; + info!( + "redox-drm: GETCONNECTOR id={} conn={} type={} modes={} encoder={}", + connector.id, connection_val, + connector_type_to_u32(connector.connector_type), + connector.modes.len(), connector.encoder_id + ); let header = DrmConnectorWire { connector_id: connector.id, - connection: match connector.connection { - crate::kms::ConnectorStatus::Connected => 1, - crate::kms::ConnectorStatus::Disconnected => 2, - crate::kms::ConnectorStatus::Unknown => 0, - }, + connection: connection_val, connector_type: connector_type_to_u32(connector.connector_type), mm_width: connector.mm_width, mm_height: connector.mm_height, @@ -730,9 +1014,310 @@ impl DrmScheme { Ok(out) } + fn encode_plane_resources(&self, requested_count: u32) -> Vec { + let mut out = bytes_of(&DrmGetPlaneResourcesWire { + plane_id_ptr: 0, + count_planes: 1, + }); + + if requested_count != 0 { + out.extend_from_slice(&PRIMARY_PLANE_ID.to_le_bytes()); + } + + out + } + + fn encode_plane(&self, req: &DrmGetPlaneWire) -> Result> { + const PRIMARY_PLANE_FORMATS: [u32; 2] = [DRM_FORMAT_XRGB8888, DRM_FORMAT_ARGB8888]; + + if req.plane_id != PRIMARY_PLANE_ID { + return Err(Error::new(ENOENT)); + } + + let (crtc_id, fb_id) = match self.active_crtc_fb.get(&1) { + Some(&active_fb) => (1, active_fb), + None => match self.active_crtc_fb.iter().next() { + Some((&active_crtc_id, &active_fb)) => (active_crtc_id, active_fb), + None => (0, 0), + }, + }; + + let mut out = bytes_of(&DrmGetPlaneWire { + plane_id: req.plane_id, + crtc_id, + fb_id, + possible_crtcs: 0x1, + gamma_size: 0, + count_format_types: PRIMARY_PLANE_FORMATS.len() as u32, + format_type_ptr: 0, + }); + + for format in PRIMARY_PLANE_FORMATS + .iter() + .take((req.count_format_types as usize).min(PRIMARY_PLANE_FORMATS.len())) + { + out.extend_from_slice(&format.to_le_bytes()); + } + + Ok(out) + } + + fn encode_object_properties(&self, req: &DrmModeObjGetPropertiesWire) -> Vec { + let (count_props, prop_ids, prop_values): (u32, &[u32], &[u64]) = + if req.obj_type == DRM_MODE_OBJECT_PLANE && req.obj_id == PRIMARY_PLANE_ID { + const IDS: [u32; PLANE_PROPERTY_COUNT] = [ + PLANE_PROP_TYPE, + PLANE_PROP_SRC_X, + PLANE_PROP_SRC_Y, + PLANE_PROP_SRC_W, + PLANE_PROP_SRC_H, + PLANE_PROP_CRTC_X, + PLANE_PROP_CRTC_Y, + PLANE_PROP_CRTC_W, + PLANE_PROP_CRTC_H, + PLANE_PROP_FB_ID, + ]; + const VALS: [u64; PLANE_PROPERTY_COUNT] = [ + DRM_PLANE_TYPE_PRIMARY, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, + ]; + (PLANE_PROPERTY_COUNT as u32, &IDS, &VALS) + } else if req.obj_type == DRM_MODE_OBJECT_CRTC && req.obj_id == 1 { + const IDS: [u32; 2] = [CRTC_PROP_ACTIVE, CRTC_PROP_MODE_ID]; + const VALS: [u64; 2] = [1, 0]; + (2, &IDS, &VALS) + } else if req.obj_type == DRM_MODE_OBJECT_CONNECTOR && req.obj_id == 1 { + const IDS: [u32; 1] = [CONN_PROP_CRTC_ID]; + const VALS: [u64; 1] = [1]; + (1, &IDS, &VALS) + } else { + (0, &[], &[]) + }; + + let mut out = bytes_of(&DrmModeObjGetPropertiesWire { + props_ptr: 0, + prop_values_ptr: 0, + count_props, + obj_id: req.obj_id, + obj_type: req.obj_type, + }); + + let copy_count = (req.count_props as usize).min(count_props as usize); + for prop_id in prop_ids.iter().take(copy_count) { + out.extend_from_slice(&prop_id.to_le_bytes()); + } + for prop_value in prop_values.iter().take(copy_count) { + out.extend_from_slice(&prop_value.to_le_bytes()); + } + + out + } + + fn encode_property(&self, prop_id: u32) -> Vec { + const PLANE_TYPE_ENUMS: [(u64, &[u8]); 3] = [ + (1, b"Primary"), + (2, b"Overlay"), + (3, b"Cursor"), + ]; + const RANGE_MIN: u64 = 0; + const RANGE_MAX: u64 = u64::MAX; + + let mut response = DrmModeGetPropertyWire { + prop_id, + ..DrmModeGetPropertyWire::default() + }; + let mut out; + + match prop_id { + PLANE_PROP_TYPE => { + response.flags = DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM; + response.count_enum_blobs = PLANE_TYPE_ENUMS.len() as u32; + copy_c_string(&mut response.name, b"type"); + + out = bytes_of(&response); + for (value, name) in PLANE_TYPE_ENUMS { + let mut enum_wire = DrmModePropertyEnumWire { + value, + ..DrmModePropertyEnumWire::default() + }; + copy_c_string(&mut enum_wire.name, name); + out.extend_from_slice(&bytes_of(&enum_wire)); + } + } + PLANE_PROP_SRC_X => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"SRC_X"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_SRC_Y => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"SRC_Y"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_SRC_W => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"SRC_W"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_SRC_H => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"SRC_H"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_CRTC_X => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"CRTC_X"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_CRTC_Y => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"CRTC_Y"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_CRTC_W => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"CRTC_W"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_CRTC_H => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"CRTC_H"); + + out = bytes_of(&response); + out.extend_from_slice(&RANGE_MIN.to_le_bytes()); + out.extend_from_slice(&RANGE_MAX.to_le_bytes()); + } + PLANE_PROP_FB_ID => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_OBJECT; + copy_c_string(&mut response.name, b"FB_ID"); + + out = bytes_of(&response); + } + CRTC_PROP_ACTIVE => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_RANGE; + response.count_values = 2; + copy_c_string(&mut response.name, b"ACTIVE"); + + out = bytes_of(&response); + out.extend_from_slice(&0u64.to_le_bytes()); + out.extend_from_slice(&1u64.to_le_bytes()); + } + CRTC_PROP_MODE_ID => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_BLOB; + copy_c_string(&mut response.name, b"MODE_ID"); + + out = bytes_of(&response); + } + CONN_PROP_CRTC_ID => { + response.flags = DRM_MODE_PROP_ATOMIC | DRM_MODE_PROP_OBJECT; + response.count_values = 0; + copy_c_string(&mut response.name, b"CRTC_ID"); + + out = bytes_of(&response); + } + _ => out = bytes_of(&response), + } + + out + } + + fn pixel_format_bpp(pixel_format: u32) -> Option { + match pixel_format { + DRM_FORMAT_XRGB8888 | DRM_FORMAT_ARGB8888 => Some(32), + _ => None, + } + } + // ---- ioctl dispatch ---- fn handle_ioctl(&mut self, id: usize, request: usize, payload: &[u8]) -> Result { + let request_name = match request { + DRM_IOCTL_MODE_GETRESOURCES => "GETRESOURCES", + DRM_IOCTL_MODE_GETCONNECTOR => "GETCONNECTOR", + DRM_IOCTL_MODE_GETMODES => "GETMODES", + DRM_IOCTL_MODE_SETCRTC => "SETCRTC", + DRM_IOCTL_MODE_GETCRTC => "GETCRTC", + DRM_IOCTL_MODE_GETENCODER => "GETENCODER", + DRM_IOCTL_MODE_PAGE_FLIP => "PAGE_FLIP", + DRM_IOCTL_MODE_CREATE_DUMB => "CREATE_DUMB", + DRM_IOCTL_MODE_MAP_DUMB => "MAP_DUMB", + DRM_IOCTL_MODE_DESTROY_DUMB => "DESTROY_DUMB", + DRM_IOCTL_MODE_ADDFB => "ADDFB", + DRM_IOCTL_MODE_RMFB => "RMFB", + DRM_IOCTL_GET_CAP => "GET_CAP", + DRM_IOCTL_SET_CLIENT_CAP => "SET_CLIENT_CAP", + DRM_IOCTL_VERSION => "VERSION", + DRM_IOCTL_GEM_CREATE => "GEM_CREATE", + DRM_IOCTL_GEM_CLOSE => "GEM_CLOSE", + DRM_IOCTL_GEM_MMAP => "GEM_MMAP", + DRM_IOCTL_PRIME_HANDLE_TO_FD => "PRIME_HANDLE_TO_FD", + DRM_IOCTL_PRIME_FD_TO_HANDLE => "PRIME_FD_TO_HANDLE", + DRM_IOCTL_GET_MAGIC => "GET_MAGIC", + DRM_IOCTL_AUTH_MAGIC => "AUTH_MAGIC", + DRM_IOCTL_SET_MASTER => "SET_MASTER", + DRM_IOCTL_DROP_MASTER => "DROP_MASTER", + DRM_IOCTL_MODE_OBJ_GETPROPERTIES => "OBJ_GETPROPERTIES", + DRM_IOCTL_MODE_GETPROPERTY => "GETPROPERTY", + DRM_IOCTL_MODE_GETPROPBLOB => "GETPROPBLOB", + DRM_IOCTL_MODE_CREATE_LEASE => "CREATE_LEASE", + DRM_IOCTL_MODE_LIST_LESSEES => "LIST_LESSEES", + DRM_IOCTL_MODE_OBJ_SETPROPERTY => "OBJ_SETPROPERTY", + DRM_IOCTL_MODE_GETPLANERESOURCES => "GETPLANERESOURCES", + DRM_IOCTL_MODE_GETPLANE => "GETPLANE", + DRM_IOCTL_MODE_SETPLANE => "SETPLANE", + DRM_IOCTL_MODE_ADDFB2 => "ADDFB2", + DRM_IOCTL_GET_PCI_INFO => "GET_PCI_INFO", + DRM_IOCTL_VIRTGPU_MAP => "VIRTGPU_MAP", + DRM_IOCTL_VIRTGPU_EXECBUFFER => "VIRTGPU_EXECBUFFER", + DRM_IOCTL_VIRTGPU_GETPARAM => "VIRTGPU_GETPARAM", + DRM_IOCTL_VIRTGPU_RESOURCE_CREATE => "VIRTGPU_RESOURCE_CREATE", + DRM_IOCTL_VIRTGPU_RESOURCE_INFO => "VIRTGPU_RESOURCE_INFO", + DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST => "VIRTGPU_TRANSFER_FROM_HOST", + DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST => "VIRTGPU_TRANSFER_TO_HOST", + DRM_IOCTL_VIRTGPU_WAIT => "VIRTGPU_WAIT", + DRM_IOCTL_VIRTGPU_GET_CAPS => "VIRTGPU_GET_CAPS", + DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB => "VIRTGPU_RESOURCE_CREATE_BLOB", + DRM_IOCTL_VIRTGPU_CONTEXT_INIT => "VIRTGPU_CONTEXT_INIT", + _ => "UNKNOWN", + }; + log::info!( + "redox-drm: ioctl handle {} request {} (0x{:04X}) payload {} bytes", + id, + request_name, + request, + payload.len() + ); let response = match request { DRM_IOCTL_MODE_GETRESOURCES => self.encode_resources(), @@ -859,14 +1444,21 @@ impl DrmScheme { DRM_IOCTL_MODE_CREATE_DUMB => { let mut req = decode_wire::(payload)?; + info!( + "redox-drm: CREATE_DUMB width={} height={} bpp={} flags={}", + req.width, req.height, req.bpp, req.flags + ); let pitch = (req.width.saturating_mul(req.bpp).saturating_add(7)) / 8; req.pitch = pitch; req.size = (pitch as u64).saturating_mul(req.height as u64); self.validate_gem_create_size(req.size, "CREATE_DUMB")?; req.handle = self .driver - .gem_create(req.size) - .map_err(driver_to_syscall)?; + .gem_create(req.size, req.width, req.height) + .map_err(|e| { + warn!("redox-drm: CREATE_DUMB gem_create({}) failed: {:?}", req.size, e); + driver_to_syscall(e) + })?; if let Some(handle) = self.handles.get_mut(&id) { handle.owned_gems.push(req.handle); } @@ -896,10 +1488,12 @@ impl DrmScheme { return Err(Error::new(EBUSY)); } } - req.offset = self - .driver - .gem_mmap(req.handle) - .map_err(driver_to_syscall)? as u64; + req.offset = u64::try_from( + self.driver + .gem_mmap(req.handle) + .map_err(driver_to_syscall)?, + ) + .map_err(|_| Error::new(EINVAL))?; if let Some(handle) = self.handles.get_mut(&id) { handle.mapped_gem = Some(req.handle); } @@ -987,6 +1581,21 @@ impl DrmScheme { bytes_of(&resp) } + DRM_IOCTL_MODE_GETPLANERESOURCES => { + let req = decode_wire::(payload)?; + self.encode_plane_resources(req.count_planes) + } + + DRM_IOCTL_MODE_GETPLANE => { + let req = decode_wire::(payload)?; + self.encode_plane(&req)? + } + + DRM_IOCTL_MODE_SETPLANE => { + let _req = decode_wire::(payload)?; + Vec::new() + } + DRM_IOCTL_MODE_ADDFB => { let req = decode_wire::(payload)?; if req.handle == 0 { @@ -1065,6 +1674,94 @@ impl DrmScheme { bytes_of(&resp) } + DRM_IOCTL_MODE_ADDFB2 => { + let mut req = decode_wire::(payload)?; + let handle = req.handles[0]; + if handle == 0 { + return Err(Error::new(EINVAL)); + } + if req.width == 0 || req.height == 0 { + warn!( + "redox-drm: ADDFB2 zero dimension width={} height={} format=0x{:08x}", + req.width, req.height, req.pixel_format + ); + return Err(Error::new(EINVAL)); + } + let bpp = match Self::pixel_format_bpp(req.pixel_format) { + Some(bits) => bits, + None => { + warn!( + "redox-drm: ADDFB2 unsupported pixel format 0x{:08x}", + req.pixel_format + ); + return Err(Error::new(EINVAL)); + } + }; + let min_stride = (req.width.saturating_mul(bpp).saturating_add(7)) / 8; + let pitch = if req.pitches[0] != 0 { + req.pitches[0] + } else { + min_stride + }; + if pitch == 0 || pitch < min_stride { + warn!( + "redox-drm: ADDFB2 pitch {} below minimum stride {} ({}x{})", + pitch, min_stride, req.width, bpp + ); + return Err(Error::new(EINVAL)); + } + let required_size = match (pitch as u64) + .checked_mul(req.height as u64) + .and_then(|size| size.checked_add(req.offsets[0] as u64)) + { + Some(size) => size, + None => { + warn!( + "redox-drm: ADDFB2 size overflows pitch={} height={} offset={}", + pitch, req.height, req.offsets[0] + ); + return Err(Error::new(EINVAL)); + } + }; + let owned = self + .handles + .get(&id) + .map(|h| Self::handle_has_gem_ref(h, handle)) + .unwrap_or(false); + if !owned { + warn!("redox-drm: ADDFB2 handle {} not owned by this fd", handle); + return Err(Error::new(EBADF)); + } + let actual_size = self.driver.gem_size(handle).map_err(|e| { + warn!("redox-drm: ADDFB2 handle {} not found: {}", handle, e); + Error::new(ENOENT) + })?; + if required_size > actual_size { + warn!( + "redox-drm: ADDFB2 requires {} bytes but GEM {} is {} bytes", + required_size, handle, actual_size + ); + return Err(Error::new(EINVAL)); + } + let fb_id = self.next_fb_id; + self.next_fb_id = self.next_fb_id.saturating_add(1); + self.fb_registry.insert( + fb_id, + FbInfo { + gem_handle: handle, + width: req.width, + height: req.height, + pitch, + bpp, + }, + ); + if let Some(handle_state) = self.handles.get_mut(&id) { + handle_state.owned_fbs.push(fb_id); + } + req.fb_id = fb_id; + bytes_of(&req) + } + DRM_IOCTL_MODE_RMFB => { let req = decode_wire::(payload)?; let owned = self @@ -1095,9 +1792,39 @@ impl DrmScheme { DRM_IOCTL_GET_CAP => { let mut req = decode_wire::(payload)?; + const DRM_CAP_DUMB_BUFFER: u64 = 0x1; + const DRM_CAP_VBLANK_HIGH_CRTC: u64 = 0x2; + const DRM_CAP_DUMB_PREFERRED_DEPTH: u64 = 0x3; + const DRM_CAP_DUMB_PREFER_SHADOW: u64 = 0x4; + const DRM_CAP_PRIME: u64 = 0x5; + const DRM_CAP_TIMESTAMP_MONOTONIC: u64 = 0x6; + const DRM_CAP_ASYNC_PAGE_FLIP: u64 = 0x7; + const DRM_CAP_CURSOR_WIDTH: u64 = 0x8; + const DRM_CAP_CURSOR_HEIGHT: u64 = 0x9; + const DRM_CAP_ADDFB2_MODIFIERS: u64 = 0x10; + const DRM_CAP_PAGE_FLIP_TARGET: u64 = 0x11; + const DRM_CAP_CRTC_IN_VBLANK_EVENT: u64 = 0x12; + const DRM_CAP_SYNCOBJ: u64 = 0x13; + const DRM_CAP_SYNCOBJ_TIMELINE: u64 = 0x14; + const DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP: u64 = 0x15; + const DRM_PRIME_CAP_IMPORT: u64 = 0x1; + const DRM_PRIME_CAP_EXPORT: u64 = 0x2; req.value = match req.capability { - 0 => 1, - 1 => 1, + DRM_CAP_DUMB_BUFFER => 1, + DRM_CAP_VBLANK_HIGH_CRTC => 1, + DRM_CAP_DUMB_PREFERRED_DEPTH => 32, + DRM_CAP_DUMB_PREFER_SHADOW => 1, + DRM_CAP_PRIME => DRM_PRIME_CAP_IMPORT | DRM_PRIME_CAP_EXPORT, + DRM_CAP_TIMESTAMP_MONOTONIC => 1, + DRM_CAP_ASYNC_PAGE_FLIP => 1, + DRM_CAP_CURSOR_WIDTH => 64, + DRM_CAP_CURSOR_HEIGHT => 64, + DRM_CAP_ADDFB2_MODIFIERS => 1, + DRM_CAP_PAGE_FLIP_TARGET => 0, + DRM_CAP_CRTC_IN_VBLANK_EVENT => 1, + DRM_CAP_SYNCOBJ => 0, + DRM_CAP_SYNCOBJ_TIMELINE => 0, + DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP => 0, _ => 0, }; bytes_of(&req) @@ -1106,12 +1833,16 @@ impl DrmScheme { DRM_IOCTL_SET_CLIENT_CAP => Vec::new(), DRM_IOCTL_VERSION => { - let resp = DrmVersionWire { + let driver_name = self.driver.driver_name(); + let mut name_bytes = [0u8; 64]; + let copy_len = driver_name.len().min(63); + name_bytes[..copy_len].copy_from_slice(&driver_name.as_bytes()[..copy_len]); + bytes_of(&DrmVersionWire { major: 1, minor: 0, patch: 0, - }; - bytes_of(&resp) + name: name_bytes, + }) } DRM_IOCTL_GEM_CREATE => { @@ -1119,7 +1850,7 @@ impl DrmScheme { self.validate_gem_create_size(req.size, "GEM_CREATE")?; req.handle = self .driver - .gem_create(req.size) + .gem_create(req.size, 0, 0) .map_err(driver_to_syscall)?; if let Some(handle) = self.handles.get_mut(&id) { handle.owned_gems.push(req.handle); @@ -1197,16 +1928,292 @@ impl DrmScheme { return Err(Error::new(EBUSY)); } } - req.offset = self - .driver - .gem_mmap(req.handle) - .map_err(driver_to_syscall)? as u64; + req.offset = u64::try_from( + self.driver + .gem_mmap(req.handle) + .map_err(driver_to_syscall)?, + ) + .map_err(|_| Error::new(EINVAL))?; if let Some(handle) = self.handles.get_mut(&id) { handle.mapped_gem = Some(req.handle); } bytes_of(&req) } + DRM_IOCTL_VIRTGPU_GETPARAM => { + let mut req = decode_wire::(payload)?; + req.value = match req.param { + VIRTGPU_PARAM_3D_FEATURES if self.driver.has_virgl_3d() => 1, + _ => 0, + }; + bytes_of(&req) + } + + DRM_IOCTL_VIRTGPU_GET_CAPS => { + let req = decode_wire::(payload)?; + let capset_info = self + .driver + .virgl_get_capset_info(0) + .map_err(driver_to_syscall)?; + let capset_id = if req.cap_set_id != 0 { + req.cap_set_id + } else { + capset_info.capset_id + }; + let capset_version = if req.cap_set_ver != 0 { + req.cap_set_ver.min(capset_info.capset_max_version) + } else { + capset_info.capset_max_version + }; + let capset = self + .driver + .virgl_get_capset(capset_id, capset_version) + .map_err(driver_to_syscall)?; + let capset_limit = + usize::try_from(capset_info.capset_max_size).map_err(|_| Error::new(EINVAL))?; + let requested_len = if req.size != 0 { + usize::try_from(req.size).map_err(|_| Error::new(EINVAL))? + } else { + capset_limit + }; + capset + .data + .into_iter() + .take(requested_len.min(capset_limit)) + .collect() + } + + DRM_IOCTL_VIRTGPU_CONTEXT_INIT => { + let _req = decode_wire::(payload)?; + self.driver + .virgl_ctx_create(1, "virgl", 1) + .map_err(driver_to_syscall)?; + Vec::new() + } + + DRM_IOCTL_VIRTGPU_RESOURCE_CREATE => { + let mut req = decode_wire::(payload)?; + let allocation_size = if req.size != 0 { + u64::from(req.size) + } else if req.stride != 0 { + u64::from(req.stride) + .checked_mul(u64::from(req.height.max(1))) + .and_then(|size| size.checked_mul(u64::from(req.depth.max(1)))) + .and_then(|size| size.checked_mul(u64::from(req.array_size.max(1)))) + .ok_or_else(|| { + warn!( + "redox-drm: VIRTGPU_RESOURCE_CREATE rejected — computed backing size overflow" + ); + Error::new(EINVAL) + })? + } else { + warn!( + "redox-drm: VIRTGPU_RESOURCE_CREATE rejected — size and stride are both zero" + ); + return Err(Error::new(EINVAL)); + }; + self.validate_gem_create_size(allocation_size, "VIRTGPU_RESOURCE_CREATE")?; + req.bo_handle = self + .driver + .gem_create(allocation_size, req.width, req.height) + .map_err(driver_to_syscall)?; + if let Some(handle) = self.handles.get_mut(&id) { + handle.owned_gems.push(req.bo_handle); + } + if let Err(error) = self.driver.virgl_resource_create_3d( + req.bo_handle, + req.target, + req.format, + req.bind, + req.width, + req.height, + req.depth, + req.array_size, + req.last_level, + req.nr_samples, + req.flags, + 1, + ) { + let _ = self.driver.gem_close(req.bo_handle); + if let Some(handle) = self.handles.get_mut(&id) { + handle.owned_gems.retain(|&gem| gem != req.bo_handle); + } + return Err(driver_to_syscall(error)); + } + req.res_handle = req.bo_handle; + bytes_of(&req) + } + + DRM_IOCTL_VIRTGPU_RESOURCE_INFO => { + let mut req = decode_wire::(payload)?; + let owned = self + .handles + .get(&id) + .map(|h| Self::handle_has_gem_ref(h, req.bo_handle)) + .unwrap_or(false); + if !owned { + warn!( + "redox-drm: VIRTGPU_RESOURCE_INFO handle {} not owned by this fd", + req.bo_handle + ); + return Err(Error::new(EBADF)); + } + let size = self + .driver + .gem_size(req.bo_handle) + .map_err(driver_to_syscall)?; + req.res_handle = req.bo_handle; + req.size = u32::try_from(size).map_err(|_| Error::new(EINVAL))?; + req.blob_mem = 0; + bytes_of(&req) + } + + DRM_IOCTL_VIRTGPU_TRANSFER_TO_HOST => { + let req = decode_wire::(payload)?; + let owned = self + .handles + .get(&id) + .map(|h| Self::handle_has_gem_ref(h, req.bo_handle)) + .unwrap_or(false); + if !owned { + warn!( + "redox-drm: VIRTGPU_TRANSFER_TO_HOST handle {} not owned by this fd", + req.bo_handle + ); + return Err(Error::new(EBADF)); + } + self.driver + .virgl_transfer_to_host_3d( + 1, + req.bo_handle, + req.box_.x, + req.box_.y, + req.box_.z, + req.box_.w, + req.box_.h, + req.box_.d, + u64::from(req.offset), + req.level, + req.stride, + req.layer_stride, + ) + .map_err(driver_to_syscall)?; + Vec::new() + } + + DRM_IOCTL_VIRTGPU_TRANSFER_FROM_HOST => { + let req = decode_wire::(payload)?; + let owned = self + .handles + .get(&id) + .map(|h| Self::handle_has_gem_ref(h, req.bo_handle)) + .unwrap_or(false); + if !owned { + warn!( + "redox-drm: VIRTGPU_TRANSFER_FROM_HOST handle {} not owned by this fd", + req.bo_handle + ); + return Err(Error::new(EBADF)); + } + self.driver + .virgl_transfer_from_host_3d( + 1, + req.bo_handle, + req.box_.x, + req.box_.y, + req.box_.z, + req.box_.w, + req.box_.h, + req.box_.d, + u64::from(req.offset), + req.level, + req.stride, + req.layer_stride, + ) + .map_err(driver_to_syscall)?; + Vec::new() + } + + DRM_IOCTL_VIRTGPU_EXECBUFFER => { + let req = decode_wire::(payload)?; + let inline = payload + .get(size_of::()..) + .unwrap_or(&[]); + let command_len = usize::try_from(req.size).map_err(|_| Error::new(EINVAL))?; + if inline.len() < command_len { + warn!( + "redox-drm: VIRTGPU_EXECBUFFER rejected — inline command payload {} shorter than declared size {}", + inline.len(), command_len + ); + return Err(Error::new(EINVAL)); + } + self.driver + .virgl_submit_3d(1, &inline[..command_len]) + .map_err(driver_to_syscall)?; + Vec::new() + } + + DRM_IOCTL_VIRTGPU_WAIT => { + let req = decode_wire::(payload)?; + if req.handle != 0 { + let owned = self + .handles + .get(&id) + .map(|h| Self::handle_has_gem_ref(h, req.handle)) + .unwrap_or(false); + if !owned { + warn!( + "redox-drm: VIRTGPU_WAIT handle {} not owned by this fd", + req.handle + ); + return Err(Error::new(EBADF)); + } + } + Vec::new() + } + + DRM_IOCTL_VIRTGPU_MAP => { + let mut req = decode_wire::(payload)?; + let owned = self + .handles + .get(&id) + .map(|h| Self::handle_has_gem_ref(h, req.handle)) + .unwrap_or(false); + if !owned { + warn!( + "redox-drm: VIRTGPU_MAP handle {} not owned by this fd", + req.handle + ); + return Err(Error::new(EBADF)); + } + if let Some(handle) = self.handles.get(&id) { + if handle.mapped_gem_refs != 0 && handle.mapped_gem != Some(req.handle) { + warn!( + "redox-drm: VIRTGPU_MAP handle {} rejected — another GEM is still mapped", + req.handle + ); + return Err(Error::new(EBUSY)); + } + } + req.offset = u64::try_from( + self.driver + .gem_mmap(req.handle) + .map_err(driver_to_syscall)?, + ) + .map_err(|_| Error::new(EINVAL))?; + if let Some(handle) = self.handles.get_mut(&id) { + handle.mapped_gem = Some(req.handle); + } + bytes_of(&req) + } + + DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB => { + warn!( + "redox-drm: VIRTGPU_RESOURCE_CREATE_BLOB rejected — virgl blob resources are not implemented" + ); + return Err(Error::new(EOPNOTSUPP)); + } + DRM_IOCTL_REDOX_AMD_SDMA_SUBMIT => { let mut req = decode_wire::(payload)?; if req.flags != 0 { @@ -1285,7 +2292,7 @@ impl DrmScheme { self.prime_exports.insert(token, req.handle); let resp = DrmPrimeHandleToFdResponseWire { - fd: token as i32, + fd: i32::try_from(token).map_err(|_| Error::new(EINVAL))?, _pad: 0, }; bytes_of(&resp) @@ -1360,6 +2367,82 @@ impl DrmScheme { bytes_of(&resp) } + DRM_IOCTL_GET_MAGIC => { + let magic: u32 = 1; + bytes_of(&magic) + } + + DRM_IOCTL_AUTH_MAGIC => { + if payload.is_empty() { + vec![0; size_of::()] + } else { + payload.to_vec() + } + } + + DRM_IOCTL_SET_MASTER | DRM_IOCTL_DROP_MASTER => { + vec![] + } + + DRM_IOCTL_MODE_OBJ_GETPROPERTIES => { + let req = decode_wire::(payload)?; + self.encode_object_properties(&req) + } + + DRM_IOCTL_MODE_GETPROPERTY => { + let req = decode_wire::(payload)?; + self.encode_property(req.prop_id) + } + + DRM_IOCTL_MODE_GETPROPBLOB => { + let mut resp = zeroed_response(payload.len()); + copy_bytes(&mut resp, payload, 0, 4); + copy_bytes(&mut resp, payload, 8, 8); + write_u32(&mut resp, 4, 0); + resp + } + + DRM_IOCTL_MODE_CREATE_LEASE => { + let mut resp = zeroed_response(payload.len()); + copy_bytes(&mut resp, payload, 0, 16); + write_u32(&mut resp, 16, 0); + write_u32(&mut resp, 20, 0); + resp + } + + DRM_IOCTL_MODE_LIST_LESSEES => { + let mut resp = zeroed_response(payload.len()); + copy_bytes(&mut resp, payload, 8, 8); + write_u32(&mut resp, 0, 0); + resp + } + + DRM_IOCTL_MODE_OBJ_SETPROPERTY => { + vec![] + } + + 0 => { + vec![] + } + + DRM_IOCTL_GET_PCI_INFO => { + let info = &self.pci_info; + let loc = &info.location; + let mut resp = Vec::with_capacity(24); + resp.extend_from_slice(&info.vendor_id.to_le_bytes()); + resp.extend_from_slice(&info.device_id.to_le_bytes()); + resp.extend_from_slice(&info.subsystem_vendor_id.to_le_bytes()); + resp.extend_from_slice(&info.subsystem_device_id.to_le_bytes()); + resp.push(0); + resp.extend_from_slice(&[0u8; 3]); + resp.extend_from_slice(&loc.segment.to_le_bytes()); + resp.push(loc.bus); + resp.push(loc.device); + resp.push(loc.function); + resp.extend_from_slice(&[0u8; 3]); + resp + } + _ => { warn!("redox-drm: unsupported ioctl {:#x}", request); return Err(Error::new(EOPNOTSUPP)); @@ -1374,6 +2457,7 @@ impl DrmScheme { let handle = self.handles.get_mut(&id).ok_or_else(|| Error::new(EBADF))?; let len = response.len(); + log::info!("redox-drm: ioctl handle {} response {} bytes", id, len); handle.response = response; Ok(len) } @@ -1429,6 +2513,11 @@ impl SchemeSync for DrmScheme { } let id = self.allocate_handle(node); + log::info!( + "redox-drm: open path={} -> handle {}", + path.trim_matches('/'), + id + ); Ok(OpenResult::ThisScheme { number: id, flags: NewFdFlags::empty(), @@ -1450,6 +2539,11 @@ impl SchemeSync for DrmScheme { if !handle.response.is_empty() { let len = handle.response.len().min(buf.len()); buf[..len].copy_from_slice(&handle.response[..len]); + if len >= handle.response.len() { + handle.response.clear(); + } else { + handle.response.drain(..len); + } return Ok(len); } @@ -1478,8 +2572,8 @@ impl SchemeSync for DrmScheme { } }; let request = usize::from_le_bytes(*request_bytes); - let written = self.handle_ioctl(id, request, payload)?; - Ok(written) + let _ = self.handle_ioctl(id, request, payload)?; + Ok(buf.len()) } fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { @@ -1498,7 +2592,7 @@ impl SchemeSync for DrmScheme { fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { let handle = self.handles.get(&id).ok_or_else(|| Error::new(EBADF))?; - stat.st_mode = MODE_FILE | 0o666; + stat.st_mode = MODE_CHR | 0o666; stat.st_size = if matches!(handle.node, NodeKind::Root) { 0 } else if !handle.response.is_empty() { @@ -1699,6 +2793,31 @@ fn bytes_of(value: &T) -> Vec { unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec() } +fn copy_c_string(dst: &mut [u8], src: &[u8]) { + let copy_len = src.len().min(dst.len().saturating_sub(1)); + if copy_len != 0 { + dst[..copy_len].copy_from_slice(&src[..copy_len]); + } +} + +fn zeroed_response(len: usize) -> Vec { + vec![0; len] +} + +fn copy_bytes(dst: &mut [u8], src: &[u8], offset: usize, len: usize) { + let end = offset.saturating_add(len); + if let (Some(dst_slice), Some(src_slice)) = (dst.get_mut(offset..end), src.get(offset..end)) { + dst_slice.copy_from_slice(src_slice); + } +} + +fn write_u32(buf: &mut [u8], offset: usize, value: u32) { + let end = offset.saturating_add(size_of::()); + if let Some(bytes) = buf.get_mut(offset..end) { + bytes.copy_from_slice(&value.to_le_bytes()); + } +} + fn read_u32(buf: &[u8], offset: usize) -> Result { let end = offset.saturating_add(size_of::()); let bytes = buf.get(offset..end).ok_or_else(|| Error::new(EINVAL))?; @@ -1733,12 +2852,59 @@ mod tests { use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; - use redox_scheme::SchemeBlockMut; + use redox_driver_sys::pci::PciLocation; use super::*; use crate::driver::{DriverError, DriverEvent, GpuDriver}; use crate::kms::{ConnectorInfo, ModeInfo}; + trait SchemeTestExt { + fn test_open( + &mut self, + path: &str, + flags: usize, + fcntl_flags: u32, + uid: u32, + ) -> Result>; + fn test_read(&mut self, id: usize, buf: &mut [u8]) -> Result>; + fn test_write(&mut self, id: usize, buf: &[u8]) -> Result>; + fn test_fsync(&mut self, id: usize) -> Result<()>; + fn test_fevent(&mut self, id: usize, flags: EventFlags) -> Result>; + } + + impl SchemeTestExt for DrmScheme { + fn test_open( + &mut self, + path: &str, + flags: usize, + fcntl_flags: u32, + _uid: u32, + ) -> Result> { + let result = SchemeSync::openat(self, 0, path, flags, fcntl_flags, &test_ctx())?; + match result { + OpenResult::ThisScheme { number, .. } => Ok(Some(number)), + OpenResult::OtherScheme { .. } | OpenResult::OtherSchemeMultiple { .. } => Ok(None), + OpenResult::WouldBlock => Ok(None), + } + } + + fn test_read(&mut self, id: usize, buf: &mut [u8]) -> Result> { + SchemeSync::read(self, id, buf, 0, 0, &test_ctx()).map(Some) + } + + fn test_write(&mut self, id: usize, buf: &[u8]) -> Result> { + SchemeSync::write(self, id, buf, 0, 0, &test_ctx()).map(Some) + } + + fn test_fsync(&mut self, id: usize) -> Result<()> { + SchemeSync::fsync(self, id, &test_ctx()) + } + + fn test_fevent(&mut self, id: usize, flags: EventFlags) -> Result> { + SchemeSync::fevent(self, id, flags, &test_ctx()).map(Some) + } + } + #[derive(Default)] struct FakeDriverState { next_handle: GemHandle, @@ -1811,7 +2977,7 @@ mod tests { Ok(0) } - fn gem_create(&self, size: u64) -> crate::driver::Result { + fn gem_create(&self, size: u64, _width: u32, _height: u32) -> crate::driver::Result { let mut state = self.state.lock().unwrap(); let handle = state.next_handle; state.next_handle = state.next_handle.saturating_add(1); @@ -1875,12 +3041,42 @@ mod tests { } fn open_card(scheme: &mut DrmScheme) -> usize { - scheme.open("card0", 0, 0, 0).unwrap().unwrap() + scheme.test_open("card0", 0, 0, 0).unwrap().unwrap() + } + + fn test_ctx() -> CallerCtx { + unsafe { core::mem::zeroed() } + } + + fn test_scheme(driver: Arc) -> DrmScheme { + DrmScheme::new( + driver, + PciDeviceInfo { + location: PciLocation { + segment: 0, + bus: 0, + device: 0, + function: 0, + }, + vendor_id: 0x1AF4, + device_id: 0x1050, + subsystem_vendor_id: 0, + subsystem_device_id: 0, + revision: 0, + class_code: 0x03, + subclass: 0, + prog_if: 0, + header_type: 0, + irq: None, + bars: Vec::new(), + capabilities: Vec::new(), + }, + ) } fn open_connector(scheme: &mut DrmScheme, connector_id: u32) -> usize { scheme - .open(&format!("card0Connector/{connector_id}"), 0, 0, 0) + .test_open(&format!("card0Connector/{connector_id}"), 0, 0, 0) .unwrap() .unwrap() } @@ -1893,20 +3089,34 @@ mod tests { ) -> Result { let mut buf = request.to_le_bytes().to_vec(); buf.extend_from_slice(&bytes_of(payload)); - scheme.write(id, &buf).map(|written| written.unwrap_or(0)) + scheme + .test_write(id, &buf) + .map(|written| written.unwrap_or(0)) } fn read_response(scheme: &mut DrmScheme, id: usize) -> T { let mut buf = vec![0; size_of::()]; - let len = scheme.read(id, &mut buf).unwrap().unwrap(); + let len = scheme.test_read(id, &mut buf).unwrap().unwrap(); assert_eq!(len, size_of::()); decode_wire::(&buf).unwrap() } + fn read_response_bytes(scheme: &mut DrmScheme, id: usize) -> Vec { + let expected_len = scheme + .handles + .get(&id) + .map(|handle| handle.response.len()) + .unwrap_or(0); + let mut buf = vec![0; expected_len]; + let len = scheme.test_read(id, &mut buf).unwrap().unwrap(); + assert_eq!(len, expected_len); + buf + } + #[test] fn private_cs_submit_rejects_imported_dma_buf_handles() { let driver = Arc::new(FakeDriver::new(true)); - let mut scheme = DrmScheme::new(driver.clone()); + let mut scheme = test_scheme(driver.clone()); let exporter = open_card(&mut scheme); let importer = open_card(&mut scheme); @@ -1953,7 +3163,7 @@ mod tests { #[test] fn prime_handle_to_fd_returns_distinct_nonzero_tokens() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); for _ in 0..2 { @@ -1988,7 +3198,7 @@ mod tests { #[test] fn private_cs_wait_is_explicitly_unsupported_without_backend_support() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let wait = RedoxPrivateCsWait { seqno: 1, @@ -2003,10 +3213,10 @@ mod tests { #[test] fn fsync_is_not_a_fake_render_sync_success() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); - let err = scheme.fsync(card).unwrap_err(); + let err = scheme.test_fsync(card).unwrap_err(); assert_eq!(err.errno, EOPNOTSUPP); } @@ -2014,7 +3224,7 @@ mod tests { #[test] fn private_cs_submit_still_reaches_backend_for_local_gems() { let driver = Arc::new(FakeDriver::new(true)); - let mut scheme = DrmScheme::new(driver.clone()); + let mut scheme = test_scheme(driver.clone()); let card = open_card(&mut scheme); for _ in 0..2 { @@ -2054,7 +3264,7 @@ mod tests { #[test] fn private_cs_submit_rejects_out_of_bounds_ranges() { let driver = Arc::new(FakeDriver::new(true)); - let mut scheme = DrmScheme::new(driver.clone()); + let mut scheme = test_scheme(driver.clone()); let card = open_card(&mut scheme); for _ in 0..2 { @@ -2089,7 +3299,7 @@ mod tests { #[test] fn vblank_driver_event_retires_pending_page_flip() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); scheme.fb_registry.insert( 7, @@ -2114,7 +3324,7 @@ mod tests { #[test] fn non_vblank_driver_event_does_not_retire_pending_page_flip() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); scheme.fb_registry.insert( @@ -2134,53 +3344,57 @@ mod tests { assert_eq!(scheme.pending_flip_fb.get(&1), Some(&(2, 9))); assert!(scheme.fb_registry.contains_key(&9)); assert_eq!( - scheme.fevent(card, EventFlags::EVENT_READ).unwrap(), + scheme.test_fevent(card, EventFlags::EVENT_READ).unwrap(), Some(EventFlags::EVENT_READ) ); } #[test] fn hotplug_event_is_readable_from_card_handle() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); scheme.handle_driver_event(DriverEvent::Hotplug { connector_id: 7 }); assert_eq!( - scheme.fevent(card, EventFlags::EVENT_READ).unwrap(), + scheme.test_fevent(card, EventFlags::EVENT_READ).unwrap(), Some(EventFlags::EVENT_READ) ); let mut buf = [0u8; 32]; - let len = scheme.read(card, &mut buf).unwrap().unwrap(); + let len = scheme.test_read(card, &mut buf).unwrap().unwrap(); assert_eq!(&buf[..len], b"hotplug:7\n"); assert_eq!( - scheme.fevent(card, EventFlags::EVENT_READ).unwrap(), + scheme.test_fevent(card, EventFlags::EVENT_READ).unwrap(), Some(EventFlags::empty()) ); } #[test] fn hotplug_event_targets_matching_connector_handle_only() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let connector_a = open_connector(&mut scheme, 1); let connector_b = open_connector(&mut scheme, 2); scheme.handle_driver_event(DriverEvent::Hotplug { connector_id: 2 }); assert_eq!( - scheme.fevent(connector_a, EventFlags::EVENT_READ).unwrap(), + scheme + .test_fevent(connector_a, EventFlags::EVENT_READ) + .unwrap(), Some(EventFlags::empty()) ); assert_eq!( - scheme.fevent(connector_b, EventFlags::EVENT_READ).unwrap(), + scheme + .test_fevent(connector_b, EventFlags::EVENT_READ) + .unwrap(), Some(EventFlags::EVENT_READ) ); } #[test] fn vblank_event_is_readable_from_card_handle() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); scheme.handle_driver_event(DriverEvent::Vblank { @@ -2189,22 +3403,22 @@ mod tests { }); assert_eq!( - scheme.fevent(card, EventFlags::EVENT_READ).unwrap(), + scheme.test_fevent(card, EventFlags::EVENT_READ).unwrap(), Some(EventFlags::EVENT_READ) ); let mut buf = [0u8; 32]; - let len = scheme.read(card, &mut buf).unwrap().unwrap(); + let len = scheme.test_read(card, &mut buf).unwrap().unwrap(); assert_eq!(&buf[..len], b"vblank:4:12\n"); assert_eq!( - scheme.fevent(card, EventFlags::EVENT_READ).unwrap(), + scheme.test_fevent(card, EventFlags::EVENT_READ).unwrap(), Some(EventFlags::empty()) ); } #[test] fn gem_create_rejects_oversized_allocations() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { size: MAX_SCHEME_GEM_BYTES + 1, @@ -2218,7 +3432,7 @@ mod tests { #[test] fn create_dumb_rejects_oversized_allocations() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmCreateDumbWire { width: 16384, @@ -2234,7 +3448,7 @@ mod tests { #[test] fn gem_has_other_refs_returns_false_when_only_current_handle_owns_gem() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2253,7 +3467,7 @@ mod tests { #[test] fn gem_has_other_refs_returns_true_when_another_handle_owns_gem() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card_a = open_card(&mut scheme); let card_b = open_card(&mut scheme); @@ -2287,7 +3501,7 @@ mod tests { #[test] fn gem_is_mapped_returns_false_initially() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2305,7 +3519,7 @@ mod tests { #[test] fn gem_is_mapped_returns_true_after_pin() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2326,7 +3540,7 @@ mod tests { #[test] fn gem_export_refcount_starts_at_zero() { - let scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let scheme = test_scheme(Arc::new(FakeDriver::new(false))); assert_eq!( scheme.gem_export_refcount(9999), @@ -2337,7 +3551,7 @@ mod tests { #[test] fn bump_export_ref_increments_from_zero_to_one() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let gem_handle: GemHandle = 42; scheme.bump_export_ref(gem_handle); @@ -2351,7 +3565,7 @@ mod tests { #[test] fn bump_export_ref_saturates_on_overflow() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let gem_handle: GemHandle = 42; scheme.gem_export_refs.insert(gem_handle, usize::MAX); @@ -2367,7 +3581,7 @@ mod tests { #[test] fn drop_export_ref_decrements_count() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let gem_handle: GemHandle = 42; scheme.bump_export_ref(gem_handle); @@ -2385,7 +3599,7 @@ mod tests { #[test] fn drop_export_ref_removes_entry_when_count_reaches_zero() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let gem_handle: GemHandle = 42; scheme.bump_export_ref(gem_handle); @@ -2402,7 +3616,7 @@ mod tests { #[test] fn drop_export_ref_cleans_up_prime_exports() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let gem_handle: GemHandle = 77; let export_token: u32 = 100; @@ -2422,7 +3636,7 @@ mod tests { #[test] fn gem_can_close_returns_false_when_gem_backs_framebuffer() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2458,7 +3672,7 @@ mod tests { #[test] fn gem_can_close_returns_false_when_gem_is_mapped() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2483,7 +3697,7 @@ mod tests { #[test] fn gem_can_close_returns_true_when_unreferenced() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2506,7 +3720,7 @@ mod tests { #[test] fn allocate_handle_returns_sequential_ids() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let id_a = scheme.allocate_handle(NodeKind::Card); let id_b = scheme.allocate_handle(NodeKind::Card); @@ -2519,7 +3733,7 @@ mod tests { #[test] fn is_fb_active_returns_false_for_unknown_fb() { - let scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let scheme = test_scheme(Arc::new(FakeDriver::new(false))); assert!( !scheme.is_fb_active(12345), @@ -2529,7 +3743,7 @@ mod tests { #[test] fn is_fb_active_returns_true_for_active_crtc_fb() { - let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); let card = open_card(&mut scheme); let create = DrmGemCreateWire { @@ -2581,9 +3795,150 @@ mod tests { ); } + #[test] + fn get_plane_resources_reports_primary_plane() { + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + + let first = DrmGetPlaneResourcesWire::default(); + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_GETPLANERESOURCES, &first).unwrap(); + let first_bytes = read_response_bytes(&mut scheme, card); + let first_resp = decode_wire::(&first_bytes).unwrap(); + assert_eq!(first_resp.count_planes, 1); + assert_eq!(first_bytes.len(), size_of::()); + + let second = DrmGetPlaneResourcesWire { + count_planes: 1, + ..DrmGetPlaneResourcesWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_GETPLANERESOURCES, &second).unwrap(); + let second_bytes = read_response_bytes(&mut scheme, card); + let second_resp = decode_wire::(&second_bytes).unwrap(); + assert_eq!(second_resp.count_planes, 1); + assert_eq!( + read_u32(&second_bytes, size_of::()).unwrap(), + PRIMARY_PLANE_ID + ); + } + + #[test] + fn get_plane_reports_primary_formats() { + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + + let first = DrmGetPlaneWire { + plane_id: PRIMARY_PLANE_ID, + ..DrmGetPlaneWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_GETPLANE, &first).unwrap(); + let first_bytes = read_response_bytes(&mut scheme, card); + let first_resp = decode_wire::(&first_bytes).unwrap(); + assert_eq!(first_resp.count_format_types, 2); + assert_eq!(first_resp.possible_crtcs, 0x1); + + let second = DrmGetPlaneWire { + plane_id: PRIMARY_PLANE_ID, + count_format_types: 2, + ..DrmGetPlaneWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_GETPLANE, &second).unwrap(); + let second_bytes = read_response_bytes(&mut scheme, card); + let second_resp = decode_wire::(&second_bytes).unwrap(); + assert_eq!(second_resp.count_format_types, 2); + assert_eq!( + read_u32(&second_bytes, size_of::()).unwrap(), + DRM_FORMAT_XRGB8888 + ); + assert_eq!( + read_u32( + &second_bytes, + size_of::() + size_of::() + ) + .unwrap(), + DRM_FORMAT_ARGB8888 + ); + } + + #[test] + fn plane_object_properties_expose_primary_type() { + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + + let props = DrmModeObjGetPropertiesWire { + count_props: 1, + obj_id: PRIMARY_PLANE_ID, + obj_type: DRM_MODE_OBJECT_PLANE, + ..DrmModeObjGetPropertiesWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &props).unwrap(); + let prop_bytes = read_response_bytes(&mut scheme, card); + let prop_resp = decode_wire::(&prop_bytes).unwrap(); + assert_eq!(prop_resp.count_props, PLANE_PROPERTY_COUNT as u32); + assert_eq!( + read_u32(&prop_bytes, size_of::()).unwrap(), + PLANE_PROP_TYPE + ); + let value_offset = size_of::() + size_of::(); + let value_bytes = prop_bytes[value_offset..value_offset + size_of::()] + .try_into() + .unwrap(); + assert_eq!(u64::from_le_bytes(value_bytes), DRM_PLANE_TYPE_PRIMARY); + + let property = DrmModeGetPropertyWire { + prop_id: PLANE_PROP_TYPE, + ..DrmModeGetPropertyWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_GETPROPERTY, &property).unwrap(); + let property_resp = read_response::(&mut scheme, card); + assert_eq!( + property_resp.flags, + DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM + ); + assert_eq!( + std::str::from_utf8(&property_resp.name) + .unwrap() + .trim_end_matches(char::from(0)), + "type" + ); + } + + #[test] + fn addfb2_registers_framebuffer_from_primary_handle() { + let mut scheme = test_scheme(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + + let create = DrmGemCreateWire { + size: 640 * 480 * 4, + ..DrmGemCreateWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap(); + let created = read_response::(&mut scheme, card); + + let mut handles = [0; 4]; + handles[0] = created.handle; + let mut pitches = [0; 4]; + pitches[0] = 640 * 4; + let addfb2 = DrmAddFb2Wire { + width: 640, + height: 480, + pixel_format: DRM_FORMAT_XRGB8888, + handles, + pitches, + ..DrmAddFb2Wire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_ADDFB2, &addfb2).unwrap(); + let response = read_response::(&mut scheme, card); + + assert_ne!(response.fb_id, 0); + let fb_info = scheme.fb_registry.get(&response.fb_id).unwrap(); + assert_eq!(fb_info.gem_handle, created.handle); + assert_eq!(fb_info.pitch, 640 * 4); + assert_eq!(fb_info.bpp, 32); + } + #[test] fn validate_gem_create_size_rejects_zero() { - let scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let scheme = test_scheme(Arc::new(FakeDriver::new(false))); let err = scheme .validate_gem_create_size(0, "test-zero-size") diff --git a/local/recipes/kde/kf6-attica/source/CMakeLists.txt b/local/recipes/kde/kf6-attica/source/CMakeLists.txt index 9b4fe40855..2d6063ba3a 100644 --- a/local/recipes/kde/kf6-attica/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-attica/source/CMakeLists.txt @@ -57,7 +57,7 @@ add_subdirectory(src) # Enable unit testing if (BUILD_TESTING) -################################# add_subdirectory(autotests) +################################################### add_subdirectory(autotests) add_subdirectory(tests) endif () diff --git a/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt b/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt index b553483ede..060fc1d967 100644 --- a/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kcmutils/source/CMakeLists.txt @@ -117,6 +117,24 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) # shall we use DBus? # enabled per default on Linux & BSD systems diff --git a/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt b/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt index 936720cf9e..861efca3b2 100644 --- a/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kcolorscheme/source/CMakeLists.txt @@ -95,6 +95,24 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") diff --git a/local/recipes/kde/kf6-kcompletion/source/CMakeLists.txt b/local/recipes/kde/kf6-kcompletion/source/CMakeLists.txt index d1d4881da0..232ad69634 100644 --- a/local/recipes/kde/kf6-kcompletion/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kcompletion/source/CMakeLists.txt @@ -86,6 +86,24 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(KF6Codecs ${KF_DEP_VERSION} REQUIRED) find_package(KF6Config ${KF_DEP_VERSION} REQUIRED) diff --git a/local/recipes/kde/kf6-kconfig/recipe.toml b/local/recipes/kde/kf6-kconfig/recipe.toml index e2d7d04ab4..b6dbb2fedf 100644 --- a/local/recipes/kde/kf6-kconfig/recipe.toml +++ b/local/recipes/kde/kf6-kconfig/recipe.toml @@ -31,7 +31,7 @@ cmake "${COOKBOOK_SOURCE}" \ -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \ -DBUILD_TESTING=OFF \ -DBUILD_QCH=OFF \ - -DKCONFIG_USE_QML=OFF \ + -DKCONFIG_USE_QML=ON \ -DUSE_DBUS=OFF \ -Wno-dev diff --git a/local/recipes/kde/kf6-kconfigwidgets/source/CMakeLists.txt b/local/recipes/kde/kf6-kconfigwidgets/source/CMakeLists.txt index ffa4f2adbf..1db3ad4b0f 100644 --- a/local/recipes/kde/kf6-kconfigwidgets/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kconfigwidgets/source/CMakeLists.txt @@ -89,6 +89,24 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) # shall we use DBus? # enabled per default on Linux & BSD systems diff --git a/local/recipes/kde/kf6-kdeclarative/source/CMakeLists.txt b/local/recipes/kde/kf6-kdeclarative/source/CMakeLists.txt index 47458d1663..e4c3535c3b 100644 --- a/local/recipes/kde/kf6-kdeclarative/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kdeclarative/source/CMakeLists.txt @@ -32,7 +32,7 @@ find_package(KF6GuiAddons ${KF_DEP_VERSION} REQUIRED) if(NOT WIN32 AND NOT APPLE AND NOT ANDROID AND NOT REDOX) -######################################################## find_package(KF6GlobalAccel ${KF_DEP_VERSION} REQUIRED) +########################################################################## find_package(KF6GlobalAccel ${KF_DEP_VERSION} REQUIRED) set(HAVE_KGLOBALACCEL TRUE) else() set(HAVE_KGLOBALACCEL FALSE) diff --git a/local/recipes/kde/kf6-kiconthemes/source/CMakeLists.txt b/local/recipes/kde/kf6-kiconthemes/source/CMakeLists.txt index 9b516742e0..8e38afe92c 100644 --- a/local/recipes/kde/kf6-kiconthemes/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kiconthemes/source/CMakeLists.txt @@ -108,6 +108,25 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6Svg ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) # shall we use DBus? diff --git a/local/recipes/kde/kf6-kidletime/recipe.toml b/local/recipes/kde/kf6-kidletime/recipe.toml index 9312f3a7ca..ba13220902 100644 --- a/local/recipes/kde/kf6-kidletime/recipe.toml +++ b/local/recipes/kde/kf6-kidletime/recipe.toml @@ -1,4 +1,4 @@ -#TODO: KIdleTime — idle time detection. Used by KWin for screen dimming/locking. +# KIdleTime — idle time detection. Used by KWin for screen dimming/locking. [source] tar = "https://invent.kde.org/frameworks/kidletime/-/archive/v6.10.0/kidletime-v6.10.0.tar.gz" diff --git a/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt b/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt index 85c7a57350..de80eb8ef7 100644 --- a/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kitemmodels/source/CMakeLists.txt @@ -38,7 +38,7 @@ set_package_properties(Qt6Qml PROPERTIES ) if (TARGET Qt6::Qml) -################################### include(ECMQmlModule) +##################################################### include(ECMQmlModule) endif() set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") diff --git a/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt b/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt index 3cebee1172..9da8303f36 100644 --- a/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt +++ b/local/recipes/kde/kf6-kitemmodels/source/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_subdirectory(core) if (TARGET Qt6::Qml) -################################## add_subdirectory(qml) +#################################################### add_subdirectory(qml) endif() ecm_qt_install_logging_categories( diff --git a/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt b/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt index 710a2b073b..9ac16dbae2 100644 --- a/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kitemviews/source/CMakeLists.txt @@ -73,6 +73,25 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") diff --git a/local/recipes/kde/kf6-kjobwidgets/source/CMakeLists.txt b/local/recipes/kde/kf6-kjobwidgets/source/CMakeLists.txt index f3d8f2dacb..46886035ac 100644 --- a/local/recipes/kde/kf6-kjobwidgets/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kjobwidgets/source/CMakeLists.txt @@ -75,6 +75,24 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) if(NOT WIN32 AND NOT APPLE AND NOT ANDROID AND NOT HAIKU) option(WITH_X11 "Build with support for QX11Info::appUserTime()" ON) diff --git a/local/recipes/kde/kf6-ksvg/recipe.toml b/local/recipes/kde/kf6-ksvg/recipe.toml new file mode 100644 index 0000000000..516f7f757d --- /dev/null +++ b/local/recipes/kde/kf6-ksvg/recipe.toml @@ -0,0 +1,57 @@ +#TODO: KSvg — SVG rendering library with theme re-coloring and disk caching +[source] +tar = "https://invent.kde.org/frameworks/ksvg/-/archive/v6.10.0/ksvg-v6.10.0.tar.gz" + +[build] +template = "custom" +dependencies = [ + "qtbase", + "kf6-extra-cmake-modules", + "kf6-karchive", + "kf6-kconfig", + "kf6-kcolorscheme", + "kf6-kcoreaddons", + "kf6-kguiaddons", +] +script = """ +DYNAMIC_INIT + +HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build" +source "${COOKBOOK_ROOT}/local/scripts/lib/qt-sysroot.sh" + +redbear_qt_link_sysroot_dirs "${COOKBOOK_SYSROOT}" plugins mkspecs metatypes modules + +sed -i "s/^ecm_install_po_files_as_qm/#ecm_install_po_files_as_qm/" \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true +sed -i 's/^ki18n_install(po)/#ki18n_install(po)/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true + +# Disable QML/Quick declarative imports — not needed without Qt6Quick +sed -i 's/^include(ECMGenerateQmlTypes)/#include(ECMGenerateQmlTypes)/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true +sed -i 's/^include(ECMQmlModule)/#include(ECMQmlModule)/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true + +rm -f CMakeCache.txt +rm -rf CMakeFiles + +cmake "${COOKBOOK_SOURCE}" \ + -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ + -DQT_HOST_PATH="${HOST_BUILD}" \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \ + -DBUILD_TESTING=OFF \ + -DBUILD_QCH=OFF \ + -DBUILD_TOOLS=OFF \ + -DUSE_DBUS=OFF \ + -Wno-dev + +cmake --build . -j${COOKBOOK_MAKE_JOBS} +cmake --install . --prefix "${COOKBOOK_STAGE}/usr" + +for lib in "${COOKBOOK_STAGE}/usr/lib/"libKF6*.so.*; do + [ -f "${lib}" ] || continue + patchelf --remove-rpath "${lib}" 2>/dev/null || true +done +""" diff --git a/local/recipes/kde/kf6-ksvg/source/CMakeLists.txt b/local/recipes/kde/kf6-ksvg/source/CMakeLists.txt index 4fc0c45011..e1c5043364 100644 --- a/local/recipes/kde/kf6-ksvg/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-ksvg/source/CMakeLists.txt @@ -24,10 +24,10 @@ include(ECMSetupVersion) include(ECMQtDeclareLoggingCategory) include(ECMAddQch) include(KDEPackageAppTemplates) -include(ECMGenerateQmlTypes) +#include(ECMGenerateQmlTypes) include(ECMMarkNonGuiExecutable) include(ECMDeprecationSettings) -include(ECMQmlModule) +#include(ECMQmlModule) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") diff --git a/local/recipes/kde/kf6-ktextwidgets/source/CMakeLists.txt b/local/recipes/kde/kf6-ktextwidgets/source/CMakeLists.txt index 0e64d935dd..7d53d66559 100644 --- a/local/recipes/kde/kf6-ktextwidgets/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-ktextwidgets/source/CMakeLists.txt @@ -90,6 +90,24 @@ find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) +find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED) if (WITH_TEXT_TO_SPEECH) find_package(Qt6 ${REQUIRED_QT_VERSION} CONFIG REQUIRED TextToSpeech) diff --git a/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt b/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt index 6ff0964ae3..63f4e1f6b8 100644 --- a/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-kwayland/source/CMakeLists.txt @@ -93,6 +93,25 @@ find_package(Qt6WaylandClientPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) +find_package(Qt6WaylandClientPrivate REQUIRED) set_package_properties(Wayland PROPERTIES TYPE REQUIRED ) diff --git a/local/recipes/kde/kf6-kxmlgui/source/src/kswitchlanguagedialog_p.cpp b/local/recipes/kde/kf6-kxmlgui/source/src/kswitchlanguagedialog_p.cpp index 8e3a00a714..982c144dd4 100644 --- a/local/recipes/kde/kf6-kxmlgui/source/src/kswitchlanguagedialog_p.cpp +++ b/local/recipes/kde/kf6-kxmlgui/source/src/kswitchlanguagedialog_p.cpp @@ -74,10 +74,10 @@ void initializeLanguages() // Ideally setting the LANGUAGE would change the default QLocale too // but unfortunately this is too late since the QCoreApplication constructor // already created a QLocale at this stage so we need to set the reset it -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // by triggering the creation and destruction of a QSystemLocale +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // by triggering the creation and destruction of a QSystemLocale // this is highly dependent on Qt internals, so may break, but oh well -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QSystemLocale *dummy = new QSystemLocale(); -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// delete dummy; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QSystemLocale *dummy = new QSystemLocale(); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// delete dummy; } } diff --git a/local/recipes/kde/kf6-pty/source/CMakeLists.txt b/local/recipes/kde/kf6-pty/source/CMakeLists.txt index 827bf39577..0db9a5b583 100644 --- a/local/recipes/kde/kf6-pty/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-pty/source/CMakeLists.txt @@ -65,9 +65,9 @@ ecm_set_disabled_deprecation_versions( ) add_subdirectory( src ) -######if (BUILD_TESTING) -###### add_subdirectory( autotests ) -######endif() +########################if (BUILD_TESTING) +######################## add_subdirectory( autotests ) +########################endif() if (BUILD_QCH) ecm_install_qch_export( diff --git a/local/recipes/kde/kf6-solid/source/CMakeLists.txt b/local/recipes/kde/kf6-solid/source/CMakeLists.txt index 0bce0cdfce..ddf936537a 100644 --- a/local/recipes/kde/kf6-solid/source/CMakeLists.txt +++ b/local/recipes/kde/kf6-solid/source/CMakeLists.txt @@ -78,7 +78,7 @@ set_package_properties(PList PROPERTIES if (CMAKE_SYSTEM_NAME MATCHES Linux) # Used by the UDisks backend on Linux - #########################################################################find_package(LibMount) + ###########################################################################################find_package(LibMount) set_package_properties(LibMount PROPERTIES TYPE REQUIRED) endif() diff --git a/local/recipes/kde/kwin/recipe.toml b/local/recipes/kde/kwin/recipe.toml index 01c45f351a..441b60b70f 100644 --- a/local/recipes/kde/kwin/recipe.toml +++ b/local/recipes/kde/kwin/recipe.toml @@ -1,8 +1,4 @@ -#TODO: KWin — Qt6::Sensors now available (2026-04-30). Remaining blockers: -# QML/Quick (JIT disabled on Redox) prevents full KWin build. -# Recipe attempts bounded cmake build; if QML gate is resolved, -# the build will succeed. Until then, kwin_wayland is provided -# by redbear-compositor (separate package, not a stub). +#TODO: KWin — full build with Qt6Quick/QML. Effect frames, scripted effects, OSD overlay, outline overlay all enabled. [source] tar = "https://invent.kde.org/plasma/kwin/-/archive/v6.3.4/kwin-v6.3.4.tar.gz" blake3 = "2aa1e234a75b0aa94f0da3a74d93e2a8e49b30a3afb12dc24b2ecd3abaa94e7f" @@ -13,30 +9,47 @@ dependencies = [ "qtbase", "qtdeclarative", "qt5compat", - "qt6-sensors", "kf6-extra-cmake-modules", - "kf6-kcoreaddons", - "kf6-ki18n", + "kf6-kauth", + "kf6-kcolorscheme", + "kf6-kcompletion", "kf6-kconfig", + "kf6-kconfigwidgets", + "kf6-kcoreaddons", "kf6-kcrash", "kf6-kdbusaddons", - "kf6-kservice", - "kf6-kwayland", - "kf6-kwindowsystem", - "kf6-kconfigwidgets", "kf6-kglobalaccel", + "kf6-kguiaddons", + "kf6-ki18n", "kf6-kidletime", - "kf6-knotifications", "kf6-kpackage", - "kf6-kdeclarative", - "kf6-kio", - "kdecoration", + "kf6-kservice", + "kf6-ksvg", + "kf6-kwayland", + "kf6-kwidgetsaddons", + "kf6-kwindowsystem", + "kf6-kxmlgui", + "kf6-kiconthemes", "kf6-kcmutils", + "kf6-kdeclarative", + "kf6-knotifications", + "kf6-kio", + "kf6-kitemmodels", + "kf6-kitemviews", + "kf6-kjobwidgets", + "kf6-ktextwidgets", + "kdecoration", "plasma-wayland-protocols", - "libepoxy-stub", - "libudev-stub", + "libepoxy", + "libdisplay-info", + "libxcvt", + "lcms2", "wayland-protocols", "redbear-compositor", + "libxkbcommon", + "libinput", + "libudev-stub", + "libxcb", ] script = """ DYNAMIC_INIT @@ -44,84 +57,128 @@ DYNAMIC_INIT HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build" STAGE="${COOKBOOK_STAGE}/usr" -for qtdir in plugins mkspecs metatypes modules; do - if [ -d "${COOKBOOK_SYSROOT}/usr/${qtdir}" ] && [ ! -e "${COOKBOOK_SYSROOT}/${qtdir}" ]; then - ln -s "usr/${qtdir}" "${COOKBOOK_SYSROOT}/${qtdir}" +source "${COOKBOOK_ROOT}/local/scripts/lib/qt-sysroot.sh" +redbear_qt_link_sysroot_dirs "${COOKBOOK_SYSROOT}" plugins mkspecs metatypes modules + +if [ -d "${COOKBOOK_SYSROOT}/plugins" ] && [ ! -L "${COOKBOOK_SYSROOT}/plugins" ]; then + if [ -d "${COOKBOOK_SYSROOT}/usr/plugins" ]; then + cp -an "${COOKBOOK_SYSROOT}/plugins/." "${COOKBOOK_SYSROOT}/usr/plugins/" 2>/dev/null || true + rm -rf "${COOKBOOK_SYSROOT}/plugins" + ln -s usr/plugins "${COOKBOOK_SYSROOT}/plugins" fi -done +fi -# Attempt real cmake build with Redox-viable feature set -sed -i "s/^ecm_install_po_files_as_qm/#ecm_install_po_files_as_qm/" \ - "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null -sed -i 's/^ki18n_install(po)/#ki18n_install(po)/' \ - "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null -sed -i '/include(ECMQmlModule)/s/^/#/' "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null +cp -a "${COOKBOOK_ROOT}/recipes/libs/freetype2/target/x86_64-unknown-redox/stage/usr/." "${COOKBOOK_SYSROOT}/" 2>/dev/null || true +cp -a "${COOKBOOK_ROOT}/recipes/dev/fontconfig/target/x86_64-unknown-redox/stage/usr/." "${COOKBOOK_SYSROOT}/" 2>/dev/null || true +cp -rn "${HOST_BUILD}/include/QtGui/6.11.0/QtGui/private/." "${COOKBOOK_SYSROOT}/include/QtGui/6.11.0/QtGui/private/" 2>/dev/null || true -# Remove Qt components not available in cross-build -sed -i '/^ UiTools$/d' "${COOKBOOK_SOURCE}/CMakeLists.txt" -sed -i '/^ Sensors$/d' "${COOKBOOK_SOURCE}/CMakeLists.txt" +FT2_DIR="${COOKBOOK_ROOT}/recipes/libs/freetype2/target/x86_64-unknown-redox/stage/usr" +FC_DIR="${COOKBOOK_ROOT}/recipes/dev/fontconfig/target/x86_64-unknown-redox/stage/usr" +CROSS_PKGCONFIG="${COOKBOOK_ROOT}/bin/x86_64-unknown-redox-pkg-config" -# Remove KF6::Svg references (not available in cross-build) -find "${COOKBOOK_SOURCE}" -name "CMakeLists.txt" -exec sed -i '/KF6::Svg/d' {} + +sed -i 's/Canberra::Canberra/$,Canberra::Canberra,>/' \ + "${COOKBOOK_SOURCE}/src/plugins/systembell/CMakeLists.txt" 2>/dev/null || true + +sed -i '/^[[:space:]]*UiTools$/d' "${COOKBOOK_SOURCE}/CMakeLists.txt" +sed -i 's/find_package(Canberra REQUIRED)/find_package(Canberra QUIET)/' "${COOKBOOK_SOURCE}/CMakeLists.txt" +sed -i 's/add_subdirectory(killer)/#add_subdirectory(killer) # disabled: X11-only/' "${COOKBOOK_SOURCE}/src/helpers/CMakeLists.txt" + +printf '%s\n' '#ifndef SUN_LEN' '#define SUN_LEN(s) (sizeof(*(s)) - sizeof((s)->sun_path) + strnlen((s)->sun_path, sizeof((s)->sun_path)))' '#endif' | cat - "${COOKBOOK_SOURCE}/src/helpers/wayland_wrapper/wl-socket.c" > /tmp/wlsocket.tmp && mv /tmp/wlsocket.tmp "${COOKBOOK_SOURCE}/src/helpers/wayland_wrapper/wl-socket.c" + +python3 -c " +path = '$COOKBOOK_SOURCE/src/cursor.cpp' +with open(path) as f: t = f.read() +t = t.replace('#include ', '') +with open(path, 'w') as f: f.write(t) +" + +sed -i 's/message(FATAL_ERROR "Compiler does not support C++20 ranges")/message(WARNING "C++20 ranges test skipped — strtold toolchain issue")/' "${COOKBOOK_SOURCE}/CMakeLists.txt" + +sed -i '/#include "tabletmodemanager.h"/a #include ' \ + "${COOKBOOK_SOURCE}/src/tabletmodemanager.cpp" 2>/dev/null || true + +sed -i '/#include "device.h"/a #include ' \ + "${COOKBOOK_SOURCE}/src/backends/libinput/device.cpp" 2>/dev/null || true + +sed -i 's/accept4(socketDescriptor, nullptr, nullptr, SOCK_CLOEXEC)/accept(socketDescriptor, nullptr, nullptr)/' \ + "${COOKBOOK_SOURCE}/src/wayland/display.cpp" 2>/dev/null || true + +echo '#ifndef F_ADD_SEALS +#define F_ADD_SEALS 1033 +#define F_GET_SEALS 1034 +#define F_SEAL_SEAL 0x0001 +#define F_SEAL_SHRINK 0x0002 +#define F_SEAL_GROW 0x0004 +#define F_SEAL_WRITE 0x0008 +#endif' >> "${COOKBOOK_SYSROOT}/include/fcntl.h" + +printf '%s\n' '#define F_ADD_SEALS 1033' '#define F_GET_SEALS 1034' '#define F_SEAL_SEAL 0x0001' '#define F_SEAL_SHRINK 0x0002' '#define F_SEAL_GROW 0x0004' '#define F_SEAL_WRITE 0x0008' | cat - "${COOKBOOK_SOURCE}/src/utils/ramfile.cpp" > /tmp/ramfile.tmp && mv /tmp/ramfile.tmp "${COOKBOOK_SOURCE}/src/utils/ramfile.cpp" rm -f CMakeCache.txt rm -rf CMakeFiles -# Qt cmake Imported targets check plugins/ for .so files, but qtwayland and other -# modules install them under usr/plugins/. Mirror any missing files across. -# Also remove QmlPlugins dirs which reference host paths from QML module. -find "${COOKBOOK_SYSROOT}" -type d -name "QmlPlugins" -exec rm -rf {} + 2>/dev/null || true -if [ -d "${COOKBOOK_SYSROOT}/usr/plugins" ] && [ -d "${COOKBOOK_SYSROOT}/plugins" ]; then - for src in $(find "${COOKBOOK_SYSROOT}/usr/plugins" -name "*.so"); do - dst="${COOKBOOK_SYSROOT}/plugins/${src#${COOKBOOK_SYSROOT}/usr/plugins/}" - if [ ! -e "$dst" ]; then - mkdir -p "$(dirname "$dst")" - ln -sf "$src" "$dst" 2>/dev/null || true - fi - done -fi +rm -rf "${COOKBOOK_SOURCE}/po" -# Stub missing KF6 packages needed by dependencies -mkdir -p "${COOKBOOK_SYSROOT}/lib/cmake/KF6Svg" -cat > "${COOKBOOK_SYSROOT}/lib/cmake/KF6Svg/KF6SvgConfig.cmake" << 'KF6EOF' -set(KF6Svg_FOUND TRUE) -KF6EOF +HOST_SCANNER="${COOKBOOK_ROOT}/local/recipes/kde/kwin/target/qtwaylandscanner-host/qtwaylandscanner_kde" -# Stub missing KF6 packages needed by dependencies -mkdir -p "${COOKBOOK_SYSROOT}/lib/cmake/KF6Svg" -cat > "${COOKBOOK_SYSROOT}/lib/cmake/KF6Svg/KF6SvgConfig.cmake" << 'KF6EOF' -set(KF6Svg_FOUND TRUE) -KF6EOF +HOST_SCANNER_BUILD="${COOKBOOK_ROOT}/local/recipes/kde/kwin/target/qtwaylandscanner-host" +mkdir -p "${HOST_SCANNER_BUILD}" +env -u LDFLAGS -u LIBRARY_PATH -u PKG_CONFIG_PATH -u PKG_CONFIG_LIBDIR \ + /usr/bin/pkg-config --exists Qt6Core 2>/dev/null && \ + env -u LDFLAGS -u LIBRARY_PATH -u PKG_CONFIG_PATH -u PKG_CONFIG_LIBDIR \ + g++ -std=c++20 -fPIC -o "${HOST_SCANNER_BUILD}/qtwaylandscanner_kde" \ + "${COOKBOOK_SOURCE}/src/wayland/tools/qtwaylandscanner.cpp" \ + $(env -u LDFLAGS -u LIBRARY_PATH -u PKG_CONFIG_PATH -u PKG_CONFIG_LIBDIR /usr/bin/pkg-config --cflags --libs Qt6Core) || \ + env -u LDFLAGS -u LIBRARY_PATH g++ -std=c++20 -fPIC -o "${HOST_SCANNER_BUILD}/qtwaylandscanner_kde" \ + "${COOKBOOK_SOURCE}/src/wayland/tools/qtwaylandscanner.cpp" \ + -I"${HOST_BUILD}/include" -I"${HOST_BUILD}/include/QtCore" \ + -DQT_CORE_LIB -L"${HOST_BUILD}/lib" -lQt6Core -Wl,-rpath,"${HOST_BUILD}/lib" + +cp -f /usr/bin/wayland-scanner "${COOKBOOK_SYSROOT}/bin/wayland-scanner" + +grep -q "strtold" "${COOKBOOK_SYSROOT}/include/stdlib.h" 2>/dev/null || sed -i '/^#endif/ i long double strtold(const char *nptr, char **endptr);' "${COOKBOOK_SYSROOT}/include/stdlib.h" cmake "${COOKBOOK_SOURCE}" \ -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ -DQT_HOST_PATH="${HOST_BUILD}" \ + -DKF6_HOST_TOOLING="${HOST_BUILD}/lib/cmake" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \ + -DPKG_CONFIG_EXECUTABLE="${CROSS_PKGCONFIG}" \ + -DFREETYPE_LIBRARY="${FT2_DIR}/lib/libfreetype.so" \ + -DFREETYPE_INCLUDE_DIRS="${FT2_DIR}/include/freetype2" \ + -DFontconfig_LIBRARY="${FC_DIR}/lib/libfontconfig.so" \ + -DFontconfig_INCLUDE_DIR="${FC_DIR}/include" \ -DBUILD_TESTING=OFF \ -DBUILD_QCH=OFF \ + -DKWIN_BUILD_AUTO_ROTATION=OFF \ -DKWIN_BUILD_X11=OFF \ + -DKWIN_BUILD_X11_BACKEND=OFF \ -DKWIN_BUILD_KCMS=OFF \ -DKWIN_BUILD_SCREENLOCKER=OFF \ -DKWIN_BUILD_TABBOX=OFF \ -DKWIN_BUILD_GLOBALSHORTCUTS=OFF \ -DKWIN_BUILD_RUNNERS=OFF \ -DKWIN_BUILD_NOTIFICATIONS=OFF \ - -Wno-dev 2>&1 -cmake --build . -j${COOKBOOK_MAKE_JOBS} 2>&1 -cmake --install . --prefix "${STAGE}" 2>&1 + -DKWIN_BUILD_ACTIVITIES=OFF \ + -DKWIN_BUILD_EIS=OFF \ + -DQt6GuiPrivate_DIR="${COOKBOOK_SYSROOT}/lib/cmake/Qt6GuiPrivate" \ + -DQt6CorePrivate_DIR="${COOKBOOK_SYSROOT}/lib/cmake/Qt6CorePrivate" \ + -DQT_FIND_PRIVATE_MODULES=ON \ + -DQTWAYLANDSCANNER_KDE_EXECUTABLE="${HOST_SCANNER}" \ + -DPLASMA_WAYLAND_PROTOCOLS_DIR="${COOKBOOK_SYSROOT}/usr/share/plasma-wayland-protocols" \ + -DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE \ + -DCMAKE_INSTALL_RPATH="/usr/lib" \ + -Wno-dev - # Downstream cmake configs for KF6WindowSystem/KF6Config (needed by plasma-framework, plasma-workspace, plasma-desktop — not KWin itself) - mkdir -p "${STAGE}/lib/cmake/KF6WindowSystem" "${STAGE}/lib/cmake/KF6Config" - cat > "${STAGE}/lib/cmake/KF6WindowSystem/KF6WindowSystemConfig.cmake" << 'EOFCMAKE' -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) -set(KF6WindowSystem_LIBRARIES Qt6::Gui) -EOFCMAKE - cat > "${STAGE}/lib/cmake/KF6Config/KF6ConfigConfig.cmake" << 'EOFCMAKE' -find_package(Qt6 REQUIRED COMPONENTS Core) -set(KF6Config_LIBRARIES Qt6::Core) -EOFCMAKE +cmake --build . -j${COOKBOOK_MAKE_JOBS} +cmake --install . --prefix "${STAGE}" + +for bin in "${STAGE}/bin/"* "${STAGE}/lib/"lib*.so.*; do + [ -f "${bin}" ] || continue + patchelf --set-rpath "/usr/lib" "${bin}" 2>/dev/null || true +done """ [package] diff --git a/local/recipes/libs/freetype2/recipe.toml b/local/recipes/libs/freetype2/recipe.toml new file mode 100644 index 0000000000..df04187938 --- /dev/null +++ b/local/recipes/libs/freetype2/recipe.toml @@ -0,0 +1,15 @@ +[source] +tar = "https://sourceforge.net/projects/freetype/files/freetype2/2.13.3/freetype-2.13.3.tar.xz/download" +blake3 = "07a01894ccdb584943ce817b57341a8595ce9a92bfaa77c602ec4757dfabd5e2" + +[build] +template = "custom" +dependencies = [ + "libpng", + "zlib" +] +script = """ +DYNAMIC_INIT + +cookbook_meson -Dpng=disabled +""" diff --git a/local/recipes/libs/lcms2/recipe.toml b/local/recipes/libs/lcms2/recipe.toml new file mode 100644 index 0000000000..66bfebda8c --- /dev/null +++ b/local/recipes/libs/lcms2/recipe.toml @@ -0,0 +1,30 @@ +[source] +tar = "https://github.com/mm2/Little-CMS/archive/refs/tags/lcms2.19.tar.gz" +blake3 = "730f873079fc24b195f87557c872814206805242e977960ee9e8aff8cd6ab28b" + +[build] +template = "custom" +script = """ +DYNAMIC_INIT + +mkdir -p build +cd build + +cmake "${COOKBOOK_SOURCE}" \ + -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \ + -DBUILD_SHARED_LIBS=ON \ + -DHCMS2_BUILD_TESTS=OFF \ + -DHCMS2_BUILD_TOOLS=OFF \ + -Wno-dev + +cmake --build . -j${COOKBOOK_MAKE_JOBS} +cmake --install . --prefix "${COOKBOOK_STAGE}/usr" + +for lib in "${COOKBOOK_STAGE}/usr/lib/"lib*.so.*; do + [ -f "${lib}" ] || continue + patchelf --remove-rpath "${lib}" 2>/dev/null || true +done +""" diff --git a/local/recipes/libs/libdisplay-info/recipe.toml b/local/recipes/libs/libdisplay-info/recipe.toml new file mode 100644 index 0000000000..2b7dab9afb --- /dev/null +++ b/local/recipes/libs/libdisplay-info/recipe.toml @@ -0,0 +1,52 @@ +[source] +path = "source" + +[build] +template = "custom" +script = ''' +DYNAMIC_INIT + +python3 - <<'PY' "${COOKBOOK_SOURCE}" +from pathlib import Path +import sys + +source_root = Path(sys.argv[1]) +meson_build = source_root / "meson.build" + +if not meson_build.exists(): + meson_build.write_text( + """project('libdisplay-info', 'c', + version: '0.2.0', + meson_version: '>= 0.54.0', + default_options: ['warning_level=1', 'buildtype=debugoptimized']) + +inc = include_directories('include') + +libdisplay_info = shared_library('display-info', + 'di.c', + include_directories: inc, + version: meson.project_version(), + install: true) + +install_headers( + 'include/libdisplay-info/cta.h', + 'include/libdisplay-info/displayid.h', + 'include/libdisplay-info/edid.h', + 'include/libdisplay-info/info.h', + subdir: 'libdisplay-info', +) + +pkg = import('pkgconfig') +pkg.generate( + libdisplay_info, + name: 'libdisplay-info', + description: 'Display identification data parsing library', + filebase: 'libdisplay-info', + version: meson.project_version(), +) +""" + ) +PY + +cookbook_meson +''' diff --git a/local/recipes/libs/libdisplay-info/source/meson.build b/local/recipes/libs/libdisplay-info/source/meson.build new file mode 100644 index 0000000000..fb0a5040fe --- /dev/null +++ b/local/recipes/libs/libdisplay-info/source/meson.build @@ -0,0 +1,29 @@ +project('libdisplay-info', 'c', + version: '0.2.0', + meson_version: '>= 0.54.0', + default_options: ['warning_level=1', 'buildtype=debugoptimized']) + +inc = include_directories('include') + +libdisplay_info = shared_library('display-info', + 'di.c', + include_directories: inc, + version: meson.project_version(), + install: true) + +install_headers( + 'include/libdisplay-info/cta.h', + 'include/libdisplay-info/displayid.h', + 'include/libdisplay-info/edid.h', + 'include/libdisplay-info/info.h', + subdir: 'libdisplay-info', +) + +pkg = import('pkgconfig') +pkg.generate( + libdisplay_info, + name: 'libdisplay-info', + description: 'Display identification data parsing library', + filebase: 'libdisplay-info', + version: meson.project_version(), +) diff --git a/local/recipes/libs/libdrm/redox.patch b/local/recipes/libs/libdrm/redox.patch index 18bf9ae5b7..0508c8ed20 100644 --- a/local/recipes/libs/libdrm/redox.patch +++ b/local/recipes/libs/libdrm/redox.patch @@ -1,3 +1,4 @@ +diff -ruN source-old/include/drm/drm.h source/include/drm/drm.h --- source-old/include/drm/drm.h +++ source/include/drm/drm.h @@ -44,7 +44,11 @@ @@ -12,6 +13,7 @@ #include typedef int8_t __s8; typedef uint8_t __u8; +diff -ruN source-old/xf86drm.c source/xf86drm.c --- source-old/xf86drm.c +++ source/xf86drm.c @@ -57,6 +57,19 @@ @@ -34,7 +36,19 @@ #if HAVE_SYS_SYSCTL_H #include #endif -@@ -304,9 +317,14 @@ +@@ -75,6 +88,7 @@ + #endif + + #include "xf86drm.h" ++#include "xf86drm_redox.h" + #include "libdrm_macros.h" + #include "drm_fourcc.h" + +@@ -300,13 +314,18 @@ + uint64_t type = (modifier >> 52) & 0xf; + + FILE *fp; +- size_t size = 0; char *modifier_name = NULL; bool result = false; @@ -42,6 +56,7 @@ + fprintf(stderr, "open_memstream not available on Redox\n"); + return NULL; +#else ++ size_t size = 0; fp = open_memstream(&modifier_name, &size); if (!fp) return NULL; @@ -49,14 +64,17 @@ switch (type) { case DRM_FORMAT_MOD_ARM_TYPE_AFBC: -@@ -409,9 +427,14 @@ +@@ -407,11 +426,16 @@ + uint64_t tile_version = AMD_FMT_MOD_GET(TILE_VERSION, modifier); + FILE *fp; char *mod_amd = NULL; - size_t size = 0; +- size_t size = 0; +#if defined(__redox__) + fprintf(stderr, "open_memstream not available on Redox\n"); + return NULL; +#else ++ size_t size = 0; fp = open_memstream(&mod_amd, &size); if (!fp) return NULL; @@ -64,7 +82,267 @@ switch (tile_version) { case AMD_FMT_MOD_TILE_VER_GFX9: -@@ -823,6 +846,21 @@ +@@ -680,18 +704,259 @@ + free(pt); + } + ++#if defined(__redox__) ++static int redox_drm_write_all(int fd, const void *buf, size_t len) ++{ ++ const uint8_t *bytes = buf; ++ size_t offset = 0; ++ ++ while (offset < len) { ++ ssize_t written = write(fd, bytes + offset, len - offset); ++ if (written < 0) { ++ if (errno == EINTR || errno == EAGAIN) ++ continue; ++ return -1; ++ } ++ if (written == 0) { ++ errno = EIO; ++ return -1; ++ } ++ offset += (size_t)written; ++ } ++ ++ return 0; ++} ++ ++static int redox_drm_read_all(int fd, void *buf, size_t len) ++{ ++ uint8_t *bytes = buf; ++ size_t offset = 0; ++ ++ while (offset < len) { ++ ssize_t read_count = read(fd, bytes + offset, len - offset); ++ if (read_count < 0) { ++ if (errno == EINTR || errno == EAGAIN) ++ continue; ++ return -1; ++ } ++ if (read_count == 0) { ++ errno = EIO; ++ return -1; ++ } ++ offset += (size_t)read_count; ++ } ++ ++ return 0; ++} ++ ++static int redox_drm_send_request(int fd, unsigned long request_code, ++ const void *payload, size_t payload_size) ++{ ++ const size_t request_word_size = sizeof(uintptr_t); ++ const size_t total_size = request_word_size + payload_size; ++ uint8_t stack_request[sizeof(uintptr_t)]; ++ uint8_t *request_bytes = stack_request; ++ uintptr_t request_word = (uintptr_t)request_code; ++ size_t i; ++ ++ if (total_size > sizeof(stack_request)) { ++ request_bytes = drmMalloc(total_size); ++ if (!request_bytes) { ++ errno = ENOMEM; ++ return -1; ++ } ++ } ++ ++ for (i = 0; i < request_word_size; ++i) ++ request_bytes[i] = (uint8_t)((request_word >> (i * 8)) & 0xffU); ++ ++ if (payload_size != 0) ++ memcpy(request_bytes + request_word_size, payload, payload_size); ++ ++ if (redox_drm_write_all(fd, request_bytes, total_size) != 0) { ++ if (request_bytes != stack_request) ++ drmFree(request_bytes); ++ return -1; ++ } ++ ++ if (request_bytes != stack_request) ++ drmFree(request_bytes); ++ ++ return 0; ++} ++ ++static int redox_drm_get_response_size(int fd, size_t *response_size) ++{ ++ stat_t st; ++ ++ if (fstat(fd, &st) != 0) ++ return -1; ++ ++ *response_size = (size_t)st.st_size; ++ return 0; ++} ++ ++unsigned long redox_translate_request(unsigned long linux_nr) ++{ ++ switch (linux_nr) { ++ case 0x0C: ++ return REDOX_DRM_IOCTL_GET_CAP; ++ case 0x0D: ++ return REDOX_DRM_IOCTL_SET_CLIENT_CAP; ++ case 0x09: ++ return REDOX_DRM_IOCTL_GEM_CLOSE; ++ case 0xA6: ++ return REDOX_DRM_IOCTL_MODE_GETENCODER; ++ case 0xB3: ++ return REDOX_DRM_IOCTL_MODE_MAP_DUMB; ++ case 0xB4: ++ return REDOX_DRM_IOCTL_MODE_DESTROY_DUMB; ++ case 0xAF: ++ return REDOX_DRM_IOCTL_MODE_RMFB; ++ default: ++ return 0; ++ } ++} ++ ++int redox_drm_exchange(int fd, unsigned long request_code, ++ const void *payload, size_t payload_size, ++ void *response, size_t response_capacity, ++ size_t *response_size) ++{ ++ uint8_t stack_buffer[64]; ++ uint8_t *response_buffer = stack_buffer; ++ size_t actual_response_size = 0; ++ ++ if (redox_drm_send_request(fd, request_code, payload, payload_size) != 0) ++ return -1; ++ ++ if (redox_drm_get_response_size(fd, &actual_response_size) != 0) ++ return -1; ++ ++ if (response_size) ++ *response_size = actual_response_size; ++ ++ if (actual_response_size == 0) ++ return 0; ++ ++ if (response && response_capacity >= actual_response_size) { ++ return redox_drm_read_all(fd, response, actual_response_size); ++ } ++ ++ if (actual_response_size > sizeof(stack_buffer)) { ++ response_buffer = drmMalloc(actual_response_size); ++ if (!response_buffer) { ++ errno = ENOMEM; ++ return -1; ++ } ++ } ++ ++ if (redox_drm_read_all(fd, response_buffer, actual_response_size) != 0) { ++ if (response_buffer != stack_buffer) ++ drmFree(response_buffer); ++ return -1; ++ } ++ ++ if (response_buffer != stack_buffer) ++ drmFree(response_buffer); ++ ++ if (response && response_capacity < actual_response_size) { ++ errno = EMSGSIZE; ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int redox_drm_exchange_alloc(int fd, unsigned long request_code, ++ const void *payload, size_t payload_size, ++ void **response, size_t *response_size) ++{ ++ size_t actual_response_size = 0; ++ void *allocated_response = NULL; ++ ++ if (!response) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (redox_drm_send_request(fd, request_code, payload, payload_size) != 0) ++ return -1; ++ ++ if (redox_drm_get_response_size(fd, &actual_response_size) != 0) ++ return -1; ++ ++ if (actual_response_size != 0) { ++ allocated_response = drmMalloc(actual_response_size); ++ if (!allocated_response) { ++ errno = ENOMEM; ++ return -1; ++ } ++ ++ if (redox_drm_read_all(fd, allocated_response, actual_response_size) != 0) { ++ drmFree(allocated_response); ++ return -1; ++ } ++ } ++ ++ *response = allocated_response; ++ if (response_size) ++ *response_size = actual_response_size; ++ return 0; ++} ++ ++static size_t redox_drm_expected_response_size(unsigned long linux_nr, size_t arg_size) ++{ ++ switch (linux_nr) { ++ case 0x0C: ++ case 0xA6: ++ case 0xB3: ++ return arg_size; ++ default: ++ return 0; ++ } ++} ++ ++int redox_drm_simple_ioctl(int fd, unsigned long request, void *arg) ++{ ++ const unsigned long linux_nr = REDOX_LINUX_IOCTL_NR(request); ++ const unsigned long request_code = redox_translate_request(linux_nr); ++ const size_t arg_size = REDOX_LINUX_IOCTL_SIZE(request); ++ const size_t expected_response_size = redox_drm_expected_response_size(linux_nr, arg_size); ++ ++ if (request_code == 0) { ++ errno = ENOTTY; ++ return -1; ++ } ++ ++ if (redox_drm_exchange(fd, request_code, ++ arg, arg ? arg_size : 0, ++ expected_response_size ? arg : NULL, ++ expected_response_size, ++ NULL) != 0) { ++ return -1; ++ } ++ ++ return 0; ++} ++#endif ++ + /** + * Call ioctl, restarting if it is interrupted + */ + drm_public int + drmIoctl(int fd, unsigned long request, void *arg) + { ++ #if defined(__redox__) ++ return redox_drm_simple_ioctl(fd, request, arg); ++ #else + int ret; + + do { + ret = ioctl(fd, request, arg); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + return ret; ++ #endif + } + + static unsigned long drmGetKeyFromFd(int fd) +@@ -823,6 +1088,21 @@ return NULL; } @@ -86,7 +364,7 @@ /** * Open the DRM device, creating it if necessary. * -@@ -839,7 +877,6 @@ +@@ -839,7 +1119,6 @@ static int drmOpenDevice(dev_t dev, int minor, int type) { stat_t st; @@ -94,7 +372,7 @@ char buf[DRM_NODE_NAME_MAX]; int fd; mode_t devmode = DRM_DEV_MODE, serv_mode; -@@ -850,10 +887,8 @@ +@@ -850,10 +1129,8 @@ gid_t group = DRM_DEV_GID; #endif @@ -106,7 +384,7 @@ drmMsg("drmOpenDevice: node name is %s\n", buf); if (drm_server_info && drm_server_info->get_perms) { -@@ -958,15 +993,13 @@ +@@ -958,15 +1235,13 @@ { int fd; char buf[DRM_NODE_NAME_MAX]; @@ -123,7 +401,103 @@ if ((fd = open(buf, O_RDWR | O_CLOEXEC)) >= 0) return fd; return -errno; -@@ -3306,7 +3339,8 @@ +@@ -1060,6 +1335,7 @@ + return -1; + } + ++#if !defined(__redox__) + static const char *drmGetMinorName(int type) + { + switch (type) { +@@ -1071,6 +1347,7 @@ + return NULL; + } + } ++#endif + + /** + * Open the device by bus ID. +@@ -1168,7 +1445,11 @@ + for (i = base; i < base + DRM_MAX_MINOR; i++) { + if ((fd = drmOpenMinor(i, 1, type)) >= 0) { + if ((version = drmGetVersion(fd))) { ++ #if defined(__redox__) ++ if (!version->name || !strcmp(version->name, name)) { ++ #else + if (!strcmp(version->name, name)) { ++ #endif + drmFreeVersion(version); + id = drmGetBusid(fd); + drmMsg("drmGetBusid returned '%s'\n", id ? id : "NULL"); +@@ -1321,6 +1602,7 @@ + * Used by drmGetVersion() to free the memory pointed by \p %v as well as all + * the non-null strings pointers in it. + */ ++#if !defined(__redox__) + static void drmFreeKernelVersion(drm_version_t *v) + { + if (!v) +@@ -1330,6 +1612,7 @@ + drmFree(v->desc); + drmFree(v); + } ++#endif + + + /** +@@ -1342,6 +1625,7 @@ + * Used by drmGetVersion() to translate the information returned by the ioctl + * interface in a private structure into the public structure counterpart. + */ ++#if !defined(__redox__) + static void drmCopyVersion(drmVersionPtr d, const drm_version_t *s) + { + d->version_major = s->version_major; +@@ -1354,6 +1638,7 @@ + d->desc_len = s->desc_len; + d->desc = s->desc ? strdup(s->desc) : NULL; + } ++#endif + + + /** +@@ -1373,6 +1658,27 @@ + */ + drm_public drmVersionPtr drmGetVersion(int fd) + { ++ #if defined(__redox__) ++ drmVersionPtr retval = drmMalloc(sizeof(*retval)); ++ struct redox_drm_version_wire version; ++ ++ if (!retval) ++ return NULL; ++ ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_VERSION, ++ NULL, 0, ++ &version, sizeof(version), ++ NULL) != 0) { ++ drmFree(retval); ++ return NULL; ++ } ++ ++ memclear(*retval); ++ retval->version_major = version.major; ++ retval->version_minor = version.minor; ++ retval->version_patchlevel = version.patch; ++ return retval; ++ #else + drmVersionPtr retval; + drm_version_t *version = drmMalloc(sizeof(*version)); + +@@ -1403,6 +1709,7 @@ + drmCopyVersion(retval, version); + drmFreeKernelVersion(version); + return retval; ++ #endif + } + + +@@ -3306,7 +3613,8 @@ d = sbuf.st_rdev; for (i = 0; i < DRM_MAX_MINOR; i++) { @@ -133,7 +507,67 @@ if (stat(name, &sbuf) == 0 && sbuf.st_rdev == d) break; } -@@ -3495,7 +3529,6 @@ +@@ -3366,6 +3674,23 @@ + drm_public int drmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags, + int *prime_fd) + { ++ #if defined(__redox__) ++ struct redox_drm_prime_handle_to_fd_wire request; ++ struct redox_drm_prime_handle_to_fd_response_wire response; ++ ++ memclear(request); ++ request.handle = handle; ++ request.flags = flags; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_PRIME_HANDLE_TO_FD, ++ &request, sizeof(request), ++ &response, sizeof(response), ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ *prime_fd = response.fd; ++ return 0; ++ #else + struct drm_prime_handle args; + int ret; + +@@ -3379,10 +3704,27 @@ + + *prime_fd = args.fd; + return 0; ++ #endif + } + + drm_public int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle) + { ++ #if defined(__redox__) ++ struct redox_drm_prime_fd_to_handle_wire request; ++ struct redox_drm_prime_fd_to_handle_response_wire response; ++ ++ memclear(request); ++ request.fd = prime_fd; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_PRIME_FD_TO_HANDLE, ++ &request, sizeof(request), ++ &response, sizeof(response), ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ *handle = response.handle; ++ return 0; ++ #else + struct drm_prime_handle args; + int ret; + +@@ -3394,6 +3736,7 @@ + + *handle = args.handle; + return 0; ++ #endif + } + + drm_public int drmCloseBufferHandle(int fd, uint32_t handle) +@@ -3495,7 +3838,6 @@ #else struct stat sbuf; char buf[PATH_MAX + 1]; @@ -141,7 +575,7 @@ unsigned int maj, min; int n; -@@ -3508,10 +3541,9 @@ +@@ -3508,11 +3850,10 @@ if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode)) return NULL; @@ -151,10 +585,12 @@ return NULL; - - n = snprintf(buf, sizeof(buf), dev_name, DRM_DIR_NAME, min); - if (n == -1 || n >= sizeof(buf)) +- if (n == -1 || n >= sizeof(buf)) ++ if ((size_t)n >= sizeof(buf)) return NULL; -@@ -4877,7 +4909,6 @@ + return strdup(buf); +@@ -4877,7 +5218,6 @@ #else struct stat sbuf; char node[PATH_MAX + 1]; @@ -162,7 +598,7 @@ int node_type; int maj, min, n; -@@ -4894,12 +4925,8 @@ +@@ -4894,12 +5234,8 @@ if (node_type == -1) return NULL; @@ -177,6 +613,7 @@ return NULL; return strdup(node); +diff -ruN source-old/xf86drm.h source/xf86drm.h --- source-old/xf86drm.h +++ source/xf86drm.h @@ -47,7 +47,7 @@ @@ -201,3 +638,905 @@ #else #define DRM_DIR_NAME "/dev/dri" #define DRM_PRIMARY_MINOR_NAME "card" +diff -ruN source-old/xf86drmMode.c source/xf86drmMode.c +--- source-old/xf86drmMode.c ++++ source/xf86drmMode.c +@@ -50,6 +50,7 @@ + #include "libdrm_macros.h" + #include "xf86drmMode.h" + #include "xf86drm.h" ++#include "xf86drm_redox.h" + #include + #include + #include +@@ -68,6 +69,212 @@ + return ret < 0 ? -errno : ret; + } + ++static void* drmAllocCpy(char *array, int count, int entry_size); ++ ++#if defined(__redox__) ++static void redox_drm_mode_from_wire(drmModeModeInfoPtr mode, ++ const struct redox_drm_mode_wire *wire, ++ const char *name) ++{ ++ memclear(*mode); ++ mode->clock = wire->clock; ++ mode->hdisplay = wire->hdisplay; ++ mode->hsync_start = wire->hsync_start; ++ mode->hsync_end = wire->hsync_end; ++ mode->htotal = wire->htotal; ++ mode->hskew = wire->hskew; ++ mode->vdisplay = wire->vdisplay; ++ mode->vsync_start = wire->vsync_start; ++ mode->vsync_end = wire->vsync_end; ++ mode->vtotal = wire->vtotal; ++ mode->vscan = wire->vscan; ++ mode->vrefresh = wire->vrefresh; ++ mode->flags = wire->flags; ++ mode->type = wire->type; ++ if (name) { ++ size_t name_len = strlen(name); ++ if (name_len >= DRM_DISPLAY_MODE_LEN) ++ name_len = DRM_DISPLAY_MODE_LEN - 1; ++ memcpy(mode->name, name, name_len); ++ } ++} ++ ++static void redox_drm_mode_to_wire(struct redox_drm_mode_wire *wire, ++ const drmModeModeInfo *mode) ++{ ++ memclear(*wire); ++ wire->clock = mode->clock; ++ wire->hdisplay = mode->hdisplay; ++ wire->hsync_start = mode->hsync_start; ++ wire->hsync_end = mode->hsync_end; ++ wire->htotal = mode->htotal; ++ wire->hskew = mode->hskew; ++ wire->vdisplay = mode->vdisplay; ++ wire->vsync_start = mode->vsync_start; ++ wire->vsync_end = mode->vsync_end; ++ wire->vtotal = mode->vtotal; ++ wire->vscan = mode->vscan; ++ wire->vrefresh = mode->vrefresh; ++ wire->flags = mode->flags; ++ wire->type = mode->type; ++} ++ ++static int redox_drm_fetch_resources(int fd, ++ struct redox_drm_resources_wire *header, ++ uint32_t **connector_ids) ++{ ++ void *response = NULL; ++ size_t response_size = 0; ++ size_t expected_size; ++ ++ if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETRESOURCES, ++ NULL, 0, ++ &response, &response_size) != 0) { ++ return -1; ++ } ++ ++ if (response_size < sizeof(*header)) { ++ drmFree(response); ++ errno = EPROTO; ++ return -1; ++ } ++ ++ memcpy(header, response, sizeof(*header)); ++ expected_size = sizeof(*header) + ++ ((size_t)header->connector_count * sizeof(uint32_t)); ++ if (response_size < expected_size) { ++ drmFree(response); ++ errno = EPROTO; ++ return -1; ++ } ++ ++ *connector_ids = NULL; ++ if (header->connector_count != 0) { ++ *connector_ids = drmAllocCpy((char *)response + sizeof(*header), ++ header->connector_count, sizeof(uint32_t)); ++ if (!*connector_ids) { ++ drmFree(response); ++ errno = ENOMEM; ++ return -1; ++ } ++ } ++ ++ drmFree(response); ++ return 0; ++} ++ ++static int redox_drm_parse_connector_response(const void *blob, size_t blob_size, ++ struct redox_drm_connector_wire *header, ++ drmModeModeInfoPtr *modes_out) ++{ ++ const uint8_t *bytes = blob; ++ drmModeModeInfoPtr modes = NULL; ++ size_t offset = sizeof(*header); ++ uint32_t i; ++ ++ if (blob_size < sizeof(*header)) { ++ errno = EPROTO; ++ return -1; ++ } ++ ++ memcpy(header, bytes, sizeof(*header)); ++ if (header->mode_count != 0) { ++ modes = drmMalloc(header->mode_count * sizeof(*modes)); ++ if (!modes) { ++ errno = ENOMEM; ++ return -1; ++ } ++ } ++ ++ for (i = 0; i < header->mode_count; ++i) { ++ struct redox_drm_mode_wire mode_wire; ++ const char *name; ++ size_t name_len; ++ ++ if (blob_size - offset < sizeof(mode_wire)) { ++ drmFree(modes); ++ errno = EPROTO; ++ return -1; ++ } ++ ++ memcpy(&mode_wire, bytes + offset, sizeof(mode_wire)); ++ offset += sizeof(mode_wire); ++ name = (const char *)bytes + offset; ++ name_len = strnlen(name, blob_size - offset); ++ if (offset + name_len >= blob_size) { ++ drmFree(modes); ++ errno = EPROTO; ++ return -1; ++ } ++ ++ redox_drm_mode_from_wire(&modes[i], &mode_wire, name); ++ offset += name_len + 1; ++ } ++ ++ *modes_out = modes; ++ return 0; ++} ++ ++static int redox_drm_fetch_connector(int fd, uint32_t connector_id, ++ struct redox_drm_connector_wire *header, ++ drmModeModeInfoPtr *modes_out) ++{ ++ void *response = NULL; ++ size_t response_size = 0; ++ int ret; ++ ++ if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETCONNECTOR, ++ &connector_id, sizeof(connector_id), ++ &response, &response_size) != 0) { ++ return -1; ++ } ++ ++ ret = redox_drm_parse_connector_response(response, response_size, ++ header, modes_out); ++ drmFree(response); ++ return ret; ++} ++ ++static uint32_t redox_drm_connector_type_id(int fd, uint32_t connector_id, ++ uint32_t connector_type) ++{ ++ struct redox_drm_resources_wire resources; ++ uint32_t *connector_ids = NULL; ++ uint32_t ordinal = 0; ++ uint32_t i; ++ ++ if (redox_drm_fetch_resources(fd, &resources, &connector_ids) != 0) ++ return 0; ++ ++ for (i = 0; i < resources.connector_count; ++i) { ++ struct redox_drm_connector_wire header; ++ drmModeModeInfoPtr modes = NULL; ++ ++ if (redox_drm_fetch_connector(fd, connector_ids[i], &header, &modes) != 0) ++ break; ++ drmFree(modes); ++ ++ if (header.connector_type == connector_type) ++ ++ordinal; ++ if (header.connector_id == connector_id) ++ break; ++ } ++ ++ drmFree(connector_ids); ++ return ordinal; ++} ++ ++static int redox_drm_page_flip_unsupported(uint32_t flags) ++{ ++ if (flags & (DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_TARGET)) { ++ errno = EOPNOTSUPP; ++ return -1; ++ } ++ ++ return 0; ++} ++#endif ++ + /* + * Util functions + */ +@@ -153,16 +360,104 @@ + + drm_public int drmIsKMS(int fd) + { ++ #if defined(__redox__) ++ struct redox_drm_resources_wire resources; ++ uint32_t *connector_ids = NULL; ++ int is_kms; ++ ++ if (redox_drm_fetch_resources(fd, &resources, &connector_ids) != 0) ++ return 0; ++ ++ is_kms = resources.crtc_count > 0 && ++ resources.connector_count > 0 && ++ resources.encoder_count > 0; ++ drmFree(connector_ids); ++ return is_kms; ++ #else + struct drm_mode_card_res res = {0}; + + if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) != 0) + return 0; + + return res.count_crtcs > 0 && res.count_connectors > 0 && res.count_encoders > 0; ++ #endif + } + + drm_public drmModeResPtr drmModeGetResources(int fd) + { ++ #if defined(__redox__) ++ struct redox_drm_resources_wire resources; ++ uint32_t *connector_ids = NULL; ++ drmModeResPtr r = NULL; ++ uint32_t i; ++ ++ if (redox_drm_fetch_resources(fd, &resources, &connector_ids) != 0) ++ return NULL; ++ ++ r = drmMalloc(sizeof(*r)); ++ if (!r) { ++ drmFree(connector_ids); ++ return NULL; ++ } ++ memclear(*r); ++ ++ r->count_connectors = resources.connector_count; ++ r->count_crtcs = resources.crtc_count; ++ r->count_encoders = resources.encoder_count; ++ r->connectors = connector_ids; ++ ++ if (resources.crtc_count != 0) { ++ r->crtcs = drmMalloc(resources.crtc_count * sizeof(*r->crtcs)); ++ if (!r->crtcs) ++ goto err_allocs; ++ for (i = 0; i < resources.crtc_count; ++i) ++ r->crtcs[i] = i + 1; ++ } ++ ++ if (resources.encoder_count != 0) { ++ r->encoders = drmMalloc(resources.encoder_count * sizeof(*r->encoders)); ++ if (!r->encoders) ++ goto err_allocs; ++ memset(r->encoders, 0, resources.encoder_count * sizeof(*r->encoders)); ++ } ++ ++ for (i = 0; i < resources.connector_count; ++i) { ++ struct redox_drm_connector_wire connector; ++ drmModeModeInfoPtr modes = NULL; ++ uint32_t j; ++ ++ if (redox_drm_fetch_connector(fd, connector_ids[i], &connector, &modes) != 0) ++ goto err_allocs; ++ ++ if (i < resources.encoder_count) ++ r->encoders[i] = connector.encoder_id; ++ ++ for (j = 0; j < connector.mode_count; ++j) { ++ uint32_t width = modes[j].hdisplay; ++ uint32_t height = modes[j].vdisplay; ++ if (r->min_width == 0 || width < r->min_width) ++ r->min_width = width; ++ if (width > r->max_width) ++ r->max_width = width; ++ if (r->min_height == 0 || height < r->min_height) ++ r->min_height = height; ++ if (height > r->max_height) ++ r->max_height = height; ++ } ++ drmFree(modes); ++ } ++ ++ if (resources.encoder_count > resources.connector_count) { ++ for (i = resources.connector_count; i < resources.encoder_count; ++i) ++ r->encoders[i] = 0; ++ } ++ ++ return r; ++ ++err_allocs: ++ drmModeFreeResources(r); ++ return NULL; ++ #else + struct drm_mode_card_res res, counts; + drmModeResPtr r = 0; + +@@ -253,6 +548,7 @@ + drmFree(U642VOID(res.encoder_id_ptr)); + + return r; ++ #endif + } + + +@@ -260,6 +556,26 @@ + uint8_t bpp, uint32_t pitch, uint32_t bo_handle, + uint32_t *buf_id) + { ++ #if defined(__redox__) ++ struct redox_drm_add_fb_wire wire; ++ ++ memclear(wire); ++ wire.width = width; ++ wire.height = height; ++ wire.pitch = pitch; ++ wire.bpp = bpp; ++ wire.depth = depth; ++ wire.handle = bo_handle; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_ADDFB, ++ &wire, sizeof(wire), ++ &wire, sizeof(wire), ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ *buf_id = wire.fb_id; ++ return 0; ++ #else + struct drm_mode_fb_cmd f; + int ret; + +@@ -276,6 +592,7 @@ + + *buf_id = f.fb_id; + return 0; ++ #endif + } + + drm_public int drmModeAddFB2WithModifiers(int fd, uint32_t width, +@@ -317,7 +634,22 @@ + + drm_public int drmModeRmFB(int fd, uint32_t bufferId) + { ++ #if defined(__redox__) ++ struct redox_drm_rm_fb_wire wire; ++ ++ memclear(wire); ++ wire.fb_id = bufferId; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_RMFB, ++ &wire, sizeof(wire), ++ NULL, 0, ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ return 0; ++ #else + return DRM_IOCTL(fd, DRM_IOCTL_MODE_RMFB, &bufferId); ++ #endif + } + + drm_public int drmModeCloseFB(int fd, uint32_t buffer_id) +@@ -374,6 +706,40 @@ + + drm_public drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId) + { ++ #if defined(__redox__) ++ struct redox_drm_get_crtc_wire wire; ++ drmModeCrtcPtr r; ++ ++ memclear(wire); ++ wire.crtc_id = crtcId; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_GETCRTC, ++ &wire, sizeof(wire), ++ &wire, sizeof(wire), ++ NULL) != 0) { ++ return NULL; ++ } ++ ++ r = drmMalloc(sizeof(*r)); ++ if (!r) ++ return NULL; ++ ++ memclear(*r); ++ r->crtc_id = wire.crtc_id; ++ r->x = wire.x; ++ r->y = wire.y; ++ r->mode_valid = wire.mode_valid; ++ if (r->mode_valid) { ++ char mode_name[DRM_DISPLAY_MODE_LEN]; ++ snprintf(mode_name, sizeof(mode_name), "%ux%u@%u", ++ wire.mode.hdisplay, wire.mode.vdisplay, wire.mode.vrefresh); ++ redox_drm_mode_from_wire(&r->mode, &wire.mode, mode_name); ++ r->width = wire.mode.hdisplay; ++ r->height = wire.mode.vdisplay; ++ } ++ r->buffer_id = wire.fb_id; ++ r->gamma_size = 0; ++ return r; ++ #else + struct drm_mode_crtc crtc; + drmModeCrtcPtr r; + +@@ -402,12 +768,47 @@ + r->buffer_id = crtc.fb_id; + r->gamma_size = crtc.gamma_size; + return r; ++ #endif + } + + drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, + uint32_t x, uint32_t y, uint32_t *connectors, int count, + drmModeModeInfoPtr mode) + { ++ #if defined(__redox__) ++ struct redox_drm_set_crtc_wire wire; ++ ++ if ((x != 0 || y != 0) && bufferId != 0) { ++ errno = EOPNOTSUPP; ++ return -errno; ++ } ++ if (count < 0 || count > 8) { ++ errno = EINVAL; ++ return -errno; ++ } ++ if (bufferId != 0 && !mode) { ++ errno = EINVAL; ++ return -errno; ++ } ++ ++ memclear(wire); ++ wire.crtc_id = crtcId; ++ wire.fb_handle = bufferId; ++ wire.connector_count = count; ++ if (count > 0) ++ memcpy(wire.connectors, connectors, count * sizeof(*connectors)); ++ if (mode) ++ redox_drm_mode_to_wire(&wire.mode, mode); ++ ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_SETCRTC, ++ &wire, sizeof(wire), ++ NULL, 0, ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ return 0; ++ #else + struct drm_mode_crtc crtc; + + memclear(crtc); +@@ -423,6 +824,7 @@ + } + + return DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc); ++ #endif + } + + /* +@@ -480,6 +882,30 @@ + */ + drm_public drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) + { ++ #if defined(__redox__) ++ struct drm_mode_get_encoder enc; ++ drmModeEncoderPtr r = NULL; ++ ++ memclear(enc); ++ enc.encoder_id = encoder_id; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_GETENCODER, ++ &enc, sizeof(enc), ++ &enc, sizeof(enc), ++ NULL) != 0) { ++ return NULL; ++ } ++ ++ r = drmMalloc(sizeof(*r)); ++ if (!r) ++ return NULL; ++ ++ r->encoder_id = enc.encoder_id; ++ r->crtc_id = enc.crtc_id; ++ r->encoder_type = enc.encoder_type; ++ r->possible_crtcs = enc.possible_crtcs; ++ r->possible_clones = enc.possible_clones; ++ return r; ++ #else + struct drm_mode_get_encoder enc; + drmModeEncoderPtr r = NULL; + +@@ -499,6 +925,7 @@ + r->possible_clones = enc.possible_clones; + + return r; ++ #endif + } + + /* +@@ -507,6 +934,50 @@ + static drmModeConnectorPtr + _drmModeGetConnector(int fd, uint32_t connector_id, int probe) + { ++ #if defined(__redox__) ++ struct redox_drm_connector_wire conn; ++ drmModeConnectorPtr r = NULL; ++ drmModeModeInfoPtr modes = NULL; ++ ++ (void)probe; ++ if (redox_drm_fetch_connector(fd, connector_id, &conn, &modes) != 0) ++ return NULL; ++ ++ r = drmMalloc(sizeof(*r)); ++ if (!r) ++ goto err_allocs; ++ memclear(*r); ++ ++ r->connector_id = conn.connector_id; ++ r->encoder_id = conn.encoder_id; ++ r->connection = conn.connection; ++ r->mmWidth = conn.mm_width; ++ r->mmHeight = conn.mm_height; ++ r->subpixel = DRM_MODE_SUBPIXEL_UNKNOWN; ++ r->count_modes = conn.mode_count; ++ r->modes = modes; ++ r->count_props = 0; ++ r->props = NULL; ++ r->prop_values = NULL; ++ r->count_encoders = conn.encoder_id ? 1 : 0; ++ if (r->count_encoders != 0) { ++ r->encoders = drmMalloc(sizeof(*r->encoders)); ++ if (!r->encoders) ++ goto err_allocs; ++ r->encoders[0] = conn.encoder_id; ++ } ++ r->connector_type = conn.connector_type; ++ r->connector_type_id = redox_drm_connector_type_id(fd, ++ conn.connector_id, ++ conn.connector_type); ++ return r; ++ ++err_allocs: ++ drmFree(modes); ++ drmFree(r ? r->encoders : NULL); ++ drmFree(r); ++ return NULL; ++ #else + struct drm_mode_get_connector conn, counts; + drmModeConnectorPtr r = NULL; + struct drm_mode_modeinfo stack_mode; +@@ -608,6 +1079,7 @@ + drmFree(U642VOID(conn.encoders_ptr)); + + return r; ++ #endif + } + + drm_public drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id) +@@ -1100,6 +1572,27 @@ + drm_public int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, + uint32_t flags, void *user_data) + { ++ #if defined(__redox__) ++ struct redox_drm_page_flip_wire flip; ++ uint64_t sequence; ++ ++ (void)user_data; ++ if (redox_drm_page_flip_unsupported(flags) != 0) ++ return -errno; ++ ++ memclear(flip); ++ flip.fb_handle = fb_id; ++ flip.crtc_id = crtc_id; ++ flip.flags = flags; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_PAGE_FLIP, ++ &flip, sizeof(flip), ++ &sequence, sizeof(sequence), ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ return 0; ++ #else + struct drm_mode_crtc_page_flip flip; + + memclear(flip); +@@ -1109,12 +1602,23 @@ + flip.flags = flags; + + return DRM_IOCTL(fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip); ++ #endif + } + + drm_public int drmModePageFlipTarget(int fd, uint32_t crtc_id, uint32_t fb_id, + uint32_t flags, void *user_data, + uint32_t target_vblank) + { ++ #if defined(__redox__) ++ (void)fd; ++ (void)crtc_id; ++ (void)fb_id; ++ (void)flags; ++ (void)user_data; ++ (void)target_vblank; ++ errno = EOPNOTSUPP; ++ return -errno; ++ #else + struct drm_mode_crtc_page_flip_target flip_target; + + memclear(flip_target); +@@ -1125,6 +1629,7 @@ + flip_target.sequence = target_vblank; + + return DRM_IOCTL(fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip_target); ++ #endif + } + + drm_public int drmModeSetPlane(int fd, uint32_t plane_id, uint32_t crtc_id, +@@ -1839,6 +2344,26 @@ + uint32_t flags, uint32_t *handle, uint32_t *pitch, + uint64_t *size) + { ++ #if defined(__redox__) ++ struct redox_drm_create_dumb_wire create; ++ ++ memclear(create); ++ create.width = width; ++ create.height = height; ++ create.bpp = bpp; ++ create.flags = flags; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_CREATE_DUMB, ++ &create, sizeof(create), ++ &create, sizeof(create), ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ *handle = create.handle; ++ *pitch = create.pitch; ++ *size = create.size; ++ return 0; ++ #else + int ret; + struct drm_mode_create_dumb create = { + .width = width, +@@ -1855,21 +2380,52 @@ + *pitch = create.pitch; + *size = create.size; + return 0; ++ #endif + } + + drm_public int + drmModeDestroyDumbBuffer(int fd, uint32_t handle) + { ++ #if defined(__redox__) ++ struct redox_drm_destroy_dumb_wire destroy; ++ ++ memclear(destroy); ++ destroy.handle = handle; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_DESTROY_DUMB, ++ &destroy, sizeof(destroy), ++ NULL, 0, ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ return 0; ++ #else + struct drm_mode_destroy_dumb destroy = { + .handle = handle, + }; + + return DRM_IOCTL(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); ++ #endif + } + + drm_public int + drmModeMapDumbBuffer(int fd, uint32_t handle, uint64_t *offset) + { ++ #if defined(__redox__) ++ struct redox_drm_map_dumb_wire map; ++ ++ memclear(map); ++ map.handle = handle; ++ if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_MAP_DUMB, ++ &map, sizeof(map), ++ &map, sizeof(map), ++ NULL) != 0) { ++ return -errno; ++ } ++ ++ *offset = map.offset; ++ return 0; ++ #else + int ret; + struct drm_mode_map_dumb map = { + .handle = handle, +@@ -1881,4 +2437,5 @@ + + *offset = map.offset; + return 0; ++ #endif + } +diff -ruN source-old/xf86drm_redox.h source/xf86drm_redox.h +--- source-old/xf86drm_redox.h ++++ source/xf86drm_redox.h +@@ -0,0 +1,162 @@ ++#ifndef _XF86DRM_REDOX_H_ ++#define _XF86DRM_REDOX_H_ ++ ++#if defined(__redox__) ++ ++#include ++#include ++ ++#define REDOX_DRM_IOCTL_BASE 0x00A0UL ++ ++#define REDOX_LINUX_IOCTL_NR(request) ((unsigned long)((request) & 0xffUL)) ++#define REDOX_LINUX_IOCTL_SIZE(request) (((unsigned long)(request) >> 16) & 0x3fffUL) ++ ++#define REDOX_DRM_IOCTL_MODE_GETRESOURCES (REDOX_DRM_IOCTL_BASE + 0) ++#define REDOX_DRM_IOCTL_MODE_SETCRTC (REDOX_DRM_IOCTL_BASE + 2) ++#define REDOX_DRM_IOCTL_MODE_GETCRTC (REDOX_DRM_IOCTL_BASE + 3) ++#define REDOX_DRM_IOCTL_MODE_GETENCODER (REDOX_DRM_IOCTL_BASE + 6) ++#define REDOX_DRM_IOCTL_MODE_GETCONNECTOR (REDOX_DRM_IOCTL_BASE + 7) ++#define REDOX_DRM_IOCTL_MODE_PAGE_FLIP (REDOX_DRM_IOCTL_BASE + 16) ++#define REDOX_DRM_IOCTL_MODE_CREATE_DUMB (REDOX_DRM_IOCTL_BASE + 18) ++#define REDOX_DRM_IOCTL_MODE_MAP_DUMB (REDOX_DRM_IOCTL_BASE + 19) ++#define REDOX_DRM_IOCTL_MODE_DESTROY_DUMB (REDOX_DRM_IOCTL_BASE + 20) ++#define REDOX_DRM_IOCTL_MODE_ADDFB (REDOX_DRM_IOCTL_BASE + 21) ++#define REDOX_DRM_IOCTL_MODE_RMFB (REDOX_DRM_IOCTL_BASE + 22) ++#define REDOX_DRM_IOCTL_GET_CAP (REDOX_DRM_IOCTL_BASE + 23) ++#define REDOX_DRM_IOCTL_SET_CLIENT_CAP (REDOX_DRM_IOCTL_BASE + 24) ++#define REDOX_DRM_IOCTL_VERSION (REDOX_DRM_IOCTL_BASE + 25) ++#define REDOX_DRM_IOCTL_GEM_CLOSE (REDOX_DRM_IOCTL_BASE + 27) ++#define REDOX_DRM_IOCTL_PRIME_HANDLE_TO_FD (REDOX_DRM_IOCTL_BASE + 29) ++#define REDOX_DRM_IOCTL_PRIME_FD_TO_HANDLE (REDOX_DRM_IOCTL_BASE + 30) ++ ++struct redox_drm_resources_wire { ++ uint32_t connector_count; ++ uint32_t crtc_count; ++ uint32_t encoder_count; ++}; ++ ++struct redox_drm_connector_wire { ++ uint32_t connector_id; ++ uint32_t connection; ++ uint32_t connector_type; ++ uint32_t mm_width; ++ uint32_t mm_height; ++ uint32_t encoder_id; ++ uint32_t mode_count; ++}; ++ ++struct redox_drm_mode_wire { ++ uint32_t clock; ++ uint16_t hdisplay; ++ uint16_t hsync_start; ++ uint16_t hsync_end; ++ uint16_t htotal; ++ uint16_t hskew; ++ uint16_t vdisplay; ++ uint16_t vsync_start; ++ uint16_t vsync_end; ++ uint16_t vtotal; ++ uint16_t vscan; ++ uint32_t vrefresh; ++ uint32_t flags; ++ uint32_t type; ++}; ++ ++struct redox_drm_set_crtc_wire { ++ uint32_t crtc_id; ++ uint32_t fb_handle; ++ uint32_t connector_count; ++ uint32_t connectors[8]; ++ struct redox_drm_mode_wire mode; ++}; ++ ++struct redox_drm_page_flip_wire { ++ uint32_t crtc_id; ++ uint32_t fb_handle; ++ uint32_t flags; ++}; ++ ++struct redox_drm_create_dumb_wire { ++ uint32_t width; ++ uint32_t height; ++ uint32_t bpp; ++ uint32_t flags; ++ uint32_t pitch; ++ uint32_t reserved0; ++ uint64_t size; ++ uint32_t handle; ++ uint32_t reserved1; ++}; ++ ++struct redox_drm_map_dumb_wire { ++ uint32_t handle; ++ uint32_t pad; ++ uint64_t offset; ++}; ++ ++struct redox_drm_destroy_dumb_wire { ++ uint32_t handle; ++}; ++ ++struct redox_drm_add_fb_wire { ++ uint32_t width; ++ uint32_t height; ++ uint32_t pitch; ++ uint32_t bpp; ++ uint32_t depth; ++ uint32_t handle; ++ uint32_t fb_id; ++}; ++ ++struct redox_drm_rm_fb_wire { ++ uint32_t fb_id; ++}; ++ ++struct redox_drm_get_crtc_wire { ++ uint32_t crtc_id; ++ uint32_t fb_id; ++ uint32_t x; ++ uint32_t y; ++ uint32_t mode_valid; ++ struct redox_drm_mode_wire mode; ++}; ++ ++struct redox_drm_version_wire { ++ int32_t major; ++ int32_t minor; ++ int32_t patch; ++}; ++ ++struct redox_drm_prime_handle_to_fd_wire { ++ uint32_t handle; ++ uint32_t flags; ++}; ++ ++struct redox_drm_prime_handle_to_fd_response_wire { ++ int32_t fd; ++ uint32_t pad; ++}; ++ ++struct redox_drm_prime_fd_to_handle_wire { ++ int32_t fd; ++ uint32_t pad; ++}; ++ ++struct redox_drm_prime_fd_to_handle_response_wire { ++ uint32_t handle; ++ uint32_t pad; ++}; ++ ++unsigned long redox_translate_request(unsigned long linux_nr); ++int redox_drm_simple_ioctl(int fd, unsigned long request, void *arg); ++int redox_drm_exchange(int fd, unsigned long request_code, ++ const void *payload, size_t payload_size, ++ void *response, size_t response_capacity, ++ size_t *response_size); ++int redox_drm_exchange_alloc(int fd, unsigned long request_code, ++ const void *payload, size_t payload_size, ++ void **response, size_t *response_size); ++ ++#endif ++ ++#endif diff --git a/local/recipes/libs/libdrm/source/xf86drm.c b/local/recipes/libs/libdrm/source/xf86drm.c index 3a1058963e..0379aafd7e 100644 --- a/local/recipes/libs/libdrm/source/xf86drm.c +++ b/local/recipes/libs/libdrm/source/xf86drm.c @@ -88,6 +88,7 @@ #endif #include "xf86drm.h" +#include "xf86drm_redox.h" #include "libdrm_macros.h" #include "drm_fourcc.h" @@ -313,7 +314,6 @@ drmGetFormatModifierNameFromArm(uint64_t modifier) uint64_t type = (modifier >> 52) & 0xf; FILE *fp; - size_t size = 0; char *modifier_name = NULL; bool result = false; @@ -321,6 +321,7 @@ drmGetFormatModifierNameFromArm(uint64_t modifier) fprintf(stderr, "open_memstream not available on Redox\n"); return NULL; #else + size_t size = 0; fp = open_memstream(&modifier_name, &size); if (!fp) return NULL; @@ -425,12 +426,12 @@ drmGetFormatModifierNameFromAmd(uint64_t modifier) uint64_t tile_version = AMD_FMT_MOD_GET(TILE_VERSION, modifier); FILE *fp; char *mod_amd = NULL; - size_t size = 0; #if defined(__redox__) fprintf(stderr, "open_memstream not available on Redox\n"); return NULL; #else + size_t size = 0; fp = open_memstream(&mod_amd, &size); if (!fp) return NULL; @@ -703,18 +704,1048 @@ drm_public void drmFree(void *pt) free(pt); } +#if defined(__redox__) +static int redox_drm_write_all(int fd, const void *buf, size_t len) +{ + const uint8_t *bytes = buf; + size_t offset = 0; + + while (offset < len) { + ssize_t written = write(fd, bytes + offset, len - offset); + if (written < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (written == 0) { + errno = EIO; + return -1; + } + offset += (size_t)written; + } + + return 0; +} + +static int redox_drm_read_all(int fd, void *buf, size_t len) +{ + uint8_t *bytes = buf; + size_t offset = 0; + + while (offset < len) { + ssize_t read_count = read(fd, bytes + offset, len - offset); + if (read_count < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (read_count == 0) { + errno = EIO; + return -1; + } + offset += (size_t)read_count; + } + + return 0; +} + +static int redox_drm_send_request(int fd, unsigned long request_code, + const void *payload, size_t payload_size) +{ + const size_t request_word_size = sizeof(uintptr_t); + const size_t total_size = request_word_size + payload_size; + uint8_t stack_request[sizeof(uintptr_t)]; + uint8_t *request_bytes = stack_request; + uintptr_t request_word = (uintptr_t)request_code; + size_t i; + + if (total_size > sizeof(stack_request)) { + request_bytes = drmMalloc(total_size); + if (!request_bytes) { + errno = ENOMEM; + return -1; + } + } + + for (i = 0; i < request_word_size; ++i) + request_bytes[i] = (uint8_t)((request_word >> (i * 8)) & 0xffU); + + if (payload_size != 0) + memcpy(request_bytes + request_word_size, payload, payload_size); + + if (redox_drm_write_all(fd, request_bytes, total_size) != 0) { + if (request_bytes != stack_request) + drmFree(request_bytes); + return -1; + } + + if (request_bytes != stack_request) + drmFree(request_bytes); + + return 0; +} + +static int redox_drm_get_response_size(int fd, size_t *response_size) +{ + stat_t st; + + if (fstat(fd, &st) != 0) + return -1; + + *response_size = (size_t)st.st_size; + return 0; +} + +unsigned long redox_translate_request(unsigned long linux_nr) +{ + switch (linux_nr) { + case 0x0C: + return REDOX_DRM_IOCTL_GET_CAP; + case 0x0D: + return REDOX_DRM_IOCTL_SET_CLIENT_CAP; + case 0x09: + return REDOX_DRM_IOCTL_GEM_CLOSE; + case 0x0B: + return REDOX_DRM_IOCTL_GEM_OPEN; + case 0x0A: + return REDOX_DRM_IOCTL_GEM_FLINK; + case 0x2D: + return REDOX_DRM_IOCTL_PRIME_HANDLE_TO_FD; + case 0x2E: + return REDOX_DRM_IOCTL_PRIME_FD_TO_HANDLE; + case 0xA6: + return REDOX_DRM_IOCTL_MODE_GETENCODER; + case 0xB3: + return REDOX_DRM_IOCTL_MODE_MAP_DUMB; + case 0xB4: + return REDOX_DRM_IOCTL_MODE_DESTROY_DUMB; + case 0xAF: + return REDOX_DRM_IOCTL_MODE_RMFB; + case 0x02: + return REDOX_DRM_IOCTL_GET_MAGIC; + case 0x11: + return REDOX_DRM_IOCTL_AUTH_MAGIC; + case 0x1e: + return REDOX_DRM_IOCTL_SET_MASTER; + case 0x1f: + return REDOX_DRM_IOCTL_DROP_MASTER; + case 0xB9: + return REDOX_DRM_IOCTL_MODE_OBJ_GETPROPERTIES; + case 0xAA: + return REDOX_DRM_IOCTL_MODE_GETPROPERTY; + case 0xAC: + return REDOX_DRM_IOCTL_MODE_GETPROPBLOB; + case 0xC6: + return REDOX_DRM_IOCTL_MODE_CREATE_LEASE; + case 0xC7: + return REDOX_DRM_IOCTL_MODE_LIST_LESSEES; + case 0xBA: + return REDOX_DRM_IOCTL_MODE_OBJ_SETPROPERTY; + case 0xB5: + return REDOX_DRM_IOCTL_MODE_GETPLANERESOURCES; + case 0xB6: + return REDOX_DRM_IOCTL_MODE_GETPLANE; + case 0xB7: + return REDOX_DRM_IOCTL_MODE_SETPLANE; + case 0xB8: + return REDOX_DRM_IOCTL_MODE_ADDFB2; + case 0xA0: + return REDOX_DRM_IOCTL_MODE_GETRESOURCES; + case 0xA1: + return REDOX_DRM_IOCTL_MODE_GETCRTC; + case 0xA2: + return REDOX_DRM_IOCTL_MODE_SETCRTC; + case 0xA7: + return REDOX_DRM_IOCTL_MODE_GETCONNECTOR; + case 0xAE: + return REDOX_DRM_IOCTL_MODE_ADDFB; + case 0xB0: + return REDOX_DRM_IOCTL_MODE_PAGE_FLIP; + case 0xB2: + return REDOX_DRM_IOCTL_MODE_CREATE_DUMB; + case 0x00: + return REDOX_DRM_IOCTL_VERSION; + default: + return 0; + } +} + +int redox_drm_exchange(int fd, unsigned long request_code, + const void *payload, size_t payload_size, + void *response, size_t response_capacity, + size_t *response_size) +{ + uint8_t stack_buffer[64]; + uint8_t *response_buffer = stack_buffer; + size_t actual_response_size = 0; + + if (redox_drm_send_request(fd, request_code, payload, payload_size) != 0) + return -1; + + if (redox_drm_get_response_size(fd, &actual_response_size) != 0) + return -1; + + if (response_size) + *response_size = actual_response_size; + + if (actual_response_size == 0) + return 0; + + if (response && response_capacity >= actual_response_size) { + return redox_drm_read_all(fd, response, actual_response_size); + } + + if (actual_response_size > sizeof(stack_buffer)) { + response_buffer = drmMalloc(actual_response_size); + if (!response_buffer) { + errno = ENOMEM; + return -1; + } + } + + if (redox_drm_read_all(fd, response_buffer, actual_response_size) != 0) { + if (response_buffer != stack_buffer) + drmFree(response_buffer); + return -1; + } + + if (response_buffer != stack_buffer) + drmFree(response_buffer); + + if (response && response_capacity < actual_response_size) { + errno = EMSGSIZE; + return -1; + } + + return 0; +} + +int redox_drm_exchange_alloc(int fd, unsigned long request_code, + const void *payload, size_t payload_size, + void **response, size_t *response_size) +{ + size_t actual_response_size = 0; + void *allocated_response = NULL; + + if (!response) { + errno = EINVAL; + return -1; + } + + if (redox_drm_send_request(fd, request_code, payload, payload_size) != 0) + return -1; + + if (redox_drm_get_response_size(fd, &actual_response_size) != 0) + return -1; + + if (actual_response_size != 0) { + allocated_response = drmMalloc(actual_response_size); + if (!allocated_response) { + errno = ENOMEM; + return -1; + } + + if (redox_drm_read_all(fd, allocated_response, actual_response_size) != 0) { + drmFree(allocated_response); + return -1; + } + } + + *response = allocated_response; + if (response_size) + *response_size = actual_response_size; + return 0; +} + +static void redox_drm_modeinfo_from_wire(struct drm_mode_modeinfo *mode, + const struct redox_drm_mode_wire *wire, + const char *name) +{ + size_t name_len = 0; + + memclear(*mode); + mode->clock = wire->clock; + mode->hdisplay = wire->hdisplay; + mode->hsync_start = wire->hsync_start; + mode->hsync_end = wire->hsync_end; + mode->htotal = wire->htotal; + mode->hskew = wire->hskew; + mode->vdisplay = wire->vdisplay; + mode->vsync_start = wire->vsync_start; + mode->vsync_end = wire->vsync_end; + mode->vtotal = wire->vtotal; + mode->vscan = wire->vscan; + mode->vrefresh = wire->vrefresh; + mode->flags = wire->flags; + mode->type = wire->type; + + if (!name) + return; + + name_len = strlen(name); + if (name_len >= DRM_DISPLAY_MODE_LEN) + name_len = DRM_DISPLAY_MODE_LEN - 1; + memcpy(mode->name, name, name_len); +} + +static int redox_drm_fetch_resources_wire(int fd, + struct redox_drm_resources_wire *header, + uint32_t **connector_ids) +{ + void *response = NULL; + size_t response_size = 0; + size_t expected_size; + + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETRESOURCES, + NULL, 0, + &response, &response_size) != 0) { + return -1; + } + + if (response_size < sizeof(*header)) { + drmFree(response); + errno = EPROTO; + return -1; + } + + memcpy(header, response, sizeof(*header)); + expected_size = sizeof(*header) + + ((size_t)header->connector_count * sizeof(uint32_t)); + if (response_size < expected_size) { + drmFree(response); + errno = EPROTO; + return -1; + } + + *connector_ids = NULL; + if (header->connector_count != 0) { + *connector_ids = drmMalloc(header->connector_count * sizeof(uint32_t)); + if (!*connector_ids) { + drmFree(response); + errno = ENOMEM; + return -1; + } + memcpy(*connector_ids, (const uint8_t *)response + sizeof(*header), + header->connector_count * sizeof(uint32_t)); + } + + drmFree(response); + return 0; +} + +static int redox_drm_fetch_connector_blob(int fd, uint32_t connector_id, + void **response, + size_t *response_size) +{ + return redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETCONNECTOR, + &connector_id, sizeof(connector_id), + response, response_size); +} + +static int redox_drm_parse_connector_blob(const void *blob, size_t blob_size, + struct redox_drm_connector_wire *header, + struct drm_mode_modeinfo *modes, + uint32_t modes_capacity, + uint32_t *min_width, + uint32_t *max_width, + uint32_t *min_height, + uint32_t *max_height) +{ + const uint8_t *bytes = blob; + size_t offset = sizeof(*header); + uint32_t i; + + if (blob_size < sizeof(*header)) { + errno = EPROTO; + return -1; + } + + memcpy(header, bytes, sizeof(*header)); + for (i = 0; i < header->mode_count; ++i) { + struct redox_drm_mode_wire mode_wire; + const char *name; + size_t name_len; + uint32_t width; + uint32_t height; + + if (blob_size - offset < sizeof(mode_wire)) { + errno = EPROTO; + return -1; + } + + memcpy(&mode_wire, bytes + offset, sizeof(mode_wire)); + offset += sizeof(mode_wire); + name = (const char *)bytes + offset; + name_len = strnlen(name, blob_size - offset); + if (offset + name_len >= blob_size) { + errno = EPROTO; + return -1; + } + + if (modes && i < modes_capacity) + redox_drm_modeinfo_from_wire(&modes[i], &mode_wire, name); + + width = mode_wire.hdisplay; + height = mode_wire.vdisplay; + if (min_width && (*min_width == 0 || width < *min_width)) + *min_width = width; + if (max_width && width > *max_width) + *max_width = width; + if (min_height && (*min_height == 0 || height < *min_height)) + *min_height = height; + if (max_height && height > *max_height) + *max_height = height; + + offset += name_len + 1; + } + + return 0; +} + +struct redox_drm_get_plane_resources_wire { + uint64_t plane_id_ptr; + uint32_t count_planes; +}; + +struct redox_drm_get_plane_wire { + uint32_t plane_id; + uint32_t crtc_id; + uint32_t fb_id; + uint32_t possible_crtcs; + uint32_t gamma_size; + uint32_t count_format_types; + uint64_t format_type_ptr; +}; + +struct redox_drm_mode_obj_get_properties_wire { + uint64_t props_ptr; + uint64_t prop_values_ptr; + uint32_t count_props; + uint32_t obj_id; + uint32_t obj_type; +}; + +static uint32_t redox_drm_connector_type_id(int fd, uint32_t connector_id, + uint32_t connector_type) +{ + struct redox_drm_resources_wire resources; + uint32_t *connector_ids = NULL; + uint32_t ordinal = 0; + uint32_t i; + + if (redox_drm_fetch_resources_wire(fd, &resources, &connector_ids) != 0) + return 0; + + for (i = 0; i < resources.connector_count; ++i) { + struct redox_drm_connector_wire header; + void *response = NULL; + size_t response_size = 0; + int ret; + + ret = redox_drm_fetch_connector_blob(fd, connector_ids[i], + &response, &response_size); + if (ret != 0) + break; + + ret = redox_drm_parse_connector_blob(response, response_size, + &header, NULL, 0, + NULL, NULL, NULL, NULL); + drmFree(response); + if (ret != 0) + break; + + if (header.connector_type == connector_type) + ++ordinal; + if (header.connector_id == connector_id) + break; + } + + drmFree(connector_ids); + return ordinal; +} + +static int redox_drm_ioctl_getplaneresources(int fd, + struct drm_mode_get_plane_res *resources) +{ + struct redox_drm_get_plane_resources_wire response; + void *response_blob = NULL; + size_t response_size = 0; + size_t expected_size; + uint32_t input_plane_count; + uint32_t copy_count; + + if (!resources) { + errno = EINVAL; + return -1; + } + + input_plane_count = resources->count_planes; + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETPLANERESOURCES, + resources, sizeof(*resources), + &response_blob, &response_size) != 0) { + return -1; + } + + if (response_size < sizeof(response)) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + memcpy(&response, response_blob, sizeof(response)); + resources->count_planes = response.count_planes; + copy_count = input_plane_count < response.count_planes ? + input_plane_count : response.count_planes; + if (copy_count != 0) { + expected_size = sizeof(response) + + ((size_t)copy_count * sizeof(uint32_t)); + if (response_size < expected_size) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + if (resources->plane_id_ptr) { + memcpy((void *)(uintptr_t)resources->plane_id_ptr, + (const uint8_t *)response_blob + sizeof(response), + copy_count * sizeof(uint32_t)); + } + } + + drmFree(response_blob); + return 0; +} + +static int redox_drm_ioctl_getplane(int fd, struct drm_mode_get_plane *plane) +{ + struct redox_drm_get_plane_wire response; + void *response_blob = NULL; + size_t response_size = 0; + size_t expected_size; + uint32_t input_format_count; + uint32_t copy_count; + + if (!plane) { + errno = EINVAL; + return -1; + } + + input_format_count = plane->count_format_types; + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETPLANE, + plane, sizeof(*plane), + &response_blob, &response_size) != 0) { + return -1; + } + + if (response_size < sizeof(response)) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + memcpy(&response, response_blob, sizeof(response)); + plane->plane_id = response.plane_id; + plane->crtc_id = response.crtc_id; + plane->fb_id = response.fb_id; + plane->possible_crtcs = response.possible_crtcs; + plane->gamma_size = response.gamma_size; + plane->count_format_types = response.count_format_types; + + copy_count = input_format_count < response.count_format_types ? + input_format_count : response.count_format_types; + if (copy_count != 0) { + expected_size = sizeof(response) + + ((size_t)copy_count * sizeof(uint32_t)); + if (response_size < expected_size) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + if (plane->format_type_ptr) { + memcpy((void *)(uintptr_t)plane->format_type_ptr, + (const uint8_t *)response_blob + sizeof(response), + copy_count * sizeof(uint32_t)); + } + } + + drmFree(response_blob); + return 0; +} + +static int redox_drm_ioctl_obj_getproperties( + int fd, + struct drm_mode_obj_get_properties *properties) +{ + struct redox_drm_mode_obj_get_properties_wire response; + void *response_blob = NULL; + size_t response_size = 0; + size_t expected_size; + uint32_t input_count; + uint32_t copy_count; + + if (!properties) { + errno = EINVAL; + return -1; + } + + input_count = properties->count_props; + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_OBJ_GETPROPERTIES, + properties, sizeof(*properties), + &response_blob, &response_size) != 0) { + return -1; + } + + if (response_size < sizeof(response)) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + memcpy(&response, response_blob, sizeof(response)); + properties->count_props = response.count_props; + properties->obj_id = response.obj_id; + properties->obj_type = response.obj_type; + + copy_count = input_count < response.count_props ? + input_count : response.count_props; + if (copy_count != 0) { + expected_size = sizeof(response) + + ((size_t)copy_count * sizeof(uint32_t)) + + ((size_t)copy_count * sizeof(uint64_t)); + if (response_size < expected_size) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + if (properties->props_ptr) { + memcpy((void *)(uintptr_t)properties->props_ptr, + (const uint8_t *)response_blob + sizeof(response), + copy_count * sizeof(uint32_t)); + } + + if (properties->prop_values_ptr) { + memcpy((void *)(uintptr_t)properties->prop_values_ptr, + (const uint8_t *)response_blob + sizeof(response) + + ((size_t)copy_count * sizeof(uint32_t)), + copy_count * sizeof(uint64_t)); + } + } + + drmFree(response_blob); + return 0; +} + +static int redox_drm_ioctl_getproperty( + int fd, + struct drm_mode_get_property *prop) +{ + struct drm_mode_get_property response_wire; + void *response_blob = NULL; + size_t response_size = 0; + size_t expected_size; + uint32_t input_count_values; + uint32_t input_count_enums; + uint32_t copy_values; + uint32_t copy_enums; + size_t values_data; + + if (!prop) { + errno = EINVAL; + return -1; + } + + input_count_values = prop->count_values; + input_count_enums = prop->count_enum_blobs; + + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETPROPERTY, + prop, sizeof(*prop), + &response_blob, &response_size) != 0) { + return -1; + } + + if (response_size < sizeof(response_wire)) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + memcpy(&response_wire, response_blob, sizeof(response_wire)); + prop->prop_id = response_wire.prop_id; + prop->flags = response_wire.flags; + memcpy(prop->name, response_wire.name, DRM_PROP_NAME_LEN); + prop->count_values = response_wire.count_values; + prop->count_enum_blobs = response_wire.count_enum_blobs; + + copy_values = input_count_values < response_wire.count_values ? + input_count_values : response_wire.count_values; + if (copy_values != 0 && prop->values_ptr) { + expected_size = sizeof(response_wire) + + ((size_t)copy_values * sizeof(uint64_t)); + if (response_size < expected_size) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + memcpy((void *)(uintptr_t)prop->values_ptr, + (const uint8_t *)response_blob + sizeof(response_wire), + copy_values * sizeof(uint64_t)); + } + + copy_enums = input_count_enums < response_wire.count_enum_blobs ? + input_count_enums : response_wire.count_enum_blobs; + if (copy_enums != 0 && prop->enum_blob_ptr) { + values_data = (size_t)response_wire.count_values * sizeof(uint64_t); + expected_size = sizeof(response_wire) + values_data + + ((size_t)copy_enums * sizeof(struct drm_mode_property_enum)); + if (response_size < expected_size) { + drmFree(response_blob); + errno = EPROTO; + return -1; + } + + memcpy((void *)(uintptr_t)prop->enum_blob_ptr, + (const uint8_t *)response_blob + sizeof(response_wire) + values_data, + copy_enums * sizeof(struct drm_mode_property_enum)); + } + + drmFree(response_blob); + return 0; +} + +static int redox_drm_ioctl_version(int fd, struct drm_version *version) +{ + struct redox_drm_version_wire response; + size_t driver_name_len; + size_t name_capacity; + + if (!version) { + errno = EINVAL; + return -1; + } + + name_capacity = version->name_len; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_VERSION, + NULL, 0, + &response, sizeof(response), + NULL) != 0) { + return -1; + } + + driver_name_len = strnlen(response.name, sizeof(response.name)); + version->version_major = response.major; + version->version_minor = response.minor; + version->version_patchlevel = response.patch; + version->name_len = driver_name_len; + version->date_len = 0; + version->desc_len = 0; + + if (version->name && name_capacity != 0) { + size_t copy_len = driver_name_len < name_capacity ? + driver_name_len : name_capacity; + memcpy(version->name, response.name, copy_len); + } + + return 0; +} + +static int redox_drm_ioctl_getresources(int fd, struct drm_mode_card_res *resources) +{ + struct redox_drm_resources_wire response; + uint32_t *connector_ids = NULL; + uint32_t input_crtc_count; + uint32_t input_connector_count; + uint32_t input_encoder_count; + uint32_t i; + int ret = -1; + + if (!resources) { + errno = EINVAL; + return -1; + } + + input_crtc_count = resources->count_crtcs; + input_connector_count = resources->count_connectors; + input_encoder_count = resources->count_encoders; + + if (redox_drm_fetch_resources_wire(fd, &response, &connector_ids) != 0) + return -1; + + resources->count_fbs = 0; + resources->count_crtcs = response.crtc_count; + resources->count_connectors = response.connector_count; + resources->count_encoders = response.encoder_count; + resources->min_width = 0; + resources->max_width = 0; + resources->min_height = 0; + resources->max_height = 0; + + if (resources->connector_id_ptr && + input_connector_count >= response.connector_count) { + memcpy((void *)(uintptr_t)resources->connector_id_ptr, connector_ids, + response.connector_count * sizeof(uint32_t)); + } + + if (resources->crtc_id_ptr && input_crtc_count >= response.crtc_count) { + uint32_t *crtcs = (uint32_t *)(uintptr_t)resources->crtc_id_ptr; + for (i = 0; i < response.crtc_count; ++i) + crtcs[i] = i + 1; + } + + for (i = 0; i < response.connector_count; ++i) { + struct redox_drm_connector_wire header; + void *connector_blob = NULL; + size_t connector_blob_size = 0; + + if (redox_drm_fetch_connector_blob(fd, connector_ids[i], + &connector_blob, + &connector_blob_size) != 0) { + goto out; + } + + if (redox_drm_parse_connector_blob(connector_blob, connector_blob_size, + &header, NULL, 0, + &resources->min_width, + &resources->max_width, + &resources->min_height, + &resources->max_height) != 0) { + drmFree(connector_blob); + goto out; + } + + if (resources->encoder_id_ptr && + input_encoder_count >= response.encoder_count && + i < response.encoder_count) { + ((uint32_t *)(uintptr_t)resources->encoder_id_ptr)[i] = + header.encoder_id; + } + + drmFree(connector_blob); + } + + if (resources->encoder_id_ptr && input_encoder_count >= response.encoder_count) { + uint32_t *encoders = (uint32_t *)(uintptr_t)resources->encoder_id_ptr; + for (i = response.connector_count; i < response.encoder_count; ++i) + encoders[i] = 0; + } + + ret = 0; + +out: + drmFree(connector_ids); + return ret; +} + +static int redox_drm_ioctl_getcrtc(int fd, struct drm_mode_crtc *crtc) +{ + struct redox_drm_get_crtc_wire request; + struct redox_drm_get_crtc_wire response; + + if (!crtc) { + errno = EINVAL; + return -1; + } + + memclear(request); + request.crtc_id = crtc->crtc_id; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_GETCRTC, + &request, sizeof(request), + &response, sizeof(response), + NULL) != 0) { + return -1; + } + + crtc->count_connectors = 0; + crtc->crtc_id = response.crtc_id; + crtc->fb_id = response.fb_id; + crtc->x = response.x; + crtc->y = response.y; + crtc->gamma_size = 0; + crtc->mode_valid = response.mode_valid; + if (crtc->mode_valid) { + char mode_name[DRM_DISPLAY_MODE_LEN]; + + snprintf(mode_name, sizeof(mode_name), "%ux%u@%u", + response.mode.hdisplay, + response.mode.vdisplay, + response.mode.vrefresh); + redox_drm_modeinfo_from_wire(&crtc->mode, &response.mode, mode_name); + } else { + memclear(crtc->mode); + } + + return 0; +} + +static int redox_drm_ioctl_getconnector(int fd, + struct drm_mode_get_connector *connector) +{ + struct redox_drm_connector_wire header; + void *response = NULL; + size_t response_size = 0; + uint32_t input_mode_count; + uint32_t input_encoder_count; + + if (!connector) { + errno = EINVAL; + return -1; + } + + input_mode_count = connector->count_modes; + input_encoder_count = connector->count_encoders; + if (redox_drm_fetch_connector_blob(fd, connector->connector_id, + &response, &response_size) != 0) { + return -1; + } + + if (redox_drm_parse_connector_blob(response, response_size, + &header, + NULL, 0, + NULL, NULL, NULL, NULL) != 0) { + drmFree(response); + return -1; + } + + if (connector->modes_ptr && input_mode_count >= header.mode_count && + redox_drm_parse_connector_blob(response, response_size, + &header, + (struct drm_mode_modeinfo *)(uintptr_t)connector->modes_ptr, + header.mode_count, + NULL, NULL, NULL, NULL) != 0) { + drmFree(response); + return -1; + } + + connector->count_modes = header.mode_count; + connector->count_props = 0; + connector->count_encoders = header.encoder_id ? 1U : 0U; + connector->encoder_id = header.encoder_id; + connector->connector_id = header.connector_id; + connector->connector_type = header.connector_type; + connector->connector_type_id = redox_drm_connector_type_id(fd, + header.connector_id, + header.connector_type); + connector->connection = header.connection; + connector->mm_width = header.mm_width; + connector->mm_height = header.mm_height; + connector->subpixel = 0; + + if (connector->count_encoders != 0 && connector->encoders_ptr && + input_encoder_count >= connector->count_encoders) { + ((uint32_t *)(uintptr_t)connector->encoders_ptr)[0] = header.encoder_id; + } + + drmFree(response); + return 0; +} + +static size_t redox_drm_expected_response_size(unsigned long linux_nr, size_t arg_size) +{ + switch (linux_nr) { + case 0x00: + return sizeof(struct redox_drm_version_wire); + case 0x02: + case 0x0C: + case 0x11: + case 0xA6: + case 0xA9: + case 0xAA: + case 0xAC: + case 0xB8: + case 0xB9: + case 0xB3: + case 0xC6: + case 0xC7: + case 0xBA: + return arg_size; + case 0x1e: + case 0x1f: + return 0; + case 0xA0: + return sizeof(struct redox_drm_resources_wire); + case 0xA1: + return sizeof(struct redox_drm_get_crtc_wire); + case 0xA7: + return sizeof(struct redox_drm_connector_wire); + case 0x60: + return sizeof(struct redox_drm_pci_info_wire); + default: + return 0; + } +} + + int redox_drm_simple_ioctl(int fd, unsigned long request, void *arg) + { + const unsigned long linux_nr = REDOX_LINUX_IOCTL_NR(request); + const unsigned long request_code = redox_translate_request(linux_nr); + const size_t arg_size = REDOX_LINUX_IOCTL_SIZE(request); + const size_t capped_arg_size = arg_size > 256 ? 256 : arg_size; + const size_t expected_response_size = redox_drm_expected_response_size(linux_nr, arg_size); + + fprintf(stderr, "[LIBDRM] ioctl nr=0x%02lX request_code=0x%04lX arg_size=%zu\n", + linux_nr, request_code, arg_size); + fflush(stderr); + + if (request_code == 0) { + fprintf(stderr, "[LIBDRM] UNHANDLED ioctl nr=0x%02lX → ENOTTY\n", linux_nr); + fflush(stderr); + errno = ENOTTY; + return -1; + } + + if (arg) { + switch (linux_nr) { + case 0x00: + return redox_drm_ioctl_version(fd, arg); + case 0xA0: + return redox_drm_ioctl_getresources(fd, arg); + case 0xA1: + return redox_drm_ioctl_getcrtc(fd, arg); + case 0xA7: + return redox_drm_ioctl_getconnector(fd, arg); + case 0xB5: + return redox_drm_ioctl_getplaneresources(fd, arg); + case 0xB6: + return redox_drm_ioctl_getplane(fd, arg); + case 0xB9: + return redox_drm_ioctl_obj_getproperties(fd, arg); + case 0xAA: + return redox_drm_ioctl_getproperty(fd, arg); + default: + break; + } + } + + if (redox_drm_exchange(fd, request_code, + arg, arg ? capped_arg_size : 0, + expected_response_size ? arg : NULL, + expected_response_size, + NULL) != 0) { + return -1; + } + + return 0; + } +#endif + /** * Call ioctl, restarting if it is interrupted */ drm_public int drmIoctl(int fd, unsigned long request, void *arg) { + #if defined(__redox__) + return redox_drm_simple_ioctl(fd, request, arg); + #else int ret; do { ret = ioctl(fd, request, arg); } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); return ret; + #endif } static unsigned long drmGetKeyFromFd(int fd) @@ -1093,6 +2124,7 @@ static int drmGetMinorType(int major, int minor) return -1; } +#if !defined(__redox__) static const char *drmGetMinorName(int type) { switch (type) { @@ -1104,6 +2136,7 @@ static const char *drmGetMinorName(int type) return NULL; } } +#endif /** * Open the device by bus ID. @@ -1201,7 +2234,11 @@ static int drmOpenByName(const char *name, int type) for (i = base; i < base + DRM_MAX_MINOR; i++) { if ((fd = drmOpenMinor(i, 1, type)) >= 0) { if ((version = drmGetVersion(fd))) { + #if defined(__redox__) + if (!version->name || !strcmp(version->name, name)) { + #else if (!strcmp(version->name, name)) { + #endif drmFreeVersion(version); id = drmGetBusid(fd); drmMsg("drmGetBusid returned '%s'\n", id ? id : "NULL"); @@ -1354,6 +2391,7 @@ drm_public void drmFreeVersion(drmVersionPtr v) * Used by drmGetVersion() to free the memory pointed by \p %v as well as all * the non-null strings pointers in it. */ +#if !defined(__redox__) static void drmFreeKernelVersion(drm_version_t *v) { if (!v) @@ -1363,6 +2401,7 @@ static void drmFreeKernelVersion(drm_version_t *v) drmFree(v->desc); drmFree(v); } +#endif /** @@ -1375,6 +2414,7 @@ static void drmFreeKernelVersion(drm_version_t *v) * Used by drmGetVersion() to translate the information returned by the ioctl * interface in a private structure into the public structure counterpart. */ +#if !defined(__redox__) static void drmCopyVersion(drmVersionPtr d, const drm_version_t *s) { d->version_major = s->version_major; @@ -1387,6 +2427,7 @@ static void drmCopyVersion(drmVersionPtr d, const drm_version_t *s) d->desc_len = s->desc_len; d->desc = s->desc ? strdup(s->desc) : NULL; } +#endif /** @@ -1406,6 +2447,32 @@ static void drmCopyVersion(drmVersionPtr d, const drm_version_t *s) */ drm_public drmVersionPtr drmGetVersion(int fd) { + #if defined(__redox__) + drmVersionPtr retval = drmMalloc(sizeof(*retval)); + struct redox_drm_version_wire version; + + if (!retval) + return NULL; + + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_VERSION, + NULL, 0, + &version, sizeof(version), + NULL) != 0) { + drmFree(retval); + return NULL; + } + + memclear(*retval); + retval->version_major = version.major; + retval->version_minor = version.minor; + retval->version_patchlevel = version.patch; + if (version.name[0] != '\0') { + size_t name_len = strnlen(version.name, sizeof(version.name)); + retval->name_len = name_len; + retval->name = strndup(version.name, name_len); + } + return retval; + #else drmVersionPtr retval; drm_version_t *version = drmMalloc(sizeof(*version)); @@ -1436,6 +2503,7 @@ drm_public drmVersionPtr drmGetVersion(int fd) drmCopyVersion(retval, version); drmFreeKernelVersion(version); return retval; + #endif } @@ -3400,6 +4468,23 @@ drm_public int drmGetNodeTypeFromFd(int fd) drm_public int drmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags, int *prime_fd) { + #if defined(__redox__) + struct redox_drm_prime_handle_to_fd_wire request; + struct redox_drm_prime_handle_to_fd_response_wire response; + + memclear(request); + request.handle = handle; + request.flags = flags; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_PRIME_HANDLE_TO_FD, + &request, sizeof(request), + &response, sizeof(response), + NULL) != 0) { + return -errno; + } + + *prime_fd = response.fd; + return 0; + #else struct drm_prime_handle args; int ret; @@ -3413,10 +4498,27 @@ drm_public int drmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags, *prime_fd = args.fd; return 0; + #endif } drm_public int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle) { + #if defined(__redox__) + struct redox_drm_prime_fd_to_handle_wire request; + struct redox_drm_prime_fd_to_handle_response_wire response; + + memclear(request); + request.fd = prime_fd; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_PRIME_FD_TO_HANDLE, + &request, sizeof(request), + &response, sizeof(response), + NULL) != 0) { + return -errno; + } + + *handle = response.handle; + return 0; + #else struct drm_prime_handle args; int ret; @@ -3428,6 +4530,7 @@ drm_public int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle) *handle = args.handle; return 0; + #endif } drm_public int drmCloseBufferHandle(int fd, uint32_t handle) @@ -3544,7 +4647,7 @@ static char *drmGetMinorNameForFD(int fd, int type) n = drmGetNodePath(buf, sizeof(buf), type, min); if (n < 0) return NULL; - if (n == -1 || n >= sizeof(buf)) + if ((size_t)n >= sizeof(buf)) return NULL; return strdup(buf); @@ -4761,6 +5864,58 @@ drm_public int drmGetNodeTypeFromDevId(dev_t devid) */ drm_public int drmGetDevice2(int fd, uint32_t flags, drmDevicePtr *device) { +#if defined(__redox__) + struct stat sbuf; + drmDevicePtr dev; + char *addr; + + if (fd == -1 || device == NULL) + return -EINVAL; + + if (fstat(fd, &sbuf)) + return -errno; + + if (!S_ISCHR(sbuf.st_mode)) + return -EINVAL; + + const char *node = "/scheme/drm/card0"; + + dev = drmDeviceAlloc(DRM_NODE_PRIMARY, node, + sizeof(drmPciBusInfo), + sizeof(drmPciDeviceInfo), &addr); + if (!dev) + return -ENOMEM; + + dev->bustype = DRM_BUS_PCI; + + struct redox_drm_pci_info_wire pci_info; + size_t resp_size = 0; + int ret = redox_drm_exchange(fd, REDOX_DRM_IOCTL_GET_PCI_INFO, + NULL, 0, + &pci_info, sizeof(pci_info), &resp_size); + + if (ret == 0 && resp_size >= 20) { + dev->businfo.pci = (drmPciBusInfoPtr)addr; + dev->businfo.pci->domain = pci_info.domain; + dev->businfo.pci->bus = pci_info.bus; + dev->businfo.pci->dev = pci_info.dev; + dev->businfo.pci->func = pci_info.func; + + addr += sizeof(drmPciBusInfo); + dev->deviceinfo.pci = (drmPciDeviceInfoPtr)addr; + dev->deviceinfo.pci->vendor_id = pci_info.vendor_id; + dev->deviceinfo.pci->device_id = pci_info.device_id; + dev->deviceinfo.pci->subvendor_id = pci_info.subvendor_id; + dev->deviceinfo.pci->subdevice_id = pci_info.subdevice_id; + dev->deviceinfo.pci->revision_id = pci_info.revision_id; + } else { + free(dev); + return -ENODEV; + } + + *device = dev; + return 0; +#else struct stat sbuf; if (fd == -1) @@ -4773,6 +5928,7 @@ drm_public int drmGetDevice2(int fd, uint32_t flags, drmDevicePtr *device) return -EINVAL; return drmGetDeviceFromDevId(sbuf.st_rdev, flags, device); +#endif } /** diff --git a/local/recipes/libs/libdrm/source/xf86drmMode.c b/local/recipes/libs/libdrm/source/xf86drmMode.c index a4873a0fa0..25abe10d17 100644 --- a/local/recipes/libs/libdrm/source/xf86drmMode.c +++ b/local/recipes/libs/libdrm/source/xf86drmMode.c @@ -50,6 +50,7 @@ #include "libdrm_macros.h" #include "xf86drmMode.h" #include "xf86drm.h" +#include "xf86drm_redox.h" #include #include #include @@ -68,6 +69,212 @@ static inline int DRM_IOCTL(int fd, unsigned long cmd, void *arg) return ret < 0 ? -errno : ret; } +static void* drmAllocCpy(char *array, int count, int entry_size); + +#if defined(__redox__) +static void redox_drm_mode_from_wire(drmModeModeInfoPtr mode, + const struct redox_drm_mode_wire *wire, + const char *name) +{ + memclear(*mode); + mode->clock = wire->clock; + mode->hdisplay = wire->hdisplay; + mode->hsync_start = wire->hsync_start; + mode->hsync_end = wire->hsync_end; + mode->htotal = wire->htotal; + mode->hskew = wire->hskew; + mode->vdisplay = wire->vdisplay; + mode->vsync_start = wire->vsync_start; + mode->vsync_end = wire->vsync_end; + mode->vtotal = wire->vtotal; + mode->vscan = wire->vscan; + mode->vrefresh = wire->vrefresh; + mode->flags = wire->flags; + mode->type = wire->type; + if (name) { + size_t name_len = strlen(name); + if (name_len >= DRM_DISPLAY_MODE_LEN) + name_len = DRM_DISPLAY_MODE_LEN - 1; + memcpy(mode->name, name, name_len); + } +} + +static void redox_drm_mode_to_wire(struct redox_drm_mode_wire *wire, + const drmModeModeInfo *mode) +{ + memclear(*wire); + wire->clock = mode->clock; + wire->hdisplay = mode->hdisplay; + wire->hsync_start = mode->hsync_start; + wire->hsync_end = mode->hsync_end; + wire->htotal = mode->htotal; + wire->hskew = mode->hskew; + wire->vdisplay = mode->vdisplay; + wire->vsync_start = mode->vsync_start; + wire->vsync_end = mode->vsync_end; + wire->vtotal = mode->vtotal; + wire->vscan = mode->vscan; + wire->vrefresh = mode->vrefresh; + wire->flags = mode->flags; + wire->type = mode->type; +} + +static int redox_drm_fetch_resources(int fd, + struct redox_drm_resources_wire *header, + uint32_t **connector_ids) +{ + void *response = NULL; + size_t response_size = 0; + size_t expected_size; + + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETRESOURCES, + NULL, 0, + &response, &response_size) != 0) { + return -1; + } + + if (response_size < sizeof(*header)) { + drmFree(response); + errno = EPROTO; + return -1; + } + + memcpy(header, response, sizeof(*header)); + expected_size = sizeof(*header) + + ((size_t)header->connector_count * sizeof(uint32_t)); + if (response_size < expected_size) { + drmFree(response); + errno = EPROTO; + return -1; + } + + *connector_ids = NULL; + if (header->connector_count != 0) { + *connector_ids = drmAllocCpy((char *)response + sizeof(*header), + header->connector_count, sizeof(uint32_t)); + if (!*connector_ids) { + drmFree(response); + errno = ENOMEM; + return -1; + } + } + + drmFree(response); + return 0; +} + +static int redox_drm_parse_connector_response(const void *blob, size_t blob_size, + struct redox_drm_connector_wire *header, + drmModeModeInfoPtr *modes_out) +{ + const uint8_t *bytes = blob; + drmModeModeInfoPtr modes = NULL; + size_t offset = sizeof(*header); + uint32_t i; + + if (blob_size < sizeof(*header)) { + errno = EPROTO; + return -1; + } + + memcpy(header, bytes, sizeof(*header)); + if (header->mode_count != 0) { + modes = drmMalloc(header->mode_count * sizeof(*modes)); + if (!modes) { + errno = ENOMEM; + return -1; + } + } + + for (i = 0; i < header->mode_count; ++i) { + struct redox_drm_mode_wire mode_wire; + const char *name; + size_t name_len; + + if (blob_size - offset < sizeof(mode_wire)) { + drmFree(modes); + errno = EPROTO; + return -1; + } + + memcpy(&mode_wire, bytes + offset, sizeof(mode_wire)); + offset += sizeof(mode_wire); + name = (const char *)bytes + offset; + name_len = strnlen(name, blob_size - offset); + if (offset + name_len >= blob_size) { + drmFree(modes); + errno = EPROTO; + return -1; + } + + redox_drm_mode_from_wire(&modes[i], &mode_wire, name); + offset += name_len + 1; + } + + *modes_out = modes; + return 0; +} + +static int redox_drm_fetch_connector(int fd, uint32_t connector_id, + struct redox_drm_connector_wire *header, + drmModeModeInfoPtr *modes_out) +{ + void *response = NULL; + size_t response_size = 0; + int ret; + + if (redox_drm_exchange_alloc(fd, REDOX_DRM_IOCTL_MODE_GETCONNECTOR, + &connector_id, sizeof(connector_id), + &response, &response_size) != 0) { + return -1; + } + + ret = redox_drm_parse_connector_response(response, response_size, + header, modes_out); + drmFree(response); + return ret; +} + +static uint32_t redox_drm_connector_type_id(int fd, uint32_t connector_id, + uint32_t connector_type) +{ + struct redox_drm_resources_wire resources; + uint32_t *connector_ids = NULL; + uint32_t ordinal = 0; + uint32_t i; + + if (redox_drm_fetch_resources(fd, &resources, &connector_ids) != 0) + return 0; + + for (i = 0; i < resources.connector_count; ++i) { + struct redox_drm_connector_wire header; + drmModeModeInfoPtr modes = NULL; + + if (redox_drm_fetch_connector(fd, connector_ids[i], &header, &modes) != 0) + break; + drmFree(modes); + + if (header.connector_type == connector_type) + ++ordinal; + if (header.connector_id == connector_id) + break; + } + + drmFree(connector_ids); + return ordinal; +} + +static int redox_drm_page_flip_unsupported(uint32_t flags) +{ + if (flags & (DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_TARGET)) { + errno = EOPNOTSUPP; + return -1; + } + + return 0; +} +#endif + /* * Util functions */ @@ -153,16 +360,104 @@ drm_public void drmModeFreeEncoder(drmModeEncoderPtr ptr) drm_public int drmIsKMS(int fd) { + #if defined(__redox__) + struct redox_drm_resources_wire resources; + uint32_t *connector_ids = NULL; + int is_kms; + + if (redox_drm_fetch_resources(fd, &resources, &connector_ids) != 0) + return 0; + + is_kms = resources.crtc_count > 0 && + resources.connector_count > 0 && + resources.encoder_count > 0; + drmFree(connector_ids); + return is_kms; + #else struct drm_mode_card_res res = {0}; if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) != 0) return 0; return res.count_crtcs > 0 && res.count_connectors > 0 && res.count_encoders > 0; + #endif } drm_public drmModeResPtr drmModeGetResources(int fd) { + #if defined(__redox__) + struct redox_drm_resources_wire resources; + uint32_t *connector_ids = NULL; + drmModeResPtr r = NULL; + uint32_t i; + + if (redox_drm_fetch_resources(fd, &resources, &connector_ids) != 0) + return NULL; + + r = drmMalloc(sizeof(*r)); + if (!r) { + drmFree(connector_ids); + return NULL; + } + memclear(*r); + + r->count_connectors = resources.connector_count; + r->count_crtcs = resources.crtc_count; + r->count_encoders = resources.encoder_count; + r->connectors = connector_ids; + + if (resources.crtc_count != 0) { + r->crtcs = drmMalloc(resources.crtc_count * sizeof(*r->crtcs)); + if (!r->crtcs) + goto err_allocs; + for (i = 0; i < resources.crtc_count; ++i) + r->crtcs[i] = i + 1; + } + + if (resources.encoder_count != 0) { + r->encoders = drmMalloc(resources.encoder_count * sizeof(*r->encoders)); + if (!r->encoders) + goto err_allocs; + memset(r->encoders, 0, resources.encoder_count * sizeof(*r->encoders)); + } + + for (i = 0; i < resources.connector_count; ++i) { + struct redox_drm_connector_wire connector; + drmModeModeInfoPtr modes = NULL; + uint32_t j; + + if (redox_drm_fetch_connector(fd, connector_ids[i], &connector, &modes) != 0) + goto err_allocs; + + if (i < resources.encoder_count) + r->encoders[i] = connector.encoder_id; + + for (j = 0; j < connector.mode_count; ++j) { + uint32_t width = modes[j].hdisplay; + uint32_t height = modes[j].vdisplay; + if (r->min_width == 0 || width < r->min_width) + r->min_width = width; + if (width > r->max_width) + r->max_width = width; + if (r->min_height == 0 || height < r->min_height) + r->min_height = height; + if (height > r->max_height) + r->max_height = height; + } + drmFree(modes); + } + + if (resources.encoder_count > resources.connector_count) { + for (i = resources.connector_count; i < resources.encoder_count; ++i) + r->encoders[i] = 0; + } + + return r; + +err_allocs: + drmModeFreeResources(r); + return NULL; + #else struct drm_mode_card_res res, counts; drmModeResPtr r = 0; @@ -253,6 +548,7 @@ err_allocs: drmFree(U642VOID(res.encoder_id_ptr)); return r; + #endif } @@ -260,6 +556,26 @@ drm_public int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t dep uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *buf_id) { + #if defined(__redox__) + struct redox_drm_add_fb_wire wire; + + memclear(wire); + wire.width = width; + wire.height = height; + wire.pitch = pitch; + wire.bpp = bpp; + wire.depth = depth; + wire.handle = bo_handle; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_ADDFB, + &wire, sizeof(wire), + &wire, sizeof(wire), + NULL) != 0) { + return -errno; + } + + *buf_id = wire.fb_id; + return 0; + #else struct drm_mode_fb_cmd f; int ret; @@ -276,6 +592,7 @@ drm_public int drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t dep *buf_id = f.fb_id; return 0; + #endif } drm_public int drmModeAddFB2WithModifiers(int fd, uint32_t width, @@ -317,7 +634,22 @@ drm_public int drmModeAddFB2(int fd, uint32_t width, uint32_t height, drm_public int drmModeRmFB(int fd, uint32_t bufferId) { + #if defined(__redox__) + struct redox_drm_rm_fb_wire wire; + + memclear(wire); + wire.fb_id = bufferId; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_RMFB, + &wire, sizeof(wire), + NULL, 0, + NULL) != 0) { + return -errno; + } + + return 0; + #else return DRM_IOCTL(fd, DRM_IOCTL_MODE_RMFB, &bufferId); + #endif } drm_public int drmModeCloseFB(int fd, uint32_t buffer_id) @@ -374,6 +706,40 @@ drm_public int drmModeDirtyFB(int fd, uint32_t bufferId, drm_public drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId) { + #if defined(__redox__) + struct redox_drm_get_crtc_wire wire; + drmModeCrtcPtr r; + + memclear(wire); + wire.crtc_id = crtcId; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_GETCRTC, + &wire, sizeof(wire), + &wire, sizeof(wire), + NULL) != 0) { + return NULL; + } + + r = drmMalloc(sizeof(*r)); + if (!r) + return NULL; + + memclear(*r); + r->crtc_id = wire.crtc_id; + r->x = wire.x; + r->y = wire.y; + r->mode_valid = wire.mode_valid; + if (r->mode_valid) { + char mode_name[DRM_DISPLAY_MODE_LEN]; + snprintf(mode_name, sizeof(mode_name), "%ux%u@%u", + wire.mode.hdisplay, wire.mode.vdisplay, wire.mode.vrefresh); + redox_drm_mode_from_wire(&r->mode, &wire.mode, mode_name); + r->width = wire.mode.hdisplay; + r->height = wire.mode.vdisplay; + } + r->buffer_id = wire.fb_id; + r->gamma_size = 0; + return r; + #else struct drm_mode_crtc crtc; drmModeCrtcPtr r; @@ -402,12 +768,47 @@ drm_public drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtcId) r->buffer_id = crtc.fb_id; r->gamma_size = crtc.gamma_size; return r; + #endif } drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, uint32_t x, uint32_t y, uint32_t *connectors, int count, drmModeModeInfoPtr mode) { + #if defined(__redox__) + struct redox_drm_set_crtc_wire wire; + + if ((x != 0 || y != 0) && bufferId != 0) { + errno = EOPNOTSUPP; + return -errno; + } + if (count < 0 || count > 8) { + errno = EINVAL; + return -errno; + } + if (bufferId != 0 && !mode) { + errno = EINVAL; + return -errno; + } + + memclear(wire); + wire.crtc_id = crtcId; + wire.fb_handle = bufferId; + wire.connector_count = count; + if (count > 0) + memcpy(wire.connectors, connectors, count * sizeof(*connectors)); + if (mode) + redox_drm_mode_to_wire(&wire.mode, mode); + + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_SETCRTC, + &wire, sizeof(wire), + NULL, 0, + NULL) != 0) { + return -errno; + } + + return 0; + #else struct drm_mode_crtc crtc; memclear(crtc); @@ -423,6 +824,7 @@ drm_public int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId, } return DRM_IOCTL(fd, DRM_IOCTL_MODE_SETCRTC, &crtc); + #endif } /* @@ -480,6 +882,30 @@ drm_public int drmModeMoveCursor(int fd, uint32_t crtcId, int x, int y) */ drm_public drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) { + #if defined(__redox__) + struct drm_mode_get_encoder enc; + drmModeEncoderPtr r = NULL; + + memclear(enc); + enc.encoder_id = encoder_id; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_GETENCODER, + &enc, sizeof(enc), + &enc, sizeof(enc), + NULL) != 0) { + return NULL; + } + + r = drmMalloc(sizeof(*r)); + if (!r) + return NULL; + + r->encoder_id = enc.encoder_id; + r->crtc_id = enc.crtc_id; + r->encoder_type = enc.encoder_type; + r->possible_crtcs = enc.possible_crtcs; + r->possible_clones = enc.possible_clones; + return r; + #else struct drm_mode_get_encoder enc; drmModeEncoderPtr r = NULL; @@ -499,6 +925,7 @@ drm_public drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) r->possible_clones = enc.possible_clones; return r; + #endif } /* @@ -507,6 +934,50 @@ drm_public drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) static drmModeConnectorPtr _drmModeGetConnector(int fd, uint32_t connector_id, int probe) { + #if defined(__redox__) + struct redox_drm_connector_wire conn; + drmModeConnectorPtr r = NULL; + drmModeModeInfoPtr modes = NULL; + + (void)probe; + if (redox_drm_fetch_connector(fd, connector_id, &conn, &modes) != 0) + return NULL; + + r = drmMalloc(sizeof(*r)); + if (!r) + goto err_allocs; + memclear(*r); + + r->connector_id = conn.connector_id; + r->encoder_id = conn.encoder_id; + r->connection = conn.connection; + r->mmWidth = conn.mm_width; + r->mmHeight = conn.mm_height; + r->subpixel = DRM_MODE_SUBPIXEL_UNKNOWN; + r->count_modes = conn.mode_count; + r->modes = modes; + r->count_props = 0; + r->props = NULL; + r->prop_values = NULL; + r->count_encoders = conn.encoder_id ? 1 : 0; + if (r->count_encoders != 0) { + r->encoders = drmMalloc(sizeof(*r->encoders)); + if (!r->encoders) + goto err_allocs; + r->encoders[0] = conn.encoder_id; + } + r->connector_type = conn.connector_type; + r->connector_type_id = redox_drm_connector_type_id(fd, + conn.connector_id, + conn.connector_type); + return r; + +err_allocs: + drmFree(modes); + drmFree(r ? r->encoders : NULL); + drmFree(r); + return NULL; + #else struct drm_mode_get_connector conn, counts; drmModeConnectorPtr r = NULL; struct drm_mode_modeinfo stack_mode; @@ -608,6 +1079,7 @@ err_allocs: drmFree(U642VOID(conn.encoders_ptr)); return r; + #endif } drm_public drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id) @@ -1100,6 +1572,27 @@ drm_public int drmHandleEvent(int fd, drmEventContextPtr evctx) drm_public int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data) { + #if defined(__redox__) + struct redox_drm_page_flip_wire flip; + uint64_t sequence; + + (void)user_data; + if (redox_drm_page_flip_unsupported(flags) != 0) + return -errno; + + memclear(flip); + flip.fb_handle = fb_id; + flip.crtc_id = crtc_id; + flip.flags = flags; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_PAGE_FLIP, + &flip, sizeof(flip), + &sequence, sizeof(sequence), + NULL) != 0) { + return -errno; + } + + return 0; + #else struct drm_mode_crtc_page_flip flip; memclear(flip); @@ -1109,12 +1602,23 @@ drm_public int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, flip.flags = flags; return DRM_IOCTL(fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip); + #endif } drm_public int drmModePageFlipTarget(int fd, uint32_t crtc_id, uint32_t fb_id, uint32_t flags, void *user_data, uint32_t target_vblank) { + #if defined(__redox__) + (void)fd; + (void)crtc_id; + (void)fb_id; + (void)flags; + (void)user_data; + (void)target_vblank; + errno = EOPNOTSUPP; + return -errno; + #else struct drm_mode_crtc_page_flip_target flip_target; memclear(flip_target); @@ -1125,6 +1629,7 @@ drm_public int drmModePageFlipTarget(int fd, uint32_t crtc_id, uint32_t fb_id, flip_target.sequence = target_vblank; return DRM_IOCTL(fd, DRM_IOCTL_MODE_PAGE_FLIP, &flip_target); + #endif } drm_public int drmModeSetPlane(int fd, uint32_t plane_id, uint32_t crtc_id, @@ -1839,6 +2344,26 @@ drmModeCreateDumbBuffer(int fd, uint32_t width, uint32_t height, uint32_t bpp, uint32_t flags, uint32_t *handle, uint32_t *pitch, uint64_t *size) { + #if defined(__redox__) + struct redox_drm_create_dumb_wire create; + + memclear(create); + create.width = width; + create.height = height; + create.bpp = bpp; + create.flags = flags; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_CREATE_DUMB, + &create, sizeof(create), + &create, sizeof(create), + NULL) != 0) { + return -errno; + } + + *handle = create.handle; + *pitch = create.pitch; + *size = create.size; + return 0; + #else int ret; struct drm_mode_create_dumb create = { .width = width, @@ -1855,21 +2380,52 @@ drmModeCreateDumbBuffer(int fd, uint32_t width, uint32_t height, uint32_t bpp, *pitch = create.pitch; *size = create.size; return 0; + #endif } drm_public int drmModeDestroyDumbBuffer(int fd, uint32_t handle) { + #if defined(__redox__) + struct redox_drm_destroy_dumb_wire destroy; + + memclear(destroy); + destroy.handle = handle; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_DESTROY_DUMB, + &destroy, sizeof(destroy), + NULL, 0, + NULL) != 0) { + return -errno; + } + + return 0; + #else struct drm_mode_destroy_dumb destroy = { .handle = handle, }; return DRM_IOCTL(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); + #endif } drm_public int drmModeMapDumbBuffer(int fd, uint32_t handle, uint64_t *offset) { + #if defined(__redox__) + struct redox_drm_map_dumb_wire map; + + memclear(map); + map.handle = handle; + if (redox_drm_exchange(fd, REDOX_DRM_IOCTL_MODE_MAP_DUMB, + &map, sizeof(map), + &map, sizeof(map), + NULL) != 0) { + return -errno; + } + + *offset = map.offset; + return 0; + #else int ret; struct drm_mode_map_dumb map = { .handle = handle, @@ -1881,4 +2437,5 @@ drmModeMapDumbBuffer(int fd, uint32_t handle, uint64_t *offset) *offset = map.offset; return 0; + #endif } diff --git a/local/recipes/libs/libdrm/source/xf86drm_redox.h b/local/recipes/libs/libdrm/source/xf86drm_redox.h new file mode 100644 index 0000000000..3b6c2a6a25 --- /dev/null +++ b/local/recipes/libs/libdrm/source/xf86drm_redox.h @@ -0,0 +1,194 @@ +#ifndef _XF86DRM_REDOX_H_ +#define _XF86DRM_REDOX_H_ + +#if defined(__redox__) + +#include +#include + +#define REDOX_DRM_IOCTL_BASE 0x00A0UL + +#define REDOX_LINUX_IOCTL_NR(request) ((unsigned long)((request) & 0xffUL)) +#define REDOX_LINUX_IOCTL_SIZE(request) (((unsigned long)(request) >> 16) & 0x3fffUL) + +#define REDOX_DRM_IOCTL_MODE_GETRESOURCES (REDOX_DRM_IOCTL_BASE + 0) +#define REDOX_DRM_IOCTL_MODE_SETCRTC (REDOX_DRM_IOCTL_BASE + 2) +#define REDOX_DRM_IOCTL_MODE_GETCRTC (REDOX_DRM_IOCTL_BASE + 3) +#define REDOX_DRM_IOCTL_MODE_GETENCODER (REDOX_DRM_IOCTL_BASE + 6) +#define REDOX_DRM_IOCTL_MODE_GETCONNECTOR (REDOX_DRM_IOCTL_BASE + 7) +#define REDOX_DRM_IOCTL_MODE_PAGE_FLIP (REDOX_DRM_IOCTL_BASE + 16) +#define REDOX_DRM_IOCTL_MODE_CREATE_DUMB (REDOX_DRM_IOCTL_BASE + 18) +#define REDOX_DRM_IOCTL_MODE_MAP_DUMB (REDOX_DRM_IOCTL_BASE + 19) +#define REDOX_DRM_IOCTL_MODE_DESTROY_DUMB (REDOX_DRM_IOCTL_BASE + 20) +#define REDOX_DRM_IOCTL_MODE_ADDFB (REDOX_DRM_IOCTL_BASE + 21) +#define REDOX_DRM_IOCTL_MODE_RMFB (REDOX_DRM_IOCTL_BASE + 22) +#define REDOX_DRM_IOCTL_GET_CAP (REDOX_DRM_IOCTL_BASE + 23) +#define REDOX_DRM_IOCTL_SET_CLIENT_CAP (REDOX_DRM_IOCTL_BASE + 24) +#define REDOX_DRM_IOCTL_VERSION (REDOX_DRM_IOCTL_BASE + 25) +#define REDOX_DRM_IOCTL_GEM_CLOSE (REDOX_DRM_IOCTL_BASE + 27) +#define REDOX_DRM_IOCTL_GEM_FLINK (REDOX_DRM_IOCTL_BASE + 10) +#define REDOX_DRM_IOCTL_GEM_OPEN (REDOX_DRM_IOCTL_BASE + 11) +#define REDOX_DRM_IOCTL_PRIME_HANDLE_TO_FD (REDOX_DRM_IOCTL_BASE + 29) +#define REDOX_DRM_IOCTL_PRIME_FD_TO_HANDLE (REDOX_DRM_IOCTL_BASE + 30) +#define REDOX_DRM_IOCTL_GET_MAGIC (REDOX_DRM_IOCTL_BASE + 33) +#define REDOX_DRM_IOCTL_AUTH_MAGIC (REDOX_DRM_IOCTL_BASE + 34) +#define REDOX_DRM_IOCTL_SET_MASTER (REDOX_DRM_IOCTL_BASE + 35) +#define REDOX_DRM_IOCTL_DROP_MASTER (REDOX_DRM_IOCTL_BASE + 36) +#define REDOX_DRM_IOCTL_MODE_OBJ_GETPROPERTIES (REDOX_DRM_IOCTL_BASE + 0x50) +#define REDOX_DRM_IOCTL_MODE_GETPROPERTY (REDOX_DRM_IOCTL_BASE + 0x51) +#define REDOX_DRM_IOCTL_MODE_GETPROPBLOB (REDOX_DRM_IOCTL_BASE + 0x52) +#define REDOX_DRM_IOCTL_MODE_CREATE_LEASE (REDOX_DRM_IOCTL_BASE + 0x53) +#define REDOX_DRM_IOCTL_MODE_LIST_LESSEES (REDOX_DRM_IOCTL_BASE + 0x54) +#define REDOX_DRM_IOCTL_MODE_OBJ_SETPROPERTY (REDOX_DRM_IOCTL_BASE + 0x55) +#define REDOX_DRM_IOCTL_MODE_GETPLANERESOURCES (REDOX_DRM_IOCTL_BASE + 0x56) +#define REDOX_DRM_IOCTL_MODE_GETPLANE (REDOX_DRM_IOCTL_BASE + 0x57) +#define REDOX_DRM_IOCTL_MODE_SETPLANE (REDOX_DRM_IOCTL_BASE + 0x58) +#define REDOX_DRM_IOCTL_MODE_ADDFB2 (REDOX_DRM_IOCTL_BASE + 0x59) +#define REDOX_DRM_IOCTL_GET_PCI_INFO (REDOX_DRM_IOCTL_BASE + 0x60) + +struct redox_drm_pci_info_wire { + uint16_t vendor_id; + uint16_t device_id; + uint16_t subvendor_id; + uint16_t subdevice_id; + uint8_t revision_id; + uint8_t pad[3]; + uint16_t domain; + uint8_t bus; + uint8_t dev; + uint8_t func; + uint8_t pad2[3]; +}; + +struct redox_drm_resources_wire { + uint32_t connector_count; + uint32_t crtc_count; + uint32_t encoder_count; +}; + +struct redox_drm_connector_wire { + uint32_t connector_id; + uint32_t connection; + uint32_t connector_type; + uint32_t mm_width; + uint32_t mm_height; + uint32_t encoder_id; + uint32_t mode_count; +}; + +struct redox_drm_mode_wire { + uint32_t clock; + uint16_t hdisplay; + uint16_t hsync_start; + uint16_t hsync_end; + uint16_t htotal; + uint16_t hskew; + uint16_t vdisplay; + uint16_t vsync_start; + uint16_t vsync_end; + uint16_t vtotal; + uint16_t vscan; + uint32_t vrefresh; + uint32_t flags; + uint32_t type; +}; + +struct redox_drm_set_crtc_wire { + uint32_t crtc_id; + uint32_t fb_handle; + uint32_t connector_count; + uint32_t connectors[8]; + struct redox_drm_mode_wire mode; +}; + +struct redox_drm_page_flip_wire { + uint32_t crtc_id; + uint32_t fb_handle; + uint32_t flags; +}; + +struct redox_drm_create_dumb_wire { + uint32_t width; + uint32_t height; + uint32_t bpp; + uint32_t flags; + uint32_t pitch; + uint32_t reserved0; + uint64_t size; + uint32_t handle; + uint32_t reserved1; +}; + +struct redox_drm_map_dumb_wire { + uint32_t handle; + uint32_t pad; + uint64_t offset; +}; + +struct redox_drm_destroy_dumb_wire { + uint32_t handle; +}; + +struct redox_drm_add_fb_wire { + uint32_t width; + uint32_t height; + uint32_t pitch; + uint32_t bpp; + uint32_t depth; + uint32_t handle; + uint32_t fb_id; +}; + +struct redox_drm_rm_fb_wire { + uint32_t fb_id; +}; + +struct redox_drm_get_crtc_wire { + uint32_t crtc_id; + uint32_t fb_id; + uint32_t x; + uint32_t y; + uint32_t mode_valid; + struct redox_drm_mode_wire mode; +}; + +struct redox_drm_version_wire { + int32_t major; + int32_t minor; + int32_t patch; + char name[64]; +}; + +struct redox_drm_prime_handle_to_fd_wire { + uint32_t handle; + uint32_t flags; +}; + +struct redox_drm_prime_handle_to_fd_response_wire { + int32_t fd; + uint32_t pad; +}; + +struct redox_drm_prime_fd_to_handle_wire { + int32_t fd; + uint32_t pad; +}; + +struct redox_drm_prime_fd_to_handle_response_wire { + uint32_t handle; + uint32_t pad; +}; + +unsigned long redox_translate_request(unsigned long linux_nr); +int redox_drm_simple_ioctl(int fd, unsigned long request, void *arg); +int redox_drm_exchange(int fd, unsigned long request_code, + const void *payload, size_t payload_size, + void *response, size_t response_capacity, + size_t *response_size); +int redox_drm_exchange_alloc(int fd, unsigned long request_code, + const void *payload, size_t payload_size, + void **response, size_t *response_size); + +#endif + +#endif diff --git a/local/recipes/libs/libepoxy/recipe.toml b/local/recipes/libs/libepoxy/recipe.toml new file mode 100644 index 0000000000..dd46d597aa --- /dev/null +++ b/local/recipes/libs/libepoxy/recipe.toml @@ -0,0 +1,18 @@ +[source] +path = "source" + +[build] +template = "custom" +dependencies = [ + "mesa", +] +script = """ +DYNAMIC_INIT + +cookbook_meson \ + -Ddocs=false \ + -Dtests=false \ + -Dglx=no \ + -Dx11=false \ + -Degl=yes +""" diff --git a/local/recipes/libs/libxcvt/recipe.toml b/local/recipes/libs/libxcvt/recipe.toml new file mode 100644 index 0000000000..efdab45fd8 --- /dev/null +++ b/local/recipes/libs/libxcvt/recipe.toml @@ -0,0 +1,10 @@ +[source] +path = "source" + +[build] +template = "custom" +script = """ +DYNAMIC_INIT + +cookbook_meson +""" diff --git a/local/recipes/qt/qtbase/futex-redox-support.patch b/local/recipes/qt/qtbase/futex-redox-support.patch new file mode 120000 index 0000000000..af78630ec9 --- /dev/null +++ b/local/recipes/qt/qtbase/futex-redox-support.patch @@ -0,0 +1 @@ +../../../../local/patches/qtbase/futex-redox-support.patch \ No newline at end of file diff --git a/local/recipes/qt/qtbase/recipe.toml b/local/recipes/qt/qtbase/recipe.toml index cd10e48fc5..e28a8f93cd 100644 --- a/local/recipes/qt/qtbase/recipe.toml +++ b/local/recipes/qt/qtbase/recipe.toml @@ -4,9 +4,11 @@ # Redox platform detection and syscall adaptations in redox.patch [source] tar = "https://download.qt.io/official_releases/qt/6.8/6.8.2/submodules/qtbase-everywhere-src-6.8.2.tar.xz" +blake3 = "6e9a81b44a2f6a12ce36b77a990a1e18586afe2ab2b140113b4ec59c6ba5d3c6" +# TODO: qtwayland-empty-cursor-guards and qtwaylandscanner-null-guard-listeners +# were for Qt 6.8.2; changes applied directly to v6.11-patched source tree. +# Regenerate proper patches for 6.11.0 and restore to this list. patches = [ - "qtwaylandscanner-null-guard-listeners.patch", - "qtwayland-empty-cursor-guards.patch", ] [build] @@ -650,10 +652,10 @@ fi cmake "${COOKBOOK_SOURCE}" \ -GNinja \ -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ - -DCMAKE_SHARED_LINKER_FLAGS="-lc -lffi -lredbear-qt-strtold-compat" \ - -DCMAKE_EXE_LINKER_FLAGS="-lc -lffi -lredbear-qt-strtold-compat" \ - -DCMAKE_C_STANDARD_LIBRARIES="-lffi -lredbear-qt-strtold-compat" \ - -DCMAKE_CXX_STANDARD_LIBRARIES="-lffi -lredbear-qt-strtold-compat" \ + -DCMAKE_SHARED_LINKER_FLAGS="-lc -lffi -lgcc -lredbear-qt-strtold-compat" \ + -DCMAKE_EXE_LINKER_FLAGS="-lc -lffi -lgcc -lredbear-qt-strtold-compat" \ + -DCMAKE_C_STANDARD_LIBRARIES="-lffi -lgcc -lredbear-qt-strtold-compat" \ + -DCMAKE_CXX_STANDARD_LIBRARIES="-lffi -lgcc -lredbear-qt-strtold-compat" \ -DQT_HOST_PATH="${HOST_BUILD}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=Release \ diff --git a/local/recipes/qt/qtdeclarative/recipe.toml b/local/recipes/qt/qtdeclarative/recipe.toml index 19775e2afc..2106a285e5 100644 --- a/local/recipes/qt/qtdeclarative/recipe.toml +++ b/local/recipes/qt/qtdeclarative/recipe.toml @@ -275,12 +275,4 @@ redbear_qt_copy_stage_qt6_cmake_to_sysroot "${STAGE_USR}" "${SYSROOT}" redbear_qt_copy_optional_stage_dir_to_sysroot "${STAGE_USR}" "${SYSROOT}" metatypes redbear_qt_copy_optional_stage_dir_to_sysroot "${STAGE_USR}" "${SYSROOT}" plugins redbear_qt_copy_optional_stage_dir_to_sysroot "${STAGE_USR}" "${SYSROOT}" qml - -for stdlib in \ - "${COOKBOOK_STAGE}/usr/include/stdlib.h" \ - "${SYSROOT}/include/stdlib.h" \ - "${SYSROOT}/usr/include/stdlib.h"; do - [ -f "$stdlib" ] || continue - sed -i '/strtold[[:space:]]*(/d' "$stdlib" 2>/dev/null || true -done """ diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index 9791790210..37ddc2cef1 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -208,7 +208,7 @@ impl Driver for DriverConfig { }; if !std::path::Path::new(&actual_path).exists() { - return ProbeResult::Deferred { + return ProbeResult::Fatal { reason: format!("driver binary not found: {}", actual_path), }; } diff --git a/local/recipes/system/driver-manager/source/src/hotplug.rs b/local/recipes/system/driver-manager/source/src/hotplug.rs index 390719b11d..c5bfdc5b3a 100644 --- a/local/recipes/system/driver-manager/source/src/hotplug.rs +++ b/local/recipes/system/driver-manager/source/src/hotplug.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; @@ -10,6 +10,11 @@ use redox_driver_core::manager::ProbeEvent; use crate::scheme::{DriverManagerScheme, notify_bind, notify_unbind}; +/// Maximum times a single deferred device+driver pair is retried before +/// permanently abandoning it. Deferred means a dependency scheme (e.g. +/// scheme:firmware) is not yet ready, so the device may bind later. +const MAX_DEFERRED_RETRIES: u32 = 5; + pub fn run_hotplug_loop( manager: Arc>, scheme: Arc, @@ -20,6 +25,9 @@ pub fn run_hotplug_loop( poll_interval_ms ); + let mut deferred_retries: BTreeMap<(String, String), u32> = BTreeMap::new(); + let mut permanently_fatal: BTreeSet<(String, String)> = BTreeSet::new(); + loop { thread::sleep(Duration::from_millis(poll_interval_ms)); @@ -57,18 +65,41 @@ pub fn run_hotplug_loop( result, } => { track_pci_device(device, &mut seen_pci_devices); + let key = (device.path.clone(), driver_name.clone()); + + // Skip devices that were permanently fatal in a previous cycle. + // enumerate() re-probes all unbound devices each poll, but a Fatal + // result means the driver binary is genuinely absent (e.g. ided on + // a live ISO that doesn't ship it) — no amount of re-probing will + // change the outcome. + if permanently_fatal.contains(&key) { + continue; + } + match result { ProbeResult::Bound => { log::info!("hotplug: bound {} -> {}", device.path, driver_name); notify_bound_device(scheme.as_ref(), device, driver_name); } ProbeResult::Deferred { reason } => { - log::info!( - "hotplug: deferred {} -> {} ({})", - device.path, - driver_name, - reason - ); + let retries = deferred_retries.entry(key).or_insert(0); + *retries += 1; + if *retries <= MAX_DEFERRED_RETRIES { + log::info!( + "hotplug: deferred {} -> {} ({})", + device.path, + driver_name, + reason + ); + } else if *retries == MAX_DEFERRED_RETRIES + 1 { + log::warn!( + "hotplug: giving up on {} -> {} after {} retries: {}", + device.path, + driver_name, + MAX_DEFERRED_RETRIES, + reason + ); + } } ProbeResult::Fatal { reason } => { log::error!( @@ -77,6 +108,7 @@ pub fn run_hotplug_loop( driver_name, reason ); + permanently_fatal.insert(key); } _ => {} } diff --git a/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor b/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor index 2abd78e1d0..d1812a8a41 100755 --- a/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor +++ b/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor @@ -2,8 +2,13 @@ set -euo pipefail -export DISPLAY="" -export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" +# NOTE: Neither WAYLAND_DISPLAY nor DISPLAY may be exported when starting +# KWin. KWin uses their presence (even as empty strings) to select the +# nested Wayland or X11 backend instead of the bare-metal DRM/KMS backend. +# Clients (the greeter UI, KDE session apps) receive WAYLAND_DISPLAY from +# redbear-session-launch instead. +unset DISPLAY +unset WAYLAND_DISPLAY export XDG_SESSION_TYPE=wayland export LIBSEAT_BACKEND=seatd export SEATD_SOCK=/run/seatd.sock @@ -13,6 +18,10 @@ export QML2_IMPORT_PATH="${QML2_IMPORT_PATH:-/usr/qml}" export QT_WAYLAND_SHELL_INTEGRATION="${QT_WAYLAND_SHELL_INTEGRATION:-xdg-shell}" export XCURSOR_THEME="${XCURSOR_THEME:-Pop}" export XKB_CONFIG_ROOT="${XKB_CONFIG_ROOT:-/usr/share/X11/xkb}" +export KWIN_COMPOSE="${KWIN_COMPOSE:-O2}" +export QT_DEBUG_PLUGINS=1 +export QT_LOGGING_RULES="${QT_LOGGING_RULES:-*.debug=true}" +export MESA_LOADER_DRIVER_OVERRIDE="${MESA_LOADER_DRIVER_OVERRIDE:-virtio_gpu}" if [ -z "${XDG_RUNTIME_DIR:-}" ]; then export XDG_RUNTIME_DIR="/tmp/run/redbear-greeter" @@ -20,53 +29,27 @@ fi mkdir -p "$XDG_RUNTIME_DIR" -parse_drm_devices() { - local devices="${1:-}" - local part="" - - IFS=':' read -r -a parts <<< "$devices" - for part in "${parts[@]}"; do - if [ -n "$part" ]; then - printf '%s\n' "$part" - fi - done +drm_scheme_ready() { + # Try to open /scheme/drm/card0 via read (head -c 1). + # On Redox, stat and test -e are unreliable for scheme paths, + # but opening the scheme file descriptor works. + ( head -c 1 "/scheme/drm/card0" ) >/dev/null 2>&1 } -drm_devices_ready() { - local devices="${1:-}" - local device="" - - for device in $(parse_drm_devices "$devices"); do - if [ ! -e "$device" ]; then - return 1 - fi - done - - return 0 -} - -wait_for_drm_devices() { - local devices="${1:-}" - local wait_seconds="${REDBEAR_DRM_WAIT_SECONDS:-2}" +wait_for_drm_scheme() { + local wait_seconds="${REDBEAR_DRM_WAIT_SECONDS:-10}" local attempts=0 - if [ -z "$devices" ]; then - return 1 + if drm_scheme_ready; then + return 0 fi - if [ "$wait_seconds" -le 0 ] 2>/dev/null; then - drm_devices_ready "$devices" - return $? - fi + echo "redbear-greeter-compositor: waiting up to ${wait_seconds}s for DRM device(s): /scheme/drm:/scheme/drm/card0" >&2 attempts=$((wait_seconds * 2)) - if ! drm_devices_ready "$devices"; then - echo "redbear-greeter-compositor: waiting up to ${wait_seconds}s for DRM device(s): ${devices}" >&2 - fi - while [ "$attempts" -gt 0 ]; do - if drm_devices_ready "$devices"; then + if drm_scheme_ready; then return 0 fi @@ -74,13 +57,9 @@ wait_for_drm_devices() { attempts=$((attempts - 1)) done - drm_devices_ready "$devices" + drm_scheme_ready } -if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] && command -v dbus-launch >/dev/null 2>&1; then - eval "$(dbus-launch --sh-syntax)" -fi - # Prefer kwin_wayland (real KWin compositor). Fall back to redbear-compositor only if # KWin is not installed (e.g. text-only target accidentally includes greeter). COMPOSITOR="" @@ -97,27 +76,28 @@ else fi # pcid-spawner is intentionally async in Red Bear OS, so service ordering only guarantees that -# spawning has started. Wait for the DRM scheme root first, then the selected card node. -if [ -n "${KWIN_DRM_DEVICES:-}" ]; then - desired_drm_devices="/scheme/drm:${KWIN_DRM_DEVICES}" -else - desired_drm_devices="/scheme/drm:/scheme/drm/card0" -fi - -if wait_for_drm_devices "$desired_drm_devices"; then +# spawning has started. Wait for the DRM scheme to appear. +if wait_for_drm_scheme; then export KWIN_DRM_DEVICES="${KWIN_DRM_DEVICES:-/scheme/drm/card0}" echo "redbear-greeter-compositor: using DRM compositor backend (KWIN_DRM_DEVICES=${KWIN_DRM_DEVICES})" >&2 - if [ "$COMPOSITOR" = "kwin_wayland" ] || [ "$COMPOSITOR" = "/usr/bin/kwin_wayland" ]; then - exec "$COMPOSITOR" --drm - else - exec "$COMPOSITOR" --drm - fi -else - unset KWIN_DRM_DEVICES - echo "redbear-greeter-compositor: DRM device not ready after wait, using virtual compositor backend" >&2 - if [ "$COMPOSITOR" = "kwin_wayland" ] || [ "$COMPOSITOR" = "/usr/bin/kwin_wayland" ]; then - exec "$COMPOSITOR" --virtual - else - exec "$COMPOSITOR" --virtual - fi + + # NOTE: QDBusConnection::sessionBus() blocks on Redox when attempting Unix socket + # connect(), even when dbus-daemon is running. All KWin sessionBus() calls in the + # startup path have been bypassed in the KWin source. We do NOT set + # DBUS_SESSION_BUS_ADDRESS so that remaining sessionBus() calls with env-var guards + # skip gracefully. If a future change re-enables D-Bus session bus, the Qt/Redox + # Unix socket connect() issue must be resolved first. + + echo "redbear-greeter-compositor: env DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS:-unset} DBUS_SYSTEM_BUS_ADDRESS=${DBUS_SYSTEM_BUS_ADDRESS:-unset}" >&2 + echo "redbear-greeter-compositor: launching $COMPOSITOR" >&2 + QT_QPA_PLATFORM=offscreen "$COMPOSITOR" + EXIT_CODE=$? + echo "redbear-greeter-compositor: compositor exited with code $EXIT_CODE" >&2 + exit $EXIT_CODE fi + +# No DRM at all — fall back to virtual backend. +unset KWIN_DRM_DEVICES +echo "redbear-greeter-compositor: no DRM device found, using virtual compositor backend" >&2 +"$COMPOSITOR" +exit $? diff --git a/local/recipes/system/redbear-greeter/source/redbear-kde-session b/local/recipes/system/redbear-greeter/source/redbear-kde-session index f9df81ea0d..fa405228b3 100755 --- a/local/recipes/system/redbear-greeter/source/redbear-kde-session +++ b/local/recipes/system/redbear-greeter/source/redbear-kde-session @@ -28,6 +28,7 @@ export XDG_SESSION_DESKTOP="${XDG_SESSION_DESKTOP:-KDE}" export XDG_SESSION_ID="${XDG_SESSION_ID:-c1}" export XDG_SESSION_TYPE="${XDG_SESSION_TYPE:-wayland}" export XKB_CONFIG_ROOT="${XKB_CONFIG_ROOT:-/usr/share/X11/xkb}" +export KWIN_COMPOSE="${KWIN_COMPOSE:-Q}" if [ -z "${XDG_RUNTIME_DIR:-}" ]; then export XDG_RUNTIME_DIR="/tmp/run/user/$(id -u)" @@ -89,12 +90,18 @@ trap cleanup EXIT INT TERM kwin_mode="virtual" +# Redox scheme paths do not respond to stat()/test -e, but opening and +# reading a byte works. Use the same probe the compositor wrapper uses. +drm_scheme_ready() { + ( head -c 1 "/scheme/drm/card0" ) >/dev/null 2>&1 +} + set_kwin_mode() { local requested="${REDBEAR_KDE_SESSION_BACKEND:-auto}" case "$requested" in drm) - if [ -z "${KWIN_DRM_DEVICES:-}" ] && [ -e /scheme/drm/card0 ]; then + if [ -z "${KWIN_DRM_DEVICES:-}" ] && drm_scheme_ready; then export KWIN_DRM_DEVICES=/scheme/drm/card0 fi if [ -n "${KWIN_DRM_DEVICES:-}" ]; then @@ -109,7 +116,7 @@ set_kwin_mode() { auto|"") if [ -n "${KWIN_DRM_DEVICES:-}" ]; then kwin_mode="drm" - elif [ -e /scheme/drm/card0 ]; then + elif drm_scheme_ready; then export KWIN_DRM_DEVICES=/scheme/drm/card0 kwin_mode="drm" else @@ -257,6 +264,11 @@ kwin_pid=$! if ! wait_for_wayland_socket; then printf '%s\n' "redbear-kde-session: $COMPOSITOR failed to expose $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" >&2 + printf '%s\n' "redbear-kde-session: kwin_pid=$kwin_pid, kwin_alive=$(kill -0 $kwin_pid 2>/dev/null && echo yes || echo no)" >&2 + printf '%s\n' "redbear-kde-session: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >&2 + printf '%s\n' "redbear-kde-session: WAYLAND_DISPLAY=$WAYLAND_DISPLAY" >&2 + printf '%s\n' "redbear-kde-session: contents of $XDG_RUNTIME_DIR:" >&2 + ls -la "$XDG_RUNTIME_DIR" 2>/dev/null >&2 || echo "(dir not found)" >&2 exit 1 fi diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 7a92e20dc2..baf8d1065b 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -33,6 +33,7 @@ patches = [ "P4-acpi-shutdown-hardening.patch", "P4-acpi-s3-sleep.patch", "P4-pcid-public-client-channel.patch", + "P4-pcid-config-scheme.patch", "P4-pcid-spawner-pci-coordinate-env.patch", "P4-initfs-usb-drm-services.patch", "P4-initfs-release-virtio-gpu.patch", diff --git a/recipes/core/relibc/recipe.toml b/recipes/core/relibc/recipe.toml index 5dd2413e47..cd37754eda 100644 --- a/recipes/core/relibc/recipe.toml +++ b/recipes/core/relibc/recipe.toml @@ -83,6 +83,10 @@ patches = [ "P3-ipc-tests.patch", # ST_RDONLY/ST_NOSUID constants for statvfs (POSIX) "P3-statvfs-constants.patch", + "P3-ld-so-usr-lib-search-path.patch", + # Remove relibc's DRM ioctl handler — libdrm's redox_drm_simple_ioctl() + # handles all DRM ioctls with its own scheme:drm wire format. + "P5-remove-drm-ioctl-intercept.patch", ] [build] diff --git a/recipes/libs/freetype2/recipe.toml b/recipes/libs/freetype2/recipe.toml index ca3ce1e40f..03654dd35f 100644 --- a/recipes/libs/freetype2/recipe.toml +++ b/recipes/libs/freetype2/recipe.toml @@ -9,6 +9,6 @@ dependencies = [ "zlib" ] script = """ -DYNAMIC_STATIC_INIT -cookbook_meson +DYNAMIC_INIT +cookbook_meson -Dpng=disabled """ diff --git a/recipes/system/dbus b/recipes/system/dbus new file mode 120000 index 0000000000..36cdba5404 --- /dev/null +++ b/recipes/system/dbus @@ -0,0 +1 @@ +../../local/recipes/system/dbus \ No newline at end of file diff --git a/scripts/run_full.sh b/scripts/run_full.sh index 85a931da31..0765aeb2be 100755 --- a/scripts/run_full.sh +++ b/scripts/run_full.sh @@ -1,3 +1,3 @@ #!/bin/bash -qemu-system-x86_64 -m 8G -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive file=/home/kellito/Builds/rbos/build/x86_64/redbear-full.iso,format=raw -device virtio-gpu-pci -serial mon:stdio +qemu-system-x86_64 -m 8G -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive file=/home/kellito/Builds/rbos/build/x86_64/redbear-full.iso,format=raw -device virtio-gpu-pci -enable-kvm -serial mon:stdio