diff --git a/MAINTAINERS b/MAINTAINERS index 491823243a5fc1..d152848f6be827 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2723,7 +2723,9 @@ F: drivers/pwm/pwm-apple.c F: drivers/reset/reset-apple-cio.c F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* +F: tools/asahi/m3-uapi-* F: tools/asahi/pmp-bootargs-tests.rs +F: tools/asahi/test-m3-uapi.py F: drivers/spi/spi-apple.c F: drivers/spmi/spmi-apple-controller.c F: drivers/thunderbolt/.kunitconfig.apple-dpin diff --git a/drivers/gpu/drm/apple/j613_dcp_activate.c b/drivers/gpu/drm/apple/j613_dcp_activate.c index 6e20a5afc914c5..6d0a2620694692 100644 --- a/drivers/gpu/drm/apple/j613_dcp_activate.c +++ b/drivers/gpu/drm/apple/j613_dcp_activate.c @@ -40,6 +40,13 @@ bool apple_t8122_25g83_board(void) return ok; } +/* Match the PMGR provider: automatic domains can be clock-gated while active. */ +static bool j613_power_state_active(u32 state) +{ + return FIELD_GET(GENMASK(7, 4), state) == 15 || + ((state & BIT(28)) && FIELD_GET(GENMASK(3, 0), state) == 15); +} + /* These are DT domain nodes, carrying the PMGR offsets, not copied registers. */ static int j613_power_ready(void) { @@ -82,7 +89,7 @@ static int j613_power_ready(void) return -ENOMEM; state = readl(pmgr + offset); iounmap(pmgr); - if (FIELD_GET(GENMASK(7, 4), state) != 15) + if (!j613_power_state_active(state)) return -EBUSY; } return 0; diff --git a/drivers/gpu/drm/asahi/m3/m3_adt_config.rs b/drivers/gpu/drm/asahi/m3/m3_adt_config.rs index 1893c113cf2a7d..355cd44b040254 100644 --- a/drivers/gpu/drm/asahi/m3/m3_adt_config.rs +++ b/drivers/gpu/drm/asahi/m3/m3_adt_config.rs @@ -260,7 +260,14 @@ impl PstatePolicy { /// The runtime backend's InitData contents: the generated object images and the /// performance-state policy. +pub(crate) struct ExtendedInputs { + pub(crate) power: hw::PwrConfig, + pub(crate) sram_uv: KVec, + pub(crate) bases: super::m3_mmio_26::RegisterBases, +} + pub(crate) struct Contents { + pub(crate) extended: Option, /// Generated images, per owner; None for owners that stay zero. Only the three image /// owners' images are uploaded; the records' are checked. images: KVec>>, @@ -304,6 +311,10 @@ impl Contents { experiment: Option<&Experiment>, ) -> Result { let dev = pdev.as_ref(); + if firmware.profile() == super::m3_profile::Profile::Extended { + if experiment.is_some() { return Err(EINVAL); } + return Self::extended(dev, soc); + } let param = m3_params::initdata_param(); check_version(dev, firmware, experiment)?; let iomaps = match experiment { @@ -370,12 +381,54 @@ impl Contents { ); let read_only_slots = experiment.map(|e| e.read_only_slots()).unwrap_or(0); let hwdata_object = soc.hwdata_object; - Ok(Contents { images, pstates, iomaps, table, read_only_slots, hwdata_object }) + Ok(Contents { extended: None, images, pstates, iomaps, table, read_only_slots, hwdata_object }) + } + + fn extended(dev: &device::Device, soc: &Soc) -> Result { + let cfg = soc.hwcfg.ok_or(ENODEV)?; + if soc.chip_id != 0x8122 || cfg.chip_id != 0x8122 { return Err(ENODEV); } + let power = hw::PwrConfig::load(dev, cfg)?; + let tables = initdata::PStateTables::new(&power, true, None, false)?; + if tables.len != 9 || tables.max != 8 || tables.max_frequency_khz(&power) != 1_338_000 { + return Err(EINVAL); + } + let node = dev.of_node().ok_or(ENODEV)?; + let opps = node.parse_phandle(c_str!("operating-points-v2"), 0).ok_or(EINVAL)?; + let mut sram_uv = KVec::new(); + for opp in opps.children() { + let values: KVec = opp.get_property(c_str!("opp-microvolt-sram"))?; + if values.len() != 1 { return Err(EINVAL); } + sram_uv.push(values[0], GFP_KERNEL)?; + } + if sram_uv.len() != power.perf_states.len() { return Err(EINVAL); } + let block = |index: usize| -> Result { + Ok(cfg.io_mappings.get(index).and_then(|value| value.as_ref()).ok_or(EINVAL)?.base as u64) + }; + let bases = super::m3_mmio_26::RegisterBases { + gpu: soc.windows.sgx, interrupt: block(2)?, + memory: block(11)?.checked_add(0x1c4000).ok_or(EOVERFLOW)?, + telemetry_write: block(18)?, telemetry_read: block(19)?, + neural: block(25)?, metrology: block(26)?, + }; + let mut freqs = [0; 16]; + for (index, value) in freqs.iter_mut().enumerate().take(tables.len) { + *value = tables.primary_state(&power, index).freq_hz / 1_000_000; + } + // This profile enables the native firmware's thermal/PID controllers. + // The legacy host-only thermal governor must not replace those inputs. + let pstates = PstatePolicy { table_max: 8, table_max_mhz: 1338, + max: 8, safe: 8, boot: 1, max_mhz: 1338, safe_mhz: 1338, + boot_mhz: freqs[1], freqs, thermal: ThermalMode::Off }; + let mut images = KVec::new(); + for _ in 0..super::m3_init_26::COUNT { images.push(None, GFP_KERNEL)?; } + Ok(Self { extended: Some(ExtendedInputs { power, sram_uv, bases }), + images, pstates, iomaps: &[], table: (8, 1338), read_only_slots: 0, hwdata_object: None }) } /// Check the contents before they are uploaded. pub(crate) fn check_for_upload(&self) -> Result { - if self.images.len() == storage::COUNT { + let count = if self.extended.is_some() { super::m3_init_26::COUNT } else { storage::COUNT }; + if self.images.len() == count { Ok(()) } else { Err(EINVAL) diff --git a/drivers/gpu/drm/asahi/m3/m3_channel_format.rs b/drivers/gpu/drm/asahi/m3/m3_channel_format.rs new file mode 100644 index 00000000000000..45eebda112b738 --- /dev/null +++ b/drivers/gpu/drm/asahi/m3/m3_channel_format.rs @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Bounded firmware channel records. Publication and retirement belong to Config. + +use super::m3_init_layout::Error; + +pub(crate) const CONTROL_BYTES: usize = 64; +pub(crate) const RECEIVE_BYTES: usize = 72; +pub(crate) const SLOTS: u32 = 256; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum Action { + Initialize, + Register { + pipe: u32, + engine: u32, + }, + Idle { + enabled: bool, + }, + Release { + group: u8, + index: u8, + weight: u8, + address: u64, + }, +} + +impl Action { + /// The first two actions precede the first generation-bearing publication. + pub(crate) fn generation(self, published: u32, consumed: u32) -> Result { + match self { + Self::Initialize | Self::Register { .. } => { + if published != 0 || consumed != 0 { + return Err(Error::Address); + } + Ok(0) + } + _ => published.max(consumed).checked_add(1).ok_or(Error::Address), + } + } + + pub(crate) fn encode(self, output: &mut [u8]) -> Result<(), Error> { + let mut words = [0u32; 3]; + let mut release = None; + match self { + Self::Initialize => words[0] = 0x16, + Self::Register { pipe, engine } => { + if pipe >= 3 || !(1..=2).contains(&engine) { + return Err(Error::Address); + } + words = [0x1b, pipe, engine]; + } + Self::Idle { enabled } => words = [0x0a, enabled.into(), 0], + Self::Release { + group, + index, + weight, + address, + } => { + if index == 255 + || weight == 255 + || address < 0xffff_fc00_0000_0000 + || address & 7 != 0 + { + return Err(Error::Address); + } + words[0] = 0x14; + words[2] = u32::from_le_bytes([group, index, weight, 4]); + release = Some(address); + } + } + let bytes = output.get_mut(..CONTROL_BYTES).ok_or(Error::Size)?; + bytes.fill(0); + for (index, word) in words.iter().enumerate() { + bytes[index * 4..index * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + if let Some(address) = release { + bytes[12..20].copy_from_slice(&address.to_le_bytes()); + } + Ok(()) + } +} + +/// An empty ring is required before publishing a synchronous control transaction. +/// Both consumers must retire it before the next control action may be published. +pub(crate) fn idle_cursor(cursors: [u32; 3]) -> Result { + if cursors.iter().any(|value| *value >= SLOTS) { + return Err(Error::Bounds); + } + if cursors[0] != cursors[2] || cursors[1] != cursors[2] { + return Err(Error::Address); + } + Ok(cursors[2]) +} + +pub(crate) fn payload_offset(thread: usize, slot: u32) -> Result { + if thread >= 9 { + return Err(Error::Bounds); + } + Ok((thread * 256 + (slot & 255) as usize) * 0xd8) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn initialization_records_have_only_their_defined_words() { + for action in [Action::Initialize, Action::Register { pipe: 2, engine: 1 }] { + let mut output = [0xa5; CONTROL_BYTES + 8]; + action.encode(&mut output).unwrap(); + assert_eq!(&output[12..CONTROL_BYTES], &[0; CONTROL_BYTES - 12]); + assert_eq!(&output[CONTROL_BYTES..], &[0xa5; 8]); + assert_eq!(action.generation(0, 0), Ok(0)); + assert!(action.generation(1, 0).is_err()); + } + } + + #[test] + fn invalid_actions_preserve_the_entire_output() { + for action in [ + Action::Register { pipe: 3, engine: 1 }, + Action::Register { pipe: 0, engine: 0 }, + Action::Release { + group: 0, + index: 255, + weight: 1, + address: 0xffff_fc21_0000_0000, + }, + Action::Release { + group: 0, + index: 0, + weight: 1, + address: 8, + }, + ] { + let mut output = [0xa5; CONTROL_BYTES]; + assert!(action.encode(&mut output).is_err()); + assert_eq!(output, [0xa5; CONTROL_BYTES]); + } + } + + #[test] + fn generations_follow_consumption_and_never_wrap() { + let action = Action::Idle { enabled: true }; + assert_eq!(action.generation(3, 5), Ok(6)); + assert_eq!(action.generation(5, 3), Ok(6)); + assert!(action.generation(u32::MAX, 0).is_err()); + assert!(action.generation(0, u32::MAX).is_err()); + let mut output = [0; CONTROL_BYTES]; + action.encode(&mut output).unwrap(); + assert_eq!(&output[..8], &[10, 0, 0, 0, 1, 0, 0, 0]); + } + + #[test] + fn both_consumers_and_all_indices_are_checked() { + assert_eq!(idle_cursor([255, 255, 255]), Ok(255)); + for cursors in [[0, 1, 1], [1, 0, 1], [256, 256, 256]] { + assert!(idle_cursor(cursors).is_err()); + } + assert_eq!(payload_offset(8, 0x101), Ok((8 * 256 + 1) * 0xd8)); + assert!(payload_offset(9, 0).is_err()); + } + + #[test] + fn release_uses_the_packed_owned_pointer() { + let action = Action::Release { + group: 1, + index: 2, + weight: 3, + address: 0xffff_fc21_0400_0000, + }; + let mut output = [0; CONTROL_BYTES]; + action.encode(&mut output).unwrap(); + assert_eq!(&output[8..12], &[1, 2, 3, 4]); + assert_eq!( + u64::from_le_bytes(output[12..20].try_into().unwrap()), + 0xffff_fc21_0400_0000 + ); + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_client.rs b/drivers/gpu/drm/asahi/m3/m3_client.rs index dff1af0783965d..c1a07d61120d13 100644 --- a/drivers/gpu/drm/asahi/m3/m3_client.rs +++ b/drivers/gpu/drm/asahi/m3/m3_client.rs @@ -35,7 +35,7 @@ use crate::{ const AGX3_PARAMS_SIZE: u64 = (core::mem::offset_of!(uapi::drm_asahi_params_global, gpu_hal_generation) + core::mem::size_of::()) as u64; -const _: () = assert!(AGX3_PARAMS_SIZE == 600); +const _: () = assert!(AGX3_PARAMS_SIZE == super::m3_profile::PARAMS_REQUIRED); /// Refused clients that are logged per boot; later refusals only count. const REFUSALS_LOGGED: u32 = 16; @@ -45,10 +45,19 @@ static REFUSALS: AtomicU32 = AtomicU32::new(0); /// [`AGX3_PARAMS_SIZE`] bytes (so it knows this GPU runs AGX3 command streams), and pass /// VM_CREATE flags 0 (legacy HAL). `size` is the largest GET_PARAMS size the file asked for. pub(crate) fn admit_vm(device: &kernel::device::Device, size: u64, flags: u32) -> Result { + admit_vm_for(device, super::m3_profile::Profile::Legacy, size, flags) +} + +pub(crate) fn admit_vm_for( + device: &kernel::device::Device, + profile: super::m3_profile::Profile, + size: u64, + flags: u32, +) -> Result { { // The positive acknowledgement of the command ABI (7.1.12 `g16_profile::client_admitted`): // GET_PARAMS coverage, and VM_CREATE flags 0 on the legacy-HAL M3 runtime. - if size >= AGX3_PARAMS_SIZE && flags == 0 { + if profile.vm_acknowledged(size, flags) { return Ok(()); } let refusals = REFUSALS.fetch_add(1, Ordering::Relaxed).saturating_add(1); @@ -64,11 +73,13 @@ pub(crate) fn admit_vm(device: &kernel::device::Device, size: u64, flags: u32) - }; dev_info!( device, - "M3: refusing a VM for {}[{}]: it asked GET_PARAMS for {} of the {} parameter bytes, so it cannot know this GPU runs AGX3 command streams; M3-aware userspace is needed{}\n", + "M3: refusing a VM for {}[{}]: GET_PARAMS bytes={}/{} HAL={} flags={:#x}{}\n", name, task.group_leader().pid(), size, AGX3_PARAMS_SIZE, + profile.hal(), + flags, last ); } diff --git a/drivers/gpu/drm/asahi/m3/m3_compute_layout.rs b/drivers/gpu/drm/asahi/m3/m3_compute_layout.rs index 74bed858b71e26..ace58e38f38a6d 100644 --- a/drivers/gpu/drm/asahi/m3/m3_compute_layout.rs +++ b/drivers/gpu/drm/asahi/m3/m3_compute_layout.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT use super::{m3_pool_layout::GpuRegion, m3_queue_layout::{Error, FirmwareVa}}; pub(crate) const SIZE: usize = 0x893; +pub(crate) const EXTENDED_SIZE: usize = 0x8b0; pub(crate) const REGISTERS: usize = 0x20; pub(crate) const REGISTER_COUNT: usize = 22; /// G15G takes the same list without its last two registers (0xa599, 0xd411). @@ -64,12 +65,29 @@ impl Command { /// Validate before any output mutation. Encoding operates on ordinary host /// bytes; the caller copies them into the owned unpublished command buffer. pub(crate) fn encode(&self, out: &mut [u8]) -> Result<(), Error> { - if out.len() < SIZE { return Err(Error::Size); } + self.encode_for(super::m3_profile::Profile::Legacy, 0, 0, out) + } + + /// Firmware envelopes vary independently of the GPU's CDM stream format. + pub(crate) fn encode_for(&self, profile: super::m3_profile::Profile, + asid_generation: u8, event_slot: u8, out: &mut [u8]) -> Result<(), Error> { + self.encode_with_usc(profile, asid_generation, event_slot, 0, out) + } + + pub(crate) fn encode_with_usc(&self, profile: super::m3_profile::Profile, + asid_generation: u8, event_slot: u8, usc_base: u64, out: &mut [u8]) -> Result<(), Error> { + let extended = profile == super::m3_profile::Profile::Extended; + if usc_base & 0xffff_ffff != 0 || usc_base >= 1 << 42 + || (!extended && usc_base != 0) { return Err(Error::Address); } + let size = if extended { EXTENDED_SIZE } else { SIZE }; + if out.len() < size { return Err(Error::Size); } + if extended && (self.register_set != RegisterSet::G15G || event_slot >= 4 + || (self.context != 0 && asid_generation == 0)) { return Err(Error::Address); } if self.context >= 64 || self.cdm_last.get() < self.cdm.get() { return Err(Error::Address); } if self.preemption.size() < 0x14a0 || self.microsequence_size == 0 || self.microsequence_size > 4096 { return Err(Error::Size); } - self.gpu_alias.add((SIZE - 3) as u64)?; // inclusive final aligned dword + self.gpu_alias.add(((size - 1) & !3) as u64)?; // inclusive final aligned dword let registers = self.gpu_alias.add(REGISTERS as u64)?; let preemption = self.preemption.base(); let values = [ @@ -82,45 +100,157 @@ impl Command { (0x1a0b9, 0), (0x1a0c1, 0), (0x101d1, 0), (0xd479, 0), (0x1a0e9, 8), (0x107a1, 0xff0000), (0xa599, 0x13200400020), (0xd411, 0x200000001), ]; - let bytes = &mut out[..SIZE];bytes.fill(0); + let bytes = &mut out[..size];bytes.fill(0); word(bytes, 0, 3); // compute opcode - pointer(bytes, COUNTER, u64::from(self.counter)); - word(bytes, CONTEXT, self.context); - pointer(bytes, 0x14, self.notifier.get()); - let count = match self.register_set { + if extended { + word(bytes, 0xc, self.context); + pointer(bytes, 0x10, self.notifier.get()); + } else { + pointer(bytes, COUNTER, u64::from(self.counter)); + word(bytes, CONTEXT, self.context); + pointer(bytes, 0x14, self.notifier.get()); + } + let legacy_count = match self.register_set { RegisterSet::G15S => REGISTER_COUNT, RegisterSet::G15G => G15G_REGISTER_COUNT, }; - for (n, (register, value)) in values.into_iter().take(count).enumerate() { - word(bytes, REGISTERS+n*REGISTER_STRIDE, register); - pointer(bytes, REGISTERS+n*REGISTER_STRIDE+4, value); + let mut count = 0; + let mut emit = |register, value| { + word(bytes, REGISTERS + count * REGISTER_STRIDE, register); + pointer(bytes, REGISTERS + count * REGISTER_STRIDE + 4, value); + count += 1; + }; + let limit = if extended { REGISTER_COUNT } else { legacy_count }; + for (register, value) in values.into_iter().take(limit) { + if extended && register == 0x12091 { continue; } + emit(register, value); + } + if extended { + emit(0x1a540, 1); + emit(0x014a9, 1); + emit(0x0a351, 1); + if usc_base != 0 { emit(0x10071, usc_base); } } pointer(bytes, 0x720, registers.get()); bytes[0x728..0x72a].copy_from_slice(&(count as u16).to_le_bytes()); bytes[0x72a..0x72c].copy_from_slice(&((count*REGISTER_STRIDE) as u16).to_le_bytes()); pointer(bytes, 0x760, self.microsequence.get()); word(bytes, 0x768, self.microsequence_size); - // ComputeInfo2 mirrors the preemption buffer, inclusive CDM end and - // qualified control word from the register list. - pointer(bytes, 0x76c+0x28, preemption); - pointer(bytes, 0x76c+0x30, self.cdm_last.get()); - pointer(bytes, 0x76c+0x58, QUALIFIED_COMPUTE_CONTROL); - word(bytes, 0x7cc+0x14, u32::MAX); // EncoderParams.unk_mask - pointer(bytes, META, self.stamp.get()); - pointer(bytes, META+8, self.fw_stamp.get()); - word(bytes, STAMP_VALUE, self.stamp_value); - word(bytes, META+0x14, 2); // qualified compute event slot - word(bytes, FLUSH_STAMPS, u32::from(self.flush_stamps)); - word(bytes, META+0x20, 0xc10000); // producer UUID - word(bytes, QUEUE_COUNT, self.counter); - pointer(bytes, TAIL+8, self.timestamps[0].get()); - pointer(bytes, TAIL+16, self.timestamps[1].get()); - pointer(bytes, TAIL+0x2e, self.pool.get()); - bytes[TAIL+0x36] = 1; // SharedCommandTail.unk_846 - pointer(bytes, TAIL+0x37, 0xad0000); - pointer(bytes, TAIL+0x3f, 0x1600000); - pointer(bytes, TAIL+0x47, self.tail_scratch.get()); - bytes[TAIL+0x4f] = 1; // SharedCommandTail.unk_85f + let info = if extended { 0x770 } else { 0x76c }; + pointer(bytes, info + 0x28, preemption); + pointer(bytes, info + 0x30, self.cdm_last.get()); + pointer(bytes, info + 0x58, QUALIFIED_COMPUTE_CONTROL); + word(bytes, if extended { 0x7e8 } else { 0x7e0 }, u32::MAX); + let meta = if extended { 0x800 } else { META }; + pointer(bytes, meta, self.stamp.get()); + pointer(bytes, meta + 8, self.fw_stamp.get()); + word(bytes, meta + 0x10, self.stamp_value); + word(bytes, meta + 0x14, 2); + if extended { word(bytes, meta + 0x18, event_slot.into()); } + word(bytes, meta + 0x1c, u32::from(self.flush_stamps)); + word(bytes, meta + 0x20, 0xc10000); + word(bytes, meta + 0x24, self.counter); + let tail = if extended { 0x82c } else { TAIL }; + pointer(bytes, tail + 8, self.timestamps[0].get()); + pointer(bytes, tail + 16, self.timestamps[1].get()); + pointer(bytes, tail + if extended { 0x3e } else { 0x2e }, self.pool.get()); + bytes[tail + if extended { 0x46 } else { 0x36 }] = 1; + pointer(bytes, tail + if extended { 0x47 } else { 0x37 }, + if extended { 0x1588000 } else { 0xad0000 }); + pointer(bytes, tail + if extended { 0x4f } else { 0x3f }, + if extended { 0xef8000 } else { 0x1600000 }); + pointer(bytes, tail + if extended { 0x57 } else { 0x47 }, self.tail_scratch.get()); + if extended { bytes[0x88b] = asid_generation; } + else { bytes[TAIL + 0x4f] = 1; } Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use super::super::m3_profile::Profile; + + fn fixture() -> Command { + let firmware = |n: u64| FirmwareVa::new(0xffff_fc21_8000_0000 + n * 0x4000).unwrap(); + Command { + context: 3, counter: 7, notifier: firmware(0), + preemption: GpuRegion::new(0x5000_0000, 0x4000).unwrap(), + cdm: GpuVa::new(0x6000_0000).unwrap(), + cdm_last: GpuVa::new(0x6000_0ffc).unwrap(), + gpu_alias: GpuVa::new(0x7000_0000).unwrap(), + microsequence: firmware(1), microsequence_size: 0x300, + stamp: firmware(2), fw_stamp: firmware(3), stamp_value: 0x700, + timestamps: [firmware(4), firmware(5)], pool: firmware(6), + tail_scratch: firmware(7), flush_stamps: true, + register_set: RegisterSet::G15G, + } + } + fn read32(b: &[u8], o: usize) -> u32 { + u32::from_le_bytes(b[o..o + 4].try_into().unwrap()) + } + fn read64(b: &[u8], o: usize) -> u64 { + u64::from_le_bytes(b[o..o + 8].try_into().unwrap()) + } + + #[test] + fn extended_envelope_preserves_stream_and_native_visibility_boundary() { + let c = fixture(); + let mut b = [0xa5; EXTENDED_SIZE + 16]; + c.encode_for(Profile::Extended, 9, 2, &mut b).unwrap(); + assert_eq!(read32(&b, 0xc), c.context); + assert_eq!(read64(&b, 0x10), c.notifier.get()); + assert_eq!(read32(&b, 0x728) & 0xffff, 24); + let count = (read32(&b, 0x728) & 0xffff) as usize; + let cdm = (0..count).find(|n| read32(&b, 0x20 + n * 12) == 0x1a420).unwrap(); + assert_eq!(read64(&b, 0x24 + cdm * 12), c.cdm.get()); + assert_eq!(read64(&b, 0x7a0), c.cdm_last.get()); + assert_eq!(read64(&b, 0x800), c.stamp.get()); + assert_eq!(read64(&b, 0x808), c.fw_stamp.get()); + assert_eq!(read32(&b, 0x818), 2); + assert_eq!(read32(&b, 0x81c), 1); + assert_eq!(read64(&b, 0x834), c.timestamps[0].get()); + assert_eq!(read64(&b, 0x86a), c.pool.get()); + assert_eq!(read64(&b, 0x883), c.tail_scratch.get()); + assert_eq!(b[0x88b], 9); + assert_eq!(&b[EXTENDED_SIZE..], &[0xa5; 16]); + } + + #[test] + fn extended_validation_never_mutates_a_rejected_envelope() { + for (generation, slot) in [(0, 0), (1, 4), (1, 255)] { + let mut b = [0xa5; EXTENDED_SIZE]; + assert!(fixture().encode_for(Profile::Extended, generation, slot, &mut b).is_err()); + assert_eq!(b, [0xa5; EXTENDED_SIZE]); + } + let mut b = [0xa5; EXTENDED_SIZE - 1]; + assert!(fixture().encode_for(Profile::Extended, 1, 0, &mut b).is_err()); + assert_eq!(b, [0xa5; EXTENDED_SIZE - 1]); + } + + #[test] + fn legacy_wrapper_keeps_legacy_geometry_and_register_order() { + let c = fixture(); + let mut b = [0xa5; EXTENDED_SIZE]; + c.encode(&mut b).unwrap(); + assert_eq!(read32(&b, CONTEXT), c.context); + assert_eq!(read64(&b, 0x14), c.notifier.get()); + assert_eq!(read32(&b, 0x728) & 0xffff, 20); + assert_eq!(read32(&b, REGISTERS + 8 * REGISTER_STRIDE), 0x12091); + assert_eq!(read64(&b, META), c.stamp.get()); + assert_eq!(&b[SIZE..], &[0xa5; EXTENDED_SIZE - SIZE]); + } + #[test] + fn optional_usc_window_is_a_dispatch_register_without_stream_rewriting() { + let c = fixture(); + let mut output = [0xa5; EXTENDED_SIZE]; + c.encode_with_usc(Profile::Extended, 1, 0, 1 << 32, &mut output).unwrap(); + assert_eq!(read32(&output, 0x728) & 0xffff, 25); + assert_eq!(read32(&output, REGISTERS + 24 * REGISTER_STRIDE), 0x10071); + assert_eq!(read64(&output, REGISTERS + 24 * REGISTER_STRIDE + 4), 1 << 32); + output.fill(0xa5); + assert!(c.encode_with_usc(Profile::Extended, 1, 0, 1, &mut output).is_err()); + assert_eq!(output, [0xa5; EXTENDED_SIZE]); + } + +} diff --git a/drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs b/drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs index a10c3040d570f0..bd78c03823aa07 100644 --- a/drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs +++ b/drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs @@ -82,3 +82,113 @@ impl Sequence { Ok(()) } } + +impl Sequence { + pub(crate) const EXTENDED_LENGTH: usize = 0x300; + pub(crate) const EXTENDED_ATTACHMENTS: usize = 0x58; + pub(crate) const EXTENDED_HAS_ATTACHMENTS: usize = 0x2c0; + + /// The extended firmware operates on a native G15 CDM stream. Its firmware + /// microsequence has wider timestamp operations and different engine state. + pub(crate) fn encode_extended(&self, auxiliary: command::GpuVa, + context_store: super::m3_pool_layout::GpuRegion, queue_sequence: u64, + output: &mut [u8]) -> Result<(), Error> { + if self.context >= 64 || self.generation != 0 { return Err(Error::Address); } + if context_store.size() < 0x20000 { return Err(Error::Size); } + let registers = self.command.at(command::REGISTERS, 24 * command::REGISTER_STRIDE)?; + let engine = self.command.at(0x76c, 0x60)?; + let clock = self.command.at(0x82c, 8)?; + let pairs = self.command.at(0x834, 16)?; + let end_pair = self.command.at(0x83c, 8)?; + let pool_pointer = self.command.at(0x86a, 8)?; + let status = self.command.at(0x8ac, 4)?; + let time_aux = self.command.at(0x89c, 8)?; + let elapsed = self.command.at(0x7dc, 8)?; + let tail = self.command.at(0x88c, 8)?; + let activity = self.stats.at(0, 8)?; + let bytes = output.get_mut(..STORAGE).ok_or(Error::Size)?; + bytes.fill(0); + word(bytes, 0, 0x0b); + pointer(bytes, 0x14, registers); + pointer(bytes, 0x1c, activity); + pointer(bytes, 0x24, self.queue.get()); + word(bytes, 0x2c, self.context); + word(bytes, 0x30, 1); + pointer(bytes, 0x38, queue_sequence); + pointer(bytes, 0x44, engine); + word(bytes, 0x50, 1); + pointer(bytes, 0x160, self.pool.get()); + pointer(bytes, 0x178, auxiliary.get()); + for (offset, delta) in [0, 0xf400, 0x1e800, 0x1f000].into_iter().enumerate() { + pointer(bytes, 0x188 + offset * 8, context_store.base() + delta); + } + pointer(bytes, 0x1a8, status); + word(bytes, 0x1b8, 2); + // Timestamp records are 76 bytes; WFI sits between the two records. + for (offset, opcode, selected) in [(0x1bc, 0x8000_0003, pairs), (0x20c, 3, end_pair)] { + word(bytes, offset, opcode); + pointer(bytes, offset + 4, clock); + pointer(bytes, offset + 0xc, pairs); + pointer(bytes, offset + 0x14, selected); + pointer(bytes, offset + 0x1c, self.queue.get()); + pointer(bytes, offset + 0x2c, time_aux); + pointer(bytes, offset + 0x34, elapsed); + pointer(bytes, offset + 0x44, 0xc10000); + } + word(bytes, 0x208, 1); + let finish = 0x258; + word(bytes, finish, 0x0c); + pointer(bytes, finish + 4, activity); + pointer(bytes, finish + 0xc, self.queue.get()); + word(bytes, finish + 0x14, self.context); + pointer(bytes, finish + 0x18, engine); + pointer(bytes, finish + 0x24, 0xc10000); + pointer(bytes, finish + 0x2c, self.fw_stamp.get()); + word(bytes, finish + 0x34, self.stamp_value); + pointer(bytes, finish + 0x5c, pool_pointer); + word(bytes, finish + 0x64, (-(finish as i32)) as u32); + pointer(bytes, finish + 0x69, status); + pointer(bytes, finish + 0x71, tail); + word(bytes, 0x2d4, 0x4000_0002); + Ok(()) + } +} + +#[cfg(test)] +mod extended_tests { + use super::*; + fn fixture() -> Sequence { + let fw = |i: u64| FirmwareVa::new(0xffff_fc21_a000_0000 + i * 0x4000).unwrap(); + Sequence { command: Region::new(fw(0).get(), command::EXTENDED_SIZE).unwrap(), + stats: Region::new(fw(1).get(), 0xe10).unwrap(), + notifier: Region::new(fw(2).get(), 0x200).unwrap(), queue: fw(3), + context: 3, generation: 0, pool: fw(4), scratch: [fw(5); 5], + fw_stamp: fw(6), stamp_value: 0x700 } + } + fn ptr(bytes: &[u8], at: usize) -> u64 { u64::from_le_bytes(bytes[at..at + 8].try_into().unwrap()) } + #[test] fn extended_timestamps_and_retirement_refer_to_their_live_owners() { + let s = fixture(); + let mut output = [0xa5; STORAGE + 8]; + s.encode_extended(command::GpuVa::new(0x5000_0000).unwrap(), + super::super::m3_pool_layout::GpuRegion::new(0x6000_0000, 0x20000).unwrap(), 7, &mut output).unwrap(); + assert_eq!(ptr(&output, 0x1c), s.stats.at(0, 8).unwrap()); + assert_eq!(ptr(&output, 0x38), 7); + assert_eq!(ptr(&output, 0x188), 0x6000_0000); + assert_eq!(ptr(&output, 0x1bc + 0x14), s.command.at(0x834, 8).unwrap()); + assert_eq!(ptr(&output, 0x20c + 0x14), s.command.at(0x83c, 8).unwrap()); + assert_eq!(ptr(&output, 0x258 + 0x2c), s.fw_stamp.get()); + assert_eq!(ptr(&output, 0x258 + 0x5c), s.command.at(0x86a, 8).unwrap()); + assert_eq!(&output[STORAGE..], &[0xa5; 8]); + } + #[test] fn extended_sequence_rejects_legacy_command_extents_and_event_generations() { + for generation in [0, 1] { + let mut s = fixture(); + s.generation = generation; + if generation == 0 { s.command = Region::new(s.command.at(0, 1).unwrap(), command::SIZE).unwrap(); } + let mut output = [0xa5; STORAGE]; + assert!(s.encode_extended(command::GpuVa::new(0x5000_0000).unwrap(), + super::super::m3_pool_layout::GpuRegion::new(0x6000_0000, 0x20000).unwrap(), 0, &mut output).is_err()); + assert_eq!(output, [0xa5; STORAGE]); + } + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_compute_storage.rs b/drivers/gpu/drm/asahi/m3/m3_compute_storage.rs index 6bd3aecda387d3..a897a45f0922de 100644 --- a/drivers/gpu/drm/asahi/m3/m3_compute_storage.rs +++ b/drivers/gpu/drm/asahi/m3/m3_compute_storage.rs @@ -87,3 +87,69 @@ pub(crate) fn allocation(index:usize)->Result { pub(crate) fn coalesce_stamp_flush(wide:bool,timestamps:[u64;2],last:bool)->bool { wide && timestamps==[0,0] && !last } + +pub(crate) fn allocation_for(profile: super::m3_profile::Profile, index: usize) + -> Result { + let mut owner = allocation(index)?; + if profile == super::m3_profile::Profile::Legacy { return Ok(owner); } + match index { + // The initial allocator has eight 4-MiB payload owners. Retain the + // historical indices without allocating unused 4-MiB payloads. + 9..=22 => { owner.size = 0x4000; owner.mapping = Mapping::FirmwareOnly; } + POOL_PAGES => owner.size = 0x20000, + POOL_TABLE => { owner.size = 0x4000; owner.mapping = Mapping::Client(POOL_TABLE_VA & !0x3fff); } + POOL => owner.size = 0xa0, + GPU_CONTEXT => owner.size = 0x38, + NOTIFIER => owner.size = 0x200, + EVENT => owner.size = 8, + QUEUE => owner.size = 0x24c0, + PREEMPTION => owner.size = 0x4000, + COMMAND => { owner.size = 0x8b0; owner.mapping = Mapping::CommandAlias(0x73_0000_0000); } + TAIL_SCRATCH => { owner.size = 0x4000; owner.mapping = Mapping::Client(0x72_0100_0000); } + 46 => { owner.size = 0x40000; owner.mapping = Mapping::Client(0x72_0000_0000); } + 45 | 47..=49 => { owner.size = 0x4000; owner.mapping = Mapping::FirmwareOnly; } + _ => {} + } + Ok(owner) +} + +pub(crate) fn slot_offset_for(profile: super::m3_profile::Profile, index: usize, slot: usize) + -> Result { + if slot >= SLOTS { return Err(InvalidOwner); } + let size = allocation_for(profile, index)?.size; + Ok(if RESET.contains(&index) { ((size + 0x3fff) & !0x3fff) * slot } else { 0 }) +} + +pub(crate) fn batch_allocation_for(profile: super::m3_profile::Profile, index: usize) + -> Result { + let mut owner = allocation_for(profile, index)?; + owner.size += slot_offset_for(profile, index, SLOTS - 1)?; + Ok(owner) +} + +#[cfg(test)] +mod extended_tests { + use super::*; + use super::super::m3_profile::Profile; + #[test] fn extended_commands_do_not_straddle_another_slots_page() { + let command = allocation_for(Profile::Extended, COMMAND).unwrap(); + let Mapping::CommandAlias(base) = command.mapping else { panic!("missing command alias") }; + assert_eq!(base & 0x3fff, 0); + for slot in 0..SLOTS - 1 { + let start = slot_offset_for(Profile::Extended, COMMAND, slot).unwrap(); + let next = slot_offset_for(Profile::Extended, COMMAND, slot + 1).unwrap(); + assert!(start + command.size <= next); + } + assert_eq!(batch_allocation_for(Profile::Extended, QUEUE).unwrap().size, 0x24c0); + assert!(slot_offset_for(Profile::Extended, COMMAND, SLOTS).is_err()); + } + #[test] fn legacy_plan_is_byte_for_byte_the_same_for_every_owner_and_slot() { + for index in 0..COUNT { + assert_eq!(allocation_for(Profile::Legacy, index), allocation(index)); + for slot in 0..SLOTS { + assert_eq!(slot_offset_for(Profile::Legacy, index, slot), slot_offset(index, slot)); + } + assert_eq!(batch_allocation_for(Profile::Legacy, index), batch_allocation(index)); + } + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_config.rs b/drivers/gpu/drm/asahi/m3/m3_config.rs index c4a44c4b4f3b0e..bfb408b07a5307 100644 --- a/drivers/gpu/drm/asahi/m3/m3_config.rs +++ b/drivers/gpu/drm/asahi/m3/m3_config.rs @@ -49,6 +49,9 @@ impl Records { pub(crate) struct Config { objects: KVec, + profile: super::m3_profile::Profile, + control_generation: u32, + io_descriptors: Option>, _iomaps: KVec, pub(crate) completed_events: u64, pstates: crate::m3::m3_adt_config::PstateWatch, @@ -65,6 +68,17 @@ impl Config { pub(crate) fn new(dev: &driver::AsahiDrmDevice, uat: &mmu::Uat, firmware: &crate::m3::m3_firmware::Firmware, contents: &crate::m3::m3_adt_config::Contents) -> Result { contents.check_for_upload()?; + if let Some(inputs) = &contents.extended { + if firmware.profile() != super::m3_profile::Profile::Extended { return Err(EINVAL); } + let owners = ExtendedStorage::new(dev, uat, &inputs.power, &inputs.sram_uv, inputs.bases)?; + let thermal = crate::m3::m3_thermal::Governor::new(dev.as_ref().as_ref(), &contents.pstates)?; + return Ok(Self { objects: owners.objects, _iomaps: owners.iomaps, + profile: firmware.profile(), control_generation: 0, completed_events: 0, + io_descriptors: Some(owners.descriptors), + pstates: crate::m3::m3_adt_config::PstateWatch::new(contents.pstates), thermal, + request_top: 8, request_value: 0, request_checked: false, request_check_at: None, + request_writes: 0, request_observed: 0 }); + } let mut objects = KVec::new(); let mut iomaps = KVec::new(); for index in 0..storage::COUNT { @@ -140,7 +154,8 @@ impl Config { crate::m3::m3_adt_config::check_upload(dev.as_ref().as_ref(), firmware, contents.iomaps, &mut objects)?; agx_memory::publish(); let thermal = crate::m3::m3_thermal::Governor::new(dev.as_ref().as_ref(), &contents.pstates)?; - Ok(Self { objects, _iomaps: iomaps, completed_events: 0, + Ok(Self { objects, profile: firmware.profile(), control_generation: 0, io_descriptors: None, + _iomaps: iomaps, completed_events: 0, pstates: crate::m3::m3_adt_config::PstateWatch::new(contents.pstates), thermal, request_top:contents.table.0,request_value:0,request_checked:false, request_check_at:None,request_writes:0,request_observed:0 }) @@ -153,6 +168,15 @@ impl Config { objects[RUNTIME_POINTERS].write(0, &records.runtime)?; objects[CONTROL_REGION].write(0, &records.control) } + pub(crate) fn publish_io_table(&self, + pdev: &kernel::platform::Device>, + firmware: &crate::m3::m3_firmware::Firmware) -> Result { + if let Some(descriptors) = &self.io_descriptors { + firmware.publish_io_table(pdev, descriptors)?; + } + Ok(()) + } + pub(crate) fn root(&self) -> u64 { self.objects[storage::ROOT].va() } pub(crate) fn log_globals_debug(&mut self,dev:&driver::AsahiDrmDevice)->Result { let value=self.objects[GLOBALS].read_u32(crate::m3::m3_globals_debug::OFFSET)?; @@ -161,6 +185,8 @@ impl Config { Ok(()) } pub(crate) fn performance_request(&mut self,chip:u32,busy:bool)->Result { + // Native controller inputs supersede the legacy fixed-request interface. + if self.profile == super::m3_profile::Profile::Extended { return Ok(()); } let Some(value)=crate::m3::m3_consistent_perf::request(chip, crate::m3::m3_params::consistent_perf(),busy,self.request_top) else {return Ok(());}; if value==0 && self.request_value==0 {return Ok(());} @@ -192,6 +218,14 @@ impl Config { /// state the firmware reports against the ceiling and the runtime cap. Called after boot and /// after every job, by the job thread. pub(crate) fn check_pstate(&mut self, dev: &driver::AsahiDrmDevice, device: &crate::m3::m3_device::Device, what: &str) -> Result { + if self.profile == super::m3_profile::Profile::Extended { + let state = device.pstate_register()? & 15; + if state > self.request_top { + dev_err!(dev.as_ref(), "M3: native performance state {} exceeds {} ({})\n", state, self.request_top, what); + return Err(ERANGE); + } + return Ok(()); + } let now = kernel::time::Instant::::now(); if let Some(cap) = self.thermal.update(dev.as_ref().as_ref(), now) { crate::m3::m3_adt_config::publish_runtime_cap(&mut self.objects[GLOBALS], cap)?; @@ -203,13 +237,25 @@ impl Config { Ok(()) } pub(crate) fn stats_region(&self) -> Result { - let owner = &self.objects[HARDWARE_DATA]; + let owner = &self.objects[if self.profile == super::m3_profile::Profile::Extended { + UNKNOWN_C3 + } else { HARDWARE_DATA }]; init::Region::new(owner.va(), owner.size()).map_err(|_| EINVAL) } pub(crate) fn log_active_power(&mut self, dev: &driver::AsahiDrmDevice, device: &crate::m3::m3_device::Device, sequence: u32) -> Result { + if self.profile == super::m3_profile::Profile::Extended { + let state = device.pstate_register()? & 15; + let performance = &mut self.objects[super::m3_init_26::PERFORMANCE_CONTROL]; + let actual = performance.read_u32(0x2c)?; + let target = performance.read_u32(0x30)?; + let hardware = performance.read_u32(0x38)?; + dev_info!(dev.as_ref(), "M3_NATIVE_POWER sample={} state={} actual={} target={} hardware={}\n", + sequence, state, actual, target, hardware); + return Ok(()); + } let cap = self.thermal.allowed(kernel::time::Instant::::now()); self.pstates.log_active(dev.as_ref().as_ref(), &mut self.objects[HARDWARE_DATA], device.pstate_register()?, cap, sequence)?; @@ -229,6 +275,7 @@ impl Config { } pub(crate) fn render_pb(&mut self) -> Result { + if self.profile == super::m3_profile::Profile::Extended { return Err(EOPNOTSUPP); } self.objects[UNKNOWN_C1].write(0,&storage::parameter_buffer(crate::m3::m3_render::PB_PAGES).map_err(|_|EINVAL)?) } pub(crate) fn submit_queue(&mut self, kind:usize, queue:u64, head:u16,event:u8,new:bool)->Result { @@ -261,6 +308,9 @@ impl Config { Ok(true) } pub(crate) fn ready(&mut self) -> Result { + if self.profile == super::m3_profile::Profile::Extended { + return Ok(self.objects[RUNTIME_FLAGS].read_u32(0x14)? == 1); + } // NewRegionB+4 is an input initialized to 1, not an acknowledgement. // These are firmware-written transitions present in compute-15's // pre-submit snapshot; both start at zero in the exported schema. @@ -268,6 +318,13 @@ impl Config { && self.objects[RUNTIME_FLAGS].read_u32(4)? == 1) } pub(crate) fn log_ready(&mut self, dev: &driver::AsahiDrmDevice) -> Result { + if self.profile == super::m3_profile::Profile::Extended { + let ready = self.objects[RUNTIME_FLAGS].read_u32(0x14)?; + let consumed = self.objects[CONTROL_REGION].read_u32(0xeae8)?; + dev_info!(dev.as_ref(), "M3: extended firmware ready={} control-generation={}/{}\n", + ready, consumed, self.control_generation); + return Ok(()); + } let magic=self.objects[RUNTIME_POINTERS].read_u32(0x314)?; let state=self.objects[RUNTIME_POINTERS].read_u32(0x2fc)?; let a=self.objects[RUNTIME_FLAGS].read_u32(4)?; @@ -278,12 +335,14 @@ impl Config { } /// The firmware's halt counter and its halted and resume flags. pub(crate) fn halt_state(&mut self) -> Result<(u64,u32,u32)> { + if self.profile == super::m3_profile::Profile::Extended { return Err(EOPNOTSUPP); } let control=&mut self.objects[CONTROL_REGION]; Ok((control.read_u64(0x4580)?,control.read_u32(0x4590)?,control.read_u32(0x45a0)?)) } /// Let a halted firmware continue: clear halted, then set resume. These /// words keep the halt/resume layout of earlier Apple GPU firmware. pub(crate) fn resume_halted(&mut self) -> Result { + if self.profile == super::m3_profile::Profile::Extended { return Err(EOPNOTSUPP); } let control=&mut self.objects[CONTROL_REGION]; control.u32(0x4590,0)?; agx_memory::publish(); @@ -297,6 +356,18 @@ impl Config { /// at +0x40a0, and the details it leaves for a recovery event at +0x44a0. /// Nothing is acknowledged, cleared or resumed here. pub(crate) fn log_recovery_state(&mut self, dev: &driver::AsahiDrmDevice, events: u64) -> Result { + if self.profile == super::m3_profile::Profile::Extended { + for (owner, start, length) in [(super::m3_init_26::RECOVERY_ACTIVITY, 0, 0x60), + (CONTROL_REGION, 0x4ebc, 0x100)] { + for offset in (start..start + length).step_by(32) { + let mut bytes = [0; 32]; + self.objects[owner].read(offset, &mut bytes)?; + dev_err!(dev.as_ref(), "M3: firmware recovery owner={} offset={:#x} bytes={:02x?}\n", + owner, offset, bytes); + } + } + return Ok(()); + } let control=&mut self.objects[CONTROL_REGION]; let halt_count=control.read_u64(0x4580)?; let halted=control.read_u32(0x4590)?; @@ -312,7 +383,7 @@ impl Config { /// index (at most 16), as `drain` reads them. Nothing is consumed or acknowledged, and the /// completion count is not changed. pub(crate) fn log_event_ring(&mut self, dev: &driver::AsahiDrmDevice) -> Result { - const SIZE: usize = 0x38; + let size = if self.profile == super::m3_profile::Profile::Extended { 0x48 } else { 0x38 }; const COUNT: usize = 256; let r = self.objects[storage::EVENT*2].read_u32(0)? as usize; let w = self.objects[storage::EVENT*2].read_u32(0x20)? as usize; @@ -321,8 +392,8 @@ impl Config { let mut index = r; for _ in 0..16 { if index == w { break; } - let mut raw = [0u8; SIZE]; - self.objects[storage::EVENT*2+1].read(index * SIZE, &mut raw)?; + let mut raw = [0u8; 0x48]; + self.objects[storage::EVENT*2+1].read(index * size, &mut raw[..size])?; dev_info!(dev.as_ref(), "M3 event ring entry {} kind={} bytes={:02x?}\n", index, u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]), raw); index = (index + 1) % COUNT; @@ -343,10 +414,40 @@ impl Config { agx_memory::publish(); Ok(next) } + /// Publish an extended-format synchronous control. The caller rings the + /// doorbell after this method and waits for both retirement cursors. + pub(crate) fn control_extended(&mut self, action: super::m3_channel_format::Action) -> Result { + use super::m3_channel_format as format; + if self.profile != super::m3_profile::Profile::Extended { return Err(EINVAL); } + let state = storage::DEVICE_CONTROL * 2; + let cursors = [self.objects[state].read_u32(0)?, + self.objects[state].read_u32(0x10)?, self.objects[state].read_u32(0x20)?]; + let cursor = format::idle_cursor(cursors).map_err(|error| { + if error == init::Error::Bounds { EIO } else { EBUSY } + })?; + let consumed = self.objects[CONTROL_REGION].read_u32(0xeae8)?; + let generation = action.generation(self.control_generation, consumed).map_err(|_| EOVERFLOW)?; + let mut record = [0; format::CONTROL_BYTES]; + action.encode(&mut record).map_err(|_| EINVAL)?; + self.objects[state + 1].write(cursor as usize * format::CONTROL_BYTES, &record)?; + if generation != 0 { + self.objects[GLOBALS].write(0xe41, &generation.to_le_bytes())?; + } + agx_memory::publish(); + let next = (cursor + 1) % format::SLOTS; + self.objects[state].u32(0x20, next)?; + agx_memory::publish(); + self.control_generation = generation; + Ok(next) + } + pub(crate) fn control_done(&mut self, next: u32) -> Result { Ok(self.objects[storage::DEVICE_CONTROL*2].read_u32(0)? == next && self.objects[storage::DEVICE_CONTROL*2].read_u32(0x10)? == next) } pub(crate) fn drain(&mut self, dev: &driver::AsahiDrmDevice) -> Result { + if self.profile == super::m3_profile::Profile::Extended { + return self.drain_extended(dev); + } // Event, trace, stats and six independent firmware log rings. for (state, ring, size, count, channels) in [(storage::EVENT*2,storage::EVENT*2+1,0x38,256,1), (storage::TRACE*2,storage::TRACE*2+1,0x38,512,1), @@ -392,4 +493,147 @@ impl Config { } Ok(()) } + fn drain_extended(&mut self, dev: &driver::AsahiDrmDevice) -> Result { + use super::m3_channel_format as format; + // Snapshot each producer once. New arrivals remain for the next drain; + // a corrupt cursor is rejected before reading its ring allocation. + for (channel, capacity, threads) in [(storage::FIRMWARE_LOG, 256, 9), + (storage::EVENT, 256, 1), (storage::TRACE, 512, 1), (storage::STATS, 256, 1)] { + let state = channel * 2; + for thread in 0..threads { + let at = thread * 0x30; + let mut read = self.objects[state].read_u32(at)?; + let write = self.objects[state].read_u32(at + 0x20)?; + if read >= capacity || write >= capacity { return Err(EIO); } + let before = read; + while read != write { + let offset = (thread * capacity as usize + read as usize) * format::RECEIVE_BYTES; + let mut record = [0; format::RECEIVE_BYTES]; + self.objects[state + 1].read(offset, &mut record)?; + let kind = u32::from_le_bytes(record[..4].try_into().map_err(|_| EIO)?); + if channel == storage::FIRMWARE_LOG { + let slot = u32::from_le_bytes(record[8..12].try_into().map_err(|_| EIO)?); + let payload = format::payload_offset(thread, slot).map_err(|_| EIO)?; + if crate::debug::debug_enabled(crate::debug::DebugFlags::KTraceCh) { + let mut text = [0; 200]; + self.objects[FWLOG_PAYLOAD].read(payload + 0x10, &mut text)?; + let len = text.iter().position(|value| *value == 0).unwrap_or(text.len()); + dev_info!(dev.as_ref(), "M3 firmware log[{}]: {}\n", thread, + core::str::from_utf8(&text[..len]).unwrap_or("")); + } + } else if channel == storage::EVENT { + if kind != 1 { + dev_err!(dev.as_ref(), "M3 firmware event {}: {:02x?}\n", kind, record); + return Err(EIO); + } + let flags = u32::from_le_bytes(record[4..8].try_into().map_err(|_| EIO)?); + self.completed_events = self.completed_events.saturating_add((flags & 7).count_ones().into()); + } else if (channel == storage::TRACE && kind != 5) + || (channel == storage::STATS && kind == 0x16) { + dev_err!(dev.as_ref(), "M3 firmware channel {} record {}: {:02x?}\n", channel, kind, record); + return Err(EIO); + } + read = (read + 1) % capacity; + } + if read != before { self.objects[state].u32(at, read)?; } + } + } + agx_memory::publish(); + Ok(()) + } + +} + +/// All extended initialization owners, retained as one construction transaction. +/// Nothing is published to RTKit by this constructor. A failed allocation drops +/// its mappings before backing storage and leaves no root visible to firmware. +pub(crate) struct ExtendedStorage { + pub(crate) objects: KVec, + pub(crate) iomaps: KVec, + pub(crate) descriptors: KVec, + pub(crate) maximum_khz: u32, +} + +impl ExtendedStorage { + pub(crate) fn new( + dev: &driver::AsahiDrmDevice, + uat: &mmu::Uat, + pwr: &super::hw::PwrConfig, + sram_uv: &[u32], + bases: super::m3_mmio_26::RegisterBases, + ) -> Result { + use super::{m3_init_26 as plan, m3_mmio_26 as io}; + let mut objects = KVec::new(); + for index in 0..plan::COUNT { + let entry = plan::allocation(index).map_err(|_| EINVAL)?; + let alias = match index { + plan::GLOBAL_POOL => Some((uat.kernel_lower_vm(), 0x70_0000_0000)), + plan::GLOBAL_PARAMETERS => Some((uat.kernel_lower_vm(), 0x70_0000_8000)), + _ => None, + }; + let object = if plan::cached(index) { + Buffer::at_prot(dev, uat.kernel_vm(), alias, Some(entry.address), entry.size, + prot::PROT_FW_PRIV_RW, prot::PROT_GPU_SHARED_RW)? + } else { + Buffer::at(dev, uat.kernel_vm(), alias, Some(entry.address), entry.size, entry.gpu_shared)? + }; + objects.push(object, GFP_KERNEL)?; + } + let mut descriptors = KVec::new(); + descriptors.resize(io::SLOTS * io::RECORD_BYTES, 0, GFP_KERNEL)?; + let mut iomaps = KVec::new(); + for slot in 0..io::SLOTS { + let mut region = None; + if let Some(area) = io::area(slot, bases).map_err(|_| EINVAL)? { + let address = 0xffff_fc23_0000_0000 + slot as u64 * 0x10_0000; + let access = if area.writable() { prot::PROT_FW_MMIO_RW } else { prot::PROT_FW_MMIO_RO }; + for (physical, bytes, offset) in area.banks() { + if bytes == 0 { continue; } + let mapping = uat.kernel_vm().map_io_m3(address + offset as u64, + physical.try_into()?, bytes, access)?; + iomaps.push(mapping, GFP_KERNEL)?; + } + region = Some(init::Region::new(address, area.mapped_bytes()).map_err(|_| EINVAL)?); + } + io::encode(slot, bases, region, + &mut descriptors[slot * io::RECORD_BYTES..(slot + 1) * io::RECORD_BYTES]) + .map_err(|_| EINVAL)?; + } + // Runtime+0x18 references Fender SRAM, rather than anonymous DMA RAM. + // Retain its normal shared mapping independently of its MMIO view. + let scratch_physical = bases.gpu.checked_add(0xd6_0000).ok_or(EOVERFLOW)?; + let scratch = uat.kernel_vm().map_io_m3(0xffff_fc22_0000_0000, + scratch_physical.try_into()?, 0x8000, prot::PROT_FW_SHARED_RW)?; + let scratch_region = init::Region::new(scratch.iova(), 0x8000).map_err(|_| EINVAL)?; + iomaps.push(scratch, GFP_KERNEL)?; + let mut regions = [init::Region::new(0xffff_fc21_0000_0000, 1).map_err(|_| EINVAL)?; plan::COUNT]; + for (index, object) in objects.iter().enumerate() { + regions[index] = init::Region::new(object.va(), object.size()).map_err(|_| EINVAL)?; + } + let graph = plan::PointerGraph { owners: ®ions, scratch: scratch_region, + pool_alias: objects[plan::GLOBAL_POOL].gpu_va()?, + parameter_alias: objects[plan::GLOBAL_PARAMETERS].gpu_va()? }; + let mut root = [0; 0xc8]; + let mut runtime = [0; plan::RUNTIME_SIZE]; + let mut control = KVec::new(); + control.resize(plan::allocation(CONTROL_REGION).map_err(|_| EINVAL)?.size, 0, GFP_KERNEL)?; + graph.root(&mut root).map_err(|_| EINVAL)?; + graph.runtime(&mut runtime).map_err(|_| EINVAL)?; + graph.firmware_control(&mut control).map_err(|_| EINVAL)?; + let images = super::m3_power_26::PowerImages::new(pwr, sram_uv, + objects[plan::SENSOR_VALUE].va(), objects[plan::RETENTION_STATE].va(), + agx_memory::TIMESTAMP_RANGE.start, &descriptors)?; + objects[storage::ROOT].write(0, &root)?; + objects[RUNTIME_POINTERS].write(0, &runtime)?; + objects[CONTROL_REGION].write(0, &control)?; + objects[HARDWARE_DATA].write(0, &images.hardware)?; + objects[plan::PERFORMANCE_CONTROL].write(0, &images.performance)?; + objects[GLOBALS_POWER].write(0, &images.power)?; + objects[GLOBALS].write(0, &images.globals)?; + // Fragment scheduler entries start unassigned; these are input sentinels. + objects[UNKNOWN_C1].u32(0xc18, u32::MAX)?; + objects[UNKNOWN_C1].u32(0xc30, u32::MAX)?; + agx_memory::publish(); + Ok(Self { objects, iomaps, descriptors, maximum_khz: images.maximum_khz }) + } } diff --git a/drivers/gpu/drm/asahi/m3/m3_device.rs b/drivers/gpu/drm/asahi/m3/m3_device.rs index c43017a8933c0f..05a3961369a6d2 100644 --- a/drivers/gpu/drm/asahi/m3/m3_device.rs +++ b/drivers/gpu/drm/asahi/m3/m3_device.rs @@ -176,6 +176,12 @@ impl Device { /// Read-only snapshot. Do not select or acknowledge fault banks here; /// the firmware owns those registers until recovery is ported. pub(crate) fn log_engine_state(&self, vm: &crate::mmu::Vm) -> Result { + if self.firmware.profile() == super::m3_profile::Profile::Extended { + // Adaptive clock gating can leave individual engine banks inaccessible + // even while the GPU performance-state register says powered up. + dev_info!(self.dev.as_ref(), "M3: adaptive firmware fault evidence uses retained metadata; engine banks may be gated\n"); + return Ok(()); + } let sgx_guard = self.sgx.try_access().ok_or(ENODEV)?; let sgx = sgx_guard.as_view(); let pstate = sgx.try_read32(0xe01000)? & 0xf; diff --git a/drivers/gpu/drm/asahi/m3/m3_drm.rs b/drivers/gpu/drm/asahi/m3/m3_drm.rs index bcb9fd32c7d255..4aa982ff95a5cf 100644 --- a/drivers/gpu/drm/asahi/m3/m3_drm.rs +++ b/drivers/gpu/drm/asahi/m3/m3_drm.rs @@ -86,6 +86,7 @@ pub(crate) struct Backend { core_mask: u64, max_frequency_khz: u32, soc: &'static crate::m3::m3_soc::Soc, + profile: super::m3_profile::Profile, _progress: Pin>>, _activity: Pin>>, _memory: Pin>>, @@ -125,6 +126,7 @@ impl Backend { let firmware = crate::m3::m3_firmware::identify_loaded(pdev, soc, resources, experiment) .inspect_err(refused("the firmware identity"))?; dev_info!(pdev.as_ref(), "M3: firmware identity accepted\n"); + let profile = firmware.profile(); // InitData source and contents, before any GPU register access. let contents = crate::m3::m3_adt_config::Contents::select(pdev, &firmware, soc, experiment) .inspect_err(refused("the InitData contents"))?; @@ -160,7 +162,7 @@ impl Backend { let completion_work = crate::m3::m3_completion::Completion::new(shared.clone())?; // From here on every failure drops the backend, which stops the runtime. let backend = Arc::new(Self { shared, health, ids: gpu::SequenceIDs::default(), - scheduler, core_mask, max_frequency_khz, soc, + scheduler, core_mask, max_frequency_khz, soc, profile, _progress: progress, _activity: activity, _memory: memory, _timing: timing, _completion: completion, _overlap: overlap }, GFP_KERNEL)?; // The completion worker services this runtime from now on; the RTKit callbacks and @@ -208,7 +210,9 @@ impl gpu::Gpu for Backend { } params.max_frequency_khz = self.max_frequency_khz; params.usc_generation = 3; - params.gpu_hal_generation = hw::GpuHalGeneration::Legacy as u32; + params.gpu_hal_generation = self.profile.hal(); + params.max_commands_per_submission = super::agx_uapi::UAPI_MAX_HARDWARE_COMMANDS; + params.max_attachments = super::agx_uapi::UAPI_MAX_ATTACHMENTS as u32; Ok(()) } fn supports_vm_status(&self) -> bool { true } @@ -217,10 +221,12 @@ impl gpu::Gpu for Backend { fn feature_flags(&self) -> u64 { let mut flags = uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_VM_STATUS as u64 | uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_SCHEDULED_QUEUES as u64; - if self.soc.features.fragment_dependency && crate::m3::m3_params::early_tiling() { + if self.profile == super::m3_profile::Profile::Legacy + && self.soc.features.fragment_dependency && crate::m3::m3_params::early_tiling() { flags |= uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_FRAGMENT_DEPENDENCY as u64; } - if self.soc.features.compute_wide_visibility { + if self.profile == super::m3_profile::Profile::Legacy + && self.soc.features.compute_wide_visibility { flags |= uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_COMPUTE_WIDE_VISIBILITY as u64; } flags @@ -233,17 +239,17 @@ impl gpu::Gpu for Backend { } fn uat_geometry(&self) -> mmu::UatGeometry { mmu::UatGeometry { ias: 42, oas: 42 } } fn user_range(&self) -> Range { - mmu::UAT_PGSZ as u64..crate::m3::initdata::RUNTIME_UNKNOWN_PAGE + mmu::UAT_PGSZ as u64..self.profile.user_end() } /// The dummy page sits where InitData tells the firmware it is (unk_30), as on 7.1.12; the /// 42-bit geometry's own unk_page() is a different address. fn unknown_page(&self) -> u64 { - crate::m3::initdata::RUNTIME_UNKNOWN_PAGE + self.profile.user_end() } fn tracked_submission(&self) -> bool { true } /// Only M3-aware userspace may create a VM (7.1.12's per-file client gate). fn admit_vm(&self, dev: &kernel::device::Device, params_size: u64, flags: u32) -> Result { - crate::m3::m3_client::admit_vm(dev, params_size, flags) + crate::m3::m3_client::admit_vm_for(dev, self.profile, params_size, flags) } fn new_vm(&self, range: Range) -> Result { Option::as_mut(&mut *self.shared.lock()).ok_or(ENODEV)?.new_vm(self.ids.vm.next(), range) @@ -252,7 +258,8 @@ impl gpu::Gpu for Backend { _ualloc_priv: Arc>, priority: u32, usc_exec_base: u64) -> Result> { Ok(KBox::new(crate::m3::m3_submit::Queue::new(self.shared.clone(),self.scheduler.clone(),vm,priority,usc_exec_base, - self.soc.features.fragment_dependency)?,GFP_KERNEL)?) + self.feature_flags() & uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_FRAGMENT_DEPENDENCY as u64 != 0, + self.feature_flags() & uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_COMPUTE_WIDE_VISIBILITY as u64 != 0)?,GFP_KERNEL)?) } fn map_timestamp_buffer(&self, bo: gem::ObjectRef, range: Range) -> Result { crate::timestamp::Buffer::firmware( diff --git a/drivers/gpu/drm/asahi/m3/m3_firmware.rs b/drivers/gpu/drm/asahi/m3/m3_firmware.rs index 0d2835174c6775..6bc87a33aaa052 100644 --- a/drivers/gpu/drm/asahi/m3/m3_firmware.rs +++ b/drivers/gpu/drm/asahi/m3/m3_firmware.rs @@ -62,6 +62,19 @@ pub(crate) static KNOWN_IMAGES_T8122: [KnownImage; 1] = [KnownImage { initdata_magic: Some(G15_V14_8_3_INITDATA), }]; +/// Image record for the extended format. The SoC's runtime selection does not use it yet. +pub(crate) static T8122_IMAGES_26: [KnownImage; 1] = [KnownImage { + name: "T8122 C0 firmware-compat 26.6.2", + uuid: None, + stkg_sha256: Some([ + 0x3a, 0x6b, 0x8b, 0x5e, 0x83, 0x38, 0x4d, 0x5c, + 0xc1, 0x92, 0x9d, 0x91, 0xb6, 0x36, 0x40, 0xfe, + 0xe8, 0xf5, 0x52, 0xb5, 0x10, 0x8c, 0x4e, 0xa6, + 0x20, 0xbe, 0xdb, 0x7b, 0xbc, 0xa0, 0x31, 0x64, + ]), + initdata_magic: Some(0x0c89_c357_8392_04b8), +}]; + /// The same T8122 image for the start experiment (`t8122_start`, only with /// `asahi.t8122_start=1`), where `asahi.t8122_initdata_version` may replace the version. pub(crate) static KNOWN_IMAGES_T8122_EXPERIMENT: [KnownImage; 1] = [KnownImage { @@ -117,6 +130,10 @@ pub(crate) struct Layout { /// Offset of the 8-byte tag that precedes the 8 per-boot bytes zeroed before hashing. pub(crate) entropy_tag_offset: usize, pub(crate) entropy_tag: [u8; 8], + /// An additional independent boot value, when required by this image. + extra_boot_value: Option<(usize, [u8; 8])>, + /// Expected boot-table location and byte count in the image header. + boot_table: Option<(u32, u32)>, /// The firmware version, for logs. pub(crate) version: &'static str, } @@ -127,6 +144,8 @@ pub(crate) static T6030_LAYOUT: Layout = Layout { vas: [0xffff_fc00_0000_0000, 0xffff_fc00_0005_c000], entropy_tag_offset: 0x58e88, entropy_tag: *b"GKTS\x08\x00\x00\x00", + extra_boot_value: None, + boot_table: None, version: "RTKit-2419.140.12.release", }; @@ -137,23 +156,73 @@ pub(crate) static T8122_LAYOUT: Layout = Layout { vas: [0xffff_fc00_0000_0000, 0xffff_fc00_0006_4000], entropy_tag_offset: 0x5d54a, entropy_tag: *b"GKTS\x08\x00\x00\x00", + extra_boot_value: None, + boot_table: None, version: "RTKit-2419.140.12.release", }; pub(crate) const TEXT_SHA256: [u8; 32] = [ 0x11, 0xe4, 0x9f, 0x75, 0xb, 0x67, 0x1a, 0x2b, 0x13, 0xd0, 0x92, 0xdb, 0x1c, 0xbc, 0xa3, 0xa8, 0x77, 0x55, 0x86, 0xd6, 0xb1, 0xe3, 0xaa, 0xe8, 0xa6, 0xa4, 0x64, 0xa7, 0x80, 0x2, 0x93, 0xd5]; +/// T8122 C0, firmware-compat 26.6.2. Selection remains separate from runtime admission. +pub(crate) static T8122_LAYOUT_26: Layout = Layout { + text_size: 393216, + data_size: 1228800, + vas: [0xffff_fc00_0000_0000, 0xffff_fc00_0006_0000], + entropy_tag_offset: 380492, + entropy_tag: *b"GKTS\x08\x00\x00\x00", + extra_boot_value: Some((380719, *b"ECAP\x08\x00\x00\x00")), + boot_table: Some((380492, 561)), + version: "RTKit-3255.160.4.release", +}; + fn normalize_boot_entropy(layout: &Layout, text: &mut [u8]) -> bool { - let tag = layout.entropy_tag_offset; if text.len() != layout.text_size as usize { return false; } - let Some(end) = tag.checked_add(16) else { return false; }; - let Some(entropy) = text.get_mut(tag..end) else { return false; }; - if entropy[..8] != layout.entropy_tag { - return false; + if let Some((offset, count)) = layout.boot_table { + let Some(header) = text.get(0x22c..0x234) else { + return false; + }; + if header[..4] != offset.to_le_bytes() || header[4..] != count.to_le_bytes() { + return false; + } + let Some(end) = (offset as usize).checked_add(count as usize) else { + return false; + }; + if text.get(offset as usize..end).is_none() { + return false; + } + } + let fields = [ + Some((layout.entropy_tag_offset, layout.entropy_tag)), + layout.extra_boot_value, + ]; + // Validate every field before touching even the first boot value. + for (offset, tag) in fields.into_iter().flatten() { + let Some(end) = offset.checked_add(16) else { + return false; + }; + let Some(field) = text.get(offset..end) else { + return false; + }; + if field[..8] != tag { + return false; + } + if let Some((base, count)) = layout.boot_table { + if offset < base as usize || end > base as usize + count as usize { + return false; + } + } + } + if let Some((other, _)) = layout.extra_boot_value { + if other < layout.entropy_tag_offset + 16 && layout.entropy_tag_offset < other + 16 { + return false; + } + } + for (offset, _) in fields.into_iter().flatten() { + text[offset + 8..offset + 16].fill(0); } - entropy[8..].fill(0); true } @@ -167,6 +236,14 @@ pub(crate) struct Firmware { } impl Firmware { + pub(crate) fn profile(&self) -> super::m3_profile::Profile { + if core::ptr::eq(self.layout, &T8122_LAYOUT_26) { + super::m3_profile::Profile::Extended + } else { + super::m3_profile::Profile::Legacy + } + } + pub(crate) const fn version(&self) -> &'static str { self.layout.version } @@ -178,6 +255,51 @@ impl Firmware { } } +/// Only the admitted extended image has this host-owned boot descriptor slot. +fn loader_span(profile: super::m3_profile::Profile, data_bytes: u64, input_bytes: usize) + -> Option> { + if profile != super::m3_profile::Profile::Extended || input_bytes != 53 * 40 + || data_bytes != T8122_LAYOUT_26.data_size { return None; } + let start: usize = 0x12_28e0; + let end = start.checked_add(input_bytes)?; + if end as u64 > data_bytes { return None; } + Some(start..end) +} + +#[cfg(not(test))] +impl Firmware { + /// Call after RTKit application startup and before exposing the InitData root. + /// The protocol grants the host this slot only during that boot interval. + pub(crate) fn publish_io_table( + &self, + pdev: &kernel::platform::Device>, + descriptors: &[u8], + ) -> kernel::error::Result { + use kernel::{c_str, io::mem::{Mem, MemFlag}, prelude::*}; + let data = self.resources.regions[5]; + let span = loader_span(self.profile(), data.size, descriptors.len()).ok_or(EINVAL)?; + let node = pdev.as_ref().of_node().ok_or(ENODEV)?; + let resource = agx_resources::reserved_resource(&node, c_str!("fw-data"))?; + if resource.start() != data.base || resource.size() != data.size { return Err(EINVAL); } + // SAFETY: This exact admitted no-map reservation remains firmware-owned. + // Only its bounded host boot slot is accessed, before root publication. + let mapping = unsafe { Mem::try_new(resource, MemFlag::WC.into()) }?; + if span.end > mapping.size() { return Err(EINVAL); } + // Validate the whole slot before changing any byte. Unexpected prior + // contents indicate a protocol/ownership mismatch, not a patchable image. + for offset in span.clone() { + // SAFETY: span is bounded by the retained mapping above. + if unsafe { mapping.ptr().add(offset).read_volatile() } != 0 { return Err(EBUSY); } + } + for (offset, byte) in span.zip(descriptors.iter().copied()) { + // SAFETY: same bounded host slot, exclusive under the boot protocol. + unsafe { mapping.ptr().add(offset).write_volatile(byte) }; + } + crate::m3::agx_memory::publish(); + Ok(()) + } +} + #[cfg(not(test))] pub(crate) fn identify_loaded( pdev: &kernel::platform::Device>, @@ -393,4 +515,75 @@ mod tests { assert!(!normalize_boot_entropy(&layout, &mut text)); assert_eq!(text, vec![0xa5; 15]); } + + fn image_26() -> Vec { + let mut image = vec![0x5a; 393216]; + image[0x22c..0x230].copy_from_slice(&380492u32.to_le_bytes()); + image[0x230..0x234].copy_from_slice(&561u32.to_le_bytes()); + image[380492..380500].copy_from_slice(b"GKTS\x08\x00\x00\x00"); + image[380719..380727].copy_from_slice(b"ECAP\x08\x00\x00\x00"); + image + } + + #[test] + fn newer_image_masks_only_both_boot_values() { + let mut image = image_26(); + let before = image.clone(); + assert!(normalize_boot_entropy(&T8122_LAYOUT_26, &mut image)); + for index in 0..image.len() { + let expected = if (380500..380508).contains(&index) || (380727..380735).contains(&index) + { + 0 + } else { + before[index] + }; + assert_eq!(image[index], expected, "byte {index}"); + } + } + + #[test] + fn invalid_second_field_or_table_does_not_mask_first_value() { + for index in [0x22c, 0x230, 380492, 380496, 380719, 380723] { + let mut image = image_26(); + image[index] ^= 1; + let before = image.clone(); + assert!(!normalize_boot_entropy(&T8122_LAYOUT_26, &mut image)); + assert_eq!(image, before); + } + let mut image = image_26(); + image.pop(); + let before = image.clone(); + assert!(!normalize_boot_entropy(&T8122_LAYOUT_26, &mut image)); + assert_eq!(image, before); + } + + #[test] + fn extended_record_cannot_replace_legacy_identity() { + let newer = &T8122_IMAGES_26[0]; + let digest = newer.stkg_sha256.unwrap(); + assert!(newer.accepts_runtime(None, &digest)); + assert_eq!(newer.initdata_magic, Some(0x0c89_c357_8392_04b8)); + for index in 0..digest.len() { + let mut wrong = digest; + wrong[index] ^= 1; + assert!(!newer.accepts_runtime(None, &wrong)); + } + assert!(!KNOWN_IMAGES_T8122[0].accepts_runtime(None, &digest)); + assert!(!newer.accepts_runtime(KNOWN_IMAGES_T8122[0].uuid, &[0; 32])); + } + + #[test] + fn loader_slot_is_exact_bounded_and_profile_specific() { + use super::super::m3_profile::Profile; + let length = 53 * 40; + assert_eq!(loader_span(Profile::Extended, 0x12c000, length), Some(0x1228e0..0x123128)); + for size in [0, 0x123127, 0x12bfff, 0x12c001, u64::MAX] { + assert!(loader_span(Profile::Extended, size, length).is_none()); + } + for bytes in [0, length - 1, length + 1, usize::MAX] { + assert!(loader_span(Profile::Extended, 0x12c000, bytes).is_none()); + } + assert!(loader_span(Profile::Legacy, 0x12c000, length).is_none()); + } + } diff --git a/drivers/gpu/drm/asahi/m3/m3_init_26.rs b/drivers/gpu/drm/asahi/m3/m3_init_26.rs new file mode 100644 index 00000000000000..a86432b2bbe2b5 --- /dev/null +++ b/drivers/gpu/drm/asahi/m3/m3_init_26.rs @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Allocation and pointer records for the extended M3 initialization format. +//! Owners supply mapped regions; these builders never allocate or publish DMA. + +use super::m3_init_layout::{Error, Region, Root}; +use super::m3_init_storage::{self as storage, Allocation, Initial}; + +pub(crate) const COUNT: usize = 56; +pub(crate) const DYNAMIC_STATE: usize = 49; +pub(crate) const PERFORMANCE_CONTROL: usize = 50; +pub(crate) const RECOVERY_ACTIVITY: usize = 51; +pub(crate) const SENSOR_VALUE: usize = 52; +pub(crate) const RETENTION_STATE: usize = 53; +pub(crate) const GLOBAL_POOL: usize = 54; +pub(crate) const GLOBAL_PARAMETERS: usize = 55; +pub(crate) const RUNTIME_SIZE: usize = 0x4b8; +pub(crate) const LOG_THREADS: usize = 9; +pub(crate) const RX_BYTES: usize = 0x48; +pub(crate) const LOG_PAYLOAD_BYTES: usize = 0xd8; + +/// Extend the existing M3 owner indices instead of introducing another owner graph. +pub(crate) fn allocation(index: usize) -> Result { + let size = match index { + 0..=23 if index % 2 == 0 => 0x30, + 0..=23 => 0x1800, + 24 | 26 | 30 | 32 => 0x30, + 25 => 0x4000, + 27 | 33 => 0x4800, + 28 => LOG_THREADS * 0x30, + 29 => LOG_THREADS * 256 * RX_BYTES, + 31 => 512 * RX_BYTES, + 34 => 0x40, + 35 => 0x3fc0, + 36 => 0xc8, + 37 => 0x4000, + 38 => RUNTIME_SIZE, + 39 => 0x2658, + 40 => 0x88, + 41 => LOG_THREADS * 256 * LOG_PAYLOAD_BYTES, + 42 => 0xc18, + 43 => 0x1248, + 44 => 0xe10, + 45 => 0xe48, + 46 => 0x670, + 47 => 0xeaf0, + 48 => 0x20, + DYNAMIC_STATE | RETENTION_STATE | GLOBAL_POOL | GLOBAL_PARAMETERS => 0x4000, + PERFORMANCE_CONTROL => 0x6578, + RECOVERY_ACTIVITY => 0x60, + SENSOR_VALUE => 4, + _ => return Err(Error::Bounds), + }; + // Separate page-aligned owners, with room for the largest receive allocation. + // This namespace is disjoint from the existing fixed work and timestamp arenas. + Ok(Allocation { + address: 0xffff_fc21_0000_0000 + (index as u64) * 0x10_0000, + size, + gpu_shared: matches!(index, GLOBAL_POOL | GLOBAL_PARAMETERS), + initial: Initial::Zero, + }) +} + +pub(crate) fn cached(index: usize) -> bool { + matches!(index, 39 | PERFORMANCE_CONTROL) +} + +pub(crate) struct PointerGraph<'a> { + pub(crate) owners: &'a [Region; COUNT], + pub(crate) scratch: Region, + pub(crate) pool_alias: u64, + pub(crate) parameter_alias: u64, +} + +impl PointerGraph<'_> { + /// Check the complete graph, including padded mapping ownership, before encoding it. + fn addresses(&self) -> Result<[u64; COUNT], Error> { + let mut addresses = [0; COUNT]; + for (index, owner) in self.owners.iter().enumerate() { + let size = allocation(index)?.size; + addresses[index] = owner.at(0, size)?; + if addresses[index] & 0x3fff != 0 + || addresses[index] < 0xffff_fc20_0000_0000 + || addresses[index] >= 0xffff_fc2f_fc00_0000 + { + return Err(Error::Address); + } + let mapped = (size + 0x3fff) & !0x3fff; + let end = addresses[index] + .checked_add(mapped as u64) + .ok_or(Error::Address)?; + if end > 0xffff_fc2f_fc00_0000 { + return Err(Error::Address); + } + for prior in 0..index { + let prior_size = (allocation(prior)?.size + 0x3fff) & !0x3fff; + if addresses[index] < addresses[prior] + prior_size as u64 + && addresses[prior] < addresses[index] + mapped as u64 + { + return Err(Error::Address); + } + } + } + let scratch = self.scratch.at(0, 0x8000)?; + let scratch_end = scratch.checked_add(0x8000).ok_or(Error::Address)?; + if scratch & 0x3fff != 0 + || scratch < 0xffff_fc20_0000_0000 + || scratch_end > 0xffff_fc2f_fc00_0000 + { + return Err(Error::Address); + } + for (index, address) in addresses.iter().enumerate() { + let bytes = (allocation(index)?.size + 0x3fff) & !0x3fff; + if scratch < *address + bytes as u64 && *address < scratch_end { + return Err(Error::Address); + } + } + for alias in [self.pool_alias, self.parameter_alias] { + if alias & 0x3fff != 0 || !(0x70_0000_0000..0x70_0200_0000).contains(&alias) { + return Err(Error::Address); + } + } + if self.pool_alias == self.parameter_alias { + return Err(Error::Address); + } + Ok(addresses) + } + + pub(crate) fn root(&self, output: &mut [u8]) -> Result<(), Error> { + self.addresses()?; + Root { + region_a: self.owners[storage::REGION_A], + runtime: self.owners[storage::RUNTIME_POINTERS], + globals: self.owners[storage::GLOBALS], + new_a: self.owners[storage::RUNTIME_FLAGS], + control: self.owners[storage::CONTROL_REGION], + power: self.owners[storage::GLOBALS_POWER], + } + .encode_26(output, self.owners[DYNAMIC_STATE]) + } + + pub(crate) fn runtime(&self, output: &mut [u8]) -> Result<(), Error> { + let addresses = self.addresses()?; + let scratch = self.scratch.at(0, 0x8000)?; + let bytes = output.get_mut(..RUNTIME_SIZE).ok_or(Error::Size)?; + bytes.fill(0); + let pointer = |bytes: &mut [u8], offset: usize, value: u64| { + bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); + }; + pointer(bytes, 0x18, scratch); + pointer(bytes, 0x2d0, self.parameter_alias); + pointer(bytes, 0x2e0, self.pool_alias); + bytes[0x250..0x254].copy_from_slice(&1u32.to_le_bytes()); + bytes[0x2f8..0x2fc].copy_from_slice(&4u32.to_le_bytes()); + for (offset, owner) in [ + (0, storage::HARDWARE_DATA), + (0x10, storage::UNKNOWN_PAIR), + (0x1c0, 26), + (0x1c8, 27), + (0x1d0, 28), + (0x1d8, 29), + (0x1e0, 30), + (0x1e8, 31), + (0x1f0, 32), + (0x1f8, 33), + (0x200, storage::FWLOG_PAYLOAD), + (0x254, storage::UNKNOWN_C0), + (0x25c, storage::UNKNOWN_C1), + (0x264, storage::UNKNOWN_C3), + (0x26c, RECOVERY_ACTIVITY), + (0x2d8, GLOBAL_PARAMETERS), + (0x2e8, GLOBAL_POOL), + (0x469, PERFORMANCE_CONTROL), + ] { + pointer(bytes, offset, addresses[owner]); + } + for channel in 0..13 { + let offset = 0x20 + channel * 0x20; + let state = addresses[channel * 2]; + pointer(bytes, offset, state); + pointer(bytes, offset + 8, state + 0x10); + pointer(bytes, offset + 16, state + 0x20); + pointer(bytes, offset + 24, addresses[channel * 2 + 1]); + } + Ok(()) + } + + pub(crate) fn firmware_control(&self, output: &mut [u8]) -> Result<(), Error> { + let addresses = self.addresses()?; + let bytes = output + .get_mut(..allocation(storage::CONTROL_REGION)?.size) + .ok_or(Error::Size)?; + bytes.fill(0); + bytes[0x4f88..0x4f90].copy_from_slice(&addresses[storage::FW_CONTROL_STATE].to_le_bytes()); + bytes[0x4f90..0x4f98].copy_from_slice(&addresses[storage::FW_CONTROL_RING].to_le_bytes()); + bytes[0x4fd4..0x4fd8].copy_from_slice(&1u32.to_le_bytes()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn owners(delta: u64) -> [Region; COUNT] { + core::array::from_fn(|index| { + let item = allocation(index).unwrap(); + Region::new(item.address + delta, item.size).unwrap() + }) + } + + fn graph(owners: &[Region; COUNT]) -> PointerGraph<'_> { + PointerGraph { + owners, + scratch: Region::new(0xffff_fc22_0000_0000, 0x8000).unwrap(), + pool_alias: 0x70_0000_0000, + parameter_alias: 0x70_0000_8000, + } + } + + #[test] + fn complete_plan_has_disjoint_page_owners_and_nine_log_threads() { + let owners = owners(0); + assert!(graph(&owners).addresses().is_ok()); + assert_eq!(allocation(28).unwrap().size, 0x1b0); + assert_eq!(allocation(29).unwrap().size, 0x28800); + assert_eq!(allocation(41).unwrap().size, 0x79800); + assert!(allocation(COUNT).is_err()); + } + + #[test] + fn runtime_references_relocate_and_keep_the_packed_power_pointer() { + for delta in [0, 0x1000_0000] { + let owners = owners(delta); + let mut output = [0xa5; RUNTIME_SIZE + 8]; + graph(&owners).runtime(&mut output).unwrap(); + let read = |at| u64::from_le_bytes(output[at..at + 8].try_into().unwrap()); + assert_eq!( + read(0x469), + allocation(PERFORMANCE_CONTROL).unwrap().address + delta + ); + assert_eq!(read(0x1a0), allocation(24).unwrap().address + delta); + assert_eq!(read(0x1b8), allocation(25).unwrap().address + delta); + assert_eq!(read(0x2d0), 0x70_0000_8000); + assert_eq!(&output[RUNTIME_SIZE..], &[0xa5; 8]); + } + } + + #[test] + fn every_short_runtime_rejects_before_mutation() { + let owners = owners(0); + for length in 0..RUNTIME_SIZE { + let mut output = [0xa5; RUNTIME_SIZE]; + assert_eq!( + graph(&owners).runtime(&mut output[..length]), + Err(Error::Size) + ); + assert_eq!(output, [0xa5; RUNTIME_SIZE]); + } + } + + #[test] + fn every_owner_is_checked_before_any_record_is_written() { + for index in 0..COUNT { + let mut owners = owners(0); + let item = allocation(index).unwrap(); + owners[index] = Region::new(item.address, item.size - 1).unwrap(); + let mut output = [0xa5; RUNTIME_SIZE]; + assert!(graph(&owners).runtime(&mut output).is_err()); + assert_eq!(output, [0xa5; RUNTIME_SIZE]); + } + let mut owners = owners(0); + owners[1] = owners[0]; + let mut output = [0xa5; RUNTIME_SIZE]; + assert!(graph(&owners).runtime(&mut output).is_err()); + assert_eq!(output, [0xa5; RUNTIME_SIZE]); + } + #[test] + fn only_atomic_controller_owners_are_firmware_cacheable() { + let selected: Vec<_> = (0..COUNT).filter(|index| cached(*index)).collect(); + assert_eq!(selected, vec![39, PERFORMANCE_CONTROL]); + } + + #[test] + fn scratch_requires_its_firmware_host_window() { + let owners = owners(0); + let mut value = graph(&owners); + value.scratch = Region::new(0xffff_fc10_0000_0000, 0x8000).unwrap(); + let mut output = [0xa5; RUNTIME_SIZE]; + assert_eq!(value.runtime(&mut output), Err(Error::Address)); + assert_eq!(output, [0xa5; RUNTIME_SIZE]); + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_init_layout.rs b/drivers/gpu/drm/asahi/m3/m3_init_layout.rs index 92cc818e030ccb..75f06abe6f4110 100644 --- a/drivers/gpu/drm/asahi/m3/m3_init_layout.rs +++ b/drivers/gpu/drm/asahi/m3/m3_init_layout.rs @@ -78,13 +78,38 @@ impl Root { (root::CONTROL, self.control.at(0, CONTROL_SIZE)?), (root::POWER, self.power.at(0, 2292)?), ]; - let bytes = out.get_mut(..ROOT_SIZE).ok_or(Error::Size)?; + Self::write_root(out, ROOT_SIZE, 0x0c08_e21e_8380_0490, &references) + } + + /// Encode the extended root without admitting a firmware image or publishing it. + pub(crate) fn encode_26(&self, out: &mut [u8], extra: Region) -> Result<(), Error> { + let references = [ + (root::REGION_A, self.region_a.at(0, 0x4000)?), + (root::RUNTIME, self.runtime.at(0, 0x4b8)?), + (root::GLOBALS, self.globals.at(0, 0xe48)?), + (root::NEW_A, self.new_a.at(0, 0x20)?), + (root::CONTROL, self.control.at(0, 0xeaf0)?), + (root::POWER, self.power.at(0, 0x670)?), + (0xc0, extra.at(0, 0x4000)?), + ]; + if references.iter().any(|(_, address)| address & 7 != 0) { + return Err(Error::Address); + } + Self::write_root(out, 0xc8, 0x0c89_c357_8392_04b8, &references) + } + + fn write_root( + out: &mut [u8], + size: usize, + version: u64, + references: &[(usize, u64)], + ) -> Result<(), Error> { + let bytes = out.get_mut(..size).ok_or(Error::Size)?; bytes.fill(0); - // Opaque qualified G15/V14_8_3 version tuple, not a semantic version. - for (i, value) in [0x490u16, 0x8380, 0xe21e, 0xc08].iter().enumerate() { - bytes[root::VERSION + i * 2..root::VERSION + i * 2 + 2].copy_from_slice(&value.to_le_bytes()); + pointer(bytes, root::VERSION, version); + for &(offset, value) in references { + pointer(bytes, offset, value); } - for (offset, value) in references { pointer(bytes, offset, value); } word(bytes, root::HOST_ALLOCATIONS, 1); bytes[root::PAGE_SIZE..root::PAGE_SIZE + 2].copy_from_slice(&0x4000u16.to_le_bytes()); bytes[root::PAGE_BITS] = 14; @@ -193,3 +218,107 @@ impl ControlRegion { Ok(()) } } + +#[cfg(test)] +mod root_format_tests { + use super::*; + + fn allocations() -> (Root, Region) { + let region = + |index: u64| Region::new(0xffff_fc01_0000_0000 + index * 0x10000, 0x10000).unwrap(); + ( + Root { + region_a: region(1), + runtime: region(2), + globals: region(3), + new_a: region(4), + control: region(5), + power: region(6), + }, + region(7), + ) + } + + #[test] + fn root_revision_changes_only_the_version_and_extension() { + let (root, extra) = allocations(); + let mut older = [0x55; 0xd0]; + let mut newer = older; + root.encode(&mut older).unwrap(); + root.encode_26(&mut newer, extra).unwrap(); + assert_eq!(&older[8..0xc0], &newer[8..0xc0]); + assert_eq!( + u64::from_le_bytes(newer[..8].try_into().unwrap()), + 0x0c89_c357_8392_04b8 + ); + assert_eq!( + u64::from_le_bytes(newer[0xc0..0xc8].try_into().unwrap()), + 0xffff_fc01_0007_0000 + ); + assert_eq!(&older[0xc0..], &[0x55; 16]); + assert_eq!(&newer[0xc8..], &[0x55; 8]); + for (offset, index) in [ + (8, 1), + (0x18, 2), + (0x20, 3), + (0xa8, 4), + (0xb0, 5), + (0xb8, 6), + ] { + assert_eq!( + u64::from_le_bytes(newer[offset..offset + 8].try_into().unwrap()), + 0xffff_fc01_0000_0000 + index * 0x10000 + ); + } + } + + #[test] + fn every_short_destination_is_unchanged() { + let (root, extra) = allocations(); + for length in 0..0xc8 { + let mut output = [0xa5; 0xc8]; + assert_eq!( + root.encode_26(&mut output[..length], extra), + Err(Error::Size) + ); + assert_eq!(output, [0xa5; 0xc8]); + } + } + + #[test] + fn every_reference_requires_its_own_complete_allocation() { + for (index, required) in [0x4000, 0x4b8, 0xe48, 0x20, 0xeaf0, 0x670, 0x4000] + .into_iter() + .enumerate() + { + for (size, aligned, valid) in [ + (required - 1, true, false), + (required, false, false), + (required, true, true), + ] { + let (mut root, mut extra) = allocations(); + let reference = match index { + 0 => &mut root.region_a, + 1 => &mut root.runtime, + 2 => &mut root.globals, + 3 => &mut root.new_a, + 4 => &mut root.control, + 5 => &mut root.power, + _ => &mut extra, + }; + *reference = + Region::new(0xffff_fc02_0000_0000 + u64::from(!aligned), size).unwrap(); + let mut output = [0xa5; 0xc8]; + let result = root.encode_26(&mut output, extra); + assert_eq!( + result.is_ok(), + valid, + "reference {index}, size {size}, aligned {aligned}" + ); + if !valid { + assert_eq!(output, [0xa5; 0xc8]); + } + } + } + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_mmio_26.rs b/drivers/gpu/drm/asahi/m3/m3_mmio_26.rs new file mode 100644 index 00000000000000..a17bc0ee399c34 --- /dev/null +++ b/drivers/gpu/drm/asahi/m3/m3_mmio_26.rs @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! T8122 firmware MMIO descriptors. Mapping ownership stays with the M3 runtime. + +use super::m3_init_layout::{Error, Region}; + +pub(crate) const SLOTS: usize = 53; +pub(crate) const RECORD_BYTES: usize = 40; +pub(crate) const TABLE_OFFSET: usize = 0x640; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Area { + pub(crate) physical: u64, + pub(crate) bytes: usize, + relative_register: u64, + writable: bool, + split: bool, +} + +impl Area { + pub(crate) fn writable(self) -> bool { + self.writable + } + + pub(crate) fn page_offset(self) -> usize { + (self.physical & 0x3fff) as usize + } + + pub(crate) fn mapped_bytes(self) -> usize { + (self.page_offset() + self.bytes + 0x3fff) & !0x3fff + } + + /// At most two physical banks; the second bank occupies the next virtual pages. + pub(crate) fn banks(self) -> [(u64, usize, usize); 2] { + if self.split { + [ + (self.physical, self.bytes / 2, 0), + (self.physical + 0x200_0000, self.bytes / 2, self.bytes / 2), + ] + } else { + [(self.physical & !0x3fff, self.mapped_bytes(), 0), (0, 0, 0)] + } + } +} + +/// Register resources obtained from the admitted T8122 hardware configuration. +/// Their physical addresses are supplied by the owner, not duplicated in a wire table. +#[derive(Clone, Copy)] +pub(crate) struct RegisterBases { + pub(crate) gpu: u64, + pub(crate) interrupt: u64, + pub(crate) memory: u64, + pub(crate) telemetry_write: u64, + pub(crate) telemetry_read: u64, + pub(crate) neural: u64, + pub(crate) metrology: u64, +} + +/// Select the wire slot and map its logical hardware block independently of its VA. +pub(crate) fn area(slot: usize, bases: RegisterBases) -> Result, Error> { + let (base, offset, bytes, tagged, writable, split) = match slot { + 0 => (bases.interrupt, 0, 0x4000, false, true, false), + 3 => (bases.memory, 0, 0x30000, false, true, true), + 9 => (bases.telemetry_write, 0, 0x1000, false, true, false), + 10 => (bases.telemetry_read, 0, 0x2000, false, false, false), + 12 => (bases.neural, 0, 0x4000, false, true, false), + 14 => (bases.metrology, 0, 0x8000, false, false, false), + 17 => (bases.gpu, 0, 0x20000, false, true, false), + 26 => (bases.gpu, 0xd04000, 0x8000, true, true, false), + 27 => (bases.gpu, 0xd0d000, 0x1000, true, true, false), + 28 => (bases.gpu, 0xd50000, 0x8000, true, true, false), + 29 => (bases.gpu, 0xd10000, 0x4000, true, true, false), + 31 => (bases.gpu, 0xd40000, 0x4000, true, true, false), + 32 => (bases.gpu, 0xd60000, 0x8000, true, true, false), + 35 => (bases.gpu, 0xe00000, 0x4000, true, true, false), + 39 => (bases.gpu, 0xe08000, 0x8000, false, true, false), + 40 => (bases.gpu, 0xe1c000, 0x4000, true, true, false), + 0..SLOTS => return Ok(None), + _ => return Err(Error::Bounds), + }; + let physical = base.checked_add(offset).ok_or(Error::Address)?; + if physical == 0 || physical.checked_add(bytes as u64).ok_or(Error::Address)? > 1 << 42 { + return Err(Error::Address); + } + if split + && (physical & 0x3fff != 0 + || physical + .checked_add(0x200_0000) + .and_then(|v| v.checked_add((bytes / 2) as u64)) + .filter(|v| *v <= 1 << 42) + .is_none()) + { + return Err(Error::Address); + } + Ok(Some(Area { + physical, + bytes, + relative_register: if tagged { offset } else { 0 }, + writable, + split, + })) +} + +fn empty_mode(slot: usize) -> u64 { + if matches!(slot, 2 | 5..=8 | 30 | 33 | 34 | 37 | 38 | 41..=43 | 46..=49 | 52) { + 2 + } else { + 0 + } +} + +/// Encode a complete record after checking the owned mapping; errors preserve all output. +pub(crate) fn encode( + slot: usize, + bases: RegisterBases, + mapping: Option, + output: &mut [u8], +) -> Result<(), Error> { + let descriptor = area(slot, bases)?; + let (physical, va, bytes, requested, register, mode) = match (descriptor, mapping) { + (None, None) => (0, 0, 0, 0, 0, empty_mode(slot)), + (Some(item), Some(mapping)) => { + let page = mapping.at(0, item.mapped_bytes())?; + if page & 0x3fff != 0 { + return Err(Error::Address); + } + let va = page + .checked_add(item.page_offset() as u64) + .ok_or(Error::Address)?; + ( + item.physical, + va, + item.bytes as u32, + if item.split { + (item.bytes / 2) as u32 + } else { + item.bytes as u32 + }, + item.relative_register, + if item.writable { 2 } else { 0 }, + ) + } + _ => return Err(Error::Address), + }; + let bytes_out = output.get_mut(..RECORD_BYTES).ok_or(Error::Size)?; + bytes_out.fill(0); + bytes_out[0..8].copy_from_slice(&physical.to_le_bytes()); + bytes_out[8..16].copy_from_slice(&va.to_le_bytes()); + bytes_out[16..20].copy_from_slice(&bytes.to_le_bytes()); + bytes_out[20..24].copy_from_slice(&requested.to_le_bytes()); + bytes_out[24..32].copy_from_slice(®ister.to_le_bytes()); + bytes_out[32..40].copy_from_slice(&mode.to_le_bytes()); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Resource owners exercise subpage mappings and split memory-controller banks. + const BASES: RegisterBases = RegisterBases { + gpu: 0x2_9000_0000, + interrupt: 0x2_d101_4000, + memory: 0x2_201c_4000, + telemetry_write: 0x2_d03d_0000, + telemetry_read: 0x2_d03c_0000, + neural: 0x3_1145_c000, + metrology: 0x2_d028_0000, + }; + + #[test] + fn j613_slot28_bank_and_wire_tag_agree() { + let bank = area(28, BASES).unwrap().unwrap(); + assert_eq!(bank.physical, BASES.gpu + 0xd50000); + assert_eq!(bank.bytes, 0x8000); + let mapped = Region::new(0xffff_fc23_01c0_0000, 0x8000).unwrap(); + let mut bytes = [0; RECORD_BYTES]; + encode(28, BASES, Some(mapped), &mut bytes).unwrap(); + assert_eq!(&bytes[0..8], &(BASES.gpu + 0xd50000).to_le_bytes()); + assert_eq!(&bytes[24..32], &0xd50000u64.to_le_bytes()); + assert_ne!(bank.physical, BASES.gpu + 0xd58000); + } + + #[test] + fn descriptor_inventory_ends_before_the_retention_pointer() { + assert_eq!(TABLE_OFFSET + SLOTS * RECORD_BYTES, 0xe88); + assert_eq!( + (0..SLOTS) + .filter(|slot| area(*slot, BASES).unwrap().is_some()) + .count(), + 16 + ); + assert!(area(SLOTS, BASES).is_err()); + } + + #[test] + fn discontiguous_memory_controller_banks_have_distinct_physical_owners() { + let item = area(3, BASES).unwrap().unwrap(); + assert_eq!( + item.banks(), + [ + (0x2_201c_4000, 0x18000, 0), + (0x2_221c_4000, 0x18000, 0x18000) + ] + ); + let mapping = Region::new(0xffff_fc23_0000_0000, 0x30000).unwrap(); + let mut bytes = [0xa5; RECORD_BYTES]; + encode(3, BASES, Some(mapping), &mut bytes).unwrap(); + assert_eq!( + u32::from_le_bytes(bytes[16..20].try_into().unwrap()), + 0x30000 + ); + assert_eq!( + u32::from_le_bytes(bytes[20..24].try_into().unwrap()), + 0x18000 + ); + } + + #[test] + fn subpage_mapping_keeps_the_register_offset() { + let item = area(27, BASES).unwrap().unwrap(); + assert_eq!(item.page_offset(), 0x1000); + assert_eq!(item.banks()[0], (0x2_90d0_c000, 0x4000, 0)); + let mut output = [0xa5; RECORD_BYTES + 8]; + encode( + 27, + BASES, + Some(Region::new(0xffff_fc23_0000_0000, 0x4000).unwrap()), + &mut output, + ) + .unwrap(); + assert_eq!( + u64::from_le_bytes(output[8..16].try_into().unwrap()), + 0xffff_fc23_0000_1000 + ); + assert_eq!(&output[RECORD_BYTES..], &[0xa5; 8]); + } + + #[test] + fn empty_slots_keep_modes_without_fabricating_mappings() { + for slot in 0..SLOTS { + if area(slot, BASES).unwrap().is_some() { + continue; + } + let mut output = [0xa5; RECORD_BYTES]; + encode(slot, BASES, None, &mut output).unwrap(); + assert_eq!(&output[..32], &[0; 32]); + assert_eq!( + u64::from_le_bytes(output[32..40].try_into().unwrap()), + empty_mode(slot) + ); + } + } + + #[test] + fn invalid_mapping_or_output_never_partially_changes_a_descriptor() { + let good = Region::new(0xffff_fc23_0000_0000, 0x4000).unwrap(); + for (slot, mapping) in [ + (SLOTS, None), + (0, None), + (1, Some(good)), + (0, Some(Region::new(0xffff_fc23_0000_0001, 0x4000).unwrap())), + (0, Some(Region::new(0xffff_fc23_0000_0000, 0x3fff).unwrap())), + ] { + let mut output = [0xa5; RECORD_BYTES]; + assert!(encode(slot, BASES, mapping, &mut output).is_err()); + assert_eq!(output, [0xa5; RECORD_BYTES]); + } + for length in 0..RECORD_BYTES { + let mut output = [0xa5; RECORD_BYTES]; + assert_eq!( + encode(0, BASES, Some(good), &mut output[..length]), + Err(Error::Size) + ); + assert_eq!(output, [0xa5; RECORD_BYTES]); + } + } + #[test] + fn split_banks_require_page_alignment_and_both_fit_the_bus() { + for memory in [BASES.memory + 4, (1 << 42) - 0x100_0000] { + let mut bases = BASES; + bases.memory = memory; + let mut output = [0xa5; RECORD_BYTES]; + assert!(encode( + 3, + bases, + Some(Region::new(0xffff_fc21_0000_0000, 0x30000).unwrap()), + &mut output + ) + .is_err()); + assert_eq!(output, [0xa5; RECORD_BYTES]); + } + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_pool.rs b/drivers/gpu/drm/asahi/m3/m3_pool.rs index ab50ab7eca8a17..4eb284e1291bdb 100644 --- a/drivers/gpu/drm/asahi/m3/m3_pool.rs +++ b/drivers/gpu/drm/asahi/m3/m3_pool.rs @@ -39,3 +39,34 @@ impl Owners { objects[self.manager].write(0, &manager) } } + +impl Owners { + pub(crate) fn initialize_extended(&self, objects: &mut [Buffer], slot: u8, + compute_only: bool) -> Result { + let mut backings = KVec::new(); + for index in self.backing.clone().take(8) { + let buffer = objects.get(index).ok_or(EINVAL)?; + backings.push(wire::GpuRegion::new(buffer.gpu_va()?, buffer.size()).map_err(|_| EINVAL)?, GFP_KERNEL)?; + } + let backing = wire::ModernBacking(&backings); + let populated = backing.pages().map_err(|_| EINVAL)?; + let pages = objects.get(self.pages).ok_or(EINVAL)?; + let entries = wire::GpuRegion::new(pages.gpu_va()?, pages.size()).map_err(|_| EINVAL)?; + let table = objects.get(self.table).ok_or(EINVAL)?; + let groups = wire::GpuRegion::new(table.gpu_va()?, table.size()).map_err(|_| EINVAL)?; + if groups.size() < populated / 512 * 64 { return Err(EINVAL); } + let descriptor = wire::ModernPool { entries, groups, + submitted: FirmwareVa::new(objects.get(self.counter).ok_or(EINVAL)?.va()).map_err(|_| EINVAL)?, + identity: FirmwareVa::new(objects.get(self.manager).ok_or(EINVAL)?.va()).map_err(|_| EINVAL)?, + slot, capacity: (entries.size() / 8).try_into()?, populated: populated.try_into()?, compute_only }; + let mut bytes = [0; wire::ModernPool::SIZE]; + descriptor.encode(&mut bytes).map_err(|_| EINVAL)?; + for index in 0..populated { + objects[self.pages].u64(index * 8, backing.page(index).map_err(|_| EINVAL)?)?; + } + for index in 0..populated / 512 { + objects[self.table].write(index * 64, &backing.group(index).map_err(|_| EINVAL)?)?; + } + objects[self.manager].write(0, &bytes) + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_pool_layout.rs b/drivers/gpu/drm/asahi/m3/m3_pool_layout.rs index a8474d262bc26d..ac7e6771f7880b 100644 --- a/drivers/gpu/drm/asahi/m3/m3_pool_layout.rs +++ b/drivers/gpu/drm/asahi/m3/m3_pool_layout.rs @@ -69,3 +69,131 @@ impl<'a> Pool<'a> { Ok(()) } } + +/// Extended firmware's private-memory pool descriptor. The owners and initial +/// population are supplied separately from the descriptor's firmware state. +pub(crate) struct ModernPool { + pub(crate) identity: FirmwareVa, + pub(crate) slot: u8, + pub(crate) entries: GpuRegion, + pub(crate) groups: GpuRegion, + pub(crate) submitted: FirmwareVa, + pub(crate) capacity: u32, + pub(crate) populated: u32, + pub(crate) compute_only: bool, +} + +impl ModernPool { + pub(crate) const SIZE: usize = 0xa0; + + pub(crate) fn encode(&self, output: &mut [u8]) -> Result<(), Error> { + let entry_bytes = (self.capacity as usize).checked_mul(8).ok_or(Error::Size)?; + let group_bytes = (self.populated as usize / 512).checked_mul(8).ok_or(Error::Size)?; + if !(1..=64).contains(&self.slot) || self.capacity == 0 || self.populated >= self.capacity || self.populated % 512 != 0 + || self.entries.base() & 0x3fff != 0 || self.groups.base() & 0x3fff != 0 + || self.entries.size() < entry_bytes || self.groups.size() < group_bytes + || self.submitted.get() & 7 != 0 || self.identity.get() & 7 != 0 { return Err(Error::Address); } + let group_count: u32 = group_bytes.try_into().map_err(|_| Error::Size)?; + let bytes = output.get_mut(..Self::SIZE).ok_or(Error::Size)?; + bytes.fill(0); + let word = |bytes: &mut [u8], offset: usize, value: u32| { + bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + }; + bytes[..8].copy_from_slice(&self.identity.get().to_le_bytes()); + word(bytes, 8, self.slot.into()); + word(bytes, 12, (self.populated == 0).into()); + word(bytes, 16, 2); + bytes[0x14..0x1c].copy_from_slice(&self.entries.base().to_le_bytes()); + word(bytes, 0x1c, self.capacity); + word(bytes, 0x24, self.populated); + word(bytes, 0x28, (self.populated == 0).into()); + word(bytes, 0x2c, self.populated); + bytes[0x30..0x38].copy_from_slice(&self.groups.base().to_le_bytes()); + word(bytes, 0x40, 4); + word(bytes, 0x48, group_count); + bytes[0x4c..0x54].copy_from_slice(&self.submitted.get().to_le_bytes()); + word(bytes, 0x5c, (self.compute_only && self.populated != 0).into()); + word(bytes, 0x84, (self.populated != 0).into()); + word(bytes, 0x94, 2); + Ok(()) + } +} + +#[cfg(test)] +mod modern_tests { + use super::*; + fn pool(compute_only: bool) -> ModernPool { + ModernPool { identity: FirmwareVa::new(0xffff_fc21_9100_0000).unwrap(), slot: 1, + entries: GpuRegion::new(0x5000_0000, 0x20000).unwrap(), + groups: GpuRegion::new(0x6000_0000, 0x4000).unwrap(), + submitted: FirmwareVa::new(0xffff_fc21_9000_0000).unwrap(), + capacity: 0x4000, populated: 512, compute_only } + } + fn word(bytes: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()) + } + #[test] fn shared_render_pool_never_gets_compute_exclusion() { + for compute in [false, true] { + let mut output = [0xa5; ModernPool::SIZE + 8]; + pool(compute).encode(&mut output).unwrap(); + assert_eq!(word(&output, 0x5c), compute.into()); + assert_eq!(word(&output, 0x48), 8); + assert_eq!(word(&output, 0x84), 1); + assert_eq!(&output[ModernPool::SIZE..], &[0xa5; 8]); + } + } + #[test] fn bad_population_and_insufficient_owners_reject_before_mutation() { + for pages in [1, 511, 513, 0x4000] { + let mut value = pool(true); value.populated = pages; + let mut output = [0xa5; ModernPool::SIZE]; + assert!(value.encode(&mut output).is_err()); + assert_eq!(output, [0xa5; ModernPool::SIZE]); + } + let mut value = pool(true); value.capacity = 0x4001; + let mut output = [0xa5; ModernPool::SIZE]; + assert!(value.encode(&mut output).is_err()); + assert_eq!(output, [0xa5; ModernPool::SIZE]); + } +} + +/// Populate the extended freelist from independently retained 4-MiB owners. +/// Hardware group records describe 2-MiB subregions, not entire owners. +pub(crate) struct ModernBacking<'a>(pub(crate) &'a [GpuRegion]); +impl ModernBacking<'_> { + pub(crate) fn pages(&self) -> Result { + if self.0.is_empty() || self.0.len() > 8 + || self.0.iter().any(|owner| owner.size() != BACKING_SIZE || owner.base() & 0x3fff != 0) { + return Err(Error::Size); + } + Ok(self.0.len() * BACKING_SIZE / PAGE_SIZE) + } + pub(crate) fn page(&self, index: usize) -> Result { + if index >= self.pages()? { return Err(Error::Index); } + let per_owner = BACKING_SIZE / PAGE_SIZE; + Ok(self.0[index / per_owner].base() + (index % per_owner * PAGE_SIZE) as u64) + } + pub(crate) fn group(&self, index: usize) -> Result<[u8; 64], Error> { + if index >= self.pages()? / 512 { return Err(Error::Index); } + let base = self.page(index * 512)?; + let mut record = [0; 64]; + record[..8].copy_from_slice(&(base | (1 << 61)).to_le_bytes()); + Ok(record) + } +} + +#[cfg(test)] +mod backing_tests { + use super::*; + #[test] fn group_records_never_cross_a_noncontiguous_backing_owner() { + let owners = [GpuRegion::new(0x5000_0000, BACKING_SIZE).unwrap(), + GpuRegion::new(0x6000_0000, BACKING_SIZE).unwrap()]; + let backing = ModernBacking(&owners); + assert_eq!(backing.pages(), Ok(2048)); + assert_eq!(backing.page(1024), Ok(0x6000_0000)); + let second = backing.group(2).unwrap(); + assert_eq!(u64::from_le_bytes(second[..8].try_into().unwrap()), (1 << 61) | 0x6000_0000); + assert_eq!(&second[8..], &[0; 56]); + assert!(backing.group(4).is_err()); + assert!(backing.page(2048).is_err()); + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_power_26.rs b/drivers/gpu/drm/asahi/m3/m3_power_26.rs new file mode 100644 index 00000000000000..2619c24b4eb6a3 --- /dev/null +++ b/drivers/gpu/drm/asahi/m3/m3_power_26.rs @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Build the extended M3 controller images from admitted device-tree inputs. +//! These are constructor inputs. Firmware owns subsequent controller outputs. + +use super::{hw, initdata::PStateTables}; +use crate::{f32, float::F32}; +use kernel::prelude::*; + +struct Image(KVec); +impl Image { + fn new(size: usize) -> Result { + let mut bytes = KVec::new(); + bytes.resize(size, 0, GFP_KERNEL)?; + Ok(Self(bytes)) + } + fn word(&mut self, offset: usize, value: u32) -> Result { + self.0 + .get_mut(offset..offset.checked_add(4).ok_or(EOVERFLOW)?) + .ok_or(ERANGE)? + .copy_from_slice(&value.to_le_bytes()); + Ok(()) + } + fn pointer(&mut self, offset: usize, value: u64) -> Result { + self.0 + .get_mut(offset..offset.checked_add(8).ok_or(EOVERFLOW)?) + .ok_or(ERANGE)? + .copy_from_slice(&value.to_le_bytes()); + Ok(()) + } + fn float(&mut self, offset: usize, value: F32) -> Result { + let bits = value.to_bits(); + if bits & 0x7f80_0000 == 0x7f80_0000 { + return Err(EINVAL); + } + self.word(offset, bits) + } + fn reciprocal(&mut self, negative: usize, positive: usize, periods: u32) -> Result { + if periods == 0 { + return Err(EINVAL); + } + let alpha = f32!(1.0) / F32::from(periods); + self.float(negative, f32!(1.0) - alpha)?; + self.float(positive, alpha) + } +} + +pub(crate) struct PowerImages { + pub(crate) hardware: KVec, + pub(crate) performance: KVec, + pub(crate) power: KVec, + pub(crate) globals: KVec, + pub(crate) maximum_khz: u32, +} + +impl PowerImages { + /// `sram_uv` follows the same OPP iteration order as PwrConfig::perf_states. + /// No reference-bin voltage or experimental OPP-power estimate is substituted. + pub(crate) fn new( + pwr: &hw::PwrConfig, + sram_uv: &[u32], + sensor: u64, + retention: u64, + timestamp_base: u64, + io_records: &[u8], + ) -> Result { + let table = PStateTables::new(pwr, true, None, false)?; + let second = table.secondary.ok_or(EINVAL)?; + if table.len != 9 + || table.max != 8 + || sram_uv.len() != pwr.perf_states.len() + || pwr.perf_base_pstate == 0 + || pwr.perf_base_pstate > 8 + || !(1..=1000).contains(&pwr.power_sample_period) + || pwr.pwr_min_duty_cycle > 100 + || pwr.avg_power_min_duty_cycle > 100 + || io_records.len() != super::m3_mmio_26::SLOTS * super::m3_mmio_26::RECORD_BYTES + || [sensor, retention, timestamp_base] + .iter() + .any(|value| *value < 0xffff_fc00_0000_0000 || value & 7 != 0) + { + return Err(EINVAL); + } + let top = &pwr.perf_states[table.primary[8] as usize]; + let base = &pwr.perf_states[table.primary[pwr.perf_base_pstate as usize] as usize]; + if top.freq_hz != 1_338_000_000 || base.freq_hz >= top.freq_hz { + return Err(EINVAL); + } + let period = pwr.power_sample_period; + let ppm_periods = pwr.ppm_filter_time_constant_ms / period; + let average_periods = pwr.avg_power_filter_tc_ms / period; + if [ + ppm_periods, + average_periods, + pwr.pwr_filter_time_constant, + pwr.perf_filter_time_constant, + pwr.perf_filter_time_constant2, + pwr.avg_power_target_filter_tc, + pwr.se_filter_time_constant, + pwr.se_filter_time_constant_1, + ] + .contains(&0) + { + return Err(EINVAL); + } + let clocks = |count: u32| { + count + .checked_mul(period) + .and_then(|v| v.checked_mul(24_000)) + .ok_or(EOVERFLOW) + }; + let dt = F32::from(period) / f32!(1000.0); + let ceiling = 800; + let base_units = pwr.perf_base_pstate * 100; + // The profile controller budget is independent of the loader's experimental + // OPP power model. It is not a demand floor or a replacement voltage table. + let budget = 22_044u32; + let mut main = Image::new(0x2658)?; + let mut performance = Image::new(0x6578)?; + let mut power = Image::new(0x670)?; + let mut globals = Image::new(0xe48)?; + + main.pointer(0x28, timestamp_base)?; + main.0[0x640..0xe88].copy_from_slice(io_records); + main.pointer(0xe88, retention)?; + main.word(0xe90, 0x8122)?; + main.word(0xe94, 2)?; + main.word(0xe9c, 4)?; + main.word(0xea0, 1)?; + main.word(0xea4, 1)?; + main.word(0xeb8, 1)?; + main.word(0xec0, 1)?; + main.word(0xec8, 1)?; + main.word(0xed0, 24_000)?; + main.word(0xed4, 1)?; + main.word(0xed8, period)?; + main.word(0xee0, 1)?; + main.word(0xee4, 1)?; + main.word(0xee8, 1)?; + main.word(0xf04, 31)?; + main.pointer(0xf24, 4)?; + main.word(0xf34, 1)?; + // Firmware completion is polled through its normal bounded status path. + main.word(0xf38, 0)?; + main.word(0xf4c, 49)?; + main.word(0xf6c, 1)?; + main.word(0xf88, 10)?; + main.word(0xf8c, 3)?; + main.word(0xfac, 1)?; + main.word(0xfb8, 22)?; + main.word(0xfbc, 5)?; + main.word(0xfc0, 10)?; + main.word(0xfc4, 8)?; + main.word(0x1cd8, 8)?; + for state in 0..9 { + let primary = table.primary[state] as usize; + let secondary = second[state] as usize; + let item = &pwr.perf_states[primary]; + let auxiliary = &pwr.perf_states[secondary]; + if item.volt_mv.len() != 1 + || auxiliary.volt_mv.len() != 1 + || !(100..=1100).contains(&item.volt_mv[0]) + || !(100..=1100).contains(&auxiliary.volt_mv[0]) + || !(100_000..=1_100_000).contains(&sram_uv[primary]) + || !(100_000..=1_100_000).contains(&sram_uv[secondary]) + || sram_uv[primary].div_ceil(1000) < item.volt_mv[0] + || sram_uv[secondary].div_ceil(1000) < auxiliary.volt_mv[0] + { + return Err(EINVAL); + } + main.word(0xfc8 + state * 4, item.freq_hz / 1_000_000)?; + main.word(0x1808 + state * 4, auxiliary.freq_hz / 1_000_000)?; + main.float(0x1848 + state * 4, f32!(1.02))?; + for lane in 0..16 { + main.word(0x1008 + state * 64 + lane * 4, item.volt_mv[0])?; + main.word( + 0x1408 + state * 64 + lane * 4, + sram_uv[primary].div_ceil(1000), + )?; + } + main.word(0x1cdc + state * 4, auxiliary.freq_hz / 1_000_000)?; + if state != 0 { + main.word(0x1d1c + state * 64, auxiliary.volt_mv[0])?; + } + main.word(0x211c + state * 64, sram_uv[secondary].div_ceil(1000))?; + if state != 0 { + // Profile indices for the admitted eight-state ladder. + let core_profile = if state <= 3 { state } else { 2 * state - 3 }; + let auxiliary_profile = if state <= 4 { state } else { 2 * state - 4 }; + main.word(0x19c8 + state * 4, core_profile as u32)?; + main.word(0x1a08 + state * 4, auxiliary_profile as u32)?; + let boost = item.freq_hz.saturating_sub(base.freq_hz) / 1_000_000; + main.word( + 0x1908 + state * 4, + 100 * boost / ((top.freq_hz - base.freq_hz) / 1_000_000), + )?; + } + } + // The native relative-power calibration of each running firmware state. + // Encode only these semantic percentages, not an object-image overlay. + for (state, percent) in [15, 25, 36, 46, 58, 75, 90, 100].into_iter().enumerate() { + main.word(0x18c8 + (state + 1) * 4, percent)?; + } + main.word(0x2544, 4)?; + main.word(0x2548, 6)?; + main.word(0x254c, 3)?; + main.word(0x2550, 7)?; + main.word(0x2554, 7)?; + main.word(0x2568, 10)?; + main.word(0x2570, 1)?; + main.word(0x258c, 0x160000)?; + main.word(0x2594, 1)?; + main.word(0x2598, 1)?; + main.word(0x259c, 1)?; + main.word(0x25a4, 1)?; + main.word(0x25a8, 1)?; + main.word(0x25ac, 1)?; + main.0[0x25b4..0x25e4].fill(0xff); + main.word(0x2604, 1)?; + main.word(0x2634, 1)?; + + globals.word(0x24, 3000)?; + globals.word(0x78, 1)?; + globals.word(0x84, budget)?; + for offset in [0x88, 0x8c, 0x98] { + globals.word(offset, ceiling)?; + } + globals.word(0x998, 40)?; + globals.word(0x99c, 10)?; + globals.word(0x9a0, 250)?; + globals.word(0x9b8, 2)?; + globals.word(0x9bc, 40)?; + globals.word(0x9c0, 5)?; + globals.word(0x9c8, 40)?; + globals.word(0x9cc, 50)?; + globals.word(0xe28, 3)?; + + power.word(0x10, average_periods)?; + power.float(0x14, pwr.avg_power_ki_only * dt)?; + power.float(0x18, pwr.avg_power_kp)?; + power.word(0x1c, pwr.avg_power_min_duty_cycle)?; + power.word(0x20, pwr.avg_power_target_filter_tc)?; + power.word(0xc8, 11_000)?; + power.word(0xcc, 8_000)?; + power.float(0xd8, pwr.fast_die0_proportional_gain)?; + power.float(0xdc, pwr.fast_die0_integral_gain * dt)?; + power.word(0x12c, budget)?; + power.float(0x130, pwr.ppm_kp)?; + power.float(0x134, pwr.ppm_ki * dt)?; + power.word(0x14c, 1)?; + + performance.word(4, pwr.pwr_sample_period_aic_clks)?; + performance.word(8, pwr.pwr_sample_period_aic_clks)?; + performance.word(0x10, 4)?; + performance.float(0x14, f32!(1.0))?; + performance.word(0x2c, 1)?; + performance.word(0x30, 1)?; + // Leave the observed hardware state at zero until firmware writes it. + for offset in [0x40, 0x80, 0xb78, 0xb80, 0xbc0] { + performance.word(offset, base_units)?; + } + for offset in [0x44, 0x6c, 0x70, 0x84] { + performance.word(offset, 1)?; + } + for offset in [ + 0x48, 0x88, 0x9f4, 0x9f8, 0xa38, 0xaac, 0xaf0, 0xb7c, 0x221c, 0x22b4, 0x22b8, 0x22f8, + 0x2870, 0x29f8, 0x29fc, 0x2a3c, 0x2b88, 0x2c44, 0x2c88, + ] { + performance.word(offset, ceiling)?; + } + performance.word(0x50, 100)?; + performance.word(0x60, 4)?; + performance.float(0x64, f32!(1.0))?; + performance.word(0x90, 100)?; + performance.word(0x9b8, 625)?; + performance.reciprocal(0x9c4, 0x9cc, pwr.pwr_filter_time_constant)?; + performance.float(0x9d4, pwr.pwr_integral_gain)?; + performance.float(0x9dc, F32::from(pwr.pwr_integral_min_clamp))?; + performance.float(0x9e0, F32::from(budget))?; + performance.float(0x9e4, pwr.pwr_proportional_gain)?; + performance.float(0x9ec, -F32::from(ceiling) / F32::from(budget))?; + for offset in [0x9f0, 0xaa8, 0x22b0] { + performance.word(offset, pwr.pwr_min_duty_cycle)?; + } + for offset in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + performance.word(offset, budget)?; + } + performance.word(0xa70, ppm_periods.checked_mul(4).ok_or(EOVERFLOW)?)?; + performance.reciprocal(0xa7c, 0xa84, ppm_periods)?; + performance.float(0xa8c, pwr.ppm_ki * dt)?; + performance.float(0xa98, f32!(65536.0))?; + performance.float(0xa9c, pwr.ppm_kp)?; + performance.word(0xac8, pwr.ppm_filter_time_constant_ms)?; + performance.word(0xad0, clocks(ppm_periods)?)?; + performance.word(0xb28, pwr.perf_tgt_utilization)?; + performance.word(0xb30, pwr.perf_boost_min_util)?; + performance.word(0xb34, pwr.perf_boost_ce_step)?; + performance.word(0xb38, pwr.perf_reset_iters)?; + performance.word(0xb40, 6)?; + performance.word(0xb44, 1)?; + performance.word(0xb48, pwr.perf_filter_drop_threshold)?; + performance.reciprocal(0xb4c, 0xb54, pwr.perf_filter_time_constant)?; + performance.reciprocal(0xb50, 0xb58, pwr.perf_filter_time_constant2)?; + performance.float(0xb5c, pwr.perf_integral_gain)?; + performance.float(0xb60, pwr.perf_integral_gain2)?; + performance.float(0xb64, F32::from(pwr.perf_integral_min_clamp))?; + performance.float(0xb68, f32!(95.0))?; + performance.float(0xb6c, pwr.perf_proportional_gain)?; + performance.float(0xb70, pwr.perf_proportional_gain2)?; + performance.float(0xb74, F32::from(8 - pwr.perf_base_pstate) / f32!(0.95))?; + performance.word(0xb8c, pwr.perf_tgt_utilization)?; + performance.float(0x21f0, f32!(65536.0))?; + performance.float(0x21f4, F32::from(pwr.pwr_min_duty_cycle))?; + performance.float(0x21f8, F32::from(ceiling))?; + performance.word(0x21fc, 1338)?; + performance.float(0x2218, f32!(100.0))?; + performance.float(0x2224, f32!(0.8))?; + performance.float(0x2228, f32!(0.2))?; + performance.word(0x2268, 0x4248)?; + performance.word(0x2270, 8_000)?; + performance.word(0x2278, 4)?; + performance.float(0x228c, f32!(1.0))?; + performance.float(0x2294, pwr.fast_die0_integral_gain * dt)?; + performance.float(0x22a0, f32!(65536.0))?; + performance.float(0x22a4, pwr.fast_die0_proportional_gain)?; + performance.word(0x22c4, 11_000)?; + performance.word(0x28c0, 1)?; + performance.reciprocal(0x28dc, 0x28e0, pwr.avg_power_target_filter_tc)?; + performance.word( + 0x28e4, + pwr.avg_power_target_filter_tc + .checked_mul(4) + .ok_or(EOVERFLOW)?, + )?; + performance.word( + 0x28e8, + pwr.avg_power_target_filter_tc + .checked_mul(period) + .ok_or(EOVERFLOW)?, + )?; + performance.word(0x28ec, clocks(pwr.avg_power_target_filter_tc)?)?; + performance.word(0x29bc, average_periods.checked_mul(4).ok_or(EOVERFLOW)?)?; + performance.reciprocal(0x29c8, 0x29d0, average_periods)?; + performance.float(0x29d8, pwr.avg_power_ki_only * dt)?; + performance.float(0x29e4, f32!(65536.0))?; + performance.float(0x29e8, pwr.avg_power_kp)?; + performance.word(0x29f4, pwr.avg_power_min_duty_cycle)?; + performance.float(0x2a04, F32::from(budget))?; + performance.word(0x2a14, pwr.avg_power_filter_tc_ms)?; + performance.word(0x2a1c, clocks(average_periods)?)?; + performance.word(0x2b4c, 50)?; + performance.word(0x2b50, 1)?; + performance.reciprocal(0x2b58, 0x2b60, pwr.se_filter_time_constant)?; + performance.reciprocal(0x2b5c, 0x2b64, pwr.se_filter_time_constant_1)?; + performance.float(0x2b68, pwr.se_ki * dt)?; + performance.float(0x2b6c, pwr.se_ki_1 * dt)?; + performance.float(0x2b74, f32!(65536.0))?; + performance.float(0x2b78, pwr.se_kp)?; + performance.float(0x2b7c, pwr.se_kp_1)?; + performance.word(0x2b84, 100)?; + performance.word(0x2b8c, 100)?; + performance.word(0x2b98, pwr.se_target)?; + performance.word( + 0x2ba4, + pwr.se_filter_time_constant + .checked_mul(period) + .ok_or(EOVERFLOW)?, + )?; + performance.word( + 0x2ba8, + pwr.se_filter_time_constant_1 + .checked_mul(period) + .ok_or(EOVERFLOW)?, + )?; + performance.word(0x2bac, clocks(pwr.se_filter_time_constant)?)?; + performance.word(0x2bb4, clocks(pwr.se_filter_time_constant_1)?)?; + performance.float(0x2b94, f32!(8000.0))?; + for offset in [0x2bc0, 0x2bc4, 0x2c30] { + performance.float(offset, f32!(65536.0))?; + } + performance.word(0x2be8, 8000)?; + performance.word(0x2bec, 4)?; + performance.word(0x2bf0, pwr.se_inactive_threshold)?; + performance.word(0x2bf4, pwr.se_engagement_criteria as u32)?; + performance.word(0x2bf8, 2)?; + performance.word(0x2bfc, 4)?; + performance.word(0x2c00, pwr.se_reset_criteria)?; + performance.word(0x2c40, pwr.avg_power_min_duty_cycle)?; + performance.word(0x2e2c, 4)?; + performance.float(0x2e38, f32!(0.96875))?; + performance.float(0x2e40, f32!(0.03125))?; + performance.float(0x2e48, f32!(5.0))?; + performance.float(0x2e54, f32!(65536.0))?; + performance.float(0x2e58, f32!(15.0))?; + performance.word(0x2e64, 700)?; + for offset in [0x2e68, 0x2e6c, 0x2e78] { + performance.word(offset, ceiling)?; + } + performance.word(0x2f08, 0x4248)?; + performance.word(0x2f10, 125)?; + performance.word(0x2f14, 1)?; + performance.pointer(0x3b50, sensor)?; + performance.word(0x3b58, 0x4b48)?; + performance.word(0x3b60, 1)?; + performance.word(0x62a8, 1)?; + Ok(Self { + hardware: main.0, + performance: performance.0, + power: power.0, + globals: globals.0, + maximum_khz: top.freq_hz / 1000, + }) + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_profile.rs b/drivers/gpu/drm/asahi/m3/m3_profile.rs new file mode 100644 index 00000000000000..004ac2c7d1eef2 --- /dev/null +++ b/drivers/gpu/drm/asahi/m3/m3_profile.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Firmware-interface properties, independent of the physical G15 GPU record format. + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Profile { + Legacy, + Extended, +} + +pub(crate) const PARAMS_REQUIRED: u64 = 600; + +impl Profile { + pub(crate) const fn firmware_compat(self) -> [u32; 3] { + match self { + Self::Legacy => [14, 8, 3], + Self::Extended => [26, 6, 2], + } + } + + pub(crate) const fn hal(self) -> u32 { + match self { + Self::Legacy => 0, + Self::Extended => 200, + } + } + + pub(crate) const fn user_end(self) -> u64 { + match self { + Self::Legacy => 0x2ff_ffff_8000, + Self::Extended => (1 << 42) - 2 * 0x4000, + } + } + + pub(crate) fn gpu_duration_ns(self, elapsed: u64) -> Option { + match self { + Self::Legacy => elapsed.checked_mul(1000).map(|value| value / 24), + Self::Extended => Some(elapsed), + } + } + + pub(crate) const fn vm_acknowledged(self, params_bytes: u64, flags: u32) -> bool { + // Both profiles consume G15 records; the existing VM padding stays zero. + params_bytes >= PARAMS_REQUIRED && flags == 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn profile_acknowledgement_is_exact_and_file_query_covers_hal() { + for profile in [Profile::Legacy, Profile::Extended] { + let expected = 0; + for size in [0, 599, 600, 616, u64::MAX] { + for flags in [0, 1, 2, 3, u32::MAX] { + assert_eq!( + profile.vm_acknowledged(size, flags), + size >= 600 && flags == expected + ); + } + } + } + } + + #[test] + fn firmware_interface_version_does_not_change_physical_identity() { + assert_eq!(Profile::Legacy.hal(), 0); + assert_eq!(Profile::Extended.hal(), 200); + assert_eq!(Profile::Legacy.firmware_compat(), [14, 8, 3]); + assert_eq!(Profile::Extended.firmware_compat(), [26, 6, 2]); + assert_eq!(Profile::Extended.user_end(), 0x3ff_ffff_8000); + assert_eq!(Profile::Legacy.user_end(), 0x2ff_ffff_8000); + } + #[test] + fn native_nanoseconds_are_never_rescaled_as_legacy_ticks() { + assert_eq!( + Profile::Extended.gpu_duration_ns(24_000_000), + Some(24_000_000) + ); + assert_eq!( + Profile::Legacy.gpu_duration_ns(24_000_000), + Some(1_000_000_000) + ); + assert_eq!(Profile::Extended.gpu_duration_ns(u64::MAX), Some(u64::MAX)); + assert_eq!(Profile::Legacy.gpu_duration_ns(u64::MAX), None); + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_queue_layout.rs b/drivers/gpu/drm/asahi/m3/m3_queue_layout.rs index 39cc1f5f2e4908..c3411bfdff8ed2 100644 --- a/drivers/gpu/drm/asahi/m3/m3_queue_layout.rs +++ b/drivers/gpu/drm/asahi/m3/m3_queue_layout.rs @@ -113,3 +113,44 @@ pub(crate) fn ring_entry(index: u32, capacity: u32, seeds: &[FirmwareVa]) -> Res if seeds.is_empty() || seeds.len() > capacity as usize || capacity > 65536 || index >= capacity { return Err(Error::Index); } Ok(if (index as usize) < seeds.len() { seeds[index as usize].0.to_le_bytes() } else { [0xcc; 8] }) } + +impl QueueInfo { + pub(crate) const EXTENDED_SIZE: usize = 0x24c0; + + /// Scheduler storage belongs to this same retained allocation; it is not + /// interchangeable with the legacy GPU scratch object. + pub(crate) fn encode_extended(&self, owner: FirmwareVa, output: &mut [u8]) -> Result<(), Error> { + let end = owner.get().checked_add(Self::EXTENDED_SIZE as u64).ok_or(Error::Address)?; + if end > 0xffff_fc30_0000_0000 { return Err(Error::Address); } + let bytes = output.get_mut(..Self::EXTENDED_SIZE).ok_or(Error::Size)?; + bytes.fill(0); + for (offset, value) in [(0, self.state.get()), (8, self.ring.get()), + (16, self.job_list.get()), (24, owner.get() + 0xb0), (0xa4, self.context.get())] { + bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); + } + for offset in [0x2c, 0x4c] { bytes[offset..offset + 4].fill(0xff); } + for offset in [0x30, 0x34, 0x48] { + bytes[offset..offset + 4].copy_from_slice(&2u32.to_le_bytes()); + } + bytes[0x38..0x40].copy_from_slice(&0xffff_0000_0000_0000u64.to_le_bytes()); + Ok(()) + } +} + +#[cfg(test)] +mod extended_tests { + use super::*; + #[test] fn scheduler_pointer_relocates_with_its_retained_queue_owner() { + let fw = |i: u64| FirmwareVa::new(0xffff_fc21_4000_0000 + i * 0x4000).unwrap(); + let info = QueueInfo { state: fw(1), ring: fw(2), job_list: fw(3), + gpu_scratch: fw(4), context: fw(5), uuid: 9 }; + let mut output = vec![0xa5; QueueInfo::EXTENDED_SIZE + 8]; + info.encode_extended(fw(0), &mut output).unwrap(); + assert_eq!(u64::from_le_bytes(output[24..32].try_into().unwrap()), fw(0).get() + 0xb0); + assert_eq!(&output[0xb0..QueueInfo::EXTENDED_SIZE], &vec![0; QueueInfo::EXTENDED_SIZE - 0xb0]); + assert_eq!(&output[QueueInfo::EXTENDED_SIZE..], &[0xa5; 8]); + let mut short = [0xa5; INFO_SIZE]; + assert!(info.encode_extended(fw(0), &mut short).is_err()); + assert_eq!(short, [0xa5; INFO_SIZE]); + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_runtime.rs b/drivers/gpu/drm/asahi/m3/m3_runtime.rs index 1a366d7f4b3407..40399a80a01f39 100644 --- a/drivers/gpu/drm/asahi/m3/m3_runtime.rs +++ b/drivers/gpu/drm/asahi/m3/m3_runtime.rs @@ -1214,6 +1214,9 @@ impl Runtime { } fn boot_inner(&mut self, pdev: &platform::Device>) -> Result { use crate::m3::t8122_start::BootStep; + if self.inner.device.firmware().profile() == super::m3_profile::Profile::Extended { + return self.boot_extended(pdev); + } let root = self.inner.config.root(); dev_info!(pdev.as_ref(), "M3: publishing owned initdata {:#x}\n", root); self.inner.boot_step = BootStep::Publish; @@ -1245,6 +1248,60 @@ impl Runtime { dev_info!(pdev.as_ref(), "M3: firmware accepted owned initdata and device controls\n"); Ok(()) } + fn boot_extended(&mut self, pdev: &platform::Device>) -> Result { + use super::m3_channel_format::Action; + use crate::m3::t8122_start::BootStep; + // The first control is pending when the root becomes visible. Its + // retirement comes from InitData consumption, without a work kick. + let first = self.inner.config.control_extended(Action::Initialize)?; + self.inner.config.publish_io_table(pdev, self.inner.device.firmware())?; + let root = self.inner.config.root(); + Pin::new(&mut self.inner.transport).send_message(0x20, + 0x0081_0000_0000_0000 | (root & ((1u64 << 44) - 1)))?; + self.inner.boot_step = BootStep::AwaitReady; + let deadline = Instant::::now(); + while !self.inner.config.ready()? { + let inner: &mut Inner = &mut *self.inner; + inner.config.drain(&inner.drm)?; + if !inner.state.healthy() { return Err(EIO); } + if deadline.elapsed() >= Delta::from_secs(2) { return Err(ETIMEDOUT); } + fsleep(Delta::from_millis(1)); + } + self.retire_extended_control(first)?; + self.inner.boot_step = BootStep::DeviceControl; + // Each data master registers its two firmware engine classes. + for ordinal in 0..6 { + let action = Action::Register { pipe: ordinal / 2, engine: ordinal % 2 + 1 }; + self.send_extended_control(action)?; + } + self.inner.boot_step = BootStep::PostReady; + self.send_extended_control(Action::Idle { enabled: true })?; + let inner: &mut Inner = &mut *self.inner; + inner.config.check_pstate(&inner.drm, &inner.device, "after native boot")?; + inner.config.log_ready(&inner.drm)?; + inner.state.health.set_gpu_pending(false); + dev_info!(pdev.as_ref(), "M3: extended firmware ready; native controllers and idle policy admitted\n"); + Ok(()) + } + + fn send_extended_control(&mut self, action: super::m3_channel_format::Action) -> Result { + let cursor = self.inner.config.control_extended(action)?; + Pin::new(&mut self.inner.transport).send_message(0x21, 0x0084_0000_0000_0011)?; + self.retire_extended_control(cursor) + } + + fn retire_extended_control(&mut self, expected: u32) -> Result { + let deadline = Instant::::now(); + loop { + let inner: &mut Inner = &mut *self.inner; + inner.config.drain(&inner.drm)?; + if !inner.state.healthy() { return Err(EIO); } + if inner.config.control_done(expected)? { return Ok(()); } + if deadline.elapsed() >= Delta::from_secs(2) { return Err(ETIMEDOUT); } + fsleep(Delta::from_millis(1)); + } + } + fn send_control(&mut self, opcode: u32) -> Result { let next = match self.inner.config.control(opcode) { Ok(next) => next, diff --git a/drivers/gpu/drm/asahi/m3/m3_soc.rs b/drivers/gpu/drm/asahi/m3/m3_soc.rs index 4b81475612b6fa..7500822c4124c5 100644 --- a/drivers/gpu/drm/asahi/m3/m3_soc.rs +++ b/drivers/gpu/drm/asahi/m3/m3_soc.rs @@ -291,6 +291,7 @@ pub(crate) static T8122: Soc = Soc { cores_per_cluster: 10, mailbox_compatibles: &[ b"apple,t8122-agx-asc-mailbox\0", + b"apple,t8122-agx-asc-mailbox\0apple,asc-mailbox-v4\0", b"apple,t8122-asc-mailbox\0apple,asc-mailbox-v4\0", ], mailbox_interrupts: &[0, 723, 4, 0, 724, 4, 0, 725, 4, 0, 726, 4], diff --git a/drivers/gpu/drm/asahi/m3/m3_state_layout.rs b/drivers/gpu/drm/asahi/m3/m3_state_layout.rs index 199b7082b26c29..6eeafea666e742 100644 --- a/drivers/gpu/drm/asahi/m3/m3_state_layout.rs +++ b/drivers/gpu/drm/asahi/m3/m3_state_layout.rs @@ -66,3 +66,55 @@ pub(crate) fn manager_misc(out: &mut [u8]) -> Result<(), Error> { word(bytes, 0x20, 1); // cpu_flag / reset Ok(()) } + +pub(crate) const EXTENDED_CONTEXT_SIZE: usize = 0x38; +pub(crate) const EXTENDED_NOTIFIER_SIZE: usize = 0x200; + +pub(crate) fn context_extended(output: &mut [u8]) -> Result<(), Error> { + let bytes = output.get_mut(..EXTENDED_CONTEXT_SIZE).ok_or(Error::Size)?; + bytes.fill(0); + bytes[..2].fill(0xff); + bytes[5] = 1; + bytes[0x26] = 2; + bytes[0x33] = 0xff; + Ok(()) +} + +impl Notifier { + pub(crate) fn encode_extended(&self, output: &mut [u8]) -> Result<(), Error> { + if self.context >= 64 { return Err(Error::Address); } + let bytes = output.get_mut(..EXTENDED_NOTIFIER_SIZE).ok_or(Error::Size)?; + bytes.fill(0); + bytes[..8].copy_from_slice(&self.threshold.get().to_le_bytes()); + word(bytes, 0x10, 0x50); + word(bytes, NOTIFIER_CONTEXT, self.context); + bytes[0xa8..0xb0].fill(0xff); + word(bytes, 0x114, 1); + Ok(()) + } +} + +#[cfg(test)] +mod extended_tests { + use super::*; + #[test] fn registration_state_is_left_to_firmware() { + let mut bytes = [0xa5; EXTENDED_CONTEXT_SIZE + 8]; + context_extended(&mut bytes).unwrap(); + assert_eq!(&bytes[..6], &[255, 255, 0, 0, 0, 1]); + assert_eq!(bytes[0x26], 2); + assert_eq!(bytes[0x33], 255); + assert_eq!(&bytes[EXTENDED_CONTEXT_SIZE..], &[0xa5; 8]); + } + #[test] fn notifier_has_zero_generation_and_distinct_firmware_storage() { + let notifier = Notifier { threshold: FirmwareVa::new(0xffff_fc21_7000_0000).unwrap(), context: 3 }; + let mut bytes = [0xa5; EXTENDED_NOTIFIER_SIZE + 8]; + notifier.encode_extended(&mut bytes).unwrap(); + assert_eq!(&bytes[8..12], &[0; 4]); + assert_eq!(&bytes[0xa8..0xb0], &[255; 8]); + assert_eq!(&bytes[0x114..0x118], &[1, 0, 0, 0]); + assert_eq!(&bytes[EXTENDED_NOTIFIER_SIZE..], &[0xa5; 8]); + let mut short = [0xa5; NOTIFIER_SIZE]; + assert!(notifier.encode_extended(&mut short).is_err()); + assert_eq!(short, [0xa5; NOTIFIER_SIZE]); + } +} diff --git a/drivers/gpu/drm/asahi/m3/m3_submit.rs b/drivers/gpu/drm/asahi/m3/m3_submit.rs index 1212113af1618a..c3a25c9a2dc04d 100644 --- a/drivers/gpu/drm/asahi/m3/m3_submit.rs +++ b/drivers/gpu/drm/asahi/m3/m3_submit.rs @@ -177,11 +177,12 @@ pub(crate) struct Queue { shared:Shared,vm:mmu::Vm,usc:u64,fences:FenceContexts, /// Whether the SoC advertises early tiling (`m3_soc::Features::fragment_dependency`). early_tiling:bool, + wide_visibility:bool, urgency:u8, } impl Queue { pub(crate) fn new(shared:Shared,scheduler:Arc>,vm:mmu::Vm,priority:u32,usc:u64, - early_tiling:bool)->Result { + early_tiling:bool, wide_visibility:bool)->Result { agx_uapi::QueueUscWindow{base:usc,user_start:0x4000,user_end:(1u64<<42)-0x8000}.validate().map_err(|_|EINVAL)?; // file.rs passes REALTIME - UAPI priority: 3 is UAPI LOW, 0 is UAPI REALTIME. let urgency=3u8.saturating_sub(priority.min(3) as u8); @@ -189,7 +190,7 @@ impl Queue { 1=>sched::Priority::High,0=>sched::Priority::Kernel,_=>return Err(EINVAL)}; Ok(Self {entity:sched::Entity::new(&scheduler,priority)?,_scheduler:scheduler, shared,vm,usc,fences:FenceContexts::new(1,c_str!("asahi_m3_queue"),kernel::static_lock_class!())?, - early_tiling,urgency}) + early_tiling,wide_visibility,urgency}) } } impl queue::Queue for Queue { @@ -217,7 +218,8 @@ impl queue::Queue for Queue { let agx_uapi::ParsedHardwareCommand::Compute{mut payload,attachments,..}=command else {return Err(ENOTSUPP);}; // Other engines and helpers need their own M3 firmware translation. let wide=kernel::uapi::drm_asahi_compute_flags_DRM_ASAHI_COMPUTE_WIDE_VISIBILITY as u32; - if payload.flags & !wide !=0 || payload.sampler_count!=0 || payload.sampler_heap!=0 { + if payload.flags & !wide !=0 || (!self.wide_visibility && payload.flags & wide != 0) + || payload.sampler_count!=0 || payload.sampler_heap!=0 { pr_err!("M3 compute rejected: flags={:#x} samplers={} heap={:#x} attachments={} timestamps={}/{}\n",payload.flags,payload.sampler_count,payload.sampler_heap,attachments.as_slice().len(),payload.timestamps.start.handle,payload.timestamps.end.handle); return Err(ENOTSUPP); } diff --git a/drivers/gpu/drm/asahi/m3/mod.rs b/drivers/gpu/drm/asahi/m3/mod.rs index 1d10657c02f662..d885da1a8647ca 100644 --- a/drivers/gpu/drm/asahi/m3/mod.rs +++ b/drivers/gpu/drm/asahi/m3/mod.rs @@ -198,3 +198,12 @@ mod t8122_admission; pub(crate) mod m3_soc; mod t8122_hwdata; + +mod m3_init_26; +mod m3_profile; +mod m3_mmio_26; + +#[cfg(CONFIG_DRM_ASAHI_M3_T8122)] +mod m3_power_26; + +mod m3_channel_format; diff --git a/drivers/gpu/drm/asahi/m3/t8122_admission.rs b/drivers/gpu/drm/asahi/m3/t8122_admission.rs index 7505065810acc3..c66ccb72cd18d7 100644 --- a/drivers/gpu/drm/asahi/m3/t8122_admission.rs +++ b/drivers/gpu/drm/asahi/m3/t8122_admission.rs @@ -2,6 +2,8 @@ //! Read-only T8122 identity admission. Firmware startup remains disabled. +use super::m3_profile::Profile; + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) struct Identity { pub(crate) active_cores: u32, @@ -16,12 +18,23 @@ pub(crate) fn validate_identity( result: u32, id_version: u32, masks: [u32; 3], +) -> Option { + validate_identity_for(Profile::Legacy, firmware_compat, schema, result, id_version, masks) +} + +pub(crate) fn validate_identity_for( + profile: Profile, + firmware_compat: &[u32], + schema: u32, + result: u32, + id_version: u32, + masks: [u32; 3], ) -> Option { let family = id_version >> 24; let variant = (id_version >> 16) & 0xff; let revision = (id_version >> 8) & 0xff; let active_cores = masks[0].count_ones(); - if firmware_compat != [14, 8, 3] + if firmware_compat != profile.firmware_compat() || schema != 1 || result != 1 || family != 7 @@ -94,6 +107,14 @@ pub(crate) fn opp_table_shape(opps: impl IntoIterator) -> Opt #[cfg(not(test))] pub(crate) fn read_identity( pdev: &kernel::platform::Device>, +) -> kernel::error::Result { + read_identity_for(pdev, Profile::Legacy) +} + +#[cfg(not(test))] +pub(crate) fn read_identity_for( + pdev: &kernel::platform::Device>, + profile: Profile, ) -> kernel::error::Result { use kernel::{bindings, c_str, prelude::*}; @@ -146,7 +167,8 @@ pub(crate) fn read_identity( observation.get_u32(c_str!("core-mask-1"))?, observation.get_u32(c_str!("core-mask-2"))?, ]; - validate_identity(firmware_compat.as_slice(), schema, result, id_version, masks).ok_or(ENODEV) + validate_identity_for(profile, firmware_compat.as_slice(), schema, result, id_version, masks) + .ok_or(ENODEV) } #[cfg(test)] @@ -164,6 +186,29 @@ mod tests { } } + #[test] + fn extended_identity_requires_its_actual_version_and_the_same_g15g_bounds() { + for mask in [0xff, 0x3ff] { + let good = || validate_identity_for(Profile::Extended, &[26, 6, 2], 1, 1, G15G_C0, [mask, 0, 0]); + assert_eq!(good().unwrap().active_cores, mask.count_ones()); + assert!(validate_identity(&[26, 6, 2], 1, 1, G15G_C0, [mask, 0, 0]).is_none()); + assert!(validate_identity_for(Profile::Extended, &[14, 8, 3], 1, 1, G15G_C0, [mask, 0, 0]).is_none()); + } + for (schema, result, id, masks) in [ + (0, 1, G15G_C0, [0xff, 0, 0]), + (1, 0, G15G_C0, [0xff, 0, 0]), + (1, 1, G15G_C0 ^ (1 << 24), [0xff, 0, 0]), + (1, 1, G15G_C0 ^ (1 << 16), [0xff, 0, 0]), + (1, 1, G15G_C0 ^ (1 << 8), [0xff, 0, 0]), + (1, 1, G15G_C0, [0x1ff, 0, 0]), + (1, 1, G15G_C0, [0x7ff, 0, 0]), + (1, 1, G15G_C0, [0xff, 1, 0]), + (1, 1, G15G_C0, [0xff, 0, 1]), + ] { + assert!(validate_identity_for(Profile::Extended, &[26, 6, 2], schema, result, id, masks).is_none()); + } + } + /// The booted J613 ladder (`perf-states`, frequency then millivolts), in ADT order: fourteen /// states, eight voltages above the idle one, 1338 MHz at the top. const J613_PERF_STATES: [(u64, u32); 14] = [ diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs index ed032ab146dd06..faf1f086fd11c3 100644 --- a/drivers/gpu/drm/asahi/mmu.rs +++ b/drivers/gpu/drm/asahi/mmu.rs @@ -1156,8 +1156,7 @@ impl VmBind { self.1 } - /// Stable wire incarnation for this active root lease. M4 reserves zero as unbound. - #[cfg(CONFIG_DRM_ASAHI_G16)] + /// Stable firmware wire incarnation for this active root lease; zero is unbound. pub(crate) fn generation(&self) -> u8 { self.0.binding.lock().generation } @@ -2670,7 +2669,6 @@ impl Vm { } /// Spend the binding generation only for an incarnation exposed to firmware. - #[cfg(CONFIG_DRM_ASAHI_G16)] pub(crate) fn mark_binding_published(&self) -> Result { let uat = self.gpuvm.lock_inner().uat_inner.clone(); let binding = self.binding.lock(); diff --git a/tools/asahi/m3-firmware-tests.rs b/tools/asahi/m3-firmware-tests.rs new file mode 100644 index 00000000000000..90539441d623ed --- /dev/null +++ b/tools/asahi/m3-firmware-tests.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +//! Host tests of production firmware identity and normalization. +#![allow(dead_code)] +#[path = "../../drivers/gpu/drm/asahi/m3/m3_profile.rs"] +pub(crate) mod m3_profile; +#[path = "../../drivers/gpu/drm/asahi/m3/agx_resources.rs"] +pub(crate) mod resources; +mod m3 { + pub(crate) use crate::firmware as m3_firmware; + pub(crate) use crate::m3_profile; + pub(crate) use crate::resources as m3_resources; +} +#[path = "../../drivers/gpu/drm/asahi/m3/m3_firmware.rs"] +pub(crate) mod firmware; diff --git a/tools/asahi/m3-runtime-tests.rs b/tools/asahi/m3-runtime-tests.rs index fd4dc90489e817..458603366c954b 100644 --- a/tools/asahi/m3-runtime-tests.rs +++ b/tools/asahi/m3-runtime-tests.rs @@ -15,3 +15,34 @@ mod m3_init_storage; mod m3_thermal_policy; #[path = "../../drivers/gpu/drm/asahi/m3/t8122_hwdata.rs"] mod t8122_hwdata; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_init_26.rs"] +mod m3_init_26; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_profile.rs"] +mod m3_profile; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_mmio_26.rs"] +mod m3_mmio_26; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_queue_layout.rs"] +mod m3_queue_layout; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_pool_layout.rs"] +mod m3_pool_layout; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_compute_layout.rs"] +mod m3_compute_layout; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_channel_format.rs"] +mod m3_channel_format; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_compute_sequence.rs"] +mod m3_compute_sequence; + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_state_layout.rs"] +mod m3_state_layout; + + +#[path = "../../drivers/gpu/drm/asahi/m3/m3_compute_storage.rs"] +mod m3_compute_storage; diff --git a/tools/asahi/m3-uapi-fixture.c b/tools/asahi/m3-uapi-fixture.c new file mode 100644 index 00000000000000..9ee09940960093 --- /dev/null +++ b/tools/asahi/m3-uapi-fixture.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +#include +#include +#include +#include + +#ifndef ASAHI_HEADER +#define ASAHI_HEADER "drm/asahi_drm.h" +#endif +#include ASAHI_HEADER + +static_assert(sizeof(struct drm_asahi_cmd_header) == 8, "command header"); +static_assert(sizeof(struct drm_asahi_attachment) == 24, "attachment"); +static_assert(sizeof(struct drm_asahi_timestamps) == 16, "timestamps"); +static_assert(offsetof(struct drm_asahi_cmd_compute, ts_end_copies) == 64, + "compute prefix"); +static_assert(offsetof(struct drm_asahi_cmd_render, ts_frag_end_copies) == 256, + "render prefix"); + +static int emit(unsigned int type, const void *payload, size_t size) +{ + struct drm_asahi_cmd_header header = { + .cmd_type = type, + .size = size, + .vdm_barrier = DRM_ASAHI_BARRIER_NONE, + .cdm_barrier = DRM_ASAHI_BARRIER_NONE, + }; + + return fwrite(&header, sizeof(header), 1, stdout) != 1 || + fwrite(payload, size, 1, stdout) != 1; +} + +/* Named C offsets are exported independently of the Rust decoder. */ +static void layout(void) +{ + printf("profile.params_required %zu 0\n", + offsetof(struct drm_asahi_params_global, gpu_hal_generation) + sizeof(__u32)); + printf("profile.vm_create_size %zu 0\n", sizeof(struct drm_asahi_vm_create)); + printf("profile.hal200 %u 0\n", (unsigned int)DRM_ASAHI_GPU_HAL_200); + printf("profile.features %u 0\n", + DRM_ASAHI_FEATURE_VM_STATUS | DRM_ASAHI_FEATURE_SCHEDULED_QUEUES); + printf("header %zu 0\n", sizeof(struct drm_asahi_cmd_header)); + printf("attachment %zu 0\n", sizeof(struct drm_asahi_attachment)); + printf("timestamps %zu 0\n", sizeof(struct drm_asahi_timestamps)); + printf("cmd_render.flags %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, flags), + sizeof(((struct drm_asahi_cmd_render *)0)->flags)); + printf("cmd_render.isp_zls_pixels %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_zls_pixels), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_zls_pixels)); + printf("cmd_render.vdm_ctrl_stream_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, vdm_ctrl_stream_base), + sizeof(((struct drm_asahi_cmd_render *)0)->vdm_ctrl_stream_base)); + printf("cmd_render.vertex_helper.binary %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, vertex_helper.binary), + sizeof(((struct drm_asahi_cmd_render *)0)->vertex_helper.binary)); + printf("cmd_render.vertex_helper.cfg %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, vertex_helper.cfg), + sizeof(((struct drm_asahi_cmd_render *)0)->vertex_helper.cfg)); + printf("cmd_render.vertex_helper.data %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, vertex_helper.data), + sizeof(((struct drm_asahi_cmd_render *)0)->vertex_helper.data)); + printf("cmd_render.fragment_helper.binary %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, fragment_helper.binary), + sizeof(((struct drm_asahi_cmd_render *)0)->fragment_helper.binary)); + printf("cmd_render.fragment_helper.cfg %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, fragment_helper.cfg), + sizeof(((struct drm_asahi_cmd_render *)0)->fragment_helper.cfg)); + printf("cmd_render.fragment_helper.data %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, fragment_helper.data), + sizeof(((struct drm_asahi_cmd_render *)0)->fragment_helper.data)); + printf("cmd_render.isp_scissor_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_scissor_base), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_scissor_base)); + printf("cmd_render.isp_dbias_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_dbias_base), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_dbias_base)); + printf("cmd_render.isp_oclqry_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_oclqry_base), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_oclqry_base)); + printf("cmd_render.depth.base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, depth.base), + sizeof(((struct drm_asahi_cmd_render *)0)->depth.base)); + printf("cmd_render.depth.comp_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, depth.comp_base), + sizeof(((struct drm_asahi_cmd_render *)0)->depth.comp_base)); + printf("cmd_render.depth.stride %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, depth.stride), + sizeof(((struct drm_asahi_cmd_render *)0)->depth.stride)); + printf("cmd_render.depth.comp_stride %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, depth.comp_stride), + sizeof(((struct drm_asahi_cmd_render *)0)->depth.comp_stride)); + printf("cmd_render.stencil.base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, stencil.base), + sizeof(((struct drm_asahi_cmd_render *)0)->stencil.base)); + printf("cmd_render.stencil.comp_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, stencil.comp_base), + sizeof(((struct drm_asahi_cmd_render *)0)->stencil.comp_base)); + printf("cmd_render.stencil.stride %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, stencil.stride), + sizeof(((struct drm_asahi_cmd_render *)0)->stencil.stride)); + printf("cmd_render.stencil.comp_stride %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, stencil.comp_stride), + sizeof(((struct drm_asahi_cmd_render *)0)->stencil.comp_stride)); + printf("cmd_render.zls_ctrl %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, zls_ctrl), + sizeof(((struct drm_asahi_cmd_render *)0)->zls_ctrl)); + printf("cmd_render.ppp_multisamplectl %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ppp_multisamplectl), + sizeof(((struct drm_asahi_cmd_render *)0)->ppp_multisamplectl)); + printf("cmd_render.sampler_heap %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, sampler_heap), + sizeof(((struct drm_asahi_cmd_render *)0)->sampler_heap)); + printf("cmd_render.ppp_ctrl %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ppp_ctrl), + sizeof(((struct drm_asahi_cmd_render *)0)->ppp_ctrl)); + printf("cmd_render.width_px %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, width_px), + sizeof(((struct drm_asahi_cmd_render *)0)->width_px)); + printf("cmd_render.height_px %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, height_px), + sizeof(((struct drm_asahi_cmd_render *)0)->height_px)); + printf("cmd_render.layers %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, layers), + sizeof(((struct drm_asahi_cmd_render *)0)->layers)); + printf("cmd_render.sampler_count %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, sampler_count), + sizeof(((struct drm_asahi_cmd_render *)0)->sampler_count)); + printf("cmd_render.utile_width_px %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, utile_width_px), + sizeof(((struct drm_asahi_cmd_render *)0)->utile_width_px)); + printf("cmd_render.utile_height_px %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, utile_height_px), + sizeof(((struct drm_asahi_cmd_render *)0)->utile_height_px)); + printf("cmd_render.samples %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, samples), + sizeof(((struct drm_asahi_cmd_render *)0)->samples)); + printf("cmd_render.sample_size_B %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, sample_size_B), + sizeof(((struct drm_asahi_cmd_render *)0)->sample_size_B)); + printf("cmd_render.isp_merge_upper_x %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_merge_upper_x), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_merge_upper_x)); + printf("cmd_render.isp_merge_upper_y %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_merge_upper_y), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_merge_upper_y)); + printf("cmd_render.bg.usc %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, bg.usc), + sizeof(((struct drm_asahi_cmd_render *)0)->bg.usc)); + printf("cmd_render.bg.rsrc_spec %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, bg.rsrc_spec), + sizeof(((struct drm_asahi_cmd_render *)0)->bg.rsrc_spec)); + printf("cmd_render.bg_rsrc_spec_hi %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, bg_rsrc_spec_hi), + sizeof(((struct drm_asahi_cmd_render *)0)->bg_rsrc_spec_hi)); + printf("cmd_render.eot.usc %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, eot.usc), + sizeof(((struct drm_asahi_cmd_render *)0)->eot.usc)); + printf("cmd_render.eot.rsrc_spec %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, eot.rsrc_spec), + sizeof(((struct drm_asahi_cmd_render *)0)->eot.rsrc_spec)); + printf("cmd_render.bg_eot_rsrc_spec_hi %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, bg_eot_rsrc_spec_hi), + sizeof(((struct drm_asahi_cmd_render *)0)->bg_eot_rsrc_spec_hi)); + printf("cmd_render.partial_bg.usc %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, partial_bg.usc), + sizeof(((struct drm_asahi_cmd_render *)0)->partial_bg.usc)); + printf("cmd_render.partial_bg.rsrc_spec %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, partial_bg.rsrc_spec), + sizeof(((struct drm_asahi_cmd_render *)0)->partial_bg.rsrc_spec)); + printf("cmd_render.bg_partial_rsrc_spec_hi %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, bg_partial_rsrc_spec_hi), + sizeof(((struct drm_asahi_cmd_render *)0)->bg_partial_rsrc_spec_hi)); + printf("cmd_render.partial_eot.usc %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, partial_eot.usc), + sizeof(((struct drm_asahi_cmd_render *)0)->partial_eot.usc)); + printf("cmd_render.partial_eot.rsrc_spec %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, partial_eot.rsrc_spec), + sizeof(((struct drm_asahi_cmd_render *)0)->partial_eot.rsrc_spec)); + printf("cmd_render.bg_eot_partial_rsrc_spec_hi %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, bg_eot_partial_rsrc_spec_hi), + sizeof(((struct drm_asahi_cmd_render *)0)->bg_eot_partial_rsrc_spec_hi)); + printf("cmd_render.isp_bgobjdepth %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_bgobjdepth), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_bgobjdepth)); + printf("cmd_render.isp_bgobjvals %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, isp_bgobjvals), + sizeof(((struct drm_asahi_cmd_render *)0)->isp_bgobjvals)); + printf("cmd_render.ts_vtx.start.handle %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_vtx.start.handle), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_vtx.start.handle)); + printf("cmd_render.ts_vtx.start.offset %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_vtx.start.offset), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_vtx.start.offset)); + printf("cmd_render.ts_vtx.end.handle %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_vtx.end.handle), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_vtx.end.handle)); + printf("cmd_render.ts_vtx.end.offset %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_vtx.end.offset), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_vtx.end.offset)); + printf("cmd_render.ts_frag.start.handle %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_frag.start.handle), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_frag.start.handle)); + printf("cmd_render.ts_frag.start.offset %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_frag.start.offset), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_frag.start.offset)); + printf("cmd_render.ts_frag.end.handle %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_frag.end.handle), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_frag.end.handle)); + printf("cmd_render.ts_frag.end.offset %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_frag.end.offset), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_frag.end.offset)); + printf("cmd_compute.flags %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, flags), + sizeof(((struct drm_asahi_cmd_compute *)0)->flags)); + printf("cmd_compute.sampler_count %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, sampler_count), + sizeof(((struct drm_asahi_cmd_compute *)0)->sampler_count)); + printf("cmd_compute.cdm_ctrl_stream_base %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, cdm_ctrl_stream_base), + sizeof(((struct drm_asahi_cmd_compute *)0)->cdm_ctrl_stream_base)); + printf("cmd_compute.cdm_ctrl_stream_end %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, cdm_ctrl_stream_end), + sizeof(((struct drm_asahi_cmd_compute *)0)->cdm_ctrl_stream_end)); + printf("cmd_compute.sampler_heap %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, sampler_heap), + sizeof(((struct drm_asahi_cmd_compute *)0)->sampler_heap)); + printf("cmd_compute.helper.binary %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, helper.binary), + sizeof(((struct drm_asahi_cmd_compute *)0)->helper.binary)); + printf("cmd_compute.helper.cfg %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, helper.cfg), + sizeof(((struct drm_asahi_cmd_compute *)0)->helper.cfg)); + printf("cmd_compute.helper.data %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, helper.data), + sizeof(((struct drm_asahi_cmd_compute *)0)->helper.data)); + printf("cmd_compute.ts.start.handle %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, ts.start.handle), + sizeof(((struct drm_asahi_cmd_compute *)0)->ts.start.handle)); + printf("cmd_compute.ts.start.offset %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, ts.start.offset), + sizeof(((struct drm_asahi_cmd_compute *)0)->ts.start.offset)); + printf("cmd_compute.ts.end.handle %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, ts.end.handle), + sizeof(((struct drm_asahi_cmd_compute *)0)->ts.end.handle)); + printf("cmd_compute.ts.end.offset %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, ts.end.offset), + sizeof(((struct drm_asahi_cmd_compute *)0)->ts.end.offset)); + printf("attachment.pointer %zu %zu\n", + offsetof(struct drm_asahi_attachment, pointer), + sizeof(((struct drm_asahi_attachment *)0)->pointer)); + printf("attachment.size %zu %zu\n", + offsetof(struct drm_asahi_attachment, size), + sizeof(((struct drm_asahi_attachment *)0)->size)); + printf("cmd_render.ts_frag_end_copies %zu %zu\n", + offsetof(struct drm_asahi_cmd_render, ts_frag_end_copies), + sizeof(((struct drm_asahi_cmd_render *)0)->ts_frag_end_copies)); + printf("cmd_compute.ts_end_copies %zu %zu\n", + offsetof(struct drm_asahi_cmd_compute, ts_end_copies), + sizeof(((struct drm_asahi_cmd_compute *)0)->ts_end_copies)); +} + +int main(int argc, char **argv) +{ + struct drm_asahi_attachment attachment = {0}; + + attachment.pointer = 0xcdb09376593c1f02ULL; + attachment.size = 0xceb194775a3d2003ULL; + + struct drm_asahi_cmd_render render = {0}; + + render.flags = 0x5b3e2104; + render.isp_zls_pixels = 0x5c3f2205; + render.vdm_ctrl_stream_base = 0xd1b4977a5d402306ULL; + render.vertex_helper.binary = 0x5e412407; + render.vertex_helper.cfg = 0x5f422508; + render.vertex_helper.data = 0xd4b79a7d60432609ULL; + render.fragment_helper.binary = 0x6144270a; + render.fragment_helper.cfg = 0x6245280b; + render.fragment_helper.data = 0xd7ba9d806346290cULL; + render.isp_scissor_base = 0xd8bb9e8164472a0dULL; + render.isp_dbias_base = 0xd9bc9f8265482b0eULL; + render.isp_oclqry_base = 0xdabda08366492c0fULL; + render.depth.base = 0xdbbea184674a2d10ULL; + render.depth.comp_base = 0xdcbfa285684b2e11ULL; + render.depth.stride = 0x694c2f12; + render.depth.comp_stride = 0x6a4d3013; + render.stencil.base = 0xdfc2a5886b4e3114ULL; + render.stencil.comp_base = 0xe0c3a6896c4f3215ULL; + render.stencil.stride = 0x6d503316; + render.stencil.comp_stride = 0x6e513417; + render.zls_ctrl = 0xe3c6a98c6f523518ULL; + render.ppp_multisamplectl = 0xe4c7aa8d70533619ULL; + render.sampler_heap = 0xe5c8ab8e7154371aULL; + render.ppp_ctrl = 0x7255381b; + render.width_px = 0x391c; + render.height_px = 0x3a1d; + render.layers = 0x3b1e; + render.sampler_count = 0x3c1f; + render.utile_width_px = 0x20; + render.utile_height_px = 0x21; + render.samples = 0x22; + render.sample_size_B = 0x23; + render.isp_merge_upper_x = 0x7b5e4124; + render.isp_merge_upper_y = 0x7c5f4225; + render.bg.usc = 0x7d604326; + render.bg.rsrc_spec = 0x7e614427; + render.bg_rsrc_spec_hi = 0x7f624528; + render.eot.usc = 0x80634629; + render.eot.rsrc_spec = 0x8164472a; + render.bg_eot_rsrc_spec_hi = 0x8265482b; + render.partial_bg.usc = 0x8366492c; + render.partial_bg.rsrc_spec = 0x84674a2d; + render.bg_partial_rsrc_spec_hi = 0x85684b2e; + render.partial_eot.usc = 0x86694c2f; + render.partial_eot.rsrc_spec = 0x876a4d30; + render.bg_eot_partial_rsrc_spec_hi = 0x886b4e31; + render.isp_bgobjdepth = 0x896c4f32; + render.isp_bgobjvals = 0x8a6d5033; + render.ts_vtx.start.handle = 0x8b6e5134; + render.ts_vtx.start.offset = 0x8c6f5235; + render.ts_vtx.end.handle = 0x8d705336; + render.ts_vtx.end.offset = 0x8e715437; + render.ts_frag.start.handle = 0x8f725538; + render.ts_frag.start.offset = 0x90735639; + render.ts_frag.end.handle = 0x9174573a; + render.ts_frag.end.offset = 0x9275583b; + + struct drm_asahi_cmd_compute compute = {0}; + + compute.flags = 0x9376593c; + compute.sampler_count = 0x94775a3d; + compute.cdm_ctrl_stream_base = 0xbeccfb295785b3eULL; + compute.cdm_ctrl_stream_end = 0xcedd0b396795c3fULL; + compute.sampler_heap = 0xdeed1b4977a5d40ULL; + compute.helper.binary = 0x987b5e41; + compute.helper.cfg = 0x997c5f42; + compute.helper.data = 0x10f1d4b79a7d6043ULL; + compute.ts.start.handle = 0x9b7e6144; + compute.ts.start.offset = 0x9c7f6245; + compute.ts.end.handle = 0x9d806346; + compute.ts.end.offset = 0x9e816447; + + size_t render_size = + offsetof(struct drm_asahi_cmd_render, ts_frag_end_copies); + size_t compute_size = + offsetof(struct drm_asahi_cmd_compute, ts_end_copies); + + if (argc == 2 && !strcmp(argv[1], "--layout")) { + layout(); + return 0; + } else if (argc == 2 && !strcmp(argv[1], "--full")) { + render_size = sizeof(render); + compute_size = sizeof(compute); + } else if (argc != 1) { + return 1; + } + + return emit(DRM_ASAHI_SET_VERTEX_ATTACHMENTS, &attachment, + sizeof(attachment)) || + emit(DRM_ASAHI_CMD_RENDER, &render, render_size) || + emit(DRM_ASAHI_CMD_COMPUTE, &compute, compute_size); +} diff --git a/tools/asahi/m3-uapi-tests.rs b/tools/asahi/m3-uapi-tests.rs new file mode 100644 index 00000000000000..4dc6012f5213e7 --- /dev/null +++ b/tools/asahi/m3-uapi-tests.rs @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +//! Offline tests of the production parser. All addresses are synthetic. +#![allow(dead_code)] + +#[path = "../../drivers/gpu/drm/asahi/m3/t8122_admission.rs"] +mod admission; +#[path = "../../drivers/gpu/drm/asahi/m3/m3_profile.rs"] +mod m3_profile; +#[path = "../../drivers/gpu/drm/asahi/m3/agx_uapi.rs"] +mod uapi; +use uapi::*; + +fn frame(kind: u16, payload: &[u8]) -> Vec { + let mut bytes = Vec::new(); + for word in [ + kind, + payload.len() as u16, + UAPI_BARRIER_NONE, + UAPI_BARRIER_NONE, + ] { + bytes.extend_from_slice(&word.to_le_bytes()); + } + bytes.extend_from_slice(payload); + bytes +} + +fn parse(bytes: &[u8]) -> Result { + let mut parser = UapiCommandParser::new(bytes); + while parser.next_hardware()?.is_some() {} + parser.finish() +} + +// Each offset below is checked against offsetof of the corresponding C member. +fn layout() -> &'static std::collections::BTreeMap { + static LAYOUT: std::sync::OnceLock> = + std::sync::OnceLock::new(); + LAYOUT.get_or_init(|| { + let path = std::env::var("ASAHI_UAPI_LAYOUT").expect("C layout path"); + std::fs::read_to_string(path) + .unwrap() + .lines() + .map(|line| { + let words: Vec<_> = line.split_whitespace().collect(); + ( + words[0].to_string(), + (words[1].parse().unwrap(), words[2].parse().unwrap()), + ) + }) + .collect() + }) +} + +fn field(raw: &[u8], name: &str, offset: usize, width: usize, actual: u64) { + assert_eq!(layout()[name], (offset, width), "C offset/width for {name}"); + let mut expected = [0u8; 8]; + for (i, byte) in expected[..width].iter_mut().enumerate() { + *byte = raw.get(offset + i).copied().unwrap_or(0); + } + assert_eq!(actual, u64::from_le_bytes(expected), "decoded {name}"); +} + +fn check_render(p: UapiRenderCommand, raw: &[u8]) { + field(raw, "cmd_render.flags", 0, 4, p.flags as u64); + field( + raw, + "cmd_render.isp_zls_pixels", + 4, + 4, + p.depth_dimensions as u64, + ); + field( + raw, + "cmd_render.vdm_ctrl_stream_base", + 8, + 8, + p.vdm_base as u64, + ); + field( + raw, + "cmd_render.vertex_helper.binary", + 16, + 4, + p.vertex_helper.binary as u64, + ); + field( + raw, + "cmd_render.vertex_helper.cfg", + 20, + 4, + p.vertex_helper.config as u64, + ); + field( + raw, + "cmd_render.vertex_helper.data", + 24, + 8, + p.vertex_helper.data as u64, + ); + field( + raw, + "cmd_render.fragment_helper.binary", + 32, + 4, + p.fragment_helper.binary as u64, + ); + field( + raw, + "cmd_render.fragment_helper.cfg", + 36, + 4, + p.fragment_helper.config as u64, + ); + field( + raw, + "cmd_render.fragment_helper.data", + 40, + 8, + p.fragment_helper.data as u64, + ); + field( + raw, + "cmd_render.isp_scissor_base", + 48, + 8, + p.scissor_base as u64, + ); + field( + raw, + "cmd_render.isp_dbias_base", + 56, + 8, + p.depth_bias_base as u64, + ); + field( + raw, + "cmd_render.isp_oclqry_base", + 64, + 8, + p.occlusion_query_base as u64, + ); + field(raw, "cmd_render.depth.base", 72, 8, p.depth.base as u64); + field( + raw, + "cmd_render.depth.comp_base", + 80, + 8, + p.depth.compression_base as u64, + ); + field(raw, "cmd_render.depth.stride", 88, 4, p.depth.stride as u64); + field( + raw, + "cmd_render.depth.comp_stride", + 92, + 4, + p.depth.compression_stride as u64, + ); + field(raw, "cmd_render.stencil.base", 96, 8, p.stencil.base as u64); + field( + raw, + "cmd_render.stencil.comp_base", + 104, + 8, + p.stencil.compression_base as u64, + ); + field( + raw, + "cmd_render.stencil.stride", + 112, + 4, + p.stencil.stride as u64, + ); + field( + raw, + "cmd_render.stencil.comp_stride", + 116, + 4, + p.stencil.compression_stride as u64, + ); + field(raw, "cmd_render.zls_ctrl", 120, 8, p.zls_control as u64); + field( + raw, + "cmd_render.ppp_multisamplectl", + 128, + 8, + p.multisample_control as u64, + ); + field( + raw, + "cmd_render.sampler_heap", + 136, + 8, + p.sampler_heap as u64, + ); + field(raw, "cmd_render.ppp_ctrl", 144, 4, p.ppp_control as u64); + field(raw, "cmd_render.width_px", 148, 2, p.width as u64); + field(raw, "cmd_render.height_px", 150, 2, p.height as u64); + field(raw, "cmd_render.layers", 152, 2, p.layers as u64); + field( + raw, + "cmd_render.sampler_count", + 154, + 2, + p.sampler_count as u64, + ); + field( + raw, + "cmd_render.utile_width_px", + 156, + 1, + p.utile_width as u64, + ); + field( + raw, + "cmd_render.utile_height_px", + 157, + 1, + p.utile_height as u64, + ); + field(raw, "cmd_render.samples", 158, 1, p.samples as u64); + field( + raw, + "cmd_render.sample_size_B", + 159, + 1, + p.sample_size as u64, + ); + field( + raw, + "cmd_render.isp_merge_upper_x", + 160, + 4, + p.merge_upper_x as u64, + ); + field( + raw, + "cmd_render.isp_merge_upper_y", + 164, + 4, + p.merge_upper_y as u64, + ); + field(raw, "cmd_render.bg.usc", 168, 4, p.background.usc as u64); + field( + raw, + "cmd_render.bg.rsrc_spec", + 172, + 4, + (p.background.resource_spec & 0xffffffff) as u64, + ); + field( + raw, + "cmd_render.bg_rsrc_spec_hi", + 248, + 4, + (p.background.resource_spec >> 32) as u64, + ); + field(raw, "cmd_render.eot.usc", 176, 4, p.end_of_tile.usc as u64); + field( + raw, + "cmd_render.eot.rsrc_spec", + 180, + 4, + (p.end_of_tile.resource_spec & 0xffffffff) as u64, + ); + field( + raw, + "cmd_render.bg_eot_rsrc_spec_hi", + 240, + 4, + (p.end_of_tile.resource_spec >> 32) as u64, + ); + field( + raw, + "cmd_render.partial_bg.usc", + 184, + 4, + p.partial_background.usc as u64, + ); + field( + raw, + "cmd_render.partial_bg.rsrc_spec", + 188, + 4, + (p.partial_background.resource_spec & 0xffffffff) as u64, + ); + field( + raw, + "cmd_render.bg_partial_rsrc_spec_hi", + 252, + 4, + (p.partial_background.resource_spec >> 32) as u64, + ); + field( + raw, + "cmd_render.partial_eot.usc", + 192, + 4, + p.partial_end_of_tile.usc as u64, + ); + field( + raw, + "cmd_render.partial_eot.rsrc_spec", + 196, + 4, + (p.partial_end_of_tile.resource_spec & 0xffffffff) as u64, + ); + field( + raw, + "cmd_render.bg_eot_partial_rsrc_spec_hi", + 244, + 4, + (p.partial_end_of_tile.resource_spec >> 32) as u64, + ); + field( + raw, + "cmd_render.isp_bgobjdepth", + 200, + 4, + p.depth_clear as u64, + ); + field( + raw, + "cmd_render.isp_bgobjvals", + 204, + 4, + p.stencil_clear as u64, + ); + field( + raw, + "cmd_render.ts_vtx.start.handle", + 208, + 4, + p.vertex_timestamps.start.handle as u64, + ); + field( + raw, + "cmd_render.ts_vtx.start.offset", + 212, + 4, + p.vertex_timestamps.start.offset as u64, + ); + field( + raw, + "cmd_render.ts_vtx.end.handle", + 216, + 4, + p.vertex_timestamps.end.handle as u64, + ); + field( + raw, + "cmd_render.ts_vtx.end.offset", + 220, + 4, + p.vertex_timestamps.end.offset as u64, + ); + field( + raw, + "cmd_render.ts_frag.start.handle", + 224, + 4, + p.fragment_timestamps.start.handle as u64, + ); + field( + raw, + "cmd_render.ts_frag.start.offset", + 228, + 4, + p.fragment_timestamps.start.offset as u64, + ); + field( + raw, + "cmd_render.ts_frag.end.handle", + 232, + 4, + p.fragment_timestamps.end.handle as u64, + ); + field( + raw, + "cmd_render.ts_frag.end.offset", + 236, + 4, + p.fragment_timestamps.end.offset as u64, + ); +} + +fn check_compute(p: UapiComputeCommand, raw: &[u8]) { + field(raw, "cmd_compute.flags", 0, 4, p.flags as u64); + field( + raw, + "cmd_compute.sampler_count", + 4, + 4, + p.sampler_count as u64, + ); + field( + raw, + "cmd_compute.cdm_ctrl_stream_base", + 8, + 8, + p.control_stream_base as u64, + ); + field( + raw, + "cmd_compute.cdm_ctrl_stream_end", + 16, + 8, + p.control_stream_end as u64, + ); + field( + raw, + "cmd_compute.sampler_heap", + 24, + 8, + p.sampler_heap as u64, + ); + field( + raw, + "cmd_compute.helper.binary", + 32, + 4, + p.helper.binary as u64, + ); + field(raw, "cmd_compute.helper.cfg", 36, 4, p.helper.config as u64); + field(raw, "cmd_compute.helper.data", 40, 8, p.helper.data as u64); + field( + raw, + "cmd_compute.ts.start.handle", + 48, + 4, + p.timestamps.start.handle as u64, + ); + field( + raw, + "cmd_compute.ts.start.offset", + 52, + 4, + p.timestamps.start.offset as u64, + ); + field( + raw, + "cmd_compute.ts.end.handle", + 56, + 4, + p.timestamps.end.handle as u64, + ); + field( + raw, + "cmd_compute.ts.end.offset", + 60, + 4, + p.timestamps.end.offset as u64, + ); +} + +fn fixture() -> Vec { + std::fs::read(std::env::var("ASAHI_UAPI_FIXTURE").expect("C fixture path")).unwrap() +} + +#[test] +fn c_header_producer_matches_production_decoder() { + let offsets = layout(); + assert_eq!(offsets["header"].0, UAPI_COMMAND_HEADER_SIZE); + assert_eq!(offsets["attachment"].0, UAPI_ATTACHMENT_SIZE); + assert_eq!(offsets["timestamps"].0, 16); + assert_eq!(offsets["cmd_render.ts_frag_end_copies"].0, UAPI_RENDER_SIZE); + assert_eq!(offsets["cmd_compute.ts_end_copies"].0, UAPI_COMPUTE_SIZE); + let bytes = fixture(); + let render_offset = 2 * UAPI_COMMAND_HEADER_SIZE + UAPI_ATTACHMENT_SIZE; + let render_size = u16::from_le_bytes( + bytes[render_offset - 6..render_offset - 4] + .try_into() + .unwrap(), + ) as usize; + let compute_offset = render_offset + render_size + UAPI_COMMAND_HEADER_SIZE; + let mut parser = UapiCommandParser::new(&bytes); + let Some(ParsedHardwareCommand::Render { + index, + header, + payload, + vertex_attachments, + fragment_attachments, + }) = parser.next_hardware().unwrap() + else { + panic!("render missing") + }; + assert_eq!(index, 2); + assert_eq!( + header, + UapiCommandHeader { + command_type: 0, + size: render_size as u16, + render_barrier: UAPI_BARRIER_NONE, + compute_barrier: UAPI_BARRIER_NONE, + } + ); + check_render(payload, &bytes[render_offset..render_offset + render_size]); + assert_eq!(vertex_attachments.count, 1); + let attachment = vertex_attachments.as_slice()[0]; + field( + &bytes[8..32], + "attachment.pointer", + 0, + 8, + attachment.address, + ); + field(&bytes[8..32], "attachment.size", 8, 8, attachment.size); + assert!(fragment_attachments.as_slice().is_empty()); + let Some(ParsedHardwareCommand::Compute { + index, + header, + payload, + attachments, + }) = parser.next_hardware().unwrap() + else { + panic!("compute missing") + }; + assert_eq!(index, 3); + assert_eq!( + header, + UapiCommandHeader { + command_type: 1, + size: (bytes.len() - compute_offset) as u16, + render_barrier: UAPI_BARRIER_NONE, + compute_barrier: UAPI_BARRIER_NONE, + } + ); + check_compute(payload, &bytes[compute_offset..]); + assert!(attachments.as_slice().is_empty()); + assert!(parser.next_hardware().unwrap().is_none()); + assert_eq!(parser.finish(), Ok(2)); +} + +#[test] +fn short_payloads_zero_extend_but_missing_bytes_are_rejected() { + // Every length includes cuts within each multi-byte field, for both kinds. + for (kind, size) in [(0, 256), (1, 64)] { + let supplied: Vec = (0..size).map(|i| (i % 254 + 1) as u8).collect(); + for length in 0..=size { + let raw = &supplied[..length]; + let bytes = frame(kind, raw); + let mut parser = UapiCommandParser::new(&bytes); + match parser.next_hardware().unwrap().unwrap() { + ParsedHardwareCommand::Render { payload, .. } => check_render(payload, raw), + ParsedHardwareCommand::Compute { payload, .. } => check_compute(payload, raw), + } + assert!(parser.next_hardware().unwrap().is_none()); + assert_eq!(parser.finish(), Ok(1)); + for cut in 1..bytes.len() { + assert!(parse(&bytes[..cut]).is_err()); + } + } + } + assert_eq!(parse(&[]), Err(UapiTranslateError::HardwareCommandCount)); +} + +#[test] +fn named_optional_timestamp_copies_are_refused() { + for (kind, name) in [ + (0, "cmd_render.ts_frag_end_copies"), + (1, "cmd_compute.ts_end_copies"), + ] { + let (offset, size) = layout()[name]; + let mut raw = vec![0; offset + size]; + assert_eq!(parse(&frame(kind, &raw)), Ok(1)); + for i in offset..raw.len() { + raw[i] = 1; + assert_eq!( + parse(&frame(kind, &raw)), + Err(UapiTranslateError::PayloadTail), + "{name} byte {i}" + ); + raw[i] = 0; + } + } +} + +#[test] +fn unsupported_nonzero_extensions_never_disappear() { + for (kind, size) in [(0, UAPI_RENDER_SIZE), (1, UAPI_COMPUTE_SIZE)] { + for extra in [1, 8, 64, 128] { + let mut payload = vec![0; size + extra]; + assert_eq!(parse(&frame(kind, &payload)), Ok(1)); + for offset in size..payload.len() { + payload[offset] = 1; + assert_eq!( + parse(&frame(kind, &payload)), + Err(UapiTranslateError::PayloadTail) + ); + payload[offset] = 0; + } + } + } + assert_eq!( + parse(&frame(0xffff, &[])), + Err(UapiTranslateError::CommandType) + ); +} + +#[test] +fn attachment_shapes_counts_and_reserved_fields_are_checked() { + for kind in 2..=4 { + for size in 1..UAPI_ATTACHMENT_SIZE { + assert_eq!( + parse(&frame(kind, &vec![0; size])), + Err(UapiTranslateError::AttachmentPayloadSize) + ); + } + let mut bytes = frame(kind, &vec![0; UAPI_ATTACHMENT_SIZE * UAPI_MAX_ATTACHMENTS]); + bytes.extend(frame(1, &[])); + assert_eq!(parse(&bytes), Ok(1)); + assert_eq!( + parse(&frame( + kind, + &vec![0; UAPI_ATTACHMENT_SIZE * (UAPI_MAX_ATTACHMENTS + 1)] + )), + Err(UapiTranslateError::AttachmentCount) + ); + for offset in 16..24 { + let mut payload = [0; UAPI_ATTACHMENT_SIZE]; + payload[offset] = 1; + assert_eq!( + parse(&frame(kind, &payload)), + Err(UapiTranslateError::AttachmentFields) + ); + } + let mut bytes = frame(kind, &[]); + bytes[4..6].copy_from_slice(&0u16.to_le_bytes()); + assert_eq!(parse(&bytes), Err(UapiTranslateError::AttachmentBarrier)); + } +} + +#[test] +fn attachment_lists_reach_their_commands() { + let entry = |pointer: u64, size: u64| { + let mut raw = [0u8; UAPI_ATTACHMENT_SIZE]; + raw[..8].copy_from_slice(&pointer.to_le_bytes()); + raw[8..16].copy_from_slice(&size.to_le_bytes()); + raw + }; + let vertex = entry(0x1_2345_6000, 0x1_0000_0040).to_vec(); + let fragment = [ + entry(0x2_0000_8000, 0x80), + entry(0x3_0000_c000, 0x2_0000_0100), + ] + .concat(); + let compute = entry(0x4_0000_1000, 0x5_0000_0010).to_vec(); + let check = |list: UapiAttachmentList, raw: &[u8]| { + assert_eq!(list.as_slice().len(), raw.len() / UAPI_ATTACHMENT_SIZE); + for (attachment, raw) in list.as_slice().iter().zip(raw.chunks(UAPI_ATTACHMENT_SIZE)) { + field(raw, "attachment.pointer", 0, 8, attachment.address); + field(raw, "attachment.size", 8, 8, attachment.size); + } + }; + + // Lists persist until replaced; an empty list clears only its own kind. + let mut bytes = frame(2, &vertex); + bytes.extend(frame(3, &fragment)); + bytes.extend(frame(4, &compute)); + bytes.extend(frame(0, &[])); + bytes.extend(frame(1, &[])); + bytes.extend(frame(3, &[])); + bytes.extend(frame(0, &[])); + let mut parser = UapiCommandParser::new(&bytes); + for expected_fragment in [&fragment[..], &[]] { + let Some(ParsedHardwareCommand::Render { + vertex_attachments, + fragment_attachments, + .. + }) = parser.next_hardware().unwrap() + else { + panic!("render missing") + }; + check(vertex_attachments, &vertex); + check(fragment_attachments, expected_fragment); + if !expected_fragment.is_empty() { + let Some(ParsedHardwareCommand::Compute { attachments, .. }) = + parser.next_hardware().unwrap() + else { + panic!("compute missing") + }; + check(attachments, &compute); + } + } + assert!(parser.next_hardware().unwrap().is_none()); + assert_eq!(parser.finish(), Ok(3)); +} + +#[test] +fn dependencies_count_hardware_commands_and_bound_the_submit() { + for field in [4, 6] { + let mut bytes = frame(1, &[]); + bytes[field..field + 2].copy_from_slice(&1u16.to_le_bytes()); + assert_eq!(parse(&bytes), Err(UapiTranslateError::BarrierFuture)); + } + let mut bytes = frame(2, &[]); + bytes.extend(frame(0, &[])); + let mut compute = frame(1, &[]); + compute[4..6].copy_from_slice(&1u16.to_le_bytes()); + bytes.extend(compute); + assert_eq!(parse(&bytes), Ok(2)); + let command = frame(1, &[]); + assert_eq!( + parse(&command.repeat(UAPI_MAX_HARDWARE_COMMANDS as usize)), + Ok(UAPI_MAX_HARDWARE_COMMANDS) + ); + assert_eq!( + parse(&command.repeat(UAPI_MAX_HARDWARE_COMMANDS as usize + 1)), + Err(UapiTranslateError::HardwareCommandCount) + ); +} + +struct Region { + start: u64, + end: u64, + writable: bool, +} +impl GpuAddressSpace for Region { + fn covers(&self, address: u64, size: u64, access: GpuAccess) -> bool { + let Some(end) = address.checked_add(size) else { + return false; + }; + address >= self.start && end <= self.end && (access == GpuAccess::Read || self.writable) + } +} + +#[test] +fn attachment_validation_rejects_readonly_zero_and_wrapped_ranges() { + let mut list = UapiAttachmentList::EMPTY; + list.count = 1; + let mut region = Region { + start: 0x4000, + end: 0x8000, + writable: false, + }; + list.entries[0] = UapiAttachment { + address: 0x4000, + size: 0x4000, + }; + assert_eq!( + validate_attachments(®ion, &list), + Err(UapiTranslateError::AddressRange) + ); + region.writable = true; + assert_eq!(validate_attachments(®ion, &list), Ok(())); + for (address, size) in [(0x4000, 0), (0x4000, 0x4001), (u64::MAX, 2)] { + list.entries[0] = UapiAttachment { address, size }; + assert_eq!( + validate_attachments(®ion, &list), + Err(UapiTranslateError::AddressRange) + ); + } +} + +#[test] +fn usc_windows_reject_misalignment_overflow_and_unmapped_programs() { + let queue = QueueUscWindow { + base: USC_WINDOW_SIZE, + user_start: 0x4000, + user_end: 2 * USC_WINDOW_SIZE, + }; + assert_eq!(queue.validate(), Ok(())); + assert_eq!( + QueueUscWindow { + base: queue.base + 1, + ..queue + } + .validate(), + Err(UapiTranslateError::QueueUscAlignment) + ); + assert_eq!( + QueueUscWindow { + base: u64::MAX & !(USC_WINDOW_SIZE - 1), + ..queue + } + .validate(), + Err(UapiTranslateError::QueueUscRange) + ); + assert_eq!( + QueueUscWindow { + user_end: queue.user_end - 1, + ..queue + } + .validate(), + Err(UapiTranslateError::QueueUscRange) + ); + let region = Region { + start: queue.base + 8, + end: queue.base + 12, + writable: false, + }; + assert_eq!(queue.full_program(®ion, 9), Ok(queue.base + 8)); + assert_eq!( + queue.full_program(®ion, 0), + Err(UapiTranslateError::ProgramOffset) + ); + assert_eq!( + queue.full_program(®ion, 16), + Err(UapiTranslateError::AddressRange) + ); +} + +#[test] +fn unsupported_firmware_cannot_reach_the_existing_profile() { + let identity = (1, 1, 0x07022000, [0xff, 0, 0]); + assert!(admission::validate_identity( + &[14, 8, 3], + identity.0, + identity.1, + identity.2, + identity.3 + ) + .is_some()); + for version in [&[26, 6, 2][..], &[14, 8][..], &[14, 8, 3, 0][..], &[][..]] { + assert!(admission::validate_identity( + version, identity.0, identity.1, identity.2, identity.3 + ) + .is_none()); + } +} + +#[test] +fn firmware_profile_agrees_with_the_actual_c_header() { + assert_eq!( + layout()["profile.params_required"].0 as u64, + m3_profile::PARAMS_REQUIRED + ); + assert_eq!(layout()["profile.vm_create_size"].0, 24); + assert_eq!( + layout()["profile.hal200"].0 as u32, + m3_profile::Profile::Extended.hal() + ); + assert_eq!(layout()["profile.features"].0, 0x0a); +} diff --git a/tools/asahi/test-j613-dcp-power.py b/tools/asahi/test-j613-dcp-power.py new file mode 100644 index 00000000000000..3bc1a3379c253e --- /dev/null +++ b/tools/asahi/test-j613-dcp-power.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Compile the production J613 PMGR predicate and test PMGR fields and unrelated bits.""" +from pathlib import Path +import os +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[2] +source = (root / "drivers/gpu/drm/apple/j613_dcp_activate.c").read_text() +start = source.index("static bool j613_power_state_active(") +end = source.index("\n}\n", start) + 3 +stub = r""" +#include +#include +#include +typedef uint32_t u32; +#define BIT(n) (1U << (n)) +#define GENMASK(a,b) (((1U << ((a)-(b)+1))-1) << (b)) +#define FIELD_GET(mask,v) (((v) & (mask)) >> __builtin_ctz(mask)) +""" +main = r""" +int main(void) { + /* Only ACTUAL[7:4], TARGET[3:0] and AUTO_ENABLE[28] affect readiness. */ + const u32 unrelated_mask = ~(GENMASK(7, 0) | BIT(28)); + u32 unrelated[32] = {0, unrelated_mask, + 0xaaaaaaaaU & unrelated_mask, + 0x55555555U & unrelated_mask}; + unsigned int count = 4; + for (unsigned int bit = 8; bit < 32; bit++) + if (bit != 28) unrelated[count++] = BIT(bit); + for (unsigned int i = 0; i < count; i++) + for (u32 actual = 0; actual < 16; actual++) + for (u32 target = 0; target < 16; target++) + for (u32 automatic = 0; automatic < 2; automatic++) { + u32 word = unrelated[i] | (actual << 4) | target | (automatic << 28); + bool expected = actual == 15 || (target == 15 && automatic); + if (j613_power_state_active(word) != expected) { + fprintf(stderr, "PMGR state 0x%08x: expected active=%d\n", word, expected); + return 1; + } + } + if (!j613_power_state_active(0x1404024f)) return 2; + if (j613_power_state_active(0x0404024f)) return 3; + if (j613_power_state_active(0x14040240)) return 4; + puts("PASS: 13824 PMGR combinations and captured-state regression controls"); + return 0; +} +""" +with tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / "power.c" + path.write_text(stub + source[start:end] + main) + for flags in (["-O0"], ["-O2"]): + exe = str(path.with_suffix("")) + subprocess.run([os.environ.get("CC", "cc"), "-Wall", "-Wextra", "-Werror", + *flags, str(path), "-o", exe], check=True) + subprocess.run([exe], check=True) diff --git a/tools/asahi/test-m3-mailbox.py b/tools/asahi/test-m3-mailbox.py new file mode 100644 index 00000000000000..167b0566b2e5dd --- /dev/null +++ b/tools/asahi/test-m3-mailbox.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise the production T8122 mailbox predicate with captured DTB properties. + +Usage: python3 tools/asahi/test-m3-mailbox.py [runtime.dtb daily.dtb] +Requires dtc's fdtget. No target access or MMIO. +""" +import hashlib +import os +from pathlib import Path +import re +import subprocess +import sys +import tempfile + +ROOT = Path(__file__).resolve().parents[2] +board = (ROOT / 'drivers/gpu/drm/asahi/m3/m3_board.rs').read_text() +soc = (ROOT / 'drivers/gpu/drm/asahi/m3/m3_soc.rs').read_text() +t8122 = soc[soc.index('pub(crate) static T8122:'):] +compatibles = re.search(r'mailbox_compatibles: &(\[.*?\]),', t8122, re.S)[1] +irqs = re.search(r'mailbox_interrupts: &(\[.*?\]),', t8122, re.S)[1] +base = re.search(r'mailbox: (0x[0-9a-fA-F_]+),', t8122)[1] +regfn = re.search(r'fn expected_mailbox_reg\(.*?\n}', board, re.S)[0] +predicate = board[board.index(' if !soc.mailbox_compatibles'):] +predicate = predicate[:predicate.index('\n {')].strip()[3:] + + +def prop(dtb, node, name, kind): + result = subprocess.check_output(['fdtget', '-t', kind, str(dtb), node, name], text=True) + return [int(v, 16) for v in result.split()] + + +def vec(values): + return 'vec![' + ','.join(map(str, values)) + ']' + + +fixtures = [] +for argument in sys.argv[1:]: + dtb = Path(argument) + gpu = '/soc/gpu@292400000' + # Both retained captures use this mailbox path; verify the phandle link, + # not its capture-specific numeric value. + mbox = '/soc/mbox@292408000' + if prop(dtb, gpu, 'mboxes', 'x') != prop(dtb, mbox, 'phandle', 'x'): + raise RuntimeError('GPU mailbox phandle mismatch') + values = [prop(dtb, mbox, name, kind) for name, kind in + [('compatible', 'bx'), ('reg', 'x'), ('interrupt-names', 'bx'), + ('interrupts', 'x'), ('#mbox-cells', 'x')]] + if len(values[4]) != 1: + raise RuntimeError('bad #mbox-cells length') + fixtures.append('check(' + ','.join(vec(v) for v in values[:4]) + ',' + str(values[4][0]) + ');') + print(dtb.name, hashlib.sha256(dtb.read_bytes()).hexdigest()) +if not sys.argv[1:]: + # Public t8122-j613-dcp-25g83.dtsi and t8122-gpu.dtsi resource contract. + names = list(b'send-empty\0send-not-empty\0recv-empty\0recv-not-empty\0') + for compat in (b'apple,t8122-agx-asc-mailbox\0apple,asc-mailbox-v4\0', + b'apple,t8122-asc-mailbox\0apple,asc-mailbox-v4\0'): + values = [list(compat), [2, 0x92408000, 0, 0x4000], names, + [0,723,4,0,724,4,0,725,4,0,726,4]] + fixtures.append('check(' + ','.join(vec(v) for v in values) + ',0);') +if len(fixtures) != 2: + raise SystemExit('supply runtime.dtb and daily.dtb') + +source = ''' +struct Windows { mailbox: u64 } +struct Soc { mailbox_compatibles: &'static [&'static [u8]], mailbox_interrupts: &'static [u32], windows: Windows } +''' + regfn + ''' +fn accepts(soc: &Soc, compat: &Vec, mbox_reg: &Vec, irq_names: &Vec, irqs: &Vec, cells: u32) -> bool { + !(''' + predicate + ''') +} +fn check(compat: Vec, reg: Vec, names: Vec, irqs: Vec, cells: u32) { + let soc = Soc { mailbox_compatibles: &''' + compatibles + ''', mailbox_interrupts: &''' + irqs + ''', windows: Windows { mailbox: ''' + base + ''' } }; + let ok = |c: &Vec, r: &Vec, n: &Vec, i: &Vec, x| accepts(&soc,c,r,n,i,x); + assert!(ok(&compat,®,&names,&irqs,cells)); + for i in 0..reg.len() { let mut v=reg.clone(); v[i]^=1; assert!(!ok(&compat,&v,&names,&irqs,cells)); } + for i in 0..irqs.len() { let mut v=irqs.clone(); v[i]^=1; assert!(!ok(&compat,®,&names,&v,cells)); } + assert!(!ok(&compat,®,&names,&irqs,1)); + let mut v=names.clone(); v[0]^=1; assert!(!ok(&compat,®,&v,&irqs,cells)); + for v in [b"apple,asc-mailbox-v4\\0".to_vec(), b"apple,t8122-agx-asc-mailbox\\0unknown\\0".to_vec(), b"apple,t6031-agx-asc-mailbox\\0".to_vec()] { assert!(!ok(&v,®,&names,&irqs,cells)); } + // Compatible lists are ordered: reversing either supported pair must fail. + for pair in soc.mailbox_compatibles { + let parts: Vec<_> = pair.split(|byte| *byte == 0).filter(|part| !part.is_empty()).collect(); + if parts.len() == 2 { + let mut reversed = parts[1].to_vec(); + reversed.push(0); + reversed.extend_from_slice(parts[0]); + reversed.push(0); + assert!(!ok(&reversed,®,&names,&irqs,cells)); + } + } + for v in soc.mailbox_compatibles { assert!(ok(&v.to_vec(),®,&names,&irqs,cells)); } +} +fn main() { +''' + '\n'.join(fixtures) + '\nprintln!("mailbox: both captures, all exact supported lists and resource/identity mutation controls PASS");\n}\n' +with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) + (path / 'test.rs').write_text(source) + subprocess.run([os.environ.get('RUSTC', 'rustc'), '--edition=2021', str(path / 'test.rs'), '-o', str(path / 'test')], check=True) + subprocess.run([str(path / 'test')], check=True) +print('m3_board.rs', hashlib.sha256(board.encode()).hexdigest()) +print('m3_soc.rs', hashlib.sha256(soc.encode()).hexdigest()) diff --git a/tools/asahi/test-m3-power.py b/tools/asahi/test-m3-power.py new file mode 100644 index 00000000000000..51bea088cd9db5 --- /dev/null +++ b/tools/asahi/test-m3-power.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Run the production controller writer and soft-float code without a GPU. + +Kernel allocation/DT adapters are replaced by host adapters. Public production +power types and PStateTables are extracted as complete balanced Rust items, +never restated by hand. Their input hashes are printed with the test evidence. +""" +from pathlib import Path +import hashlib +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[2] +M3 = ROOT / "drivers/gpu/drm/asahi/m3" + + +def item(source, anchor): + start = source.index(anchor) + opening = source.index("{", start) + depth = 1 + end = opening + 1 + while depth: + if source[end] == "{": + depth += 1 + elif source[end] == "}": + depth -= 1 + end += 1 + return source[start:end] + + +shim = r''' +#![allow(dead_code)] +extern crate self as kernel; +pub type Error = i32; +pub type Result = core::result::Result; +pub const EINVAL: Error = 22; +pub const EOVERFLOW: Error = 75; +pub const ERANGE: Error = 34; +pub const GFP_KERNEL: () = (); +pub unsafe trait Zeroable {} +#[derive(Debug)] +pub struct KVec(Vec); +impl Default for KVec { fn default() -> Self { Self(Vec::new()) } } +impl KVec { + pub fn new() -> Self { Self::default() } + pub fn resize(&mut self, n: usize, value: T, _: ()) -> Result where T: Clone { + self.0.resize(n, value); Ok(()) + } +} +impl core::ops::Deref for KVec { type Target = Vec; fn deref(&self) -> &Vec { &self.0 } } +impl core::ops::DerefMut for KVec { fn deref_mut(&mut self) -> &mut Vec { &mut self.0 } } +pub mod prelude { pub use crate::{Error,Result,EINVAL,EOVERFLOW,ERANGE,GFP_KERNEL,KVec,Zeroable}; } +pub mod of { + pub struct Property<'a>(pub &'a [u8]); + pub trait PropertyUnit: Sized { const UNIT_SIZE: usize; fn from_bytes(data: &[u8]) -> crate::Result; } + impl PropertyUnit for u32 { + const UNIT_SIZE: usize = 4; + fn from_bytes(data: &[u8]) -> crate::Result { + Ok(u32::from_be_bytes(data.try_into().map_err(|_| crate::EINVAL)?)) + } + } + impl TryFrom> for u32 { + type Error = crate::Error; + fn try_from(value: Property<'_>) -> crate::Result { Self::from_bytes(value.0) } + } +} +''' +fixture = r''' +#[cfg(test)] mod controller_tests { + use super::*; + use float::F32; + fn input() -> hw::PwrConfig { + let mut p = hw::PwrConfig::default(); + p.perf_states = KVec((0..9).map(|i| hw::PState { + freq_hz: if i == 8 { 1_338_000_000 } else { i * 100_000_000 }, + volt_mv: KVec(vec![if i == 0 { 125 } else { 450 + i * 50 }]), pwr_mw: i * 1000, + }).collect()); + p.perf_base_pstate = 1; + p.power_sample_period = 8; + p.ppm_filter_time_constant_ms = 16; + p.avg_power_filter_tc_ms = 16; + p.pwr_filter_time_constant = 2; + p.perf_filter_time_constant = 2; + p.perf_filter_time_constant2 = 2; + p.avg_power_target_filter_tc = 2; + p.se_filter_time_constant = 2; + p.se_filter_time_constant_1 = 2; + p.pwr_min_duty_cycle = 30; + p.avg_power_min_duty_cycle = 20; + p + } + fn build(p: &hw::PwrConfig, sram: &[u32]) -> Result { + m3_power_26::PowerImages::new(p, sram, 0xffff_fc21_0340_0000, + 0xffff_fc21_0350_0000, 0xffff_fc2f_fc00_0000, &[0; 53 * 40]) + } + fn word(b: &[u8], at: usize) -> u32 { u32::from_le_bytes(b[at..at + 4].try_into().unwrap()) } + #[test] fn native_controller_bounds_are_distinct_from_outputs_and_hardware_state() { + let image = build(&input(), &[950_001; 9]).unwrap(); + assert_eq!(image.hardware.len(), 0x2658); + assert_eq!(image.performance.len(), 0x6578); + assert_eq!(image.power.len(), 0x670); + assert_eq!(image.globals.len(), 0xe48); + assert_eq!(image.maximum_khz, 1_338_000); + assert_eq!(word(&image.performance, 0x2c), 1); + assert_eq!(word(&image.performance, 0x30), 1); + assert_eq!(word(&image.performance, 0x38), 0); + assert_eq!(word(&image.performance, 0x9f0), 30); + assert_eq!(word(&image.performance, 0xa38), 800); + assert_eq!(word(&image.performance, 0xb78), 100); + assert_eq!(word(&image.performance, 0xb7c), 800); + assert_eq!(word(&image.hardware, 0x1408 + 64), 951); + assert_eq!(word(&image.performance, 0x2268), 0x4248); + assert_eq!(word(&image.performance, 0x3b58), 0x4b48); + } + #[test] fn malformed_tunables_and_nonfinite_gains_are_rejected() { + for period in [0, 1001, u32::MAX] { + let mut p = input(); p.power_sample_period = period; + assert!(build(&p, &[950_000; 9]).is_err()); + } + for bits in [0x7f800000, 0xff800000, 0x7fc00001] { + let mut p = input(); p.pwr_integral_gain = F32::from_bits(bits); + assert!(build(&p, &[950_000; 9]).is_err()); + } + assert!(build(&input(), &[950_000; 8]).is_err()); + assert!(build(&input(), &[100_000; 9]).is_err()); + let mut p = input(); p.avg_power_target_filter_tc = u32::MAX; + assert!(build(&p, &[950_000; 9]).is_err()); + } + #[test] fn dt_order_is_not_the_firmware_voltage_order() { + let p = input(); + let expected = build(&p, &[950_000; 9]).unwrap(); + let mut shuffled = input(); + shuffled.perf_states.swap(2, 6); + let actual = build(&shuffled, &[950_000; 9]).unwrap(); + assert_eq!(actual.hardware.0, expected.hardware.0); + assert_eq!(actual.performance.0, expected.performance.0); + } +} +''' + + +def main(): + hw = (M3 / "hw.rs").read_text() + init = (M3 / "initdata.rs").read_text() + code = shim + # Keep the production soft-float prefix verbatim. Its unrelated existing + # print-only TODO test lacks an import and is not part of this harness. + soft_float = (ROOT / "drivers/gpu/drm/asahi/float.rs").read_text() + code += "mod float {\n" + soft_float.split("// TODO: Make this an actual test", 1)[0] + "\n}\n" + code += "mod hw { use crate::{KVec,float::F32};\n" + for name in ["PState", "PowerZone", "CsAfrPwrConfig", "PwrConfig"]: + code += "#[derive(Default)]\n" + item(hw, "pub(crate) struct " + name + " {") + "\n" + code += item(hw, "impl PState {") + "\n" + code += "}\nmod initdata { use crate::{prelude::*,hw}; const PSTATE_TABLE_ENTRIES: usize = 16;\n" + code += item(init, "pub(crate) struct PStateTables {") + "\n" + code += item(init, "impl PStateTables {") + "\n}\n" + code += "mod m3_mmio_26 { pub const SLOTS:usize=53; pub const RECORD_BYTES:usize=40; }\n" + code += f'#[path = "{M3}/m3_power_26.rs"] mod m3_power_26;\n' + code += fixture + for path in [M3 / "hw.rs", M3 / "initdata.rs", M3 / "m3_power_26.rs", ROOT / "drivers/gpu/drm/asahi/float.rs"]: + print(hashlib.sha256(path.read_bytes()).hexdigest(), path.relative_to(ROOT), flush=True) + with tempfile.TemporaryDirectory(prefix="m3-power-") as directory: + source = Path(directory) / "test.rs" + source.write_text(code) + for flags in [[], ["-O"]]: + binary = Path(directory) / "tests" + subprocess.run(["rustc", "--edition=2021", "--test", *flags, str(source), "-o", str(binary)], check=True) + subprocess.run([str(binary)], check=True) + + +if __name__ == "__main__": + main() diff --git a/tools/asahi/test-m3-uapi.py b/tools/asahi/test-m3-uapi.py new file mode 100755 index 00000000000000..a63249e18869d4 --- /dev/null +++ b/tools/asahi/test-m3-uapi.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Compile public C producers and run the production Rust parser offline.""" +import argparse +import hashlib +import os +from pathlib import Path +import subprocess +import sys +import tempfile + + +def run(command, **kwargs): + subprocess.run(command, check=True, **kwargs) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--mesa", type=Path, help="matched public Mesa source") + args = parser.parse_args() + root = Path(__file__).resolve().parents[2] + if sys.byteorder != "little": + raise SystemExit("C fixtures currently require a little-endian host") + cc = os.environ.get("CC", "cc") + rustc = os.environ.get("RUSTC", "rustc") + fixture_source = root / "tools/asahi/m3-uapi-fixture.c" + with tempfile.TemporaryDirectory(prefix="asahi-uapi-") as temporary: + work = Path(temporary) + headers = work / "headers" + run(["make", "-s", "-C", str(root), "O=" + str(work / "headers-build"), + "ARCH=arm64", "headers_install", "INSTALL_HDR_PATH=" + str(headers)]) + sources = [(headers / "include", "drm/asahi_drm.h")] + if args.mesa: + sources.append((args.mesa.resolve() / "include", "drm-uapi/asahi_drm.h")) + fixtures = [] + layouts = [] + prefixes = [] + for index, (includes, header) in enumerate(sources): + producer = work / ("producer-" + str(index)) + run([cc, "-std=c11", "-Wall", "-Wextra", "-Werror", + "-I" + str(includes), '-DASAHI_HEADER="' + header + '"', + str(fixture_source), "-o", str(producer)]) + prefix = work / ("prefix-" + str(index)) + with prefix.open("wb") as output: + run([str(producer)], stdout=output) + prefixes.append(prefix) + layout = work / ("layout-" + str(index)) + with layout.open("wb") as output: + run([str(producer), "--layout"], stdout=output) + layouts.append(layout) + fixture = work / ("fixture-" + str(index)) + with fixture.open("wb") as output: + run([str(producer), "--full"], stdout=output) + fixtures.append(fixture) + reference = prefixes[0].read_bytes() + if any(prefix.read_bytes() != reference for prefix in prefixes[1:]): + raise SystemExit("kernel/Mesa canonical fixture mismatch") + print("canonical fixture sha256=" + hashlib.sha256(reference).hexdigest(), flush=True) + for optimized in (False, True): + binary = work / ("tests-optimized" if optimized else "tests-debug") + command = [rustc, "--edition=2021", "--test", + str(root / "tools/asahi/m3-uapi-tests.rs"), "-o", str(binary)] + if optimized: + command.append("-O") + run(command) + for index, fixture in enumerate(prefixes + fixtures): + env = dict(os.environ, ASAHI_UAPI_FIXTURE=str(fixture), + ASAHI_UAPI_LAYOUT=str(layouts[index % len(layouts)])) + run([str(binary)], env=env) + + +if __name__ == "__main__": + main() diff --git a/tools/aurora-ci/host-tests.json b/tools/aurora-ci/host-tests.json index a1b93d695dfd6e..1b51602b132cec 100644 --- a/tools/aurora-ci/host-tests.json +++ b/tools/aurora-ci/host-tests.json @@ -1,2319 +1,2373 @@ { - "comment": [ - "Hermetic host test suites, run by host-tests.py from the top of the tree.", - "{output} is a fresh path that does not exist yet; its parent does.", - "expect: pass (exit 0, the default) or fail (non-zero; mutation checks that report a caught mutant that way).", - "known_failure: runs, is reported, and does not fail the job; remove it once the suite passes.", - "commits: history the suite reads; host-tests.py --fetch-commits fetches it into a shallow checkout.", - "Every test-like file under the discovery roots is listed in suites or excluded (tests/test_host_tests.py checks).", - "helpers: files that look like suites but are run by a listed suite.", - "serial: the suite measures time; it runs alone, after the parallel batch.", - "group: the CI job that runs the suite; aurora-sep-slow holds the longest aurora-sep suites so they run beside the rest." - ], - "apt": [ - "bison", - "bubblewrap", - "clang", - "dbus", - "device-tree-compiler", - "flex", - "libarchive-tools", - "libegl-dev", - "libgles-dev", - "libnotify-bin", - "lld", - "llvm", - "make", - "makepkg", - "python3-gi", - "zstd" - ], - "discovery": [ - "tools/asahi", - "tools/asahi-m3", - "tools/aurora-*", - "tools/testing/apple", - "tools/testing/selftests/drivers/apple", - "tools/testing/selftests/drivers/gpu/apple-*", - "tools/testing/selftests/drivers/gpu/asahi-g15" - ], - "suites": [ - { - "name": "tools/asahi/apple-dart-fw-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/apple-dart-fw-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/apple-dart-range-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/apple-dart-range-tests.py" - ], - "known_failure": "its mocks predate the current apple-dart.c (no u32, fw_mirror/fw_handoff, apple_dart_fw_invalidate; apple_dart_hw_sync_locked takes three arguments now)" - }, - { - "name": "tools/asahi/avd-codec-tail-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/avd-codec-tail-tests.py", - "--mutations", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/avd-av1-tail-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/avd-av1-tail-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/avd-av1-tail-tests.py --mutation round-up", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/avd-av1-tail-tests.py", - "--out", - "{output}", - "--mutation", - "round-up" - ], - "expect": "fail", - "note": "inverted: exit status is the controls program status, non-zero when the mutant is caught" - }, - { - "name": "tools/asahi/avd-av1-tail-tests.py --mutation cpu-unaligned", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/avd-av1-tail-tests.py", - "--out", - "{output}", - "--mutation", - "cpu-unaligned" - ], - "expect": "fail", - "note": "inverted: exit status is the controls program status, non-zero when the mutant is caught" - }, - { - "name": "tools/asahi/bcm4377-id-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-id-tests.py", - "--out", - "{output}", - "--old-ref", - "87ddae1397f6f70817cb57df76477a86bdf96424" - ], - "commits": [ - "87ddae1397f6f70817cb57df76477a86bdf96424" - ] - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py --mutation epoch", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}", - "--mutation", - "epoch" - ], - "expect": "pass", - "note": "self-checking: exits 0 when the mutant aborts" - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py --mutation close", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}", - "--mutation", - "close" - ], - "expect": "pass", - "note": "self-checking: exits 0 when the mutant aborts" - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py --mutation producer-fence", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}", - "--mutation", - "producer-fence" - ], - "expect": "pass", - "note": "self-checking: exits 0 when the mutant aborts" - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py --mutation active-fence", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}", - "--mutation", - "active-fence" - ], - "expect": "pass", - "note": "self-checking: exits 0 when the mutant aborts" - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py --mutation board", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}", - "--mutation", - "board" - ], - "expect": "pass", - "note": "self-checking: exits 0 when the mutant aborts" - }, - { - "name": "tools/asahi/bcm4377-resume-tests.py --old-ref 36fab84538bed0f2f823c0e6745e4b0f5e6c2a1e", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/bcm4377-resume-tests.py", - "--out", - "{output}", - "--old-ref", - "36fab84538bed0f2f823c0e6745e4b0f5e6c2a1e" - ], - "expect": "pass", - "note": "self-checking: original driver (87ddae1397f6^) must abort cases 0,1,2,28", - "commits": [ - "36fab84538bed0f2f823c0e6745e4b0f5e6c2a1e" - ] - }, - { - "name": "tools/asahi/brcmf-assoc-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-assoc-tests.py", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/brcmf-assoc-tests.py --mutation short-ie", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-assoc-tests.py", - "--output", - "{output}", - "--mutation", - "short-ie" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-assoc-tests.py --mutation header-length", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-assoc-tests.py", - "--output", - "{output}", - "--mutation", - "header-length" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-assoc-tests.py --mutation skb-capacity", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-assoc-tests.py", - "--output", - "{output}", - "--mutation", - "skb-capacity" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-assoc-tests.py --mutation cmplt-header", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-assoc-tests.py", - "--output", - "{output}", - "--mutation", - "cmplt-header" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-assoc-tests.py --old-association f58ae50192550e6fc67c956f361f525881a1f969", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-assoc-tests.py", - "--output", - "{output}", - "--old-association", - "f58ae50192550e6fc67c956f361f525881a1f969" - ], - "expect": "fail", - "note": "inverted; old getter from ecd1ea4d0f06^", - "commits": [ - "f58ae50192550e6fc67c956f361f525881a1f969" - ] - }, - { - "name": "tools/asahi/brcmf-bss-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-bss-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/brcmfmac-scan-timeout-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmfmac-scan-timeout-tests.py", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/brcmfmac-scan-timeout-tests.py --old-source {output}/old.c", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmfmac-scan-timeout-tests.py", - "--output", - "{output}", - "--old-source", - "{output}/old.c" - ], - "expect": "pass", - "note": "self-checking; cfg80211.c from f58ae5019255^", - "setup": [ - [ - "sh", - "-c", - "mkdir -p \"$1\" && git show 661c0a5740e8f36816f7e79a784c36c026097368:drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c > \"$1/old.c\"", - "sh", - "{output}" - ] - ], - "commits": [ - "661c0a5740e8f36816f7e79a784c36c026097368" - ] - }, - { - "name": "tools/asahi/brcmf-pcie-dma-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-pcie-dma-tests.py", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/brcmf-pcie-dma-tests.py --mutation narrow-stream", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-pcie-dma-tests.py", - "--output", - "{output}", - "--mutation", - "narrow-stream" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-pcie-dma-tests.py --mutation ignore-aperture", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-pcie-dma-tests.py", - "--output", - "{output}", - "--mutation", - "ignore-aperture" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-pcie-dma-tests.py --mutation revision-wide", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-pcie-dma-tests.py", - "--output", - "{output}", - "--mutation", - "revision-wide" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/brcmf-pcie-dma-tests.py --old-dma 654386a164a1e93063f879491f10bebfc1439291", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-pcie-dma-tests.py", - "--output", - "{output}", - "--old-dma", - "654386a164a1e93063f879491f10bebfc1439291" - ], - "expect": "fail", - "note": "inverted; probe from dbdeefb63947^", - "commits": [ - "654386a164a1e93063f879491f10bebfc1439291" - ] - }, - { - "name": "tools/asahi/brcmf-pcie-dma-tests.py --firmware-revision b0de24a8c59735f21023c93592dd9a2eb9e155a4", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-pcie-dma-tests.py", - "--output", - "{output}", - "--firmware-revision", - "b0de24a8c59735f21023c93592dd9a2eb9e155a4" - ], - "expect": "fail", - "note": "inverted; firmware table from 2d17f269503c^", - "commits": [ - "b0de24a8c59735f21023c93592dd9a2eb9e155a4" - ] - }, - { - "name": "tools/asahi/brcmf-roam-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/brcmf-roam-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/compute-preempt-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/compute-preempt-tests.py", - "--out", - "{output}" - ], - "rust": "1.85.0" - }, - { - "name": "tools/asahi/compute-preempt-tests.py --old-ref f885ea0de2cd638bda1e0d3d38a901d927a503f5", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/compute-preempt-tests.py", - "--out", - "{output}", - "--old-ref", - "f885ea0de2cd638bda1e0d3d38a901d927a503f5" - ], - "rust": "1.85.0", - "expect": "pass", - "note": "self-checking; policy from c6f4ea502bdd^", - "commits": [ - "f885ea0de2cd638bda1e0d3d38a901d927a503f5" - ] - }, - { - "name": "tools/asahi/dcp14-backlight-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/dcp14-backlight-tests.py" - ] - }, - { - "name": "tools/asahi/dcp14-backlight-tests.py --old-source {output}/old.c", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/dcp14-backlight-tests.py", - "--old-source", - "{output}/old.c" - ], - "expect": "pass", - "note": "self-checking (-DOLD); iomfb_v14_7.c from 2e5a8e777ada^", - "setup": [ - [ - "sh", - "-c", - "mkdir -p \"$1\" && git show e19eb212f57ec32db9026666c89dd610062e9f24:drivers/gpu/drm/apple/iomfb_v14_7.c > \"$1/old.c\"", - "sh", - "{output}" - ] - ], - "commits": [ - "e19eb212f57ec32db9026666c89dd610062e9f24" - ] - }, - { - "name": "tools/asahi/dcp14-panel-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/dcp14-panel-tests.py" - ] - }, - { - "name": "tools/asahi/dcp14-panel-tests.py --old-source {output}/old.c", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/dcp14-panel-tests.py", - "--old-source", - "{output}/old.c" - ], - "expect": "pass", - "note": "self-checking; iomfb_v14_7.c from 7d5a612eceaa^", - "setup": [ - [ - "sh", - "-c", - "mkdir -p \"$1\" && git show a5713d126ea098fd3b6b833fc4022762540aac6c:drivers/gpu/drm/apple/iomfb_v14_7.c > \"$1/old.c\"", - "sh", - "{output}" - ] - ], - "commits": [ - "a5713d126ea098fd3b6b833fc4022762540aac6c" - ] - }, - { - "name": "tools/asahi/devcoredump-init-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/devcoredump-init-tests.py" - ] - }, - { - "name": "tools/asahi/devcoredump-init-tests.py --old-source {output}/old.c", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/devcoredump-init-tests.py", - "--old-source", - "{output}/old.c" - ], - "expect": "pass", - "note": "self-checking (-DOLD); devcoredump.c from ebd3dc459ac1^", - "setup": [ - [ - "sh", - "-c", - "mkdir -p \"$1\" && git show fb7cca304cb381dc589fadbed479c6f3b36e5e23:drivers/base/devcoredump.c > \"$1/old.c\"", - "sh", - "{output}" - ] - ], - "commits": [ - "fb7cca304cb381dc589fadbed479c6f3b36e5e23" - ] - }, - { - "name": "tools/asahi/display26-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/display26-tests.py" - ] - }, - { - "name": "tools/asahi/dma-heap-fd-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/dma-heap-fd-tests.py", - "--out", - "{output}", - "--old-ref", - "94380ecd5ff9b2a2b9f6e8a0b5c465159ccfaf71" - ], - "commits": [ - "94380ecd5ff9b2a2b9f6e8a0b5c465159ccfaf71" - ] - }, - { - "name": "tools/asahi/mt7932-association-events-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/mt7932-association-events-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/mt7932-association-events-tests.py --old-source {output}/old.c", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/mt7932-association-events-tests.py", - "--out", - "{output}", - "--old-source", - "{output}/old.c" - ], - "expect": "pass", - "note": "self-checking; link.c from c980f7435d50^ (the fix). The test commit itself (f885ea0de2cd) is test-only, so its parent is NOT a valid old source.", - "setup": [ - [ - "sh", - "-c", - "mkdir -p \"$1\" && git show 818bde483e1618783005ac76e1c58057a4bb4cfa:drivers/net/wireless/mediatek/mt7932/link.c > \"$1/old.c\"", - "sh", - "{output}" - ] - ], - "commits": [ - "818bde483e1618783005ac76e1c58057a4bb4cfa" - ] - }, - { - "name": "tools/asahi/mt7932-connect-result-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/mt7932-connect-result-tests.py", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/mt7932-connect-result-tests.py --old-source {output}/old.c", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/mt7932-connect-result-tests.py", - "--output", - "{output}", - "--old-source", - "{output}/old.c" - ], - "expect": "pass", - "note": "self-checking; link.c from 6c3ee18a0725^", - "setup": [ - [ - "sh", - "-c", - "mkdir -p \"$1\" && git show 7db4eadeeba6512726fc527cb624ae99742de607:drivers/net/wireless/mediatek/mt7932/link.c > \"$1/old.c\"", - "sh", - "{output}" - ] - ], - "commits": [ - "7db4eadeeba6512726fc527cb624ae99742de607" - ] - }, - { - "name": "tools/asahi/mt7932-owe-retry-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/mt7932-owe-retry-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/neo-auxiliary-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-auxiliary-tests.py", - "--output", - "{output}" - ], - "rust": "1.85.0" - }, - { - "name": "tools/asahi/neo-auxiliary-tests.py --mutation", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-auxiliary-tests.py", - "--output", - "{output}", - "--mutation" - ], - "rust": "1.85.0", - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/neo-auxiliary-tests.py --old", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-auxiliary-tests.py", - "--output", - "{output}", - "--old" - ], - "rust": "1.85.0", - "expect": "fail", - "note": "inverted; reads 990a45b609dd", - "commits": [ - "990a45b609ddcaa49f5386e1c11530599226c0e1" - ] - }, - { - "name": "tools/asahi/neo-stability-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-stability-tests.py", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/neo-stability-tests.py --mutation sep-syscore", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-stability-tests.py", - "--output", - "{output}", - "--mutation", - "sep-syscore" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/neo-stability-tests.py --mutation wake-irq", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-stability-tests.py", - "--output", - "{output}", - "--mutation", - "wake-irq" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/neo-stability-tests.py --mutation stale-policy", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-stability-tests.py", - "--output", - "{output}", - "--mutation", - "stale-policy" - ], - "expect": "fail", - "note": "inverted" - }, - { - "name": "tools/asahi/neo-stability-tests.py --old-usb", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-stability-tests.py", - "--output", - "{output}", - "--old-usb" - ], - "expect": "fail", - "note": "inverted; reads 990a45b609dd", - "commits": [ - "990a45b609ddcaa49f5386e1c11530599226c0e1" - ] - }, - { - "name": "tools/asahi/neo-stability-tests.py --old-regulatory", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/neo-stability-tests.py", - "--output", - "{output}", - "--old-regulatory" - ], - "expect": "fail", - "note": "inverted; reads 990a45b609dd", - "commits": [ - "990a45b609ddcaa49f5386e1c11530599226c0e1" - ] - }, - { - "name": "tools/asahi/pmp-j613-hwmon-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/pmp-j613-hwmon-tests.py" - ] - }, - { - "name": "tools/asahi/qspi-nvram-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/qspi-nvram-tests.py", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/rtkit-oslog-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/rtkit-oslog-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/rtkit-oslog-tests.py --mutate admission", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/rtkit-oslog-tests.py", - "--out", - "{output}", - "--mutate", - "admission" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutate)" - }, - { - "name": "tools/asahi/rtkit-oslog-tests.py --mutate channel", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/rtkit-oslog-tests.py", - "--out", - "{output}", - "--mutate", - "channel" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutate)" - }, - { - "name": "tools/asahi/rtkit-oslog-tests.py --mutate counter", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/rtkit-oslog-tests.py", - "--out", - "{output}", - "--mutate", - "counter" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutate)" - }, - { - "name": "tools/asahi/rtkit-oslog-tests.py --mutate reset", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/rtkit-oslog-tests.py", - "--out", - "{output}", - "--mutate", - "reset" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutate)" - }, - { - "name": "tools/asahi/rtkit-oslog-tests.py --ref 0cee491386e3dff5a09b60d5703f6a7be71b0883", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/rtkit-oslog-tests.py", - "--out", - "{output}", - "--ref", - "0cee491386e3dff5a09b60d5703f6a7be71b0883" - ], - "expect": "fail", - "note": "inverted; rtkit.c from 1369d18b4ec7^", - "commits": [ - "0cee491386e3dff5a09b60d5703f6a7be71b0883" - ] - }, - { - "name": "tools/asahi/tbnet-refill-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tbnet-refill-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/tbnet-refill-tests.py --mutant refill-count", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tbnet-refill-tests.py", - "--out", - "{output}", - "--mutant", - "refill-count" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutant)" - }, - { - "name": "tools/asahi/tbnet-refill-tests.py --mutant no-retry", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tbnet-refill-tests.py", - "--out", - "{output}", - "--mutant", - "no-retry" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutant)" - }, - { - "name": "tools/asahi/tbnet-refill-tests.py --mutant no-cancel", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tbnet-refill-tests.py", - "--out", - "{output}", - "--mutant", - "no-cancel" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutant)" - }, - { - "name": "tools/asahi/tbnet-refill-tests.py --mutant worker-allocation", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tbnet-refill-tests.py", - "--out", - "{output}", - "--mutant", - "worker-allocation" - ], - "expect": "fail", - "note": "inverted (note: flag is --mutant)" - }, - { - "name": "tools/asahi/tbnet-refill-tests.py --ref f7f5fc8e97cf5238c8669d9fca0d024b40174209", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tbnet-refill-tests.py", - "--out", - "{output}", - "--ref", - "f7f5fc8e97cf5238c8669d9fca0d024b40174209" - ], - "expect": "fail", - "note": "inverted; main.c from 71700a159448^", - "commits": [ - "f7f5fc8e97cf5238c8669d9fca0d024b40174209" - ] - }, - { - "name": "tools/asahi/tunnel-route-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tunnel-route-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/m3-batching-tests.rs", - "group": "tools", - "argv": [ - "{output}", - "--test-threads=1" - ], - "setup": [ - [ - "rustc", - "--edition=2021", - "--test", - "tools/asahi/m3-batching-tests.rs", - "-o", - "{output}" - ] - ], - "rust": "1.85.0" - }, - { - "name": "tools/asahi/m3-runtime-tests.rs", - "group": "tools", - "argv": [ - "{output}", - "--test-threads=1" - ], - "setup": [ - [ - "rustc", - "--edition=2021", - "--test", - "tools/asahi/m3-runtime-tests.rs", - "-o", - "{output}" - ] - ], - "rust": "1.85.0" - }, - { - "name": "tools/asahi/pmp-bootargs-tests.rs", - "group": "tools", - "argv": [ - "{output}", - "--test-threads=1" - ], - "setup": [ - [ - "rustc", - "--edition=2021", - "--test", - "tools/asahi/pmp-bootargs-tests.rs", - "-o", - "{output}" - ] - ], - "rust": "1.85.0" - }, - { - "name": "tools/asahi/pmp-power-tests.rs", - "group": "tools", - "argv": [ - "{output}", - "--test-threads=1" - ], - "setup": [ - [ - "rustc", - "--edition=2021", - "--test", - "tools/asahi/pmp-power-tests.rs", - "-o", - "{output}" - ] - ], - "rust": "1.85.0" - }, - { - "name": "tools/asahi-m3/test_completion_progress.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi-m3/test_completion_progress.py" - ] - }, - { - "name": "tools/asahi-m3/test_fault_snapshot.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi-m3/test_fault_snapshot.py" - ] - }, - { - "name": "tools/asahi-m3/test-retirement-progress.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi-m3/test-retirement-progress.py" - ] - }, - { - "name": "tools/asahi-m3/test-gem-object-size.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi-m3/test-gem-object-size.py" - ], - "rust": "1.85.0" - }, - { - "name": "tools/aurora-sep/tests/test_adt_extract.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_adt_extract.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_air_gpu.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_air_gpu.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_desktop_fixes.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_desktop_fixes.py", - "-v" - ], - "commits": [ - "05d6db3921dec6a6667a7ec8b1b0c75f0f66bafe" - ] - }, - { - "name": "tools/aurora-sep/tests/test_diag_sensor.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_diag_sensor.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_download_resume.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_download_resume.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_esp_history.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_esp_history.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_extract_mesa_calibration.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_extract_mesa_calibration.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_frozen_dependencies.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_frozen_dependencies.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_host_entropy.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_host_entropy.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_host_state_check.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_host_state_check.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m1n1_rollback.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m1n1_rollback.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_air.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_air.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_air_default.py", - "group": "aurora-sep-slow", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_air_default.py", - "-v" - ], - "commits": [ - "4f2a68f8b88a6b407f072356c248d22f076b401a" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_air_mesa.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_air_mesa.py", - "-v" - ], - "commits": [ - "4f2a68f8b88a6b407f072356c248d22f076b401a" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_boot_profile.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_boot_profile.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_cmdline.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_cmdline.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_flow.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_flow.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_gpu_check.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_gpu_check.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_handoff.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_handoff.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_kernel_only.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_kernel_only.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_max.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_max.py", - "-v" - ], - "commits": [ - "2ca6ff6e3480e11dfd51b720763c0fbd99fa015e", - "26cdc069a3dfa86b901ac3c407bad14cc2fb7efc" - ], - "serial": true - }, - { - "name": "tools/aurora-sep/tests/test_m3_persistent.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_persistent.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_pro_mesa.py", - "group": "aurora-sep-slow", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_pro_mesa.py", - "-v" - ], - "commits": [ - "26cdc069a3dfa86b901ac3c407bad14cc2fb7efc", - "df0c433027e9eb283b14a56894efcc817c8a3cfb", - "1d41e1b7ff5db2b31de8fcee56959d70ce152c0a" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_pro_perf.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_pro_perf.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_report.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_report.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3_stack_assembly.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3_stack_assembly.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_m3max_kit.py", - "group": "aurora-sep-slow", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_m3max_kit.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_neo_gpu.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_neo_gpu.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_packaging.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_packaging.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_pm_domains.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_pm_domains.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_ready_pam_hint.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_ready_pam_hint.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_refkey_seal_admission.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_refkey_seal_admission.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_release.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_release.py", - "-v" - ], - "commits": [ - "d14f756c9a65752ca532b93391f775387ba4f191", - "df0c433027e9eb283b14a56894efcc817c8a3cfb", - "1d41e1b7ff5db2b31de8fcee56959d70ce152c0a" - ] - }, - { - "name": "tools/aurora-sep/tests/test_sbio_exchange.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_sbio_exchange.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_sensor_recheck.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_sensor_recheck.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_sensor_wake.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_sensor_wake.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_sep_ready.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_sep_ready.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_store_shipped.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_store_shipped.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_t8112_sep_dma.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_t8112_sep_dma.py", - "-v" - ], - "setup": [ - [ - "make", - "-s", - "O={output}/kbuild", - "ARCH=arm64", - "allnoconfig" - ], - [ - "make", - "-s", - "O={output}/kbuild", - "ARCH=arm64", - "scripts_dtc" - ] - ], - "env": { - "DTC": "{output}/kbuild/scripts/dtc/dtc" - } - }, - { - "name": "tools/aurora-sep/tests/test_t8122_sep.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_t8122_sep.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_unsupported_soc.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_unsupported_soc.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_verify_bringup.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_verify_bringup.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/tests/test_xart_locator.py", - "group": "aurora-sep", - "argv": [ - "python3", - "-m", - "unittest", - "discover", - "-s", - "tools/aurora-sep/tests", - "-p", - "test_xart_locator.py", - "-v" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-udev-rule.sh", - "group": "tools", - "argv": [ - "sh", - "tools/aurora-sep/recipes/mesa-m3/tests/test-udev-rule.sh" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh (sh)", - "group": "tools", - "argv": [ - "sh", - "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh (bash)", - "group": "tools", - "argv": [ - "bash", - "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-user-setup.sh", - "group": "tools", - "argv": [ - "sh", - "tools/aurora-sep/recipes/mesa-m3/tests/test-user-setup.sh" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py", - "group": "tools", - "argv": [ - "python3", - "tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c", - "group": "tools", - "setup": [ - [ - "cc", - "-std=c11", - "-Wall", - "-Wextra", - "-Werror", - "-pedantic", - "-O2", - "tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c", - "-ldl", - "-o", - "{output}" - ] - ], - "argv": [ - "{output}" - ] - }, - { - "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-uwsm.sh", - "group": "tools", - "argv": [ - "sh", - "tools/aurora-sep/recipes/mesa-m3/tests/test-uwsm.sh" - ] - }, - { - "name": "tools/aurora-display-notifier/tests/test_notifier.py", - "group": "tools", - "argv": [ + "comment": [ + "Hermetic host test suites, run by host-tests.py from the top of the tree.", + "{output} is a fresh path that does not exist yet; its parent does.", + "expect: pass (exit 0, the default) or fail (non-zero; mutation checks that report a caught mutant that way).", + "known_failure: runs, is reported, and does not fail the job; remove it once the suite passes.", + "commits: history the suite reads; host-tests.py --fetch-commits fetches it into a shallow checkout.", + "Every test-like file under the discovery roots is listed in suites or excluded (tests/test_host_tests.py checks).", + "helpers: files that look like suites but are run by a listed suite.", + "serial: the suite measures time; it runs alone, after the parallel batch.", + "group: the CI job that runs the suite; aurora-sep-slow holds the longest aurora-sep suites so they run beside the rest." + ], + "apt": [ + "bison", + "bubblewrap", + "clang", + "dbus", + "device-tree-compiler", + "flex", + "libarchive-tools", + "libegl-dev", + "libgles-dev", + "libnotify-bin", + "lld", + "llvm", "make", - "-C", - "tools/aurora-display-notifier/", - "test" - ] - }, - { - "name": "tools/aurora-display-notifier/tests/integration_dbus.py", - "group": "tools", - "argv": [ - "dbus-run-session", - "--", - "env", - "AURORA_TEST_PRIVATE_BUS=1", - "python3", - "tools/aurora-display-notifier/tests/integration_dbus.py" - ] - }, - { - "name": "tools/aurora-tb/pr14-regression.py", - "group": "tools", - "argv": [ - "python3", - "tools/aurora-tb/pr14-regression.py", - "--negative-controls" - ] - }, - { - "name": "tools/aurora-tb/tbnet-lifecycle-test.py", - "group": "tools", - "argv": [ - "python3", - "tools/aurora-tb/tbnet-lifecycle-test.py", - "--source", - ".", - "--output", - "{output}", - "--negative-controls" - ], - "known_failure": "its fixture predates the receive refill retry (1d25feb6bb33): no rx_refill_work member or cancel_delayed_work_sync mock" - }, - { - "name": "tools/aurora-wake/test-s2idle-rtc-regression.sh", - "group": "tools", - "argv": [ - "bash", - "tools/aurora-wake/test-s2idle-rtc-regression.sh" - ] - }, - { - "name": "tools/aurora-userpages/userpages.c", - "group": "tools", - "argv": [ - "cc", - "-O2", - "-g", - "-Wall", - "-Wextra", - "-Werror", - "-Wl,-z,max-page-size=65536", - "tools/aurora-userpages/userpages.c", - "-o", - "{output}" - ] - }, - { - "name": "tools/testing/apple/check-backlight.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/apple/check-backlight.py", - "--output", - "{output}" - ], - "known_failure": "its include path lacks linux/math64.h, which dcp_backlight.h now includes" - }, - { - "name": "tools/testing/apple/check-backlight-retry.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/apple/check-backlight-retry.py", - "--output", - "{output}" - ], - "known_failure": "the extracted code now calls min(), div_u64() and mul_u32_u32(), which the fixture does not mock" - }, - { - "name": "tools/testing/apple/check-lifetime.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/apple/check-lifetime.py", - "--output", - "{output}" - ], - "known_failure": "the apple_dcp_afkep mock lacks lock and stopping, and spin_lock_irqsave is not mocked" - }, - { - "name": "tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh", - "group": "tools", - "argv": [ - "sh", - "tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh" - ], - "known_failure": "the send and edid stages predate afk.c changes (afk_send_command_common, reply_len)" - }, - { - "name": "tools/testing/selftests/drivers/gpu/apple-afkv2/test-admission.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/selftests/drivers/gpu/apple-afkv2/test-admission.py" - ] - }, - { - "name": "tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py", - "--mutations", - "--output", - "{output}" - ] - }, - { - "name": "tools/testing/selftests/drivers/gpu/apple-dcp/run.sh", - "group": "tools", - "argv": [ - "sh", - "tools/testing/selftests/drivers/gpu/apple-dcp/run.sh", - "--output", - "{output}" - ] - }, - { - "name": "tools/testing/selftests/drivers/gpu/test_t8140_external_bw.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/selftests/drivers/gpu/test_t8140_external_bw.py", - "-v" - ] - }, - { - "name": "tools/testing/selftests/drivers/net/mt7932_e2e_test.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/selftests/drivers/net/mt7932_e2e_test.py", - "-v" - ] - }, - { - "name": "tools/asahi/tb-dp-repair-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tb-dp-repair-tests.py", - "--mutations", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/t6030-clock-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/t6030-clock-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/t6030-wiring-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/t6030-wiring-tests.py", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/tiled-split-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/tiled-split-tests.py", - "--mutations", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/t6030-pmp-supplier-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/t6030-pmp-supplier-tests.py", - "--mutations", - "--output", - "{output}" - ] - }, - { - "name": "tools/asahi/atc-usb3-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/atc-usb3-tests.py", - "--mutations", - "--out", - "{output}" - ] - }, - { - "name": "tools/asahi/g15-input-error-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-input-error-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-queue-credit-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-queue-credit-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-probe-init-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-probe-init-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-retention-tests.py", - "group": "tools", - "commits": [ - "31da169251bd34d5810a0874563d9a019b85bf56" - ], - "argv": [ - "python3", - "tools/asahi/g15-retention-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual G15 and KBox ownership bodies with drop-count allocation failures; old-source P1 controls must fail at runtime. Public synthetic inputs only." - }, - { - "name": "tools/asahi/g15-maintained-handoff-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-maintained-handoff-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-zero-words-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-zero-words-tests.py", - "--mutations", - "--evidence", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-pb-growth-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-pb-growth-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-stats-drain-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-stats-drain-tests.py", - "--mutations", - "--output", - "{output}" - ], - "rust": "1.93.1", - "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." - }, - { - "name": "tools/asahi/g15-display-order-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/g15-display-order-tests.py" - ], - "rust": "1.93.1", - "note": "Actual display publication, T6030/T6031 link and admit() bodies on OF/device-link and admission-order models; C and admit() mutants must build and fail. No hardware or private fixtures." - }, - { - "name": "tools/testing/selftests/drivers/gpu/asahi-g15/run.py", - "group": "tools", - "argv": [ - "python3", - "tools/testing/selftests/drivers/gpu/asahi-g15/run.py" - ], - "rust": "1.93.1", - "note": "Public synthetic Rust/C host cases; two unavailable private-hardware fixture cases remain explicitly ignored." - }, - { - "name": "tools/asahi/t6030-pmp-dt-tests.py", - "group": "tools", - "setup": [ - [ - "make", - "-s", - "O={output}/kbuild", - "ARCH=arm64", - "allnoconfig" - ], - [ - "make", - "-s", - "O={output}/kbuild", - "ARCH=arm64", - "scripts_dtc" - ] - ], - "argv": [ - "python3", - "tools/asahi/t6030-pmp-dt-tests.py", - "--dtc", - "{output}/kbuild/scripts/dtc/dtc", - "--output", - "{output}/dtbs" - ], - "note": "Compile J514S/J516S default and static PMP profiles; check supplier nodes and alias in the resulting DTBs." - }, - { - "name": "tools/asahi/t6030-pmp-graph-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/t6030-pmp-graph-tests.py", - "--mutations", - "--output", - "{output}" - ], - "note": "Actual production-body host boundaries and runtime mutants; no firmware or hardware qualification." - }, - { - "name": "tools/asahi/t6030-display-admission-tests.py", - "group": "tools", - "argv": [ - "python3", - "tools/asahi/t6030-display-admission-tests.py", - "--mutations", - "--output", - "{output}" - ], - "note": "Actual production-body host boundaries and runtime mutants; no firmware or hardware qualification." - }, - { - "name": "tools/asahi/t6030-display-linkage-tests.py", - "group": "tools", - "setup": [ - [ - "make", - "-s", - "O={output}-conf", - "allnoconfig" - ] - ], - "argv": [ - "python3", - "tools/asahi/t6030-display-linkage-tests.py", - "--conf", - "{output}-conf/scripts/kconfig/conf", - "--output", - "{output}" - ], - "note": "Build only the host Kconfig tool in an isolated output directory; check all built-in/module dependency combinations." + "makepkg", + "python3-gi", + "zstd" + ], + "discovery": [ + "tools/asahi", + "tools/asahi-m3", + "tools/aurora-*", + "tools/testing/apple", + "tools/testing/selftests/drivers/apple", + "tools/testing/selftests/drivers/gpu/apple-*", + "tools/testing/selftests/drivers/gpu/asahi-g15" + ], + "suites": [ + { + "name": "tools/asahi/apple-dart-fw-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/apple-dart-fw-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/apple-dart-range-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/apple-dart-range-tests.py" + ], + "known_failure": "its mocks predate the current apple-dart.c (no u32, fw_mirror/fw_handoff, apple_dart_fw_invalidate; apple_dart_hw_sync_locked takes three arguments now)" + }, + { + "name": "tools/asahi/avd-codec-tail-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/avd-codec-tail-tests.py", + "--mutations", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/avd-av1-tail-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/avd-av1-tail-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/avd-av1-tail-tests.py --mutation round-up", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/avd-av1-tail-tests.py", + "--out", + "{output}", + "--mutation", + "round-up" + ], + "expect": "fail", + "note": "inverted: exit status is the controls program status, non-zero when the mutant is caught" + }, + { + "name": "tools/asahi/avd-av1-tail-tests.py --mutation cpu-unaligned", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/avd-av1-tail-tests.py", + "--out", + "{output}", + "--mutation", + "cpu-unaligned" + ], + "expect": "fail", + "note": "inverted: exit status is the controls program status, non-zero when the mutant is caught" + }, + { + "name": "tools/asahi/bcm4377-id-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-id-tests.py", + "--out", + "{output}", + "--old-ref", + "87ddae1397f6f70817cb57df76477a86bdf96424" + ], + "commits": [ + "87ddae1397f6f70817cb57df76477a86bdf96424" + ] + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py --mutation epoch", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}", + "--mutation", + "epoch" + ], + "expect": "pass", + "note": "self-checking: exits 0 when the mutant aborts" + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py --mutation close", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}", + "--mutation", + "close" + ], + "expect": "pass", + "note": "self-checking: exits 0 when the mutant aborts" + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py --mutation producer-fence", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}", + "--mutation", + "producer-fence" + ], + "expect": "pass", + "note": "self-checking: exits 0 when the mutant aborts" + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py --mutation active-fence", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}", + "--mutation", + "active-fence" + ], + "expect": "pass", + "note": "self-checking: exits 0 when the mutant aborts" + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py --mutation board", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}", + "--mutation", + "board" + ], + "expect": "pass", + "note": "self-checking: exits 0 when the mutant aborts" + }, + { + "name": "tools/asahi/bcm4377-resume-tests.py --old-ref 36fab84538bed0f2f823c0e6745e4b0f5e6c2a1e", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/bcm4377-resume-tests.py", + "--out", + "{output}", + "--old-ref", + "36fab84538bed0f2f823c0e6745e4b0f5e6c2a1e" + ], + "expect": "pass", + "note": "self-checking: original driver (87ddae1397f6^) must abort cases 0,1,2,28", + "commits": [ + "36fab84538bed0f2f823c0e6745e4b0f5e6c2a1e" + ] + }, + { + "name": "tools/asahi/brcmf-assoc-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-assoc-tests.py", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/brcmf-assoc-tests.py --mutation short-ie", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-assoc-tests.py", + "--output", + "{output}", + "--mutation", + "short-ie" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-assoc-tests.py --mutation header-length", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-assoc-tests.py", + "--output", + "{output}", + "--mutation", + "header-length" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-assoc-tests.py --mutation skb-capacity", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-assoc-tests.py", + "--output", + "{output}", + "--mutation", + "skb-capacity" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-assoc-tests.py --mutation cmplt-header", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-assoc-tests.py", + "--output", + "{output}", + "--mutation", + "cmplt-header" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-assoc-tests.py --old-association f58ae50192550e6fc67c956f361f525881a1f969", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-assoc-tests.py", + "--output", + "{output}", + "--old-association", + "f58ae50192550e6fc67c956f361f525881a1f969" + ], + "expect": "fail", + "note": "inverted; old getter from ecd1ea4d0f06^", + "commits": [ + "f58ae50192550e6fc67c956f361f525881a1f969" + ] + }, + { + "name": "tools/asahi/brcmf-bss-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-bss-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/brcmfmac-scan-timeout-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmfmac-scan-timeout-tests.py", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/brcmfmac-scan-timeout-tests.py --old-source {output}/old.c", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmfmac-scan-timeout-tests.py", + "--output", + "{output}", + "--old-source", + "{output}/old.c" + ], + "expect": "pass", + "note": "self-checking; cfg80211.c from f58ae5019255^", + "setup": [ + [ + "sh", + "-c", + "mkdir -p \"$1\" && git show 661c0a5740e8f36816f7e79a784c36c026097368:drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c > \"$1/old.c\"", + "sh", + "{output}" + ] + ], + "commits": [ + "661c0a5740e8f36816f7e79a784c36c026097368" + ] + }, + { + "name": "tools/asahi/brcmf-pcie-dma-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-pcie-dma-tests.py", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/brcmf-pcie-dma-tests.py --mutation narrow-stream", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-pcie-dma-tests.py", + "--output", + "{output}", + "--mutation", + "narrow-stream" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-pcie-dma-tests.py --mutation ignore-aperture", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-pcie-dma-tests.py", + "--output", + "{output}", + "--mutation", + "ignore-aperture" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-pcie-dma-tests.py --mutation revision-wide", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-pcie-dma-tests.py", + "--output", + "{output}", + "--mutation", + "revision-wide" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/brcmf-pcie-dma-tests.py --old-dma 654386a164a1e93063f879491f10bebfc1439291", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-pcie-dma-tests.py", + "--output", + "{output}", + "--old-dma", + "654386a164a1e93063f879491f10bebfc1439291" + ], + "expect": "fail", + "note": "inverted; probe from dbdeefb63947^", + "commits": [ + "654386a164a1e93063f879491f10bebfc1439291" + ] + }, + { + "name": "tools/asahi/brcmf-pcie-dma-tests.py --firmware-revision b0de24a8c59735f21023c93592dd9a2eb9e155a4", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-pcie-dma-tests.py", + "--output", + "{output}", + "--firmware-revision", + "b0de24a8c59735f21023c93592dd9a2eb9e155a4" + ], + "expect": "fail", + "note": "inverted; firmware table from 2d17f269503c^", + "commits": [ + "b0de24a8c59735f21023c93592dd9a2eb9e155a4" + ] + }, + { + "name": "tools/asahi/brcmf-roam-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/brcmf-roam-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/compute-preempt-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/compute-preempt-tests.py", + "--out", + "{output}" + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/compute-preempt-tests.py --old-ref f885ea0de2cd638bda1e0d3d38a901d927a503f5", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/compute-preempt-tests.py", + "--out", + "{output}", + "--old-ref", + "f885ea0de2cd638bda1e0d3d38a901d927a503f5" + ], + "rust": "1.85.0", + "expect": "pass", + "note": "self-checking; policy from c6f4ea502bdd^", + "commits": [ + "f885ea0de2cd638bda1e0d3d38a901d927a503f5" + ] + }, + { + "name": "tools/asahi/dcp14-backlight-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/dcp14-backlight-tests.py" + ] + }, + { + "name": "tools/asahi/dcp14-backlight-tests.py --old-source {output}/old.c", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/dcp14-backlight-tests.py", + "--old-source", + "{output}/old.c" + ], + "expect": "pass", + "note": "self-checking (-DOLD); iomfb_v14_7.c from 2e5a8e777ada^", + "setup": [ + [ + "sh", + "-c", + "mkdir -p \"$1\" && git show e19eb212f57ec32db9026666c89dd610062e9f24:drivers/gpu/drm/apple/iomfb_v14_7.c > \"$1/old.c\"", + "sh", + "{output}" + ] + ], + "commits": [ + "e19eb212f57ec32db9026666c89dd610062e9f24" + ] + }, + { + "name": "tools/asahi/dcp14-panel-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/dcp14-panel-tests.py" + ] + }, + { + "name": "tools/asahi/dcp14-panel-tests.py --old-source {output}/old.c", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/dcp14-panel-tests.py", + "--old-source", + "{output}/old.c" + ], + "expect": "pass", + "note": "self-checking; iomfb_v14_7.c from 7d5a612eceaa^", + "setup": [ + [ + "sh", + "-c", + "mkdir -p \"$1\" && git show a5713d126ea098fd3b6b833fc4022762540aac6c:drivers/gpu/drm/apple/iomfb_v14_7.c > \"$1/old.c\"", + "sh", + "{output}" + ] + ], + "commits": [ + "a5713d126ea098fd3b6b833fc4022762540aac6c" + ] + }, + { + "name": "tools/asahi/devcoredump-init-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/devcoredump-init-tests.py" + ] + }, + { + "name": "tools/asahi/devcoredump-init-tests.py --old-source {output}/old.c", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/devcoredump-init-tests.py", + "--old-source", + "{output}/old.c" + ], + "expect": "pass", + "note": "self-checking (-DOLD); devcoredump.c from ebd3dc459ac1^", + "setup": [ + [ + "sh", + "-c", + "mkdir -p \"$1\" && git show fb7cca304cb381dc589fadbed479c6f3b36e5e23:drivers/base/devcoredump.c > \"$1/old.c\"", + "sh", + "{output}" + ] + ], + "commits": [ + "fb7cca304cb381dc589fadbed479c6f3b36e5e23" + ] + }, + { + "name": "tools/asahi/display26-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/display26-tests.py" + ] + }, + { + "name": "tools/asahi/dma-heap-fd-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/dma-heap-fd-tests.py", + "--out", + "{output}", + "--old-ref", + "94380ecd5ff9b2a2b9f6e8a0b5c465159ccfaf71" + ], + "commits": [ + "94380ecd5ff9b2a2b9f6e8a0b5c465159ccfaf71" + ] + }, + { + "name": "tools/asahi/mt7932-association-events-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/mt7932-association-events-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/mt7932-association-events-tests.py --old-source {output}/old.c", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/mt7932-association-events-tests.py", + "--out", + "{output}", + "--old-source", + "{output}/old.c" + ], + "expect": "pass", + "note": "self-checking; link.c from c980f7435d50^ (the fix). The test commit itself (f885ea0de2cd) is test-only, so its parent is NOT a valid old source.", + "setup": [ + [ + "sh", + "-c", + "mkdir -p \"$1\" && git show 818bde483e1618783005ac76e1c58057a4bb4cfa:drivers/net/wireless/mediatek/mt7932/link.c > \"$1/old.c\"", + "sh", + "{output}" + ] + ], + "commits": [ + "818bde483e1618783005ac76e1c58057a4bb4cfa" + ] + }, + { + "name": "tools/asahi/mt7932-connect-result-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/mt7932-connect-result-tests.py", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/mt7932-connect-result-tests.py --old-source {output}/old.c", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/mt7932-connect-result-tests.py", + "--output", + "{output}", + "--old-source", + "{output}/old.c" + ], + "expect": "pass", + "note": "self-checking; link.c from 6c3ee18a0725^", + "setup": [ + [ + "sh", + "-c", + "mkdir -p \"$1\" && git show 7db4eadeeba6512726fc527cb624ae99742de607:drivers/net/wireless/mediatek/mt7932/link.c > \"$1/old.c\"", + "sh", + "{output}" + ] + ], + "commits": [ + "7db4eadeeba6512726fc527cb624ae99742de607" + ] + }, + { + "name": "tools/asahi/mt7932-owe-retry-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/mt7932-owe-retry-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/neo-auxiliary-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-auxiliary-tests.py", + "--output", + "{output}" + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/neo-auxiliary-tests.py --mutation", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-auxiliary-tests.py", + "--output", + "{output}", + "--mutation" + ], + "rust": "1.85.0", + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/neo-auxiliary-tests.py --old", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-auxiliary-tests.py", + "--output", + "{output}", + "--old" + ], + "rust": "1.85.0", + "expect": "fail", + "note": "inverted; reads 990a45b609dd", + "commits": [ + "990a45b609ddcaa49f5386e1c11530599226c0e1" + ] + }, + { + "name": "tools/asahi/neo-stability-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-stability-tests.py", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/neo-stability-tests.py --mutation sep-syscore", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-stability-tests.py", + "--output", + "{output}", + "--mutation", + "sep-syscore" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/neo-stability-tests.py --mutation wake-irq", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-stability-tests.py", + "--output", + "{output}", + "--mutation", + "wake-irq" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/neo-stability-tests.py --mutation stale-policy", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-stability-tests.py", + "--output", + "{output}", + "--mutation", + "stale-policy" + ], + "expect": "fail", + "note": "inverted" + }, + { + "name": "tools/asahi/neo-stability-tests.py --old-usb", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-stability-tests.py", + "--output", + "{output}", + "--old-usb" + ], + "expect": "fail", + "note": "inverted; reads 990a45b609dd", + "commits": [ + "990a45b609ddcaa49f5386e1c11530599226c0e1" + ] + }, + { + "name": "tools/asahi/neo-stability-tests.py --old-regulatory", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/neo-stability-tests.py", + "--output", + "{output}", + "--old-regulatory" + ], + "expect": "fail", + "note": "inverted; reads 990a45b609dd", + "commits": [ + "990a45b609ddcaa49f5386e1c11530599226c0e1" + ] + }, + { + "name": "tools/asahi/pmp-j613-hwmon-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/pmp-j613-hwmon-tests.py" + ] + }, + { + "name": "tools/asahi/qspi-nvram-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/qspi-nvram-tests.py", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/rtkit-oslog-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/rtkit-oslog-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/rtkit-oslog-tests.py --mutate admission", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/rtkit-oslog-tests.py", + "--out", + "{output}", + "--mutate", + "admission" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutate)" + }, + { + "name": "tools/asahi/rtkit-oslog-tests.py --mutate channel", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/rtkit-oslog-tests.py", + "--out", + "{output}", + "--mutate", + "channel" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutate)" + }, + { + "name": "tools/asahi/rtkit-oslog-tests.py --mutate counter", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/rtkit-oslog-tests.py", + "--out", + "{output}", + "--mutate", + "counter" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutate)" + }, + { + "name": "tools/asahi/rtkit-oslog-tests.py --mutate reset", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/rtkit-oslog-tests.py", + "--out", + "{output}", + "--mutate", + "reset" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutate)" + }, + { + "name": "tools/asahi/rtkit-oslog-tests.py --ref 0cee491386e3dff5a09b60d5703f6a7be71b0883", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/rtkit-oslog-tests.py", + "--out", + "{output}", + "--ref", + "0cee491386e3dff5a09b60d5703f6a7be71b0883" + ], + "expect": "fail", + "note": "inverted; rtkit.c from 1369d18b4ec7^", + "commits": [ + "0cee491386e3dff5a09b60d5703f6a7be71b0883" + ] + }, + { + "name": "tools/asahi/tbnet-refill-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tbnet-refill-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/tbnet-refill-tests.py --mutant refill-count", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tbnet-refill-tests.py", + "--out", + "{output}", + "--mutant", + "refill-count" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutant)" + }, + { + "name": "tools/asahi/tbnet-refill-tests.py --mutant no-retry", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tbnet-refill-tests.py", + "--out", + "{output}", + "--mutant", + "no-retry" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutant)" + }, + { + "name": "tools/asahi/tbnet-refill-tests.py --mutant no-cancel", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tbnet-refill-tests.py", + "--out", + "{output}", + "--mutant", + "no-cancel" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutant)" + }, + { + "name": "tools/asahi/tbnet-refill-tests.py --mutant worker-allocation", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tbnet-refill-tests.py", + "--out", + "{output}", + "--mutant", + "worker-allocation" + ], + "expect": "fail", + "note": "inverted (note: flag is --mutant)" + }, + { + "name": "tools/asahi/tbnet-refill-tests.py --ref f7f5fc8e97cf5238c8669d9fca0d024b40174209", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tbnet-refill-tests.py", + "--out", + "{output}", + "--ref", + "f7f5fc8e97cf5238c8669d9fca0d024b40174209" + ], + "expect": "fail", + "note": "inverted; main.c from 71700a159448^", + "commits": [ + "f7f5fc8e97cf5238c8669d9fca0d024b40174209" + ] + }, + { + "name": "tools/asahi/tunnel-route-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tunnel-route-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/m3-batching-tests.rs", + "group": "tools", + "argv": [ + "{output}", + "--test-threads=1" + ], + "setup": [ + [ + "rustc", + "--edition=2021", + "--test", + "tools/asahi/m3-batching-tests.rs", + "-o", + "{output}" + ] + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/m3-runtime-tests.rs", + "group": "tools", + "argv": [ + "{output}", + "--test-threads=1" + ], + "setup": [ + [ + "rustc", + "--edition=2021", + "--test", + "tools/asahi/m3-runtime-tests.rs", + "-o", + "{output}" + ] + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/pmp-bootargs-tests.rs", + "group": "tools", + "argv": [ + "{output}", + "--test-threads=1" + ], + "setup": [ + [ + "rustc", + "--edition=2021", + "--test", + "tools/asahi/pmp-bootargs-tests.rs", + "-o", + "{output}" + ] + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/pmp-power-tests.rs", + "group": "tools", + "argv": [ + "{output}", + "--test-threads=1" + ], + "setup": [ + [ + "rustc", + "--edition=2021", + "--test", + "tools/asahi/pmp-power-tests.rs", + "-o", + "{output}" + ] + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi-m3/test_completion_progress.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi-m3/test_completion_progress.py" + ] + }, + { + "name": "tools/asahi-m3/test_fault_snapshot.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi-m3/test_fault_snapshot.py" + ] + }, + { + "name": "tools/asahi-m3/test-retirement-progress.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi-m3/test-retirement-progress.py" + ] + }, + { + "name": "tools/asahi-m3/test-gem-object-size.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi-m3/test-gem-object-size.py" + ], + "rust": "1.85.0" + }, + { + "name": "tools/aurora-sep/tests/test_adt_extract.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_adt_extract.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_air_gpu.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_air_gpu.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_desktop_fixes.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_desktop_fixes.py", + "-v" + ], + "commits": [ + "05d6db3921dec6a6667a7ec8b1b0c75f0f66bafe" + ] + }, + { + "name": "tools/aurora-sep/tests/test_diag_sensor.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_diag_sensor.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_download_resume.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_download_resume.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_esp_history.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_esp_history.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_extract_mesa_calibration.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_extract_mesa_calibration.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_frozen_dependencies.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_frozen_dependencies.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_host_entropy.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_host_entropy.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_host_state_check.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_host_state_check.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m1n1_rollback.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m1n1_rollback.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_air.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_air.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_air_default.py", + "group": "aurora-sep-slow", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_air_default.py", + "-v" + ], + "commits": [ + "4f2a68f8b88a6b407f072356c248d22f076b401a" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_air_mesa.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_air_mesa.py", + "-v" + ], + "commits": [ + "4f2a68f8b88a6b407f072356c248d22f076b401a" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_boot_profile.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_boot_profile.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_cmdline.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_cmdline.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_flow.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_flow.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_gpu_check.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_gpu_check.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_handoff.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_handoff.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_kernel_only.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_kernel_only.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_max.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_max.py", + "-v" + ], + "commits": [ + "2ca6ff6e3480e11dfd51b720763c0fbd99fa015e", + "26cdc069a3dfa86b901ac3c407bad14cc2fb7efc" + ], + "serial": true + }, + { + "name": "tools/aurora-sep/tests/test_m3_persistent.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_persistent.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_pro_mesa.py", + "group": "aurora-sep-slow", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_pro_mesa.py", + "-v" + ], + "commits": [ + "26cdc069a3dfa86b901ac3c407bad14cc2fb7efc", + "df0c433027e9eb283b14a56894efcc817c8a3cfb", + "1d41e1b7ff5db2b31de8fcee56959d70ce152c0a" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_pro_perf.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_pro_perf.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_report.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_report.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3_stack_assembly.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3_stack_assembly.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_m3max_kit.py", + "group": "aurora-sep-slow", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_m3max_kit.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_neo_gpu.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_neo_gpu.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_packaging.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_packaging.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_pm_domains.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_pm_domains.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_ready_pam_hint.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_ready_pam_hint.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_refkey_seal_admission.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_refkey_seal_admission.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_release.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_release.py", + "-v" + ], + "commits": [ + "d14f756c9a65752ca532b93391f775387ba4f191", + "df0c433027e9eb283b14a56894efcc817c8a3cfb", + "1d41e1b7ff5db2b31de8fcee56959d70ce152c0a" + ] + }, + { + "name": "tools/aurora-sep/tests/test_sbio_exchange.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_sbio_exchange.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_sensor_recheck.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_sensor_recheck.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_sensor_wake.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_sensor_wake.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_sep_ready.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_sep_ready.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_store_shipped.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_store_shipped.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_t8112_sep_dma.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_t8112_sep_dma.py", + "-v" + ], + "setup": [ + [ + "make", + "-s", + "O={output}/kbuild", + "ARCH=arm64", + "allnoconfig" + ], + [ + "make", + "-s", + "O={output}/kbuild", + "ARCH=arm64", + "scripts_dtc" + ] + ], + "env": { + "DTC": "{output}/kbuild/scripts/dtc/dtc" + } + }, + { + "name": "tools/aurora-sep/tests/test_t8122_sep.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_t8122_sep.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_unsupported_soc.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_unsupported_soc.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_verify_bringup.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_verify_bringup.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/tests/test_xart_locator.py", + "group": "aurora-sep", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tools/aurora-sep/tests", + "-p", + "test_xart_locator.py", + "-v" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-udev-rule.sh", + "group": "tools", + "argv": [ + "sh", + "tools/aurora-sep/recipes/mesa-m3/tests/test-udev-rule.sh" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh (sh)", + "group": "tools", + "argv": [ + "sh", + "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh (bash)", + "group": "tools", + "argv": [ + "bash", + "tools/aurora-sep/recipes/mesa-m3/tests/test-session-env.sh" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-user-setup.sh", + "group": "tools", + "argv": [ + "sh", + "tools/aurora-sep/recipes/mesa-m3/tests/test-user-setup.sh" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py", + "group": "tools", + "argv": [ + "python3", + "tools/aurora-sep/recipes/mesa-m3/tests/test-system-provider.py" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c", + "group": "tools", + "setup": [ + [ + "cc", + "-std=c11", + "-Wall", + "-Wextra", + "-Werror", + "-pedantic", + "-O2", + "tools/aurora-sep/recipes/mesa-m3/tests/test-system-board.c", + "-ldl", + "-o", + "{output}" + ] + ], + "argv": [ + "{output}" + ] + }, + { + "name": "tools/aurora-sep/recipes/mesa-m3/tests/test-uwsm.sh", + "group": "tools", + "argv": [ + "sh", + "tools/aurora-sep/recipes/mesa-m3/tests/test-uwsm.sh" + ] + }, + { + "name": "tools/aurora-display-notifier/tests/test_notifier.py", + "group": "tools", + "argv": [ + "make", + "-C", + "tools/aurora-display-notifier/", + "test" + ] + }, + { + "name": "tools/aurora-display-notifier/tests/integration_dbus.py", + "group": "tools", + "argv": [ + "dbus-run-session", + "--", + "env", + "AURORA_TEST_PRIVATE_BUS=1", + "python3", + "tools/aurora-display-notifier/tests/integration_dbus.py" + ] + }, + { + "name": "tools/aurora-tb/pr14-regression.py", + "group": "tools", + "argv": [ + "python3", + "tools/aurora-tb/pr14-regression.py", + "--negative-controls" + ] + }, + { + "name": "tools/aurora-tb/tbnet-lifecycle-test.py", + "group": "tools", + "argv": [ + "python3", + "tools/aurora-tb/tbnet-lifecycle-test.py", + "--source", + ".", + "--output", + "{output}", + "--negative-controls" + ], + "known_failure": "its fixture predates the receive refill retry (1d25feb6bb33): no rx_refill_work member or cancel_delayed_work_sync mock" + }, + { + "name": "tools/aurora-wake/test-s2idle-rtc-regression.sh", + "group": "tools", + "argv": [ + "bash", + "tools/aurora-wake/test-s2idle-rtc-regression.sh" + ] + }, + { + "name": "tools/aurora-userpages/userpages.c", + "group": "tools", + "argv": [ + "cc", + "-O2", + "-g", + "-Wall", + "-Wextra", + "-Werror", + "-Wl,-z,max-page-size=65536", + "tools/aurora-userpages/userpages.c", + "-o", + "{output}" + ] + }, + { + "name": "tools/testing/apple/check-backlight.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/apple/check-backlight.py", + "--output", + "{output}" + ], + "known_failure": "its include path lacks linux/math64.h, which dcp_backlight.h now includes" + }, + { + "name": "tools/testing/apple/check-backlight-retry.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/apple/check-backlight-retry.py", + "--output", + "{output}" + ], + "known_failure": "the extracted code now calls min(), div_u64() and mul_u32_u32(), which the fixture does not mock" + }, + { + "name": "tools/testing/apple/check-lifetime.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/apple/check-lifetime.py", + "--output", + "{output}" + ], + "known_failure": "the apple_dcp_afkep mock lacks lock and stopping, and spin_lock_irqsave is not mocked" + }, + { + "name": "tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh", + "group": "tools", + "argv": [ + "sh", + "tools/testing/selftests/drivers/gpu/apple-afkv2/run-afkv2-tests.sh" + ], + "known_failure": "the send and edid stages predate afk.c changes (afk_send_command_common, reply_len)" + }, + { + "name": "tools/testing/selftests/drivers/gpu/apple-afkv2/test-admission.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/selftests/drivers/gpu/apple-afkv2/test-admission.py" + ] + }, + { + "name": "tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py", + "--mutations", + "--output", + "{output}" + ] + }, + { + "name": "tools/testing/selftests/drivers/gpu/apple-dcp/run.sh", + "group": "tools", + "argv": [ + "sh", + "tools/testing/selftests/drivers/gpu/apple-dcp/run.sh", + "--output", + "{output}" + ] + }, + { + "name": "tools/testing/selftests/drivers/gpu/test_t8140_external_bw.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/selftests/drivers/gpu/test_t8140_external_bw.py", + "-v" + ] + }, + { + "name": "tools/testing/selftests/drivers/net/mt7932_e2e_test.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/selftests/drivers/net/mt7932_e2e_test.py", + "-v" + ] + }, + { + "name": "tools/asahi/tb-dp-repair-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tb-dp-repair-tests.py", + "--mutations", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/t6030-clock-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/t6030-clock-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/t6030-wiring-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/t6030-wiring-tests.py", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/tiled-split-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/tiled-split-tests.py", + "--mutations", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/t6030-pmp-supplier-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/t6030-pmp-supplier-tests.py", + "--mutations", + "--output", + "{output}" + ] + }, + { + "name": "tools/asahi/atc-usb3-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/atc-usb3-tests.py", + "--mutations", + "--out", + "{output}" + ] + }, + { + "name": "tools/asahi/g15-input-error-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-input-error-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-queue-credit-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-queue-credit-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-probe-init-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-probe-init-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-retention-tests.py", + "group": "tools", + "commits": [ + "31da169251bd34d5810a0874563d9a019b85bf56" + ], + "argv": [ + "python3", + "tools/asahi/g15-retention-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual G15 and KBox ownership bodies with drop-count allocation failures; old-source P1 controls must fail at runtime. Public synthetic inputs only." + }, + { + "name": "tools/asahi/g15-maintained-handoff-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-maintained-handoff-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-zero-words-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-zero-words-tests.py", + "--mutations", + "--evidence", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-pb-growth-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-pb-growth-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-stats-drain-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-stats-drain-tests.py", + "--mutations", + "--output", + "{output}" + ], + "rust": "1.93.1", + "note": "Actual production-body host control plus runtime mutants; kernel services modeled, no private hardware fixtures." + }, + { + "name": "tools/asahi/g15-display-order-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/g15-display-order-tests.py" + ], + "rust": "1.93.1", + "note": "Actual display publication, T6030/T6031 link and admit() bodies on OF/device-link and admission-order models; C and admit() mutants must build and fail. No hardware or private fixtures." + }, + { + "name": "tools/testing/selftests/drivers/gpu/asahi-g15/run.py", + "group": "tools", + "argv": [ + "python3", + "tools/testing/selftests/drivers/gpu/asahi-g15/run.py" + ], + "rust": "1.93.1", + "note": "Public synthetic Rust/C host cases; two unavailable private-hardware fixture cases remain explicitly ignored." + }, + { + "name": "tools/asahi/t6030-pmp-dt-tests.py", + "group": "tools", + "setup": [ + [ + "make", + "-s", + "O={output}/kbuild", + "ARCH=arm64", + "allnoconfig" + ], + [ + "make", + "-s", + "O={output}/kbuild", + "ARCH=arm64", + "scripts_dtc" + ] + ], + "argv": [ + "python3", + "tools/asahi/t6030-pmp-dt-tests.py", + "--dtc", + "{output}/kbuild/scripts/dtc/dtc", + "--output", + "{output}/dtbs" + ], + "note": "Compile J514S/J516S default and static PMP profiles; check supplier nodes and alias in the resulting DTBs." + }, + { + "name": "tools/asahi/t6030-pmp-graph-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/t6030-pmp-graph-tests.py", + "--mutations", + "--output", + "{output}" + ], + "note": "Actual production-body host boundaries and runtime mutants; no firmware or hardware qualification." + }, + { + "name": "tools/asahi/t6030-display-admission-tests.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/t6030-display-admission-tests.py", + "--mutations", + "--output", + "{output}" + ], + "note": "Actual production-body host boundaries and runtime mutants; no firmware or hardware qualification." + }, + { + "name": "tools/asahi/t6030-display-linkage-tests.py", + "group": "tools", + "setup": [ + [ + "make", + "-s", + "O={output}-conf", + "allnoconfig" + ] + ], + "argv": [ + "python3", + "tools/asahi/t6030-display-linkage-tests.py", + "--conf", + "{output}-conf/scripts/kconfig/conf", + "--output", + "{output}" + ], + "note": "Build only the host Kconfig tool in an isolated output directory; check all built-in/module dependency combinations." + }, + { + "name": "tools/asahi/test-j613-dcp-power.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/test-j613-dcp-power.py" + ] + }, + { + "name": "tools/asahi/test-m3-mailbox.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/test-m3-mailbox.py" + ] + }, + { + "name": "tools/asahi/m3-firmware-tests.rs", + "group": "tools", + "argv": [ + "{output}", + "--test-threads=1" + ], + "setup": [ + [ + "rustc", + "--edition=2021", + "--test", + "tools/asahi/m3-firmware-tests.rs", + "-o", + "{output}" + ] + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/test-m3-uapi.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/test-m3-uapi.py" + ], + "rust": "1.85.0" + }, + { + "name": "tools/asahi/test-m3-power.py", + "group": "tools", + "argv": [ + "python3", + "tools/asahi/test-m3-power.py" + ], + "rust": "1.85.0" + } + ], + "helpers": { + "tools/testing/selftests/drivers/gpu/apple-dcp/run.py": "the implementation behind tools/testing/selftests/drivers/gpu/apple-dcp/run.sh, which is listed", + "tools/testing/selftests/drivers/gpu/asahi-g15/uat-owner.c": "compiled and executed by the registered asahi-g15/run.py actual reservation-owner fixture", + "tools/testing/selftests/drivers/gpu/asahi-g15/pmp-bridge.c": "compiled and executed by the registered asahi-g15/run.py maintained PMP fixture", + "tools/asahi/t6030-pmp-graph-tests.c": "compiled and executed by the corresponding registered Python actual-body host suite", + "tools/asahi/t6030-display-admission-tests.c": "compiled and executed by the corresponding registered Python actual-body host suite", + "tools/asahi/m3-uapi-tests.rs": "compiled and executed by the registered tools/asahi/test-m3-uapi.py paired-header suite" + }, + "excluded": { + "tools/aurora-sep/tests/coordinator/runtime-check.sh": "Needs /dev/sep-bio, a running kernel booted with trusted.source=applesep, the hw_random sysfs interface and fprintd. Coordinator payload for an approved device.", + "tools/aurora-sep/tests/coordinator/deletion-check.sh": "Needs /dev/sep-bio, fprintd over the system bus and a previously enrolled expendable test account; spans a reboot.", + "tools/aurora-sep/tests/coordinator/sep-live-count.py": "Opens /dev/sep-bio on a running kernel. Not picked up by `unittest discover` (no test_ prefix) - keep it that way.", + "tools/aurora-wake/s2idle-rtc-regression.sh": "Real suspend-to-idle cycles with rtcwake on an Apple machine, as root. Its argument checks run hermetically via test-s2idle-rtc-regression.sh (in suites.json).", + "tools/aurora-wake/attended-bt-wake.sh": "J700 with a paired Bluetooth keyboard, a person pressing keys, root.", + "tools/aurora-wake/bt-wake-cycle.sh": "One s2idle cycle on a J700 with Bluetooth wake, root.", + "tools/aurora-wake/bt-wake-power.sh": "Alternating RTC-timed sleeps on a J700, root.", + "tools/aurora-wake/bt-wake-emulated.sh": "Drives two other machines over SSH (target and a BlueZ host).", + "tools/aurora-wake/hog-keyboard.py": "BLE HID keyboard emulator for a second BlueZ host (system bus + Bluetooth adapter).", + "tools/aurora-tb/tb-pcie-report": "Reads live Thunderbolt/PCIe state and the kernel log with a dock attached (may need sudo).", + "tools/aurora-m3/m3-serial.py": "macOS helper (sudo, macvdmtool) recording a second Mac's serial console over a USB-C cable.", + "tools/testing/selftests/drivers/apple/j813-peripherals.sh": "Physical J813 check (exits 4 unless /proc/device-tree/compatible has apple,j813 and euid 0).", + "tools/testing/selftests/drivers/net/mt7932_e2e.py": "End-to-end Wi-Fi on a J700 via nmcli and the real network. Its helper unit tests (mt7932_e2e_test.py) are in suites.json.", + "tools/testing/selftests/arm64/user4k/": "kselftest programs for the user4k granule API: need an arm64 Aurora kernel booted with user4k=on (and an aarch64 toolchain to build). check-any-quarter.py / runtime-app.py consume output of those programs.", + "tools/aurora-sep/recipes/mesa-m3/tests/test-probe.sh": "Needs a built mesa-m3-probe (C, links Mesa/EGL/Wayland) and a headless weston for the wayland case.", + "tools/aurora-sep/recipes/mesa-m3/tests/test-confirm.sh": "Needs a built mesa-m3-wlcheck (Wayland client) passed as its argument.", + "tools/aurora-sep/recipes/mesa-m3/tests/build-checks.sh": "Runs inside the PKGBUILD package() step against a freshly built Mesa tree (makepkg, aarch64).", + "tools/aurora-bindgen/build.py": "Downloads the pinned bindgen 0.73.2 source archive (codeload.github.com) and crates.io dependencies, then cargo build --locked -j2 (89 s here incl. its own test.py run; passed). Viable as an optional cached job; GitHub runners have network.", + "tools/aurora-bindgen/test.py": "Hermetic once a patched bindgen exists (--bindgen PATH, 2 s, passed). --baseline-bindgen must be an unpatched 0.73.2; the system 0.73.1 fails the tag-only equality check by design.", + "tools/asahi-m3/completion_progress.py, fault_snapshot.py, retirement_progress.py": "Libraries that read /sys/kernel/debug/asahi-m3/* or /dev/kmsg on an M3 machine. Their offline tests are in suites.json.", + "tools/aurora-sep/air-gpu/*.sh, tools/aurora-sep/pro/*, tools/aurora-sep/neo/*": "On-device helpers; exercised through fixtures by tools/aurora-sep/tests (test_air_gpu, test_m3_pro_perf, test_neo_gpu).", + "drivers/**/*-test.c, *_test.c, tools/testing/kunit/configs/{apple_firmware,arm64_user4k*}.config": "KUnit suites: need a kernel build and tools/testing/kunit/kunit.py (arm64 under QEMU). Not host scripts.", + "tools/aurora-sep/tests: env-gated cases": "Skipped unless set: AURORA_ADT_FIXTURE (real ADT dump), AIR_GPU_TEST_VULKAN=1 (real Vulkan job on a render node), AURORA_PRO_MESA_PKG / AURORA_M1N1_PKG (release packages); test_m3max_kit real-package cases need /nonexistent/... packages. The rest of each module runs." } - ], - "helpers": { - "tools/testing/selftests/drivers/gpu/apple-dcp/run.py": "the implementation behind tools/testing/selftests/drivers/gpu/apple-dcp/run.sh, which is listed", - "tools/testing/selftests/drivers/gpu/asahi-g15/uat-owner.c": "compiled and executed by the registered asahi-g15/run.py actual reservation-owner fixture", - "tools/testing/selftests/drivers/gpu/asahi-g15/pmp-bridge.c": "compiled and executed by the registered asahi-g15/run.py maintained PMP fixture", - "tools/asahi/t6030-pmp-graph-tests.c": "compiled and executed by the corresponding registered Python actual-body host suite", - "tools/asahi/t6030-display-admission-tests.c": "compiled and executed by the corresponding registered Python actual-body host suite" - }, - "excluded": { - "tools/aurora-sep/tests/coordinator/runtime-check.sh": "Needs /dev/sep-bio, a running kernel booted with trusted.source=applesep, the hw_random sysfs interface and fprintd. Coordinator payload for an approved device.", - "tools/aurora-sep/tests/coordinator/deletion-check.sh": "Needs /dev/sep-bio, fprintd over the system bus and a previously enrolled expendable test account; spans a reboot.", - "tools/aurora-sep/tests/coordinator/sep-live-count.py": "Opens /dev/sep-bio on a running kernel. Not picked up by `unittest discover` (no test_ prefix) - keep it that way.", - "tools/aurora-wake/s2idle-rtc-regression.sh": "Real suspend-to-idle cycles with rtcwake on an Apple machine, as root. Its argument checks run hermetically via test-s2idle-rtc-regression.sh (in suites.json).", - "tools/aurora-wake/attended-bt-wake.sh": "J700 with a paired Bluetooth keyboard, a person pressing keys, root.", - "tools/aurora-wake/bt-wake-cycle.sh": "One s2idle cycle on a J700 with Bluetooth wake, root.", - "tools/aurora-wake/bt-wake-power.sh": "Alternating RTC-timed sleeps on a J700, root.", - "tools/aurora-wake/bt-wake-emulated.sh": "Drives two other machines over SSH (target and a BlueZ host).", - "tools/aurora-wake/hog-keyboard.py": "BLE HID keyboard emulator for a second BlueZ host (system bus + Bluetooth adapter).", - "tools/aurora-tb/tb-pcie-report": "Reads live Thunderbolt/PCIe state and the kernel log with a dock attached (may need sudo).", - "tools/aurora-m3/m3-serial.py": "macOS helper (sudo, macvdmtool) recording a second Mac's serial console over a USB-C cable.", - "tools/testing/selftests/drivers/apple/j813-peripherals.sh": "Physical J813 check (exits 4 unless /proc/device-tree/compatible has apple,j813 and euid 0).", - "tools/testing/selftests/drivers/net/mt7932_e2e.py": "End-to-end Wi-Fi on a J700 via nmcli and the real network. Its helper unit tests (mt7932_e2e_test.py) are in suites.json.", - "tools/testing/selftests/arm64/user4k/": "kselftest programs for the user4k granule API: need an arm64 Aurora kernel booted with user4k=on (and an aarch64 toolchain to build). check-any-quarter.py / runtime-app.py consume output of those programs.", - "tools/aurora-sep/recipes/mesa-m3/tests/test-probe.sh": "Needs a built mesa-m3-probe (C, links Mesa/EGL/Wayland) and a headless weston for the wayland case.", - "tools/aurora-sep/recipes/mesa-m3/tests/test-confirm.sh": "Needs a built mesa-m3-wlcheck (Wayland client) passed as its argument.", - "tools/aurora-sep/recipes/mesa-m3/tests/build-checks.sh": "Runs inside the PKGBUILD package() step against a freshly built Mesa tree (makepkg, aarch64).", - "tools/aurora-bindgen/build.py": "Downloads the pinned bindgen 0.73.2 source archive (codeload.github.com) and crates.io dependencies, then cargo build --locked -j2 (89 s here incl. its own test.py run; passed). Viable as an optional cached job; GitHub runners have network.", - "tools/aurora-bindgen/test.py": "Hermetic once a patched bindgen exists (--bindgen PATH, 2 s, passed). --baseline-bindgen must be an unpatched 0.73.2; the system 0.73.1 fails the tag-only equality check by design.", - "tools/asahi-m3/completion_progress.py, fault_snapshot.py, retirement_progress.py": "Libraries that read /sys/kernel/debug/asahi-m3/* or /dev/kmsg on an M3 machine. Their offline tests are in suites.json.", - "tools/aurora-sep/air-gpu/*.sh, tools/aurora-sep/pro/*, tools/aurora-sep/neo/*": "On-device helpers; exercised through fixtures by tools/aurora-sep/tests (test_air_gpu, test_m3_pro_perf, test_neo_gpu).", - "drivers/**/*-test.c, *_test.c, tools/testing/kunit/configs/{apple_firmware,arm64_user4k*}.config": "KUnit suites: need a kernel build and tools/testing/kunit/kunit.py (arm64 under QEMU). Not host scripts.", - "tools/aurora-sep/tests: env-gated cases": "Skipped unless set: AURORA_ADT_FIXTURE (real ADT dump), AIR_GPU_TEST_VULKAN=1 (real Vulkan job on a render node), AURORA_PRO_MESA_PKG / AURORA_M1N1_PKG (release packages); test_m3max_kit real-package cases need /nonexistent/... packages. The rest of each module runs." - } }