Repository navigation
CI only: erikwb GPU PRs #1610 + #1611 + #1752 composed on integration/aurora (do not merge) - #1834
Closed
DjDeveloperr wants to merge 11 commits into
Closed
DjDeveloperr wants to merge 11 commits into
DjDeveloperr wants to merge 11 commits into
Conversation
On G17, reclaim may release the backing of VM-private objects of idle VMs, which the VM's next job then waits to get back. A client that would rather not pay that for some of its objects (a server holding model weights between requests, Vulkan's memory priority 1.0) had no way to say which. DRM_IOCTL_ASAHI_GEM_RESIDENCY sets or clears a per-object keep flag, reported by DRM_ASAHI_FEATURE_GEM_RESIDENCY (bit 35) on G17. The flag is a priority among the objects that reclaim may release, and no more: - Which reclaim releases GPU memory does not change. The VM shrinkers are not memcg-aware, so only reclaim on behalf of the whole system calls them (kswapd, direct reclaim, a request to the root memory cgroup, drop_caches). Reclaim inside a memory cgroup below the root releases no GPU memory, kept or not. - Which VMs it takes from does not change: an idle one, with no job, binding change or other use in progress or in the last two seconds, and past the backoff that follows a restoration. The memory of a VM in use stays, kept or not. - Within such a VM, kept objects are released after the others, and only when those left the scan target unmet. - Contents advised DONTNEED are purged as before, kept or not. The request is therefore a hint. Userspace cannot rely on a kept object being resident, and a client that stays active, or whose memory cgroup is the one running out of memory, keeps its GPU memory whether it asks or not. A user VM now has two shrinkers. The existing one purges advised contents and releases the idle objects that are not kept, as before. The new one counts the kept objects and has a batch no scan target can reach, so the shrinker core calls its scan only when the target reaches everything it counted: at the last two of the thirteen reclaim priorities, or the last three once scans deferred by earlier passes have built up. Its scan releases what the first shrinker's does and then, only if that did not reach the target, the kept objects. It is registered after the first, so a reclaim pass that reaches it has asked the VM's first shrinker before. Reclaim comes to those priorities when the earlier ones did not free what it was asked for: under memory pressure that nothing else relieves, for a request that names more than the system has to spare, and at once with drop_caches. Kept objects are not a second pass of the first shrinker's scan because the shrinker core hides the reclaim priority from a scan: counted there, they would be released at any priority whenever a VM had nothing else to give, and left uncounted, a VM with only kept objects would never be scanned at all. The flag is changed under the object's reservation, which idle reclaim holds for the VM it walks, so a request takes effect for every reclaim pass that starts after the ioctl returns; backing already released comes back with the next job, as before. Shared, imported and user-memory objects are never released and accept the flag without effect on them. Kept objects count against the memory limits as before. Their sizes are also totalled for the device (user_kept in the gpu_memory sysfs file) until the object is freed. Tested: KUnit in QEMU (arm64, 16K pages), 546 tests, 544 passed, 2 skipped (unrelated configuration); the new asahi_memacct test kept_bytes_follow_the_object checks that the kept total changes only on a change of state, is left as it is by a transfer to another client, is cleared by a free and ignores kernel objects and imports. KUnit does not reach the shrinkers: what reclaim does with kept objects is covered by the selftest that follows, which passes on the J716c (M5 Max MacBook Pro), the case that runs every shrinker at the last priority included. There, with a Vulkan driver that maps memory priority 1.0 to this ioctl, the memory priority and pageable allocation cases of the Vulkan CTS give 430 passed of 430. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
A kselftest harness program for the render node that exercises
DRM_IOCTL_ASAHI_GEM_RESIDENCY without submitting GPU work:
- a VM-private object: a keep request moves user_kept (gpu_memory
sysfs file) by the object's size, once for a repeated request, a
clear request moves it back, and freeing a kept object stops
counting it;
- unknown flags fail with EINVAL and an unknown handle with ENOENT,
and neither changes user_kept;
- a shared object takes the flag and is counted the same way;
- light_reclaim_spares_kept_objects: of two idle bound objects,
reclaim releases the one that is not kept first, never the kept one
while the other is resident; the kept one stays through 16 further
reclaim requests; once its request is withdrawn it is released too;
- kept_objects_stay_purgeable: a kept object advised DONTNEED is
purged by reclaim;
- requests_serialize_with_reclaim: while a thread sets and clears the
request on one object as fast as it can and reclaim runs, an object
beside it that is not kept is released, which shows that reclaim got
to the VM during the race, a kept object beside it is not, the state
a request that has returned found is the one reclaim leaves, and
user_kept counts each object once;
- kept_objects_go_under_urgent_pressure: when every shrinker is run at
the last reclaim priority, a kept object is released as well,
without being purged.
The four cases that make the kernel reclaim act on the whole system.
They need root and run only when asked for in the environment:
ASAHI_RESIDENCY_RECLAIM=1 for the three that write to memory.reclaim
of the root memory cgroup, ASAHI_RESIDENCY_DROP_CACHES=1 for the last
one, which writes 2 to /proc/sys/vm/drop_caches.
A memory.reclaim request names an amount, not a priority: the kernel
works down its reclaim priorities until it has reclaimed that much,
and where nothing else can be reclaimed it ends at the last ones, at
which kept objects go like everything else. The three cases therefore
bring the memory to reclaim: a sparse temporary file of 2 GiB in the
working directory, read once into the page cache, from which requests
of 4 MiB are usually met within the first priorities. The objects have
2^19 pages (8 GiB with 16K pages), the least a VM must have for the
lightest priority to scan its shrinker. The cases skip without four
times the objects' size of available memory, in a working directory on
tmpfs and in a memory cgroup with memory.min or memory.low protection.
When a kept object was released, a case has to tell whether reclaim
had leave to release it. It does so in one of two ways, and says which:
- From the record of the shrinker passes, where it can have one: the
events vmscan:mm_shrink_slab_start and mm_shrink_slab_end in a trace
instance (tracefs at /sys/kernel/tracing). The shrinker core calls a
VM's shrinker of kept objects only in a pass whose target reaches
everything that shrinker counted, and the start event shows both
numbers. The events name a shrinker by its pointer, so a case knows
its own VM's shrinker by what it counts, the pages of the VM's idle
kept objects: each case adds a random 0 to 1023 pages to its kept
object, and takes for its VM's shrinker the one that never counted
anything else. Leave to release the object is a pass that called that
shrinker before the case first saw the object gone and had not ended
when the case last saw it resident; a pass ends with the end event
of the same shrinker from the same task. With such a pass the case
is skipped ("as it may"), with their number and priority; without
one it fails, whatever passes other VMs' shrinkers or other times
had. If more than one shrinker in the record counts what the test's
VM counts and a pass called one of them, the case is skipped as
"inconclusive", which claims nothing about the release. The record
is used when no event was lost and it shows the driver's other
shrinker at work, which it knows by the names of the scan functions.
- Otherwise from signs of the environment, a heuristic: whether the
kernel met every request, whether 512 MiB of the file were still
cached after each, and whether kswapd or direct reclaim scanned pages
meanwhile. A kept object released with one of these signs skips the
case, with the sign and the word that it is a heuristic; without one
the case fails. The signs do not show which priorities reclaim
reached: reclaim that comes to the last ones without leaving a sign
fails a case that the record would skip.
kept_objects_go_under_urgent_pressure also requires, where it has the
record, a pass that called its VM's shrinker of kept objects before
the object went.
The trace instance belongs to the program: main() makes it before the
cases fork, named asahi-residency-<pid>, and removes it when the
program exits or is ended by a signal, so a case that fails, times out
or is killed leaves nothing behind. The signals that end the program
wait while the instance is set up or removed, and the handler makes
only async-signal-safe calls, on paths made beforehand. An instance of
that name that exists already is left alone, and the cases then go by
the heuristic.
The result of every series of reclaim requests is checked for an error
(a failed request, or a client total that could not be read) before it
is taken for a released object.
The program is listed in the selftests' TARGETS. It skips where
renderD128 is not this driver's node or lacks the feature.
Tested on the J716c (M5 Max MacBook Pro), built there with gcc against
this tree's UAPI header:
- unprivileged, without the two variables: pass 3, skip 4;
- as root with both variables: pass 7. The object that is not kept
went with the first request and the kept one stayed through 16 more;
the object no longer kept and the advised one went with the next
request each. In the race, 11 million residency requests ran while
reclaim released the control object, with its 11th request, and the
toggled one; the kept object stayed, also through 16 further
requests. At least 1995 MiB of the cold cache's 2048 were left. The
record had one shrinker with the count of the test's VM in each case:
51 and 70 passes over it in these two cases, none of which called
it, and in the urgent case one that did, at priority 0;
- as root with ASAHI_RESIDENCY_RECLAIM=1, right afterwards and once
more later: pass 6, skip 1, with similar figures;
- as root with ASAHI_RESIDENCY_RECLAIM=1 and tracefs hidden from the
program (a private mount over /sys/kernel/tracing): pass 6, skip 1,
by the heuristic;
- five changed copies of the program, light_reclaim_spares_kept_objects
only. One that never asks to keep the object fails, with the record
and without. One that runs every shrinker at the last priority
between the case's two phases is skipped with "as it may: ... 1,
down to priority 0 (traced)". One that does not keep the object while
such a pass calls another client's shrinker of kept objects fails
(no shrinker with the count of the test's VM), and so does one in
which such a pass called a shrinker with that count and ended before
the case first looked. One with a second client whose kept object
has the same size is skipped with "inconclusive: 2 shrinkers ...".
Without the record all five fail;
- a case killed with its process group while it records, the program
ended by SIGTERM, and an instance of its name made beforehand: the
program removes its instance in the first two and leaves the foreign
one alone, with the case passing by the heuristic. Sent SIGTERM at a
random moment of its first 0.3 s, 30 times, it left no instance.
Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
…usy time agx_stats on the M5 reported utilisation only: pstate, the frequencies and busy_ns read "unsupported", so there was no way to tell whether the GPU runs at full clock during a workload. The M5 firmware reports its performance state in its statistics ring: tag 0x1b names the state now in effect (words 1 and 3, an index into the device tree's operating points, 1 = 338 MHz to 13 = 1620 MHz), and tag 0x19 the interval that just ended (start in words 3-4, end in words 1-2, state in word 8). The decoder now takes the state from 0x1b, time stamped by the end of the preceding 0x19, and the reader turns it into achieved_frequency_hz through the board's performance state table. A report whose two state words differ, or that names state 0 or a state beyond the table, is not used; a gate makes the state stale, as before. Its utilisation records (tag 0x13, every 8 ms) carry their own timer timestamps. busy_ns now integrates the device utilisation over the window each record closes. A record after a gap of more than two periods (the GPU was gated or records were lost) or with an earlier timestamp only sets a new baseline, as the legacy integrator does after a power change, so an idle gap is never credited. requested_frequency_hz, power_mw and temperature_raw stay unsupported. Documentation/ABI/testing/sysfs-driver-asahi-agx-stats and Documentation/gpu/asahi-usage-stats.rst describe the M5 values. Checked on the J716c (M5 Max MacBook Pro) with a single-thread loop of dependent integer operations, whose rate is proportional to the clock (54.0 M iterations/s at 1620 MHz): - one 1.4 s job that fills the GPU: every agx_stats read during it (12, 100 ms apart) gave pstate 13, achieved_frequency_hz 1620000000 and 100 % device utilisation. - 60 jobs of 20000 iterations, 30 ms apart: 48 ran in about 1792 us (335 MHz against the 338 MHz of state 1) and 12 in under 600 us. Of the agx_stats reads during the run (1 ms apart) that found a current state, 48 gave pstate 1 with achieved_frequency_hz 338000000 and 11 gave 13 with 1620000000; the others found the GPU gated. - three jobs of 300 ms at full clock, 300 ms apart, twice: busy_ns advanced by 871 and 863 ms for 905 and 919 ms of work, over 1.8 s (one 8 ms window is lost at each power-up). Tests: the telemetry unit tests (g17-telemetry-tests.rs on the host, 9 passed, and KUnit asahi_g17_telemetry) cover the M5 decode from firmware records, the busy windows, gates, gaps and other firmwares; g17-telemetry-integration-tests.py passes and still fails each of its mutations. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
Documentation/gpu/drm-usage-stats.rst defines drm-maxfreq-<engine>, the highest frequency of an engine, and drivers that know it also print the frequency the engine runs at as drm-curfreq-<engine>. A Rust driver could only print the busy time. DriverFile::engine_frequency returns the current frequency, if the driver knows it, and the highest, in Hz; show_fdinfo prints them for every engine engine_stats emits. All engines get the same pair, which suits a device whose engines share one clock. The default, None, prints nothing, so drivers that do not implement it are unchanged. Built for arm64; the next commit uses it. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
Tools that read a client's fdinfo (gputop, nvtop) see its engine busy time but not the clock the GPU runs at, which agx_stats now reports on the M5. Every engine of a client's fdinfo gets drm-curfreq-<engine>, the achieved frequency of the performance state the firmware reports, while it reports one (it does not while the GPU is gated), and drm-maxfreq-<engine>, the highest frequency in the board's table (1620 MHz on the J716c). One clock drives the vertex, fragment and compute engines. Each read drains the statistics rings under the device mutex, as an agx_stats read does. GPUs without a board performance table (all but the M5) print neither key. Documentation/gpu/asahi-usage-stats.rst said that drm-maxfreq-* was left out on purpose, because the engine times must not be scaled by a frequency. That still holds and drm-cycles-* stays absent: the document now says that the two keys show the device's clock at the time of the read, and that an fdinfo read on the M5 takes the device mutex for them. On the J716c (M5 Max MacBook Pro), the fdinfo of a client running a 1.4 s job that fills the GPU reads, for each of vertex, fragment and compute, drm-curfreq-<engine>: 1620000000 Hz and drm-maxfreq-<engine>: 1620000000 Hz; that of an idle client with the GPU gated has the drm-maxfreq lines only. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
The scheduler's watchdog polls a G17 queue every two seconds and takes a compute command that a poll finds still running at the head of its queue to have hung: on the M5 it renews nothing, and the command fails about two seconds after it got there. A compute job that needs longer, a long prefill of an inference engine for example, has to be cut into small command buffers with few in flight, and still fails when other clients share the GPU: two processes each submitting a 1.1 s compute job that fills the GPU at the same moment ran side by side, one finished after 1.57 s and the other was killed 2.0 s after its submission, with about 0.3 s of its own work left. drm_asahi_queue_create gets compute_timeout_ms, advertised by DRM_ASAHI_FEATURE_COMPUTE_TIMEOUT (bit 36): a watchdog allowance for each compute command of the queue. The driver clamps it (at least the default of one poll, at most the compute_timeout_max_ms module parameter, 30000 by default) and returns the allowance in effect. At a poll, the watchdog now first renews a command that is within its allowance. Render commands keep the default. It is an allowance, not a deadline: - The watchdog still polls every two seconds, so a command may run up to one poll longer than its allowance. - The start of a command is known only approximately. The scheduler arms the watchdog when the job ahead completes, and again when a later job is pushed, so a job's first expiry comes at least one poll after it reached the head of the queue, later if pushes postponed it; the time is counted from one poll before that expiry. A job still in publication at an expiry (waiting for capacity) has not started: its time counts from the first expiry that finds it published. - Past the allowance, the renewals and deferrals that false_timeout() already grants still apply, in the same order as before: the execution renewals of ray and acceleration-structure work, the shared-work renewals (not on the M5) and the deferral of work behind another VM's earlier work. Under the default allowance the first expiry of a published job is not renewed by it, as before. The one change there: a job that was still in publication at an earlier expiry now gets one poll from the expiry that first finds it published, where before it could fail at that expiry however recently it had been published. There is no bound on the sum of the allowances: a queue kept full of long commands occupies the GPU for as long as it is fed. Nor are the other clients shielded from such a queue: they share the GPU with it as the firmware schedules them, and a command of another client can wait until a long command ends (the numbers are below). The struct grows at its end: a caller passing the old size gets the default, and a kernel without the feature ignores the field. Measured on the J716c (M5 Max MacBook Pro), with compute jobs of a single-thread loop of known length. Before this change: - one job of 2.13 s or 2.41 s: killed at 2.04-2.05 s (ETIMEDOUT, the client sees a lost device); one of 1.86 s: completes. - four jobs of 0.93 s submitted back to back on one queue, with and without timestamp queries: all complete, the last 3.71 s after its submission; two of 1.6 s: the second completes 3.23 s after its submission. Jobs queued behind their own queue do not lose time. - one 1.1 s job on each of two queues of one process: both complete (2.24 s for the second), both queues share one firmware queue. With it, and a Vulkan driver that passes a requested allowance on: - no request: a 2.13 s job is killed at 2.04 s, as before. - 30000 requested: jobs of 5 s and 20 s complete; a 40 s job is killed 30.7 s after its submission, as it is when 600000 is requested. - three 16 s jobs submitted back to back on one queue: all complete, 16.0, 32.0 and 48.0 s after the first submission. - two processes each submitting a 1.1 s job that fills the GPU at the same moment: both complete, after 1.93 s and 2.37 s. Sharing, measured with one client keeping a queue full of compute jobs that fill the GPU (640 threads), of 1.5 s under the default allowance and of 5 s and 20 s with 30000 requested, while a second client submits a short compute job every 100 ms (a single thread of 5 ms, or 5 ms of work filling the GPU) under the default allowance: - On the integration/aurora of 2026-11-09 (about 1000 jobs of each kind beside the long jobs): the second client's jobs took a median of 6 to 9 ms and at most 105 ms, whatever the length of the first client's jobs. The desktop compositor's render jobs, about 1 ms on an idle GPU, took up to 0.7 s beside jobs of any of the three lengths, and 4 to 5 of them completed per second beside the 5 s and 20 s jobs against 9 to 10 beside the 1.5 s jobs and 43 idle; six of its jobs waited between 1.4 s and more than 2 s beside the long jobs, none beside the 1.5 s jobs. - On the integration/aurora this change is based on (about 730 jobs of the kind that fills the GPU): most took 6 ms, but six waited until about the end of the first client's running job: 1.5 s beside a 1.5 s job, and 3.4, 4.5, 5.7, 8.3 and 18.4 s beside 20 s jobs (the 4.5, 8.3 and 18.4 s ones with the first client's queue at low priority, which did not help). The second client's watchdog deferred the ones past two seconds behind the other VM's work; the last one ran out of deferrals after 18 s and failed with ETIMEDOUT, 2.3 s before the first client's job ended. So on the current tree a queue with a long allowance can hold up another client's command for as long as its own command runs, and past 18 s that client's work fails. Under the default allowance the same wait is bounded by the two seconds a command may run. Tests: the timeout tests (g17-ray-query-timeout-tests.sh on the host, 23 passed, and KUnit) cover the requested allowances, the default, a 30 s allowance counted from the head of the queue and ended at the first poll past it, a first expiry delayed by later pushes, and a job that waited in publication. The asahi-m4 layout probe compares the frozen queue_create bindings as a prefix of the grown struct and asserts the new tail. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
…llowance With a compute allowance, one client's command may run for up to compute_timeout_max_ms (30 s by default), and another client's command, render or compute, can wait on the GPU until it ends. The waiting job's watchdog defers it behind the other VM's older work at most eight polls (MAX_RENEWALS, 16 s) and then fails it: a compositor frame that waited behind a 20 s compute command of another client failed with ETIMEDOUT after about 18 s, and the compositor lost its device. A job's watchdog now records when its allowance ends in the packet, and the cross-VM heads carry whether that job is still inside it. A waiting job whose blocker is live and inside its allowance is renewed with a new decision, Excuse, which does not use its eight renewals. Once the blocker completes, or its allowance has passed, the existing rules apply again, with what the job has left of its eight renewals for the blocker's own failure and teardown. Unchanged: - A blocker that already failed excuses nothing, as before. - The oldest unfinished work is never excused: it has no blocker. - A blocker under the default allowance excuses nothing, so clients that do not ask for an allowance see the decisions they saw before. Render and compute watchdogs decide through the same attribution, and both see compute heads as blockers, so a render job waiting behind a long compute command is covered too. The excuse is bounded on the waiting job's side: at most compute_timeout_max_ms in polls (15 with the default 30 s), however many blockers take turns. This rule therefore renews a job at most 15 + 8 times by default, 46 s of renewals. A job's own allowance and its other deferrals are counted separately and come on top, and each renewal re-arms a timer rather than an absolute deadline, so the time until a waiting job is failed (spared) can be longer than that. The watchdog lines for cross-VM decisions now print the excused polls and whether the blocker was inside its allowance, and are written at the first excuse, the first counted deferral and the final decision. Tests: KUnit and the host timeout tests cover a live blocker inside its allowance past eight renewals (excused), the excuse used up (renewals, then spared), a blocker past its allowance and a failed one (the decisions as before), no blocker and the oldest work (own), the bound, blockers taking turns (the counters are the waiting job's and are not reset), and when an allowance ends. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
On the G17 GPUs other than the M5, the watchdog renews a published job for up to fourteen polls while the GPU keeps completing other work (renew_shared), which bounds a job under the default allowance at thirty seconds on a shared GPU. These renewals were tried once a queue's allowance had passed, so the two added up: with the longest allowance (30 s by default) a command on a busy GPU could run for about 58 s, and a 40 s command could complete there instead of failing at about 30 s as it does on the M5, where the shared renewals do not apply. A poll that an allowance renews beyond what the default allowance renews now counts as one of the job's shared renewals. The two run from the same start, and a job is renewed for the longer of its allowance and the thirty seconds sharing gives any job, not for their sum. The one poll every allowance renews after a wait in publication is not counted, so asking never leaves a job fewer polls than the default. For an ordinary compute command that never finishes, polled every two seconds: - With an allowance of 30 s it is failed one poll after 30 s on a shared GPU or alone, on every G17, instead of after about 58 s on a busy G17 other than the M5. - With a shorter allowance on a shared GPU it is failed at thirty seconds, as a command without an allowance is. - Under the default allowance nothing changes. The renewals and deferrals that do not depend on sharing still come on top, on every G17 as before: the extra polls of ray and acceleration- structure work, and the deferrals of work waiting behind other work. A poll that runs late also still moves the estimated start, as the comment of compute_timeout_ms says; that comment now also says that the shared renewals run alongside an allowance. Tests: the timeout unit tests (KUnit and host) apply the allowance and the shared renewals in the watchdog's order to a job that never finishes: under the default allowance it fails at 2 s alone and 30 s on a shared GPU; at 30 s alone and shared with 30000; at 6 s alone and 30 s shared with 6000; at 30 s shared with 2001 and 20000; at 40 s with 40000; and after a wait in publication every allowance from the default to 30000 fails at the same poll on a shared GPU. This was not run on a G17 other than the M5 Max, where the shared renewals do not apply. Developed with Claude Code. Signed-off-by: Erik Bourget <erik@ebourget.net>
This was referenced Nov 13, 2026
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CI only, not for merging. It runs the open erikwb GPU PRs together on integration/aurora, so their merge order gets a whole-kernel build (
kunit, DRM_ASAHI=y) and the host tests on the combined source. Neither #1610 nor #1752 touches a path that starts a kernel build in CI on its own.Composition (
d99bda2d5513, tree173f25a9a0f6):4277bddd6312cb8258cba2e2(GEM_RESIDENCY)17eca40c50cd(M5 pstate, frequency and busy time)772bb5b12cb1(compute watchdog allowance)The one conflict is in
include/uapi/drm/asahi_drm.h: #1610'sDRM_ASAHI_FEATURE_GEM_RESIDENCY(bit 35) and #1752'sDRM_ASAHI_FEATURE_COMPUTE_TIMEOUT(bit 36) are both added right after bit 34. The resolution keeps both, in bit order. Whichever PR lands second needs the same resolution.The same source is the M5 Pro test kernel for these PRs. This PR will be closed once they have merged.
🤖 Generated with Claude Code