Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ad691cd
drm/asahi: publish bound G17 compute work without the admission passes
DjDeveloperr Oct 23, 2026
1121ae3
drm/asahi: wake G17 render publication when its pair is returned
DjDeveloperr Oct 23, 2026
abd8593
drm/asahi: clear each G17 compute descriptor only once
DjDeveloperr Oct 23, 2026
3ef3720
drm/asahi: retain compute pool backing through pending channel admission
DjDeveloperr Oct 23, 2026
f35d7ea
dma-buf: stop polling an array after its first pending member
DjDeveloperr Oct 23, 2026
319805f
drm/asahi: avoid unbound compute inline attempts on compute-only queues
DjDeveloperr Oct 23, 2026
5a4822c
drm/asahi: preserve low-pressure compute inline probes
DjDeveloperr Oct 23, 2026
026065c
drm/asahi: submit same-VM compute input waits through firmware
DjDeveloperr Oct 24, 2026
89c8873
drm/asahi: treat a firmware source as complete once its retirement is…
DjDeveloperr Oct 24, 2026
3ff4f4b
drm/asahi: publish G17 commands from the ioctl at any queue depth
DjDeveloperr Oct 24, 2026
a0dfc3c
drm/asahi: map G17 compute descriptors cached for the firmware
DjDeveloperr Oct 24, 2026
a36e352
drm/asahi: size the G17 compute QID budget by open logical queues
DjDeveloperr Oct 24, 2026
b141ca9
drm/asahi: order same-VM render attachment overlaps in firmware
DjDeveloperr Oct 24, 2026
a475f4f
drm/asahi: map G17 compute command records cached for the firmware
DjDeveloperr Oct 24, 2026
ca71e44
drm/asahi: stop the G17 compute completion scan once every record is …
DjDeveloperr Oct 24, 2026
1cdac9b
drm/asahi: publish G17 compute into a bound queue without the device …
DjDeveloperr Oct 24, 2026
7242bf8
drm/asahi: send a G17 compute queue configuration only when it changes
DjDeveloperr Oct 24, 2026
014605a
drm/asahi: add G17 resource wait domains and use them for queue creation
DjDeveloperr Oct 24, 2026
64f717d
drm/asahi: wait for G17 compute capacity on the release that frees it
DjDeveloperr Oct 24, 2026
327e50f
drm/asahi: wait for a G17 compute source only through its completion …
DjDeveloperr Oct 24, 2026
6f4d3bb
drm/asahi: add G17 command lifecycle tracepoints
DjDeveloperr Oct 24, 2026
e24e3ba
drm/asahi: ring the G17 compute pipe doorbell for every publication
DjDeveloperr Oct 24, 2026
94c66ec
drm/asahi: map the G17 compute queue graph cached for the firmware
DjDeveloperr Oct 24, 2026
db365ec
drm/asahi: wait for G17 render capacity on the release that frees it
DjDeveloperr Oct 24, 2026
06bc290
drm/asahi: give the G17 render scheduler the pair's depth as credits
DjDeveloperr Oct 24, 2026
3d1f204
drm/asahi: keep a G17 render free list registered while the firmware …
DjDeveloperr Oct 24, 2026
b7fa0d4
drm/asahi: take the G17 device mutex less often to publish a render
DjDeveloperr Oct 24, 2026
d272a70
drm/asahi: publish a G17 render into its pair without the device mutex
DjDeveloperr Oct 24, 2026
6428f7f
drm/asahi: lend a G17 render pair without the identity check first
DjDeveloperr Oct 24, 2026
8cf02f5
drm/asahi: wait for G17 render admission before run_job
DjDeveloperr Oct 24, 2026
550fb35
drm/asahi: trace the G17 core power state at each work doorbell
DjDeveloperr Oct 24, 2026
f381ac9
drm/asahi: trace G17 host-to-firmware messages
DjDeveloperr Oct 24, 2026
adcd56e
drm/asahi: maintain cumulative completion scopes without repeated fen…
DjDeveloperr Oct 24, 2026
bcdfdaf
drm/asahi: test G17 queue budgets with firmware-reserved identities
DjDeveloperr Oct 24, 2026
971d431
drm/asahi: size cached compute records for every native wait
DjDeveloperr Oct 24, 2026
9bf3039
drm/asahi: index pending compute completion identities before reading…
DjDeveloperr Oct 24, 2026
2644618
drm/asahi: retain a deep working set of validated GPU page ranges
DjDeveloperr Oct 24, 2026
b6248ac
drm/asahi: avoid locking pending private completion fences
DjDeveloperr Oct 24, 2026
b08c817
drm/asahi: give G17 firmware restarts time to drain long work
DjDeveloperr Oct 25, 2026
f1db16a
drm/asahi: fail commands a GPU restart interrupted instead of complet…
DjDeveloperr Oct 25, 2026
30f8556
drm/asahi: stop the G17 device on a watchdog storm only without progress
DjDeveloperr Oct 25, 2026
2c0abe2
drm/asahi: scan only the G17 compute completion records written since…
DjDeveloperr Oct 25, 2026
39bdd5b
drm/asahi: release pruned G17 frontier entries after the ioctl publishes
DjDeveloperr Oct 25, 2026
b41ecb5
drm/asahi: publish both G17 render QoS owners in one pass
DjDeveloperr Oct 25, 2026
b86c445
drm/asahi: stop copying G17 fragment registers to the unused client page
DjDeveloperr Oct 25, 2026
5a55068
drm/asahi: read the G17 render work rings once per direct publication
DjDeveloperr Oct 25, 2026
b67018e
drm/asahi: request the G17 tiling descriptor slot before preparing a …
DjDeveloperr Oct 25, 2026
4d0362d
drm/asahi: store G17 register arrays without an intermediate copy
DjDeveloperr Oct 25, 2026
861b39d
drm/asahi: also request the G17 queue configuration records early
DjDeveloperr Oct 25, 2026
66ef051
drm/asahi: scan only render pool slots for G17 free-list leases
DjDeveloperr Oct 25, 2026
1aec5be
drm/asahi: omit G17 render queue configurations that only reload the …
DjDeveloperr Oct 25, 2026
d0df244
drm/asahi: keep one CPU mapping per G17 timestamp buffer
DjDeveloperr Oct 25, 2026
3ff8c49
drm/asahi: withhold G17 compute doorbells behind two unretired commands
DjDeveloperr Oct 25, 2026
b1f5dc6
drm/asahi: drop G17 compute doorbells that another doorbell announced
DjDeveloperr Oct 25, 2026
d38cce2
drm/asahi: announce withheld G17 compute doorbells whenever retiremen…
DjDeveloperr Oct 25, 2026
2d82861
drm/asahi: build the G17 driver without warnings
DjDeveloperr Oct 25, 2026
9a0ed80
drm/asahi: renew G17 job deadlines while the GPU keeps retiring work
DjDeveloperr Oct 25, 2026
afbfbc0
drm/asahi: kill closed G17 contexts only while no other work runs
DjDeveloperr Oct 25, 2026
31279ab
drm/asahi: fail G17 render groups a recovery dropped out of order at …
DjDeveloperr Oct 25, 2026
7012a59
drm/asahi: announce withheld G17 compute doorbells after a recovery
DjDeveloperr Oct 25, 2026
8e257af
drm/asahi: move the G17 cumulative-signals feature to bit 29
DjDeveloperr Oct 25, 2026
76d4e88
drm/asahi: build the G17 compute QID budget test against the variant …
DjDeveloperr Oct 26, 2026
d0da7e7
drm/asahi: check the G17 QID floor test against the open-queue comput…
DjDeveloperr Oct 26, 2026
18b408b
drm/asahi: check the G17 admission churn test against its deadline, n…
DjDeveloperr Oct 26, 2026
de1be1b
Merge integration/aurora into fix/g17-recovery-dropped-groups-20261025
DjDeveloperr Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions drivers/dma-buf/dma-fence-array.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,11 @@ static bool dma_fence_array_signaled(struct dma_fence *fence)
unsigned int i;

