diff --git a/local/patches/base/redox.patch b/local/patches/base/redox.patch index fb3517e9..4826744f 100644 --- a/local/patches/base/redox.patch +++ b/local/patches/base/redox.patch @@ -222,15 +222,6 @@ diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml index 2d22a8f9..fea105c8 100644 --- a/drivers/acpid/Cargo.toml +++ b/drivers/acpid/Cargo.toml -@@ -8,7 +8,7 @@ edition = "2018" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - - [dependencies] --acpi = { git = "https://github.com/jackpot51/acpi.git" } -+acpi = { path = "../acpi" } - arrayvec = "0.7.6" - log.workspace = true - num-derive = "0.3" @@ -21,6 +21,7 @@ rustc-hash = "1.1.0" thiserror.workspace = true ron.workspace = true @@ -240,7 +231,7 @@ index 2d22a8f9..fea105c8 100644 amlserde = { path = "../amlserde" } common = { path = "../common" } diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..24799ae2 100644 +index 94a1eb17..58bcc22d 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -8,6 +8,7 @@ use std::str::FromStr; @@ -753,897 +744,6 @@ index 94a1eb17..24799ae2 100644 let format_err = |err| format!("{:?}", err); let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache)); //TODO: use these parsed tables for the rest of acpid -@@ -269,7 +731,7 @@ impl AmlSymbols { - unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? }; - let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?; - let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?; -- for (region, handler) in self.aml_region_handlers.drain(..) { -+ for (region, handler) in default_aml_region_handlers() { - interpreter.install_region_handler(region, handler); - } - self.aml_context = Some(interpreter); -@@ -356,6 +818,21 @@ impl AmlSymbols { - - self.symbol_cache = symbol_cache; - } -+ -+ pub fn reset(&mut self) { -+ self.aml_context = None; -+ self.symbol_cache = FxHashMap::default(); -+ *self.page_cache.lock().unwrap() = AmlPageCache::default(); -+ } -+} -+ -+fn default_aml_region_handlers() -> Vec<(RegionSpace, Box)> { -+ let mut handlers: Vec<(RegionSpace, Box)> = Vec::new(); -+ -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ handlers.push((RegionSpace::EmbeddedControl, Box::new(crate::ec::Ec::new()))); -+ -+ handlers - } - - #[derive(Debug, Error)] -@@ -368,6 +845,8 @@ pub enum AmlEvalError { - DeserializationError, - #[error("AML not initialized")] - NotInitialized, -+ #[error("PCI registration not ready")] -+ PciNotReady, - } - impl From for AmlEvalError { - fn from(value: AmlError) -> Self { -@@ -375,10 +854,122 @@ impl From for AmlEvalError { - } - } - -+#[derive(Clone, Debug)] -+pub struct AcpiPowerAdapter { -+ pub id: String, -+ pub path: String, -+ pub online: bool, -+} -+ -+#[derive(Clone, Debug)] -+pub struct AcpiBattery { -+ pub id: String, -+ pub path: String, -+ pub state: u64, -+ pub present_rate: Option, -+ pub remaining_capacity: Option, -+ pub present_voltage: Option, -+ pub power_unit: Option, -+ pub design_capacity: Option, -+ pub last_full_capacity: Option, -+ pub design_voltage: Option, -+ pub technology: Option, -+ pub model: Option, -+ pub serial: Option, -+ pub battery_type: Option, -+ pub oem_info: Option, -+ pub percentage: Option, -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerSnapshot { -+ pub adapters: Vec, -+ pub batteries: Vec, -+} -+ -+impl AcpiPowerSnapshot { -+ pub fn on_battery(&self) -> bool { -+ if self.adapters.iter().any(|adapter| adapter.online) { -+ return false; -+ } -+ -+ self.batteries -+ .iter() -+ .any(|battery| battery.state & 0x1 != 0) -+ } -+} -+ -+fn sanitize_power_id(path: &str) -> String { -+ let sanitized = path -+ .chars() -+ .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '_' }) -+ .collect::() -+ .trim_matches('_') -+ .to_string(); -+ -+ if sanitized.is_empty() { -+ String::from("device") -+ } else { -+ sanitized -+ } -+} -+ -+fn aml_value_as_integer(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::Integer(value) => Some(*value), -+ _ => None, -+ } -+} -+ -+fn aml_value_as_string(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::String(value) => { -+ let trimmed = value.trim(); -+ if trimmed.is_empty() { -+ None -+ } else { -+ Some(trimmed.to_string()) -+ } -+ } -+ _ => None, -+ } -+} -+ -+fn power_unit_name(value: u64) -> Option<&'static str> { -+ match value { -+ 0 => Some("mWh"), -+ 1 => Some("mAh"), -+ _ => None, -+ } -+} -+ -+fn battery_technology_name(value: u64) -> Option<&'static str> { -+ match value { -+ 0 => Some("non-rechargeable"), -+ 1 => Some("rechargeable"), -+ _ => None, -+ } -+} -+ -+fn battery_percentage(remaining_capacity: u64, full_capacity: u64) -> Option { -+ if full_capacity == 0 { -+ return None; -+ } -+ -+ Some((remaining_capacity as f64 * 100.0 / full_capacity as f64).clamp(0.0, 100.0)) -+} -+ - pub struct AcpiContext { - tables: Vec, - dsdt: Option, - fadt: Option, -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ s5_values: RwLock>, -+ reset_reg: Option, -+ reset_value: u8, -+ -+ pci_fd: RwLock>, - - aml_symbols: RwLock, - -@@ -388,16 +979,190 @@ pub struct AcpiContext { - sdt_order: RwLock>>, - - pub next_ctx: RwLock, -+ dmi_info: Option, - } - - impl AcpiContext { -+ fn evaluate_acpi_object( -+ &self, -+ path: &str, -+ object: &str, -+ args: &[u64], -+ ) -> Result { -+ let full_path = format!("{path}.{object}"); -+ let aml_name = -+ AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; -+ let args = args -+ .iter() -+ .copied() -+ .map(AmlSerdeValue::Integer) -+ .collect::>(); -+ -+ self.aml_eval(aml_name, args) -+ } -+ -+ fn try_evaluate_acpi_object( -+ &self, -+ path: &str, -+ object: &str, -+ args: &[u64], -+ ) -> Option { -+ match self.evaluate_acpi_object(path, object, args) { -+ Ok(value) => Some(value), -+ Err(error) => { -+ log::debug!("Failed to evaluate {}.{}: {:?}", path, object, error); -+ None -+ } -+ } -+ } -+ -+ fn power_device_paths(&self) -> Result, AmlEvalError> { -+ let mut symbols = self.aml_symbols.write(); -+ let pci_fd = self.pci_fd.read(); -+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; -+ -+ let mut names = Vec::with_capacity(512); -+ { -+ let mut namespace = interpreter.namespace.lock(); -+ namespace -+ .traverse(|level_aml_name, level| { -+ for (child_seg, _) in level.values.iter() { -+ if let Ok(aml_name) = -+ AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) -+ { -+ names.push(aml_name); -+ } -+ } -+ Ok(true) -+ }) -+ .map_err(AmlEvalError::from)?; -+ } -+ -+ let mut namespace = interpreter.namespace.lock(); -+ let mut devices = Vec::new(); -+ -+ for name in names { -+ let Ok(object) = namespace.get(name.clone()) else { -+ continue; -+ }; -+ -+ if matches!(object.deref(), Object::Device) { -+ devices.push(name.to_string()); -+ } -+ } -+ -+ Ok(devices) -+ } -+ -+ fn power_device_present(&self, path: &str) -> bool { -+ match self.try_evaluate_acpi_object(path, "_STA", &[]) { -+ Some(AmlSerdeValue::Integer(status)) => status & 0x1 != 0, -+ Some(_) => false, -+ None => true, -+ } -+ } -+ -+ fn power_adapter_from_path(&self, path: &str) -> Option { -+ let online = match self.try_evaluate_acpi_object(path, "_PSR", &[])? { -+ AmlSerdeValue::Integer(value) => value != 0, -+ _ => return None, -+ }; -+ -+ Some(AcpiPowerAdapter { -+ id: sanitize_power_id(path), -+ path: path.to_string(), -+ online, -+ }) -+ } -+ -+ fn power_battery_from_path(&self, path: &str) -> Option { -+ let bif_contents = match self.try_evaluate_acpi_object(path, "_BIF", &[])? { -+ AmlSerdeValue::Package { contents } => contents, -+ _ => return None, -+ }; -+ let bst_contents = match self.try_evaluate_acpi_object(path, "_BST", &[])? { -+ AmlSerdeValue::Package { contents } => contents, -+ _ => return None, -+ }; -+ -+ if bif_contents.len() < 13 || bst_contents.len() < 4 { -+ return None; -+ } -+ -+ let state = aml_value_as_integer(&bst_contents[0])?; -+ let present_rate = aml_value_as_integer(&bst_contents[1]); -+ let remaining_capacity = aml_value_as_integer(&bst_contents[2]); -+ let present_voltage = aml_value_as_integer(&bst_contents[3]); -+ -+ let design_capacity = aml_value_as_integer(&bif_contents[1]); -+ let last_full_capacity = aml_value_as_integer(&bif_contents[2]); -+ let design_voltage = aml_value_as_integer(&bif_contents[4]); -+ let percentage = remaining_capacity.and_then(|remaining| { -+ let full_capacity = last_full_capacity.or(design_capacity)?; -+ battery_percentage(remaining, full_capacity) -+ }); -+ -+ Some(AcpiBattery { -+ id: sanitize_power_id(path), -+ path: path.to_string(), -+ state, -+ present_rate, -+ remaining_capacity, -+ present_voltage, -+ power_unit: aml_value_as_integer(&bif_contents[0]) -+ .and_then(power_unit_name) -+ .map(str::to_string), -+ design_capacity, -+ last_full_capacity, -+ design_voltage, -+ technology: aml_value_as_integer(&bif_contents[3]) -+ .and_then(battery_technology_name) -+ .map(str::to_string), -+ model: aml_value_as_string(&bif_contents[9]), -+ serial: aml_value_as_string(&bif_contents[10]), -+ battery_type: aml_value_as_string(&bif_contents[11]), -+ oem_info: aml_value_as_string(&bif_contents[12]), -+ percentage, -+ }) -+ } -+ -+ pub fn power_snapshot(&self) -> Result { -+ let mut adapters = Vec::new(); -+ let mut batteries = Vec::new(); -+ -+ for device_path in self.power_device_paths()? { -+ if !self.power_device_present(&device_path) { -+ continue; -+ } -+ -+ if let Some(adapter) = self.power_adapter_from_path(&device_path) { -+ adapters.push(adapter); -+ } -+ if let Some(battery) = self.power_battery_from_path(&device_path) { -+ batteries.push(battery); -+ } -+ } -+ -+ adapters.sort_by(|left, right| left.id.cmp(&right.id)); -+ batteries.sort_by(|left, right| left.id.cmp(&right.id)); -+ -+ Ok(AcpiPowerSnapshot { -+ adapters, -+ batteries, -+ }) -+ } -+ - pub fn aml_eval( - &self, - symbol: AmlName, - args: Vec, - ) -> Result { -+ if !self.pci_ready() { -+ return Err(AmlEvalError::PciNotReady); -+ } - let mut symbols = self.aml_symbols.write(); -- let interpreter = symbols.aml_context_mut(None)?; -+ let pci_fd = self.pci_fd.read(); -+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; - interpreter.acquire_global_lock(16)?; - - let args = args -@@ -424,10 +1189,55 @@ impl AcpiContext { - .flatten() - } - -- pub fn init( -- rxsdt_physaddrs: impl Iterator, -- ec: Vec<(RegionSpace, Box)>, -- ) -> Self { -+ pub fn evaluate_acpi_method( -+ &self, -+ path: &str, -+ method: &str, -+ args: &[u64], -+ ) -> Result, AmlEvalError> { -+ match self.evaluate_acpi_object(path, method, args)? { -+ AmlSerdeValue::Integer(value) => Ok(vec![value]), -+ AmlSerdeValue::Package { contents } => contents -+ .into_iter() -+ .map(|value| match value { -+ AmlSerdeValue::Integer(value) => Ok(value), -+ _ => Err(AmlEvalError::DeserializationError), -+ }) -+ .collect(), -+ _ => Err(AmlEvalError::DeserializationError), -+ } -+ } -+ -+ pub fn device_power_on(&self, device_path: &str) { -+ match self.evaluate_acpi_method(device_path, "_PS0", &[]) { -+ Ok(values) => { -+ log::debug!("{}._PS0 => {:?}", device_path, values); -+ } -+ Err(error) => { -+ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); -+ } -+ } -+ } -+ -+ pub fn device_power_off(&self, device_path: &str) { -+ match self.evaluate_acpi_method(device_path, "_PS3", &[]) { -+ Ok(values) => { -+ log::debug!("{}._PS3 => {:?}", device_path, values); -+ } -+ Err(error) => { -+ log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error); -+ } -+ } -+ } -+ -+ pub fn device_get_performance(&self, device_path: &str) -> Result { -+ self.evaluate_acpi_method(device_path, "_PPC", &[])? -+ .into_iter() -+ .next() -+ .ok_or(AmlEvalError::DeserializationError) -+ } -+ -+ pub fn init(rxsdt_physaddrs: impl Iterator) -> Self { - let tables = rxsdt_physaddrs - .map(|physaddr| { - let physaddr: usize = physaddr -@@ -440,17 +1250,28 @@ impl AcpiContext { - }) - .collect::>(); - -+ let dmi_info = load_dmi_info(); -+ let tables = apply_acpi_table_quirks(tables, dmi_info.as_ref()); -+ - let mut this = Self { - tables, - dsdt: None, - fadt: None, -+ pm1a_cnt_blk: 0, -+ pm1b_cnt_blk: 0, -+ s5_values: RwLock::new(None), -+ reset_reg: None, -+ reset_value: 0, -+ -+ pci_fd: RwLock::new(None), - - // Temporary values -- aml_symbols: RwLock::new(AmlSymbols::new(ec)), -+ aml_symbols: RwLock::new(AmlSymbols::new()), - - next_ctx: RwLock::new(0), - - sdt_order: RwLock::new(Vec::new()), -+ dmi_info, - }; - - for table in &this.tables { -@@ -458,7 +1279,10 @@ impl AcpiContext { - } - - Fadt::init(&mut this); -- //TODO (hangs on real hardware): Dmar::init(&this); -+ // DMAR (Intel VT-d) init — previously disabled due to iterator bug (type_bytes copied -+ // instead of len_bytes in DmarRawIter). Safe to call now: on AMD systems, no DMAR table -+ // exists and this returns early with a warning. -+ Dmar::init(&this); - - this - } -@@ -521,22 +1345,28 @@ impl AcpiContext { - pub fn tables(&self) -> &[Sdt] { - &self.tables - } -+ pub fn dmi_info(&self) -> Option<&DmiInfo> { -+ self.dmi_info.as_ref() -+ } - pub fn new_index(&self, signature: &SdtSignature) { - self.sdt_order.write().push(Some(*signature)); - } - - pub fn aml_lookup(&self, symbol: &str) -> Option { -- if let Ok(aml_symbols) = self.aml_symbols(None) { -+ if !self.pci_ready() { -+ return None; -+ } -+ if let Ok(aml_symbols) = self.aml_symbols() { - aml_symbols.lookup(symbol) - } else { - None - } - } - -- pub fn aml_symbols( -- &self, -- pci_fd: Option<&libredox::Fd>, -- ) -> Result, AmlError> { -+ pub fn aml_symbols(&self) -> Result, AmlError> { -+ if !self.pci_ready() { -+ return Err(AmlError::NoHandlerForRegionAccess(RegionSpace::PciConfig)); -+ } - // return the cached value if it exists - let symbols = self.aml_symbols.read(); - if !symbols.symbols_cache().is_empty() { -@@ -549,8 +1379,13 @@ impl AcpiContext { - log::trace!("Creating symbols list"); - - let mut aml_symbols = self.aml_symbols.write(); -+ let pci_fd = self.pci_fd.read(); - -- aml_symbols.build_cache(pci_fd); -+ aml_symbols.build_cache(pci_fd.as_ref()); -+ -+ if aml_symbols.symbols_cache().is_empty() { -+ return Err(AmlError::NoHandlerForRegionAccess(RegionSpace::PciConfig)); -+ } - - // return the cached value - Ok(RwLockWriteGuard::downgrade(aml_symbols)) -@@ -559,99 +1394,164 @@ impl AcpiContext { - /// Discard any cached symbols list. To be called if the AML namespace changes. - pub fn aml_symbols_reset(&self) { - let mut aml_symbols = self.aml_symbols.write(); -- aml_symbols.symbol_cache = FxHashMap::default(); -+ aml_symbols.reset(); - } - -- /// Set Power State -- /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf -- /// - search for PM1a -- /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details -- pub fn set_global_s_state(&self, state: u8) { -- if state != 5 { -- return; -- } -- let fadt = match self.fadt() { -- Some(fadt) => fadt, -- None => { -- log::error!("Cannot set global S-state due to missing FADT."); -- return; -- } -- }; -+ pub fn pci_ready(&self) -> bool { -+ self.pci_fd.read().is_some() -+ } - -- let port = fadt.pm1a_control_block as u16; -- let mut val = 1 << 13; -+ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> Result<(), libredox::Fd> { -+ let mut aml_symbols = self.aml_symbols.write(); -+ let mut registered_pci_fd = self.pci_fd.write(); - -- let aml_symbols = self.aml_symbols.read(); -+ if registered_pci_fd.is_some() { -+ return Err(pci_fd); -+ } - -- let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { -- Ok(aml_name) => aml_name, -- Err(error) => { -- log::error!("Could not build AmlName for \\_S5, {:?}", error); -- return; -- } -- }; -+ *registered_pci_fd = Some(pci_fd); - -- let s5 = match &aml_symbols.aml_context { -- Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { -- Ok(s5) => s5, -- Err(error) => { -- log::error!("Cannot set S-state, missing \\_S5, {:?}", error); -- return; -- } -- }, -- None => { -- log::error!("Cannot set S-state, AML context not initialized"); -- return; -- } -- }; -+ if aml_symbols.aml_context.is_some() || !aml_symbols.symbol_cache.is_empty() { -+ log::warn!("PCI registration arrived after AML init; rebuilding AML interpreter state"); -+ aml_symbols.reset(); -+ *self.s5_values.write() = None; -+ } - -- let package = match s5.deref() { -- acpi::aml::object::Object::Package(package) => package, -- _ => { -- log::error!("Cannot set S-state, \\_S5 is not a package"); -- return; -- } -- }; -+ Ok(()) -+ } - -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; -- } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; -- } -+ pub fn acpi_shutdown(&self) { -+ let Some((slp_typa_s5, slp_typb_s5)) = self.ensure_s5_values() else { -+ log::error!("Cannot shut down with ACPI: failed to resolve \\_S5 sleep type values"); -+ return; - }; - -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -+ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000; -+ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000; - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - { -- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); -- Pio::::new(port).write(val); -- } -+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { -+ log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); -+ return; -+ }; - -- // TODO: Handle SLP_TYPb -+ log::warn!( -+ "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})", -+ pm1a_port, -+ pm1a_value -+ ); -+ Pio::::new(pm1a_port).write(pm1a_value); -+ -+ if self.pm1b_cnt_blk != 0 { -+ match u16::try_from(self.pm1b_cnt_blk) { -+ Ok(pm1b_port) => { -+ log::warn!( -+ "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})", -+ pm1b_port, -+ pm1b_value -+ ); -+ Pio::::new(pm1b_port).write(pm1b_value); -+ } -+ Err(_) => { -+ log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk); -+ } -+ } -+ } -+ } - - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - { - log::error!( -- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", -- port, -- val -+ "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})", -+ self.pm1a_cnt_blk, -+ self.pm1b_cnt_blk - ); - } -+ } -+ -+ pub fn acpi_reboot(&self) { -+ match self.reset_reg { -+ Some(reset_reg) => { -+ log::warn!( -+ "Reboot with ACPI reset register {:?} value {:#X}", -+ reset_reg, -+ self.reset_value -+ ); -+ reset_reg.write_u8(self.reset_value); -+ } -+ None => { -+ log::error!("Cannot reboot with ACPI: no reset register present in FADT"); -+ } -+ } -+ } -+ -+ /// Set Power State -+ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf -+ /// - search for PM1a -+ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details -+ pub fn set_global_s_state(&self, state: u8) { -+ if state != 5 { -+ return; -+ } -+ -+ if self.fadt().is_none() { -+ log::error!("Cannot set global S-state due to missing FADT."); -+ return; -+ } -+ -+ self.acpi_shutdown(); - - loop { - core::hint::spin_loop(); - } - } -+ -+ fn evaluate_s5_values(&self) -> Option<(u8, u8)> { -+ match AmlName::from_str("\\_S5") { -+ Ok(s5_name) => match self.aml_eval(s5_name, Vec::new()) { -+ Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), contents.get(1)) -+ { -+ ( -+ Some(AmlSerdeValue::Integer(slp_typa)), -+ Some(AmlSerdeValue::Integer(slp_typb)), -+ ) => match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { -+ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Some((slp_typa_s5, slp_typb_s5)), -+ _ => { -+ log::warn!("\\_S5 values do not fit in u8: {:?}", contents); -+ None -+ } -+ }, -+ _ => { -+ log::warn!("\\_S5 package did not contain two integers: {:?}", contents); -+ None -+ } -+ }, -+ Ok(value) => { -+ log::warn!("\\_S5 returned unexpected AML value: {:?}", value); -+ None -+ } -+ Err(error) => { -+ log::warn!("Failed to evaluate \\_S5: {:?}", error); -+ None -+ } -+ }, -+ Err(error) => { -+ log::warn!("Could not build AmlName for \\_S5: {:?}", error); -+ None -+ } -+ } -+ } -+ -+ fn ensure_s5_values(&self) -> Option<(u8, u8)> { -+ if let Some(values) = *self.s5_values.read() { -+ return Some(values); -+ } -+ -+ let values = self.evaluate_s5_values()?; -+ *self.s5_values.write() = Some(values); -+ Some(values) -+ } - } - - #[repr(C, packed)] -@@ -707,7 +1607,7 @@ unsafe impl plain::Plain for FadtStruct {} - - #[repr(C, packed)] - #[derive(Clone, Copy, Debug, Default)] --pub struct GenericAddressStructure { -+pub struct GenericAddress { - address_space: u8, - bit_width: u8, - bit_offset: u8, -@@ -715,11 +1615,77 @@ pub struct GenericAddressStructure { - address: u64, - } - -+impl GenericAddress { -+ pub fn is_empty(&self) -> bool { -+ self.address == 0 -+ } -+ -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ pub fn write_u8(&self, value: u8) { -+ match self.address_space { -+ 0 => { -+ let raw_address = self.address; -+ let Ok(address) = usize::try_from(raw_address) else { -+ log::error!( -+ "Reset register physical address is invalid: {:#X}", -+ raw_address -+ ); -+ return; -+ }; -+ let page = address / PAGE_SIZE * PAGE_SIZE; -+ let offset = address % PAGE_SIZE; -+ let virt = unsafe { -+ common::physmap( -+ page, -+ PAGE_SIZE, -+ common::Prot::RW, -+ common::MemoryType::default(), -+ ) -+ }; -+ -+ match virt { -+ Ok(virt) => unsafe { -+ (virt as *mut u8).add(offset).write_volatile(value); -+ let _ = libredox::call::munmap(virt, PAGE_SIZE); -+ }, -+ Err(error) => { -+ log::error!("Failed to map ACPI reset register: {}", error); -+ } -+ } -+ } -+ 1 => match u16::try_from(self.address) { -+ Ok(port) => { -+ Pio::::new(port).write(value); -+ } -+ Err(_) => { -+ let raw_address = self.address; -+ log::error!("Reset register I/O port is invalid: {:#X}", raw_address); -+ } -+ }, -+ address_space => { -+ log::warn!( -+ "Unsupported ACPI reset register address space {} for {:?}", -+ address_space, -+ self -+ ); -+ } -+ } -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ pub fn write_u8(&self, _value: u8) { -+ log::error!( -+ "Cannot access ACPI reset register {:?} on this architecture", -+ self -+ ); -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct FadtAcpi2Struct { - // 12 byte structure; see below for details -- pub reset_reg: GenericAddressStructure, -+ pub reset_reg: GenericAddress, - - pub reset_value: u8, - reserved3: [u8; 3], -@@ -728,14 +1694,14 @@ pub struct FadtAcpi2Struct { - pub x_firmware_control: u64, - pub x_dsdt: u64, - -- pub x_pm1a_event_block: GenericAddressStructure, -- pub x_pm1b_event_block: GenericAddressStructure, -- pub x_pm1a_control_block: GenericAddressStructure, -- pub x_pm1b_control_block: GenericAddressStructure, -- pub x_pm2_control_block: GenericAddressStructure, -- pub x_pm_timer_block: GenericAddressStructure, -- pub x_gpe0_block: GenericAddressStructure, -- pub x_gpe1_block: GenericAddressStructure, -+ pub x_pm1a_event_block: GenericAddress, -+ pub x_pm1b_event_block: GenericAddress, -+ pub x_pm1a_control_block: GenericAddress, -+ pub x_pm1b_control_block: GenericAddress, -+ pub x_pm2_control_block: GenericAddress, -+ pub x_pm_timer_block: GenericAddress, -+ pub x_gpe0_block: GenericAddress, -+ pub x_gpe1_block: GenericAddress, - } - unsafe impl plain::Plain for FadtAcpi2Struct {} - -@@ -793,9 +1759,27 @@ impl Fadt { - None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), - }; - -- log::debug!("FACP at {:X}", { dsdt_ptr }); -+ let pm1a_evt_blk = u64::from(fadt.pm1a_event_block); -+ let pm1b_evt_blk = u64::from(fadt.pm1b_event_block); -+ let pm1a_cnt_blk = u64::from(fadt.pm1a_control_block); -+ let pm1b_cnt_blk = u64::from(fadt.pm1b_control_block); -+ let (reset_reg, reset_value) = match fadt.acpi_2_struct() { -+ Some(fadt2) if !fadt2.reset_reg.is_empty() => { -+ (Some(fadt2.reset_reg), fadt2.reset_value) -+ } -+ _ => (None, 0), -+ }; - -- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { -+ log::debug!("FACP at {:X}", { dsdt_ptr }); -+ log::debug!( -+ "FADT power blocks: PM1a_EVT={:#X}, PM1b_EVT={:#X}, PM1a_CNT={:#X}, PM1b_CNT={:#X}", -+ pm1a_evt_blk, -+ pm1b_evt_blk, -+ pm1a_cnt_blk, -+ pm1b_cnt_blk -+ ); -+ -+ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) { - Ok(dsdt) => dsdt, - Err(error) => { - log::error!("Failed to load DSDT: {}", error); -@@ -805,6 +1789,10 @@ impl Fadt { - - context.fadt = Some(fadt.clone()); - context.dsdt = Some(Dsdt(dsdt_sdt.clone())); -+ context.pm1a_cnt_blk = pm1a_cnt_blk; -+ context.pm1b_cnt_blk = pm1b_cnt_blk; -+ context.reset_reg = reset_reg; -+ context.reset_value = reset_value; - - context.tables.push(dsdt_sdt); - } diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs index c42b379a..f4dff276 100644 --- a/drivers/acpid/src/acpi/dmar/mod.rs @@ -2316,18 +1416,6 @@ index 5a5040c3..5f1232bd 100644 + assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None); + } +} -diff --git a/drivers/amlserde/Cargo.toml b/drivers/amlserde/Cargo.toml -index ea76f6b6..ae63aea8 100644 ---- a/drivers/amlserde/Cargo.toml -+++ b/drivers/amlserde/Cargo.toml -@@ -9,6 +9,6 @@ license = "MIT/Apache-2.0" - edition = "2021" - - [dependencies] --acpi = { git = "https://github.com/jackpot51/acpi.git" } -+acpi = { path = "../acpi" } - serde.workspace = true - toml.workspace = true diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml index acbb4e78..210731ae 100644 --- a/drivers/graphics/ihdgd/config.toml diff --git a/local/patches/orbutils/redox.patch b/local/patches/orbutils/redox.patch new file mode 100644 index 00000000..0c0b0383 --- /dev/null +++ b/local/patches/orbutils/redox.patch @@ -0,0 +1,57 @@ +diff --git a/orbutils/src/orblogin/keymap.rs b/orbutils/src/orblogin/keymap.rs +index 8d5d8cf0..8ef61992 100644 +--- a/orbutils/src/orblogin/keymap.rs ++++ b/orbutils/src/orblogin/keymap.rs +@@ +-use std::{fs, path::PathBuf, process::Command}; ++use std::process::Command; + +diff --git a/orbutils/src/orblogin/main.rs b/orbutils/src/orblogin/main.rs +index 660aa8f1..1a9ca703 100644 +--- a/orbutils/src/orblogin/main.rs ++++ b/orbutils/src/orblogin/main.rs +@@ +-use log::{error, info}; ++use log::{error, info}; ++use std::path::Path; + use std::process::Command; + use std::{env, io, str}; +@@ + fn normal_usernames() -> Vec { + let users = match AllUsers::authenticator(Config::default()) { + Ok(ok) => ok, + Err(_) => return Vec::new(), + }; +@@ + usernames.sort(); + usernames + } ++ ++fn available_session_options(default_launcher: &str) -> Vec<(&'static str, &'static str)> { ++ let mut options = vec![("Orbital", "launcher")]; ++ ++ if default_launcher == "orbital-wayland" || Path::new("/usr/bin/orbital-wayland").exists() { ++ options.push(("Wayland", "orbital-wayland")); ++ } ++ ++ if default_launcher == "orbital-kde" || Path::new("/usr/bin/orbital-kde").exists() { ++ options.push(("KDE", "orbital-kde")); ++ } ++ ++ options ++} +@@ +- let session_options = vec![ +- ("Orbital", "launcher"), +- ("Wayland", "orbital-wayland"), +- ("KDE", "orbital-kde"), +- ]; ++ let session_options = available_session_options(launcher_cmd); +@@ + let _ = + syscall::fcntl(before_ns_fd.raw(), syscall::F_SETFD, syscall::O_CLOEXEC); + before_ns_fd + }; ++ #[cfg(not(target_os = "redox"))] ++ let _ = username; + match command.spawn() { diff --git a/local/patches/relibc/P0-strtold-cpp-linkage-and-compat.patch b/local/patches/relibc/P0-strtold-cpp-linkage-and-compat.patch index 077268f5..b02e3971 100644 --- a/local/patches/relibc/P0-strtold-cpp-linkage-and-compat.patch +++ b/local/patches/relibc/P0-strtold-cpp-linkage-and-compat.patch @@ -17,27 +17,27 @@ index 62e98108..a9c72392 100644 return (double)(*val); } diff --git a/src/header/stdlib/cbindgen.toml b/src/header/stdlib/cbindgen.toml -index 2e02e68a..07867c32 100644 +index 2e02e68a..c2643c49 100644 --- a/src/header/stdlib/cbindgen.toml +++ b/src/header/stdlib/cbindgen.toml -@@ -1,7 +1,20 @@ +@@ -1,17 +1,4 @@ sys_includes = ["stddef.h", "alloca.h", "wchar.h", "features.h"] include_guard = "_RELIBC_STDLIB_H" - trailer = """ -+#ifndef _RELIBC_STDLIB_STRTOLD_H -+#define _RELIBC_STDLIB_STRTOLD_H -+ -+#ifdef __cplusplus -+extern \"C\" { -+#endif -+ - long double strtold(const char *nptr, char **endptr); -+ -+#ifdef __cplusplus -+} -+#endif -+ -+#endif - """ +-trailer = """ +-#ifndef _RELIBC_STDLIB_STRTOLD_H +-#define _RELIBC_STDLIB_STRTOLD_H +- +-#ifdef __cplusplus +-extern \"C\" { +-#endif +- +-long double strtold(const char *nptr, char **endptr); +- +-#ifdef __cplusplus +-} +-#endif +- +-#endif +-""" language = "C" style = "Type" diff --git a/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c b/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c index adefc77f..7c88ba42 100644 --- a/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c +++ b/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c @@ -222,29 +222,14 @@ int amdgpu_dc_init(void *mmio_base, size_t mmio_size) { const struct firmware *fw = NULL; int fw_ret = request_firmware(&fw, firmware_name, NULL); - bool firmware_required = - g_pci_dev && pci_has_quirk(g_pci_dev, PCI_QUIRK_NEED_FIRMWARE); if (fw_ret != 0 || !fw) { - if (firmware_required) { - pr_err("amdgpu_redox: firmware %s is required by quirk policy (flags=%#llx, err=%d)\n", - firmware_name, - (unsigned long long)quirk_flags, - fw_ret); - return fw_ret != 0 ? fw_ret : -ENOENT; - } - pr_warn("amdgpu_redox: firmware %s not available (err=%d), continuing without (quirks=%#llx)\n", + pr_warn("amdgpu_redox: firmware %s not available in backend load path (err=%d), continuing with Rust-side quirk policy already applied (quirks=%#llx)\n", firmware_name, fw_ret, (unsigned long long)quirk_flags); } else { - if (firmware_required) { - printk("amdgpu_redox: firmware %s loaded (%zu bytes) to satisfy NEED_FIRMWARE quirk\n", - firmware_name, - fw->size); - } else { - printk("amdgpu_redox: firmware %s loaded (%zu bytes)\n", firmware_name, fw->size); - } + printk("amdgpu_redox: firmware %s loaded (%zu bytes)\n", firmware_name, fw->size); release_firmware(fw); } } @@ -280,7 +265,6 @@ int amdgpu_redox_init(void *mmio_base, size_t mmio_size, uint64_t fb_phys, size_ } g_pci_dev->mmio_base = g_mmio_base; - g_pci_dev->resource_start[0] = (phys_addr_t)(uintptr_t)g_mmio_base; g_pci_dev->resource_len[0] = g_mmio_size; g_device.pci_dev = g_pci_dev; diff --git a/local/recipes/gpu/amdgpu/source/redox_stubs.c b/local/recipes/gpu/amdgpu/source/redox_stubs.c index c83cc045..9b166813 100644 --- a/local/recipes/gpu/amdgpu/source/redox_stubs.c +++ b/local/recipes/gpu/amdgpu/source/redox_stubs.c @@ -222,6 +222,8 @@ void redox_dma_free_coherent(size_t size, void *vaddr, dma_addr_t dma_handle) static struct pci_dev g_pci_dev; static int g_pci_dev_populated; +#define REDOX_MAX_FIRMWARE_BYTES (64U * 1024U * 1024U) + void redox_pci_set_device_info(u16 vendor, u16 device, u8 bus_number, u8 dev_number, u8 func_number, u8 revision, u32 irq, @@ -319,6 +321,11 @@ int redox_request_firmware(const struct firmware **fw, const char *name, void *d return -EIO; } + if ((unsigned long long)st.st_size > REDOX_MAX_FIRMWARE_BYTES) { + close(fd); + return -EFBIG; + } + image = calloc(1, sizeof(*image)); data = malloc((size_t)st.st_size); if (!image || !data) { diff --git a/local/recipes/gpu/redox-drm/source/Cargo.toml b/local/recipes/gpu/redox-drm/source/Cargo.toml index ea28e85b..1f16a4d7 100644 --- a/local/recipes/gpu/redox-drm/source/Cargo.toml +++ b/local/recipes/gpu/redox-drm/source/Cargo.toml @@ -14,6 +14,7 @@ redox_scheme = { package = "redox-scheme", version = "0.1" } log = "0.4" thiserror = "2" bitflags = "2" +getrandom = "0.2" [patch.crates-io] redox-driver-sys = { path = "../../../drivers/redox-driver-sys/source" } diff --git a/local/recipes/gpu/redox-drm/source/src/driver.rs b/local/recipes/gpu/redox-drm/source/src/driver.rs index 464d1409..9347bcda 100644 --- a/local/recipes/gpu/redox-drm/source/src/driver.rs +++ b/local/recipes/gpu/redox-drm/source/src/driver.rs @@ -5,6 +5,42 @@ use crate::kms::{ConnectorInfo, ModeInfo}; pub type Result = std::result::Result; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DriverEvent { + Vblank { crtc_id: u32, count: u64 }, + Hotplug { connector_id: u32 }, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct RedoxPrivateCsSubmit { + pub src_handle: GemHandle, + pub dst_handle: GemHandle, + pub src_offset: u64, + pub dst_offset: u64, + pub byte_count: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct RedoxPrivateCsSubmitResult { + pub seqno: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct RedoxPrivateCsWait { + pub seqno: u64, + pub timeout_ns: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct RedoxPrivateCsWaitResult { + pub completed: bool, + pub completed_seqno: u64, +} + #[derive(Debug, Error)] pub enum DriverError { #[error("driver initialization failed: {0}")] @@ -16,7 +52,6 @@ pub enum DriverError { #[error("resource not found: {0}")] NotFound(String), - #[allow(dead_code)] #[error("operation not supported: {0}")] Unsupported(&'static str), @@ -58,5 +93,23 @@ pub trait GpuDriver: Send + Sync { #[allow(dead_code)] fn get_edid(&self, connector_id: u32) -> Vec; - fn handle_irq(&self) -> Result>; + fn handle_irq(&self) -> Result>; + + fn redox_private_cs_submit( + &self, + _submit: &RedoxPrivateCsSubmit, + ) -> Result { + Err(DriverError::Unsupported( + "private command submission is unavailable on this backend", + )) + } + + fn redox_private_cs_wait( + &self, + _wait: &RedoxPrivateCsWait, + ) -> Result { + Err(DriverError::Unsupported( + "private command completion waits are unavailable on this backend", + )) + } } 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 e491e46e..13cc16fa 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 @@ -10,7 +10,7 @@ use log::{debug, info, warn}; use redox_driver_sys::memory::MmioRegion; use redox_driver_sys::pci::{PciBarInfo, PciDevice, PciDeviceInfo}; -use crate::driver::{DriverError, GpuDriver, Result}; +use crate::driver::{DriverError, DriverEvent, GpuDriver, Result}; use crate::drivers::interrupt::InterruptHandle; use crate::gem::{GemHandle, GemManager}; use crate::kms::connector::{synthetic_edid, Connector}; @@ -41,17 +41,10 @@ const AMD_HPD_RX_INT_ACK_MASK: u32 = 0x0000_0100; const AMD_IH_STATUS_INTERRUPT_PENDING_MASK: u32 = 0x0000_0001; const AMD_IH_STATUS_RING_OVERFLOW_MASK: u32 = 0x0000_0002; -#[derive(Clone, Debug)] -pub enum IrqEvent { - Vblank { crtc_id: u32, count: u64 }, - Hotplug { connector_id: u32 }, - Unknown, -} - pub struct AmdDriver { info: PciDeviceInfo, mmio: MmioRegion, - irq_handle: Option, + irq_handle: Mutex>, display: DisplayCore, gem: Mutex, connectors: Mutex>, @@ -119,6 +112,7 @@ impl AmdDriver { info.location )) })?); + let irq_mode = irq_handle.as_ref().map(|handle| handle.mode_name()).unwrap_or("none"); let display = DisplayCore::with_framebuffer(mmio.as_ptr(), mmio.size(), fb_phys, fb_size)?; let (connectors, encoders) = detect_display_topology(&display)?; @@ -140,16 +134,17 @@ impl AmdDriver { } info!( - "redox-drm: AMD driver ready for {} with {} connector(s), {} firmware blob(s) loaded", + "redox-drm: AMD driver ready for {} with {} connector(s), {} firmware blob(s) loaded, IRQ mode {}", info.location, connectors.len(), - fw_count + fw_count, + irq_mode ); Ok(Self { info, mmio, - irq_handle, + irq_handle: Mutex::new(irq_handle), display, gem: Mutex::new(GemManager::new()), connectors: Mutex::new(connectors), @@ -163,7 +158,7 @@ impl AmdDriver { }) } - pub fn process_irq(&self) -> Result { + pub fn process_irq(&self) -> Result> { let ih_status = self.read_mmio_reg(AMD_IH_STATUS); let ih_cntl = self.read_mmio_reg(AMD_IH_CNTL); let ih_rptr = self.read_mmio_reg(AMD_IH_RB_RPTR); @@ -188,7 +183,7 @@ impl AmdDriver { connector_id, ih_status, ih_cntl, ih_rptr, ih_wptr ); - return Ok(IrqEvent::Hotplug { connector_id }); + return Ok(Some(DriverEvent::Hotplug { connector_id })); } if ring_pending || (ih_status & AMD_IH_STATUS_INTERRUPT_PENDING_MASK != 0) { @@ -201,12 +196,12 @@ impl AmdDriver { crtc_id, count, ih_status, ih_cntl, ih_rptr, ih_wptr ); - return Ok(IrqEvent::Vblank { crtc_id, count }); + return Ok(Some(DriverEvent::Vblank { crtc_id, count })); } } self.acknowledge_ih(ih_wptr); - Ok(IrqEvent::Unknown) + Ok(None) } fn read_mmio_reg(&self, register_index: usize) -> u32 { @@ -541,32 +536,53 @@ impl GpuDriver for AmdDriver { } } - fn handle_irq(&self) -> Result> { + fn handle_irq(&self) -> Result> { + let irq_event = { + let mut irq_handle = self + .irq_handle + .lock() + .map_err(|_| DriverError::Initialization("AMD IRQ state poisoned".into()))?; + match irq_handle.as_mut() { + Some(handle) => handle.try_wait()?, + None => return Ok(None), + } + }; + + if !irq_event { + return Ok(None); + } + + let irq = self + .irq_handle + .lock() + .ok() + .and_then(|guard| guard.as_ref().map(|h| h.irq())); + match self.process_irq()? { - IrqEvent::Vblank { crtc_id, count } => { + Some(DriverEvent::Vblank { crtc_id, count }) => { debug!( "redox-drm: handled AMD vblank IRQ for {} CRTC {} count={} irq={:?}", self.info.location, crtc_id, count, - self.irq_handle.as_ref().map(|h| h.irq()) + irq ); - Ok(Some((crtc_id, count))) + Ok(Some(DriverEvent::Vblank { crtc_id, count })) } - IrqEvent::Hotplug { connector_id } => { + Some(DriverEvent::Hotplug { connector_id }) => { info!( "redox-drm: handled AMD hotplug IRQ for {} connector {} irq={:?}", self.info.location, connector_id, - self.irq_handle.as_ref().map(|h| h.irq()) + irq ); - Ok(None) + Ok(Some(DriverEvent::Hotplug { connector_id })) } - IrqEvent::Unknown => { + None => { debug!( "redox-drm: handled AMD IRQ for {} with no decoded source irq={:?}", self.info.location, - self.irq_handle.as_ref().map(|h| h.irq()) + irq ); Ok(None) } 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 fdc5d463..6998a49e 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 @@ -9,8 +9,9 @@ use std::sync::Mutex; use log::{debug, info, warn}; use redox_driver_sys::memory::MmioRegion; use redox_driver_sys::pci::{PciBarInfo, PciDevice, PciDeviceInfo}; +use redox_driver_sys::quirks::PciQuirkFlags; -use crate::driver::{DriverError, GpuDriver, Result}; +use crate::driver::{DriverError, DriverEvent, GpuDriver, Result}; use crate::drivers::interrupt::InterruptHandle; use crate::gem::{GemHandle, GemManager}; use crate::kms::connector::{synthetic_edid, Connector}; @@ -57,6 +58,27 @@ impl IntelDriver { ))); } + let quirks = info.quirks(); + if !quirks.is_empty() { + info!( + "redox-drm: Intel init for {} using quirk policy {:?}", + info.location, quirks + ); + } + if quirks.contains(PciQuirkFlags::DISABLE_ACCEL) { + return Err(DriverError::Pci(format!( + "device {:#06x}:{:#06x} at {} has DISABLE_ACCEL quirk — refusing Intel init", + info.vendor_id, info.device_id, info.location + ))); + } + if quirks.contains(PciQuirkFlags::NEED_FIRMWARE) { + info!( + "redox-drm: Intel device {} entered init with explicit firmware policy and {} cached blob(s)", + info.location, + firmware.len() + ); + } + let gtt_bar = find_memory_bar(&info, 0, "GGTT BAR0")?; let mmio_bar = find_memory_bar(&info, 2, "MMIO BAR2")?; validate_intel_bars(&info, >t_bar, &mmio_bar)?; @@ -93,18 +115,21 @@ impl IntelDriver { None } }; + let irq_mode = irq_handle.as_ref().map(|handle| handle.mode_name()).unwrap_or("none"); if !firmware.is_empty() { - warn!( - "redox-drm: Intel driver ignores {} firmware blob(s); i915-class GPUs usually boot without scheme:firmware blobs", - firmware.len() + info!( + "redox-drm: Intel startup firmware cache populated with {} blob(s) for {}", + firmware.len(), + info.location ); } info!( - "redox-drm: Intel driver ready for {} with {} connector(s)", + "redox-drm: Intel driver ready for {} with {} connector(s), IRQ mode {}", info.location, - connectors.len() + connectors.len(), + irq_mode ); Ok(Self { @@ -177,7 +202,7 @@ impl IntelDriver { Ok(connector.info.connector_type_id.saturating_sub(1) as u8) } - fn process_irq(&self) -> Result> { + fn process_irq(&self) -> Result> { let previous = self.cached_connectors(); let current = self.refresh_connectors()?; @@ -186,6 +211,20 @@ impl IntelDriver { "redox-drm: Intel hotplug event detected on {}", self.info.location ); + if let Some(connector) = current + .iter() + .find(|connector| { + previous + .iter() + .find(|old| old.id == connector.id) + .map(|old| old.connection != connector.connection) + .unwrap_or(true) + }) + { + return Ok(Some(DriverEvent::Hotplug { + connector_id: connector.id, + })); + } } let ring_busy = self @@ -200,7 +239,7 @@ impl IntelDriver { "redox-drm: Intel IRQ decoded as display event crtc={} ring_busy={}", crtc_id, ring_busy ); - return Ok(Some((crtc_id, count))); + return Ok(Some(DriverEvent::Vblank { crtc_id, count })); } if ring_busy { @@ -459,7 +498,7 @@ impl GpuDriver for IntelDriver { } } - fn handle_irq(&self) -> Result> { + fn handle_irq(&self) -> Result> { let irq_event = { let mut irq_handle = self .irq_handle diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/interrupt.rs b/local/recipes/gpu/redox-drm/source/src/drivers/interrupt.rs index 67b94da5..657c34da 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/interrupt.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/interrupt.rs @@ -23,10 +23,22 @@ pub enum InterruptHandle { }, } +fn force_legacy_irq(quirks: PciQuirkFlags) -> bool { + quirks.contains(PciQuirkFlags::FORCE_LEGACY_IRQ) +} + impl InterruptHandle { pub fn setup(device_info: &PciDeviceInfo, pci_device: &mut PciDevice) -> Result { let quirks = device_info.quirks(); + if force_legacy_irq(quirks) { + info!( + "redox-drm: forcing legacy IRQ for {} (FORCE_LEGACY_IRQ quirk)", + device_info.location + ); + return Self::try_legacy(device_info); + } + if !quirks.contains(PciQuirkFlags::NO_MSIX) { if let Ok(Some(handle)) = Self::try_msix(device_info, pci_device) { return Ok(handle); @@ -49,13 +61,6 @@ impl InterruptHandle { ); } - if quirks.contains(PciQuirkFlags::FORCE_LEGACY_IRQ) { - info!( - "redox-drm: forcing legacy IRQ for {} (FORCE_LEGACY_IRQ quirk)", - device_info.location - ); - } - Self::try_legacy(device_info) } @@ -196,7 +201,6 @@ impl InterruptHandle { .map_err(|e| DriverError::Io(e.to_string())) } InterruptHandle::Msi { handle, .. } | InterruptHandle::Legacy { handle, .. } => { - let mut buf = [0u8; 8]; let _ = handle.wait().map_err(|e| DriverError::Io(e.to_string()))?; Ok(()) } @@ -210,7 +214,31 @@ impl InterruptHandle { } } + pub fn mode_name(&self) -> &'static str { + match self { + InterruptHandle::Msix { .. } => "MSI-X", + InterruptHandle::Msi { .. } => "MSI", + InterruptHandle::Legacy { .. } => "legacy INTx", + } + } + pub fn is_msix(&self) -> bool { matches!(self, InterruptHandle::Msix { .. }) } } + +#[cfg(test)] +mod tests { + use super::force_legacy_irq; + use redox_driver_sys::quirks::PciQuirkFlags; + + #[test] + fn force_legacy_irq_only_triggers_on_quirk() { + assert!(!force_legacy_irq(PciQuirkFlags::empty())); + assert!(!force_legacy_irq(PciQuirkFlags::NO_MSI)); + assert!(force_legacy_irq(PciQuirkFlags::FORCE_LEGACY_IRQ)); + assert!(force_legacy_irq( + PciQuirkFlags::FORCE_LEGACY_IRQ | PciQuirkFlags::NO_MSIX + )); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/gem.rs b/local/recipes/gpu/redox-drm/source/src/gem.rs index 4a166dd6..b631cb67 100644 --- a/local/recipes/gpu/redox-drm/source/src/gem.rs +++ b/local/recipes/gpu/redox-drm/source/src/gem.rs @@ -7,6 +7,8 @@ use crate::driver::{DriverError, Result}; pub type GemHandle = u32; +const MAX_GEM_BYTES: u64 = 256 * 1024 * 1024; + #[derive(Clone, Debug)] pub struct GemObject { #[allow(dead_code)] @@ -43,6 +45,11 @@ impl GemManager { "GEM create size must be non-zero", )); } + if size > MAX_GEM_BYTES { + return Err(DriverError::InvalidArgument( + "GEM create size exceeds the trusted shared-core limit", + )); + } let handle = self.next_handle; self.next_handle = self.next_handle.saturating_add(1); @@ -113,13 +120,4 @@ impl GemManager { pub fn gpu_addr(&self, handle: GemHandle) -> Result> { Ok(self.object(handle)?.gpu_addr) } - - #[allow(dead_code)] - pub fn object_mut_ptr(&mut self, handle: GemHandle) -> Result { - let allocation = self - .objects - .get_mut(&handle) - .ok_or_else(|| DriverError::NotFound(format!("unknown GEM handle {handle}")))?; - Ok(allocation.dma.as_mut_ptr() as usize) - } } diff --git a/local/recipes/gpu/redox-drm/source/src/main.rs b/local/recipes/gpu/redox-drm/source/src/main.rs index 993ad302..d44103b2 100644 --- a/local/recipes/gpu/redox-drm/source/src/main.rs +++ b/local/recipes/gpu/redox-drm/source/src/main.rs @@ -20,12 +20,15 @@ use redox_driver_sys::pci::{ enumerate_pci_class, PciDevice, PciDeviceInfo, PciLocation, PCI_CLASS_DISPLAY, PCI_VENDOR_ID_AMD, PCI_VENDOR_ID_INTEL, }; +use redox_driver_sys::quirks::PciQuirkFlags; use redox_scheme::{SignalBehavior, Socket}; -use crate::driver::{DriverError, GpuDriver, Result}; +use crate::driver::{DriverError, DriverEvent, GpuDriver, Result}; use crate::drivers::DriverRegistry; use crate::scheme::DrmScheme; +const MAX_FIRMWARE_BLOB_BYTES: u64 = 64 * 1024 * 1024; + struct StderrLogger { level: LevelFilter, } @@ -70,13 +73,16 @@ fn run() -> Result<()> { .map_err(|e| DriverError::Initialization(format!("failed to register drm scheme: {e}")))?; info!("redox-drm: registered scheme:drm"); - let (vblank_tx, vblank_rx) = mpsc::sync_channel::<(u32, u64)>(8); + let (event_tx, event_rx) = mpsc::sync_channel::(8); let irq_driver: Arc = driver.clone(); std::thread::spawn(move || loop { match irq_driver.handle_irq() { - Ok(Some((crtc_id, count))) => { - let _ = vblank_tx.try_send((crtc_id, count)); + Ok(Some(event)) => { + if event_tx.send(event).is_err() { + error!("redox-drm: event consumer dropped, stopping IRQ event thread"); + break; + } } Ok(None) => {} Err(e) => { @@ -87,12 +93,12 @@ fn run() -> Result<()> { }); let drm_scheme = Arc::new(Mutex::new(DrmScheme::new(driver))); - let vblank_scheme = drm_scheme.clone(); + let event_scheme = drm_scheme.clone(); std::thread::spawn(move || loop { - if let Ok((crtc_id, vblank_count)) = vblank_rx.recv() { - if let Ok(mut scheme) = vblank_scheme.lock() { - scheme.retire_vblank(crtc_id, vblank_count); + if let Ok(event) = event_rx.recv() { + if let Ok(mut scheme) = event_scheme.lock() { + scheme.handle_driver_event(event); } } }); @@ -209,20 +215,42 @@ struct FirmwareCache { blobs: HashMap>, } -impl FirmwareCache { - fn load_for_device(info: &PciDeviceInfo) -> Result { - if info.vendor_id != PCI_VENDOR_ID_AMD { - info!( - "redox-drm: skipping firmware load for Intel GPU {}", - info.location - ); - return Ok(Self { - blobs: HashMap::new(), - }); - } +struct FirmwareExpectation { + vendor_name: &'static str, + keys: &'static [&'static str], + required: bool, + required_label: &'static str, +} - let firmware_keys: &[&str] = if info.vendor_id == PCI_VENDOR_ID_AMD { - &[ +const AMD_DISPLAY_FIRMWARE_KEYS: &[&str] = &[ + "amdgpu/dcn_3_1_dmcub", + "amdgpu/dmcub_dcn20.bin", + "amdgpu/dmcub_dcn31.bin", +]; + +const INTEL_TGL_DMC_KEYS: &[&str] = &["i915/tgl_dmc.bin", "i915/tgl_dmc_ver2_12.bin"]; +const INTEL_ADLP_DMC_KEYS: &[&str] = &["i915/adlp_dmc.bin", "i915/adlp_dmc_ver2_16.bin"]; +const INTEL_DG2_DMC_KEYS: &[&str] = &["i915/dg2_dmc.bin", "i915/dg2_dmc_ver2_06.bin"]; +const INTEL_MTL_DMC_KEYS: &[&str] = &["i915/mtl_dmc.bin"]; + +fn intel_display_firmware_keys(device_id: u16) -> Option<&'static [&'static str]> { + match device_id { + 0x9A40 | 0x9A49 | 0x9A60 | 0x9A68 | 0x9A70 | 0x9A78 => Some(INTEL_TGL_DMC_KEYS), + 0x46A6 => Some(INTEL_ADLP_DMC_KEYS), + 0x5690 | 0x5691 | 0x5692 | 0x5693 | 0x5694 | 0x5696 | 0x5697 | 0x56A0 | 0x56A1 + | 0x56A5 | 0x56A6 | 0x56B0 | 0x56B1 | 0x56B2 | 0x56B3 | 0x56C0 | 0x56C1 => { + Some(INTEL_DG2_DMC_KEYS) + } + 0x7D55 | 0x7D45 | 0x7D40 => Some(INTEL_MTL_DMC_KEYS), + _ => None, + } +} + +fn firmware_expectation(info: &PciDeviceInfo, quirks: PciQuirkFlags) -> FirmwareExpectation { + match info.vendor_id { + PCI_VENDOR_ID_AMD => FirmwareExpectation { + vendor_name: "AMD", + keys: &[ "amdgpu/psp_13_0_0_sos", "amdgpu/psp_13_0_0_ta", "amdgpu/gc_11_0_0_pfp", @@ -238,47 +266,165 @@ impl FirmwareCache { "amdgpu/sdma_5_2", "amdgpu/vcn_3_0_0", "amdgpu/vcn_3_1_0", - ] - } else { - &[] - }; + ], + required: quirks.contains(PciQuirkFlags::NEED_FIRMWARE), + required_label: "AMD firmware", + }, + PCI_VENDOR_ID_INTEL => { + let keys = intel_display_firmware_keys(info.device_id).unwrap_or(&[]); + FirmwareExpectation { + vendor_name: "Intel", + keys, + required: !keys.is_empty(), + required_label: "Intel display DMC firmware", + } + } + _ => FirmwareExpectation { + vendor_name: "unknown", + keys: &[], + required: false, + required_label: "firmware", + }, + } +} + +fn summarize_missing_firmware(missing: &[String]) -> String { + const MAX_SHOWN: usize = 3; + + if missing.is_empty() { + return "none".to_string(); + } + + let shown: Vec<&str> = missing.iter().take(MAX_SHOWN).map(String::as_str).collect(); + if missing.len() > MAX_SHOWN { + format!("{} (+{} more)", shown.join(", "), missing.len() - MAX_SHOWN) + } else { + shown.join(", ") + } +} + +fn firmware_requirement_error( + expectation: &FirmwareExpectation, + loaded: &HashMap>, + missing: &[String], +) -> Option { + if !expectation.required { + return None; + } + + if loaded.is_empty() { + return Some(format!( + "no {} firmware blobs available from scheme:firmware; checked {} candidates ({})", + expectation.required_label, + expectation.keys.len(), + summarize_missing_firmware(missing) + )); + } + + if expectation.vendor_name == "AMD" + && !AMD_DISPLAY_FIRMWARE_KEYS + .iter() + .any(|key| loaded.contains_key(*key)) + { + return Some(format!( + "AMD firmware policy requires a DMCUB/display blob before backend init; checked {} candidates ({})", + expectation.keys.len(), + summarize_missing_firmware(missing) + )); + } + + None +} + +impl FirmwareCache { + fn load_for_device(info: &PciDeviceInfo) -> Result { + let quirks = info.quirks(); + let expectation = firmware_expectation(info, quirks); + + if expectation.keys.is_empty() { + if expectation.required { + info!( + "redox-drm: {} GPU {} declares NEED_FIRMWARE in canonical quirk policy, but no Rust-side firmware manifest is defined for this vendor yet", + expectation.vendor_name, + info.location + ); + } else { + info!( + "redox-drm: skipping firmware preload for {} GPU {} (no Rust-side firmware manifest)", + expectation.vendor_name, + info.location + ); + } + return Ok(Self { + blobs: HashMap::new(), + }); + } let mut blobs = HashMap::new(); - let mut loaded_any = false; + let mut missing = Vec::new(); - for &key in firmware_keys { + info!( + "redox-drm: firmware preload for {} GPU {} expects {} candidate blob(s); required_by_quirk={}", + expectation.vendor_name, + info.location, + expectation.keys.len(), + expectation.required + ); + + for &key in expectation.keys { let path = format!("/scheme/firmware/{}", key); match File::open(&path) { Ok(mut file) => { let metadata = file.metadata(); - let estimated_size = metadata.map(|m| m.len() as usize).unwrap_or(1024 * 1024); - let mut buf = Vec::with_capacity(estimated_size); + let estimated_size = metadata.map(|m| m.len()).unwrap_or(1024 * 1024); + if estimated_size > MAX_FIRMWARE_BLOB_BYTES { + info!( + "redox-drm: firmware {} rejected — {} bytes exceeds trusted preload cap {}", + key, + estimated_size, + MAX_FIRMWARE_BLOB_BYTES + ); + missing.push(key.to_string()); + continue; + } + let mut buf = Vec::with_capacity(estimated_size as usize); match file.read_to_end(&mut buf) { Ok(bytes_read) => { info!("redox-drm: loaded firmware {} ({} bytes)", key, bytes_read); - loaded_any = true; blobs.insert(key.to_string(), buf); } Err(e) => { info!("redox-drm: failed to read firmware {}: {}", key, e); + missing.push(key.to_string()); } } } Err(e) => { info!("redox-drm: firmware {} not available: {}", key, e); + missing.push(key.to_string()); } } } - if !loaded_any && info.vendor_id == PCI_VENDOR_ID_AMD { - return Err(DriverError::NotFound( - "no AMD firmware blobs available from scheme:firmware".to_string(), - )); + if let Some(message) = firmware_requirement_error(&expectation, &blobs, &missing) { + return Err(DriverError::NotFound(message)); + } + + if !missing.is_empty() { + info!( + "redox-drm: firmware preload for {} GPU {} left {} blob(s) unavailable: {}", + expectation.vendor_name, + info.location, + missing.len(), + summarize_missing_firmware(&missing) + ); } info!( - "redox-drm: firmware cache populated with {} blob(s)", - blobs.len() + "redox-drm: firmware cache populated with {} blob(s) for {} GPU {}", + blobs.len(), + expectation.vendor_name, + info.location ); Ok(Self { blobs }) } @@ -309,3 +455,128 @@ fn main() { process::exit(1); } } + +#[cfg(test)] +mod tests { + use super::*; + + fn test_gpu_info(vendor_id: u16, device_id: u16) -> PciDeviceInfo { + PciDeviceInfo { + location: PciLocation { + segment: 0, + bus: 0, + device: 0, + function: 0, + }, + vendor_id, + device_id, + subsystem_vendor_id: 0, + subsystem_device_id: 0, + revision: 0, + class_code: PCI_CLASS_DISPLAY, + subclass: 0, + prog_if: 0, + header_type: 0, + irq: None, + bars: Vec::new(), + capabilities: Vec::new(), + } + } + + #[test] + fn firmware_expectation_marks_amd_need_firmware_as_required() { + let expectation = firmware_expectation( + &PciDeviceInfo { + location: PciLocation { + segment: 0, + bus: 0, + device: 0, + function: 0, + }, + vendor_id: PCI_VENDOR_ID_AMD, + device_id: 0x744C, + subsystem_vendor_id: 0, + subsystem_device_id: 0, + revision: 0, + class_code: PCI_CLASS_DISPLAY, + subclass: 0, + prog_if: 0, + header_type: 0, + irq: None, + bars: Vec::new(), + capabilities: Vec::new(), + }, + PciQuirkFlags::from_bits_truncate(PciQuirkFlags::NEED_FIRMWARE.bits()), + ); + + assert_eq!(expectation.vendor_name, "AMD"); + assert!(expectation.required); + assert!(!expectation.keys.is_empty()); + } + + #[test] + fn summarize_missing_firmware_truncates_long_lists() { + let summary = summarize_missing_firmware(&[ + "a".to_string(), + "b".to_string(), + "c".to_string(), + "d".to_string(), + ]); + + assert_eq!(summary, "a, b, c (+1 more)"); + } + + #[test] + fn amd_required_firmware_needs_display_blob() { + let expectation = firmware_expectation( + &PciDeviceInfo { + location: PciLocation { + segment: 0, + bus: 0, + device: 0, + function: 0, + }, + vendor_id: PCI_VENDOR_ID_AMD, + device_id: 0x744C, + subsystem_vendor_id: 0, + subsystem_device_id: 0, + revision: 0, + class_code: PCI_CLASS_DISPLAY, + subclass: 0, + prog_if: 0, + header_type: 0, + irq: None, + bars: Vec::new(), + capabilities: Vec::new(), + }, + PciQuirkFlags::from_bits_truncate(PciQuirkFlags::NEED_FIRMWARE.bits()), + ); + let mut loaded = HashMap::new(); + loaded.insert("amdgpu/gc_11_0_0_pfp".to_string(), vec![1, 2, 3]); + let missing = vec!["amdgpu/dmcub_dcn31.bin".to_string()]; + + let error = firmware_requirement_error(&expectation, &loaded, &missing); + + assert!(error.is_some()); + assert!(error.unwrap().contains("DMCUB/display blob")); + } + + #[test] + fn intel_tgl_manifest_is_required_from_startup() { + let expectation = firmware_expectation(&test_gpu_info(PCI_VENDOR_ID_INTEL, 0x9A49), PciQuirkFlags::empty()); + + assert_eq!(expectation.vendor_name, "Intel"); + assert!(expectation.required); + assert_eq!(expectation.required_label, "Intel display DMC firmware"); + assert!(expectation.keys.contains(&"i915/tgl_dmc_ver2_12.bin")); + } + + #[test] + fn unknown_intel_device_has_no_startup_manifest_yet() { + let expectation = firmware_expectation(&test_gpu_info(PCI_VENDOR_ID_INTEL, 0x3E92), PciQuirkFlags::empty()); + + assert_eq!(expectation.vendor_name, "Intel"); + assert!(!expectation.required); + assert!(expectation.keys.is_empty()); + } +} diff --git a/local/recipes/gpu/redox-drm/source/src/scheme.rs b/local/recipes/gpu/redox-drm/source/src/scheme.rs index 163f4f04..aa4cfe55 100644 --- a/local/recipes/gpu/redox-drm/source/src/scheme.rs +++ b/local/recipes/gpu/redox-drm/source/src/scheme.rs @@ -2,13 +2,17 @@ use std::collections::{BTreeMap, HashSet}; use std::mem::size_of; use std::sync::Arc; +use getrandom::getrandom; use log::{debug, warn}; use redox_scheme::SchemeBlockMut; use syscall04::data::Stat; use syscall04::error::{Error, Result, EBADF, EBUSY, EINVAL, ENOENT, EOPNOTSUPP}; use syscall04::flag::{EventFlags, MapFlags, MunmapFlags, MODE_FILE}; -use crate::driver::GpuDriver; +use crate::driver::{ + DriverEvent, GpuDriver, RedoxPrivateCsSubmit, RedoxPrivateCsSubmitResult, RedoxPrivateCsWait, + RedoxPrivateCsWaitResult, +}; use crate::gem::GemHandle; use crate::kms::ModeInfo; @@ -43,6 +47,12 @@ const DRM_IOCTL_GEM_CLOSE: usize = DRM_IOCTL_BASE + 27; const DRM_IOCTL_GEM_MMAP: usize = DRM_IOCTL_BASE + 28; const DRM_IOCTL_PRIME_HANDLE_TO_FD: usize = DRM_IOCTL_BASE + 29; const DRM_IOCTL_PRIME_FD_TO_HANDLE: usize = DRM_IOCTL_BASE + 30; +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 MAX_SCHEME_GEM_BYTES: u64 = 256 * 1024 * 1024; // ---- Wire types for DRM ioctls ---- #[repr(C)] @@ -238,6 +248,29 @@ struct DrmPrimeFdToHandleResponseWire { _pad: u32, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct RedoxAmdSdmaSubmitWire { + src_handle: u32, + dst_handle: u32, + flags: u32, + _pad: u32, + src_offset: u64, + dst_offset: u64, + size: u64, + seqno: u64, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct RedoxAmdSdmaWaitWire { + seqno: u64, + timeout_ns: u64, + flags: u32, + completed: u32, + completed_seqno: u64, +} + // ---- Internal handle types ---- #[derive(Clone, Debug)] @@ -257,6 +290,7 @@ struct Handle { mapped_gem_refs: usize, owned_fbs: Vec, owned_gems: Vec, + imported_gems: HashSet, closing: bool, } @@ -264,7 +298,6 @@ pub struct DrmScheme { driver: Arc, next_id: usize, next_fb_id: u32, - next_export_token: u32, handles: BTreeMap, active_crtc_fb: BTreeMap, active_crtc_mode: BTreeMap, @@ -281,7 +314,6 @@ impl DrmScheme { driver, next_id: 0, next_fb_id: 1, - next_export_token: 1, handles: BTreeMap::new(), active_crtc_fb: BTreeMap::new(), active_crtc_mode: BTreeMap::new(), @@ -293,25 +325,6 @@ impl DrmScheme { } } - #[allow(dead_code)] - pub fn on_close(&mut self, id: usize) { - let mapped = self - .handles - .get(&id) - .map(|handle| handle.mapped_gem_refs != 0) - .unwrap_or(false); - if mapped { - if let Some(handle) = self.handles.get_mut(&id) { - handle.closing = true; - } - return; - } - - if let Some(handle) = self.handles.remove(&id) { - self.finalize_handle_close(handle); - } - } - fn is_fb_active(&self, fb_id: u32) -> bool { self.active_crtc_fb.values().any(|&id| id == fb_id) || self.pending_flip_fb.values().any(|&(_, id)| id == fb_id) @@ -321,6 +334,14 @@ impl DrmScheme { handle.owned_gems.contains(&gem_handle) } + fn handle_has_local_gem_ref(handle: &Handle, gem_handle: GemHandle) -> bool { + Self::handle_has_gem_ref(handle, gem_handle) && !handle.imported_gems.contains(&gem_handle) + } + + fn handle_has_imported_gem_ref(handle: &Handle, gem_handle: GemHandle) -> bool { + Self::handle_has_gem_ref(handle, gem_handle) && handle.imported_gems.contains(&gem_handle) + } + fn gem_is_still_referenced(&self, gem_handle: GemHandle) -> bool { self.handles .values() @@ -341,6 +362,26 @@ impl DrmScheme { self.gem_export_refs.get(&gem_handle).copied().unwrap_or(0) } + fn allocate_export_token(&self) -> Result { + for _ in 0..64 { + let mut bytes = [0u8; 4]; + getrandom(&mut bytes).map_err(|e| { + warn!("redox-drm: failed to draw PRIME export token entropy: {e}"); + Error::new(syscall04::error::EIO) + })?; + + let token = u32::from_le_bytes(bytes) & 0x7fff_ffff; + if token == 0 || self.prime_exports.contains_key(&token) { + continue; + } + + return Ok(token); + } + + warn!("redox-drm: unable to allocate unique PRIME export token"); + Err(Error::new(EBUSY)) + } + fn bump_export_ref(&mut self, gem_handle: GemHandle) { let entry = self.gem_export_refs.entry(gem_handle).or_insert(0); *entry = entry.saturating_add(1); @@ -372,6 +413,113 @@ impl DrmScheme { && self.gem_export_refcount(gem_handle) == 0 } + fn validate_private_cs_handles( + &self, + id: usize, + src_handle: GemHandle, + dst_handle: GemHandle, + operation: &str, + ) -> Result<()> { + let handle = self.handles.get(&id).ok_or_else(|| Error::new(EBADF))?; + + if !Self::handle_has_gem_ref(handle, src_handle) + || !Self::handle_has_gem_ref(handle, dst_handle) + { + warn!( + "redox-drm: {} rejected — src={} dst={} not owned by this fd", + operation, src_handle, dst_handle + ); + return Err(Error::new(EBADF)); + } + + if Self::handle_has_imported_gem_ref(handle, src_handle) + || Self::handle_has_imported_gem_ref(handle, dst_handle) + { + warn!( + "redox-drm: {} rejected — imported DMA-BUF handles are outside the bounded private CS path", + operation + ); + return Err(Error::new(EOPNOTSUPP)); + } + + Ok(()) + } + + fn validate_private_cs_ranges( + &self, + submit: &RedoxPrivateCsSubmit, + operation: &str, + ) -> Result<()> { + if submit.byte_count == 0 { + warn!("redox-drm: {} rejected — zero-sized submission", operation); + return Err(Error::new(EINVAL)); + } + + let src_size = self + .driver + .gem_size(submit.src_handle) + .map_err(driver_to_syscall)?; + let dst_size = self + .driver + .gem_size(submit.dst_handle) + .map_err(driver_to_syscall)?; + + let src_end = submit + .src_offset + .checked_add(submit.byte_count) + .ok_or_else(|| { + warn!("redox-drm: {} rejected — source range overflow", operation); + Error::new(EINVAL) + })?; + if src_end > src_size { + warn!( + "redox-drm: {} rejected — source range {}..{} exceeds GEM size {}", + operation, + submit.src_offset, + src_end, + src_size + ); + return Err(Error::new(EINVAL)); + } + + let dst_end = submit + .dst_offset + .checked_add(submit.byte_count) + .ok_or_else(|| { + warn!("redox-drm: {} rejected — destination range overflow", operation); + Error::new(EINVAL) + })?; + if dst_end > dst_size { + warn!( + "redox-drm: {} rejected — destination range {}..{} exceeds GEM size {}", + operation, + submit.dst_offset, + dst_end, + dst_size + ); + return Err(Error::new(EINVAL)); + } + + Ok(()) + } + + fn validate_gem_create_size(&self, size: u64, operation: &str) -> Result<()> { + if size == 0 { + warn!("redox-drm: {} rejected — zero-sized GEM allocation", operation); + return Err(Error::new(EINVAL)); + } + if size > MAX_SCHEME_GEM_BYTES { + warn!( + "redox-drm: {} rejected — size {} exceeds trusted shared-core cap {}", + operation, + size, + MAX_SCHEME_GEM_BYTES + ); + return Err(Error::new(EINVAL)); + } + Ok(()) + } + fn maybe_close_gem(&mut self, gem_handle: GemHandle, context: &str) -> bool { if !self.gem_can_close(gem_handle) { return false; @@ -404,6 +552,7 @@ impl DrmScheme { mapped_gem_refs: 0, owned_fbs: Vec::new(), owned_gems: Vec::new(), + imported_gems: HashSet::new(), closing: false, }, ); @@ -485,6 +634,13 @@ impl DrmScheme { } } + pub fn handle_driver_event(&mut self, event: DriverEvent) { + match event { + DriverEvent::Vblank { crtc_id, count } => self.retire_vblank(crtc_id, count), + DriverEvent::Hotplug { .. } => {} + } + } + fn try_reap_fb(&mut self, fb_id: u32) { let gem_handle = match self.fb_registry.get(&fb_id) { Some(info) => info.gem_handle, @@ -507,7 +663,11 @@ impl DrmScheme { crtc_count: 1, encoder_count: connectors.len() as u32, }; - bytes_of(&payload) + let mut out = bytes_of(&payload); + for connector in connectors { + out.extend_from_slice(&bytes_of(&connector.id)); + } + out } fn encode_connector(&self, connector_id: u32) -> Result> { @@ -673,6 +833,7 @@ impl DrmScheme { 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) @@ -758,6 +919,7 @@ impl DrmScheme { } if let Some(handle) = self.handles.get_mut(&id) { handle.owned_gems.retain(|&h| h != req.handle); + handle.imported_gems.remove(&req.handle); } Vec::new() } @@ -925,9 +1087,7 @@ impl DrmScheme { DRM_IOCTL_GEM_CREATE => { let mut req = decode_wire::(payload)?; - if req.size == 0 { - return Err(Error::new(EINVAL)); - } + self.validate_gem_create_size(req.size, "GEM_CREATE")?; req.handle = self .driver .gem_create(req.size) @@ -980,6 +1140,7 @@ impl DrmScheme { } if let Some(handle) = self.handles.get_mut(&id) { handle.owned_gems.retain(|&h| h != req.handle); + handle.imported_gems.remove(&req.handle); } Vec::new() } @@ -1017,6 +1178,65 @@ impl DrmScheme { bytes_of(&req) } + DRM_IOCTL_REDOX_AMD_SDMA_SUBMIT => { + let mut req = decode_wire::(payload)?; + if req.flags != 0 { + warn!( + "redox-drm: AMD SDMA submit rejected — unsupported flags {:#x}", + req.flags + ); + return Err(Error::new(EINVAL)); + } + if req.size == 0 { + warn!("redox-drm: AMD SDMA submit rejected — zero-sized copy"); + return Err(Error::new(EINVAL)); + } + + self.validate_private_cs_handles( + id, + req.src_handle, + req.dst_handle, + "AMD SDMA submit", + )?; + + let submit = RedoxPrivateCsSubmit { + src_handle: req.src_handle, + dst_handle: req.dst_handle, + src_offset: req.src_offset, + dst_offset: req.dst_offset, + byte_count: req.size, + }; + self.validate_private_cs_ranges(&submit, "AMD SDMA submit")?; + req.seqno = self + .driver + .redox_private_cs_submit(&submit) + .map_err(driver_to_syscall)? + .seqno; + bytes_of(&req) + } + + DRM_IOCTL_REDOX_AMD_SDMA_WAIT => { + let mut req = decode_wire::(payload)?; + if req.flags != 0 { + warn!( + "redox-drm: AMD SDMA wait rejected — unsupported flags {:#x}", + req.flags + ); + return Err(Error::new(EINVAL)); + } + + let result = self + .driver + .redox_private_cs_wait(&RedoxPrivateCsWait { + seqno: req.seqno, + timeout_ns: req.timeout_ns, + }) + .map_err(driver_to_syscall)?; + req.completed = u32::from(result.completed); + req.completed_seqno = result.completed_seqno; + bytes_of(&req) + } + DRM_IOCTL_PRIME_HANDLE_TO_FD => { let req = decode_wire::(payload)?; let owned = self @@ -1032,8 +1252,7 @@ impl DrmScheme { return Err(Error::new(EBADF)); } - let token = self.next_export_token; - self.next_export_token = self.next_export_token.saturating_add(1); + let token = self.allocate_export_token()?; self.prime_exports.insert(token, req.handle); let resp = DrmPrimeHandleToFdResponseWire { @@ -1077,6 +1296,7 @@ impl DrmScheme { let handle = self.handles.get_mut(&id).ok_or_else(|| Error::new(EBADF))?; if !handle.owned_gems.contains(&gem_handle) { handle.owned_gems.push(gem_handle); + handle.imported_gems.insert(gem_handle); } let resp = DrmPrimeFdToHandleResponseWire { @@ -1086,6 +1306,31 @@ impl DrmScheme { bytes_of(&resp) } + DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT => { + let req = decode_wire::(payload)?; + self.validate_private_cs_handles( + id, + req.src_handle, + req.dst_handle, + "private CS submit", + )?; + self.validate_private_cs_ranges(&req, "private CS submit")?; + let resp: RedoxPrivateCsSubmitResult = self + .driver + .redox_private_cs_submit(&req) + .map_err(driver_to_syscall)?; + bytes_of(&resp) + } + + DRM_IOCTL_REDOX_PRIVATE_CS_WAIT => { + let req = decode_wire::(payload)?; + let resp: RedoxPrivateCsWaitResult = self + .driver + .redox_private_cs_wait(&req) + .map_err(driver_to_syscall)?; + bytes_of(&resp) + } + _ => { warn!("redox-drm: unsupported ioctl {:#x}", request); return Err(Error::new(EOPNOTSUPP)); @@ -1190,7 +1435,10 @@ impl SchemeBlockMut for DrmScheme { fn fsync(&mut self, id: usize) -> Result> { let _ = self.handles.get(&id).ok_or_else(|| Error::new(EBADF))?; - Ok(Some(0)) + warn!( + "redox-drm: fsync rejected — shared core has no implicit render-fence sync contract" + ); + Err(Error::new(EOPNOTSUPP)) } fn fevent(&mut self, id: usize, _flags: EventFlags) -> Result> { @@ -1384,5 +1632,418 @@ fn decode_wire(buf: &[u8]) -> Result { fn driver_to_syscall(error: crate::driver::DriverError) -> Error { warn!("redox-drm: driver error: {}", error); - Error::new(EINVAL) + match error { + crate::driver::DriverError::Unsupported(_) => Error::new(EOPNOTSUPP), + crate::driver::DriverError::InvalidArgument(_) => Error::new(EINVAL), + crate::driver::DriverError::NotFound(_) => Error::new(ENOENT), + crate::driver::DriverError::Initialization(_) + | crate::driver::DriverError::Mmio(_) + | crate::driver::DriverError::Pci(_) + | crate::driver::DriverError::Buffer(_) + | crate::driver::DriverError::Io(_) => Error::new(EINVAL), + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::sync::{Arc, Mutex}; + + use redox_scheme::SchemeBlockMut; + + use super::*; + use crate::driver::{DriverError, DriverEvent, GpuDriver}; + use crate::kms::{ConnectorInfo, ModeInfo}; + + #[derive(Default)] + struct FakeDriverState { + next_handle: GemHandle, + gem_sizes: BTreeMap, + submit_calls: usize, + } + + struct FakeDriver { + state: Mutex, + support_private_cs: bool, + } + + impl FakeDriver { + fn new(support_private_cs: bool) -> Self { + Self { + state: Mutex::new(FakeDriverState { + next_handle: 1, + ..FakeDriverState::default() + }), + support_private_cs, + } + } + + fn submit_calls(&self) -> usize { + self.state.lock().unwrap().submit_calls + } + } + + impl GpuDriver for FakeDriver { + fn driver_name(&self) -> &str { + "fake" + } + + fn driver_desc(&self) -> &str { + "fake" + } + + fn driver_date(&self) -> &str { + "1970-01-01" + } + + fn detect_connectors(&self) -> Vec { + Vec::new() + } + + fn get_modes(&self, _connector_id: u32) -> Vec { + Vec::new() + } + + fn set_crtc( + &self, + _crtc_id: u32, + _fb_handle: u32, + _connectors: &[u32], + _mode: &ModeInfo, + ) -> crate::driver::Result<()> { + Ok(()) + } + + fn page_flip(&self, _crtc_id: u32, _fb_handle: u32, _flags: u32) -> crate::driver::Result { + Ok(0) + } + + fn get_vblank(&self, _crtc_id: u32) -> crate::driver::Result { + Ok(0) + } + + fn gem_create(&self, size: u64) -> crate::driver::Result { + let mut state = self.state.lock().unwrap(); + let handle = state.next_handle; + state.next_handle = state.next_handle.saturating_add(1); + state.gem_sizes.insert(handle, size); + Ok(handle) + } + + fn gem_close(&self, handle: GemHandle) -> crate::driver::Result<()> { + let removed = self.state.lock().unwrap().gem_sizes.remove(&handle); + if removed.is_some() { + Ok(()) + } else { + Err(DriverError::NotFound(format!("unknown GEM handle {handle}"))) + } + } + + fn gem_mmap(&self, handle: GemHandle) -> crate::driver::Result { + if self.state.lock().unwrap().gem_sizes.contains_key(&handle) { + Ok((handle as usize).saturating_mul(4096)) + } else { + Err(DriverError::NotFound(format!("unknown GEM handle {handle}"))) + } + } + + fn gem_size(&self, handle: GemHandle) -> crate::driver::Result { + self.state + .lock() + .unwrap() + .gem_sizes + .get(&handle) + .copied() + .ok_or_else(|| DriverError::NotFound(format!("unknown GEM handle {handle}"))) + } + + fn get_edid(&self, _connector_id: u32) -> Vec { + Vec::new() + } + + fn handle_irq(&self) -> crate::driver::Result> { + Ok(None) + } + + fn redox_private_cs_submit( + &self, + _submit: &RedoxPrivateCsSubmit, + ) -> crate::driver::Result { + if !self.support_private_cs { + return Err(DriverError::Unsupported( + "private command submission is unavailable on this backend", + )); + } + + let mut state = self.state.lock().unwrap(); + state.submit_calls = state.submit_calls.saturating_add(1); + Ok(RedoxPrivateCsSubmitResult { seqno: 7 }) + } + } + + fn open_card(scheme: &mut DrmScheme) -> usize { + scheme.open("card0", 0, 0, 0).unwrap().unwrap() + } + + fn write_ioctl(scheme: &mut DrmScheme, id: usize, request: usize, payload: &T) -> 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)) + } + + 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(); + assert_eq!(len, size_of::()); + decode_wire::(&buf).unwrap() + } + + #[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 exporter = open_card(&mut scheme); + let importer = open_card(&mut scheme); + + let create = DrmGemCreateWire { + size: 4096, + ..DrmGemCreateWire::default() + }; + write_ioctl(&mut scheme, exporter, DRM_IOCTL_GEM_CREATE, &create).unwrap(); + let created = read_response::(&mut scheme, exporter); + + let export = DrmPrimeHandleToFdWire { + handle: created.handle, + flags: 0, + }; + write_ioctl(&mut scheme, exporter, DRM_IOCTL_PRIME_HANDLE_TO_FD, &export).unwrap(); + let exported = read_response::(&mut scheme, exporter); + + let import = DrmPrimeFdToHandleWire { + fd: exported.fd, + _pad: 0, + }; + write_ioctl(&mut scheme, importer, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import).unwrap(); + let imported = read_response::(&mut scheme, importer); + + let submit = RedoxPrivateCsSubmit { + src_handle: imported.handle, + dst_handle: imported.handle, + src_offset: 0, + dst_offset: 0, + byte_count: 64, + }; + let err = write_ioctl( + &mut scheme, + importer, + DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT, + &submit, + ) + .unwrap_err(); + + assert_eq!(err.errno, EOPNOTSUPP); + assert_eq!(driver.submit_calls(), 0); + } + + #[test] + fn prime_handle_to_fd_returns_distinct_nonzero_tokens() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + + for _ in 0..2 { + let create = DrmGemCreateWire { + size: 4096, + ..DrmGemCreateWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap(); + let _ = read_response::(&mut scheme, card); + } + + let handles = scheme.handles.get(&card).unwrap().owned_gems.clone(); + + let export_a = DrmPrimeHandleToFdWire { + handle: handles[0], + flags: 0, + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_PRIME_HANDLE_TO_FD, &export_a).unwrap(); + let token_a = read_response::(&mut scheme, card).fd; + + let export_b = DrmPrimeHandleToFdWire { + handle: handles[1], + flags: 0, + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_PRIME_HANDLE_TO_FD, &export_b).unwrap(); + let token_b = read_response::(&mut scheme, card).fd; + + assert_ne!(token_a, 0); + assert_ne!(token_b, 0); + assert_ne!(token_a, token_b); + } + + #[test] + fn private_cs_wait_is_explicitly_unsupported_without_backend_support() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + let wait = RedoxPrivateCsWait { + seqno: 1, + timeout_ns: 0, + }; + + let err = write_ioctl(&mut scheme, card, DRM_IOCTL_REDOX_PRIVATE_CS_WAIT, &wait).unwrap_err(); + + assert_eq!(err.errno, EOPNOTSUPP); + } + + #[test] + fn fsync_is_not_a_fake_render_sync_success() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + + let err = scheme.fsync(card).unwrap_err(); + + assert_eq!(err.errno, EOPNOTSUPP); + } + + #[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 card = open_card(&mut scheme); + + for _ in 0..2 { + let create = DrmGemCreateWire { + size: 4096, + ..DrmGemCreateWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap(); + let _ = read_response::(&mut scheme, card); + } + + let handles = match scheme.handles.get(&card) { + Some(handle) => handle.owned_gems.clone(), + None => panic!("missing fake card handle"), + }; + let submit = RedoxPrivateCsSubmit { + src_handle: handles[0], + dst_handle: handles[1], + src_offset: 0, + dst_offset: 0, + byte_count: 128, + }; + + write_ioctl(&mut scheme, card, DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT, &submit).unwrap(); + let response = read_response::(&mut scheme, card); + + assert_eq!(response.seqno, 7); + assert_eq!(driver.submit_calls(), 1); + } + + #[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 card = open_card(&mut scheme); + + for _ in 0..2 { + let create = DrmGemCreateWire { + size: 4096, + ..DrmGemCreateWire::default() + }; + write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap(); + let _ = read_response::(&mut scheme, card); + } + + let handles = scheme.handles.get(&card).unwrap().owned_gems.clone(); + let submit = RedoxPrivateCsSubmit { + src_handle: handles[0], + dst_handle: handles[1], + src_offset: 4090, + dst_offset: 0, + byte_count: 64, + }; + + let err = write_ioctl(&mut scheme, card, DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT, &submit) + .unwrap_err(); + + assert_eq!(err.errno, EINVAL); + assert_eq!(driver.submit_calls(), 0); + } + + #[test] + fn vblank_driver_event_retires_pending_page_flip() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + + scheme.fb_registry.insert( + 7, + FbInfo { + gem_handle: 41, + width: 0, + height: 0, + pitch: 0, + bpp: 0, + }, + ); + scheme.pending_flip_fb.insert(3, (5, 7)); + + scheme.handle_driver_event(DriverEvent::Vblank { + crtc_id: 3, + count: 5, + }); + + assert!(!scheme.pending_flip_fb.contains_key(&3)); + assert!(!scheme.fb_registry.contains_key(&7)); + } + + #[test] + fn non_vblank_driver_event_does_not_retire_pending_page_flip() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + + scheme.fb_registry.insert( + 9, + FbInfo { + gem_handle: 99, + width: 0, + height: 0, + pitch: 0, + bpp: 0, + }, + ); + scheme.pending_flip_fb.insert(1, (2, 9)); + + scheme.handle_driver_event(DriverEvent::Hotplug { connector_id: 1 }); + + assert_eq!(scheme.pending_flip_fb.get(&1), Some(&(2, 9))); + assert!(scheme.fb_registry.contains_key(&9)); + } + + #[test] + fn gem_create_rejects_oversized_allocations() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + let create = DrmGemCreateWire { + size: MAX_SCHEME_GEM_BYTES + 1, + ..DrmGemCreateWire::default() + }; + + let err = write_ioctl(&mut scheme, card, DRM_IOCTL_GEM_CREATE, &create).unwrap_err(); + + assert_eq!(err.errno, EINVAL); + } + + #[test] + fn create_dumb_rejects_oversized_allocations() { + let mut scheme = DrmScheme::new(Arc::new(FakeDriver::new(false))); + let card = open_card(&mut scheme); + let create = DrmCreateDumbWire { + width: 16384, + height: 16384, + bpp: 32, + ..DrmCreateDumbWire::default() + }; + + let err = write_ioctl(&mut scheme, card, DRM_IOCTL_MODE_CREATE_DUMB, &create).unwrap_err(); + + assert_eq!(err.errno, EINVAL); + } }