Expand redox-drm DRM scheme, amdgpu port, and update patches
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -222,15 +222,6 @@ diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml
|
|||||||
index 2d22a8f9..fea105c8 100644
|
index 2d22a8f9..fea105c8 100644
|
||||||
--- a/drivers/acpid/Cargo.toml
|
--- a/drivers/acpid/Cargo.toml
|
||||||
+++ b/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"
|
@@ -21,6 +21,7 @@ rustc-hash = "1.1.0"
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
ron.workspace = true
|
ron.workspace = true
|
||||||
@@ -240,7 +231,7 @@ index 2d22a8f9..fea105c8 100644
|
|||||||
amlserde = { path = "../amlserde" }
|
amlserde = { path = "../amlserde" }
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
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
|
--- a/drivers/acpid/src/acpi.rs
|
||||||
+++ b/drivers/acpid/src/acpi.rs
|
+++ b/drivers/acpid/src/acpi.rs
|
||||||
@@ -8,6 +8,7 @@ use std::str::FromStr;
|
@@ -8,6 +8,7 @@ use std::str::FromStr;
|
||||||
@@ -753,897 +744,6 @@ index 94a1eb17..24799ae2 100644
|
|||||||
let format_err = |err| format!("{:?}", err);
|
let format_err = |err| format!("{:?}", err);
|
||||||
let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
||||||
//TODO: use these parsed tables for the rest of acpid
|
//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<dyn RegionHandler>)> {
|
|
||||||
+ let mut handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)> = 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<AmlError> for AmlEvalError {
|
|
||||||
fn from(value: AmlError) -> Self {
|
|
||||||
@@ -375,10 +854,122 @@ impl From<AmlError> 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<u64>,
|
|
||||||
+ pub remaining_capacity: Option<u64>,
|
|
||||||
+ pub present_voltage: Option<u64>,
|
|
||||||
+ pub power_unit: Option<String>,
|
|
||||||
+ pub design_capacity: Option<u64>,
|
|
||||||
+ pub last_full_capacity: Option<u64>,
|
|
||||||
+ pub design_voltage: Option<u64>,
|
|
||||||
+ pub technology: Option<String>,
|
|
||||||
+ pub model: Option<String>,
|
|
||||||
+ pub serial: Option<String>,
|
|
||||||
+ pub battery_type: Option<String>,
|
|
||||||
+ pub oem_info: Option<String>,
|
|
||||||
+ pub percentage: Option<f64>,
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+#[derive(Clone, Debug, Default)]
|
|
||||||
+pub struct AcpiPowerSnapshot {
|
|
||||||
+ pub adapters: Vec<AcpiPowerAdapter>,
|
|
||||||
+ pub batteries: Vec<AcpiBattery>,
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+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::<String>()
|
|
||||||
+ .trim_matches('_')
|
|
||||||
+ .to_string();
|
|
||||||
+
|
|
||||||
+ if sanitized.is_empty() {
|
|
||||||
+ String::from("device")
|
|
||||||
+ } else {
|
|
||||||
+ sanitized
|
|
||||||
+ }
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+fn aml_value_as_integer(value: &AmlSerdeValue) -> Option<u64> {
|
|
||||||
+ match value {
|
|
||||||
+ AmlSerdeValue::Integer(value) => Some(*value),
|
|
||||||
+ _ => None,
|
|
||||||
+ }
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+fn aml_value_as_string(value: &AmlSerdeValue) -> Option<String> {
|
|
||||||
+ 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<f64> {
|
|
||||||
+ 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<Sdt>,
|
|
||||||
dsdt: Option<Dsdt>,
|
|
||||||
fadt: Option<Fadt>,
|
|
||||||
+ pm1a_cnt_blk: u64,
|
|
||||||
+ pm1b_cnt_blk: u64,
|
|
||||||
+ s5_values: RwLock<Option<(u8, u8)>>,
|
|
||||||
+ reset_reg: Option<GenericAddress>,
|
|
||||||
+ reset_value: u8,
|
|
||||||
+
|
|
||||||
+ pci_fd: RwLock<Option<libredox::Fd>>,
|
|
||||||
|
|
||||||
aml_symbols: RwLock<AmlSymbols>,
|
|
||||||
|
|
||||||
@@ -388,16 +979,190 @@ pub struct AcpiContext {
|
|
||||||
sdt_order: RwLock<Vec<Option<SdtSignature>>>,
|
|
||||||
|
|
||||||
pub next_ctx: RwLock<u64>,
|
|
||||||
+ dmi_info: Option<DmiInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AcpiContext {
|
|
||||||
+ fn evaluate_acpi_object(
|
|
||||||
+ &self,
|
|
||||||
+ path: &str,
|
|
||||||
+ object: &str,
|
|
||||||
+ args: &[u64],
|
|
||||||
+ ) -> Result<AmlSerdeValue, AmlEvalError> {
|
|
||||||
+ 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::<Vec<_>>();
|
|
||||||
+
|
|
||||||
+ self.aml_eval(aml_name, args)
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ fn try_evaluate_acpi_object(
|
|
||||||
+ &self,
|
|
||||||
+ path: &str,
|
|
||||||
+ object: &str,
|
|
||||||
+ args: &[u64],
|
|
||||||
+ ) -> Option<AmlSerdeValue> {
|
|
||||||
+ 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<Vec<String>, 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<AcpiPowerAdapter> {
|
|
||||||
+ 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<AcpiBattery> {
|
|
||||||
+ 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<AcpiPowerSnapshot, AmlEvalError> {
|
|
||||||
+ 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<AmlSerdeValue>,
|
|
||||||
) -> Result<AmlSerdeValue, AmlEvalError> {
|
|
||||||
+ 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<Item = u64>,
|
|
||||||
- ec: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
|
||||||
- ) -> Self {
|
|
||||||
+ pub fn evaluate_acpi_method(
|
|
||||||
+ &self,
|
|
||||||
+ path: &str,
|
|
||||||
+ method: &str,
|
|
||||||
+ args: &[u64],
|
|
||||||
+ ) -> Result<Vec<u64>, 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<u64, AmlEvalError> {
|
|
||||||
+ self.evaluate_acpi_method(device_path, "_PPC", &[])?
|
|
||||||
+ .into_iter()
|
|
||||||
+ .next()
|
|
||||||
+ .ok_or(AmlEvalError::DeserializationError)
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ pub fn init(rxsdt_physaddrs: impl Iterator<Item = u64>) -> Self {
|
|
||||||
let tables = rxsdt_physaddrs
|
|
||||||
.map(|physaddr| {
|
|
||||||
let physaddr: usize = physaddr
|
|
||||||
@@ -440,17 +1250,28 @@ impl AcpiContext {
|
|
||||||
})
|
|
||||||
.collect::<Vec<Sdt>>();
|
|
||||||
|
|
||||||
+ 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<String> {
|
|
||||||
- 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<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
|
|
||||||
+ pub fn aml_symbols(&self) -> Result<RwLockReadGuard<'_, AmlSymbols>, 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::<u16>::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::<u16>::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::<u16>::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::<u8>::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
|
diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs
|
||||||
index c42b379a..f4dff276 100644
|
index c42b379a..f4dff276 100644
|
||||||
--- a/drivers/acpid/src/acpi/dmar/mod.rs
|
--- 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);
|
+ 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
|
diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml
|
||||||
index acbb4e78..210731ae 100644
|
index acbb4e78..210731ae 100644
|
||||||
--- a/drivers/graphics/ihdgd/config.toml
|
--- a/drivers/graphics/ihdgd/config.toml
|
||||||
|
|||||||
@@ -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<String> {
|
||||||
|
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() {
|
||||||
@@ -17,27 +17,27 @@ index 62e98108..a9c72392 100644
|
|||||||
return (double)(*val);
|
return (double)(*val);
|
||||||
}
|
}
|
||||||
diff --git a/src/header/stdlib/cbindgen.toml b/src/header/stdlib/cbindgen.toml
|
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
|
--- a/src/header/stdlib/cbindgen.toml
|
||||||
+++ b/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"]
|
sys_includes = ["stddef.h", "alloca.h", "wchar.h", "features.h"]
|
||||||
include_guard = "_RELIBC_STDLIB_H"
|
include_guard = "_RELIBC_STDLIB_H"
|
||||||
trailer = """
|
-trailer = """
|
||||||
+#ifndef _RELIBC_STDLIB_STRTOLD_H
|
-#ifndef _RELIBC_STDLIB_STRTOLD_H
|
||||||
+#define _RELIBC_STDLIB_STRTOLD_H
|
-#define _RELIBC_STDLIB_STRTOLD_H
|
||||||
+
|
-
|
||||||
+#ifdef __cplusplus
|
-#ifdef __cplusplus
|
||||||
+extern \"C\" {
|
-extern \"C\" {
|
||||||
+#endif
|
-#endif
|
||||||
+
|
-
|
||||||
long double strtold(const char *nptr, char **endptr);
|
-long double strtold(const char *nptr, char **endptr);
|
||||||
+
|
-
|
||||||
+#ifdef __cplusplus
|
-#ifdef __cplusplus
|
||||||
+}
|
-}
|
||||||
+#endif
|
-#endif
|
||||||
+
|
-
|
||||||
+#endif
|
-#endif
|
||||||
"""
|
-"""
|
||||||
language = "C"
|
language = "C"
|
||||||
style = "Type"
|
style = "Type"
|
||||||
|
|||||||
@@ -222,29 +222,14 @@ int amdgpu_dc_init(void *mmio_base, size_t mmio_size)
|
|||||||
{
|
{
|
||||||
const struct firmware *fw = NULL;
|
const struct firmware *fw = NULL;
|
||||||
int fw_ret = request_firmware(&fw, firmware_name, 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 (fw_ret != 0 || !fw) {
|
||||||
if (firmware_required) {
|
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",
|
||||||
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",
|
|
||||||
firmware_name,
|
firmware_name,
|
||||||
fw_ret,
|
fw_ret,
|
||||||
(unsigned long long)quirk_flags);
|
(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 {
|
} 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);
|
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->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_pci_dev->resource_len[0] = g_mmio_size;
|
||||||
|
|
||||||
g_device.pci_dev = g_pci_dev;
|
g_device.pci_dev = g_pci_dev;
|
||||||
|
|||||||
@@ -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 struct pci_dev g_pci_dev;
|
||||||
static int g_pci_dev_populated;
|
static int g_pci_dev_populated;
|
||||||
|
|
||||||
|
#define REDOX_MAX_FIRMWARE_BYTES (64U * 1024U * 1024U)
|
||||||
|
|
||||||
void redox_pci_set_device_info(u16 vendor, u16 device,
|
void redox_pci_set_device_info(u16 vendor, u16 device,
|
||||||
u8 bus_number, u8 dev_number,
|
u8 bus_number, u8 dev_number,
|
||||||
u8 func_number, u8 revision, u32 irq,
|
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;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((unsigned long long)st.st_size > REDOX_MAX_FIRMWARE_BYTES) {
|
||||||
|
close(fd);
|
||||||
|
return -EFBIG;
|
||||||
|
}
|
||||||
|
|
||||||
image = calloc(1, sizeof(*image));
|
image = calloc(1, sizeof(*image));
|
||||||
data = malloc((size_t)st.st_size);
|
data = malloc((size_t)st.st_size);
|
||||||
if (!image || !data) {
|
if (!image || !data) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ redox_scheme = { package = "redox-scheme", version = "0.1" }
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
|
getrandom = "0.2"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
redox-driver-sys = { path = "../../../drivers/redox-driver-sys/source" }
|
redox-driver-sys = { path = "../../../drivers/redox-driver-sys/source" }
|
||||||
|
|||||||
@@ -5,6 +5,42 @@ use crate::kms::{ConnectorInfo, ModeInfo};
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, DriverError>;
|
pub type Result<T> = std::result::Result<T, DriverError>;
|
||||||
|
|
||||||
|
#[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)]
|
#[derive(Debug, Error)]
|
||||||
pub enum DriverError {
|
pub enum DriverError {
|
||||||
#[error("driver initialization failed: {0}")]
|
#[error("driver initialization failed: {0}")]
|
||||||
@@ -16,7 +52,6 @@ pub enum DriverError {
|
|||||||
#[error("resource not found: {0}")]
|
#[error("resource not found: {0}")]
|
||||||
NotFound(String),
|
NotFound(String),
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[error("operation not supported: {0}")]
|
#[error("operation not supported: {0}")]
|
||||||
Unsupported(&'static str),
|
Unsupported(&'static str),
|
||||||
|
|
||||||
@@ -58,5 +93,23 @@ pub trait GpuDriver: Send + Sync {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn get_edid(&self, connector_id: u32) -> Vec<u8>;
|
fn get_edid(&self, connector_id: u32) -> Vec<u8>;
|
||||||
fn handle_irq(&self) -> Result<Option<(u32, u64)>>;
|
fn handle_irq(&self) -> Result<Option<DriverEvent>>;
|
||||||
|
|
||||||
|
fn redox_private_cs_submit(
|
||||||
|
&self,
|
||||||
|
_submit: &RedoxPrivateCsSubmit,
|
||||||
|
) -> Result<RedoxPrivateCsSubmitResult> {
|
||||||
|
Err(DriverError::Unsupported(
|
||||||
|
"private command submission is unavailable on this backend",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redox_private_cs_wait(
|
||||||
|
&self,
|
||||||
|
_wait: &RedoxPrivateCsWait,
|
||||||
|
) -> Result<RedoxPrivateCsWaitResult> {
|
||||||
|
Err(DriverError::Unsupported(
|
||||||
|
"private command completion waits are unavailable on this backend",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use log::{debug, info, warn};
|
|||||||
use redox_driver_sys::memory::MmioRegion;
|
use redox_driver_sys::memory::MmioRegion;
|
||||||
use redox_driver_sys::pci::{PciBarInfo, PciDevice, PciDeviceInfo};
|
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::drivers::interrupt::InterruptHandle;
|
||||||
use crate::gem::{GemHandle, GemManager};
|
use crate::gem::{GemHandle, GemManager};
|
||||||
use crate::kms::connector::{synthetic_edid, Connector};
|
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_INTERRUPT_PENDING_MASK: u32 = 0x0000_0001;
|
||||||
const AMD_IH_STATUS_RING_OVERFLOW_MASK: u32 = 0x0000_0002;
|
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 {
|
pub struct AmdDriver {
|
||||||
info: PciDeviceInfo,
|
info: PciDeviceInfo,
|
||||||
mmio: MmioRegion,
|
mmio: MmioRegion,
|
||||||
irq_handle: Option<InterruptHandle>,
|
irq_handle: Mutex<Option<InterruptHandle>>,
|
||||||
display: DisplayCore,
|
display: DisplayCore,
|
||||||
gem: Mutex<GemManager>,
|
gem: Mutex<GemManager>,
|
||||||
connectors: Mutex<Vec<Connector>>,
|
connectors: Mutex<Vec<Connector>>,
|
||||||
@@ -119,6 +112,7 @@ impl AmdDriver {
|
|||||||
info.location
|
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 display = DisplayCore::with_framebuffer(mmio.as_ptr(), mmio.size(), fb_phys, fb_size)?;
|
||||||
let (connectors, encoders) = detect_display_topology(&display)?;
|
let (connectors, encoders) = detect_display_topology(&display)?;
|
||||||
@@ -140,16 +134,17 @@ impl AmdDriver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
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,
|
info.location,
|
||||||
connectors.len(),
|
connectors.len(),
|
||||||
fw_count
|
fw_count,
|
||||||
|
irq_mode
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
info,
|
info,
|
||||||
mmio,
|
mmio,
|
||||||
irq_handle,
|
irq_handle: Mutex::new(irq_handle),
|
||||||
display,
|
display,
|
||||||
gem: Mutex::new(GemManager::new()),
|
gem: Mutex::new(GemManager::new()),
|
||||||
connectors: Mutex::new(connectors),
|
connectors: Mutex::new(connectors),
|
||||||
@@ -163,7 +158,7 @@ impl AmdDriver {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_irq(&self) -> Result<IrqEvent> {
|
pub fn process_irq(&self) -> Result<Option<DriverEvent>> {
|
||||||
let ih_status = self.read_mmio_reg(AMD_IH_STATUS);
|
let ih_status = self.read_mmio_reg(AMD_IH_STATUS);
|
||||||
let ih_cntl = self.read_mmio_reg(AMD_IH_CNTL);
|
let ih_cntl = self.read_mmio_reg(AMD_IH_CNTL);
|
||||||
let ih_rptr = self.read_mmio_reg(AMD_IH_RB_RPTR);
|
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
|
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) {
|
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
|
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);
|
self.acknowledge_ih(ih_wptr);
|
||||||
Ok(IrqEvent::Unknown)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_mmio_reg(&self, register_index: usize) -> u32 {
|
fn read_mmio_reg(&self, register_index: usize) -> u32 {
|
||||||
@@ -541,32 +536,53 @@ impl GpuDriver for AmdDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_irq(&self) -> Result<Option<(u32, u64)>> {
|
fn handle_irq(&self) -> Result<Option<DriverEvent>> {
|
||||||
|
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()? {
|
match self.process_irq()? {
|
||||||
IrqEvent::Vblank { crtc_id, count } => {
|
Some(DriverEvent::Vblank { crtc_id, count }) => {
|
||||||
debug!(
|
debug!(
|
||||||
"redox-drm: handled AMD vblank IRQ for {} CRTC {} count={} irq={:?}",
|
"redox-drm: handled AMD vblank IRQ for {} CRTC {} count={} irq={:?}",
|
||||||
self.info.location,
|
self.info.location,
|
||||||
crtc_id,
|
crtc_id,
|
||||||
count,
|
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!(
|
info!(
|
||||||
"redox-drm: handled AMD hotplug IRQ for {} connector {} irq={:?}",
|
"redox-drm: handled AMD hotplug IRQ for {} connector {} irq={:?}",
|
||||||
self.info.location,
|
self.info.location,
|
||||||
connector_id,
|
connector_id,
|
||||||
self.irq_handle.as_ref().map(|h| h.irq())
|
irq
|
||||||
);
|
);
|
||||||
Ok(None)
|
Ok(Some(DriverEvent::Hotplug { connector_id }))
|
||||||
}
|
}
|
||||||
IrqEvent::Unknown => {
|
None => {
|
||||||
debug!(
|
debug!(
|
||||||
"redox-drm: handled AMD IRQ for {} with no decoded source irq={:?}",
|
"redox-drm: handled AMD IRQ for {} with no decoded source irq={:?}",
|
||||||
self.info.location,
|
self.info.location,
|
||||||
self.irq_handle.as_ref().map(|h| h.irq())
|
irq
|
||||||
);
|
);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ use std::sync::Mutex;
|
|||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
use redox_driver_sys::memory::MmioRegion;
|
use redox_driver_sys::memory::MmioRegion;
|
||||||
use redox_driver_sys::pci::{PciBarInfo, PciDevice, PciDeviceInfo};
|
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::drivers::interrupt::InterruptHandle;
|
||||||
use crate::gem::{GemHandle, GemManager};
|
use crate::gem::{GemHandle, GemManager};
|
||||||
use crate::kms::connector::{synthetic_edid, Connector};
|
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 gtt_bar = find_memory_bar(&info, 0, "GGTT BAR0")?;
|
||||||
let mmio_bar = find_memory_bar(&info, 2, "MMIO BAR2")?;
|
let mmio_bar = find_memory_bar(&info, 2, "MMIO BAR2")?;
|
||||||
validate_intel_bars(&info, >t_bar, &mmio_bar)?;
|
validate_intel_bars(&info, >t_bar, &mmio_bar)?;
|
||||||
@@ -93,18 +115,21 @@ impl IntelDriver {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let irq_mode = irq_handle.as_ref().map(|handle| handle.mode_name()).unwrap_or("none");
|
||||||
|
|
||||||
if !firmware.is_empty() {
|
if !firmware.is_empty() {
|
||||||
warn!(
|
info!(
|
||||||
"redox-drm: Intel driver ignores {} firmware blob(s); i915-class GPUs usually boot without scheme:firmware blobs",
|
"redox-drm: Intel startup firmware cache populated with {} blob(s) for {}",
|
||||||
firmware.len()
|
firmware.len(),
|
||||||
|
info.location
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"redox-drm: Intel driver ready for {} with {} connector(s)",
|
"redox-drm: Intel driver ready for {} with {} connector(s), IRQ mode {}",
|
||||||
info.location,
|
info.location,
|
||||||
connectors.len()
|
connectors.len(),
|
||||||
|
irq_mode
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -177,7 +202,7 @@ impl IntelDriver {
|
|||||||
Ok(connector.info.connector_type_id.saturating_sub(1) as u8)
|
Ok(connector.info.connector_type_id.saturating_sub(1) as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_irq(&self) -> Result<Option<(u32, u64)>> {
|
fn process_irq(&self) -> Result<Option<DriverEvent>> {
|
||||||
let previous = self.cached_connectors();
|
let previous = self.cached_connectors();
|
||||||
let current = self.refresh_connectors()?;
|
let current = self.refresh_connectors()?;
|
||||||
|
|
||||||
@@ -186,6 +211,20 @@ impl IntelDriver {
|
|||||||
"redox-drm: Intel hotplug event detected on {}",
|
"redox-drm: Intel hotplug event detected on {}",
|
||||||
self.info.location
|
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
|
let ring_busy = self
|
||||||
@@ -200,7 +239,7 @@ impl IntelDriver {
|
|||||||
"redox-drm: Intel IRQ decoded as display event crtc={} ring_busy={}",
|
"redox-drm: Intel IRQ decoded as display event crtc={} ring_busy={}",
|
||||||
crtc_id, ring_busy
|
crtc_id, ring_busy
|
||||||
);
|
);
|
||||||
return Ok(Some((crtc_id, count)));
|
return Ok(Some(DriverEvent::Vblank { crtc_id, count }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ring_busy {
|
if ring_busy {
|
||||||
@@ -459,7 +498,7 @@ impl GpuDriver for IntelDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_irq(&self) -> Result<Option<(u32, u64)>> {
|
fn handle_irq(&self) -> Result<Option<DriverEvent>> {
|
||||||
let irq_event = {
|
let irq_event = {
|
||||||
let mut irq_handle = self
|
let mut irq_handle = self
|
||||||
.irq_handle
|
.irq_handle
|
||||||
|
|||||||
@@ -23,10 +23,22 @@ pub enum InterruptHandle {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn force_legacy_irq(quirks: PciQuirkFlags) -> bool {
|
||||||
|
quirks.contains(PciQuirkFlags::FORCE_LEGACY_IRQ)
|
||||||
|
}
|
||||||
|
|
||||||
impl InterruptHandle {
|
impl InterruptHandle {
|
||||||
pub fn setup(device_info: &PciDeviceInfo, pci_device: &mut PciDevice) -> Result<Self> {
|
pub fn setup(device_info: &PciDeviceInfo, pci_device: &mut PciDevice) -> Result<Self> {
|
||||||
let quirks = device_info.quirks();
|
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 !quirks.contains(PciQuirkFlags::NO_MSIX) {
|
||||||
if let Ok(Some(handle)) = Self::try_msix(device_info, pci_device) {
|
if let Ok(Some(handle)) = Self::try_msix(device_info, pci_device) {
|
||||||
return Ok(handle);
|
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)
|
Self::try_legacy(device_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +201,6 @@ impl InterruptHandle {
|
|||||||
.map_err(|e| DriverError::Io(e.to_string()))
|
.map_err(|e| DriverError::Io(e.to_string()))
|
||||||
}
|
}
|
||||||
InterruptHandle::Msi { handle, .. } | InterruptHandle::Legacy { handle, .. } => {
|
InterruptHandle::Msi { handle, .. } | InterruptHandle::Legacy { handle, .. } => {
|
||||||
let mut buf = [0u8; 8];
|
|
||||||
let _ = handle.wait().map_err(|e| DriverError::Io(e.to_string()))?;
|
let _ = handle.wait().map_err(|e| DriverError::Io(e.to_string()))?;
|
||||||
Ok(())
|
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 {
|
pub fn is_msix(&self) -> bool {
|
||||||
matches!(self, InterruptHandle::Msix { .. })
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use crate::driver::{DriverError, Result};
|
|||||||
|
|
||||||
pub type GemHandle = u32;
|
pub type GemHandle = u32;
|
||||||
|
|
||||||
|
const MAX_GEM_BYTES: u64 = 256 * 1024 * 1024;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GemObject {
|
pub struct GemObject {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -43,6 +45,11 @@ impl GemManager {
|
|||||||
"GEM create size must be non-zero",
|
"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;
|
let handle = self.next_handle;
|
||||||
self.next_handle = self.next_handle.saturating_add(1);
|
self.next_handle = self.next_handle.saturating_add(1);
|
||||||
@@ -113,13 +120,4 @@ impl GemManager {
|
|||||||
pub fn gpu_addr(&self, handle: GemHandle) -> Result<Option<u64>> {
|
pub fn gpu_addr(&self, handle: GemHandle) -> Result<Option<u64>> {
|
||||||
Ok(self.object(handle)?.gpu_addr)
|
Ok(self.object(handle)?.gpu_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn object_mut_ptr(&mut self, handle: GemHandle) -> Result<usize> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,15 @@ use redox_driver_sys::pci::{
|
|||||||
enumerate_pci_class, PciDevice, PciDeviceInfo, PciLocation, PCI_CLASS_DISPLAY,
|
enumerate_pci_class, PciDevice, PciDeviceInfo, PciLocation, PCI_CLASS_DISPLAY,
|
||||||
PCI_VENDOR_ID_AMD, PCI_VENDOR_ID_INTEL,
|
PCI_VENDOR_ID_AMD, PCI_VENDOR_ID_INTEL,
|
||||||
};
|
};
|
||||||
|
use redox_driver_sys::quirks::PciQuirkFlags;
|
||||||
use redox_scheme::{SignalBehavior, Socket};
|
use redox_scheme::{SignalBehavior, Socket};
|
||||||
|
|
||||||
use crate::driver::{DriverError, GpuDriver, Result};
|
use crate::driver::{DriverError, DriverEvent, GpuDriver, Result};
|
||||||
use crate::drivers::DriverRegistry;
|
use crate::drivers::DriverRegistry;
|
||||||
use crate::scheme::DrmScheme;
|
use crate::scheme::DrmScheme;
|
||||||
|
|
||||||
|
const MAX_FIRMWARE_BLOB_BYTES: u64 = 64 * 1024 * 1024;
|
||||||
|
|
||||||
struct StderrLogger {
|
struct StderrLogger {
|
||||||
level: LevelFilter,
|
level: LevelFilter,
|
||||||
}
|
}
|
||||||
@@ -70,13 +73,16 @@ fn run() -> Result<()> {
|
|||||||
.map_err(|e| DriverError::Initialization(format!("failed to register drm scheme: {e}")))?;
|
.map_err(|e| DriverError::Initialization(format!("failed to register drm scheme: {e}")))?;
|
||||||
info!("redox-drm: registered scheme:drm");
|
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::<DriverEvent>(8);
|
||||||
|
|
||||||
let irq_driver: Arc<dyn GpuDriver> = driver.clone();
|
let irq_driver: Arc<dyn GpuDriver> = driver.clone();
|
||||||
std::thread::spawn(move || loop {
|
std::thread::spawn(move || loop {
|
||||||
match irq_driver.handle_irq() {
|
match irq_driver.handle_irq() {
|
||||||
Ok(Some((crtc_id, count))) => {
|
Ok(Some(event)) => {
|
||||||
let _ = vblank_tx.try_send((crtc_id, count));
|
if event_tx.send(event).is_err() {
|
||||||
|
error!("redox-drm: event consumer dropped, stopping IRQ event thread");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -87,12 +93,12 @@ fn run() -> Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let drm_scheme = Arc::new(Mutex::new(DrmScheme::new(driver)));
|
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 {
|
std::thread::spawn(move || loop {
|
||||||
if let Ok((crtc_id, vblank_count)) = vblank_rx.recv() {
|
if let Ok(event) = event_rx.recv() {
|
||||||
if let Ok(mut scheme) = vblank_scheme.lock() {
|
if let Ok(mut scheme) = event_scheme.lock() {
|
||||||
scheme.retire_vblank(crtc_id, vblank_count);
|
scheme.handle_driver_event(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -209,20 +215,42 @@ struct FirmwareCache {
|
|||||||
blobs: HashMap<String, Vec<u8>>,
|
blobs: HashMap<String, Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FirmwareCache {
|
struct FirmwareExpectation {
|
||||||
fn load_for_device(info: &PciDeviceInfo) -> Result<Self> {
|
vendor_name: &'static str,
|
||||||
if info.vendor_id != PCI_VENDOR_ID_AMD {
|
keys: &'static [&'static str],
|
||||||
info!(
|
required: bool,
|
||||||
"redox-drm: skipping firmware load for Intel GPU {}",
|
required_label: &'static str,
|
||||||
info.location
|
}
|
||||||
);
|
|
||||||
return Ok(Self {
|
|
||||||
blobs: HashMap::new(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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_sos",
|
||||||
"amdgpu/psp_13_0_0_ta",
|
"amdgpu/psp_13_0_0_ta",
|
||||||
"amdgpu/gc_11_0_0_pfp",
|
"amdgpu/gc_11_0_0_pfp",
|
||||||
@@ -238,47 +266,165 @@ impl FirmwareCache {
|
|||||||
"amdgpu/sdma_5_2",
|
"amdgpu/sdma_5_2",
|
||||||
"amdgpu/vcn_3_0_0",
|
"amdgpu/vcn_3_0_0",
|
||||||
"amdgpu/vcn_3_1_0",
|
"amdgpu/vcn_3_1_0",
|
||||||
]
|
],
|
||||||
|
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 {
|
} else {
|
||||||
&[]
|
shown.join(", ")
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn firmware_requirement_error(
|
||||||
|
expectation: &FirmwareExpectation,
|
||||||
|
loaded: &HashMap<String, Vec<u8>>,
|
||||||
|
missing: &[String],
|
||||||
|
) -> Option<String> {
|
||||||
|
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<Self> {
|
||||||
|
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 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);
|
let path = format!("/scheme/firmware/{}", key);
|
||||||
match File::open(&path) {
|
match File::open(&path) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
let metadata = file.metadata();
|
let metadata = file.metadata();
|
||||||
let estimated_size = metadata.map(|m| m.len() as usize).unwrap_or(1024 * 1024);
|
let estimated_size = metadata.map(|m| m.len()).unwrap_or(1024 * 1024);
|
||||||
let mut buf = Vec::with_capacity(estimated_size);
|
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) {
|
match file.read_to_end(&mut buf) {
|
||||||
Ok(bytes_read) => {
|
Ok(bytes_read) => {
|
||||||
info!("redox-drm: loaded firmware {} ({} bytes)", key, bytes_read);
|
info!("redox-drm: loaded firmware {} ({} bytes)", key, bytes_read);
|
||||||
loaded_any = true;
|
|
||||||
blobs.insert(key.to_string(), buf);
|
blobs.insert(key.to_string(), buf);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("redox-drm: failed to read firmware {}: {}", key, e);
|
info!("redox-drm: failed to read firmware {}: {}", key, e);
|
||||||
|
missing.push(key.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("redox-drm: firmware {} not available: {}", key, e);
|
info!("redox-drm: firmware {} not available: {}", key, e);
|
||||||
|
missing.push(key.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !loaded_any && info.vendor_id == PCI_VENDOR_ID_AMD {
|
if let Some(message) = firmware_requirement_error(&expectation, &blobs, &missing) {
|
||||||
return Err(DriverError::NotFound(
|
return Err(DriverError::NotFound(message));
|
||||||
"no AMD firmware blobs available from scheme:firmware".to_string(),
|
}
|
||||||
));
|
|
||||||
|
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!(
|
info!(
|
||||||
"redox-drm: firmware cache populated with {} blob(s)",
|
"redox-drm: firmware cache populated with {} blob(s) for {} GPU {}",
|
||||||
blobs.len()
|
blobs.len(),
|
||||||
|
expectation.vendor_name,
|
||||||
|
info.location
|
||||||
);
|
);
|
||||||
Ok(Self { blobs })
|
Ok(Self { blobs })
|
||||||
}
|
}
|
||||||
@@ -309,3 +455,128 @@ fn main() {
|
|||||||
process::exit(1);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ use std::collections::{BTreeMap, HashSet};
|
|||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use getrandom::getrandom;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use redox_scheme::SchemeBlockMut;
|
use redox_scheme::SchemeBlockMut;
|
||||||
use syscall04::data::Stat;
|
use syscall04::data::Stat;
|
||||||
use syscall04::error::{Error, Result, EBADF, EBUSY, EINVAL, ENOENT, EOPNOTSUPP};
|
use syscall04::error::{Error, Result, EBADF, EBUSY, EINVAL, ENOENT, EOPNOTSUPP};
|
||||||
use syscall04::flag::{EventFlags, MapFlags, MunmapFlags, MODE_FILE};
|
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::gem::GemHandle;
|
||||||
use crate::kms::ModeInfo;
|
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_GEM_MMAP: usize = DRM_IOCTL_BASE + 28;
|
||||||
const DRM_IOCTL_PRIME_HANDLE_TO_FD: usize = DRM_IOCTL_BASE + 29;
|
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_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 ----
|
// ---- Wire types for DRM ioctls ----
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -238,6 +248,29 @@ struct DrmPrimeFdToHandleResponseWire {
|
|||||||
_pad: u32,
|
_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 ----
|
// ---- Internal handle types ----
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -257,6 +290,7 @@ struct Handle {
|
|||||||
mapped_gem_refs: usize,
|
mapped_gem_refs: usize,
|
||||||
owned_fbs: Vec<u32>,
|
owned_fbs: Vec<u32>,
|
||||||
owned_gems: Vec<GemHandle>,
|
owned_gems: Vec<GemHandle>,
|
||||||
|
imported_gems: HashSet<GemHandle>,
|
||||||
closing: bool,
|
closing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +298,6 @@ pub struct DrmScheme {
|
|||||||
driver: Arc<dyn GpuDriver>,
|
driver: Arc<dyn GpuDriver>,
|
||||||
next_id: usize,
|
next_id: usize,
|
||||||
next_fb_id: u32,
|
next_fb_id: u32,
|
||||||
next_export_token: u32,
|
|
||||||
handles: BTreeMap<usize, Handle>,
|
handles: BTreeMap<usize, Handle>,
|
||||||
active_crtc_fb: BTreeMap<u32, u32>,
|
active_crtc_fb: BTreeMap<u32, u32>,
|
||||||
active_crtc_mode: BTreeMap<u32, ModeInfo>,
|
active_crtc_mode: BTreeMap<u32, ModeInfo>,
|
||||||
@@ -281,7 +314,6 @@ impl DrmScheme {
|
|||||||
driver,
|
driver,
|
||||||
next_id: 0,
|
next_id: 0,
|
||||||
next_fb_id: 1,
|
next_fb_id: 1,
|
||||||
next_export_token: 1,
|
|
||||||
handles: BTreeMap::new(),
|
handles: BTreeMap::new(),
|
||||||
active_crtc_fb: BTreeMap::new(),
|
active_crtc_fb: BTreeMap::new(),
|
||||||
active_crtc_mode: 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 {
|
fn is_fb_active(&self, fb_id: u32) -> bool {
|
||||||
self.active_crtc_fb.values().any(|&id| id == fb_id)
|
self.active_crtc_fb.values().any(|&id| id == fb_id)
|
||||||
|| self.pending_flip_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)
|
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 {
|
fn gem_is_still_referenced(&self, gem_handle: GemHandle) -> bool {
|
||||||
self.handles
|
self.handles
|
||||||
.values()
|
.values()
|
||||||
@@ -341,6 +362,26 @@ impl DrmScheme {
|
|||||||
self.gem_export_refs.get(&gem_handle).copied().unwrap_or(0)
|
self.gem_export_refs.get(&gem_handle).copied().unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allocate_export_token(&self) -> Result<u32> {
|
||||||
|
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) {
|
fn bump_export_ref(&mut self, gem_handle: GemHandle) {
|
||||||
let entry = self.gem_export_refs.entry(gem_handle).or_insert(0);
|
let entry = self.gem_export_refs.entry(gem_handle).or_insert(0);
|
||||||
*entry = entry.saturating_add(1);
|
*entry = entry.saturating_add(1);
|
||||||
@@ -372,6 +413,113 @@ impl DrmScheme {
|
|||||||
&& self.gem_export_refcount(gem_handle) == 0
|
&& 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 {
|
fn maybe_close_gem(&mut self, gem_handle: GemHandle, context: &str) -> bool {
|
||||||
if !self.gem_can_close(gem_handle) {
|
if !self.gem_can_close(gem_handle) {
|
||||||
return false;
|
return false;
|
||||||
@@ -404,6 +552,7 @@ impl DrmScheme {
|
|||||||
mapped_gem_refs: 0,
|
mapped_gem_refs: 0,
|
||||||
owned_fbs: Vec::new(),
|
owned_fbs: Vec::new(),
|
||||||
owned_gems: Vec::new(),
|
owned_gems: Vec::new(),
|
||||||
|
imported_gems: HashSet::new(),
|
||||||
closing: false,
|
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) {
|
fn try_reap_fb(&mut self, fb_id: u32) {
|
||||||
let gem_handle = match self.fb_registry.get(&fb_id) {
|
let gem_handle = match self.fb_registry.get(&fb_id) {
|
||||||
Some(info) => info.gem_handle,
|
Some(info) => info.gem_handle,
|
||||||
@@ -507,7 +663,11 @@ impl DrmScheme {
|
|||||||
crtc_count: 1,
|
crtc_count: 1,
|
||||||
encoder_count: connectors.len() as u32,
|
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<Vec<u8>> {
|
fn encode_connector(&self, connector_id: u32) -> Result<Vec<u8>> {
|
||||||
@@ -673,6 +833,7 @@ impl DrmScheme {
|
|||||||
let pitch = (req.width.saturating_mul(req.bpp).saturating_add(7)) / 8;
|
let pitch = (req.width.saturating_mul(req.bpp).saturating_add(7)) / 8;
|
||||||
req.pitch = pitch;
|
req.pitch = pitch;
|
||||||
req.size = (pitch as u64).saturating_mul(req.height as u64);
|
req.size = (pitch as u64).saturating_mul(req.height as u64);
|
||||||
|
self.validate_gem_create_size(req.size, "CREATE_DUMB")?;
|
||||||
req.handle = self
|
req.handle = self
|
||||||
.driver
|
.driver
|
||||||
.gem_create(req.size)
|
.gem_create(req.size)
|
||||||
@@ -758,6 +919,7 @@ impl DrmScheme {
|
|||||||
}
|
}
|
||||||
if let Some(handle) = self.handles.get_mut(&id) {
|
if let Some(handle) = self.handles.get_mut(&id) {
|
||||||
handle.owned_gems.retain(|&h| h != req.handle);
|
handle.owned_gems.retain(|&h| h != req.handle);
|
||||||
|
handle.imported_gems.remove(&req.handle);
|
||||||
}
|
}
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@@ -925,9 +1087,7 @@ impl DrmScheme {
|
|||||||
|
|
||||||
DRM_IOCTL_GEM_CREATE => {
|
DRM_IOCTL_GEM_CREATE => {
|
||||||
let mut req = decode_wire::<DrmGemCreateWire>(payload)?;
|
let mut req = decode_wire::<DrmGemCreateWire>(payload)?;
|
||||||
if req.size == 0 {
|
self.validate_gem_create_size(req.size, "GEM_CREATE")?;
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
}
|
|
||||||
req.handle = self
|
req.handle = self
|
||||||
.driver
|
.driver
|
||||||
.gem_create(req.size)
|
.gem_create(req.size)
|
||||||
@@ -980,6 +1140,7 @@ impl DrmScheme {
|
|||||||
}
|
}
|
||||||
if let Some(handle) = self.handles.get_mut(&id) {
|
if let Some(handle) = self.handles.get_mut(&id) {
|
||||||
handle.owned_gems.retain(|&h| h != req.handle);
|
handle.owned_gems.retain(|&h| h != req.handle);
|
||||||
|
handle.imported_gems.remove(&req.handle);
|
||||||
}
|
}
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
@@ -1017,6 +1178,65 @@ impl DrmScheme {
|
|||||||
bytes_of(&req)
|
bytes_of(&req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DRM_IOCTL_REDOX_AMD_SDMA_SUBMIT => {
|
||||||
|
let mut req = decode_wire::<RedoxAmdSdmaSubmitWire>(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::<RedoxAmdSdmaWaitWire>(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 => {
|
DRM_IOCTL_PRIME_HANDLE_TO_FD => {
|
||||||
let req = decode_wire::<DrmPrimeHandleToFdWire>(payload)?;
|
let req = decode_wire::<DrmPrimeHandleToFdWire>(payload)?;
|
||||||
let owned = self
|
let owned = self
|
||||||
@@ -1032,8 +1252,7 @@ impl DrmScheme {
|
|||||||
return Err(Error::new(EBADF));
|
return Err(Error::new(EBADF));
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = self.next_export_token;
|
let token = self.allocate_export_token()?;
|
||||||
self.next_export_token = self.next_export_token.saturating_add(1);
|
|
||||||
self.prime_exports.insert(token, req.handle);
|
self.prime_exports.insert(token, req.handle);
|
||||||
|
|
||||||
let resp = DrmPrimeHandleToFdResponseWire {
|
let resp = DrmPrimeHandleToFdResponseWire {
|
||||||
@@ -1077,6 +1296,7 @@ impl DrmScheme {
|
|||||||
let handle = self.handles.get_mut(&id).ok_or_else(|| Error::new(EBADF))?;
|
let handle = self.handles.get_mut(&id).ok_or_else(|| Error::new(EBADF))?;
|
||||||
if !handle.owned_gems.contains(&gem_handle) {
|
if !handle.owned_gems.contains(&gem_handle) {
|
||||||
handle.owned_gems.push(gem_handle);
|
handle.owned_gems.push(gem_handle);
|
||||||
|
handle.imported_gems.insert(gem_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = DrmPrimeFdToHandleResponseWire {
|
let resp = DrmPrimeFdToHandleResponseWire {
|
||||||
@@ -1086,6 +1306,31 @@ impl DrmScheme {
|
|||||||
bytes_of(&resp)
|
bytes_of(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT => {
|
||||||
|
let req = decode_wire::<RedoxPrivateCsSubmit>(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::<RedoxPrivateCsWait>(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);
|
warn!("redox-drm: unsupported ioctl {:#x}", request);
|
||||||
return Err(Error::new(EOPNOTSUPP));
|
return Err(Error::new(EOPNOTSUPP));
|
||||||
@@ -1190,7 +1435,10 @@ impl SchemeBlockMut for DrmScheme {
|
|||||||
|
|
||||||
fn fsync(&mut self, id: usize) -> Result<Option<usize>> {
|
fn fsync(&mut self, id: usize) -> Result<Option<usize>> {
|
||||||
let _ = self.handles.get(&id).ok_or_else(|| Error::new(EBADF))?;
|
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<Option<EventFlags>> {
|
fn fevent(&mut self, id: usize, _flags: EventFlags) -> Result<Option<EventFlags>> {
|
||||||
@@ -1384,5 +1632,418 @@ fn decode_wire<T: Copy>(buf: &[u8]) -> Result<T> {
|
|||||||
|
|
||||||
fn driver_to_syscall(error: crate::driver::DriverError) -> Error {
|
fn driver_to_syscall(error: crate::driver::DriverError) -> Error {
|
||||||
warn!("redox-drm: driver error: {}", 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<GemHandle, u64>,
|
||||||
|
submit_calls: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FakeDriver {
|
||||||
|
state: Mutex<FakeDriverState>,
|
||||||
|
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<ConnectorInfo> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_modes(&self, _connector_id: u32) -> Vec<ModeInfo> {
|
||||||
|
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<u64> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_vblank(&self, _crtc_id: u32) -> crate::driver::Result<u64> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gem_create(&self, size: u64) -> crate::driver::Result<GemHandle> {
|
||||||
|
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<usize> {
|
||||||
|
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<u64> {
|
||||||
|
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<u8> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_irq(&self) -> crate::driver::Result<Option<DriverEvent>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redox_private_cs_submit(
|
||||||
|
&self,
|
||||||
|
_submit: &RedoxPrivateCsSubmit,
|
||||||
|
) -> crate::driver::Result<RedoxPrivateCsSubmitResult> {
|
||||||
|
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<T>(scheme: &mut DrmScheme, id: usize, request: usize, payload: &T) -> Result<usize> {
|
||||||
|
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<T: Copy>(scheme: &mut DrmScheme, id: usize) -> T {
|
||||||
|
let mut buf = vec![0; size_of::<T>()];
|
||||||
|
let len = scheme.read(id, &mut buf).unwrap().unwrap();
|
||||||
|
assert_eq!(len, size_of::<T>());
|
||||||
|
decode_wire::<T>(&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::<DrmGemCreateWire>(&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::<DrmPrimeHandleToFdResponseWire>(&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::<DrmPrimeFdToHandleResponseWire>(&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::<DrmGemCreateWire>(&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::<DrmPrimeHandleToFdResponseWire>(&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::<DrmPrimeHandleToFdResponseWire>(&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::<DrmGemCreateWire>(&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::<RedoxPrivateCsSubmitResult>(&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::<DrmGemCreateWire>(&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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user