/*
* We need to read num_pending before checking the enable_signal bit
* to avoid racing with the enable_signaling() implementation, which
* might decrement the counter, and cause a partial check.
* atomic_read_acquire() pairs with atomic_dec_and_test() in
* dma_fence_array_enable_signaling()
*
* The !--num_pending check is here to account for the any_signaled case
* if we race with enable_signaling(), that means the !num_pending check
* in the is_signalling_enabled branch might be outdated (num_pending
* might have been decremented), but that's fine. The user will get the
* right value when testing again later.
* Read the counter before the enable bit so callback installation
* cannot make a partial count look complete. The acquire pairs with
* the counter decrements in dma_fence_array_enable_signaling().
* If enabling races with the lazy scan, every child must still be
* observed complete before that scan can return true.
*/
num_pending = atomic_read_acquire(&array->num_pending);
if (test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &array->base.flags)) {
Expand All @@ -126,8 +120,16 @@ static bool dma_fence_array_signaled(struct dma_fence *fence)
return false;
}

/*
* Arrays require every member to complete. A pending member already
* proves the array incomplete; polling its successors only repeats work
* during timeline-chain walks. Keep the completion counter and the
* enabled-signaling path unchanged.
*/
for (i = 0; i < array->num_fences; ++i) {
if (dma_fence_is_signaled(array->fences[i]) && !--num_pending)
if (!dma_fence_is_signaled(array->fences[i]))
return false;
if (!--num_pending)
goto signal;
}
return false;
Expand Down Expand Up @@ -256,8 +258,7 @@ EXPORT_SYMBOL(dma_fence_array_init);
* and fill it with the fences it wants to add to the object. Ownership of this
* array is taken and dma_fence_put() is used on each fence on release.
*
* If @signal_on_any is true the fence array signals if any fence in the array
* signals, otherwise it signals when all fences in the array signal.
* The array signals when every fence in the array signals.
*/
struct dma_fence_array *dma_fence_array_create(int num_fences,
struct dma_fence **fences,
Expand Down
75 changes: 75 additions & 0 deletions drivers/dma-buf/st-dma-fence-unwrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,26 @@
struct mock_fence {
struct dma_fence base;
spinlock_t lock;
atomic_t polls;
};

static const char *mock_name(struct dma_fence *f)
{
return "mock";
}

static bool mock_signaled(struct dma_fence *fence)
{
struct mock_fence *f = container_of(fence, struct mock_fence, base);

atomic_inc(&f->polls);
return false;
}

static const struct dma_fence_ops mock_ops = {
.get_driver_name = mock_name,
.get_timeline_name = mock_name,
.signaled = mock_signaled,
};

static struct dma_fence *__mock_fence(u64 context, u64 seqno)
Expand All @@ -36,6 +46,7 @@ static struct dma_fence *__mock_fence(u64 context, u64 seqno)
return NULL;

spin_lock_init(&f->lock);
atomic_set(&f->polls, 0);
dma_fence_init(&f->base, &mock_ops, &f->lock, context, seqno);

return &f->base;
Expand Down Expand Up @@ -584,7 +595,71 @@ static void test_unwrap_merge_complex_seqno(struct kunit *test)
dma_fence_put(f1);
}

/*
* A pending all-of array needs no polls of later members. Keep its
* independent member fences so their polling and out-of-order completion
* remain observable without enabling callbacks on the array.
*/
static void test_array_pending_prefix(struct kunit *test)
{
struct dma_fence *first = mock_fence(), *last = mock_fence();
struct dma_fence *array;
struct mock_fence *last_mock;

if (!first || !last) {
KUNIT_FAIL(test, "Could not allocate member fences");
goto out;
}
last_mock = container_of(last, struct mock_fence, base);
array = mock_array(2, dma_fence_get(first), dma_fence_get(last));
if (!array) {
KUNIT_FAIL(test, "Could not allocate fence array");
goto out;
}
KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array));
KUNIT_EXPECT_EQ(test, atomic_read(&last_mock->polls), 0);
dma_fence_signal(last);
KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array));
dma_fence_signal(first);
KUNIT_EXPECT_TRUE(test, dma_fence_is_signaled(array));
dma_fence_put(array);
out:
dma_fence_put(last);
dma_fence_put(first);
}

static void test_array_pending_suffix(struct kunit *test)
{
struct dma_fence *first = mock_fence(), *last = mock_fence();
struct dma_fence *array;

if (!first || !last) {
KUNIT_FAIL(test, "Could not allocate member fences");
goto out;
}
array = mock_array(2, dma_fence_get(first), dma_fence_get(last));
if (!array) {
KUNIT_FAIL(test, "Could not allocate fence array");
goto out;
}
dma_fence_signal(first);
KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array));
/* Installing callbacks after a lazy partial poll must still account
* for the already signaled first member and wait for the last.
*/
dma_fence_enable_signaling(array);
KUNIT_EXPECT_FALSE(test, dma_fence_is_signaled(array));
dma_fence_signal(last);
KUNIT_EXPECT_GT(test, dma_fence_wait_timeout(array, false, HZ), 0L);
dma_fence_put(array);
out:
dma_fence_put(last);
dma_fence_put(first);
}

static struct kunit_case dma_fence_unwrap_cases[] = {
KUNIT_CASE(test_array_pending_prefix),
KUNIT_CASE(test_array_pending_suffix),
KUNIT_CASE(test_sanitycheck),
KUNIT_CASE(test_unwrap_array),
KUNIT_CASE(test_unwrap_chain),
Expand Down
3 changes: 2 additions & 1 deletion drivers/gpu/drm/asahi/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: GPL-2.0

obj-$(CONFIG_DRM_ASAHI) += asahi.o
asahi-y := asahi_core.o fwid.o sysfs.o
asahi-y := asahi_core.o fwid.o sysfs.o g17/sync_input.o g17/sync_output.o g17/sync_scope.o g17/trace.o
CFLAGS_g17/trace.o := -I$(src)/g17

asahi-$(CONFIG_DRM_ASAHI_G15) += g15/glue.o

Expand Down
45 changes: 37 additions & 8 deletions drivers/gpu/drm/asahi/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,18 @@ pub(crate) struct SyncItem {
pub(crate) fence: Option<dma_fence::Fence>,
pub(crate) chain_fence: Option<dma_fence::FenceChain>,
pub(crate) timeline_value: u64,
pub(crate) cumulative: Option<crate::g17::sync_output::SignalBundle>,
pub(crate) timeline: bool,
}

impl SyncItem {
fn parse_one(file: &DrmFile, data: uapi::drm_asahi_sync, out: bool) -> Result<SyncItem> {
fn parse_one(file: &DrmFile, mut data: uapi::drm_asahi_sync, out: bool,
bundled: bool) -> Result<SyncItem>
{
if data.sync_type == crate::g17::sync_output::SYNC_CUMULATIVE {
if !out || !bundled || data.timeline_value == 0 { return Err(EINVAL); }
data.sync_type = uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ;
}
match data.sync_type {
uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_SYNCOBJ => {
if data.timeline_value != 0 {
Expand All @@ -151,6 +159,8 @@ impl SyncItem {
syncobj,
chain_fence: None,
timeline_value: data.timeline_value,
cumulative: None,
timeline: false,
})
}
uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ => {
Expand All @@ -173,12 +183,10 @@ impl SyncItem {
Ok(SyncItem {
fence,
syncobj,
chain_fence: if out {
Some(dma_fence::FenceChain::new()?)
} else {
None
},
chain_fence: None,
timeline_value: data.timeline_value,
cumulative: None,
timeline: true,
})
}
_ => {
Expand All @@ -193,12 +201,14 @@ impl SyncItem {
ptr: u64,
in_count: u32,
out_count: u32,
allow_cumulative: bool,
) -> Result<KVec<SyncItem>> {
let count = in_count.checked_add(out_count).ok_or(EOVERFLOW)?;

const STRIDE: usize = core::mem::size_of::<uapi::drm_asahi_sync>();
let size = STRIDE.checked_mul(count as usize).ok_or(EOVERFLOW)?;
let mut vec = KVec::with_capacity(count as usize, GFP_KERNEL)?;
let mut anchor = None;

// SAFETY: We only read this once, so there are no TOCTOU issues.
let mut reader = UserSlice::new(UserPtr::from_addr(ptr as _), size).reader();
Expand All @@ -214,7 +224,25 @@ impl SyncItem {
// SAFETY: All bit patterns in the struct are valid
let sync = unsafe { sync.assume_init() };

vec.push(SyncItem::parse_one(file, sync, i >= in_count)?, GFP_KERNEL)?;
if sync.sync_type == crate::g17::sync_output::SYNC_CUMULATIVE {
if !allow_cumulative || i < in_count || anchor.is_some() {
return Err(EINVAL);
}
anchor = Some(i as usize);
}
vec.push(Self::parse_one(file, sync, i >= in_count,
allow_cumulative)?, GFP_KERNEL)?;
}
if let Some(anchor) = anchor {
let bundle = crate::g17::sync_output::SignalBundle::new(
&vec[in_count as usize..], anchor - in_count as usize)?;
vec[anchor].cumulative = Some(bundle);
} else {
for sync in &mut vec[in_count as usize..] {
if sync.timeline {
sync.chain_fence = Some(dma_fence::FenceChain::new()?);
}
}
}

Ok(vec)
Expand Down Expand Up @@ -1398,7 +1426,8 @@ impl File {
copy_commands(&mut vec)?;
}
let syncs =
SyncItem::parse_array(file, data.syncs, data.in_sync_count, data.out_sync_count)?;
SyncItem::parse_array(file, data.syncs, data.in_sync_count, data.out_sync_count,
gpu.as_any().is::<crate::g17::Gpu>())?;

mod_dev_dbg!(
device,
Expand Down
111 changes: 111 additions & 0 deletions drivers/gpu/drm/asahi/g17/availability.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

//! Resource-specific scheduler waits. A caller samples a domain before a failed try, then
//! registers against that sample after rolling back unpublished claims; the release that frees
//! the resource bumps the generation and signals exactly the waiters of its own domain.
//! Successful acquisition never allocates a fence, and a release without waiters costs one
//! lock round trip.

use super::fence::{self, CompletionFence};
use kernel::{
c_str,
dma_fence::{Fence, FenceContexts, RawDmaFence, UserFence},
new_mutex,
prelude::*,
sync::{Arc, Mutex},
};

struct Entry {
fence: UserFence<CompletionFence>,
next: Option<KBox<Entry>>,
}

struct State {
generation: u64,
waiters: Option<KBox<Entry>>,
}

#[pin_data]
pub(crate) struct Availability {
#[pin]
state: Mutex<State>,
fences: FenceContexts,
}

impl Availability {
pub(crate) fn new() -> Result<Arc<Self>> {
Arc::pin_init(
try_pin_init!(Self {
state <- new_mutex!(
State { generation: 0, waiters: None },
"g17::Availability"
),
fences: FenceContexts::new(1, c_str!("asahi_resource"), kernel::static_lock_class!())?,
}),
GFP_KERNEL,
)
}

/// Sample before checking capacity under the resource's own synchronization.
pub(crate) fn observe(&self) -> u64 {
self.state.lock().generation
}

pub(crate) fn has_waiters(&self) -> bool {
self.state.lock().waiters.is_some()
}

/// A fence the next notification signals. None means capacity changed after the sample:
/// retry instead of waiting. Called with no resource lock held. The recheck after allocation
/// closes both the failed-check/registration and the fence-allocation/release races. A
/// waiter that goes away leaves its entry for the next notification to signal.
pub(crate) fn wait(&self, observed: u64) -> Result<Option<Fence>> {
if self.observe() != observed {
return Ok(None);
}
let fence = fence::independent(&self.fences)?;
let mut entry = KBox::new(
Entry { fence: fence.clone(), next: None },
GFP_KERNEL,
)?;
let mut state = self.state.lock();
if state.generation != observed {
return Ok(None);
}
entry.next = state.waiters.take();
state.waiters = Some(entry);
Ok(Some(Fence::from_fence(&fence)))
}

/// Blocks until a notification after `observed`, for at most `timeout_ms`. For callers
/// outside the scheduler that own no other wait.
pub(crate) fn block(&self, observed: u64, timeout_ms: u32, interruptible: bool) -> Result {
let Some(fence) = self.wait(observed)? else { return Ok(()); };
// SAFETY: The owned fence keeps the raw DMA fence alive throughout the wait.
let waited = unsafe {
kernel::bindings::dma_fence_wait_timeout(
fence.raw(),
interruptible,
kernel::time::msecs_to_jiffies(timeout_ms) as isize,
)
};
if waited < 0 {
return Err(Error::from_errno(waited as i32));
}
Ok(())
}

/// Wakes every waiter of this domain. Call after dropping the resource's own lock; fence
/// callbacks run outside the domain lock.
pub(crate) fn notify(&self) {
let mut waiters = {
let mut state = self.state.lock();
state.generation = state.generation.wrapping_add(1);
state.waiters.take()
};
while let Some(mut entry) = waiters {
waiters = entry.next.take();
entry.fence.signal();
}
}
}
5 changes: 5 additions & 0 deletions drivers/gpu/drm/asahi/g17/capacity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ impl kernel::debugfs::Writer for View {
let state = self.0.state.lock();
let Some(firmware) = state.as_deref() else { return writeln!(f, "online=0"); };
writeln!(f, "online=1")?;
let (converted, published) = super::sync_input::counters();
writeln!(f, "fw_inputs converted={} published={} native={} pending={}",
converted, published, super::sync_input::native_count(),
super::sync_input::pending_count())?;
writeln!(f, "compute_admission_demand={}", self.0.compute_demand.load(Ordering::Acquire))?;
firmware.queues.write_capacity(f)?;
firmware.pool.write_capacity(f)?;
writeln!(f, "render_pool_ids used={} total=127", firmware.render_ids.used())?;
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/g17/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ impl Rings {
record.opcode().asserts_gpu_power(),
next,
)?;
super::trace::message(super::trace::CONTROL, record.opcode() as u64);
Ok(next)
}
}
Expand Down
Loading